Skip to content

Commit

Permalink
feat: simply name filtering on UI & fix API proto
Browse files Browse the repository at this point in the history
Signed-off-by: Adrien Delannoy <[email protected]>
  • Loading branch information
Adrien-D committed Jun 11, 2024
1 parent 622831c commit 34806fc
Show file tree
Hide file tree
Showing 8 changed files with 101 additions and 104 deletions.
22 changes: 12 additions & 10 deletions api/openapi-spec/swagger.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 6 additions & 4 deletions pkg/apiclient/workflow/workflow.proto
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,13 @@ message WorkflowGetRequest {

message WorkflowListRequest {
string namespace = 1;
string namePrefix = 2;
string namePattern = 3;
k8s.io.apimachinery.pkg.apis.meta.v1.ListOptions listOptions = 4;
k8s.io.apimachinery.pkg.apis.meta.v1.ListOptions listOptions = 2;
// Fields to be included or excluded in the response. e.g. "items.spec,items.status.phase", "-items.status.nodes"
string fields = 5;
string fields = 3;
// Return workflows having this name prefix
string namePrefix = 4;
// Return workflows with name containing this value
string namePattern = 5;
}

message WorkflowResubmitRequest {
Expand Down
13 changes: 7 additions & 6 deletions server/utils/list_options.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,13 @@ import (
)

type ListOptions struct {
Namespace, Name, NamePrefix, NamePattern string
MinStartedAt, MaxStartedAt time.Time
LabelRequirements labels.Requirements
Limit, Offset int
ShowRemainingItemCount bool
StartedAtAscending bool
Namespace, Name string
NamePrefix, NamePattern string
MinStartedAt, MaxStartedAt time.Time
LabelRequirements labels.Requirements
Limit, Offset int
ShowRemainingItemCount bool
StartedAtAscending bool
}

func (l ListOptions) WithLimit(limit int) ListOptions {
Expand Down
2 changes: 1 addition & 1 deletion ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"node": ">=20"
},
"dependencies": {
"argo-ui": "git+https://github.com/argoproj/argo-ui.git#741f07c28b446f432e0a46ff47cadc841883887c",
"argo-ui": "git+https://github.com/argoproj/argo-ui.git#00a8b573b86f90e09fdf455b20fd6056a619a5e2",
"chart.js": "^2.9.4",
"chartjs-plugin-annotation": "^0.5.7",
"classnames": "^2.5.1",
Expand Down
4 changes: 4 additions & 0 deletions ui/src/app/shared/components/input-filter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ interface InputProps {
placeholder?: string;
name: string;
onChange: (input: string) => void;
autoHighlight?: boolean;
filterSuggestions?: boolean;
}

export function InputFilter(props: InputProps) {
Expand Down Expand Up @@ -56,6 +58,8 @@ export function InputFilter(props: InputProps) {
props.onChange(newValue);
}}
renderInput={renderInput}
filterSuggestions={props.filterSuggestions}
autoHighlight={props.autoHighlight}
/>
<a
onClick={() => {
Expand Down
8 changes: 4 additions & 4 deletions ui/src/app/shared/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ export const Utils = {
resourceVersion?: string;
}) {
const queryParams: string[] = [];
const fieldSelector = this.fieldSelectorParams(filter.namespace, filter.name, filter.createdAfter, filter.finishedBefore, filter.namePrefix, filter.namePattern);
const fieldSelector = this.fieldSelectorParams(filter.namespace, filter.name, filter.createdAfter, filter.finishedBefore);
if (fieldSelector.length > 0) {
queryParams.push(`listOptions.fieldSelector=${fieldSelector}`);
}
Expand All @@ -152,12 +152,12 @@ export const Utils = {
queryParams.push(`listOptions.limit=${filter.pagination.limit}`);
}
}
if (filter.namePattern) {
queryParams.push(`namePattern=${filter.namePattern}`);
}
if (filter.namePrefix) {
queryParams.push(`namePrefix=${filter.namePrefix}`);
}
if (filter.namePattern) {
queryParams.push(`namePattern=${filter.namePattern}`);
}
if (filter.resourceVersion) {
queryParams.push(`listOptions.resourceVersion=${filter.resourceVersion}`);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import * as React from 'react';
import {useMemo} from 'react';
import DatePicker from 'react-datepicker';
import classNames from 'classnames';

import 'react-datepicker/dist/react-datepicker.css';

import * as models from '../../../../models';
Expand All @@ -10,11 +12,29 @@ import {DataLoaderDropdown} from '../../../shared/components/data-loader-dropdow
import {NamespaceFilter} from '../../../shared/components/namespace-filter';
import {TagsInput} from '../../../shared/components/tags-input/tags-input';
import {services} from '../../../shared/services';

import {InputFilter} from '../../../shared/components/input-filter';
import './workflow-filters.scss';
import {DropDown} from '../../../shared/components/dropdown/dropdown';
import classNames from 'classnames';

import './workflow-filters.scss';

const NAME_FILTERS = [
{
title: 'Name Pattern',
id: 'namePattern' as const
},
{
title: 'Name Prefix',
id: 'namePrefix' as const
},
{
title: 'Name Exact',
id: 'name' as const
}
];

export const NAME_FILTER_KEYS = NAME_FILTERS.map(item => item.id);

export type NameFilterKeys = (typeof NAME_FILTER_KEYS)[number];

interface WorkflowFilterProps {
workflows: models.Workflow[];
Expand All @@ -29,39 +49,12 @@ interface WorkflowFilterProps {
setLabels: (labels: string[]) => void;
setCreatedAfter: (createdAfter: Date) => void;
setFinishedBefore: (finishedBefore: Date) => void;
name: string;
setName: (name: string) => void;
namePrefix: string;
setNamePrefix: (namePrefix: string) => void;
namePattern: string;
setNamePattern: (namePattern: string) => void;
nameFilter: {value: string; type: NameFilterKeys};
setNameFilter: (nameFilter: {value: string; type: NameFilterKeys}) => void;
}

const NAME_FILTERS = [
{
title: 'Name Pattern',
id: 'namePattern'
},
{
title: 'Name Prefix',
id: 'namePrefix'
},
{
title: 'Name Exact',
id: 'name'
}
];

export function WorkflowFilters(props: WorkflowFilterProps) {
const [nameFilter, setNameFilter] = React.useState(() => {
if (props.namePrefix) {
return NAME_FILTERS[1];
}
if (props.name) {
return NAME_FILTERS[2];
}
return NAME_FILTERS[0];
});
const nameFilter = NAME_FILTERS.find(item => item.id === props.nameFilter.type);
function setLabel(name: string, value: string) {
props.setLabels([name.concat('=' + value)]);
}
Expand Down Expand Up @@ -90,12 +83,9 @@ export function WorkflowFilters(props: WorkflowFilterProps) {
return results;
}, [props.workflows, props.phaseItems]);

const handleNameFilterChange = (item: {title: string; id: string}) => {
props.setNamePrefix('');
props.setNamePattern('');
props.setName('');
setNameFilter(item);
};
function handleNameFilterChange(item: {title: string; id: string}) {
props.setNameFilter({value: props.nameFilter.value, type: item.id as NameFilterKeys});
}

return (
<div className='wf-filters-container'>
Expand Down Expand Up @@ -123,11 +113,14 @@ export function WorkflowFilters(props: WorkflowFilterProps) {
))}
</ul>
</DropDown>
{nameFilter.id === 'namePrefix' ? <InputFilter value={props.namePrefix} name='wfNamePrefix' onChange={props.setNamePrefix} placeholder='Search...' /> : null}
{nameFilter.id === 'namePattern' ? (
<InputFilter value={props.namePattern} name='wfNamePattern' onChange={props.setNamePattern} placeholder='Search...' />
) : null}
{nameFilter.id === 'name' ? <InputFilter value={props.name} name='wfName' onChange={props.setName} placeholder='Search...' /> : null}
<InputFilter
value={props.nameFilter.value}
name='wfNameFilter'
onChange={value => props.setNameFilter({value, type: props.nameFilter.type})}
placeholder='Search...'
autoHighlight={false}
filterSuggestions
/>
</div>
<div className='columns small-2 xlarge-12'>
<p className='wf-filters-container__title'>Labels</p>
Expand Down
65 changes: 30 additions & 35 deletions ui/src/app/workflows/components/workflows-list/workflows-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ import {services} from '../../../shared/services';
import {Utils} from '../../../shared/utils';
import * as Actions from '../../../shared/workflow-operations-map';
import {WorkflowCreator} from '../workflow-creator';
import {WorkflowFilters} from '../workflow-filters/workflow-filters';
import {NAME_FILTER_KEYS, WorkflowFilters} from '../workflow-filters/workflow-filters';
import type {NameFilterKeys} from '../workflow-filters/workflow-filters';
import {WorkflowsRow} from '../workflows-row/workflows-row';
import {WorkflowsSummaryContainer} from '../workflows-summary-container/workflows-summary-container';
import {WorkflowsToolbar} from '../workflows-toolbar/workflows-toolbar';
Expand Down Expand Up @@ -85,24 +86,21 @@ export function WorkflowsList({match, location, history}: RouteComponentProps<an
const [links, setLinks] = useState<models.Link[]>([]);
const [columns, setColumns] = useState<models.Column[]>([]);
const [error, setError] = useState<Error>();
// don't get the storage saved value if there is a name filter in the URL, as it will override the storage value
const hasNameUrlFiltering = queryParams.has('name') || queryParams.has('namePrefix') || queryParams.has('namePattern');
const [name, setName] = useState(() => {
const savedOptions = hasNameUrlFiltering ? '' : storage.getItem('options', {}).name;
const nameQueryParam = queryParams.get('name');
return (nameQueryParam ? nameQueryParam : savedOptions) || '';
});
const [namePrefix, setNamePrefix] = useState(() => {
const savedOptions = hasNameUrlFiltering ? '' : storage.getItem('options', {}).namePrefix;
const namePrefixQueryParam = queryParams.get('namePrefix');
return (namePrefixQueryParam ? namePrefixQueryParam : savedOptions) || '';
});
const [namePattern, setNamePattern] = useState(() => {
const savedOptions = hasNameUrlFiltering ? '' : storage.getItem('options', {}).namePattern;
const namePatternQueryParam = queryParams.get('namePattern');
return (namePatternQueryParam ? namePatternQueryParam : savedOptions) || '';
const [nameFilter, setNameFilter] = useState((): {type: NameFilterKeys; value: string} => {
const keys = NAME_FILTER_KEYS;
const nameUrlFilterType = keys.find(key => queryParams.get(key));
// don't get the storage saved value if there is a name filter in the URL, as it will override the storage value
if (nameUrlFilterType) {
return {type: nameUrlFilterType, value: queryParams.get(nameUrlFilterType)};
}
const savedOptions = storage.getItem('options', {});
for (const key of keys) {
if (savedOptions[key]) {
return {type: key, value: savedOptions[key]};
}
}
return {type: 'namePattern', value: ''};
});

const batchActionDisabled = useMemo<Actions.OperationDisabled>(() => {
const nowDisabled: any = {...allBatchActionsEnabled};
for (const action of Object.keys(nowDisabled)) {
Expand Down Expand Up @@ -137,11 +135,9 @@ export function WorkflowsList({match, location, history}: RouteComponentProps<an
useEffect(() => {
// add empty selectedPhases + selectedLabels for forward-compat w/ old version: previous code relies on them existing, so if you move up a version and back down, it breaks
const options = {selectedPhases: [], selectedLabels: []} as unknown as WorkflowListRenderOptions;
options[nameFilter.type] = nameFilter.value;
options.phases = phases;
options.labels = labels;
options.name = name;
options.namePrefix = namePrefix;
options.namePattern = namePattern;
if (pagination.limit) {
options.paginationLimit = pagination.limit;
}
Expand All @@ -156,19 +152,22 @@ export function WorkflowsList({match, location, history}: RouteComponentProps<an
if (pagination.limit) {
params.append('limit', pagination.limit.toString());
}
if (name) {
params.append('name', name);
if (options.name) {
params.append('name', options.name);
}
if (namePrefix) {
params.append('namePrefix', namePrefix);
if (options.namePrefix) {
params.append('namePrefix', options.namePrefix);
}
if (namePattern) {
params.append('namePattern', namePattern);
if (options.namePattern) {
params.append('namePattern', options.namePattern);
}
history.push(historyUrl('workflows' + (Utils.managedNamespace ? '' : '/{namespace}'), {namespace, extraSearchParams: params}));
}, [namespace, name, namePrefix, namePattern, phases.toString(), labels.toString(), pagination.limit, pagination.offset]); // referential equality, so use values, not refs
}, [namespace, nameFilter, phases.toString(), labels.toString(), pagination.limit, pagination.offset]); // referential equality, so use values, not refs

useEffect(() => {
const name = nameFilter.type === 'name' ? nameFilter.value : undefined;
const namePrefix = nameFilter.type === 'namePrefix' ? nameFilter.value : undefined;
const namePattern = nameFilter.type === 'namePattern' ? nameFilter.value : undefined;
const listWatch = new ListWatch(
() => services.workflows.list(namespace, phases, labels, pagination, undefined, name, namePrefix, namePattern),
(resourceVersion: string) => services.workflows.watchFields({namespace, phases, labels, resourceVersion}),
Expand All @@ -188,7 +187,7 @@ export function WorkflowsList({match, location, history}: RouteComponentProps<an
clearSelectedWorkflows();
listWatch.stop();
};
}, [namespace, phases.toString(), labels.toString(), pagination.limit, pagination.offset, name, namePrefix, namePattern]); // referential equality, so use values, not refs
}, [namespace, phases.toString(), labels.toString(), pagination.limit, pagination.offset, nameFilter]); // referential equality, so use values, not refs

useCollectEvent('openedWorkflowList');

Expand Down Expand Up @@ -244,12 +243,8 @@ export function WorkflowsList({match, location, history}: RouteComponentProps<an
setFinishedBefore(date);
clearSelectedWorkflows(); // date filters are client-side, but clear similar to the server-side ones for consistency
}}
name={name}
setName={setName}
namePrefix={namePrefix}
setNamePrefix={setNamePrefix}
namePattern={namePattern}
setNamePattern={setNamePattern}
nameFilter={nameFilter}
setNameFilter={setNameFilter}
/>
</div>
</div>
Expand Down

0 comments on commit 34806fc

Please sign in to comment.