Merge branch 'master' into 14160-default-data-explorer-view-improvements
authorMichal Klobukowski <michal.klobukowski@contractors.roche.com>
Thu, 6 Sep 2018 06:53:22 +0000 (08:53 +0200)
committerMichal Klobukowski <michal.klobukowski@contractors.roche.com>
Thu, 6 Sep 2018 06:53:22 +0000 (08:53 +0200)
refs #14160

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

src/components/column-selector/column-selector.tsx
src/components/dropdown-menu/dropdown-menu.tsx
src/routes/route-change-handlers.ts
src/store/navigation/navigation-action.ts
src/views-components/main-app-bar/main-app-bar.test.tsx
src/views-components/main-app-bar/main-app-bar.tsx

index 5e4b3397fab9e3b695d4c92bb37ad1e5e99c60b9..210dc5aedda07bcf59f15cb7607c7f2b73662294 100644 (file)
@@ -3,7 +3,7 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import * as React from 'react';
-import { WithStyles, StyleRulesCallback, withStyles, IconButton, Paper, List, Checkbox, ListItemText, ListItem } from '@material-ui/core';
+import { WithStyles, StyleRulesCallback, withStyles, IconButton, Paper, List, Checkbox, ListItemText, ListItem, Tooltip } from '@material-ui/core';
 import MenuIcon from "@material-ui/icons/Menu";
 import { DataColumn } from '../data-table/data-column';
 import { Popover } from "../popover/popover";
@@ -56,5 +56,7 @@ export const ColumnSelector = withStyles(styles)(
 
 export const ColumnSelectorTrigger = (props: IconButtonProps) =>
     <IconButton {...props}>
-        <MenuIcon />
+        <Tooltip title="Filters">
+            <MenuIcon />
+        </Tooltip>
     </IconButton>;
index 73b279b25e44886f2cb1639c425c55305e622fd3..a00df75dd4e67709f2a40549f90199740781e396 100644 (file)
@@ -6,10 +6,12 @@ import * as React from 'react';
 import Menu from '@material-ui/core/Menu';
 import IconButton from '@material-ui/core/IconButton';
 import { PopoverOrigin } from '@material-ui/core/Popover';
+import { Tooltip } from '@material-ui/core';
 
 interface DropdownMenuProps {
     id: string;
     icon: React.ReactElement<any>;
+    title: string;
 }
 
 interface DropdownMenuState {
@@ -22,12 +24,12 @@ export class DropdownMenu extends React.Component<DropdownMenuProps, DropdownMen
     };
 
     transformOrigin: PopoverOrigin = {
-        vertical: "top",
-        horizontal: "center"
+        vertical: -50,
+        horizontal: 0
     };
 
     render() {
-        const { icon, id, children } = this.props;
+        const { icon, id, children, title } = this.props;
         const { anchorEl } = this.state;
         return (
             <div>
@@ -36,7 +38,9 @@ export class DropdownMenu extends React.Component<DropdownMenuProps, DropdownMen
                     aria-haspopup="true"
                     color="inherit"
                     onClick={this.handleOpen}>
-                    {icon}
+                    <Tooltip title={title}>
+                        {icon}
+                    </Tooltip>
                 </IconButton>
                 <Menu
                     id={id}
@@ -44,7 +48,6 @@ export class DropdownMenu extends React.Component<DropdownMenuProps, DropdownMen
                     open={Boolean(anchorEl)}
                     onClose={this.handleClose}
                     onClick={this.handleClose}
-                    anchorOrigin={this.transformOrigin}
                     transformOrigin={this.transformOrigin}>
                     {children}
                 </Menu>
index b29e5d1e1afdf993b54135048cce74563dacc515..00fb4bc05acbfcf8a4dc47e3c20f19516443cf19 100644 (file)
@@ -4,8 +4,9 @@
 
 import { History, Location } from 'history';
 import { RootStore } from '~/store/store';
-import {  matchProcessRoute, matchProcessLogRoute, matchProjectRoute, matchCollectionRoute, matchFavoritesRoute, matchTrashRoute } from './routes';
+import { matchProcessRoute, matchProcessLogRoute, matchProjectRoute, matchCollectionRoute, matchFavoritesRoute, matchTrashRoute, matchRootRoute } from './routes';
 import { loadProject, loadCollection, loadFavorites, loadTrash, loadProcess, loadProcessLog } from '~/store/workbench/workbench-actions';
+import { navigateToRootProject } from '~/store/navigation/navigation-action';
 
 export const addRouteChangeHandlers = (history: History, store: RootStore) => {
     const handler = handleLocationChange(store);
@@ -14,6 +15,7 @@ export const addRouteChangeHandlers = (history: History, store: RootStore) => {
 };
 
 const handleLocationChange = (store: RootStore) => ({ pathname }: Location) => {
+    const rootMatch = matchRootRoute(pathname);
     const projectMatch = matchProjectRoute(pathname);
     const collectionMatch = matchCollectionRoute(pathname);
     const favoriteMatch = matchFavoritesRoute(pathname);
@@ -33,5 +35,7 @@ const handleLocationChange = (store: RootStore) => ({ pathname }: Location) => {
         store.dispatch(loadProcess(processMatch.params.id));
     } else if (processLogMatch) {
         store.dispatch(loadProcessLog(processLogMatch.params.id));
+    } else if (rootMatch) {
+        store.dispatch(navigateToRootProject);
     }
 };
index ddb9d29ffdaa05a79f591773412f2723f4500337..0e87769e24a57095f4e74fd578f5c46ed1358c59 100644 (file)
@@ -7,9 +7,10 @@ import { push } from "react-router-redux";
 import { ResourceKind, extractUuidKind } from '~/models/resource';
 import { getCollectionUrl } from "~/models/collection";
 import { getProjectUrl } from "~/models/project";
-
 import { SidePanelTreeCategory } from '../side-panel-tree/side-panel-tree-actions';
 import { Routes, getProcessUrl, getProcessLogUrl } from '~/routes/routes';
+import { RootState } from '~/store/store';
+import { ServiceRepository } from '~/services/services';
 
 export const navigateTo = (uuid: string) =>
     async (dispatch: Dispatch) => {
@@ -20,7 +21,7 @@ export const navigateTo = (uuid: string) =>
             dispatch<any>(navigateToCollection(uuid));
         } else if (kind === ResourceKind.CONTAINER_REQUEST) {
             dispatch<any>(navigateToProcess(uuid));
-        } 
+        }
         if (uuid === SidePanelTreeCategory.FAVORITES) {
             dispatch<any>(navigateToFavorites);
         }
@@ -36,4 +37,11 @@ export const navigateToCollection = compose(push, getCollectionUrl);
 
 export const navigateToProcess = compose(push, getProcessUrl);
 
-export const navigateToProcessLogs = compose(push, getProcessLogUrl);
\ No newline at end of file
+export const navigateToProcessLogs = compose(push, getProcessLogUrl);
+
+export const navigateToRootProject = (dispatch: Dispatch, getState: () => RootState, services: ServiceRepository) => {
+    const rootProjectUuid = services.authService.getUuid();
+    if (rootProjectUuid) {
+        dispatch(navigateToProject(rootProjectUuid));
+    }
+};
\ No newline at end of file
index 030fb353e3d3e52f15dbfd22f478126a87b55807..69b4dd648cc86ea2b60fa2ebc8fb16173c1e6e29 100644 (file)
@@ -11,6 +11,7 @@ import { Breadcrumbs } from "~/components/breadcrumbs/breadcrumbs";
 import { DropdownMenu } from "~/components/dropdown-menu/dropdown-menu";
 import { Button, MenuItem, IconButton } from "@material-ui/core";
 import { User } from "~/models/user";
+import { MemoryRouter } from 'react-router-dom';
 
 configure({ adapter: new Adapter() });
 
@@ -26,9 +27,11 @@ describe("<MainAppBar />", () => {
 
     it("renders all components and the menu for authenticated user if user prop has value", () => {
         const mainAppBar = mount(
-            <MainAppBar
-                {...mockMainAppBarProps({ user })}
-            />
+            <MemoryRouter>
+                <MainAppBar
+                    {...mockMainAppBarProps({ user })}
+                />
+            </MemoryRouter>
         );
         expect(mainAppBar.find(SearchBar)).toHaveLength(1);
         expect(mainAppBar.find(Breadcrumbs)).toHaveLength(1);
@@ -38,9 +41,11 @@ describe("<MainAppBar />", () => {
     it("renders only the menu for anonymous user if user prop is undefined", () => {
         const menuItems = { accountMenu: [], helpMenu: [], anonymousMenu: [{ label: 'Sign in' }] };
         const mainAppBar = mount(
-            <MainAppBar
-                {...mockMainAppBarProps({ user: undefined, menuItems })}
-            />
+            <MemoryRouter>
+                <MainAppBar
+                    {...mockMainAppBarProps({ user: undefined, menuItems })}
+                />
+            </MemoryRouter>
         );
         expect(mainAppBar.find(SearchBar)).toHaveLength(0);
         expect(mainAppBar.find(Breadcrumbs)).toHaveLength(0);
@@ -51,9 +56,11 @@ describe("<MainAppBar />", () => {
     it("communicates with <SearchBar />", () => {
         const onSearch = jest.fn();
         const mainAppBar = mount(
-            <MainAppBar
-                {...mockMainAppBarProps({ searchText: 'search text', searchDebounce: 2000, onSearch, user })}
-            />
+            <MemoryRouter>
+                <MainAppBar
+                    {...mockMainAppBarProps({ searchText: 'search text', searchDebounce: 2000, onSearch, user })}
+                />
+            </MemoryRouter>
         );
         const searchBar = mainAppBar.find(SearchBar);
         expect(searchBar.prop("value")).toBe("search text");
@@ -66,9 +73,11 @@ describe("<MainAppBar />", () => {
         const onMenuItemClick = jest.fn();
         const menuItems = { accountMenu: [{ label: "log out" }], helpMenu: [], anonymousMenu: [] };
         const mainAppBar = mount(
-            <MainAppBar
-                {...mockMainAppBarProps({ menuItems, onMenuItemClick, user })}
-            />
+            <MemoryRouter>
+                <MainAppBar
+                    {...mockMainAppBarProps({ menuItems, onMenuItemClick, user })}
+                />
+            </MemoryRouter>
         );
 
         mainAppBar.find(DropdownMenu).at(0).find(IconButton).simulate("click");
index de6be7e7948fa78fbf8a232db386139a351052a1..04e0fb804a75dba4ac8eae7116d4606617889aa6 100644 (file)
@@ -3,11 +3,24 @@
 // SPDX-License-Identifier: AGPL-3.0
 
 import * as React from "react";
-import { AppBar, Toolbar, Typography, Grid, IconButton, Badge, Button, MenuItem } from "@material-ui/core";
+import { AppBar, Toolbar, Typography, Grid, IconButton, Badge, Button, MenuItem, Tooltip } from "@material-ui/core";
+import { StyleRulesCallback, WithStyles, withStyles } from '@material-ui/core/styles';
+import { ArvadosTheme } from '~/common/custom-theme';
+import { Link } from "react-router-dom";
 import { User, getUserFullname } from "~/models/user";
 import { SearchBar } from "~/components/search-bar/search-bar";
 import { DropdownMenu } from "~/components/dropdown-menu/dropdown-menu";
 import { DetailsIcon, NotificationIcon, UserPanelIcon, HelpIcon } from "~/components/icon/icon";
+import { Routes } from '~/routes/routes';
+
+type CssRules = 'link';
+
+const styles: StyleRulesCallback<CssRules> = (theme: ArvadosTheme) => ({
+    link: {
+        textDecoration: 'none',
+        color: 'inherit'
+    }
+});
 
 export interface MainAppBarMenuItem {
     label: string;
@@ -34,61 +47,68 @@ export interface MainAppBarActionProps {
     onDetailsPanelToggle: () => void;
 }
 
-export type MainAppBarProps = MainAppBarDataProps & MainAppBarActionProps;
+export type MainAppBarProps = MainAppBarDataProps & MainAppBarActionProps & WithStyles<CssRules>;
 
-export const MainAppBar: React.SFC<MainAppBarProps> = (props) => {
-    return <AppBar position="static">
-        <Toolbar>
-            <Grid container justify="space-between">
-                <Grid item xs={3}>
-                    <Typography variant="headline" color="inherit" noWrap>
-                        Arvados 2
-                    </Typography>
-                    <Typography variant="body1" color="inherit" noWrap >
-                        {props.buildInfo}
-                    </Typography>
-                </Grid>
-                <Grid item xs={6} container alignItems="center">
-                    {
-                        props.user && <SearchBar
-                            value={props.searchText}
-                            onSearch={props.onSearch}
-                            debounce={props.searchDebounce}
-                        />
-                    }
+export const MainAppBar = withStyles(styles)(
+    (props: MainAppBarProps) => {
+        return <AppBar position="static">
+            <Toolbar>
+                <Grid container justify="space-between">
+                    <Grid item xs={3}>
+                        <Typography variant="headline" color="inherit" noWrap>
+                            <Link to={Routes.ROOT} className={props.classes.link}>
+                                Arvados 2
+                            </Link>
+                        </Typography>
+                        <Typography variant="body1" color="inherit" noWrap >
+                            {props.buildInfo}
+                        </Typography>
+                    </Grid>
+                    <Grid item xs={6} container alignItems="center">
+                        {
+                            props.user && <SearchBar
+                                value={props.searchText}
+                                onSearch={props.onSearch}
+                                debounce={props.searchDebounce}
+                            />
+                        }
+                    </Grid>
+                    <Grid item xs={3} container alignItems="center" justify="flex-end">
+                        {
+                            props.user ? renderMenuForUser(props) : renderMenuForAnonymous(props)
+                        }
+                    </Grid>
                 </Grid>
-                <Grid item xs={3} container alignItems="center" justify="flex-end">
-                    {
-                        props.user ? renderMenuForUser(props) : renderMenuForAnonymous(props)
-                    }
-                </Grid>
-            </Grid>
-        </Toolbar>
-        <Toolbar >
-            {props.user && <props.breadcrumbs />}
-            {props.user && <IconButton color="inherit" onClick={props.onDetailsPanelToggle}>
-                <DetailsIcon />
-            </IconButton>
-            }
-        </Toolbar>
-    </AppBar>;
-};
+            </Toolbar>
+            <Toolbar >
+                {props.user && <props.breadcrumbs />}
+                {props.user && <IconButton color="inherit" onClick={props.onDetailsPanelToggle}>
+                    <Tooltip title="Additional Info">
+                        <DetailsIcon />
+                    </Tooltip>
+                </IconButton>}
+            </Toolbar>
+        </AppBar>;
+    }
+);
 
 const renderMenuForUser = ({ user, menuItems, onMenuItemClick }: MainAppBarProps) => {
     return (
         <>
             <IconButton color="inherit">
-                <Badge badgeContent={3} color="primary">
-                    <NotificationIcon />
-                </Badge>
+                <Tooltip title="Notification">
+                    <Badge badgeContent={3} color="primary">
+                        <NotificationIcon />
+                    </Badge>
+                </Tooltip>
             </IconButton>
-            <DropdownMenu icon={<UserPanelIcon />} id="account-menu">
+            <DropdownMenu icon={<UserPanelIcon />} id="account-menu" title="Account Management">
                 <MenuItem>
                     {getUserFullname(user)}
                 </MenuItem>
                 {renderMenuItems(menuItems.accountMenu, onMenuItemClick)}
             </DropdownMenu>
-            <DropdownMenu icon={<HelpIcon />} id="help-menu">
+            <DropdownMenu icon={<HelpIcon />} id="help-menu" title="Help">
                 {renderMenuItems(menuItems.helpMenu, onMenuItemClick)}
             </DropdownMenu>
         </>