Skip to content

Commit

Permalink
feat: UI Resubmit [archived] workflows with parameter (#4662)
Browse files Browse the repository at this point in the history
  • Loading branch information
toyamagu-2021 committed May 14, 2023
1 parent 5a45dff commit 9b26bd8
Show file tree
Hide file tree
Showing 10 changed files with 229 additions and 109 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {WorkflowArtifacts} from '../../../workflows/components/workflow-artifact
import {ANNOTATION_KEY_POD_NAME_VERSION} from '../../../shared/annotations';
import {getPodName, getTemplateNameFromNode} from '../../../shared/pod-name';
import {getResolvedTemplates} from '../../../shared/template-resolution';
import {ResubmitWorkflowPanel} from '../../../workflows/components/resubmit-workflow-panel';
import {ArtifactPanel} from '../../../workflows/components/workflow-details/artifact-panel';
import {WorkflowResourcePanel} from '../../../workflows/components/workflow-details/workflow-resource-panel';
import {WorkflowLogsViewer} from '../../../workflows/components/workflow-logs-viewer/workflow-logs-viewer';
Expand Down Expand Up @@ -170,6 +171,7 @@ export const ArchivedWorkflowDetails = ({history, location, match}: RouteCompone
<SlidingPanel isShown={!!sidePanel} onClose={() => setSidePanel(null)}>
{sidePanel === 'yaml' && <WorkflowYamlViewer workflow={workflow} selectedNode={node} />}
{sidePanel === 'logs' && <WorkflowLogsViewer workflow={workflow} initialPodName={podName()} nodeId={nodeId} container={container} archived={true} />}
{sidePanel === 'resubmit' && <ResubmitWorkflowPanel workflow={workflow} workflowParameters={workflow.spec.arguments.parameters || []} />}
</SlidingPanel>
</>
);
Expand All @@ -192,21 +194,6 @@ export const ArchivedWorkflowDetails = ({history, location, match}: RouteCompone
});
};

const resubmitArchivedWorkflow = () => {
if (!confirm('Are you sure you want to resubmit this archived workflow?')) {
return;
}
services.archivedWorkflows
.resubmit(workflow.metadata.uid, workflow.metadata.namespace)
.then(newWorkflow => (document.location.href = uiUrl(`workflows/${newWorkflow.metadata.namespace}/${newWorkflow.metadata.name}`)))
.catch(e => {
ctx.notifications.show({
content: 'Failed to resubmit archived workflow ' + e,
type: NotificationType.Error
});
});
};

const retryArchivedWorkflow = () => {
if (!confirm('Are you sure you want to retry this archived workflow?')) {
return;
Expand Down Expand Up @@ -255,7 +242,7 @@ export const ArchivedWorkflowDetails = ({history, location, match}: RouteCompone
title: 'Resubmit',
iconClassName: 'fa fa-plus-circle',
disabled: false,
action: () => resubmitArchivedWorkflow()
action: () => setSidePanel('resubmit')
},
{
title: 'Delete',
Expand Down
80 changes: 80 additions & 0 deletions ui/src/app/shared/components/parameters-input/parameters-input.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import {Select, Tooltip} from 'argo-ui';
import * as React from 'react';
import {Parameter} from '../../../../models';
import {Utils} from '../../utils';

interface ParametersInputProps {
parameters: Parameter[];
onChange?: (parameters: Parameter[]) => void;
}

export class ParametersInput extends React.Component<ParametersInputProps, {parameters: Parameter[]}> {
constructor(props: ParametersInputProps) {
super(props);
this.state = {parameters: props.parameters || []};
}

public render() {
return (
<>
{this.props.parameters.map((parameter, index) => (
<div key={parameter.name + '_' + index} style={{marginBottom: 14}}>
<label>{parameter.name}</label>
{parameter.description && (
<Tooltip content={parameter.description}>
<i className='fa fa-question-circle' style={{marginLeft: 4}} />
</Tooltip>
)}
{(parameter.enum && this.displaySelectFieldForEnumValues(parameter)) || this.displayInputFieldForSingleValue(parameter)}
</div>
))}
</>
);
}

private displaySelectFieldForEnumValues(parameter: Parameter) {
return (
<Select
key={parameter.name}
value={Utils.getValueFromParameter(parameter)}
options={parameter.enum.map(value => ({
value,
title: value
}))}
onChange={e => {
const newParameters: Parameter[] = this.state.parameters.map(p => ({
name: p.name,
value: p.name === parameter.name ? e.value : Utils.getValueFromParameter(p),
enum: p.enum
}));
this.setState({parameters: newParameters});
this.onParametersChange(newParameters);
}}
/>
);
}

private displayInputFieldForSingleValue(parameter: Parameter) {
return (
<textarea
className='argo-field'
value={Utils.getValueFromParameter(parameter)}
onChange={e => {
const newParameters: Parameter[] = this.state.parameters.map(p => ({
name: p.name,
value: p.name === parameter.name ? e.target.value : Utils.getValueFromParameter(p),
enum: p.enum
}));
this.setState({parameters: newParameters});
this.onParametersChange(newParameters);
}}
/>
);
}

private onParametersChange(parameters: Parameter[]) {
if (this.props.onChange) {
this.props.onChange(parameters);
}
}
}
5 changes: 3 additions & 2 deletions ui/src/app/shared/services/archived-workflows-service.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as models from '../../../models';
import {ResubmitOpts} from '../../../models/resubmit-opts';
import {Pagination} from '../pagination';
import {Utils} from '../utils';
import requests from './requests';
Expand Down Expand Up @@ -47,10 +48,10 @@ export const ArchivedWorkflowsService = {
return (await requests.get(url)).body as models.Labels;
},

resubmit(uid: string, namespace: string) {
resubmit(uid: string, namespace: string, opts?: ResubmitOpts) {
return requests
.put(`api/v1/archived-workflows/${uid}/resubmit`)
.send({namespace})
.send({namespace, ...opts})
.then(res => res.body as models.Workflow);
},

Expand Down
8 changes: 6 additions & 2 deletions ui/src/app/shared/services/workflows-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {EMPTY, from, Observable, of} from 'rxjs';
import {catchError, filter, map, mergeMap, switchMap} from 'rxjs/operators';
import * as models from '../../../models';
import {Event, LogEntry, NodeStatus, Workflow, WorkflowList, WorkflowPhase} from '../../../models';
import {ResubmitOpts} from '../../../models/resubmit-opts';
import {SubmitOpts} from '../../../models/submit-opts';
import {uiUrl} from '../base';
import {Pagination} from '../pagination';
Expand Down Expand Up @@ -117,8 +118,11 @@ export const WorkflowsService = {
return requests.put(`api/v1/workflows/${namespace}/${name}/retry`).then(res => res.body as Workflow);
},

resubmit(name: string, namespace: string) {
return requests.put(`api/v1/workflows/${namespace}/${name}/resubmit`).then(res => res.body as Workflow);
resubmit(name: string, namespace: string, opts?: ResubmitOpts) {
return requests
.put(`api/v1/workflows/${namespace}/${name}/resubmit`)
.send(opts)
.then(res => res.body as Workflow);
},

suspend(name: string, namespace: string) {
Expand Down
10 changes: 9 additions & 1 deletion ui/src/app/shared/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as models from '../../models';
import {NODE_PHASE} from '../../models';
import {NODE_PHASE, Parameter} from '../../models';
import {Pagination} from './pagination';

const managedNamespaceKey = 'managedNamespace';
Expand Down Expand Up @@ -190,5 +190,13 @@ export const Utils = {
labelSelector += labels.join(',');
}
return labelSelector;
},

getValueFromParameter(p: Parameter) {
if (p.value === undefined) {
return p.default;
} else {
return p.value;
}
}
};
2 changes: 1 addition & 1 deletion ui/src/app/shared/workflow-operations-map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ export const WorkflowOperationsMap: WorkflowOperations = {
title: 'RESUBMIT',
iconClassName: 'fa fa-plus-circle',
disabled: () => false,
action: (wf: Workflow) => services.workflows.resubmit(wf.metadata.name, wf.metadata.namespace)
action: (wf: Workflow) => services.workflows.resubmit(wf.metadata.name, wf.metadata.namespace, null)
},
SUSPEND: {
title: 'SUSPEND',
Expand Down
94 changes: 94 additions & 0 deletions ui/src/app/workflows/components/resubmit-workflow-panel.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import {Checkbox} from 'argo-ui';
import * as React from 'react';
import {Parameter, Workflow} from '../../../models';
import {uiUrl} from '../../shared/base';
import {ErrorNotice} from '../../shared/components/error-notice';
import {ParametersInput} from '../../shared/components/parameters-input/parameters-input';
import {services} from '../../shared/services';
import {Utils} from '../../shared/utils';

interface Props {
workflow: Workflow;
workflowParameters: Parameter[];
isArchived?: boolean;
}

interface State {
workflowParameters: Parameter[];
memoized: boolean;
error?: Error;
isSubmitting: boolean;
}

export class ResubmitWorkflowPanel extends React.Component<Props, State> {
constructor(props: any) {
super(props);
const state: State = {
workflowParameters: JSON.parse(JSON.stringify(this.props.workflowParameters)),
memoized: false,
isSubmitting: false
};
this.state = state;
}

public render() {
return (
<>
<h4>Submit Workflow</h4>
<h5>
{this.props.workflow.metadata.namespace}/{this.props.workflow.metadata.name}
</h5>
{this.state.error && <ErrorNotice error={this.state.error} />}
<div className='white-box'>
<div key='parameters' style={{marginBottom: 25}}>
<label>Parameters</label>
{this.state.workflowParameters.length > 0 && (
<ParametersInput parameters={this.state.workflowParameters} onChange={workflowParameters => this.setState({workflowParameters})} />
)}
{this.state.workflowParameters.length === 0 ? (
<>
<br />
<label>No parameters</label>
</>
) : (
<></>
)}
</div>

<div key='memorized' style={{marginBottom: 25}}>
<label>Memorized</label>
<div className='columns small-9'>
<Checkbox checked={this.state.memoized} onChange={memoized => this.setState({memoized})} />
</div>
</div>

<div key='resubmit'>
<button onClick={() => this.submit()} className='argo-button argo-button--base' disabled={this.state.isSubmitting}>
<i className='fa fa-plus' /> {this.state.isSubmitting ? 'Loading...' : 'Resubmit'}
</button>
</div>
</div>
</>
);
}

private submit() {
this.setState({isSubmitting: true});
if (!this.props.isArchived) {
services.workflows
.resubmit(this.props.workflow.metadata.name, this.props.workflow.metadata.namespace, {
parameters: [
...this.state.workflowParameters.filter(p => Utils.getValueFromParameter(p) !== undefined).map(p => p.name + '=' + Utils.getValueFromParameter(p))
],
memoized: this.state.memoized
})
.then((submitted: Workflow) => (document.location.href = uiUrl(`workflows/${submitted.metadata.namespace}/${submitted.metadata.name}`)))
.catch(error => this.setState({error, isSubmitting: false}));
} else {
services.archivedWorkflows
.resubmit(this.props.workflow.metadata.uid, this.props.workflow.metadata.namespace)
.then(newWorkflow => (document.location.href = uiUrl(`workflows/${newWorkflow.metadata.namespace}/${newWorkflow.metadata.name}`)))
.catch(error => this.setState({error, isSubmitting: false}));
}
}
}
Loading

0 comments on commit 9b26bd8

Please sign in to comment.