From d332f782b612fb3457c2455fc99f9127a2992480 Mon Sep 17 00:00:00 2001 From: Diljit VJ Date: Thu, 1 May 2025 13:34:44 +0530 Subject: [PATCH 1/3] fix: revert app computation cache --- app/client/src/sagas/EvaluationsSaga.ts | 20 - .../workers/Evaluation/handlers/evalTree.ts | 3 - app/client/src/workers/Evaluation/types.ts | 2 - .../AppComputationCache.test.ts | 732 ------------------ .../common/AppComputationCache/index.ts | 350 --------- .../common/AppComputationCache/types.ts | 24 - .../workers/common/DataTreeEvaluator/index.ts | 23 +- .../src/workers/common/DependencyMap/index.ts | 94 +-- 8 files changed, 19 insertions(+), 1229 deletions(-) delete mode 100644 app/client/src/workers/common/AppComputationCache/AppComputationCache.test.ts delete mode 100644 app/client/src/workers/common/AppComputationCache/index.ts delete mode 100644 app/client/src/workers/common/AppComputationCache/types.ts diff --git a/app/client/src/sagas/EvaluationsSaga.ts b/app/client/src/sagas/EvaluationsSaga.ts index 9d6a5d1e47fb..bcdc905ebc3e 100644 --- a/app/client/src/sagas/EvaluationsSaga.ts +++ b/app/client/src/sagas/EvaluationsSaga.ts @@ -77,7 +77,6 @@ import { resetWidgetsMetaState, updateMetaState } from "actions/metaActions"; import { getAllActionValidationConfig, getAllJSActionsData, - getCurrentPageDSLVersion, } from "ee/selectors/entitiesSelector"; import type { WidgetEntityConfig } from "ee/entities/DataTree/types"; import type { @@ -113,12 +112,6 @@ import { evalErrorHandler } from "./EvalErrorHandler"; import AnalyticsUtil from "ee/utils/AnalyticsUtil"; import { endSpan, startRootSpan } from "instrumentation/generateTraces"; import { transformTriggerEvalErrors } from "ee/sagas/helpers"; -import { - getApplicationLastDeployedAt, - getCurrentApplicationId, - getCurrentPageId, -} from "selectors/editorSelectors"; -import { getInstanceId } from "ee/selectors/organizationSelectors"; import type { AffectedJSObjects, EvaluationReduxAction, @@ -263,26 +256,13 @@ export function* evaluateTreeSaga( yield select(getSelectedAppTheme); log.debug({ unevalTree, configTree: unEvalAndConfigTree.configTree }); - const instanceId: string = yield select(getInstanceId); - const applicationId: string = yield select(getCurrentApplicationId); - const pageId: string = yield select(getCurrentPageId); - const lastDeployedAt: string = yield select(getApplicationLastDeployedAt); const appMode: ReturnType = yield select(getAppMode); const widgetsMeta: ReturnType = yield select(getWidgetsMeta); - const dslVersion: number | null = yield select(getCurrentPageDSLVersion); const shouldRespondWithLogs = log.getLevel() === log.levels.DEBUG; const evalTreeRequestData: EvalTreeRequestData = { - cacheProps: { - appMode, - appId: applicationId, - pageId, - timestamp: lastDeployedAt, - instanceId, - dslVersion, - }, unevalTree: unEvalAndConfigTree, widgetTypeConfigMap, widgets, diff --git a/app/client/src/workers/Evaluation/handlers/evalTree.ts b/app/client/src/workers/Evaluation/handlers/evalTree.ts index cf37832c95a6..60dd6fbad415 100644 --- a/app/client/src/workers/Evaluation/handlers/evalTree.ts +++ b/app/client/src/workers/Evaluation/handlers/evalTree.ts @@ -73,7 +73,6 @@ export async function evalTree( affectedJSObjects, allActionValidationConfig, appMode, - cacheProps, forceEvaluation, metaWidgets, shouldReplay, @@ -114,7 +113,6 @@ export async function evalTree( unevalTree, configTree, webworkerTelemetry, - cacheProps, ), webworkerTelemetry, { description: "during initialisation" }, @@ -166,7 +164,6 @@ export async function evalTree( unevalTree, configTree, webworkerTelemetry, - cacheProps, ), webworkerTelemetry, { description: "non-initialisation" }, diff --git a/app/client/src/workers/Evaluation/types.ts b/app/client/src/workers/Evaluation/types.ts index 4a67a47ed98c..713ee96c603d 100644 --- a/app/client/src/workers/Evaluation/types.ts +++ b/app/client/src/workers/Evaluation/types.ts @@ -16,7 +16,6 @@ import type { WorkerRequest } from "ee/workers/common/types"; import type { DataTreeDiff } from "ee/workers/Evaluation/evaluationUtils"; import type { APP_MODE } from "entities/App"; import type { WebworkerSpanData, Attributes } from "instrumentation/types"; -import type { ICacheProps } from "../common/AppComputationCache/types"; import type { AffectedJSObjects } from "actions/EvaluationReduxActionTypes"; import type { UpdateActionProps } from "./handlers/types"; @@ -35,7 +34,6 @@ export type EvalWorkerASyncRequest = WorkerRequest< export type EvalWorkerResponse = EvalTreeResponseData | boolean | unknown; export interface EvalTreeRequestData { - cacheProps: ICacheProps; unevalTree: unEvalAndConfigTree; widgetTypeConfigMap: WidgetTypeConfigMap; widgets: CanvasWidgetsReduxState; diff --git a/app/client/src/workers/common/AppComputationCache/AppComputationCache.test.ts b/app/client/src/workers/common/AppComputationCache/AppComputationCache.test.ts deleted file mode 100644 index 223097799ca2..000000000000 --- a/app/client/src/workers/common/AppComputationCache/AppComputationCache.test.ts +++ /dev/null @@ -1,732 +0,0 @@ -import { - EComputationCacheName, - type ICacheProps, - type IValidatedCacheProps, -} from "./types"; -import { APP_MODE } from "entities/App"; -import localforage from "localforage"; -import loglevel from "loglevel"; -import { AppComputationCache } from "./index"; - -jest.useFakeTimers(); - -// Mock functions for the main store -const setItemMock = jest.fn(); -const getItemMock = jest.fn(); -const keysMock = jest.fn(); -const removeItemMock = jest.fn(); - -// Mock functions for the cache logs store -const setItemMockLogs = jest.fn(); -const getItemMockLogs = jest.fn(); -const keysMockLogs = jest.fn(); -const removeItemMockLogs = jest.fn(); - -// Override the localforage driver to mock the local storage -localforage.defineDriver({ - _driver: localforage.LOCALSTORAGE, - _initStorage: jest.fn(), - - // These methods will be used by the instances created in AppComputationCache - keys: keysMock, - getItem: getItemMock, - setItem: setItemMock, - removeItem: removeItemMock, - - iterate: jest.fn(), - key: jest.fn(), - length: jest.fn(), - clear: jest.fn(), -}); - -// Mock localforage.createInstance to return our mocks -jest.spyOn(localforage, "createInstance").mockImplementation((options) => { - if (options.storeName === "cachedResults") { - return { - ...localforage, - setItem: setItemMock, - getItem: getItemMock, - keys: keysMock, - removeItem: removeItemMock, - }; - } else if (options.storeName === "cacheMetadataStore") { - return { - ...localforage, - setItem: setItemMockLogs, - getItem: getItemMockLogs, - keys: keysMockLogs, - removeItem: removeItemMockLogs, - }; - } else { - throw new Error("Unknown store"); - } -}); - -describe("AppComputationCache", () => { - let appComputationCache: AppComputationCache; - - beforeEach(() => { - jest.clearAllMocks(); - AppComputationCache.resetInstance(); - - // Now instantiate the singleton after mocks are set up - appComputationCache = AppComputationCache.getInstance(); - }); - - describe("generateCacheKey", () => { - test("should generate the correct cache key", () => { - const cacheProps: IValidatedCacheProps = { - appMode: APP_MODE.PUBLISHED, - timestamp: new Date("11 September 2024").toISOString(), - appId: "appId", - instanceId: "instanceId", - pageId: "pageId", - dslVersion: 1, - }; - - const cacheName = EComputationCacheName.ALL_KEYS; - - const cacheKey = appComputationCache.generateCacheKey({ - cacheName, - cacheProps, - }); - - expect(cacheKey).toBe( - `${cacheProps.instanceId}>${cacheProps.appId}>${cacheProps.pageId}>${cacheProps.appMode}>${new Date( - cacheProps.timestamp, - ).getTime()}>${cacheName}`, - ); - }); - }); - - describe("isComputationCached", () => { - test("should return false for EDIT mode", () => { - const cacheProps: ICacheProps = { - appMode: APP_MODE.EDIT, - timestamp: new Date("11 September 2024").toISOString(), - appId: "appId", - instanceId: "instanceId", - pageId: "pageId", - dslVersion: 1, - }; - - const cacheName = EComputationCacheName.ALL_KEYS; - - const result = appComputationCache.shouldComputationBeCached( - cacheName, - cacheProps, - ); - - expect(result).toBe(false); - }); - - test("should return true for PUBLISHED mode", () => { - const cacheProps: ICacheProps = { - appMode: APP_MODE.PUBLISHED, - timestamp: new Date("11 September 2024").toISOString(), - appId: "appId", - instanceId: "instanceId", - pageId: "pageId", - dslVersion: 1, - }; - - const cacheName = EComputationCacheName.ALL_KEYS; - - const result = appComputationCache.shouldComputationBeCached( - cacheName, - cacheProps, - ); - - expect(result).toBe(true); - }); - - test("should return false if appMode is undefined", () => { - const cacheProps: ICacheProps = { - timestamp: new Date("11 September 2024").toISOString(), - appId: "appId", - instanceId: "instanceId", - pageId: "pageId", - dslVersion: 1, - }; - - const cacheName = EComputationCacheName.ALL_KEYS; - - const result = appComputationCache.shouldComputationBeCached( - cacheName, - cacheProps, - ); - - expect(result).toBe(false); - }); - - test("should return false if timestamp is undefined", () => { - const cacheProps: ICacheProps = { - appMode: APP_MODE.PUBLISHED, - timestamp: "", - appId: "appId", - instanceId: "instanceId", - pageId: "pageId", - dslVersion: 1, - }; - - const cacheName = EComputationCacheName.ALL_KEYS; - - const result = appComputationCache.shouldComputationBeCached( - cacheName, - cacheProps, - ); - - expect(result).toBe(false); - }); - - test("should return false if dslVersion is undefined", () => { - const cacheProps: ICacheProps = { - appMode: APP_MODE.PUBLISHED, - timestamp: new Date("11 September 2024").toISOString(), - appId: "appId", - instanceId: "instanceId", - pageId: "pageId", - dslVersion: null, - }; - - const cacheName = EComputationCacheName.ALL_KEYS; - - const result = appComputationCache.shouldComputationBeCached( - cacheName, - cacheProps, - ); - - expect(result).toBe(false); - }); - }); - - describe("getCachedComputationResult", () => { - test("should call getItemMock and return null if cache miss", async () => { - const cacheProps: IValidatedCacheProps = { - appMode: APP_MODE.PUBLISHED, - timestamp: new Date("11 September 2024").toISOString(), - appId: "appId", - instanceId: "instanceId", - pageId: "pageId", - dslVersion: 1, - }; - - const cacheName = EComputationCacheName.ALL_KEYS; - - const cacheKey = appComputationCache.generateCacheKey({ - cacheName, - cacheProps, - }); - - getItemMock.mockResolvedValue(null); - keysMock.mockResolvedValue([]); - - const result = await appComputationCache.getCachedComputationResult({ - cacheName, - cacheProps, - }); - - expect(getItemMock).toHaveBeenCalledWith(cacheKey); - expect(result).toBe(null); - - jest.advanceTimersByTime(5000); - - expect(keysMock).toHaveBeenCalledTimes(1); - }); - - test("should call deleteInvalidCacheEntries on cache miss after 10 seconds", async () => { - const cacheProps: IValidatedCacheProps = { - appMode: APP_MODE.PUBLISHED, - timestamp: new Date("11 September 2024").toISOString(), - appId: "appId", - instanceId: "instanceId", - pageId: "pageId", - dslVersion: 1, - }; - - const cacheName = EComputationCacheName.ALL_KEYS; - - const cacheKey = appComputationCache.generateCacheKey({ - cacheName, - cacheProps, - }); - - getItemMock.mockResolvedValue(null); - keysMock.mockResolvedValue([]); - - const result = await appComputationCache.getCachedComputationResult({ - cacheName, - cacheProps, - }); - - expect(getItemMock).toHaveBeenCalledWith(cacheKey); - expect(result).toBe(null); - - jest.advanceTimersByTime(2500); - expect(keysMock).toHaveBeenCalledTimes(0); - - jest.advanceTimersByTime(2500); - jest.runAllTimers(); - - expect(keysMock).toHaveBeenCalledTimes(1); - }); - - test("should call deleteInvalidCacheEntries on dsl version mismatch after 10 seconds", async () => { - const cacheProps: IValidatedCacheProps = { - appMode: APP_MODE.PUBLISHED, - timestamp: new Date("11 September 2024").toISOString(), - appId: "appId", - instanceId: "instanceId", - pageId: "pageId", - dslVersion: 2, - }; - - const cacheName = EComputationCacheName.ALL_KEYS; - - const cacheKey = appComputationCache.generateCacheKey({ - cacheName, - cacheProps, - }); - - getItemMock.mockResolvedValue({ value: "cachedValue", dslVersion: 1 }); - - const result = await appComputationCache.getCachedComputationResult({ - cacheName, - cacheProps, - }); - - expect(getItemMock).toHaveBeenCalledWith(cacheKey); - expect(result).toBe(null); - - jest.advanceTimersByTime(2500); - expect(keysMock).toHaveBeenCalledTimes(0); - - jest.advanceTimersByTime(2500); - jest.runAllTimers(); - - expect(keysMock).toHaveBeenCalledTimes(1); - }); - - test("should call getItemMock and return cached value if cache hit", async () => { - const cacheProps: IValidatedCacheProps = { - appMode: APP_MODE.PUBLISHED, - timestamp: new Date("11 September 2024").toISOString(), - appId: "appId", - instanceId: "instanceId", - pageId: "pageId", - dslVersion: 1, - }; - - const cacheName = EComputationCacheName.ALL_KEYS; - - const cacheKey = appComputationCache.generateCacheKey({ - cacheName, - cacheProps, - }); - - getItemMock.mockResolvedValue({ value: "cachedValue", dslVersion: 1 }); - - const result = await appComputationCache.getCachedComputationResult({ - cacheName, - cacheProps, - }); - - expect(getItemMock).toHaveBeenCalledWith(cacheKey); - expect(result).toBe("cachedValue"); - }); - }); - - describe("cacheComputationResult", () => { - test("should store computation result and call trackCacheUsage when shouldCache is true", async () => { - const cacheProps: IValidatedCacheProps = { - appMode: APP_MODE.PUBLISHED, - timestamp: new Date("11 September 2024").toISOString(), - appId: "appId", - instanceId: "instanceId", - pageId: "pageId", - dslVersion: 1, - }; - - const cacheName = EComputationCacheName.ALL_KEYS; - - const cacheKey = appComputationCache.generateCacheKey({ - cacheName, - cacheProps, - }); - - const computationResult = "computedValue"; - - const trackCacheUsageSpy = jest.spyOn( - appComputationCache, - "trackCacheUsage", - ); - - await appComputationCache.cacheComputationResult({ - cacheName, - cacheProps, - computationResult, - }); - - expect(setItemMock).toHaveBeenCalledWith(cacheKey, { - value: computationResult, - dslVersion: 1, - }); - expect(trackCacheUsageSpy).toHaveBeenCalledWith(cacheKey); - - trackCacheUsageSpy.mockRestore(); - }); - - test("should not store computation result when shouldCache is false", async () => { - const cacheProps: ICacheProps = { - appMode: APP_MODE.EDIT, - timestamp: new Date("11 September 2024").toISOString(), - appId: "appId", - instanceId: "instanceId", - pageId: "pageId", - dslVersion: 1, - }; - - const cacheName = EComputationCacheName.ALL_KEYS; - - const computationResult = "computedValue"; - - await appComputationCache.cacheComputationResult({ - cacheName, - cacheProps, - computationResult, - }); - - expect(setItemMock).not.toHaveBeenCalled(); - }); - - test("should not store computation result when dsl version is invalid", async () => { - const cacheProps: ICacheProps = { - appMode: APP_MODE.PUBLISHED, - timestamp: new Date("11 September 2024").toISOString(), - appId: "appId", - instanceId: "instanceId", - pageId: "pageId", - dslVersion: null, - }; - - const cacheName = EComputationCacheName.ALL_KEYS; - - const computationResult = "computedValue"; - - await appComputationCache.cacheComputationResult({ - cacheName, - cacheProps, - computationResult, - }); - - expect(setItemMock).not.toHaveBeenCalled(); - }); - }); - - describe("fetchOrCompute", () => { - test("should return cached result if available", async () => { - const cacheProps: IValidatedCacheProps = { - appMode: APP_MODE.PUBLISHED, - timestamp: new Date("11 September 2024").toISOString(), - appId: "appId", - instanceId: "instanceId", - pageId: "pageId", - dslVersion: 1, - }; - - const cacheName = EComputationCacheName.ALL_KEYS; - - const cacheKey = appComputationCache.generateCacheKey({ - cacheName, - cacheProps, - }); - - getItemMock.mockResolvedValue({ value: "cachedValue", dslVersion: 1 }); - - const computeFn = jest.fn(() => "computedValue"); - - const result = await appComputationCache.fetchOrCompute({ - cacheName, - cacheProps, - computeFn, - }); - - expect(getItemMock).toHaveBeenCalledWith(cacheKey); - expect(computeFn).not.toHaveBeenCalled(); - expect(result).toBe("cachedValue"); - }); - - test("should compute, cache, and return result if not in cache", async () => { - getItemMock.mockResolvedValue(null); - - const cacheProps: IValidatedCacheProps = { - appMode: APP_MODE.PUBLISHED, - timestamp: new Date("11 September 2024").toISOString(), - appId: "appId", - instanceId: "instanceId", - pageId: "pageId", - dslVersion: 1, - }; - - const cacheName = EComputationCacheName.ALL_KEYS; - - const cacheKey = appComputationCache.generateCacheKey({ - cacheName, - cacheProps, - }); - - const computationResult = "computedValue"; - - const computeFn = jest.fn(() => computationResult); - - const cacheComputationResultSpy = jest.spyOn( - appComputationCache, - "cacheComputationResult", - ); - - const result = await appComputationCache.fetchOrCompute({ - cacheName, - cacheProps, - computeFn, - }); - - expect(getItemMock).toHaveBeenCalledWith(cacheKey); - expect(computeFn).toHaveBeenCalled(); - expect(cacheComputationResultSpy).toHaveBeenCalledWith({ - cacheName, - cacheProps, - computationResult, - }); - expect(result).toBe(computationResult); - - cacheComputationResultSpy.mockRestore(); - }); - - test("should handle cache errors and compute result", async () => { - getItemMock.mockRejectedValue(new Error("Cache access error")); - - const defaultLogLevel = loglevel.getLevel(); - - loglevel.setLevel("SILENT"); - - const cacheProps: IValidatedCacheProps = { - appMode: APP_MODE.PUBLISHED, - timestamp: new Date("11 September 2024").toISOString(), - appId: "appId", - instanceId: "instanceId", - pageId: "pageId", - dslVersion: 1, - }; - - const cacheName = EComputationCacheName.ALL_KEYS; - - const computationResult = "computedValue"; - - const computeFn = jest.fn(() => computationResult); - - try { - await appComputationCache.fetchOrCompute({ - cacheName, - cacheProps, - computeFn, - }); - fail("Expected error to be thrown"); - } catch (error) { - expect(error).toBeInstanceOf(Error); - expect((error as Error).message).toContain("Cache access error"); - } - - loglevel.setLevel(defaultLogLevel); - }); - - test("should not cache result when dsl version is invalid", async () => { - const cacheProps: ICacheProps = { - appMode: APP_MODE.PUBLISHED, - timestamp: new Date("11 September 2024").toISOString(), - appId: "appId", - instanceId: "instanceId", - pageId: "pageId", - dslVersion: null, - }; - - const cacheName = EComputationCacheName.ALL_KEYS; - - const computationResult = "computedValue"; - - await appComputationCache.cacheComputationResult({ - cacheName, - cacheProps, - computationResult, - }); - - expect(setItemMock).not.toHaveBeenCalled(); - }); - }); - - describe("deleteInvalidCacheEntries", () => { - test("should delete old cache entries", async () => { - const cacheProps: IValidatedCacheProps = { - appMode: APP_MODE.PUBLISHED, - timestamp: new Date("11 September 2024").toISOString(), - appId: "appId", - instanceId: "instanceId", - pageId: "pageId", - dslVersion: 1, - }; - - const currentTimestamp = new Date(cacheProps.timestamp).getTime(); - - const currentCacheKey = [ - cacheProps.instanceId, - cacheProps.appId, - cacheProps.pageId, - cacheProps.appMode, - currentTimestamp, - EComputationCacheName.ALL_KEYS, - ].join(">"); - - const oldTimestamp = new Date("10 September 2024").getTime(); - - const oldCacheKey = [ - cacheProps.instanceId, - cacheProps.appId, - cacheProps.pageId, - cacheProps.appMode, - oldTimestamp, - EComputationCacheName.ALL_KEYS, - ].join(">"); - - keysMock.mockResolvedValue([currentCacheKey, oldCacheKey]); - - await appComputationCache.deleteInvalidCacheEntries(cacheProps); - - expect(keysMock).toHaveBeenCalled(); - - expect(removeItemMock).toHaveBeenCalledWith(oldCacheKey); - expect(removeItemMock).not.toHaveBeenCalledWith(currentCacheKey); - }); - }); - - describe("trackCacheUsage", () => { - test("should update cache log", async () => { - const cacheKey = "someCacheKey"; - - const existingCacheLog = { - lastAccessedAt: Date.now() - 1000, - createdAt: Date.now() - 2000, - }; - - getItemMockLogs.mockResolvedValue(existingCacheLog); - - await appComputationCache.trackCacheUsage(cacheKey); - - expect(getItemMockLogs).toHaveBeenCalledWith(cacheKey); - - expect(setItemMockLogs).toHaveBeenCalledWith(cacheKey, { - lastAccessedAt: expect.any(Number), - createdAt: existingCacheLog.createdAt, - }); - }); - }); - - describe("isAppModeValid", () => { - test("should return true for valid app modes", () => { - expect(appComputationCache.isAppModeValid(APP_MODE.PUBLISHED)).toBe(true); - expect(appComputationCache.isAppModeValid(APP_MODE.EDIT)).toBe(true); - }); - - test("should return false for invalid app modes", () => { - expect(appComputationCache.isAppModeValid(undefined)).toBe(false); - expect(appComputationCache.isAppModeValid(null)).toBe(false); - expect(appComputationCache.isAppModeValid("invalid")).toBe(false); - }); - }); - - describe("isTimestampValid", () => { - test("should return true for valid timestamps", () => { - const validTimestamp = new Date().toISOString(); - - expect(appComputationCache.isTimestampValid(validTimestamp)).toBe(true); - }); - - test("should return false for invalid timestamps", () => { - expect(appComputationCache.isTimestampValid(undefined)).toBe(false); - expect(appComputationCache.isTimestampValid(null)).toBe(false); - expect(appComputationCache.isTimestampValid("invalid")).toBe(false); - expect(appComputationCache.isTimestampValid("2024-01-01")).toBe(false); - expect(appComputationCache.isTimestampValid("2024-01-01T00")).toBe(false); - expect(appComputationCache.isTimestampValid("2024-01-01T00:00")).toBe( - false, - ); - expect(appComputationCache.isTimestampValid("2024-01-01T00:00:00")).toBe( - false, - ); - expect( - appComputationCache.isTimestampValid("2024-01-01T00:00:00.000"), - ).toBe(false); - }); - }); - - describe("isDSLVersionValid", () => { - test("should return true for valid dsl versions", () => { - expect(appComputationCache.isDSLVersionValid(1)).toBe(true); - expect(appComputationCache.isDSLVersionValid(90)).toBe(true); - }); - - test("should return false for invalid dsl versions", () => { - expect(appComputationCache.isDSLVersionValid(0)).toBe(false); - expect(appComputationCache.isDSLVersionValid(null)).toBe(false); - expect(appComputationCache.isDSLVersionValid("invalid")).toBe(false); - expect(appComputationCache.isDSLVersionValid(undefined)).toBe(false); - expect(appComputationCache.isDSLVersionValid(NaN)).toBe(false); - expect(appComputationCache.isDSLVersionValid(Infinity)).toBe(false); - expect(appComputationCache.isDSLVersionValid(-1)).toBe(false); - }); - }); - - describe("isCacheValid", () => { - const cacheProps: IValidatedCacheProps = { - appMode: APP_MODE.PUBLISHED, - timestamp: new Date().toISOString(), - appId: "appId", - instanceId: "instanceId", - pageId: "pageId", - dslVersion: 1, - }; - - test("should return true for valid cache", () => { - const cachedValue = { - value: "cachedValue", - dslVersion: 1, - }; - - expect(appComputationCache.isCacheValid(cachedValue, cacheProps)).toBe( - true, - ); - }); - - test("should return false null cache", () => { - expect(appComputationCache.isCacheValid(null, cacheProps)).toBe(false); - }); - - test("should return false if dsl version is not present", () => { - expect( - appComputationCache.isCacheValid( - { - value: "cachedValue", - }, - cacheProps, - ), - ).toBe(false); - }); - - test("should return false if dsl version mismatch", () => { - expect( - appComputationCache.isCacheValid( - { value: "cachedValue", dslVersion: 2 }, - cacheProps, - ), - ).toBe(false); - }); - }); -}); diff --git a/app/client/src/workers/common/AppComputationCache/index.ts b/app/client/src/workers/common/AppComputationCache/index.ts deleted file mode 100644 index f872a6b6c8d5..000000000000 --- a/app/client/src/workers/common/AppComputationCache/index.ts +++ /dev/null @@ -1,350 +0,0 @@ -import { APP_MODE } from "entities/App"; -import localforage from "localforage"; -import isNull from "lodash/isNull"; -import loglevel from "loglevel"; -import { - EComputationCacheName, - type IValidatedCacheProps, - type ICacheProps, -} from "./types"; -import debounce from "lodash/debounce"; -import { isFinite, isNumber, isString } from "lodash"; - -interface ICachedData { - value: T; - dslVersion?: number; -} - -interface ICacheLog { - lastAccessedAt: number; - createdAt: number | null; -} - -export class AppComputationCache { - // Singleton instance - private static instance: AppComputationCache | null = null; - private static CACHE_KEY_DELIMITER = ">"; - - // The cache store for computation results - private readonly store: LocalForage; - - // The cache store for cache event logs - private readonly cacheLogsStore: LocalForage; - - // The app mode configuration for each cache type. This determines which app modes - // the cache should be enabled for - private readonly appModeConfig = { - [EComputationCacheName.DEPENDENCY_MAP]: [APP_MODE.PUBLISHED], - [EComputationCacheName.ALL_KEYS]: [APP_MODE.PUBLISHED], - }; - - constructor() { - this.store = localforage.createInstance({ - name: "AppComputationCache", - storeName: "cachedResults", - }); - - this.cacheLogsStore = localforage.createInstance({ - name: "AppComputationCache", - storeName: "cacheMetadataStore", - }); - } - - static getInstance(): AppComputationCache { - if (!AppComputationCache.instance) { - AppComputationCache.instance = new AppComputationCache(); - } - - return AppComputationCache.instance; - } - - isAppModeValid(appMode: unknown) { - return appMode === APP_MODE.PUBLISHED || appMode === APP_MODE.EDIT; - } - - isTimestampValid(timestamp: unknown) { - const isoStringRegex = - /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})\.(\d{3})Z$/; - - if (isString(timestamp) && !!timestamp.trim()) { - return isoStringRegex.test(timestamp); - } - - return false; - } - - isDSLVersionValid(dslVersion: unknown) { - return isNumber(dslVersion) && isFinite(dslVersion) && dslVersion > 0; - } - - debouncedDeleteInvalidCacheEntries = debounce( - this.deleteInvalidCacheEntries, - 5000, - ); - - /** - * Check if the computation result should be cached based on the app mode configuration - * @returns - A boolean indicating whether the cache should be enabled for the given app mode - */ - shouldComputationBeCached( - cacheName: EComputationCacheName, - cacheProps: ICacheProps, - ): cacheProps is IValidatedCacheProps { - const { appMode, dslVersion, timestamp } = cacheProps; - - if ( - !this.isAppModeValid(appMode) || - !this.isTimestampValid(timestamp) || - !this.isDSLVersionValid(dslVersion) - ) { - return false; - } - - return this.appModeConfig[cacheName].includes(appMode); - } - - /** - * Checks if the value should be cached based on the app mode configuration and - * caches the computation result if it should be cached. It also tracks the cache usage - * @returns - A promise that resolves when the computation result is cached - * @throws - Logs an error if the computation result cannot be cached and throws the error - */ - async cacheComputationResult({ - cacheName, - cacheProps, - computationResult, - }: { - cacheProps: ICacheProps; - cacheName: EComputationCacheName; - computationResult: T; - }) { - try { - const isCacheable = this.shouldComputationBeCached(cacheName, cacheProps); - - if (!isCacheable) { - return; - } - - const cacheKey = this.generateCacheKey({ cacheProps, cacheName }); - - await this.store.setItem>(cacheKey, { - value: computationResult, - dslVersion: cacheProps.dslVersion, - }); - - await this.trackCacheUsage(cacheKey); - } catch (error) { - loglevel.error(error); - throw error; - } - } - - /** - * Gets the cached computation result if it exists and is valid - * @returns - A promise that resolves with the cached computation result or null if it does not exist - * @throws - Logs an error if the computation result cannot be fetched and throws the error - */ - async getCachedComputationResult({ - cacheName, - cacheProps, - }: { - cacheProps: ICacheProps; - cacheName: EComputationCacheName; - }): Promise { - try { - const isCacheable = this.shouldComputationBeCached(cacheName, cacheProps); - - if (!isCacheable) { - return null; - } - - const cacheKey = this.generateCacheKey({ - cacheProps, - cacheName, - }); - - const cached = await this.store.getItem>(cacheKey); - - if (!this.isCacheValid(cached, cacheProps)) { - // Cache miss - // Delete invalid cache entries when thread is idle - setTimeout(async () => { - await this.debouncedDeleteInvalidCacheEntries(cacheProps); - }, 0); - - return null; - } - - await this.trackCacheUsage(cacheKey); - - return cached.value; - } catch (error) { - loglevel.error(error); - throw error; - } - } - - /** - * Checks if the cached value is valid - * @returns - A boolean indicating whether the cached value is valid - */ - isCacheValid( - cachedValue: ICachedData | null, - cacheProps: IValidatedCacheProps, - ): cachedValue is ICachedData { - if (isNull(cachedValue)) { - return false; - } - - if (!cachedValue.dslVersion) { - return false; - } - - return cachedValue.dslVersion === cacheProps.dslVersion; - } - - /** - * Generates a cache key from the index parts - * @returns - The generated cache key - */ - generateCacheKey({ - cacheName, - cacheProps, - }: { - cacheProps: IValidatedCacheProps; - cacheName: EComputationCacheName; - }) { - const { appId, appMode, instanceId, pageId, timestamp } = cacheProps; - - const timeStampEpoch = new Date(timestamp).getTime(); - const cacheKeyParts = [ - instanceId, - appId, - pageId, - appMode, - timeStampEpoch, - cacheName, - ]; - - return cacheKeyParts.join(AppComputationCache.CACHE_KEY_DELIMITER); - } - - /** - * Fetches the computation result from the cache or computes it if it does not exist - * @returns - A promise that resolves with the computation result - * @throws - Logs an error if the computation result cannot be fetched or computed and returns the computed fallback result - */ - async fetchOrCompute({ - cacheName, - cacheProps, - computeFn, - }: { - cacheProps: ICacheProps; - computeFn: () => Promise | T; - cacheName: EComputationCacheName; - }) { - try { - const isCacheable = this.shouldComputationBeCached(cacheName, cacheProps); - - if (!isCacheable) { - return computeFn(); - } - - const cachedResult = await this.getCachedComputationResult({ - cacheProps, - cacheName, - }); - - if (cachedResult) { - return cachedResult; - } - - const computationResult = await computeFn(); - - await this.cacheComputationResult({ - cacheProps, - computationResult, - cacheName, - }); - - return computationResult; - } catch (error) { - loglevel.error(error); - throw error; - } - } - - /** - * Tracks the cache usage by updating the last accessed timestamp of the cache - * @param name - The name of the cache - * @returns - A promise that resolves when the cache usage is tracked - * @throws - Logs an error if the cache usage cannot be tracked - */ - async trackCacheUsage(name: string) { - try { - const currentLog = await this.cacheLogsStore.getItem(name); - - await this.cacheLogsStore.setItem(name, { - lastAccessedAt: Date.now(), - createdAt: currentLog?.createdAt || Date.now(), - }); - } catch (error) { - loglevel.error("Error tracking cache usage:", error); - } - } - - /** - * Delete invalid cache entries - * @returns - A promise that resolves when the invalid cache entries are deleted - * @throws - Logs an error if the invalid cache entries cannot be deleted - */ - - async deleteInvalidCacheEntries(cacheProps: ICacheProps) { - try { - // Get previous entry keys - const cacheKeys = await this.store.keys(); - - // Get invalid cache keys - const invalidCacheKeys = cacheKeys.filter((key) => { - const keyParts = key.split(AppComputationCache.CACHE_KEY_DELIMITER); - const cacheKeyTimestamp = parseInt(keyParts[4], 10); - - if (!cacheProps.timestamp) { - return false; - } - - return ( - keyParts[0] === cacheProps.instanceId && - keyParts[1] === cacheProps.appId && - keyParts[3] === cacheProps.appMode && - cacheKeyTimestamp !== new Date(cacheProps.timestamp).getTime() - ); - }); - - // Delete invalid cache entries - await Promise.all( - invalidCacheKeys.map(async (key) => this.store.removeItem(key)), - ); - - // Delete invalid cache logs - await Promise.all( - invalidCacheKeys.map(async (key) => - this.cacheLogsStore.removeItem(key), - ), - ); - } catch (error) { - loglevel.error("Error deleting invalid cache entries:", error); - } - } - - /** - * Resets the singleton instance - */ - static resetInstance() { - AppComputationCache.instance = null; - } -} - -export const appComputationCache = AppComputationCache.getInstance(); - -export default appComputationCache; diff --git a/app/client/src/workers/common/AppComputationCache/types.ts b/app/client/src/workers/common/AppComputationCache/types.ts deleted file mode 100644 index 42a0b366537b..000000000000 --- a/app/client/src/workers/common/AppComputationCache/types.ts +++ /dev/null @@ -1,24 +0,0 @@ -import type { APP_MODE } from "entities/App"; - -export enum EComputationCacheName { - DEPENDENCY_MAP = "DEPENDENCY_MAP", - ALL_KEYS = "ALL_KEYS", -} - -export interface ICacheProps { - appId: string; - pageId: string; - appMode?: APP_MODE; - timestamp?: string; - instanceId: string; - dslVersion: number | null; -} - -export interface IValidatedCacheProps { - appId: string; - pageId: string; - appMode: APP_MODE; - timestamp: string; - instanceId: string; - dslVersion: number; -} diff --git a/app/client/src/workers/common/DataTreeEvaluator/index.ts b/app/client/src/workers/common/DataTreeEvaluator/index.ts index bf55a03944f5..396c9b77f671 100644 --- a/app/client/src/workers/common/DataTreeEvaluator/index.ts +++ b/app/client/src/workers/common/DataTreeEvaluator/index.ts @@ -146,11 +146,6 @@ import { profileFn, } from "instrumentation/generateWebWorkerTraces"; import generateOverrideContext from "ee/workers/Evaluation/generateOverrideContext"; -import appComputationCache from "../AppComputationCache"; -import { - EComputationCacheName, - type ICacheProps, -} from "../AppComputationCache/types"; import { getDataTreeContext } from "ee/workers/Evaluation/Actions"; import { WorkerEnv } from "workers/Evaluation/handlers/workerEnv"; import type { WebworkerSpanData, Attributes } from "instrumentation/types"; @@ -258,7 +253,6 @@ export default class DataTreeEvaluator { unEvalTree: any, configTree: ConfigTree, webworkerTelemetry: Record = {}, - cacheProps: ICacheProps, ) { this.setConfigTree(configTree); @@ -302,21 +296,7 @@ export default class DataTreeEvaluator { ); const allKeysGenerationStartTime = performance.now(); - try { - this.allKeys = await appComputationCache.fetchOrCompute({ - cacheProps, - cacheName: EComputationCacheName.ALL_KEYS, - computeFn: () => getAllPaths(unEvalTreeWithStrigifiedJSFunctions), - }); - } catch (error) { - this.errors.push({ - type: EvalErrorTypes.CACHE_ERROR, - message: (error as Error).message, - stack: (error as Error).stack, - }); - - this.allKeys = getAllPaths(unEvalTreeWithStrigifiedJSFunctions); - } + this.allKeys = getAllPaths(unEvalTreeWithStrigifiedJSFunctions); const allKeysGenerationEndTime = performance.now(); @@ -329,7 +309,6 @@ export default class DataTreeEvaluator { this, localUnEvalTree, configTree, - cacheProps, webworkerTelemetry, ), webworkerTelemetry, diff --git a/app/client/src/workers/common/DependencyMap/index.ts b/app/client/src/workers/common/DependencyMap/index.ts index 4674d5dc2fdf..0a41816cc630 100644 --- a/app/client/src/workers/common/DependencyMap/index.ts +++ b/app/client/src/workers/common/DependencyMap/index.ts @@ -12,11 +12,7 @@ import type { DataTreeEntityObject, } from "ee/entities/DataTree/types"; import type { ConfigTree, DataTree } from "entities/DataTree/dataTreeTypes"; -import { - EvalErrorTypes, - getEntityId, - getEvalErrorPath, -} from "utils/DynamicBindingUtils"; +import { getEntityId, getEvalErrorPath } from "utils/DynamicBindingUtils"; import { convertArrayToObject, extractInfoFromBindings } from "./utils"; import type DataTreeEvaluator from "workers/common/DataTreeEvaluator"; import { get, isEmpty, set } from "lodash"; @@ -33,11 +29,6 @@ import { } from "ee/workers/Evaluation/Actions"; import { isWidgetActionOrJsObject } from "ee/entities/DataTree/utils"; import { getValidEntityType } from "workers/common/DataTreeEvaluator/utils"; -import appComputationCache from "../AppComputationCache"; -import { - EComputationCacheName, - type ICacheProps, -} from "../AppComputationCache/types"; import type DependencyMap from "entities/DependencyMap"; import { profileFn } from "instrumentation/generateWebWorkerTraces"; import type { WebworkerSpanData, Attributes } from "instrumentation/types"; @@ -46,7 +37,6 @@ export async function createDependencyMap( dataTreeEvalRef: DataTreeEvaluator, unEvalTree: DataTree, configTree: ConfigTree, - cacheProps: ICacheProps, webworkerSpans: Record = {}, ) { const { allKeys, dependencyMap } = dataTreeEvalRef; @@ -63,76 +53,28 @@ export async function createDependencyMap( ); }); - let dependencyMapCache: Record | null = null; - - try { - dependencyMapCache = await appComputationCache.getCachedComputationResult< - Record - >({ - cacheProps, - cacheName: EComputationCacheName.DEPENDENCY_MAP, - }); - } catch (error) { - dataTreeEvalRef.errors.push({ - type: EvalErrorTypes.CACHE_ERROR, - message: (error as Error).message, - stack: (error as Error).stack, - }); - dependencyMapCache = null; - } + Object.keys(configTree).forEach((entityName) => { + const entity = unEvalTree[entityName]; + const entityConfig = configTree[entityName]; + const entityDependencies = getEntityDependencies( + entity as DataTreeEntityObject, + entityConfig, + allKeys, + ); - if (dependencyMapCache) { - profileFn("createDependencyMap.addDependency", {}, webworkerSpans, () => { - Object.entries(dependencyMapCache).forEach(([path, references]) => { - dependencyMap.addDependency(path, references); - }); - }); - } else { - let shouldCache = true; - - Object.keys(configTree).forEach((entityName) => { - const entity = unEvalTree[entityName]; - const entityConfig = configTree[entityName]; - const entityDependencies = getEntityDependencies( - entity as DataTreeEntityObject, - entityConfig, + for (const path of Object.keys(entityDependencies)) { + const pathDependencies = entityDependencies[path]; + const { errors, references } = extractInfoFromBindings( + pathDependencies, allKeys, ); - for (const path of Object.keys(entityDependencies)) { - const pathDependencies = entityDependencies[path]; - const { errors, references } = extractInfoFromBindings( - pathDependencies, - allKeys, - ); - - dependencyMap.addDependency(path, references); - dataTreeEvalRef.errors.push(...errors); - - if (errors.length) { - shouldCache = false; - } - } - }); - - DependencyMapUtils.makeParentsDependOnChildren(dependencyMap); - - if (shouldCache) { - try { - await appComputationCache.cacheComputationResult({ - cacheProps, - cacheName: EComputationCacheName.DEPENDENCY_MAP, - computationResult: dependencyMap.dependencies, - }); - } catch (error) { - dataTreeEvalRef.errors.push({ - type: EvalErrorTypes.CACHE_ERROR, - message: (error as Error).message, - stack: (error as Error).stack, - }); - } + dependencyMap.addDependency(path, references); + dataTreeEvalRef.errors.push(...errors); } - } + }); + + DependencyMapUtils.makeParentsDependOnChildren(dependencyMap); return { dependencies: dependencyMap.dependencies, From 7cb10b1c41450e526422afaf46629a90ca07480d Mon Sep 17 00:00:00 2001 From: Diljit VJ Date: Thu, 1 May 2025 13:50:17 +0530 Subject: [PATCH 2/3] fix lint issues --- .../Evaluation/__tests__/evaluation.test.ts | 15 +---------- .../Evaluation/evalTreeWithChanges.test.ts | 15 +---------- .../dataTreeEvaluator.test.ts | 25 ------------------- 3 files changed, 2 insertions(+), 53 deletions(-) diff --git a/app/client/src/workers/Evaluation/__tests__/evaluation.test.ts b/app/client/src/workers/Evaluation/__tests__/evaluation.test.ts index f468750d3911..edfaf269ef2c 100644 --- a/app/client/src/workers/Evaluation/__tests__/evaluation.test.ts +++ b/app/client/src/workers/Evaluation/__tests__/evaluation.test.ts @@ -18,7 +18,6 @@ import WidgetFactory from "WidgetProvider/factory"; import { generateDataTreeWidget } from "entities/DataTree/dataTreeWidget"; import { sortObjectWithArray } from "../../../utils/treeUtils"; import klona from "klona"; -import { APP_MODE } from "entities/App"; const klonaFullSpy = jest.fn(); @@ -578,19 +577,7 @@ describe("DataTreeEvaluator", () => { const evaluator = new DataTreeEvaluator(WIDGET_CONFIG_MAP); it("Checks the number of clone operations in first tree flow", async () => { - await evaluator.setupFirstTree( - unEvalTree, - configTree, - {}, - { - appId: "appId", - pageId: "pageId", - timestamp: "timestamp", - appMode: APP_MODE.PUBLISHED, - instanceId: "instanceId", - dslVersion: 1, - }, - ); + await evaluator.setupFirstTree(unEvalTree, configTree, {}); evaluator.evalAndValidateFirstTree(); // Hard check to not regress on the number of clone operations. Try to improve this number. expect(klonaFullSpy).toBeCalledTimes(15); diff --git a/app/client/src/workers/Evaluation/evalTreeWithChanges.test.ts b/app/client/src/workers/Evaluation/evalTreeWithChanges.test.ts index dee35e321843..d5381e0b4e2c 100644 --- a/app/client/src/workers/Evaluation/evalTreeWithChanges.test.ts +++ b/app/client/src/workers/Evaluation/evalTreeWithChanges.test.ts @@ -10,7 +10,6 @@ import type { WidgetEntity } from "plugins/Linting/lib/entity/WidgetEntity"; import type { UpdateDataTreeMessageData } from "sagas/types"; import DataTreeEvaluator from "workers/common/DataTreeEvaluator"; import * as evalTreeWithChanges from "./evalTreeWithChanges"; -import { APP_MODE } from "entities/App"; import { updateEvalProps } from "./helpers"; export const BASE_WIDGET = { widgetId: "randomID", @@ -190,19 +189,7 @@ describe("evaluateAndGenerateResponse", () => { beforeEach(async () => { // we are mimicking the first tree evaluation flow here evaluator = new DataTreeEvaluator(WIDGET_CONFIG_MAP); - await evaluator.setupFirstTree( - unEvalTree, - configTree, - {}, - { - appId: "appId", - pageId: "pageId", - timestamp: "timestamp", - appMode: APP_MODE.PUBLISHED, - instanceId: "instanceId", - dslVersion: 1, - }, - ); + await evaluator.setupFirstTree(unEvalTree, configTree, {}); evaluator.evalAndValidateFirstTree(); const dataTree = updateEvalProps(evaluator) || {}; diff --git a/app/client/src/workers/common/DataTreeEvaluator/dataTreeEvaluator.test.ts b/app/client/src/workers/common/DataTreeEvaluator/dataTreeEvaluator.test.ts index 030944958ff8..12afc1b72e7a 100644 --- a/app/client/src/workers/common/DataTreeEvaluator/dataTreeEvaluator.test.ts +++ b/app/client/src/workers/common/DataTreeEvaluator/dataTreeEvaluator.test.ts @@ -26,7 +26,6 @@ import { } from "constants/AppsmithActionConstants/ActionConstants"; import generateOverrideContext from "ee/workers/Evaluation/generateOverrideContext"; import { klona } from "klona"; -import { APP_MODE } from "entities/App"; const widgetConfigMap: Record< string, @@ -284,14 +283,6 @@ describe("DataTreeEvaluator", () => { unEvalTree as unknown as DataTree, configTree as unknown as ConfigTree, {}, - { - appId: "appId", - pageId: "pageId", - timestamp: "timestamp", - appMode: APP_MODE.PUBLISHED, - instanceId: "instanceId", - dslVersion: 1, - }, ); dataTreeEvaluator.evalAndValidateFirstTree(); }); @@ -386,14 +377,6 @@ describe("DataTreeEvaluator", () => { unEvalTree as unknown as DataTree, configTree as unknown as ConfigTree, {}, - { - appId: "appId", - pageId: "pageId", - timestamp: "timestamp", - appMode: APP_MODE.PUBLISHED, - instanceId: "instanceId", - dslVersion: 1, - }, ); dataTreeEvaluator.evalAndValidateFirstTree(); }); @@ -450,14 +433,6 @@ describe("DataTreeEvaluator", () => { nestedArrayAccessorCyclicDependency.initUnEvalTree, nestedArrayAccessorCyclicDependencyConfig.initConfigTree, {}, - { - appId: "appId", - pageId: "pageId", - timestamp: new Date().toISOString(), - appMode: APP_MODE.PUBLISHED, - instanceId: "instanceId", - dslVersion: 1, - }, ); dataTreeEvaluator.evalAndValidateFirstTree(); }); From 66914c595d0e4a9993dcc2c186a9a2b52087470e Mon Sep 17 00:00:00 2001 From: Diljit VJ Date: Thu, 1 May 2025 14:36:19 +0530 Subject: [PATCH 3/3] fix test cases --- app/client/src/sagas/EvaluationsSaga.test.ts | 24 -------------------- 1 file changed, 24 deletions(-) diff --git a/app/client/src/sagas/EvaluationsSaga.test.ts b/app/client/src/sagas/EvaluationsSaga.test.ts index f37629c0f316..08edad113aa1 100644 --- a/app/client/src/sagas/EvaluationsSaga.test.ts +++ b/app/client/src/sagas/EvaluationsSaga.test.ts @@ -65,14 +65,6 @@ describe("evaluateTreeSaga", () => { [select(getCurrentPageDSLVersion), 1], ]) .call(evalWorker.request, EVAL_WORKER_ACTIONS.EVAL_TREE, { - cacheProps: { - instanceId: "instanceId", - appId: "applicationId", - pageId: "pageId", - appMode: false, - timestamp: new Date("11 September 2024").toISOString(), - dslVersion: 1, - }, unevalTree: unEvalAndConfigTree, widgetTypeConfigMap: undefined, widgets: {}, @@ -113,14 +105,6 @@ describe("evaluateTreeSaga", () => { [select(getCurrentPageDSLVersion), 1], ]) .call(evalWorker.request, EVAL_WORKER_ACTIONS.EVAL_TREE, { - cacheProps: { - instanceId: "instanceId", - appId: "applicationId", - pageId: "pageId", - appMode: false, - timestamp: new Date("11 September 2024").toISOString(), - dslVersion: 1, - }, unevalTree: unEvalAndConfigTree, widgetTypeConfigMap: undefined, widgets: {}, @@ -170,14 +154,6 @@ describe("evaluateTreeSaga", () => { [select(getCurrentPageDSLVersion), 1], ]) .call(evalWorker.request, EVAL_WORKER_ACTIONS.EVAL_TREE, { - cacheProps: { - instanceId: "instanceId", - appId: "applicationId", - pageId: "pageId", - appMode: false, - timestamp: new Date("11 September 2024").toISOString(), - dslVersion: 1, - }, unevalTree: unEvalAndConfigTree, widgetTypeConfigMap: undefined, widgets: {},