Skip to content

Commit 3dc35d1

Browse files
dtaniwakiAlexander Matyushentsev
authored and
Alexander Matyushentsev
committed
Support workflow template (argoproj#52)
* Update node-sass to ^4.12.0 * Support workflow template * Make YAML view scrollable * Fix wording * Remove unnecessary return value
1 parent 6a95d3e commit 3dc35d1

File tree

9 files changed

+391
-73
lines changed

9 files changed

+391
-73
lines changed

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@
9292
"html-webpack-plugin": "^3.2.0",
9393
"jscs": "^3.0.7",
9494
"mocha": "^5.0.0",
95-
"node-sass": "^4.7.2",
95+
"node-sass": "^4.12.0",
9696
"nodemon": "^1.14.11",
9797
"raw-loader": "^0.5.1",
9898
"react-hot-loader": "^3.1.3",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
.slide-contents {
2+
.slide-contents--title {
3+
cursor: pointer;
4+
}
5+
.slide-contents--contents {
6+
overflow-y: hidden;
7+
max-height: 100%;
8+
9+
&.slide-contents--contents-hidden {
10+
max-height: 0;
11+
}
12+
}
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import * as React from 'react';
2+
3+
require('./slide-contents.scss');
4+
5+
export interface SlideContentsProps {
6+
title: string;
7+
contents: JSX.Element;
8+
className: string;
9+
}
10+
11+
export interface SlideContentsState {
12+
hidden: boolean;
13+
}
14+
15+
export class SlideContents extends React.Component<SlideContentsProps, SlideContentsState> {
16+
constructor(props: SlideContentsProps) {
17+
super(props);
18+
19+
this.state = {
20+
hidden: true,
21+
};
22+
23+
this.showContents = this.showContents.bind(this);
24+
this.hideContents = this.hideContents.bind(this);
25+
}
26+
27+
public showContents() {
28+
this.setState({ hidden: false });
29+
}
30+
31+
public hideContents() {
32+
this.setState({ hidden: true });
33+
}
34+
35+
public render() {
36+
const { title, contents, className } = this.props;
37+
const { hidden } = this.state;
38+
let toggleSwitch: JSX.Element | undefined;
39+
let clickAction: () => void;
40+
if (hidden) {
41+
toggleSwitch = <i className='fa fa-angle-down' aria-hidden='true' />;
42+
clickAction = this.showContents;
43+
} else {
44+
toggleSwitch = <i className='fa fa-angle-up' aria-hidden='true' onClick={this.hideContents} />;
45+
clickAction = this.hideContents;
46+
}
47+
return (
48+
<div className={`${className} slide-contents`}>
49+
<h4 className='slide-contents--title' onClick={clickAction}>
50+
<span>{title}</span>
51+
{toggleSwitch}
52+
</h4>
53+
<div className={'slide-contents--contents' + (hidden ? ' slide-contents--contents-hidden' : '')}>
54+
<div className={className}>
55+
{contents}
56+
</div>
57+
</div>
58+
</div>
59+
);
60+
}
61+
}

src/app/shared/components/utils.ts

+49
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Observable } from 'rxjs';
2+
import * as models from '../../../models';
23
import { NODE_PHASE } from '../../../models';
34

45
export const Utils = {
@@ -75,4 +76,52 @@ export const Utils = {
7576
}
7677
return Observable.from([val as T]);
7778
},
79+
80+
getResolvedTemplates(workflow: models.Workflow, node: models.NodeStatus): models.Template {
81+
let tmpTemplate = {
82+
template: node.templateName,
83+
templateRef: node.templateRef,
84+
};
85+
let scope = node.templateScope;
86+
const referencedTemplates: models.Template[] = [];
87+
let resolvedTemplate: models.Template;
88+
const maxDepth = 10;
89+
for (let i = 1; i < maxDepth + 1; i ++) {
90+
let storedTemplateName = '';
91+
if (tmpTemplate.templateRef) {
92+
storedTemplateName = `${tmpTemplate.templateRef.name}/${tmpTemplate.templateRef.template}`;
93+
scope = tmpTemplate.templateRef.name;
94+
} else {
95+
storedTemplateName = `${scope}/${tmpTemplate.template}`;
96+
}
97+
let tmpl = null;
98+
if (scope && storedTemplateName) {
99+
tmpl = workflow.status.storedTemplates[storedTemplateName];
100+
} else if (tmpTemplate.template) {
101+
tmpl = workflow.spec.templates.find((item) => item.name === tmpTemplate.template);
102+
}
103+
if (!tmpl) {
104+
// tslint:disable-next-line: no-console
105+
console.error(`StoredTemplate ${storedTemplateName} not found`);
106+
return undefined;
107+
}
108+
referencedTemplates.push(tmpl);
109+
if (!tmpl.template && !tmpl.templateRef) {
110+
break;
111+
}
112+
tmpTemplate = tmpl;
113+
if (i === maxDepth) {
114+
// tslint:disable-next-line: no-console
115+
console.error(`Template reference too deep`);
116+
return undefined;
117+
}
118+
}
119+
referencedTemplates.reverse().forEach((tmpl) => {
120+
tmpl = Object.assign({}, tmpl);
121+
delete tmpl.template;
122+
delete tmpl.templateRef;
123+
resolvedTemplate = Object.assign({}, resolvedTemplate, tmpl);
124+
});
125+
return resolvedTemplate;
126+
},
78127
};

src/app/workflows/components/workflow-node-info/workflow-node-info.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ export const WorkflowNodeSummary = (props: Props) => {
4848
</Ticker>
4949
) },
5050
];
51-
const template = props.workflow.spec.templates.find((item) => item.name === props.node.templateName);
51+
const template = Utils.getResolvedTemplates(props.workflow, props.node);
5252
return (
5353
<div className='white-box'>
5454
<div className='white-box__details'>
@@ -132,7 +132,7 @@ export class WorkflowNodeContainers extends React.Component<Props, { selectedSid
132132
}
133133

134134
public render() {
135-
const template = this.props.workflow.spec.templates.find((item) => item.name === this.props.node.templateName);
135+
const template = Utils.getResolvedTemplates(this.props.workflow, this.props.node);
136136
if (!template || (!template.container && !template.script)) {
137137
return (
138138
<div className='white-box'>

src/app/workflows/components/workflow-yaml-viewer/workflow-yaml-viewer.scss

+9-11
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,22 @@
33
.workflow-yaml-viewer {
44
font: normal 13px/1.2 'Courier', sans-serif;
55
color: $argo-color-gray-7;
6+
overflow-y: scroll;
67

7-
&__highlight {
8-
position: relative;
9-
color: white;
10-
background: $argo-color-teal-7;
11-
display: inline-block;
12-
}
13-
14-
& > ol {
8+
.workflow-yaml-section {
159
margin: 0;
16-
counter-reset: item;
17-
list-style-type: none;
18-
list-style-position: inside;
1910
margin-bottom: 50px;
2011

2112
&:last-child {
2213
margin-bottom: 0;
2314
}
15+
}
16+
17+
ol {
18+
margin: 0;
19+
counter-reset: item;
20+
list-style-type: none;
21+
list-style-position: inside;
2422

2523
& > li {
2624
height: 18px;

src/app/workflows/components/workflow-yaml-viewer/workflow-yaml-viewer.tsx

+57-11
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import * as yaml from 'yamljs';
33

44
import * as models from '../../../../models';
55
import { Utils } from '../../../shared/components';
6+
import { SlideContents } from '../../../shared/components/slide-contents/slide-contents';
67

78
require('./workflow-yaml-viewer.scss');
89

@@ -23,19 +24,64 @@ export class WorkflowYamlViewer extends React.Component<WorkflowYamlViewerProps>
2324
}
2425

2526
public render() {
26-
let nodeName = '';
27+
const contents: JSX.Element[] = [];
2728
if (this.props.selectedNode) {
28-
nodeName = this.normalizeNodeName(this.props.selectedNode.displayName || this.props.selectedNode.name);
29-
}
30-
const html = this.props.workflow.spec.templates.map((item) => {
31-
let itemStr = yaml.stringify(item, 4, 1);
32-
if (nodeName) {
33-
itemStr = this.highlightStep(item, nodeName, itemStr);
29+
const parentNode = this.props.workflow.status.nodes[this.props.selectedNode.boundaryID];
30+
if (parentNode) {
31+
const parentTemplate = Utils.getResolvedTemplates(this.props.workflow, parentNode);
32+
33+
let nodeName = '';
34+
if (this.props.selectedNode) {
35+
nodeName = this.normalizeNodeName(this.props.selectedNode.displayName || this.props.selectedNode.name);
36+
}
37+
let parentTemplateStr = yaml.stringify(parentTemplate, 4, 1);
38+
if (nodeName) {
39+
parentTemplateStr = this.highlightStep(parentTemplate, nodeName, parentTemplateStr);
40+
}
41+
contents.push(
42+
<div className='workflow-yaml-section'>
43+
<h4>Parent Node</h4>
44+
<div dangerouslySetInnerHTML={{__html: this.addCounterToDisplayedFiles(parentTemplateStr)}} />
45+
</div>,
46+
);
3447
}
35-
return this.addCounterToDisplayedFiles(itemStr);
36-
}).join('\n\n');
48+
49+
const template = Utils.getResolvedTemplates(this.props.workflow, this.props.selectedNode);
50+
const templateStr = yaml.stringify(template, 4, 1);
51+
contents.push(
52+
<div className='workflow-yaml-section'>
53+
<h4>Current Node</h4>
54+
<div dangerouslySetInnerHTML={{__html: this.addCounterToDisplayedFiles(templateStr)}} />
55+
</div>,
56+
);
57+
}
58+
const templates = this.props.workflow.spec.templates;
59+
if (templates && Object.keys(templates).length) {
60+
const templatesStr = yaml.stringify(templates, 4, 1);
61+
contents.push((
62+
<SlideContents
63+
title={'Templates'}
64+
contents={<div dangerouslySetInnerHTML={{__html: this.addCounterToDisplayedFiles(templatesStr)}} />}
65+
className='workflow-yaml-section'
66+
/>
67+
));
68+
}
69+
const storedTemplates = this.props.workflow.status.storedTemplates;
70+
if (storedTemplates && Object.keys(storedTemplates).length) {
71+
const storedTemplatesStr = yaml.stringify(storedTemplates, 4, 1);
72+
contents.push((
73+
<SlideContents
74+
title={'Stored Templates'}
75+
contents={<div dangerouslySetInnerHTML={{__html: this.addCounterToDisplayedFiles(storedTemplatesStr)}} />}
76+
className='workflow-yaml-section'
77+
/>
78+
));
79+
}
80+
3781
return (
38-
<div className='workflow-yaml-viewer' dangerouslySetInnerHTML={{ __html: html }} ref={(container) => this.container = container} />
82+
<div className='workflow-yaml-viewer' ref={(container) => this.container = container}>
83+
{contents}
84+
</div>
3985
);
4086
}
4187

@@ -47,7 +93,7 @@ export class WorkflowYamlViewer extends React.Component<WorkflowYamlViewerProps>
4793
if (item !== '') {
4894
if (item.indexOf('<span>') !== -1) {
4995
item = item.match(/^<span>\s*/)[0] + item.substr(6);
50-
item = `<li class="highlight">${item}</li>`;
96+
item = `<li class='highlight'>${item}</li>`;
5197
} else {
5298
item = item.match(/^\s*/)[0] + item;
5399
// special treatment to beautify resource templates

src/models/workflows.ts

+47
Original file line numberDiff line numberDiff line change
@@ -523,6 +523,16 @@ export interface Template {
523523
* DAG template
524524
*/
525525
dag: DAGTemplate;
526+
527+
/**
528+
* Template is the name of the template which is used as the base of this template.
529+
*/
530+
template: string;
531+
532+
/**
533+
* TemplateRef is the reference to the template resource which is used as the base of this template.
534+
*/
535+
templateRef: TemplateRef;
526536
}
527537
/**
528538
* ValueFrom describes a location in which to obtain the value to a parameter
@@ -679,6 +689,33 @@ export interface NodeStatus {
679689
* Inputs captures input parameter values and artifact locations supplied to this template invocation
680690
*/
681691
inputs: Inputs;
692+
693+
/**
694+
* TemplateRef is the reference to the template resource which this node corresponds to.
695+
* Not applicable to virtual nodes (e.g. Retry, StepGroup)
696+
*/
697+
templateRef: TemplateRef;
698+
699+
/**
700+
* TemplateScope is the template scope in which the template of this node was retrieved.
701+
*/
702+
templateScope: string;
703+
}
704+
705+
export interface TemplateRef {
706+
/**
707+
* Name is the resource name of the template.
708+
*/
709+
name: string;
710+
/**
711+
* Template is the name of referred template in the resource.
712+
*/
713+
template: string;
714+
/**
715+
* RuntimeResolution skips validation at creation time.
716+
* By enabling this option, you can create the referred workflow template before the actual runtime.
717+
*/
718+
runtimeResolution: boolean;
682719
}
683720

684721
export interface WorkflowStatus {
@@ -706,6 +743,11 @@ export interface WorkflowStatus {
706743
persistentVolumeClaims: kubernetes.Volume[];
707744

708745
compressedNodes: string;
746+
747+
/*
748+
* StoredTemplates is a mapping between a template ref and the node's status.
749+
*/
750+
storedTemplates: {[name: string]: Template};
709751
}
710752

711753
/**
@@ -797,6 +839,11 @@ export interface DAGTask {
797839
*/
798840
template: string;
799841

842+
/**
843+
* TemplateRef is the reference to the template resource to execute.
844+
*/
845+
templateRef: TemplateRef;
846+
800847
/**
801848
* Arguments are the parameter and artifact arguments to the template
802849
*/

0 commit comments

Comments
 (0)