Skip to content

Commit d2003dc

Browse files
author
Andrey Cheptsov
committed
Add fleet filters to the Offers UI
1 parent f3686d6 commit d2003dc

13 files changed

Lines changed: 180 additions & 58 deletions

File tree

frontend/src/libs/filters.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,13 @@ export const tokensToSearchParams = <RequestParamsKeys extends string>(
2121

2222
export type RequestParam = string | { min: number } | { max: number };
2323

24+
export const getNamePatternFilterRequestParams = (filteringText: string, limit: number) => {
25+
return {
26+
...(filteringText ? { name_pattern: filteringText } : {}),
27+
limit,
28+
};
29+
};
30+
2431
const convertTokenValueToRequestParam = (token: PropertyFilterProps.Query['tokens'][number]): RequestParam => {
2532
const { value, operator } = token;
2633

frontend/src/pages/Events/List/hooks/useFilters.ts

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,13 @@ import { omit } from 'lodash';
44

55
import type { PropertyFilterProps } from 'components';
66

7-
import { EMPTY_QUERY, requestParamsToTokens, tokensToRequestParams, tokensToSearchParams } from 'libs/filters';
7+
import {
8+
EMPTY_QUERY,
9+
getNamePatternFilterRequestParams,
10+
requestParamsToTokens,
11+
tokensToRequestParams,
12+
tokensToSearchParams,
13+
} from 'libs/filters';
814
import { useLazyGetProjectsQuery } from 'services/project';
915
import { useLazyGetUserListQuery } from 'services/user';
1016

@@ -340,14 +346,10 @@ export const useFilters = ({
340346
const handleLoadItems: PropertyFilterProps['onLoadItems'] = async ({ detail: { filteringProperty, filteringText } }) => {
341347
setDynamicFilteringOptions([]);
342348

343-
if (!filteringText.length) {
344-
return Promise.resolve();
345-
}
346-
347349
setFilteringStatusType('loading');
348350

349351
if (filteringProperty?.key === filterKeys.TARGET_PROJECTS || filteringProperty?.key === filterKeys.WITHIN_PROJECTS) {
350-
await getProjects({ name_pattern: filteringText, limit })
352+
await getProjects(getNamePatternFilterRequestParams(filteringText, limit))
351353
.unwrap()
352354
.then(({ data }) =>
353355
data.map(({ project_name, project_id }) => ({
@@ -361,7 +363,7 @@ export const useFilters = ({
361363
}
362364

363365
if (filteringProperty?.key === filterKeys.TARGET_USERS || filteringProperty?.key === filterKeys.ACTORS) {
364-
await getUsers({ name_pattern: filteringText, limit })
366+
await getUsers(getNamePatternFilterRequestParams(filteringText, limit))
365367
.unwrap()
366368
.then(({ data }) =>
367369
data.map(({ username, id }) => ({

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

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

1010
import { DATE_TIME_FORMAT } from 'consts';
1111
import { useLocalStorageState } from 'hooks';
12-
import { EMPTY_QUERY, requestParamsToTokens, tokensToRequestParams, tokensToSearchParams } from 'libs/filters';
12+
import {
13+
EMPTY_QUERY,
14+
getNamePatternFilterRequestParams,
15+
requestParamsToTokens,
16+
tokensToRequestParams,
17+
tokensToSearchParams,
18+
} from 'libs/filters';
1319
import {
1420
formatFleetBackend,
1521
formatFleetResources,
@@ -190,14 +196,10 @@ export const useFilters = () => {
190196
const handleLoadItems: PropertyFilterProps['onLoadItems'] = async ({ detail: { filteringProperty, filteringText } }) => {
191197
setDynamicFilteringOptions([]);
192198

193-
if (!filteringText.length) {
194-
return Promise.resolve();
195-
}
196-
197199
setFilteringStatusType('loading');
198200

199201
if (filteringProperty?.key === filterKeys.PROJECT_NAME) {
200-
await getProjects({ name_pattern: filteringText, limit })
202+
await getProjects(getNamePatternFilterRequestParams(filteringText, limit))
201203
.unwrap()
202204
.then(({ data }) =>
203205
data.map(({ project_name }) => ({

frontend/src/pages/Instances/List/hooks/useFilters.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,13 @@ import { ToggleProps } from '@cloudscape-design/components';
55
import type { PropertyFilterProps } from 'components';
66

77
import { useLocalStorageState } from 'hooks';
8-
import { EMPTY_QUERY, requestParamsToTokens, tokensToRequestParams, tokensToSearchParams } from 'libs/filters';
8+
import {
9+
EMPTY_QUERY,
10+
getNamePatternFilterRequestParams,
11+
requestParamsToTokens,
12+
tokensToRequestParams,
13+
tokensToSearchParams,
14+
} from 'libs/filters';
915
import { useLazyGetProjectsQuery } from 'services/project';
1016

1117
type RequestParamsKeys = keyof Pick<TInstanceListRequestParams, 'only_active' | 'project_names' | 'fleet_ids'>;
@@ -85,16 +91,10 @@ export const useFilters = () => {
8591
const handleLoadItems: PropertyFilterProps['onLoadItems'] = async ({ detail: { filteringProperty, filteringText } }) => {
8692
setDynamicFilteringOptions([]);
8793

88-
console.log({ filteringProperty, filteringText });
89-
90-
if (!filteringText.length) {
91-
return Promise.resolve();
92-
}
93-
9494
setFilteringStatusType('loading');
9595

9696
if (filteringProperty?.key === filterKeys.PROJECT_NAMES) {
97-
await getProjects({ name_pattern: filteringText, limit })
97+
await getProjects(getNamePatternFilterRequestParams(filteringText, limit))
9898
.unwrap()
9999
.then(({ data }) =>
100100
data.map(({ project_name }) => ({

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

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,13 @@ import type { PropertyFilterProps } from 'components';
77
import { Button, ListEmptyMessage, NavigateLink, TableProps } from 'components';
88

99
import { DATE_TIME_FORMAT } from 'consts';
10-
import { EMPTY_QUERY, requestParamsToTokens, tokensToRequestParams, tokensToSearchParams } from 'libs/filters';
10+
import {
11+
EMPTY_QUERY,
12+
getNamePatternFilterRequestParams,
13+
requestParamsToTokens,
14+
tokensToRequestParams,
15+
tokensToSearchParams,
16+
} from 'libs/filters';
1117
import { ROUTES } from 'routes';
1218
import { useLazyGetProjectsQuery } from 'services/project';
1319
import { useLazyGetUserListQuery } from 'services/user';
@@ -184,14 +190,10 @@ export const useFilters = () => {
184190
const handleLoadItems: PropertyFilterProps['onLoadItems'] = async ({ detail: { filteringProperty, filteringText } }) => {
185191
setFilteringOptions([]);
186192

187-
if (!filteringText.length) {
188-
return Promise.resolve();
189-
}
190-
191193
setFilteringStatusType('loading');
192194

193195
if (filteringProperty?.key === filterKeys.PROJECT_NAME) {
194-
await getProjects({ name_pattern: filteringText, limit })
196+
await getProjects(getNamePatternFilterRequestParams(filteringText, limit))
195197
.unwrap()
196198
.then(({ data }) =>
197199
data.map(({ project_name }) => ({
@@ -203,7 +205,7 @@ export const useFilters = () => {
203205
}
204206

205207
if (filteringProperty?.key === filterKeys.USER_NAME) {
206-
await getUsers({ name_pattern: filteringText, limit })
208+
await getUsers(getNamePatternFilterRequestParams(filteringText, limit))
207209
.unwrap()
208210
.then(({ data }) =>
209211
data.map(({ username }) => ({

frontend/src/pages/Offers/List/helpers.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,14 @@ export const getPropertyFilterOptions = (gpus: IGpu[]) => {
4040
};
4141
};
4242

43+
export const getFleetFilterValue = (fleet: IFleet, selectedProjectName?: string) => {
44+
if (selectedProjectName && fleet.project_name === selectedProjectName) {
45+
return fleet.name;
46+
}
47+
48+
return `${fleet.project_name}/${fleet.name}`;
49+
};
50+
4351
export const round = (number: number) => Math.round(number * 100) / 100;
4452

4553
export const renderRange = (range: { min?: number; max?: number }) => {

frontend/src/pages/Offers/List/hooks/useFilters.ts

Lines changed: 93 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,31 @@ import type { MultiselectProps, PropertyFilterProps } from 'components';
55

66
import {
77
EMPTY_QUERY,
8+
getNamePatternFilterRequestParams,
89
requestParamsToArray,
910
requestParamsToTokens,
1011
tokensToRequestParams,
1112
tokensToSearchParams,
1213
} from 'libs/filters';
14+
import { useLazyGetProjectFleetsQuery } from 'services/fleet';
1315
import { useGetProjectsQuery, useLazyGetProjectsQuery } from 'services/project';
1416

15-
import { getPropertyFilterOptions } from '../helpers';
17+
import { getFleetFilterValue, getPropertyFilterOptions } from '../helpers';
1618

17-
type RequestParamsKeys = 'project_name' | 'gpu_name' | 'gpu_count' | 'gpu_memory' | 'backend' | 'spot_policy' | 'group_by';
19+
type RequestParamsKeys =
20+
| 'project_name'
21+
| 'gpu_name'
22+
| 'gpu_count'
23+
| 'gpu_memory'
24+
| 'backend'
25+
| 'fleet'
26+
| 'spot_policy'
27+
| 'group_by';
1828

1929
export type UseFiltersArgs = {
2030
gpus: IGpu[];
2131
withSearchParams?: boolean;
32+
showFleetFilter?: boolean;
2233
permanentFilters?: Partial<Record<RequestParamsKeys, string>>;
2334
defaultFilters?: Partial<Record<RequestParamsKeys, string | string[]>>;
2435
};
@@ -29,10 +40,11 @@ export const filterKeys: Record<string, RequestParamsKeys> = {
2940
GPU_COUNT: 'gpu_count',
3041
GPU_MEMORY: 'gpu_memory',
3142
BACKEND: 'backend',
43+
FLEET: 'fleet',
3244
SPOT_POLICY: 'spot_policy',
3345
};
3446

35-
const multipleChoiceKeys: RequestParamsKeys[] = ['gpu_name', 'backend'];
47+
const multipleChoiceKeys: RequestParamsKeys[] = ['gpu_name', 'backend', 'fleet'];
3648

3749
const spotPolicyOptions = [
3850
{
@@ -80,6 +92,12 @@ const filteringProperties = [
8092
propertyLabel: 'Backend',
8193
groupValuesLabel: 'Backend values',
8294
},
95+
{
96+
key: filterKeys.FLEET,
97+
operators: ['='],
98+
propertyLabel: 'Fleet',
99+
groupValuesLabel: 'Fleet values',
100+
},
83101
{
84102
key: filterKeys.SPOT_POLICY,
85103
operators: ['='],
@@ -93,22 +111,40 @@ const defaultGroupByOptions = [{ ...gpuFilterOption }, { label: 'Backend', value
93111
const groupByRequestParamName: RequestParamsKeys = 'group_by';
94112
const limit = 100;
95113

96-
export const useFilters = ({ gpus, withSearchParams = true, permanentFilters = {}, defaultFilters }: UseFiltersArgs) => {
114+
export const useFilters = ({
115+
gpus,
116+
withSearchParams = true,
117+
showFleetFilter = false,
118+
permanentFilters = {},
119+
defaultFilters,
120+
}: UseFiltersArgs) => {
97121
const [searchParams, setSearchParams] = useSearchParams();
98122
const [dynamicFilteringOptions, setDynamicFilteringOptions] = useState<PropertyFilterProps.FilteringOption[]>([]);
99123
const [filteringStatusType, setFilteringStatusType] = useState<PropertyFilterProps.StatusType | undefined>();
100124
const [getProjects] = useLazyGetProjectsQuery();
125+
const [getProjectFleets] = useLazyGetProjectFleetsQuery();
101126
const { data: projectsData } = useGetProjectsQuery({ limit: 1 });
102127
const projectNameIsChecked = useRef(false);
128+
const prevSelectedProjectName = useRef<string | undefined>();
103129

104130
const [propertyFilterQuery, setPropertyFilterQuery] = useState<PropertyFilterProps.Query>(() => {
105131
const queryFromSearchParams = requestParamsToTokens<RequestParamsKeys>({
106132
searchParams,
107133
filterKeys,
108134
defaultFilterValues: defaultFilters,
109135
});
110-
if (queryFromSearchParams.tokens.length > 0) {
111-
return queryFromSearchParams;
136+
137+
const tokens = showFleetFilter
138+
? queryFromSearchParams.tokens
139+
: queryFromSearchParams.tokens.filter((token) => token.propertyKey !== filterKeys.FLEET);
140+
141+
const query = {
142+
...queryFromSearchParams,
143+
tokens,
144+
};
145+
146+
if (query.tokens.length > 0) {
147+
return query;
112148
}
113149

114150
return EMPTY_QUERY;
@@ -179,8 +215,14 @@ export const useFilters = ({ gpus, withSearchParams = true, permanentFilters = {
179215

180216
const filteringPropertiesForShowing = useMemo(() => {
181217
const permanentFilterKeys = Object.keys(permanentFilters);
182-
return filteringProperties.filter(({ key }) => !permanentFilterKeys.includes(key));
183-
}, [permanentFilters]);
218+
return filteringProperties.filter(({ key }) => {
219+
if (key === filterKeys.FLEET && !showFleetFilter) {
220+
return false;
221+
}
222+
223+
return !permanentFilterKeys.includes(key);
224+
});
225+
}, [permanentFilters, showFleetFilter]);
184226

185227
const setSearchParamsHandle = ({
186228
tokens,
@@ -252,17 +294,19 @@ export const useFilters = ({ gpus, withSearchParams = true, permanentFilters = {
252294
};
253295
}, [propertyFilterQuery, permanentFilters]);
254296

297+
const selectedProjectName = useMemo(() => {
298+
const projectName = filteringRequestParams['project_name'];
299+
300+
return typeof projectName === 'string' ? projectName : undefined;
301+
}, [filteringRequestParams]);
302+
255303
const handleLoadItems: PropertyFilterProps['onLoadItems'] = async ({ detail: { filteringProperty, filteringText } }) => {
256304
setDynamicFilteringOptions([]);
257305

258-
if (!filteringText.length) {
259-
return Promise.resolve();
260-
}
261-
262306
setFilteringStatusType('loading');
263307

264308
if (filteringProperty?.key === filterKeys.PROJECT_NAME) {
265-
await getProjects({ name_pattern: filteringText, limit })
309+
await getProjects(getNamePatternFilterRequestParams(filteringText, limit))
266310
.unwrap()
267311
.then(({ data }) =>
268312
data.map(({ project_name }) => ({
@@ -273,6 +317,24 @@ export const useFilters = ({ gpus, withSearchParams = true, permanentFilters = {
273317
.then(setDynamicFilteringOptions);
274318
}
275319

320+
if (showFleetFilter && filteringProperty?.key === filterKeys.FLEET && selectedProjectName) {
321+
await getProjectFleets({
322+
projectName: selectedProjectName,
323+
includeImported: true,
324+
})
325+
.unwrap()
326+
.then((fleets) =>
327+
fleets
328+
.map((fleet) => ({
329+
propertyKey: filterKeys.FLEET,
330+
value: getFleetFilterValue(fleet, selectedProjectName),
331+
}))
332+
.filter(({ value }) => value.toLowerCase().includes(filteringText.toLowerCase()))
333+
.slice(0, limit),
334+
)
335+
.then(setDynamicFilteringOptions);
336+
}
337+
276338
setFilteringStatusType(undefined);
277339
};
278340

@@ -296,6 +358,24 @@ export const useFilters = ({ gpus, withSearchParams = true, permanentFilters = {
296358
}
297359
}, [projectsData]);
298360

361+
useEffect(() => {
362+
const prevProjectName = prevSelectedProjectName.current;
363+
prevSelectedProjectName.current = selectedProjectName;
364+
365+
if (!showFleetFilter || prevProjectName === selectedProjectName) {
366+
return;
367+
}
368+
369+
if (!propertyFilterQuery.tokens.some((token) => token.propertyKey === filterKeys.FLEET)) {
370+
return;
371+
}
372+
373+
onChangePropertyFilterHandle({
374+
tokens: propertyFilterQuery.tokens.filter((token) => token.propertyKey !== filterKeys.FLEET),
375+
operation: propertyFilterQuery.operation,
376+
});
377+
}, [propertyFilterQuery, selectedProjectName, showFleetFilter]);
378+
299379
return {
300380
filteringRequestParams,
301381
clearFilter,

0 commit comments

Comments
 (0)