19231: Add smaller page sizes (10 and 20 items) to load faster
[arvados-workbench2.git] / src / store / resource-type-filters / resource-type-filters.ts
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 import { difference, pipe, values, includes, __ } from 'lodash/fp';
6 import { createTree, setNode, TreeNodeStatus, TreeNode, Tree } from 'models/tree';
7 import { DataTableFilterItem, DataTableFilters } from 'components/data-table-filters/data-table-filters-tree';
8 import { ResourceKind } from 'models/resource';
9 import { FilterBuilder } from 'services/api/filter-builder';
10 import { getSelectedNodes } from 'models/tree';
11 import { CollectionType } from 'models/collection';
12 import { GroupContentsResourcePrefix } from 'services/groups-service/groups-service';
13 import { ContainerState } from 'models/container';
14 import { ContainerRequestState } from 'models/container-request';
15
16 export enum ProcessStatusFilter {
17     ALL = 'All',
18     RUNNING = 'Running',
19     FAILED = 'Failed',
20     COMPLETED = 'Completed',
21     CANCELLED = 'Cancelled',
22     ONHOLD = 'On hold',
23     QUEUED = 'Queued'
24 }
25
26 export enum ObjectTypeFilter {
27     PROJECT = 'Project',
28     WORKFLOW = 'Workflow',
29     COLLECTION = 'Data collection',
30     DEFINITION = 'Definition',
31 }
32
33 export enum GroupTypeFilter {
34     PROJECT = 'Project (normal)',
35     FILTER_GROUP = 'Filter group',
36 }
37
38 export enum CollectionTypeFilter {
39     GENERAL_COLLECTION = 'General',
40     OUTPUT_COLLECTION = 'Output',
41     LOG_COLLECTION = 'Log',
42     INTERMEDIATE_COLLECTION = 'Intermediate',
43 }
44
45 export enum ProcessTypeFilter {
46     MAIN_PROCESS = 'Runs',
47     CHILD_PROCESS = 'Intermediate Steps',
48 }
49
50 const initFilter = (name: string, parent = '', isSelected?: boolean, isExpanded?: boolean) =>
51     setNode<DataTableFilterItem>({
52         id: name,
53         value: { name },
54         parent,
55         children: [],
56         active: false,
57         selected: isSelected !== undefined ? isSelected : true,
58         initialState: isSelected !== undefined ? isSelected : true,
59         expanded: isExpanded !== undefined ? isExpanded : false,
60         status: TreeNodeStatus.LOADED,
61     });
62
63 export const getSimpleObjectTypeFilters = pipe(
64     (): DataTableFilters => createTree<DataTableFilterItem>(),
65     initFilter(ObjectTypeFilter.PROJECT),
66     initFilter(ObjectTypeFilter.WORKFLOW),
67     initFilter(ObjectTypeFilter.COLLECTION),
68     initFilter(ObjectTypeFilter.DEFINITION),
69 );
70
71 // Using pipe() with more than 7 arguments makes the return type be 'any',
72 // causing compile issues.
73 export const getInitialResourceTypeFilters = pipe(
74     (): DataTableFilters => createTree<DataTableFilterItem>(),
75     pipe(
76         initFilter(ObjectTypeFilter.PROJECT, '', true, true),
77         initFilter(GroupTypeFilter.PROJECT, ObjectTypeFilter.PROJECT),
78         initFilter(GroupTypeFilter.FILTER_GROUP, ObjectTypeFilter.PROJECT),
79     ),
80     pipe(
81         initFilter(ObjectTypeFilter.WORKFLOW, '', false, true),
82         initFilter(ObjectTypeFilter.DEFINITION, ObjectTypeFilter.WORKFLOW),
83         initFilter(ProcessTypeFilter.MAIN_PROCESS, ObjectTypeFilter.WORKFLOW),
84         initFilter(ProcessTypeFilter.CHILD_PROCESS, ObjectTypeFilter.WORKFLOW, false),
85     ),
86     pipe(
87         initFilter(ObjectTypeFilter.COLLECTION, '', true, true),
88         initFilter(CollectionTypeFilter.GENERAL_COLLECTION, ObjectTypeFilter.COLLECTION),
89         initFilter(CollectionTypeFilter.OUTPUT_COLLECTION, ObjectTypeFilter.COLLECTION),
90         initFilter(CollectionTypeFilter.INTERMEDIATE_COLLECTION, ObjectTypeFilter.COLLECTION),
91         initFilter(CollectionTypeFilter.LOG_COLLECTION, ObjectTypeFilter.COLLECTION),
92     ),
93
94 );
95
96 export const getInitialProcessTypeFilters = pipe(
97     (): DataTableFilters => createTree<DataTableFilterItem>(),
98     initFilter(ProcessTypeFilter.MAIN_PROCESS),
99     initFilter(ProcessTypeFilter.CHILD_PROCESS, '', false)
100 );
101
102 export const getInitialProcessStatusFilters = pipe(
103     (): DataTableFilters => createTree<DataTableFilterItem>(),
104     pipe(
105         initFilter(ProcessStatusFilter.ALL, '', true),
106         initFilter(ProcessStatusFilter.ONHOLD, '', false),
107         initFilter(ProcessStatusFilter.QUEUED, '', false),
108         initFilter(ProcessStatusFilter.RUNNING, '', false),
109         initFilter(ProcessStatusFilter.COMPLETED, '', false),
110         initFilter(ProcessStatusFilter.CANCELLED, '', false),
111         initFilter(ProcessStatusFilter.FAILED, '', false),
112     ),
113 );
114
115 export const getTrashPanelTypeFilters = pipe(
116     (): DataTableFilters => createTree<DataTableFilterItem>(),
117     initFilter(ObjectTypeFilter.PROJECT),
118     initFilter(ObjectTypeFilter.COLLECTION),
119     initFilter(CollectionTypeFilter.GENERAL_COLLECTION, ObjectTypeFilter.COLLECTION),
120     initFilter(CollectionTypeFilter.OUTPUT_COLLECTION, ObjectTypeFilter.COLLECTION),
121     initFilter(CollectionTypeFilter.INTERMEDIATE_COLLECTION, ObjectTypeFilter.COLLECTION),
122     initFilter(CollectionTypeFilter.LOG_COLLECTION, ObjectTypeFilter.COLLECTION),
123 );
124
125 const createFiltersBuilder = (filters: DataTableFilters) =>
126     ({ fb: new FilterBuilder(), selectedFilters: getSelectedNodes(filters) });
127
128 const getMatchingFilters = (values: string[], filters: TreeNode<DataTableFilterItem>[]) =>
129     filters
130         .map(f => f.id)
131         .filter(includes(__, values));
132
133 const objectTypeToResourceKind = (type: ObjectTypeFilter) => {
134     switch (type) {
135         case ObjectTypeFilter.PROJECT:
136             return ResourceKind.PROJECT;
137         case ObjectTypeFilter.WORKFLOW:
138             return ResourceKind.PROCESS;
139         case ObjectTypeFilter.COLLECTION:
140             return ResourceKind.COLLECTION;
141         case ObjectTypeFilter.DEFINITION:
142             return ResourceKind.WORKFLOW;
143     }
144 };
145
146 const serializeObjectTypeFilters = ({ fb, selectedFilters }: ReturnType<typeof createFiltersBuilder>) => {
147     const groupFilters = getMatchingFilters(values(GroupTypeFilter), selectedFilters);
148     const collectionFilters = getMatchingFilters(values(CollectionTypeFilter), selectedFilters);
149     const processFilters = getMatchingFilters(values(ProcessTypeFilter), selectedFilters);
150     const typeFilters = pipe(
151         () => new Set(getMatchingFilters(values(ObjectTypeFilter), selectedFilters)),
152         set => groupFilters.length > 0
153             ? set.add(ObjectTypeFilter.PROJECT)
154             : set,
155         set => collectionFilters.length > 0
156             ? set.add(ObjectTypeFilter.COLLECTION)
157             : set,
158         set => processFilters.length > 0
159             ? set.add(ObjectTypeFilter.WORKFLOW)
160             : set,
161         set => Array.from(set)
162     )();
163
164     return {
165         fb: typeFilters.length > 0
166             ? fb.addIsA('uuid', typeFilters.map(objectTypeToResourceKind))
167             : fb.addIsA('uuid', ResourceKind.NONE),
168         selectedFilters,
169     };
170 };
171
172 const collectionTypeToPropertyValue = (type: CollectionTypeFilter) => {
173     switch (type) {
174         case CollectionTypeFilter.GENERAL_COLLECTION:
175             return CollectionType.GENERAL;
176         case CollectionTypeFilter.OUTPUT_COLLECTION:
177             return CollectionType.OUTPUT;
178         case CollectionTypeFilter.LOG_COLLECTION:
179             return CollectionType.LOG;
180         case CollectionTypeFilter.INTERMEDIATE_COLLECTION:
181             return CollectionType.INTERMEDIATE;
182         default:
183             return CollectionType.GENERAL;
184     }
185 };
186
187 const serializeCollectionTypeFilters = ({ fb, selectedFilters }: ReturnType<typeof createFiltersBuilder>) => pipe(
188     () => getMatchingFilters(values(CollectionTypeFilter), selectedFilters),
189     filters => filters.map(collectionTypeToPropertyValue),
190     mappedFilters => ({
191         fb: buildCollectionTypeFilters({ fb, filters: mappedFilters }),
192         selectedFilters
193     })
194 )();
195
196 const COLLECTION_TYPES = values(CollectionType);
197
198 const NON_GENERAL_COLLECTION_TYPES = difference(COLLECTION_TYPES, [CollectionType.GENERAL]);
199
200 const COLLECTION_PROPERTIES_PREFIX = `${GroupContentsResourcePrefix.COLLECTION}.properties`;
201
202 const buildCollectionTypeFilters = ({ fb, filters }: { fb: FilterBuilder, filters: CollectionType[] }) => {
203     switch (true) {
204         case filters.length === 0 || filters.length === COLLECTION_TYPES.length:
205             return fb;
206         case includes(CollectionType.GENERAL, filters):
207             return fb.addNotIn('type', difference(NON_GENERAL_COLLECTION_TYPES, filters), COLLECTION_PROPERTIES_PREFIX);
208         default:
209             return fb.addIn('type', filters, COLLECTION_PROPERTIES_PREFIX);
210     }
211 };
212
213 const serializeGroupTypeFilters = ({ fb, selectedFilters }: ReturnType<typeof createFiltersBuilder>) => pipe(
214     () => getMatchingFilters(values(GroupTypeFilter), selectedFilters),
215     filters => filters,
216     mappedFilters => ({
217         fb: buildGroupTypeFilters({ fb, filters: mappedFilters, use_prefix: true }),
218         selectedFilters
219     })
220 )();
221
222 const GROUP_TYPES = values(GroupTypeFilter);
223
224 const buildGroupTypeFilters = ({ fb, filters, use_prefix }: { fb: FilterBuilder, filters: string[], use_prefix: boolean }) => {
225     switch (true) {
226         case filters.length === 0 || filters.length === GROUP_TYPES.length:
227             return fb;
228         case includes(GroupTypeFilter.PROJECT, filters):
229             return fb.addEqual('groups.group_class', 'project');
230         case includes(GroupTypeFilter.FILTER_GROUP, filters):
231             return fb.addEqual('groups.group_class', 'filter');
232         default:
233             return fb;
234     }
235 };
236
237 const serializeProcessTypeFilters = ({ fb, selectedFilters }: ReturnType<typeof createFiltersBuilder>) => pipe(
238     () => getMatchingFilters(values(ProcessTypeFilter), selectedFilters),
239     filters => filters,
240     mappedFilters => ({
241         fb: buildProcessTypeFilters({ fb, filters: mappedFilters, use_prefix: true }),
242         selectedFilters
243     })
244 )();
245
246 const PROCESS_TYPES = values(ProcessTypeFilter);
247 const PROCESS_PREFIX = GroupContentsResourcePrefix.PROCESS;
248
249 const buildProcessTypeFilters = ({ fb, filters, use_prefix }: { fb: FilterBuilder, filters: string[], use_prefix: boolean }) => {
250     switch (true) {
251         case filters.length === 0 || filters.length === PROCESS_TYPES.length:
252             return fb;
253         case includes(ProcessTypeFilter.MAIN_PROCESS, filters):
254             return fb.addEqual('requesting_container_uuid', null, use_prefix ? PROCESS_PREFIX : '');
255         case includes(ProcessTypeFilter.CHILD_PROCESS, filters):
256             return fb.addDistinct('requesting_container_uuid', null, use_prefix ? PROCESS_PREFIX : '');
257         default:
258             return fb;
259     }
260 };
261
262 export const serializeResourceTypeFilters = pipe(
263     createFiltersBuilder,
264     serializeObjectTypeFilters,
265     serializeGroupTypeFilters,
266     serializeCollectionTypeFilters,
267     serializeProcessTypeFilters,
268     ({ fb }) => fb.getFilters(),
269 );
270
271 export const serializeOnlyProcessTypeFilters = pipe(
272     createFiltersBuilder,
273     ({ fb, selectedFilters }: ReturnType<typeof createFiltersBuilder>) => pipe(
274         () => getMatchingFilters(values(ProcessTypeFilter), selectedFilters),
275         filters => filters,
276         mappedFilters => ({
277             fb: buildProcessTypeFilters({ fb, filters: mappedFilters, use_prefix: false }),
278             selectedFilters
279         })
280     )(),
281     ({ fb }) => fb.getFilters(),
282 );
283
284 export const serializeSimpleObjectTypeFilters = (filters: Tree<DataTableFilterItem>) => {
285     return getSelectedNodes(filters)
286         .map(f => f.id)
287         .map(objectTypeToResourceKind);
288 };
289
290 export const buildProcessStatusFilters = (fb: FilterBuilder, activeStatusFilter: string, resourcePrefix?: string): FilterBuilder => {
291     switch (activeStatusFilter) {
292         case ProcessStatusFilter.ONHOLD: {
293             fb.addDistinct('state', ContainerRequestState.FINAL, resourcePrefix);
294             fb.addEqual('priority', '0', resourcePrefix);
295             fb.addIn('container.state', [ContainerState.QUEUED, ContainerState.LOCKED], resourcePrefix);
296             break;
297         }
298         case ProcessStatusFilter.COMPLETED: {
299             fb.addEqual('container.state', ContainerState.COMPLETE, resourcePrefix);
300             fb.addEqual('container.exit_code', '0', resourcePrefix);
301             break;
302         }
303         case ProcessStatusFilter.FAILED: {
304             fb.addEqual('container.state', ContainerState.COMPLETE, resourcePrefix);
305             fb.addDistinct('container.exit_code', '0', resourcePrefix);
306             break;
307         }
308         case ProcessStatusFilter.QUEUED: {
309             fb.addEqual('container.state', ContainerState.QUEUED, resourcePrefix);
310             fb.addDistinct('priority', '0', resourcePrefix);
311             break;
312         }
313         case ProcessStatusFilter.CANCELLED:
314         case ProcessStatusFilter.RUNNING: {
315             fb.addEqual('container.state', activeStatusFilter, resourcePrefix);
316             break;
317         }
318     }
319     return fb;
320 };