[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