Merge pull request #5923 from mabashian/4967-prompt-on-launch-checkboxes

[POC] Adds FieldWithPrompt component to handle fields that are also promptable

Reviewed-by: https://github.com/apps/softwarefactory-project-zuul
This commit is contained in:
softwarefactory-project-zuul[bot]
2020-02-14 18:51:07 +00:00
committed by GitHub
6 changed files with 196 additions and 56 deletions

View File

@@ -0,0 +1,71 @@
import React from 'react';
import { bool, node, string } from 'prop-types';
import { withI18n } from '@lingui/react';
import { t } from '@lingui/macro';
import { CheckboxField, FieldTooltip } from '@components/FormField';
import styled from 'styled-components';
const FieldHeader = styled.div`
display: flex;
justify-content: space-between;
padding-bottom: var(--pf-c-form__label--PaddingBottom);
label {
--pf-c-form__label--PaddingBottom: 0px;
}
`;
const StyledCheckboxField = styled(CheckboxField)`
--pf-c-check__label--FontSize: var(--pf-c-form__label--FontSize);
`;
function FieldWithPrompt({
children,
fieldId,
i18n,
isRequired,
label,
promptId,
promptName,
tooltip,
}) {
return (
<div className="pf-c-form__group">
<FieldHeader>
<div>
<label className="pf-c-form__label" htmlFor={fieldId}>
<span className="pf-c-form__label-text">{label}</span>
{isRequired && (
<span className="pf-c-form__label-required" aria-hidden="true">
*
</span>
)}
</label>
{tooltip && <FieldTooltip content={tooltip} />}
</div>
<StyledCheckboxField
id={promptId}
label={i18n._(t`Prompt On Launch`)}
name={promptName}
/>
</FieldHeader>
{children}
</div>
);
}
FieldWithPrompt.propTypes = {
fieldId: string.isRequired,
isRequired: bool,
label: string.isRequired,
promptId: string.isRequired,
promptName: string.isRequired,
tooltip: node,
};
FieldWithPrompt.defaultProps = {
isRequired: false,
tooltip: null,
};
export default withI18n()(FieldWithPrompt);

View File

@@ -0,0 +1,66 @@
import React from 'react';
import { mountWithContexts } from '@testUtils/enzymeHelpers';
import { Field, Formik } from 'formik';
import FieldWithPrompt from './FieldWithPrompt';
describe('FieldWithPrompt', () => {
let wrapper;
afterEach(() => {
wrapper.unmount();
});
test('Required asterisk and Tooltip hidden when not required and tooltip not provided', () => {
wrapper = mountWithContexts(
<Formik
initialValues={{
ask_limit_on_launch: false,
limit: '',
}}
>
{() => (
<FieldWithPrompt
fieldId="job-template-limit"
label="Limit"
promptId="job-template-ask-limit-on-launch"
promptName="ask_limit_on_launch"
>
<Field name="limit">
{() => <input id="job-template-limit" type="text" />}
</Field>
</FieldWithPrompt>
)}
</Formik>
);
expect(wrapper.find('.pf-c-form__label-required')).toHaveLength(0);
expect(wrapper.find('Tooltip')).toHaveLength(0);
});
test('Required asterisk and Tooltip shown when required and tooltip provided', () => {
wrapper = mountWithContexts(
<Formik
initialValues={{
ask_limit_on_launch: false,
limit: '',
}}
>
{() => (
<FieldWithPrompt
fieldId="job-template-limit"
label="Limit"
promptId="job-template-ask-limit-on-launch"
promptName="ask_limit_on_launch"
tooltip="Help text"
isRequired
>
<Field name="limit">
{() => <input id="job-template-limit" type="text" />}
</Field>
</FieldWithPrompt>
)}
</Formik>
);
expect(wrapper.find('.pf-c-form__label-required')).toHaveLength(1);
expect(wrapper.find('Tooltip')).toHaveLength(1);
});
});

View File

@@ -0,0 +1 @@
export { default } from './FieldWithPrompt';

View File

@@ -8,25 +8,26 @@ import { JobTemplatesAPI, LabelsAPI } from '@api';
jest.mock('@api'); jest.mock('@api');
const jobTemplateData = { const jobTemplateData = {
name: 'Foo',
description: 'Baz',
job_type: 'run',
inventory: 1,
project: 2,
playbook: 'Bar',
forks: 0,
limit: '',
verbosity: '0',
job_slice_count: 1,
timeout: 0,
job_tags: '',
skip_tags: '',
diff_mode: false,
allow_callbacks: false, allow_callbacks: false,
allow_simultaneous: false, allow_simultaneous: false,
use_fact_cache: false, ask_job_type_on_launch: false,
description: 'Baz',
diff_mode: false,
forks: 0,
host_config_key: '', host_config_key: '',
inventory: 1,
job_slice_count: 1,
job_tags: '',
job_type: 'run',
limit: '',
name: 'Foo',
playbook: 'Bar',
project: 2,
scm_branch: '', scm_branch: '',
skip_tags: '',
timeout: 0,
use_fact_cache: false,
verbosity: '0',
}; };
describe('<JobTemplateAdd />', () => { describe('<JobTemplateAdd />', () => {

View File

@@ -9,27 +9,24 @@ import JobTemplateEdit from './JobTemplateEdit';
jest.mock('@api'); jest.mock('@api');
const mockJobTemplate = { const mockJobTemplate = {
id: 1,
name: 'Foo',
description: 'Bar',
job_type: 'run',
inventory: 2,
project: 3,
playbook: 'Baz',
type: 'job_template',
forks: 0,
limit: '',
verbosity: '0',
job_slice_count: 1,
timeout: 0,
job_tags: '',
skip_tags: '',
diff_mode: false,
allow_callbacks: false, allow_callbacks: false,
allow_simultaneous: false, allow_simultaneous: false,
use_fact_cache: false, ask_job_type_on_launch: false,
description: 'Bar',
diff_mode: false,
forks: 0,
host_config_key: '', host_config_key: '',
id: 1,
inventory: 2,
job_slice_count: 1,
job_tags: '',
job_type: 'run',
limit: '',
name: 'Foo',
playbook: 'Baz',
project: 3,
scm_branch: '', scm_branch: '',
skip_tags: '',
summary_fields: { summary_fields: {
user_capabilities: { user_capabilities: {
edit: true, edit: true,
@@ -50,6 +47,10 @@ const mockJobTemplate = {
name: 'Boo', name: 'Boo',
}, },
}, },
timeout: 0,
type: 'job_template',
use_fact_cache: false,
verbosity: '0',
}; };
const mockRelatedCredentials = { const mockRelatedCredentials = {

View File

@@ -21,6 +21,7 @@ import FormField, {
FieldTooltip, FieldTooltip,
FormSubmitError, FormSubmitError,
} from '@components/FormField'; } from '@components/FormField';
import FieldWithPrompt from '@components/FieldWithPrompt';
import FormRow from '@components/FormRow'; import FormRow from '@components/FormRow';
import CollapsibleSection from '@components/CollapsibleSection'; import CollapsibleSection from '@components/CollapsibleSection';
import { required } from '@util/validators'; import { required } from '@util/validators';
@@ -227,37 +228,35 @@ class JobTemplateForm extends Component {
type="text" type="text"
label={i18n._(t`Description`)} label={i18n._(t`Description`)}
/> />
<Field <FieldWithPrompt
name="job_type" fieldId="template-job-type"
validate={required(null, i18n)} isRequired
onBlur={handleBlur} label={i18n._(t`Job Type`)}
promptId="template-ask-job-type-on-launch"
promptName="ask_job_type_on_launch"
tooltip={i18n._(t`For job templates, select run to execute
the playbook. Select check to only check playbook syntax,
test environment setup, and report problems without
executing the playbook.`)}
> >
{({ form, field }) => { <Field
const isValid = !form.touched.job_type || !form.errors.job_type; name="job_type"
return ( validate={required(null, i18n)}
<FormGroup onBlur={handleBlur}
fieldId="template-job-type" >
helperTextInvalid={form.errors.job_type} {({ form, field }) => {
isRequired const isValid = !form.touched.job_type || !form.errors.job_type;
isValid={isValid} return (
label={i18n._(t`Job Type`)}
>
<FieldTooltip
content={i18n._(t`For job templates, select run to execute
the playbook. Select check to only check playbook syntax,
test environment setup, and report problems without
executing the playbook.`)}
/>
<AnsibleSelect <AnsibleSelect
isValid={isValid} isValid={isValid}
id="template-job-type" id="template-job-type"
data={jobTypeOptions} data={jobTypeOptions}
{...field} {...field}
/> />
</FormGroup> );
); }}
}} </Field>
</Field> </FieldWithPrompt>
<Field <Field
name="inventory" name="inventory"
validate={required(i18n._(t`Select a value for this field`), i18n)} validate={required(i18n._(t`Select a value for this field`), i18n)}
@@ -613,6 +612,7 @@ const FormikApp = withFormik({
? summary_fields.inventory.organization_id ? summary_fields.inventory.organization_id
: null; : null;
return { return {
ask_job_type_on_launch: template.ask_job_type_on_launch || false,
name: template.name || '', name: template.name || '',
description: template.description || '', description: template.description || '',
job_type: template.job_type || 'run', job_type: template.job_type || 'run',