19315: Add container started by to process details attributes
[arvados-workbench2.git] / src / store / processes / processes-actions.ts
index 47d6824a3075ad153bee90de36b63f25adead642..8a94d8cfcabb8413f522f568e660ede6084cc3e7 100644 (file)
@@ -3,72 +3,95 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import { Dispatch } from "redux";
-import { RootState } from '~/store/store';
-import { ServiceRepository } from '~/services/services';
-import { updateResources } from '~/store/resources/resources-actions';
-import { FilterBuilder } from '~/services/api/filter-builder';
-import { ContainerRequestResource } from '~/models/container-request';
+import { RootState } from 'store/store';
+import { ServiceRepository } from 'services/services';
+import { updateResources } from 'store/resources/resources-actions';
 import { Process } from './process';
-import { dialogActions } from '~/store/dialog/dialog-actions';
-import { snackbarActions, SnackbarKind } from '~/store/snackbar/snackbar-actions';
-import { projectPanelActions } from '~/store/project-panel/project-panel-action';
-import { navigateToRunProcess } from '~/store/navigation/navigation-action';
-import { goToStep, runProcessPanelActions } from '~/store/run-process-panel/run-process-panel-actions';
-import { getResource } from '~/store/resources/resources';
+import { dialogActions } from 'store/dialog/dialog-actions';
+import { snackbarActions, SnackbarKind } from 'store/snackbar/snackbar-actions';
+import { projectPanelActions } from 'store/project-panel/project-panel-action';
+import { navigateToRunProcess } from 'store/navigation/navigation-action';
+import { goToStep, runProcessPanelActions } from 'store/run-process-panel/run-process-panel-actions';
+import { getResource } from 'store/resources/resources';
 import { initialize } from "redux-form";
-import { RUN_PROCESS_BASIC_FORM, RunProcessBasicFormData } from "~/views/run-process-panel/run-process-basic-form";
-import { RunProcessAdvancedFormData, RUN_PROCESS_ADVANCED_FORM } from "~/views/run-process-panel/run-process-advanced-form";
-import { MOUNT_PATH_CWL_WORKFLOW, MOUNT_PATH_CWL_INPUT } from '~/models/process';
-import { getWorkflow, getWorkflowInputs } from "~/models/workflow";
+import { RUN_PROCESS_BASIC_FORM, RunProcessBasicFormData } from "views/run-process-panel/run-process-basic-form";
+import { RunProcessAdvancedFormData, RUN_PROCESS_ADVANCED_FORM } from "views/run-process-panel/run-process-advanced-form";
+import { MOUNT_PATH_CWL_WORKFLOW, MOUNT_PATH_CWL_INPUT } from 'models/process';
+import { CommandInputParameter, getWorkflow, getWorkflowInputs, getWorkflowOutputs } from "models/workflow";
+import { ProjectResource } from "models/project";
+import { UserResource } from "models/user";
+import { CommandOutputParameter } from "cwlts/mappings/v1.0/CommandOutputParameter";
 
 export const loadProcess = (containerRequestUuid: string) =>
     async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository): Promise<Process> => {
-        const response = await services.workflowService.list();
-        dispatch(runProcessPanelActions.SET_WORKFLOWS(response.items));
         const containerRequest = await services.containerRequestService.get(containerRequestUuid);
         dispatch<any>(updateResources([containerRequest]));
+
+        if (containerRequest.outputUuid) {
+            const collection = await services.collectionService.get(containerRequest.outputUuid);
+            dispatch<any>(updateResources([collection]));
+        }
+
         if (containerRequest.containerUuid) {
             const container = await services.containerService.get(containerRequest.containerUuid);
             dispatch<any>(updateResources([container]));
-            await dispatch<any>(loadSubprocesses(containerRequest.containerUuid));
+            if (container.runtimeUserUuid) {
+                const runtimeUser = await services.userService.get(container.runtimeUserUuid);
+                dispatch<any>(updateResources([runtimeUser]));
+            }
             return { containerRequest, container };
         }
         return { containerRequest };
     };
 
-export const loadSubprocesses = (containerUuid: string) =>
+export const loadContainers = (filters: string, loadMounts: boolean = true) =>
     async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
-        const containerRequests = await dispatch<any>(loadContainerRequests(
-            new FilterBuilder().addEqual('requestingContainerUuid', containerUuid).getFilters()
-        )) as ContainerRequestResource[];
-
-        const containerUuids: string[] = containerRequests.reduce((uuids, { containerUuid }) =>
-            containerUuid
-                ? [...uuids, containerUuid]
-                : uuids, []);
-
-        if (containerUuids.length > 0) {
-            await dispatch<any>(loadContainers(
-                new FilterBuilder().addIn('uuid', containerUuids).getFilters()
-            ));
+        let args: any = { filters };
+        if (!loadMounts) {
+            args.select = containerFieldsNoMounts;
         }
-    };
-
-const MAX_AMOUNT_OF_SUBPROCESSES = 10000;
-
-export const loadContainerRequests = (filters: string) =>
-    async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
-        const { items } = await services.containerRequestService.list({ filters, limit: MAX_AMOUNT_OF_SUBPROCESSES });
+        const { items } = await services.containerService.list(args);
         dispatch<any>(updateResources(items));
         return items;
     };
 
-export const loadContainers = (filters: string) =>
-    async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
-        const { items } = await services.containerService.list({ filters });
-        dispatch<any>(updateResources(items));
-        return items;
-    };
+// Until the api supports unselecting fields, we need a list of all other fields to omit mounts
+const containerFieldsNoMounts = [
+    "auth_uuid",
+    "command",
+    "container_image",
+    "created_at",
+    "cwd",
+    "environment",
+    "etag",
+    "exit_code",
+    "finished_at",
+    "gateway_address",
+    "href",
+    "interactive_session_started",
+    "kind",
+    "lock_count",
+    "locked_by_uuid",
+    "log",
+    "modified_at",
+    "modified_by_client_uuid",
+    "modified_by_user_uuid",
+    "output_path",
+    "output_properties",
+    "output_storage_classes",
+    "output",
+    "owner_uuid",
+    "priority",
+    "progress",
+    "runtime_auth_scopes",
+    "runtime_constraints",
+    "runtime_status",
+    "runtime_user_uuid",
+    "scheduling_parameters",
+    "started_at",
+    "state",
+    "uuid",
+]
 
 export const cancelRunningWorkflow = (uuid: string) =>
     async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
@@ -91,7 +114,8 @@ export const reRunProcess = (processUuid: string, workflowUuid: string) =>
             const stringifiedDefinition = JSON.stringify(process.mounts[MOUNT_PATH_CWL_WORKFLOW].content);
             const newWorkflow = { ...workflow, definition: stringifiedDefinition };
 
-            const basicInitialData: RunProcessBasicFormData = { name: `Copy of: ${process.name}`, description: process.description };
+            const owner = getResource<ProjectResource | UserResource>(workflow.ownerUuid)(getState().resources);
+            const basicInitialData: RunProcessBasicFormData = { name: `Copy of: ${process.name}`, description: process.description, owner };
             dispatch<any>(initialize(RUN_PROCESS_BASIC_FORM, basicInitialData));
 
             const advancedInitialData: RunProcessAdvancedFormData = {
@@ -100,7 +124,7 @@ export const reRunProcess = (processUuid: string, workflowUuid: string) =>
                 ram: process.runtimeConstraints.ram,
                 vcpus: process.runtimeConstraints.vcpus,
                 keep_cache_ram: process.runtimeConstraints.keep_cache_ram,
-                api: process.runtimeConstraints.API
+                acr_container_image: process.containerImage
             };
             dispatch<any>(initialize(RUN_PROCESS_ADVANCED_FORM, advancedInitialData));
 
@@ -113,8 +137,25 @@ export const reRunProcess = (processUuid: string, workflowUuid: string) =>
         }
     };
 
-const getInputs = (data: any) => {
+/*
+ * Fetches raw inputs from containerRequest mounts with fallback to properties
+ * Returns undefined if containerRequest not loaded
+ * Returns [] if inputs not found in mounts or props
+ */
+export const getRawInputs = (data: any): CommandInputParameter[] | undefined => {
+    if (!data) { return undefined; }
+    const mountInput = data.mounts?.[MOUNT_PATH_CWL_INPUT]?.content;
+    const propsInput = data.properties?.cwl_input;
+    if (!mountInput && !propsInput) { return []; }
+    return (mountInput || propsInput);
+}
+
+export const getInputs = (data: any): CommandInputParameter[] => {
+    // Definitions from mounts are needed so we return early if missing
     if (!data || !data.mounts || !data.mounts[MOUNT_PATH_CWL_WORKFLOW]) { return []; }
+    const content  = getRawInputs(data) as any;
+    if (!content) { return []; }
+
     const inputs = getWorkflowInputs(data.mounts[MOUNT_PATH_CWL_WORKFLOW].content);
     return inputs ? inputs.map(
         (it: any) => (
@@ -122,10 +163,56 @@ const getInputs = (data: any) => {
                 type: it.type,
                 id: it.id,
                 label: it.label,
-                default: data.mounts[MOUNT_PATH_CWL_INPUT].content[it.id],
+                default: content[it.id],
+                value: content[it.id.split('/').pop()] || [],
                 doc: it.doc
             }
-    )
+        )
+    ) : [];
+};
+
+/*
+ * Fetches raw outputs from containerRequest properties
+ * Assumes containerRequest is loaded
+ */
+export const getRawOutputs = (data: any): CommandInputParameter[] | undefined => {
+    if (!data || !data.properties || !data.properties.cwl_output) { return undefined; }
+    return (data.properties.cwl_output);
+}
+
+export type InputCollectionMount = {
+    path: string;
+    pdh: string;
+}
+
+export const getInputCollectionMounts = (data: any): InputCollectionMount[] => {
+    if (!data || !data.mounts) { return []; }
+    return Object.keys(data.mounts)
+        .map(key => ({
+            ...data.mounts[key],
+            path: key,
+        }))
+        .filter(mount => mount.kind === 'collection' &&
+                mount.portable_data_hash &&
+                mount.path)
+        .map(mount => ({
+            path: mount.path,
+            pdh: mount.portable_data_hash,
+        }));
+};
+
+export const getOutputParameters = (data: any): CommandOutputParameter[] => {
+    if (!data || !data.mounts || !data.mounts[MOUNT_PATH_CWL_WORKFLOW]) { return []; }
+    const outputs = getWorkflowOutputs(data.mounts[MOUNT_PATH_CWL_WORKFLOW].content);
+    return outputs ? outputs.map(
+        (it: any) => (
+            {
+                type: it.type,
+                id: it.id,
+                label: it.label,
+                doc: it.doc
+            }
+        )
     ) : [];
 };
 
@@ -151,5 +238,3 @@ export const removeProcessPermanently = (uuid: string) =>
         dispatch(projectPanelActions.REQUEST_ITEMS());
         dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Removed.', hideDuration: 2000, kind: SnackbarKind.SUCCESS }));
     };
-
-