14096-move-to-process
authorPawel Kowalczyk <pawel.kowalczyk@contractors.roche.com>
Fri, 7 Sep 2018 13:50:24 +0000 (15:50 +0200)
committerPawel Kowalczyk <pawel.kowalczyk@contractors.roche.com>
Fri, 7 Sep 2018 13:50:24 +0000 (15:50 +0200)
Feature #14096

Arvados-DCO-1.1-Signed-off-by: Pawel Kowalczyk <pawel.kowalczyk@contractors.roche.com>

src/index.tsx
src/routes/routes.ts
src/services/common-service/common-resource-service.ts
src/store/context-menu/context-menu-actions.ts
src/store/processes/process-move-actions.ts [new file with mode: 0644]
src/store/workbench/workbench-actions.ts
src/views-components/context-menu/action-sets/process-action-set.ts
src/views-components/context-menu/action-sets/process-resource-action-set.ts [new file with mode: 0644]
src/views-components/context-menu/context-menu.tsx
src/views-components/dialog-forms/move-process-dialog.ts [new file with mode: 0644]
src/views/workbench/workbench.tsx

index a921b471383dbc399521b9298ee7f80bd3cb8a00..8ab089a89fcb3ff9ede3026c509c8de09d611dca 100644 (file)
@@ -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 { 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);
 
 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.COLLECTION, collectionActionSet);
 addMenuActionSet(ContextMenuKind.COLLECTION_RESOURCE, collectionResourceActionSet);
 addMenuActionSet(ContextMenuKind.PROCESS, processActionSet);
+addMenuActionSet(ContextMenuKind.PROCESS_RESOURCE, processResourceActionSet);
 addMenuActionSet(ContextMenuKind.TRASH, trashActionSet);
 
 fetchConfig()
 addMenuActionSet(ContextMenuKind.TRASH, trashActionSet);
 
 fetchConfig()
index f108e0b8be423592cb50578a8667e468c3251e55..29941b47e89989cec2417e653c8364fa5b3a21f7 100644 (file)
@@ -25,6 +25,8 @@ export const getResourceUrl = (uuid: string) => {
             return getProjectUrl(uuid);
         case ResourceKind.COLLECTION:
             return getCollectionUrl(uuid);
             return getProjectUrl(uuid);
         case ResourceKind.COLLECTION:
             return getCollectionUrl(uuid);
+        case ResourceKind.PROCESS:
+            return getProcessUrl(uuid);
         default:
             return undefined;
     }
         default:
             return undefined;
     }
index 7b36b71cf42d7ce6ba289712eac1f2955b047ec4..abc856ffe39b6d45291ec65e9f59aa607d339d94 100644 (file)
@@ -32,6 +32,7 @@ export interface Errors {
 export enum CommonResourceServiceError {
     UNIQUE_VIOLATION = 'UniqueViolation',
     OWNERSHIP_CYCLE = 'OwnershipCycle',
 export enum CommonResourceServiceError {
     UNIQUE_VIOLATION = 'UniqueViolation',
     OWNERSHIP_CYCLE = 'OwnershipCycle',
+    MODIFYING_CONTAINER_REQUEST_FINAL_STATE = 'ModifyingFinalState',
     UNKNOWN = 'Unknown',
     NONE = 'None'
 }
     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;
                 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;
         }
             default:
                 return CommonResourceServiceError.UNKNOWN;
         }
index bb404b88963b9fbd358db6cdba1aa82a6e7bbf20..eabf41ae715ad715e6072ca46383c0499461d170 100644 (file)
@@ -88,7 +88,7 @@ export const openProcessContextMenu = (event: React.MouseEvent<HTMLElement>) =>
     (dispatch: Dispatch, getState: () => RootState) => {
         const { location } = getState().router;
         const pathname = location ? location.pathname : '';
     (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,
         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;
             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:
         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 (file)
index 0000000..d59cdca
--- /dev/null
@@ -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<any>(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
index 036f4a3469448fd64f2b04151907bc6309ebe901..cb65c24ec7b8940a2c000630f04d23cc7c80db5b 100644 (file)
@@ -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 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';
 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<any>(activateSidePanelTreeItem(process.containerRequest.ownerUuid));
         dispatch<any>(setProcessBreadcrumbs(uuid));
         dispatch(loadDetailsPanel(uuid));
         await dispatch<any>(activateSidePanelTreeItem(process.containerRequest.ownerUuid));
         dispatch<any>(setProcessBreadcrumbs(uuid));
         dispatch(loadDetailsPanel(uuid));
-        
+
+    };
+
+export const moveProcess = (data: MoveToFormDialogData) =>
+    async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+        try {
+            const process = await dispatch<any>(processMoveActions.moveProcess(data));
+            dispatch<any>(updateResources([process]));
+            dispatch<any>(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) =>
     };
 
 export const loadProcessLog = (uuid: string) =>
index 2897455bc4f2b2479e55f52c91729ab13cd5b995..3a824ecc6772b19cbff0ac2612135cb51cb4c4d0 100644 (file)
@@ -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';
 } 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 = [[
     {
 
 export const processActionSet: ContextMenuActionSet = [[
     {
@@ -30,9 +31,7 @@ export const processActionSet: ContextMenuActionSet = [[
     {
         icon: MoveToIcon,
         name: "Move to",
     {
         icon: MoveToIcon,
         name: "Move to",
-        execute: (dispatch, resource) => {
-            // add code
-        }
+        execute: (dispatch, resource) => dispatch<any>(openMoveProcessDialog(resource))
     },
     {
         component: ToggleFavoriteAction,
     },
     {
         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 (file)
index 0000000..64e30f7
--- /dev/null
@@ -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<any>(openMoveProcessDialog(resource))
+    },
+    {
+        component: ToggleFavoriteAction,
+        execute: (dispatch, resource) => {
+            dispatch<any>(toggleFavorite(resource)).then(() => {
+                dispatch<any>(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
+        }
+    }
+]];
index a545b2bdddfb447ebe5bf43ee768a841d52d36e8..d5c82be2d7f0736243f5e97661e4f7c7ec51d7a4 100644 (file)
@@ -65,5 +65,6 @@ export enum ContextMenuKind {
     COLLECTION = 'Collection',
     COLLECTION_RESOURCE = 'CollectionResource',
     PROCESS = "Process",
     COLLECTION = 'Collection',
     COLLECTION_RESOURCE = 'CollectionResource',
     PROCESS = "Process",
+    PROCESS_RESOURCE = 'ProcessResource',
     PROCESS_LOGS = "ProcessLogs"
 }
     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 (file)
index 0000000..baea34b
--- /dev/null
@@ -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<MoveToFormDialogData>({
+        form: PROCESS_MOVE_FORM_NAME,
+        onSubmit: (data, dispatch) => {
+            dispatch(moveProcess(data));
+        }
+    })
+)(DialogMoveTo);
\ No newline at end of file
index 3c281087c2addf20ad8f3f6e7e31500673a28782..f202b6983359642fd7eadf1c667d880a1855fac6 100644 (file)
@@ -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 { 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 { 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;
 import { TrashPanel } from "~/views/trash-panel/trash-panel";
 
 const APP_BAR_HEIGHT = 100;
@@ -189,6 +189,7 @@ export const Workbench = withStyles(styles)(
                         <FilesUploadCollectionDialog />
                         <UpdateProjectDialog />
                         <MoveCollectionDialog />
                         <FilesUploadCollectionDialog />
                         <UpdateProjectDialog />
                         <MoveCollectionDialog />
+                        <MoveProcessDialog />
                         <MoveProjectDialog />
                         <CurrentTokenDialog
                             currentToken={this.props.currentToken}
                         <MoveProjectDialog />
                         <CurrentTokenDialog
                             currentToken={this.props.currentToken}