diff --git a/awx/ui_next/src/components/CodeEditor/VariablesField.jsx b/awx/ui_next/src/components/CodeEditor/VariablesField.jsx
index ef9a551561..0e7633e470 100644
--- a/awx/ui_next/src/components/CodeEditor/VariablesField.jsx
+++ b/awx/ui_next/src/components/CodeEditor/VariablesField.jsx
@@ -1,5 +1,5 @@
import React, { useState, useEffect, useCallback } from 'react';
-import { string, bool } from 'prop-types';
+import { string, bool, func, oneOf } from 'prop-types';
import { t } from '@lingui/macro';
import { useField } from 'formik';
@@ -24,11 +24,20 @@ const StyledCheckboxField = styled(CheckboxField)`
margin-left: auto;
`;
-function VariablesField({ id, name, label, readOnly, promptId, tooltip }) {
+function VariablesField({
+ id,
+ name,
+ label,
+ readOnly,
+ promptId,
+ tooltip,
+ initialMode,
+ onModeChange,
+}) {
// track focus manually, because the Code Editor library doesn't wire
// into Formik completely
const [shouldValidate, setShouldValidate] = useState(false);
- const [mode, setMode] = useState(YAML_MODE);
+ const [mode, setMode] = useState(initialMode || YAML_MODE);
const validate = useCallback(
value => {
if (!shouldValidate) {
@@ -54,6 +63,7 @@ function VariablesField({ id, name, label, readOnly, promptId, tooltip }) {
// mode's useState above couldn't be initialized to JSON_MODE because
// the field value had to be defined below it
setMode(JSON_MODE);
+ onModeChange(JSON_MODE);
helpers.setValue(JSON.stringify(JSON.parse(field.value), null, 2));
}
}, []); // eslint-disable-line react-hooks/exhaustive-deps
@@ -76,6 +86,7 @@ function VariablesField({ id, name, label, readOnly, promptId, tooltip }) {
if (newMode === YAML_MODE && !isJsonEdited && lastYamlValue !== null) {
helpers.setValue(lastYamlValue, false);
setMode(newMode);
+ onModeChange(newMode);
return;
}
@@ -86,6 +97,7 @@ function VariablesField({ id, name, label, readOnly, promptId, tooltip }) {
: yamlToJson(field.value);
helpers.setValue(newVal, false);
setMode(newMode);
+ onModeChange(newMode);
} catch (err) {
helpers.setError(err.message);
}
@@ -163,10 +175,14 @@ VariablesField.propTypes = {
label: string.isRequired,
readOnly: bool,
promptId: string,
+ initialMode: oneOf([YAML_MODE, JSON_MODE]),
+ onModeChange: func,
};
VariablesField.defaultProps = {
readOnly: false,
promptId: null,
+ initialMode: YAML_MODE,
+ onModeChange: () => {},
};
function VariablesFieldInternals({
@@ -189,7 +205,11 @@ function VariablesFieldInternals({
if (mode === YAML_MODE) {
return;
}
- helpers.setValue(JSON.stringify(JSON.parse(field.value), null, 2));
+ try {
+ helpers.setValue(JSON.stringify(JSON.parse(field.value), null, 2));
+ } catch (e) {
+ helpers.setError(e.message);
+ }
}, []); // eslint-disable-line react-hooks/exhaustive-deps
return (
diff --git a/awx/ui_next/src/components/LaunchPrompt/LaunchPrompt.jsx b/awx/ui_next/src/components/LaunchPrompt/LaunchPrompt.jsx
index 614fde426a..ddb0a1e7cd 100644
--- a/awx/ui_next/src/components/LaunchPrompt/LaunchPrompt.jsx
+++ b/awx/ui_next/src/components/LaunchPrompt/LaunchPrompt.jsx
@@ -13,7 +13,6 @@ import AlertModal from '../AlertModal';
function PromptModalForm({
launchConfig,
-
onCancel,
onSubmit,
resource,
@@ -33,7 +32,6 @@ function PromptModalForm({
launchConfig,
surveyConfig,
resource,
-
resourceDefaultCredentials
);
@@ -124,7 +122,6 @@ function PromptModalForm({
function LaunchPrompt({
launchConfig,
-
onCancel,
onLaunch,
resource = {},
diff --git a/awx/ui_next/src/components/LaunchPrompt/steps/OtherPromptsStep.jsx b/awx/ui_next/src/components/LaunchPrompt/steps/OtherPromptsStep.jsx
index e938cb541d..f521c6e92d 100644
--- a/awx/ui_next/src/components/LaunchPrompt/steps/OtherPromptsStep.jsx
+++ b/awx/ui_next/src/components/LaunchPrompt/steps/OtherPromptsStep.jsx
@@ -20,7 +20,7 @@ const FieldHeader = styled.div`
}
`;
-function OtherPromptsStep({ launchConfig }) {
+function OtherPromptsStep({ launchConfig, variablesMode, onVarModeChange }) {
return (
diff --git a/awx/ui_next/src/components/LaunchPrompt/steps/OtherPromptsStep.test.jsx b/awx/ui_next/src/components/LaunchPrompt/steps/OtherPromptsStep.test.jsx
index 852cf97927..ca962d87c1 100644
--- a/awx/ui_next/src/components/LaunchPrompt/steps/OtherPromptsStep.test.jsx
+++ b/awx/ui_next/src/components/LaunchPrompt/steps/OtherPromptsStep.test.jsx
@@ -107,4 +107,29 @@ describe('OtherPromptsStep', () => {
true
);
});
+
+ test('should pass mode and onModeChange to VariablesField', async () => {
+ let wrapper;
+ const onModeChange = jest.fn();
+ await act(async () => {
+ wrapper = mountWithContexts(
+
+
+
+ );
+ });
+
+ expect(wrapper.find('VariablesField').prop('initialMode')).toEqual(
+ 'javascript'
+ );
+ expect(wrapper.find('VariablesField').prop('onModeChange')).toEqual(
+ onModeChange
+ );
+ });
});
diff --git a/awx/ui_next/src/components/LaunchPrompt/steps/PreviewStep.jsx b/awx/ui_next/src/components/LaunchPrompt/steps/PreviewStep.jsx
index e5e9e90bdd..e4bc717ec4 100644
--- a/awx/ui_next/src/components/LaunchPrompt/steps/PreviewStep.jsx
+++ b/awx/ui_next/src/components/LaunchPrompt/steps/PreviewStep.jsx
@@ -34,20 +34,24 @@ function PreviewStep({ resource, launchConfig, surveyConfig, formErrors }) {
};
if (launchConfig.ask_variables_on_launch || launchConfig.survey_enabled) {
- const initialExtraVars =
- launchConfig.ask_variables_on_launch && (overrides.extra_vars || '---');
- if (surveyConfig?.spec) {
- const passwordFields = surveyConfig.spec
- .filter(q => q.type === 'password')
- .map(q => q.variable);
- const masked = maskPasswords(surveyValues, passwordFields);
- overrides.extra_vars = yaml.safeDump(
- mergeExtraVars(initialExtraVars, masked)
- );
- } else {
- overrides.extra_vars = yaml.safeDump(
- mergeExtraVars(initialExtraVars, {})
- );
+ try {
+ const initialExtraVars =
+ launchConfig.ask_variables_on_launch && (overrides.extra_vars || '---');
+ if (surveyConfig?.spec) {
+ const passwordFields = surveyConfig.spec
+ .filter(q => q.type === 'password')
+ .map(q => q.variable);
+ const masked = maskPasswords(surveyValues, passwordFields);
+ overrides.extra_vars = yaml.safeDump(
+ mergeExtraVars(initialExtraVars, masked)
+ );
+ } else {
+ overrides.extra_vars = yaml.safeDump(
+ mergeExtraVars(initialExtraVars, {})
+ );
+ }
+ } catch (e) {
+ //
}
}
diff --git a/awx/ui_next/src/components/LaunchPrompt/steps/useInventoryStep.jsx b/awx/ui_next/src/components/LaunchPrompt/steps/useInventoryStep.jsx
index 0e69e0877c..c19bdf1ccc 100644
--- a/awx/ui_next/src/components/LaunchPrompt/steps/useInventoryStep.jsx
+++ b/awx/ui_next/src/components/LaunchPrompt/steps/useInventoryStep.jsx
@@ -12,12 +12,7 @@ const InventoryAlert = styled(Alert)`
const STEP_ID = 'inventory';
-export default function useInventoryStep(
- launchConfig,
- resource,
-
- visitedSteps
-) {
+export default function useInventoryStep(launchConfig, resource, visitedSteps) {
const [, meta, helpers] = useField('inventory');
const formError =
!resource || resource?.type === 'workflow_job_template'
diff --git a/awx/ui_next/src/components/LaunchPrompt/steps/useOtherPromptsStep.jsx b/awx/ui_next/src/components/LaunchPrompt/steps/useOtherPromptsStep.jsx
index f1581812e2..4b0fc00742 100644
--- a/awx/ui_next/src/components/LaunchPrompt/steps/useOtherPromptsStep.jsx
+++ b/awx/ui_next/src/components/LaunchPrompt/steps/useOtherPromptsStep.jsx
@@ -1,44 +1,77 @@
-import React from 'react';
+import React, { useState } from 'react';
import { t } from '@lingui/macro';
-import { jsonToYaml, parseVariableField } from '../../../util/yaml';
+import { useField } from 'formik';
+import { jsonToYaml, yamlToJson } from '../../../util/yaml';
import OtherPromptsStep from './OtherPromptsStep';
import StepName from './StepName';
const STEP_ID = 'other';
+export const YAML_MODE = 'yaml';
+export const JSON_MODE = 'javascript';
const getVariablesData = resource => {
if (resource?.extra_data) {
return jsonToYaml(JSON.stringify(resource.extra_data));
}
if (resource?.extra_vars && resource?.extra_vars !== '---') {
- return jsonToYaml(JSON.stringify(parseVariableField(resource.extra_vars)));
+ return resource.extra_vars;
}
return '---';
};
+const FIELD_NAMES = [
+ 'job_type',
+ 'limit',
+ 'verbosity',
+ 'diff_mode',
+ 'job_tags',
+ 'skip_tags',
+ 'extra_vars',
+];
+
export default function useOtherPromptsStep(launchConfig, resource) {
+ const [variablesField] = useField('extra_vars');
+ const [variablesMode, setVariablesMode] = useState(null);
+ const [isTouched, setIsTouched] = useState(false);
+
+ const handleModeChange = mode => {
+ setVariablesMode(mode);
+ };
+
+ const validateVariables = () => {
+ if (!isTouched) {
+ return false;
+ }
+ try {
+ if (variablesMode === JSON_MODE) {
+ JSON.parse(variablesField.value);
+ } else {
+ yamlToJson(variablesField.value);
+ }
+ } catch (error) {
+ return true;
+ }
+ return false;
+ };
+ const hasError = launchConfig.ask_variables_on_launch
+ ? validateVariables()
+ : false;
+
return {
- step: getStep(launchConfig),
+ step: getStep(launchConfig, hasError, variablesMode, handleModeChange),
initialValues: getInitialValues(launchConfig, resource),
isReady: true,
contentError: null,
- hasError: false,
+ hasError,
setTouched: setFieldTouched => {
- [
- 'job_type',
- 'limit',
- 'verbosity',
- 'diff_mode',
- 'job_tags',
- 'skip_tags',
- 'extra_vars',
- ].forEach(field => setFieldTouched(field, true, false));
+ setIsTouched(true);
+ FIELD_NAMES.forEach(fieldName => setFieldTouched(fieldName, true, false));
},
validate: () => {},
};
}
-function getStep(launchConfig) {
+function getStep(launchConfig, hasError, variablesMode, handleModeChange) {
if (!shouldShowPrompt(launchConfig)) {
return null;
}
@@ -46,11 +79,17 @@ function getStep(launchConfig) {
id: STEP_ID,
key: 5,
name: (
-
+
{t`Other prompts`}
),
- component: ,
+ component: (
+
+ ),
enableNext: true,
};
}
diff --git a/awx/ui_next/src/components/LaunchPrompt/useLaunchSteps.js b/awx/ui_next/src/components/LaunchPrompt/useLaunchSteps.js
index 74b962c0e1..e8b0b82c5f 100644
--- a/awx/ui_next/src/components/LaunchPrompt/useLaunchSteps.js
+++ b/awx/ui_next/src/components/LaunchPrompt/useLaunchSteps.js
@@ -54,12 +54,10 @@ export default function useLaunchSteps(
launchConfig,
resource,
resourceDefaultCredentials,
-
true
),
useCredentialPasswordsStep(
launchConfig,
-
showCredentialPasswordsStep(formikValues.credentials, launchConfig),
visited
),
@@ -77,52 +75,54 @@ export default function useLaunchSteps(
const stepsAreReady = !steps.some(s => !s.isReady);
useEffect(() => {
- if (stepsAreReady) {
- const initialValues = steps.reduce((acc, cur) => {
- return {
- ...acc,
- ...cur.initialValues,
- };
- }, {});
-
- const newFormValues = { ...initialValues };
-
- Object.keys(formikValues).forEach(formikValueKey => {
- if (
- formikValueKey === 'credential_passwords' &&
- Object.prototype.hasOwnProperty.call(
- newFormValues,
- 'credential_passwords'
- )
- ) {
- const formikCredentialPasswords = formikValues.credential_passwords;
- Object.keys(formikCredentialPasswords).forEach(
- credentialPasswordValueKey => {
- if (
- Object.prototype.hasOwnProperty.call(
- newFormValues.credential_passwords,
- credentialPasswordValueKey
- )
- ) {
- newFormValues.credential_passwords[credentialPasswordValueKey] =
- formikCredentialPasswords[credentialPasswordValueKey];
- }
- }
- );
- } else if (
- Object.prototype.hasOwnProperty.call(newFormValues, formikValueKey)
- ) {
- newFormValues[formikValueKey] = formikValues[formikValueKey];
- }
- });
-
- resetForm({
- values: newFormValues,
- touched,
- });
-
- setIsReady(true);
+ if (!stepsAreReady) {
+ return;
}
+
+ const initialValues = steps.reduce((acc, cur) => {
+ return {
+ ...acc,
+ ...cur.initialValues,
+ };
+ }, {});
+
+ const newFormValues = { ...initialValues };
+
+ Object.keys(formikValues).forEach(formikValueKey => {
+ if (
+ formikValueKey === 'credential_passwords' &&
+ Object.prototype.hasOwnProperty.call(
+ newFormValues,
+ 'credential_passwords'
+ )
+ ) {
+ const formikCredentialPasswords = formikValues.credential_passwords;
+ Object.keys(formikCredentialPasswords).forEach(
+ credentialPasswordValueKey => {
+ if (
+ Object.prototype.hasOwnProperty.call(
+ newFormValues.credential_passwords,
+ credentialPasswordValueKey
+ )
+ ) {
+ newFormValues.credential_passwords[credentialPasswordValueKey] =
+ formikCredentialPasswords[credentialPasswordValueKey];
+ }
+ }
+ );
+ } else if (
+ Object.prototype.hasOwnProperty.call(newFormValues, formikValueKey)
+ ) {
+ newFormValues[formikValueKey] = formikValues[formikValueKey];
+ }
+ });
+
+ resetForm({
+ values: newFormValues,
+ touched,
+ });
+
+ setIsReady(true);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [formikValues.credentials, stepsAreReady]);