21067: Fix bug deleting projects from shared-with-me 21067-process-panel-error
authorPeter Amstutz <peter.amstutz@curii.com>
Mon, 30 Oct 2023 19:24:39 +0000 (15:24 -0400)
committerPeter Amstutz <peter.amstutz@curii.com>
Mon, 30 Oct 2023 19:24:39 +0000 (15:24 -0400)
Arvados-DCO-1.1-Signed-off-by: Peter Amstutz <peter.amstutz@curii.com>

cypress/integration/page-not-found.spec.js
src/store/trash/trash-actions.ts
src/views/collection-panel/collection-panel.tsx
src/views/not-found-panel/not-found-panel.tsx
src/views/process-panel/process-panel-root.tsx
src/views/project-panel/project-panel.tsx
src/views/workflow-panel/registered-workflow-panel.tsx

index 4df4135c878ed63e4ef667eda50697985a31201d..6eab27c827dc3b1d0ffbc6b4cd22ce0f79e9b6fb 100644 (file)
@@ -45,8 +45,7 @@ describe('Page not found tests', function() {
             cy.goToPath(path);
 
             // then
-            cy.get('[data-cy=not-found-page]').should('not.exist');
-            cy.get('[data-cy=not-found-content]').should('exist');
+            cy.get('[data-cy=not-found-view]').should('exist');
         });
     });
-})
\ No newline at end of file
+})
index 884293a90e57e4e914486fe864b9bb4ea9240473..62b669220e68e2e6d3089f878f7fe4e73f29b962 100644 (file)
@@ -9,104 +9,112 @@ import { snackbarActions, SnackbarKind } from "store/snackbar/snackbar-actions";
 import { trashPanelActions } from "store/trash-panel/trash-panel-action";
 import { activateSidePanelTreeItem, loadSidePanelTreeProjects } from "store/side-panel-tree/side-panel-tree-actions";
 import { projectPanelActions } from "store/project-panel/project-panel-action-bind";
+import { sharedWithMePanelActions } from "store/shared-with-me-panel/shared-with-me-panel-actions";
 import { ResourceKind } from "models/resource";
 import { navigateTo, navigateToTrash } from "store/navigation/navigation-action";
-import { matchCollectionRoute } from "routes/routes";
+import { matchCollectionRoute, matchSharedWithMeRoute } from "routes/routes";
 
 export const toggleProjectTrashed =
     (uuid: string, ownerUuid: string, isTrashed: boolean, isMulti: boolean) =>
-    async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository): Promise<any> => {
-        let errorMessage = "";
-        let successMessage = "";
-        let untrashedResource;
-        try {
-            if (isTrashed) {
-                errorMessage = "Could not restore project from trash";
-                successMessage = "Restored project from trash";
-                untrashedResource = await services.groupsService.untrash(uuid);
-                dispatch<any>(isMulti || !untrashedResource ? navigateToTrash : navigateTo(uuid));
-                dispatch<any>(activateSidePanelTreeItem(uuid));
-            } else {
-                errorMessage = "Could not move project to trash";
-                successMessage = "Added project to trash";
-                await services.groupsService.trash(uuid);
-                dispatch<any>(loadSidePanelTreeProjects(ownerUuid));
-                dispatch<any>(navigateTo(ownerUuid));
-            }
-            if (untrashedResource) {
-                dispatch(
-                    snackbarActions.OPEN_SNACKBAR({
-                        message: successMessage,
-                        hideDuration: 2000,
-                        kind: SnackbarKind.SUCCESS,
-                    })
-                );
-            }
-        } catch (e) {
-            if (e.status === 422) {
-                dispatch(
-                    snackbarActions.OPEN_SNACKBAR({
-                        message: "Could not restore project from trash: Duplicate name at destination",
-                        kind: SnackbarKind.ERROR,
-                    })
-                );
-            } else {
-                dispatch(
-                    snackbarActions.OPEN_SNACKBAR({
-                        message: errorMessage,
-                        kind: SnackbarKind.ERROR,
-                    })
-                );
+        async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository): Promise<any> => {
+            let errorMessage = "";
+            let successMessage = "";
+            let untrashedResource;
+            try {
+                if (isTrashed) {
+                    errorMessage = "Could not restore project from trash";
+                    successMessage = "Restored project from trash";
+                    untrashedResource = await services.groupsService.untrash(uuid);
+                    dispatch<any>(isMulti || !untrashedResource ? navigateToTrash : navigateTo(uuid));
+                    dispatch<any>(activateSidePanelTreeItem(uuid));
+                } else {
+                    errorMessage = "Could not move project to trash";
+                    successMessage = "Added project to trash";
+                    await services.groupsService.trash(uuid);
+                    dispatch<any>(loadSidePanelTreeProjects(ownerUuid));
+
+                    const { location } = getState().router;
+                    if (matchSharedWithMeRoute(location ? location.pathname : "")) {
+                        dispatch(sharedWithMePanelActions.REQUEST_ITEMS());
+                    }
+                    else {
+                        dispatch<any>(navigateTo(ownerUuid));
+                    }
+                }
+                if (untrashedResource) {
+                    dispatch(
+                        snackbarActions.OPEN_SNACKBAR({
+                            message: successMessage,
+                            hideDuration: 2000,
+                            kind: SnackbarKind.SUCCESS,
+                        })
+                    );
+                }
+            } catch (e) {
+                if (e.status === 422) {
+                    dispatch(
+                        snackbarActions.OPEN_SNACKBAR({
+                            message: "Could not restore project from trash: Duplicate name at destination",
+                            kind: SnackbarKind.ERROR,
+                        })
+                    );
+                } else {
+                    dispatch(
+                        snackbarActions.OPEN_SNACKBAR({
+                            message: errorMessage,
+                            kind: SnackbarKind.ERROR,
+                        })
+                    );
+                }
             }
-        }
-    };
+        };
 
 export const toggleCollectionTrashed =
     (uuid: string, isTrashed: boolean) =>
-    async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository): Promise<any> => {
-        let errorMessage = "";
-        let successMessage = "";
-        try {
-            if (isTrashed) {
-                const { location } = getState().router;
-                errorMessage = "Could not restore collection from trash";
-                successMessage = "Restored from trash";
-                await services.collectionService.untrash(uuid);
-                if (matchCollectionRoute(location ? location.pathname : "")) {
-                    dispatch(navigateToTrash);
+        async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository): Promise<any> => {
+            let errorMessage = "";
+            let successMessage = "";
+            try {
+                if (isTrashed) {
+                    const { location } = getState().router;
+                    errorMessage = "Could not restore collection from trash";
+                    successMessage = "Restored from trash";
+                    await services.collectionService.untrash(uuid);
+                    if (matchCollectionRoute(location ? location.pathname : "")) {
+                        dispatch(navigateToTrash);
+                    }
+                    dispatch(trashPanelActions.REQUEST_ITEMS());
+                } else {
+                    errorMessage = "Could not move collection to trash";
+                    successMessage = "Added to trash";
+                    await services.collectionService.trash(uuid);
+                    dispatch(projectPanelActions.REQUEST_ITEMS());
                 }
-                dispatch(trashPanelActions.REQUEST_ITEMS());
-            } else {
-                errorMessage = "Could not move collection to trash";
-                successMessage = "Added to trash";
-                await services.collectionService.trash(uuid);
-                dispatch(projectPanelActions.REQUEST_ITEMS());
-            }
-            dispatch(
-                snackbarActions.OPEN_SNACKBAR({
-                    message: successMessage,
-                    hideDuration: 2000,
-                    kind: SnackbarKind.SUCCESS,
-                })
-            );
-        } catch (e) {
-            if (e.status === 422) {
                 dispatch(
                     snackbarActions.OPEN_SNACKBAR({
-                        message: "Could not restore collection from trash: Duplicate name at destination",
-                        kind: SnackbarKind.ERROR,
-                    })
-                );
-            } else {
-                dispatch(
-                    snackbarActions.OPEN_SNACKBAR({
-                        message: errorMessage,
-                        kind: SnackbarKind.ERROR,
+                        message: successMessage,
+                        hideDuration: 2000,
+                        kind: SnackbarKind.SUCCESS,
                     })
                 );
+            } catch (e) {
+                if (e.status === 422) {
+                    dispatch(
+                        snackbarActions.OPEN_SNACKBAR({
+                            message: "Could not restore collection from trash: Duplicate name at destination",
+                            kind: SnackbarKind.ERROR,
+                        })
+                    );
+                } else {
+                    dispatch(
+                        snackbarActions.OPEN_SNACKBAR({
+                            message: errorMessage,
+                            kind: SnackbarKind.ERROR,
+                        })
+                    );
+                }
             }
-        }
-    };
+        };
 
 export const toggleTrashed = (kind: ResourceKind, uuid: string, ownerUuid: string, isTrashed: boolean) => (dispatch: Dispatch) => {
     if (kind === ResourceKind.PROJECT) {
index eed9c7de71dd904e8d4419cc41eced617425f783..d93d6e9258673e5dc49979a6262f9a9bef47570d 100644 (file)
@@ -37,7 +37,7 @@ import { Link as ButtonLink } from '@material-ui/core';
 import { ResourceWithName, ResponsiblePerson } from 'views-components/data-explorer/renderers';
 import { MPVContainer, MPVPanelContent, MPVPanelState } from 'components/multi-panel-view/multi-panel-view';
 import { resourceIsFrozen } from 'common/frozen-resources';
-import { DefaultView } from "components/default-view/default-view";
+import { NotFoundView } from 'views/not-found-panel/not-found-panel';
 
 type CssRules = 'root'
     | 'button'
@@ -230,16 +230,10 @@ export const CollectionPanel = withStyles(styles)(connect(
                             </Card>
                         </MPVPanelContent>
                     </MPVContainer >
-                    : <Grid
-                        container
-                        alignItems="center"
-                        justify="center"
-                        style={{ minHeight: "100%" }}>
-                        <DefaultView
-                            icon={CollectionIcon}
-                            messages={["Collection not found"]}
-                        />
-                    </Grid>
+                    : <NotFoundView
+                        icon={CollectionIcon}
+                        messages={["Collection not found"]}
+                    />
                     ;
             }
 
index 148c331e2971b91ee826ca92a9a1f57b2ba8f312..f54c00c32a2f2856636b263bb19f75b8326d6666 100644 (file)
@@ -3,8 +3,12 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import { RootState } from 'store/store';
+import React from 'react';
 import { connect } from 'react-redux';
 import { NotFoundPanelRoot, NotFoundPanelRootDataProps } from 'views/not-found-panel/not-found-panel-root';
+import { Grid } from '@material-ui/core';
+import { DefaultView } from "components/default-view/default-view";
+import { IconType } from 'components/icon/icon';
 
 const mapStateToProps = (state: RootState): NotFoundPanelRootDataProps => {
     return {
@@ -17,3 +21,26 @@ const mapDispatchToProps = null;
 
 export const NotFoundPanel = connect(mapStateToProps, mapDispatchToProps)
     (NotFoundPanelRoot) as any;
+
+export interface NotFoundViewDataProps {
+    messages: string[];
+    icon?: IconType;
+}
+
+// TODO: optionally pass in the UUID and check if the
+// reason the item is not found is because
+// it or a parent project is actually in the trash.
+// If so, offer to untrash the item or the parent project.
+export const NotFoundView =
+    ({ messages, icon: Icon }: NotFoundViewDataProps) =>
+        <Grid
+            container
+            alignItems="center"
+            justify="center"
+            style={{ minHeight: "100%" }}
+            data-cy="not-found-view">
+            <DefaultView
+                icon={Icon}
+                messages={messages}
+            />
+        </Grid>;
index d019d1418fcf0c8841a94989e1211daffaf864b6..7a24089901333e5195af66081bf14bb8369e0035 100644 (file)
@@ -3,8 +3,7 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import React from "react";
-import { Grid, StyleRulesCallback, WithStyles, withStyles } from "@material-ui/core";
-import { DefaultView } from "components/default-view/default-view";
+import { StyleRulesCallback, WithStyles, withStyles } from "@material-ui/core";
 import { ProcessIcon } from "components/icon/icon";
 import { Process } from "store/processes/process";
 import { SubprocessPanel } from "views/subprocess-panel/subprocess-panel";
@@ -24,6 +23,7 @@ import { AuthState } from "store/auth/auth-reducer";
 import { ProcessCmdCard } from "./process-cmd-card";
 import { ContainerRequestResource } from "models/container-request";
 import { OutputDetails, NodeInstanceType } from "store/process-panel/process-panel";
+import { NotFoundView } from 'views/not-found-panel/not-found-panel';
 
 type CssRules = "root";
 
@@ -209,16 +209,10 @@ export const ProcessPanelRoot = withStyles(styles)(
                 </MPVPanelContent>
             </MPVContainer>
         ) : (
-            <Grid
-                container
-                alignItems="center"
-                justify="center"
-                style={{ minHeight: "100%" }}>
-                <DefaultView
-                    icon={ProcessIcon}
-                    messages={["Process not found"]}
-                />
-            </Grid>
+            <NotFoundView
+                icon={ProcessIcon}
+                messages={["Process not found"]}
+            />
         );
     }
 );
index 2f274c97d50c54df981966c5b61bf3543dadddb3..2cc751bffd6e78526b38ea07e8d0a5ca4d0683f2 100644 (file)
@@ -6,7 +6,7 @@ import React from 'react';
 import withStyles from '@material-ui/core/styles/withStyles';
 import { DispatchProp, connect } from 'react-redux';
 import { RouteComponentProps } from 'react-router';
-import { StyleRulesCallback, WithStyles, Grid } from '@material-ui/core';
+import { StyleRulesCallback, WithStyles } from '@material-ui/core';
 
 import { DataExplorer } from 'views-components/data-explorer/data-explorer';
 import { DataColumns } from 'components/data-table/data-table';
@@ -51,7 +51,7 @@ import { GroupClass, GroupResource } from 'models/group';
 import { CollectionResource } from 'models/collection';
 import { resourceIsFrozen } from 'common/frozen-resources';
 import { ProjectResource } from 'models/project';
-import { DefaultView } from "components/default-view/default-view";
+import { NotFoundView } from 'views/not-found-panel/not-found-panel';
 
 type CssRules = 'root' | 'button';
 
@@ -276,16 +276,11 @@ export const ProjectPanel = withStyles(styles)(
                             defaultViewMessages={DEFAULT_VIEW_MESSAGES}
                         />
                     </div>
-                    : <Grid
-                        container
-                        alignItems="center"
-                        justify="center"
-                        style={{ minHeight: "100%" }}>
-                        <DefaultView
-                            icon={ProjectIcon}
-                            messages={["Project not found"]}
-                        />
-                    </Grid>;
+                    :
+                    <NotFoundView
+                        icon={ProjectIcon}
+                        messages={["Project not found"]}
+                    />
             }
 
             isCurrentItemChild = (resource: Resource) => {
index da273719bf1b8baa544c47d58c653db32af0ff67..50192e543dbe7f3cf7a3368a9743cd5a278b394d 100644 (file)
@@ -12,8 +12,7 @@ import {
     Card,
     CardHeader,
     CardContent,
-    IconButton,
-    Grid
+    IconButton
 } from '@material-ui/core';
 import { connect, DispatchProp } from "react-redux";
 import { RouteComponentProps } from 'react-router';
@@ -27,7 +26,7 @@ import { getResource } from 'store/resources/resources';
 import { openContextMenu, resourceUuidToContextMenuKind } from 'store/context-menu/context-menu-actions';
 import { MPVContainer, MPVPanelContent, MPVPanelState } from 'components/multi-panel-view/multi-panel-view';
 import { ProcessIOCard, ProcessIOCardType } from 'views/process-panel/process-io-card';
-import { DefaultView } from "components/default-view/default-view";
+import { NotFoundView } from 'views/not-found-panel/not-found-panel';
 
 type CssRules = 'root'
     | 'button'
@@ -202,16 +201,11 @@ export const RegisteredWorkflowPanel = withStyles(styles)(connect(
                             </Card>
                         </MPVPanelContent>
                     </MPVContainer>
-                    : <Grid
-                        container
-                        alignItems="center"
-                        justify="center"
-                        style={{ minHeight: "100%" }}>
-                        <DefaultView
-                            icon={WorkflowIcon}
-                            messages={["Workflow not found"]}
-                        />
-                    </Grid>;
+                    :
+                    <NotFoundView
+                        icon={WorkflowIcon}
+                        messages={["Workflow not found"]}
+                    />
             }
 
             handleContextMenu = (event: React.MouseEvent<any>) => {