diff --git a/awx/ui_next/src/screens/Inventory/InventoryAdd/InventoryAdd.jsx b/awx/ui_next/src/screens/Inventory/InventoryAdd/InventoryAdd.jsx
index db7f5be27c..e01a336390 100644
--- a/awx/ui_next/src/screens/Inventory/InventoryAdd/InventoryAdd.jsx
+++ b/awx/ui_next/src/screens/Inventory/InventoryAdd/InventoryAdd.jsx
@@ -57,7 +57,9 @@ function InventoryAdd() {
);
await Promise.all(associatePromises);
}
- const url = history.location.pathname.search('smart')
+ const url = history.location.pathname.startsWith(
+ '/inventories/smart_inventory'
+ )
? `/inventories/smart_inventory/${inventoryId}/details`
: `/inventories/inventory/${inventoryId}/details`;
diff --git a/awx/ui_next/src/screens/Inventory/InventoryAdd/InventoryAdd.test.jsx b/awx/ui_next/src/screens/Inventory/InventoryAdd/InventoryAdd.test.jsx
index 1616d17499..26b0619a7d 100644
--- a/awx/ui_next/src/screens/Inventory/InventoryAdd/InventoryAdd.test.jsx
+++ b/awx/ui_next/src/screens/Inventory/InventoryAdd/InventoryAdd.test.jsx
@@ -41,8 +41,7 @@ describe('', () => {
test('Initially renders successfully', () => {
expect(wrapper.length).toBe(1);
});
-
- test('handleSubmit should call the api', async () => {
+ test('handleSubmit should call the api and redirect to details page', async () => {
const instanceGroups = [{ name: 'Bizz', id: 1 }, { name: 'Buzz', id: 2 }];
await waitForElement(wrapper, 'isLoading', el => el.length === 0);
@@ -64,6 +63,7 @@ describe('', () => {
IG.id
)
);
+ expect(history.location.pathname).toBe('/inventories/inventory/13/details');
});
test('handleCancel should return the user back to the inventories list', async () => {
diff --git a/awx/ui_next/src/screens/Template/JobTemplateAdd/JobTemplateAdd.test.jsx b/awx/ui_next/src/screens/Template/JobTemplateAdd/JobTemplateAdd.test.jsx
index adfeb31b37..508b85de82 100644
--- a/awx/ui_next/src/screens/Template/JobTemplateAdd/JobTemplateAdd.test.jsx
+++ b/awx/ui_next/src/screens/Template/JobTemplateAdd/JobTemplateAdd.test.jsx
@@ -26,6 +26,7 @@ const jobTemplateData = {
allow_simultaneous: false,
use_fact_cache: false,
host_config_key: '',
+ scm_branch: '',
};
describe('', () => {
diff --git a/awx/ui_next/src/screens/Template/JobTemplateDetail/JobTemplateDetail.jsx b/awx/ui_next/src/screens/Template/JobTemplateDetail/JobTemplateDetail.jsx
index 8783e93297..94955e78f5 100644
--- a/awx/ui_next/src/screens/Template/JobTemplateDetail/JobTemplateDetail.jsx
+++ b/awx/ui_next/src/screens/Template/JobTemplateDetail/JobTemplateDetail.jsx
@@ -196,6 +196,7 @@ class JobTemplateDetail extends Component {
) : (
renderMissingDataDetail(i18n._(t`Project`))
)}
+
diff --git a/awx/ui_next/src/screens/Template/JobTemplateDetail/JobTemplateDetail.test.jsx b/awx/ui_next/src/screens/Template/JobTemplateDetail/JobTemplateDetail.test.jsx
index a06d1e8db5..7ae6b0319c 100644
--- a/awx/ui_next/src/screens/Template/JobTemplateDetail/JobTemplateDetail.test.jsx
+++ b/awx/ui_next/src/screens/Template/JobTemplateDetail/JobTemplateDetail.test.jsx
@@ -143,4 +143,19 @@ describe('', () => {
template.summary_fields.credentials[0]
);
});
+ test('should render SCM_Branch', async () => {
+ const mockTemplate = { ...template };
+ mockTemplate.scm_branch = 'Foo branch';
+
+ const wrapper = mountWithContexts(
+
+ );
+ await waitForElement(
+ wrapper,
+ 'JobTemplateDetail',
+ el => el.state('hasContentLoading') === false
+ );
+ const SCMBranch = wrapper.find('Detail[label="SCM Branch"]');
+ expect(SCMBranch.prop('value')).toBe('Foo branch');
+ });
});
diff --git a/awx/ui_next/src/screens/Template/JobTemplateEdit/JobTemplateEdit.test.jsx b/awx/ui_next/src/screens/Template/JobTemplateEdit/JobTemplateEdit.test.jsx
index e7750e2a87..42fd375188 100644
--- a/awx/ui_next/src/screens/Template/JobTemplateEdit/JobTemplateEdit.test.jsx
+++ b/awx/ui_next/src/screens/Template/JobTemplateEdit/JobTemplateEdit.test.jsx
@@ -29,6 +29,7 @@ const mockJobTemplate = {
allow_simultaneous: false,
use_fact_cache: false,
host_config_key: '',
+ scm_branch: '',
summary_fields: {
user_capabilities: {
edit: true,
@@ -161,6 +162,11 @@ describe('', () => {
JobTemplatesAPI.readInstanceGroups.mockReturnValue({
data: { results: mockInstanceGroups },
});
+ ProjectsAPI.readDetail.mockReturnValue({
+ id: 1,
+ allow_override: true,
+ name: 'foo',
+ });
});
afterEach(() => {
diff --git a/awx/ui_next/src/screens/Template/shared/JobTemplateForm.jsx b/awx/ui_next/src/screens/Template/shared/JobTemplateForm.jsx
index b62f7603ec..c2f1ca9c4c 100644
--- a/awx/ui_next/src/screens/Template/shared/JobTemplateForm.jsx
+++ b/awx/ui_next/src/screens/Template/shared/JobTemplateForm.jsx
@@ -28,7 +28,7 @@ import {
ProjectLookup,
MultiCredentialsLookup,
} from '@components/Lookup';
-import { JobTemplatesAPI } from '@api';
+import { JobTemplatesAPI, ProjectsAPI } from '@api';
import LabelSelect from './LabelSelect';
import PlaybookSelect from './PlaybookSelect';
@@ -81,16 +81,31 @@ class JobTemplateForm extends Component {
this.loadRelatedInstanceGroups = this.loadRelatedInstanceGroups.bind(this);
this.handleProjectUpdate = this.handleProjectUpdate.bind(this);
this.setContentError = this.setContentError.bind(this);
+ this.fetchProject = this.fetchProject.bind(this);
}
componentDidMount() {
const { validateField } = this.props;
this.setState({ contentError: null, hasContentLoading: true });
// TODO: determine when LabelSelect has finished loading labels
- Promise.all([this.loadRelatedInstanceGroups()]).then(() => {
- this.setState({ hasContentLoading: false });
- validateField('project');
- });
+ Promise.all([this.loadRelatedInstanceGroups(), this.fetchProject()]).then(
+ () => {
+ this.setState({ hasContentLoading: false });
+ validateField('project');
+ }
+ );
+ }
+
+ async fetchProject() {
+ const { project } = this.state;
+ if (project && project.id) {
+ try {
+ const { data: projectData } = await ProjectsAPI.readDetail(project.id);
+ this.setState({ project: projectData });
+ } catch (err) {
+ this.setState({ contentError: err });
+ }
+ }
}
async loadRelatedInstanceGroups() {
@@ -124,6 +139,8 @@ class JobTemplateForm extends Component {
handleProjectUpdate(project) {
const { setFieldValue } = this.props;
setFieldValue('project', project.id);
+ setFieldValue('playbook', 0);
+ setFieldValue('scm_branch', '');
this.setState({ project });
}
@@ -147,6 +164,7 @@ class JobTemplateForm extends Component {
i18n,
template,
} = this.props;
+
const jobTypeOptions = [
{
value: '',
@@ -269,6 +287,14 @@ class JobTemplateForm extends Component {
/>
)}
+ {project && project.allow_override && (
+
+ )}
', () => {
project: 3,
playbook: 'Baz',
type: 'job_template',
+ scm_branch: 'Foo',
summary_fields: {
inventory: {
id: 2,
@@ -88,6 +89,11 @@ describe('', () => {
ProjectsAPI.readPlaybooks.mockReturnValue({
data: ['debug.yml'],
});
+ ProjectsAPI.readDetail.mockReturnValue({
+ name: 'foo',
+ id: 1,
+ allow_override: true,
+ });
});
afterEach(() => {
@@ -126,7 +132,6 @@ describe('', () => {
/>
);
});
-
await waitForElement(wrapper, 'EmptyStateBody', el => el.length === 0);
await act(async () => {
wrapper.find('input#template-name').simulate('change', {
@@ -145,16 +150,27 @@ describe('', () => {
wrapper.find('ProjectLookup').invoke('onChange')({
id: 4,
name: 'project',
+ allow_override: true,
+ });
+ });
+ wrapper.update();
+ await act(async () => {
+ wrapper.find('input#scm_branch').simulate('change', {
+ target: { value: 'devel', name: 'scm_branch' },
});
wrapper.find('AnsibleSelect[name="playbook"]').simulate('change', {
target: { value: 'new baz type', name: 'playbook' },
});
+ });
+
+ await act(async () => {
wrapper
.find('CredentialChip')
.at(0)
.prop('onClick')();
});
wrapper.update();
+
expect(wrapper.find('input#template-name').prop('value')).toEqual(
'new foo'
);
@@ -171,7 +187,9 @@ describe('', () => {
expect(wrapper.find('ProjectLookup').prop('value')).toEqual({
id: 4,
name: 'project',
+ allow_override: true,
});
+ expect(wrapper.find('input#scm_branch').prop('value')).toEqual('devel');
expect(
wrapper.find('AnsibleSelect[name="playbook"]').prop('value')
).toEqual('new baz type');
diff --git a/awx/ui_next/src/screens/Template/shared/PlaybookSelect.jsx b/awx/ui_next/src/screens/Template/shared/PlaybookSelect.jsx
index 8f0687ba2a..b0e24fe283 100644
--- a/awx/ui_next/src/screens/Template/shared/PlaybookSelect.jsx
+++ b/awx/ui_next/src/screens/Template/shared/PlaybookSelect.jsx
@@ -15,6 +15,7 @@ function PlaybookSelect({
i18n,
}) {
const [options, setOptions] = useState([]);
+
useEffect(() => {
if (!projectId) {
return;
@@ -28,6 +29,7 @@ function PlaybookSelect({
label: playbook,
isDisabled: false,
}));
+
opts.unshift({
value: '',
key: '',
@@ -40,7 +42,6 @@ function PlaybookSelect({
}
})();
}, [projectId, i18n, onError]);
-
return (