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