Skip to content

Commit 2828851

Browse files
authored
Fix PipelineNodeUtil categorization of steps, stages and parallel branches (#584)
1 parent 352f59e commit 2828851

File tree

4 files changed

+121
-20
lines changed

4 files changed

+121
-20
lines changed

src/main/java/io/jenkins/plugins/pipelinegraphview/utils/FlowNodeWrapper.java

+1-2
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
import org.jenkinsci.plugins.workflow.actions.ErrorAction;
1818
import org.jenkinsci.plugins.workflow.actions.LabelAction;
1919
import org.jenkinsci.plugins.workflow.cps.nodes.StepStartNode;
20-
import org.jenkinsci.plugins.workflow.graph.AtomNode;
2120
import org.jenkinsci.plugins.workflow.graph.BlockEndNode;
2221
import org.jenkinsci.plugins.workflow.graph.BlockStartNode;
2322
import org.jenkinsci.plugins.workflow.graph.FlowNode;
@@ -149,7 +148,7 @@ public WorkflowRun getRun() {
149148
}
150149

151150
private static NodeType getNodeType(FlowNode node) {
152-
if (node instanceof AtomNode) {
151+
if (PipelineNodeUtil.isStep(node)) {
153152
return NodeType.STEP;
154153
} else if (PipelineNodeUtil.isStage(node)) {
155154
return NodeType.STAGE;

src/main/java/io/jenkins/plugins/pipelinegraphview/utils/PipelineNodeUtil.java

+56-18
Original file line numberDiff line numberDiff line change
@@ -22,22 +22,25 @@
2222
import org.jenkinsci.plugins.workflow.actions.LabelAction;
2323
import org.jenkinsci.plugins.workflow.actions.LogAction;
2424
import org.jenkinsci.plugins.workflow.actions.QueueItemAction;
25-
import org.jenkinsci.plugins.workflow.actions.StageAction;
2625
import org.jenkinsci.plugins.workflow.actions.TagsAction;
2726
import org.jenkinsci.plugins.workflow.actions.ThreadNameAction;
2827
import org.jenkinsci.plugins.workflow.cps.nodes.StepAtomNode;
2928
import org.jenkinsci.plugins.workflow.cps.nodes.StepStartNode;
29+
import org.jenkinsci.plugins.workflow.cps.steps.ParallelStep;
30+
import org.jenkinsci.plugins.workflow.graph.AtomNode;
3031
import org.jenkinsci.plugins.workflow.graph.FlowNode;
3132
import org.jenkinsci.plugins.workflow.steps.FlowInterruptedException;
3233
import org.jenkinsci.plugins.workflow.steps.StepDescriptor;
3334
import org.jenkinsci.plugins.workflow.support.actions.PauseAction;
3435
import org.jenkinsci.plugins.workflow.support.steps.ExecutorStep;
36+
import org.jenkinsci.plugins.workflow.support.steps.StageStep;
3537
import org.jenkinsci.plugins.workflow.support.steps.input.InputAction;
3638

3739
/** @author Vivek Pandey */
3840
public class PipelineNodeUtil {
3941

4042
private static final String DECLARATIVE_DISPLAY_NAME_PREFIX = "Declarative: ";
43+
private static final String PARALLEL_SYNTHETIC_STAGE_NAME = "Parallel";
4144

4245
public static String getDisplayName(@NonNull FlowNode node) {
4346
ThreadNameAction threadNameAction = node.getAction(ThreadNameAction.class);
@@ -47,11 +50,43 @@ public static String getDisplayName(@NonNull FlowNode node) {
4750
: name;
4851
}
4952

53+
public static boolean isStep(FlowNode node) {
54+
if (node != null) {
55+
if (node instanceof AtomNode) {
56+
return true;
57+
}
58+
if (node instanceof StepStartNode) {
59+
StepStartNode stepStartNode = (StepStartNode) node;
60+
boolean takesImplicitBlockArgument = false;
61+
StepDescriptor sd = stepStartNode.getDescriptor();
62+
if (sd != null) {
63+
takesImplicitBlockArgument = sd.takesImplicitBlockArgument();
64+
}
65+
return !isStage(node)
66+
&& !isParallelBranch(node)
67+
&& stepStartNode.isBody()
68+
&& !takesImplicitBlockArgument;
69+
}
70+
}
71+
return false;
72+
}
73+
5074
public static boolean isStage(FlowNode node) {
51-
return node != null
52-
&& ((node.getAction(StageAction.class) != null)
53-
|| (node.getAction(LabelAction.class) != null
54-
&& node.getAction(ThreadNameAction.class) == null));
75+
if (node != null) {
76+
if (node instanceof StepStartNode) {
77+
StepStartNode stepStartNode = (StepStartNode) node;
78+
if (stepStartNode.getDescriptor() != null) {
79+
StepDescriptor sd = stepStartNode.getDescriptor();
80+
return sd != null && StageStep.DescriptorImpl.class.equals(sd.getClass()) && stepStartNode.isBody();
81+
}
82+
}
83+
LabelAction labelAction = node.getAction(LabelAction.class);
84+
ThreadNameAction threadNameAction = node.getAction(ThreadNameAction.class);
85+
return labelAction != null
86+
&& PARALLEL_SYNTHETIC_STAGE_NAME.equals(labelAction.getDisplayName())
87+
&& threadNameAction == null;
88+
}
89+
return false;
5590
}
5691

5792
public static boolean isSyntheticStage(@Nullable FlowNode node) {
@@ -115,9 +150,14 @@ public static boolean isPreSyntheticStage(@Nullable FlowNode node) {
115150
}
116151

117152
public static boolean isParallelBranch(@Nullable FlowNode node) {
118-
return node != null
119-
&& node.getAction(LabelAction.class) != null
120-
&& node.getAction(ThreadNameAction.class) != null;
153+
if (node != null && node instanceof StepStartNode) {
154+
StepStartNode stepStartNode = (StepStartNode) node;
155+
if (stepStartNode.getDescriptor() != null) {
156+
StepDescriptor sd = stepStartNode.getDescriptor();
157+
return sd != null && ParallelStep.DescriptorImpl.class.equals(sd.getClass()) && stepStartNode.isBody();
158+
}
159+
}
160+
return false;
121161
}
122162

123163
public static boolean isUnhandledException(@Nullable FlowNode node) {
@@ -202,17 +242,15 @@ public static boolean isPaused(@NonNull FlowNode step) {
202242
return (pauseAction != null && pauseAction.isPaused());
203243
}
204244

205-
/* Untested way of determining if we are a parallel block.
206-
* WARNING: Use with caution.
207-
*/
208245
protected static boolean isParallelBlock(@NonNull FlowNode node) {
209-
/*
210-
* TODO: Find a better method - list of expected labels.
211-
* Seems to only have (not sure if this is true for other nodes as well though):
212-
* org.jenkinsci.plugins.workflow.support.actions.LogStorageAction
213-
* org.jenkinsci.plugins.workflow.actions.TimingAction
214-
*/
215-
return getDisplayName(node).startsWith("Execute in parallel");
246+
if (node != null && node instanceof StepStartNode) {
247+
StepStartNode stepStartNode = (StepStartNode) node;
248+
if (stepStartNode.getDescriptor() != null) {
249+
StepDescriptor sd = stepStartNode.getDescriptor();
250+
return sd != null && ParallelStep.DescriptorImpl.class.equals(sd.getClass()) && !stepStartNode.isBody();
251+
}
252+
}
253+
return false;
216254
}
217255

218256
/**

src/test/java/io/jenkins/plugins/pipelinegraphview/utils/PipelineNodeUtilTest.java

+50
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import static org.hamcrest.MatcherAssert.assertThat;
44
import static org.hamcrest.Matchers.*;
5+
import static org.junit.Assert.assertFalse;
6+
import static org.junit.Assert.assertTrue;
57

68
import hudson.console.AnnotatedLargeText;
79
import hudson.model.Result;
@@ -61,4 +63,52 @@ void pipelineCallsUndefinedVar(JenkinsRule j) throws Exception {
6163
startsWith(
6264
"Found unhandled groovy.lang.MissingPropertyException exception:\nNo such property: undefined for class: groovy.lang.Binding"));
6365
}
66+
67+
@Issue("GH#583")
68+
@Test
69+
void isStageTest(JenkinsRule j) throws Exception {
70+
WorkflowRun run =
71+
TestUtils.createAndRunJob(j, "githubIssue583_stages", "gh583_stages.jenkinsfile", Result.SUCCESS);
72+
PipelineNodeGraphAdapter builder = new PipelineNodeGraphAdapter(run);
73+
FlowNode stageANode = TestUtils.getNodesByDisplayName(run, "A").get(0);
74+
assertFalse(PipelineNodeUtil.isStep(stageANode));
75+
assertTrue(PipelineNodeUtil.isStage(stageANode));
76+
assertFalse(PipelineNodeUtil.isParallelBranch(stageANode));
77+
78+
FlowNode stageCNode = TestUtils.getNodesByDisplayName(run, "C").get(0);
79+
String stageCId = stageCNode.getId();
80+
assertFalse(PipelineNodeUtil.isStep(stageCNode));
81+
assertTrue(PipelineNodeUtil.isStage(stageCNode));
82+
assertFalse(PipelineNodeUtil.isParallelBranch(stageCNode));
83+
84+
List<FlowNodeWrapper> stageCSteps = builder.getStageSteps(stageCId);
85+
FlowNode stepCEcho = stageCSteps.get(0).getNode();
86+
assertTrue(PipelineNodeUtil.isStep(stepCEcho));
87+
assertFalse(PipelineNodeUtil.isStage(stepCEcho));
88+
assertFalse(PipelineNodeUtil.isParallelBranch(stepCEcho));
89+
90+
FlowNode stageDNode = TestUtils.getNodesByDisplayName(run, "D").get(0);
91+
String stageDId = stageDNode.getId();
92+
assertFalse(PipelineNodeUtil.isStep(stageDNode));
93+
assertTrue(PipelineNodeUtil.isStage(stageDNode));
94+
assertFalse(PipelineNodeUtil.isParallelBranch(stageDNode));
95+
96+
List<FlowNodeWrapper> stageDSteps = builder.getStageSteps(stageDId);
97+
FlowNode stepDEcho = stageDSteps.get(0).getNode();
98+
assertTrue(PipelineNodeUtil.isStep(stepDEcho));
99+
assertFalse(PipelineNodeUtil.isStage(stepDEcho));
100+
assertFalse(PipelineNodeUtil.isParallelBranch(stepDEcho));
101+
102+
FlowNode branchB1Node =
103+
TestUtils.getNodesByDisplayName(run, "Branch: B1").get(0);
104+
assertFalse(PipelineNodeUtil.isStep(branchB1Node));
105+
assertFalse(PipelineNodeUtil.isStage(branchB1Node));
106+
assertTrue(PipelineNodeUtil.isParallelBranch(branchB1Node));
107+
108+
FlowNode branchB2Node =
109+
TestUtils.getNodesByDisplayName(run, "Branch: B2").get(0);
110+
assertFalse(PipelineNodeUtil.isStep(branchB2Node));
111+
assertFalse(PipelineNodeUtil.isStage(branchB2Node));
112+
assertTrue(PipelineNodeUtil.isParallelBranch(branchB2Node));
113+
}
64114
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
stage("A") {
2+
parallel([
3+
"B1": {
4+
stage("C") {
5+
echo("C")
6+
}
7+
},
8+
"B2": {
9+
stage("D") {
10+
echo("D")
11+
}
12+
}
13+
])
14+
}

0 commit comments

Comments
 (0)