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

View File

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