Lift config context one level higher.

- Refactor About component to use config context.
- Update About component unit tests.
This commit is contained in:
kialam
2019-01-02 09:03:48 -07:00
committed by Jake McDermott
parent a217a387c6
commit e77d81dd5b
3 changed files with 161 additions and 259 deletions

View File

@@ -31,26 +31,4 @@ describe('<About />', () => {
expect(onAboutModalClose).toBeCalled(); expect(onAboutModalClose).toBeCalled();
aboutWrapper.unmount(); aboutWrapper.unmount();
}); });
test('sets error on api request failure', async () => {
api.get = jest.fn().mockImplementation(() => {
const err = new Error('404 error');
err.response = { status: 404, message: 'problem' };
return Promise.reject(err);
});
aboutWrapper = mount(
<I18nProvider>
<About isOpen />
</I18nProvider>
);
const aboutComponentInstance = aboutWrapper.find(About).instance();
await aboutComponentInstance.componentDidMount();
expect(aboutComponentInstance.state.error.response.status).toBe(404);
aboutWrapper.unmount();
});
test('API Config endpoint is valid', () => {
expect(API_CONFIG).toBeDefined();
});
}); });

View File

@@ -1,11 +1,8 @@
import React, { Fragment } from 'react'; import React, { Fragment } from 'react';
<<<<<<< HEAD
import { I18nProvider, I18n } from '@lingui/react';
import { t } from '@lingui/macro';
=======
import { ConfigContext } from './context'; import { ConfigContext } from './context';
>>>>>>> Implement React Context API import { I18nProvider, I18n } from '@lingui/react';
import { t } from '@lingui/macro';
import { import {
Redirect, Redirect,
Switch, Switch,
@@ -66,8 +63,8 @@ const catalogs = { en, ja };
// This spits out the language and the region. Example: es-US // This spits out the language and the region. Example: es-US
const language = (navigator.languages && navigator.languages[0]) const language = (navigator.languages && navigator.languages[0])
|| navigator.language || navigator.language
|| navigator.userLanguage; || navigator.userLanguage;
const languageWithoutRegionCode = language.toLowerCase().split(/[_-]+/)[0]; const languageWithoutRegionCode = language.toLowerCase().split(/[_-]+/)[0];
@@ -88,6 +85,10 @@ class App extends React.Component {
this.setState(({ isNavOpen }) => ({ isNavOpen: !isNavOpen })); this.setState(({ isNavOpen }) => ({ isNavOpen: !isNavOpen }));
}; };
onLogoClick = () => {
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' });
@@ -136,182 +137,121 @@ class App extends React.Component {
[BackgroundImageSrc.filter]: '/assets/images/background-filter.svg' [BackgroundImageSrc.filter]: '/assets/images/background-filter.svg'
}} }}
/> />
<Switch> <ConfigContext.Provider value={config}>
<ConditionalRedirect shouldRedirect={() => api.isAuthenticated()} redirectPath="/" path="/login" component={() => <Login logo={logo} loginInfo={loginInfo} />} /> <Switch>
<Fragment> <ConditionalRedirect
<Page shouldRedirect={() => api.isAuthenticated()}
header={( redirectPath="/"
<PageHeader path="/login"
logo={<TowerLogo onClick={this.onLogoClick} />} component={() => <Login logo={logo} loginInfo={loginInfo} />}
toolbar={PageToolbar} />
showNavToggle <Fragment>
onNavToggle={this.onNavToggle} <Page
/> header={(
)} <PageHeader
sidebar={( logo={<TowerLogo onClick={this.onLogoClick} />}
<PageSidebar toolbar={PageToolbar}
isNavOpen={isNavOpen} showNavToggle
nav={( onNavToggle={this.onNavToggle}
<Nav aria-label="Primary Navigation"> />
<NavList> )}
<SideNavItems sidebar={(
history={history} <PageSidebar
items={[ isNavOpen={isNavOpen}
{ nav={(
groupName: 'views', <I18n>
title: 'Views', {({ i18n }) => (
routes: [ <Nav aria-label={i18n._(t`Primary Navigation`)}>
{ <NavList>
path: 'home', <NavExpandableGroup
title: 'Dashboard' groupId="views_group"
}, title={i18n._("Views")}
{ routes={[
path: 'jobs', { path: '/home', title: i18n._('Dashboard') },
title: 'Jobs' { path: '/jobs', title: i18n._('Jobs') },
}, { path: '/schedules', title: i18n._('Schedules') },
{ { path: '/portal', title: i18n._('Portal Mode') },
path: 'schedules', ]}
title: 'Schedules' />
}, <NavExpandableGroup
{ groupId="resources_group"
path: 'portal', title={i18n._("Resources")}
title: 'Portal Mode' routes={[
}, { path: '/templates', title: i18n._('Templates') },
] { path: '/credentials', title: i18n._('Credentials') },
}, { path: '/projects', title: i18n._('Projects') },
{ { path: '/inventories', title: i18n._('Inventories') },
groupName: 'resources', { path: '/inventory_scripts', title: i18n._('Inventory Scripts') }
title: 'Resources', ]}
routes: [ />
{ <NavExpandableGroup
path: 'templates', groupId="access_group"
title: 'Templates' title={i18n._("Access")}
}, routes={[
{ { path: '/organizations', title: i18n._('Organizations') },
path: 'credentials', { path: '/users', title: i18n._('Users') },
title: 'Credentials' { path: '/teams', title: i18n._('Teams') }
}, ]}
{ />
path: 'projects', <NavExpandableGroup
title: 'Projects' groupId="administration_group"
}, title={i18n._("Administration")}
{ routes={[
path: 'inventories', { path: '/credential_types', title: i18n._('Credential Types') },
title: 'Inventories' { path: '/notification_templates', title: i18n._('Notifications') },
}, { path: '/management_jobs', title: i18n._('Management Jobs') },
{ { path: '/instance_groups', title: i18n._('Instance Groups') },
path: 'inventory_scripts', { path: '/applications', title: i18n._('Integrations') }
title: 'Inventory Scripts' ]}
} />
] <NavExpandableGroup
}, groupId="settings_group"
{ title={i18n._("Settings")}
groupName: 'access', routes={[
title: 'Access', { path: '/auth_settings', title: i18n._('Authentication') },
routes: [ { path: '/jobs_settings', title: i18n._('Jobs') },
{ { path: '/system_settings', title: i18n._('System') },
path: 'organizations', { path: '/ui_settings', title: i18n._('User Interface') },
title: 'Organizations' { path: '/license', title: i18n._('License') }
}, ]}
{ />
path: 'users', </NavList>
title: 'Users' </Nav>
}, )}
{ </I18n>
path: 'teams', )}
title: 'Teams' />
} )}
] useCondensed
}, >
{ <ConditionalRedirect shouldRedirect={() => !api.isAuthenticated()} redirectPath="/login" exact path="/" component={() => (<Redirect to="/home" />)} />
groupName: 'administration', <ConditionalRedirect shouldRedirect={() => !api.isAuthenticated()} redirectPath="/login" path="/home" component={Dashboard} />
title: 'Administration', <ConditionalRedirect shouldRedirect={() => !api.isAuthenticated()} redirectPath="/login" path="/jobs" component={Jobs} />
routes: [ <ConditionalRedirect shouldRedirect={() => !api.isAuthenticated()} redirectPath="/login" path="/schedules" component={Schedules} />
{ <ConditionalRedirect shouldRedirect={() => !api.isAuthenticated()} redirectPath="/login" path="/portal" component={Portal} />
path: 'credential_types', <ConditionalRedirect shouldRedirect={() => !api.isAuthenticated()} redirectPath="/login" path="/templates" component={Templates} />
title: 'Credential Types', <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} />
path: 'notification_templates', <ConditionalRedirect shouldRedirect={() => !api.isAuthenticated()} redirectPath="/login" path="/inventory_scripts" component={InventoryScripts} />
title: 'Notifications'
},
{
path: 'management_jobs',
title: 'Management Jobs'
},
{
path: 'instance_groups',
title: 'Instance Groups'
},
{
path: 'applications',
title: 'Integrations'
}
]
},
{
groupName: 'settings',
title: 'Settings',
routes: [
{
path: 'auth_settings',
title: 'Authentication',
},
{
path: 'jobs_settings',
title: 'Jobs'
},
{
path: 'system_settings',
title: 'System'
},
{
path: 'ui_settings',
title: 'User Interface'
},
{
path: 'license',
title: 'License'
}
]
}
]}
/>
</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} />
<ConfigContext.Provider value={config}>
<ConditionalRedirect shouldRedirect={() => !api.isAuthenticated()} redirectPath="/login" path="/organizations" component={Organizations} /> <ConditionalRedirect shouldRedirect={() => !api.isAuthenticated()} redirectPath="/login" path="/organizations" component={Organizations} />
</ConfigContext.Provider> <ConditionalRedirect shouldRedirect={() => !api.isAuthenticated()} redirectPath="/login" path="/users" component={Users} />
<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="/teams" component={Teams} /> <ConditionalRedirect shouldRedirect={() => !api.isAuthenticated()} redirectPath="/login" path="/credential_types" component={CredentialTypes} />
<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="/notification_templates" component={NotificationTemplates} /> <ConditionalRedirect shouldRedirect={() => !api.isAuthenticated()} redirectPath="/login" path="/management_jobs" component={ManagementJobs} />
<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="/instance_groups" component={InstanceGroups} /> <ConditionalRedirect shouldRedirect={() => !api.isAuthenticated()} redirectPath="/login" path="/applications" component={Applications} />
<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="/auth_settings" component={AuthSettings} /> <ConditionalRedirect shouldRedirect={() => !api.isAuthenticated()} redirectPath="/login" path="/jobs_settings" component={JobsSettings} />
<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="/system_settings" component={SystemSettings} /> <ConditionalRedirect shouldRedirect={() => !api.isAuthenticated()} redirectPath="/login" path="/ui_settings" component={UISettings} />
<ConditionalRedirect shouldRedirect={() => !api.isAuthenticated()} redirectPath="/login" path="/ui_settings" component={UISettings} /> <ConditionalRedirect shouldRedirect={() => !api.isAuthenticated()} redirectPath="/login" path="/license" component={License} />
<ConditionalRedirect shouldRedirect={() => !api.isAuthenticated()} redirectPath="/login" path="/license" component={License} />
</Page> </Page>
</Fragment> </Fragment>
</Switch> </Switch>
</ConfigContext.Provider>
</Fragment> </Fragment>
</I18nProvider> </I18nProvider>
); );

View File

@@ -5,42 +5,21 @@ import {
AboutModal, AboutModal,
TextContent, TextContent,
TextList, TextList,
TextListItem } from '@patternfly/react-core'; TextListItem
} from '@patternfly/react-core';
import heroImg from '@patternfly/patternfly-next/assets/images/pfbg_992.jpg'; import heroImg from '@patternfly/patternfly-next/assets/images/pfbg_992.jpg';
import brandImg from '../../images/tower-logo-white.svg'; import brandImg from '../../images/tower-logo-white.svg';
import logoImg from '../../images/tower-logo-login.svg'; import logoImg from '../../images/tower-logo-login.svg';
import api from '../api'; import { ConfigContext } from '../context';
import { API_CONFIG } from '../endpoints'; import PropTypes from 'prop-types';
class About extends React.Component { class About extends React.Component {
unmounting = false;
constructor (props) { constructor(props) {
super(props); super(props);
this.state = {
config: {},
error: false
};
} }
async componentDidMount () {
try {
const { data } = await api.get(API_CONFIG);
this.safeSetState({ config: data });
} catch (error) {
this.safeSetState({ error });
}
}
componentWillUnmount () {
this.unmounting = true;
}
safeSetState = obj => !this.unmounting && this.setState(obj);
createSpeechBubble = (version) => { createSpeechBubble = (version) => {
let text = `Tower ${version}`; let text = `Tower ${version}`;
let top = ''; let top = '';
@@ -63,28 +42,27 @@ class About extends React.Component {
onAboutModalClose(); onAboutModalClose();
}; };
render () { render() {
const { isOpen } = this.props; const { isOpen } = this.props;
const { config = {}, error } = this.state;
const { ansible_version = 'loading', version = 'loading' } = config;
return ( return (
<I18n> <I18n>
{({ i18n }) => ( {({ i18n }) => (
<AboutModal <ConfigContext.Consumer>
isOpen={isOpen} {({ ansible_version, version }) =>
onClose={this.handleModalToggle} <AboutModal
productName="Ansible Tower" isOpen={isOpen}
trademark={i18n._(t`Copyright 2018 Red Hat, Inc.`)} onClose={this.handleModalToggle}
brandImageSrc={brandImg} productName="Ansible Tower"
brandImageAlt={i18n._(t`Brand Image`)} trademark={i18n._(t`Copyright 2018 Red Hat, Inc.`)}
logoImageSrc={logoImg} brandImageSrc={brandImg}
logoImageAlt={i18n._(t`AboutModal Logo`)} brandImageAlt={i18n._(t`Brand Image`)}
heroImageSrc={heroImg} logoImageSrc={logoImg}
> logoImageAlt={i18n._(t`AboutModal Logo`)}
<pre> heroImageSrc={heroImg}
{ this.createSpeechBubble(version) } >
{` <pre>
{this.createSpeechBubble(version)}
{`
\\ \\
\\ ^__^ \\ ^__^
(oo)\\_______ (oo)\\_______
@@ -92,22 +70,28 @@ class About extends React.Component {
||----w | ||----w |
|| || || ||
`} `}
</pre> </pre>
<TextContent> <TextContent>
<TextList component="dl"> <TextList component="dl">
<TextListItem component="dt"> <TextListItem component="dt">
<Trans>Ansible Version</Trans> <Trans>Ansible Version</Trans>
</TextListItem> </TextListItem>
<TextListItem component="dd">{ ansible_version }</TextListItem> <TextListItem component="dd">{ansible_version}</TextListItem>
</TextList> </TextList>
</TextContent> </TextContent>
{ error ? <div>error</div> : ''} </AboutModal>
</AboutModal> }
</ConfigContext.Consumer>
)} )}
</I18n> </I18n>
); );
} }
} }
About.contextTypes = {
ansible_version: PropTypes.string,
version: PropTypes.string,
};
export default About; export default About;