From 669d67b8fbb30266791606e7b7a83b21e40d7607 Mon Sep 17 00:00:00 2001 From: Keith Grant Date: Wed, 15 Apr 2020 09:21:03 -0700 Subject: [PATCH] flush out validators, survey questions --- .../components/LaunchPrompt/SurveyStep.jsx | 109 +++++++++++++++--- .../components/StatusIcon/StatusIcon.test.jsx | 1 - .../Template/Survey/SurveyQuestionForm.jsx | 1 + awx/ui_next/src/util/validators.jsx | 22 +++- awx/ui_next/src/util/validators.test.js | 52 ++++++++- 5 files changed, 164 insertions(+), 21 deletions(-) diff --git a/awx/ui_next/src/components/LaunchPrompt/SurveyStep.jsx b/awx/ui_next/src/components/LaunchPrompt/SurveyStep.jsx index 5d18040037..8b89b3c566 100644 --- a/awx/ui_next/src/components/LaunchPrompt/SurveyStep.jsx +++ b/awx/ui_next/src/components/LaunchPrompt/SurveyStep.jsx @@ -1,14 +1,23 @@ import React, { useCallback, useEffect } from 'react'; import { withI18n } from '@lingui/react'; +import { useField } from 'formik'; import { JobTemplatesAPI, WorkflowJobTemplatesAPI } from '@api'; import { Form } from '@patternfly/react-core'; import FormField from '@components/FormField'; +import AnsibleSelect from '@components/AnsibleSelect'; import ContentLoading from '@components/ContentLoading'; import ContentError from '@components/ContentError'; 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( useCallback(async () => { const { data } = @@ -29,21 +38,33 @@ function InventoryStep({ template, i18n }) { return ; } + const fieldTypes = { + text: TextField, + textarea: TextField, + password: TextField, + multiplechoice: MultipleChoiceField, + multiselect: MultiSelectField, + integer: NumberField, + float: NumberField, + }; return (
- {survey.spec.map(question => ( - - ))} + {survey.spec.map(question => { + const Field = fieldTypes[question.type]; + return ( + + ); + })} ); } -function SurveyQuestion({ question, i18n }) { - const isNumeric = question.type === 'number' || question.type === 'integer'; +function TextField({ question, i18n }) { + const validators = [ + question.required ? required(null, i18n) : null, + question.min ? minLength(question.min, i18n) : null, + question.max ? maxLength(question.max, i18n) : null, + ]; return ( ); } -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 ( + + ); +} + +function MultipleChoiceField({ question, i18n }) { + const [field, meta] = useField(question.question_name); + console.log(question, field); + return ( + ({ + key: opt, + value: opt, + label: opt, + }))} + /> + ); +} + +function MultiSelectField({ question, i18n }) { + const [field, meta] = useField(question.question_name); + return ( + ({ + key: opt, + value: opt, + label: opt, + }))} + /> + ); +} + +export default withI18n()(SurveyStep); diff --git a/awx/ui_next/src/components/StatusIcon/StatusIcon.test.jsx b/awx/ui_next/src/components/StatusIcon/StatusIcon.test.jsx index fd47a309c8..d0b3bf8c17 100644 --- a/awx/ui_next/src/components/StatusIcon/StatusIcon.test.jsx +++ b/awx/ui_next/src/components/StatusIcon/StatusIcon.test.jsx @@ -27,7 +27,6 @@ describe('StatusIcon', () => { }); test('renders a successful status when host status is "ok"', () => { const wrapper = mount(); - wrapper.debug(); expect(wrapper).toHaveLength(1); expect(wrapper.find('StatusIcon__SuccessfulTop')).toHaveLength(1); expect(wrapper.find('StatusIcon__SuccessfulBottom')).toHaveLength(1); diff --git a/awx/ui_next/src/screens/Template/Survey/SurveyQuestionForm.jsx b/awx/ui_next/src/screens/Template/Survey/SurveyQuestionForm.jsx index 78c59d274f..f2f1e8860c 100644 --- a/awx/ui_next/src/screens/Template/Survey/SurveyQuestionForm.jsx +++ b/awx/ui_next/src/screens/Template/Survey/SurveyQuestionForm.jsx @@ -199,6 +199,7 @@ function SurveyQuestionForm({ t`Each answer choice must be on a separate line.` )} isRequired + rows="10" /> { + if (value.trim().length < min) { + return i18n._(t`This field must be at least ${min} characters`); + } + return undefined; + }; +} + export function minMaxValue(min, max, i18n) { return value => { 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) { return value => { 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) { return error; } diff --git a/awx/ui_next/src/util/validators.test.js b/awx/ui_next/src/util/validators.test.js index 067f753263..f3e37c6cde 100644 --- a/awx/ui_next/src/util/validators.test.js +++ b/awx/ui_next/src/util/validators.test.js @@ -1,4 +1,11 @@ -import { required, maxLength, noWhiteSpace, combine } from './validators'; +import { + required, + minLength, + maxLength, + noWhiteSpace, + integer, + combine, +} from './validators'; 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', () => { expect(noWhiteSpace(i18n)('this has spaces')).toEqual({ id: 'This field must not contain spaces', @@ -68,6 +90,26 @@ describe('validators', () => { 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', () => { const validators = [required(null, i18n), noWhiteSpace(i18n)]; expect(combine(validators)('')).toEqual({ @@ -78,4 +120,12 @@ describe('validators', () => { }); 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(); + }); });