diff --git a/awx/ui_next/src/api/models/SystemJobTemplates.js b/awx/ui_next/src/api/models/SystemJobTemplates.js
index 5e8b395821..f9bd96d661 100644
--- a/awx/ui_next/src/api/models/SystemJobTemplates.js
+++ b/awx/ui_next/src/api/models/SystemJobTemplates.js
@@ -10,12 +10,6 @@ class SystemJobTemplates extends Mixins {
this.baseUrl = '/api/v2/system_job_templates/';
}
- readDetail(id) {
- const path = `${this.baseUrl}${id}/`;
-
- return this.http.get(path).then(({ data }) => data);
- }
-
launch(id, data) {
return this.http.post(`${this.baseUrl}${id}/launch/`, data);
}
diff --git a/awx/ui_next/src/components/PromptDetail/PromptDetail.js b/awx/ui_next/src/components/PromptDetail/PromptDetail.js
index b23eed0bd9..c58a1f99d6 100644
--- a/awx/ui_next/src/components/PromptDetail/PromptDetail.js
+++ b/awx/ui_next/src/components/PromptDetail/PromptDetail.js
@@ -1,18 +1,15 @@
import 'styled-components/macro';
import React from 'react';
import { shape } from 'prop-types';
-
import { t, Trans } from '@lingui/macro';
import { Link } from 'react-router-dom';
import styled from 'styled-components';
import { Chip, Divider, Title } from '@patternfly/react-core';
import { toTitleCase } from 'util/strings';
-
import CredentialChip from '../CredentialChip';
import ChipGroup from '../ChipGroup';
import { DetailList, Detail, UserDateDetail } from '../DetailList';
import { VariablesDetail } from '../CodeEditor';
-
import PromptProjectDetail from './PromptProjectDetail';
import PromptInventorySourceDetail from './PromptInventorySourceDetail';
import PromptJobTemplateDetail from './PromptJobTemplateDetail';
@@ -139,123 +136,136 @@ function PromptDetail({ resource, launchConfig = {}, overrides = {} }) {
user={details?.summary_fields?.modified_by}
/>
)}
+ {details?.type === 'system_job_template' && (
+
+ )}
- {hasPromptData(launchConfig) && hasOverrides && (
- <>
- {t`Prompted Values`}
-
-
- {launchConfig.ask_job_type_on_launch && (
-
- )}
- {launchConfig.ask_credential_on_launch && (
-
- {overrides.credentials.map((cred) => (
-
- ))}
-
- }
- />
- )}
- {launchConfig.ask_inventory_on_launch && (
-
- )}
- {launchConfig.ask_scm_branch_on_launch && (
-
- )}
- {launchConfig.ask_limit_on_launch && (
-
- )}
- {Object.prototype.hasOwnProperty.call(overrides, 'verbosity') &&
- launchConfig.ask_verbosity_on_launch ? (
-
- ) : null}
- {launchConfig.ask_tags_on_launch && (
-
- {overrides.job_tags.length > 0 &&
- overrides.job_tags.split(',').map((jobTag) => (
-
- {jobTag}
-
+ {details?.type !== 'system_job_template' &&
+ hasPromptData(launchConfig) &&
+ hasOverrides && (
+ <>
+ {t`Prompted Values`}
+
+
+ {launchConfig.ask_job_type_on_launch && (
+
+ )}
+ {launchConfig.ask_credential_on_launch && (
+
+ {overrides.credentials.map((cred) => (
+
))}
-
- }
- />
- )}
- {launchConfig.ask_skip_tags_on_launch && (
-
- {overrides.skip_tags.length > 0 &&
- overrides.skip_tags.split(',').map((skipTag) => (
-
- {skipTag}
-
- ))}
-
- }
- />
- )}
- {launchConfig.ask_diff_mode_on_launch && (
-
- )}
- {(launchConfig.survey_enabled ||
- launchConfig.ask_variables_on_launch) && (
-
- )}
-
- >
- )}
+
+ }
+ />
+ )}
+ {launchConfig.ask_inventory_on_launch && (
+
+ )}
+ {launchConfig.ask_scm_branch_on_launch && (
+
+ )}
+ {launchConfig.ask_limit_on_launch && (
+
+ )}
+ {Object.prototype.hasOwnProperty.call(overrides, 'verbosity') &&
+ launchConfig.ask_verbosity_on_launch ? (
+
+ ) : null}
+ {launchConfig.ask_tags_on_launch && (
+
+ {overrides.job_tags.length > 0 &&
+ overrides.job_tags.split(',').map((jobTag) => (
+
+ {jobTag}
+
+ ))}
+
+ }
+ />
+ )}
+ {launchConfig.ask_skip_tags_on_launch && (
+
+ {overrides.skip_tags.length > 0 &&
+ overrides.skip_tags.split(',').map((skipTag) => (
+
+ {skipTag}
+
+ ))}
+
+ }
+ />
+ )}
+ {launchConfig.ask_diff_mode_on_launch && (
+
+ )}
+ {(launchConfig.survey_enabled ||
+ launchConfig.ask_variables_on_launch) && (
+
+ )}
+
+ >
+ )}
>
);
}
diff --git a/awx/ui_next/src/components/Workflow/WorkflowLegend.js b/awx/ui_next/src/components/Workflow/WorkflowLegend.js
index 96b008216c..eca1b82bef 100644
--- a/awx/ui_next/src/components/Workflow/WorkflowLegend.js
+++ b/awx/ui_next/src/components/Workflow/WorkflowLegend.js
@@ -103,6 +103,10 @@ function WorkflowLegend() {
P
{t`Project Sync`}
+
+ M
+ {t`Management Job`}
+
diff --git a/awx/ui_next/src/components/Workflow/WorkflowNodeHelp.js b/awx/ui_next/src/components/Workflow/WorkflowNodeHelp.js
index 058a9531d8..0d6c61a01c 100644
--- a/awx/ui_next/src/components/Workflow/WorkflowNodeHelp.js
+++ b/awx/ui_next/src/components/Workflow/WorkflowNodeHelp.js
@@ -62,6 +62,10 @@ function WorkflowNodeHelp({ node }) {
case 'workflow_approval':
nodeType = t`Workflow Approval`;
break;
+ case 'system_job_template':
+ case 'system_job':
+ nodeType = t`Management Job`;
+ break;
default:
nodeType = '';
}
diff --git a/awx/ui_next/src/components/Workflow/WorkflowNodeTypeLetter.js b/awx/ui_next/src/components/Workflow/WorkflowNodeTypeLetter.js
index e0eb145528..b6a880e749 100644
--- a/awx/ui_next/src/components/Workflow/WorkflowNodeTypeLetter.js
+++ b/awx/ui_next/src/components/Workflow/WorkflowNodeTypeLetter.js
@@ -53,6 +53,10 @@ function WorkflowNodeTypeLetter({ node }) {
case 'inventory_update':
nodeTypeLetter = 'I';
break;
+ case 'system_job_template':
+ case 'system_job':
+ nodeTypeLetter = 'M';
+ break;
case 'workflow_job_template':
case 'workflow_job':
nodeTypeLetter = 'W';
diff --git a/awx/ui_next/src/screens/ManagementJob/ManagementJob.js b/awx/ui_next/src/screens/ManagementJob/ManagementJob.js
index e995b1fdce..6996554488 100644
--- a/awx/ui_next/src/screens/ManagementJob/ManagementJob.js
+++ b/awx/ui_next/src/screens/ManagementJob/ManagementJob.js
@@ -41,7 +41,7 @@ function ManagementJob({ setBreadcrumb }) {
page_size: 1,
role_level: 'notification_admin_role',
}),
- ]).then(([systemJobTemplate, notificationRoles]) => ({
+ ]).then(([{ data: systemJobTemplate }, notificationRoles]) => ({
systemJobTemplate,
notificationRoles,
})),
diff --git a/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/DaysToKeepStep.jsx b/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/DaysToKeepStep.jsx
new file mode 100644
index 0000000000..bfb8266eb9
--- /dev/null
+++ b/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/DaysToKeepStep.jsx
@@ -0,0 +1,34 @@
+import React from 'react';
+import { t } from '@lingui/macro';
+import { Form } from '@patternfly/react-core';
+import { useField } from 'formik';
+import {
+ required,
+ minMaxValue,
+ integer,
+ combine,
+} from '../../../../../util/validators';
+import FormField from '../../../../../components/FormField';
+
+function DaysToKeepStep() {
+ const [, meta] = useField('daysToKeep');
+ const validators = [required(null), minMaxValue(0), integer()];
+ return (
+
+ );
+}
+
+export default DaysToKeepStep;
diff --git a/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/DaysToKeepStep.test.jsx b/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/DaysToKeepStep.test.jsx
new file mode 100644
index 0000000000..9c17f80cf7
--- /dev/null
+++ b/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/DaysToKeepStep.test.jsx
@@ -0,0 +1,28 @@
+import React from 'react';
+import { Formik } from 'formik';
+import { mountWithContexts } from '../../../../../../testUtils/enzymeHelpers';
+import DaysToKeepStep from './DaysToKeepStep';
+
+let wrapper;
+
+describe('DaysToKeepStep', () => {
+ beforeAll(() => {
+ wrapper = mountWithContexts(
+
+
+
+ );
+ });
+
+ afterAll(() => {
+ wrapper.unmount();
+ });
+
+ test('Days to keep field rendered correctly', () => {
+ expect(wrapper.find('FormField#days-to-keep').length).toBe(1);
+ expect(wrapper.find('FormField#days-to-keep').prop('isRequired')).toBe(
+ true
+ );
+ expect(wrapper.find('input#days-to-keep').prop('value')).toBe(30);
+ });
+});
diff --git a/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeAddModal.js b/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeAddModal.js
index da34b4fd52..ac37f0e562 100644
--- a/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeAddModal.js
+++ b/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeAddModal.js
@@ -56,7 +56,13 @@ function NodeAddModal() {
) {
node.promptValues = values;
}
+ if (values?.nodeType === 'system_job_template') {
+ node.promptValues = {
+ extra_data: values?.extra_data,
+ };
+ }
}
+
dispatch({
type: 'CREATE_NODE',
node,
diff --git a/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeEditModal.js b/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeEditModal.js
index b83d99b4fc..762f07aeed 100644
--- a/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeEditModal.js
+++ b/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeEditModal.js
@@ -44,6 +44,11 @@ function NodeEditModal() {
node.launchConfig = config;
}
+ if (nodeType === 'system_job_template') {
+ node.promptValues = {
+ extra_data: values?.extra_data,
+ };
+ }
}
dispatch({
diff --git a/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.js b/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.js
index 6789085a45..9b9f3fe785 100644
--- a/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.js
+++ b/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.js
@@ -1,7 +1,6 @@
import 'styled-components/macro';
import React, { useContext, useState, useEffect, useCallback } from 'react';
import { useHistory } from 'react-router-dom';
-
import { t } from '@lingui/macro';
import { Formik, useFormikContext } from 'formik';
import yaml from 'js-yaml';
@@ -27,12 +26,10 @@ import { JobTemplatesAPI, WorkflowJobTemplatesAPI } from 'api';
import Wizard from 'components/Wizard';
import AlertModal from 'components/AlertModal';
import useWorkflowNodeSteps from './useWorkflowNodeSteps';
-
import NodeNextButton from './NodeNextButton';
function NodeModalForm({
askLinkType,
-
onSave,
title,
credentialError,
@@ -66,7 +63,6 @@ function NodeModalForm({
} = useWorkflowNodeSteps(
launchConfig,
surveyConfig,
-
values.nodeResource,
askLinkType,
resourceDefaultCredentials
@@ -98,7 +94,18 @@ function NodeModalForm({
}
values.extra_data = extraVars && parseVariableField(extraVars);
delete values.extra_vars;
+ } else if (
+ values.nodeType === 'system_job_template' &&
+ ['cleanup_activitystream', 'cleanup_jobs'].includes(
+ values?.nodeResource?.job_type
+ )
+ ) {
+ values.extra_data = {
+ days: parseInt(values?.daysToKeep, 10),
+ };
}
+
+ delete values.daysToKeep;
onSave(values, launchConfig);
};
@@ -351,6 +358,7 @@ const NodeModal = ({ onSave, askLinkType, title }) => {
initialValues={{
approvalName: '',
approvalDescription: '',
+ daysToKeep: 30,
timeoutMinutes: 0,
timeoutSeconds: 0,
convergence: 'any',
diff --git a/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js b/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js
index b5efb53901..9df1047ffd 100644
--- a/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js
+++ b/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/NodeTypeStep.js
@@ -1,6 +1,5 @@
import 'styled-components/macro';
import React, { useState } from 'react';
-
import { t, Trans } from '@lingui/macro';
import styled from 'styled-components';
import { useField } from 'formik';
@@ -24,6 +23,7 @@ import { useConfig } from 'contexts/Config';
import InventorySourcesList from './InventorySourcesList';
import JobTemplatesList from './JobTemplatesList';
import ProjectsList from './ProjectsList';
+import SystemJobTemplatesList from './SystemJobTemplatesList';
import WorkflowJobTemplatesList from './WorkflowJobTemplatesList';
const NodeTypeErrorAlert = styled(Alert)`
@@ -99,6 +99,12 @@ function NodeTypeStep() {
label: t`Project Sync`,
isDisabled: false,
},
+ {
+ key: 'system_job_template',
+ value: 'system_job_template',
+ label: t`Management Job`,
+ isDisabled: false,
+ },
{
key: 'workflow_job_template',
value: 'workflow_job_template',
@@ -137,6 +143,12 @@ function NodeTypeStep() {
onUpdateNodeResource={nodeResourceHelpers.setValue}
/>
)}
+ {nodeTypeField.value === 'system_job_template' && (
+
+ )}
{nodeTypeField.value === 'workflow_job_template' && (
{
+ const params = parseQueryString(QS_CONFIG, location.search);
+ const [response, actionsResponse] = await Promise.all([
+ SystemJobTemplatesAPI.read(params, {
+ role_level: 'execute_role',
+ }),
+ SystemJobTemplatesAPI.readOptions(),
+ ]);
+ return {
+ systemJobTemplates: response.data.results,
+ count: response.data.count,
+ relatedSearchableKeys: (
+ actionsResponse?.data?.related_search_fields || []
+ ).map((val) => val.slice(0, -8)),
+ searchableKeys: Object.keys(
+ actionsResponse.data.actions?.GET || {}
+ ).filter((key) => actionsResponse.data.actions?.GET[key].filterable),
+ };
+ }, [location]),
+ {
+ systemJobTemplates: [],
+ count: 0,
+ relatedSearchableKeys: [],
+ searchableKeys: [],
+ }
+ );
+
+ useEffect(() => {
+ fetchWorkflowJobTemplates();
+ }, [fetchWorkflowJobTemplates]);
+
+ return (
+
+ {t`Name`}
+
+ }
+ renderRow={(item, index) => (
+ onUpdateNodeResource(item)}
+ onDeselect={() => onUpdateNodeResource(null)}
+ isRadio
+ />
+ )}
+ renderToolbar={(props) => }
+ showPageSizeOptions={false}
+ toolbarSearchColumns={[
+ {
+ name: t`Name`,
+ key: 'name__icontains',
+ isDefault: true,
+ },
+ ]}
+ toolbarSearchableKeys={searchableKeys}
+ toolbarRelatedSearchableKeys={relatedSearchableKeys}
+ />
+ );
+}
+
+SystemJobTemplatesList.propTypes = {
+ nodeResource: shape(),
+ onUpdateNodeResource: func.isRequired,
+};
+
+SystemJobTemplatesList.defaultProps = {
+ nodeResource: null,
+};
+
+export default SystemJobTemplatesList;
diff --git a/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/SystemJobTemplatesList.test.jsx b/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/SystemJobTemplatesList.test.jsx
new file mode 100644
index 0000000000..a4e8435001
--- /dev/null
+++ b/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/SystemJobTemplatesList.test.jsx
@@ -0,0 +1,186 @@
+import React from 'react';
+import { act } from 'react-dom/test-utils';
+import { mountWithContexts } from '../../../../../../../testUtils/enzymeHelpers';
+import { SystemJobTemplatesAPI } from '../../../../../../api';
+import SystemJobTemplatesList from './SystemJobTemplatesList';
+
+jest.mock('../../../../../../api/models/SystemJobTemplates');
+
+const nodeResource = {
+ id: 1,
+ type: 'system_job_template',
+ url: '/api/v2/system_job_templates/1/',
+ related: {
+ next_schedule: '/api/v2/schedules/1/',
+ jobs: '/api/v2/system_job_templates/1/jobs/',
+ schedules: '/api/v2/system_job_templates/1/schedules/',
+ launch: '/api/v2/system_job_templates/1/launch/',
+ notification_templates_started:
+ '/api/v2/system_job_templates/1/notification_templates_started/',
+ notification_templates_success:
+ '/api/v2/system_job_templates/1/notification_templates_success/',
+ notification_templates_error:
+ '/api/v2/system_job_templates/1/notification_templates_error/',
+ },
+ summary_fields: {},
+ created: '2021-06-29T18:58:47.571901Z',
+ modified: '2021-06-29T18:58:47.571901Z',
+ name: 'Cleanup Job Details',
+ description: 'Remove job history',
+ last_job_run: null,
+ last_job_failed: false,
+ next_job_run: '2021-07-04T18:58:47Z',
+ status: 'ok',
+ execution_environment: null,
+ job_type: 'cleanup_jobs',
+};
+const onUpdateNodeResource = jest.fn();
+
+describe('SystemJobTemplatesList', () => {
+ let wrapper;
+ afterEach(() => {
+ wrapper.unmount();
+ });
+ test('Row selected when nodeResource id matches row id and clicking new row makes expected callback', async () => {
+ SystemJobTemplatesAPI.read.mockResolvedValueOnce({
+ data: {
+ count: 2,
+ results: [
+ nodeResource,
+ {
+ id: 2,
+ type: 'system_job_template',
+ url: '/api/v2/system_job_templates/2/',
+ related: {
+ last_job: '/api/v2/system_jobs/3/',
+ next_schedule: '/api/v2/schedules/2/',
+ jobs: '/api/v2/system_job_templates/2/jobs/',
+ schedules: '/api/v2/system_job_templates/2/schedules/',
+ launch: '/api/v2/system_job_templates/2/launch/',
+ notification_templates_started:
+ '/api/v2/system_job_templates/2/notification_templates_started/',
+ notification_templates_success:
+ '/api/v2/system_job_templates/2/notification_templates_success/',
+ notification_templates_error:
+ '/api/v2/system_job_templates/2/notification_templates_error/',
+ },
+ summary_fields: {
+ last_job: {
+ id: 3,
+ name: 'Cleanup Activity Stream',
+ description: 'Remove activity stream history',
+ finished: '2021-06-29T20:38:22.770364Z',
+ status: 'successful',
+ failed: false,
+ },
+ last_update: {
+ id: 3,
+ name: 'Cleanup Activity Stream',
+ description: 'Remove activity stream history',
+ status: 'successful',
+ failed: false,
+ },
+ },
+ created: '2021-06-29T18:58:47.571901Z',
+ modified: '2021-06-29T18:58:47.571901Z',
+ name: 'Cleanup Activity Stream',
+ description: 'Remove activity stream history',
+ last_job_run: '2021-06-29T20:38:22.770364Z',
+ last_job_failed: false,
+ next_job_run: '2021-07-06T18:58:47Z',
+ status: 'successful',
+ execution_environment: null,
+ job_type: 'cleanup_activitystream',
+ },
+ ],
+ },
+ });
+ SystemJobTemplatesAPI.readOptions.mockResolvedValue({
+ data: {
+ actions: {
+ GET: {},
+ POST: {},
+ },
+ related_search_fields: [],
+ },
+ });
+ await act(async () => {
+ wrapper = mountWithContexts(
+
+ );
+ });
+ wrapper.update();
+ expect(
+ wrapper.find('CheckboxListItem[name="Cleanup Job Details"]').props()
+ .isSelected
+ ).toBe(true);
+ expect(
+ wrapper.find('CheckboxListItem[name="Cleanup Activity Stream"]').props()
+ .isSelected
+ ).toBe(false);
+ wrapper
+ .find('CheckboxListItem[name="Cleanup Activity Stream"]')
+ .prop('onSelect')();
+ expect(onUpdateNodeResource).toHaveBeenCalledWith({
+ id: 2,
+ type: 'system_job_template',
+ url: '/api/v2/system_job_templates/2/',
+ related: {
+ last_job: '/api/v2/system_jobs/3/',
+ next_schedule: '/api/v2/schedules/2/',
+ jobs: '/api/v2/system_job_templates/2/jobs/',
+ schedules: '/api/v2/system_job_templates/2/schedules/',
+ launch: '/api/v2/system_job_templates/2/launch/',
+ notification_templates_started:
+ '/api/v2/system_job_templates/2/notification_templates_started/',
+ notification_templates_success:
+ '/api/v2/system_job_templates/2/notification_templates_success/',
+ notification_templates_error:
+ '/api/v2/system_job_templates/2/notification_templates_error/',
+ },
+ summary_fields: {
+ last_job: {
+ id: 3,
+ name: 'Cleanup Activity Stream',
+ description: 'Remove activity stream history',
+ finished: '2021-06-29T20:38:22.770364Z',
+ status: 'successful',
+ failed: false,
+ },
+ last_update: {
+ id: 3,
+ name: 'Cleanup Activity Stream',
+ description: 'Remove activity stream history',
+ status: 'successful',
+ failed: false,
+ },
+ },
+ created: '2021-06-29T18:58:47.571901Z',
+ modified: '2021-06-29T18:58:47.571901Z',
+ name: 'Cleanup Activity Stream',
+ description: 'Remove activity stream history',
+ last_job_run: '2021-06-29T20:38:22.770364Z',
+ last_job_failed: false,
+ next_job_run: '2021-07-06T18:58:47Z',
+ status: 'successful',
+ execution_environment: null,
+ job_type: 'cleanup_activitystream',
+ });
+ });
+ test('Error shown when read() request errors', async () => {
+ SystemJobTemplatesAPI.read.mockRejectedValue(new Error());
+ await act(async () => {
+ wrapper = mountWithContexts(
+
+ );
+ });
+ wrapper.update();
+ expect(wrapper.find('ErrorDetail').length).toBe(1);
+ });
+});
diff --git a/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeViewModal.js b/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeViewModal.js
index 550319df41..4e7c01127e 100644
--- a/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeViewModal.js
+++ b/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeViewModal.js
@@ -136,7 +136,12 @@ function NodeViewModal({ readOnly }) {
if (promptValues) {
overrides = promptValues;
- if (launchConfig.ask_variables_on_launch || launchConfig.survey_enabled) {
+ if (
+ launchConfig.ask_variables_on_launch ||
+ launchConfig.survey_enabled ||
+ (fullUnifiedJobTemplate.type === 'system_job_template' &&
+ promptValues.extra_data)
+ ) {
overrides.extra_vars = jsonToYaml(
JSON.stringify(promptValues.extra_data)
);
@@ -150,7 +155,11 @@ function NodeViewModal({ readOnly }) {
if (launchConfig.ask_scm_branch_on_launch) {
overrides.scm_branch = originalNodeObject.scm_branch;
}
- if (launchConfig.ask_variables_on_launch || launchConfig.survey_enabled) {
+ if (
+ launchConfig.ask_variables_on_launch ||
+ launchConfig.survey_enabled ||
+ fullUnifiedJobTemplate.type === 'system_job_template'
+ ) {
overrides.extra_vars = jsonToYaml(
JSON.stringify(originalNodeObject.extra_data)
);
diff --git a/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/useDaysToKeepStep.jsx b/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/useDaysToKeepStep.jsx
new file mode 100644
index 0000000000..8990d8c8e7
--- /dev/null
+++ b/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/useDaysToKeepStep.jsx
@@ -0,0 +1,43 @@
+import React from 'react';
+import { t } from '@lingui/macro';
+import { useField } from 'formik';
+import DaysToKeepStep from './DaysToKeepStep';
+import StepName from '../../../../../components/LaunchPrompt/steps/StepName';
+
+const STEP_ID = 'daysToKeep';
+
+export default function useDaysToKeepStep() {
+ const [, nodeResourceMeta] = useField('nodeResource');
+ const [, daysToKeepMeta] = useField('daysToKeep');
+
+ return {
+ step: getStep(nodeResourceMeta, daysToKeepMeta),
+ initialValues: { daysToKeep: 30 },
+ isReady: true,
+ contentError: null,
+ hasError: !!daysToKeepMeta.error,
+ setTouched: (setFieldTouched) => {
+ setFieldTouched('daysToKeep', true, false);
+ },
+ validate: () => {},
+ };
+}
+function getStep(nodeResourceMeta, daysToKeepMeta) {
+ if (
+ ['cleanup_activitystream', 'cleanup_jobs'].includes(
+ nodeResourceMeta?.value?.job_type
+ )
+ ) {
+ return {
+ id: STEP_ID,
+ name: (
+
+ {t`Days to keep`}
+
+ ),
+ component: ,
+ enableNext: !daysToKeepMeta.error,
+ };
+ }
+ return null;
+}
diff --git a/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/useWorkflowNodeSteps.js b/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/useWorkflowNodeSteps.js
index b5cd974d16..c91313952d 100644
--- a/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/useWorkflowNodeSteps.js
+++ b/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/useWorkflowNodeSteps.js
@@ -9,6 +9,7 @@ import usePreviewStep from 'components/LaunchPrompt/steps/usePreviewStep';
import { WorkflowStateContext } from 'contexts/Workflow';
import { jsonToYaml } from 'util/yaml';
import useNodeTypeStep from './NodeTypeStep/useNodeTypeStep';
+import useDaysToKeepStep from './useDaysToKeepStep';
import useRunTypeStep from './useRunTypeStep';
function showPreviewStep(nodeType, launchConfig) {
@@ -59,6 +60,34 @@ const getNodeToEditDefaultValues = (
return initialValues;
}
+ if (nodeToEdit?.fullUnifiedJobTemplate?.type === 'system_job_template') {
+ if (
+ nodeToEdit?.promptValues?.extra_data &&
+ Object.prototype.hasOwnProperty.call(
+ nodeToEdit.promptValues.extra_data,
+ 'days'
+ )
+ ) {
+ initialValues.daysToKeep = parseInt(
+ nodeToEdit.promptValues.extra_data.days,
+ 10
+ );
+ } else if (
+ nodeToEdit?.originalNodeObject?.extra_data &&
+ Object.prototype.hasOwnProperty.call(
+ nodeToEdit.originalNodeObject.extra_data,
+ 'days'
+ )
+ ) {
+ initialValues.daysToKeep = parseInt(
+ nodeToEdit.originalNodeObject.extra_data.days,
+ 10
+ );
+ }
+
+ return initialValues;
+ }
+
if (!launchConfig || launchConfig === {}) {
return initialValues;
}
@@ -186,7 +215,6 @@ const getNodeToEditDefaultValues = (
export default function useWorkflowNodeSteps(
launchConfig,
surveyConfig,
-
resource,
askLinkType,
resourceDefaultCredentials
@@ -202,6 +230,7 @@ export default function useWorkflowNodeSteps(
const steps = [
useRunTypeStep(askLinkType),
useNodeTypeStep(launchConfig),
+ useDaysToKeepStep(),
useInventoryStep(launchConfig, resource, visited),
useCredentialsStep(launchConfig, resource, resourceDefaultCredentials),
useOtherPromptsStep(launchConfig, resource),
@@ -213,7 +242,6 @@ export default function useWorkflowNodeSteps(
steps.push(
usePreviewStep(
launchConfig,
-
resource,
surveyConfig,
hasErrors,
diff --git a/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/shared/WorkflowJobTemplateVisualizerUtils.js b/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/shared/WorkflowJobTemplateVisualizerUtils.js
index eba12a84d8..7fe8e11a44 100644
--- a/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/shared/WorkflowJobTemplateVisualizerUtils.js
+++ b/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/shared/WorkflowJobTemplateVisualizerUtils.js
@@ -2,6 +2,7 @@ import {
InventorySourcesAPI,
JobTemplatesAPI,
ProjectsAPI,
+ SystemJobTemplatesAPI,
WorkflowJobTemplatesAPI,
} from 'api';
@@ -23,6 +24,9 @@ export default function getNodeType(node) {
case 'workflow_approval_template':
case 'workflow_approval':
return ['approval', null];
+ case 'system_job_template':
+ case 'system_job':
+ return ['system_job_template', SystemJobTemplatesAPI];
default:
return [null, null];
}
diff --git a/awx/ui_next/src/util/validators.js b/awx/ui_next/src/util/validators.js
index 82e9b202fd..906efd22e2 100644
--- a/awx/ui_next/src/util/validators.js
+++ b/awx/ui_next/src/util/validators.js
@@ -57,6 +57,12 @@ export function minLength(min) {
export function minMaxValue(min, max) {
return (value) => {
+ if (!Number.isFinite(min) && value > max) {
+ return t`This field must be a number and have a value less than ${max}`;
+ }
+ if (!Number.isFinite(max) && value < min) {
+ return t`This field must be a number and have a value greater than ${min}`;
+ }
if (value < min || value > max) {
return t`This field must be a number and have a value between ${min} and ${max}`;
}