Skip to content

Conversation

@CynthiaKamau
Copy link
Contributor

@CynthiaKamau CynthiaKamau commented Jul 14, 2025

Requirements

  • This PR has a title that briefly describes the work done including the ticket number. Ensure your PR title includes a conventional commit label (such as feat, fix, or chore, among others). See existing PR titles for inspiration.

For changes to apps

If applicable

  • My work includes tests or is validated by existing tests.
  • I have updated the esm-framework mock to reflect any API changes I have made.

Summary

Add ability to evaluate expressions in extensions

Screenshots

Screen.Recording.2025-07-14.at.13.54.37.mov

Related Issue

Other

@github-actions
Copy link
Contributor

github-actions bot commented Jul 14, 2025

Size Change: -163 kB (-2.49%)

Total Size: 6.4 MB

Filename Size Change
packages/shell/esm-app-shell/dist/28a16dd14d0080c1.js 0 B -51.3 kB (removed) 🏆
packages/shell/esm-app-shell/dist/9469807bb74be638.js 0 B -29.1 kB (removed) 🏆
packages/shell/esm-app-shell/dist/e3a4c147cdc68510.js 0 B -11.4 kB (removed) 🏆
packages/shell/esm-app-shell/dist/ea49896a6a7d77d0.js 0 B -36.9 kB (removed) 🏆
packages/shell/esm-app-shell/dist/openmrs.9e6772c0ca0d26e7.js 0 B -22.2 kB (removed) 🏆
ℹ️ View Unchanged
Filename Size Change
packages/apps/esm-devtools-app/dist/180.js 12.3 kB 0 B
packages/apps/esm-devtools-app/dist/185.js 9.85 kB 0 B
packages/apps/esm-devtools-app/dist/326.js 2.63 kB 0 B
packages/apps/esm-devtools-app/dist/523.js 185 kB 0 B
packages/apps/esm-devtools-app/dist/623.js 42.7 kB 0 B
packages/apps/esm-devtools-app/dist/629.js 195 kB +272 B (+0.14%)
packages/apps/esm-devtools-app/dist/679.js 277 kB 0 B
packages/apps/esm-devtools-app/dist/770.js 11.2 kB 0 B
packages/apps/esm-devtools-app/dist/907.js 2.33 kB 0 B
packages/apps/esm-devtools-app/dist/929.js 334 B 0 B
packages/apps/esm-devtools-app/dist/939.js 5.78 kB 0 B
packages/apps/esm-devtools-app/dist/main.js 3.62 kB 0 B
packages/apps/esm-devtools-app/dist/openmrs-esm-devtools-app.js 3.68 kB 0 B
packages/apps/esm-help-menu-app/dist/185.js 8.61 kB 0 B
packages/apps/esm-help-menu-app/dist/301.js 275 kB 0 B
packages/apps/esm-help-menu-app/dist/322.js 701 B 0 B
packages/apps/esm-help-menu-app/dist/326.js 2.64 kB 0 B
packages/apps/esm-help-menu-app/dist/555.js 5.15 kB 0 B
packages/apps/esm-help-menu-app/dist/611.js 185 kB 0 B
packages/apps/esm-help-menu-app/dist/623.js 42.8 kB 0 B
packages/apps/esm-help-menu-app/dist/629.js 195 kB +275 B (+0.14%)
packages/apps/esm-help-menu-app/dist/7.js 3.41 kB 0 B
packages/apps/esm-help-menu-app/dist/76.js 469 B 0 B
packages/apps/esm-help-menu-app/dist/770.js 11.2 kB 0 B
packages/apps/esm-help-menu-app/dist/802.js 1.64 kB 0 B
packages/apps/esm-help-menu-app/dist/90.js 10.4 kB 0 B
packages/apps/esm-help-menu-app/dist/939.js 5.78 kB 0 B
packages/apps/esm-help-menu-app/dist/main.js 8.39 kB 0 B
packages/apps/esm-help-menu-app/dist/openmrs-esm-help-menu-app.js 3.62 kB 0 B
packages/apps/esm-implementer-tools-app/dist/1119.js 687 B 0 B
packages/apps/esm-implementer-tools-app/dist/1197.js 687 B 0 B
packages/apps/esm-implementer-tools-app/dist/1405.js 3.09 kB 0 B
packages/apps/esm-implementer-tools-app/dist/1736.js 258 kB 0 B
packages/apps/esm-implementer-tools-app/dist/1915.js 32.4 kB 0 B
packages/apps/esm-implementer-tools-app/dist/2146.js 688 B 0 B
packages/apps/esm-implementer-tools-app/dist/2369.js 2.74 kB 0 B
packages/apps/esm-implementer-tools-app/dist/2690.js 688 B 0 B
packages/apps/esm-implementer-tools-app/dist/3099.js 799 B 0 B
packages/apps/esm-implementer-tools-app/dist/3185.js 9.85 kB 0 B
packages/apps/esm-implementer-tools-app/dist/3584.js 687 B 0 B
packages/apps/esm-implementer-tools-app/dist/3629.js 195 kB +261 B (+0.13%)
packages/apps/esm-implementer-tools-app/dist/3709.js 3.05 kB 0 B
packages/apps/esm-implementer-tools-app/dist/4055.js 819 B 0 B
packages/apps/esm-implementer-tools-app/dist/4132.js 890 B 0 B
packages/apps/esm-implementer-tools-app/dist/4300.js 687 B 0 B
packages/apps/esm-implementer-tools-app/dist/4335.js 686 B 0 B
packages/apps/esm-implementer-tools-app/dist/4618.js 688 B 0 B
packages/apps/esm-implementer-tools-app/dist/4652.js 830 B 0 B
packages/apps/esm-implementer-tools-app/dist/4944.js 687 B 0 B
packages/apps/esm-implementer-tools-app/dist/5173.js 688 B 0 B
packages/apps/esm-implementer-tools-app/dist/523.js 185 kB 0 B
packages/apps/esm-implementer-tools-app/dist/5241.js 687 B 0 B
packages/apps/esm-implementer-tools-app/dist/5442.js 687 B 0 B
packages/apps/esm-implementer-tools-app/dist/5444.js 4.34 kB 0 B
packages/apps/esm-implementer-tools-app/dist/5563.js 13 kB 0 B
packages/apps/esm-implementer-tools-app/dist/5623.js 42.8 kB 0 B
packages/apps/esm-implementer-tools-app/dist/5661.js 864 B 0 B
packages/apps/esm-implementer-tools-app/dist/5770.js 11.2 kB 0 B
packages/apps/esm-implementer-tools-app/dist/5839.js 147 kB 0 B
packages/apps/esm-implementer-tools-app/dist/6022.js 820 B 0 B
packages/apps/esm-implementer-tools-app/dist/6090.js 3.38 kB 0 B
packages/apps/esm-implementer-tools-app/dist/6132.js 87.9 kB 0 B
packages/apps/esm-implementer-tools-app/dist/6269.js 2.59 kB 0 B
packages/apps/esm-implementer-tools-app/dist/6326.js 2.64 kB 0 B
packages/apps/esm-implementer-tools-app/dist/6468.js 801 B 0 B
packages/apps/esm-implementer-tools-app/dist/6679.js 799 B 0 B
packages/apps/esm-implementer-tools-app/dist/6840.js 688 B 0 B
packages/apps/esm-implementer-tools-app/dist/6859.js 687 B 0 B
packages/apps/esm-implementer-tools-app/dist/7097.js 687 B 0 B
packages/apps/esm-implementer-tools-app/dist/7159.js 688 B 0 B
packages/apps/esm-implementer-tools-app/dist/723.js 690 B 0 B
packages/apps/esm-implementer-tools-app/dist/7617.js 688 B 0 B
packages/apps/esm-implementer-tools-app/dist/795.js 979 B 0 B
packages/apps/esm-implementer-tools-app/dist/8163.js 688 B 0 B
packages/apps/esm-implementer-tools-app/dist/8349.js 687 B 0 B
packages/apps/esm-implementer-tools-app/dist/8611.js 4.71 kB 0 B
packages/apps/esm-implementer-tools-app/dist/8618.js 687 B 0 B
packages/apps/esm-implementer-tools-app/dist/8650.js 2.25 kB 0 B
packages/apps/esm-implementer-tools-app/dist/890.js 688 B 0 B
packages/apps/esm-implementer-tools-app/dist/8995.js 4.59 kB 0 B
packages/apps/esm-implementer-tools-app/dist/8e7b152564424838.js 7.04 kB 0 B
packages/apps/esm-implementer-tools-app/dist/9214.js 827 B 0 B
packages/apps/esm-implementer-tools-app/dist/9361.js 7.95 kB 0 B
packages/apps/esm-implementer-tools-app/dist/939.js 5.79 kB 0 B
packages/apps/esm-implementer-tools-app/dist/9538.js 781 B 0 B
packages/apps/esm-implementer-tools-app/dist/9569.js 686 B 0 B
packages/apps/esm-implementer-tools-app/dist/986.js 688 B 0 B
packages/apps/esm-implementer-tools-app/dist/9879.js 873 B 0 B
packages/apps/esm-implementer-tools-app/dist/9895.js 686 B 0 B
packages/apps/esm-implementer-tools-app/dist/9900.js 685 B 0 B
packages/apps/esm-implementer-tools-app/dist/9913.js 687 B 0 B
packages/apps/esm-implementer-tools-app/dist/main.js 20.6 kB 0 B
packages/apps/esm-implementer-tools-app/dist/openmrs-esm-implementer-tools-app.js 3.94 kB 0 B
packages/apps/esm-login-app/dist/126.js 856 B 0 B
packages/apps/esm-login-app/dist/15.js 766 B 0 B
packages/apps/esm-login-app/dist/1564.js 768 B 0 B
packages/apps/esm-login-app/dist/1567.js 1.01 kB 0 B
packages/apps/esm-login-app/dist/1805.js 22.7 kB 0 B
packages/apps/esm-login-app/dist/1845.js 768 B 0 B
packages/apps/esm-login-app/dist/2039.js 2.21 kB 0 B
packages/apps/esm-login-app/dist/215.js 890 B 0 B
packages/apps/esm-login-app/dist/2178.js 768 B 0 B
packages/apps/esm-login-app/dist/2566.js 905 B 0 B
packages/apps/esm-login-app/dist/2759.js 807 B 0 B
packages/apps/esm-login-app/dist/3230.js 928 B 0 B
packages/apps/esm-login-app/dist/3382.js 9.76 kB 0 B
packages/apps/esm-login-app/dist/3441.js 766 B 0 B
packages/apps/esm-login-app/dist/3565.js 768 B 0 B
packages/apps/esm-login-app/dist/3746.js 768 B 0 B
packages/apps/esm-login-app/dist/3925.js 959 B 0 B
packages/apps/esm-login-app/dist/3946.js 768 B 0 B
packages/apps/esm-login-app/dist/3965.js 478 B 0 B
packages/apps/esm-login-app/dist/4538.js 187 kB 0 B
packages/apps/esm-login-app/dist/4894.js 768 B 0 B
packages/apps/esm-login-app/dist/4996.js 246 kB 0 B
packages/apps/esm-login-app/dist/5130.js 768 B 0 B
packages/apps/esm-login-app/dist/5187.js 1.04 kB 0 B
packages/apps/esm-login-app/dist/5595.js 800 B 0 B
packages/apps/esm-login-app/dist/5729.js 3 kB 0 B
packages/apps/esm-login-app/dist/5755.js 11.1 kB 0 B
packages/apps/esm-login-app/dist/5961.js 767 B 0 B
packages/apps/esm-login-app/dist/6133.js 931 B 0 B
packages/apps/esm-login-app/dist/6456.js 944 B 0 B
packages/apps/esm-login-app/dist/6466.js 843 B 0 B
packages/apps/esm-login-app/dist/6613.js 854 B 0 B
packages/apps/esm-login-app/dist/6778.js 2.59 kB 0 B
packages/apps/esm-login-app/dist/6783.js 956 B 0 B
packages/apps/esm-login-app/dist/6845.js 4.22 kB 0 B
packages/apps/esm-login-app/dist/7348.js 768 B 0 B
packages/apps/esm-login-app/dist/7362.js 2.58 kB 0 B
packages/apps/esm-login-app/dist/7543.js 768 B 0 B
packages/apps/esm-login-app/dist/7559.js 5.58 kB 0 B
packages/apps/esm-login-app/dist/7607.js 767 B 0 B
packages/apps/esm-login-app/dist/772.js 924 B 0 B
packages/apps/esm-login-app/dist/7760.js 186 kB +287 B (+0.15%)
packages/apps/esm-login-app/dist/8370.js 16.2 kB 0 B
packages/apps/esm-login-app/dist/8450.js 27.3 kB 0 B
packages/apps/esm-login-app/dist/8727.js 794 B 0 B
packages/apps/esm-login-app/dist/8847.js 898 B 0 B
packages/apps/esm-login-app/dist/9015.js 768 B 0 B
packages/apps/esm-login-app/dist/9042.js 42.5 kB 0 B
packages/apps/esm-login-app/dist/906.js 1.07 kB 0 B
packages/apps/esm-login-app/dist/9065.js 783 B 0 B
packages/apps/esm-login-app/dist/9182.js 787 B 0 B
packages/apps/esm-login-app/dist/9339.js 768 B 0 B
packages/apps/esm-login-app/dist/936.js 477 B 0 B
packages/apps/esm-login-app/dist/9453.js 1.14 kB 0 B
packages/apps/esm-login-app/dist/9919.js 3.05 kB 0 B
packages/apps/esm-login-app/dist/9920.js 768 B 0 B
packages/apps/esm-login-app/dist/9938.js 1.04 kB 0 B
packages/apps/esm-login-app/dist/main.js 76.1 kB 0 B
packages/apps/esm-login-app/dist/openmrs-esm-login-app.js 24.4 kB 0 B
packages/apps/esm-offline-tools-app/dist/1119.js 1.03 kB 0 B
packages/apps/esm-offline-tools-app/dist/1197.js 1.02 kB 0 B
packages/apps/esm-offline-tools-app/dist/1405.js 3.09 kB 0 B
packages/apps/esm-offline-tools-app/dist/2062.js 8.12 kB 0 B
packages/apps/esm-offline-tools-app/dist/2070.js 1.53 kB 0 B
packages/apps/esm-offline-tools-app/dist/2146.js 1.03 kB 0 B
packages/apps/esm-offline-tools-app/dist/2690.js 1.09 kB 0 B
packages/apps/esm-offline-tools-app/dist/3099.js 1.16 kB 0 B
packages/apps/esm-offline-tools-app/dist/3584.js 1.03 kB 0 B
packages/apps/esm-offline-tools-app/dist/3629.js 195 kB +269 B (+0.14%)
packages/apps/esm-offline-tools-app/dist/3709.js 3.05 kB 0 B
packages/apps/esm-offline-tools-app/dist/3963.js 4.09 kB 0 B
packages/apps/esm-offline-tools-app/dist/4055.js 1.19 kB 0 B
packages/apps/esm-offline-tools-app/dist/4132.js 1.3 kB 0 B
packages/apps/esm-offline-tools-app/dist/4300.js 1.03 kB 0 B
packages/apps/esm-offline-tools-app/dist/4335.js 1.1 kB 0 B
packages/apps/esm-offline-tools-app/dist/4618.js 1.03 kB 0 B
packages/apps/esm-offline-tools-app/dist/4652.js 1.21 kB 0 B
packages/apps/esm-offline-tools-app/dist/4944.js 1.03 kB 0 B
packages/apps/esm-offline-tools-app/dist/5173.js 1.03 kB 0 B
packages/apps/esm-offline-tools-app/dist/5241.js 1.03 kB 0 B
packages/apps/esm-offline-tools-app/dist/5442.js 1.03 kB 0 B
packages/apps/esm-offline-tools-app/dist/5623.js 42.8 kB 0 B
packages/apps/esm-offline-tools-app/dist/5661.js 1.19 kB 0 B
packages/apps/esm-offline-tools-app/dist/5725.js 8.9 kB 0 B
packages/apps/esm-offline-tools-app/dist/5770.js 11.2 kB 0 B
packages/apps/esm-offline-tools-app/dist/6022.js 1.11 kB 0 B
packages/apps/esm-offline-tools-app/dist/6090.js 3.38 kB 0 B
packages/apps/esm-offline-tools-app/dist/6252.js 44.5 kB 0 B
packages/apps/esm-offline-tools-app/dist/6269.js 2.59 kB 0 B
packages/apps/esm-offline-tools-app/dist/6326.js 2.64 kB 0 B
packages/apps/esm-offline-tools-app/dist/6408.js 16.5 kB 0 B
packages/apps/esm-offline-tools-app/dist/6468.js 1.17 kB 0 B
packages/apps/esm-offline-tools-app/dist/6516.js 2.28 kB 0 B
packages/apps/esm-offline-tools-app/dist/6679.js 1.17 kB 0 B
packages/apps/esm-offline-tools-app/dist/6840.js 1.03 kB 0 B
packages/apps/esm-offline-tools-app/dist/6859.js 1.03 kB 0 B
packages/apps/esm-offline-tools-app/dist/7031.js 261 kB 0 B
packages/apps/esm-offline-tools-app/dist/7097.js 1.03 kB 0 B
packages/apps/esm-offline-tools-app/dist/7159.js 1.03 kB 0 B
packages/apps/esm-offline-tools-app/dist/723.js 1.03 kB 0 B
packages/apps/esm-offline-tools-app/dist/7617.js 1.03 kB 0 B
packages/apps/esm-offline-tools-app/dist/795.js 1.37 kB 0 B
packages/apps/esm-offline-tools-app/dist/8163.js 1.03 kB 0 B
packages/apps/esm-offline-tools-app/dist/8349.js 1.03 kB 0 B
packages/apps/esm-offline-tools-app/dist/8618.js 1.03 kB 0 B
packages/apps/esm-offline-tools-app/dist/8650.js 2.24 kB 0 B
packages/apps/esm-offline-tools-app/dist/890.js 1.03 kB 0 B
packages/apps/esm-offline-tools-app/dist/9214.js 1.28 kB 0 B
packages/apps/esm-offline-tools-app/dist/939.js 5.78 kB 0 B
packages/apps/esm-offline-tools-app/dist/9538.js 1.16 kB 0 B
packages/apps/esm-offline-tools-app/dist/9561.js 181 kB 0 B
packages/apps/esm-offline-tools-app/dist/9569.js 1.03 kB 0 B
packages/apps/esm-offline-tools-app/dist/986.js 1.03 kB 0 B
packages/apps/esm-offline-tools-app/dist/9879.js 1.19 kB 0 B
packages/apps/esm-offline-tools-app/dist/9895.js 1.03 kB 0 B
packages/apps/esm-offline-tools-app/dist/9900.js 1.03 kB 0 B
packages/apps/esm-offline-tools-app/dist/9913.js 1.03 kB 0 B
packages/apps/esm-offline-tools-app/dist/main.js 76.1 kB 0 B
packages/apps/esm-offline-tools-app/dist/openmrs-esm-offline-tools-app.js 3.93 kB 0 B
packages/apps/esm-primary-navigation-app/dist/1119.js 411 B 0 B
packages/apps/esm-primary-navigation-app/dist/1197.js 411 B 0 B
packages/apps/esm-primary-navigation-app/dist/1296.js 8.36 kB 0 B
packages/apps/esm-primary-navigation-app/dist/2106.js 18.2 kB 0 B
packages/apps/esm-primary-navigation-app/dist/2146.js 412 B 0 B
packages/apps/esm-primary-navigation-app/dist/2267.js 6.45 kB 0 B
packages/apps/esm-primary-navigation-app/dist/2690.js 421 B 0 B
packages/apps/esm-primary-navigation-app/dist/2727.js 1.91 kB 0 B
packages/apps/esm-primary-navigation-app/dist/3099.js 437 B 0 B
packages/apps/esm-primary-navigation-app/dist/3584.js 411 B 0 B
packages/apps/esm-primary-navigation-app/dist/3629.js 195 kB +254 B (+0.13%)
packages/apps/esm-primary-navigation-app/dist/4055.js 455 B 0 B
packages/apps/esm-primary-navigation-app/dist/4132.js 449 B 0 B
packages/apps/esm-primary-navigation-app/dist/4300.js 411 B 0 B
packages/apps/esm-primary-navigation-app/dist/4335.js 479 B 0 B
packages/apps/esm-primary-navigation-app/dist/4618.js 411 B 0 B
packages/apps/esm-primary-navigation-app/dist/4652.js 465 B 0 B
packages/apps/esm-primary-navigation-app/dist/4740.js 5.04 kB 0 B
packages/apps/esm-primary-navigation-app/dist/4944.js 411 B 0 B
packages/apps/esm-primary-navigation-app/dist/5173.js 411 B 0 B
packages/apps/esm-primary-navigation-app/dist/523.js 185 kB 0 B
packages/apps/esm-primary-navigation-app/dist/5241.js 411 B 0 B
packages/apps/esm-primary-navigation-app/dist/5442.js 411 B 0 B
packages/apps/esm-primary-navigation-app/dist/5623.js 42.8 kB 0 B
packages/apps/esm-primary-navigation-app/dist/5661.js 462 B 0 B
packages/apps/esm-primary-navigation-app/dist/5770.js 11.2 kB 0 B
packages/apps/esm-primary-navigation-app/dist/6022.js 469 B 0 B
packages/apps/esm-primary-navigation-app/dist/6326.js 2.64 kB 0 B
packages/apps/esm-primary-navigation-app/dist/6408.js 16.5 kB 0 B
packages/apps/esm-primary-navigation-app/dist/6468.js 441 B 0 B
packages/apps/esm-primary-navigation-app/dist/6537.js 4.25 kB 0 B
packages/apps/esm-primary-navigation-app/dist/6679.js 438 B 0 B
packages/apps/esm-primary-navigation-app/dist/6840.js 412 B 0 B
packages/apps/esm-primary-navigation-app/dist/6859.js 411 B 0 B
packages/apps/esm-primary-navigation-app/dist/7097.js 411 B 0 B
packages/apps/esm-primary-navigation-app/dist/7159.js 412 B 0 B
packages/apps/esm-primary-navigation-app/dist/723.js 413 B 0 B
packages/apps/esm-primary-navigation-app/dist/7617.js 411 B 0 B
packages/apps/esm-primary-navigation-app/dist/7798.js 268 kB 0 B
packages/apps/esm-primary-navigation-app/dist/795.js 524 B 0 B
packages/apps/esm-primary-navigation-app/dist/8163.js 411 B 0 B
packages/apps/esm-primary-navigation-app/dist/8349.js 411 B 0 B
packages/apps/esm-primary-navigation-app/dist/8412.js 8.49 kB 0 B
packages/apps/esm-primary-navigation-app/dist/8618.js 411 B 0 B
packages/apps/esm-primary-navigation-app/dist/890.js 412 B 0 B
packages/apps/esm-primary-navigation-app/dist/9214.js 465 B 0 B
packages/apps/esm-primary-navigation-app/dist/939.js 5.79 kB 0 B
packages/apps/esm-primary-navigation-app/dist/9538.js 449 B 0 B
packages/apps/esm-primary-navigation-app/dist/9569.js 410 B 0 B
packages/apps/esm-primary-navigation-app/dist/986.js 412 B 0 B
packages/apps/esm-primary-navigation-app/dist/9879.js 476 B 0 B
packages/apps/esm-primary-navigation-app/dist/9895.js 409 B 0 B
packages/apps/esm-primary-navigation-app/dist/9900.js 409 B 0 B
packages/apps/esm-primary-navigation-app/dist/9913.js 411 B 0 B
packages/apps/esm-primary-navigation-app/dist/main.js 26.3 kB 0 B
packages/apps/esm-primary-navigation-app/dist/openmrs-esm-primary-navigation-app.js 3.79 kB 0 B
packages/framework/esm-api/dist/config-schema.js 580 B 0 B
packages/framework/esm-api/dist/current-user.js 1.93 kB 0 B
packages/framework/esm-api/dist/environment.js 134 B 0 B
packages/framework/esm-api/dist/index.js 119 B 0 B
packages/framework/esm-api/dist/openmrs-backend-dependencies.js 119 B 0 B
packages/framework/esm-api/dist/openmrs-fetch.js 3.75 kB 0 B
packages/framework/esm-api/dist/public.js 201 B 0 B
packages/framework/esm-api/dist/setup.js 188 B 0 B
packages/framework/esm-api/dist/types/concept-resource.js 32 B 0 B
packages/framework/esm-api/dist/types/fetch.js 32 B 0 B
packages/framework/esm-api/dist/types/index.js 92 B 0 B
packages/framework/esm-api/dist/types/openmrs-resource.js 32 B 0 B
packages/framework/esm-api/dist/types/person-resource.js 32 B 0 B
packages/framework/esm-api/dist/types/user-resource.js 92 B 0 B
packages/framework/esm-config/dist/index.js 91 B 0 B
packages/framework/esm-config/dist/module-config/module-config.js 8.22 kB +502 B (+6.5%) 🔍
packages/framework/esm-config/dist/module-config/state.js 1.17 kB 0 B
packages/framework/esm-config/dist/public.js 147 B 0 B
packages/framework/esm-config/dist/types.js 210 B 0 B
packages/framework/esm-config/dist/validators/type-validators.js 299 B 0 B
packages/framework/esm-config/dist/validators/validator.js 431 B 0 B
packages/framework/esm-config/dist/validators/validators.js 726 B 0 B
packages/framework/esm-context/dist/context.js 1.09 kB 0 B
packages/framework/esm-context/dist/index.js 50 B 0 B
packages/framework/esm-context/dist/public.js 50 B 0 B
packages/framework/esm-dynamic-loading/dist/dynamic-loading.js 2.95 kB 0 B
packages/framework/esm-dynamic-loading/dist/index.js 58 B 0 B
packages/framework/esm-dynamic-loading/dist/public.js 67 B 0 B
packages/framework/esm-emr-api/dist/attachments.js 466 B 0 B
packages/framework/esm-emr-api/dist/current-patient.js 461 B 0 B
packages/framework/esm-emr-api/dist/index.js 110 B 0 B
packages/framework/esm-emr-api/dist/location.js 422 B 0 B
packages/framework/esm-emr-api/dist/public.js 109 B 0 B
packages/framework/esm-emr-api/dist/types/attachments-types.js 32 B 0 B
packages/framework/esm-emr-api/dist/types/diagnosis-resource.js 81 B 0 B
packages/framework/esm-emr-api/dist/types/encounter-resource.js 32 B 0 B
packages/framework/esm-emr-api/dist/types/fhir-resource.js 32 B 0 B
packages/framework/esm-emr-api/dist/types/fhir.js 240 B 0 B
packages/framework/esm-emr-api/dist/types/index.js 123 B 0 B
packages/framework/esm-emr-api/dist/types/location-resource.js 32 B 0 B
packages/framework/esm-emr-api/dist/types/obs-resource.js 32 B 0 B
packages/framework/esm-emr-api/dist/types/patient-resource.js 32 B 0 B
packages/framework/esm-emr-api/dist/types/visit-resource.js 32 B 0 B
packages/framework/esm-emr-api/dist/visit-type.js 310 B 0 B
packages/framework/esm-emr-api/dist/visit-utils.js 990 B 0 B
packages/framework/esm-error-handling/dist/index.js 628 B 0 B
packages/framework/esm-expression-evaluator/dist/evaluator.js 5.07 kB +34 B (+0.68%)
packages/framework/esm-expression-evaluator/dist/extractor.js 1.54 kB 0 B
packages/framework/esm-expression-evaluator/dist/globals.js 283 B 0 B
packages/framework/esm-expression-evaluator/dist/index.js 62 B 0 B
packages/framework/esm-expression-evaluator/dist/public.js 136 B 0 B
packages/framework/esm-extensions/dist/extensions.js 3.66 kB +330 B (+9.92%) ⚠️
packages/framework/esm-extensions/dist/helpers.js 165 B 0 B
packages/framework/esm-extensions/dist/index.js 112 B 0 B
packages/framework/esm-extensions/dist/left-nav.js 439 B +7 B (+1.62%)
packages/framework/esm-extensions/dist/modals.js 485 B 0 B
packages/framework/esm-extensions/dist/public.js 168 B 0 B
packages/framework/esm-extensions/dist/render.js 806 B 0 B
packages/framework/esm-extensions/dist/store.js 737 B +20 B (+2.79%)
packages/framework/esm-extensions/dist/types.js 32 B 0 B
packages/framework/esm-extensions/dist/workspaces.js 1.25 kB 0 B
packages/framework/esm-extensions/dist/workspaces2.js 891 B 0 B
packages/framework/esm-feature-flags/dist/feature-flags.js 1.11 kB 0 B
packages/framework/esm-feature-flags/dist/index.js 56 B 0 B
packages/framework/esm-feature-flags/dist/public.js 80 B 0 B
packages/framework/esm-framework/dist/openmrs-esm-framework.js 68.9 kB +151 B (+0.22%)
packages/framework/esm-globals/dist/events.js 579 B 0 B
packages/framework/esm-globals/dist/index.js 58 B 0 B
packages/framework/esm-globals/dist/public.js 159 B 0 B
packages/framework/esm-globals/dist/types.js 130 B 0 B
packages/framework/esm-navigation/dist/breadcrumbs/db.js 389 B 0 B
packages/framework/esm-navigation/dist/breadcrumbs/filter.js 458 B 0 B
packages/framework/esm-navigation/dist/history/history.js 970 B 0 B
packages/framework/esm-navigation/dist/index.js 116 B 0 B
packages/framework/esm-navigation/dist/navigation/interpolate-string.js 795 B 0 B
packages/framework/esm-navigation/dist/navigation/navigate.js 830 B 0 B
packages/framework/esm-navigation/dist/public.js 143 B 0 B
packages/framework/esm-navigation/dist/types.js 68 B 0 B
packages/framework/esm-offline/dist/dynamic-offline-data.js 1.84 kB 0 B
packages/framework/esm-offline/dist/index.js 153 B 0 B
packages/framework/esm-offline/dist/mode.js 645 B 0 B
packages/framework/esm-offline/dist/offline-db.js 657 B 0 B
packages/framework/esm-offline/dist/offline-patient-data.js 615 B 0 B
packages/framework/esm-offline/dist/public.js 245 B 0 B
packages/framework/esm-offline/dist/service-worker-http-headers.js 155 B 0 B
packages/framework/esm-offline/dist/service-worker-messaging.js 381 B 0 B
packages/framework/esm-offline/dist/service-worker.js 658 B 0 B
packages/framework/esm-offline/dist/sync.js 2.52 kB 0 B
packages/framework/esm-offline/dist/uuid-support.js 290 B 0 B
packages/framework/esm-react-utils/dist/ComponentContext.js 165 B 0 B
packages/framework/esm-react-utils/dist/ConfigurableLink.js 689 B 0 B
packages/framework/esm-react-utils/dist/Extension.js 1.09 kB 0 B
packages/framework/esm-react-utils/dist/ExtensionSlot.js 998 B +3 B (+0.3%)
packages/framework/esm-react-utils/dist/getLifecycle.js 340 B 0 B
packages/framework/esm-react-utils/dist/index.js 458 B 0 B
packages/framework/esm-react-utils/dist/openmrsComponentDecorator.js 1.37 kB 0 B
packages/framework/esm-react-utils/dist/OpenmrsContext.js 420 B 0 B
packages/framework/esm-react-utils/dist/public.js 422 B 0 B
packages/framework/esm-react-utils/dist/RenderIfValueIsTruthy.js 404 B 0 B
packages/framework/esm-react-utils/dist/useAbortController.js 490 B 0 B
packages/framework/esm-react-utils/dist/useAppContext.js 763 B 0 B
packages/framework/esm-react-utils/dist/useAssignedExtensionIds.js 413 B 0 B
packages/framework/esm-react-utils/dist/useAssignedExtensions.js 229 B 0 B
packages/framework/esm-react-utils/dist/useAttachments.js 342 B 0 B
packages/framework/esm-react-utils/dist/useBodyScrollLock.js 241 B 0 B
packages/framework/esm-react-utils/dist/useConfig.js 1.24 kB 0 B
packages/framework/esm-react-utils/dist/useConnectedExtensions.js 206 B 0 B
packages/framework/esm-react-utils/dist/useConnectivity.js 242 B 0 B
packages/framework/esm-react-utils/dist/useDebounce.js 600 B 0 B
packages/framework/esm-react-utils/dist/useDefineAppContext.js 837 B 0 B
packages/framework/esm-react-utils/dist/useEmrConfiguration.js 1.24 kB 0 B
packages/framework/esm-react-utils/dist/useExtensionInternalStore.js 162 B 0 B
packages/framework/esm-react-utils/dist/useExtensionSlot.js 423 B +86 B (+25.52%) 🚨
packages/framework/esm-react-utils/dist/useExtensionSlotMeta.js 270 B 0 B
packages/framework/esm-react-utils/dist/useExtensionSlotStore.js 168 B 0 B
packages/framework/esm-react-utils/dist/useExtensionStore.js 146 B 0 B
packages/framework/esm-react-utils/dist/useFeatureFlag.js 372 B 0 B
packages/framework/esm-react-utils/dist/useFhirFetchAll.js 392 B 0 B
packages/framework/esm-react-utils/dist/useFhirInfinite.js 442 B 0 B
packages/framework/esm-react-utils/dist/useFhirPagination.js 858 B 0 B
packages/framework/esm-react-utils/dist/useForceUpdate.js 169 B 0 B
packages/framework/esm-react-utils/dist/useLayoutType.js 401 B 0 B
packages/framework/esm-react-utils/dist/useLeftNav.js 253 B 0 B
packages/framework/esm-react-utils/dist/useLeftNavStore.js 134 B 0 B
packages/framework/esm-react-utils/dist/useLocations.js 291 B 0 B
packages/framework/esm-react-utils/dist/useOnClickOutside.js 320 B 0 B
packages/framework/esm-react-utils/dist/useOnVisible.js 632 B 0 B
packages/framework/esm-react-utils/dist/useOpenmrsFetchAll.js 714 B 0 B
packages/framework/esm-react-utils/dist/useOpenmrsInfinite.js 1.37 kB 0 B
packages/framework/esm-react-utils/dist/useOpenmrsPagination.js 1.96 kB 0 B
packages/framework/esm-react-utils/dist/useOpenmrsSWR.js 921 B 0 B
packages/framework/esm-react-utils/dist/usePagination.js 724 B 0 B
packages/framework/esm-react-utils/dist/usePatient.js 689 B 0 B
packages/framework/esm-react-utils/dist/usePrimaryIdentifierResource.js 337 B 0 B
packages/framework/esm-react-utils/dist/useRenderableExtensions.js 701 B 0 B
packages/framework/esm-react-utils/dist/UserHasAccess.js 935 B 0 B
packages/framework/esm-react-utils/dist/useSession.js 1.64 kB 0 B
packages/framework/esm-react-utils/dist/useStore.js 634 B 0 B
packages/framework/esm-react-utils/dist/useVisit.js 1.41 kB 0 B
packages/framework/esm-react-utils/dist/useVisitContextStore.js 663 B 0 B
packages/framework/esm-react-utils/dist/useVisitTypes.js 255 B 0 B
packages/framework/esm-routes/dist/constants.js 76 B 0 B
packages/framework/esm-routes/dist/index.js 77 B 0 B
packages/framework/esm-routes/dist/loaders/components.js 1.32 kB +17 B (+1.31%)
packages/framework/esm-routes/dist/loaders/helpers.js 142 B 0 B
packages/framework/esm-routes/dist/loaders/index.js 128 B 0 B
packages/framework/esm-routes/dist/loaders/load-lifecycles.js 1.41 kB 0 B
packages/framework/esm-routes/dist/loaders/pages.js 2.69 kB 0 B
packages/framework/esm-routes/dist/public.js 62 B 0 B
packages/framework/esm-routes/dist/routes.js 1.38 kB 0 B
packages/framework/esm-state/dist/index.js 48 B 0 B
packages/framework/esm-state/dist/public.js 86 B 0 B
packages/framework/esm-state/dist/state.js 949 B 0 B
packages/framework/esm-styleguide/dist/openmrs-esm-styleguide.js 73.7 kB +144 B (+0.2%)
packages/framework/esm-translations/dist/index.js 1.19 kB 0 B
packages/framework/esm-translations/dist/public.js 76 B 0 B
packages/framework/esm-translations/dist/translations.js 1.18 kB 0 B
packages/framework/esm-utils/dist/age-helpers.js 1.21 kB 0 B
packages/framework/esm-utils/dist/dates/date-util.js 3.62 kB 0 B
packages/framework/esm-utils/dist/dates/index.js 52 B 0 B
packages/framework/esm-utils/dist/get-locale.js 299 B 0 B
packages/framework/esm-utils/dist/index.js 133 B 0 B
packages/framework/esm-utils/dist/is-online.js 114 B 0 B
packages/framework/esm-utils/dist/patient-helpers.js 1 kB 0 B
packages/framework/esm-utils/dist/retry.js 801 B 0 B
packages/framework/esm-utils/dist/shallowEqual.js 511 B 0 B
packages/framework/esm-utils/dist/storage.js 324 B 0 B
packages/framework/esm-utils/dist/test-helpers.js 463 B 0 B
packages/framework/esm-utils/dist/version.js 352 B 0 B
packages/shell/esm-app-shell/dist/064e8de06e2e356f.js 1.35 kB 0 B
packages/shell/esm-app-shell/dist/10356263107f7d61.js 1.49 kB 0 B
packages/shell/esm-app-shell/dist/1a2e7dce4aba505f.js 11.8 kB 0 B
packages/shell/esm-app-shell/dist/1ae28ce44d17abac.js 10.3 kB 0 B
packages/shell/esm-app-shell/dist/1e66c632d49325a2.js 1.25 kB 0 B
packages/shell/esm-app-shell/dist/1ebd81333e605a1d.js 1.14 kB 0 B
packages/shell/esm-app-shell/dist/202d63cf2f2e3b44.js 6.61 kB 0 B
packages/shell/esm-app-shell/dist/29ba5a7edabf27ff.js 1.14 kB 0 B
packages/shell/esm-app-shell/dist/2b31ff36375a9c7b.js 1.14 kB 0 B
packages/shell/esm-app-shell/dist/30bacfcbb6677db9.js 1.14 kB 0 B
packages/shell/esm-app-shell/dist/33486be7493a9e5c.js 11.4 kB 0 B
packages/shell/esm-app-shell/dist/346a73b2c287e229.js 2.68 kB 0 B
packages/shell/esm-app-shell/dist/36e03dc49766d3d5.js 1.14 kB 0 B
packages/shell/esm-app-shell/dist/376029414dc4502a.js 8.37 kB 0 B
packages/shell/esm-app-shell/dist/376095d34165cde4.js 4.58 kB 0 B
packages/shell/esm-app-shell/dist/3806a994a9c56efc.js 11.4 kB 0 B
packages/shell/esm-app-shell/dist/42e404c066432d00.js 1.28 kB 0 B
packages/shell/esm-app-shell/dist/4f24c76e8251d1aa.js 0 B -4.37 kB (removed) 🏆
packages/shell/esm-app-shell/dist/5b9f333c2b3ecc1f.js 1.36 kB 0 B
packages/shell/esm-app-shell/dist/5baaf5f4ba0fc425.js 1.44 kB 0 B
packages/shell/esm-app-shell/dist/5bbcbcad498417ba.js 1.14 kB 0 B
packages/shell/esm-app-shell/dist/63efd7b1aefa8d6b.js 6.99 kB 0 B
packages/shell/esm-app-shell/dist/6aafe6387878cc78.js 42.9 kB 0 B
packages/shell/esm-app-shell/dist/6f1a9070302f144c.js 1.14 kB 0 B
packages/shell/esm-app-shell/dist/6f8f19c12a9266d6.js 1.14 kB 0 B
packages/shell/esm-app-shell/dist/7cc24e1adb1a6600.js 1.14 kB 0 B
packages/shell/esm-app-shell/dist/7e2ca41edf9e703f.js 1.14 kB 0 B
packages/shell/esm-app-shell/dist/7fe27aebd3b41fdb.js 1.14 kB 0 B
packages/shell/esm-app-shell/dist/88bbedc07e018589.js 1.33 kB 0 B
packages/shell/esm-app-shell/dist/8acf6107feeaabdb.js 1.18 kB 0 B
packages/shell/esm-app-shell/dist/8c645a42d4f920c3.js 1.14 kB 0 B
packages/shell/esm-app-shell/dist/92ef8ead07723dc9.js 1.14 kB 0 B
packages/shell/esm-app-shell/dist/944a993f5f0268d7.js 1.14 kB 0 B
packages/shell/esm-app-shell/dist/9b5900b80fee8287.js 1.22 kB 0 B
packages/shell/esm-app-shell/dist/9e1b9d4f13de994c.js 166 kB 0 B
packages/shell/esm-app-shell/dist/9eea5a16b09c3742.js 51.3 kB 0 B
packages/shell/esm-app-shell/dist/b07d2675466c5c6b.js 3.08 kB 0 B
packages/shell/esm-app-shell/dist/b35e830c12581878.js 1.14 kB 0 B
packages/shell/esm-app-shell/dist/b68c5489bcec4b01.js 1.14 kB 0 B
packages/shell/esm-app-shell/dist/b85d8b8700d09a57.js 1.14 kB 0 B
packages/shell/esm-app-shell/dist/ba7443539bb1b727.js 4.37 kB 0 B
packages/shell/esm-app-shell/dist/bd66e41be49f840d.js 36.9 kB 0 B
packages/shell/esm-app-shell/dist/bd6cdb37ef5c166b.js 29.4 kB 0 B
packages/shell/esm-app-shell/dist/c0e6f4a635c64d6a.js 1.45 kB 0 B
packages/shell/esm-app-shell/dist/c1d6ea14e62e3055.js 4.37 kB 0 B
packages/shell/esm-app-shell/dist/c7ecfbc63b390c41.js 1.53 kB 0 B
packages/shell/esm-app-shell/dist/c818c9fa7acc9586.js 0 B -4.37 kB (removed) 🏆
packages/shell/esm-app-shell/dist/c8b263dd751d2d92.js 1.34 kB 0 B
packages/shell/esm-app-shell/dist/c8f49997b7e0c820.js 1.14 kB 0 B
packages/shell/esm-app-shell/dist/cd5e0f4b09558b7b.js 220 kB 0 B
packages/shell/esm-app-shell/dist/cd713e64db2a8487.js 1.14 kB 0 B
packages/shell/esm-app-shell/dist/d2ef1ec3351ef998.js 15.9 kB 0 B
packages/shell/esm-app-shell/dist/d33f7329c2aba9ca.js 1.14 kB 0 B
packages/shell/esm-app-shell/dist/d8dc8dc98a8b395c.js 1.14 kB 0 B
packages/shell/esm-app-shell/dist/d8edb1d8c9af2526.js 1.14 kB 0 B
packages/shell/esm-app-shell/dist/d946a42fff6683e9.js 1.14 kB 0 B
packages/shell/esm-app-shell/dist/dbf0967e51d2eacb.js 20.8 kB 0 B
packages/shell/esm-app-shell/dist/dddb2accd863a677.js 1.14 kB 0 B
packages/shell/esm-app-shell/dist/e56f25bc4fbe4e4f.js 1.47 kB 0 B
packages/shell/esm-app-shell/dist/e9d1926092bbe2ad.js 1.38 kB 0 B
packages/shell/esm-app-shell/dist/f199ec8adcbf9ae8.js 1.14 kB 0 B
packages/shell/esm-app-shell/dist/f33e1a600fdb8c9c.js 1.59 kB 0 B
packages/shell/esm-app-shell/dist/f906ba60760193b0.js 0 B -6.61 kB (removed) 🏆
packages/shell/esm-app-shell/dist/fcc4ee93d197fee0.js 77.5 kB 0 B
packages/shell/esm-app-shell/dist/openmrs.4fa47b904cce0add.js 22.2 kB 0 B
packages/shell/esm-app-shell/dist/service-worker.js 45.5 kB +5 B (+0.01%)
packages/tooling/openmrs/dist/cli.js 3.07 kB 0 B
packages/tooling/openmrs/dist/commands/assemble.js 3.36 kB 0 B
packages/tooling/openmrs/dist/commands/build.js 1.39 kB 0 B
packages/tooling/openmrs/dist/commands/debug.js 577 B 0 B
packages/tooling/openmrs/dist/commands/develop.js 2.81 kB 0 B
packages/tooling/openmrs/dist/commands/index.js 437 B 0 B
packages/tooling/openmrs/dist/commands/start.js 889 B 0 B
packages/tooling/openmrs/dist/index.js 626 B 0 B
packages/tooling/openmrs/dist/runner.js 745 B 0 B
packages/tooling/openmrs/dist/utils/config.js 740 B 0 B
packages/tooling/openmrs/dist/utils/debugger.js 682 B 0 B
packages/tooling/openmrs/dist/utils/dependencies.js 666 B 0 B
packages/tooling/openmrs/dist/utils/devserver.js 285 B 0 B
packages/tooling/openmrs/dist/utils/helpers.js 378 B 0 B
packages/tooling/openmrs/dist/utils/importmap.js 3.31 kB 0 B
packages/tooling/openmrs/dist/utils/index.js 443 B 0 B
packages/tooling/openmrs/dist/utils/logger.js 353 B 0 B
packages/tooling/openmrs/dist/utils/npmConfig.js 822 B 0 B
packages/tooling/openmrs/dist/utils/untar.js 824 B 0 B
packages/tooling/openmrs/dist/utils/variables.js 183 B 0 B
packages/tooling/rspack-config/dist/index.js 4.15 kB 0 B
packages/tooling/typedoc-plugin-file-categories/dist/index.js 573 B 0 B
packages/tooling/webpack-config/dist/index.js 3.72 kB 0 B

compressed-size-action

@CynthiaKamau CynthiaKamau force-pushed the extension-expressions branch from a10ab77 to fb07729 Compare July 14, 2025 11:04
@CynthiaKamau CynthiaKamau marked this pull request as ready for review July 14, 2025 11:05
@gracepotma
Copy link
Contributor

gracepotma commented Aug 1, 2025

Thank you so much @CynthiaKamau for working on this!! Sorry you have been waiting for weeks for a review - Ian has been super swamped with some very urgent complex projects. Thank you for your patience and again, thank you SO MUCH for contributing this in follow up to Ian's suggestions here. I made a ticket at https://openmrs.atlassian.net/browse/O3-4931 to reflect/track this work plan so it's not just hanging out in the heap of slack :P

While we await a review, I have a few questions, please forgive me as I think they're basic, but this will help me understand and communicate this exciting work more widely.

  1. Is the goal of this to be able to configure things like the Clinical View pages to show or not show depending on patient variables? If I understand the demo screenrecording, you show some extensions (the allergy app in the example) not showing up if the patient is Female. So does this mean that this is how people would configure "If patient = Female, do not show / show the MCH Dashboard", or "If patient = enrolled in Program X, then show the X Program Page in the Left Nav". Do I have that correct?
  2. Can you point me to the specific place where an implementer would adjust such config/settings? (e.g. where did you adjust things to say "if female, don't show allergy app"?)
  3. Can this work already be used to apply to navigation and pages, or is it so far only applicable to individual apps/widgets? (e.g. in the screenrecording, the Allergy Page and Left Nav still shows for the female patient, even though the esm doesn't show up.)

Thank you again!!!! I'm super impressed by your turnaround time in response to Ian's suggestions on Slack 🙏

CCing @denniskigen because I think he'll want to be aware of this important work, and @NethmiRodrigo because this directly relates to Clinical Views (IIUC) which she's been working on / thinking about a lot lately, and we'll want to use this in our HIV Package support work for DRC.

@gracepotma gracepotma changed the title Add ability to evaluate expressions in extensions O3-4931: Add ability to evaluate expressions in extensions Aug 1, 2025
@gracepotma gracepotma requested a review from samuelmale August 1, 2025 18:06
@gracepotma
Copy link
Contributor

Per Ian's direct request, assigning this to @samuelmale as the primary reviewer.

Copy link
Member

@ibacher ibacher left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @CynthiaKamau! I think this PR as-written does much more than it should. What we should be adding here is a core building block for functionalities that can be properly leveraged by supplying data at sensible points in the application. Additionally, I've had a re-think about how this should be implemented. See my rather long comment on useAssignExtensionExpressionContext().

if (!evaluateAsBoolean(displayConditionExpression, { session })) {
// Get patient UUID from URL and add to evaluation context
const patientUuid = getPatientUuidFromUrl();
let evaluationContext: any = { ...extensionExpressionContext, session };
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
let evaluationContext: any = { ...extensionExpressionContext, session };
let evaluationContext = { ...extensionExpressionContext, session };

Comment on lines 95 to 97
export interface ExtensionExpressionContextStore {
[key: string]: any;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
export interface ExtensionExpressionContextStore {
[key: string]: any;
}
export type ExtensionExpressionContextStore = Record<string | symbol | number, unknown>;

Comment on lines 165 to 250
// Automatic patient context management
let currentPatientUuid: string | null = null;
let patientDataCache: { [uuid: string]: any } = {};

async function fetchPatientData(patientUuid: string) {
// Check cache first
if (patientDataCache[patientUuid]) {
return patientDataCache[patientUuid];
}

try {
const response = await openmrsFetch(`/ws/rest/v1/patient/${patientUuid}`);
const patientData = response.data;

// Cache the result
patientDataCache[patientUuid] = patientData;

return patientData;
} catch (error) {
return null;
}
}

function updatePatientContext() {
const patientUuid = getPatientUuidFromUrl();

if (patientUuid !== currentPatientUuid) {
currentPatientUuid = patientUuid;

if (patientUuid) {
// Fetch patient data and update context
fetchPatientData(patientUuid).then((patientData) => {
// Only update if we're still on the same patient page
if (patientData && currentPatientUuid === patientUuid) {
const currentContext = extensionExpressionContextStore.getState();
const newContext: any = {
...currentContext,
patient: patientData,
patientUuid: patientUuid,
};

extensionExpressionContextStore.setState(newContext);
}
});
} else {
// Clear patient context when not on patient page
const currentContext = extensionExpressionContextStore.getState();
const newContext = { ...currentContext };
delete newContext.patient;
delete newContext.patientUuid;
extensionExpressionContextStore.setState(newContext);
}
}
}

// Update patient context on URL changes
function handleUrlChange() {
updatePatientContext();
updateOutputStoreToCurrent();
}

// Listen for URL changes
window.addEventListener('popstate', handleUrlChange);

// Override pushState and replaceState to detect programmatic navigation
const originalPushState = history.pushState;
const originalReplaceState = history.replaceState;

history.pushState = function (...args) {
originalPushState.apply(history, args);
setTimeout(handleUrlChange, 0);
};

history.replaceState = function (...args) {
originalReplaceState.apply(history, args);
setTimeout(handleUrlChange, 0);
};

// Initial setup
updatePatientContext();

function getPatientUuidFromUrl() {
const match = /\/patient\/([a-zA-Z0-9\-]+)\/?/.exec(location.pathname);
return match && match[1];
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few things on this section of code:

  • Automatic patient context is not a concern that should be handled at the level of the esm-extensions. esm-extensions should be left quite generic. It definitely shouldn't be monitoring the URL for changes or trying to parse the patient from that context. Among other things, that URL schema only works in the context of the patient chart, but this would make it something that takes up processing cycles in every context. Instead all of this logic should be in the patient-chart-app.
  • Because of the way the patient chart works, we currently generally pump the correct patient into the extension context using the state property. Maybe we can devise a

order?: Array<string>;
configure?: ExtensionSlotConfigureValueObject;
/** Expression to determine whether this extension slot should be displayed */
displayExpression?: string;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't seem like it should be a property of extension slots. Code rendering extension slots can already make context-specific decisions to render them or not. Instead this should only apply to extensions.

import { isOnline as isOnlineFn } from '@openmrs/esm-utils';
import { isEqual, merge } from 'lodash-es';
import { checkStatusFor } from './helpers';

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change

let evaluationContext: any = { ...extensionExpressionContext, session };

if (patientUuid) {
evaluationContext = { ...extensionExpressionContext, session, patientUuid };
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't really see a lot of value to trying to always add a patientUuid field here. What possible expression do we need to support that depends on the patientUuid?

*
* @param context Additional context data to provide to extension display expressions
*/
export function useAssignExtensionExpressionContext(context: Record<string, any>) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Always prefer unknown where we do not know or care about the type of an object. Using any effectively opts-out of type-checking and makes it too easy to introduce unintended errors.

Suggested change
export function useAssignExtensionExpressionContext(context: Record<string, any>) {
export function useAssignExtensionExpressionContext(context: Record<string, unknown>) {

Comment on lines 49 to 54
const currentState = extensionExpressionContextStore.getState();
const newState = { ...currentState };
Object.keys(context).forEach((key) => {
delete newState[key];
});
extensionExpressionContextStore.setState(newState);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than writing things this way, it's better to take advantage of setState() to do:

extensionExpressionContextStore.setState((currentState) => {
  const result = {};
  Object.entries(currentState).forEach((key, value) => {
    if (!Object.hasOwn(context, key)) {
      result[key] = value;
    }
  });

  return result;
});

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And elsewhere...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, while I realize this was in my little specification, now that I look at it, I think there's actually a better way to implement extension expression context that will work much better and that's to be enable ExtensionSlot's to pass certain variables to the context via the state. So, when we render an extensionSlot it has a state property that is generally passed down to the extension.

Perhaps what we do is to push this state into the extension system itself, e.g., so when we call useExtensionSlot(), we also pass in the slot state, which can be associated with the slot initially in the call to registerExtensionSlot() and then, with a second useEffect() hook and a new function in esm-extensions that updates the state property of the slot in extensionInternalStore (which doesn't currently exist). That way, we can use the ExtensionSlot state directly in expression evaluation. This would also mean that since most extension slots in the patient chart already pass the patient as part of their state, we'd probably have to do very little work to make this mechanism work for the patient-chart (other than ensuring that the nav-group correctly passes any state to any dashboards underneath it).

It also means that each extension could evaluate rules against slot-specific data, which should help make this more generally useful.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @ibacher , let me make the necessary changes

Copy link
Member

@ibacher ibacher left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See my previous comments.

@CynthiaKamau
Copy link
Contributor Author

Thank you so much @CynthiaKamau for working on this!! Sorry you have been waiting for weeks for a review - Ian has been super swamped with some very urgent complex projects. Thank you for your patience and again, thank you SO MUCH for contributing this in follow up to Ian's suggestions here. I made a ticket at https://openmrs.atlassian.net/browse/O3-4931 to reflect/track this work plan so it's not just hanging out in the heap of slack :P

While we await a review, I have a few questions, please forgive me as I think they're basic, but this will help me understand and communicate this exciting work more widely.

  1. Is the goal of this to be able to configure things like the Clinical View pages to show or not show depending on patient variables? If I understand the demo screenrecording, you show some extensions (the allergy app in the example) not showing up if the patient is Female. So does this mean that this is how people would configure "If patient = Female, do not show / show the MCH Dashboard", or "If patient = enrolled in Program X, then show the X Program Page in the Left Nav". Do I have that correct?
  2. Can you point me to the specific place where an implementer would adjust such config/settings? (e.g. where did you adjust things to say "if female, don't show allergy app"?)
  3. Can this work already be used to apply to navigation and pages, or is it so far only applicable to individual apps/widgets? (e.g. in the screenrecording, the Allergy Page and Left Nav still shows for the female patient, even though the esm doesn't show up.)

Thank you again!!!! I'm super impressed by your turnaround time in response to Ian's suggestions on Slack 🙏

CCing @denniskigen because I think he'll want to be aware of this important work, and @NethmiRodrigo because this directly relates to Clinical Views (IIUC) which she's been working on / thinking about a lot lately, and we'll want to use this in our HIV Package support work for DRC.

  1. Yes, in OHRI, we hide nav links in the patient chart based on conditions like age and gender for specific programs, eg MCH and PMTCT, you can see the difference in the menus for male and female patients on the patient chart here : https://ethiohri-dev.globalhealthapp.net/openmrs/spa

  2. https://ethiohri-dev.globalhealthapp.net/openmrs/spa - Ethiopia

  3. The goal is do it it in nav links for starters, but it applies to extensions generally

*/

import { type Session, type SessionStore, sessionStore, userHasAccess } from '@openmrs/esm-api';
/* eslint-disable no-console */
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like a debugging leftover

);
};

export const extensionSlotConfigSchema = {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you using this anywhere?

Comment on lines 366 to 497
} catch (e) {
console.error(`Error while evaluating expression ${displayConditionExpression}`, e);
// if the expression has an error, we do not display the extension
} catch (error) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's important to log the error to the console for debugging purposes.

/**
* If supplied, this is used to determine the display expression for the extension.
* This is used to determine if the extension is displayed in the UI.
* If not supplied, the extension will be displayed in the UI by default.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the extension will be displayed in the UI by default

Well, assuming the logged-in user possesses the required privileges.

extensionConfig?.['Display expression']?.expression ??
(typeof extension.displayExpression === 'string'
? extension.displayExpression
: (extension.displayExpression as any)?.expression) ??
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ideally, extension.displayExpression should be a string property. We won't have any issues if the schema is updated.

@CynthiaKamau CynthiaKamau marked this pull request as draft August 7, 2025 07:54
@CynthiaKamau CynthiaKamau marked this pull request as ready for review August 7, 2025 08:09
@CynthiaKamau CynthiaKamau force-pushed the extension-expressions branch 2 times, most recently from 1cc660f to 35df8ab Compare August 11, 2025 09:19
"moduleName": "Module name",
"modulesWithMissingDependenciesWarning": "Some modules have unresolved backend dependencies",
"numberValidationMessage": "Value must be a number",
"objectPropertyValidationMessage": "Property {{key}}: {{error}}",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you remove this by mistake?

getExtensionSlotsConfigStore,
} from '@openmrs/esm-config';
import { evaluateAsBoolean } from '@openmrs/esm-expression-evaluator';
import { evaluateAsBoolean, type VariablesMap } from '@openmrs/esm-expression-evaluator';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you using this import?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removing it

if (!evaluateAsBoolean(displayConditionExpression, { session })) {
const context =
slotState && typeof slotState === 'object' ? { session, ...slotState } : { session, slotState };
if (!evaluateExpression(displayConditionExpression, context)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you use the framework's built-in expression evaluator?

slotName,
state,
) =>
extensionInternalStore.setState((slotState) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
extensionInternalStore.setState((slotState) => {
extensionInternalStore.setState((currentState) => {

});

export function setExtensionSlotState(slotName: string, state: ExtensionSlotCustomState) {
extensionInternalStore.setState((slotState) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, it's kind of misleading to refer to this as slotState. currentState is a better name.

if (!evaluateAsBoolean(displayConditionExpression, { session })) {
const context: VariablesMap =
slotState && typeof slotState === 'object' ? { session, ...slotState } : { session, slotState };
if (!evaluateAsBooleanAsync(displayConditionExpression, context)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hhhmmm, usingevaluateAsBooleanAsync(...) will always return a Promise, hence this condition will always be false unless you resolve the Promise. Can you use evaluateAsBoolean(...) instead?

@gracepotma
Copy link
Contributor

Thanks for your answers above @CynthiaKamau!
Sorry I didn't follow your answer to this question:

Can you point me to the specific place where an implementer would adjust such config/settings? (e.g. where did you adjust things to say "if female, don't show allergy app"?)

When I go to https://ethiohri-dev.globalhealthapp.net/openmrs/spa (logged in), I only see the home page. Where in the code or config or admin does someone configure the logic for what is shown/hidden depending on certain patient variables?

@CynthiaKamau CynthiaKamau force-pushed the extension-expressions branch from 9875f2a to 1fd0499 Compare August 15, 2025 09:15
@CynthiaKamau
Copy link
Contributor Author

Thanks for your answers above @CynthiaKamau! Sorry I didn't follow your answer to this question:

Can you point me to the specific place where an implementer would adjust such config/settings? (e.g. where did you adjust things to say "if female, don't show allergy app"?)

When I go to https://ethiohri-dev.globalhealthapp.net/openmrs/spa (logged in), I only see the home page. Where in the code or config or admin does someone configure the logic for what is shown/hidden depending on certain patient variables?

The evaluation expressions were working on the patient chart clinical views, the expressions were being added in the dashboard.meta file, like here

const displayConditionExpression = extensionConfig?.['Display conditions']?.expression ?? null;
if (displayConditionExpression !== null) {
const displayConditionExpression =
extensionConfig?.['Display conditions']?.expression ?? extension.expression ?? null;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@CynthiaKamau So, because you provide an implicit config value of "" for the expression under "Display conditions", this statement will never resolve expressions defined via extension def in the routes file (extension.expression). This is because extensionConfig?.['Display conditions']?.expression will never be null or undefined. You should instead:

Suggested change
extensionConfig?.['Display conditions']?.expression ?? extension.expression ?? null;
extensionConfig?.['Display conditions']?.expression || extension.expression || null;

Copy link
Member

@samuelmale samuelmale Aug 26, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@CynthiaKamau, I've also tested out this feature using the extension config, and it doesn't seem to work. This is because extensionConfig.['Display conditions'] points to the module's config, and not the extension config provided via the slot. Below is my demo config:

{
  "@openmrs/esm-appointments-app": {
    "appointments-metrics-slot": {
      "configure": {
        "metrics-card-scheduled-appointments": {
          "Display conditions": {
            "privileges": [],
            "expression": "false"
          }
        }
      }
    }
  }
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm... that means several other features are broken (see line 345 which should be handling that).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@CynthiaKamau, I've also tested out this feature using the extension config, and it doesn't seem to work. This is because extensionConfig.['Display conditions'] points to the module's config, and not the extension config provided via the slot. Below is my demo config:

{
  "@openmrs/esm-appointments-app": {
    "appointments-metrics-slot": {
      "configure": {
        "metrics-card-scheduled-appointments": {
          "Display conditions": {
            "privileges": [],
            "expression": "false"
          }
        }
      }
    }
  }
}

I am using the same approach used in handling privileges, which are also part of the display conditions. Since state comes from an extension, if there is no state passed from your extension the evaluation will always be false

Copy link
Member

@samuelmale samuelmale Sep 4, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@CynthiaKamau have you tested this out at runtime to confirm that it works using extension config? If so, can you show me a sample config?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@CynthiaKamau have you tested this out at runtime to confirm that it works using extension config? If so, can you show me a sample config?

{
  "@openmrs/esm-patient-chart-app": {
    "extensionSlots": {
      "patient-info-slot": {
        "configure": {
          "patient-vitals-info": {
            "Display conditions": {
              "expression": "patient.person.gender === 'F'"
            }
          }
        }
      }
    }
  }
}

Comment on lines 18 to 22
useEffect(() => {
if (state) {
setExtensionSlotState(slotName, state);
}
}, [slotName, state]);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we have this check when it seems like by accepting null or undefined here, we remove the need for calling updateInternalExtensionSotre() in the ExtensionSlot at all.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That said, what we actually need here is a guard against running this on initial render, since registerExtensionSlot() already does that. That way we guard against an unnecessary cycle of cascading updates.

This hopefully addresses some timing issues, but is also reflective of
the order these things would run in the a real-world scenario.
Might be better to just swap to JestDom?
This will hopefully make things less flaky...
@denniskigen denniskigen requested a review from ibacher October 27, 2025 13:38
* Recomputes all configuration derived stores based on current state of input stores.
* Called whenever any input store (configInternalStore, temporaryConfigStore, configExtensionStore) changes.
*/
function recomputeAllConfigs() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be roughly equivalent to what was happening before, albeit with less chaining.

const newState = { slots: { ...oldState.slots, ...newSlotStoreEntries } };
if (!equals(oldState, newState)) {

if (!equals(oldState.slots, newState.slots)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These comparison guards should mean that we're calling setState() less frequently

throw `Unknown expression type ${expression}. Expressions must either be a string or pre-compiled string.`;
}

if (typeof expression === 'string' && expression.trim().length === 0) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One issue here is that we didn't prevent attempted evaluation of an empty string


// Create context once for all extensions in this slot
const slotState = internalState.slots[slotName]?.state;
const expressionContext = slotState && typeof slotState === 'object' ? { session, ...slotState } : { session };
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using a single expressionContext is more efficient but may enable subtle errors if an expression modifies global state as a side-effect.

@ibacher ibacher merged commit b05c126 into main Oct 27, 2025
16 of 18 checks passed
@ibacher ibacher deleted the extension-expressions branch October 27, 2025 15:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants