From: Stephen Smith Date: Mon, 6 Nov 2023 14:44:20 +0000 (-0500) Subject: 20609: Add subprogress progress bar along with required bootstrap/coreUI styles X-Git-Url: https://git.arvados.org/arvados.git/commitdiff_plain/9eca8f9b0755eaeb1104a8e699a463f0ac127040 20609: Add subprogress progress bar along with required bootstrap/coreUI styles Arvados-DCO-1.1-Signed-off-by: Stephen Smith --- diff --git a/package.json b/package.json index 35c960c418..acc2db6ae1 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 ad5762dfeb..7657ae042b 100644 --- a/src/components/data-explorer/data-explorer.tsx +++ b/src/components/data-explorer/data-explorer.tsx @@ -79,6 +79,7 @@ interface DataExplorerDataProps { 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)( )} + {toolbar && (toolbar)} = (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; +} + +type ProgressBarProps = ProgressBarDataProps & ProgressBarActionProps & WithStyles; + +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(fetchSubprocessProgress(requestingContainerUuid)); + }, +}); + +export const SubprocessProgressBar = connect(null, mapDispatchToProps)(withStyles(styles)( + ({process, classes, fetchSubprocessProgress}: ProgressBarProps) => { + + const [progressData, setProgressData] = useState(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 ?
+ + + + + + + + {progressData[ProcessStatusFilter.COMPLETED]} Completed, {progressData[ProcessStatusFilter.RUNNING]} Running, {progressData[ProcessStatusFilter.FAILED]} Failed, {progressData[ProcessStatusFilter.QUEUED]} Queued + +
: <>; + } +)); + +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 ede257dc5d..ef9ff9c986 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 b440776ce0..68ed453f1a 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 => { + + 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 => { + 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 7a24089901..c972c0a6cf 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"> - + ) : ( diff --git a/src/views/subprocess-panel/subprocess-panel-root.tsx b/src/views/subprocess-panel/subprocess-panel-root.tsx index 9cf1db7753..33a1027585 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 = [ ]; 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={} />; + title={} + toolbar={} />; }; diff --git a/src/views/subprocess-panel/subprocess-panel.tsx b/src/views/subprocess-panel/subprocess-panel.tsx index 0aa02d5270..c52f054b0a 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 => ({ resources: state.resources, }); diff --git a/yarn.lock b/yarn.lock index f9dfa6a954..142694c883 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1646,6 +1646,26 @@ __metadata: 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" @@ -3800,6 +3820,8 @@ __metadata: 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 @@ -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@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"