diff --git a/src/main/frontend/pipeline-console-view/pipeline-console/main/PipelineConsoleModel.tsx b/src/main/frontend/pipeline-console-view/pipeline-console/main/PipelineConsoleModel.tsx index b8e67e271..b9cef2d45 100644 --- a/src/main/frontend/pipeline-console-view/pipeline-console/main/PipelineConsoleModel.tsx +++ b/src/main/frontend/pipeline-console-view/pipeline-console/main/PipelineConsoleModel.tsx @@ -1,10 +1,7 @@ import { Result } from "../../../pipeline-graph-view/pipeline-graph/main/PipelineGraphModel"; // re-export Result so the relative path exists in one location. -export { - Result, - decodeResultValue, -} from "../../../pipeline-graph-view/pipeline-graph/main/PipelineGraphModel"; +export { Result } from "../../../pipeline-graph-view/pipeline-graph/main/PipelineGraphModel"; export type { StageInfo, diff --git a/src/main/frontend/pipeline-graph-view/pipeline-graph/main/PipelineGraphModel.tsx b/src/main/frontend/pipeline-graph-view/pipeline-graph/main/PipelineGraphModel.tsx index b16cd282b..fd8755ea9 100644 --- a/src/main/frontend/pipeline-graph-view/pipeline-graph/main/PipelineGraphModel.tsx +++ b/src/main/frontend/pipeline-graph-view/pipeline-graph/main/PipelineGraphModel.tsx @@ -8,18 +8,7 @@ export enum Result { aborted = "aborted", not_built = "not_built", // May be pending, or job was ended before this point skipped = "skipped", // excluded via pipeline "when" clause - unknown = "unknown", // bad data or client code needs updating for new values -} - -export function decodeResultValue(resultMaybe: any): Result { - const lcase = String(resultMaybe).toLowerCase(); - - // TODO: validate this - if ((Object.values(Result) as any).includes(lcase)) { - return lcase as Result; - } - - return Result.unknown; + unknown = "unknown", // bad data } // Dimensions used for layout, px diff --git a/src/main/frontend/pipeline-graph-view/pipeline-graph/main/support/nodes.tsx b/src/main/frontend/pipeline-graph-view/pipeline-graph/main/support/nodes.tsx index d696b3f85..99eddbd3a 100644 --- a/src/main/frontend/pipeline-graph-view/pipeline-graph/main/support/nodes.tsx +++ b/src/main/frontend/pipeline-graph-view/pipeline-graph/main/support/nodes.tsx @@ -1,7 +1,7 @@ import * as React from "react"; +import { CSSProperties } from "react"; import { - decodeResultValue, LayoutInfo, NodeColumn, NodeInfo, @@ -10,7 +10,6 @@ import { import StatusIcon, { resultToColor, } from "../../../../common/components/status-icon"; -import { CSSProperties } from "react"; type SVGChildren = Array; // Fixme: Maybe refine this? Not sure what should go here, we have working code I can't make typecheck @@ -40,8 +39,6 @@ export function Node({ node }: NodeProps) { } const { title, state, url } = node.stage ?? {}; - const resultClean = decodeResultValue(state); - groupChildren.push( getPipelineNodes(PipelineGraphBuilderApi builder) { return builder.getPipelineNodes().stream() - .map(flowNodeWrapper -> { - String state = - flowNodeWrapper.getStatus().getResult().name().toLowerCase(Locale.ROOT); - if (flowNodeWrapper.getStatus().getState() != BlueRun.BlueRunState.FINISHED) { - state = flowNodeWrapper.getStatus().getState().name().toLowerCase(Locale.ROOT); - } - return new PipelineStageInternal( - flowNodeWrapper.getId(), // TODO no need to parse it BO returns a string even though the - // datatype is number on the frontend - flowNodeWrapper.getDisplayName(), - flowNodeWrapper.getParents().stream() - .map(FlowNodeWrapper::getId) - .collect(Collectors.toList()), - state, - flowNodeWrapper.getType().name(), - flowNodeWrapper.getDisplayName(), // TODO blue ocean uses timing information: "Passed in 0s" - flowNodeWrapper.isSynthetic(), - flowNodeWrapper.getTiming(), - getStageNode(flowNodeWrapper)); - }) + .map(flowNodeWrapper -> new PipelineStageInternal( + flowNodeWrapper.getId(), // TODO no need to parse it BO returns a string even though the + // datatype is number on the frontend + flowNodeWrapper.getDisplayName(), + flowNodeWrapper.getParents().stream() + .map(FlowNodeWrapper::getId) + .collect(Collectors.toList()), + PipelineState.of(flowNodeWrapper.getStatus()), + flowNodeWrapper.getType().name(), + flowNodeWrapper.getDisplayName(), // TODO blue ocean uses timing information: "Passed in 0s" + flowNodeWrapper.isSynthetic(), + flowNodeWrapper.getTiming(), + getStageNode(flowNodeWrapper))) .collect(Collectors.toList()); } diff --git a/src/main/java/io/jenkins/plugins/pipelinegraphview/utils/PipelineStage.java b/src/main/java/io/jenkins/plugins/pipelinegraphview/utils/PipelineStage.java index e24e3e64a..c37798876 100644 --- a/src/main/java/io/jenkins/plugins/pipelinegraphview/utils/PipelineStage.java +++ b/src/main/java/io/jenkins/plugins/pipelinegraphview/utils/PipelineStage.java @@ -17,7 +17,7 @@ public PipelineStage( String id, String name, List children, - String state, + PipelineState state, String type, String title, String seqContainerName, diff --git a/src/main/java/io/jenkins/plugins/pipelinegraphview/utils/PipelineStageInternal.java b/src/main/java/io/jenkins/plugins/pipelinegraphview/utils/PipelineStageInternal.java index 425413fcb..4ea127701 100644 --- a/src/main/java/io/jenkins/plugins/pipelinegraphview/utils/PipelineStageInternal.java +++ b/src/main/java/io/jenkins/plugins/pipelinegraphview/utils/PipelineStageInternal.java @@ -2,14 +2,13 @@ import java.util.Collections; import java.util.List; -import java.util.Locale; import org.jenkinsci.plugins.workflow.pipelinegraphanalysis.TimingInfo; -public class PipelineStageInternal { +class PipelineStageInternal { private String name; private List parents; - private String state; // TODO enum + private PipelineState state; private String type; // TODO enum private String title; private String id; @@ -24,7 +23,7 @@ public PipelineStageInternal( String id, String name, List parents, - String state, + PipelineState state, String type, String title, boolean synthetic, @@ -33,7 +32,7 @@ public PipelineStageInternal( this.id = id; this.name = name; this.parents = parents; - this.state = state.toLowerCase(Locale.ROOT); + this.state = state; this.type = type; this.title = title; this.synthetic = synthetic; @@ -53,7 +52,7 @@ public void setSequential(boolean sequential) { this.sequential = sequential; } - public void setState(String state) { + public void setState(PipelineState state) { this.state = state; } @@ -93,7 +92,7 @@ public List getParents() { return parents; } - public String getState() { + public PipelineState getState() { return state; } diff --git a/src/main/java/io/jenkins/plugins/pipelinegraphview/utils/PipelineState.java b/src/main/java/io/jenkins/plugins/pipelinegraphview/utils/PipelineState.java new file mode 100644 index 000000000..630ec6938 --- /dev/null +++ b/src/main/java/io/jenkins/plugins/pipelinegraphview/utils/PipelineState.java @@ -0,0 +1,47 @@ +package io.jenkins.plugins.pipelinegraphview.utils; + +import com.fasterxml.jackson.annotation.JsonValue; +import java.util.Locale; + +public enum PipelineState { + // BlueRunState + QUEUED, + RUNNING, + PAUSED, + SKIPPED, + NOT_BUILT, + FINISHED, + // BlueRunResult + SUCCESS, + UNSTABLE, + FAILURE, + UNKNOWN, + ABORTED; + + public static PipelineState of(NodeRunStatus status) { + if (status.getState() == BlueRun.BlueRunState.FINISHED) { + return switch (status.getResult()) { + case SUCCESS -> SUCCESS; + case UNSTABLE -> UNSTABLE; + case FAILURE -> FAILURE; + case NOT_BUILT -> NOT_BUILT; + case UNKNOWN -> UNKNOWN; + case ABORTED -> ABORTED; + }; + } + return switch (status.getState()) { + case QUEUED -> QUEUED; + case RUNNING -> RUNNING; + case PAUSED -> PAUSED; + case SKIPPED -> SKIPPED; + case NOT_BUILT -> NOT_BUILT; + case FINISHED -> FINISHED; // not reached but required for compiler + }; + } + + @JsonValue + @Override + public String toString() { + return name().toLowerCase(Locale.ROOT); + } +} diff --git a/src/main/java/io/jenkins/plugins/pipelinegraphview/utils/PipelineStep.java b/src/main/java/io/jenkins/plugins/pipelinegraphview/utils/PipelineStep.java index e1c8644e5..1df34f5b3 100644 --- a/src/main/java/io/jenkins/plugins/pipelinegraphview/utils/PipelineStep.java +++ b/src/main/java/io/jenkins/plugins/pipelinegraphview/utils/PipelineStep.java @@ -6,7 +6,13 @@ public class PipelineStep extends AbstractPipelineNode { private String stageId; public PipelineStep( - String id, String name, String state, String type, String title, String stageId, TimingInfo timingInfo) { + String id, + String name, + PipelineState state, + String type, + String title, + String stageId, + TimingInfo timingInfo) { super(id, name, state, type, title, timingInfo); this.stageId = stageId; } diff --git a/src/main/java/io/jenkins/plugins/pipelinegraphview/utils/PipelineStepApi.java b/src/main/java/io/jenkins/plugins/pipelinegraphview/utils/PipelineStepApi.java index 20f02af57..ca9aadb56 100644 --- a/src/main/java/io/jenkins/plugins/pipelinegraphview/utils/PipelineStepApi.java +++ b/src/main/java/io/jenkins/plugins/pipelinegraphview/utils/PipelineStepApi.java @@ -3,7 +3,6 @@ import io.jenkins.plugins.pipelinegraphview.treescanner.PipelineNodeGraphAdapter; import io.jenkins.plugins.pipelinegraphview.utils.legacy.PipelineStepVisitor; import java.util.List; -import java.util.Locale; import java.util.Map; import java.util.stream.Collectors; import org.jenkinsci.plugins.workflow.job.WorkflowRun; @@ -24,12 +23,6 @@ private List parseSteps(List stepNodes, String st } List steps = stepNodes.stream() .map(flowNodeWrapper -> { - String state = - flowNodeWrapper.getStatus().getResult().name().toLowerCase(Locale.ROOT); - if (flowNodeWrapper.getStatus().getState() != BlueRun.BlueRunState.FINISHED) { - state = flowNodeWrapper.getStatus().getState().name().toLowerCase(Locale.ROOT); - } - String displayName = flowNodeWrapper.getDisplayName(); String title = ""; if (flowNodeWrapper.getType() == FlowNodeWrapper.NodeType.UNHANDLED_EXCEPTION) { @@ -55,7 +48,7 @@ private List parseSteps(List stepNodes, String st return new PipelineStep( flowNodeWrapper.getId(), displayName, - state, + PipelineState.of(flowNodeWrapper.getStatus()), flowNodeWrapper.getType().name(), title, // TODO blue ocean uses timing information: "Passed in // 0s" diff --git a/src/main/java/io/jenkins/plugins/pipelinegraphview/utils/TreeBuilder.java b/src/main/java/io/jenkins/plugins/pipelinegraphview/utils/TreeBuilder.java deleted file mode 100644 index 8b1378917..000000000 --- a/src/main/java/io/jenkins/plugins/pipelinegraphview/utils/TreeBuilder.java +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/test/java/io/jenkins/plugins/pipelinegraphview/utils/PipelineStateTest.java b/src/test/java/io/jenkins/plugins/pipelinegraphview/utils/PipelineStateTest.java new file mode 100644 index 000000000..9b6989c6c --- /dev/null +++ b/src/test/java/io/jenkins/plugins/pipelinegraphview/utils/PipelineStateTest.java @@ -0,0 +1,77 @@ +package io.jenkins.plugins.pipelinegraphview.utils; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.util.stream.Stream; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.MethodSource; + +class PipelineStateTest { + + @ParameterizedTest + @MethodSource("whenFinished") + void of_usesResultWhenStateIsFinished(BlueRun.BlueRunResult result, PipelineState expected) { + NodeRunStatus runStatus = new NodeRunStatus(result, BlueRun.BlueRunState.FINISHED); + + PipelineState status = PipelineState.of(runStatus); + + assertEquals(expected, status); + } + + private static Stream whenFinished() { + return Stream.of( + Arguments.arguments(BlueRun.BlueRunResult.SUCCESS, PipelineState.SUCCESS), + Arguments.arguments(BlueRun.BlueRunResult.UNSTABLE, PipelineState.UNSTABLE), + Arguments.arguments(BlueRun.BlueRunResult.FAILURE, PipelineState.FAILURE), + Arguments.arguments(BlueRun.BlueRunResult.NOT_BUILT, PipelineState.NOT_BUILT), + Arguments.arguments(BlueRun.BlueRunResult.UNKNOWN, PipelineState.UNKNOWN), + Arguments.arguments(BlueRun.BlueRunResult.ABORTED, PipelineState.ABORTED)); + } + + @ParameterizedTest + @MethodSource("whenNotFinished") + void of_usesStateWhenStateIsNotFinished(BlueRun.BlueRunState state, PipelineState expected) { + NodeRunStatus runStatus = new NodeRunStatus(null, state); + + PipelineState status = PipelineState.of(runStatus); + + assertEquals(expected, status); + } + + private static Stream whenNotFinished() { + return Stream.of( + Arguments.arguments(BlueRun.BlueRunState.QUEUED, PipelineState.QUEUED), + Arguments.arguments(BlueRun.BlueRunState.RUNNING, PipelineState.RUNNING), + Arguments.arguments(BlueRun.BlueRunState.PAUSED, PipelineState.PAUSED), + Arguments.arguments(BlueRun.BlueRunState.SKIPPED, PipelineState.SKIPPED), + Arguments.arguments(BlueRun.BlueRunState.NOT_BUILT, PipelineState.NOT_BUILT)); + } + + private static final ObjectMapper MAPPER = new ObjectMapper(); + + @ParameterizedTest + @CsvSource({ + "QUEUED, queued", + "RUNNING, running", + "PAUSED, paused", + "SKIPPED, skipped", + "NOT_BUILT, not_built", + "FINISHED, finished", + "SUCCESS, success", + "UNSTABLE, unstable", + "FAILURE, failure", + "UNKNOWN, unknown", + "ABORTED, aborted" + }) + void serialization(String input, String expected) throws JsonProcessingException { + PipelineState status = PipelineState.valueOf(input); + + String serialized = MAPPER.writeValueAsString(status); + + assertEquals("\"%s\"".formatted(expected), serialized); + } +}