Merge branch '21128-toolbar-context-menu'
[arvados-workbench2.git] / src / store / process-panel / process-panel-actions.ts
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: AGPL-3.0
4
5 import { unionize, ofType, UnionOf } from "common/unionize";
6 import { getInputs, getOutputParameters, getRawInputs, getRawOutputs, loadProcess } from "store/processes/processes-actions";
7 import { Dispatch } from "redux";
8 import { 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 } 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 } from "./process-panel";
22 import { AuthState } from "store/auth/auth-reducer";
23 import { ContextMenuResource } from "store/context-menu/context-menu-actions";
24
25 export const processPanelActions = unionize({
26     RESET_PROCESS_PANEL: ofType<{}>(),
27     SET_PROCESS_PANEL_CONTAINER_REQUEST_UUID: ofType<string>(),
28     SET_PROCESS_PANEL_FILTERS: ofType<string[]>(),
29     TOGGLE_PROCESS_PANEL_FILTER: ofType<string>(),
30     SET_INPUT_RAW: ofType<WorkflowInputsData | null>(),
31     SET_INPUT_PARAMS: ofType<ProcessIOParameter[] | null>(),
32     SET_OUTPUT_RAW: ofType<OutputDetails | null>(),
33     SET_OUTPUT_DEFINITIONS: ofType<CommandOutputParameter[]>(),
34     SET_OUTPUT_PARAMS: ofType<ProcessIOParameter[] | null>(),
35     SET_NODE_INFO: ofType<NodeInfo>(),
36 });
37
38 export type ProcessPanelAction = UnionOf<typeof processPanelActions>;
39
40 export const toggleProcessPanelFilter = processPanelActions.TOGGLE_PROCESS_PANEL_FILTER;
41
42 export const loadProcessPanel = (uuid: string) => async (dispatch: Dispatch, getState: () => RootState) => {
43     // Reset subprocess data explorer if navigating to new process
44     //  Avoids resetting pagination when refreshing same process
45     if (getState().processPanel.containerRequestUuid !== uuid) {
46         dispatch(subprocessPanelActions.CLEAR());
47     }
48     dispatch(processPanelActions.RESET_PROCESS_PANEL());
49     dispatch(processLogsPanelActions.RESET_PROCESS_LOGS_PANEL());
50     dispatch<ProcessPanelAction>(processPanelActions.SET_PROCESS_PANEL_CONTAINER_REQUEST_UUID(uuid));
51     await dispatch<any>(loadProcess(uuid));
52     dispatch(initProcessPanelFilters);
53     dispatch<any>(initProcessLogsPanel(uuid));
54     dispatch<any>(loadSubprocessPanel());
55 };
56
57 export const navigateToOutput = (resource: ContextMenuResource | ContainerRequestResource) => async (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
58     try {
59         await services.collectionService.get(resource.outputUuid || '');
60         dispatch<any>(navigateTo(resource.outputUuid || ''));
61     } catch {
62         dispatch(snackbarActions.OPEN_SNACKBAR({ message: "Output collection was trashed or deleted.", hideDuration: 4000, kind: SnackbarKind.WARNING }));
63     }
64 };
65
66 export const loadInputs =
67     (containerRequest: ContainerRequestResource) => async (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
68         dispatch<ProcessPanelAction>(processPanelActions.SET_INPUT_RAW(getRawInputs(containerRequest)));
69         dispatch<ProcessPanelAction>(processPanelActions.SET_INPUT_PARAMS(formatInputData(getInputs(containerRequest), getState().auth)));
70     };
71
72 export const loadOutputs =
73     (containerRequest: ContainerRequestResource) => async (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
74         const noOutputs = { rawOutputs: {} };
75
76         if (!containerRequest.outputUuid) {
77             dispatch<ProcessPanelAction>(processPanelActions.SET_OUTPUT_RAW({ uuid: containerRequest.uuid, outputRaw: noOutputs }));
78             return;
79         }
80         try {
81             const propsOutputs = getRawOutputs(containerRequest);
82             const filesPromise = services.collectionService.files(containerRequest.outputUuid);
83             const collectionPromise = services.collectionService.get(containerRequest.outputUuid);
84             const [files, collection] = await Promise.all([filesPromise, collectionPromise]);
85
86             // If has propsOutput, skip fetching cwl.output.json
87             if (propsOutputs !== undefined) {
88                 dispatch<ProcessPanelAction>(
89                     processPanelActions.SET_OUTPUT_RAW({
90                         rawOutputs: propsOutputs,
91                         pdh: collection.portableDataHash,
92                     })
93                 );
94             } else {
95                 // Fetch outputs from keep
96                 const outputFile = files.find(file => file.name === "cwl.output.json") as CollectionFile | undefined;
97                 let outputData = outputFile ? await services.collectionService.getFileContents(outputFile) : undefined;
98                 if (outputData && (outputData = JSON.parse(outputData)) && collection.portableDataHash) {
99                     dispatch<ProcessPanelAction>(
100                         processPanelActions.SET_OUTPUT_RAW({
101                             uuid: containerRequest.uuid,
102                             outputRaw: { rawOutputs: outputData, pdh: collection.portableDataHash },
103                         })
104                     );
105                 } else {
106                     dispatch<ProcessPanelAction>(processPanelActions.SET_OUTPUT_RAW({ uuid: containerRequest.uuid, outputRaw: noOutputs }));
107                 }
108             }
109         } catch {
110             dispatch<ProcessPanelAction>(processPanelActions.SET_OUTPUT_RAW({ uuid: containerRequest.uuid, outputRaw: noOutputs }));
111         }
112     };
113
114 export const loadNodeJson =
115     (containerRequest: ContainerRequestResource) => async (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
116         const noLog = { nodeInfo: null };
117         if (!containerRequest.logUuid) {
118             dispatch<ProcessPanelAction>(processPanelActions.SET_NODE_INFO(noLog));
119             return;
120         }
121         try {
122             const filesPromise = services.collectionService.files(containerRequest.logUuid);
123             const collectionPromise = services.collectionService.get(containerRequest.logUuid);
124             const [files] = await Promise.all([filesPromise, collectionPromise]);
125
126             // Fetch node.json from keep
127             const nodeFile = files.find(file => file.name === "node.json") as CollectionFile | undefined;
128             let nodeData = nodeFile ? await services.collectionService.getFileContents(nodeFile) : undefined;
129             if (nodeData && (nodeData = JSON.parse(nodeData))) {
130                 dispatch<ProcessPanelAction>(
131                     processPanelActions.SET_NODE_INFO({
132                         nodeInfo: nodeData as NodeInstanceType,
133                     })
134                 );
135             } else {
136                 dispatch<ProcessPanelAction>(processPanelActions.SET_NODE_INFO(noLog));
137             }
138         } catch {
139             dispatch<ProcessPanelAction>(processPanelActions.SET_NODE_INFO(noLog));
140         }
141     };
142
143 export const loadOutputDefinitions =
144     (containerRequest: ContainerRequestResource) => async (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
145         if (containerRequest && containerRequest.mounts) {
146             dispatch<ProcessPanelAction>(processPanelActions.SET_OUTPUT_DEFINITIONS(getOutputParameters(containerRequest)));
147         }
148     };
149
150 export const updateOutputParams = () => async (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
151     const outputDefinitions = getState().processPanel.outputDefinitions;
152     const outputRaw = getState().processPanel.outputRaw;
153
154     if (outputRaw && outputRaw.rawOutputs) {
155         dispatch<ProcessPanelAction>(
156             processPanelActions.SET_OUTPUT_PARAMS(formatOutputData(outputDefinitions, outputRaw.rawOutputs, outputRaw.pdh, getState().auth))
157         );
158     }
159 };
160
161 export const openWorkflow = (uuid: string) => (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
162     dispatch<any>(navigateTo(uuid));
163 };
164
165 export const initProcessPanelFilters = processPanelActions.SET_PROCESS_PANEL_FILTERS([
166     ProcessStatus.QUEUED,
167     ProcessStatus.COMPLETED,
168     ProcessStatus.FAILED,
169     ProcessStatus.RUNNING,
170     ProcessStatus.ONHOLD,
171     ProcessStatus.FAILING,
172     ProcessStatus.WARNING,
173     ProcessStatus.CANCELLED,
174 ]);
175
176 export const formatInputData = (inputs: CommandInputParameter[], auth: AuthState): ProcessIOParameter[] => {
177     return inputs.map(input => {
178         return {
179             id: getIOParamId(input),
180             label: input.label || "",
181             value: getIOParamDisplayValue(auth, input),
182         };
183     });
184 };
185
186 export const formatOutputData = (
187     definitions: CommandOutputParameter[],
188     values: any,
189     pdh: string | undefined,
190     auth: AuthState
191 ): ProcessIOParameter[] => {
192     return definitions.map(output => {
193         return {
194             id: getIOParamId(output),
195             label: output.label || "",
196             value: getIOParamDisplayValue(auth, Object.assign(output, { value: values[getIOParamId(output)] || [] }), pdh),
197         };
198     });
199 };