diff --git a/awx/ui_next/src/screens/Template/Template.jsx b/awx/ui_next/src/screens/Template/Template.jsx
index fe997c5176..e1da6c64f5 100644
--- a/awx/ui_next/src/screens/Template/Template.jsx
+++ b/awx/ui_next/src/screens/Template/Template.jsx
@@ -1,8 +1,18 @@
-import React, { Component } from 'react';
+import React, { 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,261 +27,195 @@ 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: loadTemplateAndRoles,
+ } = 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(() => {
+ loadTemplateAndRoles();
+ }, [loadTemplateAndRoles]);
- 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 });
- }
- }
+ const loadSchedules = params => {
+ return JobTemplatesAPI.readSchedules(templateId, params);
+ };
- loadScheduleOptions() {
- const { template } = this.state;
- return JobTemplatesAPI.readScheduleOptions(template.id);
- }
+ const canSeeNotificationsTab = me.is_system_auditor || isNotifAdmin;
- loadSchedules(params) {
- const { template } = this.state;
- return JobTemplatesAPI.readSchedules(template.id, params);
- }
+ const tabsArray = [
+ { name: i18n._(t`Details`), link: `${match.url}/details` },
+ { name: i18n._(t`Access`), link: `${match.url}/access` },
+ ];
- render() {
- const { i18n, location, match, me, setBreadcrumb } = this.props;
- const {
- contentError,
- hasContentLoading,
- isNotifAdmin,
- template,
- } = this.state;
-
- const canSeeNotificationsTab = me.is_system_auditor || isNotifAdmin;
-
- const tabsArray = [
- { name: i18n._(t`Details`), link: `${match.url}/details` },
- { name: i18n._(t`Access`), link: `${match.url}/access` },
- ];
-
- if (canSeeNotificationsTab) {
- tabsArray.push({
- name: i18n._(t`Notifications`),
- link: `${match.url}/notifications`,
- });
- }
-
- if (template) {
- tabsArray.push({
- name: i18n._(t`Schedules`),
- link: `${match.url}/schedules`,
- });
- }
-
- tabsArray.push(
- {
- name: i18n._(t`Completed Jobs`),
- link: `${match.url}/completed_jobs`,
- },
- {
- name: i18n._(t`Survey`),
- link: `${match.url}/survey`,
- }
- );
-
- tabsArray.forEach((tab, n) => {
- tab.id = n;
+ if (canSeeNotificationsTab) {
+ tabsArray.push({
+ name: i18n._(t`Notifications`),
+ link: `${match.url}/notifications`,
});
+ }
- let cardHeader = (
-
-
-
-
-
-
- );
+ if (template) {
+ tabsArray.push({
+ name: i18n._(t`Schedules`),
+ link: `${match.url}/schedules`,
+ });
+ }
- if (
- location.pathname.endsWith('edit') ||
- location.pathname.includes('schedules/')
- ) {
- cardHeader = null;
+ tabsArray.push(
+ {
+ name: i18n._(t`Completed Jobs`),
+ link: `${match.url}/completed_jobs`,
+ },
+ {
+ name: i18n._(t`Survey`),
+ link: `${match.url}/survey`,
}
+ );
- if (!hasContentLoading && contentError) {
- return (
-
-
-
- {contentError.response.status === 404 && (
-
- {i18n._(`Template not found.`)}{' '}
- {i18n._(`View all Templates.`)}
-
- )}
-
-
-
- );
- }
+ tabsArray.forEach((tab, n) => {
+ tab.id = n;
+ });
+ let cardHeader = (
+
+
+
+
+
+
+ );
+ if (
+ location.pathname.endsWith('edit') ||
+ location.pathname.includes('schedules/')
+ ) {
+ cardHeader = null;
+ }
+
+ const contentError = rolesAndTemplateError;
+ if (!hasRolesandTemplateLoading && contentError) {
return (
- {cardHeader}
-
-
- {template && (
- (
-
- )}
- />
+
+ {contentError.response.status === 404 && (
+
+ {i18n._(`Template not found.`)}{' '}
+ {i18n._(`View all Templates.`)}
+
)}
- {template && (
- }
- />
- )}
- {template && (
- (
-
- )}
- />
- )}
- {template && (
- (
-
- )}
- />
- )}
- {canSeeNotificationsTab && (
- (
-
- )}
- />
- )}
- {template?.id && (
-
-
-
- )}
- {template && (
-
-
-
- )}
-
- !hasContentLoading && (
-
- {match.params.id && (
-
- {i18n._(`View Template Details`)}
-
- )}
-
- )
- }
- />
-
+
);
}
+
+ return (
+
+
+ {cardHeader}
+
+
+ {template && (
+
+
+
+ )}
+ {template && (
+
+
+
+ )}
+ {template && (
+
+
+
+ )}
+ {template && (
+
+
+
+ )}
+ {canSeeNotificationsTab && (
+
+
+
+ )}
+ {template?.id && (
+
+
+
+ )}
+ {template && (
+
+
+
+ )}
+ {!hasRolesandTemplateLoading && (
+
+
+ {match.params.id && (
+
+ {i18n._(`View Template Details`)}
+
+ )}
+
+
+ )}
+
+
+
+ );
}
export { Template as _Template };
diff --git a/awx/ui_next/src/screens/Template/Template.test.jsx b/awx/ui_next/src/screens/Template/Template.test.jsx
index 26583f5f0b..cebe757064 100644
--- a/awx/ui_next/src/screens/Template/Template.test.jsx
+++ b/awx/ui_next/src/screens/Template/Template.test.jsx
@@ -1,63 +1,57 @@
import React from 'react';
import { createMemoryHistory } from 'history';
import { JobTemplatesAPI, OrganizationsAPI } from '@api';
+import { act } from 'react-dom/test-utils';
+
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');
-
-JobTemplatesAPI.readDetail.mockResolvedValue({
- data: mockJobTemplateData,
-});
-
-OrganizationsAPI.read.mockResolvedValue({
- data: {
- count: 1,
- next: null,
- previous: null,
- results: [
- {
- id: 1,
- },
- ],
- },
-});
+jest.mock('@api/models/JobTemplates');
+jest.mock('@api/models/Organizations');
const mockMe = {
is_super_user: true,
is_system_auditor: false,
};
-
describe('', () => {
- test('initially renders succesfully', () => {
- mountWithContexts( {}} me={mockMe} />);
+ beforeEach(() => {
+ JobTemplatesAPI.readDetail.mockResolvedValue({
+ data: mockJobTemplateData,
+ });
+
+ OrganizationsAPI.read.mockResolvedValue({
+ data: {
+ count: 1,
+ next: null,
+ previous: null,
+ results: [
+ {
+ id: 1,
+ },
+ ],
+ },
+ });
});
- 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(
- {}} 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('initially renders succesfully', async () => {
+ await act(async () => {
+ mountWithContexts( {}} me={mockMe} />);
+ });
+ });
+ test('When component mounts API is called and the response is put in state', async () => {
+ await act(async () => {
+ mountWithContexts( {}} me={mockMe} />);
+ });
+ expect(JobTemplatesAPI.readDetail).toBeCalled();
+ expect(OrganizationsAPI.read).toBeCalled();
});
test('notifications tab shown for admins', async done => {
- const wrapper = mountWithContexts(
- {}} me={mockMe} />
- );
+ let wrapper;
+ await act(async () => {
+ wrapper = mountWithContexts(
+ {}} me={mockMe} />
+ );
+ });
const tabs = await waitForElement(
wrapper,
@@ -77,9 +71,12 @@ describe('', () => {
},
});
- const wrapper = mountWithContexts(
- {}} me={mockMe} />
- );
+ let wrapper;
+ await act(async () => {
+ wrapper = mountWithContexts(
+ {}} me={mockMe} />
+ );
+ });
const tabs = await waitForElement(
wrapper,
'.pf-c-tabs__item',
@@ -93,24 +90,28 @@ describe('', () => {
const history = createMemoryHistory({
initialEntries: ['/templates/job_template/1/foobar'],
});
- const wrapper = mountWithContexts(
- {}} me={mockMe} />,
- {
- context: {
- router: {
- history,
- route: {
- location: history.location,
- match: {
- params: { id: 1 },
- url: '/templates/job_template/1/foobar',
- path: '/templates/job_template/1/foobar',
+ let wrapper;
+ await act(async () => {
+ wrapper = mountWithContexts(
+ {}} me={mockMe} />,
+ {
+ context: {
+ router: {
+ history,
+ route: {
+ location: history.location,
+ match: {
+ params: { id: 1 },
+ url: '/templates/job_template/1/foobar',
+ path: '/templates/job_template/1/foobar',
+ },
},
},
},
- },
- }
- );
+ }
+ );
+ });
+
await waitForElement(wrapper, 'ContentError', el => el.length === 1);
});
});