mirror of
https://github.com/ansible/awx.git
synced 2026-03-19 01:47:31 -02:30
Merge pull request #68 from ansible/refactor-nav
replace navitem generator with expandable navgroup component
This commit is contained in:
63
__tests__/components/NavExpandableGroup.test.jsx
Normal file
63
__tests__/components/NavExpandableGroup.test.jsx
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { MemoryRouter } from 'react-router-dom';
|
||||||
|
import { mount } from 'enzyme';
|
||||||
|
|
||||||
|
import { Nav } from '@patternfly/react-core';
|
||||||
|
import NavExpandableGroup from '../../src/components/NavExpandableGroup';
|
||||||
|
|
||||||
|
describe('NavExpandableGroup', () => {
|
||||||
|
test('initialization and render', () => {
|
||||||
|
const component = mount(
|
||||||
|
<MemoryRouter initialEntries={['/foo']}>
|
||||||
|
<Nav aria-label="Test Navigation">
|
||||||
|
<NavExpandableGroup
|
||||||
|
groupId="test"
|
||||||
|
title="Test"
|
||||||
|
routes={[
|
||||||
|
{ path: '/foo', title: 'Foo' },
|
||||||
|
{ path: '/bar', title: 'Bar' },
|
||||||
|
{ path: '/fiz', title: 'Fiz' },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</Nav>
|
||||||
|
</MemoryRouter>
|
||||||
|
).find('NavExpandableGroup').instance();
|
||||||
|
|
||||||
|
expect(component.navItemPaths).toEqual(['/foo', '/bar', '/fiz']);
|
||||||
|
expect(component.isActiveGroup()).toEqual(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('isActivePath', () => {
|
||||||
|
const params = [
|
||||||
|
['/fo', '/foo', false],
|
||||||
|
['/foo', '/foo', true],
|
||||||
|
['/foo/1/bar/fiz', '/foo', true],
|
||||||
|
['/foo/1/bar/fiz', 'foo', false],
|
||||||
|
['/foo/1/bar/fiz', 'foo/', false],
|
||||||
|
['/foo/1/bar/fiz', '/bar', false],
|
||||||
|
['/foo/1/bar/fiz', '/fiz', false],
|
||||||
|
];
|
||||||
|
|
||||||
|
params.forEach(([location, path, expected]) => {
|
||||||
|
test(`when location is ${location}', isActivePath('${path}') returns ${expected} `, () => {
|
||||||
|
const component = mount(
|
||||||
|
<MemoryRouter initialEntries={[location]}>
|
||||||
|
<Nav aria-label="Test Navigation">
|
||||||
|
<NavExpandableGroup
|
||||||
|
groupId="test"
|
||||||
|
title="Test"
|
||||||
|
routes={[
|
||||||
|
{ path: '/foo', title: 'Foo' },
|
||||||
|
{ path: '/bar', title: 'Bar' },
|
||||||
|
{ path: '/fiz', title: 'Fiz' },
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</Nav>
|
||||||
|
</MemoryRouter>
|
||||||
|
).find('NavExpandableGroup').instance();
|
||||||
|
|
||||||
|
expect(component.isActivePath(path)).toEqual(expected);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
216
src/App.jsx
216
src/App.jsx
@@ -11,9 +11,7 @@ import {
|
|||||||
BackgroundImage,
|
BackgroundImage,
|
||||||
BackgroundImageSrc,
|
BackgroundImageSrc,
|
||||||
Nav,
|
Nav,
|
||||||
NavExpandable,
|
|
||||||
NavList,
|
NavList,
|
||||||
NavItem,
|
|
||||||
Page,
|
Page,
|
||||||
PageHeader,
|
PageHeader,
|
||||||
PageSidebar,
|
PageSidebar,
|
||||||
@@ -30,6 +28,7 @@ import HelpDropdown from './components/HelpDropdown';
|
|||||||
import LogoutButton from './components/LogoutButton';
|
import LogoutButton from './components/LogoutButton';
|
||||||
import TowerLogo from './components/TowerLogo';
|
import TowerLogo from './components/TowerLogo';
|
||||||
import ConditionalRedirect from './components/ConditionalRedirect';
|
import ConditionalRedirect from './components/ConditionalRedirect';
|
||||||
|
import NavExpandableGroup from './components/NavExpandableGroup';
|
||||||
|
|
||||||
import Applications from './pages/Applications';
|
import Applications from './pages/Applications';
|
||||||
import Credentials from './pages/Credentials';
|
import Credentials from './pages/Credentials';
|
||||||
@@ -67,41 +66,6 @@ const language = (navigator.languages && navigator.languages[0])
|
|||||||
|
|
||||||
const languageWithoutRegionCode = language.toLowerCase().split(/[_-]+/)[0];
|
const languageWithoutRegionCode = language.toLowerCase().split(/[_-]+/)[0];
|
||||||
|
|
||||||
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) {
|
||||||
@@ -160,7 +124,12 @@ class App extends React.Component {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<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={(
|
||||||
@@ -179,127 +148,56 @@ class App extends React.Component {
|
|||||||
{({ i18n }) => (
|
{({ i18n }) => (
|
||||||
<Nav aria-label={i18n._(t`Primary Navigation`)}>
|
<Nav aria-label={i18n._(t`Primary Navigation`)}>
|
||||||
<NavList>
|
<NavList>
|
||||||
<SideNavItems
|
<NavExpandableGroup
|
||||||
history={history}
|
groupId="views_group"
|
||||||
items={[
|
title={i18n._("Views")}
|
||||||
{
|
routes={[
|
||||||
groupName: 'views',
|
{ path: '/home', title: i18n._('Dashboard') },
|
||||||
title: i18n._('Views'),
|
{ path: '/jobs', title: i18n._('Jobs') },
|
||||||
routes: [
|
{ path: '/schedules', title: i18n._('Schedules') },
|
||||||
{
|
{ path: '/portal', title: i18n._('Portal Mode') },
|
||||||
path: 'home',
|
]}
|
||||||
title: i18n._('Dashboard')
|
/>
|
||||||
},
|
<NavExpandableGroup
|
||||||
{
|
groupId="resources_group"
|
||||||
path: 'jobs',
|
title={i18n._("Resources")}
|
||||||
title: i18n._('Jobs')
|
routes={[
|
||||||
},
|
{ path: '/templates', title: i18n._('Templates') },
|
||||||
{
|
{ path: '/credentials', title: i18n._('Credentials') },
|
||||||
path: 'schedules',
|
{ path: '/projects', title: i18n._('Projects') },
|
||||||
title: i18n._('Schedules')
|
{ path: '/inventories', title: i18n._('Inventories') },
|
||||||
},
|
{ path: '/inventory_scripts', title: i18n._('Inventory Scripts') }
|
||||||
{
|
]}
|
||||||
path: 'portal',
|
/>
|
||||||
title: i18n._('Portal Mode')
|
<NavExpandableGroup
|
||||||
},
|
groupId="access_group"
|
||||||
]
|
title={i18n._("Access")}
|
||||||
},
|
routes={[
|
||||||
{
|
{ path: '/organizations', title: i18n._('Organizations') },
|
||||||
groupName: 'resources',
|
{ path: '/users', title: i18n._('Users') },
|
||||||
title: i18n._('Resources'),
|
{ path: '/teams', title: i18n._('Teams') }
|
||||||
routes: [
|
]}
|
||||||
{
|
/>
|
||||||
path: 'templates',
|
<NavExpandableGroup
|
||||||
title: i18n._('Templates')
|
groupId="administration_group"
|
||||||
},
|
title={i18n._("Administration")}
|
||||||
{
|
routes={[
|
||||||
path: 'credentials',
|
{ path: '/credential_types', title: i18n._('Credential Types') },
|
||||||
title: i18n._('Credentials')
|
{ path: '/notification_templates', title: i18n._('Notifications') },
|
||||||
},
|
{ path: '/management_jobs', title: i18n._('Management Jobs') },
|
||||||
{
|
{ path: '/instance_groups', title: i18n._('Instance Groups') },
|
||||||
path: 'projects',
|
{ path: '/applications', title: i18n._('Integrations') }
|
||||||
title: i18n._('Projects')
|
]}
|
||||||
},
|
/>
|
||||||
{
|
<NavExpandableGroup
|
||||||
path: 'inventories',
|
groupId="settings_group"
|
||||||
title: i18n._('Inventories')
|
title={i18n._("Settings")}
|
||||||
},
|
routes={[
|
||||||
{
|
{ path: '/auth_settings', title: i18n._('Authentication') },
|
||||||
path: 'inventory_scripts',
|
{ path: '/jobs_settings', title: i18n._('Jobs') },
|
||||||
title: i18n._('Inventory Scripts')
|
{ path: '/system_settings', title: i18n._('System') },
|
||||||
}
|
{ path: '/ui_settings', title: i18n._('User Interface') },
|
||||||
]
|
{ path: '/license', title: i18n._('License') }
|
||||||
},
|
|
||||||
{
|
|
||||||
groupName: 'access',
|
|
||||||
title: i18n._('Access'),
|
|
||||||
routes: [
|
|
||||||
{
|
|
||||||
path: 'organizations',
|
|
||||||
title: i18n._('Organizations')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'users',
|
|
||||||
title: i18n._('Users')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'teams',
|
|
||||||
title: i18n._('Teams')
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
groupName: 'administration',
|
|
||||||
title: i18n._('Administration'),
|
|
||||||
routes: [
|
|
||||||
{
|
|
||||||
path: 'credential_types',
|
|
||||||
title: i18n._('Credential Types'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'notification_templates',
|
|
||||||
title: i18n._('Notifications')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'management_jobs',
|
|
||||||
title: i18n._('Management Jobs')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'instance_groups',
|
|
||||||
title: i18n._('Instance Groups')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'applications',
|
|
||||||
title: i18n._('Integrations')
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
groupName: 'settings',
|
|
||||||
title: i18n._('Settings'),
|
|
||||||
routes: [
|
|
||||||
{
|
|
||||||
path: 'auth_settings',
|
|
||||||
title: i18n._('Authentication'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'jobs_settings',
|
|
||||||
title: i18n._('Jobs')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'system_settings',
|
|
||||||
title: i18n._('System')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'ui_settings',
|
|
||||||
title: i18n._('User Interface')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'license',
|
|
||||||
title: i18n._('License')
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
</NavList>
|
</NavList>
|
||||||
|
|||||||
53
src/components/NavExpandableGroup.jsx
Normal file
53
src/components/NavExpandableGroup.jsx
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import React, { Component } from 'react';
|
||||||
|
import {
|
||||||
|
withRouter
|
||||||
|
} from 'react-router-dom';
|
||||||
|
import {
|
||||||
|
NavExpandable,
|
||||||
|
NavItem,
|
||||||
|
} from '@patternfly/react-core';
|
||||||
|
|
||||||
|
class NavExpandableGroup extends Component {
|
||||||
|
constructor (props) {
|
||||||
|
super(props);
|
||||||
|
const { routes } = this.props;
|
||||||
|
// Extract a list of paths from the route params and store them for later. This creates
|
||||||
|
// an array of url paths associated with any NavItem component rendered by this component.
|
||||||
|
this.navItemPaths = routes.map(({ path }) => path);
|
||||||
|
}
|
||||||
|
|
||||||
|
isActiveGroup = () => this.navItemPaths.some(this.isActivePath);
|
||||||
|
|
||||||
|
isActivePath = (path) => {
|
||||||
|
const { history } = this.props;
|
||||||
|
|
||||||
|
return history.location.pathname.startsWith(path);
|
||||||
|
};
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { routes, groupId, staticContext, ...rest } = this.props;
|
||||||
|
const isActive = this.isActiveGroup();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<NavExpandable
|
||||||
|
isActive={isActive}
|
||||||
|
isExpanded={isActive}
|
||||||
|
groupId={groupId}
|
||||||
|
{...rest}
|
||||||
|
>
|
||||||
|
{routes.map(({ path, title }) => (
|
||||||
|
<NavItem
|
||||||
|
groupId={groupId}
|
||||||
|
isActive={this.isActivePath(path)}
|
||||||
|
key={path}
|
||||||
|
to={`/#${path}`}
|
||||||
|
>
|
||||||
|
{title}
|
||||||
|
</NavItem>
|
||||||
|
))}
|
||||||
|
</NavExpandable>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withRouter(NavExpandableGroup);
|
||||||
Reference in New Issue
Block a user