Skip to content

Conversation

@LakshyaBagani
Copy link
Contributor

@LakshyaBagani LakshyaBagani commented Feb 2, 2026

Fixes #16641

Testing done

Automated Testing:

Created core/src/test/java/hudson/model/AdministrativeMonitorTest.java with 8 new unit tests covering:
Snooze expiry logic (testSnoozeExpiry).
Cleanup of expired monitors (testCleanupRemovesOnlyThisMonitor).
Persistence of snoozed state across restarts (testSnoozePersistence).
Isolation between multiple monitors (testMultipleMonitorsIndependent).
Validation of duration input (negative, zero, excessive duration checks).

Ran mvn test -Dtest=AdministrativeMonitorTest (Passed: 8/8 tests).
Ran mvn clean verify to ensure no regressions in established tests.

Manual Verification:

Verified Internationalization:
Checked core/src/main/resources/hudson/model/Messages.properties for presence of Snooze, Snooze.duration.label, etc.

Verified Accessibility:
Inspected Jelly files (BuiltInNodeMigration, ControllerExecutorsAgents, ControllerExecutorsNoAgents) to ensure <label>, id, aria-label, and title attributes were correctly added to input fields.

Verified Javadoc:
Confirmed AdministrativeMonitor.java Javadoc formatting matches master branch (no diff noise).

Screenshots (UI changes only)

Before

Screenshot from 2026-02-02 16-58-00

After

Screenshot from 2026-02-02 16-50-38

Proposed changelog entries

Allow administrative monitors to be snoozed for a specific duration (up to 1 year).

Proposed changelog category

/label rfe

Proposed upgrade guidelines

N/A

Submitter checklist

  • The issue, if it exists, is well-described.
  • The changelog entries and upgrade guidelines are appropriate for the audience affected by the change (users or developers, depending on the change) and are in the imperative mood.
  • There is automated testing or an explanation as to why this change has no tests.
  • New public classes, fields, and methods are annotated with @Restricted or have @since TODO Javadocs, as appropriate.
  • New deprecations are annotated with @Deprecated(since = "TODO") or @Deprecated(forRemoval = true, since = "TODO"), if applicable.
  • UI changes do not introduce regressions when enforcing the current default rules of Content Security Policy Plugin. In particular, new or substantially changed JavaScript is not defined inline and does not call eval to ease future introduction of Content Security Policy (CSP) directives.
  • For dependency updates, there are links to external changelogs and, if possible, full differentials.
  • For new APIs and extension points, there is a link to at least one consumer.

Desired reviewers

@timja @mawinter69 @MarkEWaite

@comment-ops-bot comment-ops-bot bot added the rfe For changelog: Minor enhancement. use `major-rfe` for changes to be highlighted label Feb 2, 2026
Copy link
Contributor

@mawinter69 mawinter69 left a comment

Choose a reason for hiding this comment

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

Do we really need minute precision for this? It will be hard to know how many minutes you have to enter when you want to snooze for a couple of days or even weeks. Hours or even days might be sufficient. Maybe instead use an input with a datetime picker.

Having the input field next to the button doesn't look that good. Already in your screenshot the placeholder is cut off. Might be worth to check if using a dialog where you choose the snooze time is better. There you could easily provide a drop down with some predefined values and a custom value.

You could just do some javascript that does a post to the doSnooze method and on success just hides the message. That would avoid a page reload.


UserPreferencesProperty.DisplayName=Preferences

snooze=Snooze
Copy link
Contributor

Choose a reason for hiding this comment

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

That is not accessible from jelly files. You can see in your screenshot that the values are not used.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You are right the previous package mismatch caused the translation lookup to fail. I have now moved the form into a dedicated t:snooze taglib at lib/hudson/snooze.jelly with its own snooze.properties file in the same folder

<f:submit name="yes" value="${%Manage}"/>
<f:submit name="no" value="${%Dismiss}"/>
</form>
<form method="post" action="${rootURL}/${it.url}/snooze" style="display: inline-flex; gap: 0.5rem; align-items: center;">
Copy link
Contributor

Choose a reason for hiding this comment

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

Looks identical in all 3 places. So that might be better placed in a taglib for easy reuse.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Refactored the duplicate code into a reusable t:snooze tag in lib/hudson.

<f:submit name="yes" value="${%Manage}"/>
<f:submit name="no" value="${%Dismiss}"/>
</form>
<form method="post" action="${rootURL}/${it.url}/snooze" style="display: inline-flex; gap: 0.5rem; align-items: center;">
Copy link
Contributor

Choose a reason for hiding this comment

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

Would prefer that this is defined in a css file

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I have replaced the inline styles with core utility classes.

String duration = req.getParameter("duration");
long durationMs;
try {
if ("custom".equals(duration)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Isn't that always true as the 'duration' field has a default of custom and there is no code that will ever change this?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I included that for future flexibility, but you are right it is redundant since the current UI only sends custom. I will simplify the logic to handle the custom path directly and remove the unnecessary check.

<f:submit name="no" value="${%Dismiss}"/>
</form>
<form method="post" action="${rootURL}/${it.url}/snooze" style="display: inline-flex; gap: 0.5rem; align-items: center;">
<j:if test="${h.isCrumbEncoded()}">
Copy link
Contributor

Choose a reason for hiding this comment

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

Why is this needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I manually added the CSRF crumb logic to ensure the snooze action was secure, but I have since realized this is redundant as Jenkins global JavaScript automatically injects crumbs into all POST forms. I will remove these lines along with the manual flex styling to keep the implementation clean and consistent with the existing patterns in the codebase.

Copy link
Contributor

@mawinter69 mawinter69 left a comment

Choose a reason for hiding this comment

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

You might want to take a look at #23859 where I implemented token expiration. This also has a dialog with a select for preset expirations and a custom input field for a date select.

}

/**
* @since 2.549
Copy link
Contributor

Choose a reason for hiding this comment

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

Please don't set a version yet but write TODO. You don't know how fast your change will be merged. The version will be filled in later automatically.

Copy link
Contributor

Choose a reason for hiding this comment

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

Please use the built-in support for dialogs. See https://weekly.ci.jenkins.io/design-library/dialogs/ how to use it

<label for="snooze-duration-${it.id}" class="jenkins-form-label" style="display:block; margin-bottom: 5px;">
${%snooze.duration.label}
</label>
<select id="snooze-duration-${it.id}" name="durationPreset"
Copy link
Contributor

Choose a reason for hiding this comment

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

The select will not be styled like other selects in Jenkins

</button>

<dialog id="snooze-dialog-${it.id}" class="snooze-dialog">
<form id="snooze-form-${it.id}" method="post"
Copy link
Contributor

Choose a reason for hiding this comment

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

Use <f:form

</select>
</div>

<div class="jenkins-form-item">
Copy link
Contributor

Choose a reason for hiding this comment

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

That could be just <f:number ...

private final Map<String, Long> snoozedAdministrativeMonitors = new HashMap<>();

/**
* @since 2.549
Copy link
Contributor

Choose a reason for hiding this comment

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

-> TODO

}

/**
* @since 2.549
Copy link
Contributor

Choose a reason for hiding this comment

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

-> TODO

}

/**
* @since 2.549
Copy link
Contributor

Choose a reason for hiding this comment

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

-> TODO

}

/**
* @since 2.549
Copy link
Contributor

Choose a reason for hiding this comment

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

-> TODO

<input type="number" id="snooze-minutes-${it.id}" name="customMinutes"
placeholder="${%snooze.duration.placeholder}" min="1" max="525600"
class="jenkins-input snooze-custom-input"
style="display: none; width: 100%;"
Copy link
Contributor

Choose a reason for hiding this comment

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

In general when you want to hide something, set the class jenkins-hidden, to later show it just remove it from the element. That avoids that you need to know which exact value is set for display when it is shown.

Copy link
Contributor

@mawinter69 mawinter69 left a comment

Choose a reason for hiding this comment

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

Seems you've never really tested this.

Image Image

Uses AJAX to avoid page reload.
</st:documentation>

<st:adjunct includes="lib.hudson.snoozeForm"/>
Copy link
Contributor

Choose a reason for hiding this comment

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

You need to rename the .js and the .css files. What happens now is that the adjunct will also include the snoozeForm.jelly, leading to a duplication of things

Copy link
Contributor

Choose a reason for hiding this comment

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

By using the built-in dialog support you will most likely not need any css. It will work out of the box also with dark theme or other themes

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

rfe For changelog: Minor enhancement. use `major-rfe` for changes to be highlighted

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[JENKINS-75222] Create a snooze feature for AdministrativeMonitors

2 participants