fix: Batch operations on appsmith store for performance gains (#19247)
This commit is contained in:
parent
dc7582f35b
commit
c8063743a2
|
|
@ -1154,7 +1154,7 @@ Cypress.Commands.add("ValidatePaginationInputDataV2", () => {
|
|||
Cypress.Commands.add("CheckForPageSaveError", () => {
|
||||
cy.get("body").then(($ele) => {
|
||||
if ($ele.find(commonlocators.saveStatusError).length) {
|
||||
cy.reload()
|
||||
cy.reload();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -65,8 +65,7 @@ export const EVALUATE_REDUX_ACTIONS = [
|
|||
// App Data
|
||||
ReduxActionTypes.SET_APP_MODE,
|
||||
ReduxActionTypes.FETCH_USER_DETAILS_SUCCESS,
|
||||
ReduxActionTypes.UPDATE_APP_PERSISTENT_STORE,
|
||||
ReduxActionTypes.UPDATE_APP_TRANSIENT_STORE,
|
||||
ReduxActionTypes.UPDATE_APP_STORE,
|
||||
ReduxActionTypes.SET_USER_CURRENT_GEO_LOCATION,
|
||||
// Widgets
|
||||
ReduxActionTypes.UPDATE_LAYOUT,
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ import { CanvasWidgetsReduxState } from "reducers/entityReducers/canvasWidgetsRe
|
|||
import { GenerateTemplatePageRequest } from "api/PageApi";
|
||||
import { ENTITY_TYPE } from "entities/AppsmithConsole";
|
||||
import { Replayable } from "entities/Replay/ReplayEntity/ReplayEditor";
|
||||
import { StoreValueActionDescription } from "@appsmith/entities/DataTree/actionTriggers";
|
||||
|
||||
export interface FetchPageListPayload {
|
||||
applicationId: string;
|
||||
|
|
@ -348,30 +347,12 @@ export const setAppMode = (payload: APP_MODE): ReduxAction<APP_MODE> => {
|
|||
};
|
||||
};
|
||||
|
||||
export const updateAppStoreEvaluated = (
|
||||
storeValueAction?: StoreValueActionDescription["payload"],
|
||||
) => ({
|
||||
type: ReduxActionTypes.UPDATE_APP_STORE_EVALUATED,
|
||||
payload: storeValueAction,
|
||||
});
|
||||
|
||||
export const updateAppTransientStore = (
|
||||
export const updateAppStore = (
|
||||
payload: Record<string, unknown>,
|
||||
storeValueAction?: StoreValueActionDescription["payload"],
|
||||
): EvaluationReduxAction<Record<string, unknown>> => ({
|
||||
type: ReduxActionTypes.UPDATE_APP_TRANSIENT_STORE,
|
||||
payload,
|
||||
postEvalActions: [updateAppStoreEvaluated(storeValueAction)],
|
||||
});
|
||||
|
||||
export const updateAppPersistentStore = (
|
||||
payload: Record<string, unknown>,
|
||||
storeValueAction?: StoreValueActionDescription["payload"],
|
||||
): EvaluationReduxAction<Record<string, unknown>> => {
|
||||
return {
|
||||
type: ReduxActionTypes.UPDATE_APP_PERSISTENT_STORE,
|
||||
type: ReduxActionTypes.UPDATE_APP_STORE,
|
||||
payload,
|
||||
postEvalActions: [updateAppStoreEvaluated(storeValueAction)],
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -477,9 +477,7 @@ export const ReduxActionTypes = {
|
|||
TOGGLE_PROPERTY_PANE_WIDGET_NAME_EDIT:
|
||||
"TOGGLE_PROPERTY_PANE_WIDGET_NAME_EDIT",
|
||||
SET_PROPERTY_PANE_WIDTH: "SET_PROPERTY_PANE_WIDTH",
|
||||
UPDATE_APP_PERSISTENT_STORE: "UPDATE_APP_PERSISTENT_STORE",
|
||||
UPDATE_APP_TRANSIENT_STORE: "UPDATE_APP_TRANSIENT_STORE",
|
||||
UPDATE_APP_STORE_EVALUATED: "UPDATE_APP_STORE_EVALUATED",
|
||||
UPDATE_APP_STORE: "UPDATE_APP_STORE",
|
||||
SET_ACTION_TO_EXECUTE_ON_PAGELOAD: "SET_ACTION_TO_EXECUTE_ON_PAGELOAD",
|
||||
TOGGLE_ACTION_EXECUTE_ON_LOAD_SUCCESS:
|
||||
"TOGGLE_ACTION_EXECUTE_ON_LOAD_SUCCESS",
|
||||
|
|
|
|||
|
|
@ -106,7 +106,6 @@ export type StoreValueActionDescription = ActionDescriptionInterface<
|
|||
key: string;
|
||||
value: string;
|
||||
persist: boolean;
|
||||
uniqueActionRequestId: string;
|
||||
},
|
||||
"STORE_VALUE"
|
||||
>;
|
||||
|
|
|
|||
|
|
@ -16,10 +16,6 @@ import {
|
|||
setAppVersionOnWorkerSaga,
|
||||
} from "sagas/EvaluationsSaga";
|
||||
import navigateActionSaga from "sagas/ActionExecution/NavigateActionSaga";
|
||||
import storeValueLocally, {
|
||||
clearLocalStore,
|
||||
removeLocalValue,
|
||||
} from "sagas/ActionExecution/StoreActionSaga";
|
||||
import downloadSaga from "sagas/ActionExecution/DownloadActionSaga";
|
||||
import copySaga from "sagas/ActionExecution/CopyActionSaga";
|
||||
import resetWidgetActionSaga from "sagas/ActionExecution/ResetWidgetActionSaga";
|
||||
|
|
@ -93,15 +89,6 @@ export function* executeActionTriggers(
|
|||
case "CLOSE_MODAL":
|
||||
yield call(closeModalSaga, trigger);
|
||||
break;
|
||||
case "STORE_VALUE":
|
||||
yield call(storeValueLocally, trigger.payload);
|
||||
break;
|
||||
case "REMOVE_VALUE":
|
||||
yield call(removeLocalValue, trigger.payload);
|
||||
break;
|
||||
case "CLEAR_STORE":
|
||||
yield call(clearLocalStore);
|
||||
break;
|
||||
case "DOWNLOAD":
|
||||
yield call(downloadSaga, trigger.payload);
|
||||
break;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
/* eslint-disable @typescript-eslint/ban-types */
|
||||
import { DataTree, DataTreeEntity } from "entities/DataTree/dataTreeFactory";
|
||||
import { set } from "lodash";
|
||||
import set from "lodash/set";
|
||||
import {
|
||||
ActionDescription,
|
||||
ActionTriggerFunctionNames,
|
||||
|
|
@ -10,12 +10,12 @@ import { EventType } from "constants/AppsmithActionConstants/ActionConstants";
|
|||
import { isAction, isAppsmithEntity, isTrueObject } from "./evaluationUtils";
|
||||
import { EvalContext } from "workers/Evaluation/evaluate";
|
||||
import { ActionCalledInSyncFieldError } from "workers/Evaluation/errorModifier";
|
||||
import { initStoreFns } from "workers/Evaluation/fns/storeFns";
|
||||
import {
|
||||
ActionDescriptionWithExecutionType,
|
||||
ActionDispatcherWithExecutionType,
|
||||
PLATFORM_FUNCTIONS,
|
||||
} from "@appsmith/workers/Evaluation/PlatformFunctions";
|
||||
|
||||
declare global {
|
||||
/** All identifiers added to the worker global scope should also
|
||||
* be included in the DEDICATED_WORKER_GLOBAL_SCOPE_IDENTIFIERS in
|
||||
|
|
@ -226,8 +226,14 @@ export const addDataTreeToContext = (args: {
|
|||
|
||||
export const addPlatformFunctionsToEvalContext = (context: any) => {
|
||||
for (const [funcName, fn] of platformFunctionEntries) {
|
||||
context[funcName] = pusher.bind({}, fn);
|
||||
Object.defineProperty(context, funcName, {
|
||||
value: pusher.bind({}, fn),
|
||||
enumerable: false,
|
||||
writable: true,
|
||||
configurable: true,
|
||||
});
|
||||
}
|
||||
initStoreFns(context);
|
||||
};
|
||||
|
||||
export const getAllAsyncFunctions = (dataTree: DataTree) => {
|
||||
|
|
@ -242,7 +248,7 @@ export const getAllAsyncFunctions = (dataTree: DataTree) => {
|
|||
}
|
||||
}
|
||||
|
||||
for (const [name] of platformFunctionEntries) {
|
||||
for (const name of Object.values(ActionTriggerFunctionNames)) {
|
||||
asyncFunctionNameMap[name] = true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -26,9 +26,10 @@ export const EVAL_WORKER_ACTIONS = {
|
|||
...EVAL_WORKER_ASYNC_ACTION,
|
||||
};
|
||||
|
||||
export const MAIN_THREAD_ACTION = {
|
||||
PROCESS_TRIGGER: "PROCESS_TRIGGER",
|
||||
PROCESS_LOGS: "PROCESS_LOGS",
|
||||
LINT_TREE: "LINT_TREE",
|
||||
PROCESS_JS_FUNCTION_EXECUTION: "PROCESS_JS_FUNCTION_EXECUTION",
|
||||
};
|
||||
export enum MAIN_THREAD_ACTION {
|
||||
PROCESS_TRIGGER = "PROCESS_TRIGGER",
|
||||
PROCESS_STORE_UPDATES = "PROCESS_STORE_UPDATES",
|
||||
PROCESS_LOGS = "PROCESS_LOGS",
|
||||
LINT_TREE = "LINT_TREE",
|
||||
PROCESS_JS_FUNCTION_EXECUTION = "PROCESS_JS_FUNCTION_EXECUTION",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -154,7 +154,7 @@ export class DataTreeFactory {
|
|||
...appData,
|
||||
// combine both persistent and transient state with the transient state
|
||||
// taking precedence in case the key is the same
|
||||
store: { ...appData.store.persistent, ...appData.store.transient },
|
||||
store: appData.store,
|
||||
theme,
|
||||
} as DataTreeAppsmith;
|
||||
(dataTree.appsmith as DataTreeAppsmith).ENTITY_TYPE = ENTITY_TYPE.APPSMITH;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { fetchApplication } from "actions/applicationActions";
|
||||
import { setAppMode, updateAppPersistentStore } from "actions/pageActions";
|
||||
import { setAppMode, updateAppStore } from "actions/pageActions";
|
||||
import {
|
||||
ApplicationPayload,
|
||||
ReduxActionErrorTypes,
|
||||
|
|
@ -69,9 +69,7 @@ export default abstract class AppEngine {
|
|||
if (!apiCalls)
|
||||
throw new PageNotFoundError(`Cannot find page with id: ${pageId}`);
|
||||
const application: ApplicationPayload = yield select(getCurrentApplication);
|
||||
yield put(
|
||||
updateAppPersistentStore(getPersistentAppStore(application.id, branch)),
|
||||
);
|
||||
yield put(updateAppStore(getPersistentAppStore(application.id, branch)));
|
||||
const toLoadPageId: string = pageId || (yield select(getDefaultPageId));
|
||||
this._urlRedirect = URLGeneratorFactory.create(
|
||||
application.applicationVersion,
|
||||
|
|
|
|||
|
|
@ -23,10 +23,7 @@ export type UrlDataState = {
|
|||
fullPath: string;
|
||||
};
|
||||
|
||||
export type AppStoreState = {
|
||||
transient: Record<string, unknown>;
|
||||
persistent: Record<string, unknown>;
|
||||
};
|
||||
export type AppStoreState = Record<string, unknown>;
|
||||
|
||||
export type AppDataState = {
|
||||
mode?: APP_MODE;
|
||||
|
|
@ -55,10 +52,7 @@ const initialState: AppDataState = {
|
|||
hash: "",
|
||||
fullPath: "",
|
||||
},
|
||||
store: {
|
||||
transient: {},
|
||||
persistent: {},
|
||||
},
|
||||
store: {},
|
||||
geolocation: {
|
||||
canBeRequested: "geolocation" in navigator,
|
||||
currentPosition: {},
|
||||
|
|
@ -93,28 +87,13 @@ const appReducer = createReducer(initialState, {
|
|||
URL: action.payload,
|
||||
};
|
||||
},
|
||||
[ReduxActionTypes.UPDATE_APP_TRANSIENT_STORE]: (
|
||||
[ReduxActionTypes.UPDATE_APP_STORE]: (
|
||||
state: AppDataState,
|
||||
action: ReduxAction<Record<string, unknown>>,
|
||||
) => {
|
||||
return {
|
||||
...state,
|
||||
store: {
|
||||
...state.store,
|
||||
transient: action.payload,
|
||||
},
|
||||
};
|
||||
},
|
||||
[ReduxActionTypes.UPDATE_APP_PERSISTENT_STORE]: (
|
||||
state: AppDataState,
|
||||
action: ReduxAction<Record<string, unknown>>,
|
||||
) => {
|
||||
return {
|
||||
...state,
|
||||
store: {
|
||||
...state.store,
|
||||
persistent: action.payload,
|
||||
},
|
||||
store: action.payload,
|
||||
};
|
||||
},
|
||||
[ReduxActionTypes.SET_USER_CURRENT_GEO_LOCATION]: (
|
||||
|
|
|
|||
|
|
@ -1,94 +1,62 @@
|
|||
import { put, select, take } from "redux-saga/effects";
|
||||
import { put, select } from "redux-saga/effects";
|
||||
import { getAppStoreName } from "constants/AppConstants";
|
||||
import localStorage from "utils/localStorage";
|
||||
import {
|
||||
updateAppPersistentStore,
|
||||
updateAppTransientStore,
|
||||
} from "actions/pageActions";
|
||||
import { updateAppStore } from "actions/pageActions";
|
||||
import AppsmithConsole from "utils/AppsmithConsole";
|
||||
import { getAppStoreData } from "selectors/entitiesSelector";
|
||||
import {
|
||||
ClearStoreActionDescription,
|
||||
RemoveValueActionDescription,
|
||||
StoreValueActionDescription,
|
||||
} from "@appsmith/entities/DataTree/actionTriggers";
|
||||
import { getCurrentGitBranch } from "selectors/gitSyncSelectors";
|
||||
import { getCurrentApplicationId } from "selectors/editorSelectors";
|
||||
import { AppStoreState } from "reducers/entityReducers/appReducer";
|
||||
import { ReduxActionTypes } from "@appsmith/constants/ReduxActionConstants";
|
||||
import { Severity, LOG_CATEGORY } from "entities/AppsmithConsole";
|
||||
import moment from "moment";
|
||||
|
||||
export default function* storeValueLocally(
|
||||
action: StoreValueActionDescription["payload"],
|
||||
) {
|
||||
if (action.persist) {
|
||||
const applicationId: string = yield select(getCurrentApplicationId);
|
||||
const branch: string | undefined = yield select(getCurrentGitBranch);
|
||||
const appStoreName = getAppStoreName(applicationId, branch);
|
||||
const existingStore = localStorage.getItem(appStoreName) || "{}";
|
||||
const parsedStore = JSON.parse(existingStore);
|
||||
parsedStore[action.key] = action.value;
|
||||
const storeString = JSON.stringify(parsedStore);
|
||||
localStorage.setItem(appStoreName, storeString);
|
||||
yield put(updateAppPersistentStore(parsedStore, action));
|
||||
AppsmithConsole.info({
|
||||
text: `store('${action.key}', '${action.value}', true)`,
|
||||
});
|
||||
} else {
|
||||
const existingStore: AppStoreState = yield select(getAppStoreData);
|
||||
const newTransientStore = {
|
||||
...existingStore.transient,
|
||||
[action.key]: action.value,
|
||||
};
|
||||
yield put(updateAppTransientStore(newTransientStore, action));
|
||||
AppsmithConsole.info({
|
||||
text: `store('${action.key}', '${action.value}', false)`,
|
||||
});
|
||||
}
|
||||
/* It is possible that user calls multiple storeValue function together, in such case we need to track completion of each action separately
|
||||
We use uniqueActionRequestId to differentiate each storeValueAction here.
|
||||
*/
|
||||
while (true) {
|
||||
const returnedAction: StoreValueActionDescription = yield take(
|
||||
ReduxActionTypes.UPDATE_APP_STORE_EVALUATED,
|
||||
);
|
||||
if (!returnedAction?.payload?.uniqueActionRequestId) {
|
||||
break;
|
||||
}
|
||||
type StoreOperation =
|
||||
| StoreValueActionDescription
|
||||
| ClearStoreActionDescription
|
||||
| RemoveValueActionDescription;
|
||||
|
||||
const { uniqueActionRequestId } = returnedAction.payload;
|
||||
if (uniqueActionRequestId === action.uniqueActionRequestId) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function* removeLocalValue(
|
||||
action: RemoveValueActionDescription["payload"],
|
||||
) {
|
||||
export function* handleStoreOperations(triggers: StoreOperation[]) {
|
||||
const applicationId: string = yield select(getCurrentApplicationId);
|
||||
const branch: string | undefined = yield select(getCurrentGitBranch);
|
||||
const appStoreName = getAppStoreName(applicationId, branch);
|
||||
const existingStore = localStorage.getItem(appStoreName) || "{}";
|
||||
const parsedStore = JSON.parse(existingStore);
|
||||
delete parsedStore[action.key];
|
||||
const storeString = JSON.stringify(parsedStore);
|
||||
const existingLocalStore = localStorage.getItem(appStoreName) || "{}";
|
||||
let parsedLocalStore = JSON.parse(existingLocalStore);
|
||||
let currentStore: AppStoreState = yield select(getAppStoreData);
|
||||
const logs: string[] = [];
|
||||
for (const t of triggers) {
|
||||
const { type } = t;
|
||||
if (type === "STORE_VALUE") {
|
||||
const { key, persist, value } = t.payload;
|
||||
if (persist) {
|
||||
parsedLocalStore[key] = value;
|
||||
}
|
||||
currentStore[key] = value;
|
||||
logs.push(`storeValue('${key}', '${value}', ${persist})`);
|
||||
} else if (type === "REMOVE_VALUE") {
|
||||
const { key } = t.payload;
|
||||
delete parsedLocalStore[key];
|
||||
delete currentStore[key];
|
||||
logs.push(`removeValue('${key}')`);
|
||||
} else if (type === "CLEAR_STORE") {
|
||||
parsedLocalStore = {};
|
||||
currentStore = {};
|
||||
logs.push(`clearStore()`);
|
||||
}
|
||||
}
|
||||
yield put(updateAppStore(currentStore));
|
||||
const storeString = JSON.stringify(parsedLocalStore);
|
||||
localStorage.setItem(appStoreName, storeString);
|
||||
yield put(updateAppPersistentStore(parsedStore));
|
||||
const existingTransientStore: AppStoreState = yield select(getAppStoreData);
|
||||
delete existingTransientStore.transient?.[action.key];
|
||||
yield put(updateAppTransientStore(existingTransientStore.transient));
|
||||
AppsmithConsole.info({
|
||||
text: `remove('${action.key}')`,
|
||||
});
|
||||
}
|
||||
|
||||
export function* clearLocalStore() {
|
||||
const applicationId: string = yield select(getCurrentApplicationId);
|
||||
const branch: string | undefined = yield select(getCurrentGitBranch);
|
||||
const appStoreName = getAppStoreName(applicationId, branch);
|
||||
localStorage.setItem(appStoreName, "{}");
|
||||
yield put(updateAppPersistentStore({}));
|
||||
yield put(updateAppTransientStore({}));
|
||||
AppsmithConsole.info({
|
||||
text: `clear()`,
|
||||
});
|
||||
AppsmithConsole.addLogs(
|
||||
logs.map((text) => ({
|
||||
text,
|
||||
severity: Severity.INFO,
|
||||
category: LOG_CATEGORY.USER_GENERATED,
|
||||
timestamp: moment().format("hh:mm:ss"),
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -617,8 +617,8 @@ export function* storeLogs(
|
|||
entityId: string,
|
||||
) {
|
||||
AppsmithConsole.addLogs(
|
||||
logs.reduce((acc: Log[], log: LogObject) => {
|
||||
acc.push({
|
||||
logs.map((log: LogObject) => {
|
||||
return {
|
||||
text: createLogTitleString(log.data),
|
||||
logData: log.data,
|
||||
source: {
|
||||
|
|
@ -629,9 +629,8 @@ export function* storeLogs(
|
|||
severity: log.severity,
|
||||
timestamp: log.timestamp,
|
||||
category: LOG_CATEGORY.USER_GENERATED,
|
||||
});
|
||||
return acc;
|
||||
}, []),
|
||||
};
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -113,6 +113,7 @@ import {
|
|||
import { BatchedJSExecutionData } from "reducers/entityReducers/jsActionsReducer";
|
||||
import { sortJSExecutionDataByCollectionId } from "workers/Evaluation/JSObject/utils";
|
||||
import { MessageType, TMessage } from "utils/MessageUtil";
|
||||
import { handleStoreOperations } from "./ActionExecution/StoreActionSaga";
|
||||
import { ActionDescription } from "@appsmith/entities/DataTree/actionTriggers";
|
||||
|
||||
const evalWorker = new GracefulWorkerService(
|
||||
|
|
@ -390,6 +391,9 @@ export function* handleEvalWorkerMessage(message: TMessage<any>) {
|
|||
yield call(evalWorker.respond, message.messageId, result);
|
||||
break;
|
||||
}
|
||||
case MAIN_THREAD_ACTION.PROCESS_STORE_UPDATES: {
|
||||
yield call(handleStoreOperations, data);
|
||||
}
|
||||
}
|
||||
yield call(evalErrorHandler, data?.errors || []);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -316,64 +316,66 @@ describe("Add functions", () => {
|
|||
const key = "some";
|
||||
const value = "thing";
|
||||
const persist = false;
|
||||
const uniqueActionRequestId = "kjebd";
|
||||
|
||||
// @ts-expect-error: mockReturnValueOnce is not available on uniqueId
|
||||
uniqueId.mockReturnValueOnce(uniqueActionRequestId);
|
||||
|
||||
expect(evalContext.storeValue(key, value, persist)).resolves.toBe({});
|
||||
expect(workerEventMock).lastCalledWith(
|
||||
messageCreator("STORE_VALUE", {
|
||||
data: {
|
||||
trigger: {
|
||||
type: "STORE_VALUE",
|
||||
payload: {
|
||||
key,
|
||||
value,
|
||||
persist,
|
||||
uniqueActionRequestId,
|
||||
},
|
||||
},
|
||||
eventType: undefined,
|
||||
},
|
||||
method: "PROCESS_TRIGGER",
|
||||
}),
|
||||
jest.useFakeTimers();
|
||||
expect(evalContext.storeValue(key, value, persist)).resolves.toStrictEqual(
|
||||
{},
|
||||
);
|
||||
jest.runAllTimers();
|
||||
expect(workerEventMock).lastCalledWith({
|
||||
messageType: "DEFAULT",
|
||||
body: {
|
||||
data: [
|
||||
{
|
||||
payload: {
|
||||
key: "some",
|
||||
persist: false,
|
||||
value: "thing",
|
||||
},
|
||||
type: "STORE_VALUE",
|
||||
},
|
||||
],
|
||||
method: "PROCESS_STORE_UPDATES",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("removeValue works", () => {
|
||||
const key = "some";
|
||||
expect(evalContext.removeValue(key)).resolves.toBe({});
|
||||
expect(workerEventMock).lastCalledWith(
|
||||
messageCreator("REMOVE_VALUE", {
|
||||
data: {
|
||||
trigger: {
|
||||
type: "REMOVE_VALUE",
|
||||
jest.useFakeTimers();
|
||||
expect(evalContext.removeValue(key)).resolves.toStrictEqual({});
|
||||
jest.runAllTimers();
|
||||
expect(workerEventMock).lastCalledWith({
|
||||
messageType: "DEFAULT",
|
||||
body: {
|
||||
data: [
|
||||
{
|
||||
payload: {
|
||||
key,
|
||||
},
|
||||
type: "REMOVE_VALUE",
|
||||
},
|
||||
eventType: undefined,
|
||||
},
|
||||
method: "PROCESS_TRIGGER",
|
||||
}),
|
||||
);
|
||||
],
|
||||
method: "PROCESS_STORE_UPDATES",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("clearStore works", () => {
|
||||
expect(evalContext.clearStore()).resolves.toBe({});
|
||||
expect(workerEventMock).lastCalledWith(
|
||||
messageCreator("CLEAR_STORE", {
|
||||
data: {
|
||||
trigger: {
|
||||
type: "CLEAR_STORE",
|
||||
jest.useFakeTimers();
|
||||
expect(evalContext.clearStore()).resolves.toStrictEqual({});
|
||||
jest.runAllTimers();
|
||||
expect(workerEventMock).lastCalledWith({
|
||||
messageType: "DEFAULT",
|
||||
body: {
|
||||
data: [
|
||||
{
|
||||
payload: null,
|
||||
type: "CLEAR_STORE",
|
||||
},
|
||||
eventType: undefined,
|
||||
},
|
||||
method: "PROCESS_TRIGGER",
|
||||
}),
|
||||
);
|
||||
],
|
||||
method: "PROCESS_STORE_UPDATES",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("download works", () => {
|
||||
|
|
|
|||
|
|
@ -250,10 +250,7 @@ describe("isFunctionAsync", () => {
|
|||
if (typeof testFunc === "string") {
|
||||
testFunc = eval(testFunc);
|
||||
}
|
||||
|
||||
functionDeterminer.setupEval({}, {});
|
||||
addPlatformFunctionsToEvalContext(self);
|
||||
|
||||
const actual = functionDeterminer.isFunctionAsync(testFunc);
|
||||
expect(actual).toBe(testCase.expected);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ import { DOM_APIS } from "./SetupDOM";
|
|||
import { JSLibraries, libraryReservedIdentifiers } from "../common/JSLibrary";
|
||||
import { errorModifier, FoundPromiseInSyncEvalError } from "./errorModifier";
|
||||
import { addDataTreeToContext } from "@appsmith/workers/Evaluation/Actions";
|
||||
import { PLATFORM_FUNCTIONS } from "@appsmith/workers/Evaluation/PlatformFunctions";
|
||||
|
||||
export type EvalResult = {
|
||||
result: any;
|
||||
|
|
@ -83,7 +82,6 @@ function resetWorkerGlobalScope() {
|
|||
continue;
|
||||
if (JSLibraries.find((lib) => lib.accessor.includes(key))) continue;
|
||||
if (libraryReservedIdentifiers[key]) continue;
|
||||
if (PLATFORM_FUNCTIONS[key]) continue;
|
||||
try {
|
||||
// @ts-expect-error: Types are not available
|
||||
delete self[key];
|
||||
|
|
|
|||
16
app/client/src/workers/Evaluation/fns/fetch.ts
Normal file
16
app/client/src/workers/Evaluation/fns/fetch.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
const _originalFetch = self.fetch;
|
||||
|
||||
export default function interceptAndOverrideHttpRequest() {
|
||||
Object.defineProperty(self, "fetch", {
|
||||
writable: false,
|
||||
configurable: false,
|
||||
value: function(...args: any) {
|
||||
if (!self.ALLOW_ASYNC) {
|
||||
self.IS_ASYNC = true;
|
||||
return;
|
||||
}
|
||||
const request = new Request(args[0], { ...args[1], credentials: "omit" });
|
||||
return _originalFetch(request);
|
||||
},
|
||||
});
|
||||
}
|
||||
54
app/client/src/workers/Evaluation/fns/storeFns.ts
Normal file
54
app/client/src/workers/Evaluation/fns/storeFns.ts
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
import {
|
||||
RemoveValueActionDescription,
|
||||
StoreValueActionDescription,
|
||||
} from "ce/entities/DataTree/actionTriggers";
|
||||
import set from "lodash/set";
|
||||
import { MAIN_THREAD_ACTION } from "@appsmith/workers/Evaluation/evalWorkerActions";
|
||||
import { addFn } from "./utils/fnGuard";
|
||||
import { TriggerCollector } from "./utils/TriggerCollector";
|
||||
|
||||
export function initStoreFns(ctx: typeof globalThis) {
|
||||
const triggerCollector = new TriggerCollector(
|
||||
MAIN_THREAD_ACTION.PROCESS_STORE_UPDATES,
|
||||
);
|
||||
function storeValue(key: string, value: string, persist = true) {
|
||||
const requestPayload: StoreValueActionDescription = {
|
||||
type: "STORE_VALUE",
|
||||
payload: {
|
||||
key,
|
||||
value,
|
||||
persist,
|
||||
},
|
||||
};
|
||||
set(self, ["appsmith", "store", key], value);
|
||||
triggerCollector.collect(requestPayload);
|
||||
return Promise.resolve({});
|
||||
}
|
||||
|
||||
function removeValue(key: string) {
|
||||
const requestPayload: RemoveValueActionDescription = {
|
||||
type: "REMOVE_VALUE",
|
||||
payload: {
|
||||
key,
|
||||
},
|
||||
};
|
||||
//@ts-expect-error no types for store
|
||||
delete self.appsmith.store[key];
|
||||
triggerCollector.collect(requestPayload);
|
||||
return Promise.resolve({});
|
||||
}
|
||||
|
||||
function clearStore() {
|
||||
//@ts-expect-error no types for store
|
||||
self.appsmith.store = {};
|
||||
triggerCollector.collect({
|
||||
type: "CLEAR_STORE",
|
||||
payload: null,
|
||||
});
|
||||
return Promise.resolve({});
|
||||
}
|
||||
|
||||
addFn(ctx, "storeValue", storeValue);
|
||||
addFn(ctx, "removeValue", removeValue);
|
||||
addFn(ctx, "clearStore", clearStore);
|
||||
}
|
||||
45
app/client/src/workers/Evaluation/fns/timeout.ts
Normal file
45
app/client/src/workers/Evaluation/fns/timeout.ts
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
import { ActionCalledInSyncFieldError } from "../errorModifier";
|
||||
import { createEvaluationContext } from "../evaluate";
|
||||
import { dataTreeEvaluator } from "../handlers/evalTree";
|
||||
|
||||
export const _internalSetTimeout = self.setTimeout;
|
||||
export const _internalClearTimeout = self.clearTimeout;
|
||||
|
||||
export default function overrideTimeout() {
|
||||
Object.defineProperty(self, "setTimeout", {
|
||||
writable: true,
|
||||
configurable: true,
|
||||
value: function(cb: (...args: any) => any, delay: number, ...args: any) {
|
||||
if (!self.ALLOW_ASYNC) {
|
||||
self.IS_ASYNC = true;
|
||||
throw new ActionCalledInSyncFieldError("setTimeout");
|
||||
}
|
||||
const evalContext = createEvaluationContext({
|
||||
dataTree: dataTreeEvaluator?.evalTree || {},
|
||||
resolvedFunctions: dataTreeEvaluator?.resolvedFunctions || {},
|
||||
isTriggerBased: true,
|
||||
});
|
||||
return _internalSetTimeout(
|
||||
function(...args: any) {
|
||||
self.ALLOW_ASYNC = true;
|
||||
Object.assign(self, evalContext);
|
||||
cb(...args);
|
||||
},
|
||||
delay,
|
||||
...args,
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
Object.defineProperty(self, "clearTimeout", {
|
||||
writable: true,
|
||||
configurable: true,
|
||||
value: function(timerId: number) {
|
||||
if (!self.ALLOW_ASYNC) {
|
||||
self.IS_ASYNC = true;
|
||||
throw new ActionCalledInSyncFieldError("clearTimeout");
|
||||
}
|
||||
return _internalClearTimeout(timerId);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
import { MessageType, sendMessage } from "utils/MessageUtil";
|
||||
import { MAIN_THREAD_ACTION } from "@appsmith/workers/Evaluation/evalWorkerActions";
|
||||
export class TriggerCollector {
|
||||
private triggers: unknown[] = [];
|
||||
constructor(private requestType: MAIN_THREAD_ACTION) {}
|
||||
collect = (trigger: unknown) => {
|
||||
if (this.triggers.length === 0) {
|
||||
queueMicrotask(() => {
|
||||
sendMessage.call(self, {
|
||||
messageType: MessageType.DEFAULT,
|
||||
body: {
|
||||
method: this.requestType,
|
||||
data: this.triggers,
|
||||
},
|
||||
});
|
||||
this.triggers = [];
|
||||
});
|
||||
}
|
||||
this.triggers.push(trigger);
|
||||
};
|
||||
}
|
||||
26
app/client/src/workers/Evaluation/fns/utils/fnGuard.ts
Normal file
26
app/client/src/workers/Evaluation/fns/utils/fnGuard.ts
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
import { ActionCalledInSyncFieldError } from "workers/Evaluation/errorModifier";
|
||||
|
||||
export function addFn(
|
||||
ctx: typeof globalThis,
|
||||
fnName: string,
|
||||
fn: (...args: any[]) => any,
|
||||
fnGuards = [isAsyncGuard],
|
||||
) {
|
||||
Object.defineProperty(ctx, fnName, {
|
||||
value: function(...args: any[]) {
|
||||
for (const guard of fnGuards) {
|
||||
guard(fn, fnName);
|
||||
}
|
||||
return fn(...args);
|
||||
},
|
||||
configurable: true,
|
||||
writable: true,
|
||||
enumerable: false,
|
||||
});
|
||||
}
|
||||
|
||||
export function isAsyncGuard(_: (...args: any[]) => any, fnName: string) {
|
||||
if (self.ALLOW_ASYNC) return;
|
||||
self.IS_ASYNC = true;
|
||||
throw new ActionCalledInSyncFieldError(fnName);
|
||||
}
|
||||
|
|
@ -53,7 +53,7 @@ import { LintErrors } from "reducers/lintingReducers/lintErrorsReducers";
|
|||
import { Severity } from "entities/AppsmithConsole";
|
||||
import { JSLibraries } from "workers/common/JSLibrary";
|
||||
import { MessageType, sendMessage } from "utils/MessageUtil";
|
||||
import { addPlatformFunctionsToEvalContext } from "@appsmith/workers/Evaluation/Actions";
|
||||
import { ActionTriggerFunctionNames } from "ce/entities/DataTree/actionTriggers";
|
||||
|
||||
export function getlintErrorsFromTree(
|
||||
pathsToLint: string[],
|
||||
|
|
@ -68,7 +68,11 @@ export function getlintErrorsFromTree(
|
|||
skipEntityFunctions: true,
|
||||
});
|
||||
|
||||
addPlatformFunctionsToEvalContext(evalContext);
|
||||
const platformFnNamesMap = Object.values(ActionTriggerFunctionNames).reduce(
|
||||
(acc, name) => ({ ...acc, [name]: true }),
|
||||
{} as { [x: string]: boolean },
|
||||
);
|
||||
Object.assign(evalContext, platformFnNamesMap);
|
||||
|
||||
const evalContextWithOutFunctions = createEvaluationContext({
|
||||
dataTree: unEvalTree,
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user