diff --git a/awx/ui_next/src/components/LaunchPrompt/LaunchPrompt.jsx b/awx/ui_next/src/components/LaunchPrompt/LaunchPrompt.jsx index e1fc3bbdad..d3219c11d2 100644 --- a/awx/ui_next/src/components/LaunchPrompt/LaunchPrompt.jsx +++ b/awx/ui_next/src/components/LaunchPrompt/LaunchPrompt.jsx @@ -107,16 +107,22 @@ function LaunchPrompt({ config, resource = {}, onLaunch, onCancel, i18n }) { return ( onLaunch(values)} > diff --git a/awx/ui_next/src/components/LaunchPrompt/steps/useCredentialsStep.jsx b/awx/ui_next/src/components/LaunchPrompt/steps/useCredentialsStep.jsx index 77278363df..8373d0efca 100644 --- a/awx/ui_next/src/components/LaunchPrompt/steps/useCredentialsStep.jsx +++ b/awx/ui_next/src/components/LaunchPrompt/steps/useCredentialsStep.jsx @@ -1,20 +1,64 @@ -import React from 'react'; +import React, { useCallback, useEffect } from 'react'; import { t } from '@lingui/macro'; +import useRequest from '../../../util/useRequest'; +import { + WorkflowJobTemplateNodesAPI, + JobTemplatesAPI, + WorkflowJobTemplatesAPI, +} from '../../../api'; + import CredentialsStep from './CredentialsStep'; const STEP_ID = 'credentials'; -export default function useCredentialsStep(config, i18n, resource) { +export default function useCredentialsStep( + config, + i18n, + selectedResource, + nodeToEdit +) { + const resource = nodeToEdit || selectedResource; + const { request: fetchCredentials, result, error, isLoading } = useRequest( + useCallback(async () => { + let credentials; + if (!nodeToEdit?.related?.credentials) { + return {}; + } + const { + data: { results }, + } = await WorkflowJobTemplateNodesAPI.readCredentials(nodeToEdit.id); + credentials = results; + if (results.length === 0 && config?.defaults?.credentials) { + const fetchCreds = config.job_template_data + ? JobTemplatesAPI.readDetail(config.job_template_data.id) + : WorkflowJobTemplatesAPI.readDetail( + config.workflow_job_template_data.id + ); + + const { + data: { + summary_fields: { credentials: defaultCreds }, + }, + } = await fetchCreds; + credentials = defaultCreds; + } + return credentials; + }, [nodeToEdit, config]) + ); + useEffect(() => { + fetchCredentials(); + }, [fetchCredentials, nodeToEdit]); + const validate = () => { return {}; }; return { step: getStep(config, i18n), - initialValues: getInitialValues(config, resource), + initialValues: getInitialValues(config, resource, result), validate, - isReady: true, - contentError: null, + isReady: !isLoading && !!result, + contentError: error, formError: null, setTouched: setFieldsTouched => { setFieldsTouched({ @@ -37,11 +81,11 @@ function getStep(config, i18n) { }; } -function getInitialValues(config, resource) { +function getInitialValues(config, resource, result) { if (!config.ask_credential_on_launch) { return {}; } return { - credentials: resource?.summary_fields?.credentials || [], + credentials: resource?.summary_fields?.credentials || result || [], }; } diff --git a/awx/ui_next/src/components/LaunchPrompt/steps/useInventoryStep.jsx b/awx/ui_next/src/components/LaunchPrompt/steps/useInventoryStep.jsx index ba047878cf..22b19fcc22 100644 --- a/awx/ui_next/src/components/LaunchPrompt/steps/useInventoryStep.jsx +++ b/awx/ui_next/src/components/LaunchPrompt/steps/useInventoryStep.jsx @@ -6,8 +6,15 @@ import StepName from './StepName'; const STEP_ID = 'inventory'; -export default function useInventoryStep(config, i18n, visitedSteps, resource) { +export default function useInventoryStep( + config, + i18n, + visitedSteps, + selectedResource, + nodeToEdit +) { const [, meta] = useField('inventory'); + const resource = nodeToEdit || selectedResource; const formError = Object.keys(visitedSteps).includes(STEP_ID) && (!meta.value || meta.error); diff --git a/awx/ui_next/src/components/LaunchPrompt/steps/useOtherPromptsStep.jsx b/awx/ui_next/src/components/LaunchPrompt/steps/useOtherPromptsStep.jsx index 88773dbfce..d3bf095c23 100644 --- a/awx/ui_next/src/components/LaunchPrompt/steps/useOtherPromptsStep.jsx +++ b/awx/ui_next/src/components/LaunchPrompt/steps/useOtherPromptsStep.jsx @@ -5,7 +5,13 @@ import OtherPromptsStep from './OtherPromptsStep'; const STEP_ID = 'other'; -export default function useOtherPrompt(config, i18n, resource) { +export default function useOtherPrompt( + config, + i18n, + selectedResource, + nodeToEdit +) { + const resource = nodeToEdit || selectedResource; return { step: getStep(config, i18n), initialValues: getInitialValues(config, resource), diff --git a/awx/ui_next/src/components/LaunchPrompt/steps/usePreviewStep.jsx b/awx/ui_next/src/components/LaunchPrompt/steps/usePreviewStep.jsx index 9777032055..423ac3caf1 100644 --- a/awx/ui_next/src/components/LaunchPrompt/steps/usePreviewStep.jsx +++ b/awx/ui_next/src/components/LaunchPrompt/steps/usePreviewStep.jsx @@ -12,7 +12,8 @@ export default function usePreviewStep( hasErrors, needsPreviewStep ) { - const showStep = needsPreviewStep && resource && Object.keys(config).length > 0; + const showStep = + needsPreviewStep && resource && Object.keys(config).length > 0; return { step: showStep ? { diff --git a/awx/ui_next/src/components/LaunchPrompt/useLaunchSteps.js b/awx/ui_next/src/components/LaunchPrompt/useLaunchSteps.js index 0f318dd3e1..501cb91eea 100644 --- a/awx/ui_next/src/components/LaunchPrompt/useLaunchSteps.js +++ b/awx/ui_next/src/components/LaunchPrompt/useLaunchSteps.js @@ -9,10 +9,10 @@ import usePreviewStep from './steps/usePreviewStep'; export default function useLaunchSteps(config, resource, i18n) { const [visited, setVisited] = useState({}); const steps = [ - useInventoryStep(config, i18n, visited ), + useInventoryStep(config, i18n, visited), useCredentialsStep(config, i18n), useOtherPromptsStep(config, i18n), - useSurveyStep(config, i18n, visited ), + useSurveyStep(config, i18n, visited), ]; const { resetForm, values: formikValues } = useFormikContext(); const hasErrors = steps.some(step => step.formError); @@ -25,7 +25,7 @@ export default function useLaunchSteps(config, resource, i18n) { resource, steps[surveyStepIndex]?.survey, hasErrors, - + true ) ); diff --git a/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeAddModal.jsx b/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeAddModal.jsx index 62d0c9705c..bd4c159a59 100644 --- a/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeAddModal.jsx +++ b/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeAddModal.jsx @@ -24,6 +24,7 @@ function NodeAddModal({ i18n }) { values.addedCredentials = added; values.removedCredentials = removed; } + let node; if (values.nodeType === 'approval') { node = { diff --git a/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.jsx b/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.jsx index 276312580b..de598daf6f 100644 --- a/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.jsx +++ b/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeModal.jsx @@ -22,11 +22,7 @@ import { WorkflowDispatchContext, WorkflowStateContext, } from '../../../../../contexts/Workflow'; -import { - JobTemplatesAPI, - WorkflowJobTemplatesAPI, - WorkflowJobTemplateNodesAPI, -} from '../../../../../api'; +import { JobTemplatesAPI, WorkflowJobTemplatesAPI } from '../../../../../api'; import Wizard from '../../../../../components/Wizard'; import useWorkflowNodeSteps from './useWorkflowNodeSteps'; import AlertModal from '../../../../../components/AlertModal'; @@ -57,8 +53,6 @@ function NodeModalForm({ askLinkType, i18n, onSave, title, credentialError }) { values, setTouched, validateForm, - setFieldValue, - resetForm, } = useFormikContext(); const [triggerNext, setTriggerNext] = useState(0); @@ -72,21 +66,6 @@ function NodeModalForm({ askLinkType, i18n, onSave, title, credentialError }) { ); history.replace(`${history.location.pathname}?${otherParts.join('&')}`); }; - useEffect(() => { - if (values?.nodeResource?.summary_fields?.credentials?.length > 0) { - setFieldValue( - 'credentials', - values.nodeResource.summary_fields.credentials - ); - } - if (nodeToEdit?.unified_job_type === 'workflow_job') { - setFieldValue('nodeType', 'workflow_job_template'); - } - if (nodeToEdit?.unified_job_type === 'job') { - setFieldValue('nodeType', 'job_template'); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [nodeToEdit, values.nodeResource]); const { request: readLaunchConfig, @@ -120,6 +99,7 @@ function NodeModalForm({ askLinkType, i18n, onSave, title, credentialError }) { return data; } } + return {}; // eslint-disable-next-line react-hooks/exhaustive-deps }, [values.nodeResource, values.nodeType]), @@ -132,7 +112,6 @@ function NodeModalForm({ askLinkType, i18n, onSave, title, credentialError }) { const { steps: promptSteps, - initialValues, isReady, visitStep, visitAllSteps, @@ -142,7 +121,8 @@ function NodeModalForm({ askLinkType, i18n, onSave, title, credentialError }) { i18n, values.nodeResource, askLinkType, - !canLaunchWithoutPrompt(values.nodeType, launchConfig) + !canLaunchWithoutPrompt(values.nodeType, launchConfig), + nodeToEdit ); const handleSaveNode = () => { @@ -158,21 +138,17 @@ function NodeModalForm({ askLinkType, i18n, onSave, title, credentialError }) { const { error, dismissError } = useDismissableError( launchConfigError || contentError || credentialError ); - useEffect(() => { - if (isReady) { - resetForm({ - values: { - ...initialValues, - nodeResource: values.nodeResource, - nodeType: values.nodeType || 'job_template', - linkType: values.linkType || 'success', - verbosity: initialValues?.verbosity?.toString(), - }, - }); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [launchConfig, values.nodeType, isReady, values.nodeResource]); - const steps = [...(isReady ? [...promptSteps] : [])]; + + const steps = [ + ...(isReady + ? [...promptSteps] + : [ + { + name: i18n._(t`Content Loading`), + component: , + }, + ]), + ]; const CustomFooter = ( @@ -243,16 +219,7 @@ function NodeModalForm({ askLinkType, i18n, onSave, title, credentialError }) { } await validateForm(); }} - steps={ - isReady - ? steps - : [ - { - name: i18n._(t`Content Loading`), - component: , - }, - ] - } + steps={steps} css="overflow: scroll" title={wizardTitle} onNext={async (nextStep, prevStep) => { @@ -272,42 +239,13 @@ const NodeModal = ({ onSave, i18n, askLinkType, title }) => { const onSaveForm = (values, linkType, config) => { onSave(values, linkType, config); }; - const { request: fetchCredentials, result, error } = useRequest( - useCallback(async () => { - const { - data: { results }, - } = await WorkflowJobTemplateNodesAPI.readCredentials( - nodeToEdit.originalNodeObject.id - ); - return results; - }, [nodeToEdit]) - ); - useEffect(() => { - if (nodeToEdit?.originalNodeObject?.related?.credentials) { - fetchCredentials(); - } - }, [fetchCredentials, nodeToEdit]); return ( onSaveForm} > @@ -317,7 +255,6 @@ const NodeModal = ({ onSave, i18n, askLinkType, title }) => { onSave={onSaveForm} i18n={i18n} title={title} - credentialError={error} askLinkType={askLinkType} /> diff --git a/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/useNodeTypeStep.jsx b/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/useNodeTypeStep.jsx index ea60283228..d4b58ffbbf 100644 --- a/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/useNodeTypeStep.jsx +++ b/awx/ui_next/src/screens/Template/WorkflowJobTemplateVisualizer/Modals/NodeModals/NodeTypeStep/useNodeTypeStep.jsx @@ -5,7 +5,7 @@ import NodeTypeStep from './NodeTypeStep'; const STEP_ID = 'nodeType'; -export default function useNodeTypeStep(i18n, resource) { +export default function useNodeTypeStep(i18n, resource, nodeToEdit) { const [, meta] = useField('nodeType'); const [approvalNameField] = useField('approvalName'); const [nodeTypeField, ,] = useField('nodeType'); @@ -13,13 +13,12 @@ export default function useNodeTypeStep(i18n, resource) { return { step: getStep( - meta, i18n, nodeTypeField, approvalNameField, nodeResouceField ), - initialValues: getInitialValues(resource), + initialValues: getInitialValues(nodeToEdit), isReady: true, contentError: null, formError: meta.error, @@ -31,7 +30,6 @@ export default function useNodeTypeStep(i18n, resource) { }; } function getStep( - meta, i18n, nodeTypeField, approvalNameField, @@ -56,24 +54,24 @@ function getStep( }; } -function getInitialValues(resource) { +function getInitialValues(nodeToEdit) { let typeOfNode; if ( - !resource?.unifiedJobTemplate?.type && - !resource?.unifiedJobTemplate?.unified_job_type + !nodeToEdit?.unifiedJobTemplate?.type && + !nodeToEdit?.unifiedJobTemplate?.unified_job_type ) { return { nodeType: 'job_template' }; } const { unifiedJobTemplate: { type, unified_job_type }, - } = resource; + } = nodeToEdit; const unifiedType = type || unified_job_type; if (unifiedType === 'job' || unifiedType === 'job_template') typeOfNode = { nodeType: 'job_template', nodeResource: - resource.originalNodeObject.summary_fields.unified_job_template, + nodeToEdit.originalNodeObject.summary_fields.unified_job_template, }; if (unifiedType === 'project' || unifiedType === 'project_update') { typeOfNode = { nodeType: 'project_sync' }; @@ -88,7 +86,9 @@ function getInitialValues(resource) { unifiedType === 'workflow_job' || unifiedType === 'workflow_job_template' ) { - typeOfNode = { nodeType: 'workflow_job_template' }; + typeOfNode = { + nodeType: 'workflow_job_template', + }; } if ( unifiedType === 'workflow_approval_template' || 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 0dfe16b3b9..98fd239593 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 @@ -1,10 +1,5 @@ -import { - useState, - useEffect -} from 'react'; -import { - useFormikContext -} from 'formik'; +import { useState, useEffect } from 'react'; +import { useFormikContext } from 'formik'; import useInventoryStep from '../../../../../components/LaunchPrompt/steps/useInventoryStep'; import useCredentialsStep from '../../../../../components/LaunchPrompt/steps/useCredentialsStep'; import useOtherPromptsStep from '../../../../../components/LaunchPrompt/steps/useOtherPromptsStep'; @@ -18,21 +13,25 @@ export default function useWorkflowNodeSteps( i18n, resource, askLinkType, - needsPreviewStep + needsPreviewStep, + nodeToEdit ) { const [visited, setVisited] = useState({}); const steps = [ useRunTypeStep(i18n, askLinkType), - useNodeTypeStep(i18n, resource), - useInventoryStep(config, i18n, visited, resource), - useCredentialsStep(config, i18n, resource), - useOtherPromptsStep(config, i18n, resource), + useNodeTypeStep(i18n, nodeToEdit), + useInventoryStep( + config, + i18n, + visited, + resource, + nodeToEdit?.originalNodeObject + ), + useCredentialsStep(config, i18n, resource, nodeToEdit?.originalNodeObject), + useOtherPromptsStep(config, i18n, resource, nodeToEdit?.originalNodeObject), useSurveyStep(config, i18n, visited, resource), ]; - const { - resetForm, - values: formikValues - } = useFormikContext(); + const { resetForm, values: formikValues } = useFormikContext(); const hasErrors = steps.some(step => step.formError); const surveyStepIndex = steps.findIndex(step => step.survey); steps.push( @@ -55,16 +54,22 @@ export default function useWorkflowNodeSteps( }; }, {}); useEffect(() => { - if (surveyStepIndex > -1 && isReady) { + if (isReady) { resetForm({ values: { - ...formikValues, - ...steps[surveyStepIndex].initialValues, + ...initialValues, + nodeResource: formikValues.nodeResource, + nodeType: formikValues.nodeType || initialValues.nodeType, + linkType: formikValues.linkType || 'success', + verbosity: initialValues?.verbosity?.toString(), }, }); } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isReady]); + }, [ + config, + isReady, + ]); const stepWithError = steps.find(s => s.contentError); const contentError = stepWithError ? stepWithError.contentError : null;