diff --git a/awx/ui_next/src/api/models/JobTemplates.js b/awx/ui_next/src/api/models/JobTemplates.js
index 03d2ab099a..5447817eaa 100644
--- a/awx/ui_next/src/api/models/JobTemplates.js
+++ b/awx/ui_next/src/api/models/JobTemplates.js
@@ -75,7 +75,7 @@ class JobTemplates extends SchedulesMixin(
return this.http.get(`${this.baseUrl}${id}/survey_spec/`);
}
- updateSurvey(id, survey = null) {
+ updateSurvey(id, survey) {
return this.http.post(`${this.baseUrl}${id}/survey_spec/`, survey);
}
diff --git a/awx/ui_next/src/api/models/WorkflowJobTemplates.js b/awx/ui_next/src/api/models/WorkflowJobTemplates.js
index e369d71cc9..3074608796 100644
--- a/awx/ui_next/src/api/models/WorkflowJobTemplates.js
+++ b/awx/ui_next/src/api/models/WorkflowJobTemplates.js
@@ -49,7 +49,21 @@ class WorkflowJobTemplates extends SchedulesMixin(NotificationsMixin(Base)) {
}
readAccessList(id, params) {
- return this.http.get(`${this.baseUrl}${id}/access_list/`, { params });
+ return this.http.get(`${this.baseUrl}${id}/access_list/`, {
+ params,
+ });
+ }
+
+ readSurvey(id) {
+ return this.http.get(`${this.baseUrl}${id}/survey_spec/`);
+ }
+
+ updateSurvey(id, survey) {
+ return this.http.post(`${this.baseUrl}${id}/survey_spec/`, survey);
+ }
+
+ destroySurvey(id) {
+ return this.http.delete(`${this.baseUrl}${id}/survey_spec/`);
}
}
diff --git a/awx/ui_next/src/screens/Template/TemplateSurvey.jsx b/awx/ui_next/src/screens/Template/TemplateSurvey.jsx
index aeef6eaf52..dae02aa02a 100644
--- a/awx/ui_next/src/screens/Template/TemplateSurvey.jsx
+++ b/awx/ui_next/src/screens/Template/TemplateSurvey.jsx
@@ -1,8 +1,8 @@
import React, { useState, useEffect, useCallback } from 'react';
-import { Switch, Route } from 'react-router-dom';
+import { Switch, Route, useParams } from 'react-router-dom';
import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro';
-import { JobTemplatesAPI } from '@api';
+import { JobTemplatesAPI, WorkflowJobTemplatesAPI } from '@api';
import ContentError from '@components/ContentError';
import AlertModal from '@components/AlertModal';
import ErrorDetail from '@components/ErrorDetail';
@@ -12,6 +12,7 @@ import { SurveyList, SurveyQuestionAdd, SurveyQuestionEdit } from './Survey';
function TemplateSurvey({ template, i18n }) {
const [surveyEnabled, setSurveyEnabled] = useState(template.survey_enabled);
+ const { templateType } = useParams();
const {
result: survey,
request: fetchSurvey,
@@ -20,9 +21,12 @@ function TemplateSurvey({ template, i18n }) {
setValue: setSurvey,
} = useRequest(
useCallback(async () => {
- const { data } = await JobTemplatesAPI.readSurvey(template.id);
+ const { data } =
+ templateType === 'workflow_job_template'
+ ? await WorkflowJobTemplatesAPI.readSurvey(template.id)
+ : await JobTemplatesAPI.readSurvey(template.id);
return data;
- }, [template.id])
+ }, [template.id, templateType])
);
useEffect(() => {
fetchSurvey();
@@ -31,10 +35,17 @@ function TemplateSurvey({ template, i18n }) {
const { request: updateSurvey, error: updateError } = useRequest(
useCallback(
async updatedSurvey => {
- await JobTemplatesAPI.updateSurvey(template.id, updatedSurvey);
+ if (templateType === 'workflow_job_template') {
+ await WorkflowJobTemplatesAPI.updateSurvey(
+ template.id,
+ updatedSurvey
+ );
+ } else {
+ await JobTemplatesAPI.updateSurvey(template.id, updatedSurvey);
+ }
setSurvey(updatedSurvey);
},
- [template.id, setSurvey]
+ [template.id, setSurvey, templateType]
)
);
const updateSurveySpec = spec => {
diff --git a/awx/ui_next/src/screens/Template/WorkflowJobTemplate.jsx b/awx/ui_next/src/screens/Template/WorkflowJobTemplate.jsx
index 06a1ee2802..9438fda421 100644
--- a/awx/ui_next/src/screens/Template/WorkflowJobTemplate.jsx
+++ b/awx/ui_next/src/screens/Template/WorkflowJobTemplate.jsx
@@ -22,6 +22,7 @@ import {
import WorkflowJobTemplateDetail from './WorkflowJobTemplateDetail';
import WorkflowJobTemplateEdit from './WorkflowJobTemplateEdit';
import { Visualizer } from './WorkflowJobTemplateVisualizer';
+import TemplateSurvey from './TemplateSurvey';
class WorkflowJobTemplate extends Component {
constructor(props) {
@@ -147,6 +148,10 @@ class WorkflowJobTemplate extends Component {
name: i18n._(t`Completed Jobs`),
link: `${match.url}/completed_jobs`,
});
+ tabsArray.push({
+ name: i18n._(t`Survey`),
+ link: `${match.url}/survey`,
+ });
tabsArray.forEach((tab, n) => {
tab.id = n;
@@ -284,6 +289,11 @@ class WorkflowJobTemplate extends Component {
)}
/>
)}
+ {template && (
+
+
+
+ )}
', () => {
const mockMe = {
is_super_user: true,
is_system_auditor: false,
};
+ let wrapper;
+ let history;
beforeAll(() => {
WorkflowJobTemplatesAPI.readDetail.mockResolvedValue({
data: {
@@ -57,16 +64,18 @@ describe('', () => {
},
},
});
- });
- test('calls api to get workflow job template data', async () => {
- const history = createMemoryHistory({
- initialEntries: ['/templates/workflow_job_template/1'],
+ OrganizationsAPI.read.mockResolvedValue({
+ data: { results: [{ id: 1, name: 'Org Foo' }] },
+ });
+ });
+ beforeEach(() => {
+ history = createMemoryHistory({
+ initialEntries: ['/templates/workflow_job_template/1/details'],
});
- let wrapper;
act(() => {
wrapper = mountWithContexts(
(
{}} me={mockMe} />
)}
@@ -75,22 +84,59 @@ describe('', () => {
context: {
router: {
history,
- route: {
- location: history.location,
- match: {
- params: { id: 1 },
- },
- },
},
},
}
);
});
+ });
+
+ test('calls api to get workflow job template data', async () => {
expect(wrapper.find('WorkflowJobTemplate').length).toBe(1);
expect(WorkflowJobTemplatesAPI.readDetail).toBeCalledWith('1');
wrapper.update();
await sleep(0);
expect(WorkflowJobTemplatesAPI.readWebhookKey).toBeCalledWith('1');
expect(CredentialsAPI.readDetail).toBeCalledWith(1234567);
+ expect(OrganizationsAPI.read).toBeCalledWith({
+ page_size: 1,
+ role_level: 'notification_admin_role',
+ });
+ });
+
+ test('renders proper tabs', async () => {
+ const tabs = [
+ 'Details',
+ 'Access',
+ 'Notifications',
+ 'Schedules',
+ 'Visualizer',
+ 'Completed Jobs',
+ 'Survey',
+ ];
+ waitForElement(wrapper, 'EmptyStateBody', el => el.length === 0);
+ wrapper.update();
+ wrapper.find('TabContainer').forEach(tc => {
+ tabs.forEach(t => expect(tc.prop(`aria-label=[${t}]`)));
+ });
+ });
+
+ test('Does not render Notifications tab', async () => {
+ OrganizationsAPI.read.mockResolvedValue({
+ data: { results: [] },
+ });
+ const tabs = [
+ 'Details',
+ 'Access',
+ 'Schedules',
+ 'Visualizer',
+ 'Completed Jobs',
+ 'Survey',
+ ];
+ waitForElement(wrapper, 'EmptyStateBody', el => el.length === 0);
+ wrapper.update();
+ wrapper.find('TabContainer').forEach(tc => {
+ tabs.forEach(t => expect(tc.prop(`aria-label=[${t}]`)));
+ });
});
});