From: Pawel Kowalczyk Date: Mon, 25 Jun 2018 10:05:09 +0000 (+0200) Subject: merge-conflicts X-Git-Tag: 1.2.0~68^2~2 X-Git-Url: https://git.arvados.org/arvados-workbench2.git/commitdiff_plain/c90e813adcec89899d9db95843295a84fb058c3e merge-conflicts Feature ##13598 Arvados-DCO-1.1-Signed-off-by: Pawel Kowalczyk --- c90e813adcec89899d9db95843295a84fb058c3e diff --cc src/components/tree/tree.tsx index 5dd45878,6731950c..2c19a831 --- a/src/components/tree/tree.tsx +++ b/src/components/tree/tree.tsx @@@ -29,8 -61,7 +29,8 @@@ export interface TreeItem interface TreeProps { items?: Array>; render: (item: TreeItem, level?: number) => ReactElement<{}>; - toggleItem: (id: string, status: TreeItemStatus) => any; + toggleItemOpen: (id: string, status: TreeItemStatus) => void; - toggleItemActive: (id: string) => void; ++ toggleItemActive: (id: string, status: TreeItemStatus) => void; level?: number; } @@@ -50,7 -81,7 +50,7 @@@ class Tree extends React.Component {items && items.map((it: TreeItem, idx: number) =>
- toggleItemActive(it.id)}> - ++ toggleItemActive(it.id, it.status)}> {it.status === TreeItemStatus.Pending ? : null} {it.toggled && it.items && it.items.length === 0 ? null : this.renderArrow(it.status, it.active ? activeArrow : inactiveArrow, it.open, it.id)} {render(it, level)} diff --cc src/store/project/project-action.ts index a58edd3c,728b1cc9..3c264d3e --- a/src/store/project/project-action.ts +++ b/src/store/project/project-action.ts @@@ -1,22 -1,30 +1,34 @@@ // Copyright (C) The Arvados Authors. All rights reserved. // // SPDX-License-Identifier: AGPL-3.0 +import { default as unionize, ofType, UnionOf } from "unionize"; import { Project } from "../../models/project"; -import { default as unionize, ofType, UnionOf } from "unionize"; + import { projectService } from "../../services/services"; + import { Dispatch } from "redux"; const actions = unionize({ CREATE_PROJECT: ofType(), REMOVE_PROJECT: ofType(), -- PROJECTS_REQUEST: ofType(), ++ PROJECTS_REQUEST: ofType(), PROJECTS_SUCCESS: ofType<{ projects: Project[], parentItemId?: string }>(), - TOGGLE_PROJECT_TREE_ITEM: ofType() + TOGGLE_PROJECT_TREE_ITEM_OPEN: ofType(), + TOGGLE_PROJECT_TREE_ITEM_ACTIVE: ofType(), + RESET_PROJECT_TREE_ACTIVITY: ofType(), }, { -- tag: 'type', -- value: 'payload' --}); ++ tag: 'type', ++ value: 'payload' ++ }); + + export const getProjectList = (parentUuid?: string) => (dispatch: Dispatch): Promise => { - dispatch(actions.PROJECTS_REQUEST()); - return projectService.getProjectList(parentUuid).then(projects => { - dispatch(actions.PROJECTS_SUCCESS({projects, parentItemId: parentUuid})); - return projects; - }); ++ if (parentUuid) { ++ dispatch(actions.PROJECTS_REQUEST(parentUuid)); ++ return projectService.getProjectList(parentUuid).then(projects => { ++ dispatch(actions.PROJECTS_SUCCESS({ projects, parentItemId: parentUuid })); ++ return projects; ++ }); ++ } return Promise.resolve([]); + }; export type ProjectAction = UnionOf; export default actions; diff --cc src/store/project/project-reducer.test.ts index e5cd57e2,f964e0ea..e8d6afc6 --- a/src/store/project/project-reducer.test.ts +++ b/src/store/project/project-reducer.test.ts @@@ -35,137 -38,70 +38,193 @@@ describe('project-reducer', () => const projects = [project, project]; const state = projectsReducer(initialState, actions.PROJECTS_SUCCESS({ projects, parentItemId: undefined })); expect(state).toEqual([{ + active: false, + open: false, + id: "test123", + items: [], + data: project, + status: 0 + }, { + active: false, + open: false, + id: "test123", + items: [], + data: project, + status: 0 + } + ]); + }); + + it('should remove activity on projects list', () => { + const initialState = [ + { + data: { + name: 'test', + href: 'href', + createdAt: '2018-01-01', + modifiedAt: '2018-01-01', + ownerUuid: 'owner-test123', + uuid: 'test123', ++ kind: 'example' + }, + id: "1", + open: true, + active: true, + status: 1 + } + ]; + const project = [ + { + data: { + name: 'test', + href: 'href', + createdAt: '2018-01-01', + modifiedAt: '2018-01-01', + ownerUuid: 'owner-test123', + uuid: 'test123', ++ kind: 'example' + }, + id: "1", + open: true, active: false, - open: false, - id: "test123", - items: [], - data: project, - status: 0 - }, { + status: 1 + } + ]; + + const state = projectsReducer(initialState, actions.RESET_PROJECT_TREE_ACTIVITY(initialState[0].id)); + expect(state).toEqual(project); + }); + + it('should toggle project tree item activity', () => { + const initialState = [ + { + data: { + name: 'test', + href: 'href', + createdAt: '2018-01-01', + modifiedAt: '2018-01-01', + ownerUuid: 'owner-test123', + uuid: 'test123', ++ kind: 'example' + }, + id: "1", + open: true, + active: false, + status: 1 + } + ]; + const project = [ + { + data: { + name: 'test', + href: 'href', + createdAt: '2018-01-01', + modifiedAt: '2018-01-01', + ownerUuid: 'owner-test123', + uuid: 'test123', ++ kind: 'example' + }, + id: "1", + open: true, + active: true, + status: 1 + } + ]; + + const state = projectsReducer(initialState, actions.TOGGLE_PROJECT_TREE_ITEM_ACTIVE(initialState[0].id)); + expect(state).toEqual(project); + }); + + + it('should close project tree item ', () => { + const initialState = [ + { + data: { + name: 'test', + href: 'href', + createdAt: '2018-01-01', + modifiedAt: '2018-01-01', + ownerUuid: 'owner-test123', + uuid: 'test123', ++ kind: 'example' + }, + id: "1", + open: true, active: false, + status: 1, + toggled: false, + } + ]; + const project = [ + { + data: { + name: 'test', + href: 'href', + createdAt: '2018-01-01', + modifiedAt: '2018-01-01', + ownerUuid: 'owner-test123', + uuid: 'test123', ++ kind: 'example' + }, + id: "1", open: false, - id: "test123", - items: [], - data: project, - status: 0 + active: false, + status: 1, + toggled: true } - ]); + ]; + + const state = projectsReducer(initialState, actions.TOGGLE_PROJECT_TREE_ITEM_OPEN(initialState[0].id)); + expect(state).toEqual(project); }); }); + + describe("findTreeBranch", () => { + + const createTreeItem = (id: string, items?: Array>): TreeItem => ({ + id, + items, + active: false, + data: "", + open: false, + status: TreeItemStatus.Initial + }); + + it("should return an array that matches path to the given item", () => { + const tree: Array> = [ + createTreeItem("1", [ + createTreeItem("1.1", [ + createTreeItem("1.1.1"), + createTreeItem("1.1.2") + ]) + ]), + createTreeItem("2", [ + createTreeItem("2.1", [ + createTreeItem("2.1.1"), + createTreeItem("2.1.2") + ]) + ]) + ]; + const branch = getTreePath(tree, "2.1.1"); + expect(branch.map(item => item.id)).toEqual(["2", "2.1", "2.1.1"]); + }); + + it("should return empty array if item is not found", () => { + const tree: Array> = [ + createTreeItem("1", [ + createTreeItem("1.1", [ + createTreeItem("1.1.1"), + createTreeItem("1.1.2") + ]) + ]), + createTreeItem("2", [ + createTreeItem("2.1", [ + createTreeItem("2.1.1"), + createTreeItem("2.1.2") + ]) + ]) + ]; + expect(getTreePath(tree, "3")).toHaveLength(0); + }); + + }); diff --cc src/store/store.ts index 49e0b5f7,6b9c31ff..6089caf3 --- a/src/store/store.ts +++ b/src/store/store.ts @@@ -6,10 -6,9 +6,11 @@@ import { createStore, applyMiddleware, import { routerMiddleware, routerReducer, RouterState } from "react-router-redux"; import thunkMiddleware from 'redux-thunk'; import { History } from "history"; + import projectsReducer, { ProjectState } from "./project/project-reducer"; +import sidePanelReducer, { SidePanelState } from './side-panel/side-panel-reducer'; import authReducer, { AuthState } from "./auth/auth-reducer"; + import collectionsReducer from "./collection/collection-reducer"; const composeEnhancers = (process.env.NODE_ENV === 'development' && @@@ -26,8 -24,8 +27,9 @@@ export interface RootState const rootReducer = combineReducers({ auth: authReducer, projects: projectsReducer, + collections: collectionsReducer, - router: routerReducer + router: routerReducer, + sidePanel: sidePanelReducer }); diff --cc src/views-components/project-tree/project-tree.test.tsx index 932a29cc,d5312130..1ba3abb8 --- a/src/views-components/project-tree/project-tree.test.tsx +++ b/src/views-components/project-tree/project-tree.test.tsx @@@ -26,13 -26,13 +26,14 @@@ describe("ProjectTree component", () = uuid: "uuid", ownerUuid: "ownerUuid", href: "href", ++ kind: 'example' }, id: "3", open: true, active: true, status: 1 }; -- const wrapper = mount( { }} />); ++ const wrapper = mount(); expect(wrapper.find(ListItemIcon)).toHaveLength(1); }); @@@ -47,6 -47,6 +48,7 @@@ uuid: "uuid", ownerUuid: "ownerUuid", href: "href", ++ kind: 'example' }, id: "3", open: false, @@@ -61,6 -61,6 +63,7 @@@ uuid: "uuid", ownerUuid: "ownerUuid", href: "href", ++ kind: 'example' }, id: "3", open: false, @@@ -68,7 -68,7 +71,7 @@@ status: 1 } ]; -- const wrapper = mount( { }} />); ++ const wrapper = mount(); expect(wrapper.find(ListItemIcon)).toHaveLength(2); }); @@@ -83,6 -83,6 +86,7 @@@ uuid: "uuid", ownerUuid: "ownerUuid", href: "href", ++ kind: 'example' }, id: "3", open: true, @@@ -97,6 -97,6 +101,7 @@@ uuid: "uuid", ownerUuid: "ownerUuid", href: "href", ++ kind: 'example' }, id: "3", open: true, @@@ -106,7 -106,7 +111,7 @@@ ] } ]; -- const wrapper = mount( { }} />); ++ const wrapper = mount(); expect(wrapper.find(Collapse)).toHaveLength(1); }); @@@ -120,13 -120,13 +125,14 @@@ uuid: "uuid", ownerUuid: "ownerUuid", href: "href", ++ kind: 'example' }, id: "3", open: false, active: true, status: 1 }; -- const wrapper = mount( { }} />); ++ const wrapper = mount(); expect(wrapper.find(CircularProgress)).toHaveLength(1); }); diff --cc src/views-components/project-tree/project-tree.tsx index 7406f7f3,fd32ff04..f51b65e0 --- a/src/views-components/project-tree/project-tree.tsx +++ b/src/views-components/project-tree/project-tree.tsx @@@ -9,39 -9,9 +9,39 @@@ import ListItemText from "@material-ui/ import ListItemIcon from '@material-ui/core/ListItemIcon'; import Typography from '@material-ui/core/Typography'; - import Tree, { TreeItem, TreeItemStatus } from '../tree/tree'; + import Tree, { TreeItem, TreeItemStatus } from '../../components/tree/tree'; import { Project } from '../../models/project'; +export interface ProjectTreeProps { + projects: Array>; + toggleOpen: (id: string, status: TreeItemStatus) => void; - toggleActive: (id: string) => void; ++ toggleActive: (id: string, status: TreeItemStatus) => void; +} + +class ProjectTree extends React.Component> { + render(): ReactElement { + const { classes, projects, toggleOpen, toggleActive } = this.props; + const { active, listItemText, row, treeContainer } = classes; + return ( +
+ ) => + + + + + {project.data.name} + } /> + + } /> +
+ ); + } +} + type CssRules = 'active' | 'listItemText' | 'row' | 'treeContainer'; const styles: StyleRulesCallback = (theme: Theme) => ({ diff --cc src/views/workbench/workbench.test.tsx index 7b9b74d0,7b9b74d0..69257922 --- a/src/views/workbench/workbench.test.tsx +++ b/src/views/workbench/workbench.test.tsx @@@ -14,7 -14,7 +14,7 @@@ const history = createBrowserHistory() it('renders without crashing', () => { const div = document.createElement('div'); -- const store = configureStore({ projects: [], router: { location: null }, auth: {} }, createBrowserHistory()); ++ const store = configureStore({ projects: [], router: { location: null }, auth: {}, sidePanel: [] }, createBrowserHistory()); ReactDOM.render( diff --cc src/views/workbench/workbench.tsx index 9e274325,bac0b473..4f9843cb --- a/src/views/workbench/workbench.tsx +++ b/src/views/workbench/workbench.tsx @@@ -12,20 -11,20 +10,23 @@@ import { Route, Switch } from "react-ro import authActions from "../../store/auth/auth-action"; import { User } from "../../models/user"; import { RootState } from "../../store/store"; - import MainAppBar, { MainAppBarActionProps, MainAppBarMenuItem } from '../../components/main-app-bar/main-app-bar'; + import MainAppBar, { MainAppBarActionProps, MainAppBarMenuItem } from '../../views-components/main-app-bar/main-app-bar'; import { Breadcrumb } from '../../components/breadcrumbs/breadcrumbs'; import { push } from 'react-router-redux'; - import projectActions from "../../store/project/project-action"; - import sidePanelActions from '../../store/side-panel/side-panel-action'; - import ProjectTree from '../../components/project-tree/project-tree'; + import projectActions, { getProjectList } from "../../store/project/project-action"; + import ProjectTree from '../../views-components/project-tree/project-tree'; import { TreeItem, TreeItemStatus } from "../../components/tree/tree"; import { Project } from "../../models/project"; + import { getTreePath } from '../../store/project/project-reducer'; + import ProjectPanel from '../project-panel/project-panel'; ++import sidePanelActions from '../../store/side-panel/side-panel-action'; +import { projectService } from '../../services/services'; +import SidePanel, { SidePanelItem } from '../../components/side-panel/side-panel'; const drawerWidth = 240; + const appBarHeight = 102; - type CssRules = 'root' | 'appBar' | 'drawerPaper' | 'content' | 'toolbar'; + type CssRules = 'root' | 'appBar' | 'drawerPaper' | 'content' | 'contentWrapper' | 'toolbar'; const styles: StyleRulesCallback = (theme: Theme) => ({ root: { @@@ -46,15 -45,18 +47,20 @@@ drawerPaper: { position: 'relative', width: drawerWidth, + display: 'flex', + flexDirection: 'column', }, - content: { - flexGrow: 1, + contentWrapper: { backgroundColor: theme.palette.background.default, - padding: theme.spacing.unit * 3, - height: '100%', + display: "flex", + flexGrow: 1, minWidth: 0, + paddingTop: appBarHeight + }, + content: { + padding: theme.spacing.unit * 3, + overflowY: "auto", + flexGrow: 1 }, toolbar: theme.mixins.toolbar }); @@@ -130,7 -124,9 +129,9 @@@ class Workbench extends React.Component mainAppBarActions: MainAppBarActionProps = { - onBreadcrumbClick: (breadcrumb: NavBreadcrumb) => this.props.dispatch(push(breadcrumb.path)), + onBreadcrumbClick: ({ itemId, status }: NavBreadcrumb) => { - this.toggleProjectTreeItem(itemId, status); ++ this.toggleProjectTreeItemOpen(itemId, status); + }, onSearch: searchText => { this.setState({ searchText }); this.props.dispatch(push(`/search?q=${searchText}`)); @@@ -138,34 -134,30 +139,60 @@@ onMenuItemClick: (menuItem: NavMenuItem) => menuItem.action() }; - toggleProjectTreeItem = (itemId: string, status: TreeItemStatus) => { + toggleProjectTreeItemOpen = (itemId: string, status: TreeItemStatus) => { if (status === TreeItemStatus.Loaded) { + this.openProjectItem(itemId); + this.props.dispatch(projectActions.TOGGLE_PROJECT_TREE_ITEM_OPEN(itemId)); + this.props.dispatch(projectActions.TOGGLE_PROJECT_TREE_ITEM_ACTIVE(itemId)); } else { - this.props.dispatch(projectService.getProjectList(itemId)).then(() => { - this.props.dispatch(projectActions.TOGGLE_PROJECT_TREE_ITEM_OPEN(itemId)); - this.props.dispatch(projectActions.TOGGLE_PROJECT_TREE_ITEM_ACTIVE(itemId)); - }); + this.props.dispatch(getProjectList(itemId)) - .then(() => this.openProjectItem(itemId)); ++ .then(() => { ++ this.openProjectItem(itemId); ++ this.props.dispatch(projectActions.TOGGLE_PROJECT_TREE_ITEM_OPEN(itemId)); ++ this.props.dispatch(projectActions.TOGGLE_PROJECT_TREE_ITEM_ACTIVE(itemId)); ++ }); } } - toggleProjectTreeItemActive = (itemId: string) => { - this.props.dispatch(projectActions.TOGGLE_PROJECT_TREE_ITEM_ACTIVE(itemId)); - this.props.dispatch(sidePanelActions.RESET_SIDE_PANEL_ACTIVITY(itemId)); ++ toggleProjectTreeItemActive = (itemId: string, status: TreeItemStatus) => { ++ if (status === TreeItemStatus.Loaded) { ++ this.openProjectItem(itemId); ++ this.props.dispatch(projectActions.TOGGLE_PROJECT_TREE_ITEM_ACTIVE(itemId)); ++ this.props.dispatch(sidePanelActions.RESET_SIDE_PANEL_ACTIVITY(itemId)); ++ } else { ++ this.props.dispatch(getProjectList(itemId)) ++ .then(() => { ++ this.openProjectItem(itemId); ++ this.props.dispatch(projectActions.TOGGLE_PROJECT_TREE_ITEM_ACTIVE(itemId)); ++ this.props.dispatch(sidePanelActions.RESET_SIDE_PANEL_ACTIVITY(itemId)); ++ }); ++ } + } + + toggleSidePanelOpen = (itemId: string) => { + this.props.dispatch(sidePanelActions.TOGGLE_SIDE_PANEL_ITEM_OPEN(itemId)); + } + + toggleSidePanelActive = (itemId: string) => { + this.props.dispatch(sidePanelActions.TOGGLE_SIDE_PANEL_ITEM_ACTIVE(itemId)); + this.props.dispatch(projectActions.RESET_PROJECT_TREE_ACTIVITY(itemId)); + } + + openProjectItem = (itemId: string) => { + const branch = getTreePath(this.props.projects, itemId); + this.setState({ + breadcrumbs: branch.map(item => ({ + label: item.data.name, + itemId: item.data.uuid, + status: item.status + })) + }); - this.props.dispatch(projectActions.TOGGLE_PROJECT_TREE_ITEM(itemId)); ++ this.props.dispatch(projectActions.TOGGLE_PROJECT_TREE_ITEM_ACTIVE(itemId)); + this.props.dispatch(push(`/project/${itemId}`)); + } + render() { - const { classes, user } = this.props; + const { classes, user, projects, sidePanelItems } = this.props; return (
@@@ -184,21 -176,16 +211,22 @@@ paper: classes.drawerPaper, }}>
- - - - ++ ++ ++ } -
-
- - - +
+
+ + + +
);