diff --git a/package.json b/package.json index 79ded404f8..19d6396363 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "frontend", - "version": "0.16.23", + "version": "0.16.24", "author": "IDEMS International", "license": "See LICENSE", "homepage": "https://idems.international/", diff --git a/packages/scripts/src/commands/app-data/convert/processors/flowParser/parsers/data_pipe.parser.spec.ts b/packages/scripts/src/commands/app-data/convert/processors/flowParser/parsers/data_pipe.parser.spec.ts index caeb6a19f6..6c70fd4712 100644 --- a/packages/scripts/src/commands/app-data/convert/processors/flowParser/parsers/data_pipe.parser.spec.ts +++ b/packages/scripts/src/commands/app-data/convert/processors/flowParser/parsers/data_pipe.parser.spec.ts @@ -96,4 +96,33 @@ describe("data_pipe Parser", () => { }); expect(deferred).toEqual(["data_pipe.test_pipe_defer"]); }); + + // QA - https://github.com/IDEMSInternational/parenting-app-ui/issues/2184 + // NOTE - test case more explicitly handled by jsEvaluator.spec + it("Supports text with line break characters", async () => { + const parser = new DataPipeParser({ + processedFlowHashmap: { + data_list: { + test_data_list: [{ id: 1, text: "normal" }, { id: 2, text: "line\nbreak" }, { id: 3 }], + }, + }, + } as any); + const ops: IDataPipeOperation[] = [ + { + input_source: "test_data_list", + operation: "filter", + args_list: "id < 3" as any, // will be parsed during process + output_target: "test_output", + }, + ]; + const output = parser.run({ + flow_name: "test_line_breaks", + flow_type: "data_pipe", + rows: ops, + }); + expect(output._generated.data_list.test_output.rows).toEqual([ + { id: 1, text: "normal" }, + { id: 2, text: "line\nbreak" }, + ]); + }); }); diff --git a/packages/scripts/src/commands/version.ts b/packages/scripts/src/commands/version.ts index 9b374fc732..4920133bde 100644 --- a/packages/scripts/src/commands/version.ts +++ b/packages/scripts/src/commands/version.ts @@ -1,7 +1,7 @@ import * as fs from "fs-extra"; import { Command } from "commander"; import inquirer from "inquirer"; -import { APP_BUILD_GRADLE_PATH, MAIN_PACKAGE_PATH } from "../paths"; +import { APP_BUILD_GRADLE_PATH, MAIN_PACKAGE_PATH, ANDROID_BUILD_ACTION_PATH } from "../paths"; /*************************************************************************************** * CLI @@ -26,6 +26,7 @@ async function version(options: IProgramOptions) { const newVersion = await promptNewVersion(oldVersion); updatePackageJson(newVersion); updateGradleBuild(newVersion); + updateAndroidBuildAction(newVersion); } function updateGradleBuild(newVersionName: string) { @@ -47,6 +48,20 @@ async function updatePackageJson(newVersion: string) { fs.writeJSONSync(MAIN_PACKAGE_PATH, packageJson, { spaces: 2 }); } +function updateAndroidBuildAction(newVersionName) { + let androidBuildAction = fs.readFileSync(ANDROID_BUILD_ACTION_PATH, { encoding: "utf-8" }); + const newVersionCode = _generateVersionCode(newVersionName); + androidBuildAction = androidBuildAction.replace( + /[0-9]+\/\$VERSION_CODE\//g, + `${newVersionCode}/$VERSION_CODE/` + ); + androidBuildAction = androidBuildAction.replace( + /[0-9]+\.[0-9]+\.[0-9]+\/\$VERSION\//g, + `${newVersionName}/$VERSION/` + ); + fs.writeFileSync(ANDROID_BUILD_ACTION_PATH, androidBuildAction, { encoding: "utf-8" }); +} + async function promptNewVersion(currentVersion: string) { const { version } = await inquirer.prompt([ { diff --git a/packages/shared/src/models/jsEvaluator/jsEvaluator.spec.ts b/packages/shared/src/models/jsEvaluator/jsEvaluator.spec.ts index 7e95d8fad8..1ebe162ae0 100644 --- a/packages/shared/src/models/jsEvaluator/jsEvaluator.spec.ts +++ b/packages/shared/src/models/jsEvaluator/jsEvaluator.spec.ts @@ -1,16 +1,21 @@ import { JSEvaluator } from "./jsEvaluator"; +const constants = { + a: 1, + b: 2, + nestedConstant: { array: [1] }, +}; +const functions = { + isEven: (n) => n % 2 === 0, +}; + describe("JS Evaluator", () => { - const constants = { - a: 1, - b: 2, - nestedConstant: { array: [1] }, - }; - const functions = { - isEven: (n) => n % 2 === 0, - }; - const evaluator = new JSEvaluator(); - evaluator.setGlobalContext({ constants, functions }); + let evaluator: JSEvaluator; + beforeEach(() => { + evaluator = new JSEvaluator(); + evaluator.setGlobalContext({ constants, functions }); + }); + it("expression: Math.min(5,7)", () => { expect(evaluator.evaluate("Math.min(5,7)")).toEqual(5); }); @@ -35,4 +40,18 @@ describe("JS Evaluator", () => { evaluator.setGlobalContext({ constants: { ...invalidConstants, ...constants } }); expect(() => evaluator.evaluate("Math.min(a,b)")).toThrowError("Unexpected token 'default'"); }); + it("handles escape characters", () => { + // Case 1 - evaluation string with linebreak + expect(evaluator.evaluate("'Hello\n'+this.name", { name: "Ada" })).toEqual("Hello\nAda"); + // Case 2 - this context with linebreak + expect(evaluator.evaluate("'Hello'+this.name", { name: "\nAda" })).toEqual("Hello\nAda"); + // Case 3 - global constant with linebreak + evaluator.setGlobalContext({ constants: { name: "\nAda" } }); + expect(evaluator.evaluate("'Hello'+name", { name: "\nAda" })).toEqual("Hello\nAda"); + }); + it("handles special characters", () => { + // Case 1 - single quotation (used to wrap string values in evaluator) + evaluator.setGlobalContext({ constants: { name: "Ada'" } }); + expect(evaluator.evaluate("'Hello '+name")).toEqual("Hello Ada'"); + }); }); diff --git a/packages/shared/src/models/jsEvaluator/jsEvaluator.ts b/packages/shared/src/models/jsEvaluator/jsEvaluator.ts index a44d06a595..083213e93f 100644 --- a/packages/shared/src/models/jsEvaluator/jsEvaluator.ts +++ b/packages/shared/src/models/jsEvaluator/jsEvaluator.ts @@ -63,8 +63,9 @@ export class JSEvaluator { */ evaluate(expression: string, executionContext = {}) { const funcString = `${this.evaluationContextBase} (${expression});`; + const cleanedFuncString = this.cleanFunctionString(funcString); try { - const func = new Function(funcString); + const func = new Function(cleanedFuncString); const evaluated = func.apply(executionContext); return evaluated; } catch (error) { @@ -85,10 +86,19 @@ export class JSEvaluator { private parseContextValue(value: any) { if (value) { if (typeof value === "object") value = JSON.stringify(value); - if (typeof value === "string") return `'${value}'`; + if (typeof value === "string") { + // when returning a string value escape single quote which would otherwise conflict with return + const escapedValue = value.replace(/'/g, "\\'"); + return `'${escapedValue}'`; + } } return value; } + + private cleanFunctionString(str: string) { + // Linebreak characters will break JS evaluator so add additional escape + return str.replace(/(?:\r)/g, "\\r").replace(/(?:\n)/g, "\\n"); + } } /** Generic object containing list of functions */ diff --git a/packages/shared/src/paths.ts b/packages/shared/src/paths.ts index f376c3fbfc..ee24521188 100644 --- a/packages/shared/src/paths.ts +++ b/packages/shared/src/paths.ts @@ -22,3 +22,7 @@ export const RESOURCE_FOLDER_PATH = path.join(ROOT_DIR, "resources"); export const ANDROID_RES_PATH = path.join(ROOT_DIR, "android/app/src/main/res"); export const APP_BUILD_GRADLE_PATH = path.join(ROOT_DIR, "android/app/build.gradle"); +export const ANDROID_BUILD_ACTION_PATH = path.join( + ROOT_DIR, + ".github/workflows/reusable-android-build.yml" +); diff --git a/packages/workflows/src/deployment.workflows.ts b/packages/workflows/src/deployment.workflows.ts index 3575512a3c..b301ec66ae 100644 --- a/packages/workflows/src/deployment.workflows.ts +++ b/packages/workflows/src/deployment.workflows.ts @@ -77,6 +77,12 @@ const workflows: IDeploymentWorkflows = { ], }, set: { + options: [ + { + flags: "--skip-refresh", + description: "Skip remote content refresh", + }, + ], label: "Set active deployment", steps: [ { @@ -94,6 +100,7 @@ const workflows: IDeploymentWorkflows = { }, { name: "refresh_remote_content", + condition: async ({ options }) => !options.skipRefresh, function: async ({ tasks, config }) => { if (config.git?.content_repo) { await tasks.git().refreshRemoteRepo(); diff --git a/packages/workflows/src/sync.workflows.ts b/packages/workflows/src/sync.workflows.ts index 2864ec7503..88cc2003c9 100644 --- a/packages/workflows/src/sync.workflows.ts +++ b/packages/workflows/src/sync.workflows.ts @@ -204,7 +204,7 @@ const workflows: IDeploymentWorkflows = { { name: "authorize", function: async ({ tasks }) => { - tasks.gdrive.authorize(); + await tasks.gdrive.authorize(); process.exit(0); }, }, diff --git a/src/assets/icon/shared/start.svg b/src/assets/icon/shared/start.svg deleted file mode 100644 index 1cb15fd0ef..0000000000 --- a/src/assets/icon/shared/start.svg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:08cc133a3af50b2af60ddfdc98a1b6f24b511b118e1dae867d0ff97ea48bf318 -size 204 diff --git a/src/assets/images/github.png b/src/assets/images/github.png deleted file mode 100644 index 42c75e7cf2..0000000000 --- a/src/assets/images/github.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:a4c27448b5f97253b9a035a45a38d2266bffd94f790a843b2d62b6bdf05b5324 -size 21835 diff --git a/src/assets/images/splash-screen/0.svg b/src/assets/images/splash-screen/0.svg deleted file mode 100644 index a334b9c6eb..0000000000 --- a/src/assets/images/splash-screen/0.svg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:ed11a248ad212a679c2b216c7c3c7a562ea0e3a7af998e7ff15624969f99fae6 -size 42993 diff --git a/src/assets/images/splash-screen/1.svg b/src/assets/images/splash-screen/1.svg deleted file mode 100644 index cb9daad9e0..0000000000 --- a/src/assets/images/splash-screen/1.svg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:2cb84ef2c96a0f5244fad4c7c4e18c78998c7ce7ecfa5639b04894cacf9e0503 -size 10139 diff --git a/src/assets/images/splash-screen/2.svg b/src/assets/images/splash-screen/2.svg deleted file mode 100644 index 3b3357b48d..0000000000 --- a/src/assets/images/splash-screen/2.svg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:1431fc4d8bbeb47fc8cefaa9f47d76dcbc8021c17e26ee97d0ba7a2f01d7d545 -size 14706 diff --git a/src/assets/images/star.svg b/src/assets/images/star.svg deleted file mode 100644 index bb890688ae..0000000000 --- a/src/assets/images/star.svg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:43bdd998d0dda1382061cd554ff83ade2594c0a4dbbce3d4ecd1ab8b08cd6ea1 -size 781 diff --git a/src/assets/images/sync.svg b/src/assets/images/sync.svg deleted file mode 100644 index 7d5cbbb264..0000000000 --- a/src/assets/images/sync.svg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:3bc4204dea5318b556e65a46997ba6292f8a63cdc80e082b72d6f238b622b314 -size 1879 diff --git a/src/assets/logos/PLH.svg b/src/assets/logos/PLH.svg deleted file mode 100644 index a718745fdc..0000000000 --- a/src/assets/logos/PLH.svg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:bfcd849013d50db9a63624b488d26a4c3b10c43983acefb2facf3859cb891bb6 -size 102991 diff --git a/src/assets/logos/Unicef.svg b/src/assets/logos/Unicef.svg deleted file mode 100644 index 2b542ca6a1..0000000000 --- a/src/assets/logos/Unicef.svg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:6999218a81ae0b20cb32664b8020ca554b1f37fd0782ed33c9a648508decd1ed -size 65563 diff --git a/src/assets/logos/WHO.svg b/src/assets/logos/WHO.svg deleted file mode 100644 index 4935007b6f..0000000000 --- a/src/assets/logos/WHO.svg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:472d4096e409707240abd1541ec98323b8dc491ab5d3ce1658358c5f32952f2b -size 75549 diff --git a/src/assets/shapes.svg b/src/assets/shapes.svg deleted file mode 100644 index 39b6b815be..0000000000 --- a/src/assets/shapes.svg +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:cf1b68281716f452dd620f81fd31d98bfef7dd521b8098433710bda7a32918f2 -size 1176