decouple App and Login components

This commit is contained in:
Jake McDermott
2019-01-02 01:35:34 -05:00
parent 6efd523db2
commit 9c6df68557
3 changed files with 259 additions and 255 deletions

View File

@@ -1,12 +1,12 @@
import React from 'react'; import React from 'react';
import { HashRouter as Router } from 'react-router-dom'; 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';
import { API_LOGOUT, API_CONFIG } from '../src/endpoints'; import { API_LOGOUT, API_CONFIG } from '../src/endpoints';
import Dashboard from '../src/pages/Dashboard'; import Dashboard from '../src/pages/Dashboard';
import Login from '../src/pages/Login';
import { asyncFlush } from '../jest.setup'; import { asyncFlush } from '../jest.setup';
const DEFAULT_ACTIVE_GROUP = 'views_group'; const DEFAULT_ACTIVE_GROUP = 'views_group';
@@ -24,30 +24,6 @@ describe('<App />', () => {
expect(appWrapper.length).toBe(1); expect(appWrapper.length).toBe(1);
}); });
test('renders login page when not authenticated', () => {
api.isAuthenticated = jest.fn();
api.isAuthenticated.mockReturnValue(false);
const appWrapper = mount(<Router><App /></Router>);
const login = appWrapper.find(Login);
expect(login.length).toBe(1);
const dashboard = appWrapper.find(Dashboard);
expect(dashboard.length).toBe(0);
});
test('renders dashboard when authenticated', () => {
api.isAuthenticated = jest.fn();
api.isAuthenticated.mockReturnValue(true);
const appWrapper = mount(<Router><App routeGroups={routeGroups} /></Router>);
const dashboard = appWrapper.find(Dashboard);
expect(dashboard.length).toBe(1);
const login = appWrapper.find(Login);
expect(login.length).toBe(0);
});
test('onNavToggle sets state.isNavOpen to opposite', () => { test('onNavToggle sets state.isNavOpen to opposite', () => {
const appWrapper = shallow(<App />); const appWrapper = shallow(<App />);
expect(appWrapper.state().isNavOpen).toBe(true); expect(appWrapper.state().isNavOpen).toBe(true);

View File

@@ -1,6 +1,4 @@
import React, { Fragment } from 'react'; import React, { Component } from 'react';
import { ConfigContext } from './context';
import { import {
Redirect, Redirect,
Switch, Switch,
@@ -20,15 +18,15 @@ import { global_breakpoint_md as breakpointMd } from '@patternfly/react-tokens';
import api from './api'; import api from './api';
import { API_LOGOUT, API_CONFIG } from './endpoints'; import { API_LOGOUT, API_CONFIG } from './endpoints';
import { ConfigContext } from './context';
import Login from './pages/Login';
import HelpDropdown from './components/HelpDropdown'; 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 NavExpandableGroup from './components/NavExpandableGroup'; import NavExpandableGroup from './components/NavExpandableGroup';
class App extends React.Component { class App extends Component {
constructor(props) { constructor (props) {
super(props); super(props);
// initialize with a closed navbar if window size is small // initialize with a closed navbar if window size is small
@@ -47,13 +45,21 @@ class App extends React.Component {
}; };
onLogoClick = () => { onLogoClick = () => {
this.setState({ activeGroup: 'views_group' }); this.setState({
activeGroup: 'views_group'
});
} }
onDevLogout = async () => { onDevLogout = async () => {
await api.get(API_LOGOUT); await api.get(API_LOGOUT);
this.setState({ activeGroup: 'views_group', activeItem: 'views_group_dashboard' });
} this.setState({
activeGroup: 'views_group',
activeItem: 'views_group_dashboard',
});
window.location.replace('/#/login');
};
async componentDidMount() { async componentDidMount() {
// Grab our config data from the API and store in state // Grab our config data from the API and store in state
@@ -67,80 +73,77 @@ class App extends React.Component {
render () { render () {
const { config, isNavOpen } = this.state; const { config, isNavOpen } = this.state;
const { logo, loginInfo, navLabel, routeGroups = [] } = this.props; const { navLabel = '', routeGroups = [] } = this.props;
const header = (
<PageHeader
showNavToggle
onNavToggle={() => this.onNavToggle()}
logo={(
<TowerLogo
onClick={this.onLogoClick}
/>
)}
toolbar={(
<Toolbar>
<ToolbarGroup>
<ToolbarItem>
<HelpDropdown />
</ToolbarItem>
<ToolbarItem>
<LogoutButton
onDevLogout={() => this.onDevLogout()}
/>
</ToolbarItem>
</ToolbarGroup>
</Toolbar>
)}
/>
);
const sidebar = (
<PageSidebar
isNavOpen={isNavOpen}
nav={(
<Nav aria-label={navLabel}>
<NavList>
{routeGroups.map(params => (
<NavExpandableGroup key={params.groupId} {...params} />
))}
</NavList>
</Nav>
)}
/>
);
return ( return (
api.isAuthenticated () ? ( <ConfigContext.Provider value={config}>
<Switch> <Page
<Route path="/login" render={() => <Redirect to='/home' />} /> usecondensed="True"
<Route exact path="/" render={() => <Redirect to='/home' />} /> header={header}
<Route render={() => ( sidebar={sidebar}
<ConfigContext.Provider value={config}> >
<Page {
usecondensed="True" //
header={( // Extract a flattened array of all route params from the provided route config
<PageHeader // and use it to render route components.
showNavToggle //
onNavToggle={() => this.onNavToggle()} // [{ routes }, { routes }] -> [route, route, route] -> (<Route/><Route/><Route/>)
logo={( //
<TowerLogo routeGroups
onClick={this.onLogoClick} .reduce((allRoutes, { routes }) => allRoutes.concat(routes), [])
/> .map(({ component: Component, path }) => (
)} <Route
toolbar={( key={path}
<Toolbar> path={path}
<ToolbarGroup> render={params => (
<ToolbarItem> <Component {...params } />
<HelpDropdown />
</ToolbarItem>
<ToolbarItem>
<LogoutButton
onDevLogout={() => this.onDevLogout()}
/>
</ToolbarItem>
</ToolbarGroup>
</Toolbar>
)}
/>
)} )}
sidebar={( />
<PageSidebar ))
isNavOpen={isNavOpen} }
nav={( </Page>
<Nav aria-label={navLabel}> </ConfigContext.Provider>
<NavList>
{
routeGroups.map(params => <NavExpandableGroup key={params.groupId} {...params} />)
}
</NavList>
</Nav>
)}
/>
)}
>
{
//
// Extract a flattened array of all route params from the provided route config
// and use it to render route components.
//
// [{ routes }, { routes }] -> [route, route, route] -> (<Route/><Route/><Route/>)
//
routeGroups
.reduce((allRoutes, { routes }) => allRoutes.concat(routes), [])
.map(({ component: Component, path }) => (
<Route key={path} path={path} render={params => <Component {...params } />} />
))
}
</Page>
</ConfigContext.Provider>
)} />
</Switch>
) : (
<Switch>
<Route path="/login" render={() => <Login logo={logo} loginInfo={loginInfo} />} />
<Redirect to="/login" />
</Switch>
)
); );
} }
} }

View File

@@ -1,7 +1,9 @@
import React from 'react'; import React from 'react';
import { render } from 'react-dom'; import { render } from 'react-dom';
import { import {
HashRouter as Router, HashRouter,
Redirect,
Route,
Switch, Switch,
} from 'react-router-dom'; } from 'react-router-dom';
import { import {
@@ -59,8 +61,23 @@ export async function main () {
const { data } = await api.getRoot(); const { data } = await api.getRoot();
const { custom_logo, custom_login_info } = data; const { custom_logo, custom_login_info } = data;
const loginRoutes = (
<Switch>
<Route
path="/login"
render={() => (
<Login
logo={custom_logo}
loginInfo={custom_login_info}
/>
)}
/>
<Redirect to="/login" />
</Switch>
);
render( render(
<Router> <HashRouter>
<I18nProvider <I18nProvider
language={languageWithoutRegionCode} language={languageWithoutRegionCode}
catalogs={catalogs} catalogs={catalogs}
@@ -68,158 +85,166 @@ export async function main () {
<I18n> <I18n>
{({ i18n }) => ( {({ i18n }) => (
<Background> <Background>
<App {!api.isAuthenticated() ? loginRoutes : (
logo={custom_logo} <Switch>
loginInfo={custom_login_info} <Route path="/login" render={() => <Redirect to="/home" />} />
navLabel={i18n._(t`Primary Navigation`)} <Route exact path="/" render={() => <Redirect to="/home" />} />
routeGroups={[ <Route
{ render={() => (
title: i18n._(t`Views`), <App
groupId: 'views_group', navLabel={i18n._(t`Primary Navigation`)}
routes: [ routeGroups={[
{ {
title: i18n._(t`Dashboard`), title: i18n._(t`Views`),
path: '/home', groupId: 'views_group',
component: Dashboard routes: [
}, {
{ title: i18n._(t`Dashboard`),
title: i18n._(t`Jobs`), path: '/home',
path: '/jobs', component: Dashboard
component: Jobs },
}, {
{ title: i18n._(t`Jobs`),
title: i18n._(t`Schedules`), path: '/jobs',
path: '/schedules', component: Jobs
component: Schedules },
}, {
{ title: i18n._(t`Schedules`),
title: i18n._(t`Portal Mode`), path: '/schedules',
path: '/portal', component: Schedules
component: Portal },
}, {
], title: i18n._(t`Portal Mode`),
}, path: '/portal',
{ component: Portal
title: i18n._(t`Resources`), },
groupId: 'resources_group', ],
routes: [ },
{ {
title: i18n._(t`Templates`), title: i18n._(t`Resources`),
path: '/templates', groupId: 'resources_group',
component: Templates routes: [
}, {
{ title: i18n._(t`Templates`),
title: i18n._(t`Credentials`), path: '/templates',
path: '/credentials', component: Templates
component: Credentials },
}, {
{ title: i18n._(t`Credentials`),
title: i18n._(t`Projects`), path: '/credentials',
path: '/projects', component: Credentials
component: Projects },
}, {
{ title: i18n._(t`Projects`),
title: i18n._(t`Inventories`), path: '/projects',
path: '/inventories', component: Projects
component: Inventories },
}, {
{ title: i18n._(t`Inventories`),
title: i18n._(t`Inventory Scripts`), path: '/inventories',
path: '/inventory_scripts', component: Inventories
component: InventoryScripts },
}, {
], title: i18n._(t`Inventory Scripts`),
}, path: '/inventory_scripts',
{ component: InventoryScripts
title: i18n._(t`Access`), },
groupId: 'access_group', ],
routes: [ },
{ {
title: i18n._(t`Organizations`), title: i18n._(t`Access`),
path: '/organizations', groupId: 'access_group',
component: Organizations routes: [
}, {
{ title: i18n._(t`Organizations`),
title: i18n._(t`Users`), path: '/organizations',
path: '/users', component: Organizations
component: Users },
}, {
{ title: i18n._(t`Users`),
title: i18n._(t`Teams`), path: '/users',
path: '/teams', component: Users
component: Teams },
}, {
], title: i18n._(t`Teams`),
}, path: '/teams',
{ component: Teams
title: i18n._(t`Administration`), },
groupId: 'administration_group', ],
routes: [ },
{ {
title: i18n._(t`Credential Types`), title: i18n._(t`Administration`),
path: '/credential_types', groupId: 'administration_group',
component: CredentialTypes routes: [
}, {
{ title: i18n._(t`Credential Types`),
title: i18n._(t`Notifications`), path: '/credential_types',
path: '/notification_templates', component: CredentialTypes
component: NotificationTemplates },
}, {
{ title: i18n._(t`Notifications`),
title: i18n._(t`Management Jobs`), path: '/notification_templates',
path: '/management_jobs', component: NotificationTemplates
component: ManagementJobs },
}, {
{ title: i18n._(t`Management Jobs`),
title: i18n._(t`Instance Groups`), path: '/management_jobs',
path: '/instance_groups', component: ManagementJobs
component: InstanceGroups },
}, {
{ title: i18n._(t`Instance Groups`),
title: i18n._(t`Integrations`), path: '/instance_groups',
path: '/applications', component: InstanceGroups
component: Applications },
}, {
], title: i18n._(t`Integrations`),
}, path: '/applications',
{ component: Applications
title: i18n._(t`Settings`), },
groupId: 'settings_group', ],
routes: [ },
{ {
title: i18n._(t`Authentication`), title: i18n._(t`Settings`),
path: '/auth_settings', groupId: 'settings_group',
component: AuthSettings routes: [
}, {
{ title: i18n._(t`Authentication`),
title: i18n._(t`Jobs`), path: '/auth_settings',
path: '/jobs_settings', component: AuthSettings
component: JobsSettings },
}, {
{ title: i18n._(t`Jobs`),
title: i18n._(t`System`), path: '/jobs_settings',
path: '/system_settings', component: JobsSettings
component: SystemSettings },
}, {
{ title: i18n._(t`System`),
title: i18n._(t`User Interface`), path: '/system_settings',
path: '/ui_settings', component: SystemSettings
component: UISettings },
}, {
{ title: i18n._(t`User Interface`),
title: i18n._(t`License`), path: '/ui_settings',
path: '/license', component: UISettings
component: License },
}, {
], title: i18n._(t`License`),
}, path: '/license',
]} component: License
/> },
],
},
]}
/>
)}
/>
</Switch>
)}
</Background> </Background>
)} )}
</I18n> </I18n>
</I18nProvider> </I18nProvider>
</Router>, el); </HashRouter>, el);
}; };
export default main(); export default main();