Merge pull request #5654 from AlexSCorey/5619-BranchFieldMissing

Fixes navigation bug in InventoryAdd Adds SCM Branch field on JTForm

Reviewed-by: https://github.com/apps/softwarefactory-project-zuul
This commit is contained in:
softwarefactory-project-zuul[bot]
2020-01-17 21:46:45 +00:00
committed by GitHub
9 changed files with 81 additions and 10 deletions

View File

@@ -57,7 +57,9 @@ function InventoryAdd() {
); );
await Promise.all(associatePromises); 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/smart_inventory/${inventoryId}/details`
: `/inventories/inventory/${inventoryId}/details`; : `/inventories/inventory/${inventoryId}/details`;

View File

@@ -41,8 +41,7 @@ describe('<InventoryAdd />', () => {
test('Initially renders successfully', () => { test('Initially renders successfully', () => {
expect(wrapper.length).toBe(1); expect(wrapper.length).toBe(1);
}); });
test('handleSubmit should call the api and redirect to details page', async () => {
test('handleSubmit should call the api', async () => {
const instanceGroups = [{ name: 'Bizz', id: 1 }, { name: 'Buzz', id: 2 }]; const instanceGroups = [{ name: 'Bizz', id: 1 }, { name: 'Buzz', id: 2 }];
await waitForElement(wrapper, 'isLoading', el => el.length === 0); await waitForElement(wrapper, 'isLoading', el => el.length === 0);
@@ -64,6 +63,7 @@ describe('<InventoryAdd />', () => {
IG.id IG.id
) )
); );
expect(history.location.pathname).toBe('/inventories/inventory/13/details');
}); });
test('handleCancel should return the user back to the inventories list', async () => { test('handleCancel should return the user back to the inventories list', async () => {

View File

@@ -26,6 +26,7 @@ const jobTemplateData = {
allow_simultaneous: false, allow_simultaneous: false,
use_fact_cache: false, use_fact_cache: false,
host_config_key: '', host_config_key: '',
scm_branch: '',
}; };
describe('<JobTemplateAdd />', () => { describe('<JobTemplateAdd />', () => {

View File

@@ -196,6 +196,7 @@ class JobTemplateDetail extends Component {
) : ( ) : (
renderMissingDataDetail(i18n._(t`Project`)) renderMissingDataDetail(i18n._(t`Project`))
)} )}
<Detail label={i18n._(t`SCM Branch`)} value={template.scm_branch} />
<Detail label={i18n._(t`Playbook`)} value={playbook} /> <Detail label={i18n._(t`Playbook`)} value={playbook} />
<Detail label={i18n._(t`Forks`)} value={forks || '0'} /> <Detail label={i18n._(t`Forks`)} value={forks || '0'} />
<Detail label={i18n._(t`Limit`)} value={limit} /> <Detail label={i18n._(t`Limit`)} value={limit} />

View File

@@ -143,4 +143,19 @@ describe('<JobTemplateDetail />', () => {
template.summary_fields.credentials[0] template.summary_fields.credentials[0]
); );
}); });
test('should render SCM_Branch', async () => {
const mockTemplate = { ...template };
mockTemplate.scm_branch = 'Foo branch';
const wrapper = mountWithContexts(
<JobTemplateDetail template={mockTemplate} />
);
await waitForElement(
wrapper,
'JobTemplateDetail',
el => el.state('hasContentLoading') === false
);
const SCMBranch = wrapper.find('Detail[label="SCM Branch"]');
expect(SCMBranch.prop('value')).toBe('Foo branch');
});
}); });

View File

@@ -29,6 +29,7 @@ const mockJobTemplate = {
allow_simultaneous: false, allow_simultaneous: false,
use_fact_cache: false, use_fact_cache: false,
host_config_key: '', host_config_key: '',
scm_branch: '',
summary_fields: { summary_fields: {
user_capabilities: { user_capabilities: {
edit: true, edit: true,
@@ -161,6 +162,11 @@ describe('<JobTemplateEdit />', () => {
JobTemplatesAPI.readInstanceGroups.mockReturnValue({ JobTemplatesAPI.readInstanceGroups.mockReturnValue({
data: { results: mockInstanceGroups }, data: { results: mockInstanceGroups },
}); });
ProjectsAPI.readDetail.mockReturnValue({
id: 1,
allow_override: true,
name: 'foo',
});
}); });
afterEach(() => { afterEach(() => {

View File

@@ -28,7 +28,7 @@ import {
ProjectLookup, ProjectLookup,
MultiCredentialsLookup, MultiCredentialsLookup,
} from '@components/Lookup'; } from '@components/Lookup';
import { JobTemplatesAPI } from '@api'; import { JobTemplatesAPI, ProjectsAPI } from '@api';
import LabelSelect from './LabelSelect'; import LabelSelect from './LabelSelect';
import PlaybookSelect from './PlaybookSelect'; import PlaybookSelect from './PlaybookSelect';
@@ -81,16 +81,31 @@ class JobTemplateForm extends Component {
this.loadRelatedInstanceGroups = this.loadRelatedInstanceGroups.bind(this); this.loadRelatedInstanceGroups = this.loadRelatedInstanceGroups.bind(this);
this.handleProjectUpdate = this.handleProjectUpdate.bind(this); this.handleProjectUpdate = this.handleProjectUpdate.bind(this);
this.setContentError = this.setContentError.bind(this); this.setContentError = this.setContentError.bind(this);
this.fetchProject = this.fetchProject.bind(this);
} }
componentDidMount() { componentDidMount() {
const { validateField } = this.props; const { validateField } = this.props;
this.setState({ contentError: null, hasContentLoading: true }); this.setState({ contentError: null, hasContentLoading: true });
// TODO: determine when LabelSelect has finished loading labels // TODO: determine when LabelSelect has finished loading labels
Promise.all([this.loadRelatedInstanceGroups()]).then(() => { Promise.all([this.loadRelatedInstanceGroups(), this.fetchProject()]).then(
this.setState({ hasContentLoading: false }); () => {
validateField('project'); 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() { async loadRelatedInstanceGroups() {
@@ -124,6 +139,8 @@ class JobTemplateForm extends Component {
handleProjectUpdate(project) { handleProjectUpdate(project) {
const { setFieldValue } = this.props; const { setFieldValue } = this.props;
setFieldValue('project', project.id); setFieldValue('project', project.id);
setFieldValue('playbook', 0);
setFieldValue('scm_branch', '');
this.setState({ project }); this.setState({ project });
} }
@@ -147,6 +164,7 @@ class JobTemplateForm extends Component {
i18n, i18n,
template, template,
} = this.props; } = this.props;
const jobTypeOptions = [ const jobTypeOptions = [
{ {
value: '', value: '',
@@ -269,6 +287,14 @@ class JobTemplateForm extends Component {
/> />
)} )}
</Field> </Field>
{project && project.allow_override && (
<FormField
id="scm_branch"
name="scm_branch"
type="text"
label={i18n._(t`SCM Branch`)}
/>
)}
<Field <Field
name="playbook" name="playbook"
validate={required(i18n._(t`Select a value for this field`), i18n)} validate={required(i18n._(t`Select a value for this field`), i18n)}
@@ -583,6 +609,7 @@ const FormikApp = withFormik({
job_type: template.job_type || 'run', job_type: template.job_type || 'run',
inventory: template.inventory || '', inventory: template.inventory || '',
project: template.project || '', project: template.project || '',
scm_branch: template.scm_branch || '',
playbook: template.playbook || '', playbook: template.playbook || '',
labels: summary_fields.labels.results || [], labels: summary_fields.labels.results || [],
forks: template.forks || 0, forks: template.forks || 0,

View File

@@ -17,6 +17,7 @@ describe('<JobTemplateForm />', () => {
project: 3, project: 3,
playbook: 'Baz', playbook: 'Baz',
type: 'job_template', type: 'job_template',
scm_branch: 'Foo',
summary_fields: { summary_fields: {
inventory: { inventory: {
id: 2, id: 2,
@@ -88,6 +89,11 @@ describe('<JobTemplateForm />', () => {
ProjectsAPI.readPlaybooks.mockReturnValue({ ProjectsAPI.readPlaybooks.mockReturnValue({
data: ['debug.yml'], data: ['debug.yml'],
}); });
ProjectsAPI.readDetail.mockReturnValue({
name: 'foo',
id: 1,
allow_override: true,
});
}); });
afterEach(() => { afterEach(() => {
@@ -126,7 +132,6 @@ describe('<JobTemplateForm />', () => {
/> />
); );
}); });
await waitForElement(wrapper, 'EmptyStateBody', el => el.length === 0); await waitForElement(wrapper, 'EmptyStateBody', el => el.length === 0);
await act(async () => { await act(async () => {
wrapper.find('input#template-name').simulate('change', { wrapper.find('input#template-name').simulate('change', {
@@ -145,16 +150,27 @@ describe('<JobTemplateForm />', () => {
wrapper.find('ProjectLookup').invoke('onChange')({ wrapper.find('ProjectLookup').invoke('onChange')({
id: 4, id: 4,
name: 'project', 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', { wrapper.find('AnsibleSelect[name="playbook"]').simulate('change', {
target: { value: 'new baz type', name: 'playbook' }, target: { value: 'new baz type', name: 'playbook' },
}); });
});
await act(async () => {
wrapper wrapper
.find('CredentialChip') .find('CredentialChip')
.at(0) .at(0)
.prop('onClick')(); .prop('onClick')();
}); });
wrapper.update(); wrapper.update();
expect(wrapper.find('input#template-name').prop('value')).toEqual( expect(wrapper.find('input#template-name').prop('value')).toEqual(
'new foo' 'new foo'
); );
@@ -171,7 +187,9 @@ describe('<JobTemplateForm />', () => {
expect(wrapper.find('ProjectLookup').prop('value')).toEqual({ expect(wrapper.find('ProjectLookup').prop('value')).toEqual({
id: 4, id: 4,
name: 'project', name: 'project',
allow_override: true,
}); });
expect(wrapper.find('input#scm_branch').prop('value')).toEqual('devel');
expect( expect(
wrapper.find('AnsibleSelect[name="playbook"]').prop('value') wrapper.find('AnsibleSelect[name="playbook"]').prop('value')
).toEqual('new baz type'); ).toEqual('new baz type');

View File

@@ -15,6 +15,7 @@ function PlaybookSelect({
i18n, i18n,
}) { }) {
const [options, setOptions] = useState([]); const [options, setOptions] = useState([]);
useEffect(() => { useEffect(() => {
if (!projectId) { if (!projectId) {
return; return;
@@ -28,6 +29,7 @@ function PlaybookSelect({
label: playbook, label: playbook,
isDisabled: false, isDisabled: false,
})); }));
opts.unshift({ opts.unshift({
value: '', value: '',
key: '', key: '',
@@ -40,7 +42,6 @@ function PlaybookSelect({
} }
})(); })();
}, [projectId, i18n, onError]); }, [projectId, i18n, onError]);
return ( return (
<AnsibleSelect <AnsibleSelect
id="template-playbook" id="template-playbook"