Skip to content

Commit

Permalink
fix(ui): prefill parameters for workflow submit form. Fixes #1214
Browse files Browse the repository at this point in the history
Signed-off-by: Sairam Arunachalam <[email protected]>
  • Loading branch information
sairam91 committed Nov 21, 2024
1 parent 497f338 commit 3715fb3
Show file tree
Hide file tree
Showing 8 changed files with 117 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ export function ClusterWorkflowTemplateDetails({history, location, match}: Route
entrypoint={template.spec.entrypoint}
templates={template.spec.templates || []}
workflowParameters={template.spec.arguments.parameters || []}
history={history}
/>
</SlidingPanel>
)}
Expand Down
38 changes: 38 additions & 0 deletions ui/src/shared/get_workflow_params.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import {createBrowserHistory} from 'history';

import {getWorkflowParametersFromQuery} from './get_workflow_params';

describe('get_workflow_params', () => {
it('should return an empty object when there are no query parameters', () => {
const history = createBrowserHistory();
const result = getWorkflowParametersFromQuery(history);
expect(result).toEqual({});
});

it('should return the parameters provided in the URL', () => {
const history = createBrowserHistory();
history.location.search = '?parameters[key1]=value1&parameters[key2]=value2';
const result = getWorkflowParametersFromQuery(history);
expect(result).toEqual({
key1: 'value1',
key2: 'value2'
});
});

it('should not return any key value pairs which are not in parameters query ', () => {
const history = createBrowserHistory();
history.location.search = '?retryparameters[key1]=value1&retryparameters[key2]=value2';
const result = getWorkflowParametersFromQuery(history);
expect(result).toEqual({});
});

it('should only return the parameters provided in the URL', () => {
const history = createBrowserHistory();
history.location.search = '?parameters[key1]=value1&parameters[key2]=value2&test=123';
const result = getWorkflowParametersFromQuery(history);
expect(result).toEqual({
key1: 'value1',
key2: 'value2'
});
});
});
30 changes: 30 additions & 0 deletions ui/src/shared/get_workflow_params.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import {History} from 'history';

function extractKey(inputString: string): string | null {
// Use regular expression to match the key within square brackets
const match = inputString.match(/^parameters\[(.*?)\]$/);

// If a match is found, return the captured key
if (match) {
return match[1];
}

// If no match is found, return null or an empty string
return null; // Or return '';
}
/**
* Returns the workflow parameters from the query parameters.
*/
export function getWorkflowParametersFromQuery(history: History): {[key: string]: string} {
const queryParams = new URLSearchParams(history.location.search);

const parameters: {[key: string]: string} = {};
for (const [key, value] of queryParams.entries()) {
const q = extractKey(key);
if (q) {
parameters[q] = value;
}
}

return parameters;
}
11 changes: 5 additions & 6 deletions ui/src/shared/history.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,22 @@ import * as nsUtils from './namespaces';
* Only "truthy" values are put into the query parameters. I.e. "falsey" values include null, undefined, false, "", 0.
*/
export function historyUrl(path: string, params: {[key: string]: any}) {
const queryParams: string[] = [];
let extraSearchParams: URLSearchParams;
const queryParams = new URLSearchParams();
Object.entries(params)
.filter(([, v]) => v !== null)
.forEach(([k, v]) => {
const searchValue = '{' + k + '}';
if (path.includes(searchValue)) {
path = path.replace(searchValue, v != null ? v : '');
} else if (k === 'extraSearchParams') {
extraSearchParams = v;
(v as URLSearchParams).forEach((value, key) => queryParams.set(key, value));
} else if (v) {
queryParams.push(k + '=' + v);
queryParams.set(k, v);
}
if (k === 'namespace') {
nsUtils.setCurrentNamespace(v);
}
});
const extraString = extraSearchParams ? '&' + extraSearchParams.toString() : '';
return uiUrl(path.replace(/{[^}]*}/g, '')) + '?' + queryParams.join('&') + extraString;

return uiUrl(path.replace(/{[^}]*}/g, '')) + '?' + queryParams.toString();
}
1 change: 1 addition & 0 deletions ui/src/workflow-templates/workflow-template-details.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ export function WorkflowTemplateDetails({history, location, match}: RouteCompone
entrypoint={template.spec.entrypoint}
templates={template.spec.templates || []}
workflowParameters={template.spec.arguments.parameters || []}
history={history}
/>
)}
{sidePanel === 'share' && <WidgetGallery namespace={namespace} label={'workflows.argoproj.io/workflow-template=' + name} />}
Expand Down
17 changes: 15 additions & 2 deletions ui/src/workflows/components/submit-workflow-panel.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import {Select} from 'argo-ui/src/components/select/select';
import React, {useContext, useMemo, useState} from 'react';
import {History} from 'history';
import React, {useContext, useEffect, useMemo, useState} from 'react';

import {uiUrl} from '../../shared/base';
import {ErrorNotice} from '../../shared/components/error-notice';
import {getValueFromParameter, ParametersInput} from '../../shared/components/parameters-input';
import {TagsInput} from '../../shared/components/tags-input/tags-input';
import {Context} from '../../shared/context';
import {getWorkflowParametersFromQuery} from '../../shared/get_workflow_params';
import {Parameter, Template} from '../../shared/models';
import {services} from '../../shared/services';

Expand All @@ -16,6 +18,7 @@ interface Props {
entrypoint: string;
templates: Template[];
workflowParameters: Parameter[];
history: History;
}

const workflowEntrypoint = '<default>';
Expand All @@ -28,13 +31,23 @@ const defaultTemplate: Template = {

export function SubmitWorkflowPanel(props: Props) {
const {navigation} = useContext(Context);
const [entrypoint, setEntrypoint] = useState(workflowEntrypoint);
const [entrypoint, setEntrypoint] = useState(props.entrypoint || workflowEntrypoint);
const [parameters, setParameters] = useState<Parameter[]>([]);
const [workflowParameters, setWorkflowParameters] = useState<Parameter[]>(JSON.parse(JSON.stringify(props.workflowParameters)));
const [labels, setLabels] = useState(['submit-from-ui=true']);
const [error, setError] = useState<Error>();
const [isSubmitting, setIsSubmitting] = useState(false);

useEffect(() => {
const templatePropertiesInQuery = getWorkflowParametersFromQuery(props.history);
// Get the user arguments from the query params
const updatedParams = workflowParameters.map(param => ({
name: param.name,
value: templatePropertiesInQuery[param.name] || param.value
}));
setWorkflowParameters(updatedParams);
}, [props.history, setWorkflowParameters]);

const templates = useMemo(() => {
return [defaultTemplate].concat(props.templates);
}, [props.templates]);
Expand Down
10 changes: 9 additions & 1 deletion ui/src/workflows/components/workflow-creator.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {Select} from 'argo-ui/src/components/select/select';
import {History} from 'history';
import * as React from 'react';
import {useEffect, useState} from 'react';

Expand All @@ -15,7 +16,7 @@ import {WorkflowEditor} from './workflow-editor';

type Stage = 'choose-method' | 'submit-workflow' | 'full-editor';

export function WorkflowCreator({namespace, onCreate}: {namespace: string; onCreate: (workflow: Workflow) => void}) {
export function WorkflowCreator({namespace, onCreate, history}: {namespace: string; onCreate: (workflow: Workflow) => void; history: History}) {
const [workflowTemplates, setWorkflowTemplates] = useState<WorkflowTemplate[]>();
const [workflowTemplate, setWorkflowTemplate] = useState<WorkflowTemplate>();
const [stage, setStage] = useState<Stage>('choose-method');
Expand Down Expand Up @@ -61,6 +62,12 @@ export function WorkflowCreator({namespace, onCreate}: {namespace: string; onCre
}
}, [workflowTemplate]);

useEffect(() => {
const queryParams = new URLSearchParams(history.location.search);
const template = queryParams.get('template');
setWorkflowTemplate((workflowTemplates || []).find(tpl => tpl.metadata.name === template));
}, [workflowTemplates, setWorkflowTemplate, history]);

return (
<>
{stage === 'choose-method' && (
Expand Down Expand Up @@ -92,6 +99,7 @@ export function WorkflowCreator({namespace, onCreate}: {namespace: string; onCre
entrypoint={workflowTemplate.spec.entrypoint}
templates={workflowTemplate.spec.templates || []}
workflowParameters={workflowTemplate.spec.arguments.parameters || []}
history={history}
/>
<a onClick={() => setStage('full-editor')}>
Edit using full workflow options <i className='fa fa-caret-right' />
Expand Down
20 changes: 18 additions & 2 deletions ui/src/workflows/components/workflows-list/workflows-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ export function WorkflowsList({match, location, history}: RouteComponentProps<an
}
storage.setItem('options', options, {} as WorkflowListRenderOptions);

const params = new URLSearchParams();
const params = new URLSearchParams(history.location.search);
phases?.forEach(phase => params.append('phase', phase));
labels?.forEach(label => params.append('label', label));
if (pagination.offset) {
Expand Down Expand Up @@ -346,10 +346,26 @@ export function WorkflowsList({match, location, history}: RouteComponentProps<an
)}
</div>
</div>
<SlidingPanel isShown={!!getSidePanel()} onClose={() => navigation.goto('.', {sidePanel: null})}>
<SlidingPanel
isShown={!!getSidePanel()}
onClose={() => {
const qParams: {[key: string]: string | null} = {
sidePanel: null
};
// Remove any lingering query params
for (const key of queryParams.keys()) {
qParams[key] = null;
}
// Add back the pagination and namespace params.
qParams.limit = pagination.limit.toString();
qParams.offset = pagination.offset || null;
qParams.namespace = namespace;
navigation.goto('.', qParams);
}}>
{getSidePanel() === 'submit-new-workflow' && (
<WorkflowCreator
namespace={nsUtils.getNamespaceWithDefault(namespace)}
history={history}
onCreate={wf => navigation.goto(uiUrl(`workflows/${wf.metadata.namespace}/${wf.metadata.name}`))}
/>
)}
Expand Down

0 comments on commit 3715fb3

Please sign in to comment.