Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/main/frontend/common/components/status-icon.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,8 @@ export function resultToColor(result: Result, skeleton: boolean | undefined) {
return "jenkins-!-accent-color";
case "unstable":
return "jenkins-!-warning-color";
case "paused":
return "jenkins-!-accent-color";
default:
return "jenkins-!-skipped-color";
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import org.jenkinsci.plugins.workflow.graph.FlowNode;
import org.jenkinsci.plugins.workflow.graphanalysis.DepthFirstScanner;
import org.jenkinsci.plugins.workflow.job.WorkflowRun;
import org.jenkinsci.plugins.workflow.support.steps.input.InputAction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -67,6 +68,17 @@
// these are completely new representations.
List<PipelineStageInternal> stages = getPipelineNodes(builder);

// Get InputAction once for all stages
InputAction inputAction = run.getAction(InputAction.class);

// Set the builder and inputAction on each stage so they can check for paused steps
if (builder instanceof PipelineStepBuilderApi stepBuilder) {

Check warning on line 75 in src/main/java/io/jenkins/plugins/pipelinegraphview/utils/PipelineGraphApi.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 75 is only partially covered, one branch is missing
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A separate PR could refactor the need for this instanceof check pretty easily, there's only one implementation and the indirection is no longer needed

stages.forEach(stage -> {
stage.setBuilder(stepBuilder);
stage.setInputAction(inputAction);
});
}

// id => stage
Map<String, PipelineStageInternal> stageMap = stages.stream()
.collect(Collectors.toMap(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import io.jenkins.plugins.pipelinegraphview.analysis.TimingInfo;
import java.util.Collections;
import java.util.List;
import org.jenkinsci.plugins.workflow.support.steps.input.InputAction;

class PipelineStageInternal {

Expand All @@ -19,6 +20,8 @@
private boolean synthetic;
private TimingInfo timingInfo;
private String agent;
private PipelineStepBuilderApi builder;
private InputAction inputAction;

public PipelineStageInternal(
String id,
Expand Down Expand Up @@ -113,12 +116,60 @@
this.agent = aAgent;
}

public void setBuilder(PipelineStepBuilderApi builder) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this could be avoided by doing the refactoring mentioned in PipelineGraphApi comment and then you could just pass the builder in the constructor (or another constructor) as I'd prefer not to be mutating state

this.builder = builder;
}

public void setInputAction(InputAction inputAction) {
this.inputAction = inputAction;
}

/**
* Checks if this stage or any of its children are waiting for input.
* A stage is waiting for input if any of its steps have a non-null inputStep
* and the step state is PAUSED.
* Only performs the check if there is an InputAction attached to the WorkflowRun.
*/
private boolean isWaitingForInput(List<PipelineStage> children) {
// Early exit if there's no InputAction on the run
if (inputAction == null) {
return false;
}

// Check if any child stages are waiting for input
if (children != null && !children.isEmpty()) {

Check warning on line 140 in src/main/java/io/jenkins/plugins/pipelinegraphview/utils/PipelineStageInternal.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 140 is only partially covered, 2 branches are missing
for (PipelineStage child : children) {
if (child.state == PipelineState.PAUSED) {
return true;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we get some test coverage for this please?

A pipeline that sets an input step on it and validates the stage is now paused.

}
}

Check warning on line 145 in src/main/java/io/jenkins/plugins/pipelinegraphview/utils/PipelineStageInternal.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 141-145 are not covered by tests
}

// Check steps for this stage
if (builder != null && id != null) {

Check warning on line 149 in src/main/java/io/jenkins/plugins/pipelinegraphview/utils/PipelineStageInternal.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 149 is only partially covered, 2 branches are missing
List<FlowNodeWrapper> steps = builder.getStageSteps(id);
if (steps != null) {

Check warning on line 151 in src/main/java/io/jenkins/plugins/pipelinegraphview/utils/PipelineStageInternal.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 151 is only partially covered, one branch is missing
for (FlowNodeWrapper step : steps) {

Check warning on line 152 in src/main/java/io/jenkins/plugins/pipelinegraphview/utils/PipelineStageInternal.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 152 is only partially covered, one branch is missing
// Check if step has an input and is paused
if (step.getInputStep() != null && step.getStatus().state == BlueRun.BlueRunState.PAUSED) {

Check warning on line 154 in src/main/java/io/jenkins/plugins/pipelinegraphview/utils/PipelineStageInternal.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Partially covered line

Line 154 is only partially covered, 2 branches are missing
return true;
}
}
}
}

return false;

Check warning on line 161 in src/main/java/io/jenkins/plugins/pipelinegraphview/utils/PipelineStageInternal.java

View check run for this annotation

ci.jenkins.io / Code Coverage

Not covered lines

Lines 157-161 are not covered by tests
}

public PipelineStage toPipelineStage(List<PipelineStage> children, String runUrl) {
boolean waitingForInput = isWaitingForInput(children);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we only do this check if there is an InputAction attached to the WorkflowRun?

I think that should be optimisable so this check is only run over stages that could be waiting for input?

PipelineState effectiveState = waitingForInput ? PipelineState.PAUSED : state;

return new PipelineStage(
id,
name,
children,
state,
effectiveState,
type.name(),
title,
seqContainerName,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -532,4 +532,43 @@ void createTree_branchResult() throws Exception {
"unstable-branch{unstable}",
"]")));
}

@Issue("GH#967")
@Test
void createTree_stageWithInputStepShowsAsPaused() throws Exception {
WorkflowJob job = TestUtils.createJob(j, "pipelineWithInput", "input.jenkinsfile");
QueueTaskFuture<WorkflowRun> futureRun = job.scheduleBuild2(0);
WorkflowRun run = futureRun.waitForStart();

// Wait for the input action to be available and have executions
org.jenkinsci.plugins.workflow.support.steps.input.InputAction inputAction = null;
while (inputAction == null || inputAction.getExecutions().isEmpty()) {
inputAction = run.getAction(org.jenkinsci.plugins.workflow.support.steps.input.InputAction.class);
Thread.sleep(100);
}

// Wait a bit more for the pause to be registered
Thread.sleep(500);

// Check the graph while paused on input
PipelineGraphApi api = new PipelineGraphApi(run);
PipelineGraph graph = api.createTree();

// Find the stage with input
PipelineStage inputStage = graph.stages.stream()
.filter(s -> s.name.equals("Input"))
.findFirst()
.orElseThrow();

// Verify it shows as PAUSED
assertThat(inputStage.state, equalTo(PipelineState.PAUSED));

// Approve the input and wait for completion
org.jenkinsci.plugins.workflow.support.steps.input.InputStepExecution execution =
inputAction.getExecutions().get(0);
execution.proceed(null);
j.waitForCompletion(run);

assertThat(run.getResult(), equalTo(Result.SUCCESS));
}
}