Skip to content

Commit

Permalink
feat(ui): Introduce modal DAG renderer. Fixes: argoproj#3595 (argopro…
Browse files Browse the repository at this point in the history
  • Loading branch information
simster7 authored Sep 14, 2020
1 parent ad60746 commit b5f3191
Show file tree
Hide file tree
Showing 7 changed files with 189 additions and 83 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/ci-build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,8 @@ jobs:
path: ui/node_modules
key: ${{ runner.os }}-node-dep-v1-${{ hashFiles('**/yarn.lock') }}
- name: Install, build and Lint
env:
NODE_OPTIONS: --max-old-space-size=4096
run: |
yarn --cwd ui install
yarn --cwd ui build
Expand Down
2 changes: 2 additions & 0 deletions ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@
"dependencies": {
"@babel/core": "^7.0.0-0",
"@fortawesome/fontawesome-free": "^5.12.0",
"@types/dagre": "^0.7.44",
"argo-ui": "https://github.com/argoproj/argo-ui.git",
"classnames": "^2.2.5",
"cron-parser": "^2.16.3",
"dagre": "^0.8.5",
"formik": "^2.1.2",
"history": "^4.7.2",
"js-yaml": "^3.13.1",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,17 @@ export class WorkflowDagRenderOptionsPanel extends React.Component<WorkflowDagRe
title='Expand all nodes'>
<i className='fa fa-expand' data-fa-transform='rotate-45' />
</a>
<a
className={classNames({active: this.props.fastRenderer})}
onClick={() =>
this.props.onChange({
...this.workflowDagRenderOptions,
fastRenderer: !this.props.fastRenderer
})
}
title='Use a faster, but less pretty, renderer to display the workflow'>
<i className='fa fa-bolt' />
</a>
</div>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,13 @@
a {
padding: 10px;
color: $argo-color-gray-6;

&.active {
background-color: $argo-color-gray-3;
border: 1px solid $argo-color-gray-4;
border-radius: 5px;
cursor: default;
}
}
}

Expand Down
100 changes: 81 additions & 19 deletions ui/src/app/workflows/components/workflow-dag/workflow-dag.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as classNames from 'classnames';
import * as dagre from 'dagre';
import * as React from 'react';

import {NODE_PHASE, NodePhase, NodeStatus} from '../../../../models';
Expand All @@ -14,6 +15,7 @@ export interface WorkflowDagRenderOptions {
scale: number;
nodesToDisplay: string[];
expandNodes: Set<string>;
fastRenderer: boolean;
}

export interface WorkflowDagProps {
Expand Down Expand Up @@ -281,7 +283,8 @@ export class WorkflowDag extends React.Component<WorkflowDagProps, WorkflowDagRe
'type:Retry',
'type:Skipped',
'type:Suspend'
]
],
fastRenderer: false
} as WorkflowDagRenderOptions;
}

Expand Down Expand Up @@ -390,7 +393,56 @@ export class WorkflowDag extends React.Component<WorkflowDagProps, WorkflowDagRe
return {nodes, edges};
}

private layoutGraph(nodes: string[], edges: Edge[]) {
private layoutGraphPretty(nodes: string[], edges: Edge[]) {
const graph = new dagre.graphlib.Graph();

graph.setGraph({
edgesep: 20 / this.scale,
nodesep: 50 / this.scale,
rankdir: this.state.horizontal ? 'LR' : 'TB',
ranksep: 50 / this.scale
});

graph.setDefaultEdgeLabel(() => ({}));

nodes.forEach(node => {
graph.setNode(node, {label: node, width: this.nodeSize, height: this.nodeSize});
});
edges.forEach(edge => {
if (edge.v && edge.w) {
graph.setEdge(edge.v, edge.w);
}
});
dagre.layout(graph);

const size = this.getGraphSize(graph.nodes().map((id: string) => graph.node(id)));
this.graph = {
width: size.width,
height: size.height,
nodes: new Map<string, {x: number; y: number}>(),
edges: []
};

graph.nodes().map((id: string) => {
const node = graph.node(id);
this.graph.nodes.set(node.label, {x: node.x, y: node.y});
});
graph.edges().map((edge: Edge) => {
this.graph.edges.push(this.generateEdge(edge));
});
}

private getGraphSize(nodes: dagre.Node[]): {width: number; height: number} {
let width = 0;
let height = 0;
nodes.forEach(node => {
width = Math.max(node.x + node.width / 2, width);
height = Math.max(node.y + node.height / 2, height);
});
return {width, height};
}

private layoutGraphFast(nodes: string[], edges: Edge[]) {
const hash = {scale: this.scale, nodeCount: nodes.length, nodesToDisplay: this.state.nodesToDisplay};
// this hash check prevents having to do the expensive layout operation, if the graph does not re-laying out (e.g. phase change only)
if (this.hash === hash) {
Expand Down Expand Up @@ -433,26 +485,28 @@ export class WorkflowDag extends React.Component<WorkflowDagProps, WorkflowDagRe
});
});
});
this.graph.edges = edges.filter(e => this.graph.nodes.has(e.v) && this.graph.nodes.has(e.w)).map(e => this.generateEdge(e));
}

private generateEdge(edge: Edge) {
// `h` and `v` move the arrow heads to next to the node, otherwise they would be behind it
const h = this.state.horizontal ? this.nodeSize / 2 : 0;
const v = !this.state.horizontal ? this.nodeSize / 2 : 0;
this.graph.edges = edges
.filter(e => this.graph.nodes.has(e.v) && this.graph.nodes.has(e.w))
.map(e => ({
v: e.v,
w: e.w,
points: [
{
// for hidden nodes, we want to size them zero
x: this.graph.nodes.get(e.v).x + (this.hiddenNode(e.v) ? 0 : h),
y: this.graph.nodes.get(e.v).y + (this.hiddenNode(e.v) ? 0 : v)
},
{
x: this.graph.nodes.get(e.w).x - (this.hiddenNode(e.w) ? 0 : h),
y: this.graph.nodes.get(e.w).y - (this.hiddenNode(e.w) ? 0 : v)
}
]
}));
return {
v: edge.v,
w: edge.w,
points: [
{
// for hidden nodes, we want to size them zero
x: this.graph.nodes.get(edge.v).x + (this.hiddenNode(edge.v) ? 0 : h),
y: this.graph.nodes.get(edge.v).y + (this.hiddenNode(edge.v) ? 0 : v)
},
{
x: this.graph.nodes.get(edge.w).x - (this.hiddenNode(edge.w) ? 0 : h),
y: this.graph.nodes.get(edge.w).y - (this.hiddenNode(edge.w) ? 0 : v)
}
]
};
}

private selectNode(nodeId: string) {
Expand Down Expand Up @@ -512,4 +566,12 @@ export class WorkflowDag extends React.Component<WorkflowDagProps, WorkflowDagRe
(node.type === 'Retry' && node.children.length === 1)
);
}

private layoutGraph(nodes: string[], edges: Edge[]) {
if (this.state.fastRenderer) {
this.layoutGraphFast(nodes, edges);
} else {
this.layoutGraphPretty(nodes, edges);
}
}
}
130 changes: 66 additions & 64 deletions ui/src/app/workflows/components/workflow-details/workflow-details.scss
Original file line number Diff line number Diff line change
Expand Up @@ -33,80 +33,82 @@
border: 1px solid $argo-color-gray-4;
border-radius: 5px;
cursor: default;
}
}
}
}
}

&__graph-container {
position: relative;
overflow: auto;
height: calc(100vh - 2 * #{$top-bar-height});
width: 100%;
transition: width 0.2s;
float: left;

.workflow-dag {
margin: 0;
}
.workflow-timeline {
min-height: calc(100vh - 2 * #{$top-bar-height});
}
}
&__graph-container {
position: relative;
overflow: auto;
height: calc(100vh - 2 * #{$top-bar-height});
width: 100%;
transition: width 0.2s;
float: left;

&__step-info {
min-height: calc(100vh - 2 * #{$top-bar-height});
border-left: 1px solid $argo-color-gray-4;
width: 0;
transition: width 0.2s;
float: right
.workflow-dag {
margin: 0;
}

&__step-info-close {
display: block;
position: absolute;
cursor: pointer;
top: 1em;
right: -1em;
z-index: 8;
border-radius: 50%;
color: $argo-color-gray-5;
font-size: 20px;
padding: 5px;

@media screen and (max-width: $argo-breakpoint-md) {
top: 8px;
}

&:hover {
background-color: $argo-color-gray-4;
}
.workflow-timeline {
min-height: calc(100vh - 2 * #{$top-bar-height});
}
}

&--step-node-expanded &__graph-container {
width: calc(100% - 570px);
}
&__step-info {
min-height: calc(100vh - 2 * #{$top-bar-height});
border-left: 1px solid $argo-color-gray-4;
width: 0;
transition: width 0.2s;
float: right
}

&--step-node-expanded &__step-info {
width: 570px;
&__step-info-close {
display: block;
position: absolute;
cursor: pointer;
top: 1em;
right: -1em;
z-index: 8;
border-radius: 50%;
color: $argo-color-gray-5;
font-size: 20px;
padding: 5px;

@media screen and (max-width: $argo-breakpoint-md) {
top: 8px;
}

&--step-node-expanded &__step-info-close {
right: 1em;
&:hover {
background-color: $argo-color-gray-4;
}
}

&--step-node-expanded &__graph-container {
width: calc(100% - 570px);
}

&--step-node-expanded &__step-info {
width: 570px;
}

&--step-node-expanded &__step-info-close {
right: 1em;
}

}
.badge{
position: absolute;
margin-left: -.3%;
margin-top: -.3%;
font-size: 30%;
padding: .6em;
border-radius: 999px;
line-height: .75em;
color: white;
background: rgba(255, 0, 0, 0.85);
text-align: center;
min-width: 2em;
font-weight: bold;
min-height: 2em;
}

.badge {
position: absolute;
margin-left: -.3%;
margin-top: -.3%;
font-size: 30%;
padding: .6em;
border-radius: 999px;
line-height: .75em;
color: white;
background: rgba(255, 0, 0, 0.85);
text-align: center;
min-width: 2em;
font-weight: bold;
min-height: 2em;
}
20 changes: 20 additions & 0 deletions ui/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,11 @@
version "2.1.0"
resolved "https://registry.yarnpkg.com/@types/cookiejar/-/cookiejar-2.1.0.tgz#4b7daf2c51696cfc70b942c11690528229d1a1ce"

"@types/dagre@^0.7.44":
version "0.7.44"
resolved "https://registry.yarnpkg.com/@types/dagre/-/dagre-0.7.44.tgz#8f4b796b118ca29c132da7068fbc0d0351ee5851"
integrity sha512-N6HD+79w77ZVAaVO7JJDW5yJ9LAxM62FpgNGO9xEde+KVYjDRyhIMzfiErXpr1g0JPon9kwlBzoBK6s4fOww9Q==

"@types/deep-equal@^1.0.1":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@types/deep-equal/-/deep-equal-1.0.1.tgz#71cfabb247c22bcc16d536111f50c0ed12476b03"
Expand Down Expand Up @@ -1653,6 +1658,14 @@ dagre@^0.8.2:
graphlib "^2.1.5"
lodash "^4.17.4"

dagre@^0.8.5:
version "0.8.5"
resolved "https://registry.yarnpkg.com/dagre/-/dagre-0.8.5.tgz#ba30b0055dac12b6c1fcc247817442777d06afee"
integrity sha512-/aTqmnRta7x7MCCpExk7HQL2O4owCT2h8NT//9I1OQ9vt29Pa0BzSAkR5lwFUcQ7491yVi/3CXU9jQ5o0Mn2Sw==
dependencies:
graphlib "^2.1.8"
lodash "^4.17.15"

dashdash@^1.12.0:
version "1.14.1"
resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0"
Expand Down Expand Up @@ -2759,6 +2772,13 @@ graphlib@^2.1.5:
dependencies:
lodash "^4.11.1"

graphlib@^2.1.8:
version "2.1.8"
resolved "https://registry.yarnpkg.com/graphlib/-/graphlib-2.1.8.tgz#5761d414737870084c92ec7b5dbcb0592c9d35da"
integrity sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A==
dependencies:
lodash "^4.17.15"

gud@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/gud/-/gud-1.0.0.tgz#a489581b17e6a70beca9abe3ae57de7a499852c0"
Expand Down

0 comments on commit b5f3191

Please sign in to comment.