21700: Install Bundler system-wide in Rails postinst
[arvados.git] / services / workbench2 / 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(ProcessTypeFilter.MAIN_PROCESS, ObjectTypeFilter.WORKFLOW),
83         initFilter(ProcessTypeFilter.CHILD_PROCESS, ObjectTypeFilter.WORKFLOW, false),
84         initFilter(ObjectTypeFilter.DEFINITION, ObjectTypeFilter.WORKFLOW),
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, false),
91         initFilter(CollectionTypeFilter.LOG_COLLECTION, ObjectTypeFilter.COLLECTION, false),
92     ),
93
94 );
95
96 // Using pipe() with more than 7 arguments makes the return type be 'any',
97 // causing compile issues.
98 export const getInitialSearchTypeFilters = pipe(
99     (): DataTableFilters => createTree<DataTableFilterItem>(),
100     pipe(
101         initFilter(ObjectTypeFilter.PROJECT, '', true, true),
102         initFilter(GroupTypeFilter.PROJECT, ObjectTypeFilter.PROJECT),
103         initFilter(GroupTypeFilter.FILTER_GROUP, ObjectTypeFilter.PROJECT),
104     ),
105     pipe(
106         initFilter(ObjectTypeFilter.WORKFLOW, '', false, true),
107         initFilter(ProcessTypeFilter.MAIN_PROCESS, ObjectTypeFilter.WORKFLOW, false),
108         initFilter(ProcessTypeFilter.CHILD_PROCESS, ObjectTypeFilter.WORKFLOW, false),
109         initFilter(ObjectTypeFilter.DEFINITION, ObjectTypeFilter.WORKFLOW, false),
110     ),
111     pipe(
112         initFilter(ObjectTypeFilter.COLLECTION, '', true, true),
113         initFilter(CollectionTypeFilter.GENERAL_COLLECTION, ObjectTypeFilter.COLLECTION),
114         initFilter(CollectionTypeFilter.OUTPUT_COLLECTION, ObjectTypeFilter.COLLECTION),
115         initFilter(CollectionTypeFilter.INTERMEDIATE_COLLECTION, ObjectTypeFilter.COLLECTION, false),
116         initFilter(CollectionTypeFilter.LOG_COLLECTION, ObjectTypeFilter.COLLECTION, false),
117     ),
118 );
119
120 export const getInitialProcessTypeFilters = pipe(
121     (): DataTableFilters => createTree<DataTableFilterItem>(),
122     initFilter(ProcessTypeFilter.MAIN_PROCESS),
123     initFilter(ProcessTypeFilter.CHILD_PROCESS, '', false)
124 );
125
126 export const getInitialProcessStatusFilters = pipe(
127     (): DataTableFilters => createTree<DataTableFilterItem>(),
128     pipe(
129         initFilter(ProcessStatusFilter.ALL, '', true),
130         initFilter(ProcessStatusFilter.ONHOLD, '', false),
131         initFilter(ProcessStatusFilter.QUEUED, '', false),
132         initFilter(ProcessStatusFilter.RUNNING, '', false),
133         initFilter(ProcessStatusFilter.COMPLETED, '', false),
134         initFilter(ProcessStatusFilter.CANCELLED, '', false),
135         initFilter(ProcessStatusFilter.FAILED, '', false),
136     ),
137 );
138
139 export const getTrashPanelTypeFilters = pipe(
140     (): DataTableFilters => createTree<DataTableFilterItem>(),
141     initFilter(ObjectTypeFilter.PROJECT),
142     initFilter(ObjectTypeFilter.COLLECTION),
143     initFilter(CollectionTypeFilter.GENERAL_COLLECTION, ObjectTypeFilter.COLLECTION),
144     initFilter(CollectionTypeFilter.OUTPUT_COLLECTION, ObjectTypeFilter.COLLECTION),
145     initFilter(CollectionTypeFilter.INTERMEDIATE_COLLECTION, ObjectTypeFilter.COLLECTION),
146     initFilter(CollectionTypeFilter.LOG_COLLECTION, ObjectTypeFilter.COLLECTION),
147 );
148
149 const createFiltersBuilder = (filters: DataTableFilters) =>
150     ({ fb: new FilterBuilder(), selectedFilters: getSelectedNodes(filters) });
151
152 const getMatchingFilters = (values: string[], filters: TreeNode<DataTableFilterItem>[]) =>
153     filters
154         .map(f => f.id)
155         .filter(includes(__, values));
156
157 const objectTypeToResourceKind = (type: ObjectTypeFilter) => {
158     switch (type) {
159         case ObjectTypeFilter.PROJECT:
160             return ResourceKind.PROJECT;
161         case ObjectTypeFilter.WORKFLOW:
162             return ResourceKind.PROCESS;
163         case ObjectTypeFilter.COLLECTION:
164             return ResourceKind.COLLECTION;
165         case ObjectTypeFilter.DEFINITION:
166             return ResourceKind.WORKFLOW;
167     }
168 };
169
170 const serializeObjectTypeFilters = ({ fb, selectedFilters }: ReturnType<typeof createFiltersBuilder>) => {
171     const groupFilters = getMatchingFilters(values(GroupTypeFilter), selectedFilters);
172     const collectionFilters = getMatchingFilters(values(CollectionTypeFilter), selectedFilters);
173     const processFilters = getMatchingFilters(values(ProcessTypeFilter), selectedFilters);
174     const typeFilters = pipe(
175         () => new Set(getMatchingFilters(values(ObjectTypeFilter), selectedFilters)),
176         set => groupFilters.length > 0
177             ? set.add(ObjectTypeFilter.PROJECT)
178             : set,
179         set => collectionFilters.length > 0
180             ? set.add(ObjectTypeFilter.COLLECTION)
181             : set,
182         set => processFilters.length > 0
183             ? set.add(ObjectTypeFilter.WORKFLOW)
184             : set,
185         set => Array.from(set)
186     )();
187
188     return {
189         fb: typeFilters.length > 0
190             ? fb.addIsA('uuid', typeFilters.map(objectTypeToResourceKind))
191             : fb.addIsA('uuid', ResourceKind.NONE),
192         selectedFilters,
193     };
194 };
195
196 const collectionTypeToPropertyValue = (type: CollectionTypeFilter) => {
197     switch (type) {
198         case CollectionTypeFilter.GENERAL_COLLECTION:
199             return CollectionType.GENERAL;
200         case CollectionTypeFilter.OUTPUT_COLLECTION:
201             return CollectionType.OUTPUT;
202         case CollectionTypeFilter.LOG_COLLECTION:
203             return CollectionType.LOG;
204         case CollectionTypeFilter.INTERMEDIATE_COLLECTION:
205             return CollectionType.INTERMEDIATE;
206         default:
207             return CollectionType.GENERAL;
208     }
209 };
210
211 const serializeCollectionTypeFilters = ({ fb, selectedFilters }: ReturnType<typeof createFiltersBuilder>) => pipe(
212     () => getMatchingFilters(values(CollectionTypeFilter), selectedFilters),
213     filters => filters.map(collectionTypeToPropertyValue),
214     mappedFilters => ({
215         fb: buildCollectionTypeFilters({ fb, filters: mappedFilters }),
216         selectedFilters
217     })
218 )();
219
220 const COLLECTION_TYPES = values(CollectionType);
221
222 const NON_GENERAL_COLLECTION_TYPES = difference(COLLECTION_TYPES, [CollectionType.GENERAL]);
223
224 const COLLECTION_PROPERTIES_PREFIX = `${GroupContentsResourcePrefix.COLLECTION}.properties`;
225
226 const buildCollectionTypeFilters = ({ fb, filters }: { fb: FilterBuilder, filters: CollectionType[] }) => {
227     switch (true) {
228         case filters.length === 0 || filters.length === COLLECTION_TYPES.length:
229             return fb;
230         case includes(CollectionType.GENERAL, filters):
231             return fb.addNotIn('type', difference(NON_GENERAL_COLLECTION_TYPES, filters), COLLECTION_PROPERTIES_PREFIX);
232         default:
233             return fb.addIn('type', filters, COLLECTION_PROPERTIES_PREFIX);
234     }
235 };
236
237 const serializeGroupTypeFilters = ({ fb, selectedFilters }: ReturnType<typeof createFiltersBuilder>) => pipe(
238     () => getMatchingFilters(values(GroupTypeFilter), selectedFilters),
239     filters => filters,
240     mappedFilters => ({
241         fb: buildGroupTypeFilters({ fb, filters: mappedFilters, use_prefix: true }),
242         selectedFilters
243     })
244 )();
245
246 const GROUP_TYPES = values(GroupTypeFilter);
247
248 const buildGroupTypeFilters = ({ fb, filters, use_prefix }: { fb: FilterBuilder, filters: string[], use_prefix: boolean }) => {
249     switch (true) {
250         case filters.length === 0 || filters.length === GROUP_TYPES.length:
251             return fb;
252         case includes(GroupTypeFilter.PROJECT, filters):
253             return fb.addEqual('groups.group_class', 'project');
254         case includes(GroupTypeFilter.FILTER_GROUP, filters):
255             return fb.addEqual('groups.group_class', 'filter');
256         default:
257             return fb;
258     }
259 };
260
261 const serializeProcessTypeFilters = ({ fb, selectedFilters }: ReturnType<typeof createFiltersBuilder>) => pipe(
262     () => getMatchingFilters(values(ProcessTypeFilter), selectedFilters),
263     filters => filters,
264     mappedFilters => ({
265         fb: buildProcessTypeFilters({ fb, filters: mappedFilters, use_prefix: true }),
266         selectedFilters
267     })
268 )();
269
270 const PROCESS_TYPES = values(ProcessTypeFilter);
271 const PROCESS_PREFIX = GroupContentsResourcePrefix.PROCESS;
272
273 const buildProcessTypeFilters = ({ fb, filters, use_prefix }: { fb: FilterBuilder, filters: string[], use_prefix: boolean }) => {
274     switch (true) {
275         case filters.length === 0 || filters.length === PROCESS_TYPES.length:
276             return fb;
277         case includes(ProcessTypeFilter.MAIN_PROCESS, filters):
278             return fb.addEqual('requesting_container_uuid', null, use_prefix ? PROCESS_PREFIX : '');
279         case includes(ProcessTypeFilter.CHILD_PROCESS, filters):
280             return fb.addDistinct('requesting_container_uuid', null, use_prefix ? PROCESS_PREFIX : '');
281         default:
282             return fb;
283     }
284 };
285
286 export const serializeResourceTypeFilters = pipe(
287     createFiltersBuilder,
288     serializeObjectTypeFilters,
289     serializeGroupTypeFilters,
290     serializeCollectionTypeFilters,
291     serializeProcessTypeFilters,
292     ({ fb }) => fb.getFilters(),
293 );
294
295 export const serializeOnlyProcessTypeFilters = pipe(
296     createFiltersBuilder,
297     ({ fb, selectedFilters }: ReturnType<typeof createFiltersBuilder>) => pipe(
298         () => getMatchingFilters(values(ProcessTypeFilter), selectedFilters),
299         filters => filters,
300         mappedFilters => ({
301             fb: buildProcessTypeFilters({ fb, filters: mappedFilters, use_prefix: false }),
302             selectedFilters
303         })
304     )(),
305     ({ fb }) => fb.getFilters(),
306 );
307
308 export const serializeSimpleObjectTypeFilters = (filters: Tree<DataTableFilterItem>) => {
309     return getSelectedNodes(filters)
310         .map(f => f.id)
311         .map(objectTypeToResourceKind);
312 };
313
314 export const buildProcessStatusFilters = (fb: FilterBuilder, activeStatusFilter: string, resourcePrefix?: string): FilterBuilder => {
315     switch (activeStatusFilter) {
316         case ProcessStatusFilter.ONHOLD: {
317             fb.addDistinct('state', ContainerRequestState.FINAL, resourcePrefix);
318             fb.addEqual('priority', '0', resourcePrefix);
319             fb.addIn('container.state', [ContainerState.QUEUED, ContainerState.LOCKED], resourcePrefix);
320             break;
321         }
322         case ProcessStatusFilter.COMPLETED: {
323             fb.addEqual('container.state', ContainerState.COMPLETE, resourcePrefix);
324             fb.addEqual('container.exit_code', '0', resourcePrefix);
325             break;
326         }
327         case ProcessStatusFilter.FAILED: {
328             fb.addEqual('container.state', ContainerState.COMPLETE, resourcePrefix);
329             fb.addDistinct('container.exit_code', '0', resourcePrefix);
330             break;
331         }
332         case ProcessStatusFilter.QUEUED: {
333             fb.addIn('container.state', [ContainerState.QUEUED, ContainerState.LOCKED], resourcePrefix);
334             fb.addDistinct('priority', '0', resourcePrefix);
335             break;
336         }
337         case ProcessStatusFilter.CANCELLED:
338         case ProcessStatusFilter.RUNNING: {
339             fb.addEqual('container.state', activeStatusFilter, resourcePrefix);
340             break;
341         }
342     }
343     return fb;
344 };