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