20899: Add menu item to delete workflow record 20899-wf-delete
authorPeter Amstutz <peter.amstutz@curii.com>
Mon, 28 Aug 2023 19:19:39 +0000 (15:19 -0400)
committerPeter Amstutz <peter.amstutz@curii.com>
Mon, 28 Aug 2023 19:19:39 +0000 (15:19 -0400)
Arvados-DCO-1.1-Signed-off-by: Peter Amstutz <peter.amstutz@curii.com>

cypress/integration/workflow.spec.js
cypress/support/commands.js
src/index.tsx
src/store/context-menu/context-menu-actions.ts
src/store/resources/resources.ts
src/store/workflow-panel/workflow-panel-actions.ts
src/views-components/context-menu/action-sets/workflow-action-set.ts
src/views-components/context-menu/context-menu.tsx

index e1fa20a8f46d3c83ed3546c0329318921dfaca2f..76ad3c631dfe96fb33b836fb83f348b446aa293a 100644 (file)
@@ -234,4 +234,35 @@ describe('Registered workflow panel tests', function() {
                 });
             });
     });
+
+    it('can delete a workflow', function() {
+        cy.createResource(activeUser.token, "workflows", {workflow: {name: "Test wf"}})
+            .then(function(workflowResource) {
+                cy.loginAs(activeUser);
+                cy.goToPath(`/projects/${activeUser.user.uuid}`);
+                cy.get('[data-cy=project-panel] table tbody').contains(workflowResource.name).rightclick();
+                cy.get('[data-cy=context-menu]').contains('Delete Workflow').click();
+                cy.get('[data-cy=project-panel] table tbody').should('not.contain', workflowResource.name);
+            });
+    });
+
+    it('cannot delete readonly workflow', function() {
+        cy.createProject({
+            owningUser: adminUser,
+            targetUser: activeUser,
+            projectName: 'mySharedReadonlyProject',
+            canWrite: false,
+        });
+        cy.getAll('@mySharedReadonlyProject')
+            .then(function ([mySharedReadonlyProject]) {
+                cy.createResource(adminUser.token, "workflows", {workflow: {name: "Test wf", owner_uuid: mySharedReadonlyProject.uuid}})
+                    .then(function(workflowResource) {
+                        cy.loginAs(activeUser);
+                        cy.goToPath(`/shared-with-me`);
+                        cy.contains("mySharedReadonlyProject").click();
+                        cy.get('[data-cy=project-panel] table tbody').contains(workflowResource.name).rightclick();
+                        cy.get('[data-cy=context-menu]').should("not.contain", 'Delete Workflow');
+                    });
+            });
+    });
 });
index fadd73e0655b722202999a763763d662ba3ff5a5..67ddf45d8365bc77b769d4b7a857666d23af00ab 100644 (file)
@@ -36,12 +36,14 @@ let createdResources = [];
 
 const containerLogFolderPrefix = 'log for container ';
 
-// Clean up on a 'before' hook to allow post-mortem analysis on individual tests.
-beforeEach(function () {
+// Clean up anything that was created.  You can temporarily add
+// 'return' to the top if you need the resources to hang around to
+// debug a specific test.
+afterEach(function () {
     if (createdResources.length === 0) {
         return;
     }
-    cy.log(`Cleaning ${createdResources.length} previously created resource(s)`);
+    cy.log(`Cleaning ${createdResources.length} previously created resource(s).`);
     createdResources.forEach(function({suffix, uuid}) {
         // Don't fail when a resource isn't already there, some objects may have
         // been removed, directly or indirectly, from the test that created them.
index 9293dd74f38879e3024f969118b6fa358376b394..7cc18783d13f70d02fcef54cdf745e3d9164d257 100644 (file)
@@ -62,7 +62,7 @@ import { linkActionSet } from 'views-components/context-menu/action-sets/link-ac
 import { loadFileViewersConfig } from 'store/file-viewers/file-viewers-actions';
 import { filterGroupAdminActionSet, frozenAdminActionSet, projectAdminActionSet } from 'views-components/context-menu/action-sets/project-admin-action-set';
 import { permissionEditActionSet } from 'views-components/context-menu/action-sets/permission-edit-action-set';
-import { workflowActionSet } from 'views-components/context-menu/action-sets/workflow-action-set';
+import { workflowActionSet, readOnlyWorkflowActionSet } from 'views-components/context-menu/action-sets/workflow-action-set';
 import { snackbarActions, SnackbarKind } from "store/snackbar/snackbar-actions";
 import { openNotFoundDialog } from './store/not-found-panel/not-found-panel-action';
 import { storeRedirects } from './common/redirect-to';
@@ -108,6 +108,7 @@ addMenuActionSet(ContextMenuKind.FROZEN_PROJECT, frozenActionSet);
 addMenuActionSet(ContextMenuKind.FROZEN_PROJECT_ADMIN, frozenAdminActionSet);
 addMenuActionSet(ContextMenuKind.FILTER_GROUP_ADMIN, filterGroupAdminActionSet);
 addMenuActionSet(ContextMenuKind.PERMISSION_EDIT, permissionEditActionSet);
+addMenuActionSet(ContextMenuKind.READONLY_WORKFLOW, readOnlyWorkflowActionSet);
 addMenuActionSet(ContextMenuKind.WORKFLOW, workflowActionSet);
 addMenuActionSet(ContextMenuKind.SEARCH_RESULTS, searchResultsActionSet);
 
index e659de8a6c39c02e2d7ddbf188df4d1d7892d9bb..4abfb372f915152f62761cd4440df6605df0914a 100644 (file)
@@ -274,7 +274,7 @@ export const resourceUuidToContextMenuKind = (uuid: string, readonly = false) =>
             case ResourceKind.LINK:
                 return ContextMenuKind.LINK;
             case ResourceKind.WORKFLOW:
-                return ContextMenuKind.WORKFLOW;
+                return isEditable ? ContextMenuKind.WORKFLOW : ContextMenuKind.READONLY_WORKFLOW;
             default:
                 return;
         }
index 3f71140497f7c5df566ef096fb75afc23afcbe0f..e3fb2eb3d53b154b9e7cc254b9b7f406df1ff582 100644 (file)
@@ -9,18 +9,17 @@ import { GroupResource } from "models/group";
 
 export type ResourcesState = { [key: string]: Resource };
 
-export const getResourceWithEditableStatus = <T extends EditableResource & GroupResource>(id: string, userUuid?: string) =>
+export const getResourceWithEditableStatus = <T extends GroupResource & EditableResource>(id: string, userUuid?: string) =>
     (state: ResourcesState): T | undefined => {
         if (state[id] === undefined) { return; }
 
-        const resource = JSON.parse(JSON.stringify(state[id] as T));
+        const resource = JSON.parse(JSON.stringify(state[id])) as T;
 
         if (resource) {
-            resource.isEditable = resource.canWrite;
-
-            if (!resource.isEditable && state[resource.ownerUuid]) {
-                const resourceOwner = JSON.parse(JSON.stringify(state[resource.ownerUuid] as T));
-                resource.isEditable = resourceOwner.canWrite;
+            if (resource.canWrite === undefined) {
+                resource.isEditable = (state[resource.ownerUuid] as GroupResource)?.canWrite;
+            } else {
+                resource.isEditable = resource.canWrite;
             }
         }
 
index 2c44fae4e2495fbc9d1abe2e6c26745f4bcbe60c..94b35078b43c9649d2e97001decf3ac9eea0b9a3 100644 (file)
@@ -8,14 +8,14 @@ import { ServiceRepository } from 'services/services';
 import { bindDataExplorerActions } from 'store/data-explorer/data-explorer-action';
 import { propertiesActions } from 'store/properties/properties-actions';
 import { getProperty } from 'store/properties/properties';
-import { navigateToRunProcess } from 'store/navigation/navigation-action';
+import { navigateToRunProcess, navigateTo } from 'store/navigation/navigation-action';
 import {
     goToStep,
     runProcessPanelActions,
     loadPresets,
     getWorkflowRunnerSettings
 } from 'store/run-process-panel/run-process-panel-actions';
-import { snackbarActions } from 'store/snackbar/snackbar-actions';
+import { snackbarActions, SnackbarKind } from 'store/snackbar/snackbar-actions';
 import { initialize } from 'redux-form';
 import { RUN_PROCESS_BASIC_FORM } from 'views/run-process-panel/run-process-basic-form';
 import { RUN_PROCESS_INPUTS_FORM } from 'views/run-process-panel/run-process-inputs-form';
@@ -117,3 +117,11 @@ export const getWorkflowDetails = (state: RootState) => {
     const workflow = workflows.find(workflow => workflow.uuid === uuid);
     return workflow || undefined;
 };
+
+export const deleteWorkflow = (workflowUuid: string, ownerUuid: string) =>
+    async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+        dispatch<any>(navigateTo(ownerUuid));
+        dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Removing ...', kind: SnackbarKind.INFO }));
+        await services.workflowService.delete(workflowUuid);
+        dispatch(snackbarActions.OPEN_SNACKBAR({ message: 'Removed.', hideDuration: 2000, kind: SnackbarKind.SUCCESS }));
+    };
index a5223d1d06f199d905136acdbf9402bed806827f..1baf04228ce225fcc3b3c85d45d5259dd72ddab7 100644 (file)
@@ -3,19 +3,20 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import { ContextMenuActionSet } from "views-components/context-menu/context-menu-action-set";
-import { openRunProcess } from "store/workflow-panel/workflow-panel-actions";
+import { openRunProcess, deleteWorkflow } from "store/workflow-panel/workflow-panel-actions";
 import {
     DetailsIcon,
     AdvancedIcon,
     OpenIcon,
     Link,
-    StartIcon
+    StartIcon,
+    TrashIcon
 } from "components/icon/icon";
 import { copyToClipboardAction, openInNewTabAction } from "store/open-in-new-tab/open-in-new-tab.actions";
 import { toggleDetailsPanel } from 'store/details-panel/details-panel-action';
 import { openAdvancedTabDialog } from "store/advanced-tab/advanced-tab";
 
-export const workflowActionSet: ContextMenuActionSet = [[
+export const readOnlyWorkflowActionSet: ContextMenuActionSet = [[
     {
         icon: OpenIcon,
         name: "Open in new tab",
@@ -50,5 +51,16 @@ export const workflowActionSet: ContextMenuActionSet = [[
         execute: (dispatch, resource) => {
             dispatch<any>(openRunProcess(resource.uuid, resource.ownerUuid, resource.name));
         }
+    }
+]];
+
+export const workflowActionSet: ContextMenuActionSet = [[
+    ...readOnlyWorkflowActionSet[0],
+    {
+        icon: TrashIcon,
+        name: "Delete Workflow",
+        execute: (dispatch, resource) => {
+            dispatch<any>(deleteWorkflow(resource.uuid, resource.ownerUuid));
+        }
     },
 ]];
index 81c1a51e261bae4cf6b09c170da2f934de227ec7..1b4610eff6e5fdfe1359569edf0dcd210234335a 100644 (file)
@@ -115,5 +115,6 @@ export enum ContextMenuKind {
     PERMISSION_EDIT = "PermissionEdit",
     LINK = "Link",
     WORKFLOW = "Workflow",
+    READONLY_WORKFLOW = "ReadOnlyWorkflow",
     SEARCH_RESULTS = "SearchResults"
 }