Skip to content

Commit c5c7edb

Browse files
authored
[Feature]: Property filter on Instances page #2825 (#2826)
1 parent 9894371 commit c5c7edb

File tree

10 files changed

+186
-123
lines changed

10 files changed

+186
-123
lines changed

frontend/src/libs/filters.ts

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,54 @@
11
import type { PropertyFilterProps } from 'components';
22

3-
export const tokensToRequestParams = <RequestParamsKeys extends string>(
3+
export const tokensToSearchParams = <RequestParamsKeys extends string>(
44
tokens: PropertyFilterProps.Query['tokens'],
55
onlyActive?: boolean,
66
) => {
7-
const params: Record<RequestParamsKeys | 'only_active', string> = tokens.reduce((acc, token) => {
7+
const params = new URLSearchParams();
8+
9+
tokens.forEach((token) => {
810
if (token.propertyKey) {
9-
acc[token.propertyKey as RequestParamsKeys] = token.value;
11+
params.append(token.propertyKey as RequestParamsKeys, token.value);
1012
}
11-
12-
return acc;
13-
}, {} as Record<RequestParamsKeys | 'only_active', string>);
13+
});
1414

1515
if (onlyActive) {
16-
params['only_active'] = 'true';
16+
params.append('only_active', 'true');
1717
}
1818

1919
return params;
2020
};
2121

22+
export const tokensToRequestParams = <RequestParamsKeys extends string>({
23+
tokens,
24+
arrayFieldKeys,
25+
}: {
26+
tokens: PropertyFilterProps.Query['tokens'];
27+
arrayFieldKeys?: RequestParamsKeys[];
28+
}) => {
29+
return tokens.reduce<Record<RequestParamsKeys, string | string[]>>((acc, token) => {
30+
const propertyKey = token.propertyKey as RequestParamsKeys;
31+
32+
if (!propertyKey) {
33+
return acc;
34+
}
35+
36+
if (arrayFieldKeys?.includes(propertyKey)) {
37+
if (Array.isArray(acc[propertyKey])) {
38+
acc[propertyKey].push(token.value);
39+
} else {
40+
acc[propertyKey] = [token.value];
41+
}
42+
43+
return acc;
44+
}
45+
46+
acc[propertyKey] = token.value;
47+
48+
return acc;
49+
}, {} as Record<RequestParamsKeys, string>);
50+
};
51+
2252
export const EMPTY_QUERY: PropertyFilterProps.Query = {
2353
tokens: [],
2454
operation: 'and',

frontend/src/pages/Fleets/Details/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ export const FleetDetails: React.FC = () => {
135135
<Box variant="awsui-key-label">{t('fleets.instances.title')}</Box>
136136

137137
<div>
138-
<NavigateLink href={ROUTES.INSTANCES.LIST + `?fleetId=${data.id}`}>
138+
<NavigateLink href={ROUTES.INSTANCES.LIST + `?fleet_ids=${data.id}`}>
139139
{getFleetInstancesLinkText(data)}
140140
</NavigateLink>
141141
</div>

frontend/src/pages/Fleets/List/hooks.tsx

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { Button, ListEmptyMessage, NavigateLink, StatusIndicator, TableProps } f
99

1010
import { DATE_TIME_FORMAT } from 'consts';
1111
import { useProjectFilter } from 'hooks/useProjectFilter';
12-
import { EMPTY_QUERY, requestParamsToTokens, tokensToRequestParams } from 'libs/filters';
12+
import { EMPTY_QUERY, requestParamsToTokens, tokensToRequestParams, tokensToSearchParams } from 'libs/filters';
1313
import { getFleetInstancesLinkText, getFleetPrice, getFleetStatusIconType } from 'libs/fleet';
1414
import { ROUTES } from 'routes';
1515

@@ -76,7 +76,7 @@ export const useColumnsDefinitions = () => {
7676
id: 'instances',
7777
header: t('fleets.instances.title'),
7878
cell: (item) => (
79-
<NavigateLink href={ROUTES.INSTANCES.LIST + `?fleetId=${item.id}`}>
79+
<NavigateLink href={ROUTES.INSTANCES.LIST + `?fleet_ids=${item.id}`}>
8080
{getFleetInstancesLinkText(item)}
8181
</NavigateLink>
8282
),
@@ -153,7 +153,7 @@ export const useFilters = (localStorePrefix = 'fleet-list-page') => {
153153
return !tokens.some((item, index) => token.propertyKey === item.propertyKey && index > tokenIndex);
154154
});
155155

156-
setSearchParams(tokensToRequestParams<RequestParamsKeys>(filteredTokens, onlyActive));
156+
setSearchParams(tokensToSearchParams<RequestParamsKeys>(filteredTokens, onlyActive));
157157

158158
setPropertyFilterQuery({
159159
operation,
@@ -164,16 +164,18 @@ export const useFilters = (localStorePrefix = 'fleet-list-page') => {
164164
const onChangeOnlyActive: ToggleProps['onChange'] = ({ detail }) => {
165165
setOnlyActive(detail.checked);
166166

167-
setSearchParams(tokensToRequestParams<RequestParamsKeys>(propertyFilterQuery.tokens, detail.checked));
167+
setSearchParams(tokensToSearchParams<RequestParamsKeys>(propertyFilterQuery.tokens, detail.checked));
168168
};
169169

170170
const filteringRequestParams = useMemo(() => {
171-
const params = tokensToRequestParams<RequestParamsKeys>(propertyFilterQuery.tokens);
171+
const params = tokensToRequestParams<RequestParamsKeys>({
172+
tokens: propertyFilterQuery.tokens,
173+
});
172174

173175
return {
174176
...params,
175177
only_active: onlyActive,
176-
};
178+
} as Partial<TFleetListRequestParams>;
177179
}, [propertyFilterQuery, onlyActive]);
178180

179181
const isDisabledClearFilter = !propertyFilterQuery.tokens.length && !onlyActive;

frontend/src/pages/Instances/List/hooks/useEmptyMessage.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@ import { useTranslation } from 'react-i18next';
44
import { Button, ListEmptyMessage } from 'components';
55

66
export const useEmptyMessages = ({
7-
clearFilters,
7+
clearFilter,
88
isDisabledClearFilter,
99
}: {
10-
clearFilters?: () => void;
10+
clearFilter?: () => void;
1111
isDisabledClearFilter?: boolean;
1212
}) => {
1313
const { t } = useTranslation();
@@ -18,25 +18,25 @@ export const useEmptyMessages = ({
1818
title={t('fleets.instances.empty_message_title')}
1919
message={t('fleets.instances.empty_message_text')}
2020
>
21-
<Button disabled={isDisabledClearFilter} onClick={clearFilters}>
21+
<Button disabled={isDisabledClearFilter} onClick={clearFilter}>
2222
{t('common.clearFilter')}
2323
</Button>
2424
</ListEmptyMessage>
2525
);
26-
}, [clearFilters, isDisabledClearFilter]);
26+
}, [clearFilter, isDisabledClearFilter]);
2727

2828
const renderNoMatchMessage = useCallback<() => React.ReactNode>(() => {
2929
return (
3030
<ListEmptyMessage
3131
title={t('fleets.instances.nomatch_message_title')}
3232
message={t('fleets.instances.nomatch_message_text')}
3333
>
34-
<Button disabled={isDisabledClearFilter} onClick={clearFilters}>
34+
<Button disabled={isDisabledClearFilter} onClick={clearFilter}>
3535
{t('common.clearFilter')}
3636
</Button>
3737
</ListEmptyMessage>
3838
);
39-
}, [clearFilters, isDisabledClearFilter]);
39+
}, [clearFilter, isDisabledClearFilter]);
4040

4141
return { renderEmptyMessage, renderNoMatchMessage } as const;
4242
};
Lines changed: 83 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,104 @@
1-
import { useMemo } from 'react';
1+
import { useMemo, useState } from 'react';
22
import { useSearchParams } from 'react-router-dom';
3+
import { ToggleProps } from '@cloudscape-design/components';
4+
5+
import type { PropertyFilterProps } from 'components';
36

4-
import { useLocalStorageState } from 'hooks/useLocalStorageState';
57
import { useProjectFilter } from 'hooks/useProjectFilter';
8+
import { EMPTY_QUERY, requestParamsToTokens, tokensToRequestParams, tokensToSearchParams } from 'libs/filters';
9+
10+
type RequestParamsKeys = keyof Pick<TInstanceListRequestParams, 'only_active' | 'project_names' | 'fleet_ids'>;
11+
12+
const filterKeys: Record<string, RequestParamsKeys> = {
13+
PROJECT_NAMES: 'project_names',
14+
FLEET_IDS: 'fleet_ids',
15+
};
616

717
export const useFilters = (localStorePrefix = 'instances-list-page') => {
818
const [searchParams, setSearchParams] = useSearchParams();
9-
const [onlyActive, setOnlyActive] = useLocalStorageState<boolean>(`${localStorePrefix}-is-active`, false);
10-
const { selectedProject, setSelectedProject, projectOptions } = useProjectFilter({ localStorePrefix });
19+
const [onlyActive, setOnlyActive] = useState(() => searchParams.get('only_active') === 'true');
20+
const { projectOptions } = useProjectFilter({ localStorePrefix });
21+
22+
const [propertyFilterQuery, setPropertyFilterQuery] = useState<PropertyFilterProps.Query>(() => {
23+
console.log(requestParamsToTokens<RequestParamsKeys>({ searchParams, filterKeys }));
1124

12-
const clearFilters = () => {
25+
return requestParamsToTokens<RequestParamsKeys>({ searchParams, filterKeys });
26+
});
27+
28+
const clearFilter = () => {
29+
setSearchParams({});
1330
setOnlyActive(false);
14-
setSelectedProject(null);
31+
setPropertyFilterQuery(EMPTY_QUERY);
32+
};
1533

16-
setSearchParams((prev) => {
17-
prev.delete('fleetId');
18-
return prev;
34+
const filteringOptions = useMemo(() => {
35+
const options: PropertyFilterProps.FilteringOption[] = [];
36+
37+
projectOptions.forEach(({ value }) => {
38+
if (value)
39+
options.push({
40+
propertyKey: filterKeys.PROJECT_NAMES,
41+
value,
42+
});
43+
});
44+
45+
return options;
46+
}, [projectOptions]);
47+
48+
const filteringProperties = [
49+
{
50+
key: filterKeys.PROJECT_NAMES,
51+
operators: ['='],
52+
propertyLabel: 'Project',
53+
groupValuesLabel: 'Project values',
54+
},
55+
{
56+
key: filterKeys.FLEET_IDS,
57+
operators: ['='],
58+
propertyLabel: 'Fleet ID',
59+
},
60+
];
61+
62+
const onChangePropertyFilter: PropertyFilterProps['onChange'] = ({ detail }) => {
63+
const { tokens, operation } = detail;
64+
65+
setSearchParams(tokensToSearchParams<RequestParamsKeys>(tokens, onlyActive));
66+
67+
setPropertyFilterQuery({
68+
operation,
69+
tokens,
1970
});
2071
};
2172

22-
const selectedFleet = useMemo(() => {
23-
const fleetName = searchParams.get('fleetId');
73+
const onChangeOnlyActive: ToggleProps['onChange'] = ({ detail }) => {
74+
setOnlyActive(detail.checked);
75+
76+
setSearchParams(tokensToSearchParams<RequestParamsKeys>(propertyFilterQuery.tokens, detail.checked));
77+
};
2478

25-
if (fleetName) {
26-
return { label: fleetName, value: fleetName };
27-
}
79+
const filteringRequestParams = useMemo(() => {
80+
const params = tokensToRequestParams<RequestParamsKeys>({
81+
tokens: propertyFilterQuery.tokens,
82+
arrayFieldKeys: [filterKeys.PROJECT_NAMES, filterKeys.FLEET_IDS],
83+
});
2884

29-
return null;
30-
}, [searchParams]);
85+
return {
86+
...params,
87+
only_active: onlyActive,
88+
} as Partial<TInstanceListRequestParams>;
89+
}, [propertyFilterQuery, onlyActive]);
3190

32-
const isDisabledClearFilter = !selectedProject && !onlyActive && !selectedFleet;
91+
const isDisabledClearFilter = !propertyFilterQuery.tokens.length && !onlyActive;
3392

3493
return {
35-
projectOptions,
36-
selectedProject,
37-
setSelectedProject,
38-
selectedFleet,
94+
filteringRequestParams,
95+
clearFilter,
96+
propertyFilterQuery,
97+
onChangePropertyFilter,
98+
filteringOptions,
99+
filteringProperties,
39100
onlyActive,
40-
setOnlyActive,
41-
clearFilters,
101+
onChangeOnlyActive,
42102
isDisabledClearFilter,
43103
} as const;
44104
};

0 commit comments

Comments
 (0)