Merge branch '21838-picker-search-race' into main. Closes #21838
[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     pollProcessLogs,
99     ...props
100 }: ProcessPanelRootProps) => {
101     const outputUuid = process?.containerRequest.outputUuid;
102     const containerRequest = process?.containerRequest;
103     const inputMounts = getInputCollectionMounts(process?.containerRequest);
104
105     React.useEffect(() => {
106         if (containerRequest) {
107             // Load inputs from mounts or props
108             loadInputs(containerRequest);
109             // Fetch raw output (loads from props or keep)
110             loadOutputs(containerRequest);
111             // Loads output definitions from mounts into store
112             loadOutputDefinitions(containerRequest);
113             // load the assigned instance type from node.json in
114             // the log collection
115             loadNodeJson(containerRequest);
116         }
117     }, [containerRequest, loadInputs, loadOutputs, loadOutputDefinitions, loadNodeJson]);
118
119     const maxHeight = "100%";
120
121     // Trigger processing output params when raw or definitions change
122     React.useEffect(() => {
123         updateOutputParams();
124     }, [outputData, outputDefinitions, updateOutputParams]);
125
126         return process ? (
127             <MPVContainer
128                 className={props.classes.root}
129                 spacing={1}
130                 panelStates={panelsData}
131                 justifyContent="flex-start"
132                 direction="column"
133                 wrap="nowrap">
134                 <MPVPanelContent
135                     forwardProps
136                     xs="auto"
137                     data-cy="process-details">
138                     <ProcessDetailsCard
139                         process={process}
140                         onContextMenu={event => props.onContextMenu(event, process)}
141                         cancelProcess={props.cancelProcess}
142                         startProcess={props.startProcess}
143                         resumeOnHoldWorkflow={props.resumeOnHoldWorkflow}
144                     />
145                 </MPVPanelContent>
146                 <MPVPanelContent
147                     forwardProps
148                     xs
149                     minHeight={maxHeight}
150                     maxHeight={maxHeight}
151                     data-cy="process-logs">
152                     <ProcessLogsCard
153                         onCopy={props.onCopyToClipboard}
154                         process={process}
155                         lines={getProcessPanelLogs(processLogsPanel)}
156                         selectedFilter={{
157                             label: processLogsPanel.selectedFilter,
158                             value: processLogsPanel.selectedFilter,
159                         }}
160                         filters={processLogsPanel.filters.map(filter => ({ label: filter, value: filter }))}
161                         onLogFilterChange={props.onLogFilterChange}
162                         navigateToLog={props.navigateToLog}
163                         pollProcessLogs={pollProcessLogs}
164                     />
165                 </MPVPanelContent>
166                 <MPVPanelContent
167                     forwardProps
168                     xs
169                     maxHeight={maxHeight}
170                     data-cy="process-children">
171                     <SubprocessPanel process={process} />
172                 </MPVPanelContent>
173                 <MPVPanelContent
174                     forwardProps
175                     xs
176                     maxHeight={maxHeight}
177                     data-cy="process-outputs">
178                     <ProcessIOCard
179                         label={ProcessIOCardType.OUTPUT}
180                         process={process}
181                         params={outputParams}
182                         raw={outputData?.raw}
183                         failedToLoadOutputCollection={outputData?.failedToLoadOutputCollection}
184                         outputUuid={outputUuid || ""}
185                     />
186                 </MPVPanelContent>
187                 <MPVPanelContent
188                     forwardProps
189                     xs
190                     maxHeight={maxHeight}
191                     data-cy="process-inputs">
192                     <ProcessIOCard
193                         label={ProcessIOCardType.INPUT}
194                         process={process}
195                         params={inputParams}
196                         raw={inputRaw}
197                         mounts={inputMounts}
198                     />
199                 </MPVPanelContent>
200                 <MPVPanelContent
201                     forwardProps
202                     xs="auto"
203                     maxHeight={"50%"}
204                     data-cy="process-cmd">
205                     <ProcessCmdCard
206                         onCopy={props.onCopyToClipboard}
207                         process={process}
208                     />
209                 </MPVPanelContent>
210                 <MPVPanelContent
211                     forwardProps
212                     xs
213                     data-cy="process-resources">
214                     <ProcessResourceCard
215                         process={process}
216                         nodeInfo={nodeInfo}
217                         usageReport={usageReport}
218                     />
219                 </MPVPanelContent>
220             </MPVContainer>
221         ) : (
222             <NotFoundView
223                 icon={ProcessIcon}
224                 messages={["Process not found"]}
225             />
226         );
227 }
228 );