21702: Merge branch 'main' into 21702-keep-web-replace_files
[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 } 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
30 type CssRules = "root";
31
32 const styles: CustomStyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
33     root: {
34         width: "100%",
35     },
36 });
37
38 export interface ProcessPanelRootDataProps {
39     process?: Process;
40     subprocesses: Array<Process>;
41     filters: Array<SubprocessFilterDataProps>;
42     processLogsPanel: ProcessLogsPanel;
43     auth: AuthState;
44     inputRaw: WorkflowInputsData | null;
45     inputParams: ProcessIOParameter[] | null;
46     outputData: OutputDetails | null;
47     outputDefinitions: CommandOutputParameter[];
48     outputParams: ProcessIOParameter[] | null;
49     nodeInfo: NodeInstanceType | null;
50     usageReport: string | null;
51 }
52
53 export interface ProcessPanelRootActionProps {
54     onContextMenu: (event: React.MouseEvent<HTMLElement>, process: Process) => void;
55     onToggle: (status: string) => void;
56     cancelProcess: (uuid: string) => void;
57     startProcess: (uuid: string) => void;
58     resumeOnHoldWorkflow: (uuid: string) => void;
59     onLogFilterChange: (filter: FilterOption) => void;
60     navigateToLog: (uuid: string) => void;
61     onCopyToClipboard: (uuid: string) => void;
62     loadInputs: (containerRequest: ContainerRequestResource) => void;
63     loadOutputs: (containerRequest: ContainerRequestResource) => void;
64     loadNodeJson: (containerRequest: ContainerRequestResource) => void;
65     loadOutputDefinitions: (containerRequest: ContainerRequestResource) => void;
66     updateOutputParams: () => void;
67     pollProcessLogs: (processUuid: string) => Promise<void>;
68 }
69
70 export type ProcessPanelRootProps = ProcessPanelRootDataProps & ProcessPanelRootActionProps & WithStyles<CssRules>;
71
72 const panelsData: MPVPanelState[] = [
73     { name: "Details" },
74     { name: "Logs", visible: true },
75     { name: "Subprocesses" },
76     { name: "Outputs" },
77     { name: "Inputs" },
78     { name: "Command" },
79     { name: "Resources" },
80 ];
81
82 export const ProcessPanelRoot = withStyles(styles)(({
83     process,
84     auth,
85     processLogsPanel,
86     inputRaw,
87     inputParams,
88     outputData,
89     outputDefinitions,
90     outputParams,
91     nodeInfo,
92     usageReport,
93     loadInputs,
94     loadOutputs,
95     loadNodeJson,
96     loadOutputDefinitions,
97     updateOutputParams,
98     ...props
99 }: ProcessPanelRootProps) => {
100     const outputUuid = process?.containerRequest.outputUuid;
101     const containerRequest = process?.containerRequest;
102     const inputMounts = getInputCollectionMounts(process?.containerRequest);
103
104     React.useEffect(() => {
105         if (containerRequest) {
106             // Load inputs from mounts or props
107             loadInputs(containerRequest);
108             // Fetch raw output (loads from props or keep)
109             loadOutputs(containerRequest);
110             // Loads output definitions from mounts into store
111             loadOutputDefinitions(containerRequest);
112             // load the assigned instance type from node.json in
113             // the log collection
114             loadNodeJson(containerRequest);
115         }
116     }, [containerRequest, loadInputs, loadOutputs, loadOutputDefinitions, loadNodeJson]);
117
118     const maxHeight = "100%";
119
120     // Trigger processing output params when raw or definitions change
121     React.useEffect(() => {
122         updateOutputParams();
123     }, [outputData, outputDefinitions, updateOutputParams]);
124
125         return process ? (
126             <MPVContainer
127                 className={props.classes.root}
128                 spacing={1}
129                 panelStates={panelsData}
130                 justifyContent="flex-start"
131                 direction="column"
132                 wrap="nowrap">
133                 <MPVPanelContent
134                     forwardProps
135                     xs="auto"
136                     data-cy="process-details">
137                     <ProcessDetailsCard
138                         process={process}
139                         onContextMenu={event => props.onContextMenu(event, process)}
140                         cancelProcess={props.cancelProcess}
141                         startProcess={props.startProcess}
142                         resumeOnHoldWorkflow={props.resumeOnHoldWorkflow}
143                     />
144                 </MPVPanelContent>
145                 <MPVPanelContent
146                     forwardProps
147                     xs
148                     minHeight={maxHeight}
149                     maxHeight={maxHeight}
150                     data-cy="process-logs">
151                     <ProcessLogsCard
152                         onCopy={props.onCopyToClipboard}
153                         process={process}
154                         lines={getProcessPanelLogs(processLogsPanel)}
155                         selectedFilter={{
156                             label: processLogsPanel.selectedFilter,
157                             value: processLogsPanel.selectedFilter,
158                         }}
159                         filters={processLogsPanel.filters.map(filter => ({ label: filter, value: filter }))}
160                         onLogFilterChange={props.onLogFilterChange}
161                         navigateToLog={props.navigateToLog}
162                         pollProcessLogs={props.pollProcessLogs}
163                     />
164                 </MPVPanelContent>
165                 <MPVPanelContent
166                     forwardProps
167                     xs
168                     maxHeight={maxHeight}
169                     data-cy="process-children">
170                     <SubprocessPanel process={process} />
171                 </MPVPanelContent>
172                 <MPVPanelContent
173                     forwardProps
174                     xs
175                     maxHeight={maxHeight}
176                     data-cy="process-outputs">
177                     <ProcessIOCard
178                         label={ProcessIOCardType.OUTPUT}
179                         process={process}
180                         params={outputParams}
181                         raw={outputData?.raw}
182                         outputUuid={outputUuid || ""}
183                     />
184                 </MPVPanelContent>
185                 <MPVPanelContent
186                     forwardProps
187                     xs
188                     maxHeight={maxHeight}
189                     data-cy="process-inputs">
190                     <ProcessIOCard
191                         label={ProcessIOCardType.INPUT}
192                         process={process}
193                         params={inputParams}
194                         raw={inputRaw}
195                         mounts={inputMounts}
196                     />
197                 </MPVPanelContent>
198                 <MPVPanelContent
199                     forwardProps
200                     xs="auto"
201                     maxHeight={"50%"}
202                     data-cy="process-cmd">
203                     <ProcessCmdCard
204                         onCopy={props.onCopyToClipboard}
205                         process={process}
206                     />
207                 </MPVPanelContent>
208                 <MPVPanelContent
209                     forwardProps
210                     xs
211                     data-cy="process-resources">
212                     <ProcessResourceCard
213                         process={process}
214                         nodeInfo={nodeInfo}
215                         usageReport={usageReport}
216                     />
217                 </MPVPanelContent>
218             </MPVContainer>
219         ) : (
220             <NotFoundView
221                 icon={ProcessIcon}
222                 messages={["Process not found"]}
223             />
224         );
225     }
226 );