Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

V1next history log #578

Open
wants to merge 47 commits into
base: v0.10.x
Choose a base branch
from
Open

V1next history log #578

wants to merge 47 commits into from

Conversation

isc-shuliu
Copy link
Collaborator

@isc-shuliu isc-shuliu commented Oct 3, 2024

Implement #366
TODO List

  • Figure out what "committed" is
  • Work out the "history" command
  • Add public API for querying history
  • Record recursively loaded/installed modules
  • Properly implement "commit"
  • Allow more matching patterns when filtering histories
  • Display log entries in a prettier manner
  • Command to see the details of a history log by ID
  • Command to show supported columns for filtering
  • Allow specifying direction for sorting and limit max number of rows

@isc-tleavitt
Copy link
Contributor

isc-tleavitt commented Oct 4, 2024

Re: "committed" - this would track, in non-dev mode, if installation was attempted but failed in some way - that is, it would be set to 0 in this case and 1 if installation succeeded. The reason for the failure (probably a %Status) would be worth recording as well for a failed installation.

@isc-kiyer
Copy link
Collaborator

Re: "committed" - this would track, in non-dev mode, if installation was attempted but failed in some way - that is, it would be set to 0 in this case and 1 if installation succeeded. The reason for the failure (probably a %Status) would be worth recording as well for a failed installation.

This comes into the overall lifecycle rework where we flag the phase in which a module reached during install and track the error that occurred as well. For modules in dev mode, can permit resuming install from the failed phase onwards and for non-dev mode, always start from scratch for the module.

@isc-shuliu
Copy link
Collaborator Author

isc-shuliu commented Oct 8, 2024

Not sure why 2c240de doesn't work.

I have a local registry with modA and modB, where modB depends on modA. When installing modB, IPM will call %IPM.Utils.Module:LoadDependencies(), which uses %SYSTEM.WorkMgr to spawn a new process that runs ..LoadModuleReference().

The change in commit 2c240de is simple. We create a %Persistent history log object, save it in database, and pass the ID to the work unit. In the work unit, we save the success/failure and TimeEnd.

However, when calling %Save() from the spawned process, it raises the error Failed to acquire exclusive lock on instance of '%IPM.General.History. Why is there any lock involved?

@isc-shuliu
Copy link
Collaborator Author

isc-shuliu commented Oct 8, 2024

It appears the lock is created because we're in a transaction. (Tried stepping through the code while monitoring management portal, lock disappears after manually running TCOMMIT)

Copy link
Contributor

@isc-tleavitt isc-tleavitt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few really minor comments, but I think this is great overall.

src/cls/IPM/General/History.cls Show resolved Hide resolved
src/cls/IPM/General/History.cls Outdated Show resolved Hide resolved
<Value>CommandString</Value>
</Value>
</Data>
<DataLocation>^%IPM.General.HistoryD</DataLocation>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How is this mapped?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The data is mapped to each namespace where %IPM package is mapped. Do we want to separate them?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, that'll be fine.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To clarify: there are no global mappings for the data but since it is a % global, will live in IRISSYS? I think its best to have this be a non-% global and can remove Namespace to scope history by namespace (like the rest of IPM).

@isc-shuliu
Copy link
Collaborator Author

@isc-tleavitt
Based on your feedback, I changed the time diff display to always show seconds and exposed API queries instead of API methods.

Currently, the table data is shared among all namespaces where %IPM is mapped. Do we want to separate them?

@isc-tleavitt
Copy link
Contributor

I think this is good now, just need @isc-kiyer's review

@isc-kiyer isc-kiyer requested a review from isc-eneil October 24, 2024 14:14
Copy link
Collaborator

@isc-kiyer isc-kiyer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@isc-shuliu This is a great and exhaustive feature! My comments and some questions are up

src/cls/IPM/General/History.cls Show resolved Hide resolved
src/cls/IPM/Main.cls Outdated Show resolved Hide resolved
src/cls/IPM/Main.cls Outdated Show resolved Hide resolved
<parameter name="argument" required="false" description="Argument for `details` command" />
<example description="Show history of all packages in the current namespace in descending order">history find </example>
<example description="Show history of all packages in the current namespace where command starts with &quot;load&quot;">history find -DCommandString="load*"</example>
<example description="Show history of all packages in the current namespace where start time is later than 2000-01-01 00:00:00">history find -DTimeStart=">2000-01-01 00:00:00"</example>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than using -D modifiers, may be nice to be able to add sub-modifiers and enum for parameters or modifiers. Then for history, can have enum for the action parameter and sub-modifiers tied to different parameter enums. Is a bit extensive and invasive but I think it will be helpful for many other commands.

Happy to talk through design of this

src/cls/IPM/Storage/QualifiedModuleInfo.cls Outdated Show resolved Hide resolved
src/cls/IPM/Main.cls Outdated Show resolved Hide resolved
/// Get the history of all installations, uninstalls, and loads in given namespace
/// The filter argument is a multidimensional array with structure
/// filter(columnName) = value
/// Where value can optionally start with >, >=, <, <=, =, <> or contain * to indicate a wildcard
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than value starting with such things, perhaps make value a $ListBuild? The possible issue with it starting with that is that the property's value may actually be using one of these symbols.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good idea, my concern is that It can be cumbersome for users to construct a command string that contains $ListBuild()s. They would need to do something like

zpm "history find -DTimeStart=" _ $lb(">", "2000-01-01 00:00:00")

And I can't think of a way for it to work in interactive mode:

USER>zpm

=============================================================================
|| Welcome to the Package Manager Shell (ZPM). Version:                    ||
|| Enter q/quit to exit the shell. Enter ?/help to view available commands ||
|| Current registry https://pm.community.intersystems.com                  ||
=============================================================================
zpm:USER>history find -DTimeStart=???

src/cls/IPM/General/History.cls Outdated Show resolved Hide resolved
src/cls/IPM/General/History.cls Outdated Show resolved Hide resolved
src/cls/IPM/Main.cls Show resolved Hide resolved
@isc-tleavitt
Copy link
Contributor

This should account for #704 (the pitfalls of being stuck here for a while, while we've been focused on more pressing things...)

Copy link
Collaborator

@isc-kiyer isc-kiyer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@isc-shuliu another round of comments. Also, worthwhile adding unit tests since this is a brand new feature. Some test cases I can think about:

  • simple commands with no modifiers
  • commands with valid modifiers
  • commands with invalid modifiers
  • modules in dev and non-dev mode
  • testing all commands (load, install, uninstall)
  • filtering
  • altering number of history results to keep
  • searching per namespace vs globally
  • error handling for invalid filtering columns

<Value>CommandString</Value>
</Value>
</Data>
<DataLocation>^%IPM.General.HistoryD</DataLocation>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To clarify: there are no global mappings for the data but since it is a % global, will live in IRISSYS? I think its best to have this be a non-% global and can remove Namespace to scope history by namespace (like the rest of IPM).

/// Action of this history record. Can be load, install, or uninstall
Property Action As %String(VALUELIST = ",load,install,uninstall") [ Required ];

/// Name of the package being loaded/installed/uninstall. This is not necessarily requried. E.g., when loading a nonexistent directory.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: typo for required

{

/// Action of this history record. Can be load, install, or uninstall
Property Action As %String(VALUELIST = ",load,install,uninstall") [ Required ];
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@isc-shuliu What about module actions like compile, activate etc. Are these things we want to log? Or just in general, all commands from the zpm shell?
cc @isc-tleavitt

Property NameSpace As %String [ InitialExpression = {$NAMESPACE}, Required ];

/// User who initiated the action
Property UserName As %String [ InitialExpression = {$USERNAME}, Required ];
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Use %Library.Username

Property Committed As %Boolean [ InitialExpression = 0, Required ];

/// The command string that triggered the action
Property CommandString As %String(MAXLEN = 8192) [ Required ];
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does command string need a maxlen?

/// Where value can optionally start with >, >=, <, <=, =, <> or contain * to indicate a wildcard
ClassMethod GetHistory(ByRef filter, ascend As %Boolean = 0, limit As %Integer = 0, namespace As %String) As %SQL.StatementResult
{
Set limit = +limit // Coerce to number to prevent SQL injection
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use ? like in where clauses as TOP ? to prevent SQL injection

/// If detail is 1, will display full details each property on a separate line
/// If detail is "header", will display the column names using the same style as the data
/// Otherwise, will display on a single line, with some details omitted
ClassMethod ShowColumns(rs As %SQL.StatementResult, detail As %Boolean = 0) As %Status [ Internal, Private ]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than having detail be a fake boolean, perhaps have it be an enum? So valid options could be "header" and "full" perhaps?

/// Output "clause" is the SQL WHERE clause as a string, container zero or more ? placeholders
/// The "varargs" is an array of values to be substituted in the ? placeholders.
/// The "varargs" may already be populated with values, and new values will be appended to it.
ClassMethod ConstructSQLWhere(filter As %String, namespace As %String, Output clause As %String, ByRef varargs As %String) As %Status [ Internal ]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: here filter should be ByRef since it a multi-dim array

@@ -1889,6 +1915,19 @@ ClassMethod LoadFromRepo(tDirectoryName, ByRef tParams) [ Internal ]
}

ClassMethod Load(ByRef pCommandInfo) [ Internal ]
{
Set devMode = $Get(pCommandInfo("data", "DeveloperMode"), 1)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should devMode default be 0? Without -dev flag, it is 0 right?


#dim log As %IPM.General.History
Set log = ##class(%IPM.General.History).InstallInit(tModuleName)
Set devMode = $Get(tParams("DeveloperMode"), 0)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no tParams here. Did you mean to write pCommandInfo("data","DeveloperMode") ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants