mirror of
https://github.com/ansible/awx.git
synced 2026-05-08 09:57:35 -02:30
flush out validators, survey questions
This commit is contained in:
@@ -1,14 +1,23 @@
|
|||||||
import React, { useCallback, useEffect } from 'react';
|
import React, { useCallback, useEffect } from 'react';
|
||||||
import { withI18n } from '@lingui/react';
|
import { withI18n } from '@lingui/react';
|
||||||
|
import { useField } from 'formik';
|
||||||
import { JobTemplatesAPI, WorkflowJobTemplatesAPI } from '@api';
|
import { JobTemplatesAPI, WorkflowJobTemplatesAPI } from '@api';
|
||||||
import { Form } from '@patternfly/react-core';
|
import { Form } from '@patternfly/react-core';
|
||||||
import FormField from '@components/FormField';
|
import FormField from '@components/FormField';
|
||||||
|
import AnsibleSelect from '@components/AnsibleSelect';
|
||||||
import ContentLoading from '@components/ContentLoading';
|
import ContentLoading from '@components/ContentLoading';
|
||||||
import ContentError from '@components/ContentError';
|
import ContentError from '@components/ContentError';
|
||||||
import useRequest from '@util/useRequest';
|
import useRequest from '@util/useRequest';
|
||||||
import { required } from '@util/validators';
|
import {
|
||||||
|
required,
|
||||||
|
minMaxValue,
|
||||||
|
maxLength,
|
||||||
|
minLength,
|
||||||
|
integer,
|
||||||
|
combine,
|
||||||
|
} from '@util/validators';
|
||||||
|
|
||||||
function InventoryStep({ template, i18n }) {
|
function SurveyStep({ template, i18n }) {
|
||||||
const { result: survey, request: fetchSurvey, isLoading, error } = useRequest(
|
const { result: survey, request: fetchSurvey, isLoading, error } = useRequest(
|
||||||
useCallback(async () => {
|
useCallback(async () => {
|
||||||
const { data } =
|
const { data } =
|
||||||
@@ -29,21 +38,33 @@ function InventoryStep({ template, i18n }) {
|
|||||||
return <ContentLoading />;
|
return <ContentLoading />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const fieldTypes = {
|
||||||
|
text: TextField,
|
||||||
|
textarea: TextField,
|
||||||
|
password: TextField,
|
||||||
|
multiplechoice: MultipleChoiceField,
|
||||||
|
multiselect: MultiSelectField,
|
||||||
|
integer: NumberField,
|
||||||
|
float: NumberField,
|
||||||
|
};
|
||||||
return (
|
return (
|
||||||
<Form>
|
<Form>
|
||||||
{survey.spec.map(question => (
|
{survey.spec.map(question => {
|
||||||
<SurveyQuestion
|
const Field = fieldTypes[question.type];
|
||||||
key={question.variable}
|
return (
|
||||||
question={question}
|
<Field key={question.variable} question={question} i18n={i18n} />
|
||||||
i18n={i18n}
|
);
|
||||||
/>
|
})}
|
||||||
))}
|
|
||||||
</Form>
|
</Form>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function SurveyQuestion({ question, i18n }) {
|
function TextField({ question, i18n }) {
|
||||||
const isNumeric = question.type === 'number' || question.type === 'integer';
|
const validators = [
|
||||||
|
question.required ? required(null, i18n) : null,
|
||||||
|
question.min ? minLength(question.min, i18n) : null,
|
||||||
|
question.max ? maxLength(question.max, i18n) : null,
|
||||||
|
];
|
||||||
return (
|
return (
|
||||||
<FormField
|
<FormField
|
||||||
id={`survey-question-${question.variable}`}
|
id={`survey-question-${question.variable}`}
|
||||||
@@ -51,14 +72,66 @@ function SurveyQuestion({ question, i18n }) {
|
|||||||
label={question.question_name}
|
label={question.question_name}
|
||||||
tooltip={question.question_description}
|
tooltip={question.question_description}
|
||||||
isRequired={question.required}
|
isRequired={question.required}
|
||||||
validate={question.required ? required(null, i18n) : null}
|
validate={combine(validators)}
|
||||||
type={isNumeric ? 'number' : question.type}
|
type={question.type}
|
||||||
min={isNumeric ? question.min : null}
|
minLength={question.min}
|
||||||
max={isNumeric ? question.max : null}
|
maxLength={question.max}
|
||||||
minLength={!isNumeric ? question.min : null}
|
|
||||||
maxLength={!isNumeric ? question.max : null}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default withI18n()(InventoryStep);
|
function NumberField({ question, i18n }) {
|
||||||
|
const validators = [
|
||||||
|
question.required ? required(null, i18n) : null,
|
||||||
|
minMaxValue(question.min, question.max, i18n),
|
||||||
|
question.type === 'integer' ? integer(i18n) : null,
|
||||||
|
];
|
||||||
|
return (
|
||||||
|
<FormField
|
||||||
|
id={`survey-question-${question.variable}`}
|
||||||
|
name={question.variable}
|
||||||
|
label={question.question_name}
|
||||||
|
tooltip={question.question_description}
|
||||||
|
isRequired={question.required}
|
||||||
|
validate={combine(validators)}
|
||||||
|
type="number"
|
||||||
|
min={question.min}
|
||||||
|
max={question.max}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function MultipleChoiceField({ question, i18n }) {
|
||||||
|
const [field, meta] = useField(question.question_name);
|
||||||
|
console.log(question, field);
|
||||||
|
return (
|
||||||
|
<AnsibleSelect
|
||||||
|
id={`survey-question-${question.variable}`}
|
||||||
|
isValid={!meta.errors}
|
||||||
|
{...field}
|
||||||
|
data={question.choices.split('/n').map(opt => ({
|
||||||
|
key: opt,
|
||||||
|
value: opt,
|
||||||
|
label: opt,
|
||||||
|
}))}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function MultiSelectField({ question, i18n }) {
|
||||||
|
const [field, meta] = useField(question.question_name);
|
||||||
|
return (
|
||||||
|
<AnsibleSelect
|
||||||
|
id={`survey-question-${question.variable}`}
|
||||||
|
isValid={!meta.errors}
|
||||||
|
{...field}
|
||||||
|
data={question.choices.split('/n').map(opt => ({
|
||||||
|
key: opt,
|
||||||
|
value: opt,
|
||||||
|
label: opt,
|
||||||
|
}))}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default withI18n()(SurveyStep);
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ describe('StatusIcon', () => {
|
|||||||
});
|
});
|
||||||
test('renders a successful status when host status is "ok"', () => {
|
test('renders a successful status when host status is "ok"', () => {
|
||||||
const wrapper = mount(<StatusIcon status="ok" />);
|
const wrapper = mount(<StatusIcon status="ok" />);
|
||||||
wrapper.debug();
|
|
||||||
expect(wrapper).toHaveLength(1);
|
expect(wrapper).toHaveLength(1);
|
||||||
expect(wrapper.find('StatusIcon__SuccessfulTop')).toHaveLength(1);
|
expect(wrapper.find('StatusIcon__SuccessfulTop')).toHaveLength(1);
|
||||||
expect(wrapper.find('StatusIcon__SuccessfulBottom')).toHaveLength(1);
|
expect(wrapper.find('StatusIcon__SuccessfulBottom')).toHaveLength(1);
|
||||||
|
|||||||
@@ -199,6 +199,7 @@ function SurveyQuestionForm({
|
|||||||
t`Each answer choice must be on a separate line.`
|
t`Each answer choice must be on a separate line.`
|
||||||
)}
|
)}
|
||||||
isRequired
|
isRequired
|
||||||
|
rows="10"
|
||||||
/>
|
/>
|
||||||
<FormField
|
<FormField
|
||||||
id="question-default"
|
id="question-default"
|
||||||
|
|||||||
@@ -25,6 +25,15 @@ export function maxLength(max, i18n) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function minLength(min, i18n) {
|
||||||
|
return value => {
|
||||||
|
if (value.trim().length < min) {
|
||||||
|
return i18n._(t`This field must be at least ${min} characters`);
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function minMaxValue(min, max, i18n) {
|
export function minMaxValue(min, max, i18n) {
|
||||||
return value => {
|
return value => {
|
||||||
if (value < min || value > max) {
|
if (value < min || value > max) {
|
||||||
@@ -57,10 +66,21 @@ export function noWhiteSpace(i18n) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function integer(i18n) {
|
||||||
|
return value => {
|
||||||
|
const str = String(value);
|
||||||
|
if (/[^0-9]/.test(str)) {
|
||||||
|
return i18n._(t`This field must be an integer`);
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
export function combine(validators) {
|
export function combine(validators) {
|
||||||
return value => {
|
return value => {
|
||||||
for (let i = 0; i < validators.length; i++) {
|
for (let i = 0; i < validators.length; i++) {
|
||||||
const error = validators[i](value);
|
const validate = validators[i];
|
||||||
|
const error = validate ? validate(value) : null;
|
||||||
if (error) {
|
if (error) {
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,11 @@
|
|||||||
import { required, maxLength, noWhiteSpace, combine } from './validators';
|
import {
|
||||||
|
required,
|
||||||
|
minLength,
|
||||||
|
maxLength,
|
||||||
|
noWhiteSpace,
|
||||||
|
integer,
|
||||||
|
combine,
|
||||||
|
} from './validators';
|
||||||
|
|
||||||
const i18n = { _: val => val };
|
const i18n = { _: val => val };
|
||||||
|
|
||||||
@@ -52,6 +59,21 @@ describe('validators', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('minLength accepts value above min', () => {
|
||||||
|
expect(minLength(3, i18n)('snazzy')).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('minLength accepts value equal to min', () => {
|
||||||
|
expect(minLength(10, i18n)('abracadbra')).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('minLength rejects value below min', () => {
|
||||||
|
expect(minLength(12, i18n)('abracadbra')).toEqual({
|
||||||
|
id: 'This field must be at least {min} characters',
|
||||||
|
values: { min: 12 },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
test('noWhiteSpace returns error', () => {
|
test('noWhiteSpace returns error', () => {
|
||||||
expect(noWhiteSpace(i18n)('this has spaces')).toEqual({
|
expect(noWhiteSpace(i18n)('this has spaces')).toEqual({
|
||||||
id: 'This field must not contain spaces',
|
id: 'This field must not contain spaces',
|
||||||
@@ -68,6 +90,26 @@ describe('validators', () => {
|
|||||||
expect(noWhiteSpace(i18n)('this_has_no_whitespace')).toBeUndefined();
|
expect(noWhiteSpace(i18n)('this_has_no_whitespace')).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('integer should accept integer (number)', () => {
|
||||||
|
expect(integer(i18n)(13)).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('integer should accept integer (string)', () => {
|
||||||
|
expect(integer(i18n)('13')).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('integer should reject decimal/float', () => {
|
||||||
|
expect(integer(i18n)(13.1)).toEqual({
|
||||||
|
id: 'This field must be an integer',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('integer should reject string containing alphanum', () => {
|
||||||
|
expect(integer(i18n)('15a')).toEqual({
|
||||||
|
id: 'This field must be an integer',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
test('combine should run all validators', () => {
|
test('combine should run all validators', () => {
|
||||||
const validators = [required(null, i18n), noWhiteSpace(i18n)];
|
const validators = [required(null, i18n), noWhiteSpace(i18n)];
|
||||||
expect(combine(validators)('')).toEqual({
|
expect(combine(validators)('')).toEqual({
|
||||||
@@ -78,4 +120,12 @@ describe('validators', () => {
|
|||||||
});
|
});
|
||||||
expect(combine(validators)('ok')).toBeUndefined();
|
expect(combine(validators)('ok')).toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('combine should skip null validators', () => {
|
||||||
|
const validators = [required(null, i18n), null];
|
||||||
|
expect(combine(validators)('')).toEqual({
|
||||||
|
id: 'This field must not be blank',
|
||||||
|
});
|
||||||
|
expect(combine(validators)('ok')).toBeUndefined();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user