diff --git a/plugin/pom.xml b/plugin/pom.xml index 6d139c8d5..e56f6a39f 100644 --- a/plugin/pom.xml +++ b/plugin/pom.xml @@ -56,7 +56,7 @@ io.jenkins.tools.bom bom-${jenkins.baseline}.x - 5294.va_d2e144c80e1 + 6421.v4a_efb_4b_3a_61d pom import diff --git a/plugin/src/main/java/org/jenkinsci/plugins/workflow/cps/replay/EditPipelineAndReplayAction.java b/plugin/src/main/java/org/jenkinsci/plugins/workflow/cps/replay/EditPipelineAndReplayAction.java new file mode 100644 index 000000000..4641570d4 --- /dev/null +++ b/plugin/src/main/java/org/jenkinsci/plugins/workflow/cps/replay/EditPipelineAndReplayAction.java @@ -0,0 +1,60 @@ +/* + * The MIT License + * + * Copyright 2026, 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. + */ + +package org.jenkinsci.plugins.workflow.cps.replay; + +import hudson.Extension; +import hudson.model.Action; +import hudson.model.Run; +import java.util.Set; +import org.jspecify.annotations.NonNull; + +@Extension +public class EditPipelineAndReplayAction extends ReplayActionMenuContributor { + + @Override + public @NonNull Set getActions(Run run) { + return Set.of(new Action() { + @Override + public String getIconFileName() { + return "symbol-arrow-redo-outline plugin-ionicons-api"; + } + + @Override + public String getDisplayName() { + return Messages.EditPipelineAndReplayAction_displayName(); + } + + @Override + public String getDescription() { + return Messages.EditPipelineAndReplayAction_description(); + } + + @Override + public String getUrlName() { + return "replay"; + } + }); + } +} diff --git a/plugin/src/main/java/org/jenkinsci/plugins/workflow/cps/replay/ReplayAction.java b/plugin/src/main/java/org/jenkinsci/plugins/workflow/cps/replay/ReplayAction.java index 67ecdde74..117d169df 100644 --- a/plugin/src/main/java/org/jenkinsci/plugins/workflow/cps/replay/ReplayAction.java +++ b/plugin/src/main/java/org/jenkinsci/plugins/workflow/cps/replay/ReplayAction.java @@ -65,6 +65,11 @@ import jenkins.model.Jenkins; import jenkins.model.ParameterizedJobMixIn; import jenkins.model.TransientActionFactory; +import jenkins.model.menu.Group; +import jenkins.model.menu.Semantic; +import jenkins.model.menu.event.Event; +import jenkins.model.menu.event.JavaScriptEvent; +import jenkins.model.menu.event.SplitButtonEvent; import jenkins.scm.api.SCMRevisionAction; import net.sf.json.JSON; import net.sf.json.JSONObject; @@ -107,6 +112,10 @@ public String getDisplayName() { @Override public String getIconFileName() { + if (run.isBuilding()) { + return null; + } + return isEnabled() || isRebuildEnabled() ? "symbol-arrow-redo-outline plugin-ionicons-api" : null; } @@ -115,6 +124,40 @@ public String getUrlName() { return isEnabled() || isRebuildEnabled() ? "replay" : null; } + @Override + public Group getGroup() { + return Group.FIRST_IN_APP_BAR; + } + + @Override + public Event getEvent() { + String urlName = getUrlName() + "/rebuild"; + + JavaScriptEvent primaryEvent = JavaScriptEvent.of( + Map.of( + "type", + "replay", + "href", + urlName, + "buildSuccess", + Messages.ReplayAction_buildSuccess(), + "buildFailure", + Messages.ReplayAction_buildFailure()), + "plugin/workflow-cps/js/replay.js"); + + // Allow for plugins to add additional build options + List actions = ReplayActionMenuContributor.all().stream() + .flatMap(e -> e.getActions(run).stream()) + .toList(); + + return SplitButtonEvent.of(primaryEvent, actions); + } + + @Override + public Semantic getSemantic() { + return Semantic.BUILD; + } + /** Poke for an execution without blocking - may be null if run is very fresh or has not lazy-loaded yet. */ private @CheckForNull CpsFlowExecution getExecutionLazy() { FlowExecutionOwner owner = ((FlowExecutionOwner.Executable) run).asFlowExecutionOwner(); diff --git a/plugin/src/main/java/org/jenkinsci/plugins/workflow/cps/replay/ReplayActionMenuContributor.java b/plugin/src/main/java/org/jenkinsci/plugins/workflow/cps/replay/ReplayActionMenuContributor.java new file mode 100644 index 000000000..95dbb3899 --- /dev/null +++ b/plugin/src/main/java/org/jenkinsci/plugins/workflow/cps/replay/ReplayActionMenuContributor.java @@ -0,0 +1,44 @@ +/* + * The MIT License + * + * Copyright 2026, 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. + */ + +package org.jenkinsci.plugins.workflow.cps.replay; + +import edu.umd.cs.findbugs.annotations.NonNull; +import hudson.ExtensionList; +import hudson.ExtensionPoint; +import hudson.model.Action; +import hudson.model.Run; +import java.util.Set; + +/** + * Allows plugins to contribute additional entries to the {@link ReplayAction} split button. + */ +public abstract class ReplayActionMenuContributor implements ExtensionPoint { + + public abstract @NonNull Set getActions(Run run); + + public static @NonNull ExtensionList all() { + return ExtensionList.lookup(ReplayActionMenuContributor.class); + } +} diff --git a/plugin/src/main/resources/org/jenkinsci/plugins/workflow/cps/replay/Messages.properties b/plugin/src/main/resources/org/jenkinsci/plugins/workflow/cps/replay/Messages.properties index 6b4c6d0fc..fb10bcc02 100644 --- a/plugin/src/main/resources/org/jenkinsci/plugins/workflow/cps/replay/Messages.properties +++ b/plugin/src/main/resources/org/jenkinsci/plugins/workflow/cps/replay/Messages.properties @@ -2,5 +2,9 @@ Replay.permission.description=Ability to perform a new Pipeline build with an ed ReplayCommand.shortDescription=Replay a Pipeline build with edited script taken from standard input ReplayAction.displayName=Replay ReplayAction.rebuild.displayName=Rebuild +ReplayAction.buildSuccess=Build scheduled +ReplayAction.buildFailure=Failed to schedule build. Reload the page and try again. +EditPipelineAndReplayAction.displayName=Edit and replay +EditPipelineAndReplayAction.description=Edit Pipeline and replay ReplayCause.shortDescription=Replayed #{0} ReplayCause.rebuild.shortDescription=Rebuilt #{0} diff --git a/plugin/src/main/webapp/js/replay.js b/plugin/src/main/webapp/js/replay.js new file mode 100644 index 000000000..c3b7db855 --- /dev/null +++ b/plugin/src/main/webapp/js/replay.js @@ -0,0 +1,27 @@ +/** + * Attaches click behavior to "Replay" buttons to trigger a POST + * request and show a success or error notification. + */ +Behaviour.specify( + "button[data-type='replay']", + "button-replay", + 999, + (button) => { + button.addEventListener("click", function (event) { + const success = button.dataset.buildSuccess; + const failure = button.dataset.buildFailure; + + fetch(button.dataset.baseUrl + button.dataset.href, { + method: "post", + headers: crumb.wrap({}), + }).then((rsp) => { + if (rsp.status === 200) { + notificationBar.show(success, notificationBar.SUCCESS); + } else { + notificationBar.show(failure, notificationBar.ERROR); + } + }); + event.preventDefault(); + }); + }, +); diff --git a/pom.xml b/pom.xml index 8c207d5f0..004d0898d 100644 --- a/pom.xml +++ b/pom.xml @@ -35,8 +35,10 @@ 999999-SNAPSHOT jenkinsci/${project.artifactId}-plugin - 2.516 - ${jenkins.baseline}.3 + 2.555 + 2.566 + + 21 2.4.21 false