From 4db6eaf1aae0ffe57cdfa7ee3a68098a40b712e2 Mon Sep 17 00:00:00 2001 From: mabashian Date: Tue, 29 Jun 2021 11:01:48 -0400 Subject: [PATCH 1/7] Add UI support for management jobs in workflows --- .../src/api/models/SystemJobTemplates.js | 6 - .../src/components/Workflow/WorkflowLegend.js | 4 + .../components/Workflow/WorkflowNodeHelp.js | 4 + .../Workflow/WorkflowNodeTypeLetter.js | 4 + .../screens/ManagementJob/ManagementJob.js | 2 +- .../Modals/NodeModals/DaysToKeepStep.jsx | 34 ++++ .../Modals/NodeModals/DaysToKeepStep.test.jsx | 28 +++ .../Modals/NodeModals/NodeAddModal.js | 6 + .../Modals/NodeModals/NodeEditModal.js | 5 + .../Modals/NodeModals/NodeModal.js | 17 +- .../NodeModals/NodeTypeStep/NodeTypeStep.js | 14 +- .../NodeTypeStep/SystemJobTemplatesList.jsx | 115 +++++++++++ .../SystemJobTemplatesList.test.jsx | 186 ++++++++++++++++++ .../Modals/NodeModals/useDaysToKeepStep.jsx | 43 ++++ .../Modals/NodeModals/useWorkflowNodeSteps.js | 31 ++- .../WorkflowJobTemplateVisualizerUtils.js | 4 + awx/ui_next/src/util/validators.js | 6 + 17 files changed, 495 insertions(+), 14 deletions(-) create mode 100644 awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/DaysToKeepStep.jsx create mode 100644 awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/DaysToKeepStep.test.jsx create mode 100644 awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/SystemJobTemplatesList.jsx create mode 100644 awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/SystemJobTemplatesList.test.jsx create mode 100644 awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/useDaysToKeepStep.jsx 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/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..d381877d9f --- /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 ( +
    { + e.preventDefault(); + }} + > + + + ); +} + +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..01fa863fb7 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'; @@ -12,6 +11,7 @@ import { WizardFooter, Form, } from '@patternfly/react-core'; +import AlertModal from 'components/AlertModal'; import ContentError from 'components/ContentError'; import ContentLoading from 'components/ContentLoading'; @@ -27,12 +27,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 +64,6 @@ function NodeModalForm({ } = useWorkflowNodeSteps( launchConfig, surveyConfig, - values.nodeResource, askLinkType, resourceDefaultCredentials @@ -98,7 +95,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: values?.daysToKeep, + }; } + + delete values.daysToKeep; onSave(values, launchConfig); }; @@ -351,6 +359,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/useDaysToKeepStep.jsx b/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/useDaysToKeepStep.jsx new file mode 100644 index 0000000000..b4002eee5b --- /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..2c9d9f6344 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,33 @@ const getNodeToEditDefaultValues = ( return initialValues; } + if (nodeToEdit?.fullUnifiedJobTemplate?.type === 'system_job_template') { + if ( + nodeToEdit.isEdited && + Object.prototype.hasOwnProperty.call( + nodeToEdit?.promptValues?.extra_data, + 'days' + ) + ) { + initialValues.daysToKeep = parseInt( + nodeToEdit.promptValues.extra_data.days, + 10 + ); + } else if ( + 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 +214,6 @@ const getNodeToEditDefaultValues = ( export default function useWorkflowNodeSteps( launchConfig, surveyConfig, - resource, askLinkType, resourceDefaultCredentials @@ -202,6 +229,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 +241,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}`; } From d7521efc91e913cf8e14a835140fc3fda9ef40f4 Mon Sep 17 00:00:00 2001 From: mabashian Date: Thu, 1 Jul 2021 10:43:52 -0400 Subject: [PATCH 2/7] Fix days to keep bug when editing a new node --- .../Modals/NodeModals/useWorkflowNodeSteps.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) 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 2c9d9f6344..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 @@ -62,9 +62,9 @@ const getNodeToEditDefaultValues = ( if (nodeToEdit?.fullUnifiedJobTemplate?.type === 'system_job_template') { if ( - nodeToEdit.isEdited && + nodeToEdit?.promptValues?.extra_data && Object.prototype.hasOwnProperty.call( - nodeToEdit?.promptValues?.extra_data, + nodeToEdit.promptValues.extra_data, 'days' ) ) { @@ -73,8 +73,9 @@ const getNodeToEditDefaultValues = ( 10 ); } else if ( + nodeToEdit?.originalNodeObject?.extra_data && Object.prototype.hasOwnProperty.call( - nodeToEdit?.originalNodeObject?.extra_data, + nodeToEdit.originalNodeObject.extra_data, 'days' ) ) { From 034c665c83bab279036a41473c3d11fb2a28f644 Mon Sep 17 00:00:00 2001 From: mabashian Date: Thu, 1 Jul 2021 11:17:46 -0400 Subject: [PATCH 3/7] Adds Variables detail to node view when node is a system job template --- .../components/PromptDetail/PromptDetail.js | 244 +++++++++--------- .../Modals/NodeModals/NodeViewModal.js | 12 +- 2 files changed, 137 insertions(+), 119 deletions(-) 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/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeViewModal.js b/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeViewModal.js index 550319df41..d5903cf5ef 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,11 @@ 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' + ) { overrides.extra_vars = jsonToYaml( JSON.stringify(promptValues.extra_data) ); @@ -150,7 +154,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) ); From 8374533c5fec289f7e5f887c93075db7a21118de Mon Sep 17 00:00:00 2001 From: mabashian Date: Mon, 12 Jul 2021 12:05:22 -0400 Subject: [PATCH 4/7] Ensure that days is always an integer --- .../Modals/NodeModals/NodeModal.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 01fa863fb7..2bb9605590 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 @@ -102,7 +102,7 @@ function NodeModalForm({ ) ) { values.extra_data = { - days: values?.daysToKeep, + days: parseInt(values?.daysToKeep, 10), }; } From 874b497794e8490cceb194e6dba4cc04f33ff552 Mon Sep 17 00:00:00 2001 From: mabashian Date: Mon, 12 Jul 2021 12:05:38 -0400 Subject: [PATCH 5/7] Update label on days field --- .../Modals/NodeModals/DaysToKeepStep.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index d381877d9f..d721b8eb16 100644 --- a/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/DaysToKeepStep.jsx +++ b/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/DaysToKeepStep.jsx @@ -25,7 +25,7 @@ function DaysToKeepStep() { isRequired validate={combine(validators)} validated={!(meta.touched && meta.error) ? 'default' : 'error'} - label={t`Days to keep`} + label={t`Days of data to be retained`} /> ); From 1e30e33d300ef73c756c55c9bc25d0b35ed08865 Mon Sep 17 00:00:00 2001 From: mabashian Date: Mon, 12 Jul 2021 13:29:01 -0400 Subject: [PATCH 6/7] Fix bug where page crashed if attempting to view system job node without extra vars --- .../Modals/NodeModals/NodeViewModal.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 d5903cf5ef..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 @@ -139,7 +139,8 @@ function NodeViewModal({ readOnly }) { if ( launchConfig.ask_variables_on_launch || launchConfig.survey_enabled || - fullUnifiedJobTemplate.type === 'system_job_template' + (fullUnifiedJobTemplate.type === 'system_job_template' && + promptValues.extra_data) ) { overrides.extra_vars = jsonToYaml( JSON.stringify(promptValues.extra_data) From 5f7db084d3e4698e3d71322724ec6a9797517357 Mon Sep 17 00:00:00 2001 From: mabashian Date: Thu, 22 Jul 2021 14:00:48 -0400 Subject: [PATCH 7/7] Fix linting errors --- .../Modals/NodeModals/DaysToKeepStep.jsx | 2 +- .../Modals/NodeModals/NodeModal.js | 1 - .../NodeTypeStep/SystemJobTemplatesList.jsx | 18 +++++++++--------- .../Modals/NodeModals/useDaysToKeepStep.jsx | 2 +- 4 files changed, 11 insertions(+), 12 deletions(-) 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 index d721b8eb16..bfb8266eb9 100644 --- a/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/DaysToKeepStep.jsx +++ b/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/DaysToKeepStep.jsx @@ -15,7 +15,7 @@ function DaysToKeepStep() { const validators = [required(null), minMaxValue(0), integer()]; return (
    { + onSubmit={(e) => { e.preventDefault(); }} > 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 2bb9605590..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 @@ -11,7 +11,6 @@ import { WizardFooter, Form, } from '@patternfly/react-core'; -import AlertModal from 'components/AlertModal'; import ContentError from 'components/ContentError'; import ContentLoading from 'components/ContentLoading'; diff --git a/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/SystemJobTemplatesList.jsx b/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/SystemJobTemplatesList.jsx index 9e26f5a9d7..f83614426d 100644 --- a/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/SystemJobTemplatesList.jsx +++ b/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/SystemJobTemplatesList.jsx @@ -2,15 +2,15 @@ import React, { useEffect, useCallback } from 'react'; import { useLocation } from 'react-router-dom'; import { t } from '@lingui/macro'; import { func, shape } from 'prop-types'; -import { SystemJobTemplatesAPI } from '../../../../../../api'; -import { getQSConfig, parseQueryString } from '../../../../../../util/qs'; -import useRequest from '../../../../../../util/useRequest'; -import DataListToolbar from '../../../../../../components/DataListToolbar'; -import CheckboxListItem from '../../../../../../components/CheckboxListItem'; +import { SystemJobTemplatesAPI } from 'api'; +import { getQSConfig, parseQueryString } from 'util/qs'; +import useRequest from 'hooks/useRequest'; +import DataListToolbar from 'components/DataListToolbar'; +import CheckboxListItem from 'components/CheckboxListItem'; import PaginatedTable, { HeaderCell, HeaderRow, -} from '../../../../../../components/PaginatedTable'; +} from 'components/PaginatedTable'; const QS_CONFIG = getQSConfig('system-job-templates', { page: 1, @@ -45,10 +45,10 @@ function SystemJobTemplatesList({ nodeResource, onUpdateNodeResource }) { count: response.data.count, relatedSearchableKeys: ( actionsResponse?.data?.related_search_fields || [] - ).map(val => val.slice(0, -8)), + ).map((val) => val.slice(0, -8)), searchableKeys: Object.keys( actionsResponse.data.actions?.GET || {} - ).filter(key => actionsResponse.data.actions?.GET[key].filterable), + ).filter((key) => actionsResponse.data.actions?.GET[key].filterable), }; }, [location]), { @@ -88,7 +88,7 @@ function SystemJobTemplatesList({ nodeResource, onUpdateNodeResource }) { isRadio /> )} - renderToolbar={props => } + renderToolbar={(props) => } showPageSizeOptions={false} toolbarSearchColumns={[ { 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 index b4002eee5b..8990d8c8e7 100644 --- a/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/useDaysToKeepStep.jsx +++ b/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/useDaysToKeepStep.jsx @@ -16,7 +16,7 @@ export default function useDaysToKeepStep() { isReady: true, contentError: null, hasError: !!daysToKeepMeta.error, - setTouched: setFieldTouched => { + setTouched: (setFieldTouched) => { setFieldTouched('daysToKeep', true, false); }, validate: () => {},