Moves template.jsx over to a functional component.

This commit is contained in:
Alex Corey
2020-03-18 14:12:51 -04:00
parent 0fb800f5d0
commit 56919c5d32
2 changed files with 237 additions and 291 deletions

View File

@@ -1,8 +1,18 @@
import React, { Component } from 'react'; import React, { useState, useEffect, useCallback } from 'react';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import { withI18n } from '@lingui/react'; import { withI18n } from '@lingui/react';
import { Card, CardActions, PageSection } from '@patternfly/react-core'; import { Card, CardActions, PageSection } from '@patternfly/react-core';
import { Switch, Route, Redirect, withRouter, Link } from 'react-router-dom'; import {
Switch,
Route,
Redirect,
withRouter,
Link,
useLocation,
useParams,
useRouteMatch,
} from 'react-router-dom';
import useRequest from '@util/useRequest';
import { TabbedCardHeader } from '@components/Card'; import { TabbedCardHeader } from '@components/Card';
import CardCloseButton from '@components/CardCloseButton'; import CardCloseButton from '@components/CardCloseButton';
@@ -17,92 +27,45 @@ import JobTemplateEdit from './JobTemplateEdit';
import { JobTemplatesAPI, OrganizationsAPI } from '@api'; import { JobTemplatesAPI, OrganizationsAPI } from '@api';
import TemplateSurvey from './TemplateSurvey'; import TemplateSurvey from './TemplateSurvey';
class Template extends Component { function Template({ i18n, me, setBreadcrumb }) {
constructor(props) { const location = useLocation();
super(props); const { id: templateId } = useParams();
const match = useRouteMatch();
this.state = { const {
contentError: null, result: { isNotifAdmin, template },
hasContentLoading: true, isLoading: hasRolesandTemplateLoading,
template: null, error: rolesAndTemplateError,
isNotifAdmin: false, request: loadTempplateAndRoles,
}; } = useRequest(
this.loadTemplate = this.loadTemplate.bind(this); useCallback(async () => {
this.loadTemplateAndRoles = this.loadTemplateAndRoles.bind(this);
this.loadSchedules = this.loadSchedules.bind(this);
this.loadScheduleOptions = this.loadScheduleOptions.bind(this);
}
async componentDidMount() {
await this.loadTemplateAndRoles();
}
async componentDidUpdate(prevProps) {
const { location } = this.props;
if (location !== prevProps.location) {
await this.loadTemplate();
}
}
async loadTemplateAndRoles() {
const { match, setBreadcrumb } = this.props;
const id = parseInt(match.params.id, 10);
this.setState({ contentError: null, hasContentLoading: true });
try {
const [{ data }, notifAdminRes] = await Promise.all([ const [{ data }, notifAdminRes] = await Promise.all([
JobTemplatesAPI.readDetail(id), JobTemplatesAPI.readDetail(templateId),
OrganizationsAPI.read({ OrganizationsAPI.read({
page_size: 1, page_size: 1,
role_level: 'notification_admin_role', role_level: 'notification_admin_role',
}), }),
]); ]);
setBreadcrumb(data); setBreadcrumb(data);
this.setState({
return {
template: data, template: data,
isNotifAdmin: notifAdminRes.data.results.length > 0, isNotifAdmin: notifAdminRes.data.results.length > 0,
}); };
} catch (err) { }, [setBreadcrumb, templateId]),
this.setState({ contentError: err }); { isNotifAdmin: false, template: null }
} finally { );
this.setState({ hasContentLoading: false }); useEffect(() => {
} loadTempplateAndRoles();
} }, [loadTempplateAndRoles]);
async loadTemplate() { const loadScheduleOptions = () => {
const { setBreadcrumb, match } = this.props; return JobTemplatesAPI.readScheduleOptions(templateId);
const { id } = match.params; };
this.setState({ contentError: null, hasContentLoading: true }); const loadSchedules = params => {
try { return JobTemplatesAPI.readSchedules(templateId, params);
const { data } = await JobTemplatesAPI.readDetail(id); };
setBreadcrumb(data);
this.setState({ template: data });
} catch (err) {
this.setState({ contentError: err });
} finally {
this.setState({ hasContentLoading: false });
}
}
loadScheduleOptions() {
const { template } = this.state;
return JobTemplatesAPI.readScheduleOptions(template.id);
}
loadSchedules(params) {
const { template } = this.state;
return JobTemplatesAPI.readSchedules(template.id, params);
}
render() {
const { i18n, location, match, me, setBreadcrumb } = this.props;
const {
contentError,
hasContentLoading,
isNotifAdmin,
template,
} = this.state;
const canSeeNotificationsTab = me.is_system_auditor || isNotifAdmin; const canSeeNotificationsTab = me.is_system_auditor || isNotifAdmin;
@@ -148,7 +111,6 @@ class Template extends Component {
</CardActions> </CardActions>
</TabbedCardHeader> </TabbedCardHeader>
); );
if ( if (
location.pathname.endsWith('edit') || location.pathname.endsWith('edit') ||
location.pathname.includes('schedules/') location.pathname.includes('schedules/')
@@ -156,7 +118,8 @@ class Template extends Component {
cardHeader = null; cardHeader = null;
} }
if (!hasContentLoading && contentError) { const contentError = rolesAndTemplateError;
if (!hasRolesandTemplateLoading && contentError) {
return ( return (
<PageSection> <PageSection>
<Card> <Card>
@@ -184,61 +147,47 @@ class Template extends Component {
exact exact
/> />
{template && ( {template && (
<Route <Route key="details" path="/templates/:templateType/:id/details">
key="details"
path="/templates/:templateType/:id/details"
render={() => (
<JobTemplateDetail <JobTemplateDetail
hasTemplateLoading={hasContentLoading} hasTemplateLoading={hasRolesandTemplateLoading}
template={template} template={template}
/> />
)} </Route>
/>
)} )}
{template && ( {template && (
<Route <Route key="edit" path="/templates/:templateType/:id/edit">
key="edit" <JobTemplateEdit template={template} />
path="/templates/:templateType/:id/edit" </Route>
render={() => <JobTemplateEdit template={template} />}
/>
)} )}
{template && ( {template && (
<Route <Route key="access" path="/templates/:templateType/:id/access">
key="access"
path="/templates/:templateType/:id/access"
render={() => (
<ResourceAccessList <ResourceAccessList
resource={template} resource={template}
apiModel={JobTemplatesAPI} apiModel={JobTemplatesAPI}
/> />
)} </Route>
/>
)} )}
{template && ( {template && (
<Route <Route
key="schedules" key="schedules"
path="/templates/:templateType/:id/schedules" path="/templates/:templateType/:id/schedules"
render={() => ( >
<Schedules <Schedules
setBreadcrumb={setBreadcrumb} setBreadcrumb={setBreadcrumb}
unifiedJobTemplate={template} unifiedJobTemplate={template}
loadSchedules={this.loadSchedules} loadSchedules={loadSchedules}
loadScheduleOptions={this.loadScheduleOptions} loadScheduleOptions={loadScheduleOptions}
/>
)}
/> />
</Route>
)} )}
{canSeeNotificationsTab && ( {canSeeNotificationsTab && (
<Route <Route path="/templates/:templateType/:id/notifications">
path="/templates/:templateType/:id/notifications"
render={() => (
<NotificationList <NotificationList
id={Number(match.params.id)} id={Number(templateId)}
canToggleNotifications={isNotifAdmin} canToggleNotifications={isNotifAdmin}
apiModel={JobTemplatesAPI} apiModel={JobTemplatesAPI}
/> />
)} </Route>
/>
)} )}
{template?.id && ( {template?.id && (
<Route path="/templates/:templateType/:id/completed_jobs"> <Route path="/templates/:templateType/:id/completed_jobs">
@@ -250,11 +199,8 @@ class Template extends Component {
<TemplateSurvey template={template} /> <TemplateSurvey template={template} />
</Route> </Route>
)} )}
<Route {!hasRolesandTemplateLoading && (
key="not-found" <Route key="not-found" path="*">
path="*"
render={() =>
!hasContentLoading && (
<ContentError isNotFound> <ContentError isNotFound>
{match.params.id && ( {match.params.id && (
<Link <Link
@@ -264,15 +210,13 @@ class Template extends Component {
</Link> </Link>
)} )}
</ContentError> </ContentError>
) </Route>
} )}
/>
</Switch> </Switch>
</Card> </Card>
</PageSection> </PageSection>
); );
} }
}
export { Template as _Template }; export { Template as _Template };
export default withI18n()(withRouter(Template)); export default withI18n()(withRouter(Template));

View File

@@ -1,12 +1,22 @@
import React from 'react'; import React from 'react';
import { createMemoryHistory } from 'history'; import { createMemoryHistory } from 'history';
import { JobTemplatesAPI, OrganizationsAPI } from '@api'; import { JobTemplatesAPI, OrganizationsAPI } from '@api';
import { act } from 'react-dom/test-utils';
import { sleep } from '@testUtils/testUtils';
import { mountWithContexts, waitForElement } from '@testUtils/enzymeHelpers'; import { mountWithContexts, waitForElement } from '@testUtils/enzymeHelpers';
import Template, { _Template } from './Template'; import Template from './Template';
import mockJobTemplateData from './shared/data.job_template.json'; import mockJobTemplateData from './shared/data.job_template.json';
jest.mock('@api'); jest.mock('@api/models/JobTemplates');
jest.mock('@api/models/Organizations');
const mockMe = {
is_super_user: true,
is_system_auditor: false,
};
describe('<Template />', () => {
beforeEach(() => {
JobTemplatesAPI.readDetail.mockResolvedValue({ JobTemplatesAPI.readDetail.mockResolvedValue({
data: mockJobTemplateData, data: mockJobTemplateData,
}); });
@@ -23,41 +33,26 @@ OrganizationsAPI.read.mockResolvedValue({
], ],
}, },
}); });
});
const mockMe = { test('initially renders succesfully', async () => {
is_super_user: true, await act(async () => {
is_system_auditor: false,
};
describe('<Template />', () => {
test('initially renders succesfully', () => {
mountWithContexts(<Template setBreadcrumb={() => {}} me={mockMe} />); mountWithContexts(<Template setBreadcrumb={() => {}} me={mockMe} />);
}); });
test('When component mounts API is called and the response is put in state', async done => { });
const loadTemplateAndRoles = jest.spyOn( test('When component mounts API is called and the response is put in state', async () => {
_Template.prototype, await act(async () => {
'loadTemplateAndRoles' mountWithContexts(<Template setBreadcrumb={() => {}} me={mockMe} />);
); });
const wrapper = mountWithContexts( expect(JobTemplatesAPI.readDetail).toBeCalled();
<Template setBreadcrumb={() => {}} me={mockMe} /> expect(OrganizationsAPI.read).toBeCalled();
);
await waitForElement(
wrapper,
'Template',
el => el.state('hasContentLoading') === true
);
expect(loadTemplateAndRoles).toHaveBeenCalled();
await waitForElement(
wrapper,
'Template',
el => el.state('hasContentLoading') === true
);
done();
}); });
test('notifications tab shown for admins', async done => { test('notifications tab shown for admins', async done => {
const wrapper = mountWithContexts( let wrapper;
await act(async () => {
wrapper = mountWithContexts(
<Template setBreadcrumb={() => {}} me={mockMe} /> <Template setBreadcrumb={() => {}} me={mockMe} />
); );
});
const tabs = await waitForElement( const tabs = await waitForElement(
wrapper, wrapper,
@@ -77,9 +72,12 @@ describe('<Template />', () => {
}, },
}); });
const wrapper = mountWithContexts( let wrapper;
await act(async () => {
wrapper = mountWithContexts(
<Template setBreadcrumb={() => {}} me={mockMe} /> <Template setBreadcrumb={() => {}} me={mockMe} />
); );
});
const tabs = await waitForElement( const tabs = await waitForElement(
wrapper, wrapper,
'.pf-c-tabs__item', '.pf-c-tabs__item',
@@ -93,7 +91,9 @@ describe('<Template />', () => {
const history = createMemoryHistory({ const history = createMemoryHistory({
initialEntries: ['/templates/job_template/1/foobar'], initialEntries: ['/templates/job_template/1/foobar'],
}); });
const wrapper = mountWithContexts( let wrapper;
await act(async () => {
wrapper = mountWithContexts(
<Template setBreadcrumb={() => {}} me={mockMe} />, <Template setBreadcrumb={() => {}} me={mockMe} />,
{ {
context: { context: {
@@ -111,6 +111,8 @@ describe('<Template />', () => {
}, },
} }
); );
});
await waitForElement(wrapper, 'ContentError', el => el.length === 1); await waitForElement(wrapper, 'ContentError', el => el.length === 1);
}); });
}); });