[arvados-workbench2] created: 2.7.0-197-gdcf2e835
git repository hosting
git at public.arvados.org
Tue Nov 14 14:08:46 UTC 2023
at dcf2e835b33e926073dad1f636cf92a95493ca0b (commit)
commit dcf2e835b33e926073dad1f636cf92a95493ca0b
Author: Stephen Smith <stephen at curii.com>
Date: Tue Nov 14 09:07:08 2023 -0500
20609: Change subprocess progress bar to combine relevant process statuses
eg. Queued segment = Queued + OnHold, Failed segment = Failed + Cancelled
Arvados-DCO-1.1-Signed-off-by: Stephen Smith <stephen at curii.com>
diff --git a/src/components/subprocess-progress-bar/subprocess-progress-bar.test.tsx b/src/components/subprocess-progress-bar/subprocess-progress-bar.test.tsx
index 83737178..bd8603f9 100644
--- a/src/components/subprocess-progress-bar/subprocess-progress-bar.test.tsx
+++ b/src/components/subprocess-progress-bar/subprocess-progress-bar.test.tsx
@@ -73,8 +73,14 @@ describe("<SubprocessProgressBar />", () => {
statusResponse = {
[ProcessStatusFilter.COMPLETED]: 100,
[ProcessStatusFilter.RUNNING]: 200,
- [ProcessStatusFilter.FAILED]: 300,
- [ProcessStatusFilter.QUEUED]: 400,
+
+ // Combined into failed segment
+ [ProcessStatusFilter.FAILED]: 200,
+ [ProcessStatusFilter.CANCELLED]: 100,
+
+ // Combined into queued segment
+ [ProcessStatusFilter.QUEUED]: 300,
+ [ProcessStatusFilter.ONHOLD]: 100,
};
services.containerRequestService.list = createMockListFunc(process.containerRequest.containerUuid);
@@ -88,12 +94,14 @@ describe("<SubprocessProgressBar />", () => {
});
await progressBar.update();
- // expects 4 subprocess status list requests
+ // expects 6 subprocess status list requests
const expectedFilters = [
ProcessStatusFilter.COMPLETED,
ProcessStatusFilter.RUNNING,
ProcessStatusFilter.FAILED,
+ ProcessStatusFilter.CANCELLED,
ProcessStatusFilter.QUEUED,
+ ProcessStatusFilter.ONHOLD,
].map((state) =>
buildProcessStatusFilters(
new FilterBuilder().addEqual(
@@ -128,8 +136,12 @@ describe("<SubprocessProgressBar />", () => {
statusResponse = {
[ProcessStatusFilter.COMPLETED]: 50,
[ProcessStatusFilter.RUNNING]: 55,
- [ProcessStatusFilter.FAILED]: 60,
- [ProcessStatusFilter.QUEUED]: 335,
+
+ [ProcessStatusFilter.FAILED]: 30,
+ [ProcessStatusFilter.CANCELLED]: 30,
+
+ [ProcessStatusFilter.QUEUED]: 235,
+ [ProcessStatusFilter.ONHOLD]: 100,
};
services.containerRequestService.list = createMockListFunc(process.containerRequest.containerUuid);
diff --git a/src/store/subprocess-panel/subprocess-panel-actions.ts b/src/store/subprocess-panel/subprocess-panel-actions.ts
index 68ed453f..a67dd1f4 100644
--- a/src/store/subprocess-panel/subprocess-panel-actions.ts
+++ b/src/store/subprocess-panel/subprocess-panel-actions.ts
@@ -18,11 +18,35 @@ export const loadSubprocessPanel = () =>
dispatch(subprocessPanelActions.REQUEST_ITEMS());
};
-type ProcessStatusCount = {
+/**
+ * Holds a ProgressBarData status type and process count result
+ */
+type ProcessStatusBarCount = {
status: keyof ProgressBarData;
count: number;
};
+/**
+ * Associates each of the limited progress bar segment types with an array of
+ * ProcessStatusFilterTypes to be combined when displayed
+ */
+type ProcessStatusMap = Record<keyof ProgressBarData, ProcessStatusFilter[]>;
+
+const statusMap: ProcessStatusMap = {
+ [ProcessStatusFilter.COMPLETED]: [ProcessStatusFilter.COMPLETED],
+ [ProcessStatusFilter.RUNNING]: [ProcessStatusFilter.RUNNING],
+ [ProcessStatusFilter.FAILED]: [ProcessStatusFilter.FAILED, ProcessStatusFilter.CANCELLED],
+ [ProcessStatusFilter.QUEUED]: [ProcessStatusFilter.QUEUED, ProcessStatusFilter.ONHOLD],
+};
+
+/**
+ * Utility type to hold a pair of associated progress bar status and process status
+ */
+type ProgressBarStatusPair = {
+ barStatus: keyof ProcessStatusMap;
+ processStatus: ProcessStatusFilter;
+};
+
export const fetchSubprocessProgress = (requestingContainerUuid: string) =>
async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository): Promise<ProgressBarData | undefined> => {
@@ -48,15 +72,20 @@ export const fetchSubprocessProgress = (requestingContainerUuid: string) =>
// 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};
- });
+ const promises = (Object.keys(statusMap) as Array<keyof ProcessStatusMap>)
+ // Split statusMap into pairs of progress bar status and process status
+ .reduce((acc, curr) => [...acc, ...statusMap[curr].map(processStatus => ({barStatus: curr, processStatus}))], [] as ProgressBarStatusPair[])
+ .map(async (statusPair: ProgressBarStatusPair): Promise<ProcessStatusBarCount> => {
+ // For each status pair, request count and return bar status and count
+ const { barStatus, processStatus } = statusPair;
+ const filter = buildProcessStatusFilters(new FilterBuilder(baseFilter), processStatus);
+ const count = (await requestContainerStatusCount(filter)).itemsAvailable;
+ return {status: barStatus, count};
+ });
// Simultaneously requests each status count and apply them to the return object
(await Promise.all(promises)).forEach((singleResult) => {
- result[singleResult.status] = singleResult.count;
+ result[singleResult.status] += singleResult.count;
});
return result;
} catch (e) {
commit 356d030ba429f793d4dd5d9997395cb0a7125514
Author: Stephen Smith <stephen at curii.com>
Date: Mon Nov 13 10:42:44 2023 -0500
20609: Move subprocess progress bar between title and headermenu
Also make multi select toolbar show up in header or below header depending on
whether there is a panel title / progress bar
Arvados-DCO-1.1-Signed-off-by: Stephen Smith <stephen at curii.com>
diff --git a/src/components/data-explorer/data-explorer.tsx b/src/components/data-explorer/data-explorer.tsx
index 7657ae04..8f566192 100644
--- a/src/components/data-explorer/data-explorer.tsx
+++ b/src/components/data-explorer/data-explorer.tsx
@@ -17,15 +17,20 @@ import { CloseIcon, IconType, MaximizeIcon, UnMaximizeIcon, MoreVerticalIcon } f
import { PaperProps } from "@material-ui/core/Paper";
import { MPVPanelProps } from "components/multi-panel-view/multi-panel-view";
-type CssRules = "searchBox" | "headerMenu" | "toolbar" | "footer" | "root" | "moreOptionsButton" | "title" | "dataTable" | "container";
+type CssRules = "titleWrapper" | "searchBox" | "headerMenu" | "toolbar" | "footer" | "root" | "moreOptionsButton" | "title" | "dataTable" | "container";
const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
+ titleWrapper: {
+ display: "flex",
+ justifyContent: "space-between",
+ },
searchBox: {
paddingBottom: 0,
},
toolbar: {
paddingTop: 0,
paddingRight: theme.spacing.unit,
+ paddingLeft: "10px",
},
footer: {
overflow: "auto",
@@ -41,6 +46,8 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
paddingLeft: theme.spacing.unit * 2,
paddingTop: theme.spacing.unit * 2,
fontSize: "18px",
+ flexGrow: 0,
+ paddingRight: "10px",
},
dataTable: {
height: "100%",
@@ -50,11 +57,9 @@ const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
height: "100%",
},
headerMenu: {
- width: "100%",
- float: "right",
- display: "flex",
- flexDirection: "row-reverse",
- justifyContent: "space-between",
+ marginLeft: "auto",
+ flexBasis: "initial",
+ flexGrow: 0,
},
});
@@ -79,7 +84,7 @@ interface DataExplorerDataProps<T> {
actions?: React.ReactNode;
hideSearchInput?: boolean;
title?: React.ReactNode;
- toolbar?: React.ReactNode;
+ progressBar?: React.ReactNode;
paperKey?: string;
currentItemUuid: string;
elementPath?: string;
@@ -114,6 +119,8 @@ export const DataExplorer = withStyles(styles)(
prevRoute: "",
};
+ multiSelectToolbarInTitle = !this.props.title && !this.props.progressBar;
+
componentDidUpdate(prevProps: DataExplorerProps<T>) {
const currentRefresh = this.props.currentRefresh || "";
const currentRoute = this.props.currentRoute || "";
@@ -182,7 +189,7 @@ export const DataExplorer = withStyles(styles)(
fetchMode,
currentItemUuid,
title,
- toolbar,
+ progressBar,
doHidePanel,
doMaximizePanel,
doUnMaximizePanel,
@@ -206,7 +213,7 @@ export const DataExplorer = withStyles(styles)(
wrap="nowrap"
className={classes.container}
>
- <div>
+ <div className={classes.titleWrapper}>
{title && (
<Grid
item
@@ -216,6 +223,8 @@ export const DataExplorer = withStyles(styles)(
{title}
</Grid>
)}
+ {!!progressBar && progressBar}
+ {this.multiSelectToolbarInTitle && <MultiselectToolbar />}
{(!hideColumnSelector || !hideSearchInput || !!actions) && (
<Grid
className={classes.headerMenu}
@@ -223,25 +232,27 @@ export const DataExplorer = withStyles(styles)(
xs
>
<Toolbar className={classes.toolbar}>
- {!hideSearchInput && (
- <div className={classes.searchBox}>
- {!hideSearchInput && (
- <SearchInput
- label={searchLabel}
- value={searchValue}
- selfClearProp={""}
- onSearch={onSearch}
- />
- )}
- </div>
- )}
- {actions}
- {!hideColumnSelector && (
- <ColumnSelector
- columns={columns}
- onColumnToggle={onColumnToggle}
- />
- )}
+ <Grid container justify="space-between" wrap="nowrap" alignItems="center">
+ {!hideSearchInput && (
+ <div className={classes.searchBox}>
+ {!hideSearchInput && (
+ <SearchInput
+ label={searchLabel}
+ value={searchValue}
+ selfClearProp={""}
+ onSearch={onSearch}
+ />
+ )}
+ </div>
+ )}
+ {actions}
+ {!hideColumnSelector && (
+ <ColumnSelector
+ columns={columns}
+ onColumnToggle={onColumnToggle}
+ />
+ )}
+ </Grid>
{doUnMaximizePanel && panelMaximized && (
<Tooltip
title={`Unmaximize ${panelName || "panel"}`}
@@ -276,11 +287,10 @@ export const DataExplorer = withStyles(styles)(
</Tooltip>
)}
</Toolbar>
- <MultiselectToolbar />
</Grid>
)}
- {toolbar && (toolbar)}
</div>
+ {!this.multiSelectToolbarInTitle && <MultiselectToolbar />}
<Grid
item
xs="auto"
diff --git a/src/components/multiselect-toolbar/MultiselectToolbar.tsx b/src/components/multiselect-toolbar/MultiselectToolbar.tsx
index 3d8ae0c3..4eff8885 100644
--- a/src/components/multiselect-toolbar/MultiselectToolbar.tsx
+++ b/src/components/multiselect-toolbar/MultiselectToolbar.tsx
@@ -26,6 +26,7 @@ type CssRules = "root" | "button";
const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
root: {
display: "flex",
+ flexShrink: 0,
flexDirection: "row",
width: 0,
padding: 0,
diff --git a/src/components/subprocess-progress-bar/subprocess-progress-bar.tsx b/src/components/subprocess-progress-bar/subprocess-progress-bar.tsx
index 1d467eea..07178e79 100644
--- a/src/components/subprocess-progress-bar/subprocess-progress-bar.tsx
+++ b/src/components/subprocess-progress-bar/subprocess-progress-bar.tsx
@@ -3,7 +3,7 @@
// SPDX-License-Identifier: AGPL-3.0
import React, { useEffect, useState } from "react";
-import { StyleRulesCallback, Typography, WithStyles, withStyles } from "@material-ui/core";
+import { StyleRulesCallback, Tooltip, 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";
@@ -16,10 +16,13 @@ type CssRules = 'progressWrapper' | 'progressStacked' ;
const styles: StyleRulesCallback<CssRules> = (theme) => ({
progressWrapper: {
- margin: "0 20px",
+ margin: "25px 0 0",
+ flexGrow: 1,
+ flexBasis: "100px",
},
progressStacked: {
border: "1px solid gray",
+ height: "10px",
// Override stripe color to be close to white
"& .progress-bar-striped": {
backgroundImage:
@@ -71,18 +74,23 @@ export const SubprocessProgressBar = connect(null, mapDispatchToProps)(withStyle
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)} />
+ <Tooltip title={`${progressData[ProcessStatusFilter.COMPLETED]} Completed`}>
+ <CProgress height={10} color="success"
+ value={getStatusPercent(progressData, ProcessStatusFilter.COMPLETED)} />
+ </Tooltip>
+ <Tooltip title={`${progressData[ProcessStatusFilter.RUNNING]} Running`}>
+ <CProgress height={10} color="success" variant="striped"
+ value={getStatusPercent(progressData, ProcessStatusFilter.RUNNING)} />
+ </Tooltip>
+ <Tooltip title={`${progressData[ProcessStatusFilter.FAILED]} Failed`}>
+ <CProgress height={10} color="danger"
+ value={getStatusPercent(progressData, ProcessStatusFilter.FAILED)} />
+ </Tooltip>
+ <Tooltip title={`${progressData[ProcessStatusFilter.QUEUED]} Queued`}>
+ <CProgress height={10} color="secondary" variant="striped"
+ value={getStatusPercent(progressData, ProcessStatusFilter.QUEUED)} />
+ </Tooltip>
</CProgressStacked>
- <Typography variant="body2">
- {progressData[ProcessStatusFilter.COMPLETED]} Completed, {progressData[ProcessStatusFilter.RUNNING]} Running, {progressData[ProcessStatusFilter.FAILED]} Failed, {progressData[ProcessStatusFilter.QUEUED]} Queued
- </Typography>
</div> : <></>;
}
));
diff --git a/src/views/subprocess-panel/subprocess-panel-root.tsx b/src/views/subprocess-panel/subprocess-panel-root.tsx
index 33a10275..dd5229bb 100644
--- a/src/views/subprocess-panel/subprocess-panel-root.tsx
+++ b/src/views/subprocess-panel/subprocess-panel-root.tsx
@@ -126,5 +126,5 @@ export const SubprocessPanelRoot = (props: SubprocessPanelProps & MPVPanelProps)
panelMaximized={props.panelMaximized}
panelName={props.panelName}
title={<SubProcessesTitle/>}
- toolbar={<SubprocessProgressBar process={props.process} />} />;
+ progressBar={<SubprocessProgressBar process={props.process} />} />;
};
commit acbdf56bc7c678796cc4a8d0627ab66fd1edf37f
Author: Stephen Smith <stephen at curii.com>
Date: Tue Nov 7 15:52:22 2023 -0500
20609: Update momentjs
Arvados-DCO-1.1-Signed-off-by: Stephen Smith <stephen at curii.com>
diff --git a/package.json b/package.json
index fd9cdf78..d21d0675 100644
--- a/package.json
+++ b/package.json
@@ -50,7 +50,7 @@
"material-ui-pickers": "^2.2.4",
"mem": "4.0.0",
"mime": "^3.0.0",
- "moment": "2.29.1",
+ "moment": "^2.29.4",
"parse-duration": "0.4.4",
"prop-types": "15.7.2",
"query-string": "6.9.0",
diff --git a/yarn.lock b/yarn.lock
index f1fc857f..ef7b9d31 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3888,7 +3888,7 @@ __metadata:
material-ui-pickers: ^2.2.4
mem: 4.0.0
mime: ^3.0.0
- moment: 2.29.1
+ moment: ^2.29.4
node-sass: ^9.0.0
node-sass-chokidar: ^2.0.0
parse-duration: 0.4.4
@@ -12612,14 +12612,7 @@ __metadata:
languageName: node
linkType: hard
-"moment at npm:2.29.1":
- version: 2.29.1
- resolution: "moment at npm:2.29.1"
- checksum: 1e14d5f422a2687996be11dd2d50c8de3bd577c4a4ca79ba5d02c397242a933e5b941655de6c8cb90ac18f01cc4127e55b4a12ae3c527a6c0a274e455979345e
- languageName: node
- linkType: hard
-
-"moment at npm:^2.27.0":
+"moment at npm:^2.27.0, moment at npm:^2.29.4":
version: 2.29.4
resolution: "moment at npm:2.29.4"
checksum: 0ec3f9c2bcba38dc2451b1daed5daded747f17610b92427bebe1d08d48d8b7bdd8d9197500b072d14e326dd0ccf3e326b9e3d07c5895d3d49e39b6803b76e80e
commit 72c70bed4eb3098a92a0deb07841a0b46d9df5bf
Author: Stephen Smith <stephen at curii.com>
Date: Tue Nov 7 15:52:05 2023 -0500
20609: Update caniuse
Arvados-DCO-1.1-Signed-off-by: Stephen Smith <stephen at curii.com>
diff --git a/yarn.lock b/yarn.lock
index 7b837aab..f1fc857f 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5135,17 +5135,10 @@ __metadata:
languageName: node
linkType: hard
-"caniuse-lite at npm:^1.0.0, caniuse-lite at npm:^1.0.30000981, caniuse-lite at npm:^1.0.30001035, caniuse-lite at npm:^1.0.30001109":
- version: 1.0.30001486
- resolution: "caniuse-lite at npm:1.0.30001486"
- checksum: 5e8c2ba2679e4ad17dea6d2761a6449b814441bfeac81af6cc9d58af187df6af4b79b27befcbfc4a557e720b21c0399a7d1911c8705922e38938dcc0f40b5d4b
- languageName: node
- linkType: hard
-
-"caniuse-lite at npm:^1.0.30001541":
- version: 1.0.30001543
- resolution: "caniuse-lite at npm:1.0.30001543"
- checksum: 1a65c8b0b93913b6241c7d66e1e1f3ea0f194f7e140eefe500512641c2eb4df285991ec9869a1ba2856ea6f6d21e9f3d7bcd91971b5fb1721e3fa0390feec6f1
+"caniuse-lite at npm:^1.0.0, caniuse-lite at npm:^1.0.30000981, caniuse-lite at npm:^1.0.30001035, caniuse-lite at npm:^1.0.30001109, caniuse-lite at npm:^1.0.30001541":
+ version: 1.0.30001561
+ resolution: "caniuse-lite at npm:1.0.30001561"
+ checksum: 949829fe037e23346595614e01d362130245920503a12677f2506ce68e1240360113d6383febed41e8aa38cd0f5fd9c69c21b0af65a71c0246d560db489f1373
languageName: node
linkType: hard
commit 82646dcb3d8f2497de1a33d2250101749526662f
Author: Stephen Smith <stephen at curii.com>
Date: Tue Nov 7 15:50:37 2023 -0500
20609: Add unit tests for subprocess progress bar, includes node/dom upgrade to 16.14.x
Arvados-DCO-1.1-Signed-off-by: Stephen Smith <stephen at curii.com>
diff --git a/cypress/integration/project.spec.js b/cypress/integration/project.spec.js
index a8663d86..e6113821 100644
--- a/cypress/integration/project.spec.js
+++ b/cypress/integration/project.spec.js
@@ -564,7 +564,7 @@ describe("Project tests", function () {
);
});
- it.only("sorts displayed items correctly", () => {
+ it("sorts displayed items correctly", () => {
cy.loginAs(activeUser);
cy.get('[data-cy=project-panel] button[title="Select columns"]').click();
diff --git a/package.json b/package.json
index acc2db6a..fd9cdf78 100644
--- a/package.json
+++ b/package.json
@@ -54,11 +54,11 @@
"parse-duration": "0.4.4",
"prop-types": "15.7.2",
"query-string": "6.9.0",
- "react": "16.8.6",
+ "react": "16.14.0",
"react-copy-to-clipboard": "5.0.3",
"react-dnd": "5.0.0",
"react-dnd-html5-backend": "5.0.1",
- "react-dom": "16.8.6",
+ "react-dom": "16.14.0",
"react-dropzone": "5.1.1",
"react-highlight-words": "0.14.0",
"react-idle-timer": "4.3.6",
diff --git a/src/components/subprocess-progress-bar/subprocess-progress-bar.test.tsx b/src/components/subprocess-progress-bar/subprocess-progress-bar.test.tsx
new file mode 100644
index 00000000..83737178
--- /dev/null
+++ b/src/components/subprocess-progress-bar/subprocess-progress-bar.test.tsx
@@ -0,0 +1,153 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import React from "react";
+import { configure, mount } from "enzyme";
+import { ServiceRepository, createServices } from "services/services";
+import { configureStore } from "store/store";
+import { createBrowserHistory } from "history";
+import { mockConfig } from 'common/config';
+import { ApiActions } from "services/api/api-actions";
+import Axios from "axios";
+import MockAdapter from "axios-mock-adapter";
+import { Process } from "store/processes/process";
+import { ContainerState } from "models/container";
+import Adapter from "enzyme-adapter-react-16";
+import { SubprocessProgressBar } from "./subprocess-progress-bar";
+import { Provider } from "react-redux";
+import { FilterBuilder } from 'services/api/filter-builder';
+import { ProcessStatusFilter, buildProcessStatusFilters } from 'store/resource-type-filters/resource-type-filters';
+import {act} from "react-dom/test-utils";
+
+configure({ adapter: new Adapter() });
+
+describe("<SubprocessProgressBar />", () => {
+ const axiosInst = Axios.create({ headers: {} });
+ const axiosMock = new MockAdapter(axiosInst);
+
+ let store;
+ let services: ServiceRepository;
+ const config: any = {};
+ const actions: ApiActions = {
+ progressFn: (id: string, working: boolean) => { },
+ errorFn: (id: string, message: string) => { }
+ };
+ let statusResponse = {
+ [ProcessStatusFilter.COMPLETED]: 0,
+ [ProcessStatusFilter.RUNNING]: 0,
+ [ProcessStatusFilter.FAILED]: 0,
+ [ProcessStatusFilter.QUEUED]: 0,
+ };
+
+ const createMockListFunc = (uuid: string) => jest.fn(async (args) => {
+ const baseFilter = new FilterBuilder().addEqual('requesting_container_uuid', uuid).getFilters();
+
+ const filterResponses = Object.keys(statusResponse)
+ .map(status => ({filters: buildProcessStatusFilters(new FilterBuilder(baseFilter), status).getFilters(), value: statusResponse[status]}));
+
+ const matchedFilter = filterResponses.find(response => response.filters === args.filters);
+ if (matchedFilter) {
+ return { itemsAvailable: matchedFilter.value };
+ } else {
+ return { itemsAvailable: 0 };
+ }
+ });
+
+ beforeEach(() => {
+ services = createServices(mockConfig({}), actions, axiosInst);
+ store = configureStore(createBrowserHistory(), services, config);
+ });
+
+ it("requests subprocess progress stats for stopped processes and displays progress", async () => {
+ // when
+ const process = {
+ container: {
+ state: ContainerState.COMPLETE,
+ },
+ containerRequest: {
+ containerUuid: 'zzzzz-dz642-000000000000000',
+ },
+ } as Process;
+
+ statusResponse = {
+ [ProcessStatusFilter.COMPLETED]: 100,
+ [ProcessStatusFilter.RUNNING]: 200,
+ [ProcessStatusFilter.FAILED]: 300,
+ [ProcessStatusFilter.QUEUED]: 400,
+ };
+
+ services.containerRequestService.list = createMockListFunc(process.containerRequest.containerUuid);
+
+ let progressBar;
+ await act(async () => {
+ progressBar = mount(
+ <Provider store={store}>
+ <SubprocessProgressBar process={process} />
+ </Provider>);
+ });
+ await progressBar.update();
+
+ // expects 4 subprocess status list requests
+ const expectedFilters = [
+ ProcessStatusFilter.COMPLETED,
+ ProcessStatusFilter.RUNNING,
+ ProcessStatusFilter.FAILED,
+ ProcessStatusFilter.QUEUED,
+ ].map((state) =>
+ buildProcessStatusFilters(
+ new FilterBuilder().addEqual(
+ "requesting_container_uuid",
+ process.containerRequest.containerUuid
+ ),
+ state
+ ).getFilters()
+ );
+
+ expectedFilters.forEach((filter) => {
+ expect(services.containerRequestService.list).toHaveBeenCalledWith({limit: 0, offset: 0, filters: filter});
+ });
+
+ // Verify progress bar with correct degment widths
+ ['10%', '20%', '30%', '40%'].forEach((value, i) => {
+ const styles = progressBar.find('.progress').at(i).props().style;
+ expect(styles).toHaveProperty('width', value);
+ });
+ });
+
+ it("dislays correct progress bar widths with different values", async () => {
+ const process = {
+ container: {
+ state: ContainerState.COMPLETE,
+ },
+ containerRequest: {
+ containerUuid: 'zzzzz-dz642-000000000000001',
+ },
+ } as Process;
+
+ statusResponse = {
+ [ProcessStatusFilter.COMPLETED]: 50,
+ [ProcessStatusFilter.RUNNING]: 55,
+ [ProcessStatusFilter.FAILED]: 60,
+ [ProcessStatusFilter.QUEUED]: 335,
+ };
+
+ services.containerRequestService.list = createMockListFunc(process.containerRequest.containerUuid);
+
+ let progressBar;
+ await act(async () => {
+ progressBar = mount(
+ <Provider store={store}>
+ <SubprocessProgressBar process={process} />
+ </Provider>);
+ });
+ await progressBar.update();
+
+ // Verify progress bar with correct degment widths
+ ['10%', '11%', '12%', '67%'].forEach((value, i) => {
+ const styles = progressBar.find('.progress').at(i).props().style;
+ expect(styles).toHaveProperty('width', value);
+ });
+ });
+
+});
diff --git a/src/store/tree-picker/tree-picker-actions.test.ts b/src/store/tree-picker/tree-picker-actions.test.ts
index 9622282c..7a55503e 100644
--- a/src/store/tree-picker/tree-picker-actions.test.ts
+++ b/src/store/tree-picker/tree-picker-actions.test.ts
@@ -23,10 +23,7 @@ describe('tree-picker-actions', () => {
let store: RootStore;
let services: ServiceRepository;
- const config: any = {
-
-
- };
+ const config: any = {};
const actions: ApiActions = {
progressFn: (id: string, working: boolean) => { },
errorFn: (id: string, message: string) => { }
diff --git a/yarn.lock b/yarn.lock
index 142694c8..7b837aab 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3894,11 +3894,11 @@ __metadata:
parse-duration: 0.4.4
prop-types: 15.7.2
query-string: 6.9.0
- react: 16.8.6
+ react: 16.14.0
react-copy-to-clipboard: 5.0.3
react-dnd: 5.0.0
react-dnd-html5-backend: 5.0.1
- react-dom: 16.8.6
+ react-dom: 16.14.0
react-dropzone: 5.1.1
react-highlight-words: 0.14.0
react-idle-timer: 4.3.6
@@ -15349,17 +15349,17 @@ __metadata:
languageName: node
linkType: hard
-"react-dom at npm:16.8.6":
- version: 16.8.6
- resolution: "react-dom at npm:16.8.6"
+"react-dom at npm:16.14.0":
+ version: 16.14.0
+ resolution: "react-dom at npm:16.14.0"
dependencies:
loose-envify: ^1.1.0
object-assign: ^4.1.1
prop-types: ^15.6.2
- scheduler: ^0.13.6
+ scheduler: ^0.19.1
peerDependencies:
- react: ^16.0.0
- checksum: 7f8ebd8523eb4a14a1439efa009d020abc0529da25d0de251a4f3d5b3781061f6b30d72425f5fe944317850997efc6c1d667e99b1fd70172f30a976a00008bf6
+ react: ^16.14.0
+ checksum: 5a5c49da0f106b2655a69f96c622c347febcd10532db391c262b26aec225b235357d9da1834103457683482ab1b229af7a50f6927a6b70e53150275e31785544
languageName: node
linkType: hard
@@ -15683,15 +15683,14 @@ __metadata:
languageName: node
linkType: hard
-"react at npm:16.8.6":
- version: 16.8.6
- resolution: "react at npm:16.8.6"
+"react at npm:16.14.0":
+ version: 16.14.0
+ resolution: "react at npm:16.14.0"
dependencies:
loose-envify: ^1.1.0
object-assign: ^4.1.1
prop-types: ^15.6.2
- scheduler: ^0.13.6
- checksum: 8dfdbec9af6999c2cfb33a9389995c6401daba732e1ee7e0a4920d28fd2e8e6b0fde99dfe4b8e2f81efc4a962c92656e3e79e221323449e55850232163f15ff4
+ checksum: 8484f3ecb13414526f2a7412190575fc134da785c02695eb92bb6028c930bfe1c238d7be2a125088fec663cc7cda0a3623373c46807cf2c281f49c34b79881ac
languageName: node
linkType: hard
@@ -16696,16 +16695,6 @@ __metadata:
languageName: node
linkType: hard
-"scheduler at npm:^0.13.6":
- version: 0.13.6
- resolution: "scheduler at npm:0.13.6"
- dependencies:
- loose-envify: ^1.1.0
- object-assign: ^4.1.1
- checksum: c82c705f6d0d6df87b26bf2cca33f427e91889438c0435ade3ee7f41860eda4dd7f3171ca2d93e8fe9431f3bd831ca0e267a401a0296e4b14de05e389f82d320
- languageName: node
- linkType: hard
-
"scheduler at npm:^0.19.1":
version: 0.19.1
resolution: "scheduler at npm:0.19.1"
commit 9eca8f9b0755eaeb1104a8e699a463f0ac127040
Author: Stephen Smith <stephen at curii.com>
Date: Mon Nov 6 09:44:20 2023 -0500
20609: Add subprogress progress bar along with required bootstrap/coreUI styles
Arvados-DCO-1.1-Signed-off-by: Stephen Smith <stephen at curii.com>
diff --git a/package.json b/package.json
index 35c960c4..acc2db6a 100644
--- a/package.json
+++ b/package.json
@@ -3,6 +3,8 @@
"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",
@@ -27,6 +29,7 @@
"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",
diff --git a/src/components/data-explorer/data-explorer.tsx b/src/components/data-explorer/data-explorer.tsx
index ad5762df..7657ae04 100644
--- a/src/components/data-explorer/data-explorer.tsx
+++ b/src/components/data-explorer/data-explorer.tsx
@@ -79,6 +79,7 @@ interface DataExplorerDataProps<T> {
actions?: React.ReactNode;
hideSearchInput?: boolean;
title?: React.ReactNode;
+ toolbar?: React.ReactNode;
paperKey?: string;
currentItemUuid: string;
elementPath?: string;
@@ -181,6 +182,7 @@ export const DataExplorer = withStyles(styles)(
fetchMode,
currentItemUuid,
title,
+ toolbar,
doHidePanel,
doMaximizePanel,
doUnMaximizePanel,
@@ -277,6 +279,7 @@ export const DataExplorer = withStyles(styles)(
<MultiselectToolbar />
</Grid>
)}
+ {toolbar && (toolbar)}
</div>
<Grid
item
diff --git a/src/components/subprocess-progress-bar/subprocess-progress-bar.tsx b/src/components/subprocess-progress-bar/subprocess-progress-bar.tsx
new file mode 100644
index 00000000..1d467eea
--- /dev/null
+++ b/src/components/subprocess-progress-bar/subprocess-progress-bar.tsx
@@ -0,0 +1,97 @@
+// 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);
diff --git a/src/index.tsx b/src/index.tsx
index ede257dc..ef9ff9c9 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -91,6 +91,9 @@ import { workflowActionSet, readOnlyWorkflowActionSet } from "views-components/c
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);
diff --git a/src/store/subprocess-panel/subprocess-panel-actions.ts b/src/store/subprocess-panel/subprocess-panel-actions.ts
index b440776c..68ed453f 100644
--- a/src/store/subprocess-panel/subprocess-panel-actions.ts
+++ b/src/store/subprocess-panel/subprocess-panel-actions.ts
@@ -6,6 +6,9 @@ import { Dispatch } from 'redux';
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);
@@ -14,3 +17,52 @@ export const loadSubprocessPanel = () =>
(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;
+ }
+ };
diff --git a/src/views/process-panel/process-panel-root.tsx b/src/views/process-panel/process-panel-root.tsx
index 7a240899..c972c0a6 100644
--- a/src/views/process-panel/process-panel-root.tsx
+++ b/src/views/process-panel/process-panel-root.tsx
@@ -205,7 +205,7 @@ export const ProcessPanelRoot = withStyles(styles)(
xs
maxHeight="50%"
data-cy="process-children">
- <SubprocessPanel />
+ <SubprocessPanel process={process} />
</MPVPanelContent>
</MPVContainer>
) : (
diff --git a/src/views/subprocess-panel/subprocess-panel-root.tsx b/src/views/subprocess-panel/subprocess-panel-root.tsx
index 9cf1db77..33a10275 100644
--- a/src/views/subprocess-panel/subprocess-panel-root.tsx
+++ b/src/views/subprocess-panel/subprocess-panel-root.tsx
@@ -20,6 +20,8 @@ import { MPVPanelProps } from 'components/multi-panel-view/multi-panel-view';
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';
@@ -80,6 +82,7 @@ export const subprocessPanelColumns: DataColumns<string, ProcessResource> = [
];
export interface SubprocessPanelDataProps {
+ process: Process;
resources: ResourcesState;
}
@@ -122,5 +125,6 @@ export const SubprocessPanelRoot = (props: SubprocessPanelProps & MPVPanelProps)
doUnMaximizePanel={props.doUnMaximizePanel}
panelMaximized={props.panelMaximized}
panelName={props.panelName}
- title={<SubProcessesTitle/>} />;
+ title={<SubProcessesTitle/>}
+ toolbar={<SubprocessProgressBar process={props.process} />} />;
};
diff --git a/src/views/subprocess-panel/subprocess-panel.tsx b/src/views/subprocess-panel/subprocess-panel.tsx
index 0aa02d52..c52f054b 100644
--- a/src/views/subprocess-panel/subprocess-panel.tsx
+++ b/src/views/subprocess-panel/subprocess-panel.tsx
@@ -26,7 +26,7 @@ const mapDispatchToProps = (dispatch: Dispatch): SubprocessPanelActionProps => (
},
});
-const mapStateToProps = (state: RootState): SubprocessPanelDataProps => ({
+const mapStateToProps = (state: RootState): Omit<SubprocessPanelDataProps,'process'> => ({
resources: state.resources,
});
diff --git a/yarn.lock b/yarn.lock
index f9dfa6a9..142694c8 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1646,6 +1646,26 @@ __metadata:
languageName: node
linkType: hard
+"@coreui/coreui at npm:next":
+ version: 5.0.0-alpha.3
+ resolution: "@coreui/coreui at npm:5.0.0-alpha.3"
+ peerDependencies:
+ "@popperjs/core": ^2.11.8
+ checksum: 2363ad6be775c6a895a49126a5b9062ffa9ebd0bea6dfb835c1300cd122fb1cf18d85fe647a9c08a3a384caa871e761d8ffb28ea45c7872cb2b034df6527da20
+ languageName: node
+ linkType: hard
+
+"@coreui/react at npm:next":
+ version: 5.0.0-alpha.3
+ resolution: "@coreui/react at 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 at npm:^1.4.0":
version: 1.4.0
resolution: "@csstools/convert-colors at npm:1.4.0"
@@ -3800,6 +3820,8 @@ __metadata:
version: 0.0.0-use.local
resolution: "arvados-workbench-2 at 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
@@ -3841,6 +3863,7 @@ __metadata:
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
@@ -4605,6 +4628,15 @@ __metadata:
languageName: node
linkType: hard
+"bootstrap at npm:^5.3.2":
+ version: 5.3.2
+ resolution: "bootstrap at npm:5.3.2"
+ peerDependencies:
+ "@popperjs/core": ^2.11.8
+ checksum: d5580b253d121ffc137388d41da58dce8d15f1ccd574e12f28d4a08e7649ca15e95db645b2b677cb8025bccd446bff04138fc0fe64f8cba0ccc5dc004a8644cf
+ languageName: node
+ linkType: hard
+
"brace-expansion at npm:^1.1.7":
version: 1.1.11
resolution: "brace-expansion at npm:1.1.11"
-----------------------------------------------------------------------
hooks/post-receive
--
More information about the arvados-commits
mailing list