From 8805314e7327cda30d455d0c05075ee37f3a490e Mon Sep 17 00:00:00 2001 From: Pawel Kowalczyk Date: Fri, 7 Sep 2018 15:50:24 +0200 Subject: [PATCH] 14096-move-to-process Feature #14096 Arvados-DCO-1.1-Signed-off-by: Pawel Kowalczyk --- src/index.tsx | 2 + src/routes/routes.ts | 2 + .../common-service/common-resource-service.ts | 3 + .../context-menu/context-menu-actions.ts | 4 +- src/store/processes/process-move-actions.ts | 57 +++++++++++++++++ src/store/workbench/workbench-actions.ts | 15 ++++- .../action-sets/process-action-set.ts | 5 +- .../process-resource-action-set.ts | 61 +++++++++++++++++++ .../context-menu/context-menu.tsx | 1 + .../dialog-forms/move-process-dialog.ts | 21 +++++++ src/views/workbench/workbench.tsx | 3 +- 11 files changed, 168 insertions(+), 6 deletions(-) create mode 100644 src/store/processes/process-move-actions.ts create mode 100644 src/views-components/context-menu/action-sets/process-resource-action-set.ts create mode 100644 src/views-components/dialog-forms/move-process-dialog.ts diff --git a/src/index.tsx b/src/index.tsx index a921b471..8ab089a8 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -35,6 +35,7 @@ import { ServiceRepository } from '~/services/services'; import { initWebSocket } from '~/websocket/websocket'; import { Config } from '~/common/config'; import { addRouteChangeHandlers } from './routes/route-change-handlers'; +import { processResourceActionSet } from './views-components/context-menu/action-sets/process-resource-action-set'; const getBuildNumber = () => "BN-" + (process.env.REACT_APP_BUILD_NUMBER || "dev"); const getGitCommit = () => "GIT-" + (process.env.REACT_APP_GIT_COMMIT || "latest").substr(0, 7); @@ -53,6 +54,7 @@ addMenuActionSet(ContextMenuKind.COLLECTION_FILES_ITEM, collectionFilesItemActio addMenuActionSet(ContextMenuKind.COLLECTION, collectionActionSet); addMenuActionSet(ContextMenuKind.COLLECTION_RESOURCE, collectionResourceActionSet); addMenuActionSet(ContextMenuKind.PROCESS, processActionSet); +addMenuActionSet(ContextMenuKind.PROCESS_RESOURCE, processResourceActionSet); addMenuActionSet(ContextMenuKind.TRASH, trashActionSet); fetchConfig() diff --git a/src/routes/routes.ts b/src/routes/routes.ts index f108e0b8..29941b47 100644 --- a/src/routes/routes.ts +++ b/src/routes/routes.ts @@ -25,6 +25,8 @@ export const getResourceUrl = (uuid: string) => { return getProjectUrl(uuid); case ResourceKind.COLLECTION: return getCollectionUrl(uuid); + case ResourceKind.PROCESS: + return getProcessUrl(uuid); default: return undefined; } diff --git a/src/services/common-service/common-resource-service.ts b/src/services/common-service/common-resource-service.ts index 7b36b71c..abc856ff 100644 --- a/src/services/common-service/common-resource-service.ts +++ b/src/services/common-service/common-resource-service.ts @@ -32,6 +32,7 @@ export interface Errors { export enum CommonResourceServiceError { UNIQUE_VIOLATION = 'UniqueViolation', OWNERSHIP_CYCLE = 'OwnershipCycle', + MODIFYING_CONTAINER_REQUEST_FINAL_STATE = 'ModifyingFinalState', UNKNOWN = 'Unknown', NONE = 'None' } @@ -121,6 +122,8 @@ export const getCommonResourceServiceError = (errorResponse: any) => { return CommonResourceServiceError.UNIQUE_VIOLATION; case /ownership cycle/.test(error): return CommonResourceServiceError.OWNERSHIP_CYCLE; + case /Mounts cannot be modified in state 'Final'/.test(error): + return CommonResourceServiceError.MODIFYING_CONTAINER_REQUEST_FINAL_STATE; default: return CommonResourceServiceError.UNKNOWN; } diff --git a/src/store/context-menu/context-menu-actions.ts b/src/store/context-menu/context-menu-actions.ts index bb404b88..eabf41ae 100644 --- a/src/store/context-menu/context-menu-actions.ts +++ b/src/store/context-menu/context-menu-actions.ts @@ -88,7 +88,7 @@ export const openProcessContextMenu = (event: React.MouseEvent) => (dispatch: Dispatch, getState: () => RootState) => { const { location } = getState().router; const pathname = location ? location.pathname : ''; - const match = matchProcessRoute(pathname); + const match = matchProcessRoute(pathname); const uuid = match ? match.params.id : ''; const resource = { uuid, @@ -108,6 +108,8 @@ export const resourceKindToContextMenuKind = (uuid: string) => { return ContextMenuKind.PROJECT; case ResourceKind.COLLECTION: return ContextMenuKind.COLLECTION_RESOURCE; + case ResourceKind.PROCESS: + return ContextMenuKind.PROCESS_RESOURCE; case ResourceKind.USER: return ContextMenuKind.ROOT_PROJECT; default: diff --git a/src/store/processes/process-move-actions.ts b/src/store/processes/process-move-actions.ts new file mode 100644 index 00000000..d59cdca5 --- /dev/null +++ b/src/store/processes/process-move-actions.ts @@ -0,0 +1,57 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +import { Dispatch } from "redux"; +import { dialogActions } from "~/store/dialog/dialog-actions"; +import { startSubmit, stopSubmit, initialize } from 'redux-form'; +import { ServiceRepository } from '~/services/services'; +import { RootState } from '~/store/store'; +import { getCommonResourceServiceError, CommonResourceServiceError } from "~/services/common-service/common-resource-service"; +import { snackbarActions } from '~/store/snackbar/snackbar-actions'; +import { MoveToFormDialogData } from '~/store/move-to-dialog/move-to-dialog'; +import { resetPickerProjectTree } from '~/store/project-tree-picker/project-tree-picker-actions'; +import { projectPanelActions } from '~/store/project-panel/project-panel-action'; +import { getProcess, getProcessStatus, ProcessStatus } from '~/store/processes/process'; + +export const PROCESS_MOVE_FORM_NAME = 'processMoveFormName'; + +export const openMoveProcessDialog = (resource: { name: string, uuid: string }) => + (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { + const process = getProcess(resource.uuid)(getState().resources); + if (process) { + const processStatus = getProcessStatus(process); + if (processStatus === ProcessStatus.DRAFT) { + dispatch(resetPickerProjectTree()); + dispatch(initialize(PROCESS_MOVE_FORM_NAME, resource)); + dispatch(dialogActions.OPEN_DIALOG({ id: PROCESS_MOVE_FORM_NAME, data: {} })); + } else { + dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'You can move only draft processes.', hideDuration: 2000 })); + } + } + }; + +export const moveProcess = (resource: MoveToFormDialogData) => + async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { + dispatch(startSubmit(PROCESS_MOVE_FORM_NAME)); + try { + const process = await services.containerRequestService.get(resource.uuid); + await services.containerRequestService.update(resource.uuid, { ...process, ownerUuid: resource.ownerUuid }); + dispatch(projectPanelActions.REQUEST_ITEMS()); + dispatch(dialogActions.CLOSE_DIALOG({ id: PROCESS_MOVE_FORM_NAME })); + dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Process has been moved', hideDuration: 2000 })); + return process; + } catch (e) { + const error = getCommonResourceServiceError(e); + if (error === CommonResourceServiceError.UNIQUE_VIOLATION) { + dispatch(stopSubmit(PROCESS_MOVE_FORM_NAME, { ownerUuid: 'A process with the same name already exists in the target project.' })); + } else if (error === CommonResourceServiceError.MODIFYING_CONTAINER_REQUEST_FINAL_STATE) { + dispatch(stopSubmit(PROCESS_MOVE_FORM_NAME, { ownerUuid: 'You can move only uncommitted process.' })); + } + else { + dispatch(dialogActions.CLOSE_DIALOG({ id: PROCESS_MOVE_FORM_NAME })); + dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Could not move the process.', hideDuration: 2000 })); + } + return; + } + }; \ No newline at end of file diff --git a/src/store/workbench/workbench-actions.ts b/src/store/workbench/workbench-actions.ts index 036f4a34..cb65c24e 100644 --- a/src/store/workbench/workbench-actions.ts +++ b/src/store/workbench/workbench-actions.ts @@ -29,6 +29,7 @@ import * as collectionCopyActions from '~/store/collections/collection-copy-acti import * as collectionUpdateActions from '~/store/collections/collection-update-actions'; import * as collectionMoveActions from '~/store/collections/collection-move-actions'; import * as processesActions from '../processes/processes-actions'; +import * as processMoveActions from '~/store/processes/process-move-actions'; import { trashPanelColumns } from "~/views/trash-panel/trash-panel"; import { loadTrashPanel, trashPanelActions } from "~/store/trash-panel/trash-panel-action"; import { initProcessLogsPanel } from '../process-logs-panel/process-logs-panel-actions'; @@ -191,7 +192,19 @@ export const loadProcess = (uuid: string) => await dispatch(activateSidePanelTreeItem(process.containerRequest.ownerUuid)); dispatch(setProcessBreadcrumbs(uuid)); dispatch(loadDetailsPanel(uuid)); - + + }; + +export const moveProcess = (data: MoveToFormDialogData) => + async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => { + try { + const process = await dispatch(processMoveActions.moveProcess(data)); + dispatch(updateResources([process])); + dispatch(reloadProjectMatchingUuid([process.ownerUuid])); + dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Process has been moved.', hideDuration: 2000 })); + } catch (e) { + dispatch(snackbarActions.OPEN_SNACKBAR({ message: e.message, hideDuration: 2000 })); + } }; export const loadProcessLog = (uuid: string) => diff --git a/src/views-components/context-menu/action-sets/process-action-set.ts b/src/views-components/context-menu/action-sets/process-action-set.ts index 2897455b..3a824ecc 100644 --- a/src/views-components/context-menu/action-sets/process-action-set.ts +++ b/src/views-components/context-menu/action-sets/process-action-set.ts @@ -11,6 +11,7 @@ import { } from "~/components/icon/icon"; import { favoritePanelActions } from "~/store/favorite-panel/favorite-panel-action"; import { navigateToProcessLogs } from '~/store/navigation/navigation-action'; +import { openMoveProcessDialog } from '~/store/processes/process-move-actions'; export const processActionSet: ContextMenuActionSet = [[ { @@ -30,9 +31,7 @@ export const processActionSet: ContextMenuActionSet = [[ { icon: MoveToIcon, name: "Move to", - execute: (dispatch, resource) => { - // add code - } + execute: (dispatch, resource) => dispatch(openMoveProcessDialog(resource)) }, { component: ToggleFavoriteAction, diff --git a/src/views-components/context-menu/action-sets/process-resource-action-set.ts b/src/views-components/context-menu/action-sets/process-resource-action-set.ts new file mode 100644 index 00000000..64e30f7e --- /dev/null +++ b/src/views-components/context-menu/action-sets/process-resource-action-set.ts @@ -0,0 +1,61 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +import { ContextMenuActionSet } from "../context-menu-action-set"; +import { ToggleFavoriteAction } from "../actions/favorite-action"; +import { toggleFavorite } from "~/store/favorites/favorites-actions"; +import { RenameIcon, ShareIcon, MoveToIcon, CopyIcon, DetailsIcon, RemoveIcon } from "~/components/icon/icon"; +import { favoritePanelActions } from "~/store/favorite-panel/favorite-panel-action"; +import { openMoveProcessDialog } from '~/store/processes/process-move-actions'; + +export const processResourceActionSet: ContextMenuActionSet = [[ + { + icon: RenameIcon, + name: "Edit process", + execute: (dispatch, resource) => { + // add code + } + }, + { + icon: ShareIcon, + name: "Share", + execute: (dispatch, resource) => { + // add code + } + }, + { + icon: MoveToIcon, + name: "Move to", + execute: (dispatch, resource) => dispatch(openMoveProcessDialog(resource)) + }, + { + component: ToggleFavoriteAction, + execute: (dispatch, resource) => { + dispatch(toggleFavorite(resource)).then(() => { + dispatch(favoritePanelActions.REQUEST_ITEMS()); + }); + } + }, + { + icon: CopyIcon, + name: "Copy to project", + execute: (dispatch, resource) => { + // add code + } + }, + { + icon: DetailsIcon, + name: "View details", + execute: (dispatch, resource) => { + // add code + } + }, + { + icon: RemoveIcon, + name: "Remove", + execute: (dispatch, resource) => { + // add code + } + } +]]; diff --git a/src/views-components/context-menu/context-menu.tsx b/src/views-components/context-menu/context-menu.tsx index a545b2bd..d5c82be2 100644 --- a/src/views-components/context-menu/context-menu.tsx +++ b/src/views-components/context-menu/context-menu.tsx @@ -65,5 +65,6 @@ export enum ContextMenuKind { COLLECTION = 'Collection', COLLECTION_RESOURCE = 'CollectionResource', PROCESS = "Process", + PROCESS_RESOURCE = 'ProcessResource', PROCESS_LOGS = "ProcessLogs" } diff --git a/src/views-components/dialog-forms/move-process-dialog.ts b/src/views-components/dialog-forms/move-process-dialog.ts new file mode 100644 index 00000000..baea34bc --- /dev/null +++ b/src/views-components/dialog-forms/move-process-dialog.ts @@ -0,0 +1,21 @@ +// Copyright (C) The Arvados Authors. All rights reserved. +// +// SPDX-License-Identifier: AGPL-3.0 + +import { compose } from 'redux'; +import { withDialog } from "~/store/dialog/with-dialog"; +import { reduxForm } from 'redux-form'; +import { PROCESS_MOVE_FORM_NAME } from '~/store/processes/process-move-actions'; +import { MoveToFormDialogData } from '~/store/move-to-dialog/move-to-dialog'; +import { DialogMoveTo } from '~/views-components/dialog-move/dialog-move-to'; +import { moveProcess } from '~/store/workbench/workbench-actions'; + +export const MoveProcessDialog = compose( + withDialog(PROCESS_MOVE_FORM_NAME), + reduxForm({ + form: PROCESS_MOVE_FORM_NAME, + onSubmit: (data, dispatch) => { + dispatch(moveProcess(data)); + } + }) +)(DialogMoveTo); \ No newline at end of file diff --git a/src/views/workbench/workbench.tsx b/src/views/workbench/workbench.tsx index 3c281087..f202b698 100644 --- a/src/views/workbench/workbench.tsx +++ b/src/views/workbench/workbench.tsx @@ -34,11 +34,11 @@ import { CreateCollectionDialog } from '~/views-components/dialog-forms/create-c import { CopyCollectionDialog } from '~/views-components/dialog-forms/copy-collection-dialog'; import { UpdateCollectionDialog } from '~/views-components/dialog-forms/update-collection-dialog'; import { UpdateProjectDialog } from '~/views-components/dialog-forms/update-project-dialog'; +import { MoveProcessDialog } from '~/views-components/dialog-forms/move-process-dialog'; import { MoveProjectDialog } from '~/views-components/dialog-forms/move-project-dialog'; import { MoveCollectionDialog } from '~/views-components/dialog-forms/move-collection-dialog'; import { FilesUploadCollectionDialog } from '~/views-components/dialog-forms/files-upload-collection-dialog'; import { PartialCopyCollectionDialog } from '~/views-components/dialog-forms/partial-copy-collection-dialog'; - import { TrashPanel } from "~/views/trash-panel/trash-panel"; const APP_BAR_HEIGHT = 100; @@ -189,6 +189,7 @@ export const Workbench = withStyles(styles)( +