Merge branch '9964-output-glob-acr' refs #9964
[arvados.git] / services / workbench2 / src / store / subprocess-panel / subprocess-panel-actions.ts
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 import { Dispatch } from 'redux';
6 import { RootState } from 'store/store';
7 import { ServiceRepository } from 'services/services';
8 import { bindDataExplorerActions } from 'store/data-explorer/data-explorer-action';
9 import { FilterBuilder, joinFilters } from 'services/api/filter-builder';
10 import { ProgressBarStatus, ProgressBarCounts } from 'components/subprocess-progress-bar/subprocess-progress-bar';
11 import { ProcessStatusFilter, buildProcessStatusFilters } from 'store/resource-type-filters/resource-type-filters';
12 import { Process, isProcessRunning } from 'store/processes/process';
13 import { ProjectResource } from 'models/project';
14
15 export const SUBPROCESS_PANEL_ID = "subprocessPanel";
16 export const SUBPROCESS_ATTRIBUTES_DIALOG = 'subprocessAttributesDialog';
17 export const subprocessPanelActions = bindDataExplorerActions(SUBPROCESS_PANEL_ID);
18
19 export const loadSubprocessPanel = () =>
20     (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
21         dispatch(subprocessPanelActions.REQUEST_ITEMS());
22     };
23
24 /**
25  * Holds a ProgressBarData status type and process count result
26  */
27 type ProcessStatusCount = {
28     status: keyof ProgressBarCounts;
29     count: number;
30 };
31
32 /**
33  * Associates each of the limited progress bar segment types with an array of
34  * ProcessStatusFilterTypes to be combined when displayed
35  */
36 type ProcessStatusMap = Record<keyof ProgressBarCounts, ProcessStatusFilter[]>;
37
38 const statusMap: ProcessStatusMap = {
39         [ProcessStatusFilter.COMPLETED]: [ProcessStatusFilter.COMPLETED],
40         [ProcessStatusFilter.RUNNING]: [ProcessStatusFilter.RUNNING],
41         [ProcessStatusFilter.FAILED]: [ProcessStatusFilter.FAILED, ProcessStatusFilter.CANCELLED],
42         [ProcessStatusFilter.QUEUED]: [ProcessStatusFilter.QUEUED, ProcessStatusFilter.ONHOLD],
43 };
44
45 /**
46  * Utility type to hold a pair of associated progress bar status and process status
47  */
48 type ProgressBarStatusPair = {
49     barStatus: keyof ProcessStatusMap;
50     processStatus: ProcessStatusFilter;
51 };
52
53 const isProcess = (resource: Process | ProjectResource | undefined): resource is Process => {
54     return !!resource && 'containerRequest' in resource;
55 };
56
57 export const fetchProcessProgressBarStatus = (parentResource: Process | ProjectResource, typeFilter?: string) =>
58     async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository): Promise<ProgressBarStatus | undefined> => {
59         const requestContainerStatusCount = async (fb: FilterBuilder) => {
60             return await services.containerRequestService.list({
61                 limit: 0,
62                 offset: 0,
63                 filters: fb.getFilters(),
64             });
65         }
66
67         let baseFilter = "";
68         if (isProcess(parentResource)) {
69             baseFilter = new FilterBuilder().addEqual('requesting_container_uuid', parentResource.containerRequest.containerUuid).getFilters();
70         } else {
71             baseFilter = new FilterBuilder().addEqual('owner_uuid', parentResource.uuid).getFilters();
72         }
73
74         if (typeFilter) {
75             baseFilter = joinFilters(baseFilter, typeFilter);
76         }
77
78         if (baseFilter) {
79             try {
80                 // Create return object
81                 let result: ProgressBarCounts = {
82                     [ProcessStatusFilter.COMPLETED]: 0,
83                     [ProcessStatusFilter.RUNNING]: 0,
84                     [ProcessStatusFilter.FAILED]: 0,
85                     [ProcessStatusFilter.QUEUED]: 0,
86                 }
87
88                 // Create array of promises that returns the status associated with the item count
89                 // Helps to make the requests simultaneously while preserving the association with the status key as a typed key
90                 const promises = (Object.keys(statusMap) as Array<keyof ProcessStatusMap>)
91                     // Split statusMap into pairs of progress bar status and process status
92                     .reduce((acc, curr) => [...acc, ...statusMap[curr].map(processStatus => ({barStatus: curr, processStatus}))], [] as ProgressBarStatusPair[])
93                     .map(async (statusPair: ProgressBarStatusPair): Promise<ProcessStatusCount> => {
94                         // For each status pair, request count and return bar status and count
95                         const { barStatus, processStatus } = statusPair;
96                         const filter = buildProcessStatusFilters(new FilterBuilder(baseFilter), processStatus);
97                         const count = (await requestContainerStatusCount(filter)).itemsAvailable;
98                         return {status: barStatus, count};
99                     });
100
101                 // Simultaneously requests each status count and apply them to the return object
102                 (await Promise.all(promises)).forEach((singleResult) => {
103                     result[singleResult.status] += singleResult.count;
104                 });
105
106                 let isRunning = result[ProcessStatusFilter.RUNNING] + result[ProcessStatusFilter.QUEUED] > 0;
107
108                 if (isProcess(parentResource)) {
109                     isRunning = isProcessRunning(parentResource);
110                 }
111
112                 return {counts: result, isRunning};
113             } catch (e) {
114                 return undefined;
115             }
116         } else {
117             return undefined;
118         }
119     };