Fields work and forms save

This commit is contained in:
Alex Corey
2020-02-12 12:11:13 -05:00
parent 52a8935b20
commit d97f516c3a
6 changed files with 309 additions and 17 deletions

View File

@@ -6,6 +6,14 @@ class WorkflowJobTemplates extends Base {
this.baseUrl = '/api/v2/workflow_job_templates/'; this.baseUrl = '/api/v2/workflow_job_templates/';
} }
readWebhookKey(id) {
return this.http.get(`${this.baseUrl}${id}/webhook_key/`);
}
updateWebhookKey(id) {
return this.http.post(`${this.baseUrl}${id}/webhook_key/`);
}
associateLabel(id, label, orgId) { associateLabel(id, label, orgId) {
return this.http.post(`${this.baseUrl}${id}/labels/`, { return this.http.post(`${this.baseUrl}${id}/labels/`, {
name: label.name, name: label.name,

View File

@@ -7,6 +7,7 @@ import styled from 'styled-components';
import { Split, SplitItem } from '@patternfly/react-core'; import { Split, SplitItem } from '@patternfly/react-core';
import { CheckboxField } from '@components/FormField'; import { CheckboxField } from '@components/FormField';
import { yamlToJson, jsonToYaml, isJson } from '@util/yaml'; import { yamlToJson, jsonToYaml, isJson } from '@util/yaml';
import { FieldTooltip } from '@components/FormField';
import CodeMirrorInput from './CodeMirrorInput'; import CodeMirrorInput from './CodeMirrorInput';
import YamlJsonToggle from './YamlJsonToggle'; import YamlJsonToggle from './YamlJsonToggle';
import { JSON_MODE, YAML_MODE } from './constants'; import { JSON_MODE, YAML_MODE } from './constants';
@@ -20,7 +21,15 @@ const StyledCheckboxField = styled(CheckboxField)`
--pf-c-check__label--FontSize: var(--pf-c-form__label--FontSize); --pf-c-check__label--FontSize: var(--pf-c-form__label--FontSize);
`; `;
function VariablesField({ i18n, id, name, label, readOnly, promptId }) { function VariablesField({
i18n,
id,
name,
label,
readOnly,
promptId,
tooltip,
}) {
const [field, meta, helpers] = useField(name); const [field, meta, helpers] = useField(name);
const [mode, setMode] = useState(isJson(field.value) ? JSON_MODE : YAML_MODE); const [mode, setMode] = useState(isJson(field.value) ? JSON_MODE : YAML_MODE);
@@ -32,6 +41,7 @@ function VariablesField({ i18n, id, name, label, readOnly, promptId }) {
<label htmlFor={id} className="pf-c-form__label"> <label htmlFor={id} className="pf-c-form__label">
<span className="pf-c-form__label-text">{label}</span> <span className="pf-c-form__label-text">{label}</span>
</label> </label>
{tooltip && <FieldTooltip content={tooltip} />}
</SplitItem> </SplitItem>
<SplitItem> <SplitItem>
<YamlJsonToggle <YamlJsonToggle

View File

@@ -6,6 +6,7 @@ import { t } from '@lingui/macro';
import { CredentialsAPI } from '@api'; import { CredentialsAPI } from '@api';
import { Credential } from '@types'; import { Credential } from '@types';
import { getQSConfig, parseQueryString, mergeParams } from '@util/qs'; import { getQSConfig, parseQueryString, mergeParams } from '@util/qs';
import { FieldTooltip } from '@components/FormField';
import { FormGroup } from '@patternfly/react-core'; import { FormGroup } from '@patternfly/react-core';
import Lookup from '@components/Lookup'; import Lookup from '@components/Lookup';
import OptionsList from './shared/OptionsList'; import OptionsList from './shared/OptionsList';
@@ -28,6 +29,7 @@ function CredentialLookup({
value, value,
history, history,
i18n, i18n,
tooltip,
}) { }) {
const [credentials, setCredentials] = useState([]); const [credentials, setCredentials] = useState([]);
const [count, setCount] = useState(0); const [count, setCount] = useState(0);
@@ -60,6 +62,7 @@ function CredentialLookup({
label={label} label={label}
helperTextInvalid={helperTextInvalid} helperTextInvalid={helperTextInvalid}
> >
{tooltip && <FieldTooltip content={tooltip} />}
<Lookup <Lookup
id="credential" id="credential"
header={label} header={label}

View File

@@ -63,7 +63,7 @@ class WorkflowJobTemplate extends Component {
); );
data.summary_fields.webhook_credential.kind = name; data.summary_fields.webhook_credential.kind = name;
} }
setBreadcrumb(data);
this.setState({ template: data }); this.setState({ template: data });
setBreadcrumb(data); setBreadcrumb(data);
} catch (err) { } catch (err) {

View File

@@ -7,7 +7,11 @@ import { WorkflowJobTemplatesAPI, OrganizationsAPI } from '@api';
import ContentLoading from '@components/ContentLoading'; import ContentLoading from '@components/ContentLoading';
import { WorkflowJobTemplateForm } from '../shared'; import { WorkflowJobTemplateForm } from '../shared';
function WorkflowJobTemplateEdit({ template, hasContentLoading }) { function WorkflowJobTemplateEdit({
template,
hasTemplateLoading,
webhook_key,
}) {
const [formSubmitError, setFormSubmitError] = useState(); const [formSubmitError, setFormSubmitError] = useState();
const history = useHistory(); const history = useHistory();
@@ -57,7 +61,7 @@ function WorkflowJobTemplateEdit({ template, hasContentLoading }) {
const handleCancel = () => { const handleCancel = () => {
history.push(`/templates`); history.push(`/templates`);
}; };
if (hasContentLoading) { if (hasTemplateLoading) {
return <ContentLoading />; return <ContentLoading />;
} }
return ( return (
@@ -67,6 +71,7 @@ function WorkflowJobTemplateEdit({ template, hasContentLoading }) {
handleSubmit={handleSubmit} handleSubmit={handleSubmit}
handleCancel={handleCancel} handleCancel={handleCancel}
template={template} template={template}
webhook_key={webhook_key}
/> />
</CardBody> </CardBody>
{formSubmitError ? <div>formSubmitError</div> : ''} {formSubmitError ? <div>formSubmitError</div> : ''}

View File

@@ -1,27 +1,54 @@
import React, { useState } from 'react'; import React, { useState, useEffect } from 'react';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import { withI18n } from '@lingui/react'; import { withI18n } from '@lingui/react';
import { Formik, Field } from 'formik'; import { Formik, Field } from 'formik';
import {
import { Form, FormGroup } from '@patternfly/react-core'; Form,
FormGroup,
Checkbox,
InputGroup,
Button,
TextInput,
} from '@patternfly/react-core';
import { required } from '@util/validators'; import { required } from '@util/validators';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { SyncAltIcon } from '@patternfly/react-icons';
import { useParams } from 'react-router-dom';
import AnsibleSelect from '@components/AnsibleSelect';
import { WorkflowJobTemplatesAPI, CredentialTypesAPI } from '@api';
import FormRow from '@components/FormRow'; import FormRow from '@components/FormRow';
import FormField, { FieldTooltip } from '@components/FormField'; import FormField, { FieldTooltip, CheckboxField } from '@components/FormField';
import OrganizationLookup from '@components/Lookup/OrganizationLookup'; import OrganizationLookup from '@components/Lookup/OrganizationLookup';
import CredentialLookup from '@components/Lookup/CredentialLookup';
import { InventoryLookup } from '@components/Lookup'; import { InventoryLookup } from '@components/Lookup';
import { VariablesField } from '@components/CodeMirrorInput'; import { VariablesField } from '@components/CodeMirrorInput';
import FormActionGroup from '@components/FormActionGroup'; import FormActionGroup from '@components/FormActionGroup';
import ContentError from '@components/ContentError'; import ContentError from '@components/ContentError';
import styled from 'styled-components';
import LabelSelect from './LabelSelect'; import LabelSelect from './LabelSelect';
const GridFormGroup = styled(FormGroup)`
& > label {
grid-column: 1 / -1;
}
&& {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
}
`;
function WorkflowJobTemplateForm({ function WorkflowJobTemplateForm({
handleSubmit, handleSubmit,
handleCancel, handleCancel,
i18n, i18n,
template = {}, template = {},
className,
webhook_key,
}) { }) {
const urlOrigin = window.location.origin;
const { id } = useParams();
const [contentError, setContentError] = useState(null); const [contentError, setContentError] = useState(null);
const [inventory, setInventory] = useState( const [inventory, setInventory] = useState(
template?.summary_fields?.inventory || null template?.summary_fields?.inventory || null
@@ -29,10 +56,77 @@ function WorkflowJobTemplateForm({
const [organization, setOrganization] = useState( const [organization, setOrganization] = useState(
template?.summary_fields?.organization || null template?.summary_fields?.organization || null
); );
const [webHookKey, setWebHookKey] = useState(webhook_key);
const [credTypeId, setCredentialTypeId] = useState();
const [webhookService, setWebHookService] = useState(
template.webhook_service || ''
);
const [hasWebhooks, setHasWebhooks] = useState(
webhookService !== '' || false
);
const [webhookCredential, setWebHookCredential] = useState(
template?.summary_fields?.webhook_credential || null
);
const webhookServiceOptions = [
{
value: '',
key: '',
label: i18n._(t`Choose a Webhook Service`),
isDisabled: true,
},
{
value: 'github',
key: 'github',
namespace: 'git_hub',
label: i18n._(t`GitHub`),
isDisabled: false,
},
{
value: 'gitlab',
key: 'gitlab',
namespace: 'git_lab',
label: i18n._(t`Git Lab`),
isDisabled: false,
},
];
useEffect(() => {
if (!webhookService) {
return;
}
const loadCredentialType = async () => {
try {
const {
data: { results },
} = await CredentialTypesAPI.read({
namespace: webhookService.includes('hub')
? 'github_token'
: 'gitlab_token',
});
setCredentialTypeId(results[0].id);
} catch (err) {
setContentError(err);
}
};
loadCredentialType();
}, [webhookService]);
const changeWebhookKey = async () => {
try {
const {
data: { webhook_key: key },
} = await WorkflowJobTemplatesAPI.updateWebhookKey(id);
setWebHookKey(key);
} catch (err) {
setContentError(err);
}
};
if (contentError) { if (contentError) {
return <ContentError error={contentError} />; return <ContentError error={contentError} />;
} }
return ( return (
<Formik <Formik
onSubmit={handleSubmit} onSubmit={handleSubmit}
@@ -45,6 +139,12 @@ function WorkflowJobTemplateForm({
variables: template.variables || '---', variables: template.variables || '---',
limit: template.limit || '', limit: template.limit || '',
scmBranch: template.scm_branch || '', scmBranch: template.scm_branch || '',
allow_simultaneous: template?.allow_simultaneous || false,
webhook_url: `${urlOrigin}${template?.related?.webhook_receiver}` || '',
webhook_key: webHookKey,
webhook_credential:
template?.summary_fields?.webhook_credential?.id || null,
webhook_service: webhookService,
ask_limit_on_launch: template.ask_limit_on_launch || false, ask_limit_on_launch: template.ask_limit_on_launch || false,
ask_inventory_on_launch: template.ask_inventory_on_launch || false, ask_inventory_on_launch: template.ask_inventory_on_launch || false,
ask_variables_on_launch: template.ask_variables_on_launch || false, ask_variables_on_launch: template.ask_variables_on_launch || false,
@@ -96,9 +196,9 @@ function WorkflowJobTemplateForm({
{({ form }) => ( {({ form }) => (
<InventoryLookup <InventoryLookup
value={inventory} value={inventory}
onBlur={() => form.setFieldTouched('inventory')} tooltip={i18n._(
tooltip={i18n._(t`Select the inventory containing the hosts t`Select an inventory for the workflow. This inventory is applied to all job template nodes that prompt for an inventory.`
you want this job to manage.`)} )}
isValid={!form.touched.inventory || !form.errors.inventory} isValid={!form.touched.inventory || !form.errors.inventory}
helperTextInvalid={form.errors.inventory} helperTextInvalid={form.errors.inventory}
onChange={value => { onChange={value => {
@@ -109,7 +209,6 @@ function WorkflowJobTemplateForm({
); );
setInventory(value); setInventory(value);
}} }}
touched={form.touched.inventory}
error={form.errors.inventory} error={form.errors.inventory}
/> />
)} )}
@@ -126,18 +225,34 @@ function WorkflowJobTemplateForm({
/> />
<FormField type="text" name="limit" id="wfjt-limit" label="" /> <FormField type="text" name="limit" id="wfjt-limit" label="" />
</FormGroup> </FormGroup>
<FormField <FormGroup
type="text" fieldId="wfjt-scmBranch"
id="wfjt-scmBranch" id="wfjt-scmBranch"
label={i18n._(t`SCM Branch`)} label={i18n._(t`SCM Branch`)}
name="scmBranch" name="scmBranch"
/> >
<FieldTooltip
content={i18n._(
t`Select a branch for the workflow. This branch is applied to all job template nodes that prompt for a branch.`
)}
/>
<FormField
type="text"
id="wfjt-scmBranch"
label=""
name="scmBranch"
/>
</FormGroup>
</FormRow> </FormRow>
<FormRow> <FormRow>
<Field name="labels"> <Field name="labels">
{({ field, form }) => ( {({ form, field }) => (
<FormGroup label={i18n._(t`Labels`)} fieldId="wfjt-labels"> <FormGroup
label={i18n._(t`Labels`)}
name="wfjt-labels"
fieldId="wfjt-labels"
>
<FieldTooltip <FieldTooltip
content={i18n._(t`Optional labels that describe this job template, content={i18n._(t`Optional labels that describe this job template,
such as 'dev' or 'test'. Labels can be used to group and filter such as 'dev' or 'test'. Labels can be used to group and filter
@@ -157,8 +272,159 @@ function WorkflowJobTemplateForm({
id="host-variables" id="host-variables"
name="variables" name="variables"
label={i18n._(t`Variables`)} label={i18n._(t`Variables`)}
tooltip={i18n._(
t`Pass extra command line variables to the playbook. This is the -e or --extra-vars command line parameter for ansible-playbook. Provide key/value pairs using either YAML or JSON. Refer to the Ansible Tower documentation for example syntax.`
)}
/> />
</FormRow> </FormRow>
<GridFormGroup
className={className}
fieldId="options"
isInline
label={i18n._(t`Options`)}
css="margin-top: 20px"
>
<Checkbox
id="wfjt-webhooks"
name="has_webhooks"
label={
<span>
{i18n._(t`Webhooks`)} &nbsp;
<FieldTooltip
content={i18n._(
t`Enable webhook for this workflow job template.`
)}
/>
</span>
}
isChecked={hasWebhooks}
onChange={checked => {
setHasWebhooks(checked);
}}
/>
<CheckboxField
id="wfjt-allow_simultaneous"
name="allow_simultaneous"
label={i18n._(t`Enable Concurrent Jobs`)}
tooltip={i18n._(
t`If enabled, simultaneous runs of this workflow job template will be allowed.`
)}
/>
</GridFormGroup>
{hasWebhooks && (
<>
<FormRow>
{template.related && (
<FormGroup
className={className}
fieldId="wfjt-webhook-url"
id="wfjt-webhook-url"
name="webhook_url"
label={i18n._(t`Webhook URL`)}
>
<FieldTooltip
content={i18n._(
t`Webhook services can launch jobs with this job template by making a POST request to this URL.`
)}
/>
<FormField
type="text"
id="wfjt-webhook-url"
name="webhook_url"
label=""
isReadOnly
/>
</FormGroup>
)}
{template.related && (
<FormGroup
fieldId="wfjt-webhook-key"
type="text"
id="wfjt-webhook-key"
name="webhook_key"
label={i18n._(t`Webhook Key`)}
>
<FieldTooltip
content={i18n._(
t`Webhook services can use this as a shared secret.`
)}
/>
<InputGroup>
<TextInput
isReadOnly
aria-label="wfjt-webhook-key"
value={webHookKey}
/>
<Button variant="tertiary" onClick={changeWebhookKey}>
<SyncAltIcon />
</Button>
</InputGroup>
</FormGroup>
)}
<Field name="webhook_service">
{({ form }) => {
const isValid =
!form.errors.webhook_service;
return (
<FormGroup
name="webhook_service"
fieldId="webhook_service"
helperTextInvalid={form.errors.webhook_service}
isValid={isValid}
label={i18n._(t`Webhook Service`)}
>
<FieldTooltip
content={i18n._(t`Select a webhook service`)}
/>
<AnsibleSelect
isValid={isValid}
id="webhook_service"
data={webhookServiceOptions}
value={webhookService}
onChange={(event, val) => {
setWebHookService(val);
setWebHookCredential(null);
form.setFieldValue('webhook_credential', null);
}}
/>
</FormGroup>
);
}}
</Field>
</FormRow>
{credTypeId && (
<FormRow>
<Field name="webhook_credential">
{({ form }) => {
return (
<FormGroup
fieldId="webhook_credential"
id="webhook_credential"
name="webhook_credential"
>
<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 || null}
onChange={value => {
setWebHookCredential(value);
form.setFieldValue(
'webhook_credential',
value.id
);
}}
value={webhookCredential}
/>
</FormGroup>
);
}}
</Field>
</FormRow>
)}
</>
)}
<FormActionGroup <FormActionGroup
onCancel={handleCancel} onCancel={handleCancel}
onSubmit={formik.handleSubmit} onSubmit={formik.handleSubmit}