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