[ARVADOS-WORKBENCH2] updated: 2.1.0-216-gf18efe02

Git user git at public.arvados.org
Fri Feb 26 22:30:59 UTC 2021


Summary of changes:
 Makefile                                           |  2 +-
 src/common/plugintypes.ts                          | 29 ++++++++++++
 src/plugins.tsx                                    | 23 ++++++++++
 src/plugins/blank/index.tsx                        | 14 ++++++
 src/plugins/example/index.tsx                      | 52 ++++++++++++++++++++++
 src/plugins/root-redirect/index.tsx                | 20 +++++++++
 src/routes/route-change-handlers.ts                |  9 +++-
 src/store/breadcrumbs/breadcrumbs-actions.ts       |  2 +-
 src/store/navigation/navigation-action.ts          | 30 +++++++++++--
 .../side-panel-tree/side-panel-tree-actions.ts     | 13 ++++--
 src/store/side-panel/side-panel-action.ts          | 36 +--------------
 src/store/workbench/workbench-actions.ts           |  4 +-
 src/views/workbench/workbench.tsx                  |  9 ++--
 13 files changed, 193 insertions(+), 50 deletions(-)
 create mode 100644 src/common/plugintypes.ts
 create mode 100644 src/plugins.tsx
 create mode 100644 src/plugins/blank/index.tsx
 create mode 100644 src/plugins/example/index.tsx
 create mode 100644 src/plugins/root-redirect/index.tsx

       via  f18efe026ee07b02e56a2c972c509c2f8b742712 (commit)
      from  b28028dbe0bea1aeb3b772733a7c61ef202e1526 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.


commit f18efe026ee07b02e56a2c972c509c2f8b742712
Author: Peter Amstutz <peter.amstutz at curii.com>
Date:   Fri Feb 26 17:29:55 2021 -0500

    17426: Plugins can replace some of main UI
    
    Add example and utility plugins.
    
    Arvados-DCO-1.1-Signed-off-by: Peter Amstutz <peter.amstutz at curii.com>

diff --git a/Makefile b/Makefile
index 6da3ed10..1e135a7f 100644
--- a/Makefile
+++ b/Makefile
@@ -23,7 +23,7 @@ DESCRIPTION=Arvados Workbench2 - Arvados is a free and open source platform for
 MAINTAINER=Arvados Package Maintainers <packaging at arvados.org>
 
 # DEST_DIR will have the build package copied.
-DEST_DIR=/var/www/arvados-workbench2/workbench2/
+DEST_DIR=/var/www/$(APP_NAME)/workbench2/
 
 # Debian package file
 DEB_FILE=$(APP_NAME)_$(VERSION)-$(ITERATION)_amd64.deb
diff --git a/src/common/plugintypes.ts b/src/common/plugintypes.ts
new file mode 100644
index 00000000..489568ea
--- /dev/null
+++ b/src/common/plugintypes.ts
@@ -0,0 +1,29 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import * as React from 'react';
+import { Dispatch } from 'redux';
+import { RootStore, RootState } from '~/store/store';
+
+export type RouteListReducer = (startingList: React.ReactElement[]) => React.ReactElement[];
+export type CategoriesListReducer = (startingList: string[]) => string[];
+export type NavigateMatcher = (dispatch: Dispatch, getState: () => RootState, uuid: string) => boolean;
+export type LocationChangeMatcher = (store: RootStore, pathname: string) => boolean;
+
+export interface PluginConfig {
+    // Customize the list of possible center panels by adding or removing Route components.
+    centerPanelList: RouteListReducer[];
+
+    // Customize the list of side panel categories
+    sidePanelCategories: CategoriesListReducer[];
+
+    // Add to the list of possible dialogs by adding dialog components.
+    dialogs: React.ReactElement[];
+
+    // Add navigation actions for identifiers
+    navigateToHandlers: NavigateMatcher[];
+
+    // Add handlers for navigation actions
+    locationChangeHandlers: LocationChangeMatcher[];
+}
diff --git a/src/plugins.tsx b/src/plugins.tsx
new file mode 100644
index 00000000..fb52aade
--- /dev/null
+++ b/src/plugins.tsx
@@ -0,0 +1,23 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { PluginConfig } from '~/common/plugintypes';
+
+export const pluginConfig: PluginConfig = {
+    centerPanelList: [],
+    sidePanelCategories: [],
+    dialogs: [],
+    navigateToHandlers: [],
+    locationChangeHandlers: []
+};
+
+// Starting here, import and register your Workbench 2 plugins. //
+
+// import { register as blankUIPluginRegister } from '~/plugins/blank/index';
+import { register as examplePluginRegister, routePath as exampleRoutePath } from '~/plugins/example/index';
+import { register as rootRedirectRegister } from '~/plugins/root-redirect/index';
+
+blankUIPluginRegister(pluginConfig);
+examplePluginRegister(pluginConfig);
+rootRedirectRegister(pluginConfig, exampleRoutePath);
diff --git a/src/plugins/blank/index.tsx b/src/plugins/blank/index.tsx
new file mode 100644
index 00000000..416de42d
--- /dev/null
+++ b/src/plugins/blank/index.tsx
@@ -0,0 +1,14 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+// Example plugin.
+
+import { PluginConfig } from '~/common/plugintypes';
+
+export const register = (pluginConfig: PluginConfig) => {
+
+    pluginConfig.centerPanelList.push((elms) => []);
+
+    pluginConfig.sidePanelCategories.push((cats: string[]): string[] => []);
+};
diff --git a/src/plugins/example/index.tsx b/src/plugins/example/index.tsx
new file mode 100644
index 00000000..4fa98966
--- /dev/null
+++ b/src/plugins/example/index.tsx
@@ -0,0 +1,52 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+// Example plugin.
+
+import { PluginConfig } from '~/common/plugintypes';
+import * as React from 'react';
+import { Dispatch } from 'redux';
+import { RootState } from '~/store/store';
+import { push } from "react-router-redux";
+import { Typography } from "@material-ui/core";
+import { Route, matchPath } from "react-router";
+import { RootStore } from '~/store/store';
+import { activateSidePanelTreeItem } from '~/store/side-panel-tree/side-panel-tree-actions';
+import { setSidePanelBreadcrumbs } from '~/store/breadcrumbs/breadcrumbs-actions';
+
+const categoryName = "Plugin Example";
+export const routePath = "/examplePlugin";
+
+const ExamplePluginMainPanel = (props: {}) => {
+    return <Typography>
+        This is a example main panel plugin.
+    </Typography>;
+};
+
+export const register = (pluginConfig: PluginConfig) => {
+
+    pluginConfig.centerPanelList.push((elms) => {
+        elms.push(<Route path={routePath} component={ExamplePluginMainPanel} />);
+        return elms;
+    });
+
+    pluginConfig.navigateToHandlers.push((dispatch: Dispatch, getState: () => RootState, uuid: string) => {
+        if (uuid === categoryName) {
+            dispatch(push(routePath));
+            return true;
+        }
+        return false;
+    });
+
+    pluginConfig.sidePanelCategories.push((cats: string[]): string[] => { cats.push(categoryName); return cats; });
+
+    pluginConfig.locationChangeHandlers.push((store: RootStore, pathname: string): boolean => {
+        if (matchPath(pathname, { path: routePath, exact: true })) {
+            store.dispatch(activateSidePanelTreeItem(categoryName));
+            store.dispatch<any>(setSidePanelBreadcrumbs(categoryName));
+            return true;
+        }
+        return false;
+    });
+};
diff --git a/src/plugins/root-redirect/index.tsx b/src/plugins/root-redirect/index.tsx
new file mode 100644
index 00000000..13eeb942
--- /dev/null
+++ b/src/plugins/root-redirect/index.tsx
@@ -0,0 +1,20 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { PluginConfig } from '~/common/plugintypes';
+import { Dispatch } from 'redux';
+import { RootState } from '~/store/store';
+import { SidePanelTreeCategory } from '~/store/side-panel-tree/side-panel-tree-actions';
+import { push } from "react-router-redux";
+
+export const register = (pluginConfig: PluginConfig, redirect: string) => {
+
+    pluginConfig.navigateToHandlers.push((dispatch: Dispatch, getState: () => RootState, uuid: string) => {
+        if (uuid === SidePanelTreeCategory.PROJECTS) {
+            dispatch(push(redirect));
+            return true;
+        }
+        return false;
+    });
+};
diff --git a/src/routes/route-change-handlers.ts b/src/routes/route-change-handlers.ts
index 400ddc88..8a66e420 100644
--- a/src/routes/route-change-handlers.ts
+++ b/src/routes/route-change-handlers.ts
@@ -10,6 +10,7 @@ import { navigateToRootProject } from '~/store/navigation/navigation-action';
 import { dialogActions } from '~/store/dialog/dialog-actions';
 import { contextMenuActions } from '~/store/context-menu/context-menu-actions';
 import { searchBarActions } from '~/store/search-bar/search-bar-actions';
+import { pluginConfig } from '~/plugins';
 
 export const addRouteChangeHandlers = (history: History, store: RootStore) => {
     const handler = handleLocationChange(store);
@@ -53,6 +54,12 @@ const handleLocationChange = (store: RootStore) => ({ pathname }: Location) => {
     store.dispatch(contextMenuActions.CLOSE_CONTEXT_MENU());
     store.dispatch(searchBarActions.CLOSE_SEARCH_VIEW());
 
+    for (const locChangeFn of pluginConfig.locationChangeHandlers) {
+        if (locChangeFn(store, pathname)) {
+            return;
+        }
+    }
+
     if (projectMatch) {
         store.dispatch(WorkbenchActions.loadProject(projectMatch.params.id));
     } else if (collectionMatch) {
@@ -112,4 +119,4 @@ const handleLocationChange = (store: RootStore) => ({ pathname }: Location) => {
     } else if (allProcessesMatch) {
         store.dispatch(WorkbenchActions.loadAllProcesses());
     }
-};
\ No newline at end of file
+};
diff --git a/src/store/breadcrumbs/breadcrumbs-actions.ts b/src/store/breadcrumbs/breadcrumbs-actions.ts
index 8803cfba..b2857b69 100644
--- a/src/store/breadcrumbs/breadcrumbs-actions.ts
+++ b/src/store/breadcrumbs/breadcrumbs-actions.ts
@@ -65,7 +65,7 @@ export const setSharedWithMeBreadcrumbs = (uuid: string) =>
 export const setTrashBreadcrumbs = (uuid: string) =>
     setCategoryBreadcrumbs(uuid, SidePanelTreeCategory.TRASH);
 
-export const setCategoryBreadcrumbs = (uuid: string, category: SidePanelTreeCategory) =>
+export const setCategoryBreadcrumbs = (uuid: string, category: string) =>
     async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
         const ancestors = await services.ancestorsService.ancestors(uuid, '');
         dispatch(updateResources(ancestors));
diff --git a/src/store/navigation/navigation-action.ts b/src/store/navigation/navigation-action.ts
index d663ae37..7b55f897 100644
--- a/src/store/navigation/navigation-action.ts
+++ b/src/store/navigation/navigation-action.ts
@@ -10,9 +10,25 @@ import { Routes, getProcessLogUrl, getGroupUrl, getNavUrl } from '~/routes/route
 import { RootState } from '~/store/store';
 import { ServiceRepository } from '~/services/services';
 import { GROUPS_PANEL_LABEL } from '~/store/breadcrumbs/breadcrumbs-actions';
+import { pluginConfig } from '~/plugins';
+import { snackbarActions, SnackbarKind } from '~/store/snackbar/snackbar-actions';
+
+const navigationNotAvailable = (id: string) =>
+    snackbarActions.OPEN_SNACKBAR({
+        message: `${id} not available`,
+        hideDuration: 3000,
+        kind: SnackbarKind.ERROR
+    });
 
 export const navigateTo = (uuid: string) =>
     async (dispatch: Dispatch, getState: () => RootState) => {
+
+        for (const navToFn of pluginConfig.navigateToHandlers) {
+            if (navToFn(dispatch, getState, uuid)) {
+                return;
+            }
+        }
+
         const kind = extractUuidKind(uuid);
         switch (kind) {
             case ResourceKind.PROJECT:
@@ -27,6 +43,12 @@ export const navigateTo = (uuid: string) =>
         }
 
         switch (uuid) {
+            case SidePanelTreeCategory.PROJECTS:
+                const usr = getState().auth.user;
+                if (usr) {
+                    dispatch<any>(pushOrGoto(getNavUrl(usr.uuid, getState().auth)));
+                }
+                return;
             case SidePanelTreeCategory.FAVORITES:
                 dispatch<any>(navigateToFavorites);
                 return;
@@ -49,8 +71,11 @@ export const navigateTo = (uuid: string) =>
                 dispatch(navigateToAllProcesses);
                 return;
         }
+
+        dispatch(navigationNotAvailable(uuid));
     };
 
+
 export const navigateToNotFound = push(Routes.NO_MATCH);
 
 export const navigateToRoot = push(Routes.ROOT);
@@ -78,10 +103,7 @@ export const pushOrGoto = (url: string): AnyAction => {
 export const navigateToProcessLogs = compose(push, getProcessLogUrl);
 
 export const navigateToRootProject = (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
-    const usr = getState().auth.user;
-    if (usr) {
-        dispatch<any>(navigateTo(usr.uuid));
-    }
+    navigateTo(SidePanelTreeCategory.PROJECTS)(dispatch, getState);
 };
 
 export const navigateToSharedWithMe = push(Routes.SHARED_WITH_ME);
diff --git a/src/store/side-panel-tree/side-panel-tree-actions.ts b/src/store/side-panel-tree/side-panel-tree-actions.ts
index d0043da2..dd0f5e68 100644
--- a/src/store/side-panel-tree/side-panel-tree-actions.ts
+++ b/src/store/side-panel-tree/side-panel-tree-actions.ts
@@ -16,6 +16,8 @@ import { OrderBuilder } from '~/services/api/order-builder';
 import { ResourceKind } from '~/models/resource';
 import { GroupContentsResourcePrefix } from '~/services/groups-service/groups-service';
 import { GroupClass } from '~/models/group';
+import { CategoriesListReducer } from '~/common/plugintypes';
+import { pluginConfig } from '~/plugins';
 
 export enum SidePanelTreeCategory {
     PROJECTS = 'Projects',
@@ -44,19 +46,24 @@ export const getSidePanelTreeBranch = (uuid: string) => (treePicker: TreePicker)
     return [];
 };
 
-const SIDE_PANEL_CATEGORIES: string[] = [
+let SIDE_PANEL_CATEGORIES: string[] = [
     SidePanelTreeCategory.PROJECTS,
     SidePanelTreeCategory.SHARED_WITH_ME,
     SidePanelTreeCategory.PUBLIC_FAVORITES,
     SidePanelTreeCategory.FAVORITES,
     SidePanelTreeCategory.WORKFLOWS,
     SidePanelTreeCategory.ALL_PROCESSES,
-    SidePanelTreeCategory.TRASH,
-    "Blibber blubber"
+    SidePanelTreeCategory.TRASH
 ];
 
+const reduceCatsFn: (a: string[],
+    b: CategoriesListReducer) => string[] = (a, b) => b(a);
+
+SIDE_PANEL_CATEGORIES = pluginConfig.sidePanelCategories.reduce(reduceCatsFn, SIDE_PANEL_CATEGORIES);
+
 export const isSidePanelTreeCategory = (id: string) => SIDE_PANEL_CATEGORIES.some(category => category === id);
 
+
 export const initSidePanelTree = () =>
     (dispatch: Dispatch, getState: () => RootState, { authService }: ServiceRepository) => {
         const rootProjectUuid = getUserUuid(getState());
diff --git a/src/store/side-panel/side-panel-action.ts b/src/store/side-panel/side-panel-action.ts
index 6279aaea..28320f96 100644
--- a/src/store/side-panel/side-panel-action.ts
+++ b/src/store/side-panel/side-panel-action.ts
@@ -3,41 +3,9 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import { Dispatch } from 'redux';
-import { isSidePanelTreeCategory, SidePanelTreeCategory } from '~/store/side-panel-tree/side-panel-tree-actions';
-import { navigateToFavorites, navigateTo, navigateToTrash, navigateToSharedWithMe, navigateToWorkflows, navigateToPublicFavorites, navigateToAllProcesses } from '~/store/navigation/navigation-action';
-import {snackbarActions, SnackbarKind} from '~/store/snackbar/snackbar-actions';
+import { navigateTo } from '~/store/navigation/navigation-action';
 
 export const navigateFromSidePanel = (id: string) =>
     (dispatch: Dispatch) => {
-        if (isSidePanelTreeCategory(id)) {
-            dispatch<any>(getSidePanelTreeCategoryAction(id));
-        } else {
-            dispatch<any>(navigateTo(id));
-        }
+        dispatch<any>(navigateTo(id));
     };
-
-const getSidePanelTreeCategoryAction = (id: string) => {
-    switch (id) {
-        case SidePanelTreeCategory.FAVORITES:
-            return navigateToFavorites;
-        case SidePanelTreeCategory.PUBLIC_FAVORITES:
-            return navigateToPublicFavorites;
-        case SidePanelTreeCategory.TRASH:
-            return navigateToTrash;
-        case SidePanelTreeCategory.SHARED_WITH_ME:
-            return navigateToSharedWithMe;
-        case SidePanelTreeCategory.WORKFLOWS:
-            return navigateToWorkflows;
-        case SidePanelTreeCategory.ALL_PROCESSES:
-            return navigateToAllProcesses;
-        default:
-            return sidePanelTreeCategoryNotAvailable(id);
-    }
-};
-
-const sidePanelTreeCategoryNotAvailable = (id: string) =>
-    snackbarActions.OPEN_SNACKBAR({
-        message: `${id} not available`,
-        hideDuration: 3000,
-        kind: SnackbarKind.ERROR
-    });
diff --git a/src/store/workbench/workbench-actions.ts b/src/store/workbench/workbench-actions.ts
index 09aad2bd..217c8524 100644
--- a/src/store/workbench/workbench-actions.ts
+++ b/src/store/workbench/workbench-actions.ts
@@ -33,7 +33,7 @@ import {
     setSidePanelBreadcrumbs,
     setTrashBreadcrumbs
 } from '~/store/breadcrumbs/breadcrumbs-actions';
-import { navigateTo } from '~/store/navigation/navigation-action';
+import { navigateTo, navigateToRootProject } from '~/store/navigation/navigation-action';
 import { MoveToFormDialogData } from '~/store/move-to-dialog/move-to-dialog';
 import { ServiceRepository } from '~/services/services';
 import { getResource } from '~/store/resources/resources';
@@ -153,7 +153,7 @@ export const loadWorkbench = () =>
             if (router.location) {
                 const match = matchRootRoute(router.location.pathname);
                 if (match) {
-                    dispatch<any>(navigateTo(user.uuid));
+                    dispatch<any>(navigateToRootProject);
                 }
             }
         } else {
diff --git a/src/views/workbench/workbench.tsx b/src/views/workbench/workbench.tsx
index 8b943a75..f3872615 100644
--- a/src/views/workbench/workbench.tsx
+++ b/src/views/workbench/workbench.tsx
@@ -101,7 +101,8 @@ import { NotFoundPanel } from '../not-found-panel/not-found-panel';
 import { AutoLogout } from '~/views-components/auto-logout/auto-logout';
 import { RestoreCollectionVersionDialog } from '~/views-components/collections-dialog/restore-version-dialog';
 import { WebDavS3InfoDialog } from '~/views-components/webdav-s3-dialog/webdav-s3-dialog';
-import { pluginCenterPanelRoutes, RouteReducer, pluginDialogs } from '~/plugins';
+import { pluginConfig } from '~/plugins';
+import { RouteListReducer } from '~/common/plugintypes';
 
 type CssRules = 'root' | 'container' | 'splitter' | 'asidePanel' | 'contentWrapper' | 'content';
 
@@ -182,9 +183,9 @@ let routes = <>
 </>;
 
 const reduceRoutesFn: (a: React.ReactElement[],
-    b: RouteReducer) => React.ReactElement[] = (a, b) => b(a);
+    b: RouteListReducer) => React.ReactElement[] = (a, b) => b(a);
 
-routes = React.createElement(React.Fragment, null, pluginCenterPanelRoutes.reduce(reduceRoutesFn, React.Children.toArray(routes.props.children)));
+routes = React.createElement(React.Fragment, null, pluginConfig.centerPanelList.reduce(reduceRoutesFn, React.Children.toArray(routes.props.children)));
 
 export const WorkbenchPanel =
     withStyles(styles)((props: WorkbenchPanelProps) =>
@@ -274,6 +275,6 @@ export const WorkbenchPanel =
             <VirtualMachineAttributesDialog />
             <FedLogin />
             <WebDavS3InfoDialog />
-            {pluginDialogs}
+            {React.createElement(React.Fragment, null, pluginConfig.dialogs)}
         </Grid>
     );

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


hooks/post-receive
-- 




More information about the arvados-commits mailing list