Skip to content

Commit 578e46d

Browse files
authored
Replace build links in build and waitForBuild step console lines (#1263)
1 parent 14e5b99 commit 578e46d

16 files changed

Lines changed: 220 additions & 11 deletions

File tree

pom.xml

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,10 @@
9090
<groupId>org.jenkins-ci.plugins</groupId>
9191
<artifactId>metrics</artifactId>
9292
</dependency>
93+
<dependency>
94+
<groupId>org.jenkins-ci.plugins</groupId>
95+
<artifactId>pipeline-build-step</artifactId>
96+
</dependency>
9397
<dependency>
9498
<groupId>org.jenkins-ci.plugins</groupId>
9599
<artifactId>pipeline-input-step</artifactId>
@@ -138,11 +142,6 @@
138142
<artifactId>hamcrest</artifactId>
139143
<scope>test</scope>
140144
</dependency>
141-
<dependency>
142-
<groupId>org.jenkins-ci.plugins</groupId>
143-
<artifactId>pipeline-build-step</artifactId>
144-
<scope>test</scope>
145-
</dependency>
146145
<dependency>
147146
<groupId>org.jenkins-ci.plugins.workflow</groupId>
148147
<artifactId>workflow-api</artifactId>

src/main/frontend/common/RestClient.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,12 @@ export interface InputStep {
1818
parameters: boolean;
1919
}
2020

21+
export interface BuildStep {
22+
classicUrl: string;
23+
pipelineViewUrl: string;
24+
displayName: string;
25+
}
26+
2127
export interface AllStepsData {
2228
steps: StepInfo[];
2329
runIsComplete: boolean;
@@ -32,6 +38,7 @@ export interface StepInfo {
3238
title: string;
3339
state: Result;
3440
inputStep?: InputStep;
41+
buildStep?: BuildStep;
3542
id: string;
3643
type: string;
3744
stageId: string;

src/main/frontend/pipeline-console-view/pipeline-console/main/ConsoleLine.tsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import linkifyHtml from "linkify-html";
22
import { memo } from "react";
33

4+
import { BuildStep } from "../../../common/RestClient.tsx";
45
import { linkifyJsOptions } from "../../../common/utils/linkify-js.ts";
56
import { makeReactChildren, tokenizeANSIString } from "./Ansi.tsx";
67

@@ -11,12 +12,22 @@ export interface ConsoleLineProps {
1112
stepId: string;
1213
startByte: number;
1314
currentRunPath: string;
15+
buildStep?: BuildStep;
1416
}
1517

1618
// Console output line
1719
export const ConsoleLine = memo(function ConsoleLine(props: ConsoleLineProps) {
1820
const baseURL = `${props.currentRunPath}stages/?start-byte=${props.startByte}&selected-node=${props.stepId}`;
1921
const id = `log-${props.stepId}-${props.lineNumber}`;
22+
let content = props.content;
23+
const buildStep = props.buildStep;
24+
if (buildStep) {
25+
const classicUrl = buildStep.classicUrl;
26+
const pipelineViewUrl = buildStep.pipelineViewUrl;
27+
if (classicUrl && pipelineViewUrl) {
28+
content = content.replace(classicUrl, pipelineViewUrl);
29+
}
30+
}
2031
return (
2132
<pre
2233
style={{ background: "none", border: "none" }}
@@ -45,7 +56,7 @@ export const ConsoleLine = memo(function ConsoleLine(props: ConsoleLineProps) {
4556
</a>
4657
<div className="console-text">
4758
{makeReactChildren(
48-
tokenizeANSIString(linkifyHtml(props.content, linkifyJsOptions)),
59+
tokenizeANSIString(linkifyHtml(content, linkifyJsOptions)),
4960
id,
5061
)}
5162
</div>

src/main/frontend/pipeline-console-view/pipeline-console/main/ConsoleLogCard.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
import StatusIcon from "../../../common/components/status-icon.tsx";
1717
import Tooltip from "../../../common/components/tooltip.tsx";
1818
import { LocalizedMessageKey, useMessages } from "../../../common/i18n";
19+
import { BuildStep } from "../../../common/RestClient.tsx";
1920
import { classNames } from "../../../common/utils/classnames.ts";
2021
import { linkifyJsOptions } from "../../../common/utils/linkify-js.ts";
2122
import LiveTotal from "../../../common/utils/live-total.tsx";
@@ -154,6 +155,7 @@ export default function ConsoleLogCard({
154155
fetchExceptionText={fetchExceptionText}
155156
onStepToggle={onStepToggle}
156157
currentRunPath={currentRunPath}
158+
buildStep={step.buildStep}
157159
/>
158160
)}
159161
</div>
@@ -195,6 +197,7 @@ const ConsoleLogBody = memo(function ConsoleLogBody({
195197
fetchLogText,
196198
fetchExceptionText,
197199
currentRunPath,
200+
buildStep,
198201
}: ConsoleLogCardBodyProps) {
199202
const [stepBuffer, setStepBuffer] = useState<StepLogBufferInfo>({
200203
...(stepBuffers.get(stepId) || defaultStepBuffer()),
@@ -268,6 +271,7 @@ const ConsoleLogBody = memo(function ConsoleLogBody({
268271
stepId={stepId}
269272
stepState={stepState}
270273
currentRunPath={currentRunPath}
274+
buildStep={buildStep}
271275
/>
272276
</Suspense>
273277
</div>
@@ -304,4 +308,5 @@ export type ConsoleLogCardBodyProps = {
304308
scrollToTail: (stepId: string, element: HTMLDivElement) => void;
305309
stopTailingLogs: () => void;
306310
currentRunPath: string;
311+
buildStep?: BuildStep;
307312
};

src/main/frontend/pipeline-console-view/pipeline-console/main/ConsoleLogStream.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { useEffect, useLayoutEffect, useRef, useState } from "react";
22

3+
import { BuildStep } from "../../../common/RestClient.tsx";
34
import { ConsoleLine } from "./ConsoleLine.tsx";
45
import {
56
POLL_INTERVAL,
@@ -19,6 +20,7 @@ export default function ConsoleLogStream({
1920
fetchLogText,
2021
fetchExceptionText,
2122
currentRunPath,
23+
buildStep,
2224
}: ConsoleLogStreamProps) {
2325
const logRef = useRef<HTMLDivElement>(null);
2426
const [logVisible, setLogVisible] = useState(true);
@@ -102,6 +104,7 @@ export default function ConsoleLogStream({
102104
startByte={logBuffer.startByte}
103105
stopTailingLogs={stopTailingLogs}
104106
currentRunPath={currentRunPath}
107+
buildStep={buildStep}
105108
/>
106109
))}
107110
</div>
@@ -122,4 +125,5 @@ export interface ConsoleLogStreamProps {
122125
stopTailingLogs: () => void;
123126
scrollToTail: (stepId: string, element: HTMLDivElement) => void;
124127
currentRunPath: string;
128+
buildStep?: BuildStep;
125129
}

src/main/java/io/jenkins/plugins/pipelinegraphview/treescanner/PipelineNodeTreeScanner.java

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import edu.umd.cs.findbugs.annotations.CheckForNull;
44
import edu.umd.cs.findbugs.annotations.NonNull;
5+
import hudson.model.Run;
56
import io.jenkins.plugins.pipelinegraphview.analysis.TimingInfo;
67
import io.jenkins.plugins.pipelinegraphview.livestate.BlockResolutionCache;
78
import io.jenkins.plugins.pipelinegraphview.livestate.LiveGraphRegistry;
@@ -18,6 +19,7 @@
1819
import java.util.Set;
1920
import java.util.concurrent.TimeoutException;
2021
import org.jenkinsci.plugins.pipeline.modeldefinition.actions.ExecutionModelAction;
22+
import org.jenkinsci.plugins.workflow.actions.ArgumentsAction;
2123
import org.jenkinsci.plugins.workflow.actions.ErrorAction;
2224
import org.jenkinsci.plugins.workflow.cps.nodes.StepAtomNode;
2325
import org.jenkinsci.plugins.workflow.flow.FlowExecution;
@@ -27,6 +29,8 @@
2729
import org.jenkinsci.plugins.workflow.graph.FlowNode;
2830
import org.jenkinsci.plugins.workflow.graphanalysis.DepthFirstScanner;
2931
import org.jenkinsci.plugins.workflow.job.WorkflowRun;
32+
import org.jenkinsci.plugins.workflow.support.steps.build.DownstreamBuildAction;
33+
import org.jenkinsci.plugins.workflow.support.steps.build.WaitForBuildStep;
3034
import org.jenkinsci.plugins.workflow.support.steps.input.InputAction;
3135
import org.jenkinsci.plugins.workflow.support.steps.input.InputStep;
3236
import org.jenkinsci.plugins.workflow.support.steps.input.InputStepExecution;
@@ -219,6 +223,7 @@ private static class GraphBuilder {
219223

220224
private final Logger logger = LoggerFactory.getLogger(GraphBuilder.class);
221225
private final InputAction inputAction;
226+
private final DownstreamBuildAction downstreamBuildAction;
222227
private final boolean isDebugEnabled = logger.isDebugEnabled();
223228

224229
/*
@@ -236,6 +241,7 @@ public GraphBuilder(
236241
this.relationships = relationships;
237242
this.run = run;
238243
this.inputAction = run.getAction(InputAction.class);
244+
this.downstreamBuildAction = run.getAction(DownstreamBuildAction.class);
239245
this.execution = execution;
240246
this.enclosingIdsByNodeId = enclosingIdsByNodeId;
241247
this.activeNodeIds = activeNodeIds;
@@ -329,6 +335,7 @@ private boolean isStartNode(FlowNodeWrapper n) {
329335
wrappedNode.getStatus(),
330336
wrappedNode.getTiming(),
331337
wrappedNode.getInputStep(),
338+
wrappedNode.getDownstreamBuildRun(),
332339
wrappedNode.getRun(),
333340
wrappedNode.getType());
334341
FlowNodeWrapper closestParent = findParentNode(wrappedNode, stageMap);
@@ -597,7 +604,41 @@ private void assignParent(@NonNull FlowNodeWrapper wrappedNode, @CheckForNull Fl
597604
}
598605
}
599606

600-
return new FlowNodeWrapper(node, status, timing, inputStep, this.run);
607+
Run<?, ?> downstreamBuildRun = null;
608+
if (downstreamBuildAction != null && node instanceof StepAtomNode) {
609+
String nodeId = node.getId();
610+
downstreamBuildRun = downstreamBuildAction.getDownstreamBuilds().stream()
611+
.filter(db -> nodeId.equals(db.getFlowNodeId()))
612+
.findFirst()
613+
.map(db -> {
614+
try {
615+
return db.getBuild();
616+
} catch (Exception e) {
617+
logger.warn(
618+
"Could not get downstream build run for node {}: {}", nodeId, e.getMessage());
619+
return null;
620+
}
621+
})
622+
.orElse(null);
623+
}
624+
625+
if (downstreamBuildRun == null && node instanceof StepAtomNode stepAtomNode) {
626+
if (stepAtomNode.getDescriptor() instanceof WaitForBuildStep.DescriptorImpl) {
627+
Object runId = ArgumentsAction.getArguments(node).get("runId");
628+
if (runId instanceof String runIdStr) {
629+
try {
630+
downstreamBuildRun = Run.fromExternalizableId(runIdStr);
631+
} catch (Exception e) {
632+
logger.warn(
633+
"Could not get downstream build run for waitForBuild node {}: {}",
634+
node.getId(),
635+
e.getMessage());
636+
}
637+
}
638+
}
639+
}
640+
641+
return new FlowNodeWrapper(node, status, timing, inputStep, downstreamBuildRun, this.run);
601642
}
602643
}
603644
}

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

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import edu.umd.cs.findbugs.annotations.Nullable;
66
import hudson.model.Action;
77
import hudson.model.Result;
8+
import hudson.model.Run;
89
import io.jenkins.plugins.pipelinegraphview.Messages;
910
import io.jenkins.plugins.pipelinegraphview.analysis.TimingInfo;
1011
import io.jenkins.plugins.pipelinegraphview.steps.HideFromViewStep;
@@ -88,6 +89,7 @@ public enum NodeType {
8889
public final NodeType type;
8990
private final String displayName;
9091
private final InputStep inputStep;
92+
private final Run<?, ?> downstreamBuildRun;
9193
private final WorkflowRun run;
9294
private String causeOfFailure;
9395

@@ -101,7 +103,7 @@ public FlowNodeWrapper(
101103
@NonNull NodeRunStatus status,
102104
@NonNull TimingInfo timingInfo,
103105
@NonNull WorkflowRun run) {
104-
this(node, status, timingInfo, null, run, null);
106+
this(node, status, timingInfo, null, null, run, null);
105107
}
106108

107109
public FlowNodeWrapper(
@@ -110,7 +112,17 @@ public FlowNodeWrapper(
110112
@NonNull TimingInfo timingInfo,
111113
@Nullable InputStep inputStep,
112114
@NonNull WorkflowRun run) {
113-
this(node, status, timingInfo, inputStep, run, null);
115+
this(node, status, timingInfo, inputStep, null, run, null);
116+
}
117+
118+
public FlowNodeWrapper(
119+
@NonNull FlowNode node,
120+
@NonNull NodeRunStatus status,
121+
@NonNull TimingInfo timingInfo,
122+
@Nullable InputStep inputStep,
123+
@Nullable Run<?, ?> downstreamBuildRun,
124+
@NonNull WorkflowRun run) {
125+
this(node, status, timingInfo, inputStep, downstreamBuildRun, run, null);
114126
}
115127

116128
public FlowNodeWrapper(
@@ -120,12 +132,24 @@ public FlowNodeWrapper(
120132
@Nullable InputStep inputStep,
121133
@NonNull WorkflowRun run,
122134
@Nullable NodeType type) {
135+
this(node, status, timingInfo, inputStep, null, run, type);
136+
}
137+
138+
public FlowNodeWrapper(
139+
@NonNull FlowNode node,
140+
@NonNull NodeRunStatus status,
141+
@NonNull TimingInfo timingInfo,
142+
@Nullable InputStep inputStep,
143+
@Nullable Run<?, ?> downstreamBuildRun,
144+
@NonNull WorkflowRun run,
145+
@Nullable NodeType type) {
123146
this.node = node;
124147
this.status = status;
125148
this.timingInfo = timingInfo;
126149
this.type = type == null ? getNodeType(node) : type;
127150
this.displayName = PipelineNodeUtil.getDisplayName(node);
128151
this.inputStep = inputStep;
152+
this.downstreamBuildRun = downstreamBuildRun;
129153
this.run = run;
130154
}
131155

@@ -293,6 +317,10 @@ public boolean equals(Object obj) {
293317
return inputStep;
294318
}
295319

320+
public @CheckForNull Run<?, ?> getDownstreamBuildRun() {
321+
return downstreamBuildRun;
322+
}
323+
296324
@Override
297325
public int hashCode() {
298326
return node.hashCode();
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package io.jenkins.plugins.pipelinegraphview.utils;
2+
3+
public record PipelineBuildStep(String classicUrl, String pipelineViewUrl, String displayName) {}

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ public class PipelineStep extends AbstractPipelineNode {
1313
final int stageIdAsInt;
1414

1515
final PipelineInputStep inputStep;
16+
final PipelineBuildStep buildStep;
1617
private final Map<String, Object> flags;
1718

1819
public PipelineStep(
@@ -23,12 +24,14 @@ public PipelineStep(
2324
String title,
2425
String stageId,
2526
PipelineInputStep inputStep,
27+
PipelineBuildStep buildStep,
2628
TimingInfo timingInfo,
2729
Map<String, Object> flags) {
2830
super(id, name, state, type, title, timingInfo, null);
2931
this.stageId = stageId;
3032
this.stageIdAsInt = Integer.parseInt(stageId);
3133
this.inputStep = inputStep;
34+
this.buildStep = buildStep;
3235
this.flags = flags;
3336
}
3437

@@ -44,11 +47,13 @@ public PipelineStep(
4447
long startTimeMillis,
4548
String stageId,
4649
PipelineInputStep inputStep,
50+
PipelineBuildStep buildStep,
4751
Map<String, Object> flags) {
4852
super(id, name, state, type, title, pauseDurationMillis, totalDurationMillis, startTimeMillis, null);
4953
this.stageId = stageId;
5054
this.stageIdAsInt = Integer.parseInt(stageId);
5155
this.inputStep = inputStep;
56+
this.buildStep = buildStep;
5257
this.flags = flags;
5358
}
5459

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package io.jenkins.plugins.pipelinegraphview.utils;
22

33
import edu.umd.cs.findbugs.annotations.CheckForNull;
4+
import hudson.model.Run;
5+
import io.jenkins.plugins.pipelinegraphview.consoleview.PipelineConsoleViewAction;
46
import io.jenkins.plugins.pipelinegraphview.livestate.LiveGraphRegistry;
57
import io.jenkins.plugins.pipelinegraphview.livestate.LiveGraphSnapshot;
68
import io.jenkins.plugins.pipelinegraphview.treescanner.PipelineNodeGraphAdapter;
@@ -75,6 +77,7 @@ private List<PipelineStep> parseSteps(
7577
title,
7678
stageId,
7779
mapInputStep(flowNodeWrapper.getInputStep()),
80+
mapBuildStep(flowNodeWrapper.getDownstreamBuildRun()),
7881
flowNodeWrapper.getTiming(),
7982
flags);
8083
})
@@ -119,6 +122,15 @@ private PipelineInputStep mapInputStep(InputStep inputStep) {
119122
!inputStep.getParameters().isEmpty());
120123
}
121124

125+
private PipelineBuildStep mapBuildStep(Run<?, ?> downstreamBuildRun) {
126+
if (downstreamBuildRun == null) {
127+
return null;
128+
}
129+
String classicUrl = downstreamBuildRun.getUrl();
130+
String pipelineViewUrl = classicUrl + PipelineConsoleViewAction.URL_NAME;
131+
return new PipelineBuildStep(classicUrl, pipelineViewUrl, downstreamBuildRun.getFullDisplayName());
132+
}
133+
122134
// Strips ANSI colour-code escape sequences from step display names.
123135
private static final Pattern ANSI_COLOUR_CODES = Pattern.compile("\\e\\[(\\d+[;:]?)+m");
124136

0 commit comments

Comments
 (0)