Skip to content

feat: add eligible_at timestamp to cooldown deferral logs and notifications - #1698

Merged
nicholas-fedor merged 3 commits into
mainfrom
feat/cooldown-eligible-at
May 29, 2026
Merged

feat: add eligible_at timestamp to cooldown deferral logs and notifications#1698
nicholas-fedor merged 3 commits into
mainfrom
feat/cooldown-eligible-at

Conversation

@nicholas-fedor

@nicholas-fedor nicholas-fedor commented May 29, 2026

Copy link
Copy Markdown
Owner

This PR introduces an eligible_at timestamp field to cooldown deferral logs and notifications, showing the exact time a container becomes eligible for update.

Problem

The cooldown deferral log message only showed a human-readable duration (eligible_in, e.g. "11 hours 18 minutes 21 seconds"), forcing users to manually calculate the actual time when a container becomes eligible for update.

Solution

Compute the exact eligible-at timestamp once in evalImageAge, store it in CooldownError.EligibleAt, and thread it through Progress and ContainerStatus so it can be displayed as an RFC3339 string in logs and RFC1123 in notifications.

Changes

  • Added EligibleAt time.Time to CooldownError; computed once in evalImageAge and passed into buildCooldownError
  • Added cooldownEligibleAt time.Time field and CooldownEligibleAt() getter to ContainerStatus; updated SetCooldownInfo signature
  • Updated Progress.SetCooldownInfo to thread through eligibleAt time.Time
  • Pass cooldownErr.EligibleAt from CooldownError through to progress in update.go
  • Added eligible_at (RFC3339) to the cooldown deferral log entry in actions.go
  • Added RFC1123 template function for human-friendly notification formatting
  • Updated cooldown notification template to show eligible-at time via {{RFC1123 .}}
  • Updated all affected tests with the new eligibleAt parameter and assertions

Summary by CodeRabbit

  • New Features
    • Notifications now show exact cooldown eligibility timestamps so users see when containers become eligible for updates.
    • Progress/status displays include the cooldown eligibility time alongside existing age/delay information.
    • Improved timestamp formatting in notification messages for clearer, human-readable cooldown times.

Review Change Stack

…stem

- Add `EligibleAt` field to `CooldownError` struct to store when container becomes eligible for update
- Extend `buildCooldownError` to accept precomputed `eligibleAt` time from remaining duration calculation
- Add `cooldownEligibleAt` field to `ContainerStatus` with `CooldownEligibleAt()` getter method
- Update `SetCooldownInfo` signature in both `ContainerStatus` and `Progress` to include `eligibleAt` parameter
- Include `eligible_at` in notification template data (formatted as RFC1123) and log output (RFC3339)
- Add `RFC1123` template function for formatting timestamps in notification templates
- Update all callers to pass `eligibleAt` through the cooldown information flow
…estamp in cooldown defaults test

- Correct indentation of cooldown notification template to align with adjacent template branches
- Verify `CooldownEligibleAt()` returns zero time by default in `TestContainerStatus_CooldownDefaults`
@nicholas-fedor nicholas-fedor linked an issue May 29, 2026 that may be closed by this pull request
2 tasks
@coderabbitai

coderabbitai Bot commented May 29, 2026

Copy link
Copy Markdown
Contributor

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f73e3059-d733-4c0a-aaaf-4aafe95b5655

📥 Commits

Reviewing files that changed from the base of the PR and between c7eec17 and e777b75.

📒 Files selected for processing (2)
  • pkg/notifications/templates/funcs.go
  • pkg/session/progress_test.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • pkg/session/progress_test.go
  • pkg/notifications/templates/funcs.go

Walkthrough

This PR extends the cooldown error reporting pipeline to include an eligibility timestamp. The change adds an EligibleAt field to CooldownError, threads it through ContainerStatus and Progress session tracking, integrates it into the Update action flow, renders it in notifications via a new RFC1123 template function, and includes comprehensive test coverage throughout.

Changes

Cooldown eligibility timestamp propagation

Layer / File(s) Summary
Cooldown error structure and computation
pkg/container/cooldown.go
CooldownError struct gains EligibleAt time.Time field. Cooldown evaluation computes eligibility timestamp from remaining delay and passes it to error construction via updated buildCooldownError signature.
Container status and progress API updates
pkg/session/container_status.go, pkg/session/progress.go
ContainerStatus extends data model with cooldownEligibleAt field and new CooldownEligibleAt() accessor. Progress.SetCooldownInfo and ContainerStatus.SetCooldownInfo method signatures updated to accept and store eligibleAt time.Time parameter.
Update action cooldown integration
internal/actions/update.go
Update extracts EligibleAt from *container.CooldownError and passes structured cooldown fields (including eligibility timestamp) to progress.SetCooldownInfo. Fallback sentinel case updated with zero time.Time value.
Template functions and cooldown logging
pkg/notifications/templates/funcs.go, pkg/notifications/common_templates.go, internal/actions/actions.go
New RFC1123 template function added to parse RFC3339 and format as RFC1123. Notification template updated to render eligible_at timestamp. Action logging includes eligible_at field from container status.
Container status test coverage
pkg/session/container_status_test.go
TestContainerStatus_SetCooldownInfo and related tests updated to supply eligibleAt values, verify CooldownEligibleAt() accessor returns expected and zero timestamps across test cases.
Progress method test coverage
pkg/session/progress_test.go
TestProgress_SetCooldownInfo extended with eligibleAt field in test structure, multiple test cases populate concrete and zero timestamps, assertions verify cooldownEligibleAt on resulting ContainerStatus. Concurrent cooldown test updated for both containers.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • nicholas-fedor/watchtower#1669: Both PRs modify the cooldown plumbing end-to-end by changing pkg/container/cooldown.go and updating internal/actions/update.go to propagate cooldown metadata; this PR adds the EligibleAt/eligible_at field.
  • nicholas-fedor/watchtower#1495: Related changes to cooldown metadata plumbing in pkg/session and internal/actions, which this PR extends by adding the EligibleAt timestamp.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding an eligible_at timestamp to cooldown deferral logs and notifications. It is concise, specific, and directly reflects the primary objective of the changeset.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov

codecov Bot commented May 29, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 28.00000% with 18 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
pkg/container/cooldown.go 0.00% 9 Missing ⚠️
pkg/notifications/templates/funcs.go 0.00% 7 Missing ⚠️
internal/actions/actions.go 0.00% 1 Missing ⚠️
internal/actions/update.go 50.00% 1 Missing ⚠️

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #1698      +/-   ##
==========================================
- Coverage   74.79%   74.61%   -0.18%     
==========================================
  Files          59       60       +1     
  Lines        9949    10052     +103     
==========================================
+ Hits         7441     7500      +59     
- Misses       2243     2284      +41     
- Partials      265      268       +3     
Files with missing lines Coverage Δ
pkg/session/container_status.go 100.00% <100.00%> (ø)
pkg/session/progress.go 100.00% <100.00%> (ø)
internal/actions/actions.go 73.05% <0.00%> (-0.15%) ⬇️
internal/actions/update.go 83.66% <50.00%> (-0.09%) ⬇️
pkg/notifications/templates/funcs.go 0.00% <0.00%> (ø)
pkg/container/cooldown.go 45.16% <0.00%> (-1.51%) ⬇️

... and 3 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@codacy-production

codacy-production Bot commented May 29, 2026

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 0 complexity

Metric Results
Complexity 0

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
pkg/session/progress_test.go (1)

1305-1352: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Missing assertion for CooldownEligibleAt().

The want cases now populate cooldownEligibleAt (Lines 1192, 1217, 1262), but this verification loop only checks CooldownPassed, CooldownAge, CooldownDelay, and CooldownRemaining. The eligibility timestamp—the field this PR adds—is never asserted, so propagation through Progress.SetCooldownInfo is not actually validated.

💚 Proposed fix to assert the eligible-at timestamp
 				if gotStatus.CooldownRemaining() != wantStatus.cooldownRemaining {
 					t.Errorf(
 						"Progress.SetCooldownInfo() CooldownRemaining for %v = %v, want %v",
 						id,
 						gotStatus.CooldownRemaining(),
 						wantStatus.cooldownRemaining,
 					)
 				}
+
+				if gotStatus.CooldownEligibleAt() != wantStatus.cooldownEligibleAt {
+					t.Errorf(
+						"Progress.SetCooldownInfo() CooldownEligibleAt for %v = %v, want %v",
+						id,
+						gotStatus.CooldownEligibleAt(),
+						wantStatus.cooldownEligibleAt,
+					)
+				}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/session/progress_test.go` around lines 1305 - 1352, Add an assertion in
the verification loop to check that the eligibility timestamp is propagated:
call gotStatus.CooldownEligibleAt() and compare it to
wantStatus.cooldownEligibleAt, and emit a t.Errorf (matching the existing style)
when they differ; place this check alongside the other Cooldown* assertions in
the loop that validates Progress.SetCooldownInfo so the new cooldownEligibleAt
field is actually tested.
🧹 Nitpick comments (1)
pkg/notifications/templates/funcs.go (1)

47-56: 💤 Low value

Consider logging parse failures for consistency and observability.

The formatRFC1123 function silently returns the original string when parsing fails. For consistency with the toJSON helper (lines 34-45), which logs marshaling errors with logrus.Warn, consider logging parse failures to aid debugging of notification templates.

📊 Suggested enhancement for error logging
 func formatRFC1123(s string) string {
 	t, err := time.Parse(time.RFC3339, s)
 	if err != nil {
+		logrus.WithError(err).WithFields(logrus.Fields{
+			"value": s,
+		}).Warn("Failed to parse RFC3339 timestamp in notification template")
 		return s
 	}

 	return t.Format(time.RFC1123)
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/notifications/templates/funcs.go` around lines 47 - 56, The formatRFC1123
function currently swallows parse errors; update it to log parse failures
similarly to toJSON by calling logrus.Warnf (or the project's logger) when
time.Parse returns an error, including the input string and the parse error,
then continue returning the original string; this change should be made in the
formatRFC1123 function so failures are observable while preserving the existing
fallback behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@pkg/session/progress_test.go`:
- Around line 1305-1352: Add an assertion in the verification loop to check that
the eligibility timestamp is propagated: call gotStatus.CooldownEligibleAt() and
compare it to wantStatus.cooldownEligibleAt, and emit a t.Errorf (matching the
existing style) when they differ; place this check alongside the other Cooldown*
assertions in the loop that validates Progress.SetCooldownInfo so the new
cooldownEligibleAt field is actually tested.

---

Nitpick comments:
In `@pkg/notifications/templates/funcs.go`:
- Around line 47-56: The formatRFC1123 function currently swallows parse errors;
update it to log parse failures similarly to toJSON by calling logrus.Warnf (or
the project's logger) when time.Parse returns an error, including the input
string and the parse error, then continue returning the original string; this
change should be made in the formatRFC1123 function so failures are observable
while preserving the existing fallback behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: dc6ecd2c-8d32-4c85-92f3-70f6d1cc3082

📥 Commits

Reviewing files that changed from the base of the PR and between a62b7e0 and c7eec17.

📒 Files selected for processing (9)
  • internal/actions/actions.go
  • internal/actions/update.go
  • pkg/container/cooldown.go
  • pkg/notifications/common_templates.go
  • pkg/notifications/templates/funcs.go
  • pkg/session/container_status.go
  • pkg/session/container_status_test.go
  • pkg/session/progress.go
  • pkg/session/progress_test.go

…mprove formatRFC1123 error logging

- Verify `CooldownEligibleAt()` matches expected value in `TestProgress_SetCooldownInfo`
- Rename `formatRFC1123` parameter from `s` to `value` for clarity
- Add structured warning log with `logrus` when RFC3339 parsing fails in `formatRFC1123`
@nicholas-fedor
nicholas-fedor merged commit 46140f3 into main May 29, 2026
19 of 20 checks passed
@nicholas-fedor
nicholas-fedor deleted the feat/cooldown-eligible-at branch May 29, 2026 01:59
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.

eligible_in, but with ISO date

1 participant