Execution params in actions (#2128)

This commit is contained in:
Hetu Nandu 2020-12-15 00:18:13 +05:30 committed by GitHub
parent 7826cee7c3
commit 5bd2cc1ea4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 277 additions and 58 deletions

View 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"
}
]
}
}

View File

@ -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");
});
});
});

View File

@ -72,3 +72,4 @@ export interface ExecuteErrorPayload {
// Group 2 = path (/nested/path)
// Group 3 = params (?param=123&param2=12)
export const urlGroupsRegexExp = /^(https?:\/{2}\S+?)(\/\S*?)(\?\S*)?$/;
export const EXECUTION_PARAM_KEY = "executionParams";

View File

@ -1,5 +1,6 @@
import { WidgetProps } from "widgets/BaseWidget";
import { DataTree } from "entities/DataTree/dataTreeFactory";
import { EXECUTION_PARAM_KEY } from "./ActionConstants";
// Always add a validator function in ./Validators for these 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 JAVSCRIPT_KEYWORDS = {
export const JAVASCRIPT_KEYWORDS = {
true: "true",
await: "await",
break: "break",
@ -87,3 +88,10 @@ export const JAVSCRIPT_KEYWORDS = {
with: "with",
yield: "yield",
};
export const DATA_TREE_KEYWORDS = {
actionPaths: "actionPaths",
appsmith: "appsmith",
pageList: "pageList",
[EXECUTION_PARAM_KEY]: EXECUTION_PARAM_KEY,
};

View File

@ -9,6 +9,7 @@ import {
EventType,
ExecuteActionPayload,
ExecuteActionPayloadEvent,
EXECUTION_PARAM_KEY,
PageAction,
} from "constants/ActionConstants";
import * as log from "loglevel";
@ -63,7 +64,7 @@ import {
import { AppState } from "reducers";
import { mapToPropList } from "utils/AppsmithUtils";
import { validateResponse } from "sagas/ErrorSagas";
import { ToastType, TypeOptions } from "react-toastify";
import { TypeOptions } from "react-toastify";
import { PLUGIN_TYPE_API } from "constants/ApiEditorConstants";
import { DEFAULT_EXECUTE_ACTION_TIMEOUT_MS } from "constants/ApiConstants";
import { updateAppStore } from "actions/pageActions";
@ -71,7 +72,7 @@ import { getAppStoreName } from "constants/AppConstants";
import downloadjs from "downloadjs";
import { getType, Types } from "utils/TypeHelpers";
import { Toaster } from "components/ads/Toast";
import { Variant, ToastVariant } from "components/ads/common";
import { Variant } from "components/ads/common";
import PerformanceTracker, {
PerformanceTransactionName,
} from "utils/PerformanceTracker";
@ -239,62 +240,35 @@ const isErrorResponse = (response: ActionApiResponse) => {
return !response.data.isExecutionSuccess;
};
export function* evaluateDynamicBoundValueSaga(path: string): any {
return yield call(evaluateSingleValue, `{{${path}}}`);
export function* evaluateDynamicBoundValueSaga(
valueToEvaluate: string,
params?: Record<string, unknown>,
): any {
return yield call(evaluateSingleValue, `{{${valueToEvaluate}}}`, params);
}
const EXECUTION_PARAM_PATH = "this.params";
const getExecutionParamPath = (key: string) => `${EXECUTION_PARAM_PATH}.${key}`;
const EXECUTION_PARAM_REFERENCE_REGEX = /this.params/g;
export function* getActionParams(
bindings: string[] | undefined,
executionParams?: Record<string, any>,
) {
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(
getExecutionParamPath,
);
const paramSearchRegex = new RegExp(executionParamsPathList.join("|"), "g");
// Bindings with references to execution params
const executionBindings = bindings.filter(binding =>
paramSearchRegex.test(binding),
);
const bindingsForExecutionParams = bindings.map(binding =>
binding.replace(EXECUTION_PARAM_REFERENCE_REGEX, EXECUTION_PARAM_KEY),
);
// 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(
dataTreeBindings.map((binding: string) => {
return call(evaluateDynamicBoundValueSaga, binding);
bindingsForExecutionParams.map((binding: string) => {
return call(
evaluateDynamicBoundValueSaga,
binding,
evaluatedExecutionParams,
);
}),
);
// convert to object and transform non string values

View File

@ -35,6 +35,7 @@ import PerformanceTracker, {
import { Variant } from "components/ads/common";
import { Toaster } from "components/ads/Toast";
import * as Sentry from "@sentry/react";
import { EXECUTION_PARAM_KEY } from "../constants/ActionConstants";
let evaluationWorker: Worker;
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) {
const dataTree = yield select(getDataTree);
dataTree[EXECUTION_PARAM_KEY] = executionParams;
evaluationWorker.postMessage({
action: EVAL_WORKER_ACTIONS.EVAL_SINGLE,
dataTree,

View File

@ -7,8 +7,6 @@ import { Action } from "entities/Action";
import moment from "moment-timezone";
import { WidgetProps } from "../widgets/BaseWidget";
type StringTuple = [string, string];
export const removeBindingsFromActionObject = (obj: Action) => {
const string = JSON.stringify(obj);
const withBindings = string.replace(DATA_BIND_REGEX_GLOBAL, "{{ }}");

View File

@ -1,5 +1,9 @@
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 = (
columnWidth: 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,
* 2. Queries name
* 3. Javascript reserved names
@ -180,9 +184,10 @@ export const isNameValid = (
name: string,
invalidNames: Record<string, any>,
) => {
if (name in JAVSCRIPT_KEYWORDS || name in invalidNames) {
return false;
}
return true;
return !(
name in JAVASCRIPT_KEYWORDS ||
name in DATA_TREE_KEYWORDS ||
name in GLOBAL_FUNCTIONS ||
name in invalidNames
);
};