1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
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 } from 'store/processes/process';
13 import { ProjectResource } from 'models/project';
14 import { getResource } from 'store/resources/resources';
15 import { ContainerRequestResource } from 'models/container-request';
16 import { Resource } from 'models/resource';
18 export const SUBPROCESS_PANEL_ID = "subprocessPanel";
19 export const SUBPROCESS_ATTRIBUTES_DIALOG = 'subprocessAttributesDialog';
20 export const subprocessPanelActions = bindDataExplorerActions(SUBPROCESS_PANEL_ID);
22 export const loadSubprocessPanel = () =>
23 (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
24 dispatch(subprocessPanelActions.REQUEST_ITEMS());
28 * Holds a ProgressBarData status type and process count result
30 type ProcessStatusCount = {
31 status: keyof ProgressBarCounts;
36 * Associates each of the limited progress bar segment types with an array of
37 * ProcessStatusFilterTypes to be combined when displayed
39 type ProcessStatusMap = Record<keyof ProgressBarCounts, ProcessStatusFilter[]>;
41 const statusMap: ProcessStatusMap = {
42 [ProcessStatusFilter.COMPLETED]: [ProcessStatusFilter.COMPLETED],
43 [ProcessStatusFilter.RUNNING]: [ProcessStatusFilter.RUNNING],
44 [ProcessStatusFilter.FAILED]: [ProcessStatusFilter.FAILED, ProcessStatusFilter.CANCELLED],
45 [ProcessStatusFilter.QUEUED]: [ProcessStatusFilter.QUEUED, ProcessStatusFilter.ONHOLD],
49 * Utility type to hold a pair of associated progress bar status and process status
51 type ProgressBarStatusPair = {
52 barStatus: keyof ProcessStatusMap;
53 processStatus: ProcessStatusFilter;
57 * Type guard to distinguish Processes from other Resources
58 * @param resource The item to check
59 * @returns if the resource is a Process
61 export const isProcess = <T extends Resource>(resource: T | Process | undefined): resource is Process => {
62 return !!resource && 'containerRequest' in resource;
66 * Type guard to distinguish ContainerRequestResources from Resources
67 * @param resource The item to check
68 * @returns if the resource is a ContainerRequestResource
70 const isContainerRequest = <T extends Resource>(resource: T | ContainerRequestResource | undefined): resource is ContainerRequestResource => {
71 return !!resource && 'containerUuid' in resource;
74 export const fetchProcessProgressBarStatus = (parentResourceUuid: string, typeFilter?: string) =>
75 async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository): Promise<ProgressBarStatus | undefined> => {
76 const resources = getState().resources;
77 const parentResource = getResource<ProjectResource | ContainerRequestResource>(parentResourceUuid)(resources);
79 const requestContainerStatusCount = async (fb: FilterBuilder) => {
80 return await services.containerRequestService.list({
83 filters: fb.getFilters(),
87 let baseFilter: string = "";
88 if (isContainerRequest(parentResource) && parentResource.containerUuid) {
89 // Prevent CR without containerUuid from generating baseFilter
90 baseFilter = new FilterBuilder().addEqual('requesting_container_uuid', parentResource.containerUuid).getFilters();
91 } else if (parentResource && !isContainerRequest(parentResource)) {
92 // isCR type narrowing needed since CR without container may fall through
93 baseFilter = new FilterBuilder().addEqual('owner_uuid', parentResource.uuid).getFilters();
96 if (parentResource && baseFilter) {
97 // Add type filters from consumers that want to sync progress stats with filters
99 baseFilter = joinFilters(baseFilter, typeFilter);
103 // Create return object
104 let result: ProgressBarCounts = {
105 [ProcessStatusFilter.COMPLETED]: 0,
106 [ProcessStatusFilter.RUNNING]: 0,
107 [ProcessStatusFilter.FAILED]: 0,
108 [ProcessStatusFilter.QUEUED]: 0,
111 // Create array of promises that returns the status associated with the item count
112 // Helps to make the requests simultaneously while preserving the association with the status key as a typed key
113 const promises = (Object.keys(statusMap) as Array<keyof ProcessStatusMap>)
114 // Split statusMap into pairs of progress bar status and process status
115 .reduce((acc, curr) => [...acc, ...statusMap[curr].map(processStatus => ({barStatus: curr, processStatus}))], [] as ProgressBarStatusPair[])
116 .map(async (statusPair: ProgressBarStatusPair): Promise<ProcessStatusCount> => {
117 // For each status pair, request count and return bar status and count
118 const { barStatus, processStatus } = statusPair;
119 const filter = buildProcessStatusFilters(new FilterBuilder(baseFilter), processStatus);
120 const count = (await requestContainerStatusCount(filter)).itemsAvailable;
121 if (count === undefined) return Promise.reject();
122 return {status: barStatus, count};
125 // Simultaneously requests each status count and apply them to the return object
126 (await Promise.all(promises)).forEach((singleResult) => {
127 result[singleResult.status] += singleResult.count;
130 // CR polling is handled in progress bar based on store updates
131 // This bool triggers polling without causing a final fetch when disabled
132 // The shouldPoll logic here differs slightly from shouldPollProcess:
133 // * Process gets websocket updates through the store so using isProcessRunning
135 // * In projects, we get no websocket updates on CR state changes so we treat
136 // Queued processes as running in order to let polling keep us up to date
137 // when anything transitions to Running. This also means that a project with
138 // CRs in a stopped state won't start polling if CRs are started elsewhere
139 const shouldPollProject = isContainerRequest(parentResource)
141 : (result[ProcessStatusFilter.RUNNING] + result[ProcessStatusFilter.QUEUED]) > 0;
143 return {counts: result, shouldPollProject};