Show JS eval errors in evaluated value pane and debugger (#4463)
Co-authored-by: jsartisan <pawankumar2901@gmail.com> Co-authored-by: hetunandu <hetu@appsmith.com>
This commit is contained in:
parent
caf7f3678c
commit
4825ce2a2a
|
|
@ -215,8 +215,6 @@ const enumTypeGetter = (
|
|||
|
||||
type ActionCreatorProps = {
|
||||
value: string;
|
||||
isValid: boolean;
|
||||
validationMessage?: string;
|
||||
onValueChange: (newValue: string) => void;
|
||||
additionalAutoComplete?: Record<string, Record<string, unknown>>;
|
||||
};
|
||||
|
|
@ -263,8 +261,6 @@ type SelectorViewProps = ViewProps & {
|
|||
|
||||
type KeyValueViewProps = ViewProps;
|
||||
type TextViewProps = ViewProps & {
|
||||
isValid: boolean;
|
||||
validationMessage?: string;
|
||||
additionalAutoComplete?: Record<string, Record<string, unknown>>;
|
||||
};
|
||||
|
||||
|
|
@ -307,10 +303,8 @@ const views = {
|
|||
{props.label && <label>{props.label}</label>}
|
||||
<InputText
|
||||
additionalAutocomplete={props.additionalAutoComplete}
|
||||
errorMessage={props.validationMessage}
|
||||
evaluatedValue={props.get(props.value, false) as string}
|
||||
expected={"string"}
|
||||
isValid={props.isValid}
|
||||
label={props.label}
|
||||
onChange={(event: any) => {
|
||||
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}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -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 && (
|
||||
<ErrorText>
|
||||
<span className="t--evaluatedPopup-error">
|
||||
{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 '{{ }}'`}
|
||||
</span>
|
||||
|
|
@ -339,6 +343,7 @@ function EvaluatedValuePopup(props: Props) {
|
|||
expected={props.expected}
|
||||
hasError={props.hasError}
|
||||
hideEvaluatedValue={props.hideEvaluatedValue}
|
||||
jsError={props.jsError}
|
||||
onMouseEnter={() => {
|
||||
setContentHovered(true);
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -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<WrappedFieldMetaProps>;
|
||||
showLineNumbers?: boolean;
|
||||
className?: string;
|
||||
leftImage?: string;
|
||||
|
|
@ -335,6 +334,38 @@ class CodeEditor extends Component<Props, State> {
|
|||
});
|
||||
}
|
||||
|
||||
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<Props, State> {
|
|||
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<Props, State> {
|
|||
</Suspense>
|
||||
)}
|
||||
<EvaluatedValuePopup
|
||||
error={meta?.error}
|
||||
error={validationMessage}
|
||||
evaluatedValue={evaluated}
|
||||
evaluationSubstitutionType={evaluationSubstitutionType}
|
||||
expected={expected}
|
||||
hasError={hasError}
|
||||
hideEvaluatedValue={hideEvaluatedValue}
|
||||
isOpen={showEvaluatedValue}
|
||||
jsError={jsErrorMessage}
|
||||
theme={theme || EditorTheme.LIGHT}
|
||||
useValidationMessage={useValidationMessage}
|
||||
>
|
||||
|
|
|
|||
|
|
@ -15,9 +15,7 @@ class ActionSelectorControl extends BaseControl<ControlProps> {
|
|||
return (
|
||||
<ActionCreator
|
||||
additionalAutoComplete={this.props.additionalAutoComplete}
|
||||
isValid={this.props.isValid}
|
||||
onValueChange={this.handleValueUpdate}
|
||||
validationMessage={this.props.errorMessage}
|
||||
value={propertyValue}
|
||||
/>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -34,13 +34,14 @@ export interface ControlProps extends ControlData, ControlFunctions {
|
|||
export interface ControlData
|
||||
extends Omit<PropertyPaneControlConfig, "additionalAutoComplete"> {
|
||||
propertyValue?: any;
|
||||
isValid: boolean;
|
||||
errorMessage?: string;
|
||||
expected: string;
|
||||
expected?: string;
|
||||
evaluatedValue: any;
|
||||
validationMessage?: string;
|
||||
widgetProperties: any;
|
||||
useValidationMessage?: boolean;
|
||||
parentPropertyName: string;
|
||||
parentPropertyValue: unknown;
|
||||
additionalDynamicData: Record<string, Record<string, unknown>>;
|
||||
}
|
||||
export interface ControlFunctions {
|
||||
onPropertyChange?: (propertyName: string, propertyValue: string) => void;
|
||||
|
|
|
|||
|
|
@ -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) {
|
|||
</ActionHolder>
|
||||
<StyledOptionControlWrapper orientation={"HORIZONTAL"}>
|
||||
<CodeEditor
|
||||
dataTreePath={`${dataTreePath}.seriesName`}
|
||||
evaluatedValue={evaluated?.seriesName}
|
||||
expected={"string"}
|
||||
input={{
|
||||
|
|
@ -147,6 +145,7 @@ function DataControlComponent(props: RenderComponentProps) {
|
|||
className={"t--property-control-chart-series-data-control"}
|
||||
>
|
||||
<CodeEditor
|
||||
dataTreePath={`${dataTreePath}.data`}
|
||||
evaluatedValue={evaluated?.data}
|
||||
expected={`Array<x:string, y:number>`}
|
||||
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<ControlProps> {
|
|||
: 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<ControlProps> {
|
|||
|
||||
return (
|
||||
<DataControlComponent
|
||||
dataTreePath={`${this.props.dataTreePath}.${firstKey}`}
|
||||
deleteOption={this.deleteOption}
|
||||
evaluated={get(evaluatedValue, `${firstKey}`)}
|
||||
index={firstKey}
|
||||
|
|
@ -208,7 +201,6 @@ class ChartDataControl extends BaseControl<ControlProps> {
|
|||
length={1}
|
||||
theme={this.props.theme}
|
||||
updateOption={this.updateOption}
|
||||
validationMessage={get(validationMessage, `${firstKey}`)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
@ -221,6 +213,7 @@ class ChartDataControl extends BaseControl<ControlProps> {
|
|||
|
||||
return (
|
||||
<DataControlComponent
|
||||
dataTreePath={`${this.props.dataTreePath}.${key}`}
|
||||
deleteOption={this.deleteOption}
|
||||
evaluated={get(evaluatedValue, `${key}`)}
|
||||
index={key}
|
||||
|
|
@ -229,7 +222,6 @@ class ChartDataControl extends BaseControl<ControlProps> {
|
|||
length={dataLength}
|
||||
theme={this.props.theme}
|
||||
updateOption={this.updateOption}
|
||||
validationMessage={get(validationMessage, `${key}`)}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
|
|
|||
|
|
@ -14,23 +14,20 @@ class CodeEditorControl extends BaseControl<ControlProps> {
|
|||
dataTreePath,
|
||||
evaluatedValue,
|
||||
expected,
|
||||
isValid,
|
||||
propertyValue,
|
||||
useValidationMessage,
|
||||
validationMessage,
|
||||
} = this.props;
|
||||
|
||||
const props: Partial<ControlProps> = {};
|
||||
|
||||
if (dataTreePath) props.dataTreePath = dataTreePath;
|
||||
if (evaluatedValue) props.evaluatedValue = evaluatedValue;
|
||||
if (expected) props.expected = expected;
|
||||
|
||||
return (
|
||||
<CodeEditor
|
||||
additionalDynamicData={this.props.additionalAutoComplete}
|
||||
input={{ value: propertyValue, onChange: this.onChange }}
|
||||
meta={{
|
||||
error: isValid ? "" : validationMessage,
|
||||
touched: true,
|
||||
}}
|
||||
mode={EditorModes.TEXT_WITH_BINDING}
|
||||
size={EditorSize.EXTENDED}
|
||||
tabBehaviour={TabBehaviour.INDENT}
|
||||
|
|
|
|||
|
|
@ -52,7 +52,6 @@ class ColumnActionSelectorControl extends BaseControl<
|
|||
<InputTextWrapper>
|
||||
<InputText
|
||||
evaluatedValue={columnAction.label}
|
||||
isValid
|
||||
label={columnAction.label}
|
||||
onChange={this.updateColumnActionLabel.bind(
|
||||
this,
|
||||
|
|
@ -64,12 +63,10 @@ class ColumnActionSelectorControl extends BaseControl<
|
|||
</InputTextWrapper>
|
||||
<Wrapper>
|
||||
<ActionCreator
|
||||
isValid={(columnAction as any).isValid}
|
||||
onValueChange={this.updateColumnActionFunction.bind(
|
||||
this,
|
||||
columnAction,
|
||||
)}
|
||||
validationMessage={(columnAction as any).message}
|
||||
value={columnAction.dynamicTrigger}
|
||||
/>
|
||||
</Wrapper>
|
||||
|
|
|
|||
|
|
@ -32,8 +32,6 @@ export function InputText(props: {
|
|||
label: string;
|
||||
value: string;
|
||||
onChange: (event: React.ChangeEvent<HTMLTextAreaElement> | 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}
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<InputText
|
||||
dataTreePath={dataTreePath}
|
||||
errorMessage={validationMessage}
|
||||
expected={expected}
|
||||
isValid={isValid}
|
||||
label={label}
|
||||
onChange={this.onTextChange}
|
||||
placeholder={placeholderText}
|
||||
|
|
|
|||
|
|
@ -14,8 +14,6 @@ export function InputText(props: {
|
|||
label: string;
|
||||
value: string;
|
||||
onChange: (event: React.ChangeEvent<HTMLTextAreaElement> | 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<InputControlProps> {
|
|||
defaultValue,
|
||||
expected,
|
||||
hideEvaluatedValue,
|
||||
isValid,
|
||||
label,
|
||||
placeholderText,
|
||||
propertyValue,
|
||||
validationMessage,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
<InputText
|
||||
additionalAutocomplete={additionalAutoComplete}
|
||||
dataTreePath={dataTreePath}
|
||||
errorMessage={validationMessage}
|
||||
expected={expected}
|
||||
hideEvaluatedValue={hideEvaluatedValue}
|
||||
isValid={isValid}
|
||||
label={label}
|
||||
onChange={this.onTextChange}
|
||||
placeholder={placeholderText}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ enum LOG_TYPE {
|
|||
ACTION_EXECUTION_ERROR,
|
||||
ACTION_EXECUTION_SUCCESS,
|
||||
ENTITY_DELETED,
|
||||
EVAL_ERROR,
|
||||
}
|
||||
|
||||
export default LOG_TYPE;
|
||||
|
|
|
|||
|
|
@ -1,77 +0,0 @@
|
|||
import WIDGET_CONFIG_RESPONSE from "./WidgetConfigResponse";
|
||||
|
||||
describe("WidgetConfigResponse", () => {
|
||||
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,
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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<Message>) {
|
|||
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) {
|
||||
|
|
|
|||
|
|
@ -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: {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -158,6 +158,7 @@ export interface WidgetEvaluatedProps {
|
|||
invalidProps?: Record<string, boolean>;
|
||||
validationMessages?: Record<string, string>;
|
||||
evaluatedValues?: Record<string, any>;
|
||||
jsErrorMessages?: Record<string, string>;
|
||||
}
|
||||
|
||||
export interface EntityWithBindings {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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<any>,
|
||||
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<any>,
|
||||
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 || "",
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user