Skip to content

Commit

Permalink
feat: UI add name/prefix/pattern to filter worflows
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 10, 2024
1 parent 6798147 commit 05a697b
Show file tree
Hide file tree
Showing 8 changed files with 132 additions and 14 deletions.
5 changes: 3 additions & 2 deletions ui/src/app/shared/components/dropdown/dropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@ import './dropdown.scss';
export interface DropDownProps {
isMenu?: boolean;
anchor: JSX.Element;
closeOnInsideClick?: boolean;
children: ReactNode;
}

export function DropDown({isMenu, anchor, children}: DropDownProps) {
export function DropDown({isMenu, anchor, closeOnInsideClick, children}: DropDownProps) {
const [opened, setOpened] = useState(false);
const [left, setLeft] = useState(0);
const [top, setTop] = useState(0);
Expand Down Expand Up @@ -56,7 +57,7 @@ export function DropDown({isMenu, anchor, children}: DropDownProps) {

function close(event: MouseEvent) {
// Doesn't close when clicked inside the portal area
if (contentEl.contains(event.target as Node) || anchorEl.contains(event.target as Node)) {
if (!closeOnInsideClick && (contentEl.contains(event.target as Node) || anchorEl.contains(event.target as Node))) {
return;
}

Expand Down
10 changes: 10 additions & 0 deletions ui/src/app/shared/components/input-filter.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
.input-filter {
display: flex;
align-items: center;
justify-content: space-between;

div:first-child {
flex: 1;
}
}

6 changes: 4 additions & 2 deletions ui/src/app/shared/components/input-filter.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import {Autocomplete} from 'argo-ui/src/components/autocomplete/autocomplete';
import React, {useState} from 'react';

import './input-filter.scss';

interface InputProps {
value: string;
placeholder?: string;
Expand Down Expand Up @@ -44,7 +46,7 @@ export function InputFilter(props: InputProps) {
}

return (
<>
<div className='input-filter'>
<Autocomplete
items={localCache}
value={value}
Expand All @@ -62,6 +64,6 @@ export function InputFilter(props: InputProps) {
}}>
<i className='fa fa-times-circle' />
</a>
</>
</div>
);
}
7 changes: 5 additions & 2 deletions ui/src/app/shared/services/workflows-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,12 @@ export const WorkflowsService = {
'items.status.estimatedDuration',
'items.status.progress',
'items.spec.suspend'
]
],
name?: string,
namePrefix?: string,
namePattern?: string
) {
const params = Utils.queryParams({phases, labels, pagination});
const params = Utils.queryParams({phases, labels, pagination, name, namePrefix, namePattern});
params.push(`fields=${fields.join(',')}`);
return requests.get(`api/v1/workflows/${namespace}?${params.join('&')}`).then(res => res.body as WorkflowList);
},
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);
const fieldSelector = this.fieldSelectorParams(filter.namespace, filter.name, filter.createdAfter, filter.finishedBefore, filter.namePrefix, filter.namePattern);
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.namePrefix) {
queryParams.push(`namePrefix=${filter.namePrefix}`);
}
if (filter.namePattern) {
queryParams.push(`namePattern=${filter.namePattern}`);
}
if (filter.namePrefix) {
queryParams.push(`namePrefix=${filter.namePrefix}`);
}
if (filter.resourceVersion) {
queryParams.push(`listOptions.resourceVersion=${filter.resourceVersion}`);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,8 @@
flex-direction: row;
align-items: center;
justify-content: space-between;
}
}

#top-bar__filter-list {
column-count: 1;
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ 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';

interface WorkflowFilterProps {
workflows: models.Workflow[];
Expand All @@ -26,9 +29,39 @@ 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;
}

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];
});
function setLabel(name: string, value: string) {
props.setLabels([name.concat('=' + value)]);
}
Expand Down Expand Up @@ -57,13 +90,45 @@ 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);
};

return (
<div className='wf-filters-container'>
<div className='row'>
<div className='columns small-2 xlarge-12'>
<p className='wf-filters-container__title'>Namespace</p>
<NamespaceFilter value={props.namespace} onChange={props.setNamespace} />
</div>
<div className='columns small-2 xlarge-12'>
<DropDown
isMenu
closeOnInsideClick
anchor={
<div className={classNames('top-bar__filter')} title='Filter'>
<p className='wf-filters-container__title'>
{nameFilter.title} <i className='fa fa-angle-down' aria-hidden='true' />
</p>
</div>
}>
<ul id='top-bar__filter-list'>
{NAME_FILTERS.map((item, i) => (
<li key={i} className={classNames('top-bar__filter-item', {title: true})} onClick={() => handleNameFilterChange(item)}>
<span>{item.title}</span>
</li>
))}
</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}
</div>
<div className='columns small-2 xlarge-12'>
<p className='wf-filters-container__title'>Labels</p>
<TagsInput placeholder='' autocomplete={labelSuggestion} tags={props.labels} onChange={props.setLabels} />
Expand Down
39 changes: 36 additions & 3 deletions ui/src/app/workflows/components/workflows-list/workflows-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ interface WorkflowListRenderOptions {
paginationLimit: number;
phases: WorkflowPhase[];
labels: string[];
name: string;
namePrefix: string;
namePattern: string;
}

const actions = Actions.WorkflowOperationsMap;
Expand Down Expand Up @@ -82,6 +85,18 @@ 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>();
const [name, setName] = useState(() => {
const savedOptions = storage.getItem('options', {});
return savedOptions.name || '';
});
const [namePrefix, setNamePrefix] = useState(() => {
const savedOptions = storage.getItem('options', {});
return savedOptions.namePrefix || '';
});
const [namePattern, setNamePattern] = useState(() => {
const savedOptions = storage.getItem('options', {});
return savedOptions.namePattern || '';
});

const batchActionDisabled = useMemo<Actions.OperationDisabled>(() => {
const nowDisabled: any = {...allBatchActionsEnabled};
Expand Down Expand Up @@ -119,6 +134,9 @@ export function WorkflowsList({match, location, history}: RouteComponentProps<an
const options = {selectedPhases: [], selectedLabels: []} as unknown as WorkflowListRenderOptions;
options.phases = phases;
options.labels = labels;
options.name = name;
options.namePrefix = namePrefix;
options.namePattern = namePattern;
if (pagination.limit) {
options.paginationLimit = pagination.limit;
}
Expand All @@ -133,12 +151,21 @@ export function WorkflowsList({match, location, history}: RouteComponentProps<an
if (pagination.limit) {
params.append('limit', pagination.limit.toString());
}
if (name) {
params.append('name', name);
}
if (namePrefix) {
params.append('namePrefix', namePrefix);
}
if (namePattern) {
params.append('namePattern', namePattern);
}
history.push(historyUrl('workflows' + (Utils.managedNamespace ? '' : '/{namespace}'), {namespace, extraSearchParams: params}));
}, [namespace, phases.toString(), labels.toString(), pagination.limit, pagination.offset]); // referential equality, so use values, not refs
}, [namespace, name, namePrefix, namePattern, phases.toString(), labels.toString(), pagination.limit, pagination.offset]); // referential equality, so use values, not refs

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

useCollectEvent('openedWorkflowList');

Expand Down Expand Up @@ -212,6 +239,12 @@ 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}
/>
</div>
</div>
Expand Down

0 comments on commit 05a697b

Please sign in to comment.