[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