mirror of https://github.com/jenkinsci/jenkins.git
Add experimental Parameters details for builds
This commit is contained in:
parent
7aa344749f
commit
78c8f15755
|
@ -48,6 +48,7 @@ import java.util.TreeSet;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
import jenkins.model.RunAction2;
|
import jenkins.model.RunAction2;
|
||||||
|
import jenkins.model.experimentalflags.NewBuildPageUserExperimentalFlag;
|
||||||
import jenkins.util.SystemProperties;
|
import jenkins.util.SystemProperties;
|
||||||
import org.kohsuke.accmod.Restricted;
|
import org.kohsuke.accmod.Restricted;
|
||||||
import org.kohsuke.accmod.restrictions.NoExternalUse;
|
import org.kohsuke.accmod.restrictions.NoExternalUse;
|
||||||
|
@ -208,6 +209,12 @@ public class ParametersAction implements RunAction2, Iterable<ParameterValue>, Q
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getIconFileName() {
|
public String getIconFileName() {
|
||||||
|
Boolean newBuildPageEnabled = new NewBuildPageUserExperimentalFlag().getFlagValue();
|
||||||
|
|
||||||
|
if (newBuildPageEnabled) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return "symbol-parameters";
|
return "symbol-parameters";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,8 @@ import static jakarta.servlet.http.HttpServletResponse.SC_CONFLICT;
|
||||||
import static jakarta.servlet.http.HttpServletResponse.SC_CREATED;
|
import static jakarta.servlet.http.HttpServletResponse.SC_CREATED;
|
||||||
|
|
||||||
import edu.umd.cs.findbugs.annotations.CheckForNull;
|
import edu.umd.cs.findbugs.annotations.CheckForNull;
|
||||||
|
import edu.umd.cs.findbugs.annotations.NonNull;
|
||||||
|
import hudson.Extension;
|
||||||
import hudson.Util;
|
import hudson.Util;
|
||||||
import hudson.cli.declarative.CLIMethod;
|
import hudson.cli.declarative.CLIMethod;
|
||||||
import hudson.cli.declarative.CLIResolver;
|
import hudson.cli.declarative.CLIResolver;
|
||||||
|
@ -60,6 +62,9 @@ import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import jenkins.model.details.Detail;
|
||||||
|
import jenkins.model.details.DetailFactory;
|
||||||
|
import jenkins.model.details.ParameterizedDetail;
|
||||||
import jenkins.model.lazy.LazyBuildMixIn;
|
import jenkins.model.lazy.LazyBuildMixIn;
|
||||||
import jenkins.security.stapler.StaplerNotDispatchable;
|
import jenkins.security.stapler.StaplerNotDispatchable;
|
||||||
import jenkins.triggers.SCMTriggerItem;
|
import jenkins.triggers.SCMTriggerItem;
|
||||||
|
@ -561,6 +566,25 @@ public abstract class ParameterizedJobMixIn<JobT extends Job<JobT, RunT> & Param
|
||||||
return !isDisabled() && !((Job) this).isHoldOffBuildUntilSave();
|
return !isDisabled() && !((Job) this).isHoldOffBuildUntilSave();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Extension
|
||||||
|
final class ParameterizedDetailFactory extends DetailFactory<Run> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<Run> type() {
|
||||||
|
return Run.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override public List<? extends Detail> createFor(@NonNull Run target) {
|
||||||
|
var action = target.getAction(ParametersAction.class);
|
||||||
|
|
||||||
|
if (action == null || action.getParameters().isEmpty()) {
|
||||||
|
return List.of();
|
||||||
|
}
|
||||||
|
|
||||||
|
return List.of(new ParameterizedDetail(target));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
package jenkins.model.details;
|
||||||
|
|
||||||
|
import edu.umd.cs.findbugs.annotations.Nullable;
|
||||||
|
import hudson.model.ParametersAction;
|
||||||
|
import hudson.model.Run;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays if a run has parameters
|
||||||
|
*/
|
||||||
|
public class ParameterizedDetail extends Detail {
|
||||||
|
|
||||||
|
public final ParametersAction action;
|
||||||
|
|
||||||
|
public ParameterizedDetail(Run<?, ?> run) {
|
||||||
|
super(run);
|
||||||
|
this.action = getObject().getAction(ParametersAction.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable String getIconClassName() {
|
||||||
|
return "symbol-parameters";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable String getDisplayName() {
|
||||||
|
return action.getDisplayName();
|
||||||
|
}
|
||||||
|
}
|
|
@ -27,8 +27,8 @@ THE SOFTWARE.
|
||||||
xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form"
|
xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form"
|
||||||
xmlns:i="jelly:fmt" xmlns:p="/lib/hudson/project">
|
xmlns:i="jelly:fmt" xmlns:p="/lib/hudson/project">
|
||||||
<j:set var="escapeEntryTitleAndDescription" value="false"/>
|
<j:set var="escapeEntryTitleAndDescription" value="false"/>
|
||||||
<f:entry description="${it.formattedDescription}">
|
<j:set var="readOnlyMode" value="true"/>
|
||||||
<j:set var="readOnlyMode" value="true"/>
|
<div>
|
||||||
<f:checkbox title="${h.escape(it.name)}" name="value" checked="${it.value}"/>
|
<f:checkbox title="${h.escape(it.name)}" name="value" checked="${it.value}" description="${it.formattedDescription}" />
|
||||||
</f:entry>
|
</div>
|
||||||
</j:jelly>
|
</j:jelly>
|
||||||
|
|
|
@ -28,7 +28,9 @@ THE SOFTWARE.
|
||||||
xmlns:i="jelly:fmt" xmlns:p="/lib/hudson/project">
|
xmlns:i="jelly:fmt" xmlns:p="/lib/hudson/project">
|
||||||
<j:set var="escapeEntryTitleAndDescription" value="false"/>
|
<j:set var="escapeEntryTitleAndDescription" value="false"/>
|
||||||
<f:entry title="${h.escape(it.name)}" description="${it.formattedDescription}">
|
<f:entry title="${h.escape(it.name)}" description="${it.formattedDescription}">
|
||||||
<j:set var="readOnlyMode" value="true"/>
|
<div class="jenkins-quote jenkins-quote--full-width jenkins-quote--monospace" id="example">
|
||||||
<f:textbox name="value" value="${it.value}"/>
|
${it.value}
|
||||||
|
<l:copyButton text="${it.value}" iconOnly="true" />
|
||||||
|
</div>
|
||||||
</f:entry>
|
</f:entry>
|
||||||
</j:jelly>
|
</j:jelly>
|
|
@ -0,0 +1,52 @@
|
||||||
|
<!--
|
||||||
|
The MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2025 Jan Faracik
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<?jelly escape-by-default='true'?>
|
||||||
|
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:l="/lib/layout">
|
||||||
|
<l:dialog title="${it.displayName}">
|
||||||
|
<div class="app-parameters-dialog">
|
||||||
|
<j:invokeStatic var="currentThread" className="java.lang.Thread" method="currentThread" />
|
||||||
|
<j:invoke var="buildClass" on="${currentThread.contextClassLoader}" method="loadClass">
|
||||||
|
<j:arg value="hudson.model.Run" />
|
||||||
|
</j:invoke>
|
||||||
|
<j:set var="build" value="${request2.findAncestorObject(buildClass)}" />
|
||||||
|
<j:set var="escapeEntryTitleAndDescription" value="true" /> <!-- SECURITY-353 defense unless overridden -->
|
||||||
|
<j:set var="readOnlyMode" value="true"/>
|
||||||
|
<j:forEach var="parameterValue" items="${it.action.parameters}">
|
||||||
|
<st:include it="${parameterValue}" page="value.jelly" />
|
||||||
|
</j:forEach>
|
||||||
|
</div>
|
||||||
|
</l:dialog>
|
||||||
|
|
||||||
|
<button class="jenkins-details__item"
|
||||||
|
data-type="dialog-opener"
|
||||||
|
data-dialog-id="${dialogId}">
|
||||||
|
<div class="jenkins-details__item__icon">
|
||||||
|
<l:icon src="${it.iconClassName}" />
|
||||||
|
</div>
|
||||||
|
<span>
|
||||||
|
${it.displayName}
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</j:jelly>
|
|
@ -0,0 +1,44 @@
|
||||||
|
<!--
|
||||||
|
The MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2025 Jan Faracik
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
|
-->
|
||||||
|
<?jelly escape-by-default='true'?>
|
||||||
|
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout">
|
||||||
|
<st:documentation>
|
||||||
|
<st:attribute name="title" use="required">
|
||||||
|
The title for the dialog.
|
||||||
|
</st:attribute>
|
||||||
|
|
||||||
|
A lazy-loaded dialog. Set 'data-type="dialog-opener"' and 'data-dialog-id="${dialogId}"'
|
||||||
|
on your button to open the dialog.
|
||||||
|
</st:documentation>
|
||||||
|
|
||||||
|
<j:set var="dialogId" value="${h.generateId()}" scope="parent" />
|
||||||
|
|
||||||
|
<l:renderOnDemand clazz="${dialogId}-template" tag="template" capture="it,dialogId">
|
||||||
|
<l:ajax>
|
||||||
|
<template id="${dialogId}-template" data-title="${attrs.title}">
|
||||||
|
<d:invokeBody />
|
||||||
|
</template>
|
||||||
|
</l:ajax>
|
||||||
|
</l:renderOnDemand>
|
||||||
|
</j:jelly>
|
|
@ -292,6 +292,36 @@ function init() {
|
||||||
return dialog.show();
|
return dialog.show();
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
behaviorShim.specify(
|
||||||
|
"[data-type='dialog-opener']",
|
||||||
|
"-dialog-",
|
||||||
|
1000,
|
||||||
|
(element) => {
|
||||||
|
element.addEventListener("click", () => {
|
||||||
|
const templateId = element.dataset.dialogId + "-template";
|
||||||
|
|
||||||
|
function render() {
|
||||||
|
const template = document.querySelector("#" + templateId);
|
||||||
|
const title = template.dataset.title;
|
||||||
|
const content = template.content.firstElementChild.cloneNode(true);
|
||||||
|
|
||||||
|
behaviorShim.applySubtree(content, false);
|
||||||
|
dialog.modal(content, {
|
||||||
|
maxWidth: "550px",
|
||||||
|
title: title,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (document.querySelector("#" + templateId)) {
|
||||||
|
render();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderOnDemand(document.querySelector("." + templateId), render);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default { init };
|
export default { init };
|
||||||
|
|
|
@ -1,16 +1,33 @@
|
||||||
|
@use "../abstracts/mixins";
|
||||||
|
|
||||||
$icon-size: 1.125rem;
|
$icon-size: 1.125rem;
|
||||||
|
|
||||||
.jenkins-details {
|
.jenkins-details {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 0.75rem 1.25rem;
|
align-items: center;
|
||||||
|
gap: 0.625rem 1.25rem;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
button.jenkins-details__item,
|
||||||
|
a.jenkins-details__item {
|
||||||
|
@include mixins.item($border: false);
|
||||||
|
|
||||||
|
&::before,
|
||||||
|
&::after {
|
||||||
|
inset: 0 -0.5rem;
|
||||||
|
pointer-events: all;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&__item {
|
&__item {
|
||||||
display: grid;
|
display: flex;
|
||||||
grid-template-columns: auto 1fr;
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
color: var(--text-color-secondary);
|
color: var(--text-color-secondary) !important;
|
||||||
|
padding: 0;
|
||||||
|
min-height: 2rem;
|
||||||
|
|
||||||
&__icon {
|
&__icon {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -18,7 +35,7 @@ $icon-size: 1.125rem;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-self: start;
|
align-self: start;
|
||||||
width: $icon-size;
|
width: $icon-size;
|
||||||
height: 1lh;
|
height: 2rem;
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
width: $icon-size;
|
width: $icon-size;
|
||||||
|
@ -34,5 +51,6 @@ $icon-size: 1.125rem;
|
||||||
&__separator {
|
&__separator {
|
||||||
color: var(--text-color-secondary);
|
color: var(--text-color-secondary);
|
||||||
opacity: 0.5;
|
opacity: 0.5;
|
||||||
|
margin-inline: -0.5rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
$jenkins-dialog-padding: 1.5rem;
|
$jenkins-dialog-padding: 1.25rem;
|
||||||
$jenkins-dialog-font-size: 0.9375rem;
|
|
||||||
|
|
||||||
.jenkins-dialog {
|
.jenkins-dialog {
|
||||||
border-radius: 1rem;
|
border-radius: 1rem;
|
||||||
|
@ -25,12 +24,13 @@ $jenkins-dialog-font-size: 0.9375rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__title {
|
&__title {
|
||||||
font-size: 1.125rem;
|
font-size: 1rem;
|
||||||
font-weight: var(--font-bold-weight);
|
font-weight: var(--font-bold-weight);
|
||||||
padding: 0 $jenkins-dialog-padding;
|
padding: 0 $jenkins-dialog-padding;
|
||||||
color: var(--text-color);
|
color: var(--text-color);
|
||||||
overflow-wrap: anywhere;
|
overflow-wrap: anywhere;
|
||||||
text-box: cap alphabetic;
|
text-box: cap alphabetic;
|
||||||
|
margin-top: 0.5625rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__contents {
|
&__contents {
|
||||||
|
@ -38,7 +38,6 @@ $jenkins-dialog-font-size: 0.9375rem;
|
||||||
overflow-wrap: break-word;
|
overflow-wrap: break-word;
|
||||||
padding: 0 $jenkins-dialog-padding;
|
padding: 0 $jenkins-dialog-padding;
|
||||||
max-height: 75vh;
|
max-height: 75vh;
|
||||||
font-size: $jenkins-dialog-font-size;
|
|
||||||
color: var(--text-color-secondary);
|
color: var(--text-color-secondary);
|
||||||
|
|
||||||
&--modal {
|
&--modal {
|
||||||
|
@ -72,7 +71,7 @@ $jenkins-dialog-font-size: 0.9375rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__subtitle {
|
&__subtitle {
|
||||||
font-size: 1rem;
|
font-size: var(--font-size-sm);
|
||||||
font-weight: var(--font-bold-weight);
|
font-weight: var(--font-bold-weight);
|
||||||
color: var(--text-color-secondary);
|
color: var(--text-color-secondary);
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
@ -81,10 +80,11 @@ $jenkins-dialog-font-size: 0.9375rem;
|
||||||
|
|
||||||
&__close-button {
|
&__close-button {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: $jenkins-dialog-padding - 0.5rem;
|
top: $jenkins-dialog-padding;
|
||||||
right: $jenkins-dialog-padding - 0.5rem;
|
right: $jenkins-dialog-padding;
|
||||||
aspect-ratio: 1;
|
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
width: 2rem;
|
||||||
|
min-height: 2rem;
|
||||||
border-radius: 100%;
|
border-radius: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -87,8 +87,19 @@
|
||||||
border-radius: calc(var(--form-input-border-radius) - 0.25rem);
|
border-radius: calc(var(--form-input-border-radius) - 0.25rem);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&--full-width {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr auto;
|
||||||
|
}
|
||||||
|
|
||||||
&--monospace {
|
&--monospace {
|
||||||
font-family: var(--font-family-mono);
|
font-family: var(--font-family-mono);
|
||||||
|
|
||||||
|
// Copy buttons' tooltips inherit font-family from the parent element,
|
||||||
|
// so override it
|
||||||
|
span {
|
||||||
|
font-family: var(--font-family-sans);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
@use "../base/breakpoints";
|
@use "../base/breakpoints";
|
||||||
|
@use "../components/dialogs";
|
||||||
|
|
||||||
.build-caption-progress-container {
|
.build-caption-progress-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
@ -107,3 +108,13 @@
|
||||||
font-size: var(--font-size-sm);
|
font-size: var(--font-size-sm);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.app-parameters-dialog {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: dialogs.$jenkins-dialog-padding;
|
||||||
|
|
||||||
|
& > * {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue