Merge pull request #7375 from vjanssens/5929-consistent-subforms-in-job-template-forms

Use consistent layout for subforms in job/template forms

Reviewed-by: https://github.com/apps/softwarefactory-project-zuul
This commit is contained in:
softwarefactory-project-zuul[bot]
2020-06-29 17:15:23 +00:00
committed by GitHub
10 changed files with 206 additions and 180 deletions

View File

@@ -8,7 +8,7 @@ The API development server will need to be running. See [CONTRIBUTING.md](../../
```shell ```shell
# Start the ui development server. While running, the ui will be reachable # Start the ui development server. While running, the ui will be reachable
# at https://127.0.01:3001 and updated automatically when code changes. # at https://127.0.0.1:3001 and updated automatically when code changes.
npm --prefix=awx/ui_next start npm --prefix=awx/ui_next start
``` ```

View File

@@ -16,6 +16,8 @@ function JobTemplateAdd() {
initialInstanceGroups, initialInstanceGroups,
credentials, credentials,
webhook_credential, webhook_credential,
webhook_key,
webhook_url,
...remainingValues ...remainingValues
} = values; } = values;

View File

@@ -172,9 +172,7 @@ describe('<JobTemplateAdd />', () => {
playbook: 'Baz', playbook: 'Baz',
inventory: 2, inventory: 2,
webhook_credential: undefined, webhook_credential: undefined,
webhook_key: '',
webhook_service: '', webhook_service: '',
webhook_url: '',
}); });
}); });

View File

@@ -101,6 +101,8 @@ class JobTemplateEdit extends Component {
initialInstanceGroups, initialInstanceGroups,
credentials, credentials,
webhook_credential, webhook_credential,
webhook_key,
webhook_url,
...remainingValues ...remainingValues
} = values; } = values;

View File

@@ -271,7 +271,8 @@ describe('<JobTemplateEdit />', () => {
delete expected.id; delete expected.id;
delete expected.type; delete expected.type;
delete expected.related; delete expected.related;
expected.webhook_url = `${window.location.origin}${mockJobTemplate.related.webhook_receiver}`; delete expected.webhook_key;
delete expected.webhook_url;
expect(JobTemplatesAPI.update).toHaveBeenCalledWith(1, expected); expect(JobTemplatesAPI.update).toHaveBeenCalledWith(1, expected);
expect(JobTemplatesAPI.disassociateLabel).toHaveBeenCalledTimes(2); expect(JobTemplatesAPI.disassociateLabel).toHaveBeenCalledTimes(2);
expect(JobTemplatesAPI.associateLabel).toHaveBeenCalledTimes(4); expect(JobTemplatesAPI.associateLabel).toHaveBeenCalledTimes(4);

View File

@@ -9,6 +9,7 @@ import {
Switch, Switch,
Checkbox, Checkbox,
TextInput, TextInput,
Title,
} from '@patternfly/react-core'; } from '@patternfly/react-core';
import ContentError from '../../../components/ContentError'; import ContentError from '../../../components/ContentError';
import ContentLoading from '../../../components/ContentLoading'; import ContentLoading from '../../../components/ContentLoading';
@@ -27,6 +28,7 @@ import {
FormColumnLayout, FormColumnLayout,
FormFullWidthLayout, FormFullWidthLayout,
FormCheckboxLayout, FormCheckboxLayout,
SubFormLayout,
} from '../../../components/FormLayout'; } from '../../../components/FormLayout';
import { VariablesField } from '../../../components/CodeMirrorInput'; import { VariablesField } from '../../../components/CodeMirrorInput';
import { required } from '../../../util/validators'; import { required } from '../../../util/validators';
@@ -547,30 +549,53 @@ function JobTemplateForm({
</FormCheckboxLayout> </FormCheckboxLayout>
</FormGroup> </FormGroup>
</FormFullWidthLayout> </FormFullWidthLayout>
<WebhookSubForm
enableWebhooks={enableWebhooks} {(allowCallbacks || enableWebhooks) && (
templateType={template.type}
/>
{allowCallbacks && (
<> <>
{callbackUrl && ( <SubFormLayout>
<FormGroup {allowCallbacks && (
label={i18n._(t`Provisioning Callback URL`)} <>
fieldId="template-callback-url" <Title size="md" headingLevel="h4">
> {i18n._(t`Provisioning Callback details`)}
<TextInput </Title>
id="template-callback-url" <FormColumnLayout>
isDisabled {callbackUrl && (
value={callbackUrl} <FormGroup
/> label={i18n._(t`Provisioning Callback URL`)}
</FormGroup> fieldId="template-callback-url"
)} >
<FormField <TextInput
id="template-host-config-key" id="template-callback-url"
name="host_config_key" isDisabled
label={i18n._(t`Host Config Key`)} value={callbackUrl}
validate={allowCallbacks ? required(null, i18n) : null} />
/> </FormGroup>
)}
<FormField
id="template-host-config-key"
name="host_config_key"
label={i18n._(t`Host Config Key`)}
validate={
allowCallbacks ? required(null, i18n) : null
}
/>
</FormColumnLayout>
</>
)}
{allowCallbacks && enableWebhooks && <br />}
{enableWebhooks && (
<>
<Title size="md" headingLevel="h4">
{i18n._(t`Webhook details`)}
</Title>
<FormColumnLayout>
<WebhookSubForm templateType={template.type} />
</FormColumnLayout>
</>
)}
</SubFormLayout>
</> </>
)} )}
</FormColumnLayout> </FormColumnLayout>

View File

@@ -23,7 +23,7 @@ import {
CredentialTypesAPI, CredentialTypesAPI,
} from '../../../api'; } from '../../../api';
function WebhookSubForm({ i18n, enableWebhooks, templateType }) { function WebhookSubForm({ i18n, templateType }) {
const { id } = useParams(); const { id } = useParams();
const { pathname } = useLocation(); const { pathname } = useLocation();
@@ -36,6 +36,7 @@ function WebhookSubForm({ i18n, enableWebhooks, templateType }) {
webhookServiceHelpers, webhookServiceHelpers,
] = useField('webhook_service'); ] = useField('webhook_service');
// eslint-disable-next-line no-unused-vars
const [webhookUrlField, webhookUrlMeta, webhookUrlHelpers] = useField( const [webhookUrlField, webhookUrlMeta, webhookUrlHelpers] = useField(
'webhook_url' 'webhook_url'
); );
@@ -71,21 +72,6 @@ function WebhookSubForm({ i18n, enableWebhooks, templateType }) {
loadCredentialType(); loadCredentialType();
}, [loadCredentialType]); }, [loadCredentialType]);
useEffect(() => {
if (enableWebhooks) {
webhookServiceHelpers.setValue(webhookServiceMeta.initialValue);
webhookUrlHelpers.setValue(webhookUrlMeta.initialValue);
webhookKeyHelpers.setValue(webhookKeyMeta.initialValue);
webhookCredentialHelpers.setValue(webhookCredentialMeta.initialValue);
} else {
webhookServiceHelpers.setValue('');
webhookUrlHelpers.setValue('');
webhookKeyHelpers.setValue('');
webhookCredentialHelpers.setValue(null);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [enableWebhooks]);
const { request: fetchWebhookKey, error: webhookKeyError } = useRequest( const { request: fetchWebhookKey, error: webhookKeyError } = useRequest(
useCallback(async () => { useCallback(async () => {
const updateWebhookKey = const updateWebhookKey =
@@ -134,108 +120,106 @@ function WebhookSubForm({ i18n, enableWebhooks, templateType }) {
return <ContentLoading />; return <ContentLoading />;
} }
return ( return (
enableWebhooks && ( <FormColumnLayout>
<FormColumnLayout> <FormGroup
<FormGroup name="webhook_service"
name="webhook_service" fieldId="webhook_service"
fieldId="webhook_service" helperTextInvalid={webhookServiceMeta.error}
helperTextInvalid={webhookServiceMeta.error} label={i18n._(t`Webhook Service`)}
label={i18n._(t`Webhook Service`)} >
> <FieldTooltip content={i18n._(t`Select a webhook service.`)} />
<FieldTooltip content={i18n._(t`Select a webhook service.`)} /> <AnsibleSelect
<AnsibleSelect {...webhookServiceField}
{...webhookServiceField} id="webhook_service"
id="webhook_service" data={webhookServiceOptions}
data={webhookServiceOptions} onChange={(event, val) => {
onChange={(event, val) => { webhookServiceHelpers.setValue(val);
webhookServiceHelpers.setValue(val); webhookUrlHelpers.setValue(
webhookUrlHelpers.setValue( pathname.endsWith('/add')
pathname.endsWith('/add') ? i18n
? i18n ._(t`a new webhook url will be generated on save.`)
._(t`a new webhook url will be generated on save.`)
.toUpperCase()
: `${origin}/api/v2/${templateType}s/${id}/${val}/`
);
if (val === webhookServiceMeta.initialValue || val === '') {
webhookKeyHelpers.setValue(webhookKeyMeta.initialValue);
webhookCredentialHelpers.setValue(
webhookCredentialMeta.initialValue
);
} else {
webhookKeyHelpers.setValue(
i18n
._(t`a new webhook key will be generated on save.`)
.toUpperCase() .toUpperCase()
); : `${origin}/api/v2/${templateType}s/${id}/${val}/`
webhookCredentialHelpers.setValue(null); );
} if (val === webhookServiceMeta.initialValue || val === '') {
}} webhookKeyHelpers.setValue(webhookKeyMeta.initialValue);
webhookCredentialHelpers.setValue(
webhookCredentialMeta.initialValue
);
} else {
webhookKeyHelpers.setValue(
i18n
._(t`a new webhook key will be generated on save.`)
.toUpperCase()
);
webhookCredentialHelpers.setValue(null);
}
}}
/>
</FormGroup>
<>
<FormGroup
type="text"
fieldId="jt-webhookURL"
label={i18n._(t`Webhook URL`)}
name="webhook_url"
>
<FieldTooltip
content={i18n._(
t`Webhook services can launch jobs with this workflow job template by making a POST request to this URL.`
)}
/>
<TextInput
id="t-webhookURL"
aria-label={i18n._(t`Webhook URL`)}
value={webhookUrlField.value}
isReadOnly
/> />
</FormGroup> </FormGroup>
<> <FormGroup
<FormGroup label={i18n._(t`Webhook Key`)}
type="text" fieldId="template-webhook_key"
fieldId="jt-webhookURL" >
label={i18n._(t`Webhook URL`)} <FieldTooltip
name="webhook_url" content={i18n._(
> t`Webhook services can use this as a shared secret.`
<FieldTooltip
content={i18n._(
t`Webhook services can launch jobs with this workflow job template by making a POST request to this URL.`
)}
/>
<TextInput
id="t-webhookURL"
aria-label={i18n._(t`Webhook URL`)}
value={webhookUrlField.value}
isReadOnly
/>
</FormGroup>
<FormGroup
label={i18n._(t`Webhook Key`)}
fieldId="template-webhook_key"
>
<FieldTooltip
content={i18n._(
t`Webhook services can use this as a shared secret.`
)}
/>
<InputGroup>
<TextInput
id="template-webhook_key"
isReadOnly
aria-label="wfjt-webhook-key"
value={webhookKeyField.value}
/>
<Button
isDisabled={isUpdateKeyDisabled}
variant="tertiary"
aria-label={i18n._(t`Update webhook key`)}
onClick={changeWebhookKey}
>
<SyncAltIcon />
</Button>
</InputGroup>
</FormGroup>
</>
{credTypeId && (
<CredentialLookup
label={i18n._(t`Webhook Credential`)}
tooltip={i18n._(
t`Optionally select the credential to use to send status updates back to the webhook service.`
)} )}
credentialTypeId={credTypeId}
onChange={value => {
webhookCredentialHelpers.setValue(value || null);
}}
isValid={!webhookCredentialMeta.error}
helperTextInvalid={webhookCredentialMeta.error}
value={webhookCredentialField.value}
/> />
)} <InputGroup>
</FormColumnLayout> <TextInput
) id="template-webhook_key"
isReadOnly
aria-label="wfjt-webhook-key"
value={webhookKeyField.value}
/>
<Button
isDisabled={isUpdateKeyDisabled}
variant="tertiary"
aria-label={i18n._(t`Update webhook key`)}
onClick={changeWebhookKey}
>
<SyncAltIcon />
</Button>
</InputGroup>
</FormGroup>
</>
{credTypeId && (
<CredentialLookup
label={i18n._(t`Webhook Credential`)}
tooltip={i18n._(
t`Optionally select the credential to use to send status updates back to the webhook service.`
)}
credentialTypeId={credTypeId}
onChange={value => {
webhookCredentialHelpers.setValue(value || null);
}}
isValid={!webhookCredentialMeta.error}
helperTextInvalid={webhookCredentialMeta.error}
value={webhookCredentialField.value}
/>
)}
</FormColumnLayout>
); );
} }
export default withI18n()(WebhookSubForm); export default withI18n()(WebhookSubForm);

View File

@@ -34,7 +34,7 @@ describe('<WebhookSubForm />', () => {
wrapper = mountWithContexts( wrapper = mountWithContexts(
<Route path="templates/:templateType/:id/edit"> <Route path="templates/:templateType/:id/edit">
<Formik initialValues={initialValues}> <Formik initialValues={initialValues}>
<WebhookSubForm enableWebhooks templateType="job_template" /> <WebhookSubForm templateType="job_template" />
</Formik> </Formik>
</Route>, </Route>,
{ {
@@ -103,7 +103,7 @@ describe('<WebhookSubForm />', () => {
webhook_key: 'A NEW WEBHOOK KEY WILL BE GENERATED ON SAVE.', webhook_key: 'A NEW WEBHOOK KEY WILL BE GENERATED ON SAVE.',
}} }}
> >
<WebhookSubForm enableWebhooks templateType="job_template" /> <WebhookSubForm templateType="job_template" />
</Formik> </Formik>
</Route>, </Route>,
{ {
@@ -133,10 +133,7 @@ describe('<WebhookSubForm />', () => {
newWrapper = mountWithContexts( newWrapper = mountWithContexts(
<Route path="templates/:templateType/:id/edit"> <Route path="templates/:templateType/:id/edit">
<Formik initialValues={{ ...initialValues, webhook_url }}> <Formik initialValues={{ ...initialValues, webhook_url }}>
<WebhookSubForm <WebhookSubForm templateType="workflow_job_template" />
enableWebhooks
templateType="workflow_job_template"
/>
</Formik> </Formik>
</Route>, </Route>,
{ {

View File

@@ -4,7 +4,13 @@ import PropTypes, { shape } from 'prop-types';
import { withI18n } from '@lingui/react'; import { withI18n } from '@lingui/react';
import { useField, withFormik } from 'formik'; import { useField, withFormik } from 'formik';
import { Form, FormGroup, Checkbox, TextInput } from '@patternfly/react-core'; import {
Form,
FormGroup,
Checkbox,
TextInput,
Title,
} from '@patternfly/react-core';
import { required } from '../../../util/validators'; import { required } from '../../../util/validators';
import FieldWithPrompt from '../../../components/FieldWithPrompt'; import FieldWithPrompt from '../../../components/FieldWithPrompt';
@@ -16,6 +22,7 @@ import {
FormColumnLayout, FormColumnLayout,
FormFullWidthLayout, FormFullWidthLayout,
FormCheckboxLayout, FormCheckboxLayout,
SubFormLayout,
} from '../../../components/FormLayout'; } from '../../../components/FormLayout';
import OrganizationLookup from '../../../components/Lookup/OrganizationLookup'; import OrganizationLookup from '../../../components/Lookup/OrganizationLookup';
import { InventoryLookup } from '../../../components/Lookup'; import { InventoryLookup } from '../../../components/Lookup';
@@ -175,39 +182,47 @@ function WorkflowJobTemplateForm({
)} )}
/> />
</FormFullWidthLayout> </FormFullWidthLayout>
<FormCheckboxLayout fieldId="options" isInline label={i18n._(t`Options`)}> <FormGroup fieldId="options" label={i18n._(t`Options`)}>
<Checkbox <FormCheckboxLayout isInline>
aria-label={i18n._(t`Enable Webhook`)} <Checkbox
label={ aria-label={i18n._(t`Enable Webhook`)}
<span> label={
{i18n._(t`Enable Webhook`)} <span>
&nbsp; {i18n._(t`Enable Webhook`)}
<FieldTooltip &nbsp;
content={i18n._( <FieldTooltip
t`Enable Webhook for this workflow job template.` content={i18n._(
)} t`Enable Webhook for this workflow job template.`
/> )}
</span> />
} </span>
id="wfjt-enabled-webhooks" }
isChecked={enableWebhooks} id="wfjt-enabled-webhooks"
onChange={checked => { isChecked={enableWebhooks}
setEnableWebhooks(checked); onChange={checked => {
}} setEnableWebhooks(checked);
/> }}
<CheckboxField />
name="allow_simultaneous" <CheckboxField
id="allow_simultaneous" name="allow_simultaneous"
tooltip={i18n._( id="allow_simultaneous"
t`If enabled, simultaneous runs of this workflow job template will be allowed.` tooltip={i18n._(
)} t`If enabled, simultaneous runs of this workflow job template will be allowed.`
label={i18n._(t`Enable Concurrent Jobs`)} )}
/> label={i18n._(t`Enable Concurrent Jobs`)}
</FormCheckboxLayout> />
<WebhookSubForm </FormCheckboxLayout>
enableWebhooks={enableWebhooks} </FormGroup>
templateType={template.type}
/> {enableWebhooks && (
<SubFormLayout>
<Title size="md" headingLevel="h4">
{i18n._(t`Webhook details`)}
</Title>
<WebhookSubForm templateType={template.type} />
</SubFormLayout>
)}
{submitError && <FormSubmitError error={submitError} />} {submitError && <FormSubmitError error={submitError} />}
<FormActionGroup onCancel={handleCancel} onSubmit={handleSubmit} /> <FormActionGroup onCancel={handleCancel} onSubmit={handleSubmit} />
</Form> </Form>

View File

@@ -44,7 +44,9 @@ describe('<WorkflowJobTemplateForm/>', () => {
related: { related: {
webhook_receiver: '/api/v2/workflow_job_templates/57/gitlab/', webhook_receiver: '/api/v2/workflow_job_templates/57/gitlab/',
}, },
webhook_credential: null,
webhook_key: 'sdfghjklmnbvcdsew435678iokjhgfd', webhook_key: 'sdfghjklmnbvcdsew435678iokjhgfd',
webhook_service: 'github',
}; };
beforeEach(async () => { beforeEach(async () => {