Added data selector for workbench data explorer
authorDaniel Kos <daniel.kos@contractors.roche.com>
Fri, 22 Jun 2018 12:11:16 +0000 (14:11 +0200)
committerDaniel Kos <daniel.kos@contractors.roche.com>
Sun, 24 Jun 2018 22:43:47 +0000 (00:43 +0200)
Feature #13666

Arvados-DCO-1.1-Signed-off-by: Daniel Kos <daniel.kos@contractors.roche.com>

src/index.tsx
src/models/resource.ts
src/store/project/project-reducer.ts
src/store/store.ts
src/views-components/data-explorer/data-explorer.tsx
src/views-components/data-explorer/data-item.ts
src/views/data-explorer/data-explorer-selectors.ts
src/views/data-explorer/data-explorer.tsx
src/views/workbench/workbench.tsx
tslint.json

index cf1610f83226cd12a279b4a28db1f3494ee740a7..28f8300dbdeb526050d75a90867b6f15282d354b 100644 (file)
@@ -21,6 +21,8 @@ const history = createBrowserHistory();
 const store = configureStore({
     projects: [
     ],
+    collections: [
+    ],
     router: {
         location: null
     },
index 39b4e915cad26f9b4919e38a8c25aadd6e077512..4c198fb8cac56548a5d2ba4357c0ef271af2fcad 100644 (file)
@@ -7,3 +7,21 @@ export interface Resource {
     href: string;
     kind: string;
 }
+
+export enum ResourceKind {
+    PROJECT = "project",
+    COLLECTION = "collection",
+    PIPELINE = "pipeline",
+    LEVEL_UP = "levelup",
+    UNKNOWN = "unknown"
+}
+
+export function getResourceKind(itemKind: string) {
+    switch (itemKind) {
+        case "arvados#project": return ResourceKind.PROJECT;
+        case "arvados#collection": return ResourceKind.COLLECTION;
+        case "arvados#pipeline": return ResourceKind.PIPELINE;
+        default:
+            return ResourceKind.UNKNOWN;
+    }
+}
index 4f7545fc979ea93b9fbe4fd6ee2f4e74559e6a87..2c74eb239d1774d20937c6b44745cf526a74f6c6 100644 (file)
@@ -22,8 +22,21 @@ export function findTreeItem<T>(tree: Array<TreeItem<T>>, itemId: string): TreeI
     return item;
 }
 
+export function getActiveTreeItem<T>(tree: Array<TreeItem<T>>): TreeItem<T> | undefined {
+    let item;
+    for (const t of tree) {
+        item = t.active
+            ? t
+            : getActiveTreeItem(t.items ? t.items : []);
+        if (item) {
+            break;
+        }
+    }
+    return item;
+}
+
 export function getTreePath<T>(tree: Array<TreeItem<T>>, itemId: string): Array<TreeItem<T>> {
-    for(const item of tree){
+    for (const item of tree){
         if(item.id === itemId){
             return [item];
         } else {
index 6b9c31ff4ee3bfca4426d3364b8440625c4d6e19..6053e0378a28864e05f1f270e82a425c30a95061 100644 (file)
@@ -8,7 +8,7 @@ import thunkMiddleware from 'redux-thunk';
 import { History } from "history";
 import projectsReducer, { ProjectState } from "./project/project-reducer";
 import authReducer, { AuthState } from "./auth/auth-reducer";
-import collectionsReducer from "./collection/collection-reducer";
+import collectionsReducer, { CollectionState } from "./collection/collection-reducer";
 
 const composeEnhancers =
     (process.env.NODE_ENV === 'development' &&
@@ -18,6 +18,7 @@ const composeEnhancers =
 export interface RootState {
     auth: AuthState;
     projects: ProjectState;
+    collections: CollectionState;
     router: RouterState;
 }
 
index c4de01a35be7eafc5f6452e6715fbc2cfb47f6b3..4ba0f87ccf67b522f4e9a4176f82828a9fd15ca6 100644 (file)
@@ -3,17 +3,17 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import * as React from 'react';
-import { Typography, Grid, Paper, Toolbar } from '@material-ui/core';
+import { Grid, Paper, Toolbar, Typography } from '@material-ui/core';
 import IconButton from '@material-ui/core/IconButton';
 import MoreVertIcon from "@material-ui/icons/MoreVert";
-import { formatFileSize, formatDate } from '../../common/formatters';
+import { formatDate, formatFileSize } from '../../common/formatters';
 import { DataItem } from './data-item';
-import { DataColumns } from "../../components/data-table/data-table";
+import DataTable, { DataColumns } from "../../components/data-table/data-table";
 import ContextMenu from "../../components/context-menu/context-menu";
 import ColumnSelector from "../../components/column-selector/column-selector";
-import DataTable from "../../components/data-table/data-table";
 import { mockAnchorFromMouseEvent } from "../../components/popover/helpers";
 import { DataColumn } from "../../components/data-table/data-column";
+import { ResourceKind } from "../../models/resource";
 
 export interface DataExplorerContextActions {
     onAddToFavourite: (dataIitem: DataItem) => void;
@@ -191,10 +191,12 @@ class DataExplorer extends React.Component<DataExplorerProps, DataExplorerState>
 
 const renderIcon = (dataItem: DataItem) => {
     switch (dataItem.type) {
-        case "arvados#group":
-            return <i className="fas fa-folder fa-lg" />;
-        case "arvados#groupList":
-            return <i className="fas fa-th fa-lg" />;
+        case ResourceKind.LEVEL_UP:
+            return <i className="icon-level-up" style={{fontSize: "1rem"}}/>;
+        case ResourceKind.PROJECT:
+            return <i className="fas fa-folder fa-lg"/>;
+        case ResourceKind.COLLECTION:
+            return <i className="fas fa-th fa-lg"/>;
         default:
             return <i />;
     }
index 65e8f632fa22da90c6f00fe3e9af2a0652ff5fe6..ebff77d804b5f938d4e2b8c73927d7022fa5ec51 100644 (file)
@@ -2,12 +2,26 @@
 //
 // SPDX-License-Identifier: AGPL-3.0
 
+import { getResourceKind, Resource, ResourceKind } from "../../models/resource";
+
 export interface DataItem {
     uuid: string;
     name: string;
-    type: string;
+    type: ResourceKind;
+    url: string;
     owner: string;
     lastModified: string;
     fileSize?: number;
     status?: string;
 }
+
+function resourceToDataItem(r: Resource, kind?: ResourceKind) {
+    return {
+        uuid: r.uuid,
+        name: r.name,
+        type: kind ? kind : getResourceKind(r.kind),
+        owner: r.ownerUuid,
+        lastModified: r.modifiedAt
+    };
+}
+
index 5f17037f2322fa1a514c8d33f302fb8ba77141e8..73881fad2332c71201995ccb3d4af8d55c1ba873 100644 (file)
@@ -1,11 +1,54 @@
 import { TreeItem } from "../../components/tree/tree";
 import { Project } from "../../models/project";
 import { DataItem } from "../../views-components/data-explorer/data-item";
+import { findTreeItem } from "../../store/project/project-reducer";
+import { ResourceKind } from "../../models/resource";
+import { Collection } from "../../models/collection";
+
+
+export const projectExplorerItems = (projects: Array<TreeItem<Project>>, treeItemId: string, collections: Array<Collection>): DataItem[] => {
+    const dataItems: DataItem[] = [];
+
+    const treeItem = findTreeItem(projects, treeItemId);
+    if (treeItem) {
+        dataItems.push({
+            name: "..",
+            url: `/projects/${treeItem.data.ownerUuid}`,
+            type: ResourceKind.LEVEL_UP,
+            owner: treeItem.data.ownerUuid,
+            uuid: treeItem.data.uuid,
+            lastModified: treeItem.data.modifiedAt
+        });
+
+        if (treeItem.items) {
+            treeItem.items.forEach(p => {
+                const item = {
+                    name: p.data.name,
+                    type: ResourceKind.PROJECT,
+                    url: `/projects/${treeItem.data.uuid}`,
+                    owner: p.data.ownerUuid,
+                    uuid: p.data.uuid,
+                    lastModified: p.data.modifiedAt
+                } as DataItem;
+
+                dataItems.push(item);
+            });
+        }
+    }
+
+    collections.forEach(c => {
+        const item = {
+            name: c.name,
+            type: ResourceKind.COLLECTION,
+            url: `/collections/${c.uuid}`,
+            owner: c.ownerUuid,
+            uuid: c.uuid,
+            lastModified: c.modifiedAt
+        } as DataItem;
+
+        dataItems.push(item);
+    });
+
+    return dataItems;
+};
 
-export const mapProjectTreeItem = (item: TreeItem<Project>): DataItem => ({
-    name: item.data.name,
-    type: item.data.kind,
-    owner: item.data.ownerUuid,
-    lastModified: item.data.modifiedAt,
-    uuid: item.data.uuid
-});
index f4ee36f3b4af2542add0564b6405a075d1e38eaf..a667469a2e5317678ea31909132d0aee68b2f64a 100644 (file)
@@ -5,32 +5,35 @@
 import * as React from 'react';
 import { RouteComponentProps } from 'react-router';
 import { Project } from '../../models/project';
-import { ProjectState, findTreeItem } from '../../store/project/project-reducer';
+import { ProjectState } from '../../store/project/project-reducer';
 import { RootState } from '../../store/store';
 import { connect, DispatchProp } from 'react-redux';
 import { push } from 'react-router-redux';
-import projectActions from "../../store/project/project-action";
 import { DataColumns } from "../../components/data-table/data-table";
 import DataExplorer, { DataExplorerContextActions } from "../../views-components/data-explorer/data-explorer";
-import { mapProjectTreeItem } from "./data-explorer-selectors";
+import { projectExplorerItems } from "./data-explorer-selectors";
 import { DataItem } from "../../views-components/data-explorer/data-item";
+import { CollectionState } from "../../store/collection/collection-reducer";
+import { ResourceKind } from "../../models/resource";
+import projectActions from "../../store/project/project-action";
+import { getCollectionList } from "../../store/collection/collection-action";
 
 interface DataExplorerViewDataProps {
     projects: ProjectState;
+    collections: CollectionState;
 }
 
-type DataExplorerViewProps = DataExplorerViewDataProps & RouteComponentProps<{ name: string }> & DispatchProp;
+type DataExplorerViewProps = DataExplorerViewDataProps & RouteComponentProps<{ uuid: string }> & DispatchProp;
 type DataExplorerViewState = DataColumns<Project>;
 
 class DataExplorerView extends React.Component<DataExplorerViewProps, DataExplorerViewState> {
-
     render() {
-        const project = findTreeItem(this.props.projects, this.props.match.params.name);
-        const projectItems = project && project.items || [];
+        const treeItemId = this.props.match.params.uuid;
+        const items = projectExplorerItems(this.props.projects, treeItemId, this.props.collections);
         return (
             <DataExplorer
-                items={projectItems.map(mapProjectTreeItem)}
-                onItemClick={this.goToProject}
+                items={items}
+                onItemClick={this.goToItem}
                 contextActions={this.contextActions}
             />
         );
@@ -46,14 +49,19 @@ class DataExplorerView extends React.Component<DataExplorerViewProps, DataExplor
         onShare: console.log
     };
 
-    goToProject = (item: DataItem) => {
-        this.props.dispatch(push(`/project/${item}`));
-        this.props.dispatch(projectActions.TOGGLE_PROJECT_TREE_ITEM(item.uuid));
+    goToItem = (item: DataItem) => {
+        // FIXME: Unify project tree switch action
+        this.props.dispatch(push(item.url));
+        if (item.type === ResourceKind.PROJECT || item.type === ResourceKind.LEVEL_UP) {
+            this.props.dispatch(projectActions.TOGGLE_PROJECT_TREE_ITEM(item.uuid));
+        }
+        this.props.dispatch<any>(getCollectionList(item.uuid));
     }
 }
 
 export default connect(
     (state: RootState) => ({
-        projects: state.projects
+        projects: state.projects,
+        collections: state.collections
     })
 )(DataExplorerView);
index 41725b53ef427aa613e657d37d5f6d88cb024738..c981c902afefac1b67c337adfe54dff9a675f69c 100644 (file)
@@ -20,6 +20,7 @@ import { TreeItem, TreeItemStatus } from "../../components/tree/tree";
 import { Project } from "../../models/project";
 import { getTreePath } from '../../store/project/project-reducer';
 import DataExplorer from '../data-explorer/data-explorer';
+import { getCollectionList } from "../../store/collection/collection-action";
 
 const drawerWidth = 240;
 const appBarHeight = 102;
@@ -122,7 +123,6 @@ class Workbench extends React.Component<WorkbenchProps, WorkbenchState> {
         }
     };
 
-
     mainAppBarActions: MainAppBarActionProps = {
         onBreadcrumbClick: ({ itemId, status }: NavBreadcrumb) => {
             this.toggleProjectTreeItem(itemId, status);
@@ -141,6 +141,7 @@ class Workbench extends React.Component<WorkbenchProps, WorkbenchState> {
             this.props.dispatch<any>(getProjectList(itemId))
                 .then(() => this.openProjectItem(itemId));
         }
+        this.props.dispatch<any>(getCollectionList(itemId));
     }
 
     openProjectItem = (itemId: string) => {
@@ -183,7 +184,7 @@ class Workbench extends React.Component<WorkbenchProps, WorkbenchState> {
                 <main className={classes.contentWrapper}>
                     <div className={classes.content}>
                         <Switch>
-                            <Route path="/project/:name" component={DataExplorer} />
+                            <Route path="/project/:uuid" component={DataExplorer} />
                         </Switch>
                     </div>
                 </main>
index 4845e4d2c8ad4a60d59e7dc53d785a3227b171f0..7f02975dab42b832109e785975fee54affc60545 100644 (file)
@@ -12,7 +12,8 @@
     "no-debugger": false,
     "no-console": false,
     "no-shadowed-variable": false,
-    "semicolon": true
+    "semicolon": true,
+    "array-type": false
   },
   "linterOptions": {
     "exclude": [