X-Git-Url: https://git.arvados.org/arvados.git/blobdiff_plain/4ec55841b965ae33aad66479bf4d4ad41e6a0b62..4e508e00be54a40845147e17b2410d27a8516dc1:/services/workbench2/src/store/processes/processes-actions.ts diff --git a/services/workbench2/src/store/processes/processes-actions.ts b/services/workbench2/src/store/processes/processes-actions.ts index b6ff4b71ac..9e49aa97d8 100644 --- a/services/workbench2/src/store/processes/processes-actions.ts +++ b/services/workbench2/src/store/processes/processes-actions.ts @@ -3,20 +3,20 @@ // 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 { 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 { 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 { projectPanelDataActions } from "store/project-panel/project-panel-action-bind"; +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 { MOUNT_PATH_CWL_WORKFLOW, MOUNT_PATH_CWL_INPUT } from "models/process"; import { CommandInputParameter, getWorkflow, getWorkflowInputs, getWorkflowOutputs, WorkflowInputsData } from "models/workflow"; import { ProjectResource } from "models/project"; import { UserResource } from "models/user"; @@ -24,9 +24,13 @@ import { CommandOutputParameter } from "cwlts/mappings/v1.0/CommandOutputParamet import { ContainerResource } from "models/container"; import { ContainerRequestResource, ContainerRequestState } from "models/container-request"; import { FilterBuilder } from "services/api/filter-builder"; +import { selectedToArray } from "components/multiselect-toolbar/MultiselectToolbar"; +import { Resource, ResourceKind } from "models/resource"; +import { ContextMenuResource } from "store/context-menu/context-menu-actions"; import { CommonResourceServiceError, getCommonResourceServiceError } from "services/common-service/common-resource-service"; -export const loadProcess = (containerRequestUuid: string) => +export const loadProcess = + (containerRequestUuid: string) => async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository): Promise => { let containerRequest: ContainerRequestResource | undefined = undefined; try { @@ -40,7 +44,7 @@ export const loadProcess = (containerRequestUuid: string) => try { const collection = await services.collectionService.get(containerRequest.outputUuid, false); dispatch(updateResources([collection])); - } catch { } + } catch {} } if (containerRequest.containerUuid) { @@ -48,24 +52,25 @@ export const loadProcess = (containerRequestUuid: string) => try { container = await services.containerService.get(containerRequest.containerUuid, false); dispatch(updateResources([container])); - } catch { } + } catch {} try { if (container && container.runtimeUserUuid) { const runtimeUser = await services.userService.get(container.runtimeUserUuid, false); dispatch(updateResources([runtimeUser])); } - } catch { } + } catch {} return { containerRequest, container }; } return { containerRequest }; }; -export const loadContainers = (containerUuids: string[], loadMounts: boolean = true) => +export const loadContainers = + (containerUuids: string[], loadMounts: boolean = true) => async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { let args: any = { - filters: new FilterBuilder().addIn('uuid', containerUuids).getFilters(), + filters: new FilterBuilder().addIn("uuid", containerUuids).getFilters(), limit: containerUuids.length, }; if (!loadMounts) { @@ -114,61 +119,60 @@ const containerFieldsNoMounts = [ "state", "subrequests_cost", "uuid", -] +]; -export const cancelRunningWorkflow = (uuid: string) => - async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { - try { - const process = await services.containerRequestService.update(uuid, { priority: 0 }); - dispatch(updateResources([process])); - if (process.containerUuid) { - const container = await services.containerService.get(process.containerUuid, false); - dispatch(updateResources([container])); - } - return process; - } catch (e) { - throw new Error('Could not cancel the process.'); +export const cancelRunningWorkflow = (uuid: string) => async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { + try { + const process = await services.containerRequestService.update(uuid, { priority: 0 }); + dispatch(updateResources([process])); + if (process.containerUuid) { + const container = await services.containerService.get(process.containerUuid, false); + dispatch(updateResources([container])); } - }; + return process; + } catch (e) { + throw new Error("Could not cancel the process."); + } +}; -export const resumeOnHoldWorkflow = (uuid: string) => - async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { - try { - const process = await services.containerRequestService.update(uuid, { priority: 500 }); - dispatch(updateResources([process])); - if (process.containerUuid) { - const container = await services.containerService.get(process.containerUuid, false); - dispatch(updateResources([container])); - } - return process; - } catch (e) { - throw new Error('Could not resume the process.'); +export const resumeOnHoldWorkflow = (uuid: string) => async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { + try { + const process = await services.containerRequestService.update(uuid, { priority: 500 }); + dispatch(updateResources([process])); + if (process.containerUuid) { + const container = await services.containerService.get(process.containerUuid, false); + dispatch(updateResources([container])); } - }; + return process; + } catch (e) { + throw new Error("Could not resume the process."); + } +}; -export const startWorkflow = (uuid: string) => - async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { - try { - const process = await services.containerRequestService.update(uuid, { state: ContainerRequestState.COMMITTED }); - if (process) { - dispatch(updateResources([process])); - dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Process started', hideDuration: 2000, kind: SnackbarKind.SUCCESS })); - } else { - dispatch(snackbarActions.OPEN_SNACKBAR({ message: `Failed to start process`, kind: SnackbarKind.ERROR })); - } - } catch (e) { +export const startWorkflow = (uuid: string) => async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { + try { + const process = await services.containerRequestService.update(uuid, { state: ContainerRequestState.COMMITTED }); + if (process) { + dispatch(updateResources([process])); + dispatch(snackbarActions.OPEN_SNACKBAR({ message: "Process started", hideDuration: 2000, kind: SnackbarKind.SUCCESS })); + } else { dispatch(snackbarActions.OPEN_SNACKBAR({ message: `Failed to start process`, kind: SnackbarKind.ERROR })); } - }; + } catch (e) { + dispatch(snackbarActions.OPEN_SNACKBAR({ message: `Failed to start process`, kind: SnackbarKind.ERROR })); + } +}; -export const reRunProcess = (processUuid: string, workflowUuid: string) => - (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { +export const reRunProcess = + (processUuid: string, workflowUuid: string) => (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { const process = getResource(processUuid)(getState().resources); const workflows = getState().runProcessPanel.searchWorkflows; const workflow = workflows.find(workflow => workflow.uuid === workflowUuid); if (workflow && process) { const mainWf = getWorkflow(process.mounts[MOUNT_PATH_CWL_WORKFLOW]); - if (mainWf) { mainWf.inputs = getInputs(process); } + if (mainWf) { + mainWf.inputs = getInputs(process); + } const stringifiedDefinition = JSON.stringify(process.mounts[MOUNT_PATH_CWL_WORKFLOW].content); const newWorkflow = { ...workflow, definition: stringifiedDefinition }; @@ -182,7 +186,7 @@ export const reRunProcess = (processUuid: string, workflowUuid: string) => ram: process.runtimeConstraints.ram, vcpus: process.runtimeConstraints.vcpus, keep_cache_ram: process.runtimeConstraints.keep_cache_ram, - acr_container_image: process.containerImage + acr_container_image: process.containerImage, }; dispatch(initialize(RUN_PROCESS_ADVANCED_FORM, advancedInitialData)); @@ -201,34 +205,40 @@ export const reRunProcess = (processUuid: string, workflowUuid: string) => * Returns {} if inputs not found in mounts or props */ export const getRawInputs = (data: any): WorkflowInputsData | undefined => { - if (!data) { return 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); -} + 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 []; } + if (!data || !data.mounts || !data.mounts[MOUNT_PATH_CWL_WORKFLOW]) { + return []; + } const content = getRawInputs(data) as any; // Only escape if content is falsy to allow displaying definitions if no inputs are present // (Don't check raw content length) - if (!content) { return []; } + if (!content) { + return []; + } const inputs = getWorkflowInputs(data.mounts[MOUNT_PATH_CWL_WORKFLOW].content); - return inputs ? inputs.map( - (it: any) => ( - { - type: it.type, - id: it.id, - label: it.label, - default: content[it.id], - value: content[it.id.split('/').pop()] || [], - doc: it.doc - } - ) - ) : []; + return inputs + ? inputs.map((it: any) => ({ + type: it.type, + id: it.id, + label: it.label, + default: content[it.id], + value: content[it.id.split("/").pop()] || [], + doc: it.doc, + })) + : []; }; /* @@ -236,25 +246,27 @@ export const getInputs = (data: any): CommandInputParameter[] => { * 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); -} + 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 []; } + 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) + .filter(mount => mount.kind === "collection" && mount.portable_data_hash && mount.path) .map(mount => ({ path: mount.path, pdh: mount.portable_data_hash, @@ -262,42 +274,63 @@ export const getInputCollectionMounts = (data: any): InputCollectionMount[] => { }; export const getOutputParameters = (data: any): CommandOutputParameter[] => { - if (!data || !data.mounts || !data.mounts[MOUNT_PATH_CWL_WORKFLOW]) { return []; } + 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 - } - ) - ) : []; + return outputs + ? outputs.map((it: any) => ({ + type: it.type, + id: it.id, + label: it.label, + doc: it.doc, + })) + : []; }; -export const openRemoveProcessDialog = (uuid: string) => - (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { - dispatch(dialogActions.OPEN_DIALOG({ - id: REMOVE_PROCESS_DIALOG, - data: { - title: 'Remove process permanently', - text: 'Are you sure you want to remove this process?', - confirmButtonLabel: 'Remove', - uuid - } - })); +export const openRemoveProcessDialog = + (resource: ContextMenuResource, numOfProcesses: Number) => (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { + const confirmationText = + numOfProcesses === 1 + ? "Are you sure you want to remove this process?" + : `Are you sure you want to remove these ${numOfProcesses} processes?`; + const titleText = numOfProcesses === 1 ? "Remove process permanently" : "Remove processes permanently"; + + dispatch( + dialogActions.OPEN_DIALOG({ + id: REMOVE_PROCESS_DIALOG, + data: { + title: titleText, + text: confirmationText, + confirmButtonLabel: "Remove", + uuid: resource.uuid, + resource, + }, + }) + ); }; -export const REMOVE_PROCESS_DIALOG = 'removeProcessDialog'; +export const REMOVE_PROCESS_DIALOG = "removeProcessDialog"; -export const removeProcessPermanently = (uuid: string) => - async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { +export const removeProcessPermanently = (uuid: string) => async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { + const resource = getState().dialog.removeProcessDialog.data.resource; + const checkedList = getState().multiselect.checkedList; + + const uuidsToRemove: string[] = resource.fromContextMenu ? [resource.uuid] : selectedToArray(checkedList); + + //if no items in checkedlist, default to normal context menu behavior + if (!uuidsToRemove.length) uuidsToRemove.push(uuid); + + const processesToRemove = uuidsToRemove + .map(uuid => getResource(uuid)(getState().resources) as Resource) + .filter(resource => resource.kind === ResourceKind.PROCESS); + + for (const process of processesToRemove) { try { - dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Removing ...', kind: SnackbarKind.INFO })); - await services.containerRequestService.delete(uuid, false); - dispatch(projectPanelActions.REQUEST_ITEMS()); - dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Removed.', hideDuration: 2000, kind: SnackbarKind.SUCCESS })); + dispatch(snackbarActions.OPEN_SNACKBAR({ message: "Removing ...", kind: SnackbarKind.INFO })); + await services.containerRequestService.delete(process.uuid, false); + dispatch(projectPanelDataActions.REQUEST_ITEMS()); + dispatch(snackbarActions.OPEN_SNACKBAR({ message: "Removed.", hideDuration: 2000, kind: SnackbarKind.SUCCESS })); } catch (e) { const error = getCommonResourceServiceError(e); if (error === CommonResourceServiceError.PERMISSION_ERROR_FORBIDDEN) { @@ -306,4 +339,5 @@ export const removeProcessPermanently = (uuid: string) => dispatch(snackbarActions.OPEN_SNACKBAR({ message: `Deletion failed`, hideDuration: 2000, kind: SnackbarKind.ERROR })); } } - }; + } +};