Merge branch 'master'
authorMichal Klobukowski <michal.klobukowski@contractors.roche.com>
Mon, 18 Jun 2018 11:03:22 +0000 (13:03 +0200)
committerMichal Klobukowski <michal.klobukowski@contractors.roche.com>
Mon, 18 Jun 2018 11:03:22 +0000 (13:03 +0200)
Feature #13628

Arvados-DCO-1.1-Signed-off-by: Michal Klobukowski <michal.klobukowski@contractors.roche.com>

1  2 
src/store/project/project-reducer.test.ts
src/store/project/project-reducer.ts
src/views/workbench/workbench.tsx

index 3e828830421ed713d0d006fa7c8f0e7e788822b6,cfb73fa8b45daa4be4dbb1a0dfeee5790d5461f3..311ec9d18346f2745337ecda29fb8f97028a2463
@@@ -2,9 -2,8 +2,9 @@@
  //
  // SPDX-License-Identifier: AGPL-3.0
  
 -import projectsReducer from "./project-reducer";
 +import projectsReducer, { findTreeBranch } from "./project-reducer";
  import actions from "./project-action";
- import { TreeItem } from "../../components/tree/tree";
++import { TreeItem, TreeItemStatus } from "../../components/tree/tree";
  
  describe('project-reducer', () => {
      it('should add new project to the list', () => {
@@@ -15,7 -14,8 +15,8 @@@
              createdAt: '2018-01-01',
              modifiedAt: '2018-01-01',
              ownerUuid: 'owner-test123',
-             uuid: 'test123'
+             uuid: 'test123',
+             kind: ""
          };
  
          const state = projectsReducer(initialState, actions.CREATE_PROJECT(project));
              createdAt: '2018-01-01',
              modifiedAt: '2018-01-01',
              ownerUuid: 'owner-test123',
-             uuid: 'test123'
+             uuid: 'test123',
+             kind: ""
          };
  
          const projects = [project, project];
 -        const state = projectsReducer(initialState, actions.PROJECTS_SUCCESS({projects, parentItemId: undefined}));
 +        const state = projectsReducer(initialState, actions.PROJECTS_SUCCESS({ projects, parentItemId: undefined }));
          expect(state).toEqual([{
-             active: false,
-             open: false,
-             id: "test123",
-             items: [],
-             data: project
-         }, {
-             active: false,
-             open: false,
-             id: "test123",
-             items: [],
-             data: project
-         }
+                 active: false,
+                 open: false,
+                 id: "test123",
+                 items: [],
+                 data: project,
+                 status: 0
+             }, {
+                 active: false,
+                 open: false,
+                 id: "test123",
+                 items: [],
+                 data: project,
+                 status: 0
+             }
          ]);
      });
  });
 +
 +describe("findTreeBranch", () => {
 +
 +    const createTreeItem = (id: string, items?: Array<TreeItem<string>>): TreeItem<string> => ({
 +        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<TreeItem<string>> = [
 +            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 = findTreeBranch(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<TreeItem<string>> = [
 +            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(findTreeBranch(tree, "3")).toHaveLength(0);
 +    });
 +
 +});
index ac6d4b73ffd0fd5460ae07a7affe4a8953540803,7563ea90c229febd989ae33af0602daea5c42401..8770391acbefe09eb6d8520e1b3ff21f0aa442e6
@@@ -4,12 -4,12 +4,12 @@@
  
  import { Project } from "../../models/project";
  import actions, { ProjectAction } from "./project-action";
- import { TreeItem } from "../../components/tree/tree";
+ import { TreeItem, TreeItemStatus } from "../../components/tree/tree";
  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
      return item;
  }
  
 +export function findTreeBranch<T>(tree: Array<TreeItem<T>>, itemId: string): Array<TreeItem<T>> {
 +    for(const item of tree){
 +        if(item.id === itemId){
 +            return [item];
 +        } else {
 +            const branch = findTreeBranch(item.items || [], itemId);
 +            if(branch.length > 0){
 +                return [item, ...branch];
 +            }
 +        }
 +    }
 +    return [];
 +}
 +
  function resetTreeActivity<T>(tree: Array<TreeItem<T>>) {
      for (const t of tree) {
          t.active = false;
@@@ -47,11 -33,15 +47,15 @@@ function updateProjectTree(tree: Array<
      let treeItem;
      if (parentItemId) {
          treeItem = findTreeItem(tree, parentItemId);
+         if (treeItem) {
+             treeItem.status = TreeItemStatus.Loaded;
+         }
      }
      const items = projects.map((p, idx) => ({
          id: p.uuid,
          open: false,
          active: false,
+         status: TreeItemStatus.Initial,
          data: p,
          items: []
      } as TreeItem<Project>));
      return items;
  }
  
  const projectsReducer = (state: ProjectState = [], action: ProjectAction) => {
      return actions.match(action, {
          CREATE_PROJECT: project => [...state, project],
          REMOVE_PROJECT: () => state,
-         PROJECTS_REQUEST: () => state,
+         PROJECTS_REQUEST: itemId => {
+             const tree = _.cloneDeep(state);
+             const item = findTreeItem(tree, itemId);
+             if (item) {
+                 item.status = TreeItemStatus.Pending;
+             }
+             return tree;
+         },
          PROJECTS_SUCCESS: ({ projects, parentItemId }) => {
              return updateProjectTree(state, projects, parentItemId);
          },
@@@ -80,6 -76,7 +90,7 @@@
              if (item) {
                  item.open = !item.open;
                  item.active = true;
+                 item.toggled = true;
              }
              return tree;
          },
index cb691869b21c17ca9c406d251233ce3a1839023c,90df260b974d983842f67aa2d5ab90a7b8fd63c8..0aecc0d2b578e7045c4a6a37a892c5492cf81e65
@@@ -6,31 -6,20 +6,21 @@@ import * as React from 'react'
  
  import { StyleRulesCallback, Theme, WithStyles, withStyles } from '@material-ui/core/styles';
  import Drawer from '@material-ui/core/Drawer';
- import AppBar from '@material-ui/core/AppBar';
- import Toolbar from '@material-ui/core/Toolbar';
- import Typography from '@material-ui/core/Typography';
  import { connect, DispatchProp } from "react-redux";
- import ProjectList from "../../components/project-list/project-list";
  import { Route, Switch } from "react-router";
- import { Link } from "react-router-dom";
- import Button from "@material-ui/core/Button/Button";
  import authActions from "../../store/auth/auth-action";
- import IconButton from "@material-ui/core/IconButton/IconButton";
- import Menu from "@material-ui/core/Menu/Menu";
- import MenuItem from "@material-ui/core/MenuItem/MenuItem";
- import { AccountCircle } from "@material-ui/icons";
  import { User } from "../../models/user";
- import Grid from "@material-ui/core/Grid/Grid";
  import { RootState } from "../../store/store";
- import MainAppBar, { MainAppBarActionProps, MainAppBarMenuItems, MainAppBarMenuItem } from '../../components/main-app-bar/main-app-bar';
+ import MainAppBar, { MainAppBarActionProps, MainAppBarMenuItem } from '../../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 ProjectTree from '../../components/project-tree/project-tree';
- import { TreeItem } from "../../components/tree/tree";
+ import { TreeItem, TreeItemStatus } from "../../components/tree/tree";
  import { Project } from "../../models/project";
  import { projectService } from '../../services/services';
 +import { findTreeBranch } from '../../store/project/project-reducer';
+ import DataExplorer from '../data-explorer/data-explorer';
  
  const drawerWidth = 240;
  
@@@ -77,7 -66,7 +67,8 @@@ interface WorkbenchActionProps 
  type WorkbenchProps = WorkbenchDataProps & WorkbenchActionProps & DispatchProp & WithStyles<CssRules>;
  
  interface NavBreadcrumb extends Breadcrumb {
 -    path: string;
 +    itemId: string;
++    status: TreeItemStatus;
  }
  
  interface NavMenuItem extends MainAppBarMenuItem {
@@@ -99,7 -88,15 +90,7 @@@ class Workbench extends React.Component
      state = {
          anchorEl: null,
          searchText: "",
 -        breadcrumbs: [
 -            {
 -                label: "Projects",
 -                path: "/projects"
 -            }, {
 -                label: "Project 1",
 -                path: "/projects/project-1"
 -            }
 -        ],
 +        breadcrumbs: [],
          menuItems: {
              accountMenu: [
                  {
  
  
      mainAppBarActions: MainAppBarActionProps = {
-         onBreadcrumbClick: ({ itemId }: NavBreadcrumb) => {
-             this.toggleProjectTreeItem(itemId);
 -        onBreadcrumbClick: (breadcrumb: NavBreadcrumb) => this.props.dispatch(push(breadcrumb.path)),
++        onBreadcrumbClick: ({ itemId, status }: NavBreadcrumb) => {
++            this.toggleProjectTreeItem(itemId, status);
 +        },
          onSearch: searchText => {
              this.setState({ searchText });
              this.props.dispatch(push(`/search?q=${searchText}`));
          onMenuItemClick: (menuItem: NavMenuItem) => menuItem.action()
      };
  
-     toggleProjectTreeItem = (itemId: string) => {
+     toggleProjectTreeItem = (itemId: string, status: TreeItemStatus) => {
+         if (status === TreeItemStatus.Loaded) {
+             this.openProjectItem(itemId);
+         } else {
+             this.props.dispatch<any>(projectService.getProjectList(itemId)).then(() => this.openProjectItem(itemId));
+         }
+     }
+     openProjectItem = (itemId: string) => {
 +        const branch = findTreeBranch(this.props.projects, itemId);
 +        this.setState({
 +            breadcrumbs: branch.map(item => ({
 +                label: item.data.name,
-                 itemId: item.data.uuid
++                itemId: item.data.uuid,
++                status: item.status
 +            }))
 +        });
-         this.props.dispatch<any>(projectService.getProjectList(itemId)).then(() => {
-             this.props.dispatch(projectActions.TOGGLE_PROJECT_TREE_ITEM(itemId));
-         });
+         this.props.dispatch(projectActions.TOGGLE_PROJECT_TREE_ITEM(itemId));
+         this.props.dispatch(push(`/project/${itemId}`));
      }
  
      render() {
                      <div className={classes.toolbar} />
                      <div className={classes.toolbar} />
                      <Switch>
-                         <Route path="/project/:name" component={ProjectList} />
+                         <Route path="/project/:name" component={DataExplorer} />
                      </Switch>
                  </main>
              </div>