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}
+
+
+ }>
+
+ {NAME_FILTERS.map((item, i) => (
+ - handleNameFilterChange(item)}>
+ {item.title}
+
+ ))}
+
+
+ {nameFilter.id === 'namePrefix' ?
: null}
+ {nameFilter.id === 'namePattern' ? (
+
+ ) : null}
+ {nameFilter.id === 'name' ?
: null}
+
Labels
diff --git a/ui/src/app/workflows/components/workflows-list/workflows-list.tsx b/ui/src/app/workflows/components/workflows-list/workflows-list.tsx
index d1c9b3ab6630..c4e05930cd93 100644
--- a/ui/src/app/workflows/components/workflows-list/workflows-list.tsx
+++ b/ui/src/app/workflows/components/workflows-list/workflows-list.tsx
@@ -34,6 +34,9 @@ interface WorkflowListRenderOptions {
paginationLimit: number;
phases: WorkflowPhase[];
labels: string[];
+ name: string;
+ namePrefix: string;
+ namePattern: string;
}
const actions = Actions.WorkflowOperationsMap;
@@ -82,6 +85,18 @@ export function WorkflowsList({match, location, history}: RouteComponentProps
([]);
const [columns, setColumns] = useState([]);
const [error, setError] = useState();
+ 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(() => {
const nowDisabled: any = {...allBatchActionsEnabled};
@@ -119,6 +134,9 @@ export function WorkflowsList({match, location, history}: RouteComponentProps {
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);
@@ -156,7 +183,7 @@ export function WorkflowsList({match, location, history}: RouteComponentProps