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