From fb04895de4b12e6d6a86f769b6c94a70a8e2aa9f Mon Sep 17 00:00:00 2001 From: "@dhis2-bot" Date: Wed, 17 Jun 2026 08:47:37 +0000 Subject: [PATCH 01/16] chore(release): cut 106.5.0 [skip release] # [106.5.0](https://github.com/dhis2/capture-app/compare/v106.4.5...v106.5.0) (2026-06-17) ### Features * [DHIS2-16610] Add support for configurable terminology labels ([#4609](https://github.com/dhis2/capture-app/issues/4609)) ([b23386a](https://github.com/dhis2/capture-app/commit/b23386ae3303e26f448c7bff93eeeb0060191585)) --- CHANGELOG.md | 7 +++++++ package.json | 4 ++-- packages/rules-engine/package.json | 2 +- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b72b57ae11..25d42067e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +# [106.5.0](https://github.com/dhis2/capture-app/compare/v106.4.5...v106.5.0) (2026-06-17) + + +### Features + +* [DHIS2-16610] Add support for configurable terminology labels ([#4609](https://github.com/dhis2/capture-app/issues/4609)) ([b23386a](https://github.com/dhis2/capture-app/commit/b23386ae3303e26f448c7bff93eeeb0060191585)) + ## [106.4.5](https://github.com/dhis2/capture-app/compare/v106.4.4...v106.4.5) (2026-06-12) diff --git a/package.json b/package.json index c0c00772b9..f813d4871d 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "capture-app", "homepage": ".", - "version": "106.4.5", + "version": "106.5.0", "cacheVersion": "2", "license": "BSD-3-Clause", "private": true, @@ -15,7 +15,7 @@ "@dhis2/d2-ui-rich-text": "^7.4.0", "@dhis2/data-engine": "^3.17.0", "@dhis2/rule-engine": "3.8.1", - "@dhis2/rules-engine-javascript": "106.4.5", + "@dhis2/rules-engine-javascript": "106.5.0", "@dhis2/ui": "^10.16.2", "@emotion/css": "^11.13.5", "@emotion/react": "^11.14.0", diff --git a/packages/rules-engine/package.json b/packages/rules-engine/package.json index 766a559db8..88abd01c48 100644 --- a/packages/rules-engine/package.json +++ b/packages/rules-engine/package.json @@ -1,6 +1,6 @@ { "name": "@dhis2/rules-engine-javascript", - "version": "106.4.5", + "version": "106.5.0", "license": "BSD-3-Clause", "main": "./build/cjs/index.js", "scripts": { From 751ec20d38ce45013d1ed73ca4d45d354cf4532e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 18 Jun 2026 10:49:02 +0200 Subject: [PATCH 02/16] chore(deps-dev): bump @babel/core from 7.29.6 to 7.29.7 (#4616) Bumps [@babel/core](https://github.com/babel/babel/tree/HEAD/packages/babel-core) from 7.29.6 to 7.29.7. - [Release notes](https://github.com/babel/babel/releases) - [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md) - [Commits](https://github.com/babel/babel/commits/v7.29.7/packages/babel-core) --- updated-dependencies: - dependency-name: "@babel/core" dependency-version: 7.29.7 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 91 +++++++++++++++++++++++++++------------------------- 2 files changed, 49 insertions(+), 44 deletions(-) diff --git a/package.json b/package.json index f813d4871d..becc391957 100644 --- a/package.json +++ b/package.json @@ -83,7 +83,7 @@ }, "devDependencies": { "@actions/core": "^3.0.0", - "@babel/core": "^7.29.6", + "@babel/core": "^7.29.7", "@babel/eslint-parser": "^7.28.6", "@babel/plugin-syntax-dynamic-import": "^7.8.3", "@babel/preset-env": "^7.16.11", diff --git a/yarn.lock b/yarn.lock index ef90f92a22..1ab3a30aa0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -46,7 +46,7 @@ dependencies: "@babel/highlight" "^7.10.4" -"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.29.0", "@babel/code-frame@^7.29.7": +"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.29.7": version "7.29.7" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.29.7.tgz#f2fbbfea87c44a21590ec515b778b2c26d8866e7" integrity sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw== @@ -65,20 +65,20 @@ resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.29.7.tgz#6f0237f0f36d2e51c0570a636faed9d2d0efe629" integrity sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg== -"@babel/core@^7.1.0", "@babel/core@^7.1.6", "@babel/core@^7.12.3", "@babel/core@^7.24.4", "@babel/core@^7.27.4", "@babel/core@^7.28.0", "@babel/core@^7.29.6", "@babel/core@^7.7.2", "@babel/core@^7.8.0": - version "7.29.6" - resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.29.6.tgz#2f2c2ca1728ae73c9b68ece91d1ae6dee18ff83a" - integrity sha512-QdxmAo/ikZqqRGA8s43ww8lcql6naWRvEz0FFrl6MIlc7Gi6TroXnSdWa5U/kq6fzcpqpHesicQxFZIieZbyIA== +"@babel/core@^7.1.0", "@babel/core@^7.1.6", "@babel/core@^7.12.3", "@babel/core@^7.24.4", "@babel/core@^7.27.4", "@babel/core@^7.28.0", "@babel/core@^7.29.7", "@babel/core@^7.7.2", "@babel/core@^7.8.0": + version "7.29.7" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.29.7.tgz#80c10b17248082968b57a857b91640971f2070f7" + integrity sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA== dependencies: - "@babel/code-frame" "^7.29.0" - "@babel/generator" "^7.29.6" - "@babel/helper-compilation-targets" "^7.28.6" - "@babel/helper-module-transforms" "^7.28.6" - "@babel/helpers" "^7.29.2" - "@babel/parser" "^7.29.3" - "@babel/template" "^7.28.6" - "@babel/traverse" "^7.29.0" - "@babel/types" "^7.29.0" + "@babel/code-frame" "^7.29.7" + "@babel/generator" "^7.29.7" + "@babel/helper-compilation-targets" "^7.29.7" + "@babel/helper-module-transforms" "^7.29.7" + "@babel/helpers" "^7.29.7" + "@babel/parser" "^7.29.7" + "@babel/template" "^7.29.7" + "@babel/traverse" "^7.29.7" + "@babel/types" "^7.29.7" "@jridgewell/remapping" "^2.3.5" convert-source-map "^2.0.0" debug "^4.1.0" @@ -95,7 +95,7 @@ eslint-visitor-keys "^2.1.0" semver "^6.3.1" -"@babel/generator@^7.29.0", "@babel/generator@^7.29.6", "@babel/generator@^7.7.2": +"@babel/generator@^7.29.7", "@babel/generator@^7.7.2": version "7.29.7" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.29.7.tgz#cca0b8827e6bcf3ba176788e7f3b180ad6db2fa3" integrity sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ== @@ -113,7 +113,7 @@ dependencies: "@babel/types" "^7.27.3" -"@babel/helper-compilation-targets@^7.27.1", "@babel/helper-compilation-targets@^7.27.2", "@babel/helper-compilation-targets@^7.28.6": +"@babel/helper-compilation-targets@^7.27.1", "@babel/helper-compilation-targets@^7.27.2", "@babel/helper-compilation-targets@^7.29.7": version "7.29.7" resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz#7a1def704302401c47f64fa85589e974ae217042" integrity sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g== @@ -162,6 +162,11 @@ resolved "https://registry.yarnpkg.com/@babel/helper-globals/-/helper-globals-7.28.0.tgz#b9430df2aa4e17bc28665eadeae8aa1d985e6674" integrity sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw== +"@babel/helper-globals@^7.29.7": + version "7.29.7" + resolved "https://registry.yarnpkg.com/@babel/helper-globals/-/helper-globals-7.29.7.tgz#f04a96fbd8473241b1079243f5b3f03a3010ab7b" + integrity sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA== + "@babel/helper-member-expression-to-functions@^7.27.1": version "7.27.1" resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.27.1.tgz#ea1211276be93e798ce19037da6f06fbb994fa44" @@ -178,22 +183,22 @@ "@babel/traverse" "^7.27.1" "@babel/types" "^7.27.1" -"@babel/helper-module-imports@^7.28.6": - version "7.28.6" - resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz#60632cbd6ffb70b22823187201116762a03e2d5c" - integrity sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw== +"@babel/helper-module-imports@^7.29.7": + version "7.29.7" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz#ef25048a518e828d7393fac5882ddd73921d7396" + integrity sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g== dependencies: - "@babel/traverse" "^7.28.6" - "@babel/types" "^7.28.6" + "@babel/traverse" "^7.29.7" + "@babel/types" "^7.29.7" -"@babel/helper-module-transforms@^7.27.1", "@babel/helper-module-transforms@^7.28.6": - version "7.28.6" - resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz#9312d9d9e56edc35aeb6e95c25d4106b50b9eb1e" - integrity sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA== +"@babel/helper-module-transforms@^7.27.1", "@babel/helper-module-transforms@^7.28.6", "@babel/helper-module-transforms@^7.29.7": + version "7.29.7" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz#b062747a5997ba138637201328bbff77960574ae" + integrity sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg== dependencies: - "@babel/helper-module-imports" "^7.28.6" - "@babel/helper-validator-identifier" "^7.28.5" - "@babel/traverse" "^7.28.6" + "@babel/helper-module-imports" "^7.29.7" + "@babel/helper-validator-identifier" "^7.29.7" + "@babel/traverse" "^7.29.7" "@babel/helper-optimise-call-expression@^7.27.1": version "7.27.1" @@ -277,7 +282,7 @@ "@babel/traverse" "^7.27.1" "@babel/types" "^7.27.1" -"@babel/helpers@^7.29.2": +"@babel/helpers@^7.29.7": version "7.29.7" resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.29.7.tgz#45abfde7548997e34376c3e69feb475cffb4a607" integrity sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg== @@ -295,7 +300,7 @@ js-tokens "^4.0.0" picocolors "^1.0.0" -"@babel/parser@^7.1.0", "@babel/parser@^7.1.6", "@babel/parser@^7.14.7", "@babel/parser@^7.18.8", "@babel/parser@^7.20.15", "@babel/parser@^7.20.7", "@babel/parser@^7.29.0", "@babel/parser@^7.29.3", "@babel/parser@^7.29.7", "@babel/parser@^7.7.0": +"@babel/parser@^7.1.0", "@babel/parser@^7.1.6", "@babel/parser@^7.14.7", "@babel/parser@^7.18.8", "@babel/parser@^7.20.15", "@babel/parser@^7.20.7", "@babel/parser@^7.29.7", "@babel/parser@^7.7.0": version "7.29.7" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.29.7.tgz#837b87387cbf5ec5530cb634b3c622f68edb9334" integrity sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg== @@ -1122,7 +1127,7 @@ resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.28.4.tgz#a70226016fabe25c5783b2f22d3e1c9bc5ca3326" integrity sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ== -"@babel/template@^7.27.1", "@babel/template@^7.28.6", "@babel/template@^7.29.7", "@babel/template@^7.3.3": +"@babel/template@^7.27.1", "@babel/template@^7.29.7", "@babel/template@^7.3.3": version "7.29.7" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.29.7.tgz#4d9d4004f645cdd304de958c725162784ecac700" integrity sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg== @@ -1131,17 +1136,17 @@ "@babel/parser" "^7.29.7" "@babel/types" "^7.29.7" -"@babel/traverse@^7.27.1", "@babel/traverse@^7.28.0", "@babel/traverse@^7.28.3", "@babel/traverse@^7.28.4", "@babel/traverse@^7.28.6", "@babel/traverse@^7.29.0", "@babel/traverse@^7.7.0", "@babel/traverse@^7.7.2": - version "7.29.0" - resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.29.0.tgz#f323d05001440253eead3c9c858adbe00b90310a" - integrity sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA== +"@babel/traverse@^7.27.1", "@babel/traverse@^7.28.0", "@babel/traverse@^7.28.3", "@babel/traverse@^7.28.4", "@babel/traverse@^7.29.0", "@babel/traverse@^7.29.7", "@babel/traverse@^7.7.0", "@babel/traverse@^7.7.2": + version "7.29.7" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.29.7.tgz#c47b07a41b95da0907d026b5dd894d98de7d2f2d" + integrity sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw== dependencies: - "@babel/code-frame" "^7.29.0" - "@babel/generator" "^7.29.0" - "@babel/helper-globals" "^7.28.0" - "@babel/parser" "^7.29.0" - "@babel/template" "^7.28.6" - "@babel/types" "^7.29.0" + "@babel/code-frame" "^7.29.7" + "@babel/generator" "^7.29.7" + "@babel/helper-globals" "^7.29.7" + "@babel/parser" "^7.29.7" + "@babel/template" "^7.29.7" + "@babel/types" "^7.29.7" debug "^4.3.1" "@babel/types@7.15.0": @@ -1152,7 +1157,7 @@ "@babel/helper-validator-identifier" "^7.14.9" to-fast-properties "^2.0.0" -"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.27.1", "@babel/types@^7.27.3", "@babel/types@^7.28.6", "@babel/types@^7.29.0", "@babel/types@^7.29.7", "@babel/types@^7.3.3", "@babel/types@^7.4.4", "@babel/types@^7.7.0": +"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.27.1", "@babel/types@^7.27.3", "@babel/types@^7.29.7", "@babel/types@^7.3.3", "@babel/types@^7.4.4", "@babel/types@^7.7.0": version "7.29.7" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.29.7.tgz#8005e31d82712ee7adaef6e23c63b71a62770a92" integrity sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA== From f000b0b076ab61e843746f2ba2e24f65cd03dd45 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 18 Jun 2026 10:49:17 +0200 Subject: [PATCH 03/16] chore(deps-dev): bump @dhis2/cypress-plugins from 10.1.0 to 10.1.2 (#4614) Bumps [@dhis2/cypress-plugins](https://github.com/dhis2/cli-utils-cypress) from 10.1.0 to 10.1.2. - [Release notes](https://github.com/dhis2/cli-utils-cypress/releases) - [Changelog](https://github.com/dhis2/cli-utils-cypress/blob/master/CHANGELOG.md) - [Commits](https://github.com/dhis2/cli-utils-cypress/compare/v10.1.0...v10.1.2) --- updated-dependencies: - dependency-name: "@dhis2/cypress-plugins" dependency-version: 10.1.2 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index becc391957..82c48a700c 100644 --- a/package.json +++ b/package.json @@ -96,7 +96,7 @@ "@dhis2/cli-style": "^10.7.10", "@dhis2/cli-utils-cypress": "^10.1.2", "@dhis2/cypress-commands": "^10.0.3", - "@dhis2/cypress-plugins": "^10.0.3", + "@dhis2/cypress-plugins": "^10.1.2", "@types/node": "^25.6.2", "@types/react": "18.3.1", "@types/react-dom": "18.3.1", diff --git a/yarn.lock b/yarn.lock index 1ab3a30aa0..c2b81acbab 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2477,10 +2477,10 @@ dependencies: jscodeshift "^0.11.0" -"@dhis2/cypress-plugins@^10.0.3": - version "10.1.0" - resolved "https://registry.yarnpkg.com/@dhis2/cypress-plugins/-/cypress-plugins-10.1.0.tgz#17818f0d490f15932477ec8b06e13fc580a7e250" - integrity sha512-Kw5rq1+QIveoOR/2NSK0Y5LvvRVlB7OjdJ0mXL+3253kPKw79WuNpefKKtGQ8Jk+x9VMamkM3jJIsAtOBoWMBA== +"@dhis2/cypress-plugins@^10.1.2": + version "10.1.2" + resolved "https://registry.yarnpkg.com/@dhis2/cypress-plugins/-/cypress-plugins-10.1.2.tgz#e5f04118e2a09a0b908208c324a33abdd7231c11" + integrity sha512-6w7IhMb1xxQpn94KWnwbgNCsCkAjkuhtxneTvqNWXCZagNbo5STXiaFwrDYXaH/go/MNdpz5EPyR/bICLALc/g== "@dhis2/d2-i18n@^1.1.0", "@dhis2/d2-i18n@^1.1.3", "@dhis2/d2-i18n@^1.2.0": version "1.2.0" From 7548d44a2d5b583cabe41b54991a339d83d2e439 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 18 Jun 2026 10:55:30 +0200 Subject: [PATCH 04/16] chore(deps-dev): bump dotenv from 16.6.1 to 17.4.2 (#4615) Bumps [dotenv](https://github.com/motdotla/dotenv) from 16.6.1 to 17.4.2. - [Changelog](https://github.com/motdotla/dotenv/blob/master/CHANGELOG.md) - [Commits](https://github.com/motdotla/dotenv/compare/v16.6.1...v17.4.2) --- updated-dependencies: - dependency-name: dotenv dependency-version: 17.4.2 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 82c48a700c..5701fad747 100644 --- a/package.json +++ b/package.json @@ -108,7 +108,7 @@ "concurrently": "^8.2.2", "cypress": "12", "docdash": "^2.0.2", - "dotenv": "^16.4.5", + "dotenv": "^17.4.2", "eslint": "7.32.0", "eslint-config-airbnb": "^15.1.0", "eslint-config-react-app": "^6.0.0", diff --git a/yarn.lock b/yarn.lock index c2b81acbab..c54c982388 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6784,10 +6784,10 @@ dotenv-expand@^5.1.0: resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-5.1.0.tgz#3fbaf020bfd794884072ea26b1e9791d45a629f0" integrity sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA== -dotenv@^16.4.5: - version "16.6.1" - resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.6.1.tgz#773f0e69527a8315c7285d5ee73c4459d20a8020" - integrity sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow== +dotenv@^17.4.2: + version "17.4.2" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-17.4.2.tgz#c07e54a746e11eba021dd9e1047ced5afdc1c034" + integrity sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw== dotenv@^8.1.0: version "8.6.0" From 1458caaa160a04452e011e9bdc0e1257c21070c1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 18 Jun 2026 11:11:35 +0200 Subject: [PATCH 05/16] chore(deps-dev): bump @babel/eslint-parser from 7.28.6 to 7.29.7 (#4613) Bumps [@babel/eslint-parser](https://github.com/babel/babel/tree/HEAD/eslint/babel-eslint-parser) from 7.28.6 to 7.29.7. - [Release notes](https://github.com/babel/babel/releases) - [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md) - [Commits](https://github.com/babel/babel/commits/v7.29.7/eslint/babel-eslint-parser) --- updated-dependencies: - dependency-name: "@babel/eslint-parser" dependency-version: 7.29.7 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 5701fad747..3517da9c8f 100644 --- a/package.json +++ b/package.json @@ -84,7 +84,7 @@ "devDependencies": { "@actions/core": "^3.0.0", "@babel/core": "^7.29.7", - "@babel/eslint-parser": "^7.28.6", + "@babel/eslint-parser": "^7.29.7", "@babel/plugin-syntax-dynamic-import": "^7.8.3", "@babel/preset-env": "^7.16.11", "@babel/preset-react": "^7.26.3", diff --git a/yarn.lock b/yarn.lock index c54c982388..e8b8278804 100644 --- a/yarn.lock +++ b/yarn.lock @@ -86,10 +86,10 @@ json5 "^2.2.3" semver "^6.3.1" -"@babel/eslint-parser@^7.28.6": - version "7.28.6" - resolved "https://registry.yarnpkg.com/@babel/eslint-parser/-/eslint-parser-7.28.6.tgz#6a294a4add732ebe7ded8a8d2792dd03dd81dc3f" - integrity sha512-QGmsKi2PBO/MHSQk+AAgA9R6OHQr+VqnniFE0eMWZcVcfBZoA2dKn2hUsl3Csg/Plt9opRUWdY7//VXsrIlEiA== +"@babel/eslint-parser@^7.29.7": + version "7.29.7" + resolved "https://registry.yarnpkg.com/@babel/eslint-parser/-/eslint-parser-7.29.7.tgz#272cc7531972ff45bc0db96c45349cb7304d21e1" + integrity sha512-zxt+UJTOMKvUt3yOg+D58MLuz334pHp93qifMFcjIIO+9hN6t+ufw2gi7vDPMpxvfnHRR+3VVXvIjineCcgyXw== dependencies: "@nicolo-ribaudo/eslint-scope-5-internals" "5.1.1-v1" eslint-visitor-keys "^2.1.0" From 2d787c6b38b1a1b0fadced3552630de7930ca7d4 Mon Sep 17 00:00:00 2001 From: Fernando Gomez Date: Mon, 1 Jun 2026 16:27:05 +0200 Subject: [PATCH 06/16] feat: DHIS2-21266 - Integrate react Markdown lib - Integrate react markdown library to support standard md elements - Apply corresponding legend sets for rule actions where apply - Create Markdown e2e testing for markdown integration --- cypress/e2e/Markdown/Markdown.feature | 32 + cypress/e2e/Markdown/Markdown.js | 79 ++ i18n/en.pot | 4 +- package.json | 2 + .../WidgetFeedback/WidgetFeedback.types.ts | 4 + .../WidgetFeedbackContent/MarkdownWrapper.tsx | 170 ++++ .../WidgetFeedbackContent.tsx | 74 +- .../helpers/rulesEffectsProcessor.ts | 4 + .../RuleEngine/types/ruleEngine.types.ts | 3 + yarn.lock | 834 +++++++++++++++++- 10 files changed, 1172 insertions(+), 34 deletions(-) create mode 100644 cypress/e2e/Markdown/Markdown.feature create mode 100644 cypress/e2e/Markdown/Markdown.js create mode 100644 src/core_modules/capture-core/components/WidgetFeedback/WidgetFeedbackContent/MarkdownWrapper.tsx diff --git a/cypress/e2e/Markdown/Markdown.feature b/cypress/e2e/Markdown/Markdown.feature new file mode 100644 index 0000000000..cb2463e37e --- /dev/null +++ b/cypress/e2e/Markdown/Markdown.feature @@ -0,0 +1,32 @@ +Feature: Feedback widget renders markdown content from program rules + + Scenario: Feedback widget is visible on the enrollment dashboard + Given user lands on the enrollment edit event page by having typed #/enrollmentEventEdit?eventId=hmX770XqCSS + Then the feedback widget should be visible + + Scenario: Feedback widget list contains 4 items + Given user lands on the enrollment edit event page by having typed #/enrollmentEventEdit?eventId=hmX770XqCSS + Then the list should contain 4 items + + Scenario: First feedback item renders headers markdown correctly + Given user lands on the enrollment edit event page by having typed #/enrollmentEventEdit?eventId=hmX770XqCSS + Then the first list item should contain p with text "Headers" + And the first list item should contain h1 with text "Header 1" + And the first list item should contain h2 with text "Header 2" + And the first list item should contain h3 with text "Header 3" + And the first list item should contain h4 with text "Header 4" + And the first list item should contain h5 with text "Header 5" + And the first list item should contain h6 with text "Header 6" + + Scenario: Second feedback item renders italic heading and table correctly + Given user lands on the enrollment edit event page by having typed #/enrollmentEventEdit?eventId=hmX770XqCSS + Then the second list item h6 should contain italic text "Feedback" + And the second list item table should match: + | Column A | Column B | + | Content A | Content B | + + Scenario: Fourth feedback item renders score with color indicator correctly + Given user lands on the enrollment edit event page by having typed #/enrollmentEventEdit?eventId=hmX770XqCSS + Then the fourth list item left section should have h1 with text "1. Score 1" + And the fourth list item right section color indicator should be "rgb(238, 71, 71)" + And the fourth list item right section should have p with text "66.67%" diff --git a/cypress/e2e/Markdown/Markdown.js b/cypress/e2e/Markdown/Markdown.js new file mode 100644 index 0000000000..0b2df3d362 --- /dev/null +++ b/cypress/e2e/Markdown/Markdown.js @@ -0,0 +1,79 @@ +import {Given, Then, defineStep as And} from '@badeball/cypress-cucumber-preprocessor'; + +const LONG_TIMEOUT = { timeout: 120000 }; + +const getFeedbackDiv = () => + cy.get('[data-test="feedback-widget"]', LONG_TIMEOUT); + +// SHARED + +Given(/^user lands on the enrollment edit event page by having typed (.*)$/, (url) => { + cy.visit(url, LONG_TIMEOUT); + getFeedbackDiv().should('exist'); +}); + +Then('the feedback widget should be visible', () => { + getFeedbackDiv().should('be.visible'); +}); + +Then('the list should contain {int} items', (expectedCount) => { + getFeedbackDiv() + .find('[data-test="widget-contents"]', LONG_TIMEOUT) + .find('ul') + .find('li') + .should('have.length', expectedCount); +}); + +const getListItem = (n) => + getFeedbackDiv() + .find('[data-test="widget-contents"]', LONG_TIMEOUT) + .find('ul li') + .eq(n - 1); + +// FIRST FEEDBACK ELEMENT + +And('the first list item should contain {word} with text {string}', (tag, text) => { + getListItem(1) + .find(tag) + .should('have.text', text); +}); + +// SECOND FEEDBACK ELEMENT + +Then('the second list item {word} should contain italic text {string}', (tag, text) => { + getListItem(2) + .find(tag) + .find('em') + .should('have.text', text); +}); + +And('the second list item table should match:', (dataTable) => { + const [headerRow, ...bodyRows] = dataTable.raw(); + + getListItem(2).find('table').within(() => { + cy.get('thead th').each(($th, i) => { + expect($th.text()).to.equal(headerRow[i]); + }); + + cy.get('tbody tr').each(($tr, rowIndex) => { + bodyRows[rowIndex].forEach((cell, colIndex) => { + expect($tr.find('td').eq(colIndex).text()).to.equal(cell); + }); + }); + }); +}); + + +// FOURTH FEEDBACK ELEMENT + +Then('the fourth list item left section should have {word} with text {string}', (tag, text) => { + getListItem(4).find('> div > div').first().find(tag).should('have.text', text); +}); + +And('the fourth list item right section color indicator should be {string}', (color) => { + getListItem(4).find('> div > div').last().find('span').should('have.css', 'background-color', color); +}); + +And('the fourth list item right section should have {word} with text {string}', (tag, text) => { + getListItem(4).find('> div > div').last().find(tag).should('have.text', text); +}); diff --git a/i18n/en.pot b/i18n/en.pot index b89585a062..1c2692c5b6 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -5,8 +5,8 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -"POT-Creation-Date: 2026-06-10T13:04:43.537Z\n" -"PO-Revision-Date: 2026-06-10T13:04:43.537Z\n" +"POT-Creation-Date: 2026-06-01T14:18:23.707Z\n" +"PO-Revision-Date: 2026-06-01T14:18:23.707Z\n" msgid "The application could not be loaded." msgstr "The application could not be loaded." diff --git a/package.json b/package.json index 3517da9c8f..ee50a823d8 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "react-leaflet": "^2.8.0", "react-leaflet-draw": "0.19.0", "react-leaflet-search-unpolyfilled": "^0.1.0", + "react-markdown": "^10.1.0", "react-popper": "^2.2.4", "react-redux": "^9.2.0", "react-router-dom": "^5.3.0", @@ -57,6 +58,7 @@ "redux-logger": "^3.0.6", "redux-observable": "^2.0.0", "regenerator-runtime": "^0.14.1", + "remark-gfm": "^4.0.1", "reselect": "^4.1.7", "rxjs": "^7.8.2", "typeface-roboto": "^1.1.13", diff --git a/src/core_modules/capture-core/components/WidgetFeedback/WidgetFeedback.types.ts b/src/core_modules/capture-core/components/WidgetFeedback/WidgetFeedback.types.ts index 66b3803412..74b782beb5 100644 --- a/src/core_modules/capture-core/components/WidgetFeedback/WidgetFeedback.types.ts +++ b/src/core_modules/capture-core/components/WidgetFeedback/WidgetFeedback.types.ts @@ -2,6 +2,8 @@ export type FilteredFeedbackText = { id: string; message: string; color?: string; + priority?: number; + legendSetId?: string; } export type FilteredFeedbackKeyValue = { @@ -9,6 +11,8 @@ export type FilteredFeedbackKeyValue = { key: string; value: string; color?: string; + priority?: number; + legendSetId?: string; } export type FeedbackWidgetData = string | FilteredFeedbackText | FilteredFeedbackKeyValue; diff --git a/src/core_modules/capture-core/components/WidgetFeedback/WidgetFeedbackContent/MarkdownWrapper.tsx b/src/core_modules/capture-core/components/WidgetFeedback/WidgetFeedbackContent/MarkdownWrapper.tsx new file mode 100644 index 0000000000..b25bc8a5bb --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetFeedback/WidgetFeedbackContent/MarkdownWrapper.tsx @@ -0,0 +1,170 @@ +import React, { ComponentType } from 'react'; +import { spacersNum, colors } from '@dhis2/ui'; +import Markdown from 'react-markdown'; +import { withStyles, type WithStyles } from 'capture-core-utils/styles'; +import remarkGfm from 'remark-gfm'; + +export type MarkdownWrapperProps = { + title: string; + content?: string; + color?: string; +} + +const styles = { + container: { + width: '100%', + margin: '8px 0px', + display: 'flex', + gap: '1rem', + }, + title: { + flex: '1 0 50%', + minWidth: '50%', + }, + content: { + flex: '0 1 auto', + maxWidth: '50%', + minWidth: '0', + display: 'flex', + alignItems: 'center', + gap: '8px', + }, + legend: { + height: '12px', + width: '12px', + minWidth: '12px', + borderRadius: '2px', + }, + markdown: { + fontSize: '14px', + lineHeight: 1.45, + color: colors.grey900, + '&:first-child': { + marginTop: 0, + }, + '& p': { + margin: `${spacersNum.dp8}px 0 0 0`, + display: 'inline-block', + }, + '& p:first-child': { + marginTop: 0, + }, + '& h1': { + fontSize: '18px', + fontWeight: 700, + lineHeight: 1.25, + margin: `${spacersNum.dp12}px 0 ${spacersNum.dp8}px 0`, + paddingBottom: spacersNum.dp4, + borderBottom: `2px solid ${colors.grey400}`, + color: colors.grey900, + }, + '& h2': { + fontSize: '16px', + fontWeight: 700, + margin: `${spacersNum.dp12}px 0 ${spacersNum.dp4}px 0`, + color: colors.grey900, + }, + '& h3': { + fontSize: '15px', + fontWeight: 600, + margin: `${spacersNum.dp8}px 0 ${spacersNum.dp4}px 0`, + color: colors.grey900, + }, + '& h4, & h5, & h6': { + fontSize: '14px', + fontWeight: 600, + margin: `${spacersNum.dp8}px 0 ${spacersNum.dp4}px 0`, + color: colors.grey900, + }, + '& strong': { + fontWeight: 700, + color: colors.grey900, + }, + '& em': { + fontStyle: 'italic', + color: colors.grey900, + }, + '& a': { + color: colors.blue800, + textDecoration: 'underline', + }, + '& ul, & ol': { + margin: `${spacersNum.dp8}px 0 0 0`, + paddingInlineStart: spacersNum.dp16, + }, + '& li': { + marginTop: spacersNum.dp4, + }, + '& code': { + fontFamily: 'Menlo, Monaco, Consolas, monospace', + fontSize: '12px', + background: colors.grey200, + padding: `1px ${spacersNum.dp4}px`, + borderRadius: '3px', + }, + '& pre': { + margin: `${spacersNum.dp8}px 0 0 0`, + padding: spacersNum.dp8, + background: colors.grey200, + borderRadius: '4px', + overflow: 'auto', + fontSize: '12px', + }, + '& pre code': { + background: 'transparent', + padding: 0, + }, + '& blockquote': { + margin: `${spacersNum.dp8}px 0 0 0`, + paddingInlineStart: spacersNum.dp12, + borderInlineStart: `4px solid ${colors.grey400}`, + color: colors.grey800, + fontStyle: 'italic', + }, + '& hr': { + margin: `${spacersNum.dp12}px 0`, + border: 'none', + borderTop: `1px solid ${colors.grey400}`, + }, + '& table': { + borderCollapse: 'collapse', + width: '100%', + margin: `${spacersNum.dp8}px 0`, + fontSize: '13px', + lineHeight: '18px', + }, + '& th, & td': { + padding: `${spacersNum.dp4}px ${spacersNum.dp8}px`, + border: `1px solid ${colors.grey300}`, + textAlign: 'left', + }, + '& th': { + fontWeight: 600, + color: colors.grey800, + whiteSpace: 'nowrap', + }, + '& td': { + color: colors.grey900, + }, + }, +}; + +type Props = MarkdownWrapperProps & WithStyles; +const MarkdownWrapperComponent = ({ title, content, color, classes }:Props) => ( +
+
+
{title}
+
+ + { content && +
+ { color && } +
+ {content} +
+
+ } +
+); + +export const MarkdownWrapper = withStyles(styles)(MarkdownWrapperComponent) as ComponentType; diff --git a/src/core_modules/capture-core/components/WidgetFeedback/WidgetFeedbackContent/WidgetFeedbackContent.tsx b/src/core_modules/capture-core/components/WidgetFeedback/WidgetFeedbackContent/WidgetFeedbackContent.tsx index 9e11aa1185..2af0dffae9 100644 --- a/src/core_modules/capture-core/components/WidgetFeedback/WidgetFeedbackContent/WidgetFeedbackContent.tsx +++ b/src/core_modules/capture-core/components/WidgetFeedback/WidgetFeedbackContent/WidgetFeedbackContent.tsx @@ -1,6 +1,8 @@ -import React, { type ComponentType } from 'react'; +import React, { type ComponentType, useMemo } from 'react'; +import { useDataQuery } from '@dhis2/app-runtime'; import { spacersNum, colors } from '@dhis2/ui'; import { withStyles, type WithStyles } from 'capture-core-utils/styles'; +import { MarkdownWrapper } from 'capture-core/components/WidgetFeedback/WidgetFeedbackContent/MarkdownWrapper'; import type { FilteredFeedbackKeyValue, FeedbackWidgetData, @@ -8,9 +10,13 @@ import type { FeedbackProps, } from '../WidgetFeedback.types'; + const styles = { container: { padding: `0px ${spacersNum.dp12}px`, + maxHeight: '70vh', + overflowY: 'auto', + scrollbarWidth: 'thin', }, unorderedList: { paddingInlineStart: spacersNum.dp16, @@ -33,9 +39,54 @@ const styles = { }, }; +type Legend = { startValue: number; endValue: number; color: string }; +type LegendSetApiResponse = { legendSets: Array<{ id: string; legends: Legend[] }> }; + +const legendSetQuery = { + legendSets: { + resource: 'legendSets', + params: ({ ids }: Record) => ({ + fields: 'id,legends[id,startValue,endValue,color]', + filter: `id:in:[${ids}]`, + paging: false, + }), + }, +}; + type Props = FeedbackProps & WithStyles; const WidgetFeedbackContentComponent = ({ feedback, feedbackEmptyText, classes }: Props) => { + const legendSetIds = useMemo(() => [...new Set( + (feedback ?? []) + .filter((rule): rule is FilteredFeedbackKeyValue => + typeof rule === 'object' && ('key' in rule || 'value' in rule)) + .map(rule => rule.legendSetId) + .filter((id): id is string => !!id), + )], [feedback]); + + const { data: legendSetData } = useDataQuery(legendSetQuery, { + variables: { ids: legendSetIds.join(',') }, + lazy: legendSetIds.length === 0, + }); + + const legendSetsById = useMemo>(() => { + const map: Record = {}; + const response = legendSetData?.legendSets as LegendSetApiResponse | undefined; + (response?.legendSets ?? []).forEach((ls) => { + map[ls.id] = ls.legends; + }); + return map; + }, [legendSetData]); + + const resolveColor = (legendSetId: string | undefined, value: string): string | undefined => { + if (!legendSetId) return undefined; + const legends = legendSetsById[legendSetId]; + if (!legends?.length) return undefined; + const numeric = parseFloat(value); + if (isNaN(numeric)) return undefined; + return legends.find(l => numeric >= l.startValue && numeric < l.endValue)?.color; + }; + if (!feedback?.length) { return (
- {item.message} + ); - const renderKeyValue = (item: FilteredFeedbackKeyValue) => ( -
  • - {item.key}{item.key && item.value ? ': ' : null}{item.value} -
  • - ); + const renderKeyValue = (item: FilteredFeedbackKeyValue) => { + const color = resolveColor(item.legendSetId, item.value); + return ( +
  • + +
  • + ); + }; const renderString = (item: string, index: number) => (
  • Date: Mon, 1 Jun 2026 16:44:55 +0200 Subject: [PATCH 07/16] fix: fix code analysis report for parseFloat and isNaN --- .../WidgetFeedbackContent/WidgetFeedbackContent.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/core_modules/capture-core/components/WidgetFeedback/WidgetFeedbackContent/WidgetFeedbackContent.tsx b/src/core_modules/capture-core/components/WidgetFeedback/WidgetFeedbackContent/WidgetFeedbackContent.tsx index 2af0dffae9..1a710c5189 100644 --- a/src/core_modules/capture-core/components/WidgetFeedback/WidgetFeedbackContent/WidgetFeedbackContent.tsx +++ b/src/core_modules/capture-core/components/WidgetFeedback/WidgetFeedbackContent/WidgetFeedbackContent.tsx @@ -82,8 +82,8 @@ const WidgetFeedbackContentComponent = ({ feedback, feedbackEmptyText, classes } if (!legendSetId) return undefined; const legends = legendSetsById[legendSetId]; if (!legends?.length) return undefined; - const numeric = parseFloat(value); - if (isNaN(numeric)) return undefined; + const numeric = Number.parseFloat(value); + if (Number.isNaN(numeric)) return undefined; return legends.find(l => numeric >= l.startValue && numeric < l.endValue)?.color; }; From df378c9c1fac07b2df2fe4d94df909101f46f0cd Mon Sep 17 00:00:00 2001 From: Simona Domnisoru Date: Tue, 2 Jun 2026 11:47:00 +0200 Subject: [PATCH 08/16] chore: dedup wrap-ansi to fix the CI run --- i18n/en.pot | 4 ++-- yarn.lock | 47 +++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 45 insertions(+), 6 deletions(-) diff --git a/i18n/en.pot b/i18n/en.pot index 1c2692c5b6..730ae88c99 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -5,8 +5,8 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -"POT-Creation-Date: 2026-06-01T14:18:23.707Z\n" -"PO-Revision-Date: 2026-06-01T14:18:23.707Z\n" +"POT-Creation-Date: 2026-06-02T09:46:52.775Z\n" +"PO-Revision-Date: 2026-06-02T09:46:52.775Z\n" msgid "The application could not be loaded." msgstr "The application could not be loaded." diff --git a/yarn.lock b/yarn.lock index b51cc67b93..93d8375ae3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4018,6 +4018,13 @@ "@types/linkify-it" "^5" "@types/mdurl" "^2" +"@types/mdast@^4.0.0": + version "4.0.4" + resolved "https://registry.yarnpkg.com/@types/mdast/-/mdast-4.0.4.tgz#7ccf72edd2f1aa7dd3437e180c64373585804dd6" + integrity sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA== + dependencies: + "@types/unist" "*" + "@types/mdurl@^2": version "2.0.0" resolved "https://registry.yarnpkg.com/@types/mdurl/-/mdurl-2.0.0.tgz#d43878b5b20222682163ae6f897b20447233bdfd" @@ -4028,6 +4035,11 @@ resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.5.tgz#ec10755e871497bcd83efe927e43ec46e8c0747e" integrity sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag== +"@types/ms@*": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@types/ms/-/ms-2.1.0.tgz#052aa67a48eccc4309d7f0191b7e41434b90bb78" + integrity sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA== + "@types/node@*", "@types/node@^25.6.2": version "25.8.0" resolved "https://registry.yarnpkg.com/@types/node/-/node-25.8.0.tgz#d13033397d1c186876bed4c9b9d7f3f962097eb3" @@ -4117,6 +4129,16 @@ resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.7.tgz#baccb07a970b91707df3a3e8ba6896c57ead2d11" integrity sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw== +"@types/unist@*", "@types/unist@^3.0.0": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@types/unist/-/unist-3.0.3.tgz#acaab0f919ce69cce629c2d4ed2eb4adc1b6c20c" + integrity sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q== + +"@types/unist@^2.0.0": + version "2.0.11" + resolved "https://registry.yarnpkg.com/@types/unist/-/unist-2.0.11.tgz#11af57b127e32487774841f7a4e54eab166d03c4" + integrity sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA== + "@types/use-sync-external-store@^0.0.6": version "0.0.6" resolved "https://registry.yarnpkg.com/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz#60be8d21baab8c305132eb9cb912ed497852aadc" @@ -13938,7 +13960,7 @@ string-natural-compare@^3.0.1: resolved "https://registry.yarnpkg.com/string-natural-compare/-/string-natural-compare-3.0.1.tgz#7a42d58474454963759e8e8b7ae63d71c1e7fdf4" integrity sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw== -"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0": version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -13964,6 +13986,15 @@ string-width@^3.0.0, string-width@^3.1.0: is-fullwidth-code-point "^2.0.0" strip-ansi "^5.1.0" +string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + string-width@^5.0.1, string-width@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" @@ -14063,7 +14094,7 @@ stringify-object@^3.3.0: is-obj "^1.0.1" is-regexp "^1.0.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@6.0.1, strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -15671,8 +15702,7 @@ workbox-window@7.3.0: "@types/trusted-types" "^2.0.2" workbox-core "7.3.0" -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: - name wrap-ansi-cjs +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -15699,6 +15729,15 @@ wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^8.0.1, wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" From e9800d3b5ea9a5238cd0f63aac29bc466eac9b96 Mon Sep 17 00:00:00 2001 From: Fernando Gomez Date: Thu, 18 Jun 2026 00:59:55 +0200 Subject: [PATCH 09/16] fix: [DHIS2-21266] Apply changes from PR feedback * Folder structure * Update cypress tests --- cypress/e2e/Markdown/Markdown.feature | 14 ++--- i18n/en.pot | 4 +- .../{ => MarkdownWrapper}/MarkdownWrapper.tsx | 6 +- .../MarkdownWrapper/MarkdownWrapper.types.ts | 5 ++ .../MarkdownWrapper/index.ts | 1 + .../WidgetFeedbackContent.tsx | 50 ++------------- .../useLegendSetsById.ts | 62 +++++++++++++++++++ .../RuleEngine/types/ruleEngine.types.ts | 2 +- 8 files changed, 83 insertions(+), 61 deletions(-) rename src/core_modules/capture-core/components/WidgetFeedback/WidgetFeedbackContent/{ => MarkdownWrapper}/MarkdownWrapper.tsx (98%) create mode 100644 src/core_modules/capture-core/components/WidgetFeedback/WidgetFeedbackContent/MarkdownWrapper/MarkdownWrapper.types.ts create mode 100644 src/core_modules/capture-core/components/WidgetFeedback/WidgetFeedbackContent/MarkdownWrapper/index.ts create mode 100644 src/core_modules/capture-core/components/WidgetFeedback/WidgetFeedbackContent/useLegendSetsById.ts diff --git a/cypress/e2e/Markdown/Markdown.feature b/cypress/e2e/Markdown/Markdown.feature index cb2463e37e..13a04ebceb 100644 --- a/cypress/e2e/Markdown/Markdown.feature +++ b/cypress/e2e/Markdown/Markdown.feature @@ -1,15 +1,15 @@ Feature: Feedback widget renders markdown content from program rules Scenario: Feedback widget is visible on the enrollment dashboard - Given user lands on the enrollment edit event page by having typed #/enrollmentEventEdit?eventId=hmX770XqCSS + Given user lands on the enrollment edit event page by having typed #/enrollmentEventEdit?eventId=EKlWxFF9NPb Then the feedback widget should be visible Scenario: Feedback widget list contains 4 items - Given user lands on the enrollment edit event page by having typed #/enrollmentEventEdit?eventId=hmX770XqCSS + Given user lands on the enrollment edit event page by having typed #/enrollmentEventEdit?eventId=EKlWxFF9NPb Then the list should contain 4 items Scenario: First feedback item renders headers markdown correctly - Given user lands on the enrollment edit event page by having typed #/enrollmentEventEdit?eventId=hmX770XqCSS + Given user lands on the enrollment edit event page by having typed #/enrollmentEventEdit?eventId=EKlWxFF9NPb Then the first list item should contain p with text "Headers" And the first list item should contain h1 with text "Header 1" And the first list item should contain h2 with text "Header 2" @@ -19,14 +19,14 @@ Feature: Feedback widget renders markdown content from program rules And the first list item should contain h6 with text "Header 6" Scenario: Second feedback item renders italic heading and table correctly - Given user lands on the enrollment edit event page by having typed #/enrollmentEventEdit?eventId=hmX770XqCSS + Given user lands on the enrollment edit event page by having typed #/enrollmentEventEdit?eventId=EKlWxFF9NPb Then the second list item h6 should contain italic text "Feedback" And the second list item table should match: | Column A | Column B | | Content A | Content B | Scenario: Fourth feedback item renders score with color indicator correctly - Given user lands on the enrollment edit event page by having typed #/enrollmentEventEdit?eventId=hmX770XqCSS + Given user lands on the enrollment edit event page by having typed #/enrollmentEventEdit?eventId=EKlWxFF9NPb Then the fourth list item left section should have h1 with text "1. Score 1" - And the fourth list item right section color indicator should be "rgb(238, 71, 71)" - And the fourth list item right section should have p with text "66.67%" + And the fourth list item right section color indicator should be "rgb(57, 166, 45)" + And the fourth list item right section should have p with text "90%" diff --git a/i18n/en.pot b/i18n/en.pot index 730ae88c99..adbdf47167 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -5,8 +5,8 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -"POT-Creation-Date: 2026-06-02T09:46:52.775Z\n" -"PO-Revision-Date: 2026-06-02T09:46:52.775Z\n" +"POT-Creation-Date: 2026-06-17T22:59:57.403Z\n" +"PO-Revision-Date: 2026-06-17T22:59:57.404Z\n" msgid "The application could not be loaded." msgstr "The application could not be loaded." diff --git a/src/core_modules/capture-core/components/WidgetFeedback/WidgetFeedbackContent/MarkdownWrapper.tsx b/src/core_modules/capture-core/components/WidgetFeedback/WidgetFeedbackContent/MarkdownWrapper/MarkdownWrapper.tsx similarity index 98% rename from src/core_modules/capture-core/components/WidgetFeedback/WidgetFeedbackContent/MarkdownWrapper.tsx rename to src/core_modules/capture-core/components/WidgetFeedback/WidgetFeedbackContent/MarkdownWrapper/MarkdownWrapper.tsx index b25bc8a5bb..1b602aaa3a 100644 --- a/src/core_modules/capture-core/components/WidgetFeedback/WidgetFeedbackContent/MarkdownWrapper.tsx +++ b/src/core_modules/capture-core/components/WidgetFeedback/WidgetFeedbackContent/MarkdownWrapper/MarkdownWrapper.tsx @@ -3,12 +3,8 @@ import { spacersNum, colors } from '@dhis2/ui'; import Markdown from 'react-markdown'; import { withStyles, type WithStyles } from 'capture-core-utils/styles'; import remarkGfm from 'remark-gfm'; +import { MarkdownWrapperProps } from './MarkdownWrapper.types'; -export type MarkdownWrapperProps = { - title: string; - content?: string; - color?: string; -} const styles = { container: { diff --git a/src/core_modules/capture-core/components/WidgetFeedback/WidgetFeedbackContent/MarkdownWrapper/MarkdownWrapper.types.ts b/src/core_modules/capture-core/components/WidgetFeedback/WidgetFeedbackContent/MarkdownWrapper/MarkdownWrapper.types.ts new file mode 100644 index 0000000000..025524fd25 --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetFeedback/WidgetFeedbackContent/MarkdownWrapper/MarkdownWrapper.types.ts @@ -0,0 +1,5 @@ +export type MarkdownWrapperProps = { + title: string; + content?: string; + color?: string; +} diff --git a/src/core_modules/capture-core/components/WidgetFeedback/WidgetFeedbackContent/MarkdownWrapper/index.ts b/src/core_modules/capture-core/components/WidgetFeedback/WidgetFeedbackContent/MarkdownWrapper/index.ts new file mode 100644 index 0000000000..0ed475d71f --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetFeedback/WidgetFeedbackContent/MarkdownWrapper/index.ts @@ -0,0 +1 @@ +export { MarkdownWrapper } from './MarkdownWrapper'; diff --git a/src/core_modules/capture-core/components/WidgetFeedback/WidgetFeedbackContent/WidgetFeedbackContent.tsx b/src/core_modules/capture-core/components/WidgetFeedback/WidgetFeedbackContent/WidgetFeedbackContent.tsx index 1a710c5189..a5bcafa817 100644 --- a/src/core_modules/capture-core/components/WidgetFeedback/WidgetFeedbackContent/WidgetFeedbackContent.tsx +++ b/src/core_modules/capture-core/components/WidgetFeedback/WidgetFeedbackContent/WidgetFeedbackContent.tsx @@ -1,15 +1,14 @@ -import React, { type ComponentType, useMemo } from 'react'; -import { useDataQuery } from '@dhis2/app-runtime'; +import React, { type ComponentType } from 'react'; import { spacersNum, colors } from '@dhis2/ui'; import { withStyles, type WithStyles } from 'capture-core-utils/styles'; -import { MarkdownWrapper } from 'capture-core/components/WidgetFeedback/WidgetFeedbackContent/MarkdownWrapper'; +import { MarkdownWrapper } from './MarkdownWrapper'; import type { FilteredFeedbackKeyValue, FeedbackWidgetData, FilteredFeedbackText, FeedbackProps, } from '../WidgetFeedback.types'; - +import { useLegendSetsById } from './useLegendSetsById'; const styles = { container: { @@ -39,53 +38,12 @@ const styles = { }, }; -type Legend = { startValue: number; endValue: number; color: string }; -type LegendSetApiResponse = { legendSets: Array<{ id: string; legends: Legend[] }> }; - -const legendSetQuery = { - legendSets: { - resource: 'legendSets', - params: ({ ids }: Record) => ({ - fields: 'id,legends[id,startValue,endValue,color]', - filter: `id:in:[${ids}]`, - paging: false, - }), - }, -}; type Props = FeedbackProps & WithStyles; const WidgetFeedbackContentComponent = ({ feedback, feedbackEmptyText, classes }: Props) => { - const legendSetIds = useMemo(() => [...new Set( - (feedback ?? []) - .filter((rule): rule is FilteredFeedbackKeyValue => - typeof rule === 'object' && ('key' in rule || 'value' in rule)) - .map(rule => rule.legendSetId) - .filter((id): id is string => !!id), - )], [feedback]); - const { data: legendSetData } = useDataQuery(legendSetQuery, { - variables: { ids: legendSetIds.join(',') }, - lazy: legendSetIds.length === 0, - }); - - const legendSetsById = useMemo>(() => { - const map: Record = {}; - const response = legendSetData?.legendSets as LegendSetApiResponse | undefined; - (response?.legendSets ?? []).forEach((ls) => { - map[ls.id] = ls.legends; - }); - return map; - }, [legendSetData]); - - const resolveColor = (legendSetId: string | undefined, value: string): string | undefined => { - if (!legendSetId) return undefined; - const legends = legendSetsById[legendSetId]; - if (!legends?.length) return undefined; - const numeric = Number.parseFloat(value); - if (Number.isNaN(numeric)) return undefined; - return legends.find(l => numeric >= l.startValue && numeric < l.endValue)?.color; - }; + const { resolveColor } = useLegendSetsById({ feedback }); if (!feedback?.length) { return ( diff --git a/src/core_modules/capture-core/components/WidgetFeedback/WidgetFeedbackContent/useLegendSetsById.ts b/src/core_modules/capture-core/components/WidgetFeedback/WidgetFeedbackContent/useLegendSetsById.ts new file mode 100644 index 0000000000..b89eb0c58e --- /dev/null +++ b/src/core_modules/capture-core/components/WidgetFeedback/WidgetFeedbackContent/useLegendSetsById.ts @@ -0,0 +1,62 @@ +import { useMemo } from 'react'; +import { useDataQuery } from '@dhis2/app-runtime'; + +import type { FilteredFeedbackKeyValue, FeedbackProps } from '../WidgetFeedback.types'; + +type Legend = { startValue: number; endValue: number; color: string }; +type LegendSetApiResponse = { legendSets: Array<{ id: string; legends: Legend[] }> }; + +const legendSetQuery = { + legendSets: { + resource: 'legendSets', + params: ({ ids }: Record) => ({ + fields: 'id,legends[id,startValue,endValue,color]', + filter: `id:in:[${ids}]`, + paging: false, + }), + }, +}; + +export const useLegendSetsById = ({feedback}: { feedback: FeedbackProps['feedback'] }) => { + const legendSetIds = useMemo( + () => [ + ...new Set( + (feedback ?? []) + .filter( + (rule): rule is FilteredFeedbackKeyValue => + typeof rule === 'object' && ('key' in rule || 'value' in rule), + ) + .map(rule => rule.legendSetId) + .filter((id): id is string => !!id), + ), + ], + [feedback], + ); + + const {data: legendSetData} = useDataQuery(legendSetQuery, { + variables: {ids: legendSetIds.join(',')}, + lazy: legendSetIds.length === 0, + }); + + const legendSetsById = useMemo>(() => { + const map: Record = {}; + const response = legendSetData?.legendSets as LegendSetApiResponse | undefined; + (response?.legendSets ?? []).forEach((ls) => { + map[ls.id] = ls.legends; + }); + return map; + }, [legendSetData]); + + const resolveColor = (legendSetId: string | undefined, value: string): string | undefined => { + if (!legendSetId) return undefined; + const legends = legendSetsById[legendSetId]; + if (!legends?.length) return undefined; + const numeric = Number.parseFloat(value); + if (Number.isNaN(numeric)) return undefined; + return legends.find(l => numeric >= l.startValue && numeric < l.endValue)?.color; + }; + + return { + resolveColor, + }; +}; diff --git a/src/core_modules/capture-core/rules/RuleEngine/types/ruleEngine.types.ts b/src/core_modules/capture-core/rules/RuleEngine/types/ruleEngine.types.ts index 1c4a2156ec..9681673ffe 100644 --- a/src/core_modules/capture-core/rules/RuleEngine/types/ruleEngine.types.ts +++ b/src/core_modules/capture-core/rules/RuleEngine/types/ruleEngine.types.ts @@ -176,7 +176,7 @@ export type ProgramRuleAction = { data: string | null, programRuleActionType: string, priority?: number, - legendSetId?: string, + legendSetId?: string | null, } & ProgramRuleData; export type ProgramRule = { From 0176502e2946d3e3423276a768aaf16960cf57e5 Mon Sep 17 00:00:00 2001 From: Fernando Gomez Date: Thu, 18 Jun 2026 01:03:11 +0200 Subject: [PATCH 10/16] fix: [DHIS2-21266] Code format fix --- .../WidgetFeedbackContent/WidgetFeedbackContent.tsx | 2 -- .../WidgetFeedbackContent/useLegendSetsById.ts | 6 +++--- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/core_modules/capture-core/components/WidgetFeedback/WidgetFeedbackContent/WidgetFeedbackContent.tsx b/src/core_modules/capture-core/components/WidgetFeedback/WidgetFeedbackContent/WidgetFeedbackContent.tsx index a5bcafa817..be0b030453 100644 --- a/src/core_modules/capture-core/components/WidgetFeedback/WidgetFeedbackContent/WidgetFeedbackContent.tsx +++ b/src/core_modules/capture-core/components/WidgetFeedback/WidgetFeedbackContent/WidgetFeedbackContent.tsx @@ -40,9 +40,7 @@ const styles = { type Props = FeedbackProps & WithStyles; - const WidgetFeedbackContentComponent = ({ feedback, feedbackEmptyText, classes }: Props) => { - const { resolveColor } = useLegendSetsById({ feedback }); if (!feedback?.length) { diff --git a/src/core_modules/capture-core/components/WidgetFeedback/WidgetFeedbackContent/useLegendSetsById.ts b/src/core_modules/capture-core/components/WidgetFeedback/WidgetFeedbackContent/useLegendSetsById.ts index b89eb0c58e..f7017ddaad 100644 --- a/src/core_modules/capture-core/components/WidgetFeedback/WidgetFeedbackContent/useLegendSetsById.ts +++ b/src/core_modules/capture-core/components/WidgetFeedback/WidgetFeedbackContent/useLegendSetsById.ts @@ -17,7 +17,7 @@ const legendSetQuery = { }, }; -export const useLegendSetsById = ({feedback}: { feedback: FeedbackProps['feedback'] }) => { +export const useLegendSetsById = ({ feedback }: { feedback: FeedbackProps['feedback'] }) => { const legendSetIds = useMemo( () => [ ...new Set( @@ -33,8 +33,8 @@ export const useLegendSetsById = ({feedback}: { feedback: FeedbackProps['feedbac [feedback], ); - const {data: legendSetData} = useDataQuery(legendSetQuery, { - variables: {ids: legendSetIds.join(',')}, + const { data: legendSetData } = useDataQuery(legendSetQuery, { + variables: { ids: legendSetIds.join(',') }, lazy: legendSetIds.length === 0, }); From 85c13ebfc1a97d4e2c5dfc2fc70605d3b28b35ab Mon Sep 17 00:00:00 2001 From: Simona Domnisoru Date: Tue, 23 Jun 2026 10:55:41 +0200 Subject: [PATCH 11/16] test: adapt markadown scenarios for versions bellow 43 --- cypress/e2e/Markdown/Markdown.feature | 45 ++++++++++++++++++++------- cypress/e2e/Markdown/Markdown.js | 16 +++++----- i18n/en.pot | 4 +-- 3 files changed, 42 insertions(+), 23 deletions(-) diff --git a/cypress/e2e/Markdown/Markdown.feature b/cypress/e2e/Markdown/Markdown.feature index 13a04ebceb..f14043a59a 100644 --- a/cypress/e2e/Markdown/Markdown.feature +++ b/cypress/e2e/Markdown/Markdown.feature @@ -8,23 +8,44 @@ Feature: Feedback widget renders markdown content from program rules Given user lands on the enrollment edit event page by having typed #/enrollmentEventEdit?eventId=EKlWxFF9NPb Then the list should contain 4 items - Scenario: First feedback item renders headers markdown correctly + Scenario Outline: Headers feedback item renders markdown correctly Given user lands on the enrollment edit event page by having typed #/enrollmentEventEdit?eventId=EKlWxFF9NPb - Then the first list item should contain p with text "Headers" - And the first list item should contain h1 with text "Header 1" - And the first list item should contain h2 with text "Header 2" - And the first list item should contain h3 with text "Header 3" - And the first list item should contain h4 with text "Header 4" - And the first list item should contain h5 with text "Header 5" - And the first list item should contain h6 with text "Header 6" - - Scenario: Second feedback item renders italic heading and table correctly + Then the list item should contain p with text "Headers" + And the list item should contain h1 with text "Header 1" + And the list item should contain h2 with text "Header 2" + And the list item should contain h3 with text "Header 3" + And the list item should contain h4 with text "Header 4" + And the list item should contain h5 with text "Header 5" + And the list item should contain h6 with text "Header 6" + + @v>=43 + Examples: + | n | + | first | + + @v<43 + Examples: + | n | + | second | + + Scenario Outline: Italic heading and table feedback item renders correctly Given user lands on the enrollment edit event page by having typed #/enrollmentEventEdit?eventId=EKlWxFF9NPb - Then the second list item h6 should contain italic text "Feedback" - And the second list item table should match: + Then the list item h6 should contain italic text "Feedback" + And the list item table should match: | Column A | Column B | | Content A | Content B | + @v>=43 + Examples: + | n | + | second | + + @v<43 + Examples: + | n | + | first | + + @v>=43 Scenario: Fourth feedback item renders score with color indicator correctly Given user lands on the enrollment edit event page by having typed #/enrollmentEventEdit?eventId=EKlWxFF9NPb Then the fourth list item left section should have h1 with text "1. Score 1" diff --git a/cypress/e2e/Markdown/Markdown.js b/cypress/e2e/Markdown/Markdown.js index 0b2df3d362..11f183f876 100644 --- a/cypress/e2e/Markdown/Markdown.js +++ b/cypress/e2e/Markdown/Markdown.js @@ -30,27 +30,25 @@ const getListItem = (n) => .find('ul li') .eq(n - 1); -// FIRST FEEDBACK ELEMENT +const ordinalToIndex = { first: 1, second: 2, third: 3, fourth: 4 }; -And('the first list item should contain {word} with text {string}', (tag, text) => { - getListItem(1) +And('the {word} list item should contain {word} with text {string}', (ordinal, tag, text) => { + getListItem(ordinalToIndex[ordinal]) .find(tag) .should('have.text', text); }); -// SECOND FEEDBACK ELEMENT - -Then('the second list item {word} should contain italic text {string}', (tag, text) => { - getListItem(2) +Then('the {word} list item {word} should contain italic text {string}', (ordinal, tag, text) => { + getListItem(ordinalToIndex[ordinal]) .find(tag) .find('em') .should('have.text', text); }); -And('the second list item table should match:', (dataTable) => { +And('the {word} list item table should match:', (ordinal, dataTable) => { const [headerRow, ...bodyRows] = dataTable.raw(); - getListItem(2).find('table').within(() => { + getListItem(ordinalToIndex[ordinal]).find('table').within(() => { cy.get('thead th').each(($th, i) => { expect($th.text()).to.equal(headerRow[i]); }); diff --git a/i18n/en.pot b/i18n/en.pot index adbdf47167..317f2614d6 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -5,8 +5,8 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -"POT-Creation-Date: 2026-06-17T22:59:57.403Z\n" -"PO-Revision-Date: 2026-06-17T22:59:57.404Z\n" +"POT-Creation-Date: 2026-06-23T08:54:39.839Z\n" +"PO-Revision-Date: 2026-06-23T08:54:39.839Z\n" msgid "The application could not be loaded." msgstr "The application could not be loaded." From 48bb641f145ae32bf1be1abc69c6654fda2a59d1 Mon Sep 17 00:00:00 2001 From: Fernando Gomez Date: Thu, 25 Jun 2026 18:45:54 -0600 Subject: [PATCH 12/16] fix: [DHIS2-21266] Update MarkdownWrapper & remove unused params - Wrap MarkdownWrapper component with React.memo - Remove unused priority parameters at rulesEffectsProcessor --- .../MarkdownWrapper/MarkdownWrapper.tsx | 13 ++++++++----- .../RuleEngine/helpers/rulesEffectsProcessor.ts | 2 -- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/core_modules/capture-core/components/WidgetFeedback/WidgetFeedbackContent/MarkdownWrapper/MarkdownWrapper.tsx b/src/core_modules/capture-core/components/WidgetFeedback/WidgetFeedbackContent/MarkdownWrapper/MarkdownWrapper.tsx index 1b602aaa3a..ff2b11e5a7 100644 --- a/src/core_modules/capture-core/components/WidgetFeedback/WidgetFeedbackContent/MarkdownWrapper/MarkdownWrapper.tsx +++ b/src/core_modules/capture-core/components/WidgetFeedback/WidgetFeedbackContent/MarkdownWrapper/MarkdownWrapper.tsx @@ -1,4 +1,4 @@ -import React, { ComponentType } from 'react'; +import React, { ComponentType, memo } from 'react'; import { spacersNum, colors } from '@dhis2/ui'; import Markdown from 'react-markdown'; import { withStyles, type WithStyles } from 'capture-core-utils/styles'; @@ -144,23 +144,26 @@ const styles = { }, }, }; +const REMARK_PLUGINS = [remarkGfm]; type Props = MarkdownWrapperProps & WithStyles; -const MarkdownWrapperComponent = ({ title, content, color, classes }:Props) => ( +const MarkdownWrapperComponent = memo(({ title, content, color, classes }:Props) => (
    -
    {title}
    +
    + {title} +
    { content &&
    { color && }
    - {content} + {content}
    }
    -); +)); export const MarkdownWrapper = withStyles(styles)(MarkdownWrapperComponent) as ComponentType; diff --git a/src/core_modules/capture-core/rules/RuleEngine/helpers/rulesEffectsProcessor.ts b/src/core_modules/capture-core/rules/RuleEngine/helpers/rulesEffectsProcessor.ts index 36162b02b9..b80b14a712 100644 --- a/src/core_modules/capture-core/rules/RuleEngine/helpers/rulesEffectsProcessor.ts +++ b/src/core_modules/capture-core/rules/RuleEngine/helpers/rulesEffectsProcessor.ts @@ -175,7 +175,6 @@ export function getRulesEffectsProcessor( displayText: { id: effect.id, message: `${message} ${sanitiseFalsy(effect.data)}`, - priority: effect.priority, legendSetId: effect.legendSetId, ...effect.style, }, @@ -192,7 +191,6 @@ export function getRulesEffectsProcessor( id: effect.id, key: effect.displayContent, value: typeof data === 'number' ? numberToString(data) : String(data), - priority: effect.priority, legendSetId: effect.legendSetId, ...effect.style, }, From 295d2b1e4ed08b5e88a2d5a08387ea56fb7b6b35 Mon Sep 17 00:00:00 2001 From: Simona Domnisoru Date: Mon, 29 Jun 2026 10:48:55 +0200 Subject: [PATCH 13/16] chore: cleanup dead code --- i18n/en.pot | 4 ++-- .../WidgetFeedback/WidgetFeedback.types.ts | 2 +- .../WidgetFeedbackContent/WidgetFeedbackContent.tsx | 13 +------------ .../WidgetIndicator/WidgetIndicator.types.ts | 2 +- .../WidgetIndicatorContent.tsx | 11 ----------- .../WidgetIndicatorContent/sortIndicatorsFn.ts | 3 --- 6 files changed, 5 insertions(+), 30 deletions(-) diff --git a/i18n/en.pot b/i18n/en.pot index 55f0ebbd10..0c422f04c3 100644 --- a/i18n/en.pot +++ b/i18n/en.pot @@ -5,8 +5,8 @@ msgstr "" "Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" -"POT-Creation-Date: 2026-06-29T07:53:44.747Z\n" -"PO-Revision-Date: 2026-06-29T07:53:44.748Z\n" +"POT-Creation-Date: 2026-06-29T08:48:59.471Z\n" +"PO-Revision-Date: 2026-06-29T08:48:59.472Z\n" msgid "The application could not be loaded." msgstr "The application could not be loaded." diff --git a/src/core_modules/capture-core/components/WidgetFeedback/WidgetFeedback.types.ts b/src/core_modules/capture-core/components/WidgetFeedback/WidgetFeedback.types.ts index 74b782beb5..76e3e14df0 100644 --- a/src/core_modules/capture-core/components/WidgetFeedback/WidgetFeedback.types.ts +++ b/src/core_modules/capture-core/components/WidgetFeedback/WidgetFeedback.types.ts @@ -15,7 +15,7 @@ export type FilteredFeedbackKeyValue = { legendSetId?: string; } -export type FeedbackWidgetData = string | FilteredFeedbackText | FilteredFeedbackKeyValue; +export type FeedbackWidgetData = FilteredFeedbackText | FilteredFeedbackKeyValue; export type FeedbackInputProps = { widgetEffects?: Record; diff --git a/src/core_modules/capture-core/components/WidgetFeedback/WidgetFeedbackContent/WidgetFeedbackContent.tsx b/src/core_modules/capture-core/components/WidgetFeedback/WidgetFeedbackContent/WidgetFeedbackContent.tsx index be0b030453..67df67e402 100644 --- a/src/core_modules/capture-core/components/WidgetFeedback/WidgetFeedbackContent/WidgetFeedbackContent.tsx +++ b/src/core_modules/capture-core/components/WidgetFeedback/WidgetFeedbackContent/WidgetFeedbackContent.tsx @@ -75,30 +75,19 @@ const WidgetFeedbackContentComponent = ({ feedback, feedbackEmptyText, classes } ); }; - const renderString = (item: string, index: number) => ( -
  • - {item} -
  • - ); - return (