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