Skip to content

Commit 20f1bf1

Browse files
committed
[Feature] Property filter for runs
1 parent 2102b1b commit 20f1bf1

4 files changed

Lines changed: 141 additions & 78 deletions

File tree

frontend/src/components/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ export type { ChatBubbleProps } from '@cloudscape-design/chat-components/chat-bu
5252
export { default as Avatar } from '@cloudscape-design/chat-components/avatar';
5353
export type { AvatarProps } from '@cloudscape-design/chat-components/avatar';
5454
export { default as LineChart } from '@cloudscape-design/components/line-chart';
55+
export { default as PropertyFilter } from '@cloudscape-design/components/property-filter';
56+
export type { PropertyFilterProps } from '@cloudscape-design/components/property-filter';
5557
export type { LineChartProps } from '@cloudscape-design/components/line-chart/interfaces';
5658
export type { ModalProps } from '@cloudscape-design/components/modal';
5759
export type { TilesProps } from '@cloudscape-design/components/tiles';
Lines changed: 105 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,130 @@
1-
import { useEffect } from 'react';
1+
import { useMemo, useState } from 'react';
22
import { useSearchParams } from 'react-router-dom';
33

4-
import { SelectCSDProps } from 'components';
4+
import type { PropertyFilterProps } from 'components';
55

6-
import { useLocalStorageState } from 'hooks/useLocalStorageState';
76
import { useProjectFilter } from 'hooks/useProjectFilter';
87

98
type Args = {
109
localStorePrefix: string;
11-
projectSearchKey?: string;
12-
selectedProject?: string;
1310
};
1411

15-
export const useFilters = ({ localStorePrefix, projectSearchKey }: Args) => {
16-
const [searchParams] = useSearchParams();
17-
const { selectedProject, setSelectedProject, projectOptions } = useProjectFilter({ localStorePrefix });
18-
const [onlyActive, setOnlyActive] = useLocalStorageState<boolean>(`${localStorePrefix}-is-active`, false);
12+
type RequestParamsKeys = keyof Pick<TRunsRequestParams, 'only_active' | 'project_name'>;
1913

20-
const setSelectedOptionFromParams = (
21-
searchKey: string,
22-
options: SelectCSDProps.Options | null,
23-
set: (option: SelectCSDProps.Option) => void,
24-
) => {
25-
const searchValue = searchParams.get(searchKey);
14+
const FilterKeys: Record<string, RequestParamsKeys> = {
15+
PROJECT_NAME: 'project_name',
16+
ACTIVE: 'only_active',
17+
};
18+
19+
const EMPTY_QUERY: PropertyFilterProps.Query = {
20+
tokens: [],
21+
operation: 'and',
22+
};
2623

27-
if (!searchValue || !options?.length) return;
24+
const tokensToRequestParams = (tokens: PropertyFilterProps.Query['tokens']) => {
25+
return tokens.reduce((acc, token) => {
26+
if (token.propertyKey) {
27+
acc[token.propertyKey as RequestParamsKeys] = token.value;
28+
}
29+
30+
return acc;
31+
}, {} as Record<RequestParamsKeys, string>);
32+
};
33+
34+
export const useFilters = ({ localStorePrefix }: Args) => {
35+
const [searchParams, setSearchParams] = useSearchParams();
36+
const { projectOptions } = useProjectFilter({ localStorePrefix });
37+
38+
const [propertyFilterQuery, setPropertyFilterQuery] = useState<PropertyFilterProps.Query>(() => {
39+
const tokens = [];
2840

2941
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
3042
// @ts-ignore
31-
const selectedOption = options.find((option) => option?.value === searchValue);
43+
for (const [paramKey, paramValue] of searchParams.entries()) {
44+
if (Object.values(FilterKeys).includes(paramKey)) {
45+
tokens.push({ propertyKey: paramKey, operator: '=', value: paramValue });
46+
}
47+
}
48+
49+
if (!tokens.length) {
50+
return EMPTY_QUERY;
51+
}
52+
53+
return {
54+
...EMPTY_QUERY,
55+
tokens,
56+
};
57+
});
3258

33-
if (selectedOption) set(selectedOption);
59+
const clearFilter = () => {
60+
setSearchParams({});
61+
setPropertyFilterQuery(EMPTY_QUERY);
3462
};
3563

36-
useEffect(() => {
37-
if (!projectSearchKey) return;
64+
const filteringOptions = useMemo(() => {
65+
const options: PropertyFilterProps.FilteringOption[] = [];
3866

39-
setSelectedOptionFromParams(projectSearchKey, projectOptions, setSelectedProject);
40-
}, [searchParams, projectSearchKey, projectOptions]);
67+
projectOptions.forEach(({ value }) => {
68+
if (value)
69+
options.push({
70+
propertyKey: FilterKeys.PROJECT_NAME,
71+
value,
72+
});
73+
});
4174

42-
const clearSelected = () => {
43-
setSelectedProject(null);
44-
setOnlyActive(false);
45-
};
75+
options.push({
76+
propertyKey: FilterKeys.ACTIVE,
77+
value: 'True',
78+
});
79+
80+
return options;
81+
}, [projectOptions]);
82+
83+
const filteringProperties = [
84+
{
85+
key: FilterKeys.PROJECT_NAME,
86+
operators: ['='],
87+
propertyLabel: 'Project',
88+
groupValuesLabel: 'Project values',
89+
},
90+
{
91+
key: FilterKeys.ACTIVE,
92+
operators: ['='],
93+
propertyLabel: 'Only active',
94+
groupValuesLabel: 'Active values',
95+
},
96+
];
97+
98+
const onChangePropertyFilter: PropertyFilterProps['onChange'] = ({ detail }) => {
99+
const { tokens, operation } = detail;
46100

47-
const setSelectedProjectHandle = (project: SelectCSDProps.Option | null) => {
48-
setSelectedProject(project);
49-
setOnlyActive(false);
101+
const filteredTokens = tokens.filter((token, tokenIndex) => {
102+
return !tokens.some((item, index) => token.propertyKey === item.propertyKey && index > tokenIndex);
103+
});
104+
105+
setSearchParams(tokensToRequestParams(filteredTokens));
106+
107+
setPropertyFilterQuery({
108+
operation,
109+
tokens: filteredTokens,
110+
});
50111
};
51112

113+
const filteringRequestParams = useMemo(() => {
114+
const params = tokensToRequestParams(propertyFilterQuery.tokens);
115+
116+
return {
117+
...params,
118+
only_active: params.only_active === 'True',
119+
};
120+
}, [propertyFilterQuery]);
121+
52122
return {
53-
projectOptions,
54-
selectedProject,
55-
setSelectedProject: setSelectedProjectHandle,
56-
onlyActive,
57-
setOnlyActive,
58-
clearSelected,
123+
filteringRequestParams,
124+
clearFilter,
125+
propertyFilterQuery,
126+
onChangePropertyFilter,
127+
filteringOptions,
128+
filteringProperties,
59129
} as const;
60130
};

frontend/src/pages/Runs/List/index.tsx

Lines changed: 27 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
import React from 'react';
22
import { useTranslation } from 'react-i18next';
3-
import { useSearchParams } from 'react-router-dom';
43

5-
import { Button, FormField, Header, Loader, SelectCSD, SpaceBetween, Table, Toggle } from 'components';
4+
import { Button, Header, Loader, PropertyFilter, SpaceBetween, Table } from 'components';
65

6+
import { DEFAULT_TABLE_PAGE_SIZE } from 'consts';
77
import { useBreadcrumbs, useCollection, useInfiniteScroll } from 'hooks';
88
import { ROUTES } from 'routes';
9+
import { useLazyGetRunsQuery } from 'services/run';
910

1011
import { useRunListPreferences } from './Preferences/useRunListPreferences';
11-
import { DEFAULT_TABLE_PAGE_SIZE } from '../../../consts';
12-
import { useLazyGetRunsQuery } from '../../../services/run';
1312
import {
1413
useAbortRuns,
1514
useColumnsDefinitions,
@@ -25,7 +24,6 @@ import styles from './styles.module.scss';
2524

2625
export const RunList: React.FC = () => {
2726
const { t } = useTranslation();
28-
const [searchParams, setSearchParams] = useSearchParams();
2927
const [preferences] = useRunListPreferences();
3028

3129
useBreadcrumbs([
@@ -35,33 +33,30 @@ export const RunList: React.FC = () => {
3533
},
3634
]);
3735

38-
const { projectOptions, selectedProject, setSelectedProject, onlyActive, setOnlyActive, clearSelected } = useFilters({
39-
projectSearchKey: 'project',
36+
const {
37+
clearFilter,
38+
propertyFilterQuery,
39+
onChangePropertyFilter,
40+
filteringOptions,
41+
filteringProperties,
42+
filteringRequestParams,
43+
} = useFilters({
4044
localStorePrefix: 'administration-run-list-page',
4145
});
4246

4347
const { data, isLoading, refreshList, isLoadingMore } = useInfiniteScroll<IRun, TRunsRequestParams>({
4448
useLazyQuery: useLazyGetRunsQuery,
45-
args: { project_name: selectedProject?.value, only_active: onlyActive, limit: DEFAULT_TABLE_PAGE_SIZE },
49+
args: { ...filteringRequestParams, limit: DEFAULT_TABLE_PAGE_SIZE },
4650
getPaginationParams: (lastRun) => ({ prev_submitted_at: lastRun.submitted_at }),
4751
});
4852

49-
const isDisabledClearFilter = !selectedProject && !onlyActive;
50-
5153
const { stopRuns, isStopping } = useStopRuns();
5254
const { abortRuns, isAborting } = useAbortRuns();
5355
const { deleteRuns, isDeleting } = useDeleteRuns();
5456

5557
const { columns } = useColumnsDefinitions();
5658

57-
const clearFilter = () => {
58-
clearSelected();
59-
setOnlyActive(false);
60-
setSearchParams({});
61-
};
62-
6359
const { renderEmptyMessage, renderNoMatchMessage } = useEmptyMessages({
64-
isDisabledClearFilter,
6560
clearFilter,
6661
});
6762

@@ -143,32 +138,21 @@ export const RunList: React.FC = () => {
143138
}
144139
filter={
145140
<div className={styles.selectFilters}>
146-
<div className={styles.select}>
147-
<FormField label={t('projects.run.project')}>
148-
<SelectCSD
149-
disabled={!projectOptions?.length}
150-
options={projectOptions}
151-
selectedOption={selectedProject}
152-
onChange={(event) => {
153-
setSelectedProject(event.detail.selectedOption);
154-
}}
155-
placeholder={t('projects.run.project_placeholder')}
156-
expandToViewport={true}
157-
filteringType="auto"
158-
/>
159-
</FormField>
160-
</div>
161-
162-
<div className={styles.activeOnly}>
163-
<Toggle onChange={({ detail }) => setOnlyActive(detail.checked)} checked={onlyActive}>
164-
{t('projects.run.active_only')}
165-
</Toggle>
166-
</div>
167-
168-
<div className={styles.clear}>
169-
<Button formAction="none" onClick={clearFilter} disabled={isDisabledClearFilter}>
170-
{t('common.clearFilter')}
171-
</Button>
141+
<div className={styles.propertyFilter}>
142+
<PropertyFilter
143+
query={propertyFilterQuery}
144+
onChange={onChangePropertyFilter}
145+
expandToViewport
146+
hideOperations
147+
i18nStrings={{
148+
clearFiltersText: 'Clear filter',
149+
filteringAriaLabel: 'Find runs',
150+
filteringPlaceholder: 'Find runs',
151+
operationAndText: 'and',
152+
}}
153+
filteringOptions={filteringOptions}
154+
filteringProperties={filteringProperties}
155+
/>
172156
</div>
173157
</div>
174158
}

frontend/src/pages/Runs/List/styles.module.scss

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,13 @@
88
width: var(--select-width, 30%);
99
}
1010

11+
.propertyFilter {
12+
//width: 400px;
13+
//max-width: 100%;
14+
flex-grow: 1;
15+
min-width: 0;
16+
}
17+
1118
.activeOnly {
1219
display: flex;
1320
align-items: center;

0 commit comments

Comments
 (0)