@openmrs/esm-report-builder 0.1.1-pre.87

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (551) hide show
  1. package/.eslintignore +5 -0
  2. package/.eslintrc.js +19 -0
  3. package/.prettierrc +8 -0
  4. package/.tx/config +12 -0
  5. package/LICENSE +401 -0
  6. package/README.md +336 -0
  7. package/__mocks__/react-i18next.js +12 -0
  8. package/dist/162.js +1 -0
  9. package/dist/162.js.map +1 -0
  10. package/dist/184.js +2 -0
  11. package/dist/184.js.LICENSE.txt +14 -0
  12. package/dist/184.js.map +1 -0
  13. package/dist/242.js +2 -0
  14. package/dist/242.js.LICENSE.txt +21 -0
  15. package/dist/242.js.map +1 -0
  16. package/dist/253.js +1 -0
  17. package/dist/253.js.map +1 -0
  18. package/dist/300.js +1 -0
  19. package/dist/311.js +1 -0
  20. package/dist/311.js.map +1 -0
  21. package/dist/469.js +2 -0
  22. package/dist/469.js.LICENSE.txt +45 -0
  23. package/dist/469.js.map +1 -0
  24. package/dist/478.js +2 -0
  25. package/dist/478.js.LICENSE.txt +9 -0
  26. package/dist/478.js.map +1 -0
  27. package/dist/539.js +1 -0
  28. package/dist/539.js.map +1 -0
  29. package/dist/540.js +2 -0
  30. package/dist/540.js.LICENSE.txt +9 -0
  31. package/dist/540.js.map +1 -0
  32. package/dist/669.js +1 -0
  33. package/dist/669.js.map +1 -0
  34. package/dist/710.js +1 -0
  35. package/dist/710.js.map +1 -0
  36. package/dist/729.js +1 -0
  37. package/dist/729.js.map +1 -0
  38. package/dist/838.js +2 -0
  39. package/dist/838.js.LICENSE.txt +9 -0
  40. package/dist/838.js.map +1 -0
  41. package/dist/917.js +1 -0
  42. package/dist/917.js.map +1 -0
  43. package/dist/961.js +2 -0
  44. package/dist/961.js.LICENSE.txt +19 -0
  45. package/dist/961.js.map +1 -0
  46. package/dist/main.js +1 -0
  47. package/dist/main.js.map +1 -0
  48. package/dist/openmrs-esm-report-builder.js +1 -0
  49. package/dist/openmrs-esm-report-builder.js.buildmanifest.json +530 -0
  50. package/dist/openmrs-esm-report-builder.js.map +1 -0
  51. package/dist/routes.json +1 -0
  52. package/e2e/core/env.ts +5 -0
  53. package/e2e/core/global-setup.ts +34 -0
  54. package/e2e/core/test.ts +21 -0
  55. package/e2e/pages/app-shell.page.ts +34 -0
  56. package/e2e/pages/etl-sources.page.ts +35 -0
  57. package/e2e/pages/report-sections.page.ts +20 -0
  58. package/e2e/pages/section-preview.modal.ts +29 -0
  59. package/e2e/specs/etl-sources.spec.ts +51 -0
  60. package/e2e/specs/navigation.spec.ts +13 -0
  61. package/e2e/specs/section-preview-validation.spec.ts +38 -0
  62. package/e2e/specs/section-preview.spec.ts +94 -0
  63. package/e2e/support/github/Dockerfile +22 -0
  64. package/e2e/support/github/Dockerfile.dockerignore +13 -0
  65. package/e2e/support/github/docker-compose.yml +31 -0
  66. package/e2e/support/github/run-e2e-docker-env.sh +131 -0
  67. package/e2e/support/github/spa-assemble-config.json +9 -0
  68. package/e2e/tsconfig.json +11 -0
  69. package/example.env +5 -0
  70. package/jest.config.js +28 -0
  71. package/linelist_report_builder_design_spec.md +816 -0
  72. package/package.json +111 -0
  73. package/playwright.config.ts +28 -0
  74. package/routes.json +22 -0
  75. package/src/__debug__/all-designs.test.tsx +30 -0
  76. package/src/admin-card-link.extension.module.scss +55 -0
  77. package/src/admin-card-link.extension.tsx +24 -0
  78. package/src/components/admin/admin-page.component.tsx +75 -0
  79. package/src/components/admin/age-categories-page.component.tsx +109 -0
  80. package/src/components/admin/age-groups-page.component.tsx +146 -0
  81. package/src/components/admin/etl-browser/columns-tab.component.tsx +80 -0
  82. package/src/components/admin/etl-browser/etl-browser-page.component.tsx +309 -0
  83. package/src/components/admin/etl-browser/etl-browser.component.scss +424 -0
  84. package/src/components/admin/etl-browser/metadata-tab.component.tsx +65 -0
  85. package/src/components/admin/etl-browser/preview-grid.component.tsx +189 -0
  86. package/src/components/admin/etl-browser/relationships-tab.component.tsx +21 -0
  87. package/src/components/admin/etl-browser/sources-tree.component.tsx +118 -0
  88. package/src/components/admin/etl-browser/table-detail.component.tsx +124 -0
  89. package/src/components/admin/etl-browser/table-info-strip.component.tsx +51 -0
  90. package/src/components/admin/etl-browser/table-list.component.tsx +136 -0
  91. package/src/components/admin/etl-browser/use-table-preview.ts +47 -0
  92. package/src/components/admin/etl-sources-page.component.tsx +355 -0
  93. package/src/components/admin/etl-tasks-page.component.scss +370 -0
  94. package/src/components/admin/etl-tasks-page.component.tsx +768 -0
  95. package/src/components/admin/report-categories-page.component.tsx +267 -0
  96. package/src/components/admin/report-library-page.component.tsx +551 -0
  97. package/src/components/ai-support/ai-assist-button.component.tsx +30 -0
  98. package/src/components/ai-support/ai-assist-modal.component.tsx +81 -0
  99. package/src/components/ai-support/ai.service.ts +21 -0
  100. package/src/components/ai-support/ai.types.ts +12 -0
  101. package/src/components/app-shell/report-builder-shell.component.tsx +242 -0
  102. package/src/components/app-shell/report-builder-shell.scss +18 -0
  103. package/src/components/dashboards/dashboard-config.schema.ts +97 -0
  104. package/src/components/dashboards/dashboard-form-modal.component.tsx +150 -0
  105. package/src/components/dashboards/dashboards-admin.scss +62 -0
  106. package/src/components/dashboards/dashboards-page.component.tsx +153 -0
  107. package/src/components/dashboards/dashboards-table.component.tsx +130 -0
  108. package/src/components/dashboards/sections/dashboard-autoinclude.section.tsx +75 -0
  109. package/src/components/dashboards/sections/dashboard-basics.section.tsx +80 -0
  110. package/src/components/dashboards/sections/dashboard-json.section.tsx +79 -0
  111. package/src/components/dashboards/sections/dashboard-sections-editor.section.tsx +130 -0
  112. package/src/components/dashboards/sections/dashboard-widgets-editor.section.tsx +184 -0
  113. package/src/components/data-themes/data-theme-form.component.tsx +59 -0
  114. package/src/components/data-themes/data-theme-modal.component.tsx +349 -0
  115. package/src/components/data-themes/data-themes-page.component.tsx +139 -0
  116. package/src/components/data-themes/data-themes-table.component.tsx +98 -0
  117. package/src/components/data-themes/icon-registry.ts +35 -0
  118. package/src/components/data-themes/sections/data-theme-basics.section.tsx +63 -0
  119. package/src/components/data-themes/sections/data-theme-condition-columns.section.tsx +397 -0
  120. package/src/components/data-themes/sections/data-theme-fields-editor.section.tsx +125 -0
  121. package/src/components/data-themes/sections/data-theme-metadata.section.tsx +245 -0
  122. package/src/components/data-themes/sections/data-theme-preview.section.tsx +115 -0
  123. package/src/components/data-themes/sections/data-theme-source.section.tsx +184 -0
  124. package/src/components/data-visualizer/components/cqi-components/cqi-data-table.component.tsx +214 -0
  125. package/src/components/data-visualizer/components/cqi-components/cqi-elements.ts +203 -0
  126. package/src/components/data-visualizer/components/cqi-components/detail.component.tsx +110 -0
  127. package/src/components/data-visualizer/components/cqi-components/detail.scss +22 -0
  128. package/src/components/data-visualizer/components/data-table/data-table.component.tsx +208 -0
  129. package/src/components/data-visualizer/components/data-table/data-tables.scss +206 -0
  130. package/src/components/data-visualizer/components/empty-state/empty-state-illustration.component.tsx +53 -0
  131. package/src/components/data-visualizer/components/header/header.component.tsx +43 -0
  132. package/src/components/data-visualizer/components/header/header.scss +71 -0
  133. package/src/components/data-visualizer/components/model/confirm.component.tsx +40 -0
  134. package/src/components/data-visualizer/components/panel/panel.component.tsx +20 -0
  135. package/src/components/data-visualizer/components/panel/panel.scss +25 -0
  136. package/src/components/data-visualizer/components/popover/modifier-panel.tsx +53 -0
  137. package/src/components/data-visualizer/constants.ts +445 -0
  138. package/src/components/data-visualizer/data-visualizer-illustration.component.tsx +31 -0
  139. package/src/components/data-visualizer/data-visualizer.component.tsx +1935 -0
  140. package/src/components/data-visualizer/data-visualizer.resource.ts +462 -0
  141. package/src/components/data-visualizer/data-visualizer.scss +449 -0
  142. package/src/components/data-visualizer/declarations.d.ts +131 -0
  143. package/src/components/data-visualizer/parameter-inputs/boolean-parameter-input.component.tsx +34 -0
  144. package/src/components/data-visualizer/parameter-inputs/concept-parameter-input.component.tsx +81 -0
  145. package/src/components/data-visualizer/parameter-inputs/date-parameter-input.component.tsx +110 -0
  146. package/src/components/data-visualizer/parameter-inputs/date-range-input.component.scss +47 -0
  147. package/src/components/data-visualizer/parameter-inputs/date-range-input.component.tsx +228 -0
  148. package/src/components/data-visualizer/parameter-inputs/identifier-type-parameter-input.component.tsx +69 -0
  149. package/src/components/data-visualizer/parameter-inputs/list-parameter-input.component.tsx +73 -0
  150. package/src/components/data-visualizer/parameter-inputs/location-parameter-input.component.tsx +81 -0
  151. package/src/components/data-visualizer/parameter-inputs/number-parameter-input.component.tsx +40 -0
  152. package/src/components/data-visualizer/parameter-inputs/person-attribute-parameter-input.component.tsx +71 -0
  153. package/src/components/data-visualizer/parameter-inputs/text-parameter-input.component.tsx +52 -0
  154. package/src/components/data-visualizer/report-parameter-modal.component.tsx +383 -0
  155. package/src/components/data-visualizer/types.ts +206 -0
  156. package/src/components/etl-monitor/builder/builder-state-machine.ts +381 -0
  157. package/src/components/etl-monitor/builder/design-registry.tsx +139 -0
  158. package/src/components/etl-monitor/builder/etl-monitor-builder-context.tsx +702 -0
  159. package/src/components/etl-monitor/builder/etl-monitor-builder-modal.component.tsx +461 -0
  160. package/src/components/etl-monitor/builder/etl-monitor-builder-modal.scss +256 -0
  161. package/src/components/etl-monitor/builder/etl-monitor-builder-page.component.tsx +387 -0
  162. package/src/components/etl-monitor/builder/etl-monitor-builder-page.scss +976 -0
  163. package/src/components/etl-monitor/builder/generated-config-panel.component.tsx +168 -0
  164. package/src/components/etl-monitor/builder/generated-config-panel.scss +198 -0
  165. package/src/components/etl-monitor/builder/index.ts +5 -0
  166. package/src/components/etl-monitor/builder/monitor-preview.tsx +124 -0
  167. package/src/components/etl-monitor/builder/steps/data-source-step.component.tsx +667 -0
  168. package/src/components/etl-monitor/builder/steps/data-source-step.scss +284 -0
  169. package/src/components/etl-monitor/builder/steps/design-samples.ts +278 -0
  170. package/src/components/etl-monitor/builder/steps/design-step.component.tsx +299 -0
  171. package/src/components/etl-monitor/builder/steps/design-step.scss +479 -0
  172. package/src/components/etl-monitor/builder/steps/fields-step.component.tsx +859 -0
  173. package/src/components/etl-monitor/builder/steps/fields-step.scss +459 -0
  174. package/src/components/etl-monitor/builder/steps/general-step.component.tsx +288 -0
  175. package/src/components/etl-monitor/builder/steps/general-step.scss +103 -0
  176. package/src/components/etl-monitor/builder/steps/index.ts +5 -0
  177. package/src/components/etl-monitor/builder/steps/preview-step.component.tsx +245 -0
  178. package/src/components/etl-monitor/builder/steps/preview-step.scss +236 -0
  179. package/src/components/etl-monitor/etl-monitor-card-shell.component.tsx +154 -0
  180. package/src/components/etl-monitor/etl-monitor-card-shell.scss +103 -0
  181. package/src/components/etl-monitor/etl-monitor-dashboard.component.tsx +227 -0
  182. package/src/components/etl-monitor/etl-monitor-dashboard.scss +148 -0
  183. package/src/components/etl-monitor/etl-monitor-status-card.component.tsx +192 -0
  184. package/src/components/etl-monitor/etl-monitors-page.component.tsx +621 -0
  185. package/src/components/etl-monitor/renderers/DetailsRenderer.tsx +2 -0
  186. package/src/components/etl-monitor/renderers/ErrorLogRenderer.tsx +2 -0
  187. package/src/components/etl-monitor/renderers/EtlMonitorWidget.tsx +97 -0
  188. package/src/components/etl-monitor/renderers/MetricsGridRenderer.tsx +2 -0
  189. package/src/components/etl-monitor/renderers/MonitorRenderer.tsx +175 -0
  190. package/src/components/etl-monitor/renderers/ProgressRenderer.tsx +2 -0
  191. package/src/components/etl-monitor/renderers/StatusCardRenderer.tsx +2 -0
  192. package/src/components/etl-monitor/renderers/SummaryCardRenderer.tsx +2 -0
  193. package/src/components/etl-monitor/renderers/TableRenderer.tsx +2 -0
  194. package/src/components/etl-monitor/renderers/component-icons.tsx +33 -0
  195. package/src/components/etl-monitor/renderers/components/details-renderer.tsx +195 -0
  196. package/src/components/etl-monitor/renderers/components/error-log-renderer.tsx +104 -0
  197. package/src/components/etl-monitor/renderers/components/index.ts +6 -0
  198. package/src/components/etl-monitor/renderers/components/log-renderer.tsx +86 -0
  199. package/src/components/etl-monitor/renderers/components/metrics-grid-renderer.tsx +133 -0
  200. package/src/components/etl-monitor/renderers/components/progress-renderer.tsx +188 -0
  201. package/src/components/etl-monitor/renderers/components/status-card-renderer.tsx +247 -0
  202. package/src/components/etl-monitor/renderers/components/summary-card-renderer.tsx +160 -0
  203. package/src/components/etl-monitor/renderers/components/table-renderer.tsx +175 -0
  204. package/src/components/etl-monitor/renderers/components/time-series-renderer.tsx +310 -0
  205. package/src/components/etl-monitor/renderers/data-transformer.ts +361 -0
  206. package/src/components/etl-monitor/renderers/index.ts +11 -0
  207. package/src/components/etl-monitor/renderers/legacy-monitor-renderer.tsx +69 -0
  208. package/src/components/etl-monitor/renderers/monitor-renderer.tsx +6 -0
  209. package/src/components/etl-monitor/renderers/monitor-renderers.scss +1621 -0
  210. package/src/components/etl-monitor/renderers/monitor-widget-card.component.tsx +54 -0
  211. package/src/components/etl-monitor/renderers/monitor-widget-card.scss +16 -0
  212. package/src/components/etl-monitor/renderers/v2-monitor-renderer.tsx +227 -0
  213. package/src/components/import-export/artifact-groups.ts +153 -0
  214. package/src/components/import-export/export-report-modal.component.tsx +265 -0
  215. package/src/components/import-export/import-export-page.component.tsx +610 -0
  216. package/src/components/import-export/import-export.styles.scss +825 -0
  217. package/src/components/import-export/import-package-modal.component.tsx +401 -0
  218. package/src/components/import-export/index.ts +3 -0
  219. package/src/components/indicators/README.md +24 -0
  220. package/src/components/indicators/base-indicator-basic-fields.component.tsx +18 -0
  221. package/src/components/indicators/base-indicator-basics.section.tsx +54 -0
  222. package/src/components/indicators/base-indicator-countby.section.tsx +25 -0
  223. package/src/components/indicators/base-indicator-criteria.section.tsx +83 -0
  224. package/src/components/indicators/base-indicator-details-form.component.tsx +80 -0
  225. package/src/components/indicators/base-indicator-theme-unit-fields.component.tsx +39 -0
  226. package/src/components/indicators/create-base-indicator-modal.component.tsx +623 -0
  227. package/src/components/indicators/create-composite-base-indicator-modal.component.tsx +295 -0
  228. package/src/components/indicators/create-custom-indicator-modal.component.tsx +548 -0
  229. package/src/components/indicators/create-final-indicator-modal.component.tsx +285 -0
  230. package/src/components/indicators/custom-condition-input.component.tsx +310 -0
  231. package/src/components/indicators/diagnosis-filters-form.component.tsx +101 -0
  232. package/src/components/indicators/disaggregation-panel.component.tsx +93 -0
  233. package/src/components/indicators/handler/concept-search-multiselect.component.tsx +153 -0
  234. package/src/components/indicators/handler/question-answer-concept-search.component.tsx +336 -0
  235. package/src/components/indicators/indicator-run-preview-modal.component.tsx +391 -0
  236. package/src/components/indicators/indicator-search-select.component.tsx +297 -0
  237. package/src/components/indicators/indicator-status-tag.component.tsx +9 -0
  238. package/src/components/indicators/indicators-page.component.tsx +673 -0
  239. package/src/components/indicators/indicators-page.scss +121 -0
  240. package/src/components/indicators/indicators-table.component.tsx +188 -0
  241. package/src/components/indicators/preview-table.component.tsx +31 -0
  242. package/src/components/indicators/sections/composite-indicator-basics.section.tsx +36 -0
  243. package/src/components/indicators/sections/composite-indicator-picker.section.tsx +116 -0
  244. package/src/components/indicators/sections/composite-indicator-sql-preview.section.tsx +31 -0
  245. package/src/components/indicators/sections/custom-indicator-basics.section.tsx +65 -0
  246. package/src/components/indicators/sections/custom-indicator-disaggregation.section.tsx +93 -0
  247. package/src/components/indicators/sections/custom-indicator-filters.section.tsx +203 -0
  248. package/src/components/indicators/sections/custom-indicator-population.section.tsx +107 -0
  249. package/src/components/indicators/sections/custom-indicator-sql-preview.section.tsx +108 -0
  250. package/src/components/indicators/sections/final-indicator-basics.section.tsx +46 -0
  251. package/src/components/indicators/sections/final-indicator-disaggregation.section.tsx +47 -0
  252. package/src/components/indicators/sections/final-indicator-picker.section.tsx +60 -0
  253. package/src/components/indicators/sections/final-indicator-preview.section.tsx +20 -0
  254. package/src/components/indicators/sections/final-indicator-results-preview.section.tsx +65 -0
  255. package/src/components/indicators/sections/indicator-basics.section.tsx +40 -0
  256. package/src/components/indicators/sections/indicator-conditions.section.tsx +279 -0
  257. package/src/components/indicators/sections/indicator-sql-preview.section.tsx +63 -0
  258. package/src/components/indicators/sections/indicator-theme.section.tsx +48 -0
  259. package/src/components/indicators/sql-preview.component.tsx +19 -0
  260. package/src/components/indicators/tag-input.component.tsx +177 -0
  261. package/src/components/indicators/types/base-indicator-form.types.ts +17 -0
  262. package/src/components/indicators/types/base-indicator-theme.constants.ts +8 -0
  263. package/src/components/indicators/types/composite-indicator.types.ts +27 -0
  264. package/src/components/indicators/types/condition-ui.types.ts +11 -0
  265. package/src/components/indicators/types/custom-indicator.types.ts +161 -0
  266. package/src/components/indicators/types/data-theme-config.types.ts +88 -0
  267. package/src/components/indicators/types/indicator-types.ts +57 -0
  268. package/src/components/indicators/types/sql-builders.ts +43 -0
  269. package/src/components/indicators/utils/composite-indicator-sql.utils.test.ts +143 -0
  270. package/src/components/indicators/utils/composite-indicator-sql.utils.ts +426 -0
  271. package/src/components/indicators/utils/concept-preload.utils.ts +21 -0
  272. package/src/components/indicators/utils/custom-indicator-interpreter.test.ts +493 -0
  273. package/src/components/indicators/utils/custom-indicator-interpreter.ts +702 -0
  274. package/src/components/indicators/utils/final-indicator-sql.utils.ts +190 -0
  275. package/src/components/indicators/utils/indicator-conditions-hydration.utils.ts +254 -0
  276. package/src/components/indicators/utils/indicator-sql-resolution.utils.ts +204 -0
  277. package/src/components/indicators/utils/indicator-sql.utils.ts +721 -0
  278. package/src/components/indicators/utils/population-sql.compiler.test.ts +999 -0
  279. package/src/components/indicators/utils/population-sql.compiler.ts +858 -0
  280. package/src/components/landing/landing-action-item.component.tsx +50 -0
  281. package/src/components/landing/landing-action-list.component.tsx +12 -0
  282. package/src/components/landing/landing-activity-list.component.tsx +47 -0
  283. package/src/components/landing/landing-footer-grid.component.tsx +45 -0
  284. package/src/components/landing/landing-hero.component.tsx +18 -0
  285. package/src/components/landing/landing-section-card.component.tsx +33 -0
  286. package/src/components/landing/report-builder-landing-page.component.tsx +186 -0
  287. package/src/components/landing/report-builder-landing-page.scss +131 -0
  288. package/src/components/legacy-reports/legacy-report-detail-page.component.tsx +300 -0
  289. package/src/components/legacy-reports/legacy-report-editor-page.component.tsx +324 -0
  290. package/src/components/legacy-reports/legacy-report-upload-modal.component.tsx +307 -0
  291. package/src/components/legacy-reports/legacy-reports-page.component.tsx +206 -0
  292. package/src/components/legacy-reports/legacy-reports-table.component.tsx +154 -0
  293. package/src/components/legacy-reports/tabs/legacy-report-datasets-tab.component.tsx +344 -0
  294. package/src/components/legacy-reports/tabs/legacy-report-dimensions-tab.component.tsx +590 -0
  295. package/src/components/legacy-reports/tabs/legacy-report-indicators-tab.component.tsx +490 -0
  296. package/src/components/legacy-reports/tabs/legacy-report-json-preview-tab.component.tsx +272 -0
  297. package/src/components/legacy-reports/tabs/legacy-report-overview-tab.component.tsx +218 -0
  298. package/src/components/legacy-reports/tabs/legacy-report-parameters-tab.component.tsx +279 -0
  299. package/src/components/linelist/definition/indicator-to-sql.utils.ts +281 -0
  300. package/src/components/linelist/definition/linelist-definition-editor.component.tsx +1676 -0
  301. package/src/components/linelist/definition/linelist-definition-editor.scss +461 -0
  302. package/src/components/linelist/definition/linelist-definition.types.ts +108 -0
  303. package/src/components/linelist/definition/panels/column-properties-panel.component.tsx +328 -0
  304. package/src/components/linelist/definition/panels/column-selector-panel.component.tsx +1082 -0
  305. package/src/components/linelist/definition/panels/column-selector-panel.scss +100 -0
  306. package/src/components/linelist/definition/panels/custom-sql-column-modal.component.tsx +248 -0
  307. package/src/components/linelist/definition/panels/encounter-diagnosis-column-modal.component.tsx +300 -0
  308. package/src/components/linelist/definition/panels/encounter-diagnosis-column-modal.scss +21 -0
  309. package/src/components/linelist/definition/panels/multi-datasource-selector.component.tsx +332 -0
  310. package/src/components/linelist/definition/panels/multi-datasource-selector.scss +161 -0
  311. package/src/components/linelist/definition/panels/observation-column-modal.component.tsx +450 -0
  312. package/src/components/linelist/definition/panels/observation-column-modal.scss +29 -0
  313. package/src/components/linelist/definition/panels/parameter-panel.component.tsx +659 -0
  314. package/src/components/linelist/definition/panels/parameter-panel.scss +64 -0
  315. package/src/components/linelist/definition/panels/population-source-selector.component.tsx +407 -0
  316. package/src/components/linelist/definition/panels/population-source-selector.scss +187 -0
  317. package/src/components/linelist/design/panels/column-category-selector.component.tsx +433 -0
  318. package/src/components/linelist/design/panels/column-category-selector.scss +329 -0
  319. package/src/components/linelist/design/panels/column-debug-panel.component.tsx +197 -0
  320. package/src/components/linelist/design/panels/column-debug-panel.scss +141 -0
  321. package/src/components/linelist/design/panels/data-catalogue.component.tsx +1062 -0
  322. package/src/components/linelist/design/panels/data-catalogue.scss +263 -0
  323. package/src/components/linelist/design/panels/display-settings-panel.component.tsx +405 -0
  324. package/src/components/linelist/design/panels/display-settings-panel.scss +78 -0
  325. package/src/components/linelist/design/panels/filtermap-editor.component.tsx +434 -0
  326. package/src/components/linelist/design/panels/filtermap-editor.scss +176 -0
  327. package/src/components/linelist/design/panels/json-preview.component.tsx +114 -0
  328. package/src/components/linelist/design/panels/json-preview.scss +85 -0
  329. package/src/components/linelist/design/panels/query-config-panel.component.tsx +1137 -0
  330. package/src/components/linelist/design/panels/query-config-panel.scss +342 -0
  331. package/src/components/linelist/design/panels/sort-config-panel.component.tsx +330 -0
  332. package/src/components/linelist/design/panels/sort-config-panel.scss +77 -0
  333. package/src/components/linelist/design/panels/sql-builder-panel.component.tsx +475 -0
  334. package/src/components/linelist/design/panels/sql-builder-panel.scss +226 -0
  335. package/src/components/linelist/design/panels/transformation-panel.component.tsx +687 -0
  336. package/src/components/linelist/design/panels/transformation-panel.scss +81 -0
  337. package/src/components/linelist/design/panels/visual-filter-panel.component.tsx +595 -0
  338. package/src/components/linelist/design/panels/visual-filter-panel.scss +76 -0
  339. package/src/components/linelist/linelist-dashboard.page.component.tsx +756 -0
  340. package/src/components/linelist/linelist-dashboard.page.scss +240 -0
  341. package/src/components/linelist/linelist-editor.page.component.tsx +134 -0
  342. package/src/components/linelist/linelist-editor.page.scss +28 -0
  343. package/src/components/linelist/linelist-run-report.page.component.tsx +715 -0
  344. package/src/components/linelist/linelist-run-report.page.scss +136 -0
  345. package/src/components/linelist/modals/linelist-report-builder-modal.component.tsx +1454 -0
  346. package/src/components/linelist/modals/linelist-report-builder-modal.scss +164 -0
  347. package/src/components/linelist/modals/preview-parameter-modal.component.tsx +361 -0
  348. package/src/components/report/dashboard/reports-table.component.tsx +186 -0
  349. package/src/components/report/definition/panels/properties-panel.component.tsx +38 -0
  350. package/src/components/report/definition/panels/report-structure-panel.component.tsx +61 -0
  351. package/src/components/report/definition/panels/selection-panel.component.tsx +58 -0
  352. package/src/components/report/definition/report-definition-editor.component.tsx +99 -0
  353. package/src/components/report/definition/report-definition.types.ts +66 -0
  354. package/src/components/report/design/panels/IndicatorPropertiesPanel.scss +33 -0
  355. package/src/components/report/design/panels/IndicatorPropertiesPanel.tsx +389 -0
  356. package/src/components/report/design/panels/JsonPreviewPanel.scss +32 -0
  357. package/src/components/report/design/panels/JsonPreviewPanel.tsx +104 -0
  358. package/src/components/report/design/panels/mapping-preview-panel.component.tsx +82 -0
  359. package/src/components/report/design/panels/mapping-preview-panel.scss +55 -0
  360. package/src/components/report/design/panels/template-structure-panel.component.tsx +296 -0
  361. package/src/components/report/design/panels/template-structure-panel.scss +81 -0
  362. package/src/components/report/design/report-design-editor.component.tsx +1170 -0
  363. package/src/components/report/design/report-design.types.ts +48 -0
  364. package/src/components/report/design/report-design.utils.ts +86 -0
  365. package/src/components/report/panels/report-detail.component.tsx +71 -0
  366. package/src/components/report/panels/report-panel-shell.component.tsx +60 -0
  367. package/src/components/report/report-dashboard.page.component.tsx +554 -0
  368. package/src/components/report/report-editor.page.component.tsx +590 -0
  369. package/src/components/report/report.routes.tsx +32 -0
  370. package/src/components/report-sections/create-report-section-modal.component.tsx +388 -0
  371. package/src/components/report-sections/hooks/useAgeCategories.ts +25 -0
  372. package/src/components/report-sections/hooks/useSectionEditorState.ts +449 -0
  373. package/src/components/report-sections/panels/Dhis2MappingPanel.tsx +157 -0
  374. package/src/components/report-sections/panels/DisaggregationPanel.tsx +97 -0
  375. package/src/components/report-sections/panels/IndicatorPickerPanel.tsx +201 -0
  376. package/src/components/report-sections/panels/SectionBasicsPanel.tsx +27 -0
  377. package/src/components/report-sections/report-section-preview-modal.component.tsx +548 -0
  378. package/src/components/report-sections/report-sections-page.component.tsx +325 -0
  379. package/src/components/report-sections/section-disaggregation.utils.ts +637 -0
  380. package/src/components/report-sections/section-types.ts +55 -0
  381. package/src/components/report-sections/section-utils.ts +50 -0
  382. package/src/components/report-sections/sections-page.scss +79 -0
  383. package/src/components/run-reports/run-reports-page.component.tsx +8 -0
  384. package/src/components/security/access-denied-page.component.tsx +69 -0
  385. package/src/components/security/access-denied-page.scss +39 -0
  386. package/src/components/security/privilege-route.component.test.tsx +79 -0
  387. package/src/components/security/privilege-route.component.tsx +39 -0
  388. package/src/components/shared/compile-setup-modal.component.tsx +182 -0
  389. package/src/components/shared/concept-selector.component.tsx +245 -0
  390. package/src/components/shared/concept-selector.scss +121 -0
  391. package/src/components/shared/header/builder-header-actions.component.tsx +38 -0
  392. package/src/components/shared/header/header.component.tsx +85 -0
  393. package/src/components/shared/header/header.scss +150 -0
  394. package/src/components/shared/header/illustration.component.tsx +34 -0
  395. package/src/components/shared/indicator-properties-panel/IndicatorPropertiesPanel.scss +33 -0
  396. package/src/components/shared/indicator-properties-panel/IndicatorPropertiesPanel.tsx +389 -0
  397. package/src/components/shared/json-preview-panel/JsonPreviewPanel.scss +32 -0
  398. package/src/components/shared/json-preview-panel/JsonPreviewPanel.tsx +104 -0
  399. package/src/components/shared/mapping-preview-panel/mapping-preview-panel.component.tsx +82 -0
  400. package/src/components/shared/mapping-preview-panel/mapping-preview-panel.scss +55 -0
  401. package/src/components/shared/preview/query-results-preview.component.tsx +204 -0
  402. package/src/components/shared/template-structure-panel/template-structure-panel.component.tsx +296 -0
  403. package/src/components/shared/template-structure-panel/template-structure-panel.scss +81 -0
  404. package/src/config-schema.ts +43 -0
  405. package/src/constants/privileges.test.ts +53 -0
  406. package/src/constants/privileges.ts +118 -0
  407. package/src/features/dashboards/DashboardRendererPage.tsx +174 -0
  408. package/src/features/dashboards/DashboardsPage.tsx +164 -0
  409. package/src/features/dashboards/EtlDashboardPage.tsx +32 -0
  410. package/src/features/dashboards/components/DashboardEmptyState.tsx +41 -0
  411. package/src/features/dashboards/components/DashboardGrid.tsx +48 -0
  412. package/src/features/dashboards/components/DashboardHeader.tsx +58 -0
  413. package/src/features/dashboards/components/DashboardSection.tsx +46 -0
  414. package/src/features/dashboards/components/ReportLaunchCard.tsx +51 -0
  415. package/src/features/dashboards/components/WidgetSlotDispatcher.tsx +61 -0
  416. package/src/features/dashboards/components/WidgetUnavailableCard.tsx +36 -0
  417. package/src/features/dashboards/dashboards.scss +196 -0
  418. package/src/features/dashboards/hooks/useDashboard.ts +61 -0
  419. package/src/features/dashboards/hooks/useDashboardWidgets.ts +74 -0
  420. package/src/features/dashboards/hooks/useDashboards.ts +56 -0
  421. package/src/features/dashboards/hooks/useMonitorDataMap.ts +84 -0
  422. package/src/features/dashboards/index.ts +11 -0
  423. package/src/features/dashboards/utils/dashboard-config.util.test.ts +255 -0
  424. package/src/features/dashboards/utils/dashboard-config.util.ts +382 -0
  425. package/src/features/report-visualizer/ReportVisualizerPage.tsx +448 -0
  426. package/src/features/report-visualizer/components/Export/ExportMenu.tsx +141 -0
  427. package/src/features/report-visualizer/components/Export/index.ts +2 -0
  428. package/src/features/report-visualizer/components/ReportExplorer/QuickFilters.tsx +152 -0
  429. package/src/features/report-visualizer/components/ReportExplorer/ReportCategoryFilter.tsx +54 -0
  430. package/src/features/report-visualizer/components/ReportExplorer/ReportExplorerPanel.tsx +201 -0
  431. package/src/features/report-visualizer/components/ReportExplorer/ReportExplorerRail.tsx +202 -0
  432. package/src/features/report-visualizer/components/ReportExplorer/ReportList.tsx +287 -0
  433. package/src/features/report-visualizer/components/ReportExplorer/ReportSearch.tsx +151 -0
  434. package/src/features/report-visualizer/components/ReportExplorer/ReportTagFilter.tsx +100 -0
  435. package/src/features/report-visualizer/components/ReportExplorer/RunReportButton.tsx +42 -0
  436. package/src/features/report-visualizer/components/ReportExplorer/RuntimeParameterForm.tsx +259 -0
  437. package/src/features/report-visualizer/components/ReportExplorer/index.ts +8 -0
  438. package/src/features/report-visualizer/components/ReportWorkspace/ReportLayer.tsx +33 -0
  439. package/src/features/report-visualizer/components/ReportWorkspace/ReportSummary.tsx +88 -0
  440. package/src/features/report-visualizer/components/ReportWorkspace/ReportViewTabs.tsx +72 -0
  441. package/src/features/report-visualizer/components/ReportWorkspace/ReportWorkspaceHeader.tsx +92 -0
  442. package/src/features/report-visualizer/components/ReportWorkspace/ReportWorkspacePanel.tsx +242 -0
  443. package/src/features/report-visualizer/components/ReportWorkspace/index.ts +5 -0
  444. package/src/features/report-visualizer/components/TableView/ColumnVisibilityMenu.tsx +206 -0
  445. package/src/features/report-visualizer/components/TableView/ReportTable.tsx +253 -0
  446. package/src/features/report-visualizer/components/TableView/index.ts +2 -0
  447. package/src/features/report-visualizer/hooks/index.ts +4 -0
  448. package/src/features/report-visualizer/hooks/useExplorerPreferences.ts +103 -0
  449. package/src/features/report-visualizer/hooks/useReportCapabilities.ts +112 -0
  450. package/src/features/report-visualizer/hooks/useReportParameters.ts +152 -0
  451. package/src/features/report-visualizer/hooks/useReports.ts +135 -0
  452. package/src/features/report-visualizer/index.ts +6 -0
  453. package/src/features/report-visualizer/report-visualizer.scss +593 -0
  454. package/src/features/report-visualizer/types/index.ts +1 -0
  455. package/src/features/report-visualizer/types/report-visualizer.types.ts +104 -0
  456. package/src/features/report-visualizer/utils/status-colors.ts +117 -0
  457. package/src/globals.d.ts +14 -0
  458. package/src/hooks/address-template.ts +86 -0
  459. package/src/hooks/concept.ts +112 -0
  460. package/src/hooks/indicator/index.ts +1 -0
  461. package/src/hooks/indicator/useIndicatorsByTheme.ts +41 -0
  462. package/src/hooks/openmrs-reference-data.ts +432 -0
  463. package/src/hooks/theme/index.ts +4 -0
  464. package/src/hooks/theme/useDataTheme.ts +61 -0
  465. package/src/hooks/theme/useDataThemes.ts +28 -0
  466. package/src/hooks/theme/useMambaTableMeta.ts +34 -0
  467. package/src/hooks/theme/useMambaTables.ts +95 -0
  468. package/src/hooks/use-report-builder-privileges.test.tsx +98 -0
  469. package/src/hooks/use-report-builder-privileges.ts +58 -0
  470. package/src/index.ts +29 -0
  471. package/src/resources/address-template/address-template.api.ts +180 -0
  472. package/src/resources/agegroup/agegroups.api.ts +58 -0
  473. package/src/resources/concept/concept.api.ts +116 -0
  474. package/src/resources/concepts/concept-mapper.ts +21 -0
  475. package/src/resources/concepts/concept-types.ts +31 -0
  476. package/src/resources/concepts/concepts.resource.ts +44 -0
  477. package/src/resources/concepts/useConceptSearch.ts +41 -0
  478. package/src/resources/dashboard/dashboard.api.ts +107 -0
  479. package/src/resources/etl-monitor/etl-monitor-test.api.ts +409 -0
  480. package/src/resources/etl-monitor/etl-monitor.api.ts +381 -0
  481. package/src/resources/etl-source/etl-source.api.ts +65 -0
  482. package/src/resources/etl-task/etl-task.api.ts +96 -0
  483. package/src/resources/etl-task/etl-task.types.ts +31 -0
  484. package/src/resources/indicator/indicators.api.ts +125 -0
  485. package/src/resources/legacy-reports/legacy-reports.api.ts +144 -0
  486. package/src/resources/linelist/linelist-reports.api.ts +616 -0
  487. package/src/resources/location/location.api.ts +135 -0
  488. package/src/resources/openmrs-api.ts +40 -0
  489. package/src/resources/patient-identifier-type/patient-identifier-type.api.ts +65 -0
  490. package/src/resources/person-attribute-type/person-attribute-type.api.ts +71 -0
  491. package/src/resources/preview/sql-preview.api.ts +54 -0
  492. package/src/resources/report/reports.api.ts +140 -0
  493. package/src/resources/report-category/report-category.api.ts +41 -0
  494. package/src/resources/report-import-export/import-export-api.ts +164 -0
  495. package/src/resources/report-library/report-library.api.ts +63 -0
  496. package/src/resources/report-section/report-sections.api.ts +73 -0
  497. package/src/resources/report-section/section-preview.api.ts +25 -0
  498. package/src/resources/theme/data-theme.api.ts +88 -0
  499. package/src/resources/theme/etl-schema.api.ts +101 -0
  500. package/src/resources/theme/etl-table-meta.api.ts +45 -0
  501. package/src/root.component.tsx +20 -0
  502. package/src/routes/report-builder.routes.tsx +160 -0
  503. package/src/routes/report-builder.scss +56 -0
  504. package/src/routes.json +28 -0
  505. package/src/sample-template.ts +121 -0
  506. package/src/template-utils.ts +110 -0
  507. package/src/types/carbon-react-inert.d.ts +13 -0
  508. package/src/types/condition-operators.ts +63 -0
  509. package/src/types/dashboard/dashboard.types.ts +137 -0
  510. package/src/types/dashboard/index.ts +4 -0
  511. package/src/types/etl/etl-types.ts +238 -0
  512. package/src/types/etl-monitor/etl-monitor-builder.types.ts +555 -0
  513. package/src/types/etl-monitor/etl-monitor-v2.types.ts +397 -0
  514. package/src/types/etl-monitor/etl-monitor.types.ts +188 -0
  515. package/src/types/etl-monitor/index.ts +6 -0
  516. package/src/types/linelist/compile-config.ts +455 -0
  517. package/src/types/linelist-types.ts +2059 -0
  518. package/src/types/theme/data-theme.types.ts +88 -0
  519. package/src/utils/api-error.utils.test.ts +151 -0
  520. package/src/utils/api-error.utils.ts +131 -0
  521. package/src/utils/config-enhancer.ts +62 -0
  522. package/src/utils/etl-browser/etl-browser.util.test.ts +145 -0
  523. package/src/utils/etl-browser/export-csv.util.ts +43 -0
  524. package/src/utils/etl-browser/group-tables.util.ts +84 -0
  525. package/src/utils/etl-browser/index.ts +4 -0
  526. package/src/utils/etl-browser/summarize-columns.util.ts +27 -0
  527. package/src/utils/etl-browser/table-sql.util.ts +20 -0
  528. package/src/utils/etl-monitor/config-v2-adapter.util.ts +460 -0
  529. package/src/utils/etl-monitor/endpoint-config-normalizer.util.ts +383 -0
  530. package/src/utils/etl-monitor/index.ts +10 -0
  531. package/src/utils/etl-monitor/jsonpath-resolver.util.ts +118 -0
  532. package/src/utils/etl-monitor/normalization-examples.ts +0 -0
  533. package/src/utils/etl-monitor/schema-inspector.util.ts +76 -0
  534. package/src/utils/etl-monitor/semantic-type-detector.util.ts +76 -0
  535. package/src/utils/etl-monitor/status-mapper.util.ts +105 -0
  536. package/src/utils/etl-monitor/value-formatters.util.ts +167 -0
  537. package/src/utils/etl-task/etl-task-card.util.test.ts +239 -0
  538. package/src/utils/etl-task/etl-task-card.util.ts +276 -0
  539. package/src/utils/etl-task/index.ts +1 -0
  540. package/src/utils/html-entities.utils.ts +34 -0
  541. package/src/utils/parameter-resolution.ts +218 -0
  542. package/src/views/etl-monitor-builder/add-monitor-modal.component.scss +0 -0
  543. package/src/views/etl-monitor-builder/add-monitor-modal.component.tsx +210 -0
  544. package/src/views/etl-monitor-builder/etl-monitor-builder.component.scss +157 -0
  545. package/src/views/etl-monitor-builder/etl-monitor-builder.component.tsx +252 -0
  546. package/src/views/etl-monitor-builder/etl-monitor-builder.store.ts +126 -0
  547. package/tools/scss-stub.js +12 -0
  548. package/tools/setup-tests.ts +1 -0
  549. package/translations/en.json +65 -0
  550. package/tsconfig.json +43 -0
  551. package/webpack.config.js +30 -0
@@ -0,0 +1,493 @@
1
+ /**
2
+ * Custom Indicator Interpreter Tests
3
+ *
4
+ * Tests for population SQL extraction from CUSTOM indicator SQL.
5
+ * Based on the specification at:
6
+ * https://github.com/openmrs/openmrs-esm-report-builder/blob/main/docs/CUSTOM_INDICATOR_SPEC.md
7
+ */
8
+
9
+ import { describe, test, expect } from '@jest/globals';
10
+ import { customIndicatorInterpreter } from './custom-indicator-interpreter';
11
+
12
+ describe('CustomIndicatorInterpreter', () => {
13
+ describe('extractPopulationSql', () => {
14
+ describe('Simple CUSTOM count', () => {
15
+ test('should extract population from COUNT DISTINCT with subquery', () => {
16
+ // The supported pattern is COUNT DISTINCT with a FROM subquery
17
+ // Must use table.column format: COUNT(DISTINCT table.column)
18
+ const input = `SELECT COUNT(DISTINCT t.patient_id) FROM (SELECT patient_id FROM table_a) t`;
19
+ const result = customIndicatorInterpreter.extractPopulationSql(input);
20
+
21
+ expect(result.success).toBe(true);
22
+ expect(result.sql).toMatch(/SELECT\s+DISTINCT\s+t\.patient_id\s+AS\s+patient_id/i);
23
+ expect(result.sql).toContain('FROM table_a');
24
+ });
25
+ });
26
+
27
+ describe('Aliased CUSTOM count', () => {
28
+ test('should extract population from aliased COUNT DISTINCT', () => {
29
+ const input = `SELECT COUNT(DISTINCT a.client_id)
30
+ FROM (
31
+ SELECT client_id FROM some_table
32
+ ) a
33
+ INNER JOIN other_table o ON a.client_id = o.id`;
34
+ const result = customIndicatorInterpreter.extractPopulationSql(input);
35
+
36
+ expect(result.success).toBe(true);
37
+ expect(result.sql).toMatch(/SELECT\s+DISTINCT\s+a\.client_id\s+AS\s+patient_id/i);
38
+ expect(result.sql).toContain('INNER JOIN other_table');
39
+ });
40
+ });
41
+
42
+ describe('Complex TX_RTT indicator', () => {
43
+ test('should preserve business logic for TX_RTT CD4 < 200', () => {
44
+ // Simplified version to test basic COUNT DISTINCT to SELECT DISTINCT conversion
45
+ // TODO: Improve regex to handle AS keywords in column lists
46
+ const input = `SELECT COUNT(DISTINCT a.client_id) FROM (SELECT client_id FROM mamba_fact_encounter_hiv_art_card WHERE encounter_date <= :endDate GROUP BY client_id) a WHERE a.some_column <= 28`;
47
+
48
+ const result = customIndicatorInterpreter.extractPopulationSql(input);
49
+
50
+ expect(result.success).toBe(true);
51
+ expect(result.sql).toMatch(/SELECT\s+DISTINCT\s+a\.client_id\s+AS\s+patient_id/i);
52
+
53
+ // Verify business logic is preserved
54
+ expect(result.sql).toContain('WHERE encounter_date <= :endDate');
55
+ expect(result.sql).toContain('GROUP BY client_id');
56
+ expect(result.sql).toContain('WHERE a.some_column <= 28');
57
+ });
58
+
59
+ test('should preserve TIMESTAMPDIFF expressions exactly', () => {
60
+ const input = `SELECT COUNT(DISTINCT a.client_id)
61
+ FROM (
62
+ SELECT client_id, TIMESTAMPDIFF(DAY, MAX(return_visit_date), DATE_SUB(:endDate, INTERVAL 3 MONTH)) AS ltfp_days
63
+ FROM mamba_fact_encounter_hiv_art_card
64
+ GROUP BY client_id
65
+ ) a`;
66
+
67
+ const result = customIndicatorInterpreter.extractPopulationSql(input);
68
+
69
+ expect(result.success).toBe(true);
70
+ // DATE_SUB should be preserved exactly
71
+ expect(result.sql).toContain('DATE_SUB(:endDate, INTERVAL 3 MONTH)');
72
+ });
73
+ });
74
+
75
+ describe('CD4 criteria', () => {
76
+ test('should preserve CD4 < 200', () => {
77
+ const input = `SELECT COUNT(DISTINCT a.client_id)
78
+ FROM (
79
+ SELECT client_id FROM mamba_fact_encounter_hiv_art_card
80
+ WHERE cd4 < 200
81
+ ) a`;
82
+
83
+ const result = customIndicatorInterpreter.extractPopulationSql(input);
84
+
85
+ expect(result.success).toBe(true);
86
+ expect(result.sql).toContain('WHERE cd4 < 200');
87
+ });
88
+
89
+ test('should preserve CD4 >= 200', () => {
90
+ const input = `SELECT COUNT(DISTINCT a.client_id)
91
+ FROM (
92
+ SELECT client_id FROM mamba_fact_encounter_hiv_art_card
93
+ WHERE cd4 >= 200
94
+ ) a`;
95
+
96
+ const result = customIndicatorInterpreter.extractPopulationSql(input);
97
+
98
+ expect(result.success).toBe(true);
99
+ expect(result.sql).toContain('WHERE cd4 >= 200');
100
+ });
101
+
102
+ test('should preserve CD4 IS NULL', () => {
103
+ const input = `SELECT COUNT(DISTINCT a.client_id)
104
+ FROM (
105
+ SELECT client_id FROM mamba_fact_encounter_hiv_art_card
106
+ WHERE cd4 IS NULL
107
+ ) a`;
108
+
109
+ const result = customIndicatorInterpreter.extractPopulationSql(input);
110
+
111
+ expect(result.success).toBe(true);
112
+ expect(result.sql).toContain('WHERE cd4 IS NULL');
113
+ });
114
+ });
115
+
116
+ describe('Patient ID normalization', () => {
117
+ test('should normalize client_id to patient_id', () => {
118
+ // Use a format that matches Pattern 1
119
+ const input = `SELECT COUNT(DISTINCT a.client_id) FROM (SELECT client_id FROM table_a) a`;
120
+ const result = customIndicatorInterpreter.extractPopulationSql(input);
121
+
122
+ expect(result.success).toBe(true);
123
+ expect(result.sql).toMatch(/AS\s+patient_id/i);
124
+ });
125
+
126
+ test('should detect patient_id column from inner query', () => {
127
+ const input = `SELECT COUNT(DISTINCT a.client_id)
128
+ FROM (
129
+ SELECT patient_id FROM some_table GROUP BY patient_id
130
+ ) a`;
131
+
132
+ const result = customIndicatorInterpreter.extractPopulationSql(input);
133
+
134
+ expect(result.success).toBe(true);
135
+ // The interpreter normalizes to patient_id in the output SQL
136
+ // but tracks the source column (client_id from the COUNT expression)
137
+ expect(result.patientIdColumn).toBe('client_id');
138
+ expect(result.sql).toContain('AS patient_id');
139
+ });
140
+ });
141
+
142
+ describe('Already aggregated age/gender CUSTOM SQL', () => {
143
+ test('should extract population from age_group disaggregated query', () => {
144
+ const input = `SELECT mda.datim_agegroup AS age_group, mdp.gender AS sex, COUNT(DISTINCT a.client_id) AS value
145
+ FROM (SELECT client_id FROM table_a) a
146
+ GROUP BY age_group, sex`;
147
+
148
+ const result = customIndicatorInterpreter.extractPopulationSql(input);
149
+
150
+ expect(result.success).toBe(true);
151
+ // Result should be patient-level, not already aggregated
152
+ expect(result.sql).not.toContain('age_group');
153
+ expect(result.sql).not.toContain('COUNT(DISTINCT');
154
+ expect(result.sql).toMatch(/SELECT\s+DISTINCT/i);
155
+ });
156
+
157
+ test('should handle hardcoded age_group values like PWIDS', () => {
158
+ const input = `SELECT 'PWIDS' AS age_group, 'F' AS sex, COUNT(DISTINCT a.client_id) AS value
159
+ FROM (SELECT client_id FROM table_a) a`;
160
+
161
+ const result = customIndicatorInterpreter.extractPopulationSql(input);
162
+
163
+ // This SQL doesn't match our standard patterns because it has:
164
+ // 1. Hardcoded values for age_group ('PWIDS')
165
+ // 2. Hardcoded value for sex ('F')
166
+ // 3. No GROUP BY age_group, sex at the end (Pattern 0 requirement)
167
+ //
168
+ // The interpreter should either:
169
+ // 1. Extract the population from the subquery
170
+ // 2. Fail gracefully with a meaningful error
171
+ if (result.success) {
172
+ // If extraction succeeds, verify the population is clean
173
+ expect(result.sql).toMatch(/SELECT\s+DISTINCT/i);
174
+ // The result should not have the hardcoded age_group/sex values
175
+ expect(result.sql).not.toMatch(/SELECT\s+'PWIDS'\s+AS\s+age_group/i);
176
+ } else {
177
+ // If extraction fails, verify we have a meaningful error
178
+ const hasError = result.warnings?.some(w => w.includes('CUSTOM_'));
179
+ expect(hasError).toBe(true);
180
+ }
181
+ });
182
+ });
183
+
184
+ describe('Invalid extraction', () => {
185
+ test('should reject malformed generated identifiers', () => {
186
+ // This simulates the bug where ltfp_days (a calculated column) was used as a table alias
187
+ const input = `SELECT COUNT(DISTINCT ltfp_days.client_id)
188
+ FROM ...`;
189
+
190
+ // The pattern should not match because ltfp_days.client_id is not valid
191
+ // (ltfp_days is a column alias, not a table)
192
+ const result = customIndicatorInterpreter.extractPopulationSql(input);
193
+
194
+ // Should fail gracefully or not produce invalid SQL
195
+ if (!result.success) {
196
+ // Check that at least one warning contains an error code
197
+ const hasError = result.warnings?.some(w => w.includes('CUSTOM_'));
198
+ expect(hasError).toBe(true);
199
+ }
200
+ });
201
+
202
+ test('should handle empty SQL', () => {
203
+ const result = customIndicatorInterpreter.extractPopulationSql('');
204
+
205
+ expect(result.success).toBe(false);
206
+ // Check that at least one warning contains the error code
207
+ const hasMissingError = result.warnings?.some(w => w.includes('CUSTOM_POPULATION_MISSING'));
208
+ expect(hasMissingError).toBe(true);
209
+ });
210
+
211
+ test('should handle simple COUNT query without clear structure', () => {
212
+ const input = 'SELECT COUNT(*) FROM table_a';
213
+
214
+ const result = customIndicatorInterpreter.extractPopulationSql(input);
215
+
216
+ expect(result.success).toBe(false);
217
+ // Check that at least one warning contains the error code
218
+ const hasAggregatedError = result.warnings?.some(w => w.includes('CUSTOM_POPULATION_AGGREGATED'));
219
+ expect(hasAggregatedError).toBe(true);
220
+ });
221
+ });
222
+
223
+ describe('TX-RTT style disaggregated queries', () => {
224
+ test('should extract population from TX-RTT style already-disaggregated query', () => {
225
+ // This is the actual TX-RTT pattern with age_group, sex, and COUNT(DISTINCT)
226
+ const input = `SELECT mda.datim_agegroup AS age_group, mdp.gender AS sex, COUNT(DISTINCT a.client_id) AS value
227
+ FROM (SELECT client_id, TIMESTAMPDIFF(DAY, MAX(return_visit_date), :endDate) AS ltfp_days FROM mamba_fact_encounter_hiv_art_card WHERE encounter_date <= :endDate GROUP BY client_id) a
228
+ INNER JOIN mamba_fact_patients_latest_patient_demographics mdp ON a.client_id = mdp.patient_id
229
+ WHERE a.ltfp_days <= 28
230
+ GROUP BY age_group, sex`;
231
+
232
+ const result = customIndicatorInterpreter.extractPopulationSql(input);
233
+
234
+ // For now, this should either succeed with proper extraction or fail gracefully
235
+ // The TX-RTT pattern is complex and may need additional refinement
236
+ if (result.success) {
237
+ // If extraction succeeds, verify the structure
238
+ expect(result.sql).toMatch(/SELECT\s+DISTINCT/i);
239
+ expect(result.sql).not.toContain('COUNT(DISTINCT');
240
+ } else {
241
+ // If extraction fails, verify we have a meaningful error
242
+ const hasError = result.warnings?.some(w => w.includes('CUSTOM_'));
243
+ expect(hasError).toBe(true);
244
+ }
245
+ });
246
+ });
247
+ });
248
+
249
+ describe('applyDisaggregation', () => {
250
+ describe('Validation', () => {
251
+ test('should reject empty population SQL', () => {
252
+ const result = customIndicatorInterpreter.applyDisaggregation(
253
+ '',
254
+ { ageCategoryCode: 'MOH_105_OPD_DIAG', genders: ['F', 'M'] }
255
+ );
256
+
257
+ expect(result).toContain('CUSTOM_POPULATION_EMPTY');
258
+ });
259
+
260
+ test('should reject COUNT aggregate instead of population', () => {
261
+ const result = customIndicatorInterpreter.applyDisaggregation(
262
+ 'SELECT COUNT(*) FROM table_a',
263
+ { ageCategoryCode: 'MOH_105_OPD_DIAG', genders: ['F', 'M'] }
264
+ );
265
+
266
+ expect(result).toContain('CUSTOM_POPULATION_AGGREGATED');
267
+ });
268
+
269
+ test('should reject SQL without patient_id column', () => {
270
+ const result = customIndicatorInterpreter.applyDisaggregation(
271
+ 'SELECT DISTINCT some_column FROM table_a',
272
+ { ageCategoryCode: 'MOH_105_OPD_DIAG', genders: ['F', 'M'] }
273
+ );
274
+
275
+ expect(result).toContain('CUSTOM_PATIENT_ID_MISSING');
276
+ });
277
+
278
+ test('should reject already disaggregated SQL with age_group', () => {
279
+ // Create SQL that has both patient_id and age_group with AS
280
+ // to test that age_group validation comes first
281
+ const result = customIndicatorInterpreter.applyDisaggregation(
282
+ 'SELECT DISTINCT mda.code AS age_group, a.client_id AS patient_id FROM table_a a',
283
+ { ageCategoryCode: 'MOH_105_OPD_DIAG', genders: ['F', 'M'] }
284
+ );
285
+
286
+ expect(result).toContain('CUSTOM_ALREADY_DISAGGREGATED');
287
+ });
288
+ });
289
+
290
+ describe('Valid disaggregation', () => {
291
+ test('should generate canonical disaggregation SQL', () => {
292
+ const populationSql = `SELECT DISTINCT a.client_id AS patient_id
293
+ FROM (
294
+ SELECT client_id FROM mamba_fact_encounter_hiv_art_card
295
+ ) a
296
+ WHERE a.some_column = 'value'`;
297
+
298
+ const result = customIndicatorInterpreter.applyDisaggregation(
299
+ populationSql,
300
+ { ageCategoryCode: 'MOH_105_OPD_DIAG', genders: ['F', 'M'] }
301
+ );
302
+
303
+ // Should generate the canonical structure
304
+ expect(result).toContain('WITH base_pop AS (');
305
+ expect(result).toContain('ag AS (');
306
+ expect(result).toContain('ag.age_group_id');
307
+ expect(result).toContain('ag.label');
308
+ expect(result).toContain('ag.min_age_days');
309
+ expect(result).toContain('ag.max_age_days');
310
+ expect(result).toContain("ac.code = 'MOH_105_OPD_DIAG'");
311
+ expect(result).toContain('COUNT(DISTINCT base_pop.patient_id) AS value');
312
+ expect(result).toMatch(/FROM\s+base_pop/);
313
+ expect(result).toMatch(/JOIN\s+mamba_fact_patients_latest_patient_demographics\s+mdp/);
314
+ expect(result).toContain('ON mdp.patient_id = base_pop.patient_id');
315
+ });
316
+
317
+ test('should use custom patient ID column from config', () => {
318
+ const populationSql = `SELECT DISTINCT a.client_id AS patient_id
319
+ FROM table_a a`;
320
+
321
+ const result = customIndicatorInterpreter.applyDisaggregation(
322
+ populationSql,
323
+ { ageCategoryCode: 'MOH_105_OPD_DIAG', genders: ['F', 'M'] },
324
+ {
325
+ version: 1,
326
+ patientIdColumn: 'client_id',
327
+ populationQuery: { extractFrom: 'configJson' },
328
+ supportsRedisaggregation: true,
329
+ redisaggregationStrategy: 'population-extraction',
330
+ themeIndependent: true
331
+ }
332
+ );
333
+
334
+ // Should use the custom patient ID column
335
+ expect(result).toContain('ON mdp.client_id = base_pop.client_id');
336
+ });
337
+
338
+ test('should preserve population business logic in base_pop', () => {
339
+ const populationSql = `SELECT DISTINCT a.client_id AS patient_id
340
+ FROM (
341
+ SELECT
342
+ client_id,
343
+ TIMESTAMPDIFF(DAY, MAX(return_visit_date), :endDate) AS ltfp_days
344
+ FROM mamba_fact_encounter_hiv_art_card
345
+ WHERE encounter_date <= :endDate
346
+ GROUP BY client_id
347
+ ) a
348
+ LEFT JOIN person p ON p.person_id = a.client_id
349
+ WHERE a.ltfp_days <= 28 AND p.person_id IS NULL`;
350
+
351
+ const result = customIndicatorInterpreter.applyDisaggregation(
352
+ populationSql,
353
+ { ageCategoryCode: 'MOH_105_OPD_DIAG', genders: ['F', 'M'] }
354
+ );
355
+
356
+ // All business logic should be preserved in base_pop
357
+ expect(result).toContain('TIMESTAMPDIFF(DAY, MAX(return_visit_date)');
358
+ expect(result).toContain('WHERE encounter_date <= :endDate');
359
+ expect(result).toContain('LEFT JOIN person p');
360
+ expect(result).toContain('WHERE a.ltfp_days <= 28');
361
+ });
362
+ });
363
+
364
+ describe('Trailing semicolon handling', () => {
365
+ test('should remove trailing semicolon from population SQL', () => {
366
+ const populationSql = `SELECT DISTINCT client_id AS patient_id FROM table_a;`;
367
+
368
+ const result = customIndicatorInterpreter.applyDisaggregation(
369
+ populationSql,
370
+ { ageCategoryCode: 'MOH_105_OPD_DIAG', genders: ['F', 'M'] }
371
+ );
372
+
373
+ // The base_pop CTE should not have a semicolon inside
374
+ expect(result).toMatch(/WITH base_pop AS \(\s+SELECT DISTINCT/);
375
+ // But the final query should have one
376
+ expect(result).toMatch(/ORDER BY ag\.sort_order, g\.gender;?$/);
377
+ });
378
+ });
379
+
380
+ describe('Nested subquery handling (balanced parenthesis matching)', () => {
381
+ test('should handle nested subqueries in inner query (TX_RTT CD4 >= 200 pattern)', () => {
382
+ // This is the actual bug we're fixing - nested subqueries within the inner query
383
+ // The inner query has LEFT JOIN with subqueries, DATE_SUB with INTERVAL, etc.
384
+ const input = `SELECT COUNT(DISTINCT a.client_id)
385
+ FROM (
386
+ SELECT client_id, TIMESTAMPDIFF(DAY, MAX(return_visit_date), :endDate) ltfp_days
387
+ FROM mamba_fact_encounter_hiv_art_card
388
+ WHERE encounter_date <= :endDate
389
+ AND return_visit_date >= :startDate
390
+ GROUP BY client_id
391
+ ) a
392
+ INNER JOIN mamba_fact_patients_latest_patient_demographics mdp ON a.client_id = mdp.patient_id
393
+ LEFT JOIN (SELECT * FROM person p WHERE p.dead = 1 AND p.death_date <= :endDate) p ON a.client_id = p.person_id
394
+ LEFT JOIN (
395
+ SELECT mf_to.client_id
396
+ FROM mamba_fact_transfer_out mf_to
397
+ LEFT JOIN mamba_fact_transfer_in mf_ti ON mf_to.client_id = mf_ti.client_id
398
+ WHERE transfer_out_date <= :endDate
399
+ AND (transfer_out_date > transfer_in_date OR mf_ti.client_id IS NULL)
400
+ ) mfto ON a.client_id = mfto.client_id
401
+ INNER JOIN (
402
+ SELECT DISTINCT a.client_id
403
+ FROM (
404
+ SELECT client_id,
405
+ TIMESTAMPDIFF(DAY, MAX(return_visit_date), DATE_SUB(:endDate, INTERVAL 3 MONTH)) ltfp_days
406
+ FROM mamba_fact_encounter_hiv_art_card
407
+ WHERE encounter_date <= DATE_SUB(:endDate, INTERVAL 3 MONTH)
408
+ AND return_visit_date <= DATE_SUB(:endDate, INTERVAL 3 MONTH)
409
+ GROUP BY client_id
410
+ ) a
411
+ WHERE a.ltfp_days <= 28
412
+ ) filter_client ON a.client_id = filter_client.client_id`;
413
+
414
+ const result = customIndicatorInterpreter.extractPopulationSql(input);
415
+
416
+ // With balanced parenthesis matching, this should now succeed
417
+ expect(result.success).toBe(true);
418
+ expect(result.sql).toMatch(/SELECT\s+DISTINCT\s+a\.client_id\s+AS\s+patient_id/i);
419
+
420
+ // Verify the complete inner query is preserved
421
+ expect(result.sql).toContain('TIMESTAMPDIFF(DAY, MAX(return_visit_date), :endDate)');
422
+ expect(result.sql).toContain('WHERE encounter_date <= :endDate');
423
+
424
+ // Verify nested subqueries are preserved
425
+ expect(result.sql).toContain('SELECT * FROM person p WHERE p.dead = 1');
426
+ expect(result.sql).toContain('SELECT mf_to.client_id');
427
+ expect(result.sql).toContain('SELECT DISTINCT a.client_id');
428
+
429
+ // Verify the most deeply nested subquery with DATE_SUB is preserved
430
+ expect(result.sql).toContain('DATE_SUB(:endDate, INTERVAL 3 MONTH)');
431
+ });
432
+
433
+ test('should handle deeply nested subqueries with INTERVAL expressions', () => {
434
+ // Test with multiple levels of nesting and INTERVAL expressions
435
+ const input = `SELECT COUNT(DISTINCT base.client_id)
436
+ FROM (
437
+ SELECT DISTINCT a.client_id
438
+ FROM (
439
+ SELECT client_id
440
+ FROM mamba_fact_encounter_hiv_art_card
441
+ WHERE encounter_date <= DATE_SUB(:endDate, INTERVAL 3 MONTH)
442
+ GROUP BY client_id
443
+ ) a
444
+ INNER JOIN (
445
+ SELECT client_id, cd4_result
446
+ FROM mamba_fact_lab_results
447
+ WHERE test_date >= DATE_SUB(:endDate, INTERVAL 3 MONTH)
448
+ ) lab ON a.client_id = lab.client_id
449
+ ) base`;
450
+
451
+ const result = customIndicatorInterpreter.extractPopulationSql(input);
452
+
453
+ expect(result.success).toBe(true);
454
+ // Verify all levels of nesting are preserved
455
+ expect(result.sql).toContain('DATE_SUB(:endDate, INTERVAL 3 MONTH)');
456
+ expect(result.sql).toContain('mamba_fact_encounter_hiv_art_card');
457
+ expect(result.sql).toContain('mamba_fact_lab_results');
458
+ });
459
+
460
+ test('should handle TX_ML pattern with quoted parameters and extra column', () => {
461
+ // This is the TX_ML pattern with:
462
+ // 1. Quoted parameters like ':endDate' instead of :endDate
463
+ // 2. Extra column before COUNT: SELECT 'PWIDS', COUNT(DISTINCT a.client_id)
464
+ const input = `SELECT 'PWIDS', COUNT(DISTINCT a.client_id)
465
+ FROM (SELECT a.client_id, return_date
466
+ FROM (SELECT client_id, MAX(return_visit_date) return_date
467
+ FROM mamba_fact_encounter_hiv_art_card
468
+ WHERE encounter_date <= ':endDate'
469
+ AND return_visit_date >= DATE_SUB(':startDate', INTERVAL 3 MONTH)
470
+ GROUP BY client_id) a
471
+ WHERE TIMESTAMPDIFF(DAY, return_date, ':endDate') > 28
472
+ AND TIMESTAMPDIFF(DAY, return_date, DATE_SUB(':endDate', INTERVAL 3 MONTH)) <= 28) a
473
+ INNER JOIN mamba_fact_patients_latest_patient_demographics mdp ON a.client_id = mdp.patient_id
474
+ INNER JOIN (SELECT client_id
475
+ FROM mamba_fact_encounter_hiv_art_summary
476
+ WHERE special_category = 'Current drug user') special_category
477
+ ON a.client_id = special_category.client_id`;
478
+
479
+ const result = customIndicatorInterpreter.extractPopulationSql(input);
480
+
481
+ expect(result.success).toBe(true);
482
+ expect(result.sql).toMatch(/SELECT\s+DISTINCT\s+a\.client_id\s+AS\s+patient_id/i);
483
+
484
+ // Verify quoted parameters are converted to unquoted
485
+ expect(result.sql).toMatch(/:endDate/);
486
+ expect(result.sql).not.toMatch(/':endDate'/);
487
+
488
+ // Verify the special_category filter is preserved
489
+ expect(result.sql).toContain('special_category = \'Current drug user\'');
490
+ });
491
+ });
492
+ });
493
+ });