From e92d9a1aa45a20b40ce32260e25e044402a2a763 Mon Sep 17 00:00:00 2001 From: mikekotikov Date: Fri, 14 Jun 2024 11:27:49 +0200 Subject: [PATCH 1/3] FIO-8414: Fix required validation not working in Data Grid --- .../validation/rules/validateRequired.ts | 6 +++- src/process/validation/util.ts | 29 +++++++++++++++++++ src/utils/operators/IsEmptyValue.js | 4 +-- 3 files changed, 36 insertions(+), 3 deletions(-) diff --git a/src/process/validation/rules/validateRequired.ts b/src/process/validation/rules/validateRequired.ts index 22c681ce..a77c69eb 100644 --- a/src/process/validation/rules/validateRequired.ts +++ b/src/process/validation/rules/validateRequired.ts @@ -7,7 +7,7 @@ import { AddressComponent, DayComponent } from 'types'; -import { isEmptyObject } from '../util'; +import { isEmptyObject, doesArrayDataHaveValue } from '../util'; import { isComponentNestedDataType } from 'utils/formUtil'; import { ProcessorInfo } from 'types/process/ProcessorInfo'; @@ -47,6 +47,10 @@ const valueIsPresent = (value: any, considerFalseTruthy: boolean, isNestedDataty else if (typeof value === 'object' && !isNestedDatatype) { return Object.values(value).some((val) => valueIsPresent(val, considerFalseTruthy)); } + // If value is an array, check it's children have value + else if (Array.isArray(value) && value.length) { + return doesArrayDataHaveValue(value); + } return true; } diff --git a/src/process/validation/util.ts b/src/process/validation/util.ts index 0ac06c42..c5a08208 100644 --- a/src/process/validation/util.ts +++ b/src/process/validation/util.ts @@ -2,6 +2,9 @@ import { FieldError } from 'error'; import { Component, ValidationContext } from 'types'; import { Evaluator, unescapeHTML } from 'utils'; import { VALIDATION_ERRORS } from './i18n'; +import _isEmpty from 'lodash/isEmpty'; +import _isObject from 'lodash/isObject'; +import _isPlainObject from 'lodash/isPlainObject'; export function isComponentPersistent(component: Component) { return component.persistent ? component.persistent : true; @@ -89,3 +92,29 @@ export const interpolateErrors = (errors: FieldError[], lang: string = 'en') => }; }); }; + +export const hasValue = (value: any) => { + if (_isObject(value)) { + return !_isEmpty(value); + } + + return (typeof value === 'number' && !Number.isNaN(value)) || !!value; +} + +export const doesArrayDataHaveValue = (dataValue: any[] = []): boolean => { + if (!Array.isArray(dataValue)) { + return !!dataValue; + } + + if (!dataValue.length) { + return false; + } + + const isArrayDataComponent = dataValue.every(_isPlainObject); + + if (isArrayDataComponent) { + return dataValue.some(value => Object.values(value).some(hasValue)); + } + + return dataValue.some(hasValue); +}; diff --git a/src/utils/operators/IsEmptyValue.js b/src/utils/operators/IsEmptyValue.js index 5f55172e..70644fa6 100644 --- a/src/utils/operators/IsEmptyValue.js +++ b/src/utils/operators/IsEmptyValue.js @@ -18,8 +18,8 @@ export default class IsEmptyValue extends ConditionOperator { const isEmptyValue = isEmpty(value); if (instance && instance.root) { - const conditionTriggerComponent = instance.root.getComponent(conditionComponentPath); - return conditionTriggerComponent ? conditionTriggerComponent.isEmpty() : isEmptyValue; + const conditionTriggerComponent = instance.root.getComponent?.(conditionComponentPath); + return conditionTriggerComponent?.isEmpty ? conditionTriggerComponent.isEmpty() : isEmptyValue; } return isEmptyValue; From 411e646c5e67cfdfaf3a9ade2ba327f0e332cee9 Mon Sep 17 00:00:00 2001 From: mikekotikov Date: Thu, 8 Aug 2024 10:54:27 +0300 Subject: [PATCH 2/3] FIO-8414: Add test --- .../fixtures/forDataGridRequired.json | 91 +++++++++++++++++++ src/process/__tests__/fixtures/index.ts | 3 +- src/process/__tests__/process.test.ts | 23 ++++- 3 files changed, 115 insertions(+), 2 deletions(-) create mode 100644 src/process/__tests__/fixtures/forDataGridRequired.json diff --git a/src/process/__tests__/fixtures/forDataGridRequired.json b/src/process/__tests__/fixtures/forDataGridRequired.json new file mode 100644 index 00000000..30aec20e --- /dev/null +++ b/src/process/__tests__/fixtures/forDataGridRequired.json @@ -0,0 +1,91 @@ +{ + "form": { + "name": "dsf", + "path": "dsf", + "type": "form", + "display": "form", + "components": [ + { + "label": "Data Grid", + "reorder": false, + "addAnotherPosition": "bottom", + "layoutFixed": false, + "enableRowGroups": false, + "initEmpty": false, + "tableView": false, + "defaultValue": [ + {} + ], + "validate": { + "required": true + }, + "validateWhenHidden": false, + "key": "dataGrid", + "type": "datagrid", + "input": true, + "components": [ + { + "label": "Columns", + "columns": [ + { + "components": [ + { + "label": "Text Field", + "applyMaskOn": "change", + "tableView": true, + "validateWhenHidden": false, + "key": "textField", + "type": "textfield", + "input": true + } + ], + "width": 6, + "offset": 0, + "push": 0, + "pull": 0, + "size": "md", + "currentWidth": 6 + }, + { + "components": [], + "width": 6, + "offset": 0, + "push": 0, + "pull": 0, + "size": "md", + "currentWidth": 6 + } + ], + "key": "columns", + "type": "columns", + "input": false, + "tableView": false + } + ] + }, + { + "type": "button", + "label": "Submit", + "key": "submit", + "disableOnInvalid": true, + "input": true, + "tableView": false + } + ], + "created": "2024-08-07T08:41:53.926Z", + "modified": "2024-08-07T08:41:53.932Z", + "machineName": "tbtzzegecytgzpi:dsf" + }, + "submission": { + "data": { + + "dataGrid": [ + { + "textField": "" + } + ], + "submit": true + } + + } +} \ No newline at end of file diff --git a/src/process/__tests__/fixtures/index.ts b/src/process/__tests__/fixtures/index.ts index 05ef6349..b9aaf101 100644 --- a/src/process/__tests__/fixtures/index.ts +++ b/src/process/__tests__/fixtures/index.ts @@ -3,8 +3,9 @@ import clearOnHideWithHiddenParent from './clearOnHideWithHiddenParent.json'; import skipValidForConditionallyHiddenComp from './skipValidForConditionallyHiddenComp.json'; import skipValidForLogicallyHiddenComp from './skipValidForLogicallyHiddenComp.json'; import skipValidWithHiddenParentComp from './skipValidWithHiddenParentComp.json'; +import forDataGridRequired from './forDataGridRequired.json'; import data1a from './data1a.json'; import form1 from './form1.json'; import subs from './subs.json'; -export { clearOnHideWithCustomCondition, clearOnHideWithHiddenParent, skipValidForLogicallyHiddenComp, skipValidForConditionallyHiddenComp, skipValidWithHiddenParentComp, data1a, form1, subs }; +export { clearOnHideWithCustomCondition, clearOnHideWithHiddenParent, skipValidForLogicallyHiddenComp, skipValidForConditionallyHiddenComp, skipValidWithHiddenParentComp, forDataGridRequired, data1a, form1, subs }; diff --git a/src/process/__tests__/process.test.ts b/src/process/__tests__/process.test.ts index 40bc0d7a..1300d94c 100644 --- a/src/process/__tests__/process.test.ts +++ b/src/process/__tests__/process.test.ts @@ -3,7 +3,7 @@ import assert from 'node:assert' import type { ContainerComponent, ValidationScope } from 'types'; import { getComponent } from 'utils/formUtil'; import { process, processSync, ProcessTargets } from '../index'; -import { clearOnHideWithCustomCondition, clearOnHideWithHiddenParent, skipValidForConditionallyHiddenComp, skipValidForLogicallyHiddenComp, skipValidWithHiddenParentComp } from './fixtures' +import { clearOnHideWithCustomCondition, clearOnHideWithHiddenParent, forDataGridRequired, skipValidForConditionallyHiddenComp, skipValidForLogicallyHiddenComp, skipValidWithHiddenParentComp } from './fixtures' /* describe('Process Tests', () => { it('Should perform the processes using the processReduced method.', async () => { @@ -3040,6 +3040,27 @@ describe('Process Tests', () => { }); }); + it('Should validate when all child components are empty in required Data Grid', async () => { + const { form, submission } = forDataGridRequired; + const context = { + form, + submission, + data: submission.data, + components: form.components, + processors: ProcessTargets.submission, + scope: {}, + config: { + server: true, + }, + }; + + processSync(context); + context.processors = ProcessTargets.evaluator; + processSync(context); + + expect((context.scope as ValidationScope).errors).to.have.length(1); + }); + describe('For EditGrid:', () => { const components = [ { From 7fd5d91315bc31c64d7f0a4a9823b59f23198508 Mon Sep 17 00:00:00 2001 From: mikekotikov Date: Thu, 22 Aug 2024 19:17:34 +0300 Subject: [PATCH 3/3] FIO-8414: Remove component instance from isEmptyValue execution --- .../conditions/__tests__/conditions.test.ts | 1 + src/utils/conditions.ts | 22 +++++++++---------- src/utils/formUtil.ts | 4 ++-- src/utils/operators/IsEmptyValue.js | 13 +++-------- 4 files changed, 17 insertions(+), 23 deletions(-) diff --git a/src/process/conditions/__tests__/conditions.test.ts b/src/process/conditions/__tests__/conditions.test.ts index 6e418b9d..fda7b9c5 100644 --- a/src/process/conditions/__tests__/conditions.test.ts +++ b/src/process/conditions/__tests__/conditions.test.ts @@ -8,6 +8,7 @@ const processForm = (form: any, submission: any) => { processors: [conditionProcessInfo], components: form.components, data: submission.data, + form, scope: {} }; processSync(context); diff --git a/src/utils/conditions.ts b/src/utils/conditions.ts index d426e396..41a27136 100644 --- a/src/utils/conditions.ts +++ b/src/utils/conditions.ts @@ -50,11 +50,11 @@ export function checkCustomConditional(condition: string, context: ConditionsCon /** * Checks the legacy conditionals. - * - * @param conditional - * @param context - * @param checkDefault - * @returns + * + * @param conditional + * @param context + * @param checkDefault + * @returns */ export function checkLegacyConditional(conditional: LegacyConditional, context: ConditionsContext): boolean | null { const { row, data, component } = context; @@ -75,9 +75,9 @@ export function checkLegacyConditional(conditional: LegacyConditional, context: /** * Checks the JSON Conditionals. - * @param conditional + * @param conditional * @param context - * @returns + * @returns */ export function checkJsonConditional(conditional: JSONConditional, context: ConditionsContext): boolean | null { const { evalContext } = context; @@ -90,9 +90,9 @@ export function checkJsonConditional(conditional: JSONConditional, context: Cond /** * Checks the simple conditionals. - * @param conditional - * @param context - * @returns + * @param conditional + * @param context + * @returns */ export function checkSimpleConditional(conditional: SimpleConditional, context: ConditionsContext): boolean | null { const { component, data, row, instance, form } = context; @@ -116,7 +116,7 @@ export function checkSimpleConditional(conditional: SimpleConditional, context: const ConditionOperator = ConditionOperators[operator]; return ConditionOperator - ? new ConditionOperator().getResult({ value, comparedValue, instance, component, conditionComponentPath }) + ? new ConditionOperator().getResult({ value, comparedValue, instance, component, conditionComponentPath, data, conditionComp }) : true; }), (res) => (res !== null)); diff --git a/src/utils/formUtil.ts b/src/utils/formUtil.ts index 9568b146..e7e77bbd 100644 --- a/src/utils/formUtil.ts +++ b/src/utils/formUtil.ts @@ -1173,8 +1173,8 @@ function isValueEmpty(component: Component, value: any) { return value == null || value === '' || (isArray(value) && value.length === 0) || compValueIsEmptyArray; } -export function isComponentDataEmpty(component: Component, data: any, path: string): boolean { - const value = get(data, path); +export function isComponentDataEmpty(component: Component, data: any, path: string, valueCond?:any): boolean { + const value = isNil(valueCond) ? get(data, path): valueCond; if (isCheckboxComponent(component)) { return isValueEmpty(component, value) || value === false; } else if (isDataGridComponent(component) || isEditGridComponent(component) || isDataTableComponent(component) || hasChildComponents(component)) { diff --git a/src/utils/operators/IsEmptyValue.js b/src/utils/operators/IsEmptyValue.js index 70644fa6..248eefa3 100644 --- a/src/utils/operators/IsEmptyValue.js +++ b/src/utils/operators/IsEmptyValue.js @@ -1,5 +1,5 @@ +import { isComponentDataEmpty } from 'utils/formUtil'; import ConditionOperator from './ConditionOperator'; -import { isEmpty } from 'lodash'; export default class IsEmptyValue extends ConditionOperator { static get operatorKey() { @@ -14,15 +14,8 @@ export default class IsEmptyValue extends ConditionOperator { return false; } - execute({ value, instance, conditionComponentPath }) { - const isEmptyValue = isEmpty(value); - - if (instance && instance.root) { - const conditionTriggerComponent = instance.root.getComponent?.(conditionComponentPath); - return conditionTriggerComponent?.isEmpty ? conditionTriggerComponent.isEmpty() : isEmptyValue; - } - - return isEmptyValue; + execute({ value, conditionComponentPath, data, conditionComp}) { + return isComponentDataEmpty(conditionComp, data, conditionComponentPath, value); } getResult(options) {