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 { StyleRulesCallback, Tooltip, WithStyles, withStyles } from "@material-ui/core";
7 import { CProgressStacked, CProgress } from '@coreui/react';
8 import { useAsyncInterval } from "common/use-async-interval";
9 import { Process } from "store/processes/process";
10 import { connect } from "react-redux";
11 import { Dispatch } from "redux";
12 import { fetchProcessProgressBarStatus } from "store/subprocess-panel/subprocess-panel-actions";
13 import { ProcessStatusFilter, serializeOnlyProcessTypeFilters } from "store/resource-type-filters/resource-type-filters";
14 import { ProjectResource } from "models/project";
15 import { getDataExplorer } from "store/data-explorer/data-explorer-reducer";
16 import { RootState } from "store/store";
17 import { ProcessResource } from "models/process";
18 import { getDataExplorerColumnFilters } from "store/data-explorer/data-explorer-middleware-service";
19 import { ProjectPanelRunColumnNames } from "views/project-panel/project-panel-run";
20 import { DataColumns } from "components/data-table/data-table";
22 type CssRules = 'progressStacked';
24 const styles: StyleRulesCallback<CssRules> = (theme) => ({
26 border: "1px solid gray",
28 // Override stripe color to be close to white
29 "& .progress-bar-striped": {
31 "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)",
36 export interface ProgressBarDataProps {
37 parentResource: Process | ProjectResource | undefined;
38 dataExplorerId?: string;
42 export interface ProgressBarActionProps {
43 fetchProcessProgressBarStatus: (parentResource: Process | ProjectResource, typeFilter?: string) => Promise<ProgressBarStatus | undefined>;
46 type ProgressBarProps = ProgressBarDataProps & ProgressBarActionProps & WithStyles<CssRules>;
48 export type ProgressBarCounts = {
49 [ProcessStatusFilter.COMPLETED]: number;
50 [ProcessStatusFilter.RUNNING]: number;
51 [ProcessStatusFilter.FAILED]: number;
52 [ProcessStatusFilter.QUEUED]: number;
55 export type ProgressBarStatus = {
56 counts: ProgressBarCounts;
60 const mapStateToProps = (state: RootState, props: ProgressBarDataProps) => {
61 let typeFilter: string | undefined = undefined;
63 if (props.dataExplorerId) {
64 const dataExplorerState = getDataExplorer(state.dataExplorer, props.dataExplorerId);
65 const columns = dataExplorerState.columns as DataColumns<string, ProcessResource>;
66 typeFilter = serializeOnlyProcessTypeFilters(getDataExplorerColumnFilters(columns, ProjectPanelRunColumnNames.TYPE));
69 return { typeFilter };
72 const mapDispatchToProps = (dispatch: Dispatch): ProgressBarActionProps => ({
73 fetchProcessProgressBarStatus: (parentResource: Process | ProjectResource, typeFilter?: string) => {
74 return dispatch<any>(fetchProcessProgressBarStatus(parentResource, typeFilter));
78 export const SubprocessProgressBar = connect(mapStateToProps, mapDispatchToProps)(withStyles(styles)(
79 ({ parentResource, typeFilter, classes, fetchProcessProgressBarStatus }: ProgressBarProps) => {
81 const [progressCounts, setProgressData] = useState<ProgressBarCounts | undefined>(undefined);
82 const [isRunning, setIsRunning] = useState<boolean>(false);
84 useAsyncInterval(async () => {
86 fetchProcessProgressBarStatus(parentResource, typeFilter)
89 setProgressData(result.counts);
90 setIsRunning(result.isRunning);
94 }, isRunning ? 5000 : null);
97 if (!isRunning && parentResource) {
98 fetchProcessProgressBarStatus(parentResource, typeFilter)
101 setProgressData(result.counts);
102 setIsRunning(result.isRunning);
106 }, [fetchProcessProgressBarStatus, isRunning, parentResource, typeFilter]);
109 if (progressCounts) {
111 [ProcessStatusFilter.COMPLETED,
112 ProcessStatusFilter.RUNNING,
113 ProcessStatusFilter.FAILED,
114 ProcessStatusFilter.QUEUED].forEach(psf => {
115 if (progressCounts[psf] > 0) {
116 if (tooltip.length > 0) { tooltip += ", "; }
117 tooltip += `${progressCounts[psf]} ${psf}`;
118 total += progressCounts[psf];
122 if (tooltip.length > 0) { tooltip += ", "; }
123 tooltip += `${total} Total`;
127 return progressCounts !== undefined && getStatusTotal(progressCounts) > 0 ? <Tooltip title={tooltip}>
128 <CProgressStacked className={classes.progressStacked}>
129 <CProgress height={10} color="success"
130 value={getStatusPercent(progressCounts, ProcessStatusFilter.COMPLETED)} />
131 <CProgress height={10} color="success" variant="striped"
132 value={getStatusPercent(progressCounts, ProcessStatusFilter.RUNNING)} />
133 <CProgress height={10} color="danger"
134 value={getStatusPercent(progressCounts, ProcessStatusFilter.FAILED)} />
135 <CProgress height={10} color="secondary" variant="striped"
136 value={getStatusPercent(progressCounts, ProcessStatusFilter.QUEUED)} />
142 const getStatusTotal = (progressCounts: ProgressBarCounts) =>
143 (Object.keys(progressCounts).reduce((accumulator, key) => (accumulator += progressCounts[key]), 0));
146 * Gets the integer percent value for process status
148 const getStatusPercent = (progressCounts: ProgressBarCounts, status: keyof ProgressBarCounts) =>
149 (progressCounts[status] / getStatusTotal(progressCounts) * 100);