Skip to content

Commit b859f53

Browse files
authored
2674 prop filter (#2778)
* [Feature]: Implement property filter on Run list page #2674 * Fix logs background * Added username suggesting #2752
1 parent 6200522 commit b859f53

6 files changed

Lines changed: 57 additions & 34 deletions

File tree

frontend/src/hooks/useInfiniteScroll.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/* eslint-disable @typescript-eslint/no-explicit-any */
22
import { useCallback, useEffect, useLayoutEffect, useRef, useState } from 'react';
3+
import { isEqual } from 'lodash';
34
import { UseLazyQuery /*, UseQueryStateOptions*/ } from '@reduxjs/toolkit/dist/query/react/buildHooks';
45
import { QueryDefinition } from '@reduxjs/toolkit/query';
56

@@ -28,6 +29,9 @@ export const useInfiniteScroll = <DataItem, Args extends InfinityListArgs>({
2829
const lastRequestParams = useRef<TRunsRequestParams | undefined>(undefined);
2930
const [disabledMore, setDisabledMore] = useState(false);
3031
const { limit, ...argsProp } = args;
32+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
33+
// @ts-ignore
34+
const oldArgsProps = useRef<Partial<Args>>(argsProp);
3135

3236
const [getItems, { isLoading, isFetching }] = useLazyQuery({ ...args } as Args);
3337

@@ -53,8 +57,13 @@ export const useInfiniteScroll = <DataItem, Args extends InfinityListArgs>({
5357
};
5458

5559
useEffect(() => {
56-
getEmptyList();
57-
}, Object.values(argsProp));
60+
if (!isEqual(argsProp, oldArgsProps.current)) {
61+
getEmptyList();
62+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
63+
// @ts-ignore
64+
oldArgsProps.current = argsProp;
65+
}
66+
}, [argsProp, oldArgsProps]);
5867

5968
const getMore = async () => {
6069
if (isLoadingRef.current || disabledMore) {

frontend/src/locale/en.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,7 @@
322322
"quickstart_message_text": "Check out the quickstart guide to get started with dstack",
323323
"nomatch_message_title": "No matches",
324324
"nomatch_message_text": "We can't find a match. Try to change project or clear filter",
325+
"filter_property_placeholder": "Filter runs by properties",
325326
"project": "Project",
326327
"project_placeholder": "Filtering by project",
327328
"repo": "Repository",

frontend/src/pages/Runs/Details/Logs/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export const Logs: React.FC<IProps> = ({ className, projectName, runName, jobSub
3737
} else {
3838
terminalInstance.current.options.theme = {
3939
foreground: '#b6bec9',
40-
background: '#0f1b2a',
40+
background: '#161d26',
4141
};
4242
}
4343
}, [appliedTheme]);

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

Lines changed: 30 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
import { useMemo, useState } from 'react';
22
import { useSearchParams } from 'react-router-dom';
3+
import { ToggleProps } from '@cloudscape-design/components';
34

45
import type { PropertyFilterProps } from 'components';
56

67
import { useProjectFilter } from 'hooks/useProjectFilter';
78

9+
import { useGetUserListQuery } from '../../../../services/user';
10+
811
type Args = {
912
localStorePrefix: string;
1013
};
@@ -14,27 +17,34 @@ type RequestParamsKeys = keyof Pick<TRunsRequestParams, 'only_active' | 'project
1417
const FilterKeys: Record<string, RequestParamsKeys> = {
1518
PROJECT_NAME: 'project_name',
1619
USER_NAME: 'username',
17-
ACTIVE: 'only_active',
1820
};
1921

2022
const EMPTY_QUERY: PropertyFilterProps.Query = {
2123
tokens: [],
2224
operation: 'and',
2325
};
2426

25-
const tokensToRequestParams = (tokens: PropertyFilterProps.Query['tokens']) => {
26-
return tokens.reduce((acc, token) => {
27+
const tokensToRequestParams = (tokens: PropertyFilterProps.Query['tokens'], onlyActive?: boolean) => {
28+
const params = tokens.reduce((acc, token) => {
2729
if (token.propertyKey) {
2830
acc[token.propertyKey as RequestParamsKeys] = token.value;
2931
}
3032

3133
return acc;
3234
}, {} as Record<RequestParamsKeys, string>);
35+
36+
if (onlyActive) {
37+
params['only_active'] = 'true';
38+
}
39+
40+
return params;
3341
};
3442

3543
export const useFilters = ({ localStorePrefix }: Args) => {
3644
const [searchParams, setSearchParams] = useSearchParams();
45+
const [onlyActive, setOnlyActive] = useState(() => searchParams.get('only_active') === 'true');
3746
const { projectOptions } = useProjectFilter({ localStorePrefix });
47+
const { data: usersData } = useGetUserListQuery();
3848

3949
const [propertyFilterQuery, setPropertyFilterQuery] = useState<PropertyFilterProps.Query>(() => {
4050
const tokens = [];
@@ -59,6 +69,7 @@ export const useFilters = ({ localStorePrefix }: Args) => {
5969

6070
const clearFilter = () => {
6171
setSearchParams({});
72+
setOnlyActive(false);
6273
setPropertyFilterQuery(EMPTY_QUERY);
6374
};
6475

@@ -73,13 +84,15 @@ export const useFilters = ({ localStorePrefix }: Args) => {
7384
});
7485
});
7586

76-
options.push({
77-
propertyKey: FilterKeys.ACTIVE,
78-
value: 'True',
87+
usersData?.forEach(({ username }) => {
88+
options.push({
89+
propertyKey: FilterKeys.USER_NAME,
90+
value: username,
91+
});
7992
});
8093

8194
return options;
82-
}, [projectOptions]);
95+
}, [projectOptions, usersData]);
8396

8497
const filteringProperties = [
8598
{
@@ -93,12 +106,6 @@ export const useFilters = ({ localStorePrefix }: Args) => {
93106
operators: ['='],
94107
propertyLabel: 'User',
95108
},
96-
{
97-
key: FilterKeys.ACTIVE,
98-
operators: ['='],
99-
propertyLabel: 'Only active',
100-
groupValuesLabel: 'Active values',
101-
},
102109
];
103110

104111
const onChangePropertyFilter: PropertyFilterProps['onChange'] = ({ detail }) => {
@@ -108,20 +115,26 @@ export const useFilters = ({ localStorePrefix }: Args) => {
108115
return !tokens.some((item, index) => token.propertyKey === item.propertyKey && index > tokenIndex);
109116
});
110117

111-
setSearchParams(tokensToRequestParams(filteredTokens));
118+
setSearchParams(tokensToRequestParams(filteredTokens, onlyActive));
112119

113120
setPropertyFilterQuery({
114121
operation,
115122
tokens: filteredTokens,
116123
});
117124
};
118125

126+
const onChangeOnlyActive: ToggleProps['onChange'] = ({ detail }) => {
127+
setOnlyActive(detail.checked);
128+
129+
setSearchParams(tokensToRequestParams(propertyFilterQuery.tokens, detail.checked));
130+
};
131+
119132
const filteringRequestParams = useMemo(() => {
120133
const params = tokensToRequestParams(propertyFilterQuery.tokens);
121134

122135
return {
123136
...params,
124-
only_active: params.only_active === 'True',
137+
only_active: onlyActive,
125138
};
126139
}, [propertyFilterQuery]);
127140

@@ -132,5 +145,7 @@ export const useFilters = ({ localStorePrefix }: Args) => {
132145
onChangePropertyFilter,
133146
filteringOptions,
134147
filteringProperties,
148+
onlyActive,
149+
onChangeOnlyActive,
135150
} as const;
136151
};

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

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React from 'react';
22
import { useTranslation } from 'react-i18next';
33

4-
import { Button, Header, ListEmptyMessage, Loader, PropertyFilter, SpaceBetween, Table } from 'components';
4+
import { Button, Header, Loader, PropertyFilter, SpaceBetween, Table, Toggle } from 'components';
55

66
import { DEFAULT_TABLE_PAGE_SIZE } from 'consts';
77
import { useBreadcrumbs, useCollection, useInfiniteScroll } from 'hooks';
@@ -40,6 +40,8 @@ export const RunList: React.FC = () => {
4040
filteringOptions,
4141
filteringProperties,
4242
filteringRequestParams,
43+
onlyActive,
44+
onChangeOnlyActive,
4345
} = useFilters({
4446
localStorePrefix: 'administration-run-list-page',
4547
});
@@ -154,15 +156,21 @@ export const RunList: React.FC = () => {
154156
expandToViewport
155157
hideOperations
156158
i18nStrings={{
157-
clearFiltersText: 'Clear filter',
158-
filteringAriaLabel: 'Find runs',
159-
filteringPlaceholder: 'Find runs',
159+
clearFiltersText: t('common.clearFilter'),
160+
filteringAriaLabel: t('projects.run.filter_property_placeholder'),
161+
filteringPlaceholder: t('projects.run.filter_property_placeholder'),
160162
operationAndText: 'and',
161163
}}
162164
filteringOptions={filteringOptions}
163165
filteringProperties={filteringProperties}
164166
/>
165167
</div>
168+
169+
<div className={styles.activeOnly}>
170+
<Toggle onChange={onChangeOnlyActive} checked={onlyActive}>
171+
{t('projects.run.active_only')}
172+
</Toggle>
173+
</div>
166174
</div>
167175
}
168176
footer={<Loader show={isLoadingMore} padding={{ vertical: 'm' }} />}

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

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,19 @@
11
.selectFilters {
2-
--select-width: calc((688px - 3 * 20px) / 2);
32
display: flex;
43
flex-wrap: wrap;
54
gap: 0 20px;
65

7-
.select {
8-
width: var(--select-width, 30%);
9-
}
10-
116
.propertyFilter {
12-
//width: 400px;
13-
//max-width: 100%;
7+
max-width: 640px;
148
flex-grow: 1;
159
min-width: 0;
1610
}
1711

1812
.activeOnly {
1913
display: flex;
20-
align-items: center;
21-
padding-top: 26px;
14+
padding-top: 7px;
2215
}
2316

24-
.clear {
25-
padding-top: 26px;
26-
}
2717
}
2818

2919
.emptyMessage {

0 commit comments

Comments
 (0)