feat: meta rehydration (#19683)

The introduction of MetaWidgets in
https://github.com/appsmithorg/appsmith/pull/15839 introduces a scenario
where widgets are newly added to the unevalTree but already have defined
meta values. These previously defined meta values have higher priority
and should not get overridden by default values.

Fixes https://github.com/appsmithorg/appsmith/issues/16926


## Type of change

- Bug fix (non-breaking change which fixes an issue)
- New feature (non-breaking change which adds functionality)


## How Has This Been Tested?
> Please describe the tests that you ran to verify your changes. Provide
instructions, so we can reproduce.
> Please also list any relevant details for your test configuration.
> Delete anything that is not important

- Manual
- Jest
- Cypress

### Test Plan
> Add Testsmith test cases links that relate to this PR

### Issues raised during DP testing
> Link issues raised during DP testing for better visiblity and tracking
(copy link from comments dropped on this PR)


## Checklist:
### Dev activity
- [ ] My code follows the style guidelines of this project
- [ ] I have performed a self-review of my own code
- [ ] I have commented my code, particularly in hard-to-understand areas
- [ ] I have made corresponding changes to the documentation
- [ ] My changes generate no new warnings
- [ ] I have added tests that prove my fix is effective or that my
feature works
- [ ] New and existing unit tests pass locally with my changes
- [ ] PR is being merged under a feature flag


### QA activity:
- [ ] Test plan has been approved by relevant developers
- [ ] Test plan has been peer reviewed by QA
- [ ] Cypress test cases have been added and approved by either SDET or
manual QA
- [ ] Organized project review call with relevant stakeholders after
Round 1/2 of QA
- [ ] Added Test Plan Approved label after reveiwing all Cypress test
This commit is contained in:
Favour Ohanekwu 2023-01-13 19:29:03 +01:00 committed by GitHub
parent e268022ab4
commit 5231d307b8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 246 additions and 28 deletions

View File

@ -87,3 +87,14 @@ export const syncUpdateWidgetMetaProperty = (
},
};
};
export const resetWidgetsMetaState = (
widgetIdsToClear: string[],
): ReduxAction<{ widgetIdsToClear: string[] }> => {
return {
type: ReduxActionTypes.RESET_WIDGETS_META_STATE,
payload: {
widgetIdsToClear,
},
};
};

View File

@ -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",

View File

@ -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);

View File

@ -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;
};

View File

@ -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<string, boolean> = {};
Object.entries(defaultProps).forEach(
@ -214,11 +215,12 @@ export const generateDataTreeWidget = (
} = generateDataTreeWidgetWithoutMetaMemoized(widget);
const overridingMetaProps: Record<string, unknown> = {};
// 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;
}
});

View File

@ -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<overrideDependency>
>;
export type WidgetConfig = {

View File

@ -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;
};

View File

@ -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;
},

View File

@ -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(

View File

@ -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([

View File

@ -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() {

View File

@ -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;

View File

@ -45,4 +45,5 @@ export interface EvalTreeResponseData {
userLogs: UserLogObject[];
unEvalUpdates: DataTreeDiff[];
isCreateFirstTree: boolean;
staleMetaIds: string[];
}

View File

@ -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<string>;
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<string, any>,
sortedDependencies: Array<string>,
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 = [];

View File

@ -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({

View File

@ -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({});