[ARVADOS-WORKBENCH2] created: 2.1.0-56-gac09b3b5

Git user git at public.arvados.org
Wed Nov 11 09:21:16 UTC 2020


        at  ac09b3b5c8ecd122b778bb9c17f2aef1f00f6b05 (commit)


commit ac09b3b5c8ecd122b778bb9c17f2aef1f00f6b05
Author: Daniel Kutyła <daniel.kutyla at contractors.roche.com>
Date:   Fri Nov 6 22:15:21 2020 +0100

    16005: Open collection and project in new tab feature added
    
    Arvados-DCO-1.1-Signed-off-by: Daniel Kutyła <daniel.kutyla at contractors.roche.com>1~

diff --git a/src/common/array-utils.ts b/src/common/array-utils.ts
new file mode 100644
index 00000000..a92461c8
--- /dev/null
+++ b/src/common/array-utils.ts
@@ -0,0 +1,18 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+export const sortByProperty = (propName: string) => (obj1: any, obj2: any) => {
+    const prop1 = obj1[propName];
+    const prop2 = obj2[propName];
+    
+    if (prop1 > prop2) {
+        return 1;
+    }
+
+    if (prop1 < prop2) {
+        return -1;
+    }
+
+    return 0;
+};
diff --git a/src/common/copy-store.ts b/src/common/copy-store.ts
new file mode 100644
index 00000000..9c7f3875
--- /dev/null
+++ b/src/common/copy-store.ts
@@ -0,0 +1,28 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+const STORE_COPY_KEY = 'storeCopy';
+
+export const copyStore = (store: any) => {
+    const { localStorage } = window;
+    const state = store.getState();
+    const storeCopy = JSON.parse(JSON.stringify(state));
+    storeCopy.router.location.pathname = '/';
+
+    if (localStorage) {
+        localStorage.setItem(STORE_COPY_KEY, JSON.stringify(storeCopy));
+    }
+};
+
+export const restoreStore = () => {
+    let storeCopy = null;
+    const { localStorage } = window;
+
+    if (localStorage && localStorage.getItem(STORE_COPY_KEY)) {
+        storeCopy = localStorage.getItem(STORE_COPY_KEY);
+        localStorage.removeItem(STORE_COPY_KEY);
+    }
+
+    return storeCopy;
+};
diff --git a/src/common/redirect-to.ts b/src/common/redirect-to.ts
index f5ece21b..baf44711 100644
--- a/src/common/redirect-to.ts
+++ b/src/common/redirect-to.ts
@@ -5,14 +5,22 @@
 import { Config } from './config';
 
 const REDIRECT_TO_KEY = 'redirectTo';
+export const REDIRECT_TO_APPLY_TO_PATH = 'redirectToApplyToPath';
 
 export const storeRedirects = () => {
-    if (window.location.href.indexOf(REDIRECT_TO_KEY) > -1) {
-        const { location: { href }, localStorage } = window;
-        const redirectUrl = href.split(`${REDIRECT_TO_KEY}=`)[1];
+    let redirectUrl;
+    const { location: { href }, localStorage } = window;
+    const applyToPath = href.indexOf(REDIRECT_TO_APPLY_TO_PATH) > -1;
 
-        if (localStorage) {
-            localStorage.setItem(REDIRECT_TO_KEY, redirectUrl);
+    if (href.indexOf(REDIRECT_TO_KEY) > -1) {
+        redirectUrl = href.split(`${REDIRECT_TO_KEY}=`)[1];
+    }
+
+    if (localStorage && redirectUrl) {
+        localStorage.setItem(REDIRECT_TO_KEY, redirectUrl);
+
+        if (applyToPath) {
+            localStorage.setItem(REDIRECT_TO_APPLY_TO_PATH, 'true');
         }
     }
 };
@@ -24,9 +32,18 @@ export const handleRedirects = (token: string, config: Config) => {
     if (localStorage && localStorage.getItem(REDIRECT_TO_KEY)) {
         const redirectUrl = localStorage.getItem(REDIRECT_TO_KEY);
         localStorage.removeItem(REDIRECT_TO_KEY);
+        const applyToPath = localStorage.getItem(REDIRECT_TO_APPLY_TO_PATH);
+
         if (redirectUrl) {
-            const sep = redirectUrl.indexOf("?") > -1 ? "&" : "?";
-            window.location.href = `${keepWebServiceUrl}${redirectUrl}${sep}api_token=${token}`;
+            if (applyToPath === 'true') {
+                localStorage.removeItem(REDIRECT_TO_APPLY_TO_PATH);
+                setTimeout(() => {
+                    window.location.pathname = redirectUrl;
+                }, 0);
+            } else {
+                const sep = redirectUrl.indexOf("?") > -1 ? "&" : "?";
+                window.location.href = `${keepWebServiceUrl}${redirectUrl}${sep}api_token=${token}`;
+            }
         }
     }
 };
diff --git a/src/store/open-in-new-tab/open-in-new-tab.actions.ts b/src/store/open-in-new-tab/open-in-new-tab.actions.ts
index 17ba7402..c6462ea1 100644
--- a/src/store/open-in-new-tab/open-in-new-tab.actions.ts
+++ b/src/store/open-in-new-tab/open-in-new-tab.actions.ts
@@ -2,24 +2,36 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import { Dispatch } from 'redux';
+import * as copy from 'copy-to-clipboard';
 import { ResourceKind } from '~/models/resource';
-import { unionize, ofType } from '~/common/unionize';
+import { getClipboardUrl } from '~/views-components/context-menu/actions/helpers';
 
-export const openInNewTabActions = unionize({
-    COPY_STORE: ofType<{}>(),
-    OPEN_COLLECTION_IN_NEW_TAB: ofType<string>(),
-    OPEN_PROJECT_IN_NEW_TAB: ofType<string>()
-});
-
-export const openInNewTabAction = (resource: any) => (dispatch: Dispatch) => {
+const getUrl = (resource: any) => {
+    let url = null;
     const { uuid, kind } = resource;
 
-    dispatch(openInNewTabActions.COPY_STORE());
-
     if (kind === ResourceKind.COLLECTION) {
-        dispatch(openInNewTabActions.OPEN_COLLECTION_IN_NEW_TAB(uuid));
-    } else if (kind === ResourceKind.PROJECT) {
-        dispatch(openInNewTabActions.OPEN_PROJECT_IN_NEW_TAB(uuid));
+        url = `/collections/${uuid}`;
+    }
+    if (kind === ResourceKind.PROJECT) {
+        url = `/projects/${uuid}`;
+    }
+
+    return url;
+};
+
+export const openInNewTabAction = (resource: any) => () => {
+    const url = getUrl(resource);
+
+    if (url) {
+        window.open(`${window.location.origin}${url}`, '_blank');
+    }
+};
+
+export const copyToClipboardAction = (resource: any) => () => {
+    const url = getUrl(resource);
+
+    if (url) {
+        copy(getClipboardUrl(url, false));
     }
 };
\ No newline at end of file
diff --git a/src/store/store.ts b/src/store/store.ts
index 7beb099c..517368aa 100644
--- a/src/store/store.ts
+++ b/src/store/store.ts
@@ -163,6 +163,7 @@ export function configureStore(history: History, services: ServiceRepository, co
         collectionsContentAddress,
         subprocessMiddleware,
     ];
+
     const enhancer = composeEnhancers(applyMiddleware(redirectToMiddleware, ...middlewares));
     return createStore(rootReducer, enhancer);
 }
diff --git a/src/views-components/advanced-tab-dialog/advanced-tab-dialog.tsx b/src/views-components/advanced-tab-dialog/advanced-tab-dialog.tsx
index fdd0bd70..7e90bfd2 100644
--- a/src/views-components/advanced-tab-dialog/advanced-tab-dialog.tsx
+++ b/src/views-components/advanced-tab-dialog/advanced-tab-dialog.tsx
@@ -87,7 +87,7 @@ export const AdvancedTabDialog = compose(
                     {value === 4 && dialogContent(curlHeader, curlExample, classes)}
                 </DialogContent>
                 <DialogActions>
-                    <Button variant='text' color='primary' onClick={closeDialog}>
+                    <Button data-cy="close-advanced-dialog" variant='text' color='primary' onClick={closeDialog}>
                         Close
                     </Button>
                 </DialogActions>
diff --git a/src/views-components/context-menu/action-sets/collection-action-set.ts b/src/views-components/context-menu/action-sets/collection-action-set.ts
index 7fa6f224..4b6b9224 100644
--- a/src/views-components/context-menu/action-sets/collection-action-set.ts
+++ b/src/views-components/context-menu/action-sets/collection-action-set.ts
@@ -5,7 +5,7 @@
 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, AdvancedIcon } from "~/components/icon/icon";
+import { RenameIcon, ShareIcon, MoveToIcon, CopyIcon, DetailsIcon, AdvancedIcon, OpenIcon, Link } from "~/components/icon/icon";
 import { openCollectionUpdateDialog } from "~/store/collections/collection-update-actions";
 import { favoritePanelActions } from "~/store/favorite-panel/favorite-panel-action";
 import { openMoveCollectionDialog } from '~/store/collections/collection-move-actions';
@@ -15,16 +15,32 @@ import { toggleCollectionTrashed } from "~/store/trash/trash-actions";
 import { openSharingDialog } from '~/store/sharing-dialog/sharing-dialog-actions';
 import { openAdvancedTabDialog } from "~/store/advanced-tab/advanced-tab";
 import { toggleDetailsPanel } from '~/store/details-panel/details-panel-action';
+import { copyToClipboardAction, openInNewTabAction } from "~/store/open-in-new-tab/open-in-new-tab.actions";
 
 export const readOnlyCollectionActionSet: ContextMenuActionSet = [[
     {
         component: ToggleFavoriteAction,
+        name: 'ToggleFavoriteAction',
         execute: (dispatch, resource) => {
             dispatch<any>(toggleFavorite(resource)).then(() => {
                 dispatch<any>(favoritePanelActions.REQUEST_ITEMS());
             });
         }
     },
+    {
+        icon: OpenIcon,
+        name: "Open in new tab",
+        execute: (dispatch, resource) => {
+            dispatch<any>(openInNewTabAction(resource));
+        }
+    },
+    {
+        icon: Link,
+        name: "Copy to clipboard",
+        execute: (dispatch, resource) => {
+            dispatch<any>(copyToClipboardAction(resource));
+        }
+    },
     {
         icon: CopyIcon,
         name: "Make a copy",
@@ -73,6 +89,7 @@ export const collectionActionSet: ContextMenuActionSet = [
         },
         {
             component: ToggleTrashAction,
+            name: 'ToggleTrashAction',
             execute: (dispatch, resource) => {
                 dispatch<any>(toggleCollectionTrashed(resource.uuid, resource.isTrashed!!));
             }
diff --git a/src/views-components/context-menu/action-sets/collection-admin-action-set.ts b/src/views-components/context-menu/action-sets/collection-admin-action-set.ts
index 10a839d8..7b39d749 100644
--- a/src/views-components/context-menu/action-sets/collection-admin-action-set.ts
+++ b/src/views-components/context-menu/action-sets/collection-admin-action-set.ts
@@ -5,7 +5,7 @@
 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, AdvancedIcon } from "~/components/icon/icon";
+import { RenameIcon, ShareIcon, MoveToIcon, CopyIcon, DetailsIcon, AdvancedIcon, OpenIcon, Link } from "~/components/icon/icon";
 import { openCollectionUpdateDialog } from "~/store/collections/collection-update-actions";
 import { favoritePanelActions } from "~/store/favorite-panel/favorite-panel-action";
 import { openMoveCollectionDialog } from '~/store/collections/collection-move-actions';
@@ -18,6 +18,7 @@ import { toggleDetailsPanel } from '~/store/details-panel/details-panel-action';
 import { TogglePublicFavoriteAction } from "~/views-components/context-menu/actions/public-favorite-action";
 import { publicFavoritePanelActions } from "~/store/public-favorites-panel/public-favorites-action";
 import { togglePublicFavorite } from "~/store/public-favorites/public-favorites-actions";
+import { copyToClipboardAction, openInNewTabAction } from "~/store/open-in-new-tab/open-in-new-tab.actions";
 
 export const collectionAdminActionSet: ContextMenuActionSet = [[
     {
@@ -27,6 +28,20 @@ export const collectionAdminActionSet: ContextMenuActionSet = [[
             dispatch<any>(openCollectionUpdateDialog(resource));
         }
     },
+    {
+        icon: OpenIcon,
+        name: "Open in new tab",
+        execute: (dispatch, resource) => {
+            dispatch<any>(openInNewTabAction(resource));
+        }
+    },
+    {
+        icon: Link,
+        name: "Copy to clipboard",
+        execute: (dispatch, resource) => {
+            dispatch<any>(copyToClipboardAction(resource));
+        }
+    },
     {
         icon: ShareIcon,
         name: "Share",
@@ -36,6 +51,7 @@ export const collectionAdminActionSet: ContextMenuActionSet = [[
     },
     {
         component: ToggleFavoriteAction,
+        name: 'ToggleFavoriteAction',
         execute: (dispatch, resource) => {
             dispatch<any>(toggleFavorite(resource)).then(() => {
                 dispatch<any>(favoritePanelActions.REQUEST_ITEMS());
@@ -44,6 +60,7 @@ export const collectionAdminActionSet: ContextMenuActionSet = [[
     },
     {
         component: TogglePublicFavoriteAction,
+        name: 'TogglePublicFavoriteAction',
         execute: (dispatch, resource) => {
             dispatch<any>(togglePublicFavorite(resource)).then(() => {
                 dispatch<any>(publicFavoritePanelActions.REQUEST_ITEMS());
@@ -79,6 +96,7 @@ export const collectionAdminActionSet: ContextMenuActionSet = [[
     },
     {
         component: ToggleTrashAction,
+        name: 'ToggleTrashAction',
         execute: (dispatch, resource) => {
             dispatch<any>(toggleCollectionTrashed(resource.uuid, resource.isTrashed!!));
         }
diff --git a/src/views-components/context-menu/action-sets/collection-admin-action-set.ts b/src/views-components/context-menu/action-sets/collection-resource-action-set.ts
similarity index 70%
copy from src/views-components/context-menu/action-sets/collection-admin-action-set.ts
copy to src/views-components/context-menu/action-sets/collection-resource-action-set.ts
index 10a839d8..5bd362f5 100644
--- a/src/views-components/context-menu/action-sets/collection-admin-action-set.ts
+++ b/src/views-components/context-menu/action-sets/collection-resource-action-set.ts
@@ -4,22 +4,20 @@
 
 import { ContextMenuActionSet } from "../context-menu-action-set";
 import { ToggleFavoriteAction } from "../actions/favorite-action";
+import { ToggleTrashAction } from "~/views-components/context-menu/actions/trash-action";
 import { toggleFavorite } from "~/store/favorites/favorites-actions";
-import { RenameIcon, ShareIcon, MoveToIcon, CopyIcon, DetailsIcon, AdvancedIcon } from "~/components/icon/icon";
+import { RenameIcon, ShareIcon, MoveToIcon, CopyIcon, DetailsIcon, AdvancedIcon, OpenIcon } from '~/components/icon/icon';
 import { openCollectionUpdateDialog } from "~/store/collections/collection-update-actions";
 import { favoritePanelActions } from "~/store/favorite-panel/favorite-panel-action";
 import { openMoveCollectionDialog } from '~/store/collections/collection-move-actions';
-import { openCollectionCopyDialog } from "~/store/collections/collection-copy-actions";
-import { ToggleTrashAction } from "~/views-components/context-menu/actions/trash-action";
+import { openCollectionCopyDialog } from '~/store/collections/collection-copy-actions';
 import { toggleCollectionTrashed } from "~/store/trash/trash-actions";
-import { openSharingDialog } from '~/store/sharing-dialog/sharing-dialog-actions';
-import { openAdvancedTabDialog } from "~/store/advanced-tab/advanced-tab";
+import { openSharingDialog } from "~/store/sharing-dialog/sharing-dialog-actions";
+import { openAdvancedTabDialog } from '~/store/advanced-tab/advanced-tab';
 import { toggleDetailsPanel } from '~/store/details-panel/details-panel-action';
-import { TogglePublicFavoriteAction } from "~/views-components/context-menu/actions/public-favorite-action";
-import { publicFavoritePanelActions } from "~/store/public-favorites-panel/public-favorites-action";
-import { togglePublicFavorite } from "~/store/public-favorites/public-favorites-actions";
+import { openInNewTabAction } from "~/store/open-in-new-tab/open-in-new-tab.actions";
 
-export const collectionAdminActionSet: ContextMenuActionSet = [[
+export const collectionResourceActionSet: ContextMenuActionSet = [[
     {
         icon: RenameIcon,
         name: "Edit collection",
@@ -43,25 +41,25 @@ export const collectionAdminActionSet: ContextMenuActionSet = [[
         }
     },
     {
-        component: TogglePublicFavoriteAction,
+        icon: OpenIcon,
+        name: "Open in new tab",
         execute: (dispatch, resource) => {
-            dispatch<any>(togglePublicFavorite(resource)).then(() => {
-                dispatch<any>(publicFavoritePanelActions.REQUEST_ITEMS());
-            });
+            dispatch<any>(openInNewTabAction(resource));
         }
     },
     {
         icon: MoveToIcon,
         name: "Move to",
-        execute: (dispatch, resource) => dispatch<any>(openMoveCollectionDialog(resource))
+        execute: (dispatch, resource) => {
+            dispatch<any>(openMoveCollectionDialog(resource));
+        }
     },
     {
         icon: CopyIcon,
-        name: "Make a copy",
+        name: "Copy to project",
         execute: (dispatch, resource) => {
             dispatch<any>(openCollectionCopyDialog(resource));
         }
-
     },
     {
         icon: DetailsIcon,
@@ -83,4 +81,11 @@ export const collectionAdminActionSet: ContextMenuActionSet = [[
             dispatch<any>(toggleCollectionTrashed(resource.uuid, resource.isTrashed!!));
         }
     },
+    // {
+    //     icon: RemoveIcon,
+    //     name: "Remove",
+    //     execute: (dispatch, resource) => {
+    //         // add code
+    //     }
+    // }
 ]];
diff --git a/src/views-components/context-menu/action-sets/project-action-set.ts b/src/views-components/context-menu/action-sets/project-action-set.ts
index 4f92aeb8..c0b925c2 100644
--- a/src/views-components/context-menu/action-sets/project-action-set.ts
+++ b/src/views-components/context-menu/action-sets/project-action-set.ts
@@ -3,7 +3,7 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import { ContextMenuActionSet } from "../context-menu-action-set";
-import { NewProjectIcon, RenameIcon, MoveToIcon, DetailsIcon, AdvancedIcon } from '~/components/icon/icon';
+import { NewProjectIcon, RenameIcon, MoveToIcon, DetailsIcon, AdvancedIcon, OpenIcon, Link } from '~/components/icon/icon';
 import { ToggleFavoriteAction } from "../actions/favorite-action";
 import { toggleFavorite } from "~/store/favorites/favorites-actions";
 import { favoritePanelActions } from "~/store/favorite-panel/favorite-panel-action";
@@ -16,16 +16,32 @@ import { ShareIcon } from '~/components/icon/icon';
 import { openSharingDialog } from "~/store/sharing-dialog/sharing-dialog-actions";
 import { openAdvancedTabDialog } from "~/store/advanced-tab/advanced-tab";
 import { toggleDetailsPanel } from '~/store/details-panel/details-panel-action';
+import { copyToClipboardAction, openInNewTabAction } from "~/store/open-in-new-tab/open-in-new-tab.actions";
 
 export const readOnlyProjectActionSet: ContextMenuActionSet = [[
     {
         component: ToggleFavoriteAction,
+        name: 'ToggleFavoriteAction',
         execute: (dispatch, resource) => {
             dispatch<any>(toggleFavorite(resource)).then(() => {
                 dispatch<any>(favoritePanelActions.REQUEST_ITEMS());
             });
         }
     },
+    {
+        icon: OpenIcon,
+        name: "Open in new tab",
+        execute: (dispatch, resource) => {
+            dispatch<any>(openInNewTabAction(resource));
+        }
+    },
+    {
+        icon: Link,
+        name: "Copy to clipboard",
+        execute: (dispatch, resource) => {
+            dispatch<any>(copyToClipboardAction(resource));
+        }
+    },
     {
         icon: DetailsIcon,
         name: "View details",
@@ -75,6 +91,7 @@ export const projectActionSet: ContextMenuActionSet = [
         },
         {
             component: ToggleTrashAction,
+            name: 'ToggleTrashAction',
             execute: (dispatch, resource) => {
                 dispatch<any>(toggleProjectTrashed(resource.uuid, resource.ownerUuid, resource.isTrashed!!));
             }
diff --git a/src/views-components/context-menu/action-sets/project-admin-action-set.ts b/src/views-components/context-menu/action-sets/project-admin-action-set.ts
index f6185804..398864dc 100644
--- a/src/views-components/context-menu/action-sets/project-admin-action-set.ts
+++ b/src/views-components/context-menu/action-sets/project-admin-action-set.ts
@@ -3,7 +3,7 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import { ContextMenuActionSet } from "../context-menu-action-set";
-import { NewProjectIcon, RenameIcon, MoveToIcon, DetailsIcon, AdvancedIcon } from '~/components/icon/icon';
+import { NewProjectIcon, RenameIcon, MoveToIcon, DetailsIcon, AdvancedIcon, OpenIcon, Link } from '~/components/icon/icon';
 import { ToggleFavoriteAction } from "../actions/favorite-action";
 import { toggleFavorite } from "~/store/favorites/favorites-actions";
 import { favoritePanelActions } from "~/store/favorite-panel/favorite-panel-action";
@@ -19,6 +19,7 @@ import { toggleDetailsPanel } from '~/store/details-panel/details-panel-action';
 import { TogglePublicFavoriteAction } from "~/views-components/context-menu/actions/public-favorite-action";
 import { togglePublicFavorite } from "~/store/public-favorites/public-favorites-actions";
 import { publicFavoritePanelActions } from "~/store/public-favorites-panel/public-favorites-action";
+import { copyToClipboardAction, openInNewTabAction } from "~/store/open-in-new-tab/open-in-new-tab.actions";
 
 export const projectAdminActionSet: ContextMenuActionSet = [[
     {
@@ -28,6 +29,20 @@ export const projectAdminActionSet: ContextMenuActionSet = [[
             dispatch<any>(openProjectCreateDialog(resource.uuid));
         }
     },
+    {
+        icon: OpenIcon,
+        name: "Open in new tab",
+        execute: (dispatch, resource) => {
+            dispatch<any>(openInNewTabAction(resource));
+        }
+    },
+    {
+        icon: Link,
+        name: "Copy to clipboard",
+        execute: (dispatch, resource) => {
+            dispatch<any>(copyToClipboardAction(resource));
+        }
+    },
     {
         icon: RenameIcon,
         name: "Edit project",
@@ -44,6 +59,7 @@ export const projectAdminActionSet: ContextMenuActionSet = [[
     },
     {
         component: ToggleFavoriteAction,
+        name: 'ToggleFavoriteAction',
         execute: (dispatch, resource) => {
             dispatch<any>(toggleFavorite(resource)).then(() => {
                 dispatch<any>(favoritePanelActions.REQUEST_ITEMS());
@@ -52,6 +68,7 @@ export const projectAdminActionSet: ContextMenuActionSet = [[
     },
     {
         component: TogglePublicFavoriteAction,
+        name: 'TogglePublicFavoriteAction',
         execute: (dispatch, resource) => {
             dispatch<any>(togglePublicFavorite(resource)).then(() => {
                 dispatch<any>(publicFavoritePanelActions.REQUEST_ITEMS());
@@ -88,6 +105,7 @@ export const projectAdminActionSet: ContextMenuActionSet = [[
     },
     {
         component: ToggleTrashAction,
+        name: 'ToggleTrashAction',
         execute: (dispatch, resource) => {
             dispatch<any>(toggleProjectTrashed(resource.uuid, resource.ownerUuid, resource.isTrashed!!));
         }
diff --git a/src/views-components/context-menu/actions/helpers.ts b/src/views-components/context-menu/actions/helpers.ts
index 8dfcaca0..578af205 100644
--- a/src/views-components/context-menu/actions/helpers.ts
+++ b/src/views-components/context-menu/actions/helpers.ts
@@ -2,7 +2,9 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-export const sanitizeToken = (href: string, tokenAsQueryParam: boolean = true): string => {
+import { REDIRECT_TO_APPLY_TO_PATH } from "~/common/redirect-to";
+
+export const sanitizeToken = (href: string, tokenAsQueryParam = true): string => {
     const [prefix, suffix] = href.split('/t=');
     const [token1, token2, token3, ...rest] = suffix.split('/');
     const token = `${token1}/${token2}/${token3}`;
@@ -11,9 +13,9 @@ export const sanitizeToken = (href: string, tokenAsQueryParam: boolean = true):
     return `${[prefix, ...rest].join('/')}${tokenAsQueryParam ? `${sep}api_token=${token}` : ''}`;
 };
 
-export const getClipboardUrl = (href: string): string => {
+export const getClipboardUrl = (href: string, shouldSanitizeToken = true): string => {
     const { origin } = window.location;
-    const url = sanitizeToken(href, false);
+    const url = shouldSanitizeToken ? sanitizeToken(href, false) : href;
 
-    return `${origin}?redirectTo=${url}`;
+    return `${origin}${!shouldSanitizeToken ? `?${REDIRECT_TO_APPLY_TO_PATH}=true&` : `?`}redirectTo=${url}`;
 };
diff --git a/src/views-components/context-menu/context-menu.tsx b/src/views-components/context-menu/context-menu.tsx
index 43474dd1..b86498a0 100644
--- a/src/views-components/context-menu/context-menu.tsx
+++ b/src/views-components/context-menu/context-menu.tsx
@@ -10,6 +10,7 @@ import { createAnchorAt } from "~/components/popover/helpers";
 import { ContextMenuActionSet, ContextMenuAction } from "./context-menu-action-set";
 import { Dispatch } from "redux";
 import { memoize } from 'lodash';
+import { sortByProperty } from "~/common/array-utils";
 type DataProps = Pick<ContextMenuProps, "anchorEl" | "items" | "open"> & { resource?: ContextMenuResource };
 const mapStateToProps = (state: RootState): DataProps => {
     const { open, position, resource } = state.contextMenu;
@@ -53,7 +54,8 @@ export const ContextMenu = connect(mapStateToProps, mapDispatchToProps, mergePro
 const menuActionSets = new Map<string, ContextMenuActionSet>();
 
 export const addMenuActionSet = (name: string, itemSet: ContextMenuActionSet) => {
-    menuActionSets.set(name, itemSet);
+    const sorted = itemSet.map(items => items.sort(sortByProperty('name')));
+    menuActionSets.set(name, sorted);
 };
 
 const emptyActionSet: ContextMenuActionSet = [];

-----------------------------------------------------------------------


hooks/post-receive
-- 




More information about the arvados-commits mailing list