From 3ecfc983ea689e3d1372c21fb4f2d2365a4e8dd7 Mon Sep 17 00:00:00 2001 From: AshokThangavel Date: Sun, 14 Dec 2025 21:27:14 +0530 Subject: [PATCH 1/6] feat(history): implement automatic history purge logic --- src/cls/IPM/General/History.cls | 31 ++++++++++++++++++++++++++ src/cls/IPM/Main.cls | 4 ++++ src/cls/IPM/Repo/UniversalSettings.cls | 11 ++++++++- tests/unit_tests/Test/PM/Unit/CLI.cls | 19 ++++++++++++++++ 4 files changed, 64 insertions(+), 1 deletion(-) diff --git a/src/cls/IPM/General/History.cls b/src/cls/IPM/General/History.cls index 431bc708..48ad1587 100644 --- a/src/cls/IPM/General/History.cls +++ b/src/cls/IPM/General/History.cls @@ -158,6 +158,37 @@ ClassMethod DeleteHistory(ByRef filter) As %Integer quit rs.%ROWCOUNT } +/// Automatically purge old history based on retention settings +ClassMethod AutPurgeOldHistory() +{ + if ..GetHistoryLastPurge() = +$horolog { + quit + } + set retainDays = ##class(%IPM.Repo.UniversalSettings).GetHistoryRetain() + quit:(+retainDays <=0) + + set cutoffDate = $system.SQL.Functions.DATEADD("d", -retainDays, +$horolog) + set filter("TimeStart") = "<='" _ cutoffDate+"'" + set tSC = ..DeleteHistoryGlobally(.filter, 0) + do ..SetHistoryLastPurge() +} + +ClassMethod SetHistoryLastPurge(indx As %String = {##class(%IPM.Repo.UniversalSettings).#HistoryRetain}) +{ + new $namespace + return:(indx="") "" + set $namespace = "%SYS" + set ^IPM.settings(indx,"lastPurge") = +$horolog +} + +ClassMethod GetHistoryLastPurge(indx As %String = {##class(%IPM.Repo.UniversalSettings).#HistoryRetain}) As %Integer +{ + new $namespace + return:(indx="") "" + set $namespace = "%SYS" + return $get(^IPM.settings(indx,"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 db2b1f69..ddd667b4 100644 --- a/src/cls/IPM/Main.cls +++ b/src/cls/IPM/Main.cls @@ -785,6 +785,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 @@ -806,6 +809,7 @@ ClassMethod Shell( { set tSC = $$$OK do ..ShellInternal(pCommand,.tException) + do ##class(%IPM.General.History).AutPurgeOldHistory() if $isobject(tException) { if pTerminateOnError { do $system.Process.Terminate($job,1) diff --git a/src/cls/IPM/Repo/UniversalSettings.cls b/src/cls/IPM/Repo/UniversalSettings.cls index ce178229..4f9dd70d 100644 --- a/src/cls/IPM/Repo/UniversalSettings.cls +++ b/src/cls/IPM/Repo/UniversalSettings.cls @@ -41,7 +41,9 @@ 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"; +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 +183,11 @@ ClassMethod SetAnalyticsAvailable( return ..SetValue(..#analytics, +val, overwrite) } +ClassMethod GetHistoryRetain( + val As %String, + overwrite As %Boolean = 1) 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..d728e579 100644 --- a/tests/unit_tests/Test/PM/Unit/CLI.cls +++ b/tests/unit_tests/Test/PM/Unit/CLI.cls @@ -313,4 +313,23 @@ Method TestListPython() As %Status quit sc } +Method TestAutoPurgeOldHistory() As %Status +{ + set sc = $$$OK + set commandsList = $listbuild("config set HistoryRetain 10", "config get HistoryRetain") + set ptr = 0 + while $listnext(commandsList,ptr,command) { + do ..RunCommand(command) + } + do $$$LogMessage("Update HistoryRetain from 10 days to 1 day") + do ..RunCommand("config set HistoryRetain 1") + do $$$LogMessage("Purging old history entries older than 1 day") + do ##class(%IPM.General.History).AutPurgeOldHistory() + set afterPurge = ##class(%IPM.General.History).GetHistoryLastPurge("history_retain") + do $$$LogMessage("After purge timestamp: "_afterPurge) + do $$$LogMessage("rest set the HistoryRetain to default value by deleting the config") + do ..RunCommand("config delete HistoryRetain") + quit sc +} + } From 7397bf4bdd48123082d9c2178d0dfd0a8518d6c6 Mon Sep 17 00:00:00 2001 From: AshokThangavel Date: Tue, 16 Dec 2025 19:00:20 +0530 Subject: [PATCH 2/6] fix: removed typo from the cutoffDate --- src/cls/IPM/General/History.cls | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cls/IPM/General/History.cls b/src/cls/IPM/General/History.cls index 48ad1587..5ea3ccc8 100644 --- a/src/cls/IPM/General/History.cls +++ b/src/cls/IPM/General/History.cls @@ -168,7 +168,7 @@ ClassMethod AutPurgeOldHistory() quit:(+retainDays <=0) set cutoffDate = $system.SQL.Functions.DATEADD("d", -retainDays, +$horolog) - set filter("TimeStart") = "<='" _ cutoffDate+"'" + set filter("TimeStart") = "<='" _cutoffDate_"'" set tSC = ..DeleteHistoryGlobally(.filter, 0) do ..SetHistoryLastPurge() } From c7fdcdbc0e644d0b9c9ee781a34df5fbdd5cba82 Mon Sep 17 00:00:00 2001 From: AshokThangavel Date: Wed, 17 Dec 2025 20:22:58 +0530 Subject: [PATCH 3/6] fix: rename method typo and refactor postconditionals --- src/cls/IPM/General/History.cls | 6 ++++-- src/cls/IPM/Main.cls | 2 +- tests/unit_tests/Test/PM/Unit/CLI.cls | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/cls/IPM/General/History.cls b/src/cls/IPM/General/History.cls index 5ea3ccc8..f773f8c5 100644 --- a/src/cls/IPM/General/History.cls +++ b/src/cls/IPM/General/History.cls @@ -159,13 +159,15 @@ ClassMethod DeleteHistory(ByRef filter) As %Integer } /// Automatically purge old history based on retention settings -ClassMethod AutPurgeOldHistory() +ClassMethod AutoPurgeOldHistory() { if ..GetHistoryLastPurge() = +$horolog { quit } set retainDays = ##class(%IPM.Repo.UniversalSettings).GetHistoryRetain() - quit:(+retainDays <=0) + if +retainDays <=0 { + quit + } set cutoffDate = $system.SQL.Functions.DATEADD("d", -retainDays, +$horolog) set filter("TimeStart") = "<='" _cutoffDate_"'" diff --git a/src/cls/IPM/Main.cls b/src/cls/IPM/Main.cls index ddd667b4..23d99867 100644 --- a/src/cls/IPM/Main.cls +++ b/src/cls/IPM/Main.cls @@ -809,7 +809,7 @@ ClassMethod Shell( { set tSC = $$$OK do ..ShellInternal(pCommand,.tException) - do ##class(%IPM.General.History).AutPurgeOldHistory() + do ##class(%IPM.General.History).AutoPurgeOldHistory() if $isobject(tException) { if pTerminateOnError { do $system.Process.Terminate($job,1) diff --git a/tests/unit_tests/Test/PM/Unit/CLI.cls b/tests/unit_tests/Test/PM/Unit/CLI.cls index d728e579..10c974c3 100644 --- a/tests/unit_tests/Test/PM/Unit/CLI.cls +++ b/tests/unit_tests/Test/PM/Unit/CLI.cls @@ -324,7 +324,7 @@ Method TestAutoPurgeOldHistory() As %Status do $$$LogMessage("Update HistoryRetain from 10 days to 1 day") do ..RunCommand("config set HistoryRetain 1") do $$$LogMessage("Purging old history entries older than 1 day") - do ##class(%IPM.General.History).AutPurgeOldHistory() + do ##class(%IPM.General.History).AutoPurgeOldHistory() set afterPurge = ##class(%IPM.General.History).GetHistoryLastPurge("history_retain") do $$$LogMessage("After purge timestamp: "_afterPurge) do $$$LogMessage("rest set the HistoryRetain to default value by deleting the config") From 8ea41d14a5bbbeefcc791c68b86135193f3e2f50 Mon Sep 17 00:00:00 2001 From: AshokThangavel Date: Thu, 18 Dec 2025 18:50:28 +0530 Subject: [PATCH 4/6] fix:refactor postconditionals --- src/cls/IPM/General/History.cls | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/cls/IPM/General/History.cls b/src/cls/IPM/General/History.cls index f773f8c5..1d62ceee 100644 --- a/src/cls/IPM/General/History.cls +++ b/src/cls/IPM/General/History.cls @@ -178,7 +178,9 @@ ClassMethod AutoPurgeOldHistory() ClassMethod SetHistoryLastPurge(indx As %String = {##class(%IPM.Repo.UniversalSettings).#HistoryRetain}) { new $namespace - return:(indx="") "" + if indx="" { + quit + } set $namespace = "%SYS" set ^IPM.settings(indx,"lastPurge") = +$horolog } @@ -186,7 +188,9 @@ ClassMethod SetHistoryLastPurge(indx As %String = {##class(%IPM.Repo.UniversalSe ClassMethod GetHistoryLastPurge(indx As %String = {##class(%IPM.Repo.UniversalSettings).#HistoryRetain}) As %Integer { new $namespace - return:(indx="") "" + if indx="" { + quit + } set $namespace = "%SYS" return $get(^IPM.settings(indx,"lastPurge")) } From 27f21511aa86b539a58d0fde2a8dcae96875e4b5 Mon Sep 17 00:00:00 2001 From: AshokThangavel Date: Fri, 19 Dec 2025 00:29:58 +0530 Subject: [PATCH 5/6] docs: add details to changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 30b38d93..663d6ab7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - #950: Added support for listing installed Python packages using `list -python`, `list -py` and `list-installed -python` - #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 +- #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 From 0218852e3a70fad5b2bae78d0ea2acc6d10da70a Mon Sep 17 00:00:00 2001 From: AshokThangavel Date: Wed, 7 Jan 2026 23:13:53 +0530 Subject: [PATCH 6/6] refactor: updated the unit test TestAutoPurgeOldHistory method --- tests/unit_tests/Test/PM/Unit/CLI.cls | 35 +++++++++++++++------------ 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/tests/unit_tests/Test/PM/Unit/CLI.cls b/tests/unit_tests/Test/PM/Unit/CLI.cls index 10c974c3..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) @@ -315,21 +316,25 @@ Method TestListPython() As %Status Method TestAutoPurgeOldHistory() As %Status { - set sc = $$$OK - set commandsList = $listbuild("config set HistoryRetain 10", "config get HistoryRetain") - set ptr = 0 - while $listnext(commandsList,ptr,command) { - do ..RunCommand(command) - } - do $$$LogMessage("Update HistoryRetain from 10 days to 1 day") - do ..RunCommand("config set HistoryRetain 1") + 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") - do ##class(%IPM.General.History).AutoPurgeOldHistory() - set afterPurge = ##class(%IPM.General.History).GetHistoryLastPurge("history_retain") + 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("rest set the HistoryRetain to default value by deleting the config") - do ..RunCommand("config delete HistoryRetain") - quit sc + 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") } }