17426: Can add plugin middlewares and context menus
authorPeter Amstutz <peter.amstutz@curii.com>
Thu, 11 Mar 2021 21:03:23 +0000 (16:03 -0500)
committerDaniel KutyƂa <daniel.kutyla@contractors.roche.com>
Fri, 23 Apr 2021 11:02:55 +0000 (13:02 +0200)
Arvados-DCO-1.1-Signed-off-by: Peter Amstutz <peter.amstutz@curii.com>

src/common/plugintypes.ts
src/models/link.ts
src/plugins.tsx
src/store/context-menu/context-menu-actions.ts
src/store/store.ts
src/views-components/form-fields/project-form-fields.tsx
src/views-components/project-properties-dialog/project-properties-dialog.tsx

index 00cc1e3643572a3c040dd08814dbc1ca1d6dd720..2ce0bb1256615a5e172625d498648ac5e5f8c7e7 100644 (file)
@@ -3,16 +3,18 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import * as React from 'react';
-import { Dispatch } from 'redux';
+import { Dispatch, Middleware } from 'redux';
 import { RootStore, RootState } from '~/store/store';
 import { ResourcesState } from '~/store/resources/resources';
 import { Location } from 'history';
+import { ServiceRepository } from "~/services/services";
 
 export type ElementListReducer = (startingList: React.ReactElement[], itemClass?: string) => 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 type EnableNew = (location: Location, currentItemId: string, currentUserUUID: string | undefined, resources: ResourcesState) => boolean;
+export type MiddlewareListReducer = (startingList: Middleware[], services: ServiceRepository) => Middleware[];
 
 export interface PluginConfig {
     // Customize the list of possible center panels by adding or removing Route components.
@@ -42,4 +44,6 @@ export interface PluginConfig {
     enableNewButtonMatchers: EnableNew[];
 
     newButtonMenuList: ElementListReducer[];
+
+    middlewares: MiddlewareListReducer[];
 }
index 785d531cf7d609fec3af696d16c3fbb9028753a4..1c82fe58502a3ae8fe1ac18ee6e1350bd895749b 100644 (file)
@@ -2,7 +2,6 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
-import { TagProperty } from "~/models/tag";
 import { Resource, ResourceKind } from '~/models/resource';
 
 export interface LinkResource extends Resource {
@@ -12,7 +11,7 @@ export interface LinkResource extends Resource {
     tailKind: string;
     linkClass: string;
     name: string;
-    properties: TagProperty;
+    properties: any;
     kind: ResourceKind.LINK;
 }
 
@@ -21,4 +20,4 @@ export enum LinkClass {
     TAG = 'tag',
     PERMISSION = 'permission',
     PRESET = 'preset',
-}
\ No newline at end of file
+}
index a7d033dd98bc718c32194c8c312d54cf67b627c4..1880e62de45580207c822b08e4c337478819da66 100644 (file)
@@ -15,15 +15,18 @@ export const pluginConfig: PluginConfig = {
     appBarRight: undefined,
     accountMenuList: [],
     enableNewButtonMatchers: [],
-    newButtonMenuList: []
+    newButtonMenuList: [],
+    middlewares: []
 };
 
 // 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 sampleTrackerPluginRegister } from '~/plugins/sample-tracker/index';
+import { studyListRoutePath } from '~/plugins/sample-tracker/studyList';
+// 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);
+sampleTrackerPluginRegister(pluginConfig);
+rootRedirectRegister(pluginConfig, studyListRoutePath);
index 83335f83c5aa938d2716f05705695ed7eabce358..1997b2a64894b3aba3baa7c3666ea394dd93614c 100644 (file)
@@ -34,7 +34,7 @@ export type ContextMenuResource = {
     ownerUuid: string;
     description?: string;
     kind: ResourceKind,
-    menuKind: ContextMenuKind;
+    menuKind: ContextMenuKind | string;
     isTrashed?: boolean;
     isEditable?: boolean;
     outputUuid?: string;
@@ -167,7 +167,7 @@ export const openProjectContextMenu = (event: React.MouseEvent<HTMLElement>, res
                 kind: res.kind,
                 menuKind,
                 ownerUuid: res.ownerUuid,
-                isTrashed: ('isTrashed' in res) ? res.isTrashed: false,
+                isTrashed: ('isTrashed' in res) ? res.isTrashed : false,
             }));
         }
     };
index 517368aa43badea0d26c3bd6dbb54701257e0572..f236d02948b617885e97dbea89b7ffa237026d16 100644 (file)
@@ -70,6 +70,8 @@ import { SubprocessMiddlewareService } from '~/store/subprocess-panel/subprocess
 import { SUBPROCESS_PANEL_ID } from '~/store/subprocess-panel/subprocess-panel-actions';
 import { ALL_PROCESSES_PANEL_ID } from './all-processes-panel/all-processes-panel-action';
 import { Config } from '~/common/config';
+import { pluginConfig } from '~/plugins';
+import { MiddlewareListReducer } from '~/common/plugintypes';
 
 const composeEnhancers =
     (process.env.NODE_ENV === 'development' &&
@@ -142,7 +144,7 @@ export function configureStore(history: History, services: ServiceRepository, co
         return next(action);
     };
 
-    const middlewares: Middleware[] = [
+    let middlewares: Middleware[] = [
         routerMiddleware(history),
         thunkMiddleware.withExtraArgument(services),
         authMiddleware(services),
@@ -164,6 +166,11 @@ export function configureStore(history: History, services: ServiceRepository, co
         subprocessMiddleware,
     ];
 
+    const reduceMiddlewaresFn: (a: Middleware[],
+        b: MiddlewareListReducer) => Middleware[] = (a, b) => b(a, services);
+
+    middlewares = pluginConfig.middlewares.reduce(reduceMiddlewaresFn, middlewares);
+
     const enhancer = composeEnhancers(applyMiddleware(redirectToMiddleware, ...middlewares));
     return createStore(rootReducer, enhancer);
 }
index dc1e1612aaa92112f9fa4a4f8d4cb7672358c7be..3f576ab180afe9e41547118b8fd7b5e5544776dd 100644 (file)
@@ -11,6 +11,7 @@ import { RootState } from "~/store/store";
 
 interface ProjectNameFieldProps {
     validate: Validator[];
+    label?: string;
 }
 
 // Validation behavior depends on the value of ForwardSlashNameSubstitution.
@@ -32,7 +33,7 @@ export const ProjectNameField = connect(
             name='name'
             component={TextField}
             validate={props.validate}
-            label="Project Name"
+            label={props.label || "Project Name"}
             autoFocus={true} /></span>
     );
 
index e1874d9548fe557f91bb7253a4efca839f6c064f..c2982b3d8b412fdc1867132e81e78229da47a531 100644 (file)
@@ -40,42 +40,42 @@ const mapDispatchToProps = (dispatch: Dispatch): ProjectPropertiesDialogActionPr
     handleDelete: (key: string, value: string) => () => dispatch<any>(deleteProjectProperty(key, value)),
 });
 
-type ProjectPropertiesDialogProps =  ProjectPropertiesDialogDataProps & ProjectPropertiesDialogActionProps & WithDialogProps<{}> & WithStyles<CssRules>;
+type ProjectPropertiesDialogProps = ProjectPropertiesDialogDataProps & ProjectPropertiesDialogActionProps & WithDialogProps<{}> & WithStyles<CssRules>;
 
 export const ProjectPropertiesDialog = connect(mapStateToProps, mapDispatchToProps)(
     withStyles(styles)(
-    withDialog(PROJECT_PROPERTIES_DIALOG_NAME)(
-        ({ classes, open, closeDialog, handleDelete, project }: ProjectPropertiesDialogProps) =>
-            <Dialog open={open}
-                onClose={closeDialog}
-                fullWidth
-                maxWidth='sm'>
-                <DialogTitle>Properties</DialogTitle>
-                <DialogContent>
-                    <ProjectPropertiesForm />
-                    {project && project.properties &&
-                        Object.keys(project.properties).map(k =>
-                            Array.isArray(project.properties[k])
-                            ? project.properties[k].map((v: string) =>
-                                getPropertyChip(
-                                    k, v,
-                                    handleDelete(k, v),
-                                    classes.tag))
-                            : getPropertyChip(
-                                k, project.properties[k],
-                                handleDelete(k, project.properties[k]),
-                                classes.tag)
-                        )
-                    }
-                </DialogContent>
-                <DialogActions>
-                    <Button
-                        variant='text'
-                        color='primary'
-                        onClick={closeDialog}>
-                        Close
+        withDialog(PROJECT_PROPERTIES_DIALOG_NAME)(
+            ({ classes, open, closeDialog, handleDelete, project }: ProjectPropertiesDialogProps) =>
+                <Dialog open={open}
+                    onClose={closeDialog}
+                    fullWidth
+                    maxWidth='sm'>
+                    <DialogTitle>Properties</DialogTitle>
+                    <DialogContent>
+                        <ProjectPropertiesForm />
+                        {project && project.properties &&
+                            Object.keys(project.properties).map(k =>
+                                Array.isArray(project.properties[k])
+                                    ? project.properties[k].map((v: string) =>
+                                        getPropertyChip(
+                                            k, v,
+                                            handleDelete(k, v),
+                                            classes.tag))
+                                    : getPropertyChip(
+                                        k, project.properties[k],
+                                        handleDelete(k, project.properties[k]),
+                                        classes.tag)
+                            )
+                        }
+                    </DialogContent>
+                    <DialogActions>
+                        <Button
+                            variant='text'
+                            color='primary'
+                            onClick={closeDialog}>
+                            Close
                     </Button>
-                </DialogActions>
-            </Dialog>
-    )
-));
\ No newline at end of file
+                    </DialogActions>
+                </Dialog>
+        )
+    ));