1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
5 import { unionize, ofType, UnionOf } from "common/unionize";
6 import { getInputs, getOutputParameters, getRawInputs, getRawOutputs } from "store/processes/processes-actions";
7 import { Dispatch } from "redux";
8 import { Process, ProcessStatus } from "store/processes/process";
9 import { RootState } from "store/store";
10 import { ServiceRepository } from "services/services";
11 import { navigateTo } from "store/navigation/navigation-action";
12 import { snackbarActions } from "store/snackbar/snackbar-actions";
13 import { SnackbarKind } from "../snackbar/snackbar-actions";
14 import { loadSubprocessPanel, subprocessPanelActions } from "../subprocess-panel/subprocess-panel-actions";
15 import { initProcessLogsPanel, processLogsPanelActions } from "store/process-logs-panel/process-logs-panel-actions";
16 import { CollectionFile } from "models/collection-file";
17 import { ContainerRequestResource, ContainerStatus } from "models/container-request";
18 import { CommandOutputParameter } from "cwlts/mappings/v1.0/CommandOutputParameter";
19 import { CommandInputParameter, getIOParamId, WorkflowInputsData } from "models/workflow";
20 import { getIOParamDisplayValue, ProcessIOParameter } from "views/process-panel/process-io-card";
21 import { OutputDetails, NodeInstanceType, NodeInfo, UsageReport } from "./process-panel";
22 import { AuthState } from "store/auth/auth-reducer";
23 import { ContextMenuResource } from "store/context-menu/context-menu-actions";
24 import { OutputDataUpdate } from "./process-panel-reducer";
25 import { updateResources } from "store/resources/resources-actions";
26 import { ContainerResource } from "models/container";
28 export const processPanelActions = unionize({
29 RESET_PROCESS_PANEL: ofType<{}>(),
30 SET_PROCESS_PANEL_CONTAINER_REQUEST_UUID: ofType<string>(),
31 SET_PROCESS_PANEL_FILTERS: ofType<string[]>(),
32 TOGGLE_PROCESS_PANEL_FILTER: ofType<string>(),
33 SET_INPUT_RAW: ofType<WorkflowInputsData | null>(),
34 SET_INPUT_PARAMS: ofType<ProcessIOParameter[] | null>(),
35 SET_OUTPUT_DATA: ofType<OutputDataUpdate | null>(),
36 SET_OUTPUT_DEFINITIONS: ofType<CommandOutputParameter[]>(),
37 SET_OUTPUT_PARAMS: ofType<ProcessIOParameter[] | null>(),
38 SET_NODE_INFO: ofType<NodeInfo>(),
39 SET_USAGE_REPORT: ofType<UsageReport>(),
40 SET_CONTAINER_STATUS: ofType<ContainerStatus>(),
43 export type ProcessPanelAction = UnionOf<typeof processPanelActions>;
45 export const toggleProcessPanelFilter = processPanelActions.TOGGLE_PROCESS_PANEL_FILTER;
47 export const loadContainerStatus =
48 (containerRequestUuid: string) =>
49 (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
50 services.containerRequestService.containerStatus(containerRequestUuid, false).then(containerStatus =>
51 dispatch<any>(processPanelActions.SET_CONTAINER_STATUS(containerStatus))).catch(() => {});
54 export const loadProcess =
55 (containerRequestUuid: string) =>
56 async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository): Promise<Process | undefined> => {
57 let containerRequest: ContainerRequestResource | undefined = undefined;
59 containerRequest = await services.containerRequestService.get(containerRequestUuid);
60 dispatch<any>(updateResources([containerRequest]));
65 dispatch<any>(loadContainerStatus(containerRequestUuid));
67 if (containerRequest.outputUuid) {
69 const collection = await services.collectionService.get(containerRequest.outputUuid, false);
70 dispatch<any>(updateResources([collection]));
74 if (containerRequest.containerUuid) {
75 let container: ContainerResource | undefined = undefined;
77 container = await services.containerService.get(containerRequest.containerUuid, false);
78 dispatch<any>(updateResources([container]));
82 if (container && container.runtimeUserUuid) {
83 const runtimeUser = await services.userService.get(container.runtimeUserUuid, false);
84 dispatch<any>(updateResources([runtimeUser]));
88 return { containerRequest, container };
90 return { containerRequest };
93 export const loadProcessPanel = (uuid: string) => async (dispatch: Dispatch, getState: () => RootState) => {
94 // Reset subprocess data explorer if navigating to new process
95 // Avoids resetting pagination when refreshing same process
96 if (getState().processPanel.containerRequestUuid !== uuid) {
97 dispatch(subprocessPanelActions.CLEAR());
99 dispatch(processPanelActions.RESET_PROCESS_PANEL());
100 dispatch(processLogsPanelActions.RESET_PROCESS_LOGS_PANEL());
101 dispatch<ProcessPanelAction>(processPanelActions.SET_PROCESS_PANEL_CONTAINER_REQUEST_UUID(uuid));
102 await dispatch<any>(loadProcess(uuid));
103 dispatch(initProcessPanelFilters);
104 dispatch<any>(initProcessLogsPanel(uuid));
105 dispatch<any>(loadSubprocessPanel());
108 export const navigateToOutput = (resource: ContextMenuResource | ContainerRequestResource) => async (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
110 await services.collectionService.get(resource.outputUuid || '');
111 dispatch<any>(navigateTo(resource.outputUuid || ''));
113 dispatch(snackbarActions.OPEN_SNACKBAR({ message: "Output collection was trashed or deleted.", hideDuration: 4000, kind: SnackbarKind.WARNING }));
117 export const loadInputs =
118 (containerRequest: ContainerRequestResource) => async (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
119 dispatch<ProcessPanelAction>(processPanelActions.SET_INPUT_RAW(getRawInputs(containerRequest)));
120 dispatch<ProcessPanelAction>(processPanelActions.SET_INPUT_PARAMS(formatInputData(getInputs(containerRequest), getState().auth)));
123 export const loadOutputs =
124 (containerRequest: ContainerRequestResource) => async (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
125 const noOutputs: OutputDetails = { raw: {} };
127 if (!containerRequest.outputUuid) {
128 dispatch<ProcessPanelAction>(processPanelActions.SET_OUTPUT_DATA({
129 uuid: containerRequest.uuid,
135 const propsOutputs = getRawOutputs(containerRequest);
136 const filesPromise = services.collectionService.files(containerRequest.outputUuid);
137 const collectionPromise = services.collectionService.get(containerRequest.outputUuid);
138 const [files, collection] = await Promise.all([filesPromise, collectionPromise]);
140 // If has propsOutput, skip fetching cwl.output.json
141 if (propsOutputs !== undefined) {
142 dispatch<ProcessPanelAction>(
143 processPanelActions.SET_OUTPUT_DATA({
144 uuid: containerRequest.uuid,
147 pdh: collection.portableDataHash,
152 // Fetch outputs from keep
153 const outputFile = files.find(file => file.name === "cwl.output.json") as CollectionFile | undefined;
154 let outputData = outputFile ? await services.collectionService.getFileContents(outputFile) : undefined;
155 if (outputData && (outputData = JSON.parse(outputData)) && collection.portableDataHash) {
156 dispatch<ProcessPanelAction>(
157 processPanelActions.SET_OUTPUT_DATA({
158 uuid: containerRequest.uuid,
161 pdh: collection.portableDataHash,
166 dispatch<ProcessPanelAction>(processPanelActions.SET_OUTPUT_DATA({ uuid: containerRequest.uuid, payload: noOutputs }));
170 dispatch<ProcessPanelAction>(processPanelActions.SET_OUTPUT_DATA({ uuid: containerRequest.uuid, payload: noOutputs }));
174 export const loadNodeJson =
175 (containerRequest: ContainerRequestResource) => async (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
176 const noLog = { nodeInfo: null };
177 if (!containerRequest.logUuid) {
178 dispatch<ProcessPanelAction>(processPanelActions.SET_NODE_INFO(noLog));
182 const filesPromise = services.collectionService.files(containerRequest.logUuid);
183 const collectionPromise = services.collectionService.get(containerRequest.logUuid);
184 const [files] = await Promise.all([filesPromise, collectionPromise]);
186 // Fetch node.json from keep
187 const nodeFile = files.find(file => file.name === "node.json") as CollectionFile | undefined;
188 let nodeData = nodeFile ? await services.collectionService.getFileContents(nodeFile) : undefined;
189 if (nodeData && (nodeData = JSON.parse(nodeData))) {
190 dispatch<ProcessPanelAction>(
191 processPanelActions.SET_NODE_INFO({
192 nodeInfo: nodeData as NodeInstanceType,
196 dispatch<ProcessPanelAction>(processPanelActions.SET_NODE_INFO(noLog));
199 const usageReportFile = files.find(file => file.name === "usage_report.html") as CollectionFile | null;
200 dispatch<ProcessPanelAction>(processPanelActions.SET_USAGE_REPORT({ usageReport: usageReportFile }));
202 dispatch<ProcessPanelAction>(processPanelActions.SET_NODE_INFO(noLog));
203 dispatch<ProcessPanelAction>(processPanelActions.SET_USAGE_REPORT({ usageReport: null }));
207 export const loadOutputDefinitions =
208 (containerRequest: ContainerRequestResource) => async (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
209 if (containerRequest && containerRequest.mounts) {
210 dispatch<ProcessPanelAction>(processPanelActions.SET_OUTPUT_DEFINITIONS(getOutputParameters(containerRequest)));
214 export const updateOutputParams = () => async (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
215 const outputDefinitions = getState().processPanel.outputDefinitions;
216 const outputData = getState().processPanel.outputData;
218 if (outputData && outputData.raw) {
219 dispatch<ProcessPanelAction>(
220 processPanelActions.SET_OUTPUT_PARAMS(formatOutputData(outputDefinitions, outputData.raw, outputData.pdh, getState().auth))
225 export const openWorkflow = (uuid: string) => (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
226 dispatch<any>(navigateTo(uuid));
229 export const initProcessPanelFilters = processPanelActions.SET_PROCESS_PANEL_FILTERS([
230 ProcessStatus.QUEUED,
231 ProcessStatus.COMPLETED,
232 ProcessStatus.FAILED,
233 ProcessStatus.RUNNING,
234 ProcessStatus.ONHOLD,
235 ProcessStatus.FAILING,
236 ProcessStatus.WARNING,
237 ProcessStatus.CANCELLED,
240 export const formatInputData = (inputs: CommandInputParameter[], auth: AuthState): ProcessIOParameter[] => {
241 return inputs.flatMap((input): ProcessIOParameter[] => {
242 const processValues = getIOParamDisplayValue(auth, input);
243 return processValues.map((thisValue, i) => ({
244 id: i === 0 ? getIOParamId(input) : "",
245 label: i === 0 ? input.label || "" : "",
251 export const formatOutputData = (
252 definitions: CommandOutputParameter[],
254 pdh: string | undefined,
256 ): ProcessIOParameter[] => {
257 return definitions.flatMap((output): ProcessIOParameter[] => {
258 const processValues = getIOParamDisplayValue(auth, Object.assign(output, { value: values[getIOParamId(output)] || [] }), pdh);
259 return processValues.map((thisValue, i) => ({
260 id: i === 0 ? getIOParamId(output) : "",
261 label: i === 0 ? output.label || "" : "",