1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
5 import { unionize, ofType, UnionOf } from "common/unionize";
10 } from "store/processes/processes-actions";
11 import { Dispatch } from "redux";
12 import { Process, ProcessStatus } from "store/processes/process";
13 import { RootState } from "store/store";
14 import { ServiceRepository } from "services/services";
15 import { navigateTo } from "store/navigation/navigation-action";
16 import { snackbarActions } from "store/snackbar/snackbar-actions";
17 import { SnackbarKind } from "../snackbar/snackbar-actions";
18 import { loadSubprocessPanel, subprocessPanelActions } from "../subprocess-panel/subprocess-panel-actions";
19 import { initProcessLogsPanel, processLogsPanelActions } from "store/process-logs-panel/process-logs-panel-actions";
20 import { CollectionFile } from "models/collection-file";
21 import { ContainerRequestResource } from "models/container-request";
22 import { CommandOutputParameter } from "cwlts/mappings/v1.0/CommandOutputParameter";
23 import { CommandInputParameter, getIOParamId, WorkflowInputsData } from "models/workflow";
24 import { getIOParamDisplayValue, ProcessIOParameter } from "views/process-panel/process-io-card";
25 import { OutputDetails, NodeInstanceType, NodeInfo, UsageReport } from "./process-panel";
26 import { AuthState } from "store/auth/auth-reducer";
27 import { ContextMenuResource } from "store/context-menu/context-menu-actions";
28 import { OutputDataUpdate } from "./process-panel-reducer";
29 import { updateResources } from "store/resources/resources-actions";
30 import { ContainerResource } from "models/container";
31 import { FilterBuilder } from "services/api/filter-builder";
33 export const processPanelActions = unionize({
34 RESET_PROCESS_PANEL: ofType<{}>(),
35 SET_PROCESS_PANEL_CONTAINER_REQUEST_UUID: ofType<string>(),
36 SET_PROCESS_PANEL_FILTERS: ofType<string[]>(),
37 TOGGLE_PROCESS_PANEL_FILTER: ofType<string>(),
38 SET_INPUT_RAW: ofType<WorkflowInputsData | null>(),
39 SET_INPUT_PARAMS: ofType<ProcessIOParameter[] | null>(),
40 SET_OUTPUT_DATA: ofType<OutputDataUpdate | null>(),
41 SET_OUTPUT_DEFINITIONS: ofType<CommandOutputParameter[]>(),
42 SET_OUTPUT_PARAMS: ofType<ProcessIOParameter[] | null>(),
43 SET_NODE_INFO: ofType<NodeInfo>(),
44 SET_USAGE_REPORT: ofType<UsageReport>(),
47 export type ProcessPanelAction = UnionOf<typeof processPanelActions>;
49 export const toggleProcessPanelFilter = processPanelActions.TOGGLE_PROCESS_PANEL_FILTER;
51 export const loadProcess =
52 (containerRequestUuid: string) =>
53 async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository): Promise<Process | undefined> => {
54 let containerRequest: ContainerRequestResource | undefined = undefined;
55 let container: ContainerResource | undefined = undefined;
58 const containerRequestResult = await services.groupsService.contents(
60 filters: new FilterBuilder().addIsA('uuid', 'arvados#containerRequest')
61 .addEqual('uuid', containerRequestUuid)
63 include: ["container_uuid"]
65 if (containerRequestResult.items.length === 1) {
66 containerRequest = containerRequestResult.items[0] as ContainerRequestResource;
67 dispatch<any>(updateResources(containerRequestResult.items));
69 if (containerRequestResult.included?.length === 1) {
70 container = containerRequestResult.included[0] as ContainerResource;
71 dispatch<any>(updateResources(containerRequestResult.included));
75 if (!containerRequest) {
77 snackbarActions.OPEN_SNACKBAR({
80 kind: SnackbarKind.ERROR,
86 if (!containerRequest) {
90 if (!container && containerRequest.containerUuid) {
91 // Get the container the old fashioned way
93 container = await services.containerService.get(containerRequest.containerUuid, false);
94 dispatch<any>(updateResources([container]));
98 if (container && container.runtimeUserUuid) {
100 const runtimeUser = await services.userService.get(container.runtimeUserUuid, false);
101 dispatch<any>(updateResources([runtimeUser]));
105 if (containerRequest.outputUuid) {
107 const collection = await services.collectionService.get(containerRequest.outputUuid, false);
108 dispatch<any>(updateResources([collection]));
112 return { containerRequest, container };
115 export const loadProcessPanel = (uuid: string) => async (dispatch: Dispatch, getState: () => RootState): Promise<Process | undefined> => {
116 // Reset subprocess data explorer if navigating to new process
117 // Avoids resetting pagination when refreshing same process
118 if (getState().processPanel.containerRequestUuid !== uuid) {
119 dispatch(subprocessPanelActions.CLEAR());
121 dispatch(processPanelActions.RESET_PROCESS_PANEL());
122 dispatch(processLogsPanelActions.RESET_PROCESS_LOGS_PANEL());
123 dispatch<ProcessPanelAction>(processPanelActions.SET_PROCESS_PANEL_CONTAINER_REQUEST_UUID(uuid));
124 const process = await dispatch<any>(loadProcess(uuid));
125 dispatch(initProcessPanelFilters);
126 dispatch<any>(initProcessLogsPanel(uuid));
127 dispatch<any>(loadSubprocessPanel());
131 export const navigateToOutput = (resource: ContextMenuResource | ContainerRequestResource) => async (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
133 await services.collectionService.get(resource.outputUuid || '');
134 dispatch<any>(navigateTo(resource.outputUuid || ''));
136 dispatch(snackbarActions.OPEN_SNACKBAR({ message: "Output collection was trashed or deleted.", hideDuration: 4000, kind: SnackbarKind.WARNING }));
140 export const loadInputs =
141 (containerRequest: ContainerRequestResource) => async (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
142 dispatch<ProcessPanelAction>(processPanelActions.SET_INPUT_RAW(getRawInputs(containerRequest)));
143 dispatch<ProcessPanelAction>(processPanelActions.SET_INPUT_PARAMS(formatInputData(getInputs(containerRequest), getState().auth)));
146 export const loadOutputs =
147 (containerRequest: ContainerRequestResource) => async (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
148 const noOutputs: OutputDetails = { raw: {} };
150 if (!containerRequest.outputUuid) {
151 dispatch<ProcessPanelAction>(processPanelActions.SET_OUTPUT_DATA({
152 uuid: containerRequest.uuid,
157 let propsOutputs: any = undefined;
159 propsOutputs = getRawOutputs(containerRequest);
160 const filesPromise = services.collectionService.files(containerRequest.outputUuid);
161 const collectionPromise = services.collectionService.get(containerRequest.outputUuid);
162 const [files, collection] = await Promise.all([filesPromise, collectionPromise]);
164 // If has propsOutput, skip fetching cwl.output.json
165 if (propsOutputs !== undefined) {
166 dispatch<ProcessPanelAction>(
167 processPanelActions.SET_OUTPUT_DATA({
168 uuid: containerRequest.uuid,
171 pdh: collection.portableDataHash,
176 // Fetch outputs from keep
177 const outputFile = files.find(file => file.name === "cwl.output.json") as CollectionFile | undefined;
178 let outputData = outputFile ? await services.collectionService.getFileContents(outputFile) : undefined;
179 if (outputData && (outputData = JSON.parse(outputData)) && collection.portableDataHash) {
180 dispatch<ProcessPanelAction>(
181 processPanelActions.SET_OUTPUT_DATA({
182 uuid: containerRequest.uuid,
185 pdh: collection.portableDataHash,
190 dispatch<ProcessPanelAction>(processPanelActions.SET_OUTPUT_DATA({ uuid: containerRequest.uuid, payload: noOutputs }));
194 dispatch<ProcessPanelAction>(processPanelActions.SET_OUTPUT_DATA({ uuid: containerRequest.uuid, payload: { raw: propsOutputs, failedToLoadOutputCollection: true } }));
198 export const loadNodeJson =
199 (containerRequest: ContainerRequestResource) => async (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
200 const noLog = { nodeInfo: null };
201 if (!containerRequest.logUuid) {
202 dispatch<ProcessPanelAction>(processPanelActions.SET_NODE_INFO(noLog));
206 const filesPromise = services.collectionService.files(containerRequest.logUuid);
207 const collectionPromise = services.collectionService.get(containerRequest.logUuid);
208 const [files] = await Promise.all([filesPromise, collectionPromise]);
210 // Fetch node.json from keep
211 const nodeFile = files.find(file => file.name === "node.json") as CollectionFile | undefined;
212 let nodeData = nodeFile ? await services.collectionService.getFileContents(nodeFile) : undefined;
213 if (nodeData && (nodeData = JSON.parse(nodeData))) {
214 dispatch<ProcessPanelAction>(
215 processPanelActions.SET_NODE_INFO({
216 nodeInfo: nodeData as NodeInstanceType,
220 dispatch<ProcessPanelAction>(processPanelActions.SET_NODE_INFO(noLog));
223 const usageReportFile = files.find(file => file.name === "usage_report.html") as CollectionFile | null;
224 dispatch<ProcessPanelAction>(processPanelActions.SET_USAGE_REPORT({ usageReport: usageReportFile }));
226 dispatch<ProcessPanelAction>(processPanelActions.SET_NODE_INFO(noLog));
227 dispatch<ProcessPanelAction>(processPanelActions.SET_USAGE_REPORT({ usageReport: null }));
231 export const loadOutputDefinitions =
232 (containerRequest: ContainerRequestResource) => async (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
233 if (containerRequest && containerRequest.mounts) {
234 dispatch<ProcessPanelAction>(processPanelActions.SET_OUTPUT_DEFINITIONS(getOutputParameters(containerRequest)));
238 export const updateOutputParams = () => async (dispatch: Dispatch<any>, getState: () => RootState, services: ServiceRepository) => {
239 const outputDefinitions = getState().processPanel.outputDefinitions;
240 const outputData = getState().processPanel.outputData;
242 if (outputData && outputData.raw) {
243 dispatch<ProcessPanelAction>(
244 processPanelActions.SET_OUTPUT_PARAMS(formatOutputData(outputDefinitions, outputData.raw, outputData.pdh, getState().auth))
249 export const openWorkflow = (uuid: string) => (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
250 dispatch<any>(navigateTo(uuid));
253 export const initProcessPanelFilters = processPanelActions.SET_PROCESS_PANEL_FILTERS([
254 ProcessStatus.QUEUED,
255 ProcessStatus.COMPLETED,
256 ProcessStatus.FAILED,
257 ProcessStatus.RUNNING,
258 ProcessStatus.ONHOLD,
259 ProcessStatus.FAILING,
260 ProcessStatus.WARNING,
261 ProcessStatus.CANCELLED,
264 export const formatInputData = (inputs: CommandInputParameter[], auth: AuthState): ProcessIOParameter[] => {
265 return inputs.flatMap((input): ProcessIOParameter[] => {
266 const processValues = getIOParamDisplayValue(auth, input);
267 return processValues.map((thisValue, i) => ({
268 id: i === 0 ? getIOParamId(input) : "",
269 label: i === 0 ? input.label || "" : "",
275 export const formatOutputData = (
276 definitions: CommandOutputParameter[],
278 pdh: string | undefined,
280 ): ProcessIOParameter[] => {
281 return definitions.flatMap((output): ProcessIOParameter[] => {
282 const processValues = getIOParamDisplayValue(auth, Object.assign(output, { value: values[getIOParamId(output)] || [] }), pdh);
283 return processValues.map((thisValue, i) => ({
284 id: i === 0 ? getIOParamId(output) : "",
285 label: i === 0 ? output.label || "" : "",