From a2b275bade3f94118bfab7138ba8b1b1cc395156 Mon Sep 17 00:00:00 2001 From: Hetu Nandu Date: Fri, 22 Nov 2019 13:12:39 +0000 Subject: [PATCH] Validation parse widget property --- app/client/package.json | 1 + .../propertyControls/InputTextControl.tsx | 5 +- app/client/src/constants/WidgetValidation.ts | 10 +- app/client/src/pages/Applications/index.tsx | 5 +- app/client/src/utils/DynamicBindingUtils.ts | 16 +-- app/client/src/utils/ValidationFactory.ts | 14 ++- app/client/src/utils/ValidationRegistry.ts | 27 +--- app/client/src/utils/Validators.ts | 116 ++++++++++++++++-- app/client/src/widgets/ButtonWidget.tsx | 11 ++ app/client/src/widgets/CheckboxWidget.tsx | 2 + app/client/src/widgets/DatePickerWidget.tsx | 15 +++ app/client/src/widgets/DropdownWidget.tsx | 12 ++ app/client/src/widgets/FilepickerWidget.tsx | 10 ++ app/client/src/widgets/ImageWidget.tsx | 9 ++ app/client/src/widgets/InputWidget.tsx | 20 +++ app/client/src/widgets/RadioGroupWidget.tsx | 9 ++ app/client/src/widgets/SpinnerWidget.tsx | 9 ++ app/client/src/widgets/TableWidget.tsx | 30 ++--- app/client/src/widgets/TextWidget.tsx | 1 + 19 files changed, 248 insertions(+), 74 deletions(-) diff --git a/app/client/package.json b/app/client/package.json index df4967a0f7..3485c4b181 100644 --- a/app/client/package.json +++ b/app/client/package.json @@ -44,6 +44,7 @@ "jsonpath-plus": "^1.0.0", "lint-staged": "^9.2.5", "lodash": "^4.17.11", + "moment": "^2.24.0", "moment-timezone": "^0.5.27", "monaco-editor": "^0.15.1", "monaco-editor-webpack-plugin": "^1.7.0", diff --git a/app/client/src/components/propertyControls/InputTextControl.tsx b/app/client/src/components/propertyControls/InputTextControl.tsx index e69d3bc420..d3c37d7727 100644 --- a/app/client/src/components/propertyControls/InputTextControl.tsx +++ b/app/client/src/components/propertyControls/InputTextControl.tsx @@ -36,10 +36,7 @@ class InputTextControl extends BaseControl { } onTextChange = (event: React.ChangeEvent) => { - let value: string | number = event.target.value; - if (this.isNumberType()) { - value = _.toNumber(value); - } + const value: string = event.target.value; this.updateProperty(this.props.propertyName, value); }; diff --git a/app/client/src/constants/WidgetValidation.ts b/app/client/src/constants/WidgetValidation.ts index 6a8fae6322..14a6d6496c 100644 --- a/app/client/src/constants/WidgetValidation.ts +++ b/app/client/src/constants/WidgetValidation.ts @@ -1,10 +1,18 @@ +// Always add a validator function in ./Validators for these types export const VALIDATION_TYPES = { TEXT: "TEXT", NUMBER: "NUMBER", BOOLEAN: "BOOLEAN", OBJECT: "OBJECT", + ARRAY: "ARRAY", TABLE_DATA: "TABLE_DATA", + DATE: "DATE", +}; + +export type ValidationResponse = { + isValid: boolean; + parsed: any; }; export type ValidationType = (typeof VALIDATION_TYPES)[keyof typeof VALIDATION_TYPES]; -export type Validator = (value: any) => boolean; +export type Validator = (value: any) => ValidationResponse; diff --git a/app/client/src/pages/Applications/index.tsx b/app/client/src/pages/Applications/index.tsx index 1e189fc9cb..344d4aac4b 100644 --- a/app/client/src/pages/Applications/index.tsx +++ b/app/client/src/pages/Applications/index.tsx @@ -124,5 +124,8 @@ const mapDispatchToProps = (dispatch: any) => ({ }); export default withRouter( - connect(mapStateToProps, mapDispatchToProps)(Applications), + connect( + mapStateToProps, + mapDispatchToProps, + )(Applications), ); diff --git a/app/client/src/utils/DynamicBindingUtils.ts b/app/client/src/utils/DynamicBindingUtils.ts index 44b4e62d6d..a218e95be7 100644 --- a/app/client/src/utils/DynamicBindingUtils.ts +++ b/app/client/src/utils/DynamicBindingUtils.ts @@ -78,7 +78,7 @@ export const getDynamicValue = ( export const enhanceWithDynamicValuesAndValidations = ( widget: WidgetProps, entities: DataTree, - safeValues: boolean, + replaceWithParsed: boolean, ): WidgetProps => { if (!widget) return widget; const properties = { ...widget }; @@ -86,19 +86,19 @@ export const enhanceWithDynamicValuesAndValidations = ( Object.keys(widget).forEach((property: string) => { let value = widget[property]; // Check for dynamic bindings - if (isDynamicValue(value)) { + if (widget.dynamicBindings && property in widget.dynamicBindings) { value = getDynamicValue(value, entities); } - const isValid = ValidationFactory.validateWidgetProperty( + // Pass it through validation and parse + const { isValid, parsed } = ValidationFactory.validateWidgetProperty( widget.type, property, value, ); - if (!isValid) { - if (safeValues) value = undefined; - invalidProps[property] = true; - } - if (safeValues) properties[property] = value; + // Store all invalid props + if (!isValid) invalidProps[property] = true; + // Replace if flag is turned on + if (replaceWithParsed) properties[property] = parsed; }); return { ...properties, invalidProps }; }; diff --git a/app/client/src/utils/ValidationFactory.ts b/app/client/src/utils/ValidationFactory.ts index 4abe801757..f616408510 100644 --- a/app/client/src/utils/ValidationFactory.ts +++ b/app/client/src/utils/ValidationFactory.ts @@ -1,6 +1,10 @@ import { WidgetType } from "constants/WidgetConstants"; import WidgetFactory from "./WidgetFactory"; -import { ValidationType, Validator } from "../constants/WidgetValidation"; +import { + ValidationResponse, + ValidationType, + Validator, +} from "../constants/WidgetValidation"; // TODO: need to be strict about what the key can be export type WidgetPropertyValidationType = Record; @@ -19,17 +23,17 @@ class ValidationFactory { widgetType: WidgetType, property: string, value: any, - ) { - let isValid = true; + ): ValidationResponse { const propertyValidationTypes = WidgetFactory.getWidgetPropertyValidationMap( widgetType, ); const validationType = propertyValidationTypes[property]; const validator = this.validationMap.get(validationType); if (validator) { - isValid = validator(value); + return validator(value); + } else { + return { isValid: true, parsed: value }; } - return isValid; } } diff --git a/app/client/src/utils/ValidationRegistry.ts b/app/client/src/utils/ValidationRegistry.ts index 1ef5426163..6877700173 100644 --- a/app/client/src/utils/ValidationRegistry.ts +++ b/app/client/src/utils/ValidationRegistry.ts @@ -4,30 +4,9 @@ import { VALIDATORS } from "./Validators"; class ValidationRegistry { static registerInternalValidators() { - ValidationFactory.registerValidator( - VALIDATION_TYPES.TEXT, - VALIDATORS[VALIDATION_TYPES.TEXT], - ); - - ValidationFactory.registerValidator( - VALIDATION_TYPES.NUMBER, - VALIDATORS[VALIDATION_TYPES.NUMBER], - ); - - ValidationFactory.registerValidator( - VALIDATION_TYPES.BOOLEAN, - VALIDATORS[VALIDATION_TYPES.BOOLEAN], - ); - - ValidationFactory.registerValidator( - VALIDATION_TYPES.OBJECT, - VALIDATORS[VALIDATION_TYPES.OBJECT], - ); - - ValidationFactory.registerValidator( - VALIDATION_TYPES.TABLE_DATA, - VALIDATORS[VALIDATION_TYPES.TABLE_DATA], - ); + Object.keys(VALIDATION_TYPES).forEach(type => { + ValidationFactory.registerValidator(type, VALIDATORS[type]); + }); } } diff --git a/app/client/src/utils/Validators.ts b/app/client/src/utils/Validators.ts index 0140d79e8a..a6d15d1830 100644 --- a/app/client/src/utils/Validators.ts +++ b/app/client/src/utils/Validators.ts @@ -1,25 +1,115 @@ import _ from "lodash"; import { VALIDATION_TYPES, + ValidationResponse, ValidationType, Validator, } from "../constants/WidgetValidation"; +import moment from "moment"; export const VALIDATORS: Record = { - [VALIDATION_TYPES.TEXT]: (value: any) => _.isString(value), - [VALIDATION_TYPES.NUMBER]: (value: any) => _.isNumber(value), - [VALIDATION_TYPES.BOOLEAN]: (value: any) => _.isBoolean(value), - [VALIDATION_TYPES.OBJECT]: (value: any) => _.isObject(value), - [VALIDATION_TYPES.TABLE_DATA]: (value: any) => { - try { - let data = value; - if (_.isString(data)) { - data = JSON.parse(data as string); + [VALIDATION_TYPES.TEXT]: (value: any): ValidationResponse => { + let parsed = value; + if (_.isUndefined(value) || _.isObject(value)) { + return { isValid: false, parsed: "" }; + } + let isValid = _.isString(value); + if (!isValid) { + try { + parsed = _.toString(value); + isValid = true; + } catch (e) { + console.error(`Error when parsing ${value} to string`); + console.error(e); + return { isValid: false, parsed: "" }; } - if (!Array.isArray(data)) return false; - return _.every(data, datum => _.isObject(datum)); - } catch { - return false; + } + return { isValid, parsed }; + }, + [VALIDATION_TYPES.NUMBER]: (value: any): ValidationResponse => { + let parsed = value; + if (_.isUndefined(value)) { + return { isValid: false, parsed: 0 }; + } + let isValid = _.isNumber(value); + if (!isValid) { + try { + parsed = _.toNumber(value); + isValid = true; + } catch (e) { + console.error(`Error when parsing ${value} to number`); + console.error(e); + return { isValid: false, parsed: 0 }; + } + } + return { isValid, parsed }; + }, + [VALIDATION_TYPES.BOOLEAN]: (value: any): ValidationResponse => { + let parsed = value; + if (_.isUndefined(value)) { + return { isValid: false, parsed: false }; + } + let isValid = _.isBoolean(value); + if (!isValid) { + try { + parsed = !!value; + isValid = true; + } catch (e) { + console.error(`Error when parsing ${value} to boolean`); + console.error(e); + return { isValid: false, parsed: false }; + } + } + return { isValid, parsed }; + }, + [VALIDATION_TYPES.OBJECT]: (value: any): ValidationResponse => { + let parsed = value; + if (_.isUndefined(value)) { + return { isValid: false, parsed: {} }; + } + let isValid = _.isObject(value); + if (!isValid) { + try { + parsed = JSON.parse(value); + isValid = true; + } catch (e) { + console.error(`Error when parsing ${value} to object`); + console.error(e); + return { isValid: false, parsed: {} }; + } + } + return { isValid, parsed }; + }, + [VALIDATION_TYPES.ARRAY]: (value: any): ValidationResponse => { + let parsed = value; + try { + if (_.isUndefined(value)) { + return { isValid: false, parsed: [] }; + } + if (_.isString(value)) { + parsed = JSON.parse(parsed as string); + } + if (!Array.isArray(parsed)) { + return { isValid: false, parsed: [] }; + } + return { isValid: true, parsed }; + } catch (e) { + console.error(e); + return { isValid: false, parsed: [] }; } }, + [VALIDATION_TYPES.TABLE_DATA]: (value: any): ValidationResponse => { + const { isValid, parsed } = VALIDATORS[VALIDATION_TYPES.ARRAY](value); + if (!isValid) { + return { isValid, parsed }; + } else if (!_.every(parsed, datum => _.isObject(datum))) { + return { isValid: false, parsed: [] }; + } + return { isValid, parsed }; + }, + [VALIDATION_TYPES.DATE]: (value: any): ValidationResponse => { + const isValid = moment(value).isValid(); + const parsed = isValid ? moment(value).toDate() : new Date(); + return { isValid, parsed }; + }, }; diff --git a/app/client/src/widgets/ButtonWidget.tsx b/app/client/src/widgets/ButtonWidget.tsx index caa523828e..95438fb800 100644 --- a/app/client/src/widgets/ButtonWidget.tsx +++ b/app/client/src/widgets/ButtonWidget.tsx @@ -3,6 +3,8 @@ import BaseWidget, { WidgetProps, WidgetState } from "./BaseWidget"; import { WidgetType } from "../constants/WidgetConstants"; import ButtonComponent from "../components/designSystems/blueprint/ButtonComponent"; import { ActionPayload } from "../constants/ActionConstants"; +import { WidgetPropertyValidationType } from "utils/ValidationFactory"; +import { VALIDATION_TYPES } from "constants/WidgetValidation"; class ButtonWidget extends BaseWidget { onButtonClickBound: (event: React.MouseEvent) => void; @@ -12,6 +14,15 @@ class ButtonWidget extends BaseWidget { this.onButtonClickBound = this.onButtonClick.bind(this); } + static getPropertyValidationMap(): WidgetPropertyValidationType { + return { + text: VALIDATION_TYPES.TEXT, + isDisabled: VALIDATION_TYPES.BOOLEAN, + isVisible: VALIDATION_TYPES.BOOLEAN, + buttonStyle: VALIDATION_TYPES.TEXT, + }; + } + onButtonClick() { super.executeAction(this.props.onClick); } diff --git a/app/client/src/widgets/CheckboxWidget.tsx b/app/client/src/widgets/CheckboxWidget.tsx index b16984c1d5..b56d928b0b 100644 --- a/app/client/src/widgets/CheckboxWidget.tsx +++ b/app/client/src/widgets/CheckboxWidget.tsx @@ -11,6 +11,8 @@ class CheckboxWidget extends BaseWidget { return { isDisabled: VALIDATION_TYPES.BOOLEAN, label: VALIDATION_TYPES.TEXT, + defaultCheckedState: VALIDATION_TYPES.BOOLEAN, + isChecked: VALIDATION_TYPES.BOOLEAN, }; } diff --git a/app/client/src/widgets/DatePickerWidget.tsx b/app/client/src/widgets/DatePickerWidget.tsx index b0d5b93983..028d829809 100644 --- a/app/client/src/widgets/DatePickerWidget.tsx +++ b/app/client/src/widgets/DatePickerWidget.tsx @@ -3,8 +3,23 @@ import BaseWidget, { WidgetProps, WidgetState } from "./BaseWidget"; import { WidgetType } from "../constants/WidgetConstants"; import { ActionPayload } from "../constants/ActionConstants"; import DatePickerComponent from "../components/designSystems/blueprint/DatePickerComponent"; +import { WidgetPropertyValidationType } from "utils/ValidationFactory"; +import { VALIDATION_TYPES } from "constants/WidgetValidation"; class DatePickerWidget extends BaseWidget { + static getPropertyValidationMap(): WidgetPropertyValidationType { + return { + defaultDate: VALIDATION_TYPES.DATE, + selectedDate: VALIDATION_TYPES.DATE, + timezone: VALIDATION_TYPES.TEXT, + enableTimePicker: VALIDATION_TYPES.BOOLEAN, + dateFormat: VALIDATION_TYPES.TEXT, + label: VALIDATION_TYPES.TEXT, + datePickerType: VALIDATION_TYPES.TEXT, + maxDate: VALIDATION_TYPES.DATE, + minDate: VALIDATION_TYPES.DATE, + }; + } getPageView() { return ( { + static getPropertyValidationMap(): WidgetPropertyValidationType { + return { + placeholderText: VALIDATION_TYPES.TEXT, + label: VALIDATION_TYPES.TEXT, + options: VALIDATION_TYPES.ARRAY, + selectionType: VALIDATION_TYPES.TEXT, + selectedIndex: VALIDATION_TYPES.NUMBER, + selectedIndexArr: VALIDATION_TYPES.ARRAY, + }; + } getPageView() { return ( { uppy: any; @@ -16,6 +18,14 @@ class FilePickerWidget extends BaseWidget { this.refreshUppy(props); } + static getPropertyValidationMap(): WidgetPropertyValidationType { + return { + label: VALIDATION_TYPES.TEXT, + maxNumFiles: VALIDATION_TYPES.NUMBER, + allowedFileTypes: VALIDATION_TYPES.ARRAY, + }; + } + refreshUppy = (props: FilePickerWidgetProps) => { this.uppy = Uppy({ id: this.props.widgetId, diff --git a/app/client/src/widgets/ImageWidget.tsx b/app/client/src/widgets/ImageWidget.tsx index 98643325bc..d327bec0f5 100644 --- a/app/client/src/widgets/ImageWidget.tsx +++ b/app/client/src/widgets/ImageWidget.tsx @@ -2,8 +2,17 @@ import * as React from "react"; import BaseWidget, { WidgetProps, WidgetState } from "./BaseWidget"; import { WidgetType } from "../constants/WidgetConstants"; import ImageComponent from "../components/designSystems/appsmith/ImageComponent"; +import { WidgetPropertyValidationType } from "utils/ValidationFactory"; +import { VALIDATION_TYPES } from "constants/WidgetValidation"; class ImageWidget extends BaseWidget { + static getPropertyValidationMap(): WidgetPropertyValidationType { + return { + image: VALIDATION_TYPES.TEXT, + imageShape: VALIDATION_TYPES.TEXT, + defaultImage: VALIDATION_TYPES.TEXT, + }; + } getPageView() { return ( { + static getPropertyValidationMap(): WidgetPropertyValidationType { + return { + inputType: VALIDATION_TYPES.TEXT, + defaultText: VALIDATION_TYPES.TEXT, + isDisabled: VALIDATION_TYPES.BOOLEAN, + text: VALIDATION_TYPES.TEXT, + regex: VALIDATION_TYPES.TEXT, + errorMessage: VALIDATION_TYPES.TEXT, + placeholderText: VALIDATION_TYPES.TEXT, + maxChars: VALIDATION_TYPES.NUMBER, + minNum: VALIDATION_TYPES.NUMBER, + maxNum: VALIDATION_TYPES.NUMBER, + label: VALIDATION_TYPES.TEXT, + inputValidators: VALIDATION_TYPES.ARRAY, + focusIndex: VALIDATION_TYPES.NUMBER, + isAutoFocusEnabled: VALIDATION_TYPES.BOOLEAN, + }; + } regex = new RegExp(""); componentDidMount() { diff --git a/app/client/src/widgets/RadioGroupWidget.tsx b/app/client/src/widgets/RadioGroupWidget.tsx index 34655234bf..b7d9978b5b 100644 --- a/app/client/src/widgets/RadioGroupWidget.tsx +++ b/app/client/src/widgets/RadioGroupWidget.tsx @@ -3,8 +3,17 @@ import BaseWidget, { WidgetProps, WidgetState } from "./BaseWidget"; import { WidgetType } from "../constants/WidgetConstants"; import RadioGroupComponent from "../components/designSystems/blueprint/RadioGroupComponent"; import { ActionPayload } from "../constants/ActionConstants"; +import { WidgetPropertyValidationType } from "utils/ValidationFactory"; +import { VALIDATION_TYPES } from "constants/WidgetValidation"; class RadioGroupWidget extends BaseWidget { + static getPropertyValidationMap(): WidgetPropertyValidationType { + return { + label: VALIDATION_TYPES.TEXT, + options: VALIDATION_TYPES.ARRAY, + selectedOptionValue: VALIDATION_TYPES.TEXT, + }; + } getPageView() { return ( { + static getPropertyValidationMap(): WidgetPropertyValidationType { + return { + size: VALIDATION_TYPES.NUMBER, + value: VALIDATION_TYPES.NUMBER, + ellipsize: VALIDATION_TYPES.BOOLEAN, + }; + } getPageView() { return ( { - if (!tableData) return []; - if (_.isString(tableData)) { - return JSON.parse(tableData as string); - } else { - return tableData; - } -}; - class TableWidget extends BaseWidget { static getPropertyValidationMap(): WidgetPropertyValidationType { return { tableData: VALIDATION_TYPES.TABLE_DATA, + nextPageKey: VALIDATION_TYPES.TEXT, + prevPageKey: VALIDATION_TYPES.TEXT, + label: VALIDATION_TYPES.TEXT, + selectedRow: VALIDATION_TYPES.OBJECT, }; } getPageView() { const { tableData } = this.props; - const data = getTableArrayData(tableData); - const columns = constructColumns(data); + const columns = constructColumns(tableData); return ( {({ width, height }: { width: number; height: number }) => ( @@ -58,7 +50,7 @@ class TableWidget extends BaseWidget { width={width} height={height} columns={columns} - data={data} + data={tableData} maxHeight={height} isLoading={this.props.isLoading} selectedRowIndex={ @@ -76,10 +68,12 @@ class TableWidget extends BaseWidget { } componentDidUpdate(prevProps: TableWidgetProps) { super.componentDidUpdate(prevProps); - const newData = getTableArrayData(this.props.tableData); - if (prevProps.tableData !== this.props.tableData && prevProps.selectedRow) { + if ( + !_.isEqual(prevProps.tableData, this.props.tableData) && + prevProps.selectedRow + ) { this.updateSelectedRowProperty( - newData[prevProps.selectedRow.rowIndex], + this.props.tableData[prevProps.selectedRow.rowIndex], prevProps.selectedRow.rowIndex, ); } @@ -113,7 +107,7 @@ export interface TableWidgetProps extends WidgetProps { nextPageKey?: string; prevPageKey?: string; label: string; - tableData?: string | object[]; + tableData: object[]; recordActions?: TableAction[]; onPageChange?: ActionPayload[]; onRowSelected?: ActionPayload[]; diff --git a/app/client/src/widgets/TextWidget.tsx b/app/client/src/widgets/TextWidget.tsx index b41a97abd6..b1f45f8299 100644 --- a/app/client/src/widgets/TextWidget.tsx +++ b/app/client/src/widgets/TextWidget.tsx @@ -9,6 +9,7 @@ class TextWidget extends BaseWidget { static getPropertyValidationMap(): WidgetPropertyValidationType { return { text: VALIDATION_TYPES.TEXT, + textStyle: VALIDATION_TYPES.TEXT, }; }