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 (