[ARVADOS-WORKBENCH2] created: 1.1.4-28-g973fce0

Git user git at public.curoverse.com
Wed Jun 13 10:28:29 EDT 2018


        at  973fce01f05a50d4aa0169a88281f20476b305b2 (commit)


commit 973fce01f05a50d4aa0169a88281f20476b305b2
Author: Michal Klobukowski <michal.klobukowski at contractors.roche.com>
Date:   Wed Jun 13 16:28:16 2018 +0200

    Create data-explorer and project-explorer prototype
    
    Feature #13601
    
    Arvados-DCO-1.1-Signed-off-by: Michal Klobukowski <michal.klobukowski at contractors.roche.com>

diff --git a/src/components/data-explorer/column.ts b/src/components/data-explorer/column.ts
new file mode 100644
index 0000000..bbbc6ef
--- /dev/null
+++ b/src/components/data-explorer/column.ts
@@ -0,0 +1,9 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+export interface Column<T> {
+    header: string;
+    selected: boolean;
+    render: (item: T) => React.ReactElement<void>;
+}
\ No newline at end of file
diff --git a/src/components/data-explorer/columns-configurator/columns-configurator.tsx b/src/components/data-explorer/columns-configurator/columns-configurator.tsx
new file mode 100644
index 0000000..e0680e9
--- /dev/null
+++ b/src/components/data-explorer/columns-configurator/columns-configurator.tsx
@@ -0,0 +1,74 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import * as React from 'react';
+import { WithStyles, StyleRulesCallback, Theme, withStyles, IconButton, Popover, Paper, List, Checkbox, ListItemText, ListItem } from '@material-ui/core';
+import ColumnsIcon from "@material-ui/icons/ViewWeek";
+import { Column } from '../column';
+import { PopoverOrigin } from '@material-ui/core/Popover';
+
+export interface ColumnsConfiguratorProps {
+    columns: Array<Column<any>>;
+    onColumnToggle: (column: Column<any>) => void
+}
+
+
+class ColumnsConfigurator extends React.Component<ColumnsConfiguratorProps & WithStyles<CssRules>> {
+
+    state = {
+        anchorEl: undefined
+    }
+
+    transformOrigin: PopoverOrigin = {
+        vertical: "top",
+        horizontal: "right",
+    }
+
+    render() {
+        const { columns, onColumnToggle } = this.props;
+        return (
+            <>
+                <IconButton onClick={this.handleClick}><ColumnsIcon /></IconButton>
+                <Popover
+                    anchorEl={this.state.anchorEl}
+                    open={Boolean(this.state.anchorEl)}
+                    onClose={this.handleClose}
+                    transformOrigin={this.transformOrigin}
+                    anchorOrigin={this.transformOrigin}
+                >
+                    <Paper>
+                        <List>
+                            {
+                                columns.map((column, index) => (
+                                    <ListItem dense key={index} button onClick={() => onColumnToggle(column)}>
+                                        <Checkbox disableRipple color="primary" checked={column.selected}/>
+                                        <ListItemText>{column.header}</ListItemText>
+                                    </ListItem>
+
+                                ))
+                            }
+                        </List>
+                    </Paper>
+                </Popover>
+            </>
+        );
+    }
+
+    handleClose = () => {
+        this.setState({ anchorEl: undefined });
+    }
+
+    handleClick = (event: React.MouseEvent<HTMLElement>) => {
+        this.setState({ anchorEl: event.currentTarget });
+    }
+
+}
+
+type CssRules = "root";
+
+const styles: StyleRulesCallback<CssRules> = (theme: Theme) => ({
+    root: {}
+});
+
+export default withStyles(styles)(ColumnsConfigurator);
diff --git a/src/components/data-explorer/data-explorer.tsx b/src/components/data-explorer/data-explorer.tsx
new file mode 100644
index 0000000..9090bff
--- /dev/null
+++ b/src/components/data-explorer/data-explorer.tsx
@@ -0,0 +1,76 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import * as React from 'react';
+import { Table, TableBody, TableRow, TableCell, TableHead, StyleRulesCallback, Theme, WithStyles, withStyles, Typography, Grid } from '@material-ui/core';
+import { Column } from './column';
+import ColumnsConfigurator from "./columns-configurator/columns-configurator";
+
+export interface DataExplorerProps<T> {
+    items: T[];
+    columns: Array<Column<T>>;
+    onColumnToggle: (column: Column<T>) => void;
+    onItemClick: (item: T) => void;
+}
+
+class DataExplorer<T> extends React.Component<DataExplorerProps<T> & WithStyles<CssRules>> {
+    render() {
+        const { items, columns, classes, onItemClick, onColumnToggle } = this.props;
+        return (
+            <div>
+                <Grid container justify="flex-end">
+                    <ColumnsConfigurator {...{ columns, onColumnToggle }} />
+                </Grid>
+                <div className={classes.tableContainer}>
+                    {
+                        items.length > 0 ? (
+                            <Table>
+                                <TableHead>
+                                    <TableRow>
+                                        {
+                                            columns.filter(column => column.selected).map((column, index) => (
+                                                <TableCell key={index}>{column.header}</TableCell>
+                                            ))
+                                        }
+                                    </TableRow>
+                                </TableHead>
+                                <TableBody className={classes.tableBody}>
+                                    {
+                                        items.map((item, index) => (
+                                            <TableRow key={index} hover onClick={() => onItemClick(item)}>
+                                                {
+                                                    columns.filter(column => column.selected).map((column, index) => (
+                                                        <TableCell key={index}>
+                                                            {column.render(item)}
+                                                        </TableCell>
+                                                    ))
+                                                }
+                                            </TableRow>
+                                        ))
+                                    }
+                                </TableBody>
+                            </Table>
+                        ) : (
+                                <Typography>No items</Typography>
+                            )
+                    }
+
+                </div>
+            </div>
+        );
+    }
+}
+
+type CssRules = "tableBody" | "tableContainer";
+
+const styles: StyleRulesCallback<CssRules> = (theme: Theme) => ({
+    tableContainer: {
+        overflowX: 'auto'
+    },
+    tableBody: {
+        background: theme.palette.background.paper
+    }
+});
+
+export default withStyles(styles)(DataExplorer);
diff --git a/src/models/project.ts b/src/models/project.ts
index 83fb59b..830621b 100644
--- a/src/models/project.ts
+++ b/src/models/project.ts
@@ -9,4 +9,5 @@ export interface Project {
     uuid: string;
     ownerUuid: string;
     href: string;
+    kind: string;
 }
diff --git a/src/services/project-service/project-service.ts b/src/services/project-service/project-service.ts
index 9350dab..d454a7b 100644
--- a/src/services/project-service/project-service.ts
+++ b/src/services/project-service/project-service.ts
@@ -48,7 +48,8 @@ export default class ProjectService {
                 modifiedAt: g.modified_at,
                 href: g.href,
                 uuid: g.uuid,
-                ownerUuid: g.owner_uuid
+                ownerUuid: g.owner_uuid,
+                kind: g.kind
             } as Project));
             dispatch(actions.PROJECTS_SUCCESS({projects, parentItemId: parentUuid}));
             return projects;
diff --git a/src/store/project/project-reducer.test.ts b/src/store/project/project-reducer.test.ts
index 9c1ed3b..f6dfea7 100644
--- a/src/store/project/project-reducer.test.ts
+++ b/src/store/project/project-reducer.test.ts
@@ -14,7 +14,8 @@ describe('project-reducer', () => {
             createdAt: '2018-01-01',
             modifiedAt: '2018-01-01',
             ownerUuid: 'owner-test123',
-            uuid: 'test123'
+            uuid: 'test123',
+            kind: ""
         };
 
         const state = projectsReducer(initialState, actions.CREATE_PROJECT(project));
@@ -29,7 +30,8 @@ describe('project-reducer', () => {
             createdAt: '2018-01-01',
             modifiedAt: '2018-01-01',
             ownerUuid: 'owner-test123',
-            uuid: 'test123'
+            uuid: 'test123',
+            kind: ""
         };
 
         const projects = [project, project];
diff --git a/src/store/project/project-reducer.ts b/src/store/project/project-reducer.ts
index 887cf89..909038e 100644
--- a/src/store/project/project-reducer.ts
+++ b/src/store/project/project-reducer.ts
@@ -9,7 +9,7 @@ import * as _ from "lodash";
 
 export type ProjectState = Array<TreeItem<Project>>;
 
-function findTreeItem<T>(tree: Array<TreeItem<T>>, itemId: string): TreeItem<T> | undefined {
+export function findTreeItem<T>(tree: Array<TreeItem<T>>, itemId: string): TreeItem<T> | undefined {
     let item;
     for (const t of tree) {
         item = t.id === itemId
diff --git a/src/views/project-explorer/project-explorer.tsx b/src/views/project-explorer/project-explorer.tsx
new file mode 100644
index 0000000..de866c2
--- /dev/null
+++ b/src/views/project-explorer/project-explorer.tsx
@@ -0,0 +1,80 @@
+// Copyright (C) The Arvados Authors. All rights reserved.
+//
+// SPDX-License-Identifier: AGPL-3.0
+
+import * as React from 'react';
+import DataExplorer, { DataExplorerProps } from "../../components/data-explorer/data-explorer";
+import { RouteComponentProps } from 'react-router';
+import { Project } from '../../models/project';
+import { ProjectState, findTreeItem } 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 { Typography } from '@material-ui/core';
+import { Column } from '../../components/data-explorer/column';
+
+interface ProjectExplorerViewDataProps {
+    projects: ProjectState
+}
+
+type ProjectExplorerViewProps = ProjectExplorerViewDataProps & RouteComponentProps<{ name: string }> & DispatchProp;
+
+type ProjectExplorerViewState = Pick<DataExplorerProps<Project>, "columns">;
+
+class ProjectExplorerView extends React.Component<ProjectExplorerViewProps, ProjectExplorerViewState> {
+
+    state: ProjectExplorerViewState = {
+        columns: [
+            { header: "Name", selected: true, render: item => <Typography noWrap>{renderIcon(item.kind)} {item.name}</Typography> },
+            { header: "Created at", selected: true, render: item => <Typography noWrap>{formatDate(item.createdAt)}</Typography> },
+            { header: "Modified at", selected: true, render: item => <Typography noWrap>{formatDate(item.modifiedAt)}</Typography> },
+            { header: "UUID", selected: true, render: item => <Typography noWrap>{item.uuid}</Typography> },
+            { header: "Owner UUID", selected: true, render: item => <Typography noWrap>{item.ownerUuid}</Typography> },
+            { header: "URL", selected: true, render: item => <Typography noWrap>{item.href}</Typography> }
+        ]
+    }
+
+    render() {
+        const project = findTreeItem(this.props.projects, this.props.match.params.name);
+        const projectItems = project && project.items || [];
+        return (
+            <DataExplorer {...this.state} items={projectItems.map(item => item.data)} onItemClick={this.goToProject} onColumnToggle={this.toggleColumn} />
+        );
+    }
+
+
+    goToProject = (project: Project) => {
+        this.props.dispatch(push(`/project/${project.uuid}`));
+        this.props.dispatch(projectActions.TOGGLE_PROJECT_TREE_ITEM(project.uuid));
+    }
+
+    toggleColumn = (column: Column<Project>) => {
+        const index = this.state.columns.indexOf(column);
+        const columns = this.state.columns.slice(0);
+        columns.splice(index, 1, { ...column, selected: !column.selected });
+        this.setState({ columns });
+    }
+}
+
+const formatDate = (isoDate: string) => {
+    const date = new Date(isoDate);
+    return date.toLocaleString();
+};
+
+const renderIcon = (kind: string) => {
+    switch (kind) {
+        case "arvados#group":
+            return <i className="fas fa-folder" />;
+        case "arvados#groupList":
+            return <i className="fas fa-th" />;
+        default:
+            return <i />;
+    }
+};
+
+export default connect(
+    (state: RootState) => ({
+        projects: state.projects
+    })
+)(ProjectExplorerView);
diff --git a/src/views/workbench/workbench.tsx b/src/views/workbench/workbench.tsx
index d18d113..f453947 100644
--- a/src/views/workbench/workbench.tsx
+++ b/src/views/workbench/workbench.tsx
@@ -28,6 +28,8 @@ import ProjectTree from '../../components/project-tree/project-tree';
 import { TreeItem } from "../../components/tree/tree";
 import { Project } from "../../models/project";
 import { projectService } from '../../services/services';
+import ProjectExplorer from '../project-explorer/project-explorer';
+import { push } from 'react-router-redux';
 
 const drawerWidth = 240;
 
@@ -107,6 +109,7 @@ class Workbench extends React.Component<WorkbenchProps, WorkbenchState> {
     toggleProjectTreeItem = (itemId: string) => {
         this.props.dispatch<any>(projectService.getProjectList(itemId)).then(() => {
             this.props.dispatch(projectActions.TOGGLE_PROJECT_TREE_ITEM(itemId));
+            this.props.dispatch(push(`/project/${itemId}`))
         });
     };
 
@@ -171,7 +174,7 @@ class Workbench extends React.Component<WorkbenchProps, WorkbenchState> {
                 <main className={classes.content}>
                     <div className={classes.toolbar}/>
                     <Switch>
-                        <Route path="/project/:name" component={ProjectList}/>
+                        <Route path="/project/:name" component={ProjectExplorer}/>
                     </Switch>
                 </main>
             </div>

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


hooks/post-receive
-- 




More information about the arvados-commits mailing list