Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(ui): prefill parameters for workflow submit form. Fixes #12124 #13922

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading