1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
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';
16 export enum ProcessStatusFilter {
20 COMPLETED = 'Completed',
21 CANCELLED = 'Cancelled',
26 export enum ObjectTypeFilter {
28 WORKFLOW = 'Workflow',
29 COLLECTION = 'Data collection',
30 DEFINITION = 'Definition',
33 export enum GroupTypeFilter {
34 PROJECT = 'Project (normal)',
35 FILTER_GROUP = 'Filter group',
38 export enum CollectionTypeFilter {
39 GENERAL_COLLECTION = 'General',
40 OUTPUT_COLLECTION = 'Output',
41 LOG_COLLECTION = 'Log',
42 INTERMEDIATE_COLLECTION = 'Intermediate',
45 export enum ProcessTypeFilter {
46 MAIN_PROCESS = 'Workflow Runs',
47 CHILD_PROCESS = 'Workflow Steps',
50 const initFilter = (name: string, parent = '', isSelected?: boolean, isExpanded?: boolean) =>
51 setNode<DataTableFilterItem>({
57 selected: isSelected !== undefined ? isSelected : true,
58 initialState: isSelected !== undefined ? isSelected : true,
59 expanded: isExpanded !== undefined ? isExpanded : false,
60 status: TreeNodeStatus.LOADED,
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),
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>(),
76 initFilter(ObjectTypeFilter.PROJECT, '', true, true),
77 initFilter(GroupTypeFilter.PROJECT, ObjectTypeFilter.PROJECT),
78 initFilter(GroupTypeFilter.FILTER_GROUP, ObjectTypeFilter.PROJECT),
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),
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),
97 * Resource type filters for Data tab (excludes main/sub process runs)
99 export const getInitialDataResourceTypeFilters = pipe(
100 (): DataTableFilters => createTree<DataTableFilterItem>(),
102 initFilter(ObjectTypeFilter.PROJECT, '', true, true),
103 initFilter(GroupTypeFilter.PROJECT, ObjectTypeFilter.PROJECT),
104 initFilter(GroupTypeFilter.FILTER_GROUP, ObjectTypeFilter.PROJECT),
107 initFilter(ObjectTypeFilter.WORKFLOW, '', true, true),
108 initFilter(ObjectTypeFilter.DEFINITION, ObjectTypeFilter.WORKFLOW),
111 initFilter(ObjectTypeFilter.COLLECTION, '', true, true),
112 initFilter(CollectionTypeFilter.GENERAL_COLLECTION, ObjectTypeFilter.COLLECTION),
113 initFilter(CollectionTypeFilter.OUTPUT_COLLECTION, ObjectTypeFilter.COLLECTION),
114 initFilter(CollectionTypeFilter.INTERMEDIATE_COLLECTION, ObjectTypeFilter.COLLECTION, false),
115 initFilter(CollectionTypeFilter.LOG_COLLECTION, ObjectTypeFilter.COLLECTION, false),
120 // Using pipe() with more than 7 arguments makes the return type be 'any',
121 // causing compile issues.
122 export const getInitialSearchTypeFilters = pipe(
123 (): DataTableFilters => createTree<DataTableFilterItem>(),
125 initFilter(ObjectTypeFilter.PROJECT, '', true, true),
126 initFilter(GroupTypeFilter.PROJECT, ObjectTypeFilter.PROJECT),
127 initFilter(GroupTypeFilter.FILTER_GROUP, ObjectTypeFilter.PROJECT),
130 initFilter(ObjectTypeFilter.WORKFLOW, '', false, true),
131 initFilter(ProcessTypeFilter.MAIN_PROCESS, ObjectTypeFilter.WORKFLOW, false),
132 initFilter(ProcessTypeFilter.CHILD_PROCESS, ObjectTypeFilter.WORKFLOW, false),
133 initFilter(ObjectTypeFilter.DEFINITION, ObjectTypeFilter.WORKFLOW, false),
136 initFilter(ObjectTypeFilter.COLLECTION, '', true, true),
137 initFilter(CollectionTypeFilter.GENERAL_COLLECTION, ObjectTypeFilter.COLLECTION),
138 initFilter(CollectionTypeFilter.OUTPUT_COLLECTION, ObjectTypeFilter.COLLECTION),
139 initFilter(CollectionTypeFilter.INTERMEDIATE_COLLECTION, ObjectTypeFilter.COLLECTION, false),
140 initFilter(CollectionTypeFilter.LOG_COLLECTION, ObjectTypeFilter.COLLECTION, false),
144 export const getInitialProcessTypeFilters = pipe(
145 (): DataTableFilters => createTree<DataTableFilterItem>(),
146 initFilter(ProcessTypeFilter.MAIN_PROCESS),
147 initFilter(ProcessTypeFilter.CHILD_PROCESS, '', false)
150 export const getInitialProcessStatusFilters = pipe(
151 (): DataTableFilters => createTree<DataTableFilterItem>(),
153 initFilter(ProcessStatusFilter.ALL, '', true),
154 initFilter(ProcessStatusFilter.ONHOLD, '', false),
155 initFilter(ProcessStatusFilter.QUEUED, '', false),
156 initFilter(ProcessStatusFilter.RUNNING, '', false),
157 initFilter(ProcessStatusFilter.COMPLETED, '', false),
158 initFilter(ProcessStatusFilter.CANCELLED, '', false),
159 initFilter(ProcessStatusFilter.FAILED, '', false),
163 export const getTrashPanelTypeFilters = pipe(
164 (): DataTableFilters => createTree<DataTableFilterItem>(),
165 initFilter(ObjectTypeFilter.PROJECT),
166 initFilter(ObjectTypeFilter.COLLECTION),
167 initFilter(CollectionTypeFilter.GENERAL_COLLECTION, ObjectTypeFilter.COLLECTION),
168 initFilter(CollectionTypeFilter.OUTPUT_COLLECTION, ObjectTypeFilter.COLLECTION),
169 initFilter(CollectionTypeFilter.INTERMEDIATE_COLLECTION, ObjectTypeFilter.COLLECTION),
170 initFilter(CollectionTypeFilter.LOG_COLLECTION, ObjectTypeFilter.COLLECTION),
173 const createFiltersBuilder = (filters: DataTableFilters) =>
174 ({ fb: new FilterBuilder(), selectedFilters: getSelectedNodes(filters) });
176 const getMatchingFilters = (values: string[], filters: TreeNode<DataTableFilterItem>[]) =>
179 .filter(includes(__, values));
181 const objectTypeToResourceKind = (type: ObjectTypeFilter) => {
183 case ObjectTypeFilter.PROJECT:
184 return ResourceKind.PROJECT;
185 case ObjectTypeFilter.WORKFLOW:
186 return ResourceKind.PROCESS;
187 case ObjectTypeFilter.COLLECTION:
188 return ResourceKind.COLLECTION;
189 case ObjectTypeFilter.DEFINITION:
190 return ResourceKind.WORKFLOW;
195 * object to resource which clasifies workflow category as only registered workflows, not processes
196 * Used for data tab that excludes process runs
198 const dataObjectTypeToResourceKind = (type: ObjectTypeFilter) => {
200 case ObjectTypeFilter.PROJECT:
201 return ResourceKind.PROJECT;
202 case ObjectTypeFilter.COLLECTION:
203 return ResourceKind.COLLECTION;
204 case ObjectTypeFilter.WORKFLOW:
205 case ObjectTypeFilter.DEFINITION:
206 return ResourceKind.WORKFLOW;
210 const serializeObjectTypeFilters = ({ fb, selectedFilters }: ReturnType<typeof createFiltersBuilder>) => {
211 const groupFilters = getMatchingFilters(values(GroupTypeFilter), selectedFilters);
212 const collectionFilters = getMatchingFilters(values(CollectionTypeFilter), selectedFilters);
213 const processFilters = getMatchingFilters(values(ProcessTypeFilter), selectedFilters);
214 const typeFilters = pipe(
215 () => new Set(getMatchingFilters(values(ObjectTypeFilter), selectedFilters)),
216 set => groupFilters.length > 0
217 ? set.add(ObjectTypeFilter.PROJECT)
219 set => collectionFilters.length > 0
220 ? set.add(ObjectTypeFilter.COLLECTION)
222 set => processFilters.length > 0
223 ? set.add(ObjectTypeFilter.WORKFLOW)
225 set => Array.from(set)
229 fb: typeFilters.length > 0
230 ? fb.addIsA('uuid', typeFilters.map(objectTypeToResourceKind))
231 : fb.addIsA('uuid', ResourceKind.NONE),
237 * Serialize only data object types, excludes processes
239 const serializeDataObjectTypeFilters = ({ fb, selectedFilters }: ReturnType<typeof createFiltersBuilder>) => {
240 const groupFilters = getMatchingFilters(values(GroupTypeFilter), selectedFilters);
241 const collectionFilters = getMatchingFilters(values(CollectionTypeFilter), selectedFilters);
242 const typeFilters = pipe(
243 () => new Set(getMatchingFilters(values(ObjectTypeFilter), selectedFilters)),
244 set => groupFilters.length > 0
245 ? set.add(ObjectTypeFilter.PROJECT)
247 set => collectionFilters.length > 0
248 ? set.add(ObjectTypeFilter.COLLECTION)
250 set => Array.from(set)
254 fb: typeFilters.length > 0
255 ? fb.addIsA('uuid', typeFilters.map(dataObjectTypeToResourceKind))
256 : fb.addIsA('uuid', ResourceKind.NONE),
261 const collectionTypeToPropertyValue = (type: CollectionTypeFilter) => {
263 case CollectionTypeFilter.GENERAL_COLLECTION:
264 return CollectionType.GENERAL;
265 case CollectionTypeFilter.OUTPUT_COLLECTION:
266 return CollectionType.OUTPUT;
267 case CollectionTypeFilter.LOG_COLLECTION:
268 return CollectionType.LOG;
269 case CollectionTypeFilter.INTERMEDIATE_COLLECTION:
270 return CollectionType.INTERMEDIATE;
272 return CollectionType.GENERAL;
276 const serializeCollectionTypeFilters = ({ fb, selectedFilters }: ReturnType<typeof createFiltersBuilder>) => pipe(
277 () => getMatchingFilters(values(CollectionTypeFilter), selectedFilters),
278 filters => filters.map(collectionTypeToPropertyValue),
280 fb: buildCollectionTypeFilters({ fb, filters: mappedFilters }),
285 const COLLECTION_TYPES = values(CollectionType);
287 const NON_GENERAL_COLLECTION_TYPES = difference(COLLECTION_TYPES, [CollectionType.GENERAL]);
289 const COLLECTION_PROPERTIES_PREFIX = `${GroupContentsResourcePrefix.COLLECTION}.properties`;
291 const buildCollectionTypeFilters = ({ fb, filters }: { fb: FilterBuilder, filters: CollectionType[] }) => {
293 case filters.length === 0 || filters.length === COLLECTION_TYPES.length:
295 case includes(CollectionType.GENERAL, filters):
296 return fb.addNotIn('type', difference(NON_GENERAL_COLLECTION_TYPES, filters), COLLECTION_PROPERTIES_PREFIX);
298 return fb.addIn('type', filters, COLLECTION_PROPERTIES_PREFIX);
302 const serializeGroupTypeFilters = ({ fb, selectedFilters }: ReturnType<typeof createFiltersBuilder>) => pipe(
303 () => getMatchingFilters(values(GroupTypeFilter), selectedFilters),
306 fb: buildGroupTypeFilters({ fb, filters: mappedFilters, use_prefix: true }),
311 const GROUP_TYPES = values(GroupTypeFilter);
313 const buildGroupTypeFilters = ({ fb, filters, use_prefix }: { fb: FilterBuilder, filters: string[], use_prefix: boolean }) => {
315 case filters.length === 0 || filters.length === GROUP_TYPES.length:
317 case includes(GroupTypeFilter.PROJECT, filters):
318 return fb.addEqual('groups.group_class', 'project');
319 case includes(GroupTypeFilter.FILTER_GROUP, filters):
320 return fb.addEqual('groups.group_class', 'filter');
326 const serializeProcessTypeFilters = ({ fb, selectedFilters }: ReturnType<typeof createFiltersBuilder>) => pipe(
327 () => getMatchingFilters(values(ProcessTypeFilter), selectedFilters),
330 fb: buildProcessTypeFilters({ fb, filters: mappedFilters, use_prefix: true }),
335 const PROCESS_TYPES = values(ProcessTypeFilter);
336 const PROCESS_PREFIX = GroupContentsResourcePrefix.PROCESS;
338 const buildProcessTypeFilters = ({ fb, filters, use_prefix }: { fb: FilterBuilder, filters: string[], use_prefix: boolean }) => {
340 case filters.length === 0 || filters.length === PROCESS_TYPES.length:
342 case includes(ProcessTypeFilter.MAIN_PROCESS, filters):
343 return fb.addEqual('requesting_container_uuid', null, use_prefix ? PROCESS_PREFIX : '');
344 case includes(ProcessTypeFilter.CHILD_PROCESS, filters):
345 return fb.addDistinct('requesting_container_uuid', null, use_prefix ? PROCESS_PREFIX : '');
352 * Serializes general resource type filters with prefix for group contents API
354 export const serializeResourceTypeFilters = pipe(
355 createFiltersBuilder,
356 serializeObjectTypeFilters,
357 serializeGroupTypeFilters,
358 serializeCollectionTypeFilters,
359 serializeProcessTypeFilters,
360 ({ fb }) => fb.getFilters(),
364 * Serializes data tab resource type filters with prefix for group contents API
366 export const serializeDataResourceTypeFilters = pipe(
367 createFiltersBuilder,
368 serializeDataObjectTypeFilters,
369 serializeGroupTypeFilters,
370 serializeCollectionTypeFilters,
371 ({ fb }) => fb.getFilters(),
374 export const serializeOnlyProcessTypeFilters = pipe(
375 createFiltersBuilder,
376 ({ fb, selectedFilters }: ReturnType<typeof createFiltersBuilder>) => pipe(
377 () => getMatchingFilters(values(ProcessTypeFilter), selectedFilters),
380 fb: buildProcessTypeFilters({ fb, filters: mappedFilters, use_prefix: false }),
384 ({ fb }) => fb.getFilters(),
388 * Serializes process type filters with prefix for group contents request
389 * Uses buildProcessTypeFilters to disable filters when no process type is selected
391 export const serializeProcessTypeGroupContentsFilters = pipe(
392 createFiltersBuilder,
393 ({fb, selectedFilters }): ReturnType<typeof createFiltersBuilder> => ({
394 fb: fb.addIsA('uuid', [ResourceKind.PROCESS]),
397 ({ fb, selectedFilters }: ReturnType<typeof createFiltersBuilder>) => pipe(
398 () => getMatchingFilters(values(ProcessTypeFilter), selectedFilters),
401 fb: buildProcessTypeFilters({ fb, filters: mappedFilters, use_prefix: true }),
405 ({ fb }) => fb.getFilters(),
408 export const serializeSimpleObjectTypeFilters = (filters: Tree<DataTableFilterItem>) => {
409 return getSelectedNodes(filters)
411 .map(objectTypeToResourceKind);
414 export const buildProcessStatusFilters = (fb: FilterBuilder, activeStatusFilter: string, resourcePrefix?: string): FilterBuilder => {
415 switch (activeStatusFilter) {
416 case ProcessStatusFilter.ONHOLD: {
417 fb.addDistinct('state', ContainerRequestState.FINAL, resourcePrefix);
418 fb.addEqual('priority', '0', resourcePrefix);
419 fb.addIn('container.state', [ContainerState.QUEUED, ContainerState.LOCKED], resourcePrefix);
422 case ProcessStatusFilter.COMPLETED: {
423 fb.addEqual('container.state', ContainerState.COMPLETE, resourcePrefix);
424 fb.addEqual('container.exit_code', '0', resourcePrefix);
427 case ProcessStatusFilter.FAILED: {
428 fb.addEqual('container.state', ContainerState.COMPLETE, resourcePrefix);
429 fb.addDistinct('container.exit_code', '0', resourcePrefix);
432 case ProcessStatusFilter.QUEUED: {
433 fb.addIn('container.state', [ContainerState.QUEUED, ContainerState.LOCKED], resourcePrefix);
434 fb.addDistinct('priority', '0', resourcePrefix);
437 case ProcessStatusFilter.CANCELLED:
438 case ProcessStatusFilter.RUNNING: {
439 fb.addEqual('container.state', activeStatusFilter, resourcePrefix);