Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix: item parameter_list dynamic context #2325

Merged
merged 7 commits into from
Jun 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}

Expand Down Expand Up @@ -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 } = {};
Expand Down
14 changes: 9 additions & 5 deletions src/app/shared/components/template/processors/item.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
// 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";
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);
Expand Down Expand Up @@ -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;
});
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down
Loading