Skip to content

Commit

Permalink
Merge pull request #91 from dwnusbaum/JENKINS-39203
Browse files Browse the repository at this point in the history
[JENKINS-39203] Create new action to mark a FlowNode with a result such as unstable
  • Loading branch information
dwnusbaum committed May 10, 2019
2 parents a7826a3 + 240afee commit a7141f0
Show file tree
Hide file tree
Showing 4 changed files with 140 additions and 8 deletions.
Expand Up @@ -29,13 +29,13 @@
import hudson.remoting.ProxyException;
import javax.annotation.CheckForNull;
import org.codehaus.groovy.control.MultipleCompilationErrorsException;
import org.jenkinsci.plugins.workflow.graph.AtomNode;
import org.jenkinsci.plugins.workflow.graph.FlowNode;
import javax.annotation.Nonnull;
import jenkins.model.Jenkins;
import org.apache.commons.io.output.NullOutputStream;

/**
* Attached to {@link AtomNode} that caused an error.
* Attached to {@link FlowNode} that caused an error.
*
* This has to be Action because it's added after a node is created.
*/
Expand Down
@@ -0,0 +1,50 @@
package org.jenkinsci.plugins.workflow.actions;

import hudson.model.Result;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import org.jenkinsci.plugins.workflow.graph.FlowNode;

/**
* Action to be attached to a {@link FlowNode} to signify that some non-fatal event occurred
* during execution of a {@code Step} but execution continued normally.
*
* {@link #withMessage} should be used whenever possible to give context to the warning.
* Visualizations should treat FlowNodes with this action as if the FlowNode's result was {@link #result}.
*/
public class WarningAction implements PersistentAction {
private @Nonnull Result result;
private @CheckForNull String message;

public WarningAction(@Nonnull Result result) {
this.result = result;
}

public WarningAction withMessage(String message) {
this.message = message;
return this;
}

public @CheckForNull String getMessage() {
return message;
}

public @Nonnull Result getResult() {
return result;
}

@Override
public String getDisplayName() {
return "Warning" + (message != null ? ": " + message : "");
}

@Override
public String getIconFileName() {
return null;
}

@Override
public String getUrlName() {
return null;
}
}
28 changes: 23 additions & 5 deletions src/main/java/org/jenkinsci/plugins/workflow/graph/FlowNode.java
Expand Up @@ -29,6 +29,7 @@
import hudson.model.Action;
import hudson.model.Actionable;
import hudson.model.BallColor;
import hudson.model.Result;
import hudson.model.Saveable;
import hudson.search.SearchItem;
import java.io.IOException;
Expand All @@ -47,6 +48,7 @@
import org.jenkinsci.plugins.workflow.actions.ErrorAction;
import org.jenkinsci.plugins.workflow.actions.LabelAction;
import org.jenkinsci.plugins.workflow.actions.PersistentAction;
import org.jenkinsci.plugins.workflow.actions.WarningAction;
import org.jenkinsci.plugins.workflow.flow.FlowExecution;
import org.jenkinsci.plugins.workflow.steps.FlowInterruptedException;
import org.jenkinsci.plugins.workflow.steps.StepDescriptor;
Expand Down Expand Up @@ -271,20 +273,20 @@ public String getDisplayFunctionName() {

/**
* Returns colored orb that represents the current state of this node.
*
* TODO: this makes me wonder if we should support other colored states,
* like unstable and aborted --- seems useful.
*/
@Exported
public BallColor getIconColor() {
ErrorAction error = getError();
WarningAction warning = getPersistentAction(WarningAction.class);
BallColor c = null;
if(error != null) {
if(error.getError() instanceof FlowInterruptedException) {
c = BallColor.ABORTED;
if (error.getError() instanceof FlowInterruptedException) {
c = resultToBallColor(((FlowInterruptedException) error.getError()).getResult());
} else {
c = BallColor.RED;
}
} else if (warning != null) {
c = resultToBallColor(warning.getResult());
} else {
c = BallColor.BLUE;
}
Expand All @@ -294,6 +296,22 @@ public BallColor getIconColor() {
return c;
}

private static BallColor resultToBallColor(@Nonnull Result result) {
if (result == Result.SUCCESS) {
return BallColor.BLUE;
} else if (result == Result.UNSTABLE) {
return BallColor.YELLOW;
} else if (result == Result.FAILURE) {
return BallColor.RED;
} else if (result == Result.NOT_BUILT) {
return BallColor.NOTBUILT;
} else if (result == Result.ABORTED) {
return BallColor.ABORTED;
} else {
return BallColor.GREY;
}
}

/**
* Gets a human readable name for this type of the node.
*
Expand Down
Expand Up @@ -35,27 +35,35 @@
import java.util.List;
import java.util.logging.Level;

import com.google.common.collect.Lists;
import java.util.Set;
import org.jenkinsci.plugins.workflow.actions.ArgumentsAction;
import org.jenkinsci.plugins.workflow.actions.WarningAction;
import org.jenkinsci.plugins.workflow.cps.CpsFlowDefinition;
import org.jenkinsci.plugins.workflow.flow.FlowExecution;
import org.jenkinsci.plugins.workflow.graphanalysis.DepthFirstScanner;
import org.jenkinsci.plugins.workflow.graphanalysis.FlowScanningUtils;
import org.jenkinsci.plugins.workflow.graphanalysis.NodeStepTypePredicate;
import org.jenkinsci.plugins.workflow.job.WorkflowJob;
import org.jenkinsci.plugins.workflow.job.WorkflowRun;
import org.jenkinsci.plugins.workflow.steps.Step;
import org.jenkinsci.plugins.workflow.steps.StepContext;
import org.jenkinsci.plugins.workflow.steps.StepDescriptor;
import org.jenkinsci.plugins.workflow.steps.StepExecution;
import org.jenkinsci.plugins.workflow.test.steps.SemaphoreStep;
import org.junit.Ignore;
import org.junit.Test;
import org.kohsuke.stapler.DataBoundConstructor;

import static org.hamcrest.Matchers.hasSize;
import static org.junit.Assert.*;

import org.junit.Rule;
import org.junit.runners.model.Statement;
import org.jvnet.hudson.test.Issue;
import org.jvnet.hudson.test.LoggerRule;
import org.jvnet.hudson.test.JenkinsRule;
import org.jvnet.hudson.test.RestartableJenkinsRule;
import org.jvnet.hudson.test.TestExtension;

public class FlowNodeTest {

Expand Down Expand Up @@ -436,6 +444,25 @@ public void evaluate() throws Throwable {
assertEquals(BallColor.ABORTED, coreStepNodes.get(0).getIconColor());
}

@Test public void iconColorUsesWarningActionResult() throws Exception {
WorkflowJob job = r.jenkins.createProject(WorkflowJob.class, "p");
job.setDefinition(new CpsFlowDefinition(
"warning('UNSTABLE')\n" +
"warning('FAILURE')\n", true));
WorkflowRun b = r.assertBuildStatus(Result.SUCCESS, job.scheduleBuild2(0).get());
List<FlowNode> nodes = new DepthFirstScanner().filteredNodes(b.getExecution(), new NodeStepTypePredicate("warning"));
assertThat(nodes, hasSize(2));
assertWarning(nodes.get(0), Result.FAILURE, BallColor.RED);
assertWarning(nodes.get(1), Result.UNSTABLE, BallColor.YELLOW);
}

private void assertWarning(FlowNode node, Result expectedResult, BallColor expectedColor) {
WarningAction warningAction = node.getPersistentAction(WarningAction.class);
assertNotNull(warningAction);
assertEquals(expectedResult, warningAction.getResult());
assertEquals(expectedColor, node.getIconColor());
}

private void assertExpectedEnclosing(FlowExecution execution, String nodeId, String enclosingId) throws Exception {
FlowNode node = execution.getNode(nodeId);
assertNotNull(node);
Expand Down Expand Up @@ -478,5 +505,42 @@ private List<FlowNode> enclosingBlocksIncludingNode(FlowNode node) {
encl.addAll(node.getEnclosingBlocks());
return encl;
}

// TODO: Delete and replace with UnstableStep once workflow-basic-steps dependency has it available.
public static class WarningStep extends Step {
private final Result result;
@DataBoundConstructor
public WarningStep(String result) {
this.result = Result.fromString(result);
}
@Override
public StepExecution start(StepContext sc) throws Exception {
class Execution extends StepExecution {
private final Result result;
public Execution(StepContext sc, Result result) {
super(sc);
this.result = result;
}
@Override
public boolean start() throws Exception {
getContext().get(FlowNode.class).addAction(new WarningAction(result));
getContext().onSuccess(null);
return true;
}
}
return new Execution(sc, this.result);
}
@TestExtension("iconColorUsesWarningActionResult")
public static class DescriptorImpl extends StepDescriptor {
@Override
public String getFunctionName() {
return "warning";
}
@Override
public Set<? extends Class<?>> getRequiredContext() {
return Collections.singleton(FlowNode.class);
}
}
}
}

0 comments on commit a7141f0

Please sign in to comment.