1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
5 import React, { useEffect, useState } from "react";
6 import { CustomStyleRulesCallback } from 'common/custom-theme';
7 import { Tooltip } from "@mui/material";
8 import { WithStyles } from '@mui/styles';
9 import withStyles from '@mui/styles/withStyles';
10 import { CProgressStacked, CProgress } from '@coreui/react';
11 import { useAsyncInterval } from "common/use-async-interval";
12 import { Process, isProcessRunning } from "store/processes/process";
13 import { connect } from "react-redux";
14 import { Dispatch } from "redux";
15 import { fetchProcessProgressBarStatus, isProcess } from "store/subprocess-panel/subprocess-panel-actions";
16 import { ProcessStatusFilter, serializeOnlyProcessTypeFilters } from "store/resource-type-filters/resource-type-filters";
17 import { ProjectResource } from "models/project";
18 import { getDataExplorer } from "store/data-explorer/data-explorer-reducer";
19 import { RootState } from "store/store";
20 import { ProcessResource } from "models/process";
21 import { getDataExplorerColumnFilters } from "store/data-explorer/data-explorer-middleware-service";
22 import { ProjectPanelRunColumnNames } from "views/project-panel/project-panel-run";
23 import { DataColumns } from "components/data-table/data-column";
25 type CssRules = 'progressStacked';
27 const styles: CustomStyleRulesCallback<CssRules> = (theme) => ({
34 border: "1px solid gray",
36 // Override stripe color to be close to white
37 "& .progress-bar-striped": {
39 "linear-gradient(45deg,rgba(255,255,255,.80) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.80) 50%,rgba(255,255,255,.80) 75%,transparent 75%,transparent)",
44 export interface ProgressBarDataProps {
45 parentResource: Process | ProjectResource | undefined;
46 dataExplorerId?: string;
50 export interface ProgressBarActionProps {
51 fetchProcessProgressBarStatus: (parentResourceUuid: string, typeFilter?: string) => Promise<ProgressBarStatus | undefined>;
54 type ProgressBarProps = ProgressBarDataProps & ProgressBarActionProps & WithStyles<CssRules>;
56 export type ProgressBarCounts = {
57 [ProcessStatusFilter.COMPLETED]: number;
58 [ProcessStatusFilter.RUNNING]: number;
59 [ProcessStatusFilter.FAILED]: number;
60 [ProcessStatusFilter.QUEUED]: number;
63 export type ProgressBarStatus = {
64 counts: ProgressBarCounts;
65 shouldPollProject: boolean;
68 const mapStateToProps = (state: RootState, props: ProgressBarDataProps) => {
69 let typeFilter: string | undefined = undefined;
71 if (props.dataExplorerId) {
72 const dataExplorerState = getDataExplorer(state.dataExplorer, props.dataExplorerId);
73 const columns = dataExplorerState.columns as DataColumns<string, ProcessResource>;
74 typeFilter = serializeOnlyProcessTypeFilters(false)(getDataExplorerColumnFilters(columns, ProjectPanelRunColumnNames.TYPE));
77 return { typeFilter };
80 const mapDispatchToProps = (dispatch: Dispatch): ProgressBarActionProps => ({
81 fetchProcessProgressBarStatus: (parentResourceUuid: string, typeFilter?: string) => {
82 return dispatch<any>(fetchProcessProgressBarStatus(parentResourceUuid, typeFilter));
86 export const SubprocessProgressBar = connect(mapStateToProps, mapDispatchToProps)(withStyles(styles)(
87 ({ parentResource, typeFilter, classes, fetchProcessProgressBarStatus }: ProgressBarProps) => {
89 const [progressCounts, setProgressData] = useState<ProgressBarCounts | undefined>(undefined);
90 const [shouldPollProject, setShouldPollProject] = useState<boolean>(false);
91 const shouldPollProcess = isProcess(parentResource) ? isProcessRunning(parentResource) : false;
93 // Should polling be active based on container status
94 // or result of aggregated project process contents
95 const shouldPoll = shouldPollProject || shouldPollProcess;
97 const parentUuid = parentResource
98 ? isProcess(parentResource)
99 ? parentResource.containerRequest.uuid
100 : parentResource.uuid
103 // Runs periodically whenever polling should be happeing
104 // Either when the workflow is running (shouldPollProcess) or when the
105 // project contains steps in an active state (shouldPollProject)
106 useAsyncInterval(async () => {
108 fetchProcessProgressBarStatus(parentUuid, typeFilter)
111 setProgressData(result.counts);
112 setShouldPollProject(result.shouldPollProject);
116 }, shouldPoll ? 5000 : null);
118 // Runs fetch on first load for processes and projects, except when
119 // process is running since polling will be enabled by shouldPoll.
120 // Project polling starts false so this is still needed for project
121 // initial load to set shouldPollProject and kick off shouldPoll
122 // Watches shouldPollProcess but not shouldPollProject
123 // * This runs a final fetch when process ends & is updated through
125 // * We ignore shouldPollProject entirely since it changes to false
126 // as a result of a fetch so the data is already up to date
128 if (!shouldPollProcess && parentUuid) {
129 fetchProcessProgressBarStatus(parentUuid, typeFilter)
132 setProgressData(result.counts);
133 setShouldPollProject(result.shouldPollProject);
137 }, [fetchProcessProgressBarStatus, shouldPollProcess, parentUuid, typeFilter]);
140 if (progressCounts) {
142 [ProcessStatusFilter.COMPLETED,
143 ProcessStatusFilter.RUNNING,
144 ProcessStatusFilter.FAILED,
145 ProcessStatusFilter.QUEUED].forEach(psf => {
146 if (progressCounts[psf] > 0) {
147 if (tooltip.length > 0) { tooltip += ", "; }
148 tooltip += `${progressCounts[psf]} ${psf}`;
149 total += progressCounts[psf];
153 if (tooltip.length > 0) { tooltip += ", "; }
154 tooltip += `${total} Total`;
158 return progressCounts !== undefined && getStatusTotal(progressCounts) > 0 ? <Tooltip title={tooltip}>
159 <CProgressStacked className={classes.progressStacked}>
160 <CProgress height={10} color="success"
161 value={getStatusPercent(progressCounts, ProcessStatusFilter.COMPLETED)} />
162 <CProgress height={10} color="success" variant="striped"
163 value={getStatusPercent(progressCounts, ProcessStatusFilter.RUNNING)} />
164 <CProgress height={10} color="danger"
165 value={getStatusPercent(progressCounts, ProcessStatusFilter.FAILED)} />
166 <CProgress height={10} color="secondary" variant="striped"
167 value={getStatusPercent(progressCounts, ProcessStatusFilter.QUEUED)} />
173 const getStatusTotal = (progressCounts: ProgressBarCounts) =>
174 (Object.keys(progressCounts).reduce((accumulator, key) => (accumulator += progressCounts[key]), 0));
177 * Gets the integer percent value for process status
179 const getStatusPercent = (progressCounts: ProgressBarCounts, status: keyof ProgressBarCounts) =>
180 (progressCounts[status] / getStatusTotal(progressCounts) * 100);