diff --git a/app/client/src/widgets/wds/WDSComboBoxWidget/config/propertyPaneConfig/validations/optionsCustomValidation.ts b/app/client/src/widgets/wds/WDSComboBoxWidget/config/propertyPaneConfig/validations/optionsCustomValidation.ts index 830674837392..356d2155a85d 100644 --- a/app/client/src/widgets/wds/WDSComboBoxWidget/config/propertyPaneConfig/validations/optionsCustomValidation.ts +++ b/app/client/src/widgets/wds/WDSComboBoxWidget/config/propertyPaneConfig/validations/optionsCustomValidation.ts @@ -77,6 +77,22 @@ export function optionsCustomValidation( }); } + const isValidKeys = options.every((option) => { + return ( + _.isPlainObject(option) && + _.has(option, "label") && + _.has(option, "value") + ); + }); + + if (!isValidKeys) { + return createErrorValidationResponse(options, { + name: "ValidationError", + message: + 'This value does not evaluate to type Array<{ "label": "string", "value": "string" | number }>', + }); + } + for (let i = 0; i < options.length; i++) { const option = options[i]; diff --git a/app/client/src/widgets/wds/WDSComboBoxWidget/widget/index.tsx b/app/client/src/widgets/wds/WDSComboBoxWidget/widget/index.tsx index 116fe24ee6ac..e75189a06f58 100644 --- a/app/client/src/widgets/wds/WDSComboBoxWidget/widget/index.tsx +++ b/app/client/src/widgets/wds/WDSComboBoxWidget/widget/index.tsx @@ -152,7 +152,11 @@ class WDSComboBoxWidget extends BaseWidget< id: option["value"] as string, })); - return items; + const isValidItems = items.every( + (item) => item.label !== undefined && item.id !== undefined, + ); + + return isValidItems ? items : []; } return []; diff --git a/app/client/src/widgets/wds/WDSSelectWidget/config/propertyPaneConfig/contentConfig.ts b/app/client/src/widgets/wds/WDSSelectWidget/config/propertyPaneConfig/contentConfig.ts index ef68eec86d75..9466f1c8a93d 100644 --- a/app/client/src/widgets/wds/WDSSelectWidget/config/propertyPaneConfig/contentConfig.ts +++ b/app/client/src/widgets/wds/WDSSelectWidget/config/propertyPaneConfig/contentConfig.ts @@ -1,237 +1,16 @@ -import { - ValidationTypes, - type ValidationResponse, -} from "constants/WidgetValidation"; -import { get, isPlainObject, uniq, type LoDashStatic } from "lodash"; -import { AutocompleteDataType } from "utils/autocomplete/AutocompleteDataType"; - +import { ValidationTypes } from "constants/WidgetValidation"; import { EvaluationSubstitutionType } from "entities/DataTree/dataTreeFactory"; -import { EVAL_VALUE_PATH } from "../../../../../utils/DynamicBindingUtils"; -import type { PropertyUpdates } from "../../../../../WidgetProvider/constants"; -import type { WidgetProps } from "../../../../BaseWidget"; +import { AutocompleteDataType } from "utils/autocomplete/AutocompleteDataType"; import type { WDSSelectWidgetProps } from "../../widget/types"; import { defaultOptionValidation, optionsCustomValidation, } from "./validations"; +import type { WidgetProps } from "../../../../BaseWidget"; +import type { PropertyUpdates } from "../../../../../WidgetProvider/constants"; type WidgetTypeValue = "SELECT" | "COMBOBOX"; -interface ValidationErrorMessage { - name: string; - message: string; -} - -export const getOptionLabelValueExpressionPrefix = (widget: WidgetProps) => - `{{${widget.widgetName}.sourceData.map((item) => (`; - -export const optionLabelValueExpressionSuffix = `))}}`; - -export function getLabelValueKeyOptions( - widget: WidgetProps, -): Record[] { - // UTILS - const isTrueObject = (item: unknown): item is Record => { - return Object.prototype.toString.call(item) === "[object Object]"; - }; - - const sourceData = get(widget, `${EVAL_VALUE_PATH}.options`); - const widgetOptions = get(widget, "options"); - const options = sourceData || widgetOptions; - - // Is Form mode, otherwise it is JS mode - if (Array.isArray(widgetOptions)) { - return options.map((option: Record | string) => { - if (isTrueObject(option)) { - return { - label: option[widget.optionLabel], - value: option[widget.optionValue], - }; - } - - return []; - }); - } - - if (Array.isArray(options)) { - const x = uniq( - options.reduce((keys, obj) => { - if (isPlainObject(obj)) { - Object.keys(obj).forEach((d) => keys.push(d)); - } - - return keys; - }, []), - ).map((d: unknown) => ({ - label: d, - value: d, - })); - - return x; - } else { - return []; - } -} - -export function labelKeyValidation( - value: unknown, - widgetProps: WDSSelectWidgetProps, - _: LoDashStatic, -) { - // UTILS - const hasDuplicates = (array: unknown[]): boolean => { - const set = new Set(array); - - return set.size !== array.length; - }; - - const createErrorValidationResponse = ( - value: unknown, - message: ValidationErrorMessage, - ): ValidationResponse => ({ - isValid: false, - parsed: value, - messages: [message], - }); - - const createSuccessValidationResponse = ( - value: unknown, - ): ValidationResponse => ({ - isValid: true, - parsed: value, - }); - - if (value === "" || _.isNil(value)) { - return createErrorValidationResponse(value, { - name: "ValidationError", - message: `value does not evaluate to type: string | Array`, - }); - } - - if (Array.isArray(widgetProps.options)) { - const values = _.map(widgetProps.options, (option) => { - return option[widgetProps.optionLabel]; - }).filter((d) => d); - - if (values.length && hasDuplicates(values)) { - return createErrorValidationResponse(value, { - name: "ValidationError", - message: "Duplicate values found, value must be unique", - }); - } - } - - if (_.isString(value)) { - const keys = _.map(widgetProps.options, _.keys).flat(); - - if (!keys.includes(value)) { - return createErrorValidationResponse(value, { - name: "ValidationError", - message: "value key should be present in the options", - }); - } - - return createSuccessValidationResponse(value); - } else if (_.isArray(value)) { - const errorIndex = value.findIndex((d) => !_.isString(d)); - - if (errorIndex === -1) { - return createSuccessValidationResponse(value); - } - - return createErrorValidationResponse(value, { - name: "ValidationError", - message: `Invalid entry at index: ${errorIndex}. This value does not evaluate to type: string`, - }); - } else { - return createErrorValidationResponse(value, { - name: "ValidationError", - message: `value does not evaluate to type: string | Array`, - }); - } -} - -export function getLabelValueAdditionalAutocompleteData(props: WidgetProps) { - const keys = getLabelValueKeyOptions(props); - - return { - item: keys - .map((d) => d.label) - .reduce((prev: Record, curr: unknown) => { - prev[curr as string] = ""; - - return prev; - }, {}), - }; -} - -export function valueKeyValidation( - value: unknown, - widgetProps: WDSSelectWidgetProps, - _: LoDashStatic, -) { - // UTILS - const isTrueObject = (item: unknown): item is Record => { - return Object.prototype.toString.call(item) === "[object Object]"; - }; - - const hasDuplicates = (array: unknown[]): boolean => { - const set = new Set(array); - - return set.size !== array.length; - }; - - const createErrorValidationResponse = ( - value: unknown, - message: ValidationErrorMessage, - ): ValidationResponse => ({ - isValid: false, - parsed: value, - messages: [message], - }); - - const createSuccessValidationResponse = ( - value: unknown, - ): ValidationResponse => ({ - isValid: true, - parsed: value, - }); - - if (value === "" || _.isNil(value) || !_.isString(value)) { - return createErrorValidationResponse(value, { - name: "ValidationError", - message: - "value does not evaluate to type: string | Array", - }); - } - - if (!_.flatMap(widgetProps.options, _.keys).includes(value)) { - return createErrorValidationResponse(value, { - name: "ValidationError", - message: "value key should be present in the options", - }); - } - - if (!isTrueObject(widgetProps.options)) { - return createSuccessValidationResponse(value); - } - - const values = _.map(widgetProps.options, (option) => { - if (isTrueObject(option)) { - return option[widgetProps.optionValue]; - } - }).filter((d) => d); - - if (values.length && hasDuplicates(values)) { - return createErrorValidationResponse(value, { - name: "ValidationError", - message: "Duplicate values found, value must be unique", - }); - } - - return createSuccessValidationResponse(value); -} - export const propertyPaneContentConfig = [ { sectionName: "Data", @@ -306,82 +85,6 @@ export const propertyPaneContentConfig = [ }, evaluationSubstitutionType: EvaluationSubstitutionType.SMART_SUBSTITUTE, }, - { - helpText: "Choose or set a field from source data as the display label", - propertyName: "optionLabel", - label: "Label key", - controlType: "DROP_DOWN", - customJSControl: "WRAPPED_CODE_EDITOR", - controlConfig: { - wrapperCode: { - prefix: getOptionLabelValueExpressionPrefix, - suffix: optionLabelValueExpressionSuffix, - }, - }, - placeholderText: "", - isBindProperty: true, - isTriggerProperty: false, - isJSConvertible: true, - evaluatedDependencies: ["options"], - options: getLabelValueKeyOptions, - alwaysShowSelected: true, - validation: { - type: ValidationTypes.FUNCTION, - params: { - fn: labelKeyValidation, - expected: { - type: "String or Array", - example: `color | ["blue", "green"]`, - autocompleteDataType: AutocompleteDataType.STRING, - }, - }, - }, - dependencies: ["options", "dynamicPropertyPathList"], - additionalAutoComplete: getLabelValueAdditionalAutocompleteData, - hidden: (props: WDSSelectWidgetProps) => { - return !(props.dynamicPropertyPathList || []).some( - ({ key }) => key === "options", - ); - }, - }, - { - helpText: "Choose or set a field from source data as the value", - propertyName: "optionValue", - label: "Value key", - controlType: "DROP_DOWN", - customJSControl: "WRAPPED_CODE_EDITOR", - controlConfig: { - wrapperCode: { - prefix: getOptionLabelValueExpressionPrefix, - suffix: optionLabelValueExpressionSuffix, - }, - }, - placeholderText: "", - isBindProperty: true, - isTriggerProperty: false, - isJSConvertible: true, - evaluatedDependencies: ["options"], - options: getLabelValueKeyOptions, - alwaysShowSelected: true, - validation: { - type: ValidationTypes.FUNCTION, - params: { - fn: valueKeyValidation, - expected: { - type: "String or Array", - example: `color | [1, "orange"]`, - autocompleteDataType: AutocompleteDataType.STRING, - }, - }, - }, - dependencies: ["options", "dynamicPropertyPathList"], - additionalAutoComplete: getLabelValueAdditionalAutocompleteData, - hidden: (props: WDSSelectWidgetProps) => { - return !(props.dynamicPropertyPathList || []).some( - ({ key }) => key === "options", - ); - }, - }, { helpText: "Sets a default selected option", propertyName: "defaultOptionValue", diff --git a/app/client/src/widgets/wds/WDSSelectWidget/config/propertyPaneConfig/validations/defaultOptionValidation.ts b/app/client/src/widgets/wds/WDSSelectWidget/config/propertyPaneConfig/validations/defaultOptionValidation.ts index 4f4458fe9604..463f49d9cc4a 100644 --- a/app/client/src/widgets/wds/WDSSelectWidget/config/propertyPaneConfig/validations/defaultOptionValidation.ts +++ b/app/client/src/widgets/wds/WDSSelectWidget/config/propertyPaneConfig/validations/defaultOptionValidation.ts @@ -48,7 +48,7 @@ export function defaultOptionValidation( if (Array.isArray(options)) { const values = _.map(widgetProps.options, (option) => { if (isTrueObject(option)) { - return option[widgetProps.optionValue]; + return option["value"]; } }); diff --git a/app/client/src/widgets/wds/WDSSelectWidget/config/propertyPaneConfig/validations/optionsCustomValidation.ts b/app/client/src/widgets/wds/WDSSelectWidget/config/propertyPaneConfig/validations/optionsCustomValidation.ts index 5b526639e758..356d2155a85d 100644 --- a/app/client/src/widgets/wds/WDSSelectWidget/config/propertyPaneConfig/validations/optionsCustomValidation.ts +++ b/app/client/src/widgets/wds/WDSSelectWidget/config/propertyPaneConfig/validations/optionsCustomValidation.ts @@ -38,8 +38,23 @@ export function optionsCustomValidation( const hasDuplicates = (array: unknown[]): boolean => new Set(array).size !== array.length; - // Is Form mode, otherwise it is JS mode if (Array.isArray(options)) { + const isValidKeys = options.every((option) => { + return ( + _.isPlainObject(option) && + _.has(option, "label") && + _.has(option, "value") + ); + }); + + if (!isValidKeys) { + return createErrorValidationResponse(options, { + name: "ValidationError", + message: + 'This value does not evaluate to type Array<{ "label": "string", "value": "string" | number }>', + }); + } + return createSuccessValidationResponse(options); } @@ -62,6 +77,22 @@ export function optionsCustomValidation( }); } + const isValidKeys = options.every((option) => { + return ( + _.isPlainObject(option) && + _.has(option, "label") && + _.has(option, "value") + ); + }); + + if (!isValidKeys) { + return createErrorValidationResponse(options, { + name: "ValidationError", + message: + 'This value does not evaluate to type Array<{ "label": "string", "value": "string" | number }>', + }); + } + for (let i = 0; i < options.length; i++) { const option = options[i]; diff --git a/app/client/src/widgets/wds/WDSSelectWidget/widget/index.tsx b/app/client/src/widgets/wds/WDSSelectWidget/widget/index.tsx index 7d7048f1092e..a5e3d7e0d2cd 100644 --- a/app/client/src/widgets/wds/WDSSelectWidget/widget/index.tsx +++ b/app/client/src/widgets/wds/WDSSelectWidget/widget/index.tsx @@ -137,10 +137,16 @@ class WDSSelectWidget extends BaseWidget { options: WDSSelectWidgetProps["options"], ): SelectItem[] => { if (Array.isArray(options)) { - return options.map((option) => ({ + const items = options.map((option) => ({ label: option[this.props.optionLabel || "label"] as string, id: option[this.props.optionValue || "value"] as string, })); + + const isValidItems = items.every( + (item) => item.label !== undefined && item.id !== undefined, + ); + + return isValidItems ? items : []; } return [];