Merge pull request #10922 from appsmithorg/chore/releaseBlockerFixes-v1.6.9

chore: releaseBlockerFixes-v1.6.9
This commit is contained in:
Arpit Mohan 2022-02-08 10:36:31 +05:30 committed by GitHub
commit ff8e2a64e8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 256 additions and 68 deletions

View File

@ -1,9 +1,7 @@
import { ReduxActionTypes, ReduxAction } from "constants/ReduxActionConstants"; import { ReduxActionTypes, ReduxAction } from "constants/ReduxActionConstants";
import { BatchAction, batchAction } from "actions/batchActions"; import { BatchAction, batchAction } from "actions/batchActions";
import { Diff } from "deep-diff";
import { DataTree } from "entities/DataTree/dataTreeFactory"; import { DataTree } from "entities/DataTree/dataTreeFactory";
import { isWidget } from "../workers/evaluationUtils";
import { MetaState } from "../reducers/entityReducers/metaReducer";
import isEmpty from "lodash/isEmpty";
export interface UpdateWidgetMetaPropertyPayload { export interface UpdateWidgetMetaPropertyPayload {
widgetId: string; widgetId: string;
@ -47,18 +45,15 @@ export const resetChildrenMetaProperty = (
}; };
}; };
export const updateMetaState = (evaluatedDataTree: DataTree) => { export const updateMetaState = (
const updatedWidgetMetaState: MetaState = {}; updates: Diff<any, any>[],
Object.values(evaluatedDataTree).forEach((entity) => { updatedDataTree: DataTree,
if (isWidget(entity) && entity.widgetId && !isEmpty(entity.meta)) { ) => {
updatedWidgetMetaState[entity.widgetId] = entity.meta;
}
});
return { return {
type: ReduxActionTypes.UPDATE_META_STATE, type: ReduxActionTypes.UPDATE_META_STATE,
payload: { payload: {
updatedWidgetMetaState, updates,
updatedDataTree,
}, },
}; };
}; };

View File

@ -118,7 +118,8 @@ export interface ExecuteErrorPayload extends ErrorActionPayload {
export const urlGroupsRegexExp = /^(https?:\/{2}\S+?)(\/[\s\S]*?)?(\?(?![^{]*})[\s\S]*)?$/; export const urlGroupsRegexExp = /^(https?:\/{2}\S+?)(\/[\s\S]*?)?(\?(?![^{]*})[\s\S]*)?$/;
export const EXECUTION_PARAM_KEY = "executionParams"; export const EXECUTION_PARAM_KEY = "executionParams";
export const EXECUTION_PARAM_REFERENCE_REGEX = /this.params/g; export const EXECUTION_PARAM_REFERENCE_REGEX = /this.params|this\?.params/g;
export const THIS_DOT_PARAMS_KEY = "params";
export const RESP_HEADER_DATATYPE = "X-APPSMITH-DATATYPE"; export const RESP_HEADER_DATATYPE = "X-APPSMITH-DATATYPE";
export const API_REQUEST_HEADERS: APIHeaders = { export const API_REQUEST_HEADERS: APIHeaders = {

View File

@ -1,13 +1,16 @@
import { set, cloneDeep } from "lodash"; import { set, cloneDeep, get } from "lodash";
import { createReducer } from "utils/AppsmithUtils"; import { createReducer } from "utils/AppsmithUtils";
import { UpdateWidgetMetaPropertyPayload } from "actions/metaActions"; import { UpdateWidgetMetaPropertyPayload } from "actions/metaActions";
import isObject from "lodash/isObject";
import { DataTreeWidget } from "entities/DataTree/dataTreeFactory";
import { import {
ReduxActionTypes, ReduxActionTypes,
ReduxAction, ReduxAction,
WidgetReduxActionTypes, WidgetReduxActionTypes,
} from "constants/ReduxActionConstants"; } from "constants/ReduxActionConstants";
import { Diff } from "deep-diff";
import produce from "immer";
import { DataTree } from "entities/DataTree/dataTreeFactory";
import { isWidget } from "../../workers/evaluationUtils";
export type MetaState = Record<string, Record<string, unknown>>; export type MetaState = Record<string, Record<string, unknown>>;
@ -17,24 +20,41 @@ export const metaReducer = createReducer(initialState, {
[ReduxActionTypes.UPDATE_META_STATE]: ( [ReduxActionTypes.UPDATE_META_STATE]: (
state: MetaState, state: MetaState,
action: ReduxAction<{ action: ReduxAction<{
updatedWidgetMetaState: Record<string, DataTreeWidget>; updates: Diff<any, any>[];
updatedDataTree: DataTree;
}>, }>,
) => { ) => {
// if metaObject is updated in dataTree we also update meta values, to keep meta state in sync. const { updatedDataTree, updates } = action.payload;
const newMetaState = cloneDeep(state);
const { updatedWidgetMetaState } = action.payload;
Object.entries(updatedWidgetMetaState).forEach( // if metaObject is updated in dataTree we also update meta values, to keep meta state in sync.
([entityWidgetId, entityMetaState]) => { const newMetaState = produce(state, (draftMetaState) => {
if (isObject(newMetaState[entityWidgetId])) { if (updates.length) {
Object.keys(newMetaState[entityWidgetId]).forEach((key) => { updates.forEach((update) => {
if (key in entityMetaState) { // if meta field is updated in the dataTree then update metaReducer values.
newMetaState[entityWidgetId][key] = entityMetaState[key]; if (
update.kind === "E" &&
update.path?.length &&
update.path?.length > 1 &&
update.path[1] === "meta"
) {
// path eg: Input1.meta.defaultText
const entity = get(updatedDataTree, update.path[0]);
const metaPropertyPath = update.path.slice(2);
if (
isWidget(entity) &&
entity.widgetId &&
metaPropertyPath.length
) {
set(
draftMetaState,
[entity.widgetId, ...metaPropertyPath],
update.rhs,
);
}
} }
}); });
} }
}, });
);
return newMetaState; return newMetaState;
}, },
[ReduxActionTypes.SET_META_PROP]: ( [ReduxActionTypes.SET_META_PROP]: (

View File

@ -140,7 +140,7 @@ function* evaluateTreeSaga(
PerformanceTransactionName.SET_EVALUATED_TREE, PerformanceTransactionName.SET_EVALUATED_TREE,
); );
yield put(updateMetaState(dataTree)); yield put(updateMetaState(updates, dataTree));
const updatedDataTree = yield select(getDataTree); const updatedDataTree = yield select(getDataTree);
log.debug({ jsUpdates: jsUpdates }); log.debug({ jsUpdates: jsUpdates });

View File

@ -25,7 +25,6 @@ import {
migrateTableSanitizeColumnKeys, migrateTableSanitizeColumnKeys,
isSortableMigration, isSortableMigration,
migrateTableWidgetIconButtonVariant, migrateTableWidgetIconButtonVariant,
migrateTableWidgetNumericColumnName,
} from "./migrations/TableWidget"; } from "./migrations/TableWidget";
import { migrateTextStyleFromTextWidget } from "./migrations/TextWidgetReplaceTextStyle"; import { migrateTextStyleFromTextWidget } from "./migrations/TextWidgetReplaceTextStyle";
import { DATA_BIND_REGEX_GLOBAL } from "constants/BindingsConstants"; import { DATA_BIND_REGEX_GLOBAL } from "constants/BindingsConstants";
@ -1039,7 +1038,10 @@ export const transformDSL = (
} }
if (currentDSL.version === 50) { if (currentDSL.version === 50) {
currentDSL = migrateTableWidgetNumericColumnName(currentDSL); /*
* We're skipping this to fix a bad table migration - migrateTableWidgetNumericColumnName
* it overwrites the computedValue of the table columns
*/
currentDSL.version = LATEST_PAGE_VERSION; currentDSL.version = LATEST_PAGE_VERSION;
} }

View File

@ -490,6 +490,9 @@ export const migrateTableWidgetIconButtonVariant = (currentDSL: DSLWidget) => {
return currentDSL; return currentDSL;
}; };
/*
* DO NOT USE THIS. it overwrites conputedValues of the Table Columns
*/
export const migrateTableWidgetNumericColumnName = (currentDSL: DSLWidget) => { export const migrateTableWidgetNumericColumnName = (currentDSL: DSLWidget) => {
currentDSL.children = currentDSL.children?.map((child: WidgetProps) => { currentDSL.children = currentDSL.children?.map((child: WidgetProps) => {
if (child.type === "TABLE_WIDGET") { if (child.type === "TABLE_WIDGET") {

View File

@ -0,0 +1,109 @@
import DataTreeEvaluator from "./DataTreeEvaluator";
describe("DataTreeEvaluator", () => {
let dataTreeEvaluator: DataTreeEvaluator;
beforeAll(() => {
dataTreeEvaluator = new DataTreeEvaluator({});
});
describe("evaluateActionBindings", () => {
it("handles this.params.property", () => {
const result = dataTreeEvaluator.evaluateActionBindings(
[
"(function() { return this.params.property })()",
"(() => { return this.params.property })()",
'this.params.property || "default value"',
'this.params.property1 || "default value"',
],
{
property: "my value",
},
);
expect(result).toStrictEqual([
"my value",
"my value",
"my value",
"default value",
]);
});
it("handles this?.params.property", () => {
const result = dataTreeEvaluator.evaluateActionBindings(
[
"(() => { return this?.params.property })()",
"(function() { return this?.params.property })()",
'this?.params.property || "default value"',
'this?.params.property1 || "default value"',
],
{
property: "my value",
},
);
expect(result).toStrictEqual([
"my value",
"my value",
"my value",
"default value",
]);
});
it("handles this?.params?.property", () => {
const result = dataTreeEvaluator.evaluateActionBindings(
[
"(() => { return this?.params?.property })()",
"(function() { return this?.params?.property })()",
'this?.params?.property || "default value"',
'this?.params?.property1 || "default value"',
],
{
property: "my value",
},
);
expect(result).toStrictEqual([
"my value",
"my value",
"my value",
"default value",
]);
});
it("handles executionParams.property", () => {
const result = dataTreeEvaluator.evaluateActionBindings(
[
"(function() { return executionParams.property })()",
"(() => { return executionParams.property })()",
'executionParams.property || "default value"',
'executionParams.property1 || "default value"',
],
{
property: "my value",
},
);
expect(result).toStrictEqual([
"my value",
"my value",
"my value",
"default value",
]);
});
it("handles executionParams?.property", () => {
const result = dataTreeEvaluator.evaluateActionBindings(
[
"(function() { return executionParams?.property })()",
"(() => { return executionParams?.property })()",
'executionParams?.property || "default value"',
'executionParams?.property1 || "default value"',
],
{
property: "my value",
},
);
expect(result).toStrictEqual([
"my value",
"my value",
"my value",
"default value",
]);
});
});
});

View File

@ -55,11 +55,13 @@ import toposort from "toposort";
import { import {
EXECUTION_PARAM_KEY, EXECUTION_PARAM_KEY,
EXECUTION_PARAM_REFERENCE_REGEX, EXECUTION_PARAM_REFERENCE_REGEX,
THIS_DOT_PARAMS_KEY,
} from "constants/AppsmithActionConstants/ActionConstants"; } from "constants/AppsmithActionConstants/ActionConstants";
import { DATA_BIND_REGEX } from "constants/BindingsConstants"; import { DATA_BIND_REGEX } from "constants/BindingsConstants";
import evaluateSync, { import evaluateSync, {
createGlobalData, createGlobalData,
EvalResult, EvalResult,
EvaluateContext,
EvaluationScriptType, EvaluationScriptType,
getScriptToEval, getScriptToEval,
evaluateAsync, evaluateAsync,
@ -609,12 +611,19 @@ export default class DataTreeEvaluator {
entity.bindingPaths[propertyPath] || entity.bindingPaths[propertyPath] ||
EvaluationSubstitutionType.TEMPLATE; EvaluationSubstitutionType.TEMPLATE;
const contextData: EvaluateContext = {};
if (isAction(entity)) {
contextData.thisContext = {
params: {},
};
}
try { try {
evalPropertyValue = this.getDynamicValue( evalPropertyValue = this.getDynamicValue(
unEvalPropertyValue, unEvalPropertyValue,
currentTree, currentTree,
resolvedFunctions, resolvedFunctions,
evaluationSubstitutionType, evaluationSubstitutionType,
contextData,
undefined, undefined,
fullPropertyPath, fullPropertyPath,
); );
@ -764,6 +773,7 @@ export default class DataTreeEvaluator {
data: DataTree, data: DataTree,
resolvedFunctions: Record<string, any>, resolvedFunctions: Record<string, any>,
evaluationSubstitutionType: EvaluationSubstitutionType, evaluationSubstitutionType: EvaluationSubstitutionType,
contextData?: EvaluateContext,
callBackData?: Array<any>, callBackData?: Array<any>,
fullPropertyPath?: string, fullPropertyPath?: string,
) { ) {
@ -792,6 +802,7 @@ export default class DataTreeEvaluator {
toBeSentForEval, toBeSentForEval,
data, data,
resolvedFunctions, resolvedFunctions,
contextData,
callBackData, callBackData,
); );
if (fullPropertyPath && result.errors.length) { if (fullPropertyPath && result.errors.length) {
@ -864,10 +875,17 @@ export default class DataTreeEvaluator {
js: string, js: string,
data: DataTree, data: DataTree,
resolvedFunctions: Record<string, any>, resolvedFunctions: Record<string, any>,
contextData?: EvaluateContext,
callbackData?: Array<any>, callbackData?: Array<any>,
): EvalResult { ): EvalResult {
try { try {
return evaluateSync(js, data, resolvedFunctions, callbackData); return evaluateSync(
js,
data,
resolvedFunctions,
contextData,
callbackData,
);
} catch (e) { } catch (e) {
return { return {
result: undefined, result: undefined,
@ -1555,24 +1573,26 @@ export default class DataTreeEvaluator {
); );
} }
return bindings.map((binding) => {
// Replace any reference of 'this.params' to 'executionParams' (backwards compatibility) // Replace any reference of 'this.params' to 'executionParams' (backwards compatibility)
const bindingsForExecutionParams: string[] = bindings.map( // also helps with dealing with IIFE which are normal functions (not arrow)
(binding: string) => // because normal functions won't retain 'this' context (when executed elsewhere)
binding.replace(EXECUTION_PARAM_REFERENCE_REGEX, EXECUTION_PARAM_KEY), const replacedBinding = binding.replace(
EXECUTION_PARAM_REFERENCE_REGEX,
EXECUTION_PARAM_KEY,
); );
return this.getDynamicValue(
const dataTreeWithExecutionParams = Object.assign({}, this.evalTree, { `{{${replacedBinding}}}`,
[EXECUTION_PARAM_KEY]: evaluatedExecutionParams, this.evalTree,
});
return bindingsForExecutionParams.map((binding) =>
this.getDynamicValue(
`{{${binding}}}`,
dataTreeWithExecutionParams,
this.resolvedFunctions, this.resolvedFunctions,
EvaluationSubstitutionType.TEMPLATE, EvaluationSubstitutionType.TEMPLATE,
), // params can be accessed via "this.params" or "executionParams"
{
thisContext: { [THIS_DOT_PARAMS_KEY]: evaluatedExecutionParams },
globalContext: { [EXECUTION_PARAM_KEY]: evaluatedExecutionParams },
},
); );
});
} }
clearErrors() { clearErrors() {

View File

@ -30,6 +30,9 @@ describe("evaluateSync", () => {
triggerPaths: {}, triggerPaths: {},
validationPaths: {}, validationPaths: {},
logBlackList: {}, logBlackList: {},
overridingPropertyPaths: {},
privateWidgets: {},
propertyOverrideDependency: {},
}; };
const dataTree: DataTree = { const dataTree: DataTree = {
Input1: widget, Input1: widget,
@ -75,7 +78,7 @@ describe("evaluateSync", () => {
const result = wrongJS const result = wrongJS
return result; return result;
} }
closedFunction() closedFunction.call(THIS_CONTEXT)
`, `,
severity: "error", severity: "error",
originalBinding: "wrongJS", originalBinding: "wrongJS",
@ -89,7 +92,7 @@ describe("evaluateSync", () => {
const result = wrongJS const result = wrongJS
return result; return result;
} }
closedFunction() closedFunction.call(THIS_CONTEXT)
`, `,
severity: "error", severity: "error",
originalBinding: "wrongJS", originalBinding: "wrongJS",
@ -108,7 +111,7 @@ describe("evaluateSync", () => {
const result = {}.map() const result = {}.map()
return result; return result;
} }
closedFunction() closedFunction.call(THIS_CONTEXT)
`, `,
severity: "error", severity: "error",
originalBinding: "{}.map()", originalBinding: "{}.map()",
@ -135,7 +138,7 @@ describe("evaluateSync", () => {
const result = setTimeout(() => {}, 100) const result = setTimeout(() => {}, 100)
return result; return result;
} }
closedFunction() closedFunction.call(THIS_CONTEXT)
`, `,
severity: "error", severity: "error",
originalBinding: "setTimeout(() => {}, 100)", originalBinding: "setTimeout(() => {}, 100)",
@ -151,7 +154,7 @@ describe("evaluateSync", () => {
it("evaluates functions with callback data", () => { it("evaluates functions with callback data", () => {
const js = "(arg1, arg2) => arg1.value + arg2"; const js = "(arg1, arg2) => arg1.value + arg2";
const callbackData = [{ value: "test" }, "1"]; const callbackData = [{ value: "test" }, "1"];
const response = evaluate(js, dataTree, {}, callbackData); const response = evaluate(js, dataTree, {}, {}, callbackData);
expect(response.result).toBe("test1"); expect(response.result).toBe("test1");
}); });
it("handles EXPRESSIONS with new lines", () => { it("handles EXPRESSIONS with new lines", () => {
@ -165,22 +168,38 @@ describe("evaluateSync", () => {
}); });
it("handles TRIGGERS with new lines", () => { it("handles TRIGGERS with new lines", () => {
let js = "\n"; let js = "\n";
let response = evaluate(js, dataTree, {}, undefined); let response = evaluate(js, dataTree, {}, undefined, undefined);
expect(response.errors.length).toBe(0); expect(response.errors.length).toBe(0);
js = "\n\n\n"; js = "\n\n\n";
response = evaluate(js, dataTree, {}, undefined); response = evaluate(js, dataTree, {}, undefined, undefined);
expect(response.errors.length).toBe(0); expect(response.errors.length).toBe(0);
}); });
it("handles ANONYMOUS_FUNCTION with new lines", () => { it("handles ANONYMOUS_FUNCTION with new lines", () => {
let js = "\n"; let js = "\n";
let response = evaluate(js, dataTree, {}, undefined); let response = evaluate(js, dataTree, {}, undefined, undefined);
expect(response.errors.length).toBe(0); expect(response.errors.length).toBe(0);
js = "\n\n\n"; js = "\n\n\n";
response = evaluate(js, dataTree, {}, undefined); response = evaluate(js, dataTree, {}, undefined, undefined);
expect(response.errors.length).toBe(0); expect(response.errors.length).toBe(0);
}); });
it("has access to this context", () => {
const js = "this.contextVariable";
const thisContext = { contextVariable: "test" };
const response = evaluate(js, dataTree, {}, { thisContext });
expect(response.result).toBe("test");
// there should not be any error when accessing "this" variables
expect(response.errors).toHaveLength(0);
});
it("has access to additional global context", () => {
const js = "contextVariable";
const globalContext = { contextVariable: "test" };
const response = evaluate(js, dataTree, {}, { globalContext });
expect(response.result).toBe("test");
expect(response.errors).toHaveLength(0);
});
}); });
describe("evaluateAsync", () => { describe("evaluateAsync", () => {
@ -256,7 +275,7 @@ describe("isFunctionAsync", () => {
if (typeof testFunc === "string") { if (typeof testFunc === "string") {
testFunc = eval(testFunc); testFunc = eval(testFunc);
} }
const actual = isFunctionAsync(testFunc, {}); const actual = isFunctionAsync(testFunc, {}, {});
expect(actual).toBe(testCase.expected); expect(actual).toBe(testCase.expected);
} }
}); });

View File

@ -34,12 +34,12 @@ export const EvaluationScripts: Record<EvaluationScriptType, string> = {
const result = ${ScriptTemplate} const result = ${ScriptTemplate}
return result; return result;
} }
closedFunction() closedFunction.call(THIS_CONTEXT)
`, `,
[EvaluationScriptType.ANONYMOUS_FUNCTION]: ` [EvaluationScriptType.ANONYMOUS_FUNCTION]: `
function callback (script) { function callback (script) {
const userFunction = script; const userFunction = script;
const result = userFunction?.apply(self, ARGUMENTS); const result = userFunction?.apply(THIS_CONTEXT, ARGUMENTS);
return result; return result;
} }
callback(${ScriptTemplate}) callback(${ScriptTemplate})
@ -49,7 +49,7 @@ export const EvaluationScripts: Record<EvaluationScriptType, string> = {
const result = await ${ScriptTemplate}; const result = await ${ScriptTemplate};
return result; return result;
} }
closedFunction(); closedFunction.call(THIS_CONTEXT);
`, `,
}; };
@ -102,6 +102,18 @@ export const createGlobalData = (
const GLOBAL_DATA: Record<string, any> = {}; const GLOBAL_DATA: Record<string, any> = {};
///// Adding callback data ///// Adding callback data
GLOBAL_DATA.ARGUMENTS = evalArguments; GLOBAL_DATA.ARGUMENTS = evalArguments;
//// Adding contextual data not part of data tree
GLOBAL_DATA.THIS_CONTEXT = {};
if (context) {
if (context.thisContext) {
GLOBAL_DATA.THIS_CONTEXT = context.thisContext;
}
if (context.globalContext) {
Object.entries(context.globalContext).forEach(([key, value]) => {
GLOBAL_DATA[key] = value;
});
}
}
//// Add internal functions to dataTree; //// Add internal functions to dataTree;
const dataTreeWithFunctions = enhanceDataTreeWithFunctions( const dataTreeWithFunctions = enhanceDataTreeWithFunctions(
dataTree, dataTree,
@ -134,9 +146,13 @@ export function sanitizeScript(js: string) {
} }
/** Define a context just for this script /** Define a context just for this script
* thisContext will define it on the `this`
* globalContext will define it globally
* requestId is used for completing promises * requestId is used for completing promises
*/ */
export type EvaluateContext = { export type EvaluateContext = {
thisContext?: Record<string, any>;
globalContext?: Record<string, any>;
requestId?: string; requestId?: string;
}; };
@ -172,6 +188,7 @@ export default function evaluateSync(
userScript: string, userScript: string,
dataTree: DataTree, dataTree: DataTree,
resolvedFunctions: Record<string, any>, resolvedFunctions: Record<string, any>,
context?: EvaluateContext,
evalArguments?: Array<any>, evalArguments?: Array<any>,
): EvalResult { ): EvalResult {
return (function() { return (function() {
@ -181,7 +198,7 @@ export default function evaluateSync(
const GLOBAL_DATA: Record<string, any> = createGlobalData( const GLOBAL_DATA: Record<string, any> = createGlobalData(
dataTree, dataTree,
resolvedFunctions, resolvedFunctions,
undefined, context,
evalArguments, evalArguments,
); );
GLOBAL_DATA.ALLOW_ASYNC = false; GLOBAL_DATA.ALLOW_ASYNC = false;

View File

@ -805,10 +805,11 @@ export const overrideWidgetProperties = (
const overridingPropertyPaths = const overridingPropertyPaths =
entity.overridingPropertyPaths[propertyPath]; entity.overridingPropertyPaths[propertyPath];
overridingPropertyPaths.forEach((overriddenPropertyKey) => { overridingPropertyPaths.forEach((overriddenPropertyPath) => {
const overriddenPropertyPathArray = overriddenPropertyPath.split(".");
_.set( _.set(
currentTree, currentTree,
`${entity.widgetName}.${overriddenPropertyKey}`, [entity.widgetName, ...overriddenPropertyPathArray],
clonedValue, clonedValue,
); );
}); });
@ -824,9 +825,10 @@ export const overrideWidgetProperties = (
const defaultValue = entity[propertyOverridingKeyMap.DEFAULT]; const defaultValue = entity[propertyOverridingKeyMap.DEFAULT];
const clonedDefaultValue = cloneDeep(defaultValue); const clonedDefaultValue = cloneDeep(defaultValue);
if (defaultValue !== undefined) { if (defaultValue !== undefined) {
const propertyPathArray = propertyPath.split(".");
_.set( _.set(
currentTree, currentTree,
`${entity.widgetName}.${propertyPath}`, [entity.widgetName, ...propertyPathArray],
clonedDefaultValue, clonedDefaultValue,
); );
return { return {

View File

@ -803,7 +803,7 @@ export const VALIDATORS: Record<ValidationTypes, Validator> = {
}; };
if (config.params?.fnString && isString(config.params?.fnString)) { if (config.params?.fnString && isString(config.params?.fnString)) {
try { try {
const { result } = evaluate(config.params.fnString, {}, {}, [ const { result } = evaluate(config.params.fnString, {}, {}, undefined, [
value, value,
props, props,
_, _,