diff --git a/packages/shared/src/models/appStringEvaluator/appStringEvaluator.spec.ts b/packages/shared/src/models/appStringEvaluator/appStringEvaluator.spec.ts index 37e97c0df8..0d543d89d9 100644 --- a/packages/shared/src/models/appStringEvaluator/appStringEvaluator.spec.ts +++ b/packages/shared/src/models/appStringEvaluator/appStringEvaluator.spec.ts @@ -16,4 +16,12 @@ describe("App String Evaluator", () => { "Hello Ada Lovelace" ); }); + + // TODO - doesn't work, should address in follow-up + // replaces string part first without quotation, i.e. + // `Ada === 'Ada'` and returns just as a string (can't evaluate) + it("@row.first_name === 'Ada'", () => { + pending("TODO - fix implementation to support"); + expect(evaluator.evaluate("@row.first_name === 'Ada'")).toEqual(true); + }); }); diff --git a/src/app/shared/components/template/components/data-items/data-items.component.ts b/src/app/shared/components/template/components/data-items/data-items.component.ts index 5311a83580..2f3dcb5f2b 100644 --- a/src/app/shared/components/template/components/data-items/data-items.component.ts +++ b/src/app/shared/components/template/components/data-items/data-items.component.ts @@ -43,7 +43,7 @@ export class TmplDataItemsComponent extends TemplateBaseComponent implements OnD @Input() set row(row: FlowTypes.TemplateRow) { this._row = row; this.dataListName = this.hackGetRawDataListName(row); - this.parameterList = this.hackGetRawParameterList(row); + this.parameterList = row.parameter_list; this.subscribeToData(); } @@ -183,15 +183,6 @@ export class TmplDataItemsComponent extends TemplateBaseComponent implements OnD return row._dynamicFields?.value?.[0]?.fieldName; } - /** Copied from template-row service */ - private hackGetRawParameterList(row: FlowTypes.TemplateRow) { - const list = row.parameter_list; - const unparsedFilter = row._dynamicFields?.parameter_list?.filter?.[0].fullExpression; - if (list && unparsedFilter) { - list.filter = unparsedFilter; - } - return list; - } /** Copied from template-row service */ private async parseDataList(dataList: { [id: string]: any }) { const parsed: { [id: string]: any } = {}; diff --git a/src/app/shared/components/template/processors/item.ts b/src/app/shared/components/template/processors/item.ts index 830cc35bba..b7805d3831 100644 --- a/src/app/shared/components/template/processors/item.ts +++ b/src/app/shared/components/template/processors/item.ts @@ -1,6 +1,6 @@ // NOTE - importing from 'shared' will fail as contains non-browser packages and // name conflicts with local 'shared' folder. Import full path from packages instead -import { AppStringEvaluator } from "packages/shared/src/models/appStringEvaluator/appStringEvaluator"; +import { JSEvaluator } from "packages/shared/src/models/jsEvaluator/jsEvaluator"; import { TemplatedData } from "packages/shared/src/models/templatedData/templatedData"; import { shuffleArray } from "src/app/shared/utils"; @@ -8,7 +8,10 @@ import { FlowTypes } from "../models"; import { objectToArray } from "../utils"; export class ItemProcessor { - constructor(private dataList: any, private parameterList?: any) {} + constructor( + private dataList: any, + private parameterList?: any + ) {} public process(templateRows: any) { const data = objectToArray(this.dataList); @@ -131,9 +134,10 @@ class ItemDataPipe { filter: (items: any[] = [], expression: string) => { if (!expression) return; return items.filter((item) => { - const evaluator = new AppStringEvaluator(); - evaluator.setExecutionContext({ item }); - const evaluated = evaluator.evaluate(expression); + // NOTE - expects all non-item condition to be evaluated + // e.g. `@item.field > @local.some_value` already be evaluated to `this.item.field > "local value"` + const evaluator = new JSEvaluator(); + const evaluated = evaluator.evaluate(expression, { item }); return evaluated; }); }, diff --git a/src/app/shared/components/template/services/instance/template-row.service.ts b/src/app/shared/components/template/services/instance/template-row.service.ts index 5eeef086a2..3d953a33f5 100644 --- a/src/app/shared/components/template/services/instance/template-row.service.ts +++ b/src/app/shared/components/template/services/instance/template-row.service.ts @@ -288,9 +288,9 @@ export class TemplateRowService extends SyncServiceBase { if (type === "items") { // extract raw parameter list const itemDataList: { [id: string]: any } = row.value; - const parameterList = this.hackUnparseItemParameterList(row); const parsedItemDataList = await this.parseDataList(itemDataList); - const { itemRows } = new ItemProcessor(parsedItemDataList, parameterList).process(row.rows); + const { parameter_list, rows } = row; + const { itemRows } = new ItemProcessor(parsedItemDataList, parameter_list).process(rows); const parsedItemRows = await this.processRows(itemRows, isNestedTemplate, row.name); return parsedItemRows; } @@ -372,19 +372,6 @@ export class TemplateRowService extends SyncServiceBase { return parsed; } - /** - * When parsing item parameter lists filter references to @item will be replaced before processing - * Hacky workaround to replace back with unparsed value - */ - private hackUnparseItemParameterList(row: FlowTypes.TemplateRow) { - const list = row.parameter_list; - const unparsedFilter = row._dynamicFields?.parameter_list?.filter?.[0].fullExpression; - if (list && unparsedFilter) { - list.filter = unparsedFilter; - } - return list; - } - /** recursively filter out any rows that have a false condition */ private filterConditionalTemplateRows(rows: FlowTypes.TemplateRow[] = []) { return rows diff --git a/src/app/shared/components/template/services/template-variables.service.ts b/src/app/shared/components/template/services/template-variables.service.ts index cfd818a7ec..0dfac7c037 100644 --- a/src/app/shared/components/template/services/template-variables.service.ts +++ b/src/app/shared/components/template/services/template-variables.service.ts @@ -192,9 +192,11 @@ export class TemplateVariablesService extends AsyncServiceBase { return evaluator.fullExpression.replace(/`/gi, ""); } - // Do not evaluate if the appropriate context is not available + // If the appropriate context is not available, do not evaluate that particular evaluator + // NOTE - this will mean compound expressions will need to be evaluated later + // E.g. @item.some_field === @local.other_field -> this.item.id === "local value", which needs further evaluation if (type === "item" && !context.itemContext) { - return evaluator.fullExpression; + return this.hackProcessItemEvaluators(evaluators, context); } // process the main lookup, e.g. @local.some_val, @campaign.some_val @@ -233,6 +235,41 @@ export class TemplateVariablesService extends AsyncServiceBase { return evaluated; } + /** + * If an item contains dynamic parameter list then the template-variable service simply skips evaluation + * as it is unaware of the item context. + * e.g `filter: @item.id === @local.some_field` + * + * Template row evaluators do not have the item context and so only evaluate the local context, + * and the partial evaluation leaves `@item.id=== some_value` (without string quotation) + * + * This method reprocesses the original parameter list, retaining item references and quoting string values + * e.g. `filter: this.item.id === "local value"` + */ + private async hackProcessItemEvaluators( + evaluators: FlowTypes.TemplateRowDynamicEvaluator[], + context: IVariableContext + ) { + let expression = evaluators[0].fullExpression; + for (const evaluator of evaluators) { + let parsedValue = evaluator.matchedExpression; + // replace @item.some_field with this.some_field in preparation for item JS processor + if (evaluator.type === "item") { + parsedValue = parsedValue.replace("@", "this."); + } + // replace all other dynamic references in the same way as the template variable service + if (evaluator.type !== "item") { + const evaluated = await this.processDynamicEvaluator(evaluator, context); + parsedValue = evaluated.parsedValue; + if (typeof parsedValue === "string") { + parsedValue = `"${parsedValue}"`; + } + } + expression = expression.replace(evaluator.matchedExpression, parsedValue); + } + return expression; + } + /** * Take an expression and evaulate within a custom JavaScript context * This is done in 3 ways: