]> git.arvados.org - arvados.git/blob - services/workbench2/src/views/process-panel/process-panel-root.tsx
Merge branch 'main' into 22793-unify-tab-view
[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 {  Grid, Typography } from '@mui/material';
10 import { ProcessIcon } from "components/icon/icon";
11 import { Process, getProcess, ProcessStatus, getProcessStatus, isProcessQueued, isProcessRunning } from "store/processes/process";
12 import { SubprocessPanel } from "views/subprocess-panel/subprocess-panel";
13 import { MPVContainer, MPVPanelContent, MPVPanelState } from "components/multi-panel-view/multi-panel-view";
14 import { ProcessIOCard, ProcessIOCardType } from "./process-io-card";
15 import { ProcessResourceCard } from "./process-resource-card";
16 import { getProcessPanelLogs, ProcessLogsPanel } from "store/process-logs-panel/process-logs-panel";
17 import { ProcessLogsCard } from "./process-log-card";
18 import { FilterOption } from "views/process-panel/process-log-form";
19 import { getInputCollectionMounts } from "store/processes/processes-actions";
20 import { AuthState } from "store/auth/auth-reducer";
21 import { ProcessCmdCard } from "./process-cmd-card";
22 import { ContainerRequestResource } from "models/container-request";
23 import { ProcessPanel as ProcessPanelState } from "store/process-panel/process-panel";
24 import { NotFoundView } from 'views/not-found-panel/not-found-panel';
25 import { ArvadosTheme } from 'common/custom-theme';
26 import { useAsyncInterval } from "common/use-async-interval";
27 import { WebSocketService } from "websocket/websocket-service";
28 import { RouteComponentProps } from 'react-router';
29 import { ResourcesState } from 'store/resources/resources';
30 import { getInlineFileUrl } from "views-components/context-menu/actions/helpers";
31 import { CollectionFile } from "models/collection-file";
32 import { DetailsCardRoot } from "views-components/details-card/details-card-root";
33 import { OverviewPanel } from 'components/overview-panel/overview-panel';
34 import WarningIcon from '@mui/icons-material/Warning';
35 import { Link } from "react-router-dom";
36 import { ProcessProperties } from "store/processes/process";
37 import { getResourceUrl } from "routes/routes";
38 import { ProcessAttributes } from './process-attributes';
39
40 type CssRules = "root" | 'mpvRoot' | 'overview';
41
42 const styles: CustomStyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
43     root: {
44         width: "100%",
45         display: "flex",
46         flexDirection: "column",
47     },
48     mpvRoot: {
49         flexGrow: 1,
50         display: 'flex',
51         flexDirection: 'column',
52         flexWrap: 'nowrap',
53         minHeight: "500px",
54         '& > div': {
55             height: '100%',
56         },
57     },
58     overview: {
59         height: '100%',
60     },
61 });
62
63 export interface ProcessPanelRootDataProps {
64     resources: ResourcesState;
65     processPanel: ProcessPanelState;
66     processLogsPanel: ProcessLogsPanel;
67     auth: AuthState;
68     usageReport: CollectionFile | null;
69 }
70
71 export interface ProcessPanelRootActionProps {
72     onContextMenu: (event: React.MouseEvent<HTMLElement>, process: Process) => void;
73     onToggle: (status: string) => void;
74     cancelProcess: (uuid: string) => void;
75     startProcess: (uuid: string) => void;
76     resumeOnHoldWorkflow: (uuid: string) => void;
77     onLogFilterChange: (filter: FilterOption) => void;
78     navigateToLog: (uuid: string) => void;
79     onCopyToClipboard: (uuid: string) => void;
80     loadInputs: (containerRequest: ContainerRequestResource) => void;
81     loadOutputs: (containerRequest: ContainerRequestResource) => void;
82     loadNodeJson: (containerRequest: ContainerRequestResource) => void;
83     loadOutputDefinitions: (containerRequest: ContainerRequestResource) => void;
84     updateOutputParams: () => void;
85     pollProcessLogs: (processUuid: string) => Promise<void>;
86     refreshProcess: (processUuid: string) => Promise<void>;
87 }
88
89 export type ProcessPanelRootProps = ProcessPanelRootDataProps & ProcessPanelRootActionProps & WithStyles<CssRules>;
90
91 const panelsData: MPVPanelState[] = [
92     { name: "Overview" },
93     { name: "Logs" },
94     { name: "Subprocesses" },
95     { name: "Outputs" },
96     { name: "Inputs" },
97     { name: "Command" },
98     { name: "Resources" },
99 ];
100
101 export const ProcessPanelRoot = withStyles(styles)(({
102     auth,
103     resources,
104     processPanel,
105     processLogsPanel,
106     loadInputs,
107     loadOutputs,
108     loadNodeJson,
109     loadOutputDefinitions,
110     updateOutputParams,
111     pollProcessLogs,
112     refreshProcess,
113     onContextMenu,
114     cancelProcess,
115     startProcess,
116     resumeOnHoldWorkflow,
117     ...props
118 }: ProcessPanelRootProps & RouteComponentProps<{ id: string }>) => {
119     const process = getProcess(props.match.params.id)(resources);
120     const outputUuid = process?.containerRequest.outputUuid;
121     const containerRequest = process?.containerRequest;
122     const inputMounts = getInputCollectionMounts(process?.containerRequest);
123     const webSocketConnected = WebSocketService.getInstance().isActive();
124     const resubmittedUrl = containerRequest && getResourceUrl(containerRequest.properties[ProcessProperties.FAILED_CONTAINER_RESUBMITTED]);
125     const { inputRaw, inputParams, outputData, outputDefinitions, outputParams, nodeInfo, usageReport } = processPanel;
126
127     const usageReportWithUrl = (process || null) && usageReport && getInlineFileUrl(
128                 `${auth.config.keepWebServiceUrl}${usageReport.url}?api_token=${auth.apiToken}`,
129                 auth.config.keepWebServiceUrl,
130                 auth.config.keepWebInlineServiceUrl
131             )
132
133     React.useEffect(() => {
134         if (containerRequest) {
135             // Load inputs from mounts or props
136             loadInputs(containerRequest);
137             // Fetch raw output (loads from props or keep)
138             loadOutputs(containerRequest);
139             // Loads output definitions from mounts into store
140             loadOutputDefinitions(containerRequest);
141             // load the assigned instance type from node.json in
142             // the log collection
143             loadNodeJson(containerRequest);
144         }
145     }, [containerRequest, loadInputs, loadOutputs, loadOutputDefinitions, loadNodeJson]);
146
147     const maxHeight = "100%";
148
149     // Trigger processing output params when raw or definitions change
150     React.useEffect(() => {
151         updateOutputParams();
152     }, [outputData, outputDefinitions, updateOutputParams]);
153
154     // If WebSocket not connected, poll queued/running process for status updates
155     const shouldPoll =
156         !webSocketConnected &&
157         process && (
158             isProcessQueued(process)
159             || isProcessRunning(process)
160             // Status is unknown if has containerUuid but container resource not loaded
161             || getProcessStatus(process) === ProcessStatus.UNKNOWN
162         );
163     useAsyncInterval(async () => {
164         process && await refreshProcess(process.containerRequest.uuid);
165     }, shouldPoll ? 15000 : null);
166
167         return process ? (
168             <section className={props.classes.root}>
169                 <DetailsCardRoot />
170                 <MPVContainer
171                     className={props.classes.mpvRoot}
172                     panelStates={panelsData}
173                     justifyContent="flex-start">
174                     <MPVPanelContent
175                         forwardProps
176                         item
177                         xs="auto"
178                         className={props.classes.overview}
179                         data-cy="process-details">
180                         <>
181                             {resubmittedUrl && <Grid item xs={12}>
182                                 <Typography>
183                                     <WarningIcon />
184                                     This process failed but was automatically resubmitted.  <Link to={resubmittedUrl}> Click here to go to the resubmitted process.</Link>
185                                 </Typography>
186                             </Grid>}
187                             <OverviewPanel detailsElement={<ProcessAttributes request={process.containerRequest} container={process.container} hideProcessPanelRedundantFields />} />
188                         </>
189                     </MPVPanelContent>
190                     <MPVPanelContent
191                         forwardProps
192                         item
193                         xs
194                         minHeight={maxHeight}
195                         maxHeight={maxHeight}
196                         data-cy="process-logs">
197                         <ProcessLogsCard
198                             onCopy={props.onCopyToClipboard}
199                             process={process}
200                             lines={getProcessPanelLogs(processLogsPanel)}
201                             selectedFilter={{
202                                 label: processLogsPanel.selectedFilter,
203                                 value: processLogsPanel.selectedFilter,
204                             }}
205                             filters={processLogsPanel.filters.map(filter => ({ label: filter, value: filter }))}
206                             onLogFilterChange={props.onLogFilterChange}
207                             navigateToLog={props.navigateToLog}
208                             pollProcessLogs={pollProcessLogs}
209                         />
210                     </MPVPanelContent>
211                     <MPVPanelContent
212                         forwardProps
213                         xs
214                         item
215                         maxHeight={maxHeight}
216                         data-cy="process-children">
217                         <SubprocessPanel process={process} />
218                     </MPVPanelContent>
219                     <MPVPanelContent
220                         forwardProps
221                         xs
222                         item
223                         maxHeight={maxHeight}
224                         data-cy="process-outputs">
225                         <ProcessIOCard
226                             label={ProcessIOCardType.OUTPUT}
227                             process={process}
228                             params={outputParams}
229                             raw={outputData?.raw}
230                             failedToLoadOutputCollection={outputData?.failedToLoadOutputCollection}
231                             outputUuid={outputUuid || ""}
232                         />
233                     </MPVPanelContent>
234                     <MPVPanelContent
235                         forwardProps
236                         xs
237                         item
238                         maxHeight={maxHeight}
239                         data-cy="process-inputs">
240                         <ProcessIOCard
241                             label={ProcessIOCardType.INPUT}
242                             process={process}
243                             params={inputParams}
244                             raw={inputRaw}
245                             mounts={inputMounts}
246                         />
247                     </MPVPanelContent>
248                     <MPVPanelContent
249                         forwardProps
250                         xs="auto"
251                         item
252                         maxHeight={maxHeight}
253                         data-cy="process-cmd">
254                         <ProcessCmdCard
255                             onCopy={props.onCopyToClipboard}
256                             process={process}
257                         />
258                     </MPVPanelContent>
259                     <MPVPanelContent
260                         forwardProps
261                         xs
262                         item
263                         data-cy="process-resources">
264                         <ProcessResourceCard
265                             process={process}
266                             nodeInfo={nodeInfo}
267                             usageReport={usageReportWithUrl}
268                         />
269                     </MPVPanelContent>
270                 </MPVContainer>
271             </section>
272         ) : (
273             <NotFoundView
274                 icon={ProcessIcon}
275                 messages={["Process not found"]}
276             />
277         );
278 }
279 );