diff --git a/CHANGELOG.md b/CHANGELOG.md index f7f7f567..7ec05d6c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - #822: The CPF resource processor now supports system expressions and macros in CPF merge files - #578 Added functionality to record and display IPM history of install, uninstall, load, and update - #961: Adding creation of a lock file for a module by using the `-create-lockfile` flag on install. +- #992: Implement automatic history purge logic ### Changed - #316: All parameters, except developer mode, included with a `load`, `install` or `update` command will be propagated to dependencies diff --git a/src/cls/IPM/General/History.cls b/src/cls/IPM/General/History.cls index 431bc708..322956dc 100644 --- a/src/cls/IPM/General/History.cls +++ b/src/cls/IPM/General/History.cls @@ -158,6 +158,44 @@ ClassMethod DeleteHistory(ByRef filter) As %Integer quit rs.%ROWCOUNT } +/// Automatically purge old history based on retention settings +ClassMethod AutoPurgeOldHistory() As %Status +{ + set sc = $$$OK + if ..GetHistoryLastPurge() = +$horolog { + quit sc + } + set retainDays = ##class(%IPM.Repo.UniversalSettings).GetHistoryRetain() + if +retainDays <=0 { + quit sc + } + set cutoffDate = $system.SQL.Functions.DATEADD("d", -retainDays, +$horolog) + set filter("TimeStart") = "<='" _cutoffDate_"'" + try { + set count = ..DeleteHistoryGlobally(.filter, 0) + do ..SetHistoryLastPurge() + } catch ex { + set sc = ex.AsStatus() + } + return sc +} + +ClassMethod SetHistoryLastPurge() +{ + new $namespace + set index = ##class(%IPM.Repo.UniversalSettings).#HistoryRetain + set $namespace = "%SYS" + set ^IPM.settings(index, "lastPurge") = +$horolog +} + +ClassMethod GetHistoryLastPurge() As %String +{ + new $namespace + set index = ##class(%IPM.Repo.UniversalSettings).#HistoryRetain + set $namespace = "%SYS" + return $get(^IPM.settings(index, "lastPurge")) +} + ClassMethod DeleteHistoryGlobally( ByRef filter, verbose As %Boolean = 0) As %Integer diff --git a/src/cls/IPM/Main.cls b/src/cls/IPM/Main.cls index 8734ec9a..27bb7c10 100644 --- a/src/cls/IPM/Main.cls +++ b/src/cls/IPM/Main.cls @@ -788,6 +788,9 @@ generate /my/path -export 00000,PacketName2,IgnorePacket2^00000,PacketName3,Igno history find -globally -Daction=install -Dpackage=zpip history delete -confirm history details 3 -phases + config set HistoryRetain "days" + config set HistoryRetain 0 + config get HistoryRetain @@ -807,7 +810,10 @@ ClassMethod Shell( pTerminateOnError As %Boolean = 0, pHaltOnComplete As %Boolean = 0) As %Status { - set tSC = $$$OK + set tSC = ##class(%IPM.General.History).AutoPurgeOldHistory() + if $$$ISERR(tSC) { + return tSC + } do ..ShellInternal(pCommand,.tException) if $isobject(tException) { if pTerminateOnError { diff --git a/src/cls/IPM/Repo/UniversalSettings.cls b/src/cls/IPM/Repo/UniversalSettings.cls index ce178229..e1ff1f3e 100644 --- a/src/cls/IPM/Repo/UniversalSettings.cls +++ b/src/cls/IPM/Repo/UniversalSettings.cls @@ -41,7 +41,11 @@ Parameter UseStandalonePip = "UseStandalonePip"; /// Default value is 0, where 1.0.0-anystring is considered a pre-release of 1.0.0, hence 1.0.0-anystring < 1.0.0 Parameter SemVerPostRelease = "SemVerPostRelease"; -Parameter CONFIGURABLE = "trackingId,analytics,ColorScheme,TerminalPrompt,PublishTimeout,PipCaller,UseStandalonePip,SemVerPostRelease,DefaultLogEntryLimit"; +/// Configuration setting name used to determine the number of days +/// to retain IPM history records before they are eligible for cleanup. +Parameter HistoryRetain = "history_retain"; + +Parameter CONFIGURABLE = "trackingId,analytics,ColorScheme,TerminalPrompt,PublishTimeout,PipCaller,UseStandalonePip,SemVerPostRelease,DefaultLogEntryLimit,HistoryRetain"; /// Returns configArray, that includes all configurable settings ClassMethod GetAll(Output configArray) As %Status @@ -181,4 +185,9 @@ ClassMethod SetAnalyticsAvailable( return ..SetValue(..#analytics, +val, overwrite) } +ClassMethod GetHistoryRetain() As %Integer +{ + return ..GetValue(..#HistoryRetain) +} + } diff --git a/tests/unit_tests/Test/PM/Unit/CLI.cls b/tests/unit_tests/Test/PM/Unit/CLI.cls index a25bcd03..0e5a9f3b 100644 --- a/tests/unit_tests/Test/PM/Unit/CLI.cls +++ b/tests/unit_tests/Test/PM/Unit/CLI.cls @@ -219,10 +219,11 @@ Method CompareModifiers( } } -Method RunCommand(pCommand As %String) +Method RunCommand(pCommand As %String) As %Status { - do ##class(%IPM.Main).Shell(pCommand) + set status = ##class(%IPM.Main).Shell(pCommand) do $$$LogMessage("Run command: "_pCommand) + return status } Method AssertNoException(pCommand As %String) @@ -313,4 +314,27 @@ Method TestListPython() As %Status quit sc } +Method TestAutoPurgeOldHistory() As %Status +{ + set status = ..RunCommand("config set HistoryRetain 30") + do $$$AssertStatusOK(status, "Config set executed successfully") + + set status = ..RunCommand("config get HistoryRetain") + do $$$AssertStatusOK(status, "Config get executed successfully") + + do $$$LogMessage("Update HistoryRetain from 30 days to 1 day") + set status = ..RunCommand("config set HistoryRetain 1") + do $$$AssertStatusOK(status, "HistoryRetain is changed from 30 days to 1 day successfully") + + do $$$LogMessage("Purging old history entries older than 1 day") + set status = ##class(%IPM.General.History).AutoPurgeOldHistory() + do $$$AssertStatusOK(status, "AutoPurgeOldHistory executed successfully") + + set afterPurge = ##class(%IPM.General.History).GetHistoryLastPurge() + do $$$LogMessage("After purge timestamp: "_afterPurge) + do $$$LogMessage("reset set the HistoryRetain to default value by deleting the config") + set status = ..RunCommand("config delete HistoryRetain") + do $$$AssertStatusOK(status, "Config delete executed successfully") +} + }