Merge branch '22349-deploy-bundle-passenger'
[arvados.git] / services / workbench2 / 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 { CustomStyleRulesCallback } from 'common/custom-theme';
7 import { WithStyles } from '@mui/styles';
8 import withStyles from '@mui/styles/withStyles';
9 import { ProcessIcon } from "components/icon/icon";
10 import { Process, ProcessStatus, getProcessStatus, isProcessQueued, isProcessRunning } from "store/processes/process";
11 import { SubprocessPanel } from "views/subprocess-panel/subprocess-panel";
12 import { SubprocessFilterDataProps } from "components/subprocess-filter/subprocess-filter";
13 import { MPVContainer, MPVPanelContent, MPVPanelState } from "components/multi-panel-view/multi-panel-view";
14 import { ProcessDetailsCard } from "./process-details-card";
15 import { ProcessIOCard, ProcessIOCardType, ProcessIOParameter } from "./process-io-card";
16 import { ProcessResourceCard } from "./process-resource-card";
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 { WorkflowInputsData } 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, NodeInstanceType } from "store/process-panel/process-panel";
27 import { NotFoundView } from 'views/not-found-panel/not-found-panel';
28 import { ArvadosTheme } from 'common/custom-theme';
29 import { useAsyncInterval } from "common/use-async-interval";
30 import { WebSocketService } from "websocket/websocket-service";
31
32 type CssRules = "root";
33
34 const styles: CustomStyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
35     root: {
36         width: "100%",
37     },
38 });
39
40 export interface ProcessPanelRootDataProps {
41     process?: Process;
42     subprocesses: Array<Process>;
43     filters: Array<SubprocessFilterDataProps>;
44     processLogsPanel: ProcessLogsPanel;
45     auth: AuthState;
46     inputRaw: WorkflowInputsData | null;
47     inputParams: ProcessIOParameter[] | null;
48     outputData: OutputDetails | null;
49     outputDefinitions: CommandOutputParameter[];
50     outputParams: ProcessIOParameter[] | null;
51     nodeInfo: NodeInstanceType | null;
52     usageReport: string | null;
53 }
54
55 export interface ProcessPanelRootActionProps {
56     onContextMenu: (event: React.MouseEvent<HTMLElement>, process: Process) => void;
57     onToggle: (status: string) => void;
58     cancelProcess: (uuid: string) => void;
59     startProcess: (uuid: string) => void;
60     resumeOnHoldWorkflow: (uuid: string) => void;
61     onLogFilterChange: (filter: FilterOption) => void;
62     navigateToLog: (uuid: string) => void;
63     onCopyToClipboard: (uuid: string) => void;
64     loadInputs: (containerRequest: ContainerRequestResource) => void;
65     loadOutputs: (containerRequest: ContainerRequestResource) => void;
66     loadNodeJson: (containerRequest: ContainerRequestResource) => void;
67     loadOutputDefinitions: (containerRequest: ContainerRequestResource) => void;
68     updateOutputParams: () => void;
69     pollProcessLogs: (processUuid: string) => Promise<void>;
70     refreshProcess: (processUuid: string) => Promise<void>;
71 }
72
73 export type ProcessPanelRootProps = ProcessPanelRootDataProps & ProcessPanelRootActionProps & WithStyles<CssRules>;
74
75 const panelsData: MPVPanelState[] = [
76     { name: "Details" },
77     { name: "Logs", visible: true },
78     { name: "Subprocesses" },
79     { name: "Outputs" },
80     { name: "Inputs" },
81     { name: "Command" },
82     { name: "Resources" },
83 ];
84
85 export const ProcessPanelRoot = withStyles(styles)(({
86     process,
87     auth,
88     processLogsPanel,
89     inputRaw,
90     inputParams,
91     outputData,
92     outputDefinitions,
93     outputParams,
94     nodeInfo,
95     usageReport,
96     loadInputs,
97     loadOutputs,
98     loadNodeJson,
99     loadOutputDefinitions,
100     updateOutputParams,
101     pollProcessLogs,
102     refreshProcess,
103     ...props
104 }: ProcessPanelRootProps) => {
105     const outputUuid = process?.containerRequest.outputUuid;
106     const containerRequest = process?.containerRequest;
107     const inputMounts = getInputCollectionMounts(process?.containerRequest);
108     const webSocketConnected = WebSocketService.getInstance().isActive();
109
110     React.useEffect(() => {
111         if (containerRequest) {
112             // Load inputs from mounts or props
113             loadInputs(containerRequest);
114             // Fetch raw output (loads from props or keep)
115             loadOutputs(containerRequest);
116             // Loads output definitions from mounts into store
117             loadOutputDefinitions(containerRequest);
118             // load the assigned instance type from node.json in
119             // the log collection
120             loadNodeJson(containerRequest);
121         }
122     }, [containerRequest, loadInputs, loadOutputs, loadOutputDefinitions, loadNodeJson]);
123
124     const maxHeight = "100%";
125
126     // Trigger processing output params when raw or definitions change
127     React.useEffect(() => {
128         updateOutputParams();
129     }, [outputData, outputDefinitions, updateOutputParams]);
130
131     // If WebSocket not connected, poll queued/running process for status updates
132     const shouldPoll =
133         !webSocketConnected &&
134         process && (
135             isProcessQueued(process)
136             || isProcessRunning(process)
137             // Status is unknown if has containerUuid but container resource not loaded
138             || getProcessStatus(process) === ProcessStatus.UNKNOWN
139         );
140     useAsyncInterval(async () => {
141         process && await refreshProcess(process.containerRequest.uuid);
142     }, shouldPoll ? 15000 : null);
143
144         return process ? (
145             <MPVContainer
146                 className={props.classes.root}
147                 spacing={1}
148                 panelStates={panelsData}
149                 justifyContent="flex-start"
150                 direction="column"
151                 wrap="nowrap">
152                 <MPVPanelContent
153                     forwardProps
154                     item
155                     xs="auto"
156                     data-cy="process-details">
157                     <ProcessDetailsCard
158                         process={process}
159                         onContextMenu={event => props.onContextMenu(event, process)}
160                         cancelProcess={props.cancelProcess}
161                         startProcess={props.startProcess}
162                         resumeOnHoldWorkflow={props.resumeOnHoldWorkflow}
163                     />
164                 </MPVPanelContent>
165                 <MPVPanelContent
166                     forwardProps
167                     item
168                     xs
169                     minHeight={maxHeight}
170                     maxHeight={maxHeight}
171                     data-cy="process-logs">
172                     <ProcessLogsCard
173                         onCopy={props.onCopyToClipboard}
174                         process={process}
175                         lines={getProcessPanelLogs(processLogsPanel)}
176                         selectedFilter={{
177                             label: processLogsPanel.selectedFilter,
178                             value: processLogsPanel.selectedFilter,
179                         }}
180                         filters={processLogsPanel.filters.map(filter => ({ label: filter, value: filter }))}
181                         onLogFilterChange={props.onLogFilterChange}
182                         navigateToLog={props.navigateToLog}
183                         pollProcessLogs={pollProcessLogs}
184                     />
185                 </MPVPanelContent>
186                 <MPVPanelContent
187                     forwardProps
188                     xs
189                     item
190                     maxHeight={maxHeight}
191                     data-cy="process-children">
192                     <SubprocessPanel process={process} />
193                 </MPVPanelContent>
194                 <MPVPanelContent
195                     forwardProps
196                     xs
197                     item
198                     maxHeight={maxHeight}
199                     data-cy="process-outputs">
200                     <ProcessIOCard
201                         label={ProcessIOCardType.OUTPUT}
202                         process={process}
203                         params={outputParams}
204                         raw={outputData?.raw}
205                         failedToLoadOutputCollection={outputData?.failedToLoadOutputCollection}
206                         outputUuid={outputUuid || ""}
207                     />
208                 </MPVPanelContent>
209                 <MPVPanelContent
210                     forwardProps
211                     xs
212                     item
213                     maxHeight={maxHeight}
214                     data-cy="process-inputs">
215                     <ProcessIOCard
216                         label={ProcessIOCardType.INPUT}
217                         process={process}
218                         params={inputParams}
219                         raw={inputRaw}
220                         mounts={inputMounts}
221                     />
222                 </MPVPanelContent>
223                 <MPVPanelContent
224                     forwardProps
225                     xs="auto"
226                     item
227                     maxHeight={"50%"}
228                     data-cy="process-cmd">
229                     <ProcessCmdCard
230                         onCopy={props.onCopyToClipboard}
231                         process={process}
232                     />
233                 </MPVPanelContent>
234                 <MPVPanelContent
235                     forwardProps
236                     xs
237                     item
238                     data-cy="process-resources">
239                     <ProcessResourceCard
240                         process={process}
241                         nodeInfo={nodeInfo}
242                         usageReport={usageReport}
243                     />
244                 </MPVPanelContent>
245             </MPVContainer>
246         ) : (
247             <NotFoundView
248                 icon={ProcessIcon}
249                 messages={["Process not found"]}
250             />
251         );
252 }
253 );