mirror of
https://github.com/ansible/awx.git
synced 2026-03-03 09:48:51 -03:30
validate variables field in launch prompt
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import React, { useState, useEffect, useCallback } from 'react';
|
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 { t } from '@lingui/macro';
|
||||||
import { useField } from 'formik';
|
import { useField } from 'formik';
|
||||||
@@ -24,11 +24,20 @@ const StyledCheckboxField = styled(CheckboxField)`
|
|||||||
margin-left: auto;
|
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
|
// track focus manually, because the Code Editor library doesn't wire
|
||||||
// into Formik completely
|
// into Formik completely
|
||||||
const [shouldValidate, setShouldValidate] = useState(false);
|
const [shouldValidate, setShouldValidate] = useState(false);
|
||||||
const [mode, setMode] = useState(YAML_MODE);
|
const [mode, setMode] = useState(initialMode || YAML_MODE);
|
||||||
const validate = useCallback(
|
const validate = useCallback(
|
||||||
value => {
|
value => {
|
||||||
if (!shouldValidate) {
|
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
|
// mode's useState above couldn't be initialized to JSON_MODE because
|
||||||
// the field value had to be defined below it
|
// the field value had to be defined below it
|
||||||
setMode(JSON_MODE);
|
setMode(JSON_MODE);
|
||||||
|
onModeChange(JSON_MODE);
|
||||||
helpers.setValue(JSON.stringify(JSON.parse(field.value), null, 2));
|
helpers.setValue(JSON.stringify(JSON.parse(field.value), null, 2));
|
||||||
}
|
}
|
||||||
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
}, []); // 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) {
|
if (newMode === YAML_MODE && !isJsonEdited && lastYamlValue !== null) {
|
||||||
helpers.setValue(lastYamlValue, false);
|
helpers.setValue(lastYamlValue, false);
|
||||||
setMode(newMode);
|
setMode(newMode);
|
||||||
|
onModeChange(newMode);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,6 +97,7 @@ function VariablesField({ id, name, label, readOnly, promptId, tooltip }) {
|
|||||||
: yamlToJson(field.value);
|
: yamlToJson(field.value);
|
||||||
helpers.setValue(newVal, false);
|
helpers.setValue(newVal, false);
|
||||||
setMode(newMode);
|
setMode(newMode);
|
||||||
|
onModeChange(newMode);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
helpers.setError(err.message);
|
helpers.setError(err.message);
|
||||||
}
|
}
|
||||||
@@ -163,10 +175,14 @@ VariablesField.propTypes = {
|
|||||||
label: string.isRequired,
|
label: string.isRequired,
|
||||||
readOnly: bool,
|
readOnly: bool,
|
||||||
promptId: string,
|
promptId: string,
|
||||||
|
initialMode: oneOf([YAML_MODE, JSON_MODE]),
|
||||||
|
onModeChange: func,
|
||||||
};
|
};
|
||||||
VariablesField.defaultProps = {
|
VariablesField.defaultProps = {
|
||||||
readOnly: false,
|
readOnly: false,
|
||||||
promptId: null,
|
promptId: null,
|
||||||
|
initialMode: YAML_MODE,
|
||||||
|
onModeChange: () => {},
|
||||||
};
|
};
|
||||||
|
|
||||||
function VariablesFieldInternals({
|
function VariablesFieldInternals({
|
||||||
@@ -189,7 +205,11 @@ function VariablesFieldInternals({
|
|||||||
if (mode === YAML_MODE) {
|
if (mode === YAML_MODE) {
|
||||||
return;
|
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
|
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ import AlertModal from '../AlertModal';
|
|||||||
|
|
||||||
function PromptModalForm({
|
function PromptModalForm({
|
||||||
launchConfig,
|
launchConfig,
|
||||||
|
|
||||||
onCancel,
|
onCancel,
|
||||||
onSubmit,
|
onSubmit,
|
||||||
resource,
|
resource,
|
||||||
@@ -33,7 +32,6 @@ function PromptModalForm({
|
|||||||
launchConfig,
|
launchConfig,
|
||||||
surveyConfig,
|
surveyConfig,
|
||||||
resource,
|
resource,
|
||||||
|
|
||||||
resourceDefaultCredentials
|
resourceDefaultCredentials
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -124,7 +122,6 @@ function PromptModalForm({
|
|||||||
|
|
||||||
function LaunchPrompt({
|
function LaunchPrompt({
|
||||||
launchConfig,
|
launchConfig,
|
||||||
|
|
||||||
onCancel,
|
onCancel,
|
||||||
onLaunch,
|
onLaunch,
|
||||||
resource = {},
|
resource = {},
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ const FieldHeader = styled.div`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
function OtherPromptsStep({ launchConfig }) {
|
function OtherPromptsStep({ launchConfig, variablesMode, onVarModeChange }) {
|
||||||
return (
|
return (
|
||||||
<Form
|
<Form
|
||||||
onSubmit={e => {
|
onSubmit={e => {
|
||||||
@@ -78,6 +78,8 @@ function OtherPromptsStep({ launchConfig }) {
|
|||||||
id="prompt-variables"
|
id="prompt-variables"
|
||||||
name="extra_vars"
|
name="extra_vars"
|
||||||
label={t`Variables`}
|
label={t`Variables`}
|
||||||
|
initialMode={variablesMode}
|
||||||
|
onModeChange={onVarModeChange}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</Form>
|
</Form>
|
||||||
|
|||||||
@@ -107,4 +107,29 @@ describe('OtherPromptsStep', () => {
|
|||||||
true
|
true
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('should pass mode and onModeChange to VariablesField', async () => {
|
||||||
|
let wrapper;
|
||||||
|
const onModeChange = jest.fn();
|
||||||
|
await act(async () => {
|
||||||
|
wrapper = mountWithContexts(
|
||||||
|
<Formik initialValues={{ extra_vars: '{}' }}>
|
||||||
|
<OtherPromptsStep
|
||||||
|
variablesMode="javascript"
|
||||||
|
onVarModeChange={onModeChange}
|
||||||
|
launchConfig={{
|
||||||
|
ask_variables_on_launch: true,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Formik>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(wrapper.find('VariablesField').prop('initialMode')).toEqual(
|
||||||
|
'javascript'
|
||||||
|
);
|
||||||
|
expect(wrapper.find('VariablesField').prop('onModeChange')).toEqual(
|
||||||
|
onModeChange
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -34,20 +34,24 @@ function PreviewStep({ resource, launchConfig, surveyConfig, formErrors }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (launchConfig.ask_variables_on_launch || launchConfig.survey_enabled) {
|
if (launchConfig.ask_variables_on_launch || launchConfig.survey_enabled) {
|
||||||
const initialExtraVars =
|
try {
|
||||||
launchConfig.ask_variables_on_launch && (overrides.extra_vars || '---');
|
const initialExtraVars =
|
||||||
if (surveyConfig?.spec) {
|
launchConfig.ask_variables_on_launch && (overrides.extra_vars || '---');
|
||||||
const passwordFields = surveyConfig.spec
|
if (surveyConfig?.spec) {
|
||||||
.filter(q => q.type === 'password')
|
const passwordFields = surveyConfig.spec
|
||||||
.map(q => q.variable);
|
.filter(q => q.type === 'password')
|
||||||
const masked = maskPasswords(surveyValues, passwordFields);
|
.map(q => q.variable);
|
||||||
overrides.extra_vars = yaml.safeDump(
|
const masked = maskPasswords(surveyValues, passwordFields);
|
||||||
mergeExtraVars(initialExtraVars, masked)
|
overrides.extra_vars = yaml.safeDump(
|
||||||
);
|
mergeExtraVars(initialExtraVars, masked)
|
||||||
} else {
|
);
|
||||||
overrides.extra_vars = yaml.safeDump(
|
} else {
|
||||||
mergeExtraVars(initialExtraVars, {})
|
overrides.extra_vars = yaml.safeDump(
|
||||||
);
|
mergeExtraVars(initialExtraVars, {})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
//
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,12 +12,7 @@ const InventoryAlert = styled(Alert)`
|
|||||||
|
|
||||||
const STEP_ID = 'inventory';
|
const STEP_ID = 'inventory';
|
||||||
|
|
||||||
export default function useInventoryStep(
|
export default function useInventoryStep(launchConfig, resource, visitedSteps) {
|
||||||
launchConfig,
|
|
||||||
resource,
|
|
||||||
|
|
||||||
visitedSteps
|
|
||||||
) {
|
|
||||||
const [, meta, helpers] = useField('inventory');
|
const [, meta, helpers] = useField('inventory');
|
||||||
const formError =
|
const formError =
|
||||||
!resource || resource?.type === 'workflow_job_template'
|
!resource || resource?.type === 'workflow_job_template'
|
||||||
|
|||||||
@@ -1,44 +1,77 @@
|
|||||||
import React from 'react';
|
import React, { useState } from 'react';
|
||||||
import { t } from '@lingui/macro';
|
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 OtherPromptsStep from './OtherPromptsStep';
|
||||||
import StepName from './StepName';
|
import StepName from './StepName';
|
||||||
|
|
||||||
const STEP_ID = 'other';
|
const STEP_ID = 'other';
|
||||||
|
export const YAML_MODE = 'yaml';
|
||||||
|
export const JSON_MODE = 'javascript';
|
||||||
|
|
||||||
const getVariablesData = resource => {
|
const getVariablesData = resource => {
|
||||||
if (resource?.extra_data) {
|
if (resource?.extra_data) {
|
||||||
return jsonToYaml(JSON.stringify(resource.extra_data));
|
return jsonToYaml(JSON.stringify(resource.extra_data));
|
||||||
}
|
}
|
||||||
if (resource?.extra_vars && resource?.extra_vars !== '---') {
|
if (resource?.extra_vars && resource?.extra_vars !== '---') {
|
||||||
return jsonToYaml(JSON.stringify(parseVariableField(resource.extra_vars)));
|
return resource.extra_vars;
|
||||||
}
|
}
|
||||||
return '---';
|
return '---';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const FIELD_NAMES = [
|
||||||
|
'job_type',
|
||||||
|
'limit',
|
||||||
|
'verbosity',
|
||||||
|
'diff_mode',
|
||||||
|
'job_tags',
|
||||||
|
'skip_tags',
|
||||||
|
'extra_vars',
|
||||||
|
];
|
||||||
|
|
||||||
export default function useOtherPromptsStep(launchConfig, resource) {
|
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 {
|
return {
|
||||||
step: getStep(launchConfig),
|
step: getStep(launchConfig, hasError, variablesMode, handleModeChange),
|
||||||
initialValues: getInitialValues(launchConfig, resource),
|
initialValues: getInitialValues(launchConfig, resource),
|
||||||
isReady: true,
|
isReady: true,
|
||||||
contentError: null,
|
contentError: null,
|
||||||
hasError: false,
|
hasError,
|
||||||
setTouched: setFieldTouched => {
|
setTouched: setFieldTouched => {
|
||||||
[
|
setIsTouched(true);
|
||||||
'job_type',
|
FIELD_NAMES.forEach(fieldName => setFieldTouched(fieldName, true, false));
|
||||||
'limit',
|
|
||||||
'verbosity',
|
|
||||||
'diff_mode',
|
|
||||||
'job_tags',
|
|
||||||
'skip_tags',
|
|
||||||
'extra_vars',
|
|
||||||
].forEach(field => setFieldTouched(field, true, false));
|
|
||||||
},
|
},
|
||||||
validate: () => {},
|
validate: () => {},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function getStep(launchConfig) {
|
function getStep(launchConfig, hasError, variablesMode, handleModeChange) {
|
||||||
if (!shouldShowPrompt(launchConfig)) {
|
if (!shouldShowPrompt(launchConfig)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -46,11 +79,17 @@ function getStep(launchConfig) {
|
|||||||
id: STEP_ID,
|
id: STEP_ID,
|
||||||
key: 5,
|
key: 5,
|
||||||
name: (
|
name: (
|
||||||
<StepName hasErrors={false} id="other-prompts-step">
|
<StepName hasErrors={hasError} id="other-prompts-step">
|
||||||
{t`Other prompts`}
|
{t`Other prompts`}
|
||||||
</StepName>
|
</StepName>
|
||||||
),
|
),
|
||||||
component: <OtherPromptsStep launchConfig={launchConfig} />,
|
component: (
|
||||||
|
<OtherPromptsStep
|
||||||
|
launchConfig={launchConfig}
|
||||||
|
variablesMode={variablesMode}
|
||||||
|
onVarModeChange={handleModeChange}
|
||||||
|
/>
|
||||||
|
),
|
||||||
enableNext: true,
|
enableNext: true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,12 +54,10 @@ export default function useLaunchSteps(
|
|||||||
launchConfig,
|
launchConfig,
|
||||||
resource,
|
resource,
|
||||||
resourceDefaultCredentials,
|
resourceDefaultCredentials,
|
||||||
|
|
||||||
true
|
true
|
||||||
),
|
),
|
||||||
useCredentialPasswordsStep(
|
useCredentialPasswordsStep(
|
||||||
launchConfig,
|
launchConfig,
|
||||||
|
|
||||||
showCredentialPasswordsStep(formikValues.credentials, launchConfig),
|
showCredentialPasswordsStep(formikValues.credentials, launchConfig),
|
||||||
visited
|
visited
|
||||||
),
|
),
|
||||||
@@ -77,52 +75,54 @@ export default function useLaunchSteps(
|
|||||||
const stepsAreReady = !steps.some(s => !s.isReady);
|
const stepsAreReady = !steps.some(s => !s.isReady);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (stepsAreReady) {
|
if (!stepsAreReady) {
|
||||||
const initialValues = steps.reduce((acc, cur) => {
|
return;
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [formikValues.credentials, stepsAreReady]);
|
}, [formikValues.credentials, stepsAreReady]);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user