diff --git a/app/client/src/components/editorComponents/ActionCreator.tsx b/app/client/src/components/editorComponents/ActionCreator.tsx index ea6d2f4e67..18ace24aef 100644 --- a/app/client/src/components/editorComponents/ActionCreator.tsx +++ b/app/client/src/components/editorComponents/ActionCreator.tsx @@ -215,8 +215,6 @@ const enumTypeGetter = ( type ActionCreatorProps = { value: string; - isValid: boolean; - validationMessage?: string; onValueChange: (newValue: string) => void; additionalAutoComplete?: Record>; }; @@ -263,8 +261,6 @@ type SelectorViewProps = ViewProps & { type KeyValueViewProps = ViewProps; type TextViewProps = ViewProps & { - isValid: boolean; - validationMessage?: string; additionalAutoComplete?: Record>; }; @@ -307,10 +303,8 @@ const views = { {props.label && } { if (event.target) { @@ -769,8 +763,6 @@ function renderField(props: { value: string; field: any; label?: string; - isValid: boolean; - validationMessage?: string; apiOptionTree: TreeDropdownOption[]; widgetOptionTree: TreeDropdownOption[]; queryOptionTree: TreeDropdownOption[]; @@ -944,8 +936,6 @@ function renderField(props: { props.onValueChange(finalValueToSet); }, value: props.value, - isValid: props.isValid, - validationMessage: props.validationMessage, additionalAutoComplete: props.additionalAutoComplete, }); break; @@ -961,8 +951,6 @@ function Fields(props: { value: string; fields: any; label?: string; - isValid: boolean; - validationMessage?: string; apiOptionTree: TreeDropdownOption[]; widgetOptionTree: TreeDropdownOption[]; queryOptionTree: TreeDropdownOption[]; @@ -995,7 +983,6 @@ function Fields(props: { apiOptionTree={props.apiOptionTree} depth={props.depth + 1} fields={field} - isValid={props.isValid} label={selectorField.label} maxDepth={props.maxDepth} modalDropdownList={props.modalDropdownList} @@ -1007,7 +994,6 @@ function Fields(props: { }} pageDropdownOptions={props.pageDropdownOptions} queryOptionTree={props.queryOptionTree} - validationMessage={props.validationMessage} value={selectorField.value} widgetOptionTree={props.widgetOptionTree} /> @@ -1039,7 +1025,6 @@ function Fields(props: { apiOptionTree={props.apiOptionTree} depth={props.depth + 1} fields={field} - isValid={props.isValid} key={index} label={selectorField.label} maxDepth={props.maxDepth} @@ -1052,7 +1037,6 @@ function Fields(props: { }} pageDropdownOptions={props.pageDropdownOptions} queryOptionTree={props.queryOptionTree} - validationMessage={props.validationMessage} value={selectorField.value} widgetOptionTree={props.widgetOptionTree} /> @@ -1212,13 +1196,11 @@ export function ActionCreator(props: ActionCreatorProps) { apiOptionTree={apiOptionTree} depth={1} fields={fields} - isValid={props.isValid} maxDepth={1} modalDropdownList={modalDropdownList} onValueChange={props.onValueChange} pageDropdownOptions={pageDropdownOptions} queryOptionTree={queryOptionTree} - validationMessage={props.validationMessage} value={props.value} widgetOptionTree={widgetOptionTree} /> diff --git a/app/client/src/components/editorComponents/CodeEditor/EvaluatedValuePopup.tsx b/app/client/src/components/editorComponents/CodeEditor/EvaluatedValuePopup.tsx index 05feedb6c5..7402f3107c 100644 --- a/app/client/src/components/editorComponents/CodeEditor/EvaluatedValuePopup.tsx +++ b/app/client/src/components/editorComponents/CodeEditor/EvaluatedValuePopup.tsx @@ -116,6 +116,7 @@ interface Props { useValidationMessage?: boolean; hideEvaluatedValue?: boolean; evaluationSubstitutionType?: EvaluationSubstitutionType; + jsError?: string; } interface PopoverContentProps { @@ -129,6 +130,7 @@ interface PopoverContentProps { onMouseLeave: () => void; hideEvaluatedValue?: boolean; preparedStatementViewer: boolean; + jsError?: string; } const PreparedStatementViewerContainer = styled.span` @@ -276,7 +278,9 @@ function PopoverContent(props: PopoverContentProps) { {props.hasError && ( - {props.useValidationMessage && props.error + {props.jsError && props.jsError.length + ? props.jsError + : props.useValidationMessage && props.error ? props.error : `This value does not evaluate to type "${props.expected}". Transform it using JS inside '{{ }}'`} @@ -339,6 +343,7 @@ function EvaluatedValuePopup(props: Props) { expected={props.expected} hasError={props.hasError} hideEvaluatedValue={props.hideEvaluatedValue} + jsError={props.jsError} onMouseEnter={() => { setContentHovered(true); }} diff --git a/app/client/src/components/editorComponents/CodeEditor/index.tsx b/app/client/src/components/editorComponents/CodeEditor/index.tsx index dfc5ad660d..8ab2c80f0c 100644 --- a/app/client/src/components/editorComponents/CodeEditor/index.tsx +++ b/app/client/src/components/editorComponents/CodeEditor/index.tsx @@ -14,7 +14,7 @@ import "codemirror/addon/mode/multiplex"; import "codemirror/addon/tern/tern.css"; import { getDataTreeForAutocomplete } from "selectors/dataTreeSelectors"; import EvaluatedValuePopup from "components/editorComponents/CodeEditor/EvaluatedValuePopup"; -import { WrappedFieldInputProps, WrappedFieldMetaProps } from "redux-form"; +import { WrappedFieldInputProps } from "redux-form"; import _ from "lodash"; import { DataTree, @@ -72,7 +72,6 @@ export type EditorStyleProps = { leftIcon?: React.ReactNode; rightIcon?: React.ReactNode; height?: string | number; - meta?: Partial; showLineNumbers?: boolean; className?: string; leftImage?: string; @@ -335,6 +334,38 @@ class CodeEditor extends Component { }); } + getPropertyValidation = ( + dataTree: DataTree, + dataTreePath?: string, + ): { + isValid: boolean; + validationMessage?: string; + jsErrorMessage?: string; + } => { + if (!dataTreePath) { + return { isValid: true, validationMessage: "", jsErrorMessage: "" }; + } + const isValidPath = dataTreePath.replace("evaluatedValues", "invalidProps"); + const validationMessagePath = dataTreePath.replace( + "evaluatedValues", + "validationMessages", + ); + const jsErrorMessagePath = dataTreePath.replace( + "evaluatedValues", + "jsErrorMessages", + ); + + const isValid = !_.get(dataTree, isValidPath, false); + const validationMessage = _.get( + dataTree, + validationMessagePath, + "", + ) as string; + const jsErrorMessage = _.get(dataTree, jsErrorMessagePath, "") as string; + + return { isValid, validationMessage, jsErrorMessage }; + }; + render() { const { border, @@ -351,18 +382,23 @@ class CodeEditor extends Component { hideEvaluatedValue, hoverInteraction, input, - meta, placeholder, showLightningMenu, size, theme, useValidationMessage, } = this.props; - const hasError = !!(meta && meta.error); + const { + isValid, + jsErrorMessage, + validationMessage, + } = this.getPropertyValidation(dynamicData, dataTreePath); + const hasError = !isValid || !!jsErrorMessage; let evaluated = evaluatedValue; if (dataTreePath) { evaluated = _.get(dynamicData, dataTreePath); } + const showEvaluatedValue = this.state.isFocused && ("evaluatedValue" in this.props || @@ -395,13 +431,14 @@ class CodeEditor extends Component { )} diff --git a/app/client/src/components/propertyControls/ActionSelectorControl.tsx b/app/client/src/components/propertyControls/ActionSelectorControl.tsx index 32e6cef1a4..fe01e9b992 100644 --- a/app/client/src/components/propertyControls/ActionSelectorControl.tsx +++ b/app/client/src/components/propertyControls/ActionSelectorControl.tsx @@ -15,9 +15,7 @@ class ActionSelectorControl extends BaseControl { return ( ); diff --git a/app/client/src/components/propertyControls/BaseControl.tsx b/app/client/src/components/propertyControls/BaseControl.tsx index 8647dbb6bd..a79f1f1d7a 100644 --- a/app/client/src/components/propertyControls/BaseControl.tsx +++ b/app/client/src/components/propertyControls/BaseControl.tsx @@ -34,13 +34,14 @@ export interface ControlProps extends ControlData, ControlFunctions { export interface ControlData extends Omit { propertyValue?: any; - isValid: boolean; errorMessage?: string; - expected: string; + expected?: string; evaluatedValue: any; - validationMessage?: string; widgetProperties: any; useValidationMessage?: boolean; + parentPropertyName: string; + parentPropertyValue: unknown; + additionalDynamicData: Record>; } export interface ControlFunctions { onPropertyChange?: (propertyName: string, propertyValue: string) => void; diff --git a/app/client/src/components/propertyControls/ChartDataControl.tsx b/app/client/src/components/propertyControls/ChartDataControl.tsx index 78fab615ea..e2bbc92ca4 100644 --- a/app/client/src/components/propertyControls/ChartDataControl.tsx +++ b/app/client/src/components/propertyControls/ChartDataControl.tsx @@ -1,5 +1,5 @@ import React from "react"; -import { get, has, isString } from "lodash"; +import { get, isString } from "lodash"; import BaseControl, { ControlProps } from "./BaseControl"; import { ControlWrapper, StyledPropertyPaneButton } from "./StyledControls"; import styled from "constants/DefaultTheme"; @@ -81,10 +81,7 @@ type RenderComponentProps = { index: string; item: ChartData; length: number; - validationMessage: { - data: string; - seriesName: string; - }; + dataTreePath: string; deleteOption: (index: string) => void; updateOption: (index: string, key: string, value: string) => void; evaluated: { @@ -96,13 +93,13 @@ type RenderComponentProps = { function DataControlComponent(props: RenderComponentProps) { const { + dataTreePath, deleteOption, evaluated, index, item, length, updateOption, - validationMessage, } = props; return ( @@ -121,6 +118,7 @@ function DataControlComponent(props: RenderComponentProps) { `} input={{ @@ -161,12 +160,6 @@ function DataControlComponent(props: RenderComponentProps) { updateOption(index, "data", value); }, }} - meta={{ - error: has(validationMessage, "data") - ? get(validationMessage, "data") - : "", - touched: true, - }} mode={EditorModes.JSON_WITH_BINDING} placeholder="" size={EditorSize.EXTENDED} @@ -186,7 +179,6 @@ class ChartDataControl extends BaseControl { : this.props.propertyValue; const dataLength = Object.keys(chartData).length; - const { validationMessage } = this.props; const evaluatedValue = this.props.evaluatedValue; const firstKey = Object.keys(chartData)[0] as string; @@ -201,6 +193,7 @@ class ChartDataControl extends BaseControl { return ( { length={1} theme={this.props.theme} updateOption={this.updateOption} - validationMessage={get(validationMessage, `${firstKey}`)} /> ); } @@ -221,6 +213,7 @@ class ChartDataControl extends BaseControl { return ( { length={dataLength} theme={this.props.theme} updateOption={this.updateOption} - validationMessage={get(validationMessage, `${key}`)} /> ); })} diff --git a/app/client/src/components/propertyControls/CodeEditorControl.tsx b/app/client/src/components/propertyControls/CodeEditorControl.tsx index d2fc9fa4ed..0109900832 100644 --- a/app/client/src/components/propertyControls/CodeEditorControl.tsx +++ b/app/client/src/components/propertyControls/CodeEditorControl.tsx @@ -14,23 +14,20 @@ class CodeEditorControl extends BaseControl { dataTreePath, evaluatedValue, expected, - isValid, propertyValue, useValidationMessage, - validationMessage, } = this.props; + const props: Partial = {}; + if (dataTreePath) props.dataTreePath = dataTreePath; if (evaluatedValue) props.evaluatedValue = evaluatedValue; if (expected) props.expected = expected; return ( diff --git a/app/client/src/components/propertyControls/ComputeTablePropertyControl.tsx b/app/client/src/components/propertyControls/ComputeTablePropertyControl.tsx index 1b504a6d71..86c50603c4 100644 --- a/app/client/src/components/propertyControls/ComputeTablePropertyControl.tsx +++ b/app/client/src/components/propertyControls/ComputeTablePropertyControl.tsx @@ -32,8 +32,6 @@ export function InputText(props: { label: string; value: string; onChange: (event: React.ChangeEvent | string) => void; - isValid: boolean; - errorMessage?: string; evaluatedValue?: any; expected?: string; placeholder?: string; @@ -44,10 +42,8 @@ export function InputText(props: { const { additionalDynamicData, dataTreePath, - errorMessage, evaluatedValue, expected, - isValid, onChange, placeholder, theme, @@ -64,10 +60,6 @@ export function InputText(props: { value: value, onChange: onChange, }} - meta={{ - error: isValid ? "" : errorMessage, - touched: true, - }} mode={EditorModes.TEXT_WITH_BINDING} placeholder={placeholder} promptMessage={ @@ -93,11 +85,9 @@ class ComputeTablePropertyControl extends BaseControl< dataTreePath, defaultValue, expected, - isValid, label, propertyValue, theme, - validationMessage, } = this.props; const tableId = this.props.widgetProperties.widgetName; const value = @@ -121,9 +111,7 @@ class ComputeTablePropertyControl extends BaseControl< currentRow, }} dataTreePath={dataTreePath} - errorMessage={validationMessage} expected={expected} - isValid={isValid} label={label} onChange={this.onTextChange} theme={theme} diff --git a/app/client/src/components/propertyControls/CustomFusionChartControl.tsx b/app/client/src/components/propertyControls/CustomFusionChartControl.tsx index b1e518262c..6b741a1330 100644 --- a/app/client/src/components/propertyControls/CustomFusionChartControl.tsx +++ b/app/client/src/components/propertyControls/CustomFusionChartControl.tsx @@ -4,20 +4,11 @@ import InputTextControl, { InputText } from "./InputTextControl"; class CustomFusionChartControl extends InputTextControl { render() { const expected = "{\n type: string,\n dataSource: Object\n}"; - const { - dataTreePath, - isValid, - label, - placeholderText, - propertyValue, - validationMessage, - } = this.props; + const { dataTreePath, label, placeholderText, propertyValue } = this.props; return ( | string) => void; - isValid: boolean; - errorMessage?: string; evaluatedValue?: any; expected?: string; placeholder?: string; @@ -26,11 +24,9 @@ export function InputText(props: { }) { const { dataTreePath, - errorMessage, evaluatedValue, expected, hideEvaluatedValue, - isValid, onChange, placeholder, value, @@ -48,10 +44,6 @@ export function InputText(props: { value: value, onChange: onChange, }} - meta={{ - error: isValid ? "" : errorMessage, - touched: true, - }} mode={EditorModes.TEXT_WITH_BINDING} placeholder={placeholder} size={EditorSize.EXTENDED} @@ -70,21 +62,17 @@ class InputTextControl extends BaseControl { defaultValue, expected, hideEvaluatedValue, - isValid, label, placeholderText, propertyValue, - validationMessage, } = this.props; return ( { - it("it tests autocomplete child enhancements", () => { - const mockProps = { - childAutoComplete: "child-autocomplet", - }; - - expect( - WIDGET_CONFIG_RESPONSE.config.LIST_WIDGET.enhancements.child.autocomplete( - mockProps, - ), - ).toBe(mockProps.childAutoComplete); - }); - - it("it tests hideEvaluatedValue child enhancements", () => { - expect( - WIDGET_CONFIG_RESPONSE.config.LIST_WIDGET.enhancements.child.hideEvaluatedValue(), - ).toBe(true); - }); - - it("it tests propertyUpdateHook child enhancements with undefined parent widget", () => { - const mockParentWidget = { - widgetId: undefined, - }; - - const result = WIDGET_CONFIG_RESPONSE.config.LIST_WIDGET.enhancements.child.propertyUpdateHook( - mockParentWidget, - "child-widget-name", - "text", - "value", - false, - ); - - expect(result).toStrictEqual([]); - }); - - it("it tests propertyUpdateHook child enhancements with undefined parent widget", () => { - const mockParentWidget = { - widgetId: undefined, - }; - - const result = WIDGET_CONFIG_RESPONSE.config.LIST_WIDGET.enhancements.child.propertyUpdateHook( - mockParentWidget, - "child-widget-name", - "text", - "value", - false, - ); - - expect(result).toStrictEqual([]); - }); - - it("it tests propertyUpdateHook child enhancements with defined parent widget", () => { - const mockParentWidget = { - widgetId: "parent-widget-id", - widgetName: "parent-widget-name", - }; - - const result = WIDGET_CONFIG_RESPONSE.config.LIST_WIDGET.enhancements.child.propertyUpdateHook( - mockParentWidget, - "child-widget-name", - "text", - "value", - false, - ); - - expect(result).toStrictEqual([ - { - widgetId: "parent-widget-id", - propertyPath: "template.child-widget-name.text", - propertyValue: "{{parent-widget-name.items.map((currentItem) => )}}", - isDynamicTrigger: false, - }, - ]); - }); -}); diff --git a/app/client/src/mockResponses/WidgetConfigResponse.tsx b/app/client/src/mockResponses/WidgetConfigResponse.tsx index 521ce177cf..87debb802b 100644 --- a/app/client/src/mockResponses/WidgetConfigResponse.tsx +++ b/app/client/src/mockResponses/WidgetConfigResponse.tsx @@ -664,7 +664,14 @@ const WidgetConfigResponse: WidgetConfigReducerState = { autocomplete: (parentProps: any) => { return parentProps.childAutoComplete; }, - hideEvaluatedValue: () => true, + updateDataTreePath: (parentProps: any, dataTreePath: string) => { + return `${ + parentProps.widgetName + }.evaluatedValues.template.${dataTreePath.replace( + "evaluatedValues.", + "", + )}`; + }, propertyUpdateHook: ( parentProps: any, widgetName: string, diff --git a/app/client/src/pages/Editor/PropertyPane/PropertyControl.tsx b/app/client/src/pages/Editor/PropertyPane/PropertyControl.tsx index 272655b851..14308dce2a 100644 --- a/app/client/src/pages/Editor/PropertyPane/PropertyControl.tsx +++ b/app/client/src/pages/Editor/PropertyPane/PropertyControl.tsx @@ -38,6 +38,7 @@ import { useChildWidgetEnhancementFns, useParentWithEnhancementFn, } from "sagas/WidgetEnhancementHelpers"; +import { ControlData } from "components/propertyControls/BaseControl"; type Props = PropertyPaneControlConfig & { panel: IPanelProps; @@ -51,12 +52,13 @@ const PropertyControl = memo((props: Props) => { widgetProperties.widgetId, ); - /** get all child enhancments functions */ + /** get all child enhancements functions */ const { autoCompleteEnhancementFn: childWidgetAutoCompleteEnhancementFn, customJSControlEnhancementFn: childWidgetCustomJSControlEnhancementFn, hideEvaluatedValueEnhancementFn: childWidgetHideEvaluatedValueEnhancementFn, - propertyPaneEnhancmentFn: childWidgetPropertyUpdateEnhancementFn, + propertyPaneEnhancementFn: childWidgetPropertyUpdateEnhancementFn, + updateDataTreePathFn: childWidgetDataTreePathEnhancementFn, } = useChildWidgetEnhancementFns(widgetProperties.widgetId); const toggleDynamicProperty = useCallback( @@ -242,41 +244,28 @@ const PropertyControl = memo((props: Props) => { return null; } - const getPropertyValidation = ( - propertyName: string, - ): { isValid: boolean; validationMessage?: string } => { - let isValid = true; - let validationMessage = ""; - if (widgetProperties) { - isValid = widgetProperties.invalidProps - ? !_.has(widgetProperties.invalidProps, propertyName) - : true; - const validationMsgPresent = - widgetProperties.validationMessages && - _.has(widgetProperties.validationMessages, propertyName); - validationMessage = validationMsgPresent - ? _.get(widgetProperties.validationMessages, propertyName) - : ""; - } - return { isValid, validationMessage }; - }; - const { label, propertyName } = props; if (widgetProperties) { const propertyValue = _.get(widgetProperties, propertyName); - const dataTreePath: any = `${widgetProperties.widgetName}.evaluatedValues.${propertyName}`; + // get the dataTreePath and apply enhancement if exists + // TODO (hetu) make the dataTreePath the actual path of the property + // and evaluatedValues should not be added by default + let dataTreePath: string | undefined = + props.dataTreePath || + `${widgetProperties.widgetName}.evaluatedValues.${propertyName}`; + if (childWidgetDataTreePathEnhancementFn) { + dataTreePath = childWidgetDataTreePathEnhancementFn(dataTreePath); + } + const evaluatedValue = _.get( widgetProperties, `evaluatedValues.${propertyName}`, ); - const { isValid, validationMessage } = getPropertyValidation(propertyName); const { additionalAutoComplete, ...rest } = props; - const config = { + const config: ControlData = { ...rest, - isValid, propertyValue, - validationMessage, dataTreePath, evaluatedValue, widgetProperties, @@ -288,11 +277,12 @@ const PropertyControl = memo((props: Props) => { additionalDynamicData: {}, }; if (isPathADynamicTrigger(widgetProperties, propertyName)) { - config.isValid = true; + // config.isValid = true; config.validationMessage = ""; delete config.dataTreePath; delete config.evaluatedValue; delete config.expected; + // config.jsErrorMessage = ""; } const isDynamic: boolean = isPathADynamicProperty( diff --git a/app/client/src/reducers/uiReducers/debuggerReducer.ts b/app/client/src/reducers/uiReducers/debuggerReducer.ts index ea8901f307..e282ae6532 100644 --- a/app/client/src/reducers/uiReducers/debuggerReducer.ts +++ b/app/client/src/reducers/uiReducers/debuggerReducer.ts @@ -49,7 +49,8 @@ const debuggerReducer = createReducer(initialState, { const entityId = action.payload.source.id; const id = - action.payload.logType === LOG_TYPE.WIDGET_PROPERTY_VALIDATION_ERROR + action.payload.logType === LOG_TYPE.WIDGET_PROPERTY_VALIDATION_ERROR || + action.payload.logType === LOG_TYPE.EVAL_ERROR ? `${entityId}-${action.payload.source.propertyPath}` : entityId; const previousState = get(state.errors, id, {}); @@ -73,7 +74,8 @@ const debuggerReducer = createReducer(initialState, { const entityId = action.payload.source.id; const isWidgetErrorLog = - action.payload.logType === LOG_TYPE.WIDGET_PROPERTY_VALIDATION_ERROR; + action.payload.logType === LOG_TYPE.WIDGET_PROPERTY_VALIDATION_ERROR || + action.payload.logType === LOG_TYPE.EVAL_ERROR; const id = isWidgetErrorLog ? `${entityId}-${action.payload.source.propertyPath}` : entityId; diff --git a/app/client/src/sagas/ActionExecutionSagas.ts b/app/client/src/sagas/ActionExecutionSagas.ts index 7414a7edc3..e99ec7cf07 100644 --- a/app/client/src/sagas/ActionExecutionSagas.ts +++ b/app/client/src/sagas/ActionExecutionSagas.ts @@ -933,6 +933,18 @@ function* executePageLoadAction(pageAction: PageAction) { message += `\nERROR: "${body}"`; } + AppsmithConsole.error({ + logType: LOG_TYPE.ACTION_EXECUTION_ERROR, + text: `Execution failed with status ${response.data.statusCode}`, + source: { + type: ENTITY_TYPE.ACTION, + name: pageAction.name, + id: pageAction.id, + }, + state: response.data?.request ?? null, + message: JSON.stringify(body), + }); + yield put( executeActionError({ actionId: pageAction.id, diff --git a/app/client/src/sagas/DebuggerSagas.ts b/app/client/src/sagas/DebuggerSagas.ts index e2c6c223e8..db2122159d 100644 --- a/app/client/src/sagas/DebuggerSagas.ts +++ b/app/client/src/sagas/DebuggerSagas.ts @@ -29,7 +29,12 @@ function* onWidgetUpdateSaga(payload: LogActionPayload) { const dataTree: DataTree = yield select(getDataTree); const widget = dataTree[payload.source.name]; - if (!isWidget(widget) || !widget.validationMessages) return; + if ( + !isWidget(widget) || + !widget.validationMessages || + !widget.jsErrorMessages + ) + return; // Ignore canvas widget updates if (widget.type === WidgetTypes.CANVAS_WIDGET) { @@ -43,14 +48,17 @@ function* onWidgetUpdateSaga(payload: LogActionPayload) { const validationMessages = widget.validationMessages; const validationMessage = validationMessages[propertyPath]; + const jsErrorMessages = widget.jsErrorMessages; + const jsErrorMessage = jsErrorMessages[propertyPath]; const errors = yield select(getDebuggerErrors); const errorId = `${source.id}-${propertyPath}`; const widgetErrorLog = errors[errorId]; if (!widgetErrorLog) return; const noError = isEmpty(validationMessage); + const noJsError = isEmpty(jsErrorMessage); - if (noError) { + if (noError && noJsError) { delete errors[errorId]; yield put({ @@ -123,6 +131,7 @@ function* debuggerLogSaga(action: ReduxAction) { yield call(onWidgetUpdateSaga, payload); yield put(debuggerLog(payload)); return; + case LOG_TYPE.EVAL_ERROR: case LOG_TYPE.WIDGET_PROPERTY_VALIDATION_ERROR: if (payload.source && payload.source.propertyPath) { if (payload.text) { diff --git a/app/client/src/sagas/EvaluationsSaga.ts b/app/client/src/sagas/EvaluationsSaga.ts index 73a9e368bf..4991934f22 100644 --- a/app/client/src/sagas/EvaluationsSaga.ts +++ b/app/client/src/sagas/EvaluationsSaga.ts @@ -106,6 +106,12 @@ const evalErrorHandler = (errors: EvalError[]) => { } case EvalErrorTypes.EVAL_ERROR: { log.debug(error); + AppsmithConsole.error({ + logType: LOG_TYPE.EVAL_ERROR, + text: `The value at ${error.context?.source.propertyPath} is invalid`, + message: error.message, + source: error.context?.source, + }); break; } case EvalErrorTypes.WIDGET_PROPERTY_VALIDATION_ERROR: { diff --git a/app/client/src/sagas/WidgetEnhancementHelpers.ts b/app/client/src/sagas/WidgetEnhancementHelpers.ts index 72cc7dbbca..fe27e2614c 100644 --- a/app/client/src/sagas/WidgetEnhancementHelpers.ts +++ b/app/client/src/sagas/WidgetEnhancementHelpers.ts @@ -31,6 +31,7 @@ export enum WidgetEnhancementType { CUSTOM_CONTROL = "child.customJSControl", AUTOCOMPLETE = "child.autocomplete", HIDE_EVALUATED_VALUE = "child.hideEvaluatedValue", + UPDATE_DATA_TREE_PATH = "child.updateDataTreePath", } export function getParentWithEnhancementFn( @@ -161,16 +162,18 @@ export function useChildWidgetEnhancementFn( } } -type EnhancmentFns = { - propertyPaneEnhancmentFn: any; +type EnhancementFns = { + updateDataTreePathFn: any; + propertyPaneEnhancementFn: any; autoCompleteEnhancementFn: any; customJSControlEnhancementFn: any; hideEvaluatedValueEnhancementFn: any; }; -export function useChildWidgetEnhancementFns(widgetId: string): EnhancmentFns { - const enhancmentFns = { - propertyPaneEnhancmentFn: undefined, +export function useChildWidgetEnhancementFns(widgetId: string): EnhancementFns { + const enhancementFns = { + updateDataTreePathFn: undefined, + propertyPaneEnhancementFn: undefined, autoCompleteEnhancementFn: undefined, customJSControlEnhancementFn: undefined, hideEvaluatedValueEnhancementFn: undefined, @@ -189,8 +192,12 @@ export function useChildWidgetEnhancementFns(widgetId: string): EnhancmentFns { if (parentWithEnhancementFn) { // Get the enhancement function based on the enhancementType // from the configs - const widgetEnhancmentFns = { - propertyPaneEnhancmentFn: getWidgetEnhancementFn( + const widgetEnhancementFns = { + updateDataTreePathFn: getWidgetEnhancementFn( + parentWithEnhancementFn.type, + WidgetEnhancementType.UPDATE_DATA_TREE_PATH, + ), + propertyPaneEnhancementFn: getWidgetEnhancementFn( parentWithEnhancementFn.type, WidgetEnhancementType.PROPERTY_UPDATE, ), @@ -208,16 +215,16 @@ export function useChildWidgetEnhancementFns(widgetId: string): EnhancmentFns { ), }; - Object.keys(widgetEnhancmentFns).map((key: string) => { - const enhancementFn = get(widgetEnhancmentFns, `${key}`); + Object.keys(widgetEnhancementFns).map((key: string) => { + const enhancementFn = get(widgetEnhancementFns, `${key}`); if (parentDataFromDataTree && enhancementFn) { - set(enhancmentFns, `${key}`, (...args: unknown[]) => + set(enhancementFns, `${key}`, (...args: unknown[]) => enhancementFn(parentDataFromDataTree, ...args), ); } }); } - return enhancmentFns; + return enhancementFns; } diff --git a/app/client/src/selectors/propertyPaneSelectors.tsx b/app/client/src/selectors/propertyPaneSelectors.tsx index 6522862a3b..1b7d821bd5 100644 --- a/app/client/src/selectors/propertyPaneSelectors.tsx +++ b/app/client/src/selectors/propertyPaneSelectors.tsx @@ -50,9 +50,14 @@ export const getWidgetPropsForPropertyPane = createSelector( } if (evaluatedWidget.invalidProps) { - const { invalidProps, validationMessages } = evaluatedWidget; + const { + invalidProps, + jsErrorMessages, + validationMessages, + } = evaluatedWidget; widgetProperties.invalidProps = invalidProps; widgetProperties.validationMessages = validationMessages; + widgetProperties.jsErrorMessages = jsErrorMessages; } } return widgetProperties; diff --git a/app/client/src/utils/DynamicBindingUtils.ts b/app/client/src/utils/DynamicBindingUtils.ts index a3db79aa8e..d8cf9ce8e8 100644 --- a/app/client/src/utils/DynamicBindingUtils.ts +++ b/app/client/src/utils/DynamicBindingUtils.ts @@ -158,6 +158,7 @@ export interface WidgetEvaluatedProps { invalidProps?: Record; validationMessages?: Record; evaluatedValues?: Record; + jsErrorMessages?: Record; } export interface EntityWithBindings { diff --git a/app/client/src/utils/PropertyControlFactory.tsx b/app/client/src/utils/PropertyControlFactory.tsx index 2a1a804ab4..7c182b65ae 100644 --- a/app/client/src/utils/PropertyControlFactory.tsx +++ b/app/client/src/utils/PropertyControlFactory.tsx @@ -29,6 +29,7 @@ class PropertyControlFactory { if (customEditor) controlBuilder = this.controlMap.get(customEditor); else controlBuilder = this.controlMap.get("CODE_EDITOR"); } + if (controlBuilder) { const controlProps: ControlProps = { ...controlData, diff --git a/app/client/src/workers/DataTreeEvaluator.ts b/app/client/src/workers/DataTreeEvaluator.ts index 2c0a3baf73..5ac99562ad 100644 --- a/app/client/src/workers/DataTreeEvaluator.ts +++ b/app/client/src/workers/DataTreeEvaluator.ts @@ -393,6 +393,11 @@ export default class DataTreeEvaluator { let evalPropertyValue; const requiresEval = isABindingPath && isDynamicValue(unEvalPropertyValue); + _.set( + currentTree, + `${entityName}.jsErrorMessages.${propertyPath}`, + "", + ); if (requiresEval) { const evaluationSubstitutionType = entity.bindingPaths[propertyPath] || @@ -403,6 +408,8 @@ export default class DataTreeEvaluator { currentTree, evaluationSubstitutionType, false, + undefined, + fullPropertyPath, ); } catch (e) { this.errors.push({ @@ -547,6 +554,7 @@ export default class DataTreeEvaluator { evaluationSubstitutionType: EvaluationSubstitutionType, returnTriggers: boolean, callBackData?: Array, + fullPropertyPath?: string, ) { // Get the {{binding}} bound values const { jsSnippets, stringSegments } = getDynamicBindings(dynamicBinding); @@ -555,6 +563,7 @@ export default class DataTreeEvaluator { data, jsSnippets[0], callBackData, + fullPropertyPath, ); return result.triggers; } @@ -566,6 +575,7 @@ export default class DataTreeEvaluator { data, jsSnippet, callBackData, + fullPropertyPath, ); return result.result; } else { @@ -592,17 +602,32 @@ export default class DataTreeEvaluator { data: DataTree, js: string, callbackData?: Array, + fullPropertyPath?: string, ): EvalResult { try { return evaluate(js, data, callbackData); } catch (e) { - this.errors.push({ - type: EvalErrorTypes.EVAL_ERROR, - message: e.message, - context: { - binding: js, - }, - }); + if (fullPropertyPath) { + const { entityName, propertyPath } = getEntityNameAndPropertyPath( + fullPropertyPath, + ); + _.set(data, `${entityName}.jsErrorMessages.${propertyPath}`, e.message); + const entity = data[entityName]; + if (isWidget(entity)) { + this.errors.push({ + type: EvalErrorTypes.EVAL_ERROR, + message: e.message, + context: { + source: { + id: entity.widgetId, + name: entity.widgetName, + type: ENTITY_TYPE.WIDGET, + propertyPath: propertyPath, + }, + }, + }); + } + } return { result: undefined, triggers: [] }; } } @@ -624,6 +649,7 @@ export default class DataTreeEvaluator { EvaluationSubstitutionType.TEMPLATE, true, undefined, + fullPropertyPath, ); valueToValidate = triggers; } @@ -642,7 +668,8 @@ export default class DataTreeEvaluator { : transformed; const safeEvaluatedValue = removeFunctions(evaluatedValue); _.set(widget, `evaluatedValues.${propertyPath}`, safeEvaluatedValue); - if (!isValid) { + const jsError = _.get(widget, `jsErrorMessages.${propertyPath}`); + if (!isValid && !jsError) { this.errors.push({ type: EvalErrorTypes.WIDGET_PROPERTY_VALIDATION_ERROR, message: message || "",