From beb9a2f3d9641d52cb27e33c8396b2ded25b2470 Mon Sep 17 00:00:00 2001 From: TwiN Date: Mon, 27 Oct 2025 16:30:12 -0400 Subject: [PATCH] feat(condition): Format certificate and domain expiration durations in human-readable format (#1370) --- config/endpoint/condition.go | 41 ++++++++++++++++++++++++++++++- config/endpoint/condition_test.go | 4 +-- 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/config/endpoint/condition.go b/config/endpoint/condition.go index ee40080a..fdec241e 100644 --- a/config/endpoint/condition.go +++ b/config/endpoint/condition.go @@ -209,7 +209,46 @@ func sanitizeAndResolveNumericalWithContext(list []string, result *Result, conte } func prettifyNumericalParameters(parameters []string, resolvedParameters []int64, operator string) string { - return prettify(parameters, []string{strconv.Itoa(int(resolvedParameters[0])), strconv.Itoa(int(resolvedParameters[1]))}, operator) + resolvedStrings := make([]string, 2) + for i := 0; i < 2; i++ { + // Check if the parameter is a certificate or domain expiration placeholder + if parameters[i] == CertificateExpirationPlaceholder || parameters[i] == DomainExpirationPlaceholder { + // Format as duration string (convert milliseconds back to duration) + duration := time.Duration(resolvedParameters[i]) * time.Millisecond + resolvedStrings[i] = formatDuration(duration) + } else if _, err := time.ParseDuration(parameters[i]); err == nil { + // If the original parameter was a duration string (like "48h"), format the resolved value + // as a duration string too so it matches and doesn't show in parentheses + duration := time.Duration(resolvedParameters[i]) * time.Millisecond + resolvedStrings[i] = formatDuration(duration) + } else { + // Format as integer + resolvedStrings[i] = strconv.Itoa(int(resolvedParameters[i])) + } + } + return prettify(parameters, resolvedStrings, operator) +} + +// formatDuration formats a duration in a clean, human-readable way by removing unnecessary zero components. +// For example: 336h0m0s becomes 336h, 1h30m0s becomes 1h30m, but 1h0m15s stays as 1h0m15s. +// Truncates to whole seconds to avoid decimal values like 7353h5m54.67s. +func formatDuration(d time.Duration) string { + // Truncate to whole seconds to avoid decimal seconds + d = d.Truncate(time.Second) + s := d.String() + // Special case: if duration is zero, return "0s" + if s == "0s" { + return "0s" + } + // Remove trailing "0s" if present + if strings.HasSuffix(s, "0s") { + s = strings.TrimSuffix(s, "0s") + // Remove trailing "0m" if present after removing "0s" + if strings.HasSuffix(s, "0m") { + s = strings.TrimSuffix(s, "0m") + } + } + return s } // prettify returns a string representation of a condition with its parameters resolved between parentheses diff --git a/config/endpoint/condition_test.go b/config/endpoint/condition_test.go index aba08010..074def48 100644 --- a/config/endpoint/condition_test.go +++ b/config/endpoint/condition_test.go @@ -476,7 +476,7 @@ func TestCondition_evaluate(t *testing.T) { Condition: Condition("[CERTIFICATE_EXPIRATION] > " + strconv.FormatInt((time.Hour*24*28).Milliseconds(), 10)), Result: &Result{CertificateExpiration: time.Hour * 24 * 14}, ExpectedSuccess: false, - ExpectedOutput: "[CERTIFICATE_EXPIRATION] (1209600000) > 2419200000", + ExpectedOutput: "[CERTIFICATE_EXPIRATION] (336h) > 2419200000", }, { Name: "certificate-expiration-greater-than-duration", @@ -490,7 +490,7 @@ func TestCondition_evaluate(t *testing.T) { Condition: Condition("[CERTIFICATE_EXPIRATION] > 48h"), Result: &Result{CertificateExpiration: 24 * time.Hour}, ExpectedSuccess: false, - ExpectedOutput: "[CERTIFICATE_EXPIRATION] (86400000) > 48h (172800000)", + ExpectedOutput: "[CERTIFICATE_EXPIRATION] (24h) > 48h", }, { Name: "no-placeholders",