Skip to content

Commit db6206a

Browse files
panicboatagilgur5
andauthored
feat: Allow markdown title and description in CronWorkflows, WorkflowTemplates, & ClusterWorkflowTemplates. Fixes argoproj#12644 (argoproj#12697)
Signed-off-by: panicboat <[email protected]> Co-authored-by: Anton Gilgur <[email protected]>
1 parent 6b221f4 commit db6206a

23 files changed

+379
-122
lines changed
Loading
Loading
Loading

docs/title-and-description.md

Lines changed: 66 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ metadata:
1515
```
1616
1717
The above manifest will render as a row like the below image:
18-
![Title and Description Example](assets/workflow-title-and-description.png)
18+
![Title and Description Example](assets/title-and-description-workflow.png)
1919
2020
## Embedded Markdown
2121
@@ -34,7 +34,7 @@ metadata:
3434
```
3535
3636
The above manifest will render as a row like the below image:
37-
![Markdown Example](assets/workflow-title-and-description-markdown.png)
37+
![Markdown Example](assets/title-and-description-markdown-workflow.png)
3838
3939
Below are a few more examples:
4040
@@ -76,4 +76,67 @@ metadata:
7676
```
7777
7878
The above examples will render as rows like the below image:
79-
![More Markdown Examples](assets/workflow-title-and-description-markdown-complex.png)
79+
![More Markdown Examples](assets/title-and-description-markdown-complex-workflow.png)
80+
81+
### For `ClusterWorkflowTemplates`
82+
83+
> v3.6 and after
84+
85+
You can also add the `workflows.argoproj.io/title` and `workflows.argoproj.io/description` annotations with embedded markdown to a `ClusterWorkflowTemplate` to display in the list:
86+
87+
```yaml
88+
apiVersion: argoproj.io/v1alpha1
89+
kind: ClusterWorkflowTemplate
90+
metadata:
91+
name: my-cluster-workflow-template
92+
annotations:
93+
workflows.argoproj.io/title: '**Test Title**'
94+
workflows.argoproj.io/description: |
95+
`This is a simple hello world example.`
96+
You can also run it in Python: https://couler-proj.github.io/couler/examples/#hello-world
97+
```
98+
99+
The above manifest will render as a row like the below image:
100+
![ClusterWorkflowTemplate Example](assets/title-and-description-markdown-cluster-workflow-template.png)
101+
102+
### For `CronWorkflows`
103+
104+
> v3.6 and after
105+
106+
You can also add the `workflows.argoproj.io/title` and `workflows.argoproj.io/description` annotations with embedded markdown to a `CronWorkflow` to display in the list:
107+
108+
```yaml
109+
apiVersion: argoproj.io/v1alpha1
110+
kind: CronWorkflow
111+
metadata:
112+
name: my-cron-workflow
113+
annotations:
114+
workflows.argoproj.io/title: '**Test Title**'
115+
workflows.argoproj.io/description: |
116+
`This is a simple hello world example.`
117+
You can also run it in Python: https://couler-proj.github.io/couler/examples/#hello-world
118+
```
119+
120+
The above manifest will render as a row like the below image:
121+
![CronWorkflow Example](assets/title-and-description-markdown-cron-workflow.png)
122+
123+
### For `WorkflowTemplates`
124+
125+
> v3.6 and after
126+
127+
You can also add the `workflows.argoproj.io/title` and `workflows.argoproj.io/description` annotations with embedded markdown to a `WorkflowTemplate` to display in the list:
128+
129+
```yaml
130+
apiVersion: argoproj.io/v1alpha1
131+
kind: WorkflowTemplate
132+
metadata:
133+
name: my-workflow-template
134+
annotations:
135+
workflows.argoproj.io/title: '**Test Title**'
136+
workflows.argoproj.io/description: |
137+
`This is a simple hello world example.`
138+
You can also run it in Python: https://couler-proj.github.io/couler/examples/#hello-world
139+
```
140+
141+
The above manifest will render as a row like the below image:
142+
![WorkflowTemplate Example](assets/title-and-description-markdown-workflow-template.png)

ui/src/cluster-workflow-templates/cluster-workflow-template-list.scss

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,22 @@
55
&__row:hover {
66
box-shadow: 1px 2px 3px rgba($argo-color-gray-9, .1), 0 0 0 1px rgba($argo-color-teal-5, .5);
77
}
8-
}
8+
}
9+
10+
.cluster-workflow-templates-list {
11+
padding: 1em;
12+
13+
&__row-container {
14+
a {
15+
color: $argo-color-gray-6;
16+
}
17+
18+
a:hover {
19+
color: $argo-color-teal-5;
20+
}
21+
}
22+
}
23+
24+
.row.pt-60 {
25+
padding-top: 60px;
26+
}

ui/src/cluster-workflow-templates/cluster-workflow-template-list.tsx

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {useCollectEvent} from '../shared/use-collect-event';
1919
import {useQueryParams} from '../shared/use-query-params';
2020
import useTimestamp, {TIMESTAMP_KEYS} from '../shared/use-timestamp';
2121
import {ClusterWorkflowTemplateCreator} from './cluster-workflow-template-creator';
22+
import {ClusterWorkflowTemplateMarkdown} from './cluster-workflow-template-markdown';
2223

2324
import './cluster-workflow-template-list.scss';
2425

@@ -83,15 +84,19 @@ export function ClusterWorkflowTemplateList({history, location}: RouteComponentP
8384
</div>
8485
</div>
8586
{templates.map(t => (
86-
<Link className='row argo-table-list__row' key={t.metadata.uid} to={uiUrl(`cluster-workflow-templates/${t.metadata.name}`)}>
87-
<div className='columns small-1'>
88-
<i className='fa fa-clone' />
87+
<div className='cluster-workflow-templates-list__row-container' key={`${t.metadata.namespace}/${t.metadata.name}`}>
88+
<div className='row argo-table-list__row'>
89+
<div className='columns small-1'>
90+
<i className='fa fa-clone' />
91+
</div>
92+
<Link to={{pathname: uiUrl(`cluster-workflow-templates/${t.metadata.name}`)}} className='columns small-5'>
93+
<ClusterWorkflowTemplateMarkdown workflow={t} key={`{t.metadata.namespace}/${t.metadata.name}`} />
94+
</Link>
95+
<div className='columns small-3'>
96+
<Timestamp date={t.metadata.creationTimestamp} displayISOFormat={storedDisplayISOFormat} />
97+
</div>
8998
</div>
90-
<div className='columns small-5'>{t.metadata.name}</div>
91-
<div className='columns small-3'>
92-
<Timestamp date={t.metadata.creationTimestamp} displayISOFormat={storedDisplayISOFormat} />
93-
</div>
94-
</Link>
99+
</div>
95100
))}
96101
</div>
97102
<Footnote>
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
@import 'node_modules/argo-ui/src/styles/config';
2+
3+
.wf-rows-name {
4+
line-height: 1.5em;
5+
display: inline-block;
6+
vertical-align: middle;
7+
white-space: pre-line;
8+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import * as React from 'react';
2+
3+
import {ANNOTATION_DESCRIPTION, ANNOTATION_TITLE} from '../shared/annotations';
4+
import {SuspenseReactMarkdownGfm} from '../shared/components/suspense-react-markdown-gfm';
5+
import {ClusterWorkflowTemplate} from '../shared/models';
6+
7+
require('./cluster-workflow-template-markdown.scss');
8+
9+
interface ClusterWorkflowTemplateMarkdownProps {
10+
workflow: ClusterWorkflowTemplate;
11+
}
12+
13+
export function ClusterWorkflowTemplateMarkdown(props: ClusterWorkflowTemplateMarkdownProps) {
14+
const wf = props.workflow;
15+
// title + description vars
16+
const title = wf.metadata.annotations?.[ANNOTATION_TITLE] ?? wf.metadata.name;
17+
const description = (wf.metadata.annotations?.[ANNOTATION_DESCRIPTION] && `\n${wf.metadata.annotations[ANNOTATION_DESCRIPTION]}`) || '';
18+
const hasAnnotation = title !== wf.metadata.name || description !== '';
19+
const markdown = `${title}${description}`;
20+
21+
return <div className='wf-rows-name'>{hasAnnotation ? <SuspenseReactMarkdownGfm markdown={markdown} /> : markdown}</div>;
22+
}

ui/src/cron-workflows/cron-workflow-list.scss

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,22 @@
44
&__row:hover {
55
box-shadow: 1px 2px 3px rgba($argo-color-gray-9, .1), 0 0 0 1px rgba($argo-color-teal-5, .5);
66
}
7-
}
7+
}
8+
9+
.cron-workflows-list {
10+
padding: 1em;
11+
12+
&__row-container {
13+
a {
14+
color: $argo-color-gray-6;
15+
}
16+
17+
a:hover {
18+
color: $argo-color-teal-5;
19+
}
20+
}
21+
}
22+
23+
.row.pt-60 {
24+
padding-top: 60px;
25+
}

ui/src/cron-workflows/cron-workflow-list.tsx

Lines changed: 14 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,28 @@
11
import {Page} from 'argo-ui/src/components/page/page';
22
import {SlidingPanel} from 'argo-ui/src/components/sliding-panel/sliding-panel';
3-
import {Ticker} from 'argo-ui/src/components/ticker';
43
import * as React from 'react';
54
import {useContext, useEffect, useState} from 'react';
6-
import {Link, RouteComponentProps} from 'react-router-dom';
5+
import {RouteComponentProps} from 'react-router-dom';
76

8-
import {ANNOTATION_DESCRIPTION, ANNOTATION_TITLE} from '../shared/annotations';
97
import {uiUrl} from '../shared/base';
108
import {ErrorNotice} from '../shared/components/error-notice';
119
import {ExampleManifests} from '../shared/components/example-manifests';
1210
import {InfoIcon} from '../shared/components/fa-icons';
1311
import {Loading} from '../shared/components/loading';
14-
import {Timestamp, TimestampSwitch} from '../shared/components/timestamp';
12+
import {TimestampSwitch} from '../shared/components/timestamp';
1513
import {ZeroState} from '../shared/components/zero-state';
1614
import {Context} from '../shared/context';
17-
import {getNextScheduledTime} from '../shared/cron';
1815
import {Footnote} from '../shared/footnote';
1916
import {historyUrl} from '../shared/history';
20-
import {CronWorkflow, CronWorkflowSpec} from '../shared/models';
17+
import {CronWorkflow} from '../shared/models';
2118
import * as nsUtils from '../shared/namespaces';
2219
import {services} from '../shared/services';
2320
import {useCollectEvent} from '../shared/use-collect-event';
2421
import {useQueryParams} from '../shared/use-query-params';
2522
import useTimestamp, {TIMESTAMP_KEYS} from '../shared/use-timestamp';
2623
import {CronWorkflowCreator} from './cron-workflow-creator';
2724
import {CronWorkflowFilters} from './cron-workflow-filters';
28-
import {PrettySchedule} from './pretty-schedule';
25+
import {CronWorkflowRow} from './cron-workflow-row';
2926

3027
import './cron-workflow-list.scss';
3128

@@ -156,56 +153,16 @@ export function CronWorkflowList({match, location, history}: RouteComponentProps
156153
/>
157154
</div>
158155
</div>
159-
{cronWorkflows.map(w => (
160-
<Link
161-
className='row argo-table-list__row'
162-
key={`${w.metadata.namespace}/${w.metadata.name}`}
163-
to={uiUrl(`cron-workflows/${w.metadata.namespace}/${w.metadata.name}`)}>
164-
<div className='columns small-1'>{w.spec.suspend ? <i className='fa fa-pause' /> : <i className='fa fa-clock' />}</div>
165-
<div className='columns small-2'>
166-
{w.metadata.annotations?.[ANNOTATION_TITLE] ?? w.metadata.name}
167-
{w.metadata.annotations?.[ANNOTATION_DESCRIPTION] ? <p>{w.metadata.annotations[ANNOTATION_DESCRIPTION]}</p> : null}
168-
</div>
169-
<div className='columns small-1'>{w.metadata.namespace}</div>
170-
<div className='columns small-1'>{w.spec.timezone}</div>
171-
<div className='columns small-1'>
172-
{w.spec.schedule
173-
? w.spec.schedule
174-
: w.spec.schedules.map(schedule => (
175-
<>
176-
{schedule}
177-
<br />
178-
</>
179-
))}
180-
</div>
181-
<div className='columns small-2'>
182-
{w.spec.schedule ? (
183-
<PrettySchedule schedule={w.spec.schedule} />
184-
) : (
185-
<>
186-
{w.spec.schedules.map(schedule => (
187-
<>
188-
<PrettySchedule schedule={schedule} />
189-
<br />
190-
</>
191-
))}
192-
</>
193-
)}
194-
</div>
195-
<div className='columns small-2'>
196-
<Timestamp date={w.metadata.creationTimestamp} displayISOFormat={storedDisplayISOFormatCreation} />
197-
</div>
198-
<div className='columns small-2'>
199-
{w.spec.suspend ? (
200-
''
201-
) : (
202-
<Ticker intervalMs={1000}>
203-
{() => <Timestamp date={getSpecNextScheduledTime(w.spec)} displayISOFormat={storedDisplayISOFormatNextScheduled} />}
204-
</Ticker>
205-
)}
206-
</div>
207-
</Link>
208-
))}
156+
{cronWorkflows.map(w => {
157+
return (
158+
<CronWorkflowRow
159+
workflow={w}
160+
displayISOFormatCreation={storedDisplayISOFormatCreation}
161+
displayISOFormatNextScheduled={storedDisplayISOFormatNextScheduled}
162+
key={`{w.metadata.namespace}/${w.metadata.name}`}
163+
/>
164+
);
165+
})}
209166
</div>
210167
<Footnote>
211168
<InfoIcon /> Cron workflows are workflows that run on a preset schedule. Next scheduled run assumes workflow-controller is in UTC.{' '}
@@ -221,18 +178,3 @@ export function CronWorkflowList({match, location, history}: RouteComponentProps
221178
</Page>
222179
);
223180
}
224-
225-
function getSpecNextScheduledTime(spec: CronWorkflowSpec): Date {
226-
if (spec.schedule) {
227-
return getNextScheduledTime(spec.schedule, spec.timezone);
228-
}
229-
230-
let out: Date;
231-
spec.schedules.forEach(schedule => {
232-
const next = getNextScheduledTime(schedule, spec.timezone);
233-
if (!out || next.getTime() < out.getTime()) {
234-
out = next;
235-
}
236-
});
237-
return out;
238-
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
@import 'node_modules/argo-ui/src/styles/config';
2+
3+
.wf-rows-name {
4+
line-height: 1.5em;
5+
display: inline-block;
6+
vertical-align: middle;
7+
white-space: pre-line;
8+
}

0 commit comments

Comments
 (0)