"version": "0.1.0",
"private": true,
"dependencies": {
+ "@coreui/coreui": "next",
+ "@coreui/react": "next",
"@date-io/date-fns": "1",
"@fortawesome/fontawesome-svg-core": "1.2.28",
"@fortawesome/free-solid-svg-icons": "5.13.0",
"axios": "^0.21.1",
"babel-core": "6.26.3",
"babel-runtime": "6.26.0",
+ "bootstrap": "^5.3.2",
"caniuse-lite": "1.0.30001299",
"classnames": "2.2.6",
"cwlts": "1.15.29",
actions?: React.ReactNode;
hideSearchInput?: boolean;
title?: React.ReactNode;
+ toolbar?: React.ReactNode;
paperKey?: string;
currentItemUuid: string;
elementPath?: string;
fetchMode,
currentItemUuid,
title,
+ toolbar,
doHidePanel,
doMaximizePanel,
doUnMaximizePanel,
<MultiselectToolbar />
</Grid>
)}
+ {toolbar && (toolbar)}
</div>
<Grid
item
--- /dev/null
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import React, { useEffect, useState } from "react";
+import { StyleRulesCallback, Typography, WithStyles, withStyles } from "@material-ui/core";
+import { CProgressStacked, CProgress } from '@coreui/react';
+import { useAsyncInterval } from "common/use-async-interval";
+import { Process, isProcessRunning } from "store/processes/process";
+import { connect } from "react-redux";
+import { Dispatch } from "redux";
+import { fetchSubprocessProgress } from "store/subprocess-panel/subprocess-panel-actions";
+import { ProcessStatusFilter } from "store/resource-type-filters/resource-type-filters";
+
+type CssRules = 'progressWrapper' | 'progressStacked' ;
+
+const styles: StyleRulesCallback<CssRules> = (theme) => ({
+ progressWrapper: {
+ margin: "0 20px",
+ },
+ progressStacked: {
+ border: "1px solid gray",
+ // Override stripe color to be close to white
+ "& .progress-bar-striped": {
+ backgroundImage:
+ "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)",
+ },
+ },
+});
+
+export interface ProgressBarDataProps {
+ process: Process;
+}
+
+export interface ProgressBarActionProps {
+ fetchSubprocessProgress: (requestingContainerUuid: string) => Promise<ProgressBarData | undefined>;
+}
+
+type ProgressBarProps = ProgressBarDataProps & ProgressBarActionProps & WithStyles<CssRules>;
+
+export type ProgressBarData = {
+ [ProcessStatusFilter.COMPLETED]: number;
+ [ProcessStatusFilter.RUNNING]: number;
+ [ProcessStatusFilter.FAILED]: number;
+ [ProcessStatusFilter.QUEUED]: number;
+};
+
+const mapDispatchToProps = (dispatch: Dispatch): ProgressBarActionProps => ({
+ fetchSubprocessProgress: (requestingContainerUuid: string) => {
+ return dispatch<any>(fetchSubprocessProgress(requestingContainerUuid));
+ },
+});
+
+export const SubprocessProgressBar = connect(null, mapDispatchToProps)(withStyles(styles)(
+ ({process, classes, fetchSubprocessProgress}: ProgressBarProps) => {
+
+ const [progressData, setProgressData] = useState<ProgressBarData|undefined>(undefined);
+ const requestingContainerUuid = process.containerRequest.containerUuid;
+ const isRunning = isProcessRunning(process);
+
+ useAsyncInterval(async () => (
+ requestingContainerUuid && setProgressData(await fetchSubprocessProgress(requestingContainerUuid))
+ ), isRunning ? 5000 : null);
+
+ useEffect(() => {
+ if (!isRunning && requestingContainerUuid) {
+ fetchSubprocessProgress(requestingContainerUuid)
+ .then(result => setProgressData(result));
+ }
+ }, [fetchSubprocessProgress, isRunning, requestingContainerUuid]);
+
+ return progressData !== undefined && getStatusTotal(progressData) > 0 ? <div className={classes.progressWrapper}>
+ <CProgressStacked className={classes.progressStacked}>
+ <CProgress height={20} color="success" title="Completed"
+ value={getStatusPercent(progressData, ProcessStatusFilter.COMPLETED)} />
+ <CProgress height={20} color="success" title="Running" variant="striped" animated
+ value={getStatusPercent(progressData, ProcessStatusFilter.RUNNING)} />
+ <CProgress height={20} color="danger" title="Failed"
+ value={getStatusPercent(progressData, ProcessStatusFilter.FAILED)} />
+ <CProgress height={20} color="secondary" title="Queued" variant="striped" animated
+ value={getStatusPercent(progressData, ProcessStatusFilter.QUEUED)} />
+ </CProgressStacked>
+ <Typography variant="body2">
+ {progressData[ProcessStatusFilter.COMPLETED]} Completed, {progressData[ProcessStatusFilter.RUNNING]} Running, {progressData[ProcessStatusFilter.FAILED]} Failed, {progressData[ProcessStatusFilter.QUEUED]} Queued
+ </Typography>
+ </div> : <></>;
+ }
+));
+
+const getStatusTotal = (progressData: ProgressBarData) =>
+ (Object.keys(progressData).reduce((accumulator, key) => (accumulator += progressData[key]), 0));
+
+/**
+ * Gets the integer percent value for process status
+ */
+const getStatusPercent = (progressData: ProgressBarData, status: keyof ProgressBarData) =>
+ (progressData[status] / getStatusTotal(progressData) * 100);
import { storeRedirects } from "./common/redirect-to";
import { searchResultsActionSet } from "views-components/context-menu/action-sets/search-results-action-set";
+import 'bootstrap/dist/css/bootstrap.min.css';
+import '@coreui/coreui/dist/css/coreui.min.css';
+
console.log(`Starting arvados [${getBuildInfo()}]`);
addMenuActionSet(ContextMenuKind.ROOT_PROJECT, rootProjectActionSet);
import { RootState } from 'store/store';
import { ServiceRepository } from 'services/services';
import { bindDataExplorerActions } from 'store/data-explorer/data-explorer-action';
+import { FilterBuilder } from 'services/api/filter-builder';
+import { ProgressBarData } from 'components/subprocess-progress-bar/subprocess-progress-bar';
+import { ProcessStatusFilter, buildProcessStatusFilters } from 'store/resource-type-filters/resource-type-filters';
export const SUBPROCESS_PANEL_ID = "subprocessPanel";
export const SUBPROCESS_ATTRIBUTES_DIALOG = 'subprocessAttributesDialog';
export const subprocessPanelActions = bindDataExplorerActions(SUBPROCESS_PANEL_ID);
(dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
dispatch(subprocessPanelActions.REQUEST_ITEMS());
};
+
+type ProcessStatusCount = {
+ status: keyof ProgressBarData;
+ count: number;
+};
+
+export const fetchSubprocessProgress = (requestingContainerUuid: string) =>
+ async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository): Promise<ProgressBarData | undefined> => {
+
+ const requestContainerStatusCount = async (fb: FilterBuilder) => {
+ return await services.containerRequestService.list({
+ limit: 0,
+ offset: 0,
+ filters: fb.getFilters(),
+ });
+ }
+
+ if (requestingContainerUuid) {
+ try {
+ const baseFilter = new FilterBuilder().addEqual('requesting_container_uuid', requestingContainerUuid).getFilters();
+
+ // Create return object
+ let result: ProgressBarData = {
+ [ProcessStatusFilter.COMPLETED]: 0,
+ [ProcessStatusFilter.RUNNING]: 0,
+ [ProcessStatusFilter.FAILED]: 0,
+ [ProcessStatusFilter.QUEUED]: 0,
+ }
+
+ // Create array of promises that returns the status associated with the item count
+ // Helps to make the requests simultaneously while preserving the association with the status key as a typed key
+ const promises = Object.keys(result).map(async (status: keyof ProgressBarData): Promise<ProcessStatusCount> => {
+ const filter = buildProcessStatusFilters(new FilterBuilder(baseFilter), status);
+ const count = (await requestContainerStatusCount(filter)).itemsAvailable;
+ return {status, count};
+ });
+
+ // Simultaneously requests each status count and apply them to the return object
+ (await Promise.all(promises)).forEach((singleResult) => {
+ result[singleResult.status] = singleResult.count;
+ });
+ return result;
+ } catch (e) {
+ return undefined;
+ }
+ } else {
+ return undefined;
+ }
+ };
xs
maxHeight="50%"
data-cy="process-children">
- <SubprocessPanel />
+ <SubprocessPanel process={process} />
</MPVPanelContent>
</MPVContainer>
) : (
import { StyleRulesCallback, Typography, WithStyles, withStyles } from '@material-ui/core';
import { ArvadosTheme } from 'common/custom-theme';
import { ProcessResource } from 'models/process';
+import { SubprocessProgressBar } from 'components/subprocess-progress-bar/subprocess-progress-bar';
+import { Process } from 'store/processes/process';
type CssRules = 'iconHeader' | 'cardHeader';
];
export interface SubprocessPanelDataProps {
+ process: Process;
resources: ResourcesState;
}
doUnMaximizePanel={props.doUnMaximizePanel}
panelMaximized={props.panelMaximized}
panelName={props.panelName}
- title={<SubProcessesTitle/>} />;
+ title={<SubProcessesTitle/>}
+ toolbar={<SubprocessProgressBar process={props.process} />} />;
};
},
});
-const mapStateToProps = (state: RootState): SubprocessPanelDataProps => ({
+const mapStateToProps = (state: RootState): Omit<SubprocessPanelDataProps,'process'> => ({
resources: state.resources,
});
languageName: node
linkType: hard
+"@coreui/coreui@npm:next":
+ version: 5.0.0-alpha.3
+ resolution: "@coreui/coreui@npm:5.0.0-alpha.3"
+ peerDependencies:
+ "@popperjs/core": ^2.11.8
+ checksum: 2363ad6be775c6a895a49126a5b9062ffa9ebd0bea6dfb835c1300cd122fb1cf18d85fe647a9c08a3a384caa871e761d8ffb28ea45c7872cb2b034df6527da20
+ languageName: node
+ linkType: hard
+
+"@coreui/react@npm:next":
+ version: 5.0.0-alpha.3
+ resolution: "@coreui/react@npm:5.0.0-alpha.3"
+ peerDependencies:
+ "@coreui/coreui": ^5.0.0-alpha.2
+ react: ">=17"
+ react-dom: ">=17"
+ checksum: efd333cc346307219dcf7fe183eed65305b12e71984bcb940d80a55509d7b92523082e37045bfcb8c4b334920ca185128a9f72f3e8bec69d15cad889cbeda4b4
+ languageName: node
+ linkType: hard
+
"@csstools/convert-colors@npm:^1.4.0":
version: 1.4.0
resolution: "@csstools/convert-colors@npm:1.4.0"
version: 0.0.0-use.local
resolution: "arvados-workbench-2@workspace:."
dependencies:
+ "@coreui/coreui": next
+ "@coreui/react": next
"@date-io/date-fns": 1
"@fortawesome/fontawesome-svg-core": 1.2.28
"@fortawesome/free-solid-svg-icons": 5.13.0
axios-mock-adapter: 1.17.0
babel-core: 6.26.3
babel-runtime: 6.26.0
+ bootstrap: ^5.3.2
caniuse-lite: 1.0.30001299
classnames: 2.2.6
cwlts: 1.15.29
languageName: node
linkType: hard
+"bootstrap@npm:^5.3.2":
+ version: 5.3.2
+ resolution: "bootstrap@npm:5.3.2"
+ peerDependencies:
+ "@popperjs/core": ^2.11.8
+ checksum: d5580b253d121ffc137388d41da58dce8d15f1ccd574e12f28d4a08e7649ca15e95db645b2b677cb8025bccd446bff04138fc0fe64f8cba0ccc5dc004a8644cf
+ languageName: node
+ linkType: hard
+
"brace-expansion@npm:^1.1.7":
version: 1.1.11
resolution: "brace-expansion@npm:1.1.11"