Skip to content

Commit e8c3fc1

Browse files
timbrown5timja
andauthored
Console: Add timing information for steps and stages. (#148)
Co-authored-by: Tim <[email protected]> Co-authored-by: Tim Jacomb <[email protected]>
1 parent 919df95 commit e8c3fc1

File tree

12 files changed

+335
-61
lines changed

12 files changed

+335
-61
lines changed

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

Lines changed: 34 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
import TreeItem, { TreeItemProps } from "@material-ui/lab/TreeItem";
1111
import ExpandMoreIcon from "@material-ui/icons/ExpandMore";
1212
import ChevronRightIcon from "@material-ui/icons/ChevronRight";
13+
1314
import {
1415
StageInfo,
1516
Result,
@@ -28,6 +29,9 @@ export interface StepInfo {
2829
id: number;
2930
type: string;
3031
stageId: string;
32+
pauseDurationMillis: string;
33+
startTimeMillis: string;
34+
totalDurationMillis: string;
3135
}
3236

3337
// Tree Item for stages
@@ -60,16 +64,18 @@ const StepTreeItem = withStyles((theme: Theme) =>
6064
const getTreeItemsFromStepList = (stepsItems: StepInfo[]) => {
6165
return stepsItems.map((stepItemData) => {
6266
return (
63-
<StepTreeItem
64-
key={stepItemData.id}
65-
nodeId={String(stepItemData.id)}
66-
label={
67-
<StepStatus
68-
status={decodeResultValue(stepItemData.state)}
69-
text={stepItemData.name.replace(/[^ -~]+/g, "")}
70-
/>
71-
}
72-
/>
67+
<div>
68+
<StepTreeItem
69+
key={stepItemData.id}
70+
nodeId={String(stepItemData.id)}
71+
label={
72+
<StepStatus
73+
status={decodeResultValue(stepItemData.state)}
74+
text={stepItemData.name.replace(/[^ -~]+/g, "")}
75+
/>
76+
}
77+
/>
78+
</div>
7379
);
7480
});
7581
};
@@ -100,22 +106,24 @@ const getTreeItemsFromStage = (stageItems: StageInfo[], steps: StepInfo[]) => {
100106
children = [...children, ...stepsItems];
101107
}
102108
return (
103-
<StageTreeItem
104-
key={stageItemData.id}
105-
nodeId={String(stageItemData.id)}
106-
label={
107-
<StepStatus
108-
status={decodeResultValue(stageItemData.state)}
109-
text={stageItemData.name}
110-
/>
111-
}
112-
children={children}
113-
classes={{
114-
label: stageItemData.synthetic
115-
? "pgv-graph-node--synthetic"
116-
: undefined,
117-
}}
118-
/>
109+
<div>
110+
<StageTreeItem
111+
key={stageItemData.id}
112+
nodeId={String(stageItemData.id)}
113+
label={
114+
<StepStatus
115+
status={decodeResultValue(stageItemData.state)}
116+
text={stageItemData.name}
117+
/>
118+
}
119+
children={children}
120+
classes={{
121+
label: stageItemData.synthetic
122+
? "pgv-graph-node--synthetic"
123+
: undefined,
124+
}}
125+
/>
126+
</div>
119127
);
120128
});
121129
};

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

Lines changed: 157 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,17 @@ import SplitPane from "react-split-pane";
44
import { DataTreeView } from "./DataTreeView";
55
import { makeReactChildren, tokenizeANSIString } from "./Ansi";
66
import { Linkify } from "./Linkify";
7-
import { StageInfo } from "../../../pipeline-graph-view/pipeline-graph/main/";
7+
import { StageInfo, Result } from "../../../pipeline-graph-view/pipeline-graph/main/";
88
import { StepInfo } from "./DataTreeView";
99

10+
import Typography from '@material-ui/core/Typography';
11+
12+
import HourglassEmptyIcon from '@material-ui/icons/HourglassEmpty';
13+
import ScheduleIcon from '@material-ui/icons/Schedule';
14+
import TimerIcon from '@material-ui/icons/Timer';
15+
import InfoIcon from '@material-ui/icons/Info';
16+
import LinkIcon from '@material-ui/icons/Link';
17+
1018
import "./pipeline-console.scss";
1119

1220
interface PipelineConsoleProps {}
@@ -27,6 +35,70 @@ export interface ConsoleLineProps {
2735
key: string;
2836
}
2937

38+
export interface StageSummaryProps {
39+
stage: StageInfo,
40+
failedSteps: StepInfo[]
41+
}
42+
43+
44+
// Tree Item for stages
45+
const StageSummary = (props: StageSummaryProps) => (
46+
<React.Fragment>
47+
<div className="stage-detail-group">
48+
<Typography color="inherit" className="detail-element-header">Stage '{props.stage.name}'</Typography>
49+
<div className="detail-element" key="start-time"><ScheduleIcon className="detail-icon"/>{props.stage.startTimeMillis}</div>
50+
<div className="detail-element" key="paused-duration"><HourglassEmptyIcon className="detail-icon"/>{props.stage.pauseDurationMillis}</div>
51+
<div className="detail-element" key="duration"><TimerIcon className="detail-icon"/>{props.stage.totalDurationMillis}</div>
52+
<div className="detail-element" key="status"><InfoIcon className="detail-icon "/><span className="capitalize">{props.stage.state}</span></div>
53+
{
54+
props.failedSteps.map((value: StepInfo) => {
55+
console.log(`Found failed step ${value}`)
56+
return (<FailedStepLink step={value} key={`failed-step-link-${value.id}`}/>);
57+
})
58+
}
59+
</div>
60+
</React.Fragment>
61+
);
62+
63+
export interface StepSummaryProps {
64+
step: StepInfo,
65+
}
66+
67+
// Tree Item for stages
68+
const StepSummary = (props: StepSummaryProps) => (
69+
<React.Fragment>
70+
<div className="step-detail-group">
71+
<div className="detail-element" key="start-time"><ScheduleIcon className="detail-icon"/>{props.step.startTimeMillis}</div>
72+
<div className="detail-element" key="paused-duration"><HourglassEmptyIcon className="detail-icon"/>{props.step.pauseDurationMillis}</div>
73+
<div className="detail-element" key="duration"><TimerIcon className="detail-icon"/>{props.step.totalDurationMillis}</div>
74+
<div className="detail-element capitalize" key="status"><InfoIcon className="detail-icon"/><span className="capitalize">{props.step.state}</span></div>
75+
</div>
76+
</React.Fragment>
77+
);
78+
79+
export interface FailedStepLinkProps {
80+
step: StepInfo,
81+
}
82+
83+
const FailedStepLink = (props: FailedStepLinkProps) => (
84+
<div className="detail-element">
85+
<LinkIcon className="detail-icon"/>
86+
<a
87+
className="detail-element"
88+
href={`?selected-node=${props.step.id}`}
89+
>
90+
Failed step: {props.step.name}
91+
</a>
92+
</div>
93+
);
94+
95+
export interface ConsoleLineProps {
96+
lineNumber: string;
97+
content: (string | JSX.Element)[];
98+
stepId: string;
99+
key: string;
100+
}
101+
30102
// Tree Item for stages
31103
const ConsoleLine = (props: ConsoleLineProps) => (
32104
<div className="console-output-item" key={props.lineNumber}>
@@ -136,6 +208,7 @@ export class PipelineConsole extends React.Component<
136208
}
137209
}
138210

211+
139212
handleUrlParams() {
140213
console.debug(`In handleUrlParams.`);
141214
let params = new URLSearchParams(document.location.search.substring(1));
@@ -210,7 +283,6 @@ export class PipelineConsole extends React.Component<
210283
getStageNodeHierarchy(nodeId: string, stages: StageInfo[]): Array<string> {
211284
for (let i = 0; i < stages.length; i++) {
212285
let stage = stages[i];
213-
console.log(`Checking node id ${stage.id}`);
214286
if (String(stage.id) == nodeId) {
215287
// Found the node, so start a list of expanded nodes - it will be this and it's ancestors.
216288
return [String(stage.id)];
@@ -226,6 +298,84 @@ export class PipelineConsole extends React.Component<
226298
return [];
227299
}
228300

301+
renderStageDetails() {
302+
let focusedStage = null;
303+
for (let i = 0; i < this.state.stages.length; i++) {
304+
let stage = this.state.stages[i];
305+
if ('' + stage.id == this.state.selected) {
306+
// User has selected a stage node.
307+
focusedStage = stage;
308+
let failedSteps = [] as StepInfo[];
309+
for (let i = 0; i < this.state.steps.length; i++) {
310+
let step = this.state.steps[i];
311+
if (step.stageId === this.state.selected) {
312+
// We seem to get a mix of upper and lower case states, so normalise on lowercase.
313+
if (step.state.toLowerCase() === "unstable") {
314+
failedSteps.push(step);
315+
}
316+
}
317+
}
318+
return (
319+
<div className="console-output">
320+
<StageSummary stage={focusedStage} failedSteps={failedSteps}/>
321+
</div>
322+
);
323+
}
324+
}
325+
return (
326+
// Return empty div
327+
<div></div>
328+
)
329+
}
330+
331+
renderStepDetails() {
332+
for (let i = 0; i < this.state.steps.length; i++) {
333+
let step = this.state.steps[i];
334+
if ('' + step.id == this.state.selected) {
335+
return (
336+
<div className="console-output">
337+
<StepSummary step={step}/>
338+
</div>
339+
);
340+
}
341+
}
342+
return (
343+
// Return empty div
344+
<div></div>
345+
)
346+
}
347+
348+
renderConsoleOutput() {
349+
if (this.state.consoleText.length > 0) {
350+
const lineChunks = this.state.consoleText
351+
.split("\n")
352+
.map(tokenizeANSIString)
353+
.map(makeReactChildren);
354+
return (
355+
<div className="console-output">
356+
<pre className="console-pane console-output-item">
357+
{
358+
lineChunks.map((line, index) => {
359+
let lineNumber = String(index + 1);
360+
return (
361+
<ConsoleLine
362+
content={line}
363+
lineNumber={lineNumber}
364+
stepId={this.state.selected}
365+
key={`${this.state.selected}-${lineNumber}`}
366+
/>
367+
);
368+
})}
369+
</pre>
370+
</div>
371+
)
372+
} else {
373+
// Return empty div if no text.
374+
return (
375+
<div></div>
376+
)
377+
}
378+
}
229379
render() {
230380
const splitPaneStyle: React.CSSProperties = {
231381
position: "relative",
@@ -237,16 +387,10 @@ export class PipelineConsole extends React.Component<
237387
overflowY: "scroll",
238388
};
239389

240-
const lineChunks = this.state.consoleText
241-
.split("\n")
242-
.map(tokenizeANSIString)
243-
.map(makeReactChildren);
244-
245390
return (
246391
<React.Fragment>
247392
<div className="App">
248393
<SplitPane
249-
split="vertical"
250394
minSize={150}
251395
defaultSize={parseInt(localStorage.getItem("splitPos") || "250")}
252396
onChange={(size) => localStorage.setItem("splitPos", `${size}`)}
@@ -262,20 +406,11 @@ export class PipelineConsole extends React.Component<
262406
steps={this.state.steps}
263407
/>
264408
</div>
265-
<div className="console-output">
266-
<pre className="console-pane console-output-item">
267-
{lineChunks.map((line, index) => {
268-
let lineNumber = String(index + 1);
269-
return (
270-
<ConsoleLine
271-
content={line}
272-
lineNumber={lineNumber}
273-
stepId={this.state.selected}
274-
key={`${this.state.selected}-${lineNumber}`}
275-
/>
276-
);
277-
})}
278-
</pre>
409+
410+
<div>
411+
{this.renderStageDetails()}
412+
{this.renderStepDetails()}
413+
{this.renderConsoleOutput()}
279414
</div>
280415
</SplitPane>
281416
</div>

0 commit comments

Comments
 (0)