From 05a697b1e4b895e2bf1f2ff547e90a188e0fbc51 Mon Sep 17 00:00:00 2001 From: Adrien Delannoy Date: Mon, 10 Jun 2024 14:32:43 +0200 Subject: [PATCH] feat: UI add name/prefix/pattern to filter worflows Signed-off-by: Adrien Delannoy --- .../shared/components/dropdown/dropdown.tsx | 5 +- .../app/shared/components/input-filter.scss | 10 +++ ui/src/app/shared/components/input-filter.tsx | 6 +- .../app/shared/services/workflows-service.ts | 7 +- ui/src/app/shared/utils.ts | 8 +-- .../workflow-filters/workflow-filters.scss | 6 +- .../workflow-filters/workflow-filters.tsx | 65 +++++++++++++++++++ .../workflows-list/workflows-list.tsx | 39 ++++++++++- 8 files changed, 132 insertions(+), 14 deletions(-) create mode 100644 ui/src/app/shared/components/input-filter.scss diff --git a/ui/src/app/shared/components/dropdown/dropdown.tsx b/ui/src/app/shared/components/dropdown/dropdown.tsx index 5c9253d8125d..50b1569e531a 100644 --- a/ui/src/app/shared/components/dropdown/dropdown.tsx +++ b/ui/src/app/shared/components/dropdown/dropdown.tsx @@ -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); @@ -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; } diff --git a/ui/src/app/shared/components/input-filter.scss b/ui/src/app/shared/components/input-filter.scss new file mode 100644 index 000000000000..76dfe8e6785c --- /dev/null +++ b/ui/src/app/shared/components/input-filter.scss @@ -0,0 +1,10 @@ +.input-filter { + display: flex; + align-items: center; + justify-content: space-between; + + div:first-child { + flex: 1; + } +} + diff --git a/ui/src/app/shared/components/input-filter.tsx b/ui/src/app/shared/components/input-filter.tsx index 14e781aa420c..fd0849f4470e 100644 --- a/ui/src/app/shared/components/input-filter.tsx +++ b/ui/src/app/shared/components/input-filter.tsx @@ -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; @@ -44,7 +46,7 @@ export function InputFilter(props: InputProps) { } return ( - <> +
- +
); } diff --git a/ui/src/app/shared/services/workflows-service.ts b/ui/src/app/shared/services/workflows-service.ts index 4557d0ed2a69..8a07cec7f2c8 100644 --- a/ui/src/app/shared/services/workflows-service.ts +++ b/ui/src/app/shared/services/workflows-service.ts @@ -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); }, diff --git a/ui/src/app/shared/utils.ts b/ui/src/app/shared/utils.ts index 3f7accc7c3cc..87f45b4b7094 100644 --- a/ui/src/app/shared/utils.ts +++ b/ui/src/app/shared/utils.ts @@ -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}`); } @@ -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}`); } diff --git a/ui/src/app/workflows/components/workflow-filters/workflow-filters.scss b/ui/src/app/workflows/components/workflow-filters/workflow-filters.scss index 3e9a03ad1463..1749070ee4e8 100644 --- a/ui/src/app/workflows/components/workflow-filters/workflow-filters.scss +++ b/ui/src/app/workflows/components/workflow-filters/workflow-filters.scss @@ -32,4 +32,8 @@ flex-direction: row; align-items: center; justify-content: space-between; -} \ No newline at end of file +} + +#top-bar__filter-list { + column-count: 1; +} diff --git a/ui/src/app/workflows/components/workflow-filters/workflow-filters.tsx b/ui/src/app/workflows/components/workflow-filters/workflow-filters.tsx index 9a29c75e3a9b..2e9594a20317 100644 --- a/ui/src/app/workflows/components/workflow-filters/workflow-filters.tsx +++ b/ui/src/app/workflows/components/workflow-filters/workflow-filters.tsx @@ -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[]; @@ -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)]); } @@ -57,6 +90,13 @@ 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 (
@@ -64,6 +104,31 @@ export function WorkflowFilters(props: WorkflowFilterProps) {

Namespace

+
+ +

+ {nameFilter.title}