Execution params in actions (#2128)
This commit is contained in:
parent
7826cee7c3
commit
5bd2cc1ea4
139
app/client/cypress/fixtures/executionParamsDsl.json
Normal file
139
app/client/cypress/fixtures/executionParamsDsl.json
Normal file
|
|
@ -0,0 +1,139 @@
|
||||||
|
{
|
||||||
|
"dsl": {
|
||||||
|
"widgetName": "MainContainer",
|
||||||
|
"backgroundColor": "none",
|
||||||
|
"rightColumn": 1224,
|
||||||
|
"snapColumns": 16,
|
||||||
|
"detachFromLayout": true,
|
||||||
|
"widgetId": "0",
|
||||||
|
"topRow": 0,
|
||||||
|
"bottomRow": 1254,
|
||||||
|
"containerStyle": "none",
|
||||||
|
"snapRows": 33,
|
||||||
|
"parentRowSpace": 1,
|
||||||
|
"type": "CANVAS_WIDGET",
|
||||||
|
"canExtend": true,
|
||||||
|
"dynamicBindingPathList": [],
|
||||||
|
"version": 4,
|
||||||
|
"minHeight": 1292,
|
||||||
|
"parentColumnSpace": 1,
|
||||||
|
"leftColumn": 0,
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"backgroundColor": "#FFFFFF",
|
||||||
|
"widgetName": "Container3",
|
||||||
|
"type": "CONTAINER_WIDGET",
|
||||||
|
"containerStyle": "card",
|
||||||
|
"isVisible": true,
|
||||||
|
"isLoading": false,
|
||||||
|
"parentColumnSpace": 75.25,
|
||||||
|
"parentRowSpace": 38,
|
||||||
|
"dynamicBindingPathList": [],
|
||||||
|
"leftColumn": 0,
|
||||||
|
"rightColumn": 16,
|
||||||
|
"topRow": 0,
|
||||||
|
"bottomRow": 23,
|
||||||
|
"snapColumns": 16,
|
||||||
|
"orientation": "VERTICAL",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"backgroundColor": "transparent",
|
||||||
|
"widgetName": "8muuok24ny",
|
||||||
|
"type": "CANVAS_WIDGET",
|
||||||
|
"containerStyle": "none",
|
||||||
|
"isVisible": true,
|
||||||
|
"isLoading": false,
|
||||||
|
"parentColumnSpace": 1,
|
||||||
|
"parentRowSpace": 1,
|
||||||
|
"leftColumn": 0,
|
||||||
|
"rightColumn": 1204,
|
||||||
|
"topRow": 0,
|
||||||
|
"bottomRow": 532,
|
||||||
|
"snapColumns": 16,
|
||||||
|
"orientation": "VERTICAL",
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"isVisible": true,
|
||||||
|
"label": "Data",
|
||||||
|
"widgetName": "Table1",
|
||||||
|
"tableData": "",
|
||||||
|
"type": "TABLE_WIDGET",
|
||||||
|
"isLoading": false,
|
||||||
|
"parentColumnSpace": 71.75,
|
||||||
|
"parentRowSpace": 38,
|
||||||
|
"leftColumn": 2,
|
||||||
|
"rightColumn": 10,
|
||||||
|
"topRow": 3,
|
||||||
|
"bottomRow": 10,
|
||||||
|
"parentId": "tyiwk4xuq0",
|
||||||
|
"widgetId": "5up3r2iuvs",
|
||||||
|
"dynamicBindingPathList": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"widgetName": "StaticButton",
|
||||||
|
"rightColumn": 14,
|
||||||
|
"onClick": "",
|
||||||
|
"isDefaultClickDisabled": true,
|
||||||
|
"widgetId": "3p92qmlzfl",
|
||||||
|
"buttonStyle": "PRIMARY_BUTTON",
|
||||||
|
"topRow": 3,
|
||||||
|
"bottomRow": 4,
|
||||||
|
"parentRowSpace": 38,
|
||||||
|
"isVisible": true,
|
||||||
|
"type": "BUTTON_WIDGET",
|
||||||
|
"dynamicBindingPathList": [],
|
||||||
|
"parentId": "8muuok24ny",
|
||||||
|
"isLoading": false,
|
||||||
|
"parentColumnSpace": 71.75,
|
||||||
|
"leftColumn": 11,
|
||||||
|
"text": "Run Static",
|
||||||
|
"isDisabled": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"widgetName": "DynamicButton",
|
||||||
|
"rightColumn": 14,
|
||||||
|
"onClick": "",
|
||||||
|
"isDefaultClickDisabled": true,
|
||||||
|
"widgetId": "asdasdlnud",
|
||||||
|
"buttonStyle": "PRIMARY_BUTTON",
|
||||||
|
"topRow": 4,
|
||||||
|
"bottomRow": 5,
|
||||||
|
"parentRowSpace": 38,
|
||||||
|
"isVisible": true,
|
||||||
|
"type": "BUTTON_WIDGET",
|
||||||
|
"dynamicBindingPathList": [],
|
||||||
|
"parentId": "8muuok24ny",
|
||||||
|
"isLoading": false,
|
||||||
|
"parentColumnSpace": 71.75,
|
||||||
|
"leftColumn": 11,
|
||||||
|
"text": "Run Dynamic",
|
||||||
|
"isDisabled": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"isVisible": true,
|
||||||
|
"inputType": "TEXT",
|
||||||
|
"label": "Endpoint",
|
||||||
|
"widgetName": "EndpointInput",
|
||||||
|
"defaultText": "todos",
|
||||||
|
"type": "INPUT_WIDGET",
|
||||||
|
"isLoading": false,
|
||||||
|
"parentColumnSpace": 71.75,
|
||||||
|
"parentRowSpace": 38,
|
||||||
|
"leftColumn": 9,
|
||||||
|
"rightColumn": 14,
|
||||||
|
"topRow": 1,
|
||||||
|
"bottomRow": 2,
|
||||||
|
"parentId": "0",
|
||||||
|
"widgetId": "ufr2ik3x1q"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"widgetId": "tyiwk4xuq0",
|
||||||
|
"detachFromLayout": true,
|
||||||
|
"canExtend": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"widgetId": "3oe1ka7jon"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,89 @@
|
||||||
|
const dsl = require("../../../fixtures/executionParamsDsl.json");
|
||||||
|
const publishPage = require("../../../locators/publishWidgetspage.json");
|
||||||
|
const commonlocators = require("../../../locators/commonlocators.json");
|
||||||
|
|
||||||
|
describe("API Panel Test Functionality", function() {
|
||||||
|
before(() => {
|
||||||
|
cy.addDsl(dsl);
|
||||||
|
});
|
||||||
|
it("Will pass execution params", function() {
|
||||||
|
// Create the Api
|
||||||
|
cy.NavigateToAPI_Panel();
|
||||||
|
cy.CreateAPI("MultiApi");
|
||||||
|
cy.enterDatasourceAndPath(
|
||||||
|
"https://jsonplaceholder.typicode.com/",
|
||||||
|
"{{this.params.endpoint || 'posts'}}",
|
||||||
|
);
|
||||||
|
cy.WaitAutoSave();
|
||||||
|
// Run it
|
||||||
|
cy.RunAPI();
|
||||||
|
|
||||||
|
// Bind the table
|
||||||
|
cy.SearchEntityandOpen("Table1");
|
||||||
|
cy.testJsontext("tabledata", "{{MultiApi.data");
|
||||||
|
// Assert 'posts' data (default)
|
||||||
|
cy.readTabledataPublish("0", "2").then(cellData => {
|
||||||
|
expect(cellData).to.be.equal(
|
||||||
|
"sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Choose static button
|
||||||
|
cy.SearchEntityandOpen("StaticButton");
|
||||||
|
// toggle js of onClick
|
||||||
|
cy.get(".t--property-control-onclick")
|
||||||
|
.find(".t--js-toggle")
|
||||||
|
.click({ force: true });
|
||||||
|
// Bind with MultiApi with static value
|
||||||
|
cy.testJsontext(
|
||||||
|
"onclick",
|
||||||
|
"{{MultiApi.run(undefined, undefined, { endpoint: 'users",
|
||||||
|
);
|
||||||
|
cy.get(commonlocators.editPropCrossButton).click();
|
||||||
|
|
||||||
|
// Choose dynamic button
|
||||||
|
cy.SearchEntityandOpen("DynamicButton");
|
||||||
|
// toggle js of onClick
|
||||||
|
cy.get(".t--property-control-onclick")
|
||||||
|
.find(".t--js-toggle")
|
||||||
|
.click({ force: true });
|
||||||
|
// Bind with MultiApi with dynamicValue value
|
||||||
|
cy.testJsontext(
|
||||||
|
"onclick",
|
||||||
|
"{{MultiApi.run(undefined, undefined, { endpoint: EndpointInput.text",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Publish the app
|
||||||
|
cy.PublishtheApp();
|
||||||
|
cy.wait("@postExecute");
|
||||||
|
|
||||||
|
// Assert on load data in table
|
||||||
|
cy.readTabledataPublish("0", "2").then(cellData => {
|
||||||
|
expect(cellData).to.be.equal(
|
||||||
|
"sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Click Static button
|
||||||
|
cy.get(publishPage.buttonWidget)
|
||||||
|
.first()
|
||||||
|
.click();
|
||||||
|
// eslint-disable-next-line cypress/no-unnecessary-waiting
|
||||||
|
cy.wait(2000);
|
||||||
|
// Assert statically bound "users" data
|
||||||
|
cy.readTabledataPublish("1", "1").then(cellData => {
|
||||||
|
expect(cellData).to.be.equal("Ervin Howell");
|
||||||
|
});
|
||||||
|
|
||||||
|
// Click dynamic button
|
||||||
|
cy.get(publishPage.buttonWidget)
|
||||||
|
.eq(1)
|
||||||
|
.click();
|
||||||
|
// eslint-disable-next-line cypress/no-unnecessary-waiting
|
||||||
|
cy.wait(2000);
|
||||||
|
// Assert dynamically bound "todos" data
|
||||||
|
cy.readTabledataPublish("0", "2").then(cellData => {
|
||||||
|
expect(cellData).to.be.equal("delectus aut autem");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -72,3 +72,4 @@ export interface ExecuteErrorPayload {
|
||||||
// Group 2 = path (/nested/path)
|
// Group 2 = path (/nested/path)
|
||||||
// Group 3 = params (?param=123¶m2=12)
|
// Group 3 = params (?param=123¶m2=12)
|
||||||
export const urlGroupsRegexExp = /^(https?:\/{2}\S+?)(\/\S*?)(\?\S*)?$/;
|
export const urlGroupsRegexExp = /^(https?:\/{2}\S+?)(\/\S*?)(\?\S*)?$/;
|
||||||
|
export const EXECUTION_PARAM_KEY = "executionParams";
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { WidgetProps } from "widgets/BaseWidget";
|
import { WidgetProps } from "widgets/BaseWidget";
|
||||||
import { DataTree } from "entities/DataTree/dataTreeFactory";
|
import { DataTree } from "entities/DataTree/dataTreeFactory";
|
||||||
|
import { EXECUTION_PARAM_KEY } from "./ActionConstants";
|
||||||
|
|
||||||
// Always add a validator function in ./Validators for these types
|
// Always add a validator function in ./Validators for these types
|
||||||
export const VALIDATION_TYPES = {
|
export const VALIDATION_TYPES = {
|
||||||
|
|
@ -39,7 +40,7 @@ export type Validator = (
|
||||||
|
|
||||||
export const ISO_DATE_FORMAT = "YYYY-MM-DDTHH:mm:ss.SSSZ";
|
export const ISO_DATE_FORMAT = "YYYY-MM-DDTHH:mm:ss.SSSZ";
|
||||||
|
|
||||||
export const JAVSCRIPT_KEYWORDS = {
|
export const JAVASCRIPT_KEYWORDS = {
|
||||||
true: "true",
|
true: "true",
|
||||||
await: "await",
|
await: "await",
|
||||||
break: "break",
|
break: "break",
|
||||||
|
|
@ -87,3 +88,10 @@ export const JAVSCRIPT_KEYWORDS = {
|
||||||
with: "with",
|
with: "with",
|
||||||
yield: "yield",
|
yield: "yield",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const DATA_TREE_KEYWORDS = {
|
||||||
|
actionPaths: "actionPaths",
|
||||||
|
appsmith: "appsmith",
|
||||||
|
pageList: "pageList",
|
||||||
|
[EXECUTION_PARAM_KEY]: EXECUTION_PARAM_KEY,
|
||||||
|
};
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import {
|
||||||
EventType,
|
EventType,
|
||||||
ExecuteActionPayload,
|
ExecuteActionPayload,
|
||||||
ExecuteActionPayloadEvent,
|
ExecuteActionPayloadEvent,
|
||||||
|
EXECUTION_PARAM_KEY,
|
||||||
PageAction,
|
PageAction,
|
||||||
} from "constants/ActionConstants";
|
} from "constants/ActionConstants";
|
||||||
import * as log from "loglevel";
|
import * as log from "loglevel";
|
||||||
|
|
@ -63,7 +64,7 @@ import {
|
||||||
import { AppState } from "reducers";
|
import { AppState } from "reducers";
|
||||||
import { mapToPropList } from "utils/AppsmithUtils";
|
import { mapToPropList } from "utils/AppsmithUtils";
|
||||||
import { validateResponse } from "sagas/ErrorSagas";
|
import { validateResponse } from "sagas/ErrorSagas";
|
||||||
import { ToastType, TypeOptions } from "react-toastify";
|
import { TypeOptions } from "react-toastify";
|
||||||
import { PLUGIN_TYPE_API } from "constants/ApiEditorConstants";
|
import { PLUGIN_TYPE_API } from "constants/ApiEditorConstants";
|
||||||
import { DEFAULT_EXECUTE_ACTION_TIMEOUT_MS } from "constants/ApiConstants";
|
import { DEFAULT_EXECUTE_ACTION_TIMEOUT_MS } from "constants/ApiConstants";
|
||||||
import { updateAppStore } from "actions/pageActions";
|
import { updateAppStore } from "actions/pageActions";
|
||||||
|
|
@ -71,7 +72,7 @@ import { getAppStoreName } from "constants/AppConstants";
|
||||||
import downloadjs from "downloadjs";
|
import downloadjs from "downloadjs";
|
||||||
import { getType, Types } from "utils/TypeHelpers";
|
import { getType, Types } from "utils/TypeHelpers";
|
||||||
import { Toaster } from "components/ads/Toast";
|
import { Toaster } from "components/ads/Toast";
|
||||||
import { Variant, ToastVariant } from "components/ads/common";
|
import { Variant } from "components/ads/common";
|
||||||
import PerformanceTracker, {
|
import PerformanceTracker, {
|
||||||
PerformanceTransactionName,
|
PerformanceTransactionName,
|
||||||
} from "utils/PerformanceTracker";
|
} from "utils/PerformanceTracker";
|
||||||
|
|
@ -239,62 +240,35 @@ const isErrorResponse = (response: ActionApiResponse) => {
|
||||||
return !response.data.isExecutionSuccess;
|
return !response.data.isExecutionSuccess;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function* evaluateDynamicBoundValueSaga(path: string): any {
|
export function* evaluateDynamicBoundValueSaga(
|
||||||
return yield call(evaluateSingleValue, `{{${path}}}`);
|
valueToEvaluate: string,
|
||||||
|
params?: Record<string, unknown>,
|
||||||
|
): any {
|
||||||
|
return yield call(evaluateSingleValue, `{{${valueToEvaluate}}}`, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
const EXECUTION_PARAM_PATH = "this.params";
|
const EXECUTION_PARAM_REFERENCE_REGEX = /this.params/g;
|
||||||
const getExecutionParamPath = (key: string) => `${EXECUTION_PARAM_PATH}.${key}`;
|
|
||||||
|
|
||||||
export function* getActionParams(
|
export function* getActionParams(
|
||||||
bindings: string[] | undefined,
|
bindings: string[] | undefined,
|
||||||
executionParams?: Record<string, any>,
|
executionParams?: Record<string, any>,
|
||||||
) {
|
) {
|
||||||
if (_.isNil(bindings)) return [];
|
if (_.isNil(bindings)) return [];
|
||||||
let dataTreeBindings = bindings;
|
const evaluatedExecutionParams = yield evaluateDynamicBoundValueSaga(
|
||||||
|
JSON.stringify(executionParams),
|
||||||
if (executionParams && Object.keys(executionParams).length) {
|
);
|
||||||
// List of params in the path format
|
|
||||||
const executionParamsPathList = Object.keys(executionParams).map(
|
const bindingsForExecutionParams = bindings.map(binding =>
|
||||||
getExecutionParamPath,
|
binding.replace(EXECUTION_PARAM_REFERENCE_REGEX, EXECUTION_PARAM_KEY),
|
||||||
);
|
|
||||||
const paramSearchRegex = new RegExp(executionParamsPathList.join("|"), "g");
|
|
||||||
// Bindings with references to execution params
|
|
||||||
const executionBindings = bindings.filter(binding =>
|
|
||||||
paramSearchRegex.test(binding),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Replace references with values
|
|
||||||
const replacedBindings = executionBindings.map(binding => {
|
|
||||||
let replaced = binding;
|
|
||||||
const matches = binding.match(paramSearchRegex);
|
|
||||||
if (matches && matches.length) {
|
|
||||||
matches.forEach(match => {
|
|
||||||
// we add one for substring index to account for '.'
|
|
||||||
const paramKey = match.substring(EXECUTION_PARAM_PATH.length + 1);
|
|
||||||
let paramValue = executionParams[paramKey];
|
|
||||||
if (paramValue) {
|
|
||||||
if (typeof paramValue === "object") {
|
|
||||||
paramValue = JSON.stringify(paramValue);
|
|
||||||
}
|
|
||||||
replaced = replaced.replace(match, paramValue);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return replaced;
|
|
||||||
});
|
|
||||||
// Replace binding with replaced bindings for evaluation
|
|
||||||
dataTreeBindings = dataTreeBindings.map(key => {
|
|
||||||
if (executionBindings.includes(key)) {
|
|
||||||
return replacedBindings[executionBindings.indexOf(key)];
|
|
||||||
}
|
|
||||||
return key;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
// Evaluate all values
|
|
||||||
const values: any = yield all(
|
const values: any = yield all(
|
||||||
dataTreeBindings.map((binding: string) => {
|
bindingsForExecutionParams.map((binding: string) => {
|
||||||
return call(evaluateDynamicBoundValueSaga, binding);
|
return call(
|
||||||
|
evaluateDynamicBoundValueSaga,
|
||||||
|
binding,
|
||||||
|
evaluatedExecutionParams,
|
||||||
|
);
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
// convert to object and transform non string values
|
// convert to object and transform non string values
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ import PerformanceTracker, {
|
||||||
import { Variant } from "components/ads/common";
|
import { Variant } from "components/ads/common";
|
||||||
import { Toaster } from "components/ads/Toast";
|
import { Toaster } from "components/ads/Toast";
|
||||||
import * as Sentry from "@sentry/react";
|
import * as Sentry from "@sentry/react";
|
||||||
|
import { EXECUTION_PARAM_KEY } from "../constants/ActionConstants";
|
||||||
|
|
||||||
let evaluationWorker: Worker;
|
let evaluationWorker: Worker;
|
||||||
let workerChannel: EventChannel<any>;
|
let workerChannel: EventChannel<any>;
|
||||||
|
|
@ -110,9 +111,13 @@ function* evaluateTreeSaga(postEvalActions?: ReduxAction<unknown>[]) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function* evaluateSingleValue(binding: string) {
|
export function* evaluateSingleValue(
|
||||||
|
binding: string,
|
||||||
|
executionParams: Record<string, any> = {},
|
||||||
|
) {
|
||||||
if (evaluationWorker) {
|
if (evaluationWorker) {
|
||||||
const dataTree = yield select(getDataTree);
|
const dataTree = yield select(getDataTree);
|
||||||
|
dataTree[EXECUTION_PARAM_KEY] = executionParams;
|
||||||
evaluationWorker.postMessage({
|
evaluationWorker.postMessage({
|
||||||
action: EVAL_WORKER_ACTIONS.EVAL_SINGLE,
|
action: EVAL_WORKER_ACTIONS.EVAL_SINGLE,
|
||||||
dataTree,
|
dataTree,
|
||||||
|
|
|
||||||
|
|
@ -7,8 +7,6 @@ import { Action } from "entities/Action";
|
||||||
import moment from "moment-timezone";
|
import moment from "moment-timezone";
|
||||||
import { WidgetProps } from "../widgets/BaseWidget";
|
import { WidgetProps } from "../widgets/BaseWidget";
|
||||||
|
|
||||||
type StringTuple = [string, string];
|
|
||||||
|
|
||||||
export const removeBindingsFromActionObject = (obj: Action) => {
|
export const removeBindingsFromActionObject = (obj: Action) => {
|
||||||
const string = JSON.stringify(obj);
|
const string = JSON.stringify(obj);
|
||||||
const withBindings = string.replace(DATA_BIND_REGEX_GLOBAL, "{{ }}");
|
const withBindings = string.replace(DATA_BIND_REGEX_GLOBAL, "{{ }}");
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,9 @@
|
||||||
import { GridDefaults } from "constants/WidgetConstants";
|
import { GridDefaults } from "constants/WidgetConstants";
|
||||||
import { JAVSCRIPT_KEYWORDS } from "constants/WidgetValidation";
|
import {
|
||||||
|
DATA_TREE_KEYWORDS,
|
||||||
|
JAVASCRIPT_KEYWORDS,
|
||||||
|
} from "constants/WidgetValidation";
|
||||||
|
import { GLOBAL_FUNCTIONS } from "./autocomplete/EntityDefinitions";
|
||||||
export const snapToGrid = (
|
export const snapToGrid = (
|
||||||
columnWidth: number,
|
columnWidth: number,
|
||||||
rowHeight: number,
|
rowHeight: number,
|
||||||
|
|
@ -165,7 +169,7 @@ export const convertArrayToSentence = (arr: string[]) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* checks if the name is conflciting with
|
* checks if the name is conflicting with
|
||||||
* 1. API names,
|
* 1. API names,
|
||||||
* 2. Queries name
|
* 2. Queries name
|
||||||
* 3. Javascript reserved names
|
* 3. Javascript reserved names
|
||||||
|
|
@ -180,9 +184,10 @@ export const isNameValid = (
|
||||||
name: string,
|
name: string,
|
||||||
invalidNames: Record<string, any>,
|
invalidNames: Record<string, any>,
|
||||||
) => {
|
) => {
|
||||||
if (name in JAVSCRIPT_KEYWORDS || name in invalidNames) {
|
return !(
|
||||||
return false;
|
name in JAVASCRIPT_KEYWORDS ||
|
||||||
}
|
name in DATA_TREE_KEYWORDS ||
|
||||||
|
name in GLOBAL_FUNCTIONS ||
|
||||||
return true;
|
name in invalidNames
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user