diff --git a/awx/ui_next/src/screens/Template/WorkflowJobTemplate.jsx b/awx/ui_next/src/screens/Template/WorkflowJobTemplate.jsx
index e16a35ce1c..3e659ff002 100644
--- a/awx/ui_next/src/screens/Template/WorkflowJobTemplate.jsx
+++ b/awx/ui_next/src/screens/Template/WorkflowJobTemplate.jsx
@@ -1,317 +1,265 @@
-import React, { Component } from 'react';
+import React, { useEffect, useCallback } from 'react';
import { t } from '@lingui/macro';
import { withI18n } from '@lingui/react';
import { CaretLeftIcon } from '@patternfly/react-icons';
import { Card, PageSection } from '@patternfly/react-core';
-import { Switch, Route, Redirect, withRouter, Link } from 'react-router-dom';
+import {
+ Switch,
+ Route,
+ Redirect,
+ Link,
+ useLocation,
+ useParams,
+ useRouteMatch,
+} from 'react-router-dom';
+import RoutedTabs from '../../components/RoutedTabs';
+import useRequest from '../../util/useRequest';
import AppendBody from '../../components/AppendBody';
import ContentError from '../../components/ContentError';
import FullPage from '../../components/FullPage';
import JobList from '../../components/JobList';
-import RoutedTabs from '../../components/RoutedTabs';
-import { Schedules } from '../../components/Schedule';
-import ContentLoading from '../../components/ContentLoading';
-import { ResourceAccessList } from '../../components/ResourceAccessList';
import NotificationList from '../../components/NotificationList';
-import {
- WorkflowJobTemplatesAPI,
- CredentialsAPI,
- OrganizationsAPI,
-} from '../../api';
+import { Schedules } from '../../components/Schedule';
+import { ResourceAccessList } from '../../components/ResourceAccessList';
import WorkflowJobTemplateDetail from './WorkflowJobTemplateDetail';
import WorkflowJobTemplateEdit from './WorkflowJobTemplateEdit';
-import { Visualizer } from './WorkflowJobTemplateVisualizer';
+import { WorkflowJobTemplatesAPI, OrganizationsAPI } from '../../api';
import TemplateSurvey from './TemplateSurvey';
+import { Visualizer } from './WorkflowJobTemplateVisualizer';
-class WorkflowJobTemplate extends Component {
- constructor(props) {
- super(props);
+function WorkflowJobTemplate({ i18n, me, setBreadcrumb }) {
+ const location = useLocation();
+ const { id: templateId } = useParams();
+ const match = useRouteMatch();
- this.state = {
- contentError: null,
- hasContentLoading: true,
- template: null,
- isNotifAdmin: false,
- };
- this.createSchedule = this.createSchedule.bind(this);
- this.loadTemplate = this.loadTemplate.bind(this);
- this.loadSchedules = this.loadSchedules.bind(this);
- this.loadScheduleOptions = this.loadScheduleOptions.bind(this);
- }
-
- async componentDidMount() {
- await this.loadTemplate();
- }
-
- async componentDidUpdate(prevProps) {
- const { location } = this.props;
- if (location !== prevProps.location) {
- await this.loadTemplate();
- }
- }
-
- async loadTemplate() {
- const { setBreadcrumb, match } = this.props;
- const { id } = match.params;
-
- this.setState({ contentError: null });
- try {
- const [
- { data },
- {
- data: { actions },
- },
- ] = await Promise.all([
- WorkflowJobTemplatesAPI.readDetail(id),
- WorkflowJobTemplatesAPI.readWorkflowJobTemplateOptions(id),
+ const {
+ result: { isNotifAdmin, template },
+ isLoading: hasRolesandTemplateLoading,
+ error: rolesAndTemplateError,
+ request: loadTemplateAndRoles,
+ } = useRequest(
+ useCallback(async () => {
+ const [{ data }, actions, notifAdminRes] = await Promise.all([
+ WorkflowJobTemplatesAPI.readDetail(templateId),
+ WorkflowJobTemplatesAPI.readWorkflowJobTemplateOptions(templateId),
+ OrganizationsAPI.read({
+ page_size: 1,
+ role_level: 'notification_admin_role',
+ }),
]);
- let webhookKey;
- if (actions.PUT) {
- if (data?.webhook_service && data?.related?.webhook_key) {
- webhookKey = await WorkflowJobTemplatesAPI.readWebhookKey(id);
+
+ if (actions.data.actions.PUT) {
+ if (data.webhook_service && data?.related?.webhook_key) {
+ const {
+ data: { webhook_key },
+ } = await WorkflowJobTemplatesAPI.readWebhookKey(templateId);
+
+ data.webhook_key = webhook_key;
}
}
- if (data?.summary_fields?.webhook_credential) {
- const {
- data: {
- summary_fields: {
- credential_type: { name },
- },
- },
- } = await CredentialsAPI.readDetail(
- data.summary_fields.webhook_credential.id
- );
- data.summary_fields.webhook_credential.kind = name;
- }
- const notifAdminRes = await OrganizationsAPI.read({
- page_size: 1,
- role_level: 'notification_admin_role',
- });
setBreadcrumb(data);
- this.setState({
- template: { ...data, webhook_key: webhookKey?.data.webhook_key },
+
+ return {
+ template: data,
isNotifAdmin: notifAdminRes.data.results.length > 0,
- });
- } catch (err) {
- this.setState({ contentError: err });
- } finally {
- this.setState({ hasContentLoading: false });
- }
- }
+ };
+ }, [setBreadcrumb, templateId]),
+ { isNotifAdmin: false, template: null }
+ );
+ useEffect(() => {
+ loadTemplateAndRoles();
+ }, [loadTemplateAndRoles, location.pathname]);
- createSchedule(data) {
- const { template } = this.state;
- return WorkflowJobTemplatesAPI.createSchedule(template.id, data);
- }
+ const createSchedule = data => {
+ return WorkflowJobTemplatesAPI.createSchedule(templateId, data);
+ };
- loadScheduleOptions() {
- const { template } = this.state;
- return WorkflowJobTemplatesAPI.readScheduleOptions(template.id);
- }
+ const loadScheduleOptions = () => {
+ return WorkflowJobTemplatesAPI.readScheduleOptions(templateId);
+ };
- loadSchedules(params) {
- const { template } = this.state;
- return WorkflowJobTemplatesAPI.readSchedules(template.id, params);
- }
+ const loadSchedules = params => {
+ return WorkflowJobTemplatesAPI.readSchedules(templateId, params);
+ };
- render() {
- const { i18n, me, location, match, setBreadcrumb } = this.props;
- const {
- contentError,
- hasContentLoading,
- template,
- isNotifAdmin,
- } = this.state;
+ const canSeeNotificationsTab = me.is_system_auditor || isNotifAdmin;
+ const canAddAndEditSurvey =
+ template?.summary_fields?.user_capabilities.edit ||
+ template?.summary_fields?.user_capabilities.delete;
- const canSeeNotificationsTab = me.is_system_auditor || isNotifAdmin;
- const canToggleNotifications = isNotifAdmin;
- const canAddAndEditSurvey =
- template?.summary_fields?.user_capabilities.edit ||
- template?.summary_fields?.user_capabilities.delete;
-
- const tabsArray = [
- {
- name: (
- <>
-
- {i18n._(t`Back to Templates`)}
- >
- ),
- link: `/templates`,
- id: 99,
- },
- { name: i18n._(t`Details`), link: `${match.url}/details` },
- { name: i18n._(t`Access`), link: `${match.url}/access` },
- ];
-
- if (canSeeNotificationsTab) {
- tabsArray.push({
- name: i18n._(t`Notifications`),
- link: `${match.url}/notifications`,
- });
- }
-
- if (template) {
- tabsArray.push({
- name: i18n._(t`Schedules`),
- link: `${match.url}/schedules`,
- });
- }
+ const tabsArray = [
+ {
+ name: (
+ <>
+
+ {i18n._(t`Back to Templates`)}
+ >
+ ),
+ link: `/templates`,
+ id: 99,
+ },
+ { name: i18n._(t`Details`), link: `${match.url}/details` },
+ { name: i18n._(t`Access`), link: `${match.url}/access` },
+ ];
+ if (canSeeNotificationsTab) {
tabsArray.push({
+ name: i18n._(t`Notifications`),
+ link: `${match.url}/notifications`,
+ });
+ }
+
+ if (template) {
+ tabsArray.push({
+ name: i18n._(t`Schedules`),
+ link: `${match.url}/schedules`,
+ });
+ }
+
+ tabsArray.push(
+ {
name: i18n._(t`Visualizer`),
link: `${match.url}/visualizer`,
- });
- tabsArray.push({
+ },
+ {
name: i18n._(t`Completed Jobs`),
link: `${match.url}/completed_jobs`,
- });
- tabsArray.push({
+ },
+ {
name: canAddAndEditSurvey ? i18n._(t`Survey`) : i18n._(t`View Survey`),
link: `${match.url}/survey`,
- });
-
- tabsArray.forEach((tab, n) => {
- tab.id = n;
- });
-
- if (hasContentLoading) {
- return (
-
-
-
-
-
- );
}
+ );
- if (contentError) {
- return (
-
-
-
- {contentError.response.status === 404 && (
-
- {i18n._(t`Template not found.`)}{' '}
- {i18n._(t`View all Templates.`)}
-
- )}
-
-
-
- );
- }
+ tabsArray.forEach((tab, n) => {
+ tab.id = n;
+ });
- let showCardHeader = true;
+ let showCardHeader = true;
- if (
- location.pathname.endsWith('edit') ||
- location.pathname.includes('schedules/')
- ) {
- showCardHeader = false;
- }
+ if (
+ location.pathname.endsWith('edit') ||
+ location.pathname.includes('schedules/')
+ ) {
+ showCardHeader = false;
+ }
+ const contentError = rolesAndTemplateError;
+ if (!hasRolesandTemplateLoading && contentError) {
return (
- {showCardHeader && }
-
-
- {template && (
-
-
-
- )}
- {template && (
-
-
-
- )}
- {canSeeNotificationsTab && (
-
-
-
- )}
- {template && (
-
-
-
- )}
- {template && (
-
-
-
-
-
-
-
- )}
- {template?.id && (
-
-
-
- )}
- {template?.id && (
-
-
-
- )}
- {template && (
-
-
-
+
+ {contentError.response.status === 404 && (
+
+ {i18n._(t`Template not found.`)}{' '}
+ {i18n._(t`View all Templates.`)}
+
)}
+
+
+
+ );
+ }
+
+ return (
+
+
+ {showCardHeader && }
+
+
+ {template && (
+
+
+
+ )}
+ {template && (
+
+
+
+ )}
+ {template && (
+
+
+
+ )}
+ {template && (
+
+
+
+ )}
+ {canSeeNotificationsTab && (
+
+
+
+ )}
+ {template && (
+
+
+
+
+
+
+
+ )}
+ {template?.id && (
+
+
+
+ )}
+ {template && (
+
+
+
+ )}
+ {!hasRolesandTemplateLoading && (
{match.params.id && (
{i18n._(t`View Template Details`)}
)}
-
-
-
- );
- }
+ )}
+
+
+
+ );
}
export { WorkflowJobTemplate as _WorkflowJobTemplate };
-export default withI18n()(withRouter(WorkflowJobTemplate));
+export default withI18n()(WorkflowJobTemplate);
diff --git a/awx/ui_next/src/screens/Template/WorkflowJobTemplate.test.jsx b/awx/ui_next/src/screens/Template/WorkflowJobTemplate.test.jsx
index 2a083ffe2a..9343ede200 100644
--- a/awx/ui_next/src/screens/Template/WorkflowJobTemplate.test.jsx
+++ b/awx/ui_next/src/screens/Template/WorkflowJobTemplate.test.jsx
@@ -1,192 +1,196 @@
import React from 'react';
-import { Route } from 'react-router-dom';
import { createMemoryHistory } from 'history';
import { act } from 'react-dom/test-utils';
+import { WorkflowJobTemplatesAPI, OrganizationsAPI } from '../../api';
import {
mountWithContexts,
waitForElement,
} from '../../../testUtils/enzymeHelpers';
import WorkflowJobTemplate from './WorkflowJobTemplate';
-import { sleep } from '../../../testUtils/testUtils';
-import {
- WorkflowJobTemplatesAPI,
- CredentialsAPI,
- OrganizationsAPI,
-} from '../../api';
+import mockWorkflowJobTemplateData from './shared/data.workflow_job_template.json';
jest.mock('../../api/models/WorkflowJobTemplates');
-jest.mock('../../api/models/Credentials');
jest.mock('../../api/models/Organizations');
-describe('', () => {
- const mockMe = {
- is_super_user: true,
- is_system_auditor: false,
- };
+const mockMe = {
+ is_super_user: true,
+ is_system_auditor: false,
+};
+describe('', () => {
let wrapper;
- let history;
- beforeAll(() => {
+ beforeEach(() => {
WorkflowJobTemplatesAPI.readDetail.mockResolvedValue({
- data: {
- id: 1,
- name: 'Foo',
- description: 'Bar',
- created: '2015-07-07T17:21:26.429745Z',
- modified: '2019-08-11T19:47:37.980466Z',
- extra_vars: '',
- webhook_service: 'github',
- summary_fields: {
- webhook_credential: { id: 1234567, name: 'Foo Webhook Credential' },
- created_by: { id: 1, username: 'Athena' },
- modified_by: { id: 1, username: 'Apollo' },
- recent_jobs: [
- { id: 1, status: 'run' },
- { id: 2, status: 'run' },
- { id: 3, status: 'run' },
- ],
- labels: {
- results: [
- { name: 'Label 1', id: 1 },
- { name: 'Label 2', id: 2 },
- { name: 'Label 3', id: 3 },
- ],
- },
- user_capabilities: {},
- },
- related: {
- webhook_key: '/api/v2/workflow_job_templates/57/webhook_key/',
- },
- },
+ data: mockWorkflowJobTemplateData,
});
-
- WorkflowJobTemplatesAPI.readWebhookKey.mockResolvedValue({
- data: { webhook_key: 'WebHook Key' },
- });
- CredentialsAPI.readDetail.mockResolvedValue({
+ WorkflowJobTemplatesAPI.readWorkflowJobTemplateOptions.mockResolvedValue({
data: {
- summary_fields: {
- credential_type: { name: 'Github Personal Access Token', id: 1 },
- },
+ actions: { PUT: true },
},
});
OrganizationsAPI.read.mockResolvedValue({
- data: { results: [{ id: 1, name: 'Org Foo' }] },
+ data: {
+ count: 1,
+ next: null,
+ previous: null,
+ results: [
+ {
+ id: 1,
+ },
+ ],
+ },
+ });
+ WorkflowJobTemplatesAPI.readWebhookKey.mockResolvedValue({
+ data: {
+ webhook_key: 'key',
+ },
});
});
afterEach(() => {
jest.clearAllMocks();
wrapper.unmount();
});
- describe('User can PUT', () => {
- beforeEach(async () => {
- WorkflowJobTemplatesAPI.readWorkflowJobTemplateOptions.mockResolvedValue({
- data: { actions: { PUT: {} } },
- });
- history = createMemoryHistory({
- initialEntries: ['/templates/workflow_job_template/1/details'],
- });
- await act(async () => {
- wrapper = mountWithContexts(
- (
- {}} me={mockMe} />
- )}
- />,
- {
- context: {
- router: {
- history,
- },
- },
- }
- );
- });
- });
- test('calls api to get workflow job template data', async () => {
- expect(wrapper.find('WorkflowJobTemplate').length).toBe(1);
- expect(WorkflowJobTemplatesAPI.readDetail).toBeCalledWith('1');
- wrapper.update();
- await sleep(0);
- expect(WorkflowJobTemplatesAPI.readWebhookKey).toBeCalledWith('1');
- expect(
- WorkflowJobTemplatesAPI.readWorkflowJobTemplateOptions
- ).toBeCalled();
-
- expect(CredentialsAPI.readDetail).toBeCalledWith(1234567);
- expect(OrganizationsAPI.read).toBeCalledWith({
- page_size: 1,
- role_level: 'notification_admin_role',
- });
- });
-
- test('renders proper tabs', async () => {
- const tabs = [
- 'Details',
- 'Access',
- 'Notifications',
- 'Schedules',
- 'Visualizer',
- 'Completed Jobs',
- 'Survey',
- ];
- waitForElement(wrapper, 'EmptyStateBody', el => el.length === 0);
- wrapper.update();
- wrapper.find('TabContainer').forEach(tc => {
- tabs.forEach(t => expect(tc.prop(`aria-label=[${t}]`)));
- });
- });
-
- test('Does not render Notifications tab', async () => {
- OrganizationsAPI.read.mockResolvedValue({
- data: { results: [] },
- });
- const tabs = [
- 'Details',
- 'Access',
- 'Schedules',
- 'Visualizer',
- 'Completed Jobs',
- 'Survey',
- ];
- waitForElement(wrapper, 'EmptyStateBody', el => el.length === 0);
- wrapper.update();
- wrapper.find('TabContainer').forEach(tc => {
- tabs.forEach(t => expect(tc.prop(`aria-label=[${t}]`)));
- });
+ test('initially renders succesfully', async () => {
+ await act(async () => {
+ wrapper = mountWithContexts(
+ {}} me={mockMe} />
+ );
});
});
- describe('User cannot PUT', () => {
- beforeEach(async () => {
- WorkflowJobTemplatesAPI.readWorkflowJobTemplateOptions.mockResolvedValueOnce(
+ test('When component mounts API is called and the response is put in state', async () => {
+ await act(async () => {
+ wrapper = mountWithContexts(
+ {}} me={mockMe} />
+ );
+ });
+ expect(WorkflowJobTemplatesAPI.readDetail).toBeCalled();
+ expect(OrganizationsAPI.read).toBeCalled();
+ });
+ test('notifications tab shown for admins', async done => {
+ await act(async () => {
+ wrapper = mountWithContexts(
+ {}} me={mockMe} />
+ );
+ });
+
+ const tabs = await waitForElement(
+ wrapper,
+ '.pf-c-tabs__item',
+ el => el.length === 8
+ );
+ expect(tabs.at(3).text()).toEqual('Notifications');
+ done();
+ });
+ test('notifications tab hidden with reduced permissions', async done => {
+ OrganizationsAPI.read.mockResolvedValue({
+ data: {
+ count: 0,
+ next: null,
+ previous: null,
+ results: [],
+ },
+ });
+
+ await act(async () => {
+ wrapper = mountWithContexts(
+ {}} me={mockMe} />
+ );
+ });
+ const tabs = await waitForElement(
+ wrapper,
+ '.pf-c-tabs__item',
+ el => el.length === 7
+ );
+ tabs.forEach(tab => expect(tab.text()).not.toEqual('Notifications'));
+ done();
+ });
+
+ test('should show content error when user attempts to navigate to erroneous route', async () => {
+ const history = createMemoryHistory({
+ initialEntries: ['/templates/workflow_job_template/1/foobar'],
+ });
+
+ await act(async () => {
+ wrapper = mountWithContexts(
+ {}} me={mockMe} />,
{
- data: { actions: {} },
+ context: {
+ router: {
+ history,
+ route: {
+ location: history.location,
+ match: {
+ params: { id: 1 },
+ url: '/templates/workflow_job_template/1/foobar',
+ path: '/templates/workflow_job_template/1/foobar',
+ },
+ },
+ },
+ },
}
);
- history = createMemoryHistory({
- initialEntries: ['/templates/workflow_job_template/1/details'],
- });
- await act(async () => {
- wrapper = mountWithContexts(
- (
- {}} me={mockMe} />
- )}
- />,
- {
- context: {
- router: {
- history,
+ });
+
+ await waitForElement(wrapper, 'ContentError', el => el.length === 1);
+ });
+ test('should call to get webhook key', async () => {
+ const history = createMemoryHistory({
+ initialEntries: ['/templates/workflow_job_template/1/foobar'],
+ });
+ await act(async () => {
+ wrapper = mountWithContexts(
+ {}} me={mockMe} />,
+ {
+ context: {
+ router: {
+ history,
+ route: {
+ location: history.location,
+ match: {
+ params: { id: 1 },
+ url: '/templates/workflow_job_template/1/foobar',
+ path: '/templates/workflow_job_template/1/foobar',
+ },
},
},
- }
- );
- });
+ },
+ }
+ );
});
- test('should not call for webhook key', async () => {
- expect(WorkflowJobTemplatesAPI.readWebhookKey).not.toBeCalled();
+ expect(WorkflowJobTemplatesAPI.readWebhookKey).toHaveBeenCalled();
+ });
+ test('should not call to get webhook key', async () => {
+ WorkflowJobTemplatesAPI.readWorkflowJobTemplateOptions.mockResolvedValueOnce(
+ {
+ data: {
+ actions: {},
+ },
+ }
+ );
+
+ const history = createMemoryHistory({
+ initialEntries: ['/templates/workflow_job_template/1/foobar'],
});
+ await act(async () => {
+ wrapper = mountWithContexts(
+ {}} me={mockMe} />,
+ {
+ context: {
+ router: {
+ history,
+ route: {
+ location: history.location,
+ match: {
+ params: { id: 1 },
+ url: '/templates/workflow_job_template/1/foobar',
+ path: '/templates/workflow_job_template/1/foobar',
+ },
+ },
+ },
+ },
+ }
+ );
+ });
+ expect(WorkflowJobTemplatesAPI.readWebhookKey).not.toHaveBeenCalled();
});
});
diff --git a/awx/ui_next/src/screens/Template/shared/data.workflow_job_template.json b/awx/ui_next/src/screens/Template/shared/data.workflow_job_template.json
new file mode 100644
index 0000000000..b120d7c892
--- /dev/null
+++ b/awx/ui_next/src/screens/Template/shared/data.workflow_job_template.json
@@ -0,0 +1,95 @@
+{
+ "id": 15,
+ "type": "workflow_job_template",
+ "url": "/api/v2/workflow_job_templates/15/",
+ "related": {
+ "named_url": "/api/v2/workflow_job_templates/A workflow++/",
+ "created_by": "/api/v2/users/1/",
+ "modified_by": "/api/v2/users/1/",
+ "workflow_jobs": "/api/v2/workflow_job_templates/15/workflow_jobs/",
+ "schedules": "/api/v2/workflow_job_templates/15/schedules/",
+ "launch": "/api/v2/workflow_job_templates/15/launch/",
+ "webhook_key": "/api/v2/workflow_job_templates/15/webhook_key/",
+ "webhook_receiver": "/api/v2/workflow_job_templates/15/github/",
+ "workflow_nodes": "/api/v2/workflow_job_templates/15/workflow_nodes/",
+ "labels": "/api/v2/workflow_job_templates/15/labels/",
+ "activity_stream": "/api/v2/workflow_job_templates/15/activity_stream/",
+ "notification_templates_started": "/api/v2/workflow_job_templates/15/notification_templates_started/",
+ "notification_templates_success": "/api/v2/workflow_job_templates/15/notification_templates_success/",
+ "notification_templates_error": "/api/v2/workflow_job_templates/15/notification_templates_error/",
+ "notification_templates_approvals": "/api/v2/workflow_job_templates/15/notification_templates_approvals/",
+ "access_list": "/api/v2/workflow_job_templates/15/access_list/",
+ "object_roles": "/api/v2/workflow_job_templates/15/object_roles/",
+ "survey_spec": "/api/v2/workflow_job_templates/15/survey_spec/",
+ "copy": "/api/v2/workflow_job_templates/15/copy/"
+ },
+ "summary_fields": {
+ "created_by": {
+ "id": 1,
+ "username": "admin",
+ "first_name": "",
+ "last_name": ""
+ },
+ "modified_by": {
+ "id": 1,
+ "username": "admin",
+ "first_name": "",
+ "last_name": ""
+ },
+ "object_roles": {
+ "admin_role": {
+ "description": "Can manage all aspects of the workflow job template",
+ "name": "Admin",
+ "id": 68
+ },
+ "execute_role": {
+ "description": "May run the workflow job template",
+ "name": "Execute",
+ "id": 69
+ },
+ "read_role": {
+ "description": "May view settings for the workflow job template",
+ "name": "Read",
+ "id": 70
+ },
+ "approval_role": {
+ "description": "Can approve or deny a workflow approval node",
+ "name": "Approve",
+ "id": 71
+ }
+ },
+ "user_capabilities": {
+ "edit": true,
+ "delete": true,
+ "start": true,
+ "schedule": true,
+ "copy": true
+ },
+ "labels": {
+ "count": 0,
+ "results": []
+ },
+ "recent_jobs": []
+ },
+ "created": "2020-10-30T14:29:59.728159Z",
+ "modified": "2020-11-03T14:48:50.519450Z",
+ "name": "A workflow",
+ "description": "",
+ "last_job_run": null,
+ "last_job_failed": false,
+ "next_job_run": null,
+ "status": "never updated",
+ "extra_vars": "",
+ "organization": null,
+ "survey_enabled": false,
+ "allow_simultaneous": false,
+ "ask_variables_on_launch": false,
+ "inventory": null,
+ "limit": "",
+ "scm_branch": "",
+ "ask_inventory_on_launch": false,
+ "ask_scm_branch_on_launch": false,
+ "ask_limit_on_launch": false,
+ "webhook_service": "github",
+ "webhook_credential": null
+}