From 559679f8a3c3987f6d2449a60041aaddee4c7c86 Mon Sep 17 00:00:00 2001 From: Tsu-ba-me Date: Tue, 3 May 2022 19:33:18 -0400 Subject: [PATCH] fix(striker-ui): add basic validation workflow --- .../components/ProvisionServerDialog.tsx | 199 ++++++++++++++---- 1 file changed, 163 insertions(+), 36 deletions(-) diff --git a/striker-ui/components/ProvisionServerDialog.tsx b/striker-ui/components/ProvisionServerDialog.tsx index eaf83fe3..af405410 100644 --- a/striker-ui/components/ProvisionServerDialog.tsx +++ b/striker-ui/components/ProvisionServerDialog.tsx @@ -23,6 +23,7 @@ import { import Autocomplete from './Autocomplete'; import ContainedButton, { ContainedButtonProps } from './ContainedButton'; import MenuItem from './MenuItem'; +import MessageBox from './MessageBox'; import OutlinedInput from './OutlinedInput'; import OutlinedInputLabel from './OutlinedInputLabel'; import OutlinedInputWithLabel, { @@ -165,7 +166,13 @@ type UpdateLimitsFunction = (options?: { memory?: bigint; storageGroupUUIDMapToFree?: StorageGroupUUIDMapToFree; virtualDisks?: VirtualDiskStates; -}) => void; +}) => Partial>; + +type TestArgs = { + max: bigint | number; + min: bigint | number; + value: bigint | number; +}; const MOCK_DATA = { anvils: [ @@ -404,18 +411,16 @@ const createOutlinedSlider = ( value: number, sliderProps?: Partial, ): JSX.Element => ( - - - + ); const createOutlinedInputWithSelect = ( @@ -423,35 +428,42 @@ const createOutlinedInputWithSelect = ( label: string, selectItems: SelectItem[], { + error, inputWithLabelProps, selectProps, }: { + error?: string; inputWithLabelProps?: Partial; selectProps?: Partial; } = {}, ) => ( - + :first-child': { - flexGrow: 1, - }, - }} - > - :first-child': { + flexGrow: 1, + }, }} - /> - {createOutlinedSelect(`${id}-nested-select`, undefined, selectItems, { - selectProps, - })} - + > + + {createOutlinedSelect(`${id}-nested-select`, undefined, selectItems, { + selectProps, + })} + + {error && ( + + )} + ); const createMaxValueButton = ( @@ -1024,6 +1036,8 @@ const filterBlanks: (array: string[]) => string[] = (array: string[]) => const ProvisionServerDialog = ({ dialogProps: { open }, }: ProvisionServerDialogProps): JSX.Element => { + const inputCPUCoresMin = 1; + const [allAnvils, setAllAnvils] = useState< OrganizedAnvilDetailMetadataForProvisionServer[] >([]); @@ -1041,9 +1055,15 @@ const ProvisionServerDialog = ({ const [inputCPUCoresValue, setInputCPUCoresValue] = useState(1); const [inputCPUCoresMax, setInputCPUCoresMax] = useState(0); + const [inputCPUCoresError, setInputCPUCoresError] = useState< + string | undefined + >(); const [memory, setMemory] = useState(BIGINT_ZERO); const [memoryMax, setMemoryMax] = useState(BIGINT_ZERO); + const [inputMemoryError, setInputMemoryError] = useState< + string | undefined + >(); const [inputMemoryMax, setInputMemoryMax] = useState('0'); const [inputMemoryValue, setInputMemoryValue] = useState(''); const [inputMemoryUnit, setInputMemoryUnit] = useState('B'); @@ -1128,20 +1148,121 @@ const ProvisionServerDialog = ({ }, toUnit: ulInputMemoryUnit, }); + + return { + maxCPUCores, + maxMemory, + }; }; // The memorized version of updateLimits() should only be called during first render. // eslint-disable-next-line react-hooks/exhaustive-deps const initLimits = useCallback(updateLimits, []); + const testInput = (inputs: { [id: string]: Partial }): boolean => { + const testMax = ({ max, min }: TestArgs) => max >= min; + const testRange = ({ max, min, value }: TestArgs) => + value >= min && value <= max; + + const tests: { + [id: string]: { + defaults: TestArgs & { + onSuccess: () => void; + }; + tests: Array<{ + onFailure?: () => void; + onSuccess?: () => void; + test: (args: TestArgs) => boolean; + }>; + }; + } = { + cpuCores: { + defaults: { + max: inputCPUCoresMax, + min: inputCPUCoresMin, + onSuccess: () => { + setInputCPUCoresError(''); + }, + value: inputCPUCoresValue, + }, + tests: [ + { + onFailure: () => { + setInputCPUCoresError('Non available.'); + }, + test: testMax, + }, + { + onFailure: () => { + setInputCPUCoresError('Out of range.'); + }, + test: testRange, + }, + ], + }, + memory: { + defaults: { + max: memoryMax, + min: 1, + onSuccess: () => { + setInputMemoryError(''); + }, + value: memory, + }, + tests: [ + { + onFailure: () => { + setInputMemoryError('Non available.'); + }, + test: testMax, + }, + { + onFailure: () => { + setInputMemoryError('Out of range.'); + }, + test: testRange, + }, + ], + }, + }; + + return Object.keys(inputs).every((id: string) => { + const { + defaults: { + max: dMax, + min: dMin, + onSuccess: dOnSuccess, + value: dValue, + }, + tests: group, + } = tests[id]; + const { max = dMax, min = dMin, value = dValue } = inputs[id]; + + return group.every(({ onFailure, onSuccess = dOnSuccess, test }) => { + const result: boolean = test({ max, min, value }); + + if (result) { + onSuccess?.call(null); + } else { + onFailure?.call(null); + } + + return result; + }); + }); + }; + const changeMemory = ({ cmValue = BIGINT_ZERO, cmUnit = inputMemoryUnit, }: { cmValue?: bigint; cmUnit?: DataSizeUnit } = {}) => { setMemory(cmValue); - updateLimits({ + + const { maxMemory } = updateLimits({ inputMemoryUnit: cmUnit, memory: cmValue, }); + + testInput({ memory: { max: maxMemory, value: cmValue } }); }; const handleInputMemoryValueChange = ({ @@ -1257,6 +1378,7 @@ const ProvisionServerDialog = ({ > {createOutlinedSlider('ps-cpu-cores', 'CPU cores', inputCPUCoresValue, { + error: inputCPUCoresError, sliderProps: { onChange: (value) => { const newCPUCoresValue = value as number; @@ -1264,13 +1386,17 @@ const ProvisionServerDialog = ({ if (newCPUCoresValue !== inputCPUCoresValue) { setInputCPUCoresValue(newCPUCoresValue); - updateLimits({ + const { maxCPUCores: newCPUCoresMax } = updateLimits({ cpuCores: newCPUCoresValue, }); + + testInput({ + cpuCores: { max: newCPUCoresMax, value: newCPUCoresValue }, + }); } }, max: inputCPUCoresMax, - min: 1, + min: inputCPUCoresMin, }, })}