Skip to content

Commit 9105aae

Browse files
committed
Refactor snooze feature: use date picker and Jenkins dialog API
- Replaced custom minutes input with HTML5 date picker for snooze duration - Migrated from custom dialog to Jenkins dialog.form() API - Simplified snoozeForm.jelly by removing inline dialog markup - Renamed snoozeForm.js to snooze.js following Jenkins adjunct naming - Removed snoozeForm.css (now uses built-in Jenkins styles) - Updated doSnooze() to parse date instead of custom minutes - Changed @SInCE version to TODO for new methods
1 parent 7489e4c commit 9105aae

File tree

7 files changed

+196
-229
lines changed

7 files changed

+196
-229
lines changed

core/src/main/java/hudson/model/AbstractCIBase.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ public String getNodeName() {
6868
return "";
6969
}
7070

71-
/**
71+
/**
7272
* @deprecated
7373
* Why are you calling a method that always returns ""?
7474
* You probably want to call {@link Jenkins#getRootUrl()}
@@ -122,7 +122,7 @@ public void setDisabledAdministrativeMonitors(Set<String> disabledAdministrative
122122
private final Map<String, Long> snoozedAdministrativeMonitors = new HashMap<>();
123123

124124
/**
125-
* @since 2.549
125+
* @since TODO
126126
*/
127127
public Map<String, Long> getSnoozedAdministrativeMonitors() {
128128
synchronized (this.snoozedAdministrativeMonitors) {
@@ -131,7 +131,7 @@ public Map<String, Long> getSnoozedAdministrativeMonitors() {
131131
}
132132

133133
/**
134-
* @since 2.549
134+
* @since TODO
135135
*/
136136
public void setSnoozedAdministrativeMonitors(Map<String, Long> snoozedAdministrativeMonitors) {
137137
synchronized (this.snoozedAdministrativeMonitors) {
@@ -144,7 +144,7 @@ public void setSnoozedAdministrativeMonitors(Map<String, Long> snoozedAdministra
144144
* Implementation provided
145145
* ============================================================================================================== */
146146

147-
/**
147+
/**
148148
* Returns all {@link Node}s in the system, excluding {@link jenkins.model.Jenkins} instance itself which
149149
* represents the built-in node in this context.
150150
*/

core/src/main/java/hudson/model/AdministrativeMonitor.java

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ public boolean isEnabled() {
170170
}
171171

172172
/**
173-
* @since 2.549
173+
* @since TODO
174174
*/
175175
public boolean isSnoozed() {
176176
Map<String, Long> snoozed = Jenkins.get().getSnoozedAdministrativeMonitors();
@@ -197,7 +197,7 @@ public boolean isSnoozed() {
197197
}
198198

199199
/**
200-
* @since 2.549
200+
* @since TODO
201201
*/
202202
public void snooze(long durationMs) throws IOException {
203203
if (durationMs <= 0) {
@@ -251,7 +251,7 @@ public void doDisable(StaplerRequest2 req, StaplerResponse2 rsp) throws IOExcept
251251
}
252252

253253
/**
254-
* @since 2.549
254+
* @since TODO
255255
*/
256256
@RequirePOST
257257
public void doSnooze(StaplerRequest2 req, StaplerResponse2 rsp) throws IOException {
@@ -265,15 +265,20 @@ public void doSnooze(StaplerRequest2 req, StaplerResponse2 rsp) throws IOExcepti
265265
}
266266

267267
if ("custom".equals(preset)) {
268-
String minutesStr = req.getParameter("customMinutes");
269-
if (minutesStr == null || minutesStr.isEmpty()) {
270-
throw new IllegalArgumentException("Custom duration required");
268+
String snoozeUntilStr = req.getParameter("snoozeUntil");
269+
if (snoozeUntilStr == null || snoozeUntilStr.isEmpty()) {
270+
throw new IllegalArgumentException("Custom date required");
271271
}
272-
long minutes = Long.parseLong(minutesStr);
273-
if (minutes <= 0) {
274-
throw new IllegalArgumentException("Minutes must be positive");
272+
// Parse date in format yyyy-MM-dd
273+
java.time.LocalDate snoozeDate = java.time.LocalDate.parse(snoozeUntilStr);
274+
java.time.LocalDate today = java.time.LocalDate.now();
275+
if (!snoozeDate.isAfter(today)) {
276+
throw new IllegalArgumentException("Snooze date must be in the future");
275277
}
276-
durationMs = minutes * 60 * 1000;
278+
// Calculate milliseconds until end of that day
279+
java.time.ZonedDateTime endOfDay = snoozeDate.atStartOfDay(java.time.ZoneId.systemDefault())
280+
.plusDays(1);
281+
durationMs = endOfDay.toInstant().toEpochMilli() - System.currentTimeMillis();
277282
} else {
278283
long minutes = Long.parseLong(preset);
279284
if (minutes <= 0) {
@@ -282,12 +287,12 @@ public void doSnooze(StaplerRequest2 req, StaplerResponse2 rsp) throws IOExcepti
282287
durationMs = minutes * 60 * 1000;
283288
}
284289

285-
// Validate max duration (1 year = 525600 minutes)
286-
if (durationMs > 525600L * 60 * 1000) {
290+
// Validate max duration (1 year)
291+
if (durationMs > 365L * 24 * 60 * 60 * 1000) {
287292
throw new IllegalArgumentException("Duration exceeds maximum (1 year)");
288293
}
289294

290-
} catch (IllegalArgumentException e) {
295+
} catch (IllegalArgumentException | java.time.format.DateTimeParseException e) {
291296
rsp.sendError(StaplerResponse2.SC_BAD_REQUEST, e.getMessage());
292297
return;
293298
}
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
/*
2+
* The MIT License
3+
*
4+
* Copyright (c) 2025 Jenkins Contributors
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in
14+
* all copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22+
* THE SOFTWARE.
23+
*/
24+
25+
Behaviour.specify(".snooze-button", "snooze-button", 0, function (button) {
26+
button.onclick = function () {
27+
openSnoozeDialog(button);
28+
};
29+
});
30+
31+
function addSnoozeFormHandling(form) {
32+
var durationSelect = form.querySelector('select[name="durationPreset"]');
33+
var customDateInput = form.querySelector('input[name="snoozeUntil"]');
34+
35+
durationSelect.addEventListener("change", function () {
36+
if (durationSelect.value === "custom") {
37+
customDateInput.classList.remove("jenkins-hidden");
38+
var submitButton = form.querySelector('button[data-id="ok"]');
39+
if (customDateInput.value !== "") {
40+
submitButton.disabled = false;
41+
} else {
42+
submitButton.disabled = true;
43+
}
44+
} else {
45+
var submitButton = form.querySelector('button[data-id="ok"]');
46+
submitButton.disabled = false;
47+
customDateInput.classList.add("jenkins-hidden");
48+
}
49+
});
50+
51+
customDateInput.addEventListener("change", function () {
52+
var submitButton = form.querySelector('button[data-id="ok"]');
53+
if (customDateInput.value !== "") {
54+
submitButton.disabled = false;
55+
} else {
56+
submitButton.disabled = true;
57+
}
58+
});
59+
}
60+
61+
function openSnoozeDialog(button) {
62+
var snoozeUrl = button.dataset.snoozeUrl;
63+
var templateId = button.dataset.templateId;
64+
var dialogTitle = button.dataset.title;
65+
var snoozeText = button.dataset.buttonSnooze;
66+
var cancelText = button.dataset.buttonCancel;
67+
68+
var formTemplate = document
69+
.getElementById(templateId)
70+
.firstElementChild.cloneNode(true);
71+
var form = document.createElement("form");
72+
73+
// Set date constraints
74+
var dateInput = formTemplate.querySelector('input[name="snoozeUntil"]');
75+
var now = new Date();
76+
var tomorrow = new Date();
77+
var nextYear = new Date();
78+
var presetDate = new Date();
79+
tomorrow.setDate(now.getDate() + 1);
80+
presetDate.setDate(now.getDate() + 1);
81+
nextYear.setDate(now.getDate() + 365);
82+
dateInput.min = tomorrow.toISOString().split("T")[0];
83+
dateInput.max = nextYear.toISOString().split("T")[0];
84+
dateInput.value = presetDate.toISOString().split("T")[0];
85+
86+
form.appendChild(formTemplate);
87+
addSnoozeFormHandling(form);
88+
89+
dialog
90+
.form(form, {
91+
title: dialogTitle,
92+
okText: snoozeText,
93+
cancelText: cancelText,
94+
submitButton: false,
95+
maxWidth: "450px",
96+
minWidth: "350px",
97+
})
98+
.then(
99+
function (formData) {
100+
// Get crumb directly from document head
101+
var crumbHeaderName = document.head.getAttribute("data-crumb-header");
102+
var crumbValue = document.head.getAttribute("data-crumb-value");
103+
104+
var params = {
105+
durationPreset: formData.get("durationPreset"),
106+
snoozeUntil: formData.get("snoozeUntil"),
107+
};
108+
// Add crumb to body
109+
if (crumbHeaderName && crumbValue) {
110+
params[crumbHeaderName] = crumbValue;
111+
}
112+
113+
var headers = {
114+
"Content-Type": "application/x-www-form-urlencoded",
115+
};
116+
// Add crumb to headers
117+
if (crumbHeaderName && crumbValue) {
118+
headers[crumbHeaderName] = crumbValue;
119+
}
120+
121+
fetch(snoozeUrl, {
122+
body: new URLSearchParams(params),
123+
method: "post",
124+
headers: headers,
125+
}).then(function (rsp) {
126+
if (rsp.ok) {
127+
// Find and hide the monitor's parent element
128+
var monitorElement = button.closest(".jenkins-alert");
129+
if (monitorElement) {
130+
monitorElement.style.display = "none";
131+
} else {
132+
// Fallback: reload the page
133+
window.location.reload();
134+
}
135+
} else {
136+
rsp.text().then(function (msg) {
137+
dialog.alert("Error", {
138+
message: msg || "Failed to snooze monitor",
139+
type: "destructive",
140+
});
141+
});
142+
}
143+
});
144+
},
145+
function () {
146+
// Dialog cancelled - do nothing
147+
},
148+
);
149+
}

core/src/main/resources/lib/hudson/snoozeForm.css

Lines changed: 0 additions & 41 deletions
This file was deleted.

0 commit comments

Comments
 (0)