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")
+}
+
}