Skip to content

Commit 525a483

Browse files
timbrown5timbtimjazbynek
authored
Pipeline console log - alpha (#15)
Co-authored-by: Tim Brown <[email protected]> Co-authored-by: timb <[email protected]> Co-authored-by: Tim Jacomb <[email protected]> Co-authored-by: Zbynek Konecny <[email protected]>
1 parent a338d11 commit 525a483

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

46 files changed

+2192
-169
lines changed

package-lock.json

Lines changed: 1115 additions & 63 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,14 @@
2121
},
2222
"homepage": "https://github.com/jenkinsci/pipeline-graph-view-plugin/#readme",
2323
"dependencies": {
24+
"@material-ui/core": "^4.11.4",
25+
"@material-ui/icons": "^4.11.2",
26+
"@material-ui/lab": "^4.0.0-alpha.58",
27+
"@react-spring/web": "^9.1.2",
28+
"html-react-parser": "^1.2.7",
2429
"react": "^16.13.1",
25-
"react-dom": "^16.13.1"
30+
"react-dom": "^16.13.1",
31+
"react-split-pane": "^0.1.92"
2632
},
2733
"devDependencies": {
2834
"@types/node": "10.17.35",
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import React from "react";
2+
//import ReactDOM from "react-dom";
3+
import { FunctionComponent } from 'react';
4+
5+
import { PipelineConsole } from './pipeline-console/main/';
6+
7+
//const rootElement = document.getElementById("root");
8+
//ReactDOM.render(<DataTreeView />, rootElement);
9+
10+
const App: FunctionComponent = () => {
11+
12+
return (
13+
<React.Fragment>
14+
<div>
15+
<PipelineConsole />
16+
</div>
17+
</React.Fragment>
18+
);
19+
}
20+
export default App;
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import React from 'react';
2+
import ReactDOM from 'react-dom';
3+
import App from './app';
4+
5+
import { resetEnvironment } from '../common/reset-environment';
6+
7+
resetEnvironment();
8+
const root = document.getElementById('root');
9+
ReactDOM.render(<App />, root);
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
import * as React from "react";
2+
3+
import TreeView from "@material-ui/lab/TreeView";
4+
5+
import {
6+
fade,
7+
makeStyles,
8+
withStyles,
9+
Theme,
10+
createStyles,
11+
} from "@material-ui/core/styles";
12+
import TreeItem, { TreeItemProps } from "@material-ui/lab/TreeItem";
13+
import ExpandMoreIcon from "@material-ui/icons/ExpandMore";
14+
import ChevronRightIcon from "@material-ui/icons/ChevronRight";
15+
import {
16+
StageInfo,
17+
Result,
18+
} from "../../../pipeline-graph-view/pipeline-graph/main/";
19+
20+
/**
21+
* StageInfo is the input, in the form of an Array<StageInfo> of the top-level stages of a pipeline
22+
*/
23+
export interface StepInfo {
24+
name: string;
25+
title: string;
26+
state: Result;
27+
completePercent: number;
28+
id: number;
29+
type: string;
30+
}
31+
32+
export interface TreeItemData {
33+
id: string;
34+
name: string;
35+
children: TreeItemData[];
36+
}
37+
38+
// Tree Item for stages
39+
const StageTreeItem = withStyles((theme: Theme) =>
40+
createStyles({
41+
iconContainer: {
42+
"& .close": {
43+
opacity: 0.3,
44+
},
45+
},
46+
// TODO: Make line show status of block (green = passed, red = failed, etc.)
47+
group: {
48+
marginLeft: 7,
49+
paddingLeft: 18,
50+
borderLeft: `1px dashed ${fade(theme.palette.text.primary, 0.4)}`,
51+
},
52+
})
53+
)((props: TreeItemProps) => <TreeItem {...props} />);
54+
55+
// Tree Item for steps
56+
const StepTreeItem = withStyles((theme: Theme) =>
57+
createStyles({
58+
label: {
59+
textDecoration: "underline",
60+
},
61+
})
62+
)((props: TreeItemProps) => <TreeItem {...props} />);
63+
64+
const getTreeItemsFromStepList = (stepsItems: StepInfo[]) => {
65+
return stepsItems.map((stepItemData) => {
66+
return (
67+
<StepTreeItem
68+
key={stepItemData.id}
69+
nodeId={String(stepItemData.id)}
70+
label={stepItemData.name}
71+
/>
72+
);
73+
});
74+
};
75+
76+
const getTreeItemsFromStage = (
77+
stageItems: StageInfo[],
78+
stageSteps: Map<String, StepInfo[]>
79+
) => {
80+
return stageItems.map((stageItemData) => {
81+
let children: JSX.Element[] = []
82+
if (stageItemData.children && stageItemData.children.length > 0) {
83+
children = [...getTreeItemsFromStage(stageItemData.children, stageSteps)]
84+
}
85+
let steps = stageSteps.get(`${stageItemData.id}`);
86+
if (steps) {
87+
let stepsItems = getTreeItemsFromStepList(steps);
88+
children = [...children, ...stepsItems]
89+
}
90+
return (
91+
<StageTreeItem
92+
key={stageItemData.id}
93+
nodeId={String(stageItemData.id)}
94+
label={stageItemData.name}
95+
children={children}
96+
/>
97+
);
98+
});
99+
};
100+
101+
interface DataTreeViewProps {
102+
stages: Array<StageInfo>;
103+
onActionNodeSelect: (event: React.ChangeEvent<any>, nodeIds: string) => void;
104+
}
105+
106+
interface State {
107+
stages: Array<StageInfo>;
108+
steps: Map<String, StepInfo[]>;
109+
}
110+
111+
export class DataTreeView extends React.Component {
112+
props!: DataTreeViewProps;
113+
state: State;
114+
115+
constructor(props: DataTreeViewProps) {
116+
super(props);
117+
this.state = {
118+
stages: [],
119+
steps: new Map(),
120+
};
121+
}
122+
123+
componentDidMount() {
124+
fetch("tree")
125+
.then((res) => res.json())
126+
.then((result) =>
127+
// Get steps for a each stage and add to 'steps' state
128+
this.setState(
129+
{
130+
stages: result.data.stages,
131+
},
132+
() => {
133+
// Add Steps to state - consider moving this code to a new function.
134+
this.state.stages.forEach((stageData) => {
135+
fetch(`steps?nodeId=${stageData.id}`)
136+
.then((step_res) => step_res.json())
137+
.then((step_result) =>
138+
this.setState({
139+
steps: new Map(
140+
this.state.steps.set(`${stageData.id}`, step_result.steps)
141+
),
142+
})
143+
);
144+
});
145+
}
146+
)
147+
)
148+
.catch(console.log);
149+
}
150+
151+
handleOnNodeSelect() {
152+
this.props.onActionNodeSelect;
153+
}
154+
155+
render() {
156+
return (
157+
<TreeView
158+
defaultCollapseIcon={<ExpandMoreIcon />}
159+
defaultExpandIcon={<ChevronRightIcon />}
160+
onNodeSelect={this.props.onActionNodeSelect}
161+
>
162+
{getTreeItemsFromStage(this.state.stages, this.state.steps)}
163+
</TreeView>
164+
);
165+
}
166+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import * as React from "react";
2+
import SplitPane from 'react-split-pane';
3+
import { DataTreeView } from './DataTreeView';
4+
5+
import "./pipeline-console.scss";
6+
7+
8+
interface PipelineConsoleProps { }
9+
interface PipelineConsoleState {
10+
consoleText: string
11+
}
12+
13+
export class PipelineConsole extends React.Component {
14+
constructor(props: PipelineConsoleProps) {
15+
super(props)
16+
this.handleActionNodeSelect = this.handleActionNodeSelect.bind(this)
17+
}
18+
state: PipelineConsoleState = {
19+
consoleText: 'Select a node to view console output.'
20+
}
21+
22+
handleActionNodeSelect(event: React.ChangeEvent<any>, nodeId: string) {
23+
fetch(`consoleOutput?nodeId=${nodeId}`)
24+
.then(res => res.text())
25+
.then(text => {
26+
console.log(text)
27+
this.setState({ consoleText: text })
28+
})
29+
.catch(console.log);
30+
}
31+
32+
render() {
33+
const splitPaneStyle: React.CSSProperties = {
34+
position: 'relative',
35+
height: '100%',
36+
}
37+
const paneStyle: React.CSSProperties = {
38+
paddingLeft: '8px',
39+
textAlign: 'left'
40+
}
41+
42+
return (
43+
<React.Fragment>
44+
<div className="App">
45+
<SplitPane split="vertical" minSize={150}
46+
defaultSize={parseInt(localStorage.getItem('splitPos') || '250')}
47+
onChange={(size) => localStorage.setItem('splitPos', `${size}`)}
48+
style={splitPaneStyle}>
49+
<div style={paneStyle}>
50+
<DataTreeView stages={[]} onActionNodeSelect={this.handleActionNodeSelect} />
51+
</div>
52+
<div className="console-output">
53+
<pre className="console-pane">
54+
{this.state.consoleText}
55+
</pre>
56+
</div>
57+
</SplitPane>
58+
</div>
59+
</React.Fragment>
60+
);
61+
}
62+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
//export { TreeItemData, DataTreeView, PipelineConsole } from './DataTreeView';
2+
export { PipelineConsole } from './PipelineConsole';
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
.App {
2+
font-family: sans-serif;
3+
text-align: center;
4+
height: 80%;
5+
}
6+
.Resizer {
7+
background: #000;
8+
opacity: 0.2;
9+
z-index: 1;
10+
-moz-box-sizing: border-box;
11+
-webkit-box-sizing: border-box;
12+
box-sizing: border-box;
13+
-moz-background-clip: padding;
14+
-webkit-background-clip: padding;
15+
background-clip: padding-box;
16+
&:hover {
17+
-webkit-transition: all 2s ease;
18+
transition: all 2s ease;
19+
}
20+
}
21+
.Resizer.horizontal {
22+
height: 11px;
23+
margin: -5px 0;
24+
border-top: 5px solid rgba(255, 255, 255, 0);
25+
border-bottom: 5px solid rgba(255, 255, 255, 0);
26+
cursor: row-resize;
27+
width: 100%;
28+
&:hover {
29+
border-top: 5px solid rgba(0, 0, 0, 0.5);
30+
border-bottom: 5px solid rgba(0, 0, 0, 0.5);
31+
}
32+
}
33+
.Resizer.vertical {
34+
width: 11px;
35+
margin: 0 -5px;
36+
border-left: 5px solid rgba(255, 255, 255, 0);
37+
border-right: 5px solid rgba(255, 255, 255, 0);
38+
cursor: col-resize;
39+
&:hover {
40+
border-left: 5px solid rgba(0, 0, 0, 0.5);
41+
border-right: 5px solid rgba(0, 0, 0, 0.5);
42+
}
43+
}
44+
.Resizer.disabled {
45+
cursor: not-allowed;
46+
&:hover {
47+
border-color: transparent;
48+
}
49+
}
50+
.SplitPane {
51+
position: relative;
52+
}
53+
54+
.console-output {
55+
text-align: left;
56+
padding-left: 16px;
57+
}

src/main/frontend/app.tsx renamed to src/main/frontend/pipeline-graph-view/app.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,4 @@ const App: FunctionComponent = () => {
2424
);
2525
}
2626

27-
export default App;
27+
export default App;

src/main/frontend/index.tsx renamed to src/main/frontend/pipeline-graph-view/index.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import * as React from 'react';
22
import * as ReactDOM from 'react-dom';
33

44
import App from './app';
5-
import { resetEnvironment } from './reset-environment';
5+
import { resetEnvironment } from '../common/reset-environment';
66

77
resetEnvironment();
88
const root = document.getElementById('root');
9-
ReactDOM.render(<App/>, root);
9+
ReactDOM.render(<App/>, root);

src/main/frontend/pipeline-graph/main/PipelineGraph.tsx renamed to src/main/frontend/pipeline-graph-view/pipeline-graph/main/PipelineGraph.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ export class PipelineGraph extends React.Component {
6262
selectedStage: props.selectedStage,
6363
};
6464
}
65+
6566

6667
componentDidMount() {
6768
const onPipelineDataReceived = (data: { stages: Array<StageInfo> }) => {

0 commit comments

Comments
 (0)