mirror of
https://github.com/ansible/awx.git
synced 2026-02-26 15:36:04 -03:30
Merge pull request #6547 from AlexSCorey/6384-ConvertWFJTToHooks
Converts WFJTForm to Formik hooks Reviewed-by: https://github.com/apps/softwarefactory-project-zuul
This commit is contained in:
@@ -35,7 +35,7 @@ const mockJobTemplate = {
|
|||||||
limit: '',
|
limit: '',
|
||||||
name: 'Foo',
|
name: 'Foo',
|
||||||
playbook: 'Baz',
|
playbook: 'Baz',
|
||||||
project: { id: 3, summary_fields: { organization: { id: 1 } } },
|
project: 3,
|
||||||
scm_branch: '',
|
scm_branch: '',
|
||||||
skip_tags: '',
|
skip_tags: '',
|
||||||
summary_fields: {
|
summary_fields: {
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ class WorkflowJobTemplate extends Component {
|
|||||||
contentError: null,
|
contentError: null,
|
||||||
hasContentLoading: true,
|
hasContentLoading: true,
|
||||||
template: null,
|
template: null,
|
||||||
webhook_key: null,
|
|
||||||
isNotifAdmin: false,
|
isNotifAdmin: false,
|
||||||
};
|
};
|
||||||
this.createSchedule = this.createSchedule.bind(this);
|
this.createSchedule = this.createSchedule.bind(this);
|
||||||
@@ -59,11 +58,9 @@ class WorkflowJobTemplate extends Component {
|
|||||||
this.setState({ contentError: null });
|
this.setState({ contentError: null });
|
||||||
try {
|
try {
|
||||||
const { data } = await WorkflowJobTemplatesAPI.readDetail(id);
|
const { data } = await WorkflowJobTemplatesAPI.readDetail(id);
|
||||||
|
let webhookKey;
|
||||||
if (data?.related?.webhook_key) {
|
if (data?.related?.webhook_key) {
|
||||||
const {
|
webhookKey = await WorkflowJobTemplatesAPI.readWebhookKey(id);
|
||||||
data: { webhook_key },
|
|
||||||
} = await WorkflowJobTemplatesAPI.readWebhookKey(id);
|
|
||||||
this.setState({ webhook_key });
|
|
||||||
}
|
}
|
||||||
if (data?.summary_fields?.webhook_credential) {
|
if (data?.summary_fields?.webhook_credential) {
|
||||||
const {
|
const {
|
||||||
@@ -83,7 +80,7 @@ class WorkflowJobTemplate extends Component {
|
|||||||
});
|
});
|
||||||
setBreadcrumb(data);
|
setBreadcrumb(data);
|
||||||
this.setState({
|
this.setState({
|
||||||
template: data,
|
template: { ...data, webhook_key: webhookKey.data.webhook_key },
|
||||||
isNotifAdmin: notifAdminRes.data.results.length > 0,
|
isNotifAdmin: notifAdminRes.data.results.length > 0,
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -114,7 +111,6 @@ class WorkflowJobTemplate extends Component {
|
|||||||
contentError,
|
contentError,
|
||||||
hasContentLoading,
|
hasContentLoading,
|
||||||
template,
|
template,
|
||||||
webhook_key,
|
|
||||||
isNotifAdmin,
|
isNotifAdmin,
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
@@ -211,10 +207,7 @@ class WorkflowJobTemplate extends Component {
|
|||||||
key="wfjt-details"
|
key="wfjt-details"
|
||||||
path="/templates/workflow_job_template/:id/details"
|
path="/templates/workflow_job_template/:id/details"
|
||||||
>
|
>
|
||||||
<WorkflowJobTemplateDetail
|
<WorkflowJobTemplateDetail template={template} />
|
||||||
template={template}
|
|
||||||
webhook_key={webhook_key}
|
|
||||||
/>
|
|
||||||
</Route>
|
</Route>
|
||||||
)}
|
)}
|
||||||
{template && (
|
{template && (
|
||||||
@@ -239,10 +232,7 @@ class WorkflowJobTemplate extends Component {
|
|||||||
key="wfjt-edit"
|
key="wfjt-edit"
|
||||||
path="/templates/workflow_job_template/:id/edit"
|
path="/templates/workflow_job_template/:id/edit"
|
||||||
>
|
>
|
||||||
<WorkflowJobTemplateEdit
|
<WorkflowJobTemplateEdit template={template} />
|
||||||
template={template}
|
|
||||||
webhook_key={webhook_key}
|
|
||||||
/>
|
|
||||||
</Route>
|
</Route>
|
||||||
)}
|
)}
|
||||||
{template && (
|
{template && (
|
||||||
|
|||||||
@@ -12,11 +12,23 @@ function WorkflowJobTemplateAdd() {
|
|||||||
const [formSubmitError, setFormSubmitError] = useState(null);
|
const [formSubmitError, setFormSubmitError] = useState(null);
|
||||||
|
|
||||||
const handleSubmit = async values => {
|
const handleSubmit = async values => {
|
||||||
const { labels, organizationId, ...remainingValues } = values;
|
const {
|
||||||
|
labels,
|
||||||
|
inventory,
|
||||||
|
organization,
|
||||||
|
webhook_credential,
|
||||||
|
webhook_key,
|
||||||
|
...templatePayload
|
||||||
|
} = values;
|
||||||
|
templatePayload.inventory = inventory?.id;
|
||||||
|
templatePayload.organization = organization?.id;
|
||||||
|
templatePayload.webhook_credential = webhook_credential?.id;
|
||||||
|
const organizationId =
|
||||||
|
organization?.id || inventory?.summary_fields?.organization.id;
|
||||||
try {
|
try {
|
||||||
const {
|
const {
|
||||||
data: { id },
|
data: { id },
|
||||||
} = await WorkflowJobTemplatesAPI.create(remainingValues);
|
} = await WorkflowJobTemplatesAPI.create(templatePayload);
|
||||||
await Promise.all(await submitLabels(id, labels, organizationId));
|
await Promise.all(await submitLabels(id, labels, organizationId));
|
||||||
history.push(`/templates/workflow_job_template/${id}/details`);
|
history.push(`/templates/workflow_job_template/${id}/details`);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@@ -15,9 +15,11 @@ jest.mock('@api/models/Inventories');
|
|||||||
describe('<WorkflowJobTemplateAdd/>', () => {
|
describe('<WorkflowJobTemplateAdd/>', () => {
|
||||||
let wrapper;
|
let wrapper;
|
||||||
let history;
|
let history;
|
||||||
|
const handleSubmit = jest.fn();
|
||||||
|
const handleCancel = jest.fn();
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
WorkflowJobTemplatesAPI.create.mockResolvedValue({ data: { id: 1 } });
|
WorkflowJobTemplatesAPI.create.mockResolvedValue({ data: { id: 1 } });
|
||||||
OrganizationsAPI.read.mockResolvedValue({ results: [{ id: 1 }] });
|
OrganizationsAPI.read.mockResolvedValue({ data: { results: [{ id: 1 }] } });
|
||||||
LabelsAPI.read.mockResolvedValue({
|
LabelsAPI.read.mockResolvedValue({
|
||||||
data: {
|
data: {
|
||||||
results: [
|
results: [
|
||||||
@@ -36,7 +38,12 @@ describe('<WorkflowJobTemplateAdd/>', () => {
|
|||||||
wrapper = mountWithContexts(
|
wrapper = mountWithContexts(
|
||||||
<Route
|
<Route
|
||||||
path="/templates/workflow_job_template/add"
|
path="/templates/workflow_job_template/add"
|
||||||
component={() => <WorkflowJobTemplateAdd />}
|
component={() => (
|
||||||
|
<WorkflowJobTemplateAdd
|
||||||
|
handleSubmit={handleSubmit}
|
||||||
|
handleCancel={handleCancel}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
/>,
|
/>,
|
||||||
{
|
{
|
||||||
context: {
|
context: {
|
||||||
@@ -63,16 +70,48 @@ describe('<WorkflowJobTemplateAdd/>', () => {
|
|||||||
|
|
||||||
test('calls workflowJobTemplatesAPI with correct information on submit', async () => {
|
test('calls workflowJobTemplatesAPI with correct information on submit', async () => {
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
await wrapper.find('WorkflowJobTemplateForm').invoke('handleSubmit')({
|
wrapper.find('input#wfjt-name').simulate('change', {
|
||||||
name: 'Alex',
|
target: { value: 'Alex', name: 'name' },
|
||||||
labels: [{ name: 'Foo', id: 1 }, { name: 'bar', id: 2 }],
|
|
||||||
organizationId: 1,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
wrapper
|
||||||
|
.find('LabelSelect')
|
||||||
|
.find('SelectToggle')
|
||||||
|
.simulate('click');
|
||||||
});
|
});
|
||||||
expect(WorkflowJobTemplatesAPI.create).toHaveBeenCalledWith({
|
|
||||||
|
wrapper.update();
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
wrapper
|
||||||
|
.find('SelectOption')
|
||||||
|
.find('button[aria-label="Label 3"]')
|
||||||
|
.prop('onClick')();
|
||||||
|
});
|
||||||
|
|
||||||
|
wrapper.update();
|
||||||
|
await act(async () => {
|
||||||
|
wrapper.find('form').simulate('submit');
|
||||||
|
});
|
||||||
|
await expect(WorkflowJobTemplatesAPI.create).toHaveBeenCalledWith({
|
||||||
name: 'Alex',
|
name: 'Alex',
|
||||||
|
allow_simultaneous: false,
|
||||||
|
ask_inventory_on_launch: false,
|
||||||
|
ask_limit_on_launch: false,
|
||||||
|
ask_scm_branch_on_launch: false,
|
||||||
|
ask_variables_on_launch: false,
|
||||||
|
description: '',
|
||||||
|
extra_vars: '---',
|
||||||
|
inventory: undefined,
|
||||||
|
limit: '',
|
||||||
|
organization: undefined,
|
||||||
|
scm_branch: '',
|
||||||
|
webhook_credential: undefined,
|
||||||
|
webhook_service: '',
|
||||||
|
webhook_url: '',
|
||||||
});
|
});
|
||||||
expect(WorkflowJobTemplatesAPI.associateLabel).toHaveBeenCalledTimes(2);
|
|
||||||
|
expect(WorkflowJobTemplatesAPI.associateLabel).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('handleCancel navigates the user to the /templates', async () => {
|
test('handleCancel navigates the user to the /templates', async () => {
|
||||||
@@ -95,10 +134,16 @@ describe('<WorkflowJobTemplateAdd/>', () => {
|
|||||||
|
|
||||||
WorkflowJobTemplatesAPI.create.mockRejectedValue(error);
|
WorkflowJobTemplatesAPI.create.mockRejectedValue(error);
|
||||||
await act(async () => {
|
await act(async () => {
|
||||||
wrapper.find('WorkflowJobTemplateForm').invoke('handleSubmit')({
|
wrapper.find('input#wfjt-name').simulate('change', {
|
||||||
name: 'Foo',
|
target: { value: 'Alex', name: 'name' },
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
wrapper.update();
|
||||||
|
await act(async () => {
|
||||||
|
wrapper.find('form').simulate('submit');
|
||||||
|
});
|
||||||
|
|
||||||
expect(WorkflowJobTemplatesAPI.create).toHaveBeenCalled();
|
expect(WorkflowJobTemplatesAPI.create).toHaveBeenCalled();
|
||||||
wrapper.update();
|
wrapper.update();
|
||||||
expect(wrapper.find('WorkflowJobTemplateForm').prop('submitError')).toEqual(
|
expect(wrapper.find('WorkflowJobTemplateForm').prop('submitError')).toEqual(
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ import LaunchButton from '@components/LaunchButton';
|
|||||||
import Sparkline from '@components/Sparkline';
|
import Sparkline from '@components/Sparkline';
|
||||||
import { toTitleCase } from '@util/strings';
|
import { toTitleCase } from '@util/strings';
|
||||||
|
|
||||||
function WorkflowJobTemplateDetail({ template, i18n, webhook_key }) {
|
function WorkflowJobTemplateDetail({ template, i18n }) {
|
||||||
const {
|
const {
|
||||||
id,
|
id,
|
||||||
ask_inventory_on_launch,
|
ask_inventory_on_launch,
|
||||||
@@ -38,6 +38,7 @@ function WorkflowJobTemplateDetail({ template, i18n, webhook_key }) {
|
|||||||
summary_fields,
|
summary_fields,
|
||||||
related,
|
related,
|
||||||
webhook_credential,
|
webhook_credential,
|
||||||
|
webhook_key,
|
||||||
} = template;
|
} = template;
|
||||||
|
|
||||||
const urlOrigin = window.location.origin;
|
const urlOrigin = window.location.origin;
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ describe('<WorkflowJobTemplateDetail/>', () => {
|
|||||||
user_capabilities: { edit: true, delete: true },
|
user_capabilities: { edit: true, delete: true },
|
||||||
},
|
},
|
||||||
webhook_service: 'Github',
|
webhook_service: 'Github',
|
||||||
|
webhook_key: 'Foo webhook key',
|
||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
@@ -52,7 +53,6 @@ describe('<WorkflowJobTemplateDetail/>', () => {
|
|||||||
component={() => (
|
component={() => (
|
||||||
<WorkflowJobTemplateDetail
|
<WorkflowJobTemplateDetail
|
||||||
template={template}
|
template={template}
|
||||||
webhook_key="Foo webhook key"
|
|
||||||
hasContentLoading={false}
|
hasContentLoading={false}
|
||||||
onSetContentLoading={() => {}}
|
onSetContentLoading={() => {}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -6,17 +6,30 @@ import { getAddedAndRemoved } from '@util/lists';
|
|||||||
import { WorkflowJobTemplatesAPI, OrganizationsAPI } from '@api';
|
import { WorkflowJobTemplatesAPI, OrganizationsAPI } from '@api';
|
||||||
import { WorkflowJobTemplateForm } from '../shared';
|
import { WorkflowJobTemplateForm } from '../shared';
|
||||||
|
|
||||||
function WorkflowJobTemplateEdit({ template, webhook_key }) {
|
function WorkflowJobTemplateEdit({ template }) {
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
const [formSubmitError, setFormSubmitError] = useState(null);
|
const [formSubmitError, setFormSubmitError] = useState(null);
|
||||||
|
|
||||||
const handleSubmit = async values => {
|
const handleSubmit = async values => {
|
||||||
const { labels, ...remainingValues } = values;
|
const {
|
||||||
|
labels,
|
||||||
|
inventory,
|
||||||
|
organization,
|
||||||
|
webhook_credential,
|
||||||
|
webhook_key,
|
||||||
|
...templatePayload
|
||||||
|
} = values;
|
||||||
|
templatePayload.inventory = inventory?.id;
|
||||||
|
templatePayload.organization = organization?.id;
|
||||||
|
templatePayload.webhook_credential = webhook_credential?.id || null;
|
||||||
|
|
||||||
|
const formOrgId =
|
||||||
|
organization?.id || inventory?.summary_fields?.organization.id || null;
|
||||||
try {
|
try {
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
await submitLabels(labels, values.organization, template.organization)
|
await submitLabels(labels, formOrgId, template.organization)
|
||||||
);
|
);
|
||||||
await WorkflowJobTemplatesAPI.update(template.id, remainingValues);
|
await WorkflowJobTemplatesAPI.update(template.id, templatePayload);
|
||||||
history.push(`/templates/workflow_job_template/${template.id}/details`);
|
history.push(`/templates/workflow_job_template/${template.id}/details`);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setFormSubmitError(err);
|
setFormSubmitError(err);
|
||||||
@@ -60,7 +73,6 @@ function WorkflowJobTemplateEdit({ template, webhook_key }) {
|
|||||||
handleSubmit={handleSubmit}
|
handleSubmit={handleSubmit}
|
||||||
handleCancel={handleCancel}
|
handleCancel={handleCancel}
|
||||||
template={template}
|
template={template}
|
||||||
webhook_key={webhook_key}
|
|
||||||
submitError={formSubmitError}
|
submitError={formSubmitError}
|
||||||
/>
|
/>
|
||||||
</CardBody>
|
</CardBody>
|
||||||
|
|||||||
@@ -29,10 +29,15 @@ const mockTemplate = {
|
|||||||
describe('<WorkflowJobTemplateEdit/>', () => {
|
describe('<WorkflowJobTemplateEdit/>', () => {
|
||||||
let wrapper;
|
let wrapper;
|
||||||
let history;
|
let history;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
LabelsAPI.read.mockResolvedValue({
|
LabelsAPI.read.mockResolvedValue({
|
||||||
data: {
|
data: {
|
||||||
results: [{ name: 'Label 1', id: 1 }, { name: 'Label 2', id: 2 }],
|
results: [
|
||||||
|
{ name: 'Label 1', id: 1 },
|
||||||
|
{ name: 'Label 2', id: 2 },
|
||||||
|
{ name: 'Label 3', id: 3 },
|
||||||
|
],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
OrganizationsAPI.read.mockResolvedValue({ results: [{ id: 1 }] });
|
OrganizationsAPI.read.mockResolvedValue({ results: [{ id: 1 }] });
|
||||||
@@ -71,29 +76,65 @@ describe('<WorkflowJobTemplateEdit/>', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('api is called to properly to save the updated template.', async () => {
|
test('api is called to properly to save the updated template.', async () => {
|
||||||
await act(async () => {
|
act(() => {
|
||||||
await wrapper.find('WorkflowJobTemplateForm').invoke('handleSubmit')({
|
wrapper.find('input#wfjt-name').simulate('change', {
|
||||||
id: 6,
|
target: { value: 'Alex', name: 'name' },
|
||||||
name: 'Alex',
|
|
||||||
description: 'Apollo and Athena',
|
|
||||||
inventory: 1,
|
|
||||||
organization: 1,
|
|
||||||
labels: [{ name: 'Label 2', id: 2 }, { name: 'Generated Label' }],
|
|
||||||
scm_branch: 'master',
|
|
||||||
limit: '5000',
|
|
||||||
variables: '---',
|
|
||||||
});
|
});
|
||||||
|
wrapper.find('input#wfjt-description').simulate('change', {
|
||||||
|
target: { value: 'Apollo and Athena', name: 'description' },
|
||||||
|
});
|
||||||
|
wrapper.find('input#wfjt-description').simulate('change', {
|
||||||
|
target: { value: 'master', name: 'scm_branch' },
|
||||||
|
});
|
||||||
|
wrapper.find('input#wfjt-description').simulate('change', {
|
||||||
|
target: { value: '5000', name: 'limit' },
|
||||||
|
});
|
||||||
|
wrapper
|
||||||
|
.find('LabelSelect')
|
||||||
|
.find('SelectToggle')
|
||||||
|
.simulate('click');
|
||||||
|
});
|
||||||
|
|
||||||
|
wrapper.update();
|
||||||
|
|
||||||
|
act(() => {
|
||||||
|
wrapper
|
||||||
|
.find('SelectOption')
|
||||||
|
.find('button[aria-label="Label 3"]')
|
||||||
|
.prop('onClick')();
|
||||||
|
});
|
||||||
|
|
||||||
|
wrapper.update();
|
||||||
|
|
||||||
|
act(() =>
|
||||||
|
wrapper
|
||||||
|
.find('SelectOption')
|
||||||
|
.find('button[aria-label="Label 1"]')
|
||||||
|
.prop('onClick')()
|
||||||
|
);
|
||||||
|
|
||||||
|
wrapper.update();
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
wrapper.find('WorkflowJobTemplateForm').invoke('handleSubmit')();
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(WorkflowJobTemplatesAPI.update).toHaveBeenCalledWith(6, {
|
expect(WorkflowJobTemplatesAPI.update).toHaveBeenCalledWith(6, {
|
||||||
id: 6,
|
|
||||||
name: 'Alex',
|
name: 'Alex',
|
||||||
description: 'Apollo and Athena',
|
description: 'Apollo and Athena',
|
||||||
inventory: 1,
|
inventory: 1,
|
||||||
organization: 1,
|
organization: 1,
|
||||||
scm_branch: 'master',
|
scm_branch: 'master',
|
||||||
limit: '5000',
|
limit: '5000',
|
||||||
variables: '---',
|
extra_vars: '---',
|
||||||
|
webhook_credential: null,
|
||||||
|
webhook_url: '',
|
||||||
|
webhook_service: '',
|
||||||
|
allow_simultaneous: false,
|
||||||
|
ask_inventory_on_launch: false,
|
||||||
|
ask_limit_on_launch: false,
|
||||||
|
ask_scm_branch_on_launch: false,
|
||||||
|
ask_variables_on_launch: false,
|
||||||
});
|
});
|
||||||
wrapper.update();
|
wrapper.update();
|
||||||
await expect(WorkflowJobTemplatesAPI.disassociateLabel).toBeCalledWith(6, {
|
await expect(WorkflowJobTemplatesAPI.disassociateLabel).toBeCalledWith(6, {
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ describe('<JobTemplateForm />', () => {
|
|||||||
description: 'Bar',
|
description: 'Bar',
|
||||||
job_type: 'run',
|
job_type: 'run',
|
||||||
inventory: 2,
|
inventory: 2,
|
||||||
project: { id: 3, summary_fields: { organization: { id: 1 } } },
|
project: 3,
|
||||||
playbook: 'Baz',
|
playbook: 'Baz',
|
||||||
type: 'job_template',
|
type: 'job_template',
|
||||||
scm_branch: 'Foo',
|
scm_branch: 'Foo',
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ function LabelSelect({ value, placeholder, onChange, onError }) {
|
|||||||
|
|
||||||
const renderOptions = opts => {
|
const renderOptions = opts => {
|
||||||
return opts.map(option => (
|
return opts.map(option => (
|
||||||
<SelectOption key={option.id} value={option}>
|
<SelectOption key={option.id} aria-label={option.name} value={option}>
|
||||||
{option.name}
|
{option.name}
|
||||||
</SelectOption>
|
</SelectOption>
|
||||||
));
|
));
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ import React, { useState, useEffect, useCallback } from 'react';
|
|||||||
import { t } from '@lingui/macro';
|
import { t } from '@lingui/macro';
|
||||||
import { useRouteMatch, useParams } from 'react-router-dom';
|
import { useRouteMatch, useParams } from 'react-router-dom';
|
||||||
|
|
||||||
import { func, shape } from 'prop-types';
|
import PropTypes, { shape } from 'prop-types';
|
||||||
|
|
||||||
import { withI18n } from '@lingui/react';
|
import { withI18n } from '@lingui/react';
|
||||||
import { Formik, Field } from 'formik';
|
import { useField, withFormik } from 'formik';
|
||||||
import {
|
import {
|
||||||
Form,
|
Form,
|
||||||
FormGroup,
|
FormGroup,
|
||||||
@@ -40,38 +40,49 @@ import ContentError from '@components/ContentError';
|
|||||||
import CheckboxField from '@components/FormField/CheckboxField';
|
import CheckboxField from '@components/FormField/CheckboxField';
|
||||||
import LabelSelect from './LabelSelect';
|
import LabelSelect from './LabelSelect';
|
||||||
|
|
||||||
|
const urlOrigin = window.location.origin;
|
||||||
function WorkflowJobTemplateForm({
|
function WorkflowJobTemplateForm({
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
handleCancel,
|
handleCancel,
|
||||||
i18n,
|
i18n,
|
||||||
template = {},
|
|
||||||
webhook_key,
|
|
||||||
submitError,
|
submitError,
|
||||||
}) {
|
}) {
|
||||||
const urlOrigin = window.location.origin;
|
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
const wfjtAddMatch = useRouteMatch('/templates/workflow_job_template/add');
|
const wfjtAddMatch = useRouteMatch('/templates/workflow_job_template/add');
|
||||||
|
|
||||||
const [hasContentError, setContentError] = useState(null);
|
const [hasContentError, setContentError] = useState(null);
|
||||||
const [webhook_url, setWebhookUrl] = useState(
|
|
||||||
template?.related?.webhook_receiver
|
const [organizationField, organizationMeta, organizationHelpers] = useField(
|
||||||
? `${urlOrigin}${template.related.webhook_receiver}`
|
'organization'
|
||||||
: ''
|
|
||||||
);
|
);
|
||||||
const [inventory, setInventory] = useState(
|
const [inventoryField, inventoryMeta, inventoryHelpers] = useField(
|
||||||
template?.summary_fields?.inventory || null
|
'inventory'
|
||||||
);
|
);
|
||||||
const [organization, setOrganization] = useState(
|
const [labelsField, , labelsHelpers] = useField('labels');
|
||||||
template?.summary_fields?.organization || null
|
|
||||||
|
const [
|
||||||
|
webhookServiceField,
|
||||||
|
webhookServiceMeta,
|
||||||
|
webhookServiceHelpers,
|
||||||
|
] = useField('webhook_service');
|
||||||
|
|
||||||
|
const [webhookKeyField, webhookKeyMeta, webhookKeyHelpers] = useField(
|
||||||
|
'webhook_key'
|
||||||
);
|
);
|
||||||
const [webhookCredential, setWebhookCredential] = useState(
|
|
||||||
template?.summary_fields?.webhook_credential || null
|
const [hasWebhooks, setHasWebhooks] = useState(
|
||||||
|
Boolean(webhookServiceField.value)
|
||||||
);
|
);
|
||||||
const [webhookKey, setWebHookKey] = useState(webhook_key);
|
|
||||||
const [webhookService, setWebHookService] = useState(
|
const [
|
||||||
template.webhook_service || ''
|
webhookCredentialField,
|
||||||
|
webhookCredentialMeta,
|
||||||
|
webhookCredentialHelpers,
|
||||||
|
] = useField('webhook_credential');
|
||||||
|
|
||||||
|
const [webhookUrlField, webhookUrlMeta, webhookUrlHelpers] = useField(
|
||||||
|
'webhook_url'
|
||||||
);
|
);
|
||||||
const [hasWebhooks, setHasWebhooks] = useState(Boolean(webhookService));
|
|
||||||
|
|
||||||
const webhookServiceOptions = [
|
const webhookServiceOptions = [
|
||||||
{
|
{
|
||||||
@@ -93,6 +104,38 @@ function WorkflowJobTemplateForm({
|
|||||||
isDisabled: false,
|
isDisabled: false,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const storeWebhookValues = webhookServiceValue => {
|
||||||
|
if (
|
||||||
|
webhookServiceValue === webhookServiceMeta.initialValue ||
|
||||||
|
webhookServiceValue === ''
|
||||||
|
) {
|
||||||
|
webhookCredentialHelpers.setValue(webhookCredentialMeta.initialValue);
|
||||||
|
webhookUrlHelpers.setValue(webhookUrlMeta.initialValue);
|
||||||
|
webhookServiceHelpers.setValue(webhookServiceMeta.initialValue);
|
||||||
|
webhookKeyHelpers.setValue(webhookKeyMeta.initialValue);
|
||||||
|
} else {
|
||||||
|
webhookCredentialHelpers.setValue(null);
|
||||||
|
webhookUrlHelpers.setValue(
|
||||||
|
`${urlOrigin}/api/v2/workflow_job_templates/${id}/${webhookServiceValue}/`
|
||||||
|
);
|
||||||
|
webhookKeyHelpers.setValue(
|
||||||
|
i18n._(t`a new webhook key will be generated on save.`).toUpperCase()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleWebhookEnablement = (enabledWebhooks, webhookServiceValue) => {
|
||||||
|
if (!enabledWebhooks) {
|
||||||
|
webhookCredentialHelpers.setValue(null);
|
||||||
|
webhookServiceHelpers.setValue('');
|
||||||
|
webhookUrlHelpers.setValue('');
|
||||||
|
webhookKeyHelpers.setValue('');
|
||||||
|
} else {
|
||||||
|
storeWebhookValues(webhookServiceValue);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const {
|
const {
|
||||||
request: loadCredentialType,
|
request: loadCredentialType,
|
||||||
error: contentError,
|
error: contentError,
|
||||||
@@ -101,15 +144,15 @@ function WorkflowJobTemplateForm({
|
|||||||
} = useRequest(
|
} = useRequest(
|
||||||
useCallback(async () => {
|
useCallback(async () => {
|
||||||
let results;
|
let results;
|
||||||
if (webhookService) {
|
if (webhookServiceField.value) {
|
||||||
results = await CredentialTypesAPI.read({
|
results = await CredentialTypesAPI.read({
|
||||||
namespace: `${webhookService}_token`,
|
namespace: `${webhookServiceField.value}_token`,
|
||||||
});
|
});
|
||||||
// TODO: Consider how to handle the situation where the results returns
|
// TODO: Consider how to handle the situation where the results returns
|
||||||
// and empty array, or any of the other values is undefined or null (data, results, id)
|
// and empty array, or any of the other values is undefined or null (data, results, id)
|
||||||
}
|
}
|
||||||
return results?.data?.results[0]?.id;
|
return results?.data?.results[0]?.id;
|
||||||
}, [webhookService])
|
}, [webhookServiceField.value])
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -124,66 +167,12 @@ function WorkflowJobTemplateForm({
|
|||||||
const {
|
const {
|
||||||
data: { webhook_key: key },
|
data: { webhook_key: key },
|
||||||
} = await WorkflowJobTemplatesAPI.updateWebhookKey(id);
|
} = await WorkflowJobTemplatesAPI.updateWebhookKey(id);
|
||||||
setWebHookKey(key);
|
webhookKeyHelpers.setValue(key);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setContentError(err);
|
setContentError(err);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let initialWebhookKey = webhook_key;
|
|
||||||
const initialWebhookCredential = template?.summary_fields?.webhook_credential;
|
|
||||||
|
|
||||||
const storeWebhookValues = (form, webhookServiceValue) => {
|
|
||||||
if (
|
|
||||||
webhookServiceValue === form.initialValues.webhook_service ||
|
|
||||||
webhookServiceValue === ''
|
|
||||||
) {
|
|
||||||
form.setFieldValue(
|
|
||||||
'webhook_credential',
|
|
||||||
form.initialValues.webhook_credential
|
|
||||||
);
|
|
||||||
setWebhookCredential(initialWebhookCredential);
|
|
||||||
|
|
||||||
setWebhookUrl(
|
|
||||||
template?.related?.webhook_receiver
|
|
||||||
? `${urlOrigin}${template.related.webhook_receiver}`
|
|
||||||
: ''
|
|
||||||
);
|
|
||||||
form.setFieldValue('webhook_service', form.initialValues.webhook_service);
|
|
||||||
setWebHookService(form.initialValues.webhook_service);
|
|
||||||
|
|
||||||
setWebHookKey(initialWebhookKey);
|
|
||||||
} else {
|
|
||||||
form.setFieldValue('webhook_credential', null);
|
|
||||||
setWebhookCredential(null);
|
|
||||||
|
|
||||||
setWebhookUrl(
|
|
||||||
`${urlOrigin}/api/v2/workflow_job_templates/${template.id}/${webhookServiceValue}/`
|
|
||||||
);
|
|
||||||
|
|
||||||
setWebHookKey(
|
|
||||||
i18n._(t`a new webhook key will be generated on save.`).toUpperCase()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleWebhookEnablement = (
|
|
||||||
form,
|
|
||||||
enabledWebhooks,
|
|
||||||
webhookServiceValue
|
|
||||||
) => {
|
|
||||||
if (!enabledWebhooks) {
|
|
||||||
initialWebhookKey = webhookKey;
|
|
||||||
form.setFieldValue('webhook_credential', null);
|
|
||||||
form.setFieldValue('webhook_service', '');
|
|
||||||
setWebhookUrl('');
|
|
||||||
setWebHookService('');
|
|
||||||
setWebHookKey('');
|
|
||||||
} else {
|
|
||||||
storeWebhookValues(form, webhookServiceValue);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (hasContentError || contentError) {
|
if (hasContentError || contentError) {
|
||||||
return <ContentError error={contentError || hasContentError} />;
|
return <ContentError error={contentError || hasContentError} />;
|
||||||
}
|
}
|
||||||
@@ -193,312 +182,213 @@ function WorkflowJobTemplateForm({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Formik
|
<Form autoComplete="off" onSubmit={handleSubmit}>
|
||||||
onSubmit={values => {
|
<FormColumnLayout>
|
||||||
if (values.webhook_service === '') {
|
<FormField
|
||||||
values.webhook_credential = '';
|
id="wfjt-name"
|
||||||
}
|
name="name"
|
||||||
return handleSubmit(values);
|
type="text"
|
||||||
}}
|
label={i18n._(t`Name`)}
|
||||||
initialValues={{
|
validate={required(null, i18n)}
|
||||||
name: template.name || '',
|
isRequired
|
||||||
description: template.description || '',
|
/>
|
||||||
inventory: template?.summary_fields?.inventory?.id || null,
|
<FormField
|
||||||
organization: template?.summary_fields?.organization?.id || null,
|
id="wfjt-description"
|
||||||
labels: template.summary_fields?.labels?.results || [],
|
name="description"
|
||||||
extra_vars: template.extra_vars || '---',
|
type="text"
|
||||||
limit: template.limit || '',
|
label={i18n._(t`Description`)}
|
||||||
scm_branch: template.scm_branch || '',
|
/>
|
||||||
allow_simultaneous: template.allow_simultaneous || false,
|
<OrganizationLookup
|
||||||
webhook_credential:
|
helperTextInvalid={organizationMeta.error}
|
||||||
template?.summary_fields?.webhook_credential?.id || null,
|
onChange={value => {
|
||||||
webhook_service: template.webhook_service || '',
|
organizationHelpers.setValue(value || null);
|
||||||
ask_limit_on_launch: template.ask_limit_on_launch || false,
|
}}
|
||||||
ask_inventory_on_launch: template.ask_inventory_on_launch || false,
|
value={organizationField.value}
|
||||||
ask_variables_on_launch: template.ask_variables_on_launch || false,
|
isValid={!organizationMeta.error}
|
||||||
ask_scm_branch_on_launch: template.ask_scm_branch_on_launch || false,
|
/>
|
||||||
}}
|
<FormGroup label={i18n._(t`Inventory`)} fieldId="wfjt-inventory">
|
||||||
>
|
<FieldTooltip
|
||||||
{formik => (
|
content={i18n._(
|
||||||
<Form autoComplete="off" onSubmit={formik.handleSubmit}>
|
t`Select an inventory for the workflow. This inventory is applied to all job template nodes that prompt for an inventory.`
|
||||||
<FormColumnLayout>
|
)}
|
||||||
<FormField
|
/>
|
||||||
id="wfjt-name"
|
<InventoryLookup
|
||||||
name="name"
|
value={inventoryField.value}
|
||||||
type="text"
|
isValid={!inventoryMeta.error}
|
||||||
label={i18n._(t`Name`)}
|
helperTextInvalid={inventoryMeta.error}
|
||||||
validate={required(null, i18n)}
|
onChange={value => {
|
||||||
isRequired
|
inventoryHelpers.setValue(value || null);
|
||||||
/>
|
}}
|
||||||
<FormField
|
/>
|
||||||
id="wfjt-description"
|
</FormGroup>
|
||||||
name="description"
|
<FormField
|
||||||
type="text"
|
type="text"
|
||||||
label={i18n._(t`Description`)}
|
name="limit"
|
||||||
/>
|
id="wfjt-limit"
|
||||||
<Field
|
label={i18n._(t`Limit`)}
|
||||||
id="wfjt-organization"
|
tooltip={i18n._(
|
||||||
label={i18n._(t`Organization`)}
|
t`Provide a host pattern to further constrain the list of hosts that will be managed or affected by the workflow. This limit is applied to all job template nodes that prompt for a limit. Refer to Ansible documentation for more information and examples on patterns.`
|
||||||
name="organization"
|
)}
|
||||||
>
|
/>
|
||||||
{({ form }) => (
|
<FormField
|
||||||
<OrganizationLookup
|
type="text"
|
||||||
helperTextInvalid={form.errors.organization}
|
label={i18n._(t`SCM Branch`)}
|
||||||
onChange={value => {
|
tooltip={i18n._(
|
||||||
form.setFieldValue('organization', value?.id || null);
|
t`Select a branch for the workflow. This branch is applied to all job template nodes that prompt for a branch.`
|
||||||
setOrganization(value);
|
)}
|
||||||
}}
|
id="wfjt-scm_branch"
|
||||||
value={organization}
|
name="scm_branch"
|
||||||
isValid={!form.errors.organization}
|
/>
|
||||||
/>
|
</FormColumnLayout>
|
||||||
)}
|
<FormFullWidthLayout>
|
||||||
</Field>
|
<FormGroup label={i18n._(t`Labels`)} fieldId="template-labels">
|
||||||
<Field name="inventory">
|
<FieldTooltip
|
||||||
{({ form }) => (
|
content={i18n._(t`Optional labels that describe this job template,
|
||||||
<FormGroup
|
|
||||||
label={i18n._(t`Inventory`)}
|
|
||||||
fieldId="wfjt-inventory"
|
|
||||||
>
|
|
||||||
<FieldTooltip
|
|
||||||
content={i18n._(
|
|
||||||
t`Select an inventory for the workflow. This inventory is applied to all job template nodes that prompt for an inventory.`
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<InventoryLookup
|
|
||||||
value={inventory}
|
|
||||||
isValid={!form.errors.inventory}
|
|
||||||
helperTextInvalid={form.errors.inventory}
|
|
||||||
onChange={value => {
|
|
||||||
form.setFieldValue('inventory', value?.id || null);
|
|
||||||
setInventory(value);
|
|
||||||
form.setFieldValue('organizationId', value?.organization);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
)}
|
|
||||||
</Field>
|
|
||||||
<FormField
|
|
||||||
type="text"
|
|
||||||
name="limit"
|
|
||||||
id="wfjt-limit"
|
|
||||||
label={i18n._(t`Limit`)}
|
|
||||||
tooltip={i18n._(
|
|
||||||
t`Provide a host pattern to further constrain the list of hosts that will be managed or affected by the workflow. This limit is applied to all job template nodes that prompt for a limit. Refer to Ansible documentation for more information and examples on patterns.`
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
<FormField
|
|
||||||
type="text"
|
|
||||||
label={i18n._(t`Source Control Branch`)}
|
|
||||||
tooltip={i18n._(
|
|
||||||
t`Select a branch for the workflow. This branch is applied to all job template nodes that prompt for a branch.`
|
|
||||||
)}
|
|
||||||
id="wfjt-scm_branch"
|
|
||||||
name="scm_branch"
|
|
||||||
/>
|
|
||||||
</FormColumnLayout>
|
|
||||||
<FormFullWidthLayout>
|
|
||||||
<Field name="labels">
|
|
||||||
{({ form, field }) => (
|
|
||||||
<FormGroup
|
|
||||||
label={i18n._(t`Labels`)}
|
|
||||||
helperTextInvalid={form.errors.webhook_service}
|
|
||||||
isValid={!(form.touched.labels || form.errors.labels)}
|
|
||||||
name="wfjt-labels"
|
|
||||||
fieldId="wfjt-labels"
|
|
||||||
>
|
|
||||||
<FieldTooltip
|
|
||||||
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
|
||||||
job templates and completed jobs.`)}
|
job templates and completed jobs.`)}
|
||||||
/>
|
|
||||||
<LabelSelect
|
|
||||||
value={field.value}
|
|
||||||
onChange={labels => form.setFieldValue('labels', labels)}
|
|
||||||
onError={err => setContentError(err)}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
)}
|
|
||||||
</Field>
|
|
||||||
</FormFullWidthLayout>
|
|
||||||
<FormFullWidthLayout>
|
|
||||||
<VariablesField
|
|
||||||
id="wfjt-variables"
|
|
||||||
name="extra_vars"
|
|
||||||
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.`
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</FormFullWidthLayout>
|
|
||||||
<FormCheckboxLayout
|
|
||||||
fieldId="options"
|
|
||||||
isInline
|
|
||||||
label={i18n._(t`Options`)}
|
|
||||||
>
|
|
||||||
<Field id="wfjt-webhooks" name="hasWebhooks">
|
|
||||||
{({ form }) => (
|
|
||||||
<Checkbox
|
|
||||||
aria-label={i18n._(t`Enable Webhooks`)}
|
|
||||||
label={
|
|
||||||
<span>
|
|
||||||
{i18n._(t`Enable Webhooks`)}
|
|
||||||
|
|
||||||
<FieldTooltip
|
|
||||||
content={i18n._(
|
|
||||||
t`Enable webhooks for this workflow job template.`
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
}
|
|
||||||
id="wfjt-enabled-webhooks"
|
|
||||||
isChecked={
|
|
||||||
Boolean(form.values.webhook_service) || hasWebhooks
|
|
||||||
}
|
|
||||||
onChange={checked => {
|
|
||||||
setHasWebhooks(checked);
|
|
||||||
handleWebhookEnablement(form, checked, webhookService);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Field>
|
|
||||||
|
|
||||||
<CheckboxField
|
|
||||||
name="allow_simultaneous"
|
|
||||||
id="allow_simultaneous"
|
|
||||||
tooltip={i18n._(
|
|
||||||
t`If enabled, simultaneous runs of this workflow job template will be allowed.`
|
|
||||||
)}
|
|
||||||
label={i18n._(t`Enable Concurrent Jobs`)}
|
|
||||||
/>
|
|
||||||
</FormCheckboxLayout>
|
|
||||||
{hasWebhooks && (
|
|
||||||
<FormColumnLayout>
|
|
||||||
<Field name="webhook_service">
|
|
||||||
{({ form, field }) => (
|
|
||||||
<FormGroup
|
|
||||||
name="webhook_service"
|
|
||||||
fieldId="webhook_service"
|
|
||||||
helperTextInvalid={form.errors.webhook_service}
|
|
||||||
isValid={
|
|
||||||
!(
|
|
||||||
form.touched.webhook_service ||
|
|
||||||
form.errors.webhook_service
|
|
||||||
)
|
|
||||||
}
|
|
||||||
label={i18n._(t`Webhook Service`)}
|
|
||||||
>
|
|
||||||
<FieldTooltip
|
|
||||||
content={i18n._(t`Select a webhook service`)}
|
|
||||||
/>
|
|
||||||
<AnsibleSelect
|
|
||||||
id="webhook_service"
|
|
||||||
data={webhookServiceOptions}
|
|
||||||
value={field.value}
|
|
||||||
onChange={(event, val) => {
|
|
||||||
setWebHookService(val);
|
|
||||||
storeWebhookValues(form, val);
|
|
||||||
|
|
||||||
form.setFieldValue('webhook_service', val);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
)}
|
|
||||||
</Field>
|
|
||||||
{!wfjtAddMatch && (
|
|
||||||
<>
|
|
||||||
<FormGroup
|
|
||||||
type="text"
|
|
||||||
fieldId="wfjt-webhookURL"
|
|
||||||
label={i18n._(t`Webhook URL`)}
|
|
||||||
id="wfjt-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
|
|
||||||
aria-label={i18n._(t`Webhook URL`)}
|
|
||||||
value={webhook_url}
|
|
||||||
isReadOnly
|
|
||||||
/>
|
|
||||||
</FormGroup>
|
|
||||||
<Field>
|
|
||||||
{({ form }) => (
|
|
||||||
<FormGroup
|
|
||||||
fieldId="wfjt-webhook-key"
|
|
||||||
type="text"
|
|
||||||
id="wfjt-webhook-key"
|
|
||||||
name="webhook_key"
|
|
||||||
isValid={
|
|
||||||
!(form.touched.webhook_key || form.errors.webhook_key)
|
|
||||||
}
|
|
||||||
helperTextInvalid={form.errors.webhook_service}
|
|
||||||
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>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{credTypeId && (
|
|
||||||
// TODO: Consider how to handle the situation where the results returns
|
|
||||||
// an empty array, or any of the other values is undefined or null
|
|
||||||
// (data, results, id)
|
|
||||||
<Field name="webhook_credential">
|
|
||||||
{({ form }) => (
|
|
||||||
<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 => {
|
|
||||||
form.setFieldValue(
|
|
||||||
'webhook_credential',
|
|
||||||
value?.id || null
|
|
||||||
);
|
|
||||||
setWebhookCredential(value);
|
|
||||||
}}
|
|
||||||
isValid={!form.errors.webhook_credential}
|
|
||||||
helperTextInvalid={form.errors.webhook_credential}
|
|
||||||
value={webhookCredential}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</Field>
|
|
||||||
)}
|
|
||||||
</FormColumnLayout>
|
|
||||||
)}
|
|
||||||
{submitError && <FormSubmitError error={submitError} />}
|
|
||||||
<FormActionGroup
|
|
||||||
onCancel={handleCancel}
|
|
||||||
onSubmit={formik.handleSubmit}
|
|
||||||
/>
|
/>
|
||||||
</Form>
|
<LabelSelect
|
||||||
|
value={labelsField.value}
|
||||||
|
onChange={labels => labelsHelpers.setValue(labels)}
|
||||||
|
onError={setContentError}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
</FormFullWidthLayout>
|
||||||
|
<FormFullWidthLayout>
|
||||||
|
<VariablesField
|
||||||
|
id="wfjt-variables"
|
||||||
|
name="extra_vars"
|
||||||
|
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.`
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</FormFullWidthLayout>
|
||||||
|
<FormCheckboxLayout fieldId="options" isInline label={i18n._(t`Options`)}>
|
||||||
|
<Checkbox
|
||||||
|
aria-label={i18n._(t`Enable Webhook`)}
|
||||||
|
label={
|
||||||
|
<span>
|
||||||
|
{i18n._(t`Enable Webhook`)}
|
||||||
|
|
||||||
|
<FieldTooltip
|
||||||
|
content={i18n._(
|
||||||
|
t`Enable webhook for this workflow job template.`
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
id="wfjt-enabled-webhooks"
|
||||||
|
isChecked={Boolean(webhookServiceField.value) || hasWebhooks}
|
||||||
|
onChange={checked => {
|
||||||
|
setHasWebhooks(checked);
|
||||||
|
handleWebhookEnablement(checked, webhookServiceField.value);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<CheckboxField
|
||||||
|
name="allow_simultaneous"
|
||||||
|
id="allow_simultaneous"
|
||||||
|
tooltip={i18n._(
|
||||||
|
t`If enabled, simultaneous runs of this workflow job template will be allowed.`
|
||||||
|
)}
|
||||||
|
label={i18n._(t`Enable Concurrent Jobs`)}
|
||||||
|
/>
|
||||||
|
</FormCheckboxLayout>
|
||||||
|
{hasWebhooks && (
|
||||||
|
<FormColumnLayout>
|
||||||
|
<FormGroup
|
||||||
|
name="webhook_service"
|
||||||
|
fieldId="webhook_service"
|
||||||
|
helperTextInvalid={webhookServiceMeta.error}
|
||||||
|
isValid={!(webhookServiceMeta.touched || webhookServiceMeta.error)}
|
||||||
|
label={i18n._(t`Webhook Service`)}
|
||||||
|
>
|
||||||
|
<FieldTooltip content={i18n._(t`Select a webhook service`)} />
|
||||||
|
<AnsibleSelect
|
||||||
|
id="webhook_service"
|
||||||
|
data={webhookServiceOptions}
|
||||||
|
value={webhookServiceField.value}
|
||||||
|
onChange={(event, val) => {
|
||||||
|
storeWebhookValues(val);
|
||||||
|
|
||||||
|
webhookServiceHelpers.setValue(val);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
{!wfjtAddMatch && (
|
||||||
|
<>
|
||||||
|
<FormGroup
|
||||||
|
type="text"
|
||||||
|
fieldId="wfjt-webhookURL"
|
||||||
|
label={i18n._(t`Webhook URL`)}
|
||||||
|
id="wfjt-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
|
||||||
|
aria-label={i18n._(t`Webhook URL`)}
|
||||||
|
value={webhookUrlField.value}
|
||||||
|
isReadOnly
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
|
<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={webhookKeyField.value}
|
||||||
|
/>
|
||||||
|
<Button variant="tertiary" onClick={changeWebhookKey}>
|
||||||
|
<SyncAltIcon />
|
||||||
|
</Button>
|
||||||
|
</InputGroup>
|
||||||
|
</FormGroup>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{credTypeId && (
|
||||||
|
// TODO: Consider how to handle the situation where the results returns
|
||||||
|
// an empty array, or any of the other values is undefined or null
|
||||||
|
// (data, results, id)
|
||||||
|
<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>
|
||||||
)}
|
)}
|
||||||
</Formik>
|
{submitError && <FormSubmitError error={submitError} />}
|
||||||
|
<FormActionGroup onCancel={handleCancel} onSubmit={handleSubmit} />
|
||||||
|
</Form>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
WorkflowJobTemplateForm.propTypes = {
|
WorkflowJobTemplateForm.propTypes = {
|
||||||
handleSubmit: func.isRequired,
|
handleSubmit: PropTypes.func.isRequired,
|
||||||
handleCancel: func.isRequired,
|
handleCancel: PropTypes.func.isRequired,
|
||||||
submitError: shape({}),
|
submitError: shape({}),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -506,4 +396,38 @@ WorkflowJobTemplateForm.defaultProps = {
|
|||||||
submitError: null,
|
submitError: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default withI18n()(WorkflowJobTemplateForm);
|
const FormikApp = withFormik({
|
||||||
|
mapPropsToValues({ template = {} }) {
|
||||||
|
return {
|
||||||
|
name: template.name || '',
|
||||||
|
description: template.description || '',
|
||||||
|
inventory: template?.summary_fields?.inventory || null,
|
||||||
|
organization: template?.summary_fields?.organization || null,
|
||||||
|
labels: template.summary_fields?.labels?.results || [],
|
||||||
|
extra_vars: template.extra_vars || '---',
|
||||||
|
limit: template.limit || '',
|
||||||
|
scm_branch: template.scm_branch || '',
|
||||||
|
allow_simultaneous: template.allow_simultaneous || false,
|
||||||
|
webhook_credential: template?.summary_fields?.webhook_credential || null,
|
||||||
|
webhook_service: template.webhook_service || '',
|
||||||
|
ask_limit_on_launch: template.ask_limit_on_launch || false,
|
||||||
|
ask_inventory_on_launch: template.ask_inventory_on_launch || false,
|
||||||
|
ask_variables_on_launch: template.ask_variables_on_launch || false,
|
||||||
|
ask_scm_branch_on_launch: template.ask_scm_branch_on_launch || false,
|
||||||
|
webhook_url: template?.related?.webhook_receiver
|
||||||
|
? `${urlOrigin}${template.related.webhook_receiver}`
|
||||||
|
: '',
|
||||||
|
webhook_key: template.webhook_key || '',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
handleSubmit: async (values, { props, setErrors }) => {
|
||||||
|
try {
|
||||||
|
await props.handleSubmit(values);
|
||||||
|
} catch (errors) {
|
||||||
|
setErrors(errors);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})(WorkflowJobTemplateForm);
|
||||||
|
|
||||||
|
export { WorkflowJobTemplateForm as _WorkflowJobTemplateForm };
|
||||||
|
export default withI18n()(FormikApp);
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ describe('<WorkflowJobTemplateForm/>', () => {
|
|||||||
related: {
|
related: {
|
||||||
webhook_receiver: '/api/v2/workflow_job_templates/57/gitlab/',
|
webhook_receiver: '/api/v2/workflow_job_templates/57/gitlab/',
|
||||||
},
|
},
|
||||||
|
webhook_key: 'sdfghjklmnbvcdsew435678iokjhgfd',
|
||||||
};
|
};
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
@@ -74,7 +75,6 @@ describe('<WorkflowJobTemplateForm/>', () => {
|
|||||||
template={mockTemplate}
|
template={mockTemplate}
|
||||||
handleCancel={handleCancel}
|
handleCancel={handleCancel}
|
||||||
handleSubmit={handleSubmit}
|
handleSubmit={handleSubmit}
|
||||||
webhook_key="sdfghjklmnbvcdsew435678iokjhgfd"
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>,
|
/>,
|
||||||
@@ -106,13 +106,14 @@ describe('<WorkflowJobTemplateForm/>', () => {
|
|||||||
const fields = [
|
const fields = [
|
||||||
'FormField[name="name"]',
|
'FormField[name="name"]',
|
||||||
'FormField[name="description"]',
|
'FormField[name="description"]',
|
||||||
'Field[name="organization"]',
|
'FormGroup[label="Organization"]',
|
||||||
'Field[name="inventory"]',
|
'FormGroup[label="Inventory"]',
|
||||||
'FormField[name="limit"]',
|
'FormField[name="limit"]',
|
||||||
'FormField[name="scm_branch"]',
|
'FormField[name="scm_branch"]',
|
||||||
'Field[name="labels"]',
|
'FormGroup[label="Labels"]',
|
||||||
'VariablesField',
|
'VariablesField',
|
||||||
];
|
];
|
||||||
|
|
||||||
const assertField = field => {
|
const assertField = field => {
|
||||||
expect(wrapper.find(`${field}`).length).toBe(1);
|
expect(wrapper.find(`${field}`).length).toBe(1);
|
||||||
};
|
};
|
||||||
@@ -171,7 +172,7 @@ describe('<WorkflowJobTemplateForm/>', () => {
|
|||||||
|
|
||||||
test('webhooks and enable concurrent jobs functions properly', async () => {
|
test('webhooks and enable concurrent jobs functions properly', async () => {
|
||||||
act(() => {
|
act(() => {
|
||||||
wrapper.find('Checkbox[aria-label="Enable Webhooks"]').invoke('onChange')(
|
wrapper.find('Checkbox[aria-label="Enable Webhook"]').invoke('onChange')(
|
||||||
true,
|
true,
|
||||||
{
|
{
|
||||||
currentTarget: { value: true, type: 'change', checked: true },
|
currentTarget: { value: true, type: 'change', checked: true },
|
||||||
@@ -180,7 +181,7 @@ describe('<WorkflowJobTemplateForm/>', () => {
|
|||||||
});
|
});
|
||||||
wrapper.update();
|
wrapper.update();
|
||||||
expect(
|
expect(
|
||||||
wrapper.find('Checkbox[aria-label="Enable Webhooks"]').prop('isChecked')
|
wrapper.find('Checkbox[aria-label="Enable Webhook"]').prop('isChecked')
|
||||||
).toBe(true);
|
).toBe(true);
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
@@ -201,8 +202,7 @@ describe('<WorkflowJobTemplateForm/>', () => {
|
|||||||
).toContain('/api/v2/workflow_job_templates/57/gitlab/');
|
).toContain('/api/v2/workflow_job_templates/57/gitlab/');
|
||||||
|
|
||||||
wrapper.update();
|
wrapper.update();
|
||||||
|
expect(wrapper.find('FormGroup[name="webhook_service"]').length).toBe(1);
|
||||||
expect(wrapper.find('Field[name="webhook_service"]').length).toBe(1);
|
|
||||||
|
|
||||||
act(() => wrapper.find('AnsibleSelect').prop('onChange')({}, 'gitlab'));
|
act(() => wrapper.find('AnsibleSelect').prop('onChange')({}, 'gitlab'));
|
||||||
wrapper.update();
|
wrapper.update();
|
||||||
|
|||||||
Reference in New Issue
Block a user