fix: table search text dependency deletion (#11608)

* Replace BindingPaths with ReactivePaths and remove non-bindable property path from bindingPaths to reduce confusion.
This commit is contained in:
Rishabh Rathod 2022-04-12 18:39:26 +05:30 committed by GitHub
parent 68bca33a55
commit 136308fd7c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 850 additions and 182 deletions

View File

@ -1,5 +1,5 @@
import { Action, PluginType } from "entities/Action/index";
import { getBindingPathsOfAction } from "entities/Action/actionProperties";
import { getBindingAndReactivePathsOfAction } from "entities/Action/actionProperties";
import { EvaluationSubstitutionType } from "entities/DataTree/dataTreeFactory";
const DEFAULT_ACTION: Action = {
@ -18,12 +18,16 @@ const DEFAULT_ACTION: Action = {
organizationId: "",
pageId: "",
pluginId: "",
messages: [],
pluginType: PluginType.DB,
};
describe("getBindingPathsOfAction", () => {
describe("getReactivePathsOfAction", () => {
it("returns default list of no config is sent", () => {
const response = getBindingPathsOfAction(DEFAULT_ACTION, undefined);
const response = getBindingAndReactivePathsOfAction(
DEFAULT_ACTION,
undefined,
).reactivePaths;
expect(response).toStrictEqual({
data: EvaluationSubstitutionType.TEMPLATE,
isLoading: EvaluationSubstitutionType.TEMPLATE,
@ -73,7 +77,8 @@ describe("getBindingPathsOfAction", () => {
},
};
const response = getBindingPathsOfAction(basicAction, config);
const response = getBindingAndReactivePathsOfAction(basicAction, config)
.reactivePaths;
expect(response).toStrictEqual({
data: EvaluationSubstitutionType.TEMPLATE,
isLoading: EvaluationSubstitutionType.TEMPLATE,
@ -139,7 +144,8 @@ describe("getBindingPathsOfAction", () => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const response = getBindingPathsOfAction(basicAction, config);
const response = getBindingAndReactivePathsOfAction(basicAction, config)
.reactivePaths;
expect(response).toStrictEqual({
data: EvaluationSubstitutionType.TEMPLATE,
isLoading: EvaluationSubstitutionType.TEMPLATE,
@ -195,7 +201,8 @@ describe("getBindingPathsOfAction", () => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const response = getBindingPathsOfAction(basicAction, config);
const response = getBindingAndReactivePathsOfAction(basicAction, config)
.reactivePaths;
expect(response).toStrictEqual({
data: EvaluationSubstitutionType.TEMPLATE,
isLoading: EvaluationSubstitutionType.TEMPLATE,
@ -205,7 +212,7 @@ describe("getBindingPathsOfAction", () => {
});
});
it("checks for hidden field and returns bindingPaths accordingly", () => {
it("checks for hidden field and returns reactivePaths accordingly", () => {
const config = [
{
sectionName: "",
@ -252,7 +259,8 @@ describe("getBindingPathsOfAction", () => {
},
};
const response = getBindingPathsOfAction(basicAction, config);
const response = getBindingAndReactivePathsOfAction(basicAction, config)
.reactivePaths;
expect(response).toStrictEqual({
data: EvaluationSubstitutionType.TEMPLATE,
isLoading: EvaluationSubstitutionType.TEMPLATE,
@ -263,7 +271,8 @@ describe("getBindingPathsOfAction", () => {
basicAction.actionConfiguration.template.setting = true;
const response2 = getBindingPathsOfAction(basicAction, config);
const response2 = getBindingAndReactivePathsOfAction(basicAction, config)
.reactivePaths;
expect(response2).toStrictEqual({
data: EvaluationSubstitutionType.TEMPLATE,
isLoading: EvaluationSubstitutionType.TEMPLATE,
@ -272,4 +281,63 @@ describe("getBindingPathsOfAction", () => {
"config.body2": EvaluationSubstitutionType.TEMPLATE,
});
});
it.only("returns default list of no config is sent", () => {
const response = getBindingAndReactivePathsOfAction(
DEFAULT_ACTION,
undefined,
).bindingPaths;
expect(response).toStrictEqual({});
});
it.only("returns correct values for basic config", () => {
const config = [
{
sectionName: "",
id: 1,
children: [
{
label: "",
configProperty: "actionConfiguration.body",
controlType: "QUERY_DYNAMIC_TEXT",
},
{
label: "",
configProperty: "actionConfiguration.body2",
controlType: "QUERY_DYNAMIC_INPUT_TEXT",
},
{
label: "",
configProperty: "actionConfiguration.field1",
controlType: "QUERY_DYNAMIC_INPUT_TEXT",
evaluationSubstitutionType: "SMART_SUBSTITUTE",
},
{
label: "",
configProperty: "actionConfiguration.field2",
controlType: "QUERY_DYNAMIC_INPUT_TEXT",
evaluationSubstitutionType: "PARAMETER",
},
],
},
];
const basicAction = {
...DEFAULT_ACTION,
actionConfiguration: {
body: "basic action",
body2: "another body",
field1: "test",
field2: "anotherTest",
},
};
const response = getBindingAndReactivePathsOfAction(basicAction, config)
.bindingPaths;
expect(response).toStrictEqual({
"config.body": EvaluationSubstitutionType.TEMPLATE,
"config.body2": EvaluationSubstitutionType.TEMPLATE,
"config.field1": EvaluationSubstitutionType.SMART_SUBSTITUTE,
"config.field2": EvaluationSubstitutionType.PARAMETER,
});
});
});

View File

@ -12,9 +12,15 @@ import {
WhereClauseSubComponent,
allowedControlTypes,
} from "components/formControls/utils";
import formControlTypes from "utils/formControl/formControlTypes";
const dynamicFields = ["QUERY_DYNAMIC_TEXT", "QUERY_DYNAMIC_INPUT_TEXT"];
const dynamicFields = [
formControlTypes.QUERY_DYNAMIC_TEXT,
formControlTypes.QUERY_DYNAMIC_INPUT_TEXT,
];
type ReactivePaths = Record<string, EvaluationSubstitutionType>;
type BindingPaths = ReactivePaths;
const getCorrectEvaluationSubstitutionType = (substitutionType?: string) => {
if (substitutionType) {
if (substitutionType === EvaluationSubstitutionType.SMART_SUBSTITUTE) {
@ -26,20 +32,25 @@ const getCorrectEvaluationSubstitutionType = (substitutionType?: string) => {
return EvaluationSubstitutionType.TEMPLATE;
};
export const getBindingPathsOfAction = (
export const getBindingAndReactivePathsOfAction = (
action: Action,
formConfig?: any[],
): Record<string, EvaluationSubstitutionType> => {
const bindingPaths: Record<string, EvaluationSubstitutionType> = {
): { reactivePaths: ReactivePaths; bindingPaths: BindingPaths } => {
let reactivePaths: ReactivePaths = {
data: EvaluationSubstitutionType.TEMPLATE,
isLoading: EvaluationSubstitutionType.TEMPLATE,
datasourceUrl: EvaluationSubstitutionType.TEMPLATE,
};
const bindingPaths: BindingPaths = {};
if (!formConfig) {
return {
...bindingPaths,
reactivePaths = {
...reactivePaths,
config: EvaluationSubstitutionType.TEMPLATE,
};
return {
reactivePaths,
bindingPaths,
};
}
const recursiveFindBindingPaths = (formConfig: any) => {
if (formConfig.children) {
@ -61,7 +72,7 @@ export const getBindingPathsOfAction = (
bindingPaths[configPath] = getCorrectEvaluationSubstitutionType(
alternateViewTypeInputConfig.evaluationSubstitutionType,
);
} else if (formConfig.controlType === "ARRAY_FIELD") {
} else if (formConfig.controlType === formControlTypes.ARRAY_FIELD) {
let actionValue = _.get(action, formConfig.configProperty);
if (Array.isArray(actionValue)) {
actionValue = actionValue.filter((val) => val);
@ -81,7 +92,7 @@ export const getBindingPathsOfAction = (
});
}
}
} else if (formConfig.controlType === "WHERE_CLAUSE") {
} else if (formConfig.controlType === formControlTypes.WHERE_CLAUSE) {
const recursiveFindBindingPathsForWhereClause = (
newConfigPath: string,
actionValue: any,
@ -138,7 +149,7 @@ export const getBindingPathsOfAction = (
recursiveFindBindingPathsForWhereClause(childrenPath, value);
});
}
} else if (formConfig.controlType === "PAGINATION") {
} else if (formConfig.controlType === formControlTypes.PAGINATION) {
const limitPath = getBindingOrConfigPathsForPaginationControl(
PaginationSubComponent.Offset,
configPath,
@ -153,7 +164,7 @@ export const getBindingPathsOfAction = (
bindingPaths[offsetPath] = getCorrectEvaluationSubstitutionType(
formConfig.evaluationSubstitutionType,
);
} else if (formConfig.controlType === "SORTING") {
} else if (formConfig.controlType === formControlTypes.SORTING) {
const actionValue = _.get(action, formConfig.configProperty);
if (Array.isArray(actionValue)) {
actionValue.forEach((fieldConfig: any, index: number) => {
@ -175,7 +186,7 @@ export const getBindingPathsOfAction = (
);
});
}
} else if (formConfig.controlType === "ENTITY_SELECTOR") {
} else if (formConfig.controlType === formControlTypes.ENTITY_SELECTOR) {
if (Array.isArray(formConfig.schema)) {
formConfig.schema.forEach((schemaField: any, index: number) => {
if (allowedControlTypes.includes(schemaField.controlType)) {
@ -193,7 +204,11 @@ export const getBindingPathsOfAction = (
}
};
formConfig.forEach(recursiveFindBindingPaths);
return bindingPaths;
reactivePaths = {
...reactivePaths,
...bindingPaths,
};
return { reactivePaths, bindingPaths };
};
export const getBindingOrConfigPathsForSortingControl = (

View File

@ -2,7 +2,7 @@ import { DependencyMap, DynamicPath } from "utils/DynamicBindingUtils";
import { DataTreeAction, ENTITY_TYPE } from "entities/DataTree/dataTreeFactory";
import { ActionData } from "reducers/entityReducers/actionsReducer";
import {
getBindingPathsOfAction,
getBindingAndReactivePathsOfAction,
getDataTreeActionConfigPath,
} from "entities/Action/actionProperties";
@ -38,6 +38,11 @@ export const generateDataTreeAction = (
getDataTreeActionConfigPath,
);
});
const { bindingPaths, reactivePaths } = getBindingAndReactivePathsOfAction(
action.config,
editorConfig,
);
return {
run: {},
clear: {},
@ -55,7 +60,8 @@ export const generateDataTreeAction = (
},
ENTITY_TYPE: ENTITY_TYPE.ACTION,
isLoading: action.isLoading,
bindingPaths: getBindingPathsOfAction(action.config, editorConfig),
bindingPaths,
reactivePaths,
dependencyMap,
logBlackList: {},
datasourceUrl,

View File

@ -60,6 +60,7 @@ export interface DataTreeAction
| Record<string, unknown>;
dynamicBindingPathList: DynamicPath[];
bindingPaths: Record<string, EvaluationSubstitutionType>;
reactivePaths: Record<string, EvaluationSubstitutionType>;
ENTITY_TYPE: ENTITY_TYPE.ACTION;
dependencyMap: DependencyMap;
logBlackList: Record<string, true>;
@ -75,6 +76,7 @@ export interface DataTreeJSAction {
meta: Record<string, MetaArgs>;
dynamicBindingPathList: DynamicPath[];
bindingPaths: Record<string, EvaluationSubstitutionType>;
reactivePaths: Record<string, EvaluationSubstitutionType>;
variables: Array<string>;
dependencyMap: DependencyMap;
}
@ -106,6 +108,7 @@ export type PropertyOverrideDependency = Record<
export interface DataTreeWidget extends WidgetProps {
bindingPaths: Record<string, EvaluationSubstitutionType>;
reactivePaths: Record<string, EvaluationSubstitutionType>;
triggerPaths: Record<string, boolean>;
validationPaths: Record<string, ValidationConfig>;
ENTITY_TYPE: ENTITY_TYPE.WIDGET;

View File

@ -7,6 +7,8 @@ import { JSCollectionData } from "reducers/entityReducers/jsActionsReducer";
import { EvaluationSubstitutionType } from "entities/DataTree/dataTreeFactory";
import { DependencyMap } from "utils/DynamicBindingUtils";
const reg = /this\./g;
export const generateDataTreeJSAction = (
js: JSCollectionData,
): DataTreeJSAction => {
@ -17,7 +19,7 @@ export const generateDataTreeJSAction = (
const variables = js.config.variables;
const listVariables: Array<string> = [];
dynamicBindingPathList.push({ key: "body" });
const reg = /this\./g;
const removeThisReference = js.config.body.replace(reg, `${js.config.name}.`);
bindingPaths["body"] = EvaluationSubstitutionType.SMART_SUBSTITUTE;
@ -56,7 +58,8 @@ export const generateDataTreeJSAction = (
ENTITY_TYPE: ENTITY_TYPE.JSACTION,
body: removeThisReference,
meta: meta,
bindingPaths: bindingPaths,
bindingPaths: bindingPaths, // As all js object function referred to as action is user javascript code, we add them as binding paths.
reactivePaths: { ...bindingPaths },
dynamicBindingPathList: dynamicBindingPathList,
variables: listVariables,
dependencyMap: dependencyMap,

View File

@ -196,19 +196,24 @@ describe("generateDataTreeWidget", () => {
isDirty: true,
});
const bindingPaths = {
defaultText: EvaluationSubstitutionType.TEMPLATE,
placeholderText: EvaluationSubstitutionType.TEMPLATE,
regex: EvaluationSubstitutionType.TEMPLATE,
resetOnSubmit: EvaluationSubstitutionType.TEMPLATE,
isVisible: EvaluationSubstitutionType.TEMPLATE,
isRequired: EvaluationSubstitutionType.TEMPLATE,
isDisabled: EvaluationSubstitutionType.TEMPLATE,
errorMessage: EvaluationSubstitutionType.TEMPLATE,
};
const expected: DataTreeWidget = {
bindingPaths: {
defaultText: EvaluationSubstitutionType.TEMPLATE,
errorMessage: EvaluationSubstitutionType.TEMPLATE,
bindingPaths,
reactivePaths: {
...bindingPaths,
isDirty: EvaluationSubstitutionType.TEMPLATE,
isDisabled: EvaluationSubstitutionType.TEMPLATE,
isFocused: EvaluationSubstitutionType.TEMPLATE,
isRequired: EvaluationSubstitutionType.TEMPLATE,
isValid: EvaluationSubstitutionType.TEMPLATE,
isVisible: EvaluationSubstitutionType.TEMPLATE,
placeholderText: EvaluationSubstitutionType.TEMPLATE,
regex: EvaluationSubstitutionType.TEMPLATE,
resetOnSubmit: EvaluationSubstitutionType.TEMPLATE,
text: EvaluationSubstitutionType.TEMPLATE,
value: EvaluationSubstitutionType.TEMPLATE,
"meta.text": EvaluationSubstitutionType.TEMPLATE,

View File

@ -106,6 +106,7 @@ export const generateDataTreeWidget = (
const {
bindingPaths,
reactivePaths,
triggerPaths,
validationPaths,
} = getAllPathsFromPropertyConfig(widget, propertyPaneConfigs, {
@ -152,6 +153,7 @@ export const generateDataTreeWidget = (
propertyOverrideDependency,
overridingPropertyPaths,
bindingPaths,
reactivePaths,
triggerPaths,
validationPaths,
ENTITY_TYPE: ENTITY_TYPE.WIDGET,

View File

@ -117,8 +117,61 @@ describe("getAllPathsFromPropertyConfig", () => {
};
const config = tablePropertyPaneConfig;
const bindingPaths = {
tableData: EvaluationSubstitutionType.SMART_SUBSTITUTE,
defaultSearchText: EvaluationSubstitutionType.TEMPLATE,
defaultSelectedRow: EvaluationSubstitutionType.TEMPLATE,
isVisible: EvaluationSubstitutionType.TEMPLATE,
isSortable: EvaluationSubstitutionType.TEMPLATE,
animateLoading: EvaluationSubstitutionType.TEMPLATE,
primaryColumnId: EvaluationSubstitutionType.TEMPLATE,
compactMode: EvaluationSubstitutionType.TEMPLATE,
isVisibleDownload: EvaluationSubstitutionType.TEMPLATE,
isVisibleFilters: EvaluationSubstitutionType.TEMPLATE,
isVisiblePagination: EvaluationSubstitutionType.TEMPLATE,
isVisibleSearch: EvaluationSubstitutionType.TEMPLATE,
delimiter: EvaluationSubstitutionType.TEMPLATE,
"primaryColumns.name.computedValue": EvaluationSubstitutionType.TEMPLATE,
"primaryColumns.name.horizontalAlignment":
EvaluationSubstitutionType.TEMPLATE,
"primaryColumns.name.verticalAlignment":
EvaluationSubstitutionType.TEMPLATE,
"primaryColumns.name.textSize": EvaluationSubstitutionType.TEMPLATE,
"primaryColumns.name.fontStyle": EvaluationSubstitutionType.TEMPLATE,
"primaryColumns.name.textColor": EvaluationSubstitutionType.TEMPLATE,
// "primaryColumns.name.isVisible": EvaluationSubstitutionType.TEMPLATE,
"primaryColumns.name.isCellVisible": EvaluationSubstitutionType.TEMPLATE,
"primaryColumns.name.cellBackground": EvaluationSubstitutionType.TEMPLATE,
"primaryColumns.createdAt.inputFormat":
EvaluationSubstitutionType.TEMPLATE,
"primaryColumns.createdAt.outputFormat":
EvaluationSubstitutionType.TEMPLATE,
"primaryColumns.createdAt.computedValue":
EvaluationSubstitutionType.TEMPLATE,
"primaryColumns.createdAt.isCellVisible":
EvaluationSubstitutionType.TEMPLATE,
"primaryColumns.createdAt.horizontalAlignment":
EvaluationSubstitutionType.TEMPLATE,
"primaryColumns.createdAt.verticalAlignment":
EvaluationSubstitutionType.TEMPLATE,
"primaryColumns.createdAt.textSize": EvaluationSubstitutionType.TEMPLATE,
"primaryColumns.createdAt.fontStyle": EvaluationSubstitutionType.TEMPLATE,
"primaryColumns.createdAt.textColor": EvaluationSubstitutionType.TEMPLATE,
"primaryColumns.createdAt.cellBackground":
EvaluationSubstitutionType.TEMPLATE,
"primaryColumns.status.buttonLabel": EvaluationSubstitutionType.TEMPLATE,
"primaryColumns.status.buttonColor": EvaluationSubstitutionType.TEMPLATE,
"primaryColumns.status.isDisabled": EvaluationSubstitutionType.TEMPLATE,
"primaryColumns.status.buttonVariant":
EvaluationSubstitutionType.TEMPLATE,
"primaryColumns.status.isCellVisible":
EvaluationSubstitutionType.TEMPLATE,
};
const expected = {
bindingPaths: {
bindingPaths,
reactivePaths: {
...bindingPaths,
selectedRow: EvaluationSubstitutionType.TEMPLATE,
selectedRows: EvaluationSubstitutionType.TEMPLATE,
tableData: EvaluationSubstitutionType.SMART_SUBSTITUTE,
@ -171,8 +224,6 @@ describe("getAllPathsFromPropertyConfig", () => {
EvaluationSubstitutionType.TEMPLATE,
"primaryColumns.status.buttonLabel":
EvaluationSubstitutionType.TEMPLATE,
"primaryColumns.status.buttonVariant":
EvaluationSubstitutionType.TEMPLATE,
"primaryColumns.status.buttonColor":
EvaluationSubstitutionType.TEMPLATE,
"primaryColumns.status.isDisabled": EvaluationSubstitutionType.TEMPLATE,
@ -508,18 +559,21 @@ describe("getAllPathsFromPropertyConfig", () => {
};
const config = chartPorpertyConfig;
const bindingPaths = {
chartType: EvaluationSubstitutionType.TEMPLATE,
chartName: EvaluationSubstitutionType.TEMPLATE,
"chartData.random-id.seriesName": EvaluationSubstitutionType.TEMPLATE,
"chartData.random-id.data": EvaluationSubstitutionType.SMART_SUBSTITUTE,
xAxisName: EvaluationSubstitutionType.TEMPLATE,
yAxisName: EvaluationSubstitutionType.TEMPLATE,
isVisible: EvaluationSubstitutionType.TEMPLATE,
animateLoading: EvaluationSubstitutionType.TEMPLATE,
setAdaptiveYMin: EvaluationSubstitutionType.TEMPLATE,
};
const expected = {
bindingPaths: {
chartType: EvaluationSubstitutionType.TEMPLATE,
chartName: EvaluationSubstitutionType.TEMPLATE,
"chartData.random-id.seriesName": EvaluationSubstitutionType.TEMPLATE,
"chartData.random-id.data": EvaluationSubstitutionType.SMART_SUBSTITUTE,
xAxisName: EvaluationSubstitutionType.TEMPLATE,
yAxisName: EvaluationSubstitutionType.TEMPLATE,
isVisible: EvaluationSubstitutionType.TEMPLATE,
animateLoading: EvaluationSubstitutionType.TEMPLATE,
setAdaptiveYMin: EvaluationSubstitutionType.TEMPLATE,
},
bindingPaths,
reactivePaths: { ...bindingPaths },
triggerPaths: {
onDataPointClick: true,
},

View File

@ -10,10 +10,21 @@ import { EvaluationSubstitutionType } from "entities/DataTree/dataTreeFactory";
/**
* @typedef {Object} Paths
* @property {Object} configBindingPaths - The Binding Path
* @property {Object} configReactivePaths - The Dynamic Property Path
* @property {Object} configTriggerPaths - The Trigger Path
* @property {Object} configValidationPaths - The Validation Path
*/
/**
* All widget's property or paths where user can write javaScript bindings using mustache syntax are called bindingPaths.
* Widget's meta and derived paths aren't binding paths as user can't add or remove binding for those value.
*/
type BindingPaths = Record<string, EvaluationSubstitutionType>;
/**
* Binding paths and non-binding paths of widget/action together form reactivePaths.
*/
type ReactivePaths = Record<string, EvaluationSubstitutionType>;
/**
* This function gets the binding validation and trigger paths from a config
* @param config
@ -24,14 +35,14 @@ const checkPathsInConfig = (
config: any,
path: string,
): {
configBindingPaths: Record<string, EvaluationSubstitutionType>;
configBindingPaths: BindingPaths;
configReactivePaths: ReactivePaths;
configTriggerPaths: Record<string, true>;
configValidationPaths: Record<string, ValidationConfig>;
} => {
const configBindingPaths: Record<string, EvaluationSubstitutionType> = {};
const configBindingPaths: BindingPaths = {};
const configTriggerPaths: Record<string, true> = {};
const configValidationPaths: Record<any, ValidationConfig> = {};
// Purely a Binding Path
if (config.isBindProperty && !config.isTriggerProperty) {
configBindingPaths[path] =
@ -42,7 +53,12 @@ const checkPathsInConfig = (
} else if (config.isBindProperty && config.isTriggerProperty) {
configTriggerPaths[path] = true;
}
return { configBindingPaths, configTriggerPaths, configValidationPaths };
return {
configBindingPaths,
configReactivePaths: configBindingPaths, // All bindingPaths are reactivePaths.
configTriggerPaths,
configValidationPaths,
};
};
// "originalWidget" param here always contains the complete widget props
@ -55,7 +71,9 @@ const childHasPanelConfig = (
) => {
const panelPropertyPath = config.propertyName;
const widgetPanelPropertyValues = get(widget, panelPropertyPath);
let bindingPaths: Record<string, EvaluationSubstitutionType> = {};
let bindingPaths: BindingPaths = {};
let reactivePaths: ReactivePaths = {};
let triggerPaths: Record<string, true> = {};
let validationPaths: Record<any, ValidationConfig> = {};
if (widgetPanelPropertyValues) {
@ -86,13 +104,21 @@ const childHasPanelConfig = (
if (!isControlHidden) {
const {
configBindingPaths,
configReactivePaths,
configTriggerPaths,
configValidationPaths,
} = checkPathsInConfig(
panelColumnControlConfig,
panelPropertyConfigPath,
);
bindingPaths = { ...configBindingPaths, ...bindingPaths };
bindingPaths = {
...configBindingPaths,
...bindingPaths,
};
reactivePaths = {
...configReactivePaths,
...reactivePaths,
};
triggerPaths = { ...configTriggerPaths, ...triggerPaths };
validationPaths = {
...configValidationPaths,
@ -102,6 +128,7 @@ const childHasPanelConfig = (
if (panelColumnControlConfig.panelConfig) {
const {
bindingPaths: panelBindingPaths,
reactivePaths: panelReactivePaths,
triggerPaths: panelTriggerPaths,
validationPaths: panelValidationPaths,
} = childHasPanelConfig(
@ -110,7 +137,14 @@ const childHasPanelConfig = (
panelPropertyConfigPath,
originalWidget,
);
bindingPaths = { ...panelBindingPaths, ...bindingPaths };
bindingPaths = {
...panelBindingPaths,
...bindingPaths,
};
reactivePaths = {
...panelReactivePaths,
...reactivePaths,
};
triggerPaths = { ...panelTriggerPaths, ...triggerPaths };
validationPaths = {
...panelValidationPaths,
@ -126,7 +160,7 @@ const childHasPanelConfig = (
);
}
return { bindingPaths, triggerPaths, validationPaths };
return { reactivePaths, triggerPaths, validationPaths, bindingPaths };
};
export const getAllPathsFromPropertyConfig = (
@ -134,15 +168,16 @@ export const getAllPathsFromPropertyConfig = (
widgetConfig: readonly PropertyPaneConfig[],
defaultProperties: Record<string, any>,
): {
bindingPaths: Record<string, EvaluationSubstitutionType>;
bindingPaths: BindingPaths;
reactivePaths: ReactivePaths;
triggerPaths: Record<string, true>;
validationPaths: Record<string, ValidationConfig>;
} => {
let bindingPaths: Record<string, EvaluationSubstitutionType> = {};
Object.keys(defaultProperties).forEach(
(property) =>
(bindingPaths[property] = EvaluationSubstitutionType.TEMPLATE),
);
let bindingPaths: BindingPaths = {};
let reactivePaths: ReactivePaths = {};
Object.keys(defaultProperties).forEach((property) => {
reactivePaths[property] = EvaluationSubstitutionType.TEMPLATE;
});
let triggerPaths: Record<string, true> = {};
let validationPaths: Record<any, ValidationConfig> = {};
@ -158,11 +193,19 @@ export const getAllPathsFromPropertyConfig = (
const path = controlConfig.propertyName;
const {
configBindingPaths,
configReactivePaths,
configTriggerPaths,
configValidationPaths,
} = checkPathsInConfig(controlConfig, path);
bindingPaths = {
...bindingPaths,
...configBindingPaths,
};
// Update default path configs with the ones in the property config
bindingPaths = { ...bindingPaths, ...configBindingPaths };
reactivePaths = {
...reactivePaths,
...configReactivePaths,
};
triggerPaths = { ...triggerPaths, ...configTriggerPaths };
validationPaths = { ...validationPaths, ...configValidationPaths };
}
@ -174,7 +217,14 @@ export const getAllPathsFromPropertyConfig = (
basePath,
widget,
);
bindingPaths = { ...bindingPaths, ...resultingPaths.bindingPaths };
bindingPaths = {
...bindingPaths,
...resultingPaths.bindingPaths,
};
reactivePaths = {
...reactivePaths,
...resultingPaths.reactivePaths,
};
triggerPaths = { ...triggerPaths, ...resultingPaths.triggerPaths };
validationPaths = {
...validationPaths,
@ -195,13 +245,21 @@ export const getAllPathsFromPropertyConfig = (
const childArrayPropertyPath = `${objectIndexPropertyPath}.${childPropertyConfig.propertyName}`;
const {
configBindingPaths,
configReactivePaths,
configTriggerPaths,
configValidationPaths,
} = checkPathsInConfig(
childPropertyConfig,
childArrayPropertyPath,
);
bindingPaths = { ...bindingPaths, ...configBindingPaths };
bindingPaths = {
...bindingPaths,
...configBindingPaths,
};
reactivePaths = {
...reactivePaths,
...configReactivePaths,
};
triggerPaths = { ...triggerPaths, ...configTriggerPaths };
validationPaths = {
...validationPaths,
@ -215,7 +273,7 @@ export const getAllPathsFromPropertyConfig = (
}
});
return { bindingPaths, triggerPaths, validationPaths };
return { reactivePaths, triggerPaths, validationPaths, bindingPaths };
};
/**

View File

@ -7,7 +7,7 @@ import {
} from "components/formControls/utils";
import { useSelector, shallowEqual } from "react-redux";
import { getFormValues } from "redux-form";
import FormControlFactory from "utils/FormControlFactory";
import FormControlFactory from "utils/formControl/FormControlFactory";
import { AppState } from "reducers";
import { Action } from "entities/Action";

View File

@ -448,6 +448,7 @@ const createLoadingWidget = (
type: WidgetTypes.SKELETON_WIDGET,
ENTITY_TYPE: ENTITY_TYPE.WIDGET,
bindingPaths: {},
reactivePaths: {},
triggerPaths: {},
validationPaths: {},
logBlackList: {},

View File

@ -6,7 +6,7 @@ import {
import { getAppsmithConfigs } from "@appsmith/configs";
import * as Sentry from "@sentry/react";
import AnalyticsUtil from "./AnalyticsUtil";
import FormControlRegistry from "./FormControlRegistry";
import FormControlRegistry from "./formControl/FormControlRegistry";
import { Property } from "api/ActionAPI";
import _ from "lodash";
import { ActionDataState } from "reducers/entityReducers/actionsReducer";

View File

@ -1,5 +1,6 @@
import { Action, PluginType } from "entities/Action";
import _ from "lodash";
import { getPropertyPath } from "./DynamicBindingUtils";
import {
EVAL_VALUE_PATH,
getDynamicBindingsChangesSaga,
@ -156,6 +157,23 @@ describe("DynamicBindingPathlist", () => {
});
});
describe("getPropertyPath function", () => {
it("test getPropertyPath", () => {
const testCases = [
["Table1.searchText", "searchText"],
["Table1.selectedRow", "selectedRow"],
["Table1.meta.searchText", "meta.searchText"],
["Table1", "Table1"],
["Table1.", ""],
];
testCases.forEach(([input, expectedResult]) => {
const actualResult = getPropertyPath(input);
expect(actualResult).toStrictEqual(expectedResult);
});
});
});
describe("getNestedEvalPath", () => {
it("returns valid nested path", () => {
const actualUnpopulatedNestedPath = getEvalValuePath(

View File

@ -239,6 +239,15 @@ export const isPathADynamicBinding = (
}
return false;
};
/**
* Get property path from full property path
* Input: "Table1.meta.searchText" => Output: "meta.searchText"
* @param {string} fullPropertyPath
* @return {*}
*/
export const getPropertyPath = (fullPropertyPath: string) => {
return fullPropertyPath.substring(fullPropertyPath.indexOf(".") + 1);
};
export const getWidgetDynamicTriggerPathList = (
widget: WidgetProps,

View File

@ -19,6 +19,7 @@ const JS_object_tree: DataTreeJSAction = {
meta: {},
dynamicBindingPathList: [],
bindingPaths: {},
reactivePaths: {},
variables: [],
dependencyMap: {},
};
@ -26,6 +27,7 @@ const JS_object_tree: DataTreeJSAction = {
const Select_tree: DataTreeWidget = {
ENTITY_TYPE: ENTITY_TYPE.WIDGET,
bindingPaths: {},
reactivePaths: {},
triggerPaths: {},
validationPaths: {},
logBlackList: {},
@ -58,6 +60,7 @@ const Query_tree: DataTreeAction = {
clear: {},
dynamicBindingPathList: [],
bindingPaths: {},
reactivePaths: {},
ENTITY_TYPE: ENTITY_TYPE.ACTION,
dependencyMap: {},
logBlackList: {},

View File

@ -31,6 +31,9 @@ describe("dataTreeTypeDefCreator", () => {
bindingPaths: {
defaultText: EvaluationSubstitutionType.TEMPLATE,
},
reactivePaths: {
defaultText: EvaluationSubstitutionType.TEMPLATE,
},
triggerPaths: {
onTextChange: true,
},

View File

@ -45,50 +45,54 @@ import SortingControl, {
import EntitySelectorControl, {
EntitySelectorControlProps,
} from "components/formControls/EntitySelectorControl";
import formControlTypes from "./formControlTypes";
/**
* NOTE: If you are adding a component that uses FormControl
* then add logic for creating bindingPaths in recursiveFindBindingPaths() at entities/Action/actionProperties.ts
* then add logic for creating reactivePaths in recursiveFindReactivePaths() at entities/Action/actionProperties.ts
*/
class FormControlRegistry {
static registerFormControlBuilders() {
FormControlFactory.registerControlBuilder("INPUT_TEXT", {
FormControlFactory.registerControlBuilder(formControlTypes.INPUT_TEXT, {
buildPropertyControl(controlProps: InputControlProps): JSX.Element {
return <InputTextControl {...controlProps} />;
},
});
FormControlFactory.registerControlBuilder("FIXED_KEY_INPUT", {
buildPropertyControl(
controlProps: FixedKeyInputControlProps,
): JSX.Element {
//TODO: may not be in use
return <FixedKeyInputControl {...controlProps} />;
FormControlFactory.registerControlBuilder(
formControlTypes.FIXED_KEY_INPUT,
{
buildPropertyControl(
controlProps: FixedKeyInputControlProps,
): JSX.Element {
//TODO: may not be in use
return <FixedKeyInputControl {...controlProps} />;
},
},
});
FormControlFactory.registerControlBuilder("DROP_DOWN", {
);
FormControlFactory.registerControlBuilder(formControlTypes.DROP_DOWN, {
buildPropertyControl(controlProps: DropDownControlProps): JSX.Element {
return <DropDownControl {...controlProps} />;
},
});
FormControlFactory.registerControlBuilder("SWITCH", {
FormControlFactory.registerControlBuilder(formControlTypes.SWITCH, {
buildPropertyControl(controlProps: SwitchControlProps): JSX.Element {
return <SwitchControl {...controlProps} />;
},
});
FormControlFactory.registerControlBuilder("KEYVALUE_ARRAY", {
FormControlFactory.registerControlBuilder(formControlTypes.KEYVALUE_ARRAY, {
buildPropertyControl(
controlProps: KeyValueArrayControlProps,
): JSX.Element {
return <KeyValueArrayControl {...controlProps} />;
},
});
FormControlFactory.registerControlBuilder("FILE_PICKER", {
FormControlFactory.registerControlBuilder(formControlTypes.FILE_PICKER, {
buildPropertyControl(controlProps: FilePickerControlProps): JSX.Element {
//used by redshift datasource
return <FilePickerControl {...controlProps} />;
},
});
FormControlFactory.registerControlBuilder("KEY_VAL_INPUT", {
FormControlFactory.registerControlBuilder(formControlTypes.KEY_VAL_INPUT, {
//TODO: may not be in use, replace it with KeyValueArrayControl
buildPropertyControl(
controlProps: KeyValueInputControlProps,
@ -96,58 +100,67 @@ class FormControlRegistry {
return <KeyValueInputControl {...controlProps} />;
},
});
FormControlFactory.registerControlBuilder("QUERY_DYNAMIC_TEXT", {
buildPropertyControl(controlProps: DynamicTextFieldProps): JSX.Element {
return <DynamicTextControl {...controlProps} />;
FormControlFactory.registerControlBuilder(
formControlTypes.QUERY_DYNAMIC_TEXT,
{
buildPropertyControl(controlProps: DynamicTextFieldProps): JSX.Element {
return <DynamicTextControl {...controlProps} />;
},
},
});
FormControlFactory.registerControlBuilder("QUERY_DYNAMIC_INPUT_TEXT", {
buildPropertyControl(
controlProps: DynamicInputControlProps,
): JSX.Element {
return <DynamicInputTextControl {...controlProps} />;
);
FormControlFactory.registerControlBuilder(
formControlTypes.QUERY_DYNAMIC_INPUT_TEXT,
{
buildPropertyControl(
controlProps: DynamicInputControlProps,
): JSX.Element {
return <DynamicInputTextControl {...controlProps} />;
},
},
});
FormControlFactory.registerControlBuilder("CHECKBOX", {
);
FormControlFactory.registerControlBuilder(formControlTypes.CHECKBOX, {
buildPropertyControl(controlProps: CheckboxControlProps): JSX.Element {
//used in API datasource form only
return <CheckboxControl {...controlProps} />;
},
});
FormControlFactory.registerControlBuilder("NUMBER_INPUT", {
FormControlFactory.registerControlBuilder(formControlTypes.NUMBER_INPUT, {
buildPropertyControl(controlProps: InputControlProps): JSX.Element {
return <InputTextControl {...controlProps} />;
},
});
FormControlFactory.registerControlBuilder("ARRAY_FIELD", {
FormControlFactory.registerControlBuilder(formControlTypes.ARRAY_FIELD, {
buildPropertyControl(controlProps: FieldArrayControlProps): JSX.Element {
return <FieldArrayControl {...controlProps} />;
},
});
FormControlFactory.registerControlBuilder("WHERE_CLAUSE", {
FormControlFactory.registerControlBuilder(formControlTypes.WHERE_CLAUSE, {
buildPropertyControl(controlProps: WhereClauseControlProps): JSX.Element {
return <WhereClauseControl {...controlProps} />;
},
});
FormControlFactory.registerControlBuilder("ENTITY_SELECTOR", {
buildPropertyControl(
controlProps: EntitySelectorControlProps,
): JSX.Element {
return <EntitySelectorControl {...controlProps} />;
FormControlFactory.registerControlBuilder(
formControlTypes.ENTITY_SELECTOR,
{
buildPropertyControl(
controlProps: EntitySelectorControlProps,
): JSX.Element {
return <EntitySelectorControl {...controlProps} />;
},
},
});
);
FormControlFactory.registerControlBuilder("PAGINATION", {
FormControlFactory.registerControlBuilder(formControlTypes.PAGINATION, {
buildPropertyControl(controlProps: PaginationControlProps): JSX.Element {
return <PaginationControl {...controlProps} />;
},
});
FormControlFactory.registerControlBuilder("SORTING", {
FormControlFactory.registerControlBuilder(formControlTypes.SORTING, {
buildPropertyControl(controlProps: SortingControlProps): JSX.Element {
return <SortingControl {...controlProps} />;
},
});
FormControlFactory.registerControlBuilder("PROJECTION", {
FormControlFactory.registerControlBuilder(formControlTypes.PROJECTION, {
buildPropertyControl(controlProps: DropDownControlProps): JSX.Element {
return (
<DropDownControl

View File

@ -0,0 +1,19 @@
export default {
INPUT_TEXT: "INPUT_TEXT",
FIXED_KEY_INPUT: "FIXED_KEY_INPUT",
DROP_DOWN: "DROP_DOWN",
SWITCH: "SWITCH",
KEYVALUE_ARRAY: "KEYVALUE_ARRAY",
FILE_PICKER: "FILE_PICKER",
KEY_VAL_INPUT: "KEY_VAL_INPUT",
QUERY_DYNAMIC_TEXT: "QUERY_DYNAMIC_TEXT",
QUERY_DYNAMIC_INPUT_TEXT: "QUERY_DYNAMIC_INPUT_TEXT",
CHECKBOX: "CHECKBOX",
NUMBER_INPUT: "NUMBER_INPUT",
ARRAY_FIELD: "ARRAY_FIELD",
WHERE_CLAUSE: "WHERE_CLAUSE",
ENTITY_SELECTOR: "ENTITY_SELECTOR",
PAGINATION: "PAGINATION",
SORTING: "SORTING",
PROJECTION: "PROJECTION",
};

View File

@ -20,3 +20,16 @@ export const mapTree = (tree: Tree, callback: (tree: Tree) => Tree) => {
}
return { ...mapped };
};
/**
* This function sorts the object's value which is array of string.
*
* @param {Record<string, Array<string>>} data
* @return {*}
*/
export const sortObjectWithArray = (data: Record<string, Array<string>>) => {
Object.entries(data).map(([key, value]) => {
data[key] = value.sort();
});
return data;
};

View File

@ -16,6 +16,7 @@ describe("Add functions", () => {
dynamicBindingPathList: [],
name: "action1",
bindingPaths: {},
reactivePaths: {},
isLoading: false,
run: {},
clear: {},

View File

@ -7,6 +7,7 @@ import {
getEntityDynamicBindingPathList,
getEvalErrorPath,
getEvalValuePath,
getPropertyPath,
isChildPropertyPath,
isPathADynamicBinding,
isPathADynamicTrigger,
@ -76,7 +77,7 @@ import { JSUpdate } from "utils/JSPaneUtils";
import {
addWidgetPropertyDependencies,
overrideWidgetProperties,
} from "./evaluationUtils";
} from "../evaluationUtils";
import {
ActionValidationConfigMap,
ValidationConfig,
@ -292,19 +293,21 @@ export default class DataTreeEvaluator {
translateDiffEventToDataTreeDiffEvent(diff, localUnEvalTree),
),
);
this.logs.push({ differences, translatedDiffs });
this.logs.push({
differences,
translatedDiffs,
});
const diffCheckTimeStop = performance.now();
// Check if dependencies have changed
const updateDependenciesStart = performance.now();
this.logs.push({ differences: clone(differences), translatedDiffs });
// Find all the paths that have changed as part of the difference and update the
// global dependency map if an existing dynamic binding has now become legal
const {
dependenciesOfRemovedPaths,
removedPaths,
} = this.updateDependencyMap(translatedDiffs, localUnEvalTree);
const updateDependenciesStop = performance.now();
this.applyDifferencesToEvalTree(differences);
@ -560,8 +563,8 @@ export default class DataTreeEvaluator {
}
if (isJSAction(entity)) {
// making functions dependent on their function body entities
if (entity.bindingPaths) {
Object.keys(entity.bindingPaths).forEach((propertyPath) => {
if (entity.reactivePaths) {
Object.keys(entity.reactivePaths).forEach((propertyPath) => {
const existingDeps =
dependencies[`${entityName}.${propertyPath}`] || [];
const unevalPropValue = _.get(entity, propertyPath).toString();
@ -613,14 +616,14 @@ export default class DataTreeEvaluator {
fullPropertyPath,
);
const isABindingPath =
const isADynamicBindingPath =
(isAction(entity) || isWidget(entity) || isJSAction(entity)) &&
isPathADynamicBinding(entity, propertyPath);
const isATriggerPath =
isWidget(entity) && isPathADynamicTrigger(entity, propertyPath);
let evalPropertyValue;
const requiresEval =
isABindingPath &&
isADynamicBindingPath &&
!isATriggerPath &&
(isDynamicValue(unEvalPropertyValue) || isJSAction(entity));
if (propertyPath) {
@ -628,7 +631,7 @@ export default class DataTreeEvaluator {
}
if (requiresEval) {
const evaluationSubstitutionType =
entity.bindingPaths[propertyPath] ||
entity.reactivePaths[propertyPath] ||
EvaluationSubstitutionType.TEMPLATE;
const contextData: EvaluateContext = {};
@ -1330,14 +1333,12 @@ export default class DataTreeEvaluator {
| DataTreeWidget
| DataTreeJSAction;
const fullPropertyPath = dataTreeDiff.payload.propertyPath;
const entityPropertyPath = fullPropertyPath.substring(
fullPropertyPath.indexOf(".") + 1,
);
const isABindingPath = isPathADynamicBinding(
const entityPropertyPath = getPropertyPath(fullPropertyPath);
const isADynamicBindingPath = isPathADynamicBinding(
entity,
entityPropertyPath,
);
if (isABindingPath) {
if (isADynamicBindingPath) {
didUpdateDependencyMap = true;
const { jsSnippets } = getDynamicBindings(
@ -1381,11 +1382,12 @@ export default class DataTreeEvaluator {
}
}
}
// If the whole binding was removed then the value
// at this path would be "".
// In this case if the path exists in the dependency map
// remove it.
else if (fullPropertyPath in this.dependencyMap) {
// If the whole binding was removed, then the value at this path would be a string without any bindings.
// In this case, if the path exists in the dependency map and is a bindingPath, then remove it.
else if (
entity.bindingPaths[entityPropertyPath] &&
fullPropertyPath in this.dependencyMap
) {
didUpdateDependencyMap = true;
delete this.dependencyMap[fullPropertyPath];
}
@ -1501,7 +1503,7 @@ export default class DataTreeEvaluator {
);
}
const parentPropertyPath = convertPathToString(d.path);
Object.keys(entity.bindingPaths).forEach((relativePath) => {
Object.keys(entity.reactivePaths).forEach((relativePath) => {
const childPropertyPath = `${entityName}.${relativePath}`;
// Check if relative path has dynamic binding
if (

View File

@ -1,10 +1,27 @@
import DataTreeEvaluator from "./DataTreeEvaluator";
import DataTreeEvaluator from "../DataTreeEvaluator";
import { unEvalTree } from "./mockUnEvalTree";
import { DataTree } from "entities/DataTree/dataTreeFactory";
import { DataTreeDiff } from "workers/evaluationUtils";
import { ALL_WIDGETS_AND_CONFIG } from "utils/WidgetRegistry";
const widgetConfigMap = {};
ALL_WIDGETS_AND_CONFIG.map(([, config]) => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: No types available
if (config.type && config.properties) {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: No types available
widgetConfigMap[config.type] = {
defaultProperties: config.properties.default,
derivedProperties: config.properties.derived,
metaProperties: config.properties.meta,
};
}
});
const dataTreeEvaluator = new DataTreeEvaluator(widgetConfigMap);
describe("DataTreeEvaluator", () => {
let dataTreeEvaluator: DataTreeEvaluator;
beforeAll(() => {
dataTreeEvaluator = new DataTreeEvaluator({});
});
describe("evaluateActionBindings", () => {
it("handles this.params.property", () => {
const result = dataTreeEvaluator.evaluateActionBindings(
@ -106,4 +123,67 @@ describe("DataTreeEvaluator", () => {
]);
});
});
describe("test updateDependencyMap", () => {
beforeEach(() => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: No types available
dataTreeEvaluator.createFirstTree(unEvalTree as DataTree);
});
it("initial dependencyMap computation", () => {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: No types available
dataTreeEvaluator.updateDataTree(unEvalTree as DataTree);
expect(dataTreeEvaluator.dependencyMap).toStrictEqual({
"Button2.text": ["Button1.text"],
Button2: ["Button2.text"],
Button1: ["Button1.text"],
});
});
it(`When empty binding is modified from {{Button1.text}} to {{""}}`, () => {
const translatedDiffs = [
{
payload: {
propertyPath: "Button2.text",
value: '{{""}}',
},
event: "EDIT",
},
];
dataTreeEvaluator.updateDependencyMap(
translatedDiffs as Array<DataTreeDiff>,
dataTreeEvaluator.oldUnEvalTree,
);
expect(dataTreeEvaluator.dependencyMap).toStrictEqual({
"Button2.text": [],
Button2: ["Button2.text"],
Button1: ["Button1.text"],
});
});
it(`When binding is removed`, () => {
const translatedDiffs = [
{
payload: {
propertyPath: "Button2.text",
value: "abc",
},
event: "EDIT",
},
];
dataTreeEvaluator.updateDependencyMap(
translatedDiffs as Array<DataTreeDiff>,
dataTreeEvaluator.oldUnEvalTree,
);
expect(dataTreeEvaluator.dependencyMap).toStrictEqual({
Button2: ["Button2.text"],
Button1: ["Button1.text"],
});
});
});
});

View File

@ -0,0 +1,293 @@
export const unEvalTree = {
MainContainer: {
widgetName: "MainContainer",
backgroundColor: "none",
rightColumn: 2220,
snapColumns: 64,
detachFromLayout: true,
widgetId: "0",
topRow: 0,
bottomRow: 640,
containerStyle: "none",
snapRows: 113,
parentRowSpace: 1,
type: "CANVAS_WIDGET",
canExtend: true,
version: 52,
minHeight: 620,
parentColumnSpace: 1,
dynamicBindingPathList: [],
leftColumn: 0,
children: ["j9dpft2lpu", "l0yem4eh6l"],
defaultProps: {},
defaultMetaProps: [],
logBlackList: {},
meta: {},
propertyOverrideDependency: {},
overridingPropertyPaths: {},
reactivePaths: {},
triggerPaths: {},
validationPaths: {},
ENTITY_TYPE: "WIDGET",
privateWidgets: {},
},
Button1: {
widgetName: "Button1",
buttonColor: "#03B365",
displayName: "Button",
iconSVG: "/static/media/icon.cca02633.svg",
topRow: 15,
bottomRow: 19,
parentRowSpace: 10,
type: "BUTTON_WIDGET",
hideCard: false,
animateLoading: true,
parentColumnSpace: 26.421875,
dynamicTriggerPathList: [],
leftColumn: 20,
dynamicBindingPathList: [],
text: "button1",
isDisabled: false,
key: "r6h8y6dc8i",
rightColumn: 36,
isDefaultClickDisabled: true,
widgetId: "j9dpft2lpu",
isVisible: true,
recaptchaType: "V3",
version: 1,
parentId: "0",
renderMode: "CANVAS",
isLoading: false,
buttonVariant: "PRIMARY",
placement: "CENTER",
defaultProps: {},
defaultMetaProps: ["recaptchaToken"],
logBlackList: {},
meta: {},
propertyOverrideDependency: {},
overridingPropertyPaths: {},
reactivePaths: {
recaptchaToken: "TEMPLATE",
text: "TEMPLATE",
tooltip: "TEMPLATE",
googleRecaptchaKey: "TEMPLATE",
recaptchaType: "TEMPLATE",
isVisible: "TEMPLATE",
isDisabled: "TEMPLATE",
animateLoading: "TEMPLATE",
buttonVariant: "TEMPLATE",
placement: "TEMPLATE",
},
triggerPaths: {
onClick: true,
},
validationPaths: {
text: {
type: "TEXT",
},
tooltip: {
type: "TEXT",
},
googleRecaptchaKey: {
type: "TEXT",
},
recaptchaType: {
type: "TEXT",
params: {
allowedValues: ["V3", "V2"],
default: "V3",
},
},
isVisible: {
type: "BOOLEAN",
},
isDisabled: {
type: "BOOLEAN",
},
animateLoading: {
type: "BOOLEAN",
},
buttonVariant: {
type: "TEXT",
params: {
allowedValues: ["PRIMARY", "SECONDARY", "TERTIARY"],
default: "PRIMARY",
},
},
placement: {
type: "TEXT",
params: {
allowedValues: ["START", "BETWEEN", "CENTER"],
default: "CENTER",
},
},
},
ENTITY_TYPE: "WIDGET",
privateWidgets: {},
},
Button2: {
widgetName: "Button2",
buttonColor: "#03B365",
displayName: "Button",
iconSVG: "/static/media/icon.cca02633.svg",
topRow: 25,
bottomRow: 29,
parentRowSpace: 10,
type: "BUTTON_WIDGET",
hideCard: false,
animateLoading: true,
parentColumnSpace: 26.421875,
dynamicTriggerPathList: [],
leftColumn: 20,
dynamicBindingPathList: [
{
key: "text",
},
],
text: "{{Button1.text}}",
isDisabled: false,
key: "r6h8y6dc8i",
rightColumn: 36,
isDefaultClickDisabled: true,
widgetId: "l0yem4eh6l",
isVisible: true,
recaptchaType: "V3",
version: 1,
parentId: "0",
renderMode: "CANVAS",
isLoading: false,
buttonVariant: "PRIMARY",
placement: "CENTER",
defaultProps: {},
defaultMetaProps: ["recaptchaToken"],
logBlackList: {},
meta: {},
propertyOverrideDependency: {},
overridingPropertyPaths: {},
reactivePaths: {
recaptchaToken: "TEMPLATE",
text: "TEMPLATE",
tooltip: "TEMPLATE",
googleRecaptchaKey: "TEMPLATE",
recaptchaType: "TEMPLATE",
isVisible: "TEMPLATE",
isDisabled: "TEMPLATE",
animateLoading: "TEMPLATE",
buttonVariant: "TEMPLATE",
placement: "TEMPLATE",
},
triggerPaths: {
onClick: true,
},
validationPaths: {
text: {
type: "TEXT",
},
tooltip: {
type: "TEXT",
},
googleRecaptchaKey: {
type: "TEXT",
},
recaptchaType: {
type: "TEXT",
params: {
allowedValues: ["V3", "V2"],
default: "V3",
},
},
isVisible: {
type: "BOOLEAN",
},
isDisabled: {
type: "BOOLEAN",
},
animateLoading: {
type: "BOOLEAN",
},
buttonVariant: {
type: "TEXT",
params: {
allowedValues: ["PRIMARY", "SECONDARY", "TERTIARY"],
default: "PRIMARY",
},
},
placement: {
type: "TEXT",
params: {
allowedValues: ["START", "BETWEEN", "CENTER"],
default: "CENTER",
},
},
},
ENTITY_TYPE: "WIDGET",
privateWidgets: {},
},
pageList: [
{
pageName: "Page1",
pageId: "6200d1a2b5bfc0392b959cae",
isDefault: true,
isHidden: false,
},
{
pageName: "Page2",
pageId: "621e22cf2b75295c1c165fa6",
isDefault: false,
isHidden: false,
},
{
pageName: "Page3",
pageId: "6220c268c48234070f8ac65a",
isDefault: false,
isHidden: false,
},
],
appsmith: {
user: {
email: "rathod@appsmith.com",
organizationIds: [
"6218a61972ccd9145ec78c57",
"621913df0276eb01d22fec44",
"60caf8edb1e47a1315f0c48f",
"609114fe05c4d35a9f6cbbf2",
],
username: "rathod@appsmith.com",
name: "Rishabh",
commentOnboardingState: "RESOLVED",
role: "engineer",
useCase: "personal project",
enableTelemetry: false,
emptyInstance: false,
accountNonExpired: true,
accountNonLocked: true,
credentialsNonExpired: true,
isAnonymous: false,
isEnabled: true,
isSuperUser: false,
isConfigurable: true,
},
URL: {
fullPath:
"https://dev.appsmith.com/applications/6200d1a2b5bfc0392b959cab/pages/6220c268c48234070f8ac65a/edit?a=b",
host: "dev.appsmith.com",
hostname: "dev.appsmith.com",
queryParams: {
a: "b",
},
protocol: "https:",
pathname:
"/applications/6200d1a2b5bfc0392b959cab/pages/6220c268c48234070f8ac65a/edit",
port: "",
hash: "",
},
store: {
textColor: "#DF7E65",
},
geolocation: {
canBeRequested: true,
},
mode: "EDIT",
ENTITY_TYPE: "APPSMITH",
},
};

View File

@ -27,6 +27,7 @@ describe("evaluateSync", () => {
text: "value",
ENTITY_TYPE: ENTITY_TYPE.WIDGET,
bindingPaths: {},
reactivePaths: {},
triggerPaths: {},
validationPaths: {},
logBlackList: {},

View File

@ -7,23 +7,11 @@ import {
import { WidgetTypeConfigMap } from "utils/WidgetFactory";
import { RenderModes } from "constants/WidgetConstants";
import { PluginType } from "entities/Action";
import DataTreeEvaluator from "workers/DataTreeEvaluator";
import DataTreeEvaluator from "workers/DataTreeEvaluator/DataTreeEvaluator";
import { ValidationTypes } from "constants/WidgetValidation";
import WidgetFactory from "utils/WidgetFactory";
import { generateDataTreeWidget } from "entities/DataTree/dataTreeWidget";
/**
* This function sorts the object's value which is array of string.
*
* @param {Record<string, Array<string>>} data
* @return {*}
*/
const sortObject = (data: Record<string, Array<string>>) => {
Object.entries(data).map(([key, value]) => {
data[key] = value.sort();
});
return data;
};
import { sortObjectWithArray } from "../utils/treeUtils";
const WIDGET_CONFIG_MAP: WidgetTypeConfigMap = {
CONTAINER_WIDGET: {
@ -230,6 +218,7 @@ const BASE_WIDGET: DataTreeWidget = {
parentId: "0",
version: 1,
bindingPaths: {},
reactivePaths: {},
triggerPaths: {},
validationPaths: {},
ENTITY_TYPE: ENTITY_TYPE.WIDGET,
@ -254,7 +243,8 @@ const BASE_ACTION: DataTreeAction = {
data: {},
responseMeta: { isExecutionSuccess: false },
ENTITY_TYPE: ENTITY_TYPE.ACTION,
bindingPaths: {
bindingPaths: {},
reactivePaths: {
isLoading: EvaluationSubstitutionType.TEMPLATE,
data: EvaluationSubstitutionType.TEMPLATE,
},
@ -334,7 +324,7 @@ describe("DataTreeEvaluator", () => {
defaultText: "Default value",
widgetName: "Input1",
type: "INPUT_WIDGET_V2",
bindingPaths: {
reactivePaths: {
defaultText: EvaluationSubstitutionType.TEMPLATE,
isValid: EvaluationSubstitutionType.TEMPLATE,
value: EvaluationSubstitutionType.TEMPLATE,
@ -406,7 +396,7 @@ describe("DataTreeEvaluator", () => {
text: "{{Table1.selectedRow.test}}",
dynamicBindingPathList: [{ key: "text" }],
type: "TEXT_WIDGET",
bindingPaths: {
reactivePaths: {
text: EvaluationSubstitutionType.TEMPLATE,
},
validationPaths: {
@ -424,7 +414,7 @@ describe("DataTreeEvaluator", () => {
expect(evaluation).toHaveProperty("Text2.text", "Label");
expect(evaluation).toHaveProperty("Text3.text", "Label");
expect(sortObject(dependencyMap)).toStrictEqual(dependencyMap);
expect(sortObjectWithArray(dependencyMap)).toStrictEqual(dependencyMap);
});
it("Evaluates a value change in update run", () => {
@ -455,7 +445,9 @@ describe("DataTreeEvaluator", () => {
expect(dataTree).toHaveProperty("Text2.text", "Label");
expect(dataTree).toHaveProperty("Text3.text", "Label 3");
expect(sortObject(updatedDependencyMap)).toStrictEqual(dependencyMap);
expect(sortObjectWithArray(updatedDependencyMap)).toStrictEqual(
dependencyMap,
);
});
it("Overrides with default value", () => {
@ -470,6 +462,13 @@ describe("DataTreeEvaluator", () => {
});
it("Evaluates for value changes in nested diff paths", () => {
const bindingPaths = {
options: EvaluationSubstitutionType.TEMPLATE,
defaultOptionValue: EvaluationSubstitutionType.TEMPLATE,
isRequired: EvaluationSubstitutionType.TEMPLATE,
isVisible: EvaluationSubstitutionType.TEMPLATE,
isDisabled: EvaluationSubstitutionType.TEMPLATE,
};
const updatedUnEvalTree = {
...unEvalTree,
Dropdown2: {
@ -485,12 +484,9 @@ describe("DataTreeEvaluator", () => {
},
],
type: "SELECT_WIDGET",
bindingPaths: {
options: EvaluationSubstitutionType.TEMPLATE,
defaultOptionValue: EvaluationSubstitutionType.TEMPLATE,
isRequired: EvaluationSubstitutionType.TEMPLATE,
isVisible: EvaluationSubstitutionType.TEMPLATE,
isDisabled: EvaluationSubstitutionType.TEMPLATE,
bindingPaths,
reactivePaths: {
...bindingPaths,
isValid: EvaluationSubstitutionType.TEMPLATE,
selectedOption: EvaluationSubstitutionType.TEMPLATE,
selectedOptionValue: EvaluationSubstitutionType.TEMPLATE,
@ -533,7 +529,7 @@ describe("DataTreeEvaluator", () => {
},
]);
expect(sortObject(updatedDependencyMap)).toStrictEqual({
expect(sortObjectWithArray(updatedDependencyMap)).toStrictEqual({
Api1: ["Api1.data"],
...dependencyMap,
"Table1.tableData": ["Api1.data", "Text1.text"],
@ -579,7 +575,7 @@ describe("DataTreeEvaluator", () => {
},
]);
expect(dataTree).toHaveProperty("Text4.text", "Hey");
expect(sortObject(updatedDependencyMap)).toStrictEqual({
expect(sortObjectWithArray(updatedDependencyMap)).toStrictEqual({
Api1: ["Api1.data"],
...dependencyMap,
"Table1.tableData": ["Api1.data", "Text1.text"],
@ -599,8 +595,8 @@ describe("DataTreeEvaluator", () => {
dependencyMap: {
"config.body": ["config.pluginSpecifiedTemplates[0].value"],
},
bindingPaths: {
...BASE_ACTION.bindingPaths,
reactivePaths: {
...BASE_ACTION.reactivePaths,
"config.body": EvaluationSubstitutionType.TEMPLATE,
},
config: {
@ -646,8 +642,8 @@ describe("DataTreeEvaluator", () => {
...updatedTree2,
Api2: {
...updatedTree2.Api2,
bindingPaths: {
...updatedTree2.Api2.bindingPaths,
reactivePaths: {
...updatedTree2.Api2.reactivePaths,
"config.body": EvaluationSubstitutionType.SMART_SUBSTITUTE,
},
config: {

View File

@ -14,7 +14,7 @@ import {
removeFunctions,
validateWidgetProperty,
} from "./evaluationUtils";
import DataTreeEvaluator from "workers/DataTreeEvaluator";
import DataTreeEvaluator from "workers/DataTreeEvaluator/DataTreeEvaluator";
import ReplayEntity from "entities/Replay";
import evaluate, {
evaluateAsync,

View File

@ -41,6 +41,7 @@ const BASE_WIDGET: DataTreeWidget = {
parentId: "0",
version: 1,
bindingPaths: {},
reactivePaths: {},
triggerPaths: {},
validationPaths: {},
ENTITY_TYPE: ENTITY_TYPE.WIDGET,
@ -55,7 +56,7 @@ const testDataTree: Record<string, DataTreeWidget> = {
widgetName: "Text1",
text: "Label",
type: "TEXT_WIDGET",
bindingPaths: {
reactivePaths: {
text: EvaluationSubstitutionType.TEMPLATE,
},
validationPaths: {
@ -68,7 +69,7 @@ const testDataTree: Record<string, DataTreeWidget> = {
text: "{{Text1.text}}",
dynamicBindingPathList: [{ key: "text" }],
type: "TEXT_WIDGET",
bindingPaths: {
reactivePaths: {
text: EvaluationSubstitutionType.TEMPLATE,
},
validationPaths: {
@ -81,7 +82,7 @@ const testDataTree: Record<string, DataTreeWidget> = {
text: "{{Text1.text}}",
dynamicBindingPathList: [{ key: "text" }],
type: "TEXT_WIDGET",
bindingPaths: {
reactivePaths: {
text: EvaluationSubstitutionType.TEMPLATE,
},
validationPaths: {
@ -94,7 +95,7 @@ const testDataTree: Record<string, DataTreeWidget> = {
text: "{{Text1.text}}",
dynamicBindingPathList: [{ key: "text" }],
type: "TEXT_WIDGET",
bindingPaths: {
reactivePaths: {
text: EvaluationSubstitutionType.TEMPLATE,
},
validationPaths: {
@ -198,7 +199,7 @@ describe("privateWidgets", () => {
widgetName: "Text1",
text: "Label",
type: "TEXT_WIDGET",
bindingPaths: {
reactivePaths: {
text: EvaluationSubstitutionType.TEMPLATE,
},
validationPaths: {
@ -212,7 +213,7 @@ describe("privateWidgets", () => {
text: "{{Text1.text}}",
dynamicBindingPathList: [{ key: "text" }],
type: "TEXT_WIDGET",
bindingPaths: {
reactivePaths: {
text: EvaluationSubstitutionType.TEMPLATE,
},
validationPaths: {

View File

@ -96,7 +96,7 @@ export function getEntityNameAndPropertyPath(
//these paths are not required to go through evaluate tree as these are internal properties
const ignorePathsForEvalRegex =
".(bindingPaths|triggerPaths|validationPaths|dynamicBindingPathList)";
".(reactivePaths|bindingPaths|triggerPaths|validationPaths|dynamicBindingPathList)";
//match if paths are part of ignorePathsForEvalRegex
const isUninterestingChangeForDependencyUpdate = (path: string) => {
@ -602,7 +602,7 @@ export const isDynamicLeaf = (unEvalTree: DataTree, propertyPath: string) => {
return false;
const relativePropertyPath = convertPathToString(propPathEls);
return (
relativePropertyPath in entity.bindingPaths ||
relativePropertyPath in entity.reactivePaths ||
(isWidget(entity) && relativePropertyPath in entity.triggerPaths)
);
};
@ -642,14 +642,15 @@ export const updateJSCollectionInDataTree = (
);
}
} else {
const bindingPaths = jsCollection.bindingPaths;
bindingPaths[action.name] = EvaluationSubstitutionType.SMART_SUBSTITUTE;
bindingPaths[`${action.name}.data`] =
const reactivePaths = jsCollection.reactivePaths;
reactivePaths[action.name] =
EvaluationSubstitutionType.SMART_SUBSTITUTE;
reactivePaths[`${action.name}.data`] =
EvaluationSubstitutionType.TEMPLATE;
_.set(
modifiedDataTree,
`${jsCollection.name}.bindingPaths`,
bindingPaths,
`${jsCollection.name}.reactivePaths`,
reactivePaths,
);
const dynamicBindingPathList = jsCollection.dynamicBindingPathList;
dynamicBindingPathList.push({ key: action.name });
@ -697,12 +698,12 @@ export const updateJSCollectionInDataTree = (
(js: ParsedJSSubAction) => js.name === preAction,
);
if (!existed) {
const bindingPaths = jsCollection.bindingPaths;
delete bindingPaths[preAction];
const reactivePaths = jsCollection.reactivePaths;
delete reactivePaths[preAction];
_.set(
modifiedDataTree,
`${jsCollection.name}.bindingPaths`,
bindingPaths,
`${jsCollection.name}.reactivePaths`,
reactivePaths,
);
let dynamicBindingPathList = jsCollection.dynamicBindingPathList;
dynamicBindingPathList = dynamicBindingPathList.filter(
@ -796,12 +797,12 @@ export const removeFunctionsAndVariableJSCollection = (
}
//remove functions
let dynamicBindingPathList = entity.dynamicBindingPathList;
const bindingPaths = entity.bindingPaths;
const reactivePaths = entity.reactivePaths;
const meta = entity.meta;
let dependencyMap = entity.dependencyMap["body"];
for (let i = 0; i < functionsList.length; i++) {
const actionName = functionsList[i];
delete bindingPaths[actionName];
delete reactivePaths[actionName];
delete meta[actionName];
delete modifiedDataTree[`${entity.name}`][`${actionName}`];
dynamicBindingPathList = dynamicBindingPathList.filter(
@ -809,7 +810,7 @@ export const removeFunctionsAndVariableJSCollection = (
);
dependencyMap = dependencyMap.filter((item: any) => item !== actionName);
}
_.set(modifiedDataTree, `${entity.name}.bindingPaths`, bindingPaths);
_.set(modifiedDataTree, `${entity.name}.reactivePaths`, reactivePaths);
_.set(
modifiedDataTree,
`${entity.name}.dynamicBindingPathList`,