Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
…pp-builder into style/english-font
  • Loading branch information
FaithDaka committed Oct 22, 2024
2 parents 1f6c3da + 9a332d7 commit 5b2391e
Show file tree
Hide file tree
Showing 11 changed files with 262 additions and 106 deletions.
5 changes: 5 additions & 0 deletions .github/workflows/reusable-app-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ on:
description: Name of branch to build (defaults to event trigger sha)
type: string
default: ""
lfs:
description: Enable git lfs to include download of all binary assets (user-facing deployments)
type: boolean
default: true

outputs:
GIT_SHA:
Expand Down Expand Up @@ -75,6 +79,7 @@ jobs:
ref: ${{inputs.branch}}
path: ".idems_app/deployments/${{env.DEPLOYMENT_NAME}}"
fetch-depth: 0
lfs: ${{inputs.lfs}}

- name: Populate Encryption key
if: env.DEPLOYMENT_PRIVATE_KEY != ''
Expand Down
2 changes: 1 addition & 1 deletion packages/shared/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ import { TemplatedData } from "packages/shared"
In addition, to avoid compiler errors thrown by non-browser shared methods, explicit paths should be included to import only the supported files as required

```ts
import { AppStringEvaluator } from "packages/shared/src/models/appStringEvaluator/appStringEvaluator";
import { AppDataEvaluator } from "packages/shared/src/models/appDataEvaluator/appDataEvaluator";
import { TemplatedData } from "packages/shared/src/models/templatedData/templatedData";
```

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { AppDataEvaluator } from "./appDataEvaluator";

describe("App String Evaluator - String Replacement", () => {
const evaluator = new AppDataEvaluator();

evaluator.setExecutionContext({
row: { first_name: "Ada", last_name: "Lovelace", number_1: 1, number_2: 2 },
});

it("Hello @row.first_name @row.last_name)", () => {
expect(evaluator.evaluate("Hello @row.first_name @row.last_name")).toEqual(
"Hello Ada Lovelace"
);
});
it("{nested:[@row.first_name]}", () => {
expect(evaluator.evaluate({ nested: ["@row.first_name"] })).toEqual({ nested: ["Ada"] });
});
});

describe("App String Evaluator - JS Evaluate", () => {
const evaluator = new AppDataEvaluator();

evaluator.setExecutionContext({
row: { first_name: "Ada", last_name: "Lovelace", number_1: 1, number_2: 2 },
});

it("@row.number_1 > @row.number_2", () => {
expect(evaluator.evaluate("@row.number_1 > @row.number_2")).toEqual(false);
});

it("@row.first_name === 'Ada'", () => {
expect(evaluator.evaluate("@row.first_name === 'Ada'")).toEqual(true);
});

it("@row.first_name.length", () => {
expect(evaluator.evaluate("@row.first_name.length")).toEqual(3);
});
it("@row.first_name==='Ada' ? 'It is Ada' : 'It is not Ada'", () => {
const res = evaluator.evaluate("@row.first_name==='Ada' ? 'It is Ada' : 'It is not Ada'");
expect(res).toEqual("It is Ada");
});
});
109 changes: 109 additions & 0 deletions packages/shared/src/models/appDataEvaluator/appDataEvaluator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
import { JSEvaluator } from "../jsEvaluator/jsEvaluator";
import { TemplatedData } from "../templatedData/templatedData";
import { isObjectLiteral } from "../../utils/object-utils";
import { addJSDelimeters } from "../../utils/delimiters";

/** Variable context is stored in namespaces, e.g. `{item:{key:'value'},field:{key:'value'}}` */
type IContext = { [nameSpace: string]: { [field: string]: any } };

/**
* Utility class to allow evaluation of app data that contain a mix of context expressions,
* JavaScript and static strings. It combines both TemplatedData and jsEvaluator models
*
* @example
* ```
* const evaluator = new AppDataEvaluator()
*
* evaluator.setExecutionContext({
* row:{
* number_1:1,
* number_2:2
* }
* })
*
* const expression = `@row.number_1 > @row.number_2 ? '1 is greater' : '2 is greater'`
* evaluator.evaluate(expression1)
* // 2 is greater
* ```
* */
export class AppDataEvaluator {
private templatedData = new TemplatedData();
private jsEvaluator = new JSEvaluator();
constructor(private context = {}) {
this.setExecutionContext(context);
}
public setExecutionContext(context: IContext) {
this.context = context;
this.templatedData.updateContext(context);
}
public updateExecutionContext(update: IContext) {
this.setExecutionContext({ ...this.context, update });
}

/**
* Evaluate app expression in two stages
* 1) Replace any instances of context variables with values
* e.g. `@row.number_1 > @row.number_2 ? '1 is greater' : '2 is greater'
* -> `1 > 2 ? '1 is greater' : '2 is greater'
*
* 2) Attempt evaluation in a JS context
* -> '2 is greater'
*
* @param data Input data to evaluated. This can be any data type, including
* nested objects or arrays. All string entries within will be evaulated
*/
public evaluate(data: any) {
if (typeof data === "string") {
return this.evaluateExpression(data);
}
if (Array.isArray(data)) {
const evaluated = [];
for (const el of data) {
evaluated.push(this.evaluate(el));
}
return evaluated;
}
if (isObjectLiteral(data)) {
// create a new object to avoid changing initial data
const evaluated = {};
for (const [key, value] of Object.entries(data)) {
evaluated[key] = this.evaluate(value);
}
return evaluated;
}
return data;
}

/**
* Evaluate app string expression in two stages
* 1) Attempt to parse as a JavaScript expression
* Any variables referenced with `@` syntax will be converted to `this.`
* and evaluated with available context variables
* @example
* ```js
* evaluateExpression(`@row.number_1 > @row.number_2 ? 'bigger' : 'smaller'`)
* // intermediate interpretation
* `this.row.number_1 > this.row.number_2 ? 'bigger' : 'smaller'`
* // output
* "smaller"
* ```
*
* 2) Perform string replacement of individual variables
* @example
* ```js
* evaluateExpression(`Hello @row.first_name`)
* ```
* // output
* "Hello Ada"
*/
private evaluateExpression(expression: string) {
try {
const jsDelimited = addJSDelimeters(expression, Object.keys(this.context));
const evaluated = this.jsEvaluator.evaluate(jsDelimited, this.context);
return evaluated;
} catch (error) {
const parsed = this.templatedData.parse(expression);
return parsed;
}
}
}

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { DataFrame, toJSON } from "danfojs";
import { AppStringEvaluator } from "../../appStringEvaluator/appStringEvaluator";
import { AppDataEvaluator } from "../../appDataEvaluator/appDataEvaluator";
import BaseOperator from "./base";
import { parseStringValue } from "../../../utils";

Expand All @@ -24,7 +24,7 @@ class AppendColumnsOperator extends BaseOperator {
return arg.key && arg.valueExpression !== undefined;
}
apply() {
const evaluator = new AppStringEvaluator();
const evaluator = new AppDataEvaluator();
for (const { key, valueExpression } of this.args_list) {
const rows = toJSON(this.df) as any[];
const appendValues = rows.map((row) => {
Expand Down
2 changes: 1 addition & 1 deletion packages/shared/src/models/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export * from "./appStringEvaluator/appStringEvaluator";
export * from "./appDataEvaluator/appDataEvaluator";
export * from "./templatedData/templatedData";
export * from "./jsEvaluator/jsEvaluator";

Expand Down
Loading

0 comments on commit 5b2391e

Please sign in to comment.