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 = 'Runs',
47 CHILD_PROCESS = 'Intermediate 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, '', false, 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;
194 const serializeObjectTypeFilters = ({ fb, selectedFilters }: ReturnType<typeof createFiltersBuilder>) => {
195 const groupFilters = getMatchingFilters(values(GroupTypeFilter), selectedFilters);
196 const collectionFilters = getMatchingFilters(values(CollectionTypeFilter), selectedFilters);
197 const processFilters = getMatchingFilters(values(ProcessTypeFilter), selectedFilters);
198 const typeFilters = pipe(
199 () => new Set(getMatchingFilters(values(ObjectTypeFilter), selectedFilters)),
200 set => groupFilters.length > 0
201 ? set.add(ObjectTypeFilter.PROJECT)
203 set => collectionFilters.length > 0
204 ? set.add(ObjectTypeFilter.COLLECTION)
206 set => processFilters.length > 0
207 ? set.add(ObjectTypeFilter.WORKFLOW)
209 set => Array.from(set)
213 fb: typeFilters.length > 0
214 ? fb.addIsA('uuid', typeFilters.map(objectTypeToResourceKind))
215 : fb.addIsA('uuid', ResourceKind.NONE),
220 const collectionTypeToPropertyValue = (type: CollectionTypeFilter) => {
222 case CollectionTypeFilter.GENERAL_COLLECTION:
223 return CollectionType.GENERAL;
224 case CollectionTypeFilter.OUTPUT_COLLECTION:
225 return CollectionType.OUTPUT;
226 case CollectionTypeFilter.LOG_COLLECTION:
227 return CollectionType.LOG;
228 case CollectionTypeFilter.INTERMEDIATE_COLLECTION:
229 return CollectionType.INTERMEDIATE;
231 return CollectionType.GENERAL;
235 const serializeCollectionTypeFilters = ({ fb, selectedFilters }: ReturnType<typeof createFiltersBuilder>) => pipe(
236 () => getMatchingFilters(values(CollectionTypeFilter), selectedFilters),
237 filters => filters.map(collectionTypeToPropertyValue),
239 fb: buildCollectionTypeFilters({ fb, filters: mappedFilters }),
244 const COLLECTION_TYPES = values(CollectionType);
246 const NON_GENERAL_COLLECTION_TYPES = difference(COLLECTION_TYPES, [CollectionType.GENERAL]);
248 const COLLECTION_PROPERTIES_PREFIX = `${GroupContentsResourcePrefix.COLLECTION}.properties`;
250 const buildCollectionTypeFilters = ({ fb, filters }: { fb: FilterBuilder, filters: CollectionType[] }) => {
252 case filters.length === 0 || filters.length === COLLECTION_TYPES.length:
254 case includes(CollectionType.GENERAL, filters):
255 return fb.addNotIn('type', difference(NON_GENERAL_COLLECTION_TYPES, filters), COLLECTION_PROPERTIES_PREFIX);
257 return fb.addIn('type', filters, COLLECTION_PROPERTIES_PREFIX);
261 const serializeGroupTypeFilters = ({ fb, selectedFilters }: ReturnType<typeof createFiltersBuilder>) => pipe(
262 () => getMatchingFilters(values(GroupTypeFilter), selectedFilters),
265 fb: buildGroupTypeFilters({ fb, filters: mappedFilters, use_prefix: true }),
270 const GROUP_TYPES = values(GroupTypeFilter);
272 const buildGroupTypeFilters = ({ fb, filters, use_prefix }: { fb: FilterBuilder, filters: string[], use_prefix: boolean }) => {
274 case filters.length === 0 || filters.length === GROUP_TYPES.length:
276 case includes(GroupTypeFilter.PROJECT, filters):
277 return fb.addEqual('groups.group_class', 'project');
278 case includes(GroupTypeFilter.FILTER_GROUP, filters):
279 return fb.addEqual('groups.group_class', 'filter');
285 const serializeProcessTypeFilters = ({ fb, selectedFilters }: ReturnType<typeof createFiltersBuilder>) => pipe(
286 () => getMatchingFilters(values(ProcessTypeFilter), selectedFilters),
289 fb: buildProcessTypeFilters({ fb, filters: mappedFilters, use_prefix: true }),
294 const PROCESS_TYPES = values(ProcessTypeFilter);
295 const PROCESS_PREFIX = GroupContentsResourcePrefix.PROCESS;
297 const buildProcessTypeFilters = ({ fb, filters, use_prefix }: { fb: FilterBuilder, filters: string[], use_prefix: boolean }) => {
299 case filters.length === 0 || filters.length === PROCESS_TYPES.length:
301 case includes(ProcessTypeFilter.MAIN_PROCESS, filters):
302 return fb.addEqual('requesting_container_uuid', null, use_prefix ? PROCESS_PREFIX : '');
303 case includes(ProcessTypeFilter.CHILD_PROCESS, filters):
304 return fb.addDistinct('requesting_container_uuid', null, use_prefix ? PROCESS_PREFIX : '');
311 * Serializes general resource type filters with prefix for group contents API
313 export const serializeResourceTypeFilters = pipe(
314 createFiltersBuilder,
315 serializeObjectTypeFilters,
316 serializeGroupTypeFilters,
317 serializeCollectionTypeFilters,
318 serializeProcessTypeFilters,
319 ({ fb }) => fb.getFilters(),
322 export const serializeOnlyProcessTypeFilters = pipe(
323 createFiltersBuilder,
324 ({ fb, selectedFilters }: ReturnType<typeof createFiltersBuilder>) => pipe(
325 () => getMatchingFilters(values(ProcessTypeFilter), selectedFilters),
328 fb: buildProcessTypeFilters({ fb, filters: mappedFilters, use_prefix: false }),
332 ({ fb }) => fb.getFilters(),
336 * Serializes process type filters with prefix for group contents request
337 * Uses buildProcessTypeFilters to disable filters when no process type is selected
339 export const serializeProcessTypeGroupContentsFilters = pipe(
340 createFiltersBuilder,
341 ({fb, selectedFilters }): ReturnType<typeof createFiltersBuilder> => ({
342 fb: fb.addIsA('uuid', [ResourceKind.PROCESS]),
345 ({ fb, selectedFilters }: ReturnType<typeof createFiltersBuilder>) => pipe(
346 () => getMatchingFilters(values(ProcessTypeFilter), selectedFilters),
349 fb: buildProcessTypeFilters({ fb, filters: mappedFilters, use_prefix: true }),
353 ({ fb }) => fb.getFilters(),
356 export const serializeSimpleObjectTypeFilters = (filters: Tree<DataTableFilterItem>) => {
357 return getSelectedNodes(filters)
359 .map(objectTypeToResourceKind);
362 export const buildProcessStatusFilters = (fb: FilterBuilder, activeStatusFilter: string, resourcePrefix?: string): FilterBuilder => {
363 switch (activeStatusFilter) {
364 case ProcessStatusFilter.ONHOLD: {
365 fb.addDistinct('state', ContainerRequestState.FINAL, resourcePrefix);
366 fb.addEqual('priority', '0', resourcePrefix);
367 fb.addIn('container.state', [ContainerState.QUEUED, ContainerState.LOCKED], resourcePrefix);
370 case ProcessStatusFilter.COMPLETED: {
371 fb.addEqual('container.state', ContainerState.COMPLETE, resourcePrefix);
372 fb.addEqual('container.exit_code', '0', resourcePrefix);
375 case ProcessStatusFilter.FAILED: {
376 fb.addEqual('container.state', ContainerState.COMPLETE, resourcePrefix);
377 fb.addDistinct('container.exit_code', '0', resourcePrefix);
380 case ProcessStatusFilter.QUEUED: {
381 fb.addIn('container.state', [ContainerState.QUEUED, ContainerState.LOCKED], resourcePrefix);
382 fb.addDistinct('priority', '0', resourcePrefix);
385 case ProcessStatusFilter.CANCELLED:
386 case ProcessStatusFilter.RUNNING: {
387 fb.addEqual('container.state', activeStatusFilter, resourcePrefix);