Skip to content

Commit

Permalink
Merge pull request #2325 from IDEMSInternational/fix/item-local-context
Browse files Browse the repository at this point in the history
Fix: item parameter_list dynamic context
  • Loading branch information
esmeetewinkel authored Jun 10, 2024
2 parents 973798d + 984d802 commit ecfe6bd
Show file tree
Hide file tree
Showing 5 changed files with 59 additions and 32 deletions.
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

0 comments on commit ecfe6bd

Please sign in to comment.