diff --git a/src/main/java/io/jenkins/plugins/pipelinegraphview/consoleview/PipelineConsoleViewAction.java b/src/main/java/io/jenkins/plugins/pipelinegraphview/consoleview/PipelineConsoleViewAction.java index 20c1242df..a00684532 100644 --- a/src/main/java/io/jenkins/plugins/pipelinegraphview/consoleview/PipelineConsoleViewAction.java +++ b/src/main/java/io/jenkins/plugins/pipelinegraphview/consoleview/PipelineConsoleViewAction.java @@ -1,9 +1,19 @@ package io.jenkins.plugins.pipelinegraphview.consoleview; +import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import hudson.Plugin; import hudson.console.AnnotatedLargeText; +import hudson.model.Action; +import hudson.model.BallColor; +import hudson.model.Item; +import hudson.model.ParametersDefinitionProperty; +import hudson.model.Queue; +import hudson.model.Result; +import hudson.security.Permission; import hudson.util.HttpResponses; +import io.jenkins.plugins.pipelinegraphview.Messages; import io.jenkins.plugins.pipelinegraphview.PipelineGraphViewConfiguration; import io.jenkins.plugins.pipelinegraphview.cards.RunDetailsCard; import io.jenkins.plugins.pipelinegraphview.cards.RunDetailsItem; @@ -14,7 +24,8 @@ import io.jenkins.plugins.pipelinegraphview.cards.items.TimingRunDetailsItems; import io.jenkins.plugins.pipelinegraphview.cards.items.UpstreamCauseRunDetailsItem; import io.jenkins.plugins.pipelinegraphview.cards.items.UserIdCauseRunDetailsItem; -import io.jenkins.plugins.pipelinegraphview.utils.AbstractPipelineViewAction; +import io.jenkins.plugins.pipelinegraphview.utils.PipelineGraph; +import io.jenkins.plugins.pipelinegraphview.utils.PipelineGraphApi; import io.jenkins.plugins.pipelinegraphview.utils.PipelineNodeUtil; import io.jenkins.plugins.pipelinegraphview.utils.PipelineStep; import io.jenkins.plugins.pipelinegraphview.utils.PipelineStepApi; @@ -23,7 +34,12 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.List; +import java.util.concurrent.ExecutionException; +import jenkins.model.Jenkins; import net.sf.json.JSONObject; +import org.jenkins.ui.icon.IconSpec; +import org.jenkinsci.plugins.pipeline.modeldefinition.actions.RestartDeclarativePipelineAction; +import org.jenkinsci.plugins.workflow.cps.replay.ReplayAction; import org.jenkinsci.plugins.workflow.flow.FlowExecution; import org.jenkinsci.plugins.workflow.graph.FlowNode; import org.jenkinsci.plugins.workflow.job.WorkflowRun; @@ -31,24 +47,27 @@ import org.kohsuke.stapler.StaplerRequest2; import org.kohsuke.stapler.StaplerResponse2; import org.kohsuke.stapler.WebMethod; +import org.kohsuke.stapler.bind.JavaScriptMethod; +import org.kohsuke.stapler.interceptor.RequirePOST; import org.kohsuke.stapler.verb.GET; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class PipelineConsoleViewAction extends AbstractPipelineViewAction { +public class PipelineConsoleViewAction implements Action, IconSpec { public static final long LOG_THRESHOLD = 150 * 1024; // 150KB public static final String URL_NAME = "pipeline-overview"; private static final Logger logger = LoggerFactory.getLogger(PipelineConsoleViewAction.class); - private final WorkflowRun target; - private final PipelineStepApi stepApi; - private static final ObjectMapper MAPPER = new ObjectMapper(); + private final PipelineGraphApi graphApi; + private final WorkflowRun run; + private final PipelineStepApi stepApi; + public PipelineConsoleViewAction(WorkflowRun target) { - super(target); - this.target = target; - this.stepApi = new PipelineStepApi(target); + this.run = target; + this.graphApi = new PipelineGraphApi(this.run); + this.stepApi = new PipelineStepApi(this.run); } @Override @@ -61,23 +80,6 @@ public String getUrlName() { return URL_NAME; } - @Override - public String getIconClassName() { - return "symbol-git-network-outline plugin-ionicons-api"; - } - - public String getDurationString() { - return run.getDurationString(); - } - - public String getStartTimeString() { - return run.getTimestampString(); - } - - public String getUrl() { - return target.getUrl(); - } - // Legacy - leave in case we want to update a sub section of steps (e.g. if a stage is still // running). @GET @@ -110,8 +112,7 @@ public HttpResponse getAllSteps(StaplerRequest2 req) throws IOException { return HttpResponses.okJSON(getAllSteps()); } - // Private method for testing. - protected JSONObject getAllSteps() throws IOException { + private JSONObject getAllSteps() throws IOException { PipelineStepList steps = stepApi.getAllSteps(); String stepsJson = MAPPER.writeValueAsString(steps); if (logger.isDebugEnabled()) { @@ -204,7 +205,7 @@ public HttpResponse getConsoleOutput(StaplerRequest2 req) throws IOException { } protected JSONObject getConsoleOutputJson(String nodeId, Long requestStartByte) throws IOException { - Long startByte = 0L; + long startByte = 0L; long endByte = 0L; long textLength; String text = ""; @@ -253,7 +254,7 @@ protected JSONObject getConsoleOutputJson(String nodeId, Long requestStartByte) } private AnnotatedLargeText getLogForNode(String nodeId) throws IOException { - FlowExecution execution = target.getExecution(); + FlowExecution execution = run.getExecution(); if (execution != null) { logger.debug("getLogForNode found execution."); return PipelineNodeUtil.getLogText(execution.getNode(nodeId)); @@ -262,7 +263,7 @@ private AnnotatedLargeText getLogForNode(String nodeId) thro } private String getNodeExceptionText(String nodeId) throws IOException { - FlowExecution execution = target.getExecution(); + FlowExecution execution = run.getExecution(); if (execution != null) { logger.debug("getNodeException found execution."); return PipelineNodeUtil.getExceptionText(execution.getNode(nodeId)); @@ -271,7 +272,7 @@ private String getNodeExceptionText(String nodeId) throws IOException { } private boolean isUnhandledException(String nodeId) throws IOException { - FlowExecution execution = target.getExecution(); + FlowExecution execution = run.getExecution(); if (execution != null) { return PipelineNodeUtil.isUnhandledException(execution.getNode(nodeId)); } @@ -312,4 +313,151 @@ public RunDetailsCard getRunDetailsCard() { public boolean isShowGraphOnBuildPage() { return PipelineGraphViewConfiguration.get().isShowGraphOnBuildPage(); } + + public boolean isBuildable() { + return run.getParent().isBuildable(); + } + + public boolean isBuildInProgress() { + return run.isBuilding(); + } + + public Permission getPermission() { + return Item.BUILD; + } + + public Permission getCancelPermission() { + return Item.CANCEL; + } + + public Permission getConfigurePermission() { + return Item.CONFIGURE; + } + + public String getBuildDisplayName() { + return run.getDisplayName(); + } + + public String getBuildFullDisplayName() { + return run.getFullDisplayName(); + } + + public boolean isRebuildAvailable() { + Plugin rebuildPlugin = Jenkins.get().getPlugin("rebuild"); + if (rebuildPlugin != null && rebuildPlugin.getWrapper().isEnabled()) { + // limit rebuild to parameterized jobs otherwise it duplicates rerun's behaviour + return run.getParent().getProperty(ParametersDefinitionProperty.class) != null; + } + return false; + } + + public boolean isRestartFromStageAvailable() { + RestartDeclarativePipelineAction action = run.getAction(RestartDeclarativePipelineAction.class); + if (action != null) { + return action.isRestartEnabled(); + } + return false; + } + + /** + * Handles the rerun request using ReplayAction feature + */ + @RequirePOST + @JavaScriptMethod + public boolean doRerun() throws IOException, ExecutionException { + if (run != null) { + run.checkPermission(Item.BUILD); + ReplayAction replayAction = run.getAction(ReplayAction.class); + Queue.Item item = + replayAction.run2(replayAction.getOriginalScript(), replayAction.getOriginalLoadedScripts()); + + if (item == null) { + return false; + } + return true; + } + return false; + } + + /** + * Handles the cancel request. + */ + @RequirePOST + @JavaScriptMethod + public HttpResponse doCancel() throws IOException, ExecutionException { + if (run != null) { + run.checkPermission(getCancelPermission()); + if (run.isBuilding()) { + run.doStop(); + return HttpResponses.okJSON(); + } else { + String message = Result.ABORTED.equals(run.getResult()) + ? Messages.run_alreadyCancelled() + : Messages.run_isFinished(); + return HttpResponses.errorJSON(message); + } + } + return HttpResponses.errorJSON("No run to cancel"); + } + + public String getFullProjectDisplayName() { + return run.getParent().getFullDisplayName(); + } + + private String getBuildNumber(WorkflowRun run) { + if (run != null) { + return String.valueOf(run.getNumber()); + } + return null; + } + + public String getBuildUrl() { + return run.getUrl(); + } + + public String getPreviousBuildNumber() { + return getBuildNumber(run.getPreviousBuild()); + } + + public String getPreviousBuildUrl() { + WorkflowRun previousBuild = run.getPreviousBuild(); + return previousBuild == null ? null : previousBuild.getUrl(); + } + + public String getNextBuildNumber() { + return getBuildNumber(run.getNextBuild()); + } + + @GET + @WebMethod(name = "tree") + public HttpResponse getTree() throws JsonProcessingException { + if (run == null) { + return HttpResponses.errorJSON("No run to get tree for"); + } + run.checkPermission(Item.READ); + PipelineGraph tree = graphApi.createTree(); + String graph = MAPPER.writeValueAsString(tree); + return HttpResponses.okJSON(JSONObject.fromObject(graph)); + } + + // Icon related methods these may appear as unused but are used by /lib/hudson/buildCaption.jelly + @SuppressWarnings("unused") + public String getUrl() { + return run.getUrl(); + } + + @SuppressWarnings("unused") + public BallColor getIconColor() { + return run.getIconColor(); + } + + @Override + public String getIconClassName() { + return "symbol-git-network-outline plugin-ionicons-api"; + } + + @Override + public String getIconFileName() { + return null; + } } diff --git a/src/main/java/io/jenkins/plugins/pipelinegraphview/multipipelinegraphview/MultiPipelineGraphViewAction.java b/src/main/java/io/jenkins/plugins/pipelinegraphview/multipipelinegraphview/MultiPipelineGraphViewAction.java index fdafb39ba..96e3ea375 100644 --- a/src/main/java/io/jenkins/plugins/pipelinegraphview/multipipelinegraphview/MultiPipelineGraphViewAction.java +++ b/src/main/java/io/jenkins/plugins/pipelinegraphview/multipipelinegraphview/MultiPipelineGraphViewAction.java @@ -8,17 +8,13 @@ import hudson.util.HttpResponses; import hudson.util.RunList; import io.jenkins.plugins.pipelinegraphview.PipelineGraphViewConfiguration; -import io.jenkins.plugins.pipelinegraphview.utils.PipelineGraph; -import io.jenkins.plugins.pipelinegraphview.utils.PipelineGraphApi; import java.util.ArrayList; import java.util.List; import net.sf.json.JSONArray; -import net.sf.json.JSONObject; import org.jenkins.ui.icon.IconSpec; import org.jenkinsci.plugins.workflow.job.WorkflowJob; import org.jenkinsci.plugins.workflow.job.WorkflowRun; import org.kohsuke.stapler.HttpResponse; -import org.kohsuke.stapler.StaplerRequest2; import org.kohsuke.stapler.WebMethod; import org.kohsuke.stapler.verb.GET; @@ -63,21 +59,6 @@ public boolean isShowStageDurations() { return PipelineGraphViewConfiguration.get().isShowStageDurations(); } - @GET - @WebMethod(name = "tree") - public HttpResponse getTree(StaplerRequest2 req) throws JsonProcessingException { - String runId = req.getParameter("runId"); - WorkflowRun run = target.getBuildByNumber(Integer.parseInt(runId)); - PipelineGraphApi api = new PipelineGraphApi(run); - JSONObject graph = createGraphJson(api.createTree()); - return HttpResponses.okJSON(graph); - } - - protected JSONObject createGraphJson(PipelineGraph pipelineGraph) throws JsonProcessingException { - String graph = OBJECT_MAPPER.writeValueAsString(pipelineGraph); - return JSONObject.fromObject(graph); - } - @GET @WebMethod(name = "runs") public HttpResponse getRuns() throws JsonProcessingException { diff --git a/src/main/java/io/jenkins/plugins/pipelinegraphview/utils/AbstractPipelineViewAction.java b/src/main/java/io/jenkins/plugins/pipelinegraphview/utils/AbstractPipelineViewAction.java deleted file mode 100644 index 03e7045e6..000000000 --- a/src/main/java/io/jenkins/plugins/pipelinegraphview/utils/AbstractPipelineViewAction.java +++ /dev/null @@ -1,228 +0,0 @@ -package io.jenkins.plugins.pipelinegraphview.utils; - -import static io.jenkins.plugins.pipelinegraphview.consoleview.PipelineConsoleViewAction.URL_NAME; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import hudson.Plugin; -import hudson.model.Action; -import hudson.model.BallColor; -import hudson.model.Item; -import hudson.model.ParametersDefinitionProperty; -import hudson.model.Queue; -import hudson.model.Result; -import hudson.security.Permission; -import hudson.util.HttpResponses; -import io.jenkins.plugins.pipelinegraphview.Messages; -import java.io.IOException; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeoutException; -import java.util.logging.Level; -import java.util.logging.Logger; -import jenkins.model.Jenkins; -import net.sf.json.JSONObject; -import org.jenkins.ui.icon.IconSpec; -import org.jenkinsci.plugins.pipeline.modeldefinition.actions.RestartDeclarativePipelineAction; -import org.jenkinsci.plugins.workflow.cps.replay.ReplayAction; -import org.jenkinsci.plugins.workflow.job.WorkflowRun; -import org.kohsuke.stapler.HttpResponse; -import org.kohsuke.stapler.StaplerRequest2; -import org.kohsuke.stapler.WebMethod; -import org.kohsuke.stapler.bind.JavaScriptMethod; -import org.kohsuke.stapler.interceptor.RequirePOST; - -public abstract class AbstractPipelineViewAction implements Action, IconSpec { - private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); - private static final Logger LOGGER = Logger.getLogger(AbstractPipelineViewAction.class.getName()); - - protected final transient PipelineGraphApi api; - protected final transient WorkflowRun run; - - public AbstractPipelineViewAction(WorkflowRun target) { - this.api = new PipelineGraphApi(target); - this.run = target; - } - - public boolean isBuildable() { - return run.getParent().isBuildable(); - } - - public boolean isBuildInProgress() { - return run.isBuilding(); - } - - public Permission getPermission() { - return Item.BUILD; - } - - public Permission getCancelPermission() { - return Item.CANCEL; - } - - public Permission getConfigurePermission() { - return Item.CONFIGURE; - } - - public String getBuildDisplayName() { - return run.getDisplayName(); - } - - public String getBuildFullDisplayName() { - return run.getFullDisplayName(); - } - - public boolean isRebuildAvailable() { - Plugin rebuildPlugin = Jenkins.get().getPlugin("rebuild"); - if (rebuildPlugin != null && rebuildPlugin.getWrapper().isEnabled()) { - // limit rebuild to parameterized jobs otherwise it duplicates rerun's behaviour - return run.getParent().getProperty(ParametersDefinitionProperty.class) != null; - } - return false; - } - - public boolean isRestartFromStageAvailable() { - RestartDeclarativePipelineAction action = run.getAction(RestartDeclarativePipelineAction.class); - if (action != null) { - return action.isRestartEnabled(); - } - return false; - } - - /** - * Handles the rerun request using ReplayAction feature - */ - @RequirePOST - @JavaScriptMethod - public boolean doRerun() throws IOException, ExecutionException { - if (run != null) { - run.checkAnyPermission(Item.BUILD); - ReplayAction replayAction = run.getAction(ReplayAction.class); - Queue.Item item = - replayAction.run2(replayAction.getOriginalScript(), replayAction.getOriginalLoadedScripts()); - - if (item == null) { - return false; - } - return true; - } - return false; - } - - /** - * Handles the cancel request. - */ - @RequirePOST - @JavaScriptMethod - public HttpResponse doCancel() throws IOException, ExecutionException { - if (run != null) { - run.checkPermission(getCancelPermission()); - if (run.isBuilding()) { - run.doStop(); - return HttpResponses.okJSON(); - } else { - String message = Result.ABORTED.equals(run.getResult()) - ? Messages.run_alreadyCancelled() - : Messages.run_isFinished(); - return HttpResponses.errorJSON(message); - } - } - return HttpResponses.errorJSON("No run to cancel"); - } - - public String getFullBuildDisplayName() { - return run.getFullDisplayName(); - } - - public String getFullProjectDisplayName() { - return run.getParent().getFullDisplayName(); - } - - private String getBuildNumber(WorkflowRun run) { - if (run != null) { - return String.valueOf(run.getNumber()); - } - return null; - } - - public String getBuildUrl() { - return run.getUrl(); - } - - public String getPreviousBuildNumber() { - return getBuildNumber(run.getPreviousBuild()); - } - - public String getPreviousBuildUrl() { - WorkflowRun previousBuild = run.getPreviousBuild(); - return previousBuild == null ? null : previousBuild.getUrl(); - } - - public String getNextBuildNumber() { - return getBuildNumber(run.getNextBuild()); - } - - public BallColor getIconColor() { - return run.getIconColor(); - } - - public String getBuildStatusIconClassName() { - return run.getBuildStatusIconClassName(); - } - - protected JSONObject createJson(PipelineGraph pipelineGraph) throws JsonProcessingException { - String graph = OBJECT_MAPPER.writeValueAsString(pipelineGraph); - return JSONObject.fromObject(graph); - } - - @WebMethod(name = "tree") - public HttpResponse getTree() throws JsonProcessingException { - JSONObject graph = createJson(api.createTree()); - - return HttpResponses.okJSON(graph); - } - - @WebMethod(name = "replay") - public HttpResponse replayRun(StaplerRequest2 req) { - - JSONObject result = new JSONObject(); - - Integer estimatedNextBuildNumber; - try { - estimatedNextBuildNumber = api.replay(); - } catch (ExecutionException | InterruptedException | TimeoutException e) { - LOGGER.log(Level.SEVERE, "Failed to queue item", e); - return HttpResponses.errorJSON("failed to queue item: " + e.getMessage()); - } - - if (estimatedNextBuildNumber == null) { - return HttpResponses.errorJSON("failed to get next build number"); - } - - result.put( - "url", - appendTrailingSlashIfRequired(req.getContextPath()) - + run.getUrl().replace("/" + run.getNumber() + "/", "/" + estimatedNextBuildNumber + "/") - + URL_NAME); - - result.put("success", true); - return HttpResponses.okJSON(result); - } - - private static String appendTrailingSlashIfRequired(String string) { - if (string.endsWith("/")) { - return string; - } - - return string + "/"; - } - - @Override - public String getIconFileName() { - return null; - } - - @Override - public String getIconClassName() { - return null; - } -} diff --git a/src/main/java/io/jenkins/plugins/pipelinegraphview/utils/PipelineGraphApi.java b/src/main/java/io/jenkins/plugins/pipelinegraphview/utils/PipelineGraphApi.java index 462f34fc5..fcf33ae65 100644 --- a/src/main/java/io/jenkins/plugins/pipelinegraphview/utils/PipelineGraphApi.java +++ b/src/main/java/io/jenkins/plugins/pipelinegraphview/utils/PipelineGraphApi.java @@ -2,15 +2,9 @@ import static java.util.Collections.emptyList; -import hudson.model.Cause; -import hudson.model.CauseAction; -import hudson.model.Item; -import hudson.model.Queue; import io.jenkins.plugins.pipelinegraphview.treescanner.PipelineNodeGraphAdapter; import java.io.IOException; import java.util.*; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeoutException; import java.util.function.Function; import java.util.stream.Collectors; import org.jenkinsci.plugins.workflow.actions.WorkspaceAction; @@ -29,24 +23,6 @@ public PipelineGraphApi(WorkflowRun run) { this.run = run; } - public Integer replay() throws ExecutionException, InterruptedException, TimeoutException { - run.checkPermission(Item.BUILD); - - CauseAction causeAction = new CauseAction(new Cause.UserIdCause()); - - if (!run.getParent().isBuildable()) { - return null; - } - - Queue.Item item = - Queue.getInstance().schedule2(run.getParent(), 0, causeAction).getItem(); - if (item == null) { - return null; - } - - return run.getParent().getNextBuildNumber(); - } - private List getPipelineNodes(PipelineGraphBuilderApi builder) { return builder.getPipelineNodes().stream() .map(flowNodeWrapper -> new PipelineStageInternal(