From b6c710cfa238acfafa9d7f787411075e28e70e7c Mon Sep 17 00:00:00 2001 From: Hetu Nandu Date: Fri, 17 Jul 2020 18:25:34 +0530 Subject: [PATCH] Add support for run time params in action execution (#98) Adds a third parameter to the Action.run function that can be referenced inside an action config Usage `{{ Api1.run(...,...,{key: value}) }}` inside property pane `{{this.params.key}}` inside action pane * You can reference data tree properties in the params values: `{{ Api1.run(..., ..., { key: "Input1.text.toUpperCase()" })` * Bindings can have both params and data tree values referenced. * Param values can be javascript functions `body: {{ this.params.list.map(i => Input1.text + i ) }}` --- .../src/entities/DataTree/dataTreeFactory.ts | 11 ++- app/client/src/sagas/ActionExecutionSagas.ts | 74 ++++++++++++++++--- 2 files changed, 72 insertions(+), 13 deletions(-) diff --git a/app/client/src/entities/DataTree/dataTreeFactory.ts b/app/client/src/entities/DataTree/dataTreeFactory.ts index 901d8b2f5d..2c7d516e5e 100644 --- a/app/client/src/entities/DataTree/dataTreeFactory.ts +++ b/app/client/src/entities/DataTree/dataTreeFactory.ts @@ -28,13 +28,14 @@ export type RunActionPayload = { actionId: string; onSuccess: string; onError: string; + params: Record; }; export interface DataTreeAction extends Omit { data: ActionResponse["body"]; actionId: string; config: Partial; - run: ActionDispatcher | {}; + run: ActionDispatcher | {}; dynamicBindingPathList: Property[]; ENTITY_TYPE: ENTITY_TYPE.ACTION; } @@ -103,13 +104,19 @@ export class DataTreeFactory { dynamicBindingPathList, data: a.data ? a.data.body : {}, run: withFunctions - ? function(this: DataTreeAction, onSuccess: string, onError: string) { + ? function( + this: DataTreeAction, + onSuccess: string, + onError: string, + params = "", + ) { return { type: "RUN_ACTION", payload: { actionId: this.actionId, onSuccess: onSuccess ? `{{${onSuccess.toString()}}}` : "", onError: onError ? `{{${onError.toString()}}}` : "", + params, }, }; } diff --git a/app/client/src/sagas/ActionExecutionSagas.ts b/app/client/src/sagas/ActionExecutionSagas.ts index 76d9b734d2..2cfea4b01f 100644 --- a/app/client/src/sagas/ActionExecutionSagas.ts +++ b/app/client/src/sagas/ActionExecutionSagas.ts @@ -143,20 +143,68 @@ export function* evaluateDynamicBoundValueSaga(path: string): any { return dynamicResult.result; } -export function* getActionParams(jsonPathKeys: string[] | undefined) { - if (_.isNil(jsonPathKeys)) return []; +const EXECUTION_PARAM_PATH = "this.params"; +const getExecutionParamPath = (key: string) => `${EXECUTION_PARAM_PATH}.${key}`; + +export function* getActionParams( + bindings: string[] | undefined, + executionParams?: Record, +) { + if (_.isNil(bindings)) return []; + let dataTreeBindings = bindings; + + 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), + ); + + // 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( - jsonPathKeys.map((jsonPath: string) => { - return call(evaluateDynamicBoundValueSaga, jsonPath); + dataTreeBindings.map((binding: string) => { + return call(evaluateDynamicBoundValueSaga, binding); }), ); - const dynamicBindings: Record = {}; - jsonPathKeys.forEach((key, i) => { + // convert to object and transform non string values + const actionParams: Record = {}; + bindings.forEach((key, i) => { let value = values[i]; if (typeof value === "object") value = JSON.stringify(value); - dynamicBindings[key] = value; + actionParams[key] = value; }); - return mapToPropList(dynamicBindings); + return mapToPropList(actionParams); } export function extractBindingsFromAction(action: Action) { @@ -175,11 +223,15 @@ export function* executeActionSaga( apiAction: RunActionPayload, event: ExecuteActionPayloadEvent, ) { - const { actionId, onSuccess, onError } = apiAction; + const { actionId, onSuccess, onError, params } = apiAction; try { yield put(executeApiActionRequest({ id: apiAction.actionId })); const api: RestAction = yield select(getAction, actionId); - const params: Property[] = yield call(getActionParams, api.jsonPathKeys); + const actionParams: Property[] = yield call( + getActionParams, + api.jsonPathKeys, + params, + ); const pagination = event.type === EventType.ON_NEXT_PAGE ? "NEXT" @@ -188,7 +240,7 @@ export function* executeActionSaga( : undefined; const executeActionRequest: ExecuteActionRequest = { action: { id: actionId }, - params, + params: actionParams, paginationField: pagination, }; const timeout = yield select(getActionTimeout, actionId);