fix: improve error message and performance in JS functions (#19137)
## Description
- Added logic to replace async function undefined error with
"{{actionName}} cannot be used in this field".
- This change improves performance for
- ParseJSActions
- Triggers execution
- Each Appsmith framework action execution.
- This change adds all platform functions to evalContext permanently.
Fixes #12179
Fixes #13273
Internal discussion for error message :-
https://theappsmith.slack.com/archives/C02K0SZQ7V3/p1667457021297869?thread_ts=1667385039.225229&cid=C02K0SZQ7V3
## Type of change
- Bug fix (non-breaking change which fixes an issue)
- Performance improvement
## How Has This Been Tested?
- Manual
- Jest
- Cypress
### Test Plan
- [ ] https://github.com/appsmithorg/TestSmith/issues/2086
### 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
Co-authored-by: Aishwarya UR <aishwarya@appsmith.com>
This commit is contained in:
parent
bfd242f627
commit
6b751d914e
|
|
@ -11,6 +11,9 @@ describe("Dynamic input autocomplete", () => {
|
||||||
cy.wait(3000);
|
cy.wait(3000);
|
||||||
cy.selectEntityByName("Aditya");
|
cy.selectEntityByName("Aditya");
|
||||||
cy.openPropertyPane("buttonwidget");
|
cy.openPropertyPane("buttonwidget");
|
||||||
|
cy.testJsontext("label", "", {
|
||||||
|
parseSpecialCharSequences: true,
|
||||||
|
});
|
||||||
cy.get(dynamicInputLocators.input)
|
cy.get(dynamicInputLocators.input)
|
||||||
.first()
|
.first()
|
||||||
.click({ force: true })
|
.click({ force: true })
|
||||||
|
|
@ -27,22 +30,29 @@ describe("Dynamic input autocomplete", () => {
|
||||||
|
|
||||||
// Tests if autocomplete will open
|
// Tests if autocomplete will open
|
||||||
cy.get(dynamicInputLocators.hints).should("exist");
|
cy.get(dynamicInputLocators.hints).should("exist");
|
||||||
|
|
||||||
// Tests if data tree entities are sorted
|
// Tests if data tree entities are sorted
|
||||||
cy.get(`${dynamicInputLocators.hints} li`)
|
cy.get(`${dynamicInputLocators.hints} li`)
|
||||||
.eq(1)
|
.eq(1)
|
||||||
.should("have.text", "Button1.text");
|
.should("have.text", "Button1.text");
|
||||||
|
|
||||||
|
cy.testJsontext("label", "", {
|
||||||
|
parseSpecialCharSequences: true,
|
||||||
|
});
|
||||||
// Tests if "No suggestions" message will pop if you type any garbage
|
// Tests if "No suggestions" message will pop if you type any garbage
|
||||||
cy.get(dynamicInputLocators.input)
|
cy.get(dynamicInputLocators.input)
|
||||||
.first()
|
.first()
|
||||||
.click({ force: true })
|
.click({ force: true })
|
||||||
.type("{uparrow}", { parseSpecialCharSequences: true })
|
.type("{uparrow}", { parseSpecialCharSequences: true })
|
||||||
.type("{ctrl}{shift}{downarrow}", { parseSpecialCharSequences: true })
|
.type("{ctrl}{shift}{downarrow}", { parseSpecialCharSequences: true })
|
||||||
.type("{{ garbage", {
|
.type("{backspace}", { parseSpecialCharSequences: true })
|
||||||
parseSpecialCharSequences: true,
|
|
||||||
})
|
|
||||||
.then(() => {
|
.then(() => {
|
||||||
|
cy.get(dynamicInputLocators.input)
|
||||||
|
.first()
|
||||||
|
.click({ force: true })
|
||||||
|
.type("{{garbage", {
|
||||||
|
parseSpecialCharSequences: true,
|
||||||
|
});
|
||||||
cy.get(".CodeMirror-Tern-tooltip").should(
|
cy.get(".CodeMirror-Tern-tooltip").should(
|
||||||
"have.text",
|
"have.text",
|
||||||
"No suggestions",
|
"No suggestions",
|
||||||
|
|
@ -51,6 +61,24 @@ describe("Dynamic input autocomplete", () => {
|
||||||
});
|
});
|
||||||
cy.evaluateErrorMessage("ReferenceError: garbage is not defined");
|
cy.evaluateErrorMessage("ReferenceError: garbage is not defined");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("test if action inside non event field throws error", () => {
|
||||||
|
cy.get(dynamicInputLocators.input)
|
||||||
|
.first()
|
||||||
|
.click({ force: true })
|
||||||
|
.type("{backspace}".repeat(12))
|
||||||
|
.type("{{storeValue()}}", { parseSpecialCharSequences: false });
|
||||||
|
|
||||||
|
cy.wait(1000);
|
||||||
|
|
||||||
|
cy.evaluateErrorMessage(
|
||||||
|
"Found a reference to {{actionName}} during evaluation. Sync fields cannot execute framework actions. Please remove any direct/indirect references to {{actionName}} and try again.".replaceAll(
|
||||||
|
"{{actionName}}",
|
||||||
|
"storeValue()",
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it("opens current value popup", () => {
|
it("opens current value popup", () => {
|
||||||
// Test on api pane
|
// Test on api pane
|
||||||
cy.NavigateToAPI_Panel();
|
cy.NavigateToAPI_Panel();
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,8 @@ export enum ActionTriggerType {
|
||||||
STOP_WATCHING_CURRENT_LOCATION = "STOP_WATCHING_CURRENT_LOCATION",
|
STOP_WATCHING_CURRENT_LOCATION = "STOP_WATCHING_CURRENT_LOCATION",
|
||||||
CONFIRMATION_MODAL = "CONFIRMATION_MODAL",
|
CONFIRMATION_MODAL = "CONFIRMATION_MODAL",
|
||||||
POST_MESSAGE = "POST_MESSAGE",
|
POST_MESSAGE = "POST_MESSAGE",
|
||||||
|
SET_TIMEOUT = "SET_TIMEOUT",
|
||||||
|
CLEAR_TIMEOUT = "CLEAR_TIMEOUT",
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ActionTriggerFunctionNames: Record<ActionTriggerType, string> = {
|
export const ActionTriggerFunctionNames: Record<ActionTriggerType, string> = {
|
||||||
|
|
@ -43,6 +45,8 @@ export const ActionTriggerFunctionNames: Record<ActionTriggerType, string> = {
|
||||||
[ActionTriggerType.STOP_WATCHING_CURRENT_LOCATION]: "stopWatch",
|
[ActionTriggerType.STOP_WATCHING_CURRENT_LOCATION]: "stopWatch",
|
||||||
[ActionTriggerType.CONFIRMATION_MODAL]: "ConfirmationModal",
|
[ActionTriggerType.CONFIRMATION_MODAL]: "ConfirmationModal",
|
||||||
[ActionTriggerType.POST_MESSAGE]: "postWindowMessage",
|
[ActionTriggerType.POST_MESSAGE]: "postWindowMessage",
|
||||||
|
[ActionTriggerType.SET_TIMEOUT]: "setTimeout",
|
||||||
|
[ActionTriggerType.CLEAR_TIMEOUT]: "clearTimeout",
|
||||||
};
|
};
|
||||||
|
|
||||||
export type RunPluginActionDescription = {
|
export type RunPluginActionDescription = {
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,19 @@
|
||||||
/* eslint-disable @typescript-eslint/ban-types */
|
/* eslint-disable @typescript-eslint/ban-types */
|
||||||
import { DataTree, DataTreeEntity } from "entities/DataTree/dataTreeFactory";
|
import { DataTree, DataTreeEntity } from "entities/DataTree/dataTreeFactory";
|
||||||
import _ from "lodash";
|
import _, { set } from "lodash";
|
||||||
import {
|
import {
|
||||||
ActionDescription,
|
ActionDescription,
|
||||||
|
ActionTriggerFunctionNames,
|
||||||
ActionTriggerType,
|
ActionTriggerType,
|
||||||
} from "@appsmith/entities/DataTree/actionTriggers";
|
} from "@appsmith/entities/DataTree/actionTriggers";
|
||||||
import { NavigationTargetType } from "sagas/ActionExecution/NavigateActionSaga";
|
import { NavigationTargetType } from "sagas/ActionExecution/NavigateActionSaga";
|
||||||
import { promisifyAction } from "workers/Evaluation/PromisifyAction";
|
import { promisifyAction } from "workers/Evaluation/PromisifyAction";
|
||||||
import { klona } from "klona/full";
|
|
||||||
import uniqueId from "lodash/uniqueId";
|
import uniqueId from "lodash/uniqueId";
|
||||||
import { EventType } from "constants/AppsmithActionConstants/ActionConstants";
|
import { EventType } from "constants/AppsmithActionConstants/ActionConstants";
|
||||||
import { isAction, isAppsmithEntity, isTrueObject } from "./evaluationUtils";
|
import { isAction, isAppsmithEntity, isTrueObject } from "./evaluationUtils";
|
||||||
|
import { EvalContext } from "workers/Evaluation/evaluate";
|
||||||
|
import { ActionCalledInSyncFieldError } from "workers/Evaluation/errorModifier";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
/** All identifiers added to the worker global scope should also
|
/** All identifiers added to the worker global scope should also
|
||||||
* be included in the DEDICATED_WORKER_GLOBAL_SCOPE_IDENTIFIERS in
|
* be included in the DEDICATED_WORKER_GLOBAL_SCOPE_IDENTIFIERS in
|
||||||
|
|
@ -37,14 +40,9 @@ type ActionDispatcherWithExecutionType = (
|
||||||
...args: any[]
|
...args: any[]
|
||||||
) => ActionDescriptionWithExecutionType;
|
) => ActionDescriptionWithExecutionType;
|
||||||
|
|
||||||
export const DATA_TREE_FUNCTIONS: Record<
|
export const PLATFORM_FUNCTIONS: Record<
|
||||||
string,
|
string,
|
||||||
| ActionDispatcherWithExecutionType
|
ActionDispatcherWithExecutionType
|
||||||
| {
|
|
||||||
qualifier: (entity: DataTreeEntity) => boolean;
|
|
||||||
func: (entity: DataTreeEntity) => ActionDispatcherWithExecutionType;
|
|
||||||
path?: string;
|
|
||||||
}
|
|
||||||
> = {
|
> = {
|
||||||
navigateTo: function(
|
navigateTo: function(
|
||||||
pageNameOrUrl: string,
|
pageNameOrUrl: string,
|
||||||
|
|
@ -136,6 +134,51 @@ export const DATA_TREE_FUNCTIONS: Record<
|
||||||
executionType: ExecutionType.PROMISE,
|
executionType: ExecutionType.PROMISE,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
setInterval: function(callback: Function, interval: number, id?: string) {
|
||||||
|
return {
|
||||||
|
type: ActionTriggerType.SET_INTERVAL,
|
||||||
|
payload: {
|
||||||
|
callback: callback?.toString(),
|
||||||
|
interval,
|
||||||
|
id,
|
||||||
|
},
|
||||||
|
executionType: ExecutionType.TRIGGER,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
clearInterval: function(id: string) {
|
||||||
|
return {
|
||||||
|
type: ActionTriggerType.CLEAR_INTERVAL,
|
||||||
|
payload: {
|
||||||
|
id,
|
||||||
|
},
|
||||||
|
executionType: ExecutionType.TRIGGER,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
postWindowMessage: function(
|
||||||
|
message: unknown,
|
||||||
|
source: string,
|
||||||
|
targetOrigin: string,
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
type: ActionTriggerType.POST_MESSAGE,
|
||||||
|
payload: {
|
||||||
|
message,
|
||||||
|
source,
|
||||||
|
targetOrigin,
|
||||||
|
},
|
||||||
|
executionType: ExecutionType.TRIGGER,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const ENTITY_FUNCTIONS: Record<
|
||||||
|
string,
|
||||||
|
{
|
||||||
|
qualifier: (entity: DataTreeEntity) => boolean;
|
||||||
|
func: (entity: DataTreeEntity) => ActionDispatcherWithExecutionType;
|
||||||
|
path?: string;
|
||||||
|
}
|
||||||
|
> = {
|
||||||
run: {
|
run: {
|
||||||
qualifier: (entity) => isAction(entity),
|
qualifier: (entity) => isAction(entity),
|
||||||
func: (entity) =>
|
func: (entity) =>
|
||||||
|
|
@ -190,26 +233,6 @@ export const DATA_TREE_FUNCTIONS: Record<
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
setInterval: function(callback: Function, interval: number, id?: string) {
|
|
||||||
return {
|
|
||||||
type: ActionTriggerType.SET_INTERVAL,
|
|
||||||
payload: {
|
|
||||||
callback: callback.toString(),
|
|
||||||
interval,
|
|
||||||
id,
|
|
||||||
},
|
|
||||||
executionType: ExecutionType.TRIGGER,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
clearInterval: function(id: string) {
|
|
||||||
return {
|
|
||||||
type: ActionTriggerType.CLEAR_INTERVAL,
|
|
||||||
payload: {
|
|
||||||
id,
|
|
||||||
},
|
|
||||||
executionType: ExecutionType.TRIGGER,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
getGeoLocation: {
|
getGeoLocation: {
|
||||||
qualifier: (entity) => isAppsmithEntity(entity),
|
qualifier: (entity) => isAppsmithEntity(entity),
|
||||||
path: "appsmith.geolocation.getCurrentPosition",
|
path: "appsmith.geolocation.getCurrentPosition",
|
||||||
|
|
@ -281,70 +304,86 @@ export const DATA_TREE_FUNCTIONS: Record<
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
postWindowMessage: function(
|
|
||||||
message: unknown,
|
|
||||||
source: string,
|
|
||||||
targetOrigin: string,
|
|
||||||
) {
|
|
||||||
return {
|
|
||||||
type: ActionTriggerType.POST_MESSAGE,
|
|
||||||
payload: {
|
|
||||||
message,
|
|
||||||
source,
|
|
||||||
targetOrigin,
|
|
||||||
},
|
|
||||||
executionType: ExecutionType.TRIGGER,
|
|
||||||
};
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const enhanceDataTreeWithFunctions = (
|
const platformFunctionEntries = Object.entries(PLATFORM_FUNCTIONS);
|
||||||
dataTree: Readonly<DataTree>,
|
const entityFunctionEntries = Object.entries(ENTITY_FUNCTIONS);
|
||||||
// Whether not to add functions like "run", "clear" to entity
|
/**
|
||||||
skipEntityFunctions = false,
|
* This method returns new dataTree with entity function and platform function
|
||||||
eventType?: EventType,
|
*/
|
||||||
): DataTree => {
|
export const addDataTreeToContext = (args: {
|
||||||
const clonedDT = klona(dataTree);
|
EVAL_CONTEXT: EvalContext;
|
||||||
|
dataTree: Readonly<DataTree>;
|
||||||
|
skipEntityFunctions?: boolean;
|
||||||
|
eventType?: EventType;
|
||||||
|
isTriggerBased: boolean;
|
||||||
|
}) => {
|
||||||
|
const {
|
||||||
|
dataTree,
|
||||||
|
EVAL_CONTEXT,
|
||||||
|
eventType,
|
||||||
|
isTriggerBased,
|
||||||
|
skipEntityFunctions = false,
|
||||||
|
} = args;
|
||||||
|
const dataTreeEntries = Object.entries(dataTree);
|
||||||
|
const entityFunctionCollection: Record<string, Record<string, Function>> = {};
|
||||||
|
|
||||||
self.TRIGGER_COLLECTOR = [];
|
self.TRIGGER_COLLECTOR = [];
|
||||||
Object.entries(DATA_TREE_FUNCTIONS).forEach(([name, funcOrFuncCreator]) => {
|
|
||||||
if (
|
for (const [entityName, entity] of dataTreeEntries) {
|
||||||
typeof funcOrFuncCreator === "object" &&
|
EVAL_CONTEXT[entityName] = entity;
|
||||||
"qualifier" in funcOrFuncCreator
|
if (skipEntityFunctions || !isTriggerBased) continue;
|
||||||
) {
|
|
||||||
!skipEntityFunctions &&
|
for (const [functionName, funcCreator] of entityFunctionEntries) {
|
||||||
Object.entries(dataTree).forEach(([entityName, entity]) => {
|
if (!funcCreator.qualifier(entity)) continue;
|
||||||
if (funcOrFuncCreator.qualifier(entity)) {
|
const func = funcCreator.func(entity);
|
||||||
const func = funcOrFuncCreator.func(entity);
|
const fullPath = `${funcCreator.path || `${entityName}.${functionName}`}`;
|
||||||
const funcName = `${funcOrFuncCreator.path ||
|
set(
|
||||||
`${entityName}.${name}`}`;
|
entityFunctionCollection,
|
||||||
_.set(
|
fullPath,
|
||||||
clonedDT,
|
|
||||||
funcName,
|
|
||||||
pusher.bind(
|
|
||||||
{
|
|
||||||
TRIGGER_COLLECTOR: self.TRIGGER_COLLECTOR,
|
|
||||||
EVENT_TYPE: eventType,
|
|
||||||
},
|
|
||||||
func,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
_.set(
|
|
||||||
clonedDT,
|
|
||||||
name,
|
|
||||||
pusher.bind(
|
pusher.bind(
|
||||||
{
|
{
|
||||||
TRIGGER_COLLECTOR: self.TRIGGER_COLLECTOR,
|
EVENT_TYPE: eventType,
|
||||||
},
|
},
|
||||||
funcOrFuncCreator,
|
func,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
|
||||||
return clonedDT;
|
// if eval is not trigger based i.e., sync eval then we skip adding entity and platform function to evalContext
|
||||||
|
if (!isTriggerBased) return;
|
||||||
|
|
||||||
|
for (const [entityName, funcObj] of Object.entries(
|
||||||
|
entityFunctionCollection,
|
||||||
|
)) {
|
||||||
|
EVAL_CONTEXT[entityName] = Object.assign({}, dataTree[entityName], funcObj);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const addPlatformFunctionsToEvalContext = (context: any) => {
|
||||||
|
for (const [funcName, fn] of platformFunctionEntries) {
|
||||||
|
context[funcName] = pusher.bind({}, fn);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getAllAsyncFunctions = (dataTree: DataTree) => {
|
||||||
|
const asyncFunctionNameMap: Record<string, true> = {};
|
||||||
|
const dataTreeEntries = Object.entries(dataTree);
|
||||||
|
|
||||||
|
for (const [entityName, entity] of dataTreeEntries) {
|
||||||
|
for (const [functionName, funcCreator] of entityFunctionEntries) {
|
||||||
|
if (!funcCreator.qualifier(entity)) continue;
|
||||||
|
const fullPath = `${funcCreator.path || `${entityName}.${functionName}`}`;
|
||||||
|
asyncFunctionNameMap[fullPath] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [name] of platformFunctionEntries) {
|
||||||
|
asyncFunctionNameMap[name] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return asyncFunctionNameMap;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -363,13 +402,17 @@ export const enhanceDataTreeWithFunctions = (
|
||||||
* **/
|
* **/
|
||||||
export const pusher = function(
|
export const pusher = function(
|
||||||
this: {
|
this: {
|
||||||
TRIGGER_COLLECTOR: ActionDescription[];
|
|
||||||
EVENT_TYPE?: EventType;
|
EVENT_TYPE?: EventType;
|
||||||
},
|
},
|
||||||
action: ActionDispatcherWithExecutionType,
|
action: ActionDispatcherWithExecutionType,
|
||||||
...args: any[]
|
...args: any[]
|
||||||
) {
|
) {
|
||||||
const actionDescription = action(...args);
|
const actionDescription = action(...args);
|
||||||
|
if (!self.ALLOW_ASYNC) {
|
||||||
|
self.IS_ASYNC = true;
|
||||||
|
const actionName = ActionTriggerFunctionNames[actionDescription.type];
|
||||||
|
throw new ActionCalledInSyncFieldError(actionName);
|
||||||
|
}
|
||||||
const { executionType, payload, type } = actionDescription;
|
const { executionType, payload, type } = actionDescription;
|
||||||
const actionPayload = {
|
const actionPayload = {
|
||||||
type,
|
type,
|
||||||
|
|
@ -377,7 +420,7 @@ export const pusher = function(
|
||||||
} as ActionDescription;
|
} as ActionDescription;
|
||||||
|
|
||||||
if (executionType && executionType === ExecutionType.TRIGGER) {
|
if (executionType && executionType === ExecutionType.TRIGGER) {
|
||||||
this.TRIGGER_COLLECTOR.push(actionPayload);
|
self.TRIGGER_COLLECTOR.push(actionPayload);
|
||||||
} else {
|
} else {
|
||||||
return promisifyAction(actionPayload, this.EVENT_TYPE);
|
return promisifyAction(actionPayload, this.EVENT_TYPE);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,7 @@ export function makeEntityConfigsAsObjProperties(
|
||||||
for (const entityName of Object.keys(dataTree)) {
|
for (const entityName of Object.keys(dataTree)) {
|
||||||
const entityConfig = Object.getPrototypeOf(dataTree[entityName]) || {};
|
const entityConfig = Object.getPrototypeOf(dataTree[entityName]) || {};
|
||||||
const entity = dataTree[entityName];
|
const entity = dataTree[entityName];
|
||||||
newDataTree[entityName] = { ...entityConfig, ...entity };
|
newDataTree[entityName] = Object.assign({}, entityConfig, entity);
|
||||||
}
|
}
|
||||||
const dataTreeToReturn = sanitizeDataTree
|
const dataTreeToReturn = sanitizeDataTree
|
||||||
? JSON.parse(JSON.stringify(newDataTree))
|
? JSON.parse(JSON.stringify(newDataTree))
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,7 @@ const getOptions = (type?: string, subType?: string) => {
|
||||||
CONTEXT_MENU_ACTIONS.INTERCOM,
|
CONTEXT_MENU_ACTIONS.INTERCOM,
|
||||||
];
|
];
|
||||||
case PropertyEvaluationErrorType.PARSE:
|
case PropertyEvaluationErrorType.PARSE:
|
||||||
return [CONTEXT_MENU_ACTIONS.SNIPPET];
|
return [CONTEXT_MENU_ACTIONS.DOCS, CONTEXT_MENU_ACTIONS.SNIPPET];
|
||||||
case PropertyEvaluationErrorType.LINT:
|
case PropertyEvaluationErrorType.LINT:
|
||||||
return [CONTEXT_MENU_ACTIONS.SNIPPET];
|
return [CONTEXT_MENU_ACTIONS.SNIPPET];
|
||||||
default:
|
default:
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import { EvalErrorTypes } from "utils/DynamicBindingUtils";
|
||||||
import { JSUpdate, ParsedJSSubAction } from "utils/JSPaneUtils";
|
import { JSUpdate, ParsedJSSubAction } from "utils/JSPaneUtils";
|
||||||
import { isTypeOfFunction, parseJSObjectWithAST } from "@shared/ast";
|
import { isTypeOfFunction, parseJSObjectWithAST } from "@shared/ast";
|
||||||
import DataTreeEvaluator from "workers/common/DataTreeEvaluator";
|
import DataTreeEvaluator from "workers/common/DataTreeEvaluator";
|
||||||
import evaluateSync, { isFunctionAsync } from "workers/Evaluation/evaluate";
|
import evaluateSync from "workers/Evaluation/evaluate";
|
||||||
import {
|
import {
|
||||||
DataTreeDiff,
|
DataTreeDiff,
|
||||||
DataTreeDiffEvent,
|
DataTreeDiffEvent,
|
||||||
|
|
@ -15,6 +15,7 @@ import {
|
||||||
removeFunctionsAndVariableJSCollection,
|
removeFunctionsAndVariableJSCollection,
|
||||||
updateJSCollectionInUnEvalTree,
|
updateJSCollectionInUnEvalTree,
|
||||||
} from "workers/Evaluation/JSObject/utils";
|
} from "workers/Evaluation/JSObject/utils";
|
||||||
|
import { functionDeterminer } from "../functionDeterminer";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Here we update our unEvalTree according to the change in JSObject's body
|
* Here we update our unEvalTree according to the change in JSObject's body
|
||||||
|
|
@ -246,16 +247,19 @@ export function parseJSActions(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
functionDeterminer.setupEval(
|
||||||
|
unEvalDataTree,
|
||||||
|
dataTreeEvalRef.resolvedFunctions,
|
||||||
|
);
|
||||||
|
|
||||||
Object.keys(jsUpdates).forEach((entityName) => {
|
Object.keys(jsUpdates).forEach((entityName) => {
|
||||||
const parsedBody = jsUpdates[entityName].parsedBody;
|
const parsedBody = jsUpdates[entityName].parsedBody;
|
||||||
if (!parsedBody) return;
|
if (!parsedBody) return;
|
||||||
parsedBody.actions = parsedBody.actions.map((action) => {
|
parsedBody.actions = parsedBody.actions.map((action) => {
|
||||||
return {
|
return {
|
||||||
...action,
|
...action,
|
||||||
isAsync: isFunctionAsync(
|
isAsync: functionDeterminer.isFunctionAsync(
|
||||||
action.parsedFunction,
|
action.parsedFunction,
|
||||||
unEvalDataTree,
|
|
||||||
dataTreeEvalRef.resolvedFunctions,
|
|
||||||
dataTreeEvalRef.logs,
|
dataTreeEvalRef.logs,
|
||||||
),
|
),
|
||||||
// parsedFunction - used only to determine if function is async
|
// parsedFunction - used only to determine if function is async
|
||||||
|
|
@ -263,6 +267,9 @@ export function parseJSActions(
|
||||||
} as ParsedJSSubAction;
|
} as ParsedJSSubAction;
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
functionDeterminer.setOffEval();
|
||||||
|
|
||||||
return { jsUpdates };
|
return { jsUpdates };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { createGlobalData } from "workers/Evaluation/evaluate";
|
import { createEvaluationContext } from "workers/Evaluation/evaluate";
|
||||||
const ctx: Worker = self as any;
|
const ctx: Worker = self as any;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
@ -19,15 +19,6 @@ export const promisifyAction = (
|
||||||
actionDescription: ActionDescription,
|
actionDescription: ActionDescription,
|
||||||
eventType?: EventType,
|
eventType?: EventType,
|
||||||
) => {
|
) => {
|
||||||
if (!self.ALLOW_ASYNC) {
|
|
||||||
/**
|
|
||||||
* To figure out if any function (JS action) is async, we do a dry run so that we can know if the function
|
|
||||||
* is using an async action. We set an IS_ASYNC flag to later indicate that a promise was called.
|
|
||||||
* @link isFunctionAsync
|
|
||||||
* */
|
|
||||||
self.IS_ASYNC = true;
|
|
||||||
throw new Error("Async function called in a sync field");
|
|
||||||
}
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
// We create a new sub request id for each request going on so that we can resolve the correct one later on
|
// We create a new sub request id for each request going on so that we can resolve the correct one later on
|
||||||
const messageId = _.uniqueId(`${actionDescription.type}_`);
|
const messageId = _.uniqueId(`${actionDescription.type}_`);
|
||||||
|
|
@ -61,7 +52,7 @@ export const promisifyAction = (
|
||||||
} else {
|
} else {
|
||||||
self.ALLOW_ASYNC = true;
|
self.ALLOW_ASYNC = true;
|
||||||
// Reset the global data with the correct request id for this promise
|
// Reset the global data with the correct request id for this promise
|
||||||
const globalData = createGlobalData({
|
const evalContext = createEvaluationContext({
|
||||||
dataTree: dataTreeEvaluator.evalTree,
|
dataTree: dataTreeEvaluator.evalTree,
|
||||||
resolvedFunctions: dataTreeEvaluator.resolvedFunctions,
|
resolvedFunctions: dataTreeEvaluator.resolvedFunctions,
|
||||||
isTriggerBased: true,
|
isTriggerBased: true,
|
||||||
|
|
@ -69,10 +60,8 @@ export const promisifyAction = (
|
||||||
eventType,
|
eventType,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
for (const entity in globalData) {
|
|
||||||
// @ts-expect-error: Types are not available
|
Object.assign(self, evalContext);
|
||||||
self[entity] = globalData[entity];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Resolve or reject the promise
|
// Resolve or reject the promise
|
||||||
if (success) {
|
if (success) {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import { createGlobalData } from "./evaluate";
|
import { ActionCalledInSyncFieldError } from "./errorModifier";
|
||||||
|
import { createEvaluationContext } from "./evaluate";
|
||||||
import { dataTreeEvaluator } from "./handlers/evalTree";
|
import { dataTreeEvaluator } from "./handlers/evalTree";
|
||||||
|
|
||||||
export const _internalSetTimeout = self.setTimeout;
|
export const _internalSetTimeout = self.setTimeout;
|
||||||
|
|
@ -11,9 +12,9 @@ export default function overrideTimeout() {
|
||||||
value: function(cb: (...args: any) => any, delay: number, ...args: any) {
|
value: function(cb: (...args: any) => any, delay: number, ...args: any) {
|
||||||
if (!self.ALLOW_ASYNC) {
|
if (!self.ALLOW_ASYNC) {
|
||||||
self.IS_ASYNC = true;
|
self.IS_ASYNC = true;
|
||||||
throw new Error("Async function called in a sync field");
|
throw new ActionCalledInSyncFieldError("setTimeout");
|
||||||
}
|
}
|
||||||
const globalData = createGlobalData({
|
const evalContext = createEvaluationContext({
|
||||||
dataTree: dataTreeEvaluator?.evalTree || {},
|
dataTree: dataTreeEvaluator?.evalTree || {},
|
||||||
resolvedFunctions: dataTreeEvaluator?.resolvedFunctions || {},
|
resolvedFunctions: dataTreeEvaluator?.resolvedFunctions || {},
|
||||||
isTriggerBased: true,
|
isTriggerBased: true,
|
||||||
|
|
@ -21,7 +22,7 @@ export default function overrideTimeout() {
|
||||||
return _internalSetTimeout(
|
return _internalSetTimeout(
|
||||||
function(...args: any) {
|
function(...args: any) {
|
||||||
self.ALLOW_ASYNC = true;
|
self.ALLOW_ASYNC = true;
|
||||||
Object.assign(self, globalData);
|
Object.assign(self, evalContext);
|
||||||
cb(...args);
|
cb(...args);
|
||||||
},
|
},
|
||||||
delay,
|
delay,
|
||||||
|
|
@ -34,6 +35,10 @@ export default function overrideTimeout() {
|
||||||
writable: true,
|
writable: true,
|
||||||
configurable: true,
|
configurable: true,
|
||||||
value: function(timerId: number) {
|
value: function(timerId: number) {
|
||||||
|
if (!self.ALLOW_ASYNC) {
|
||||||
|
self.IS_ASYNC = true;
|
||||||
|
throw new ActionCalledInSyncFieldError("clearTimeout");
|
||||||
|
}
|
||||||
return _internalClearTimeout(timerId);
|
return _internalClearTimeout(timerId);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,16 @@
|
||||||
import { DataTree, ENTITY_TYPE } from "entities/DataTree/dataTreeFactory";
|
import { DataTree, ENTITY_TYPE } from "entities/DataTree/dataTreeFactory";
|
||||||
import { PluginType } from "entities/Action";
|
import { PluginType } from "entities/Action";
|
||||||
import { createGlobalData } from "workers/Evaluation/evaluate";
|
import {
|
||||||
|
createEvaluationContext,
|
||||||
|
EvalContext,
|
||||||
|
} from "workers/Evaluation/evaluate";
|
||||||
import uniqueId from "lodash/uniqueId";
|
import uniqueId from "lodash/uniqueId";
|
||||||
import { MessageType } from "utils/MessageUtil";
|
import { MessageType } from "utils/MessageUtil";
|
||||||
|
import {
|
||||||
|
addDataTreeToContext,
|
||||||
|
addPlatformFunctionsToEvalContext,
|
||||||
|
} from "@appsmith/workers/Evaluation/Actions";
|
||||||
|
|
||||||
jest.mock("lodash/uniqueId");
|
jest.mock("lodash/uniqueId");
|
||||||
|
|
||||||
describe("Add functions", () => {
|
describe("Add functions", () => {
|
||||||
|
|
@ -31,15 +39,15 @@ describe("Add functions", () => {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
self.TRIGGER_COLLECTOR = [];
|
self.TRIGGER_COLLECTOR = [];
|
||||||
const dataTreeWithFunctions = createGlobalData({
|
const evalContext = createEvaluationContext({
|
||||||
dataTree,
|
dataTree,
|
||||||
resolvedFunctions: {},
|
resolvedFunctions: {},
|
||||||
isTriggerBased: true,
|
isTriggerBased: true,
|
||||||
context: {
|
context: {},
|
||||||
requestId: "EVAL_TRIGGER",
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
addPlatformFunctionsToEvalContext(evalContext);
|
||||||
|
|
||||||
const messageCreator = (type: string, body: unknown) => ({
|
const messageCreator = (type: string, body: unknown) => ({
|
||||||
messageId: expect.stringContaining(type),
|
messageId: expect.stringContaining(type),
|
||||||
messageType: MessageType.REQUEST,
|
messageType: MessageType.REQUEST,
|
||||||
|
|
@ -58,9 +66,9 @@ describe("Add functions", () => {
|
||||||
const actionParams = { param1: "value1" };
|
const actionParams = { param1: "value1" };
|
||||||
|
|
||||||
// Old syntax works with functions
|
// Old syntax works with functions
|
||||||
expect(
|
expect(evalContext.action1.run(onSuccess, onError, actionParams)).toBe(
|
||||||
dataTreeWithFunctions.action1.run(onSuccess, onError, actionParams),
|
undefined,
|
||||||
).toBe(undefined);
|
);
|
||||||
expect(self.TRIGGER_COLLECTOR).toHaveLength(1);
|
expect(self.TRIGGER_COLLECTOR).toHaveLength(1);
|
||||||
expect(self.TRIGGER_COLLECTOR[0]).toStrictEqual({
|
expect(self.TRIGGER_COLLECTOR[0]).toStrictEqual({
|
||||||
payload: {
|
payload: {
|
||||||
|
|
@ -77,9 +85,9 @@ describe("Add functions", () => {
|
||||||
self.TRIGGER_COLLECTOR.pop();
|
self.TRIGGER_COLLECTOR.pop();
|
||||||
|
|
||||||
// Old syntax works with one undefined value
|
// Old syntax works with one undefined value
|
||||||
expect(
|
expect(evalContext.action1.run(onSuccess, undefined, actionParams)).toBe(
|
||||||
dataTreeWithFunctions.action1.run(onSuccess, undefined, actionParams),
|
undefined,
|
||||||
).toBe(undefined);
|
);
|
||||||
expect(self.TRIGGER_COLLECTOR).toHaveLength(1);
|
expect(self.TRIGGER_COLLECTOR).toHaveLength(1);
|
||||||
expect(self.TRIGGER_COLLECTOR[0]).toStrictEqual({
|
expect(self.TRIGGER_COLLECTOR[0]).toStrictEqual({
|
||||||
payload: {
|
payload: {
|
||||||
|
|
@ -95,9 +103,9 @@ describe("Add functions", () => {
|
||||||
|
|
||||||
self.TRIGGER_COLLECTOR.pop();
|
self.TRIGGER_COLLECTOR.pop();
|
||||||
|
|
||||||
expect(
|
expect(evalContext.action1.run(undefined, onError, actionParams)).toBe(
|
||||||
dataTreeWithFunctions.action1.run(undefined, onError, actionParams),
|
undefined,
|
||||||
).toBe(undefined);
|
);
|
||||||
expect(self.TRIGGER_COLLECTOR).toHaveLength(1);
|
expect(self.TRIGGER_COLLECTOR).toHaveLength(1);
|
||||||
expect(self.TRIGGER_COLLECTOR[0]).toStrictEqual({
|
expect(self.TRIGGER_COLLECTOR[0]).toStrictEqual({
|
||||||
payload: {
|
payload: {
|
||||||
|
|
@ -123,9 +131,9 @@ describe("Add functions", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Old syntax works with null values is treated as new syntax
|
// Old syntax works with null values is treated as new syntax
|
||||||
expect(
|
expect(evalContext.action1.run(null, null, actionParams)).resolves.toBe({
|
||||||
dataTreeWithFunctions.action1.run(null, null, actionParams),
|
a: "b",
|
||||||
).resolves.toBe({ a: "b" });
|
});
|
||||||
expect(workerEventMock).lastCalledWith(
|
expect(workerEventMock).lastCalledWith(
|
||||||
messageCreator("RUN_PLUGIN_ACTION", {
|
messageCreator("RUN_PLUGIN_ACTION", {
|
||||||
data: {
|
data: {
|
||||||
|
|
@ -143,7 +151,7 @@ describe("Add functions", () => {
|
||||||
|
|
||||||
// Old syntax works with undefined values is treated as new syntax
|
// Old syntax works with undefined values is treated as new syntax
|
||||||
expect(
|
expect(
|
||||||
dataTreeWithFunctions.action1.run(undefined, undefined, actionParams),
|
evalContext.action1.run(undefined, undefined, actionParams),
|
||||||
).resolves.toBe({ a: "b" });
|
).resolves.toBe({ a: "b" });
|
||||||
expect(workerEventMock).lastCalledWith(
|
expect(workerEventMock).lastCalledWith(
|
||||||
messageCreator("RUN_PLUGIN_ACTION", {
|
messageCreator("RUN_PLUGIN_ACTION", {
|
||||||
|
|
@ -162,7 +170,7 @@ describe("Add functions", () => {
|
||||||
|
|
||||||
// new syntax works
|
// new syntax works
|
||||||
expect(
|
expect(
|
||||||
dataTreeWithFunctions.action1
|
evalContext.action1
|
||||||
.run(actionParams)
|
.run(actionParams)
|
||||||
.then(onSuccess)
|
.then(onSuccess)
|
||||||
.catch(onError),
|
.catch(onError),
|
||||||
|
|
@ -182,7 +190,7 @@ describe("Add functions", () => {
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
// New syntax without params
|
// New syntax without params
|
||||||
expect(dataTreeWithFunctions.action1.run()).resolves.toBe({ a: "b" });
|
expect(evalContext.action1.run()).resolves.toBe({ a: "b" });
|
||||||
|
|
||||||
expect(workerEventMock).lastCalledWith(
|
expect(workerEventMock).lastCalledWith(
|
||||||
messageCreator("RUN_PLUGIN_ACTION", {
|
messageCreator("RUN_PLUGIN_ACTION", {
|
||||||
|
|
@ -201,7 +209,7 @@ describe("Add functions", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("action.clear works", () => {
|
it("action.clear works", () => {
|
||||||
expect(dataTreeWithFunctions.action1.clear()).resolves.toBe({});
|
expect(evalContext.action1.clear()).resolves.toBe({});
|
||||||
expect(workerEventMock).lastCalledWith(
|
expect(workerEventMock).lastCalledWith(
|
||||||
messageCreator("CLEAR_PLUGIN_ACTION", {
|
messageCreator("CLEAR_PLUGIN_ACTION", {
|
||||||
data: {
|
data: {
|
||||||
|
|
@ -223,9 +231,9 @@ describe("Add functions", () => {
|
||||||
const params = "{ param1: value1 }";
|
const params = "{ param1: value1 }";
|
||||||
const target = "NEW_WINDOW";
|
const target = "NEW_WINDOW";
|
||||||
|
|
||||||
expect(
|
expect(evalContext.navigateTo(pageNameOrUrl, params, target)).resolves.toBe(
|
||||||
dataTreeWithFunctions.navigateTo(pageNameOrUrl, params, target),
|
{},
|
||||||
).resolves.toBe({});
|
);
|
||||||
expect(workerEventMock).lastCalledWith(
|
expect(workerEventMock).lastCalledWith(
|
||||||
messageCreator("NAVIGATE_TO", {
|
messageCreator("NAVIGATE_TO", {
|
||||||
data: {
|
data: {
|
||||||
|
|
@ -247,7 +255,7 @@ describe("Add functions", () => {
|
||||||
it("showAlert works", () => {
|
it("showAlert works", () => {
|
||||||
const message = "Alert message";
|
const message = "Alert message";
|
||||||
const style = "info";
|
const style = "info";
|
||||||
expect(dataTreeWithFunctions.showAlert(message, style)).resolves.toBe({});
|
expect(evalContext.showAlert(message, style)).resolves.toBe({});
|
||||||
expect(workerEventMock).lastCalledWith(
|
expect(workerEventMock).lastCalledWith(
|
||||||
messageCreator("SHOW_ALERT", {
|
messageCreator("SHOW_ALERT", {
|
||||||
data: {
|
data: {
|
||||||
|
|
@ -268,7 +276,7 @@ describe("Add functions", () => {
|
||||||
it("showModal works", () => {
|
it("showModal works", () => {
|
||||||
const modalName = "Modal 1";
|
const modalName = "Modal 1";
|
||||||
|
|
||||||
expect(dataTreeWithFunctions.showModal(modalName)).resolves.toBe({});
|
expect(evalContext.showModal(modalName)).resolves.toBe({});
|
||||||
expect(workerEventMock).lastCalledWith(
|
expect(workerEventMock).lastCalledWith(
|
||||||
messageCreator("SHOW_MODAL_BY_NAME", {
|
messageCreator("SHOW_MODAL_BY_NAME", {
|
||||||
data: {
|
data: {
|
||||||
|
|
@ -287,7 +295,7 @@ describe("Add functions", () => {
|
||||||
|
|
||||||
it("closeModal works", () => {
|
it("closeModal works", () => {
|
||||||
const modalName = "Modal 1";
|
const modalName = "Modal 1";
|
||||||
expect(dataTreeWithFunctions.closeModal(modalName)).resolves.toBe({});
|
expect(evalContext.closeModal(modalName)).resolves.toBe({});
|
||||||
expect(workerEventMock).lastCalledWith(
|
expect(workerEventMock).lastCalledWith(
|
||||||
messageCreator("CLOSE_MODAL", {
|
messageCreator("CLOSE_MODAL", {
|
||||||
data: {
|
data: {
|
||||||
|
|
@ -313,9 +321,7 @@ describe("Add functions", () => {
|
||||||
// @ts-expect-error: mockReturnValueOnce is not available on uniqueId
|
// @ts-expect-error: mockReturnValueOnce is not available on uniqueId
|
||||||
uniqueId.mockReturnValueOnce(uniqueActionRequestId);
|
uniqueId.mockReturnValueOnce(uniqueActionRequestId);
|
||||||
|
|
||||||
expect(dataTreeWithFunctions.storeValue(key, value, persist)).resolves.toBe(
|
expect(evalContext.storeValue(key, value, persist)).resolves.toBe({});
|
||||||
{},
|
|
||||||
);
|
|
||||||
expect(workerEventMock).lastCalledWith(
|
expect(workerEventMock).lastCalledWith(
|
||||||
messageCreator("STORE_VALUE", {
|
messageCreator("STORE_VALUE", {
|
||||||
data: {
|
data: {
|
||||||
|
|
@ -337,7 +343,7 @@ describe("Add functions", () => {
|
||||||
|
|
||||||
it("removeValue works", () => {
|
it("removeValue works", () => {
|
||||||
const key = "some";
|
const key = "some";
|
||||||
expect(dataTreeWithFunctions.removeValue(key)).resolves.toBe({});
|
expect(evalContext.removeValue(key)).resolves.toBe({});
|
||||||
expect(workerEventMock).lastCalledWith(
|
expect(workerEventMock).lastCalledWith(
|
||||||
messageCreator("REMOVE_VALUE", {
|
messageCreator("REMOVE_VALUE", {
|
||||||
data: {
|
data: {
|
||||||
|
|
@ -355,7 +361,7 @@ describe("Add functions", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it("clearStore works", () => {
|
it("clearStore works", () => {
|
||||||
expect(dataTreeWithFunctions.clearStore()).resolves.toBe({});
|
expect(evalContext.clearStore()).resolves.toBe({});
|
||||||
expect(workerEventMock).lastCalledWith(
|
expect(workerEventMock).lastCalledWith(
|
||||||
messageCreator("CLEAR_STORE", {
|
messageCreator("CLEAR_STORE", {
|
||||||
data: {
|
data: {
|
||||||
|
|
@ -375,7 +381,7 @@ describe("Add functions", () => {
|
||||||
const name = "downloadedFile.txt";
|
const name = "downloadedFile.txt";
|
||||||
const type = "text";
|
const type = "text";
|
||||||
|
|
||||||
expect(dataTreeWithFunctions.download(data, name, type)).resolves.toBe({});
|
expect(evalContext.download(data, name, type)).resolves.toBe({});
|
||||||
expect(workerEventMock).lastCalledWith(
|
expect(workerEventMock).lastCalledWith(
|
||||||
messageCreator("DOWNLOAD", {
|
messageCreator("DOWNLOAD", {
|
||||||
data: {
|
data: {
|
||||||
|
|
@ -396,7 +402,7 @@ describe("Add functions", () => {
|
||||||
|
|
||||||
it("copyToClipboard works", () => {
|
it("copyToClipboard works", () => {
|
||||||
const data = "file";
|
const data = "file";
|
||||||
expect(dataTreeWithFunctions.copyToClipboard(data)).resolves.toBe({});
|
expect(evalContext.copyToClipboard(data)).resolves.toBe({});
|
||||||
expect(workerEventMock).lastCalledWith(
|
expect(workerEventMock).lastCalledWith(
|
||||||
messageCreator("COPY_TO_CLIPBOARD", {
|
messageCreator("COPY_TO_CLIPBOARD", {
|
||||||
data: {
|
data: {
|
||||||
|
|
@ -418,9 +424,9 @@ describe("Add functions", () => {
|
||||||
const widgetName = "widget1";
|
const widgetName = "widget1";
|
||||||
const resetChildren = true;
|
const resetChildren = true;
|
||||||
|
|
||||||
expect(
|
expect(evalContext.resetWidget(widgetName, resetChildren)).resolves.toBe(
|
||||||
dataTreeWithFunctions.resetWidget(widgetName, resetChildren),
|
{},
|
||||||
).resolves.toBe({});
|
);
|
||||||
expect(workerEventMock).lastCalledWith(
|
expect(workerEventMock).lastCalledWith(
|
||||||
messageCreator("RESET_WIDGET_META_RECURSIVE_BY_NAME", {
|
messageCreator("RESET_WIDGET_META_RECURSIVE_BY_NAME", {
|
||||||
data: {
|
data: {
|
||||||
|
|
@ -443,9 +449,7 @@ describe("Add functions", () => {
|
||||||
const interval = 5000;
|
const interval = 5000;
|
||||||
const id = "myInterval";
|
const id = "myInterval";
|
||||||
|
|
||||||
expect(dataTreeWithFunctions.setInterval(callback, interval, id)).toBe(
|
expect(evalContext.setInterval(callback, interval, id)).toBe(undefined);
|
||||||
undefined,
|
|
||||||
);
|
|
||||||
expect(self.TRIGGER_COLLECTOR).toEqual(
|
expect(self.TRIGGER_COLLECTOR).toEqual(
|
||||||
expect.arrayContaining([
|
expect.arrayContaining([
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
|
|
@ -463,7 +467,7 @@ describe("Add functions", () => {
|
||||||
it("clearInterval works", () => {
|
it("clearInterval works", () => {
|
||||||
const id = "myInterval";
|
const id = "myInterval";
|
||||||
|
|
||||||
expect(dataTreeWithFunctions.clearInterval(id)).toBe(undefined);
|
expect(evalContext.clearInterval(id)).toBe(undefined);
|
||||||
expect(self.TRIGGER_COLLECTOR).toEqual(
|
expect(self.TRIGGER_COLLECTOR).toEqual(
|
||||||
expect.arrayContaining([
|
expect.arrayContaining([
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
|
|
@ -483,9 +487,9 @@ describe("Add functions", () => {
|
||||||
it("Post message with first argument (message) as a string", () => {
|
it("Post message with first argument (message) as a string", () => {
|
||||||
const message = "Hello world!";
|
const message = "Hello world!";
|
||||||
|
|
||||||
expect(
|
expect(evalContext.postWindowMessage(message, source, targetOrigin)).toBe(
|
||||||
dataTreeWithFunctions.postWindowMessage(message, source, targetOrigin),
|
undefined,
|
||||||
).toBe(undefined);
|
);
|
||||||
|
|
||||||
expect(self.TRIGGER_COLLECTOR).toEqual(
|
expect(self.TRIGGER_COLLECTOR).toEqual(
|
||||||
expect.arrayContaining([
|
expect.arrayContaining([
|
||||||
|
|
@ -504,9 +508,9 @@ describe("Add functions", () => {
|
||||||
it("Post message with first argument (message) as undefined", () => {
|
it("Post message with first argument (message) as undefined", () => {
|
||||||
const message = undefined;
|
const message = undefined;
|
||||||
|
|
||||||
expect(
|
expect(evalContext.postWindowMessage(message, source, targetOrigin)).toBe(
|
||||||
dataTreeWithFunctions.postWindowMessage(message, source, targetOrigin),
|
undefined,
|
||||||
).toBe(undefined);
|
);
|
||||||
|
|
||||||
expect(self.TRIGGER_COLLECTOR).toEqual(
|
expect(self.TRIGGER_COLLECTOR).toEqual(
|
||||||
expect.arrayContaining([
|
expect.arrayContaining([
|
||||||
|
|
@ -525,9 +529,9 @@ describe("Add functions", () => {
|
||||||
it("Post message with first argument (message) as null", () => {
|
it("Post message with first argument (message) as null", () => {
|
||||||
const message = null;
|
const message = null;
|
||||||
|
|
||||||
expect(
|
expect(evalContext.postWindowMessage(message, source, targetOrigin)).toBe(
|
||||||
dataTreeWithFunctions.postWindowMessage(message, source, targetOrigin),
|
undefined,
|
||||||
).toBe(undefined);
|
);
|
||||||
|
|
||||||
expect(self.TRIGGER_COLLECTOR).toEqual(
|
expect(self.TRIGGER_COLLECTOR).toEqual(
|
||||||
expect.arrayContaining([
|
expect.arrayContaining([
|
||||||
|
|
@ -546,9 +550,9 @@ describe("Add functions", () => {
|
||||||
it("Post message with first argument (message) as a number", () => {
|
it("Post message with first argument (message) as a number", () => {
|
||||||
const message = 1826;
|
const message = 1826;
|
||||||
|
|
||||||
expect(
|
expect(evalContext.postWindowMessage(message, source, targetOrigin)).toBe(
|
||||||
dataTreeWithFunctions.postWindowMessage(message, source, targetOrigin),
|
undefined,
|
||||||
).toBe(undefined);
|
);
|
||||||
|
|
||||||
expect(self.TRIGGER_COLLECTOR).toEqual(
|
expect(self.TRIGGER_COLLECTOR).toEqual(
|
||||||
expect.arrayContaining([
|
expect.arrayContaining([
|
||||||
|
|
@ -567,9 +571,9 @@ describe("Add functions", () => {
|
||||||
it("Post message with first argument (message) as a boolean", () => {
|
it("Post message with first argument (message) as a boolean", () => {
|
||||||
const message = true;
|
const message = true;
|
||||||
|
|
||||||
expect(
|
expect(evalContext.postWindowMessage(message, source, targetOrigin)).toBe(
|
||||||
dataTreeWithFunctions.postWindowMessage(message, source, targetOrigin),
|
undefined,
|
||||||
).toBe(undefined);
|
);
|
||||||
|
|
||||||
expect(self.TRIGGER_COLLECTOR).toEqual(
|
expect(self.TRIGGER_COLLECTOR).toEqual(
|
||||||
expect.arrayContaining([
|
expect.arrayContaining([
|
||||||
|
|
@ -588,9 +592,9 @@ describe("Add functions", () => {
|
||||||
it("Post message with first argument (message) as an array", () => {
|
it("Post message with first argument (message) as an array", () => {
|
||||||
const message = [1, 2, 3, [1, 2, 3, [1, 2, 3]]];
|
const message = [1, 2, 3, [1, 2, 3, [1, 2, 3]]];
|
||||||
|
|
||||||
expect(
|
expect(evalContext.postWindowMessage(message, source, targetOrigin)).toBe(
|
||||||
dataTreeWithFunctions.postWindowMessage(message, source, targetOrigin),
|
undefined,
|
||||||
).toBe(undefined);
|
);
|
||||||
|
|
||||||
expect(self.TRIGGER_COLLECTOR).toEqual(
|
expect(self.TRIGGER_COLLECTOR).toEqual(
|
||||||
expect.arrayContaining([
|
expect.arrayContaining([
|
||||||
|
|
@ -616,9 +620,9 @@ describe("Add functions", () => {
|
||||||
randomArr: [1, 2, 3],
|
randomArr: [1, 2, 3],
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(
|
expect(evalContext.postWindowMessage(message, source, targetOrigin)).toBe(
|
||||||
dataTreeWithFunctions.postWindowMessage(message, source, targetOrigin),
|
undefined,
|
||||||
).toBe(undefined);
|
);
|
||||||
|
|
||||||
expect(self.TRIGGER_COLLECTOR).toEqual(
|
expect(self.TRIGGER_COLLECTOR).toEqual(
|
||||||
expect.arrayContaining([
|
expect.arrayContaining([
|
||||||
|
|
@ -642,3 +646,253 @@ describe("Add functions", () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const dataTree = {
|
||||||
|
Text1: {
|
||||||
|
widgetName: "Text1",
|
||||||
|
displayName: "Text",
|
||||||
|
type: "TEXT_WIDGET",
|
||||||
|
hideCard: false,
|
||||||
|
animateLoading: true,
|
||||||
|
overflow: "SCROLL",
|
||||||
|
fontFamily: "Nunito Sans",
|
||||||
|
parentColumnSpace: 15.0625,
|
||||||
|
dynamicTriggerPathList: [],
|
||||||
|
leftColumn: 4,
|
||||||
|
dynamicBindingPathList: [],
|
||||||
|
shouldTruncate: false,
|
||||||
|
text: '"2022-11-27T21:36:00.128Z"',
|
||||||
|
key: "gt93hhlp15",
|
||||||
|
isDeprecated: false,
|
||||||
|
rightColumn: 29,
|
||||||
|
textAlign: "LEFT",
|
||||||
|
dynamicHeight: "FIXED",
|
||||||
|
widgetId: "ajg9fjegvr",
|
||||||
|
isVisible: true,
|
||||||
|
fontStyle: "BOLD",
|
||||||
|
textColor: "#231F20",
|
||||||
|
version: 1,
|
||||||
|
parentId: "0",
|
||||||
|
renderMode: "CANVAS",
|
||||||
|
isLoading: false,
|
||||||
|
borderRadius: "0.375rem",
|
||||||
|
maxDynamicHeight: 9000,
|
||||||
|
fontSize: "1rem",
|
||||||
|
minDynamicHeight: 4,
|
||||||
|
value: '"2022-11-27T21:36:00.128Z"',
|
||||||
|
defaultProps: {},
|
||||||
|
defaultMetaProps: [],
|
||||||
|
logBlackList: {
|
||||||
|
value: true,
|
||||||
|
},
|
||||||
|
meta: {},
|
||||||
|
propertyOverrideDependency: {},
|
||||||
|
overridingPropertyPaths: {},
|
||||||
|
bindingPaths: {
|
||||||
|
text: "TEMPLATE",
|
||||||
|
isVisible: "TEMPLATE",
|
||||||
|
},
|
||||||
|
reactivePaths: {
|
||||||
|
value: "TEMPLATE",
|
||||||
|
fontFamily: "TEMPLATE",
|
||||||
|
},
|
||||||
|
triggerPaths: {},
|
||||||
|
validationPaths: {},
|
||||||
|
ENTITY_TYPE: "WIDGET",
|
||||||
|
privateWidgets: {},
|
||||||
|
__evaluation__: {
|
||||||
|
errors: {},
|
||||||
|
evaluatedValues: {},
|
||||||
|
},
|
||||||
|
backgroundColor: "",
|
||||||
|
borderColor: "",
|
||||||
|
},
|
||||||
|
pageList: [
|
||||||
|
{
|
||||||
|
pageName: "Page1",
|
||||||
|
pageId: "63349fb5d39f215f89b8245e",
|
||||||
|
isDefault: false,
|
||||||
|
isHidden: false,
|
||||||
|
slug: "page1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
pageName: "Page2",
|
||||||
|
pageId: "637cc6b4a3664a7fe679b7b0",
|
||||||
|
isDefault: true,
|
||||||
|
isHidden: false,
|
||||||
|
slug: "page2",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
appsmith: {
|
||||||
|
store: {},
|
||||||
|
geolocation: {
|
||||||
|
canBeRequested: true,
|
||||||
|
currentPosition: {},
|
||||||
|
},
|
||||||
|
mode: "EDIT",
|
||||||
|
ENTITY_TYPE: "APPSMITH",
|
||||||
|
},
|
||||||
|
Api2: {
|
||||||
|
run: {},
|
||||||
|
clear: {},
|
||||||
|
name: "Api2",
|
||||||
|
pluginType: "API",
|
||||||
|
config: {},
|
||||||
|
dynamicBindingPathList: [
|
||||||
|
{
|
||||||
|
key: "config.path",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
responseMeta: {
|
||||||
|
isExecutionSuccess: false,
|
||||||
|
},
|
||||||
|
ENTITY_TYPE: "ACTION",
|
||||||
|
isLoading: false,
|
||||||
|
bindingPaths: {
|
||||||
|
"config.path": "TEMPLATE",
|
||||||
|
"config.body": "SMART_SUBSTITUTE",
|
||||||
|
"config.pluginSpecifiedTemplates[1].value": "SMART_SUBSTITUTE",
|
||||||
|
"config.pluginSpecifiedTemplates[2].value.limitBased.limit.value":
|
||||||
|
"SMART_SUBSTITUTE",
|
||||||
|
},
|
||||||
|
reactivePaths: {
|
||||||
|
data: "TEMPLATE",
|
||||||
|
isLoading: "TEMPLATE",
|
||||||
|
datasourceUrl: "TEMPLATE",
|
||||||
|
"config.path": "TEMPLATE",
|
||||||
|
"config.body": "SMART_SUBSTITUTE",
|
||||||
|
"config.pluginSpecifiedTemplates[1].value": "SMART_SUBSTITUTE",
|
||||||
|
},
|
||||||
|
dependencyMap: {
|
||||||
|
"config.body": ["config.pluginSpecifiedTemplates[0].value"],
|
||||||
|
},
|
||||||
|
logBlackList: {},
|
||||||
|
datasourceUrl: "",
|
||||||
|
__evaluation__: {
|
||||||
|
errors: {
|
||||||
|
config: [],
|
||||||
|
},
|
||||||
|
evaluatedValues: {
|
||||||
|
"config.path": "/users/undefined",
|
||||||
|
config: {
|
||||||
|
timeoutInMillisecond: 10000,
|
||||||
|
paginationType: "NONE",
|
||||||
|
path: "/users/test",
|
||||||
|
headers: [],
|
||||||
|
encodeParamsToggle: true,
|
||||||
|
queryParameters: [],
|
||||||
|
bodyFormData: [],
|
||||||
|
httpMethod: "GET",
|
||||||
|
selfReferencingDataPaths: [],
|
||||||
|
pluginSpecifiedTemplates: [
|
||||||
|
{
|
||||||
|
value: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
formData: {
|
||||||
|
apiContentType: "none",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
JSObject1: {
|
||||||
|
name: "JSObject1",
|
||||||
|
actionId: "637cda3b2f8e175c6f5269d5",
|
||||||
|
pluginType: "JS",
|
||||||
|
ENTITY_TYPE: "JSACTION",
|
||||||
|
body:
|
||||||
|
"export default {\n\tstoreTest2: () => {\n\t\tlet values = [\n\t\t\t\t\tstoreValue('val1', 'number 1'),\n\t\t\t\t\tstoreValue('val2', 'number 2'),\n\t\t\t\t\tstoreValue('val3', 'number 3'),\n\t\t\t\t\tstoreValue('val4', 'number 4')\n\t\t\t\t];\n\t\treturn Promise.all(values)\n\t\t\t.then(() => {\n\t\t\tshowAlert(JSON.stringify(appsmith.store))\n\t\t})\n\t\t\t.catch((err) => {\n\t\t\treturn showAlert('Could not store values in store ' + err.toString());\n\t\t})\n\t},\n\tnewFunction: function() {\n\t\tJSObject1.storeTest()\n\t}\n}",
|
||||||
|
meta: {
|
||||||
|
newFunction: {
|
||||||
|
arguments: [],
|
||||||
|
isAsync: false,
|
||||||
|
confirmBeforeExecute: false,
|
||||||
|
},
|
||||||
|
storeTest2: {
|
||||||
|
arguments: [],
|
||||||
|
isAsync: true,
|
||||||
|
confirmBeforeExecute: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
bindingPaths: {
|
||||||
|
body: "SMART_SUBSTITUTE",
|
||||||
|
newFunction: "SMART_SUBSTITUTE",
|
||||||
|
storeTest2: "SMART_SUBSTITUTE",
|
||||||
|
},
|
||||||
|
reactivePaths: {
|
||||||
|
body: "SMART_SUBSTITUTE",
|
||||||
|
newFunction: "SMART_SUBSTITUTE",
|
||||||
|
storeTest2: "SMART_SUBSTITUTE",
|
||||||
|
},
|
||||||
|
dynamicBindingPathList: [
|
||||||
|
{
|
||||||
|
key: "body",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "newFunction",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "storeTest2",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
variables: [],
|
||||||
|
dependencyMap: {
|
||||||
|
body: ["newFunction", "storeTest2"],
|
||||||
|
},
|
||||||
|
__evaluation__: {
|
||||||
|
errors: {
|
||||||
|
storeTest2: [],
|
||||||
|
newFunction: [],
|
||||||
|
body: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
describe("Test addDataTreeToContext method", () => {
|
||||||
|
const evalContext: EvalContext = {};
|
||||||
|
beforeAll(() => {
|
||||||
|
addDataTreeToContext({
|
||||||
|
EVAL_CONTEXT: evalContext,
|
||||||
|
dataTree: (dataTree as unknown) as DataTree,
|
||||||
|
isTriggerBased: true,
|
||||||
|
});
|
||||||
|
addPlatformFunctionsToEvalContext(evalContext);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("1. Assert platform actions are added", () => {
|
||||||
|
const frameworkActions = {
|
||||||
|
navigateTo: true,
|
||||||
|
showAlert: true,
|
||||||
|
showModal: true,
|
||||||
|
closeModal: true,
|
||||||
|
storeValue: true,
|
||||||
|
removeValue: true,
|
||||||
|
clearStore: true,
|
||||||
|
download: true,
|
||||||
|
copyToClipboard: true,
|
||||||
|
resetWidget: true,
|
||||||
|
setInterval: true,
|
||||||
|
clearInterval: true,
|
||||||
|
postWindowMessage: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const actionName of Object.keys(frameworkActions)) {
|
||||||
|
expect(evalContext).toHaveProperty(actionName);
|
||||||
|
expect(typeof evalContext[actionName]).toBe("function");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("2. Assert Api has run and clear method", () => {
|
||||||
|
expect(evalContext.Api2).toHaveProperty("run");
|
||||||
|
expect(evalContext.Api2).toHaveProperty("clear");
|
||||||
|
|
||||||
|
expect(typeof evalContext.Api2.run).toBe("function");
|
||||||
|
expect(typeof evalContext.Api2.clear).toBe("function");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("3. Assert input dataTree is not mutated", () => {
|
||||||
|
expect(typeof dataTree.Api2.run).not.toBe("function");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import { createGlobalData } from "workers/Evaluation/evaluate";
|
import { createEvaluationContext } from "workers/Evaluation/evaluate";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
import { MessageType } from "utils/MessageUtil";
|
import { MessageType } from "utils/MessageUtil";
|
||||||
|
import { addPlatformFunctionsToEvalContext } from "ce/workers/Evaluation/Actions";
|
||||||
jest.mock("../handlers/evalTree", () => {
|
jest.mock("../handlers/evalTree", () => {
|
||||||
return {
|
return {
|
||||||
dataTreeEvaluator: {
|
dataTreeEvaluator: {
|
||||||
|
|
@ -13,13 +14,15 @@ jest.mock("../handlers/evalTree", () => {
|
||||||
describe("promise execution", () => {
|
describe("promise execution", () => {
|
||||||
const postMessageMock = jest.fn();
|
const postMessageMock = jest.fn();
|
||||||
const requestId = _.uniqueId("TEST_REQUEST");
|
const requestId = _.uniqueId("TEST_REQUEST");
|
||||||
const dataTreeWithFunctions = createGlobalData({
|
const evalContext = createEvaluationContext({
|
||||||
dataTree: {},
|
dataTree: {},
|
||||||
resolvedFunctions: {},
|
resolvedFunctions: {},
|
||||||
isTriggerBased: true,
|
isTriggerBased: true,
|
||||||
context: { requestId },
|
context: {},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
addPlatformFunctionsToEvalContext(evalContext);
|
||||||
|
|
||||||
const requestMessageCreator = (type: string, body: unknown) => ({
|
const requestMessageCreator = (type: string, body: unknown) => ({
|
||||||
messageId: expect.stringContaining(`${type}_`),
|
messageId: expect.stringContaining(`${type}_`),
|
||||||
messageType: MessageType.REQUEST,
|
messageType: MessageType.REQUEST,
|
||||||
|
|
@ -37,12 +40,12 @@ describe("promise execution", () => {
|
||||||
it("throws when allow async is not enabled", () => {
|
it("throws when allow async is not enabled", () => {
|
||||||
self.ALLOW_ASYNC = false;
|
self.ALLOW_ASYNC = false;
|
||||||
self.IS_ASYNC = false;
|
self.IS_ASYNC = false;
|
||||||
expect(dataTreeWithFunctions.showAlert).toThrowError();
|
expect(evalContext.showAlert).toThrowError();
|
||||||
expect(self.IS_ASYNC).toBe(true);
|
expect(self.IS_ASYNC).toBe(true);
|
||||||
expect(postMessageMock).not.toHaveBeenCalled();
|
expect(postMessageMock).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
it("sends an event from the worker", () => {
|
it("sends an event from the worker", () => {
|
||||||
dataTreeWithFunctions.showAlert("test alert", "info");
|
evalContext.showAlert("test alert", "info");
|
||||||
expect(postMessageMock).toBeCalledWith(
|
expect(postMessageMock).toBeCalledWith(
|
||||||
requestMessageCreator("SHOW_ALERT", {
|
requestMessageCreator("SHOW_ALERT", {
|
||||||
data: {
|
data: {
|
||||||
|
|
@ -60,12 +63,8 @@ describe("promise execution", () => {
|
||||||
});
|
});
|
||||||
it("returns a promise that resolves", async () => {
|
it("returns a promise that resolves", async () => {
|
||||||
postMessageMock.mockReset();
|
postMessageMock.mockReset();
|
||||||
const returnedPromise = dataTreeWithFunctions.showAlert(
|
const returnedPromise = evalContext.showAlert("test alert", "info");
|
||||||
"test alert",
|
|
||||||
"info",
|
|
||||||
);
|
|
||||||
const requestArgs = postMessageMock.mock.calls[0][0];
|
const requestArgs = postMessageMock.mock.calls[0][0];
|
||||||
console.log(requestArgs);
|
|
||||||
const messageId = requestArgs.messageId;
|
const messageId = requestArgs.messageId;
|
||||||
|
|
||||||
self.dispatchEvent(
|
self.dispatchEvent(
|
||||||
|
|
@ -91,10 +90,7 @@ describe("promise execution", () => {
|
||||||
|
|
||||||
it("returns a promise that rejects", async () => {
|
it("returns a promise that rejects", async () => {
|
||||||
postMessageMock.mockReset();
|
postMessageMock.mockReset();
|
||||||
const returnedPromise = dataTreeWithFunctions.showAlert(
|
const returnedPromise = evalContext.showAlert("test alert", "info");
|
||||||
"test alert",
|
|
||||||
"info",
|
|
||||||
);
|
|
||||||
const requestArgs = postMessageMock.mock.calls[0][0];
|
const requestArgs = postMessageMock.mock.calls[0][0];
|
||||||
self.dispatchEvent(
|
self.dispatchEvent(
|
||||||
new MessageEvent("message", {
|
new MessageEvent("message", {
|
||||||
|
|
@ -113,10 +109,7 @@ describe("promise execution", () => {
|
||||||
});
|
});
|
||||||
it("does not process till right event is triggered", async () => {
|
it("does not process till right event is triggered", async () => {
|
||||||
postMessageMock.mockReset();
|
postMessageMock.mockReset();
|
||||||
const returnedPromise = dataTreeWithFunctions.showAlert(
|
const returnedPromise = evalContext.showAlert("test alert", "info");
|
||||||
"test alert",
|
|
||||||
"info",
|
|
||||||
);
|
|
||||||
|
|
||||||
const requestArgs = postMessageMock.mock.calls[0][0];
|
const requestArgs = postMessageMock.mock.calls[0][0];
|
||||||
const correctId = requestArgs.messageId;
|
const correctId = requestArgs.messageId;
|
||||||
|
|
@ -161,10 +154,7 @@ describe("promise execution", () => {
|
||||||
});
|
});
|
||||||
it("same subRequestId is not accepted again", async () => {
|
it("same subRequestId is not accepted again", async () => {
|
||||||
postMessageMock.mockReset();
|
postMessageMock.mockReset();
|
||||||
const returnedPromise = dataTreeWithFunctions.showAlert(
|
const returnedPromise = evalContext.showAlert("test alert", "info");
|
||||||
"test alert",
|
|
||||||
"info",
|
|
||||||
);
|
|
||||||
|
|
||||||
const requestArgs = postMessageMock.mock.calls[0][0];
|
const requestArgs = postMessageMock.mock.calls[0][0];
|
||||||
const messageId = requestArgs.messageId;
|
const messageId = requestArgs.messageId;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,163 @@
|
||||||
|
import { DataTree } from "entities/DataTree/dataTreeFactory";
|
||||||
|
import { errorModifier } from "../errorModifier";
|
||||||
|
|
||||||
|
describe("Test error modifier", () => {
|
||||||
|
const dataTree = ({
|
||||||
|
Api2: {
|
||||||
|
run: {},
|
||||||
|
clear: {},
|
||||||
|
name: "Api2",
|
||||||
|
pluginType: "API",
|
||||||
|
config: {},
|
||||||
|
dynamicBindingPathList: [
|
||||||
|
{
|
||||||
|
key: "config.path",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
responseMeta: {
|
||||||
|
isExecutionSuccess: false,
|
||||||
|
},
|
||||||
|
ENTITY_TYPE: "ACTION",
|
||||||
|
isLoading: false,
|
||||||
|
bindingPaths: {
|
||||||
|
"config.path": "TEMPLATE",
|
||||||
|
"config.body": "SMART_SUBSTITUTE",
|
||||||
|
"config.pluginSpecifiedTemplates[1].value": "SMART_SUBSTITUTE",
|
||||||
|
"config.pluginSpecifiedTemplates[2].value.limitBased.limit.value":
|
||||||
|
"SMART_SUBSTITUTE",
|
||||||
|
},
|
||||||
|
reactivePaths: {
|
||||||
|
data: "TEMPLATE",
|
||||||
|
isLoading: "TEMPLATE",
|
||||||
|
datasourceUrl: "TEMPLATE",
|
||||||
|
"config.path": "TEMPLATE",
|
||||||
|
"config.body": "SMART_SUBSTITUTE",
|
||||||
|
"config.pluginSpecifiedTemplates[1].value": "SMART_SUBSTITUTE",
|
||||||
|
},
|
||||||
|
dependencyMap: {
|
||||||
|
"config.body": ["config.pluginSpecifiedTemplates[0].value"],
|
||||||
|
},
|
||||||
|
logBlackList: {},
|
||||||
|
datasourceUrl: "",
|
||||||
|
__evaluation__: {
|
||||||
|
errors: {
|
||||||
|
config: [],
|
||||||
|
},
|
||||||
|
evaluatedValues: {
|
||||||
|
"config.path": "/users/undefined",
|
||||||
|
config: {
|
||||||
|
timeoutInMillisecond: 10000,
|
||||||
|
paginationType: "NONE",
|
||||||
|
path: "/users/test",
|
||||||
|
headers: [],
|
||||||
|
encodeParamsToggle: true,
|
||||||
|
queryParameters: [],
|
||||||
|
bodyFormData: [],
|
||||||
|
httpMethod: "GET",
|
||||||
|
selfReferencingDataPaths: [],
|
||||||
|
pluginSpecifiedTemplates: [
|
||||||
|
{
|
||||||
|
value: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
formData: {
|
||||||
|
apiContentType: "none",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
JSObject1: {
|
||||||
|
name: "JSObject1",
|
||||||
|
actionId: "637cda3b2f8e175c6f5269d5",
|
||||||
|
pluginType: "JS",
|
||||||
|
ENTITY_TYPE: "JSACTION",
|
||||||
|
body:
|
||||||
|
"export default {\n\tstoreTest2: () => {\n\t\tlet values = [\n\t\t\t\t\tstoreValue('val1', 'number 1'),\n\t\t\t\t\tstoreValue('val2', 'number 2'),\n\t\t\t\t\tstoreValue('val3', 'number 3'),\n\t\t\t\t\tstoreValue('val4', 'number 4')\n\t\t\t\t];\n\t\treturn Promise.all(values)\n\t\t\t.then(() => {\n\t\t\tshowAlert(JSON.stringify(appsmith.store))\n\t\t})\n\t\t\t.catch((err) => {\n\t\t\treturn showAlert('Could not store values in store ' + err.toString());\n\t\t})\n\t},\n\tnewFunction: function() {\n\t\tJSObject1.storeTest()\n\t}\n}",
|
||||||
|
meta: {
|
||||||
|
newFunction: {
|
||||||
|
arguments: [],
|
||||||
|
isAsync: false,
|
||||||
|
confirmBeforeExecute: false,
|
||||||
|
},
|
||||||
|
storeTest2: {
|
||||||
|
arguments: [],
|
||||||
|
isAsync: true,
|
||||||
|
confirmBeforeExecute: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
bindingPaths: {
|
||||||
|
body: "SMART_SUBSTITUTE",
|
||||||
|
newFunction: "SMART_SUBSTITUTE",
|
||||||
|
storeTest2: "SMART_SUBSTITUTE",
|
||||||
|
},
|
||||||
|
reactivePaths: {
|
||||||
|
body: "SMART_SUBSTITUTE",
|
||||||
|
newFunction: "SMART_SUBSTITUTE",
|
||||||
|
storeTest2: "SMART_SUBSTITUTE",
|
||||||
|
},
|
||||||
|
dynamicBindingPathList: [
|
||||||
|
{
|
||||||
|
key: "body",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "newFunction",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "storeTest2",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
variables: [],
|
||||||
|
dependencyMap: {
|
||||||
|
body: ["newFunction", "storeTest2"],
|
||||||
|
},
|
||||||
|
__evaluation__: {
|
||||||
|
errors: {
|
||||||
|
storeTest2: [],
|
||||||
|
newFunction: [],
|
||||||
|
body: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as unknown) as DataTree;
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
errorModifier.updateAsyncFunctions(dataTree);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("TypeError for defined Api in sync field ", () => {
|
||||||
|
const error = new Error();
|
||||||
|
error.name = "TypeError";
|
||||||
|
error.message = "Api2.run is not a function";
|
||||||
|
const result = errorModifier.run(error);
|
||||||
|
expect(result).toEqual(
|
||||||
|
"Found a reference to Api2.run() during evaluation. Sync fields cannot execute framework actions. Please remove any direct/indirect references to Api2.run() and try again.",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("TypeError for undefined Api in sync field ", () => {
|
||||||
|
const error = new Error();
|
||||||
|
error.name = "TypeError";
|
||||||
|
error.message = "Api1.run is not a function";
|
||||||
|
const result = errorModifier.run(error);
|
||||||
|
expect(result).toEqual("TypeError: Api1.run is not a function");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("ReferenceError for platform function in sync field", () => {
|
||||||
|
const error = new Error();
|
||||||
|
error.name = "ReferenceError";
|
||||||
|
error.message = "storeValue is not defined";
|
||||||
|
const result = errorModifier.run(error);
|
||||||
|
expect(result).toEqual(
|
||||||
|
"Found a reference to storeValue() during evaluation. Sync fields cannot execute framework actions. Please remove any direct/indirect references to storeValue() and try again.",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("ReferenceError for undefined function in sync field", () => {
|
||||||
|
const error = new Error();
|
||||||
|
error.name = "ReferenceError";
|
||||||
|
error.message = "storeValue2 is not defined";
|
||||||
|
const result = errorModifier.run(error);
|
||||||
|
expect(result).toEqual("ReferenceError: storeValue2 is not defined");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -1,7 +1,4 @@
|
||||||
import evaluate, {
|
import evaluate, { evaluateAsync } from "workers/Evaluation/evaluate";
|
||||||
evaluateAsync,
|
|
||||||
isFunctionAsync,
|
|
||||||
} from "workers/Evaluation/evaluate";
|
|
||||||
import {
|
import {
|
||||||
DataTree,
|
DataTree,
|
||||||
DataTreeWidget,
|
DataTreeWidget,
|
||||||
|
|
@ -9,6 +6,8 @@ import {
|
||||||
} from "entities/DataTree/dataTreeFactory";
|
} from "entities/DataTree/dataTreeFactory";
|
||||||
import { RenderModes } from "constants/WidgetConstants";
|
import { RenderModes } from "constants/WidgetConstants";
|
||||||
import setupEvalEnv from "../handlers/setupEvalEnv";
|
import setupEvalEnv from "../handlers/setupEvalEnv";
|
||||||
|
import { addPlatformFunctionsToEvalContext } from "@appsmith/workers/Evaluation/Actions";
|
||||||
|
import { functionDeterminer } from "../functionDeterminer";
|
||||||
|
|
||||||
describe("evaluateSync", () => {
|
describe("evaluateSync", () => {
|
||||||
const widget: DataTreeWidget = {
|
const widget: DataTreeWidget = {
|
||||||
|
|
@ -251,7 +250,11 @@ describe("isFunctionAsync", () => {
|
||||||
if (typeof testFunc === "string") {
|
if (typeof testFunc === "string") {
|
||||||
testFunc = eval(testFunc);
|
testFunc = eval(testFunc);
|
||||||
}
|
}
|
||||||
const actual = isFunctionAsync(testFunc, {}, {});
|
|
||||||
|
functionDeterminer.setupEval({}, {});
|
||||||
|
addPlatformFunctionsToEvalContext(self);
|
||||||
|
|
||||||
|
const actual = functionDeterminer.isFunctionAsync(testFunc);
|
||||||
expect(actual).toBe(testCase.expected);
|
expect(actual).toBe(testCase.expected);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import { PluginType } from "entities/Action";
|
import { PluginType } from "entities/Action";
|
||||||
import { DataTree, ENTITY_TYPE } from "entities/DataTree/dataTreeFactory";
|
import { DataTree, ENTITY_TYPE } from "entities/DataTree/dataTreeFactory";
|
||||||
import { createGlobalData } from "../evaluate";
|
import { createEvaluationContext } from "../evaluate";
|
||||||
import "../TimeoutOverride";
|
|
||||||
import overrideTimeout from "../TimeoutOverride";
|
import overrideTimeout from "../TimeoutOverride";
|
||||||
|
import { addPlatformFunctionsToEvalContext } from "@appsmith/workers/Evaluation/Actions";
|
||||||
|
|
||||||
describe("Expects appsmith setTimeout to pass the following criteria", () => {
|
describe("Expects appsmith setTimeout to pass the following criteria", () => {
|
||||||
overrideTimeout();
|
overrideTimeout();
|
||||||
|
|
@ -107,13 +107,16 @@ describe("Expects appsmith setTimeout to pass the following criteria", () => {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
self.ALLOW_ASYNC = true;
|
self.ALLOW_ASYNC = true;
|
||||||
const dataTreeWithFunctions = createGlobalData({
|
const evalContext = createEvaluationContext({
|
||||||
dataTree,
|
dataTree,
|
||||||
resolvedFunctions: {},
|
resolvedFunctions: {},
|
||||||
isTriggerBased: true,
|
isTriggerBased: true,
|
||||||
context: {},
|
context: {},
|
||||||
});
|
});
|
||||||
setTimeout(() => dataTreeWithFunctions.action1.run(), 1000);
|
|
||||||
|
addPlatformFunctionsToEvalContext(evalContext);
|
||||||
|
|
||||||
|
setTimeout(() => evalContext.action1.run(), 1000);
|
||||||
jest.runAllTimers();
|
jest.runAllTimers();
|
||||||
expect(self.postMessage).toBeCalled();
|
expect(self.postMessage).toBeCalled();
|
||||||
});
|
});
|
||||||
|
|
|
||||||
76
app/client/src/workers/Evaluation/errorModifier.ts
Normal file
76
app/client/src/workers/Evaluation/errorModifier.ts
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
import { DataTree } from "entities/DataTree/dataTreeFactory";
|
||||||
|
import { getAllAsyncFunctions } from "@appsmith/workers/Evaluation/Actions";
|
||||||
|
|
||||||
|
const UNDEFINED_ACTION_IN_SYNC_EVAL_ERROR =
|
||||||
|
"Found a reference to {{actionName}} during evaluation. Sync fields cannot execute framework actions. Please remove any direct/indirect references to {{actionName}} and try again.";
|
||||||
|
|
||||||
|
const ErrorNameType = {
|
||||||
|
ReferenceError: "ReferenceError",
|
||||||
|
TypeError: "TypeError",
|
||||||
|
};
|
||||||
|
|
||||||
|
class ErrorModifier {
|
||||||
|
private errorNamesToScan = [
|
||||||
|
ErrorNameType.ReferenceError,
|
||||||
|
ErrorNameType.TypeError,
|
||||||
|
];
|
||||||
|
// Note all regex below groups the async function name
|
||||||
|
|
||||||
|
private asyncFunctionsNameMap: Record<string, true> = {};
|
||||||
|
|
||||||
|
updateAsyncFunctions(dataTree: DataTree) {
|
||||||
|
this.asyncFunctionsNameMap = getAllAsyncFunctions(dataTree);
|
||||||
|
}
|
||||||
|
|
||||||
|
run(error: Error) {
|
||||||
|
const errorMessage = getErrorMessage(error);
|
||||||
|
|
||||||
|
if (!this.errorNamesToScan.includes(error.name)) return errorMessage;
|
||||||
|
|
||||||
|
for (const asyncFunctionFullPath of Object.keys(
|
||||||
|
this.asyncFunctionsNameMap,
|
||||||
|
)) {
|
||||||
|
const functionNameWithWhiteSpace = " " + asyncFunctionFullPath + " ";
|
||||||
|
if (errorMessage.match(functionNameWithWhiteSpace)) {
|
||||||
|
return UNDEFINED_ACTION_IN_SYNC_EVAL_ERROR.replaceAll(
|
||||||
|
"{{actionName}}",
|
||||||
|
asyncFunctionFullPath + "()",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return errorMessage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const errorModifier = new ErrorModifier();
|
||||||
|
|
||||||
|
export class FoundPromiseInSyncEvalError extends Error {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
this.name = "";
|
||||||
|
this.message =
|
||||||
|
"Found a Promise() during evaluation. Sync fields cannot execute asynchronous code.";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ActionCalledInSyncFieldError extends Error {
|
||||||
|
constructor(actionName: string) {
|
||||||
|
super(actionName);
|
||||||
|
|
||||||
|
if (!actionName) {
|
||||||
|
this.message = "Async function called in a sync field";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.name = "";
|
||||||
|
this.message = UNDEFINED_ACTION_IN_SYNC_EVAL_ERROR.replaceAll(
|
||||||
|
"{{actionName}}",
|
||||||
|
actionName + "()",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getErrorMessage = (error: Error) => {
|
||||||
|
return error.name ? `${error.name}: ${error.message}` : error.message;
|
||||||
|
};
|
||||||
|
|
@ -6,8 +6,6 @@ import {
|
||||||
} from "utils/DynamicBindingUtils";
|
} from "utils/DynamicBindingUtils";
|
||||||
import unescapeJS from "unescape-js";
|
import unescapeJS from "unescape-js";
|
||||||
import { LogObject, Severity } from "entities/AppsmithConsole";
|
import { LogObject, Severity } from "entities/AppsmithConsole";
|
||||||
import { enhanceDataTreeWithFunctions } from "@appsmith/workers/Evaluation/Actions";
|
|
||||||
import { isEmpty } from "lodash";
|
|
||||||
import { ActionDescription } from "@appsmith/entities/DataTree/actionTriggers";
|
import { ActionDescription } from "@appsmith/entities/DataTree/actionTriggers";
|
||||||
import userLogs from "./UserLog";
|
import userLogs from "./UserLog";
|
||||||
import { EventType } from "constants/AppsmithActionConstants/ActionConstants";
|
import { EventType } from "constants/AppsmithActionConstants/ActionConstants";
|
||||||
|
|
@ -15,6 +13,11 @@ import { TriggerMeta } from "@appsmith/sagas/ActionExecution/ActionExecutionSaga
|
||||||
import indirectEval from "./indirectEval";
|
import indirectEval from "./indirectEval";
|
||||||
import { DOM_APIS } from "./SetupDOM";
|
import { DOM_APIS } from "./SetupDOM";
|
||||||
import { JSLibraries, libraryReservedIdentifiers } from "../common/JSLibrary";
|
import { JSLibraries, libraryReservedIdentifiers } from "../common/JSLibrary";
|
||||||
|
import { errorModifier, FoundPromiseInSyncEvalError } from "./errorModifier";
|
||||||
|
import {
|
||||||
|
PLATFORM_FUNCTIONS,
|
||||||
|
addDataTreeToContext,
|
||||||
|
} from "@appsmith/workers/Evaluation/Actions";
|
||||||
|
|
||||||
export type EvalResult = {
|
export type EvalResult = {
|
||||||
result: any;
|
result: any;
|
||||||
|
|
@ -78,6 +81,7 @@ function resetWorkerGlobalScope() {
|
||||||
continue;
|
continue;
|
||||||
if (JSLibraries.find((lib) => lib.accessor.includes(key))) continue;
|
if (JSLibraries.find((lib) => lib.accessor.includes(key))) continue;
|
||||||
if (libraryReservedIdentifiers[key]) continue;
|
if (libraryReservedIdentifiers[key]) continue;
|
||||||
|
if (PLATFORM_FUNCTIONS[key]) continue;
|
||||||
try {
|
try {
|
||||||
// @ts-expect-error: Types are not available
|
// @ts-expect-error: Types are not available
|
||||||
delete self[key];
|
delete self[key];
|
||||||
|
|
@ -115,17 +119,25 @@ export const getScriptToEval = (
|
||||||
};
|
};
|
||||||
|
|
||||||
const beginsWithLineBreakRegex = /^\s+|\s+$/;
|
const beginsWithLineBreakRegex = /^\s+|\s+$/;
|
||||||
export interface createGlobalDataArgs {
|
|
||||||
|
export type EvalContext = Record<string, any>;
|
||||||
|
type ResolvedFunctions = Record<string, any>;
|
||||||
|
export interface createEvaluationContextArgs {
|
||||||
dataTree: DataTree;
|
dataTree: DataTree;
|
||||||
resolvedFunctions: Record<string, any>;
|
resolvedFunctions: ResolvedFunctions;
|
||||||
context?: EvaluateContext;
|
context?: EvaluateContext;
|
||||||
evalArguments?: Array<unknown>;
|
|
||||||
isTriggerBased: boolean;
|
isTriggerBased: boolean;
|
||||||
|
evalArguments?: Array<unknown>;
|
||||||
// Whether not to add functions like "run", "clear" to entity in global data
|
// Whether not to add functions like "run", "clear" to entity in global data
|
||||||
skipEntityFunctions?: boolean;
|
skipEntityFunctions?: boolean;
|
||||||
}
|
}
|
||||||
|
/**
|
||||||
export const createGlobalData = (args: createGlobalDataArgs) => {
|
* This method created an object with dataTree and appsmith's framework actions that needs to be added to worker global scope for the JS code evaluation to then consume it.
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* - For `eval("Table1.tableData")` code to work as expected, we define Table1.tableData in worker global scope and for that we use `createEvaluationContext` to get the object to set in global scope.
|
||||||
|
*/
|
||||||
|
export const createEvaluationContext = (args: createEvaluationContextArgs) => {
|
||||||
const {
|
const {
|
||||||
context,
|
context,
|
||||||
dataTree,
|
dataTree,
|
||||||
|
|
@ -135,63 +147,54 @@ export const createGlobalData = (args: createGlobalDataArgs) => {
|
||||||
skipEntityFunctions,
|
skipEntityFunctions,
|
||||||
} = args;
|
} = args;
|
||||||
|
|
||||||
const GLOBAL_DATA: Record<string, any> = {};
|
const EVAL_CONTEXT: EvalContext = {};
|
||||||
///// Adding callback data
|
///// Adding callback data
|
||||||
GLOBAL_DATA.ARGUMENTS = evalArguments;
|
EVAL_CONTEXT.ARGUMENTS = evalArguments;
|
||||||
//// Adding contextual data not part of data tree
|
//// Adding contextual data not part of data tree
|
||||||
GLOBAL_DATA.THIS_CONTEXT = {};
|
EVAL_CONTEXT.THIS_CONTEXT = context?.thisContext || {};
|
||||||
if (context) {
|
|
||||||
if (context.thisContext) {
|
if (context?.globalContext) {
|
||||||
GLOBAL_DATA.THIS_CONTEXT = context.thisContext;
|
Object.assign(EVAL_CONTEXT, context.globalContext);
|
||||||
}
|
}
|
||||||
if (context.globalContext) {
|
|
||||||
Object.entries(context.globalContext).forEach(([key, value]) => {
|
addDataTreeToContext({
|
||||||
GLOBAL_DATA[key] = value;
|
EVAL_CONTEXT,
|
||||||
});
|
dataTree,
|
||||||
|
skipEntityFunctions: !!skipEntityFunctions,
|
||||||
|
eventType: context?.eventType,
|
||||||
|
isTriggerBased,
|
||||||
|
});
|
||||||
|
|
||||||
|
assignJSFunctionsToContext(EVAL_CONTEXT, resolvedFunctions);
|
||||||
|
|
||||||
|
return EVAL_CONTEXT;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const assignJSFunctionsToContext = (
|
||||||
|
EVAL_CONTEXT: EvalContext,
|
||||||
|
resolvedFunctions: ResolvedFunctions,
|
||||||
|
) => {
|
||||||
|
const jsObjectNames = Object.keys(resolvedFunctions || {});
|
||||||
|
for (const jsObjectName of jsObjectNames) {
|
||||||
|
const resolvedObject = resolvedFunctions[jsObjectName];
|
||||||
|
const jsObject = EVAL_CONTEXT[jsObjectName];
|
||||||
|
const jsObjectFunction: Record<string, Record<"data", unknown>> = {};
|
||||||
|
if (!jsObject) continue;
|
||||||
|
for (const fnName of Object.keys(resolvedObject)) {
|
||||||
|
const fn = resolvedObject[fnName];
|
||||||
|
if (typeof fn !== "function") continue;
|
||||||
|
// Investigate promisify of JSObject function confirmation
|
||||||
|
// Task: https://github.com/appsmithorg/appsmith/issues/13289
|
||||||
|
// Previous implementation commented code: https://github.com/appsmithorg/appsmith/pull/18471
|
||||||
|
const data = jsObject[fnName]?.data;
|
||||||
|
jsObjectFunction[fnName] = fn;
|
||||||
|
if (!!data) {
|
||||||
|
jsObjectFunction[fnName]["data"] = data;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
EVAL_CONTEXT[jsObjectName] = Object.assign({}, jsObject, jsObjectFunction);
|
||||||
}
|
}
|
||||||
if (isTriggerBased) {
|
|
||||||
//// Add internal functions to dataTree;
|
|
||||||
const dataTreeWithFunctions = enhanceDataTreeWithFunctions(
|
|
||||||
dataTree,
|
|
||||||
skipEntityFunctions,
|
|
||||||
context?.eventType,
|
|
||||||
);
|
|
||||||
///// Adding Data tree with functions
|
|
||||||
Object.assign(GLOBAL_DATA, dataTreeWithFunctions);
|
|
||||||
} else {
|
|
||||||
// Object.assign removes prototypes of the entity object making sure configs are not shown to user.
|
|
||||||
Object.assign(GLOBAL_DATA, dataTree);
|
|
||||||
}
|
|
||||||
if (!isEmpty(resolvedFunctions)) {
|
|
||||||
Object.keys(resolvedFunctions).forEach((datum: any) => {
|
|
||||||
const resolvedObject = resolvedFunctions[datum];
|
|
||||||
Object.keys(resolvedObject).forEach((key: any) => {
|
|
||||||
const dataTreeKey = GLOBAL_DATA[datum];
|
|
||||||
if (dataTreeKey) {
|
|
||||||
const data = dataTreeKey[key]?.data;
|
|
||||||
//do not remove we will be investigating this
|
|
||||||
//const isAsync = dataTreeKey?.meta[key]?.isAsync || false;
|
|
||||||
//const confirmBeforeExecute = dataTreeKey?.meta[key]?.confirmBeforeExecute || false;
|
|
||||||
dataTreeKey[key] = resolvedObject[key];
|
|
||||||
// if (isAsync && confirmBeforeExecute) {
|
|
||||||
// dataTreeKey[key] = confirmationPromise.bind(
|
|
||||||
// {},
|
|
||||||
// context?.requestId,
|
|
||||||
// resolvedObject[key],
|
|
||||||
// dataTreeKey.name + "." + key,
|
|
||||||
// );
|
|
||||||
// } else {
|
|
||||||
// dataTreeKey[key] = resolvedObject[key];
|
|
||||||
// }
|
|
||||||
if (!!data) {
|
|
||||||
dataTreeKey[key]["data"] = data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return GLOBAL_DATA;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export function sanitizeScript(js: string) {
|
export function sanitizeScript(js: string) {
|
||||||
|
|
@ -252,19 +255,22 @@ export default function evaluateSync(
|
||||||
userLogs.resetLogs();
|
userLogs.resetLogs();
|
||||||
}
|
}
|
||||||
/**** Setting the eval context ****/
|
/**** Setting the eval context ****/
|
||||||
const GLOBAL_DATA: Record<string, any> = createGlobalData({
|
const evalContext: EvalContext = createEvaluationContext({
|
||||||
dataTree,
|
dataTree,
|
||||||
resolvedFunctions,
|
resolvedFunctions,
|
||||||
isTriggerBased: isJSCollection,
|
|
||||||
context,
|
context,
|
||||||
evalArguments,
|
evalArguments,
|
||||||
|
isTriggerBased: isJSCollection,
|
||||||
});
|
});
|
||||||
GLOBAL_DATA.ALLOW_ASYNC = false;
|
|
||||||
|
evalContext.ALLOW_ASYNC = false;
|
||||||
|
|
||||||
const { script } = getUserScriptToEvaluate(
|
const { script } = getUserScriptToEvaluate(
|
||||||
userScript,
|
userScript,
|
||||||
false,
|
false,
|
||||||
evalArguments,
|
evalArguments,
|
||||||
);
|
);
|
||||||
|
|
||||||
// If nothing is present to evaluate, return instead of evaluating
|
// If nothing is present to evaluate, return instead of evaluating
|
||||||
if (!script.length) {
|
if (!script.length) {
|
||||||
return {
|
return {
|
||||||
|
|
@ -277,19 +283,20 @@ export default function evaluateSync(
|
||||||
// Set it to self so that the eval function can have access to it
|
// Set it to self so that the eval function can have access to it
|
||||||
// as global data. This is what enables access all appsmith
|
// as global data. This is what enables access all appsmith
|
||||||
// entity properties from the global context
|
// entity properties from the global context
|
||||||
for (const entity in GLOBAL_DATA) {
|
Object.assign(self, evalContext);
|
||||||
// @ts-expect-error: Types are not available
|
|
||||||
self[entity] = GLOBAL_DATA[entity];
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
result = indirectEval(script);
|
result = indirectEval(script);
|
||||||
|
if (result instanceof Promise) {
|
||||||
|
/**
|
||||||
|
* If a promise is returned in sync field then show the error to help understand sync field doesn't await to resolve promise.
|
||||||
|
* NOTE: Awaiting for promise will make sync field evaluation slower.
|
||||||
|
*/
|
||||||
|
throw new FoundPromiseInSyncEvalError();
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errorMessage = `${(error as Error).name}: ${
|
|
||||||
(error as Error).message
|
|
||||||
}`;
|
|
||||||
errors.push({
|
errors.push({
|
||||||
errorMessage: errorMessage,
|
errorMessage: errorModifier.run(error as Error),
|
||||||
severity: Severity.ERROR,
|
severity: Severity.ERROR,
|
||||||
raw: script,
|
raw: script,
|
||||||
errorType: PropertyEvaluationErrorType.PARSE,
|
errorType: PropertyEvaluationErrorType.PARSE,
|
||||||
|
|
@ -297,9 +304,11 @@ export default function evaluateSync(
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
if (!skipLogsOperations) logs = userLogs.flushLogs();
|
if (!skipLogsOperations) logs = userLogs.flushLogs();
|
||||||
for (const entity in GLOBAL_DATA) {
|
for (const entityName in evalContext) {
|
||||||
// @ts-expect-error: Types are not available
|
if (evalContext.hasOwnProperty(entityName)) {
|
||||||
delete self[entity];
|
// @ts-expect-error: Types are not available
|
||||||
|
delete self[entityName];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -325,22 +334,20 @@ export async function evaluateAsync(
|
||||||
eventType: context?.eventType,
|
eventType: context?.eventType,
|
||||||
triggerMeta: context?.triggerMeta,
|
triggerMeta: context?.triggerMeta,
|
||||||
});
|
});
|
||||||
const GLOBAL_DATA: Record<string, any> = createGlobalData({
|
const evalContext: EvalContext = createEvaluationContext({
|
||||||
dataTree,
|
dataTree,
|
||||||
resolvedFunctions,
|
resolvedFunctions,
|
||||||
isTriggerBased: true,
|
|
||||||
context,
|
context,
|
||||||
evalArguments,
|
evalArguments,
|
||||||
|
isTriggerBased: true,
|
||||||
});
|
});
|
||||||
const { script } = getUserScriptToEvaluate(userScript, true, evalArguments);
|
const { script } = getUserScriptToEvaluate(userScript, true, evalArguments);
|
||||||
GLOBAL_DATA.ALLOW_ASYNC = true;
|
evalContext.ALLOW_ASYNC = true;
|
||||||
|
|
||||||
// Set it to self so that the eval function can have access to it
|
// Set it to self so that the eval function can have access to it
|
||||||
// as global data. This is what enables access all appsmith
|
// as global data. This is what enables access all appsmith
|
||||||
// entity properties from the global context
|
// entity properties from the global context
|
||||||
Object.keys(GLOBAL_DATA).forEach((key) => {
|
Object.assign(self, evalContext);
|
||||||
// @ts-expect-error: Types are not available
|
|
||||||
self[key] = GLOBAL_DATA[key];
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
result = await indirectEval(script);
|
result = await indirectEval(script);
|
||||||
|
|
@ -370,72 +377,3 @@ export async function evaluateAsync(
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function isFunctionAsync(
|
|
||||||
userFunction: unknown,
|
|
||||||
dataTree: DataTree,
|
|
||||||
resolvedFunctions: Record<string, any>,
|
|
||||||
logs: unknown[] = [],
|
|
||||||
) {
|
|
||||||
return (function() {
|
|
||||||
/**** Setting the eval context ****/
|
|
||||||
const GLOBAL_DATA: Record<string, any> = {
|
|
||||||
ALLOW_ASYNC: false,
|
|
||||||
IS_ASYNC: false,
|
|
||||||
};
|
|
||||||
//// Add internal functions to dataTree;
|
|
||||||
const dataTreeWithFunctions = enhanceDataTreeWithFunctions(dataTree);
|
|
||||||
///// Adding Data tree with functions
|
|
||||||
Object.keys(dataTreeWithFunctions).forEach((datum) => {
|
|
||||||
GLOBAL_DATA[datum] = dataTreeWithFunctions[datum];
|
|
||||||
});
|
|
||||||
if (!isEmpty(resolvedFunctions)) {
|
|
||||||
Object.keys(resolvedFunctions).forEach((datum: any) => {
|
|
||||||
const resolvedObject = resolvedFunctions[datum];
|
|
||||||
Object.keys(resolvedObject).forEach((key: any) => {
|
|
||||||
const dataTreeKey = GLOBAL_DATA[datum];
|
|
||||||
if (dataTreeKey) {
|
|
||||||
const data = dataTreeKey[key]?.data;
|
|
||||||
dataTreeKey[key] = resolvedObject[key];
|
|
||||||
if (!!data) {
|
|
||||||
dataTreeKey[key].data = data;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// Set it to self so that the eval function can have access to it
|
|
||||||
// as global data. This is what enables access all appsmith
|
|
||||||
// entity properties from the global context
|
|
||||||
Object.keys(GLOBAL_DATA).forEach((key) => {
|
|
||||||
// @ts-expect-error: Types are not available
|
|
||||||
self[key] = GLOBAL_DATA[key];
|
|
||||||
});
|
|
||||||
try {
|
|
||||||
if (typeof userFunction === "function") {
|
|
||||||
if (userFunction.constructor.name === "AsyncFunction") {
|
|
||||||
// functions declared with an async keyword
|
|
||||||
self.IS_ASYNC = true;
|
|
||||||
} else {
|
|
||||||
const returnValue = userFunction();
|
|
||||||
if (!!returnValue && returnValue instanceof Promise) {
|
|
||||||
self.IS_ASYNC = true;
|
|
||||||
}
|
|
||||||
if (self.TRIGGER_COLLECTOR.length) {
|
|
||||||
self.IS_ASYNC = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
// We do not want to throw errors for internal operations, to users.
|
|
||||||
// logLevel should help us in debugging this.
|
|
||||||
logs.push({ error: "Error when determining async function" + e });
|
|
||||||
}
|
|
||||||
const isAsync = !!self.IS_ASYNC;
|
|
||||||
for (const entity in GLOBAL_DATA) {
|
|
||||||
// @ts-expect-error: Types are not available
|
|
||||||
delete self[entity];
|
|
||||||
}
|
|
||||||
return isAsync;
|
|
||||||
})();
|
|
||||||
}
|
|
||||||
|
|
|
||||||
72
app/client/src/workers/Evaluation/functionDeterminer.ts
Normal file
72
app/client/src/workers/Evaluation/functionDeterminer.ts
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
import { addDataTreeToContext } from "@appsmith/workers/Evaluation/Actions";
|
||||||
|
import { EvalContext, assignJSFunctionsToContext } from "./evaluate";
|
||||||
|
import { DataTree } from "entities/DataTree/dataTreeFactory";
|
||||||
|
|
||||||
|
class FunctionDeterminer {
|
||||||
|
private evalContext: EvalContext = {};
|
||||||
|
|
||||||
|
setupEval(dataTree: DataTree, resolvedFunctions: Record<string, any>) {
|
||||||
|
/**** Setting the eval context ****/
|
||||||
|
const evalContext: EvalContext = {
|
||||||
|
ALLOW_ASYNC: false,
|
||||||
|
IS_ASYNC: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
addDataTreeToContext({
|
||||||
|
dataTree,
|
||||||
|
EVAL_CONTEXT: evalContext,
|
||||||
|
isTriggerBased: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
assignJSFunctionsToContext(evalContext, resolvedFunctions);
|
||||||
|
|
||||||
|
// Set it to self so that the eval function can have access to it
|
||||||
|
// as global data. This is what enables access all appsmith
|
||||||
|
// entity properties from the global context
|
||||||
|
Object.assign(self, evalContext);
|
||||||
|
|
||||||
|
this.evalContext = evalContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
setOffEval() {
|
||||||
|
for (const entityName in this.evalContext) {
|
||||||
|
if (this.evalContext.hasOwnProperty(entityName)) {
|
||||||
|
// @ts-expect-error: Types are not available
|
||||||
|
delete self[entityName];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
isFunctionAsync(userFunction: unknown, logs: unknown[] = []) {
|
||||||
|
self.TRIGGER_COLLECTOR = [];
|
||||||
|
self.IS_ASYNC = false;
|
||||||
|
|
||||||
|
return (function() {
|
||||||
|
try {
|
||||||
|
if (typeof userFunction === "function") {
|
||||||
|
if (userFunction.constructor.name === "AsyncFunction") {
|
||||||
|
// functions declared with an async keyword
|
||||||
|
self.IS_ASYNC = true;
|
||||||
|
} else {
|
||||||
|
const returnValue = userFunction();
|
||||||
|
if (!!returnValue && returnValue instanceof Promise) {
|
||||||
|
self.IS_ASYNC = true;
|
||||||
|
}
|
||||||
|
if (self.TRIGGER_COLLECTOR.length) {
|
||||||
|
self.IS_ASYNC = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// We do not want to throw errors for internal operations, to users.
|
||||||
|
// logLevel should help us in debugging this.
|
||||||
|
logs.push({ error: "Error when determining async function" + e });
|
||||||
|
}
|
||||||
|
const isAsync = !!self.IS_ASYNC;
|
||||||
|
|
||||||
|
return isAsync;
|
||||||
|
})();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const functionDeterminer = new FunctionDeterminer();
|
||||||
|
|
@ -5,6 +5,7 @@ import setupDOM from "../SetupDOM";
|
||||||
import overrideTimeout from "../TimeoutOverride";
|
import overrideTimeout from "../TimeoutOverride";
|
||||||
import { EvalWorkerSyncRequest } from "../types";
|
import { EvalWorkerSyncRequest } from "../types";
|
||||||
import userLogs from "../UserLog";
|
import userLogs from "../UserLog";
|
||||||
|
import { addPlatformFunctionsToEvalContext } from "@appsmith/workers/Evaluation/Actions";
|
||||||
|
|
||||||
export default function() {
|
export default function() {
|
||||||
const libraries = resetJSLibraries();
|
const libraries = resetJSLibraries();
|
||||||
|
|
@ -24,6 +25,7 @@ export default function() {
|
||||||
overrideTimeout();
|
overrideTimeout();
|
||||||
interceptAndOverrideHttpRequest();
|
interceptAndOverrideHttpRequest();
|
||||||
setupDOM();
|
setupDOM();
|
||||||
|
addPlatformFunctionsToEvalContext(self);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ import {
|
||||||
import { getDynamicBindings } from "utils/DynamicBindingUtils";
|
import { getDynamicBindings } from "utils/DynamicBindingUtils";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
createGlobalData,
|
createEvaluationContext,
|
||||||
EvaluationScripts,
|
EvaluationScripts,
|
||||||
EvaluationScriptType,
|
EvaluationScriptType,
|
||||||
getScriptToEval,
|
getScriptToEval,
|
||||||
|
|
@ -53,17 +53,30 @@ import { LintErrors } from "reducers/lintingReducers/lintErrorsReducers";
|
||||||
import { Severity } from "entities/AppsmithConsole";
|
import { Severity } from "entities/AppsmithConsole";
|
||||||
import { JSLibraries } from "workers/common/JSLibrary";
|
import { JSLibraries } from "workers/common/JSLibrary";
|
||||||
import { MessageType, sendMessage } from "utils/MessageUtil";
|
import { MessageType, sendMessage } from "utils/MessageUtil";
|
||||||
|
import { addPlatformFunctionsToEvalContext } from "@appsmith/workers/Evaluation/Actions";
|
||||||
|
|
||||||
export function getlintErrorsFromTree(
|
export function getlintErrorsFromTree(
|
||||||
pathsToLint: string[],
|
pathsToLint: string[],
|
||||||
unEvalTree: DataTree,
|
unEvalTree: DataTree,
|
||||||
): LintErrors {
|
): LintErrors {
|
||||||
const lintTreeErrors: LintErrors = {};
|
const lintTreeErrors: LintErrors = {};
|
||||||
const GLOBAL_DATA_WITHOUT_FUNCTIONS = createGlobalData({
|
|
||||||
|
const evalContext = createEvaluationContext({
|
||||||
dataTree: unEvalTree,
|
dataTree: unEvalTree,
|
||||||
resolvedFunctions: {},
|
resolvedFunctions: {},
|
||||||
isTriggerBased: false,
|
isTriggerBased: false,
|
||||||
|
skipEntityFunctions: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
addPlatformFunctionsToEvalContext(evalContext);
|
||||||
|
|
||||||
|
const evalContextWithOutFunctions = createEvaluationContext({
|
||||||
|
dataTree: unEvalTree,
|
||||||
|
resolvedFunctions: {},
|
||||||
|
isTriggerBased: true,
|
||||||
|
skipEntityFunctions: true,
|
||||||
|
});
|
||||||
|
|
||||||
// trigger paths
|
// trigger paths
|
||||||
const triggerPaths = new Set<string>();
|
const triggerPaths = new Set<string>();
|
||||||
// Certain paths, like JS Object's body are binding paths where appsmith functions are needed in the global data
|
// Certain paths, like JS Object's body are binding paths where appsmith functions are needed in the global data
|
||||||
|
|
@ -91,7 +104,7 @@ export function getlintErrorsFromTree(
|
||||||
unEvalPropertyValue,
|
unEvalPropertyValue,
|
||||||
entity,
|
entity,
|
||||||
fullPropertyPath,
|
fullPropertyPath,
|
||||||
GLOBAL_DATA_WITHOUT_FUNCTIONS,
|
evalContextWithOutFunctions,
|
||||||
);
|
);
|
||||||
set(lintTreeErrors, `["${fullPropertyPath}"]`, lintErrors);
|
set(lintTreeErrors, `["${fullPropertyPath}"]`, lintErrors);
|
||||||
});
|
});
|
||||||
|
|
@ -99,12 +112,6 @@ export function getlintErrorsFromTree(
|
||||||
if (triggerPaths.size || bindingPathsRequiringFunctions.size) {
|
if (triggerPaths.size || bindingPathsRequiringFunctions.size) {
|
||||||
// we only create GLOBAL_DATA_WITH_FUNCTIONS if there are paths requiring it
|
// we only create GLOBAL_DATA_WITH_FUNCTIONS if there are paths requiring it
|
||||||
// In trigger based fields, functions such as showAlert, storeValue, etc need to be added to the global data
|
// In trigger based fields, functions such as showAlert, storeValue, etc need to be added to the global data
|
||||||
const GLOBAL_DATA_WITH_FUNCTIONS = createGlobalData({
|
|
||||||
dataTree: unEvalTree,
|
|
||||||
resolvedFunctions: {},
|
|
||||||
isTriggerBased: true,
|
|
||||||
skipEntityFunctions: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
// lint binding paths that need GLOBAL_DATA_WITH_FUNCTIONS
|
// lint binding paths that need GLOBAL_DATA_WITH_FUNCTIONS
|
||||||
if (bindingPathsRequiringFunctions.size) {
|
if (bindingPathsRequiringFunctions.size) {
|
||||||
|
|
@ -121,7 +128,7 @@ export function getlintErrorsFromTree(
|
||||||
unEvalPropertyValue,
|
unEvalPropertyValue,
|
||||||
entity,
|
entity,
|
||||||
fullPropertyPath,
|
fullPropertyPath,
|
||||||
GLOBAL_DATA_WITH_FUNCTIONS,
|
evalContext,
|
||||||
);
|
);
|
||||||
set(lintTreeErrors, `["${fullPropertyPath}"]`, lintErrors);
|
set(lintTreeErrors, `["${fullPropertyPath}"]`, lintErrors);
|
||||||
});
|
});
|
||||||
|
|
@ -141,7 +148,7 @@ export function getlintErrorsFromTree(
|
||||||
const lintErrors = lintTriggerPath(
|
const lintErrors = lintTriggerPath(
|
||||||
unEvalPropertyValue,
|
unEvalPropertyValue,
|
||||||
entity,
|
entity,
|
||||||
GLOBAL_DATA_WITH_FUNCTIONS,
|
evalContext,
|
||||||
);
|
);
|
||||||
set(lintTreeErrors, `["${triggerPath}"]`, lintErrors);
|
set(lintTreeErrors, `["${triggerPath}"]`, lintErrors);
|
||||||
});
|
});
|
||||||
|
|
@ -155,7 +162,7 @@ function lintBindingPath(
|
||||||
dynamicBinding: string,
|
dynamicBinding: string,
|
||||||
entity: DataTreeEntity,
|
entity: DataTreeEntity,
|
||||||
fullPropertyPath: string,
|
fullPropertyPath: string,
|
||||||
globalData: ReturnType<typeof createGlobalData>,
|
globalData: ReturnType<typeof createEvaluationContext>,
|
||||||
) {
|
) {
|
||||||
let lintErrors: LintError[] = [];
|
let lintErrors: LintError[] = [];
|
||||||
|
|
||||||
|
|
@ -214,7 +221,7 @@ function lintBindingPath(
|
||||||
function lintTriggerPath(
|
function lintTriggerPath(
|
||||||
userScript: string,
|
userScript: string,
|
||||||
entity: DataTreeEntity,
|
entity: DataTreeEntity,
|
||||||
globalData: ReturnType<typeof createGlobalData>,
|
globalData: ReturnType<typeof createEvaluationContext>,
|
||||||
) {
|
) {
|
||||||
const { jsSnippets } = getDynamicBindings(userScript, entity);
|
const { jsSnippets } = getDynamicBindings(userScript, entity);
|
||||||
const script = getScriptToEval(jsSnippets[0], EvaluationScriptType.TRIGGERS);
|
const script = getScriptToEval(jsSnippets[0], EvaluationScriptType.TRIGGERS);
|
||||||
|
|
|
||||||
|
|
@ -98,6 +98,7 @@ import {
|
||||||
validateActionProperty,
|
validateActionProperty,
|
||||||
validateAndParseWidgetProperty,
|
validateAndParseWidgetProperty,
|
||||||
} from "./validationUtils";
|
} from "./validationUtils";
|
||||||
|
import { errorModifier } from "workers/Evaluation/errorModifier";
|
||||||
|
|
||||||
type SortedDependencies = Array<string>;
|
type SortedDependencies = Array<string>;
|
||||||
export type EvalProps = {
|
export type EvalProps = {
|
||||||
|
|
@ -661,6 +662,9 @@ export default class DataTreeEvaluator {
|
||||||
evalMetaUpdates: EvalMetaUpdates;
|
evalMetaUpdates: EvalMetaUpdates;
|
||||||
} {
|
} {
|
||||||
const tree = klona(oldUnevalTree);
|
const tree = klona(oldUnevalTree);
|
||||||
|
|
||||||
|
errorModifier.updateAsyncFunctions(tree);
|
||||||
|
|
||||||
const evalMetaUpdates: EvalMetaUpdates = [];
|
const evalMetaUpdates: EvalMetaUpdates = [];
|
||||||
try {
|
try {
|
||||||
const evaluatedTree = sortedDependencies.reduce(
|
const evaluatedTree = sortedDependencies.reduce(
|
||||||
|
|
@ -1033,7 +1037,7 @@ export default class DataTreeEvaluator {
|
||||||
js: string,
|
js: string,
|
||||||
data: DataTree,
|
data: DataTree,
|
||||||
resolvedFunctions: Record<string, any>,
|
resolvedFunctions: Record<string, any>,
|
||||||
createGlobalData: boolean,
|
isJSObject: boolean,
|
||||||
contextData?: EvaluateContext,
|
contextData?: EvaluateContext,
|
||||||
callbackData?: Array<any>,
|
callbackData?: Array<any>,
|
||||||
skipUserLogsOperations = false,
|
skipUserLogsOperations = false,
|
||||||
|
|
@ -1043,7 +1047,7 @@ export default class DataTreeEvaluator {
|
||||||
js,
|
js,
|
||||||
data,
|
data,
|
||||||
resolvedFunctions,
|
resolvedFunctions,
|
||||||
createGlobalData,
|
isJSObject,
|
||||||
contextData,
|
contextData,
|
||||||
callbackData,
|
callbackData,
|
||||||
skipUserLogsOperations,
|
skipUserLogsOperations,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user