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