16073: Refactor process io loading into actions and reducers to eliminate infinite...
[arvados-workbench2.git] / src / views / process-panel / process-panel-root.tsx
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 import React from 'react';
6 import { Grid, StyleRulesCallback, WithStyles, withStyles } from '@material-ui/core';
7 import { DefaultView } from 'components/default-view/default-view';
8 import { ProcessIcon } from 'components/icon/icon';
9 import { Process } from 'store/processes/process';
10 import { SubprocessPanel } from 'views/subprocess-panel/subprocess-panel';
11 import { SubprocessFilterDataProps } from 'components/subprocess-filter/subprocess-filter';
12 import { MPVContainer, MPVPanelContent, MPVPanelState } from 'components/multi-panel-view/multi-panel-view';
13 import { ArvadosTheme } from 'common/custom-theme';
14 import { ProcessDetailsCard } from './process-details-card';
15 import { ProcessIOCard, ProcessIOCardType, ProcessIOParameter } from './process-io-card';
16
17 import { getProcessPanelLogs, ProcessLogsPanel } from 'store/process-logs-panel/process-logs-panel';
18 import { ProcessLogsCard } from './process-log-card';
19 import { FilterOption } from 'views/process-panel/process-log-form';
20 import { getInputCollectionMounts } from 'store/processes/processes-actions';
21 import { CommandInputParameter } from 'models/workflow';
22 import { CommandOutputParameter } from 'cwlts/mappings/v1.0/CommandOutputParameter';
23 import { AuthState } from 'store/auth/auth-reducer';
24 import { ProcessCmdCard } from './process-cmd-card';
25 import { ContainerRequestResource } from 'models/container-request';
26 import { OutputDetails } from 'store/process-panel/process-panel';
27
28 type CssRules = 'root';
29
30 const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
31     root: {
32         width: '100%',
33     },
34 });
35
36 export interface ProcessPanelRootDataProps {
37     process?: Process;
38     subprocesses: Array<Process>;
39     filters: Array<SubprocessFilterDataProps>;
40     processLogsPanel: ProcessLogsPanel;
41     auth: AuthState;
42     inputRaw: CommandInputParameter[] | null;
43     inputParams: ProcessIOParameter[] | null;
44     outputRaw: OutputDetails | null;
45     outputDefinitions: CommandOutputParameter[];
46     outputParams: ProcessIOParameter[] | null;
47 }
48
49 export interface ProcessPanelRootActionProps {
50     onContextMenu: (event: React.MouseEvent<HTMLElement>, process: Process) => void;
51     onToggle: (status: string) => void;
52     cancelProcess: (uuid: string) => void;
53     onLogFilterChange: (filter: FilterOption) => void;
54     navigateToLog: (uuid: string) => void;
55     onCopyToClipboard: (uuid: string) => void;
56     loadInputs: (containerRequest: ContainerRequestResource) => void;
57     loadOutputs: (containerRequest: ContainerRequestResource) => void;
58     loadOutputDefinitions: (containerRequest: ContainerRequestResource) => void;
59     updateOutputParams: () => void;
60 }
61
62 export type ProcessPanelRootProps = ProcessPanelRootDataProps & ProcessPanelRootActionProps & WithStyles<CssRules>;
63
64 const panelsData: MPVPanelState[] = [
65     {name: "Details"},
66     {name: "Command"},
67     {name: "Logs", visible: true},
68     {name: "Inputs"},
69     {name: "Outputs"},
70     {name: "Subprocesses"},
71 ];
72
73 export const ProcessPanelRoot = withStyles(styles)(
74     ({
75         process,
76         auth,
77         processLogsPanel,
78         inputRaw,
79         inputParams,
80         outputRaw,
81         outputDefinitions,
82         outputParams,
83         loadInputs,
84         loadOutputs,
85         loadOutputDefinitions,
86         updateOutputParams,
87         ...props
88     }: ProcessPanelRootProps) => {
89
90     const outputUuid = process?.containerRequest.outputUuid;
91     const containerRequest = process?.containerRequest;
92     const inputMounts = getInputCollectionMounts(process?.containerRequest);
93
94     React.useEffect(() => {
95         if (containerRequest) {
96             // Load inputs from mounts or props
97             loadInputs(containerRequest);
98             // Fetch raw output (loads from props or keep)
99             loadOutputs(containerRequest);
100             // Loads output definitions from mounts into store
101             loadOutputDefinitions(containerRequest);
102         }
103     }, [containerRequest, loadInputs, loadOutputs, loadOutputDefinitions]);
104
105     // Trigger processing output params when raw or definitions change
106     React.useEffect(() => {
107         updateOutputParams();
108     }, [outputRaw, outputDefinitions, updateOutputParams]);
109
110     return process
111         ? <MPVContainer className={props.classes.root} spacing={8} panelStates={panelsData}  justify-content="flex-start" direction="column" wrap="nowrap">
112             <MPVPanelContent forwardProps xs="auto" data-cy="process-details">
113                 <ProcessDetailsCard
114                     process={process}
115                     onContextMenu={event => props.onContextMenu(event, process)}
116                     cancelProcess={props.cancelProcess}
117                 />
118             </MPVPanelContent>
119             <MPVPanelContent forwardProps xs="auto" data-cy="process-cmd">
120                 <ProcessCmdCard
121                     onCopy={props.onCopyToClipboard}
122                     process={process} />
123             </MPVPanelContent>
124             <MPVPanelContent forwardProps xs maxHeight='50%' data-cy="process-logs">
125                 <ProcessLogsCard
126                     onCopy={props.onCopyToClipboard}
127                     process={process}
128                     lines={getProcessPanelLogs(processLogsPanel)}
129                     selectedFilter={{
130                         label: processLogsPanel.selectedFilter,
131                         value: processLogsPanel.selectedFilter
132                     }}
133                     filters={processLogsPanel.filters.map(
134                         filter => ({ label: filter, value: filter })
135                     )}
136                     onLogFilterChange={props.onLogFilterChange}
137                     navigateToLog={props.navigateToLog}
138                 />
139             </MPVPanelContent>
140             <MPVPanelContent forwardProps xs maxHeight='50%' data-cy="process-inputs">
141                 <ProcessIOCard
142                     label={ProcessIOCardType.INPUT}
143                     process={process}
144                     params={inputParams}
145                     raw={inputRaw}
146                     mounts={inputMounts}
147                  />
148             </MPVPanelContent>
149             <MPVPanelContent forwardProps xs maxHeight='50%' data-cy="process-outputs">
150                 <ProcessIOCard
151                     label={ProcessIOCardType.OUTPUT}
152                     process={process}
153                     params={outputParams}
154                     raw={outputRaw?.rawOutputs}
155                     outputUuid={outputUuid || ""}
156                  />
157             </MPVPanelContent>
158             <MPVPanelContent forwardProps xs maxHeight='50%' data-cy="process-children">
159                 <SubprocessPanel />
160             </MPVPanelContent>
161         </MPVContainer>
162         : <Grid container
163             alignItems='center'
164             justify='center'
165             style={{ minHeight: '100%' }}>
166             <DefaultView
167                 icon={ProcessIcon}
168                 messages={['Process not found']} />
169         </Grid>;
170     }
171 );