mirror of
https://github.com/ansible/awx.git
synced 2026-01-13 02:50:02 -03:30
validate variables field in launch prompt
This commit is contained in:
parent
13e1fc9839
commit
83b6a91623
@ -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 (
|
||||
|
||||
@ -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 = {},
|
||||
|
||||
@ -20,7 +20,7 @@ const FieldHeader = styled.div`
|
||||
}
|
||||
`;
|
||||
|
||||
function OtherPromptsStep({ launchConfig }) {
|
||||
function OtherPromptsStep({ launchConfig, variablesMode, onVarModeChange }) {
|
||||
return (
|
||||
<Form
|
||||
onSubmit={e => {
|
||||
@ -78,6 +78,8 @@ function OtherPromptsStep({ launchConfig }) {
|
||||
id="prompt-variables"
|
||||
name="extra_vars"
|
||||
label={t`Variables`}
|
||||
initialMode={variablesMode}
|
||||
onModeChange={onVarModeChange}
|
||||
/>
|
||||
)}
|
||||
</Form>
|
||||
|
||||
@ -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(
|
||||
<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) {
|
||||
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) {
|
||||
//
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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'
|
||||
|
||||
@ -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: (
|
||||
<StepName hasErrors={false} id="other-prompts-step">
|
||||
<StepName hasErrors={hasError} id="other-prompts-step">
|
||||
{t`Other prompts`}
|
||||
</StepName>
|
||||
),
|
||||
component: <OtherPromptsStep launchConfig={launchConfig} />,
|
||||
component: (
|
||||
<OtherPromptsStep
|
||||
launchConfig={launchConfig}
|
||||
variablesMode={variablesMode}
|
||||
onVarModeChange={handleModeChange}
|
||||
/>
|
||||
),
|
||||
enableNext: true,
|
||||
};
|
||||
}
|
||||
|
||||
@ -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]);
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user