workflow-file-selection
authorPawel Kowalczyk <pawel.kowalczyk@contractors.roche.com>
Fri, 28 Sep 2018 12:37:13 +0000 (14:37 +0200)
committerPawel Kowalczyk <pawel.kowalczyk@contractors.roche.com>
Fri, 28 Sep 2018 12:37:13 +0000 (14:37 +0200)
Feature #14231

Arvados-DCO-1.1-Signed-off-by: Pawel Kowalczyk <pawel.kowalczyk@contractors.roche.com>

13 files changed:
src/common/custom-theme.ts
src/routes/route-change-handlers.ts
src/routes/routes.ts
src/store/navigation/navigation-action.ts
src/store/run-process-panel/run-process-panel-actions.ts [new file with mode: 0644]
src/store/run-process-panel/run-process-panel-reducer.ts [new file with mode: 0644]
src/store/store.ts
src/store/workbench/workbench-actions.ts
src/views-components/side-panel-button/side-panel-button.tsx
src/views-components/workflow-tree-picker/workflow-tree-picker.tsx
src/views/run-process-panel/run-process-panel-root.tsx [new file with mode: 0644]
src/views/run-process-panel/run-process-panel.tsx [new file with mode: 0644]
src/views/workbench/workbench.tsx

index 8f3497947dc48f87dacdfeefe897383b76c4a2d6..98380b968e8f8b0a70d8f877a50cc48b8b968b67 100644 (file)
@@ -123,6 +123,16 @@ export const themeOptions: ArvadosThemeOptions = {
                     color: arvadosPurple
                 }
             }
+        },
+        MuiStepIcon: {
+            root: {
+                '&$active': {
+                    color: arvadosPurple
+                },
+                '&$completed': {
+                    color: 'inherited'
+                },
+            }
         }
     },
     mixins: {
index 97613147cc7087a491f6ba5682008141ff81ce96..af3bdab4b092ca61781747eb24a91733fdd20dd0 100644 (file)
@@ -4,10 +4,10 @@
 
 import { History, Location } from 'history';
 import { RootStore } from '~/store/store';
-import { matchProcessRoute, matchProcessLogRoute, matchProjectRoute, matchCollectionRoute, matchFavoritesRoute, matchTrashRoute, matchRootRoute, matchSharedWithMeRoute, matchWorkflowRoute } from './routes';
+import { matchProcessRoute, matchProcessLogRoute, matchProjectRoute, matchCollectionRoute, matchFavoritesRoute, matchTrashRoute, matchRootRoute, matchSharedWithMeRoute, matchRunProcessRoute, matchWorkflowRoute } from './routes';
 import { loadProject, loadCollection, loadFavorites, loadTrash, loadProcess, loadProcessLog } from '~/store/workbench/workbench-actions';
 import { navigateToRootProject } from '~/store/navigation/navigation-action';
-import { loadSharedWithMe, loadWorkflow } from '~/store/workbench/workbench-actions';
+import { loadSharedWithMe, loadRunProcess, loadWorkflow } from '../store/workbench/workbench-actions';
 
 export const addRouteChangeHandlers = (history: History, store: RootStore) => {
     const handler = handleLocationChange(store);
@@ -24,6 +24,7 @@ const handleLocationChange = (store: RootStore) => ({ pathname }: Location) => {
     const processMatch = matchProcessRoute(pathname);
     const processLogMatch = matchProcessLogRoute(pathname);
     const sharedWithMeMatch = matchSharedWithMeRoute(pathname);
+    const runProcessMatch = matchRunProcessRoute(pathname);
     const workflowMatch = matchWorkflowRoute(pathname);
 
     if (projectMatch) {
@@ -42,6 +43,8 @@ const handleLocationChange = (store: RootStore) => ({ pathname }: Location) => {
         store.dispatch(navigateToRootProject);
     } else if (sharedWithMeMatch) {
         store.dispatch(loadSharedWithMe);
+    } else if (runProcessMatch) {
+        store.dispatch(loadRunProcess);
     } else if (workflowMatch) {
         store.dispatch(loadWorkflow);
     }
index 34b15e1132f18cd35c6ec24b04c299ae16cf8925..432cf750cb2f28f24e75d1986fe8064503f3b21e 100644 (file)
@@ -17,6 +17,7 @@ export const Routes = {
     TRASH: '/trash',
     PROCESS_LOGS: `/process-logs/:id(${RESOURCE_UUID_PATTERN})`,
     SHARED_WITH_ME: '/shared-with-me',
+    RUN_PROCESS: '/run-process',
     WORKFLOWS: '/workflows'
 };
 
@@ -66,5 +67,8 @@ export const matchProcessLogRoute = (route: string) =>
 export const matchSharedWithMeRoute = (route: string) =>
     matchPath(route, { path: Routes.SHARED_WITH_ME });
 
+export const matchRunProcessRoute = (route: string) =>
+    matchPath(route, { path: Routes.RUN_PROCESS });
+    
 export const matchWorkflowRoute = (route: string) =>
     matchPath<ResourceRouteParams>(route, { path: Routes.WORKFLOWS });
index c8a554c7651ea5d5f955ab11bb91051ee96626b2..48bd606e41b146d1f73849d27e3957a84ab9fee8 100644 (file)
@@ -55,3 +55,5 @@ export const navigateToRootProject = (dispatch: Dispatch, getState: () => RootSt
 };
 
 export const navigateToSharedWithMe = push(Routes.SHARED_WITH_ME);
+
+export const navigateToRunProcess = push(Routes.RUN_PROCESS);
\ No newline at end of file
diff --git a/src/store/run-process-panel/run-process-panel-actions.ts b/src/store/run-process-panel/run-process-panel-actions.ts
new file mode 100644 (file)
index 0000000..299f387
--- /dev/null
@@ -0,0 +1,21 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { Dispatch } from 'redux';
+import { unionize, ofType, UnionOf } from "~/common/unionize";
+import { ServiceRepository } from "~/services/services";
+import { RootState } from '~/store/store';
+
+export const runProcessPanelActions = unionize({
+    SET_CURRENT_STEP: ofType<number>()
+});
+
+export type RunProcessPanelAction = UnionOf<typeof runProcessPanelActions>;
+
+export const loadRunProcessPanel = () =>
+    (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+        
+    };
+
+export const goToStep = (step: number) => runProcessPanelActions.SET_CURRENT_STEP(step);
\ No newline at end of file
diff --git a/src/store/run-process-panel/run-process-panel-reducer.ts b/src/store/run-process-panel/run-process-panel-reducer.ts
new file mode 100644 (file)
index 0000000..a288ae7
--- /dev/null
@@ -0,0 +1,19 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { RunProcessPanelAction, runProcessPanelActions } from '~/store/run-process-panel/run-process-panel-actions';
+
+interface RunProcessPanel {
+    currentStep: number;
+}
+
+const initialState: RunProcessPanel = {
+    currentStep: 0
+};
+
+export const runProcessPanelReducer = (state = initialState, action: RunProcessPanelAction): RunProcessPanel =>
+    runProcessPanelActions.match(action, {
+        SET_CURRENT_STEP: currentStep => ({ ...state, currentStep }),
+        default: () => state
+    });
\ No newline at end of file
index ab4b851dcdfa5f5b477e8144d347872086a603ff..c0e0b6b2de3ae30022efb9a28e255c2ec6669ebd 100644 (file)
@@ -35,6 +35,7 @@ import { processPanelReducer } from '~/store/process-panel/process-panel-reducer
 import { SHARED_WITH_ME_PANEL_ID } from '~/store/shared-with-me-panel/shared-with-me-panel-actions';
 import { SharedWithMeMiddlewareService } from './shared-with-me-panel/shared-with-me-middleware-service';
 import { progressIndicatorReducer } from './progress-indicator/progress-indicator-reducer';
+import { runProcessPanelReducer } from '~/store/run-process-panel/run-process-panel-reducer';
 import { WorkflowMiddlewareService } from './workflow-panel/workflow-middleware-service';
 import { WORKFLOW_PANEL_ID } from './workflow-panel/workflow-panel-actions';
 import { workflowTreePickerReducer } from './workflow-tree-picker/workflow-tree-picker-reducer';
@@ -99,5 +100,6 @@ const createRootReducer = (services: ServiceRepository) => combineReducers({
     fileUploader: fileUploaderReducer,
     processPanel: processPanelReducer,
     progressIndicator: progressIndicatorReducer,
-    workflowTreePicker: workflowTreePickerReducer
+    workflowTreePicker: workflowTreePickerReducer,
+    runProcessPanel: runProcessPanelReducer
 });
index 8f034ec0383b4f61cb4e8b5b98ebe688e9da0e4f..bd480bda99e10c05925da4ef5de07c6fc6520d9c 100644 (file)
@@ -46,6 +46,8 @@ import { ResourceKind, extractUuidKind } from '~/models/resource';
 import { FilterBuilder } from '~/services/api/filter-builder';
 import { GroupContentsResource } from '~/services/groups-service/groups-service';
 import { unionize, ofType, UnionOf, MatchCases } from '~/common/unionize';
+import { loadRunProcessPanel } from '~/store/run-process-panel/run-process-panel-actions';
+import { loadCollectionFiles } from '~/store/collection-panel/collection-panel-files/collection-panel-files-actions';
 
 export const WORKBENCH_LOADING_SCREEN = 'workbenchLoadingScreen';
 
@@ -197,16 +199,19 @@ export const loadCollection = (uuid: string) =>
                         dispatch(updateResources([collection]));
                         await dispatch(activateSidePanelTreeItem(collection.ownerUuid));
                         dispatch(setSidePanelBreadcrumbs(collection.ownerUuid));
+                        dispatch(loadCollectionFiles(collection.uuid));
                     },
                     SHARED: collection => {
                         dispatch(updateResources([collection]));
                         dispatch<any>(setSharedWithMeBreadcrumbs(collection.ownerUuid));
                         dispatch(activateSidePanelTreeItem(SidePanelTreeCategory.SHARED_WITH_ME));
+                        dispatch(loadCollectionFiles(collection.uuid));
                     },
                     TRASHED: collection => {
                         dispatch(updateResources([collection]));
                         dispatch(setTrashBreadcrumbs(''));
                         dispatch(activateSidePanelTreeItem(SidePanelTreeCategory.TRASH));
+                        dispatch(loadCollectionFiles(collection.uuid));
                     },
 
                 });
@@ -350,11 +355,18 @@ export const loadSharedWithMe = handleFirstTimeLoad(async (dispatch: Dispatch) =
     await dispatch<any>(setSidePanelBreadcrumbs(SidePanelTreeCategory.SHARED_WITH_ME));
 });
 
+export const loadRunProcess = handleFirstTimeLoad(
+    async (dispatch: Dispatch) => {
+        dispatch<any>(loadRunProcessPanel());
+    }
+);
+
 export const loadWorkflow = handleFirstTimeLoad(async (dispatch: Dispatch<any>) => {
     dispatch(activateSidePanelTreeItem(SidePanelTreeCategory.WORKFLOWS));
     await dispatch(loadWorkflowPanel());
     dispatch(setSidePanelBreadcrumbs(SidePanelTreeCategory.WORKFLOWS));
 });
+
 const finishLoadingProject = (project: GroupContentsResource | string) =>
     async (dispatch: Dispatch<any>) => {
         const uuid = typeof project === 'string' ? project : project.uuid;
index 89c3400b55cb00bb14bb93901d74e0b7b93a89c7..2e8433a9ddb074dedb405ba205abbadddb078803 100644 (file)
@@ -14,6 +14,7 @@ import { AddIcon, CollectionIcon, ProcessIcon, ProjectIcon } from '~/components/
 import { openProjectCreateDialog } from '~/store/projects/project-create-actions';
 import { openCollectionCreateDialog } from '~/store/collections/collection-create-actions';
 import { matchProjectRoute } from '~/routes/routes';
+import { navigateToRunProcess } from '~/store/navigation/navigation-action';
 
 type CssRules = 'button' | 'menuItem' | 'icon';
 
@@ -88,7 +89,7 @@ export const SidePanelButton = withStyles(styles)(
                                 <MenuItem className={classes.menuItem} onClick={this.handleNewCollectionClick}>
                                     <CollectionIcon className={classes.icon} /> New collection
                                 </MenuItem>
-                                <MenuItem className={classes.menuItem}>
+                                <MenuItem className={classes.menuItem} onClick={this.handleRunProcessClick}>
                                     <ProcessIcon className={classes.icon} /> Run a process
                                 </MenuItem>
                                 <MenuItem className={classes.menuItem} onClick={this.handleNewProjectClick}>
@@ -104,6 +105,10 @@ export const SidePanelButton = withStyles(styles)(
                 this.props.dispatch<any>(openProjectCreateDialog(this.props.currentItemId));
             }
 
+            handleRunProcessClick = () => {
+                this.props.dispatch<any>(navigateToRunProcess);
+            }
+
             handleNewCollectionClick = () => {
                 this.props.dispatch<any>(openCollectionCreateDialog(this.props.currentItemId));
             }
index 1dd8c107abd45c12a195dd35bcc5d99fdefb2463..f9db4eaaca3fc19cafd9999cf8ac5f497a113b15 100644 (file)
@@ -11,12 +11,14 @@ import { TreeItem, TreeItemStatus } from "~/components/tree/tree";
 import { ProjectResource } from "~/models/project";
 import { workflowTreePickerActions } from "~/store/workflow-tree-picker/workflow-tree-picker-actions";
 import { ListItemTextIcon } from "~/components/list-item-text-icon/list-item-text-icon";
-import { ProjectIcon, FavoriteIcon, ProjectsIcon, ShareMeIcon } from "~/components/icon/icon";
+import { ProjectIcon, FavoriteIcon, ProjectsIcon, ShareMeIcon, CollectionIcon } from '~/components/icon/icon';
 import { createTreePickerNode } from "~/store/tree-picker/tree-picker";
 import { RootState } from "~/store/store";
 import { ServiceRepository } from "~/services/services";
 import { FilterBuilder } from "~/services/api/filter-builder";
 import { WrappedFieldProps } from 'redux-form';
+import { ResourceKind } from '~/models/resource';
+import { GroupContentsResource } from '~/services/groups-service/groups-service';
 
 type WorkflowTreePickerProps = Pick<MainWorkflowTreePickerProps, 'onContextMenu' | 'toggleItemActive' | 'toggleItemOpen'>;
 
@@ -38,9 +40,9 @@ const toggleItemOpen = (nodeId: string, status: TreeItemStatus, pickerId: string
     (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
         if (status === TreeItemStatus.INITIAL) {
             if (pickerId === TreePickerId.PROJECTS) {
-                dispatch<any>(loadProjectTreePickerProjects(nodeId));
+                dispatch<any>(loadProjectTreePicker(nodeId));
             } else if (pickerId === TreePickerId.FAVORITES) {
-                dispatch<any>(loadFavoriteTreePickerProjects(nodeId === services.authService.getUuid() ? '' : nodeId));
+                dispatch<any>(loadFavoriteTreePicker(nodeId === services.authService.getUuid() ? '' : nodeId));
             } else {
                 // TODO: load sharedWithMe
             }
@@ -71,22 +73,23 @@ export const WorkflowTreePicker = connect(undefined, mapDispatchToProps)((props:
         </div>
     </div>);
 
-export const loadProjectTreePickerProjects = (nodeId: string) =>
+export const loadProjectTreePicker = (nodeId: string) =>
     async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
         dispatch(workflowTreePickerActions.LOAD_TREE_PICKER_NODE({ nodeId, pickerId: TreePickerId.PROJECTS }));
 
         const ownerUuid = nodeId.length === 0 ? services.authService.getUuid() || '' : nodeId;
 
         const filters = new FilterBuilder()
+            .addIsA("uuid", [ResourceKind.PROJECT, ResourceKind.COLLECTION])
             .addEqual('ownerUuid', ownerUuid)
             .getFilters();
 
-        const { items } = await services.projectService.list({ filters });
+        const { items } = await services.groupsService.contents(ownerUuid, { filters });
 
         dispatch<any>(receiveTreePickerData(nodeId, items, TreePickerId.PROJECTS));
     };
 
-export const loadFavoriteTreePickerProjects = (nodeId: string) =>
+export const loadFavoriteTreePicker = (nodeId: string) =>
     async (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
         const parentId = services.authService.getUuid() || '';
 
@@ -101,14 +104,14 @@ export const loadFavoriteTreePickerProjects = (nodeId: string) =>
                 .addEqual('ownerUuid', nodeId)
                 .getFilters();
 
-            const { items } = await services.projectService.list({ filters });
+            const { items } = await services.groupsService.contents(parentId, { filters });
 
             dispatch<any>(receiveTreePickerData(nodeId, items, TreePickerId.FAVORITES));
         }
-
     };
 
 const getProjectPickerIcon = (item: TreeItem<ProjectResource>) => {
+    console.log(item);
     switch (item.data.name) {
         case TreePickerId.FAVORITES:
             return FavoriteIcon;
@@ -116,6 +119,17 @@ const getProjectPickerIcon = (item: TreeItem<ProjectResource>) => {
             return ProjectsIcon;
         case TreePickerId.SHARED_WITH_ME:
             return ShareMeIcon;
+        default:
+            return getResourceIcon(item);
+    }
+};
+
+const getResourceIcon = (item: TreeItem<GroupContentsResource>) => {
+    switch (item.data.kind) {
+        case ResourceKind.COLLECTION:
+            return CollectionIcon;
+        case ResourceKind.PROJECT:
+            return ProjectsIcon;
         default:
             return ProjectIcon;
     }
@@ -129,11 +143,11 @@ const renderTreeItem = (item: TreeItem<ProjectResource>) =>
         hasMargin={true} />;
 
 
-export const receiveTreePickerData = (nodeId: string, projects: ProjectResource[], pickerId: string) =>
+export const receiveTreePickerData = (nodeId: string, items: GroupContentsResource[], pickerId: string) =>
     (dispatch: Dispatch) => {
         dispatch(workflowTreePickerActions.LOAD_TREE_PICKER_NODE_SUCCESS({
             nodeId,
-            nodes: projects.map(project => createTreePickerNode({ nodeId: project.uuid, value: project })),
+            nodes: items.map(item => createTreePickerNode({ nodeId: item.uuid, value: item })),
             pickerId,
         }));
 
diff --git a/src/views/run-process-panel/run-process-panel-root.tsx b/src/views/run-process-panel/run-process-panel-root.tsx
new file mode 100644 (file)
index 0000000..61a0fc0
--- /dev/null
@@ -0,0 +1,39 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import * as React from 'react';
+import { Stepper, Step, StepLabel, StepContent, Button } from '@material-ui/core';
+
+export interface RunProcessPanelRootDataProps {
+    currentStep: number;
+}
+
+export interface RunProcessPanelRootActionProps {
+    onSetStep: (step: number) => void;
+}
+
+type RunProcessPanelRootProps = RunProcessPanelRootDataProps & RunProcessPanelRootActionProps;
+
+export const RunProcessPanelRoot = ({ currentStep, onSetStep, ...props }: RunProcessPanelRootProps) =>
+    <Stepper activeStep={currentStep} orientation="vertical" elevation={2}>
+        <Step>
+            <StepLabel>Choose a workflow</StepLabel>
+            <StepContent>
+                <Button variant="contained" color="primary" onClick={() => onSetStep(1)}>
+                    Next
+                </Button>
+            </StepContent>
+        </Step>
+        <Step>
+            <StepLabel>Select inputs</StepLabel>
+            <StepContent>
+                <Button color="primary" onClick={() => onSetStep(0)}>
+                    Back
+                </Button>
+                <Button variant="contained" color="primary">
+                    Run Process
+                </Button>
+            </StepContent>
+        </Step>
+    </Stepper>;
\ No newline at end of file
diff --git a/src/views/run-process-panel/run-process-panel.tsx b/src/views/run-process-panel/run-process-panel.tsx
new file mode 100644 (file)
index 0000000..725d705
--- /dev/null
@@ -0,0 +1,23 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import { Dispatch } from 'redux';
+import { connect } from 'react-redux';
+import { RootState } from '~/store/store';
+import { RunProcessPanelRootDataProps, RunProcessPanelRootActionProps, RunProcessPanelRoot } from '~/views/run-process-panel/run-process-panel-root';
+import { goToStep } from '~/store/run-process-panel/run-process-panel-actions';
+
+const mapStateToProps = ({ runProcessPanel }: RootState): RunProcessPanelRootDataProps => {
+   return {
+       currentStep: runProcessPanel.currentStep
+   };
+};
+
+const mapDispatchToProps = (dispatch: Dispatch): RunProcessPanelRootActionProps => ({
+    onSetStep: (step: number) => {
+        dispatch<any>(goToStep(step));
+    }
+});
+
+export const RunProcessPanel = connect(mapStateToProps, mapDispatchToProps)(RunProcessPanelRoot);
\ No newline at end of file
index 776850ceb8950ffe89b2fa7f1e7fc58414dce685..47e22541508dfb7d7650006096c20a4138472d25 100644 (file)
@@ -33,12 +33,13 @@ import { MoveProjectDialog } from '~/views-components/dialog-forms/move-project-
 import { MoveCollectionDialog } from '~/views-components/dialog-forms/move-collection-dialog';
 import { FilesUploadCollectionDialog } from '~/views-components/dialog-forms/files-upload-collection-dialog';
 import { PartialCopyCollectionDialog } from '~/views-components/dialog-forms/partial-copy-collection-dialog';
-import { TrashPanel } from "~/views/trash-panel/trash-panel";
+import { ProcessCommandDialog } from '~/views-components/process-command-dialog/process-command-dialog';
 import { MainContentBar } from '~/views-components/main-content-bar/main-content-bar';
 import { Grid } from '@material-ui/core';
-import { SharedWithMePanel } from '../shared-with-me-panel/shared-with-me-panel';
+import { TrashPanel } from "~/views/trash-panel/trash-panel";
+import { SharedWithMePanel } from '~/views/shared-with-me-panel/shared-with-me-panel';
+import { RunProcessPanel } from '~/views/run-process-panel/run-process-panel';
 import SplitterLayout from 'react-splitter-layout';
-import { ProcessCommandDialog } from '~/views-components/process-command-dialog/process-command-dialog';
 import { WorkflowPanel } from '~/views/workflow-panel/workflow-panel';
 
 type CssRules = 'root' | 'container' | 'splitter' | 'asidePanel' | 'contentWrapper' | 'content';
@@ -95,6 +96,7 @@ export const WorkbenchPanel =
                                 <Route path={Routes.TRASH} component={TrashPanel} />
                                 <Route path={Routes.PROCESS_LOGS} component={ProcessLogPanel} />
                                 <Route path={Routes.SHARED_WITH_ME} component={SharedWithMePanel} />
+                                <Route path={Routes.RUN_PROCESS} component={RunProcessPanel} />
                                 <Route path={Routes.WORKFLOWS} component={WorkflowPanel} />
                             </Switch>
                         </Grid>