diff --git a/app/client/src/actions/metaActions.ts b/app/client/src/actions/metaActions.ts index a97a86e0f7..d604518e19 100644 --- a/app/client/src/actions/metaActions.ts +++ b/app/client/src/actions/metaActions.ts @@ -87,3 +87,14 @@ export const syncUpdateWidgetMetaProperty = ( }, }; }; + +export const resetWidgetsMetaState = ( + widgetIdsToClear: string[], +): ReduxAction<{ widgetIdsToClear: string[] }> => { + return { + type: ReduxActionTypes.RESET_WIDGETS_META_STATE, + payload: { + widgetIdsToClear, + }, + }; +}; diff --git a/app/client/src/ce/constants/ReduxActionConstants.tsx b/app/client/src/ce/constants/ReduxActionConstants.tsx index 5ff049dd09..65a59f222f 100644 --- a/app/client/src/ce/constants/ReduxActionConstants.tsx +++ b/app/client/src/ce/constants/ReduxActionConstants.tsx @@ -394,6 +394,7 @@ export const ReduxActionTypes = { RESET_CHILDREN_WIDGET_META: "RESET_CHILDREN_WIDGET_META", RESET_WIDGET_META: "RESET_WIDGET_META", RESET_WIDGET_META_EVALUATED: "RESET_WIDGET_META_EVALUATED", + RESET_WIDGETS_META_STATE: "RESET_WIDGETS_META_STATE", UPDATE_WIDGET_NAME_INIT: "UPDATE_WIDGET_NAME_INIT", UPDATE_WIDGET_NAME_SUCCESS: "UPDATE_WIDGET_NAME_SUCCESS", FETCH_ACTIONS_FOR_PAGE_INIT: "FETCH_ACTIONS_FOR_PAGE_INIT", diff --git a/app/client/src/ce/workers/Evaluation/evaluationUtils.test.ts b/app/client/src/ce/workers/Evaluation/evaluationUtils.test.ts index 0d16abac7b..632d4c5087 100644 --- a/app/client/src/ce/workers/Evaluation/evaluationUtils.test.ts +++ b/app/client/src/ce/workers/Evaluation/evaluationUtils.test.ts @@ -622,6 +622,7 @@ describe("5. overrideWidgetProperties", () => { propertyPath: "defaultText", value: "abcde", evalMetaUpdates, + isNewWidget: false, }); expect(overwriteObj).toStrictEqual(undefined); @@ -656,6 +657,7 @@ describe("5. overrideWidgetProperties", () => { propertyPath: "meta.text", value: "abcdefg", evalMetaUpdates, + isNewWidget: false, }); expect(overwriteObj).toStrictEqual(undefined); @@ -699,6 +701,7 @@ describe("5. overrideWidgetProperties", () => { propertyPath: "defaultSelectedRow", value: [0, 1], evalMetaUpdates, + isNewWidget: false, }); expect(overwriteObj).toStrictEqual(undefined); @@ -732,6 +735,7 @@ describe("5. overrideWidgetProperties", () => { propertyPath: "meta.selectedRowIndex", value: 0, evalMetaUpdates, + isNewWidget: false, }); expect(overwriteObj).toStrictEqual(undefined); diff --git a/app/client/src/ce/workers/Evaluation/evaluationUtils.ts b/app/client/src/ce/workers/Evaluation/evaluationUtils.ts index 165e4f6ead..b3c9e75f94 100644 --- a/app/client/src/ce/workers/Evaluation/evaluationUtils.ts +++ b/app/client/src/ce/workers/Evaluation/evaluationUtils.ts @@ -18,7 +18,7 @@ import { DataTreeJSAction, } from "entities/DataTree/dataTreeFactory"; -import _, { get, set } from "lodash"; +import _, { find, get, isEmpty, set } from "lodash"; import { WidgetTypeConfigMap } from "utils/WidgetFactory"; import { PluginType } from "entities/Action"; import { klona } from "klona/full"; @@ -737,15 +737,30 @@ export const overrideWidgetProperties = (params: { value: unknown; currentTree: DataTree; evalMetaUpdates: EvalMetaUpdates; + isNewWidget: boolean; }) => { - const { currentTree, entity, evalMetaUpdates, propertyPath, value } = params; + const { + currentTree, + entity, + evalMetaUpdates, + isNewWidget, + propertyPath, + value, + } = params; const clonedValue = klona(value); if (propertyPath in entity.overridingPropertyPaths) { const overridingPropertyPaths = entity.overridingPropertyPaths[propertyPath]; + const pathsNotToOverride = widgetPathsNotToOverride( + isNewWidget, + entity, + propertyPath, + ); + overridingPropertyPaths.forEach((overriddenPropertyPath) => { const overriddenPropertyPathArray = overriddenPropertyPath.split("."); + if (pathsNotToOverride.includes(overriddenPropertyPath)) return; _.set( currentTree, [entity.widgetName, ...overriddenPropertyPathArray], @@ -805,3 +820,43 @@ export const isATriggerPath = ( ) => { return isWidget(entity) && isPathDynamicTrigger(entity, propertyPath); }; + +// Checks if entity newly got added to the unevalTree +export const isNewEntity = (updates: DataTreeDiff[], entityName: string) => { + return !!find(updates, { + event: DataTreeDiffEvent.NEW, + payload: { propertyPath: entityName }, + }); +}; + +export const widgetPathsNotToOverride = ( + isNewWidget: boolean, + entity: DataTreeWidget, + propertyPath: string, +) => { + let pathsNotToOverride: string[] = []; + const overriddenPropertyPaths = entity.overridingPropertyPaths[propertyPath]; + + // To tell whether a widget has pre-existing meta values (although newly added), we stringify its meta object to get rid of undefined values + // An empty parsedMetaObj implies that the widget has no pre-existing meta values. + // MetaWidgets can have pre-existing meta values + const parsedMetaObj = JSON.parse(JSON.stringify(entity.meta)); + + if (isNewWidget && !isEmpty(parsedMetaObj)) { + const overriddenMetaPaths = overriddenPropertyPaths.filter( + (path) => path.split(".")[0] === "meta", + ); + // If widget is newly added but has pre-existing meta values, this meta values take precedence and should not be overridden + pathsNotToOverride = [...overriddenMetaPaths]; + // paths which these meta values override should also not get overridden + overriddenMetaPaths.forEach((path) => { + if (entity.overridingPropertyPaths.hasOwnProperty(path)) { + pathsNotToOverride = [ + ...pathsNotToOverride, + ...entity.overridingPropertyPaths[path], + ]; + } + }); + } + return pathsNotToOverride; +}; diff --git a/app/client/src/entities/DataTree/dataTreeWidget.ts b/app/client/src/entities/DataTree/dataTreeWidget.ts index a2e41a7ec0..e076bed41e 100644 --- a/app/client/src/entities/DataTree/dataTreeWidget.ts +++ b/app/client/src/entities/DataTree/dataTreeWidget.ts @@ -78,6 +78,7 @@ const generateDataTreeWidgetWithoutMeta = ( blockedDerivedProps[propertyName] = true; }); + // Map of properties that can both be overridden by meta and default values const overridingMetaPropsMap: Record = {}; Object.entries(defaultProps).forEach( @@ -214,11 +215,12 @@ export const generateDataTreeWidget = ( } = generateDataTreeWidgetWithoutMetaMemoized(widget); const overridingMetaProps: Record = {}; - // overridingMetaProps has all meta property value either from metaReducer or default set by widget whose dependent property also has default property. - Object.entries(defaultMetaProps).forEach(([key, value]) => { + // overridingMetaProps maps properties that can be overriden by either default values or meta changes to initial values. + // initial value is set to metaProps value or undefined (set to undefined to ensure that its path is present in the unevalTree). + Object.entries(defaultMetaProps).forEach(([key]) => { if (overridingMetaPropsMap[key]) { overridingMetaProps[key] = - key in widgetMetaProps ? widgetMetaProps[key] : value; + key in widgetMetaProps ? widgetMetaProps[key] : undefined; } }); diff --git a/app/client/src/entities/DataTree/types.ts b/app/client/src/entities/DataTree/types.ts index 18ff257a11..76d4437a8a 100644 --- a/app/client/src/entities/DataTree/types.ts +++ b/app/client/src/entities/DataTree/types.ts @@ -102,15 +102,16 @@ export enum OverridingPropertyType { META = "META", DEFAULT = "DEFAULT", } +export interface overrideDependency { + DEFAULT: string; + META: string; +} /** * Map of property name as key and value as object with defaultPropertyName and metaPropertyName which it depends on. */ export type PropertyOverrideDependency = Record< string, - { - DEFAULT: string | undefined; - META: string | undefined; - } + Partial >; export type WidgetConfig = { diff --git a/app/client/src/entities/DataTree/utils.ts b/app/client/src/entities/DataTree/utils.ts index 4989849aaa..a87ff6d88b 100644 --- a/app/client/src/entities/DataTree/utils.ts +++ b/app/client/src/entities/DataTree/utils.ts @@ -1,3 +1,4 @@ +import { DataTreeWidget } from "./dataTreeFactory"; import { PropertyOverrideDependency, OverridingPropertyPaths, @@ -57,3 +58,18 @@ export const setOverridingProperty = ({ overridingPropertyPaths[defaultPropertyName].push(overridingPropertyKey); } }; + +export const isMetaWidgetTemplate = (widget: DataTreeWidget) => { + return !!widget.siblingMetaWidgets; +}; + +export const isWidgetDefaultPropertyPath = ( + widget: DataTreeWidget, + propertyPath: string, +) => { + for (const property of Object.keys(widget.propertyOverrideDependency)) { + const overrideDependency = widget.propertyOverrideDependency[property]; + if (overrideDependency.DEFAULT === propertyPath) return true; + } + return false; +}; diff --git a/app/client/src/reducers/entityReducers/metaReducer/index.ts b/app/client/src/reducers/entityReducers/metaReducer/index.ts index 018fac23b0..a07ee37b3b 100644 --- a/app/client/src/reducers/entityReducers/metaReducer/index.ts +++ b/app/client/src/reducers/entityReducers/metaReducer/index.ts @@ -108,6 +108,18 @@ export const metaReducer = createReducer(initialState, { } return state; }, + [ReduxActionTypes.RESET_WIDGETS_META_STATE]: ( + state: MetaState, + action: ReduxAction<{ widgetIdsToClear: string[] }>, + ) => { + const next = { ...state }; + for (const metaWidgetId of action.payload.widgetIdsToClear) { + if (metaWidgetId && next[metaWidgetId]) { + delete next[metaWidgetId]; + } + } + return next; + }, [ReduxActionTypes.FETCH_PAGE_SUCCESS]: () => { return initialState; }, diff --git a/app/client/src/sagas/EvaluationsSaga.ts b/app/client/src/sagas/EvaluationsSaga.ts index 8b361c28ef..107451724f 100644 --- a/app/client/src/sagas/EvaluationsSaga.ts +++ b/app/client/src/sagas/EvaluationsSaga.ts @@ -58,7 +58,7 @@ import { import { JSAction } from "entities/JSCollection"; import { getAppMode } from "selectors/applicationSelectors"; import { APP_MODE } from "entities/App"; -import { get, isUndefined } from "lodash"; +import { difference, get, isEmpty, isUndefined } from "lodash"; import { setEvaluatedArgument, setEvaluatedSnippet, @@ -94,7 +94,7 @@ import { Channel } from "redux-saga"; import { FormEvaluationState } from "reducers/evaluationReducers/formEvaluationReducer"; import { FormEvalActionPayload } from "./FormEvaluationSaga"; import { getSelectedAppTheme } from "selectors/appThemingSelectors"; -import { updateMetaState } from "actions/metaActions"; +import { resetWidgetsMetaState, updateMetaState } from "actions/metaActions"; import { getAllActionValidationConfig } from "selectors/entitiesSelector"; import { DataTree, @@ -184,6 +184,7 @@ export function* evaluateTreeSaga( userLogs, unEvalUpdates, isCreateFirstTree = false, + staleMetaIds, } = workerResponse; PerformanceTracker.stopAsyncTracking( PerformanceTransactionName.DATA_TREE_EVALUATION, @@ -194,6 +195,11 @@ export function* evaluateTreeSaga( const oldDataTree: DataTree = yield select(getDataTree); const updates = diff(oldDataTree, dataTree) || []; + // Replace empty object below with list of current metaWidgets present in the viewport + const hiddenStaleMetaIds = difference(staleMetaIds, Object.keys({})); + if (!isEmpty(hiddenStaleMetaIds)) { + yield put(resetWidgetsMetaState(hiddenStaleMetaIds)); + } yield put(setEvaluatedTree(updates)); PerformanceTracker.stopAsyncTracking( diff --git a/app/client/src/workers/Evaluation/__tests__/evaluation.test.ts b/app/client/src/workers/Evaluation/__tests__/evaluation.test.ts index aed6242a87..eab575c4af 100644 --- a/app/client/src/workers/Evaluation/__tests__/evaluation.test.ts +++ b/app/client/src/workers/Evaluation/__tests__/evaluation.test.ts @@ -441,9 +441,13 @@ describe("DataTreeEvaluator", () => { const { evalOrder, nonDynamicFieldValidationOrder, + unEvalUpdates, } = evaluator.setupUpdateTree(createUnEvalTreeForEval(updatedUnEvalTree)); - - evaluator.evalAndValidateSubTree(evalOrder, nonDynamicFieldValidationOrder); + evaluator.evalAndValidateSubTree( + evalOrder, + nonDynamicFieldValidationOrder, + unEvalUpdates, + ); const dataTree = evaluator.evalTree; expect(dataTree).toHaveProperty("Text2.text", "Hey there"); expect(dataTree).toHaveProperty("Text3.text", "Hey there"); @@ -460,8 +464,13 @@ describe("DataTreeEvaluator", () => { const { evalOrder, nonDynamicFieldValidationOrder, + unEvalUpdates, } = evaluator.setupUpdateTree(createUnEvalTreeForEval(updatedUnEvalTree)); - evaluator.evalAndValidateSubTree(evalOrder, nonDynamicFieldValidationOrder); + evaluator.evalAndValidateSubTree( + evalOrder, + nonDynamicFieldValidationOrder, + unEvalUpdates, + ); const dataTree = evaluator.evalTree; const updatedDependencyMap = evaluator.dependencyMap; @@ -482,8 +491,13 @@ describe("DataTreeEvaluator", () => { const { evalOrder, nonDynamicFieldValidationOrder, + unEvalUpdates, } = evaluator.setupUpdateTree(createUnEvalTreeForEval(updatedUnEvalTree)); - evaluator.evalAndValidateSubTree(evalOrder, nonDynamicFieldValidationOrder); + evaluator.evalAndValidateSubTree( + evalOrder, + nonDynamicFieldValidationOrder, + unEvalUpdates, + ); const dataTree = evaluator.evalTree; expect(dataTree).toHaveProperty("Input1.text", "Default value"); }); @@ -526,8 +540,13 @@ describe("DataTreeEvaluator", () => { const { evalOrder, nonDynamicFieldValidationOrder, + unEvalUpdates, } = evaluator.setupUpdateTree(createUnEvalTreeForEval(updatedUnEvalTree)); - evaluator.evalAndValidateSubTree(evalOrder, nonDynamicFieldValidationOrder); + evaluator.evalAndValidateSubTree( + evalOrder, + nonDynamicFieldValidationOrder, + unEvalUpdates, + ); const dataTree = evaluator.evalTree; expect(dataTree).toHaveProperty("Dropdown2.options.0.label", "newValue"); }); @@ -551,8 +570,13 @@ describe("DataTreeEvaluator", () => { const { evalOrder, nonDynamicFieldValidationOrder, + unEvalUpdates, } = evaluator.setupUpdateTree(createUnEvalTreeForEval(updatedUnEvalTree)); - evaluator.evalAndValidateSubTree(evalOrder, nonDynamicFieldValidationOrder); + evaluator.evalAndValidateSubTree( + evalOrder, + nonDynamicFieldValidationOrder, + unEvalUpdates, + ); const dataTree = evaluator.evalTree; const updatedDependencyMap = evaluator.dependencyMap; expect(dataTree).toHaveProperty("Table1.tableData", [ @@ -601,8 +625,13 @@ describe("DataTreeEvaluator", () => { const { evalOrder, nonDynamicFieldValidationOrder, + unEvalUpdates, } = evaluator.setupUpdateTree(createUnEvalTreeForEval(updatedUnEvalTree)); - evaluator.evalAndValidateSubTree(evalOrder, nonDynamicFieldValidationOrder); + evaluator.evalAndValidateSubTree( + evalOrder, + nonDynamicFieldValidationOrder, + unEvalUpdates, + ); const dataTree = evaluator.evalTree; const updatedDependencyMap = evaluator.dependencyMap; expect(dataTree).toHaveProperty("Table1.tableData", [ @@ -654,12 +683,14 @@ describe("DataTreeEvaluator", () => { const { evalOrder, nonDynamicFieldValidationOrder: nonDynamicFieldValidationOrder2, + unEvalUpdates, } = evaluator.setupUpdateTree( createUnEvalTreeForEval((updatedTree1 as unknown) as UnEvalTree), ); evaluator.evalAndValidateSubTree( evalOrder, nonDynamicFieldValidationOrder2, + unEvalUpdates, ); expect(evaluator.dependencyMap["Api2.config.body"]).toStrictEqual([ "Api2.config.pluginSpecifiedTemplates[0].value", @@ -683,12 +714,14 @@ describe("DataTreeEvaluator", () => { const { evalOrder: newEvalOrder, nonDynamicFieldValidationOrder, + unEvalUpdates: unEvalUpdates2, } = evaluator.setupUpdateTree( createUnEvalTreeForEval((updatedTree2 as unknown) as UnEvalTree), ); evaluator.evalAndValidateSubTree( newEvalOrder, nonDynamicFieldValidationOrder, + unEvalUpdates2, ); const dataTree = evaluator.evalTree; expect(evaluator.dependencyMap["Api2.config.body"]).toStrictEqual([ @@ -718,12 +751,14 @@ describe("DataTreeEvaluator", () => { const { evalOrder: newEvalOrder2, nonDynamicFieldValidationOrder: nonDynamicFieldValidationOrder3, + unEvalUpdates: unEvalUpdates3, } = evaluator.setupUpdateTree( createUnEvalTreeForEval((updatedTree3 as unknown) as UnEvalTree), ); evaluator.evalAndValidateSubTree( newEvalOrder2, nonDynamicFieldValidationOrder3, + unEvalUpdates3, ); const dataTree3 = evaluator.evalTree; expect(evaluator.dependencyMap["Api2.config.body"]).toStrictEqual([ diff --git a/app/client/src/workers/Evaluation/handlers/evalTree.ts b/app/client/src/workers/Evaluation/handlers/evalTree.ts index 53837ee208..cae83729d0 100644 --- a/app/client/src/workers/Evaluation/handlers/evalTree.ts +++ b/app/client/src/workers/Evaluation/handlers/evalTree.ts @@ -44,6 +44,7 @@ export default function(request: EvalWorkerSyncRequest) { let userLogs: UserLogObject[] = []; let dependencies: DependencyMap = {}; let evalMetaUpdates: EvalMetaUpdates = []; + let staleMetaIds: string[] = []; const { allActionValidationConfig, @@ -86,6 +87,7 @@ export default function(request: EvalWorkerSyncRequest) { dataTree = makeEntityConfigsAsObjProperties(dataTreeResponse.evalTree, { evalProps: dataTreeEvaluator.evalProps, }); + staleMetaIds = dataTreeResponse.staleMetaIds; } else if (dataTreeEvaluator.hasCyclicalDependency || forceEvaluation) { if (dataTreeEvaluator && !isEmpty(allActionValidationConfig)) { //allActionValidationConfigs may not be set in dataTreeEvaluatior. Therefore, set it explicitly via setter method @@ -125,6 +127,7 @@ export default function(request: EvalWorkerSyncRequest) { dataTree = makeEntityConfigsAsObjProperties(dataTreeResponse.evalTree, { evalProps: dataTreeEvaluator.evalProps, }); + staleMetaIds = dataTreeResponse.staleMetaIds; } else { if (dataTreeEvaluator && !isEmpty(allActionValidationConfig)) { dataTreeEvaluator.setAllActionValidationConfig( @@ -156,6 +159,7 @@ export default function(request: EvalWorkerSyncRequest) { const updateResponse = dataTreeEvaluator.evalAndValidateSubTree( evalOrder, nonDynamicFieldValidationOrder, + unEvalUpdates, ); dataTree = makeEntityConfigsAsObjProperties(dataTreeEvaluator.evalTree, { evalProps: dataTreeEvaluator.evalProps, @@ -163,6 +167,7 @@ export default function(request: EvalWorkerSyncRequest) { evalMetaUpdates = JSON.parse( JSON.stringify(updateResponse.evalMetaUpdates), ); + staleMetaIds = updateResponse.staleMetaIds; } dataTreeEvaluator = dataTreeEvaluator as DataTreeEvaluator; dependencies = dataTreeEvaluator.inverseDependencyMap; @@ -200,7 +205,7 @@ export default function(request: EvalWorkerSyncRequest) { unEvalUpdates = []; } - return { + const evalTreeResponse: EvalTreeResponseData = { dataTree, dependencies, errors, @@ -211,7 +216,10 @@ export default function(request: EvalWorkerSyncRequest) { userLogs, unEvalUpdates, isCreateFirstTree, - } as EvalTreeResponseData; + staleMetaIds, + }; + + return evalTreeResponse; } export function clearCache() { diff --git a/app/client/src/workers/Evaluation/handlers/evalTrigger.ts b/app/client/src/workers/Evaluation/handlers/evalTrigger.ts index 7d49f0345a..1b9d487dd3 100644 --- a/app/client/src/workers/Evaluation/handlers/evalTrigger.ts +++ b/app/client/src/workers/Evaluation/handlers/evalTrigger.ts @@ -19,10 +19,12 @@ export default async function(request: EvalWorkerASyncRequest) { const { evalOrder, nonDynamicFieldValidationOrder, + unEvalUpdates, } = dataTreeEvaluator.setupUpdateTree(unEvalTree); dataTreeEvaluator.evalAndValidateSubTree( evalOrder, nonDynamicFieldValidationOrder, + unEvalUpdates, ); const evalTree = dataTreeEvaluator.evalTree; const resolvedFunctions = dataTreeEvaluator.resolvedFunctions; diff --git a/app/client/src/workers/Evaluation/types.ts b/app/client/src/workers/Evaluation/types.ts index 57692e5c66..9105e45038 100644 --- a/app/client/src/workers/Evaluation/types.ts +++ b/app/client/src/workers/Evaluation/types.ts @@ -45,4 +45,5 @@ export interface EvalTreeResponseData { userLogs: UserLogObject[]; unEvalUpdates: DataTreeDiff[]; isCreateFirstTree: boolean; + staleMetaIds: string[]; } diff --git a/app/client/src/workers/common/DataTreeEvaluator/index.ts b/app/client/src/workers/common/DataTreeEvaluator/index.ts index d2a0012951..94ae0c4fb6 100644 --- a/app/client/src/workers/common/DataTreeEvaluator/index.ts +++ b/app/client/src/workers/common/DataTreeEvaluator/index.ts @@ -40,6 +40,7 @@ import { overrideWidgetProperties, getAllPaths, isValidEntity, + isNewEntity, } from "@appsmith/workers/Evaluation/evaluationUtils"; import { difference, @@ -99,6 +100,10 @@ import { validateAndParseWidgetProperty, } from "./validationUtils"; import { errorModifier } from "workers/Evaluation/errorModifier"; +import { + isMetaWidgetTemplate, + isWidgetDefaultPropertyPath, +} from "entities/DataTree/utils"; type SortedDependencies = Array; export type EvalProps = { @@ -288,10 +293,11 @@ export default class DataTreeEvaluator { evalAndValidateFirstTree(): { evalTree: DataTree; evalMetaUpdates: EvalMetaUpdates; + staleMetaIds: string[]; } { const evaluationStartTime = performance.now(); // Evaluate - const { evalMetaUpdates, evaluatedTree } = this.evaluateTree( + const { evalMetaUpdates, evaluatedTree, staleMetaIds } = this.evaluateTree( this.oldUnEvalTree, this.resolvedFunctions, this.sortedDependencies, @@ -322,6 +328,7 @@ export default class DataTreeEvaluator { return { evalTree: this.getEvalTree(), evalMetaUpdates, + staleMetaIds, }; } @@ -522,18 +529,21 @@ export default class DataTreeEvaluator { evalAndValidateSubTree( evaluationOrder: string[], nonDynamicFieldValidationOrder: string[], + unevalUpdates: DataTreeDiff[], ): { evalMetaUpdates: EvalMetaUpdates; + staleMetaIds: string[]; } { const evaluationStartTime = performance.now(); const { evalMetaUpdates, evaluatedTree: newEvalTree, + staleMetaIds, } = this.evaluateTree( this.evalTree, this.resolvedFunctions, evaluationOrder, - { skipRevalidation: false }, + { skipRevalidation: false, isFirstTree: false, unevalUpdates }, ); const evaluationEndTime = performance.now(); const reValidateStartTime = performance.now(); @@ -553,6 +563,7 @@ export default class DataTreeEvaluator { this.logs.push({ timeTakenForEvalAndValidateSubTree }); return { evalMetaUpdates, + staleMetaIds, }; } @@ -656,16 +667,21 @@ export default class DataTreeEvaluator { oldUnevalTree: DataTree, resolvedFunctions: Record, sortedDependencies: Array, - option = { skipRevalidation: true }, + options: { + skipRevalidation: boolean; + isFirstTree: boolean; + unevalUpdates: DataTreeDiff[]; + } = { skipRevalidation: true, isFirstTree: true, unevalUpdates: [] }, ): { evaluatedTree: DataTree; evalMetaUpdates: EvalMetaUpdates; + staleMetaIds: string[]; } { const tree = klona(oldUnevalTree); - errorModifier.updateAsyncFunctions(tree); - const evalMetaUpdates: EvalMetaUpdates = []; + const { isFirstTree, skipRevalidation, unevalUpdates } = options; + let staleMetaIds: string[] = []; try { const evaluatedTree = sortedDependencies.reduce( (currentTree: DataTree, fullPropertyPath: string) => { @@ -725,6 +741,8 @@ export default class DataTreeEvaluator { evalPropertyValue = unEvalPropertyValue; } if (isWidget(entity) && !isATriggerPath) { + const isNewWidget = + isFirstTree || isNewEntity(unevalUpdates, entityName); if (propertyPath) { const parsedValue = validateAndParseWidgetProperty({ fullPropertyPath, @@ -743,15 +761,19 @@ export default class DataTreeEvaluator { parsedValue, propertyPath, evalPropertyValue, + isNewWidget, }); - if (!option.skipRevalidation) { + if (!skipRevalidation) { this.reValidateWidgetDependentProperty({ fullPropertyPath, widget: entity, currentTree, }); } + staleMetaIds = staleMetaIds.concat( + this.getStaleMetaStateIds(entity, propertyPath), + ); return currentTree; } @@ -818,13 +840,13 @@ export default class DataTreeEvaluator { }, tree, ); - return { evaluatedTree, evalMetaUpdates }; + return { evaluatedTree, evalMetaUpdates, staleMetaIds }; } catch (error) { this.errors.push({ type: EvalErrorTypes.EVAL_TREE_ERROR, message: (error as Error).message, }); - return { evaluatedTree: tree, evalMetaUpdates }; + return { evaluatedTree: tree, evalMetaUpdates, staleMetaIds }; } } @@ -1073,6 +1095,7 @@ export default class DataTreeEvaluator { evalMetaUpdates, evalPropertyValue, fullPropertyPath, + isNewWidget, parsedValue, propertyPath, }: { @@ -1080,6 +1103,7 @@ export default class DataTreeEvaluator { entity: DataTreeWidget; evalMetaUpdates: EvalMetaUpdates; fullPropertyPath: string; + isNewWidget: boolean; parsedValue: unknown; propertyPath: string; evalPropertyValue: unknown; @@ -1090,6 +1114,7 @@ export default class DataTreeEvaluator { value: parsedValue, currentTree, evalMetaUpdates, + isNewWidget, }); if (overwriteObj && overwriteObj.overwriteParsedValue) { @@ -1370,6 +1395,13 @@ export default class DataTreeEvaluator { ); }); } + // When a default value changes, meta values of metaWidgets not present in the unevalTree becomes stale + getStaleMetaStateIds(entity: DataTreeWidget, propertyPath: string) { + return isWidgetDefaultPropertyPath(entity, propertyPath) && + isMetaWidgetTemplate(entity) + ? (entity.siblingMetaWidgets as string[]) + : []; + } clearErrors() { this.errors = []; diff --git a/app/client/src/workers/common/DataTreeEvaluator/test.ts b/app/client/src/workers/common/DataTreeEvaluator/test.ts index a2de526969..475056cb2d 100644 --- a/app/client/src/workers/common/DataTreeEvaluator/test.ts +++ b/app/client/src/workers/common/DataTreeEvaluator/test.ts @@ -146,12 +146,14 @@ describe("DataTreeEvaluator", () => { const { evalOrder, nonDynamicFieldValidationOrder, + unEvalUpdates, } = dataTreeEvaluator.setupUpdateTree( (unEvalTree as unknown) as DataTree, ); dataTreeEvaluator.evalAndValidateSubTree( evalOrder, nonDynamicFieldValidationOrder, + unEvalUpdates, ); expect(dataTreeEvaluator.dependencyMap).toStrictEqual({ @@ -240,12 +242,14 @@ describe("DataTreeEvaluator", () => { const { evalOrder, nonDynamicFieldValidationOrder: nonDynamicFieldValidationOrder1, + unEvalUpdates, } = dataTreeEvaluator.setupUpdateTree( arrayAccessorCyclicDependency.apiSuccessUnEvalTree, ); dataTreeEvaluator.evalAndValidateSubTree( evalOrder, nonDynamicFieldValidationOrder1, + unEvalUpdates, ); expect(dataTreeEvaluator.dependencyMap["Api1"]).toStrictEqual([ "Api1.data", @@ -264,12 +268,14 @@ describe("DataTreeEvaluator", () => { const { evalOrder: order, nonDynamicFieldValidationOrder: nonDynamicFieldValidationOrder2, + unEvalUpdates: unEvalUpdates2, } = dataTreeEvaluator.setupUpdateTree( arrayAccessorCyclicDependency.apiFailureUnEvalTree, ); dataTreeEvaluator.evalAndValidateSubTree( order, nonDynamicFieldValidationOrder2, + unEvalUpdates2, ); expect(dataTreeEvaluator.dependencyMap["Api1"]).toStrictEqual([ @@ -293,24 +299,28 @@ describe("DataTreeEvaluator", () => { const { evalOrder: order1, nonDynamicFieldValidationOrder: nonDynamicFieldValidationOrder3, + unEvalUpdates, } = dataTreeEvaluator.setupUpdateTree( arrayAccessorCyclicDependency.apiSuccessUnEvalTree, ); dataTreeEvaluator.evalAndValidateSubTree( order1, nonDynamicFieldValidationOrder3, + unEvalUpdates, ); // success: response -> [{...}, {...}] const { evalOrder: order2, nonDynamicFieldValidationOrder: nonDynamicFieldValidationOrder4, + unEvalUpdates: unEvalUpdates2, } = dataTreeEvaluator.setupUpdateTree( arrayAccessorCyclicDependency.apiSuccessUnEvalTree2, ); dataTreeEvaluator.evalAndValidateSubTree( order2, nonDynamicFieldValidationOrder4, + unEvalUpdates2, ); expect(dataTreeEvaluator.dependencyMap["Api1"]).toStrictEqual([ @@ -333,12 +343,14 @@ describe("DataTreeEvaluator", () => { const { evalOrder: order, nonDynamicFieldValidationOrder: nonDynamicFieldValidationOrder5, + unEvalUpdates, } = dataTreeEvaluator.setupUpdateTree( nestedArrayAccessorCyclicDependency.apiSuccessUnEvalTree, ); dataTreeEvaluator.evalAndValidateSubTree( order, nonDynamicFieldValidationOrder5, + unEvalUpdates, ); expect(dataTreeEvaluator.dependencyMap["Api1"]).toStrictEqual([ "Api1.data", @@ -360,12 +372,14 @@ describe("DataTreeEvaluator", () => { const { evalOrder: order1, nonDynamicFieldValidationOrder, + unEvalUpdates: unEvalUpdates2, } = dataTreeEvaluator.setupUpdateTree( nestedArrayAccessorCyclicDependency.apiFailureUnEvalTree, ); dataTreeEvaluator.evalAndValidateSubTree( order1, nonDynamicFieldValidationOrder, + unEvalUpdates2, ); expect(dataTreeEvaluator.dependencyMap["Api1"]).toStrictEqual([ "Api1.data", @@ -391,24 +405,28 @@ describe("DataTreeEvaluator", () => { const { evalOrder: order, nonDynamicFieldValidationOrder, + unEvalUpdates, } = dataTreeEvaluator.setupUpdateTree( nestedArrayAccessorCyclicDependency.apiSuccessUnEvalTree, ); dataTreeEvaluator.evalAndValidateSubTree( order, nonDynamicFieldValidationOrder, + unEvalUpdates, ); // success: response -> [ [{...}, {...}, {...}], [{...}, {...}, {...}] ] const { evalOrder: order1, nonDynamicFieldValidationOrder: nonDynamicFieldValidationOrder2, + unEvalUpdates: unEvalUpdates2, } = dataTreeEvaluator.setupUpdateTree( nestedArrayAccessorCyclicDependency.apiSuccessUnEvalTree2, ); dataTreeEvaluator.evalAndValidateSubTree( order1, nonDynamicFieldValidationOrder2, + unEvalUpdates2, ); expect(dataTreeEvaluator.dependencyMap["Api1"]).toStrictEqual([ @@ -430,24 +448,28 @@ describe("DataTreeEvaluator", () => { const { evalOrder: order, nonDynamicFieldValidationOrder: nonDynamicFieldValidationOrder2, + unEvalUpdates, } = dataTreeEvaluator.setupUpdateTree( nestedArrayAccessorCyclicDependency.apiSuccessUnEvalTree, ); dataTreeEvaluator.evalAndValidateSubTree( order, nonDynamicFieldValidationOrder2, + unEvalUpdates, ); // success: response -> [ [{...}, {...}, {...}], [{...}, {...}, {...}], [] ] const { evalOrder: order1, nonDynamicFieldValidationOrder, + unEvalUpdates: unEvalUpdates2, } = dataTreeEvaluator.setupUpdateTree( nestedArrayAccessorCyclicDependency.apiSuccessUnEvalTree3, ); dataTreeEvaluator.evalAndValidateSubTree( order1, nonDynamicFieldValidationOrder, + unEvalUpdates2, ); expect(dataTreeEvaluator.dependencyMap["Api1"]).toStrictEqual([ "Api1.data", @@ -487,10 +509,12 @@ describe("DataTreeEvaluator", () => { const { evalOrder, nonDynamicFieldValidationOrder: nonDynamicFieldValidationOrder2, + unEvalUpdates, } = dataTreeEvaluator.setupUpdateTree(newUnEvalTree); dataTreeEvaluator.evalAndValidateSubTree( evalOrder, nonDynamicFieldValidationOrder2, + unEvalUpdates, ); expect(dataTreeEvaluator.triggerFieldDependencyMap).toEqual({ "Button3.onClick": ["Api1.run", "Button2.text"], @@ -503,10 +527,12 @@ describe("DataTreeEvaluator", () => { const { evalOrder: order1, nonDynamicFieldValidationOrder: nonDynamicFieldValidationOrder3, + unEvalUpdates: unEvalUpdates2, } = dataTreeEvaluator.setupUpdateTree(newUnEvalTree); dataTreeEvaluator.evalAndValidateSubTree( order1, nonDynamicFieldValidationOrder3, + unEvalUpdates2, ); expect(dataTreeEvaluator.triggerFieldDependencyMap).toEqual({ "Button3.onClick": ["Api1.run", "Button2.text", "Api2.run"], @@ -521,10 +547,12 @@ describe("DataTreeEvaluator", () => { const { evalOrder: order2, nonDynamicFieldValidationOrder, + unEvalUpdates: unEvalUpdates3, } = dataTreeEvaluator.setupUpdateTree(newUnEvalTree); dataTreeEvaluator.evalAndValidateSubTree( order2, nonDynamicFieldValidationOrder, + unEvalUpdates3, ); // delete Button2 @@ -532,10 +560,12 @@ describe("DataTreeEvaluator", () => { const { evalOrder: order3, nonDynamicFieldValidationOrder: nonDynamicFieldValidationOrder4, + unEvalUpdates: unEvalUpdates4, } = dataTreeEvaluator.setupUpdateTree(newUnEvalTree); dataTreeEvaluator.evalAndValidateSubTree( order3, nonDynamicFieldValidationOrder4, + unEvalUpdates4, ); expect(dataTreeEvaluator.triggerFieldDependencyMap).toEqual({ diff --git a/app/client/src/workers/common/DependencyMap/test.ts b/app/client/src/workers/common/DependencyMap/test.ts index e3758586c0..2f7e9cf23b 100644 --- a/app/client/src/workers/common/DependencyMap/test.ts +++ b/app/client/src/workers/common/DependencyMap/test.ts @@ -54,10 +54,12 @@ describe("test validationDependencyMap", () => { const { evalOrder, nonDynamicFieldValidationOrder, + unEvalUpdates, } = dataTreeEvaluator.setupUpdateTree((unEvalTree as unknown) as DataTree); dataTreeEvaluator.evalAndValidateSubTree( evalOrder, nonDynamicFieldValidationOrder, + unEvalUpdates, ); expect(dataTreeEvaluator.validationDependencyMap).toStrictEqual({});