Skip to content

Commit 1b0b8bf

Browse files
authored
Add experimental 'Parameters' detail for runs (#11116)
2 parents 9c79ec9 + ebc9343 commit 1b0b8bf

File tree

12 files changed

+277
-19
lines changed

12 files changed

+277
-19
lines changed

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
import java.util.logging.Level;
4949
import java.util.logging.Logger;
5050
import jenkins.model.RunAction2;
51+
import jenkins.model.experimentalflags.NewBuildPageUserExperimentalFlag;
5152
import jenkins.util.SystemProperties;
5253
import org.kohsuke.accmod.Restricted;
5354
import org.kohsuke.accmod.restrictions.NoExternalUse;
@@ -208,6 +209,12 @@ public String getDisplayName() {
208209

209210
@Override
210211
public String getIconFileName() {
212+
Boolean newBuildPageEnabled = new NewBuildPageUserExperimentalFlag().getFlagValue();
213+
214+
if (newBuildPageEnabled) {
215+
return null;
216+
}
217+
211218
return "symbol-parameters";
212219
}
213220

core/src/main/java/jenkins/model/ParameterizedJobMixIn.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
import static jakarta.servlet.http.HttpServletResponse.SC_CREATED;
3030

3131
import edu.umd.cs.findbugs.annotations.CheckForNull;
32+
import edu.umd.cs.findbugs.annotations.NonNull;
33+
import hudson.Extension;
3234
import hudson.Util;
3335
import hudson.cli.declarative.CLIMethod;
3436
import hudson.cli.declarative.CLIResolver;
@@ -60,6 +62,9 @@
6062
import java.util.Collections;
6163
import java.util.List;
6264
import java.util.concurrent.TimeUnit;
65+
import jenkins.model.details.Detail;
66+
import jenkins.model.details.DetailFactory;
67+
import jenkins.model.details.ParameterizedDetail;
6368
import jenkins.model.lazy.LazyBuildMixIn;
6469
import jenkins.security.stapler.StaplerNotDispatchable;
6570
import jenkins.triggers.SCMTriggerItem;
@@ -561,6 +566,25 @@ default boolean isBuildable() {
561566
return !isDisabled() && !((Job) this).isHoldOffBuildUntilSave();
562567
}
563568

569+
@Extension
570+
final class ParameterizedDetailFactory extends DetailFactory<Run> {
571+
572+
@Override
573+
public Class<Run> type() {
574+
return Run.class;
575+
}
576+
577+
@NonNull
578+
@Override public List<? extends Detail> createFor(@NonNull Run target) {
579+
var action = target.getAction(ParametersAction.class);
580+
581+
if (action == null || action.getParameters().isEmpty()) {
582+
return List.of();
583+
}
584+
585+
return List.of(new ParameterizedDetail(target));
586+
}
587+
}
564588
}
565589

566590
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package jenkins.model.details;
2+
3+
import edu.umd.cs.findbugs.annotations.Nullable;
4+
import hudson.model.ParametersAction;
5+
import hudson.model.Run;
6+
7+
/**
8+
* Displays if a run has parameters
9+
*/
10+
public class ParameterizedDetail extends Detail {
11+
12+
public final ParametersAction action;
13+
14+
public ParameterizedDetail(Run<?, ?> run) {
15+
super(run);
16+
this.action = getObject().getAction(ParametersAction.class);
17+
}
18+
19+
public @Nullable String getIconClassName() {
20+
return "symbol-parameters";
21+
}
22+
23+
@Override
24+
public @Nullable String getDisplayName() {
25+
return action.getDisplayName();
26+
}
27+
}

core/src/main/resources/hudson/model/BooleanParameterValue/value.jelly

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ THE SOFTWARE.
2727
xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form"
2828
xmlns:i="jelly:fmt" xmlns:p="/lib/hudson/project">
2929
<j:set var="escapeEntryTitleAndDescription" value="false"/>
30-
<f:entry description="${it.formattedDescription}">
31-
<j:set var="readOnlyMode" value="true"/>
32-
<f:checkbox title="${h.escape(it.name)}" name="value" checked="${it.value}"/>
33-
</f:entry>
30+
<j:set var="readOnlyMode" value="true"/>
31+
<div>
32+
<f:checkbox title="${h.escape(it.name)}" name="value" checked="${it.value}" description="${it.formattedDescription}" />
33+
</div>
3434
</j:jelly>

core/src/main/resources/hudson/model/StringParameterValue/value.jelly

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,9 @@ THE SOFTWARE.
2828
xmlns:i="jelly:fmt" xmlns:p="/lib/hudson/project">
2929
<j:set var="escapeEntryTitleAndDescription" value="false"/>
3030
<f:entry title="${h.escape(it.name)}" description="${it.formattedDescription}">
31-
<j:set var="readOnlyMode" value="true"/>
32-
<f:textbox name="value" value="${it.value}"/>
31+
<div class="jenkins-quote jenkins-quote--full-width jenkins-quote--monospace" id="example">
32+
${it.value}
33+
<l:copyButton text="${it.value}" iconOnly="true" />
34+
</div>
3335
</f:entry>
3436
</j:jelly>
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<!--
2+
The MIT License
3+
4+
Copyright (c) 2025 Jan Faracik
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+
<?jelly escape-by-default='true'?>
26+
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:l="/lib/layout">
27+
<l:dialog title="${it.displayName}" hash="parameters">
28+
<div class="app-parameters-dialog">
29+
<j:invokeStatic var="currentThread" className="java.lang.Thread" method="currentThread" />
30+
<j:invoke var="buildClass" on="${currentThread.contextClassLoader}" method="loadClass">
31+
<j:arg value="hudson.model.Run" />
32+
</j:invoke>
33+
<j:set var="build" value="${request2.findAncestorObject(buildClass)}" />
34+
<j:set var="escapeEntryTitleAndDescription" value="true" /> <!-- SECURITY-353 defense unless overridden -->
35+
<j:set var="readOnlyMode" value="true"/>
36+
<j:forEach var="parameterValue" items="${it.action.parameters}">
37+
<st:include it="${parameterValue}" page="value.jelly" />
38+
</j:forEach>
39+
</div>
40+
</l:dialog>
41+
42+
<button class="jenkins-details__item"
43+
data-type="dialog-opener"
44+
data-dialog-id="${dialogId}">
45+
<div class="jenkins-details__item__icon">
46+
<l:icon src="${it.iconClassName}" />
47+
</div>
48+
<span>
49+
${it.displayName}
50+
</span>
51+
</button>
52+
</j:jelly>
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<!--
2+
The MIT License
3+
4+
Copyright (c) 2025 Jan Faracik
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+
<?jelly escape-by-default='true'?>
25+
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout">
26+
<st:documentation>
27+
<st:attribute name="title" use="required">
28+
The title for the dialog.
29+
</st:attribute>
30+
<st:attribute name="hash">
31+
An optional hash for the dialog. When provided, the dialog can be opened and navigated
32+
directly using its unique URL hash.
33+
</st:attribute>
34+
35+
A lazy-loaded dialog. Set 'data-type="dialog-opener"' and 'data-dialog-id="${dialogId}"'
36+
on your button to open the dialog.
37+
</st:documentation>
38+
39+
<j:set var="dialogId" value="${h.generateId()}" scope="parent" />
40+
41+
<l:renderOnDemand clazz="dialog-${dialogId}-template dialog-${attrs.hash}-hash" tag="template" capture="it,dialogId">
42+
<l:ajax>
43+
<template id="dialog-${dialogId}-template" data-title="${attrs.title}" data-dialog-hash="${attrs.hash}">
44+
<d:invokeBody />
45+
</template>
46+
</l:ajax>
47+
</l:renderOnDemand>
48+
</j:jelly>

src/main/js/components/dialogs/index.js

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,6 +189,13 @@ Dialog.prototype.show = function () {
189189
(e) => {
190190
e.preventDefault();
191191

192+
// Clear any hash
193+
history.pushState(
194+
"",
195+
document.title,
196+
window.location.pathname + window.location.search,
197+
);
198+
192199
this.dialog.setAttribute("closing", "");
193200

194201
this.dialog.addEventListener(
@@ -233,6 +240,34 @@ Dialog.prototype.show = function () {
233240
});
234241
};
235242

243+
function renderOnDemandDialog(dialogId) {
244+
const templateId = "dialog-" + dialogId + "-template";
245+
246+
function render() {
247+
const template = document.querySelector("#" + templateId);
248+
const title = template.dataset.title;
249+
const hash = template.dataset.dialogHash;
250+
const content = template.content.firstElementChild.cloneNode(true);
251+
252+
if (hash) {
253+
window.location.hash = hash;
254+
}
255+
256+
behaviorShim.applySubtree(content, false);
257+
dialog.modal(content, {
258+
maxWidth: "550px",
259+
title: title,
260+
});
261+
}
262+
263+
if (document.querySelector("#" + templateId)) {
264+
render();
265+
return;
266+
}
267+
268+
renderOnDemand(document.querySelector("." + templateId), render);
269+
}
270+
236271
function init() {
237272
window.dialog = {
238273
modal: function (content, options) {
@@ -292,6 +327,29 @@ function init() {
292327
return dialog.show();
293328
},
294329
};
330+
331+
behaviorShim.specify(
332+
"[data-type='dialog-opener']",
333+
"-dialog-",
334+
1000,
335+
(element) => {
336+
element.addEventListener("click", () => {
337+
renderOnDemandDialog(element.dataset.dialogId);
338+
});
339+
},
340+
);
341+
342+
// Open the relevant dialog if the hash is set
343+
if (window.location.hash) {
344+
const element = document.querySelector(
345+
".dialog-" + window.location.hash.substring(1) + "-hash",
346+
);
347+
if (element) {
348+
renderOnDemandDialog(
349+
element.className.match(/dialog-(id\d+)-template/)[1],
350+
);
351+
}
352+
}
295353
}
296354

297355
export default { init };

src/main/scss/components/_details.scss

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,41 @@
1+
@use "../abstracts/mixins";
2+
13
$icon-size: 1.125rem;
24

35
.jenkins-details {
46
display: flex;
5-
gap: 0.75rem 1.25rem;
7+
align-items: center;
8+
gap: 0.625rem 1.25rem;
69
flex-wrap: wrap;
710

11+
button.jenkins-details__item,
12+
a.jenkins-details__item {
13+
@include mixins.item($border: false);
14+
15+
&::before,
16+
&::after {
17+
inset: 0 -0.5rem;
18+
pointer-events: all;
19+
}
20+
}
21+
822
&__item {
9-
display: grid;
10-
grid-template-columns: auto 1fr;
23+
display: flex;
24+
align-items: center;
25+
justify-content: center;
1126
gap: 0.5rem;
1227
font-weight: normal;
13-
color: var(--text-color-secondary);
28+
color: var(--text-color-secondary) !important;
29+
padding: 0;
30+
min-height: 2rem;
1431

1532
&__icon {
1633
display: flex;
1734
align-items: center;
1835
justify-content: center;
1936
align-self: start;
2037
width: $icon-size;
21-
height: 1lh;
38+
height: 2rem;
2239

2340
svg {
2441
width: $icon-size;
@@ -34,5 +51,6 @@ $icon-size: 1.125rem;
3451
&__separator {
3552
color: var(--text-color-secondary);
3653
opacity: 0.5;
54+
margin-inline: -0.5rem;
3755
}
3856
}

0 commit comments

Comments
 (0)