1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
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';
40 type CssRules = "root" | 'mpvRoot' | 'overview';
42 const styles: CustomStyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
46 flexDirection: "column",
51 flexDirection: 'column',
63 export interface ProcessPanelRootDataProps {
64 resources: ResourcesState;
65 processPanel: ProcessPanelState;
66 processLogsPanel: ProcessLogsPanel;
68 usageReport: CollectionFile | null;
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>;
89 export type ProcessPanelRootProps = ProcessPanelRootDataProps & ProcessPanelRootActionProps & WithStyles<CssRules>;
91 const panelsData: MPVPanelState[] = [
94 { name: "Subprocesses" },
98 { name: "Resources" },
101 export const ProcessPanelRoot = withStyles(styles)(({
109 loadOutputDefinitions,
116 resumeOnHoldWorkflow,
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;
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
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);
145 }, [containerRequest, loadInputs, loadOutputs, loadOutputDefinitions, loadNodeJson]);
147 const maxHeight = "100%";
149 // Trigger processing output params when raw or definitions change
150 React.useEffect(() => {
151 updateOutputParams();
152 }, [outputData, outputDefinitions, updateOutputParams]);
154 // If WebSocket not connected, poll queued/running process for status updates
156 !webSocketConnected &&
158 isProcessQueued(process)
159 || isProcessRunning(process)
160 // Status is unknown if has containerUuid but container resource not loaded
161 || getProcessStatus(process) === ProcessStatus.UNKNOWN
163 useAsyncInterval(async () => {
164 process && await refreshProcess(process.containerRequest.uuid);
165 }, shouldPoll ? 15000 : null);
168 <section className={props.classes.root}>
171 className={props.classes.mpvRoot}
172 panelStates={panelsData}
173 justifyContent="flex-start">
178 className={props.classes.overview}
179 data-cy="process-details">
181 {resubmittedUrl && <Grid item xs={12}>
184 This process failed but was automatically resubmitted. <Link to={resubmittedUrl}> Click here to go to the resubmitted process.</Link>
187 <OverviewPanel detailsElement={<ProcessAttributes request={process.containerRequest} container={process.container} hideProcessPanelRedundantFields />} />
194 minHeight={maxHeight}
195 maxHeight={maxHeight}
196 data-cy="process-logs">
198 onCopy={props.onCopyToClipboard}
200 lines={getProcessPanelLogs(processLogsPanel)}
202 label: processLogsPanel.selectedFilter,
203 value: processLogsPanel.selectedFilter,
205 filters={processLogsPanel.filters.map(filter => ({ label: filter, value: filter }))}
206 onLogFilterChange={props.onLogFilterChange}
207 navigateToLog={props.navigateToLog}
208 pollProcessLogs={pollProcessLogs}
215 maxHeight={maxHeight}
216 data-cy="process-children">
217 <SubprocessPanel process={process} />
223 maxHeight={maxHeight}
224 data-cy="process-outputs">
226 label={ProcessIOCardType.OUTPUT}
228 params={outputParams}
229 raw={outputData?.raw}
230 failedToLoadOutputCollection={outputData?.failedToLoadOutputCollection}
231 outputUuid={outputUuid || ""}
238 maxHeight={maxHeight}
239 data-cy="process-inputs">
241 label={ProcessIOCardType.INPUT}
252 maxHeight={maxHeight}
253 data-cy="process-cmd">
255 onCopy={props.onCopyToClipboard}
263 data-cy="process-resources">
267 usageReport={usageReportWithUrl}
275 messages={["Process not found"]}