Merge pull request #40 from marshmalien/side-nav-bug

Update active nav item based on url
This commit is contained in:
Marliana Lara
2018-12-05 10:32:47 -05:00
committed by GitHub
3 changed files with 243 additions and 303 deletions

View File

@@ -1,4 +1,5 @@
import React from 'react'; import React from 'react';
import { HashRouter as Router } from 'react-router-dom';
import { shallow, mount } from 'enzyme'; import { shallow, mount } from 'enzyme';
import App from '../src/App'; import App from '../src/App';
import api from '../src/api'; import api from '../src/api';
@@ -21,7 +22,7 @@ describe('<App />', () => {
api.isAuthenticated = jest.fn(); api.isAuthenticated = jest.fn();
api.isAuthenticated.mockReturnValue(false); api.isAuthenticated.mockReturnValue(false);
const appWrapper = mount(<App />); const appWrapper = mount(<Router><App /></Router>);
const login = appWrapper.find(Login); const login = appWrapper.find(Login);
expect(login.length).toBe(1); expect(login.length).toBe(1);
@@ -33,7 +34,7 @@ describe('<App />', () => {
api.isAuthenticated = jest.fn(); api.isAuthenticated = jest.fn();
api.isAuthenticated.mockReturnValue(true); api.isAuthenticated.mockReturnValue(true);
const appWrapper = mount(<App />); const appWrapper = mount(<Router><App /></Router>);
const dashboard = appWrapper.find(Dashboard); const dashboard = appWrapper.find(Dashboard);
expect(dashboard.length).toBe(1); expect(dashboard.length).toBe(1);
@@ -41,35 +42,26 @@ describe('<App />', () => {
expect(login.length).toBe(0); expect(login.length).toBe(0);
}); });
test('onNavSelect sets state.activeItem and state.activeGroup', () => {
const appWrapper = shallow(<App />);
appWrapper.instance().onNavSelect({ itemId: 'foo', groupId: 'bar' });
expect(appWrapper.state().activeItem).toBe('foo');
expect(appWrapper.state().activeGroup).toBe('bar');
});
test('onNavToggle sets state.isNavOpen to opposite', () => { test('onNavToggle sets state.isNavOpen to opposite', () => {
const appWrapper = shallow(<App />); const appWrapper = shallow(<App.WrappedComponent />);
expect(appWrapper.state().isNavOpen).toBe(true); expect(appWrapper.state().isNavOpen).toBe(true);
appWrapper.instance().onNavToggle(); appWrapper.instance().onNavToggle();
expect(appWrapper.state().isNavOpen).toBe(false); expect(appWrapper.state().isNavOpen).toBe(false);
}); });
test('onLogoClick sets selected nav back to defaults', () => { test('onLogoClick sets selected nav back to defaults', () => {
const appWrapper = shallow(<App />); const appWrapper = shallow(<App.WrappedComponent />);
appWrapper.setState({ activeGroup: 'foo', activeItem: 'bar' }); appWrapper.setState({ activeGroup: 'foo', activeItem: 'bar' });
expect(appWrapper.state().activeItem).toBe('bar'); expect(appWrapper.state().activeItem).toBe('bar');
expect(appWrapper.state().activeGroup).toBe('foo'); expect(appWrapper.state().activeGroup).toBe('foo');
appWrapper.instance().onLogoClick(); appWrapper.instance().onLogoClick();
expect(appWrapper.state().activeItem).toBe(DEFAULT_ACTIVE_ITEM);
expect(appWrapper.state().activeGroup).toBe(DEFAULT_ACTIVE_GROUP); expect(appWrapper.state().activeGroup).toBe(DEFAULT_ACTIVE_GROUP);
}); });
test('api.logout called from logout button', async () => { test('api.logout called from logout button', async () => {
api.get = jest.fn().mockImplementation(() => Promise.resolve({})); api.get = jest.fn().mockImplementation(() => Promise.resolve({}));
const appWrapper = mount(<App />); const appWrapper = shallow(<App.WrappedComponent />);
const logoutButton = appWrapper.find('LogoutButton'); appWrapper.instance().onDevLogout();
logoutButton.props().onDevLogout();
appWrapper.setState({ activeGroup: 'foo', activeItem: 'bar' }); appWrapper.setState({ activeGroup: 'foo', activeItem: 'bar' });
expect(api.get).toHaveBeenCalledTimes(1); expect(api.get).toHaveBeenCalledTimes(1);
expect(api.get).toHaveBeenCalledWith(API_LOGOUT); expect(api.get).toHaveBeenCalledWith(API_LOGOUT);

View File

@@ -1,8 +1,8 @@
import React, { Fragment } from 'react'; import React, { Fragment } from 'react';
import { import {
HashRouter as Router,
Redirect, Redirect,
Switch, Switch,
withRouter
} from 'react-router-dom'; } from 'react-router-dom';
import { import {
@@ -53,31 +53,58 @@ import Teams from './pages/Teams';
import Templates from './pages/Templates'; import Templates from './pages/Templates';
import Users from './pages/Users'; import Users from './pages/Users';
const SideNavItems = ({ items, history }) => {
const currentPath = history.location.pathname.split('/')[1];
let activeGroup;
if (currentPath !== '') {
[{ groupName: activeGroup }] = items
.map(({ groupName, routes }) => ({
groupName,
paths: routes.map(({ path }) => path)
}))
.filter(({ paths }) => paths.indexOf(currentPath) > -1);
} else {
activeGroup = 'views';
}
return (items.map(({ title, groupName, routes }) => (
<NavExpandable
key={groupName}
title={title}
groupId={`${groupName}_group`}
isActive={`${activeGroup}_group` === `${groupName}_group`}
isExpanded={`${activeGroup}_group` === `${groupName}_group`}
>
{routes.map(({ path, title: itemTitle }) => (
<NavItem
key={path}
to={`#/${path}`}
groupId={`${groupName}_group`}
isActive={currentPath === path}
>
{itemTitle}
</NavItem>
))}
</NavExpandable>
)));
};
class App extends React.Component { class App extends React.Component {
constructor (props) { constructor (props) {
super(props); super(props);
const isNavOpen = typeof window !== 'undefined' && window.innerWidth >= parseInt(breakpointMd.value, 10); const isNavOpen = typeof window !== 'undefined' && window.innerWidth >= parseInt(breakpointMd.value, 10);
this.state = { this.state = {
isNavOpen, isNavOpen
activeGroup: 'views_group',
activeItem: 'views_group_dashboard'
}; };
} }
onNavSelect = result => {
this.setState({
activeItem: result.itemId,
activeGroup: result.groupId
});
};
onNavToggle = () => { onNavToggle = () => {
this.setState(({ isNavOpen }) => ({ isNavOpen: !isNavOpen })); this.setState(({ isNavOpen }) => ({ isNavOpen: !isNavOpen }));
}; };
onLogoClick = () => { onLogoClick = () => {
this.setState({ activeGroup: 'views_group', activeItem: 'views_group_dashboard' }); this.setState({ activeGroup: 'views_group' });
} }
onDevLogout = async () => { onDevLogout = async () => {
@@ -86,8 +113,8 @@ class App extends React.Component {
} }
render () { render () {
const { activeItem, activeGroup, isNavOpen } = this.state; const { isNavOpen } = this.state;
const { logo, loginInfo } = this.props; const { logo, loginInfo, history } = this.props;
const PageToolbar = ( const PageToolbar = (
<Toolbar> <Toolbar>
@@ -103,279 +130,197 @@ class App extends React.Component {
); );
return ( return (
<Router> <Fragment>
<Fragment> <BackgroundImage
<BackgroundImage src={{
src={{ [BackgroundImageSrc.lg]: '/assets/images/pfbg_1200.jpg',
[BackgroundImageSrc.lg]: '/assets/images/pfbg_1200.jpg', [BackgroundImageSrc.md]: '/assets/images/pfbg_992.jpg',
[BackgroundImageSrc.md]: '/assets/images/pfbg_992.jpg', [BackgroundImageSrc.md2x]: '/assets/images/pfbg_992@2x.jpg',
[BackgroundImageSrc.md2x]: '/assets/images/pfbg_992@2x.jpg', [BackgroundImageSrc.sm]: '/assets/images/pfbg_768.jpg',
[BackgroundImageSrc.sm]: '/assets/images/pfbg_768.jpg', [BackgroundImageSrc.sm2x]: '/assets/images/pfbg_768@2x.jpg',
[BackgroundImageSrc.sm2x]: '/assets/images/pfbg_768@2x.jpg', [BackgroundImageSrc.xl]: '/assets/images/pfbg_2000.jpg',
[BackgroundImageSrc.xl]: '/assets/images/pfbg_2000.jpg', [BackgroundImageSrc.xs]: '/assets/images/pfbg_576.jpg',
[BackgroundImageSrc.xs]: '/assets/images/pfbg_576.jpg', [BackgroundImageSrc.xs2x]: '/assets/images/pfbg_576@2x.jpg',
[BackgroundImageSrc.xs2x]: '/assets/images/pfbg_576@2x.jpg', [BackgroundImageSrc.filter]: '/assets/images/background-filter.svg'
[BackgroundImageSrc.filter]: '/assets/images/background-filter.svg' }}
}} />
/> <Switch>
<Switch> <ConditionalRedirect shouldRedirect={() => api.isAuthenticated()} redirectPath="/" path="/login" component={() => <Login logo={logo} loginInfo={loginInfo} />} />
<ConditionalRedirect shouldRedirect={() => api.isAuthenticated()} redirectPath="/" path="/login" component={() => <Login logo={logo} loginInfo={loginInfo} />} /> <Fragment>
<Fragment> <Page
<Page header={(
header={( <PageHeader
<PageHeader logo={<TowerLogo onClick={this.onLogoClick} />}
logo={<TowerLogo onClick={this.onLogoClick} />} toolbar={PageToolbar}
toolbar={PageToolbar} showNavToggle
showNavToggle onNavToggle={this.onNavToggle}
onNavToggle={this.onNavToggle} />
/> )}
)} sidebar={(
sidebar={( <PageSidebar
<PageSidebar isNavOpen={isNavOpen}
isNavOpen={isNavOpen} nav={(
nav={( <Nav aria-label="Primary Navigation">
<Nav onSelect={this.onNavSelect} aria-label="Primary Navigation"> <NavList>
<NavList> <SideNavItems
<NavExpandable history={history}
title="Views" items={[
groupId="views_group" {
isActive={activeGroup === 'views_group'} groupName: 'views',
isExpanded={activeGroup === 'views_group'} title: 'Views',
> routes: [
<NavItem {
to="#/home" path: 'home',
groupId="views_group" title: 'Dashboard'
itemId="views_group_dashboard" },
isActive={activeItem === 'views_group_dashboard'} {
> path: 'jobs',
Dashboard title: 'Jobs'
</NavItem> },
<NavItem {
to="#/jobs" path: 'schedules',
groupId="views_group" title: 'Schedules'
itemId="views_group_jobs" },
isActive={activeItem === 'views_group_jobs'} {
> path: 'portal',
Jobs title: 'Portal Mode'
</NavItem> },
<NavItem ]
to="#/schedules" },
groupId="views_group" {
itemId="views_group_schedules" groupName: 'resources',
isActive={activeItem === 'views_group_schedules'} title: 'Resources',
> routes: [
Schedules {
</NavItem> path: 'templates',
<NavItem title: 'Templates'
to="#/portal" },
groupId="views_group" {
itemId="views_group_portal" path: 'credentials',
isActive={activeItem === 'views_group_portal'} title: 'Credentials'
> },
My View {
</NavItem> path: 'projects',
</NavExpandable> title: 'Projects'
<NavExpandable },
title="Resources" {
groupId="resources_group" path: 'inventories',
isActive={activeGroup === 'resources_group'} title: 'Inventories'
isExpanded={activeGroup === 'resources_group'} },
> {
<NavItem path: 'inventory_scripts',
to="#/templates" title: 'Inventory Scripts'
groupId="resources_group" }
itemId="resources_group_templates" ]
isActive={activeItem === 'resources_group_templates'} },
> {
Templates groupName: 'access',
</NavItem> title: 'Access',
<NavItem routes: [
to="#/credentials" {
groupId="resources_group" path: 'organizations',
itemId="resources_group_credentials" title: 'Organizations'
isActive={activeItem === 'resources_group_credentials'} },
> {
Credentials path: 'users',
</NavItem> title: 'Users'
<NavItem },
to="#/projects" {
groupId="resources_group" path: 'teams',
itemId="resources_group_projects" title: 'Teams'
isActive={activeItem === 'resources_group_projects'} }
> ]
Projects },
</NavItem> {
<NavItem groupName: 'administration',
to="#/inventories" title: 'Administration',
groupId="resources_group" routes: [
itemId="resources_group_inventories" {
isActive={activeItem === 'resources_group_inventories'} path: 'credential_types',
> title: 'Credential Types',
Inventories },
</NavItem> {
<NavItem path: 'notification_templates',
to="#/inventory_scripts" title: 'Notifications'
groupId="resources_group" },
itemId="resources_group_inventory_scripts" {
isActive={activeItem === 'resources_group_inventory_scripts'} path: 'management_jobs',
> title: 'Management Jobs'
Inventory Scripts },
</NavItem> {
</NavExpandable> path: 'instance_groups',
<NavExpandable title: 'Instance Groups'
title="Access" },
groupId="access_group" {
isActive={activeGroup === 'access_group'} path: 'applications',
isExpanded={activeGroup === 'access_group'} title: 'Integrations'
> }
<NavItem ]
to="#/organizations" },
groupId="access_group" {
itemId="access_group_organizations" groupName: 'settings',
isActive={activeItem === 'access_group_organizations'} title: 'Settings',
> routes: [
Organizations {
</NavItem> path: 'auth_settings',
<NavItem title: 'Authentication',
to="#/users" },
groupId="access_group" {
itemId="access_group_users" path: 'jobs_settings',
isActive={activeItem === 'access_group_users'} title: 'Jobs'
> },
Users {
</NavItem> path: 'system_settings',
<NavItem title: 'System'
to="#/teams" },
groupId="access_group" {
itemId="access_group_teams" path: 'ui_settings',
isActive={activeItem === 'access_group_teams'} title: 'User Interface'
> },
Teams {
</NavItem> path: 'license',
</NavExpandable> title: 'License'
<NavExpandable }
title="Administration" ]
groupId="administration_group" }
isActive={activeGroup === 'administration_group'} ]}
isExpanded={activeGroup === 'administration_group'} />
> </NavList>
<NavItem </Nav>
to="#/credential_types" )}
groupId="administration_group" />
itemId="administration_group_credential_types" )}
isActive={activeItem === 'administration_group_credential_types'} useCondensed
> >
Credential Types <ConditionalRedirect shouldRedirect={() => !api.isAuthenticated()} redirectPath="/login" exact path="/" component={() => (<Redirect to="/home" />)} />
</NavItem> <ConditionalRedirect shouldRedirect={() => !api.isAuthenticated()} redirectPath="/login" path="/home" component={Dashboard} />
<NavItem <ConditionalRedirect shouldRedirect={() => !api.isAuthenticated()} redirectPath="/login" path="/jobs" component={Jobs} />
to="#/notification_templates" <ConditionalRedirect shouldRedirect={() => !api.isAuthenticated()} redirectPath="/login" path="/schedules" component={Schedules} />
groupId="administration_group" <ConditionalRedirect shouldRedirect={() => !api.isAuthenticated()} redirectPath="/login" path="/portal" component={Portal} />
itemId="administration_group_notification_templates" <ConditionalRedirect shouldRedirect={() => !api.isAuthenticated()} redirectPath="/login" path="/templates" component={Templates} />
isActive={activeItem === 'administration_group_notification_templates'} <ConditionalRedirect shouldRedirect={() => !api.isAuthenticated()} redirectPath="/login" path="/credentials" component={Credentials} />
> <ConditionalRedirect shouldRedirect={() => !api.isAuthenticated()} redirectPath="/login" path="/projects" component={Projects} />
Notification Templates <ConditionalRedirect shouldRedirect={() => !api.isAuthenticated()} redirectPath="/login" path="/inventories" component={Inventories} />
</NavItem> <ConditionalRedirect shouldRedirect={() => !api.isAuthenticated()} redirectPath="/login" path="/inventory_scripts" component={InventoryScripts} />
<NavItem <ConditionalRedirect shouldRedirect={() => !api.isAuthenticated()} redirectPath="/login" path="/organizations" component={Organizations} />
to="#/management_jobs" <ConditionalRedirect shouldRedirect={() => !api.isAuthenticated()} redirectPath="/login" path="/users" component={Users} />
groupId="administration_group" <ConditionalRedirect shouldRedirect={() => !api.isAuthenticated()} redirectPath="/login" path="/teams" component={Teams} />
itemId="administration_group_management_jobs" <ConditionalRedirect shouldRedirect={() => !api.isAuthenticated()} redirectPath="/login" path="/credential_types" component={CredentialTypes} />
isActive={activeItem === 'administration_group_management_jobs'} <ConditionalRedirect shouldRedirect={() => !api.isAuthenticated()} redirectPath="/login" path="/notification_templates" component={NotificationTemplates} />
> <ConditionalRedirect shouldRedirect={() => !api.isAuthenticated()} redirectPath="/login" path="/management_jobs" component={ManagementJobs} />
Management Jobs <ConditionalRedirect shouldRedirect={() => !api.isAuthenticated()} redirectPath="/login" path="/instance_groups" component={InstanceGroups} />
</NavItem> <ConditionalRedirect shouldRedirect={() => !api.isAuthenticated()} redirectPath="/login" path="/applications" component={Applications} />
<NavItem <ConditionalRedirect shouldRedirect={() => !api.isAuthenticated()} redirectPath="/login" path="/auth_settings" component={AuthSettings} />
to="#/instance_groups" <ConditionalRedirect shouldRedirect={() => !api.isAuthenticated()} redirectPath="/login" path="/jobs_settings" component={JobsSettings} />
groupId="administration_group" <ConditionalRedirect shouldRedirect={() => !api.isAuthenticated()} redirectPath="/login" path="/system_settings" component={SystemSettings} />
itemId="administration_group_instance_groups" <ConditionalRedirect shouldRedirect={() => !api.isAuthenticated()} redirectPath="/login" path="/ui_settings" component={UISettings} />
isActive={activeItem === 'administration_group_instance_groups'} <ConditionalRedirect shouldRedirect={() => !api.isAuthenticated()} redirectPath="/login" path="/license" component={License} />
> </Page>
Instance Groups </Fragment>
</NavItem> </Switch>
<NavItem </Fragment>
to="#/applications"
groupId="administration_group"
itemId="administration_group_applications"
isActive={activeItem === 'administration_group_applications'}
>
Applications
</NavItem>
</NavExpandable>
<NavExpandable
title="Settings"
groupId="settings_group"
isActive={activeGroup === 'settings_group'}
isExpanded={activeGroup === 'settings_group'}
>
<NavItem
to="#/auth_settings"
groupId="settings_group"
itemId="settings_group_auth"
isActive={activeItem === 'settings_group_auth'}
>
Authentication
</NavItem>
<NavItem
to="#/jobs_settings"
groupId="settings_group"
itemId="settings_group_jobs"
isActive={activeItem === 'settings_group_jobs'}
>
Jobs
</NavItem>
<NavItem
to="#/system_settings"
groupId="settings_group"
itemId="settings_group_system"
isActive={activeItem === 'settings_group_system'}
>
System
</NavItem>
<NavItem
to="#/ui_settings"
groupId="settings_group"
itemId="settings_group_ui"
isActive={activeItem === 'settings_group_ui'}
>
User Interface
</NavItem>
</NavExpandable>
</NavList>
</Nav>
)}
/>
)}
useCondensed
>
<ConditionalRedirect shouldRedirect={() => !api.isAuthenticated()} redirectPath="/login" exact path="/" component={() => (<Redirect to="/home" />)} />
<ConditionalRedirect shouldRedirect={() => !api.isAuthenticated()} redirectPath="/login" path="/home" component={Dashboard} />
<ConditionalRedirect shouldRedirect={() => !api.isAuthenticated()} redirectPath="/login" path="/jobs" component={Jobs} />
<ConditionalRedirect shouldRedirect={() => !api.isAuthenticated()} redirectPath="/login" path="/schedules" component={Schedules} />
<ConditionalRedirect shouldRedirect={() => !api.isAuthenticated()} redirectPath="/login" path="/portal" component={Portal} />
<ConditionalRedirect shouldRedirect={() => !api.isAuthenticated()} redirectPath="/login" path="/templates" component={Templates} />
<ConditionalRedirect shouldRedirect={() => !api.isAuthenticated()} redirectPath="/login" path="/credentials" component={Credentials} />
<ConditionalRedirect shouldRedirect={() => !api.isAuthenticated()} redirectPath="/login" path="/projects" component={Projects} />
<ConditionalRedirect shouldRedirect={() => !api.isAuthenticated()} redirectPath="/login" path="/inventories" component={Inventories} />
<ConditionalRedirect shouldRedirect={() => !api.isAuthenticated()} redirectPath="/login" path="/inventory_scripts" component={InventoryScripts} />
<ConditionalRedirect shouldRedirect={() => !api.isAuthenticated()} redirectPath="/login" path="/organizations" component={Organizations} />
<ConditionalRedirect shouldRedirect={() => !api.isAuthenticated()} redirectPath="/login" path="/users" component={Users} />
<ConditionalRedirect shouldRedirect={() => !api.isAuthenticated()} redirectPath="/login" path="/teams" component={Teams} />
<ConditionalRedirect shouldRedirect={() => !api.isAuthenticated()} redirectPath="/login" path="/credential_types" component={CredentialTypes} />
<ConditionalRedirect shouldRedirect={() => !api.isAuthenticated()} redirectPath="/login" path="/notification_templates" component={NotificationTemplates} />
<ConditionalRedirect shouldRedirect={() => !api.isAuthenticated()} redirectPath="/login" path="/management_jobs" component={ManagementJobs} />
<ConditionalRedirect shouldRedirect={() => !api.isAuthenticated()} redirectPath="/login" path="/instance_groups" component={InstanceGroups} />
<ConditionalRedirect shouldRedirect={() => !api.isAuthenticated()} redirectPath="/login" path="/applications" component={Applications} />
<ConditionalRedirect shouldRedirect={() => !api.isAuthenticated()} redirectPath="/login" path="/auth_settings" component={AuthSettings} />
<ConditionalRedirect shouldRedirect={() => !api.isAuthenticated()} redirectPath="/login" path="/jobs_settings" component={JobsSettings} />
<ConditionalRedirect shouldRedirect={() => !api.isAuthenticated()} redirectPath="/login" path="/system_settings" component={SystemSettings} />
<ConditionalRedirect shouldRedirect={() => !api.isAuthenticated()} redirectPath="/login" path="/ui_settings" component={UISettings} />
<ConditionalRedirect shouldRedirect={() => !api.isAuthenticated()} redirectPath="/login" path="/license" component={License} />
</Page>
</Fragment>
</Switch>
</Fragment>
</Router>
); );
} }
} }
export default App; export default withRouter(App);

View File

@@ -1,6 +1,9 @@
import React from 'react'; import React from 'react';
import { render } from 'react-dom'; import { render } from 'react-dom';
import {
HashRouter as Router
} from 'react-router-dom';
import App from './App'; import App from './App';
import api from './api'; import api from './api';
import { API_ROOT } from './endpoints'; import { API_ROOT } from './endpoints';
@@ -15,8 +18,8 @@ import './components/DataListToolbar/styles.scss';
const el = document.getElementById('app'); const el = document.getElementById('app');
const main = async () => { const main = async () => {
const { custom_logo, custom_login_info } = await api.get(API_ROOT); const { custom_logo, custom_login_info } = await api.get(API_ROOT);
render(<App logo={custom_logo} loginInfo={custom_login_info} />, el); render(<Router><App logo={custom_logo} loginInfo={custom_login_info} /></Router>, el);
}; };
main(); main();