@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.
- package/.eslintignore +5 -0
- package/.eslintrc.js +19 -0
- package/.prettierrc +8 -0
- package/.tx/config +12 -0
- package/LICENSE +401 -0
- package/README.md +336 -0
- package/__mocks__/react-i18next.js +12 -0
- package/dist/162.js +1 -0
- package/dist/162.js.map +1 -0
- package/dist/184.js +2 -0
- package/dist/184.js.LICENSE.txt +14 -0
- package/dist/184.js.map +1 -0
- package/dist/242.js +2 -0
- package/dist/242.js.LICENSE.txt +21 -0
- package/dist/242.js.map +1 -0
- package/dist/253.js +1 -0
- package/dist/253.js.map +1 -0
- package/dist/300.js +1 -0
- package/dist/311.js +1 -0
- package/dist/311.js.map +1 -0
- package/dist/469.js +2 -0
- package/dist/469.js.LICENSE.txt +45 -0
- package/dist/469.js.map +1 -0
- package/dist/478.js +2 -0
- package/dist/478.js.LICENSE.txt +9 -0
- package/dist/478.js.map +1 -0
- package/dist/539.js +1 -0
- package/dist/539.js.map +1 -0
- package/dist/540.js +2 -0
- package/dist/540.js.LICENSE.txt +9 -0
- package/dist/540.js.map +1 -0
- package/dist/669.js +1 -0
- package/dist/669.js.map +1 -0
- package/dist/710.js +1 -0
- package/dist/710.js.map +1 -0
- package/dist/729.js +1 -0
- package/dist/729.js.map +1 -0
- package/dist/838.js +2 -0
- package/dist/838.js.LICENSE.txt +9 -0
- package/dist/838.js.map +1 -0
- package/dist/917.js +1 -0
- package/dist/917.js.map +1 -0
- package/dist/961.js +2 -0
- package/dist/961.js.LICENSE.txt +19 -0
- package/dist/961.js.map +1 -0
- package/dist/main.js +1 -0
- package/dist/main.js.map +1 -0
- package/dist/openmrs-esm-report-builder.js +1 -0
- package/dist/openmrs-esm-report-builder.js.buildmanifest.json +530 -0
- package/dist/openmrs-esm-report-builder.js.map +1 -0
- package/dist/routes.json +1 -0
- package/e2e/core/env.ts +5 -0
- package/e2e/core/global-setup.ts +34 -0
- package/e2e/core/test.ts +21 -0
- package/e2e/pages/app-shell.page.ts +34 -0
- package/e2e/pages/etl-sources.page.ts +35 -0
- package/e2e/pages/report-sections.page.ts +20 -0
- package/e2e/pages/section-preview.modal.ts +29 -0
- package/e2e/specs/etl-sources.spec.ts +51 -0
- package/e2e/specs/navigation.spec.ts +13 -0
- package/e2e/specs/section-preview-validation.spec.ts +38 -0
- package/e2e/specs/section-preview.spec.ts +94 -0
- package/e2e/support/github/Dockerfile +22 -0
- package/e2e/support/github/Dockerfile.dockerignore +13 -0
- package/e2e/support/github/docker-compose.yml +31 -0
- package/e2e/support/github/run-e2e-docker-env.sh +131 -0
- package/e2e/support/github/spa-assemble-config.json +9 -0
- package/e2e/tsconfig.json +11 -0
- package/example.env +5 -0
- package/jest.config.js +28 -0
- package/linelist_report_builder_design_spec.md +816 -0
- package/package.json +111 -0
- package/playwright.config.ts +28 -0
- package/routes.json +22 -0
- package/src/__debug__/all-designs.test.tsx +30 -0
- package/src/admin-card-link.extension.module.scss +55 -0
- package/src/admin-card-link.extension.tsx +24 -0
- package/src/components/admin/admin-page.component.tsx +75 -0
- package/src/components/admin/age-categories-page.component.tsx +109 -0
- package/src/components/admin/age-groups-page.component.tsx +146 -0
- package/src/components/admin/etl-browser/columns-tab.component.tsx +80 -0
- package/src/components/admin/etl-browser/etl-browser-page.component.tsx +309 -0
- package/src/components/admin/etl-browser/etl-browser.component.scss +424 -0
- package/src/components/admin/etl-browser/metadata-tab.component.tsx +65 -0
- package/src/components/admin/etl-browser/preview-grid.component.tsx +189 -0
- package/src/components/admin/etl-browser/relationships-tab.component.tsx +21 -0
- package/src/components/admin/etl-browser/sources-tree.component.tsx +118 -0
- package/src/components/admin/etl-browser/table-detail.component.tsx +124 -0
- package/src/components/admin/etl-browser/table-info-strip.component.tsx +51 -0
- package/src/components/admin/etl-browser/table-list.component.tsx +136 -0
- package/src/components/admin/etl-browser/use-table-preview.ts +47 -0
- package/src/components/admin/etl-sources-page.component.tsx +355 -0
- package/src/components/admin/etl-tasks-page.component.scss +370 -0
- package/src/components/admin/etl-tasks-page.component.tsx +768 -0
- package/src/components/admin/report-categories-page.component.tsx +267 -0
- package/src/components/admin/report-library-page.component.tsx +551 -0
- package/src/components/ai-support/ai-assist-button.component.tsx +30 -0
- package/src/components/ai-support/ai-assist-modal.component.tsx +81 -0
- package/src/components/ai-support/ai.service.ts +21 -0
- package/src/components/ai-support/ai.types.ts +12 -0
- package/src/components/app-shell/report-builder-shell.component.tsx +242 -0
- package/src/components/app-shell/report-builder-shell.scss +18 -0
- package/src/components/dashboards/dashboard-config.schema.ts +97 -0
- package/src/components/dashboards/dashboard-form-modal.component.tsx +150 -0
- package/src/components/dashboards/dashboards-admin.scss +62 -0
- package/src/components/dashboards/dashboards-page.component.tsx +153 -0
- package/src/components/dashboards/dashboards-table.component.tsx +130 -0
- package/src/components/dashboards/sections/dashboard-autoinclude.section.tsx +75 -0
- package/src/components/dashboards/sections/dashboard-basics.section.tsx +80 -0
- package/src/components/dashboards/sections/dashboard-json.section.tsx +79 -0
- package/src/components/dashboards/sections/dashboard-sections-editor.section.tsx +130 -0
- package/src/components/dashboards/sections/dashboard-widgets-editor.section.tsx +184 -0
- package/src/components/data-themes/data-theme-form.component.tsx +59 -0
- package/src/components/data-themes/data-theme-modal.component.tsx +349 -0
- package/src/components/data-themes/data-themes-page.component.tsx +139 -0
- package/src/components/data-themes/data-themes-table.component.tsx +98 -0
- package/src/components/data-themes/icon-registry.ts +35 -0
- package/src/components/data-themes/sections/data-theme-basics.section.tsx +63 -0
- package/src/components/data-themes/sections/data-theme-condition-columns.section.tsx +397 -0
- package/src/components/data-themes/sections/data-theme-fields-editor.section.tsx +125 -0
- package/src/components/data-themes/sections/data-theme-metadata.section.tsx +245 -0
- package/src/components/data-themes/sections/data-theme-preview.section.tsx +115 -0
- package/src/components/data-themes/sections/data-theme-source.section.tsx +184 -0
- package/src/components/data-visualizer/components/cqi-components/cqi-data-table.component.tsx +214 -0
- package/src/components/data-visualizer/components/cqi-components/cqi-elements.ts +203 -0
- package/src/components/data-visualizer/components/cqi-components/detail.component.tsx +110 -0
- package/src/components/data-visualizer/components/cqi-components/detail.scss +22 -0
- package/src/components/data-visualizer/components/data-table/data-table.component.tsx +208 -0
- package/src/components/data-visualizer/components/data-table/data-tables.scss +206 -0
- package/src/components/data-visualizer/components/empty-state/empty-state-illustration.component.tsx +53 -0
- package/src/components/data-visualizer/components/header/header.component.tsx +43 -0
- package/src/components/data-visualizer/components/header/header.scss +71 -0
- package/src/components/data-visualizer/components/model/confirm.component.tsx +40 -0
- package/src/components/data-visualizer/components/panel/panel.component.tsx +20 -0
- package/src/components/data-visualizer/components/panel/panel.scss +25 -0
- package/src/components/data-visualizer/components/popover/modifier-panel.tsx +53 -0
- package/src/components/data-visualizer/constants.ts +445 -0
- package/src/components/data-visualizer/data-visualizer-illustration.component.tsx +31 -0
- package/src/components/data-visualizer/data-visualizer.component.tsx +1935 -0
- package/src/components/data-visualizer/data-visualizer.resource.ts +462 -0
- package/src/components/data-visualizer/data-visualizer.scss +449 -0
- package/src/components/data-visualizer/declarations.d.ts +131 -0
- package/src/components/data-visualizer/parameter-inputs/boolean-parameter-input.component.tsx +34 -0
- package/src/components/data-visualizer/parameter-inputs/concept-parameter-input.component.tsx +81 -0
- package/src/components/data-visualizer/parameter-inputs/date-parameter-input.component.tsx +110 -0
- package/src/components/data-visualizer/parameter-inputs/date-range-input.component.scss +47 -0
- package/src/components/data-visualizer/parameter-inputs/date-range-input.component.tsx +228 -0
- package/src/components/data-visualizer/parameter-inputs/identifier-type-parameter-input.component.tsx +69 -0
- package/src/components/data-visualizer/parameter-inputs/list-parameter-input.component.tsx +73 -0
- package/src/components/data-visualizer/parameter-inputs/location-parameter-input.component.tsx +81 -0
- package/src/components/data-visualizer/parameter-inputs/number-parameter-input.component.tsx +40 -0
- package/src/components/data-visualizer/parameter-inputs/person-attribute-parameter-input.component.tsx +71 -0
- package/src/components/data-visualizer/parameter-inputs/text-parameter-input.component.tsx +52 -0
- package/src/components/data-visualizer/report-parameter-modal.component.tsx +383 -0
- package/src/components/data-visualizer/types.ts +206 -0
- package/src/components/etl-monitor/builder/builder-state-machine.ts +381 -0
- package/src/components/etl-monitor/builder/design-registry.tsx +139 -0
- package/src/components/etl-monitor/builder/etl-monitor-builder-context.tsx +702 -0
- package/src/components/etl-monitor/builder/etl-monitor-builder-modal.component.tsx +461 -0
- package/src/components/etl-monitor/builder/etl-monitor-builder-modal.scss +256 -0
- package/src/components/etl-monitor/builder/etl-monitor-builder-page.component.tsx +387 -0
- package/src/components/etl-monitor/builder/etl-monitor-builder-page.scss +976 -0
- package/src/components/etl-monitor/builder/generated-config-panel.component.tsx +168 -0
- package/src/components/etl-monitor/builder/generated-config-panel.scss +198 -0
- package/src/components/etl-monitor/builder/index.ts +5 -0
- package/src/components/etl-monitor/builder/monitor-preview.tsx +124 -0
- package/src/components/etl-monitor/builder/steps/data-source-step.component.tsx +667 -0
- package/src/components/etl-monitor/builder/steps/data-source-step.scss +284 -0
- package/src/components/etl-monitor/builder/steps/design-samples.ts +278 -0
- package/src/components/etl-monitor/builder/steps/design-step.component.tsx +299 -0
- package/src/components/etl-monitor/builder/steps/design-step.scss +479 -0
- package/src/components/etl-monitor/builder/steps/fields-step.component.tsx +859 -0
- package/src/components/etl-monitor/builder/steps/fields-step.scss +459 -0
- package/src/components/etl-monitor/builder/steps/general-step.component.tsx +288 -0
- package/src/components/etl-monitor/builder/steps/general-step.scss +103 -0
- package/src/components/etl-monitor/builder/steps/index.ts +5 -0
- package/src/components/etl-monitor/builder/steps/preview-step.component.tsx +245 -0
- package/src/components/etl-monitor/builder/steps/preview-step.scss +236 -0
- package/src/components/etl-monitor/etl-monitor-card-shell.component.tsx +154 -0
- package/src/components/etl-monitor/etl-monitor-card-shell.scss +103 -0
- package/src/components/etl-monitor/etl-monitor-dashboard.component.tsx +227 -0
- package/src/components/etl-monitor/etl-monitor-dashboard.scss +148 -0
- package/src/components/etl-monitor/etl-monitor-status-card.component.tsx +192 -0
- package/src/components/etl-monitor/etl-monitors-page.component.tsx +621 -0
- package/src/components/etl-monitor/renderers/DetailsRenderer.tsx +2 -0
- package/src/components/etl-monitor/renderers/ErrorLogRenderer.tsx +2 -0
- package/src/components/etl-monitor/renderers/EtlMonitorWidget.tsx +97 -0
- package/src/components/etl-monitor/renderers/MetricsGridRenderer.tsx +2 -0
- package/src/components/etl-monitor/renderers/MonitorRenderer.tsx +175 -0
- package/src/components/etl-monitor/renderers/ProgressRenderer.tsx +2 -0
- package/src/components/etl-monitor/renderers/StatusCardRenderer.tsx +2 -0
- package/src/components/etl-monitor/renderers/SummaryCardRenderer.tsx +2 -0
- package/src/components/etl-monitor/renderers/TableRenderer.tsx +2 -0
- package/src/components/etl-monitor/renderers/component-icons.tsx +33 -0
- package/src/components/etl-monitor/renderers/components/details-renderer.tsx +195 -0
- package/src/components/etl-monitor/renderers/components/error-log-renderer.tsx +104 -0
- package/src/components/etl-monitor/renderers/components/index.ts +6 -0
- package/src/components/etl-monitor/renderers/components/log-renderer.tsx +86 -0
- package/src/components/etl-monitor/renderers/components/metrics-grid-renderer.tsx +133 -0
- package/src/components/etl-monitor/renderers/components/progress-renderer.tsx +188 -0
- package/src/components/etl-monitor/renderers/components/status-card-renderer.tsx +247 -0
- package/src/components/etl-monitor/renderers/components/summary-card-renderer.tsx +160 -0
- package/src/components/etl-monitor/renderers/components/table-renderer.tsx +175 -0
- package/src/components/etl-monitor/renderers/components/time-series-renderer.tsx +310 -0
- package/src/components/etl-monitor/renderers/data-transformer.ts +361 -0
- package/src/components/etl-monitor/renderers/index.ts +11 -0
- package/src/components/etl-monitor/renderers/legacy-monitor-renderer.tsx +69 -0
- package/src/components/etl-monitor/renderers/monitor-renderer.tsx +6 -0
- package/src/components/etl-monitor/renderers/monitor-renderers.scss +1621 -0
- package/src/components/etl-monitor/renderers/monitor-widget-card.component.tsx +54 -0
- package/src/components/etl-monitor/renderers/monitor-widget-card.scss +16 -0
- package/src/components/etl-monitor/renderers/v2-monitor-renderer.tsx +227 -0
- package/src/components/import-export/artifact-groups.ts +153 -0
- package/src/components/import-export/export-report-modal.component.tsx +265 -0
- package/src/components/import-export/import-export-page.component.tsx +610 -0
- package/src/components/import-export/import-export.styles.scss +825 -0
- package/src/components/import-export/import-package-modal.component.tsx +401 -0
- package/src/components/import-export/index.ts +3 -0
- package/src/components/indicators/README.md +24 -0
- package/src/components/indicators/base-indicator-basic-fields.component.tsx +18 -0
- package/src/components/indicators/base-indicator-basics.section.tsx +54 -0
- package/src/components/indicators/base-indicator-countby.section.tsx +25 -0
- package/src/components/indicators/base-indicator-criteria.section.tsx +83 -0
- package/src/components/indicators/base-indicator-details-form.component.tsx +80 -0
- package/src/components/indicators/base-indicator-theme-unit-fields.component.tsx +39 -0
- package/src/components/indicators/create-base-indicator-modal.component.tsx +623 -0
- package/src/components/indicators/create-composite-base-indicator-modal.component.tsx +295 -0
- package/src/components/indicators/create-custom-indicator-modal.component.tsx +548 -0
- package/src/components/indicators/create-final-indicator-modal.component.tsx +285 -0
- package/src/components/indicators/custom-condition-input.component.tsx +310 -0
- package/src/components/indicators/diagnosis-filters-form.component.tsx +101 -0
- package/src/components/indicators/disaggregation-panel.component.tsx +93 -0
- package/src/components/indicators/handler/concept-search-multiselect.component.tsx +153 -0
- package/src/components/indicators/handler/question-answer-concept-search.component.tsx +336 -0
- package/src/components/indicators/indicator-run-preview-modal.component.tsx +391 -0
- package/src/components/indicators/indicator-search-select.component.tsx +297 -0
- package/src/components/indicators/indicator-status-tag.component.tsx +9 -0
- package/src/components/indicators/indicators-page.component.tsx +673 -0
- package/src/components/indicators/indicators-page.scss +121 -0
- package/src/components/indicators/indicators-table.component.tsx +188 -0
- package/src/components/indicators/preview-table.component.tsx +31 -0
- package/src/components/indicators/sections/composite-indicator-basics.section.tsx +36 -0
- package/src/components/indicators/sections/composite-indicator-picker.section.tsx +116 -0
- package/src/components/indicators/sections/composite-indicator-sql-preview.section.tsx +31 -0
- package/src/components/indicators/sections/custom-indicator-basics.section.tsx +65 -0
- package/src/components/indicators/sections/custom-indicator-disaggregation.section.tsx +93 -0
- package/src/components/indicators/sections/custom-indicator-filters.section.tsx +203 -0
- package/src/components/indicators/sections/custom-indicator-population.section.tsx +107 -0
- package/src/components/indicators/sections/custom-indicator-sql-preview.section.tsx +108 -0
- package/src/components/indicators/sections/final-indicator-basics.section.tsx +46 -0
- package/src/components/indicators/sections/final-indicator-disaggregation.section.tsx +47 -0
- package/src/components/indicators/sections/final-indicator-picker.section.tsx +60 -0
- package/src/components/indicators/sections/final-indicator-preview.section.tsx +20 -0
- package/src/components/indicators/sections/final-indicator-results-preview.section.tsx +65 -0
- package/src/components/indicators/sections/indicator-basics.section.tsx +40 -0
- package/src/components/indicators/sections/indicator-conditions.section.tsx +279 -0
- package/src/components/indicators/sections/indicator-sql-preview.section.tsx +63 -0
- package/src/components/indicators/sections/indicator-theme.section.tsx +48 -0
- package/src/components/indicators/sql-preview.component.tsx +19 -0
- package/src/components/indicators/tag-input.component.tsx +177 -0
- package/src/components/indicators/types/base-indicator-form.types.ts +17 -0
- package/src/components/indicators/types/base-indicator-theme.constants.ts +8 -0
- package/src/components/indicators/types/composite-indicator.types.ts +27 -0
- package/src/components/indicators/types/condition-ui.types.ts +11 -0
- package/src/components/indicators/types/custom-indicator.types.ts +161 -0
- package/src/components/indicators/types/data-theme-config.types.ts +88 -0
- package/src/components/indicators/types/indicator-types.ts +57 -0
- package/src/components/indicators/types/sql-builders.ts +43 -0
- package/src/components/indicators/utils/composite-indicator-sql.utils.test.ts +143 -0
- package/src/components/indicators/utils/composite-indicator-sql.utils.ts +426 -0
- package/src/components/indicators/utils/concept-preload.utils.ts +21 -0
- package/src/components/indicators/utils/custom-indicator-interpreter.test.ts +493 -0
- package/src/components/indicators/utils/custom-indicator-interpreter.ts +702 -0
- package/src/components/indicators/utils/final-indicator-sql.utils.ts +190 -0
- package/src/components/indicators/utils/indicator-conditions-hydration.utils.ts +254 -0
- package/src/components/indicators/utils/indicator-sql-resolution.utils.ts +204 -0
- package/src/components/indicators/utils/indicator-sql.utils.ts +721 -0
- package/src/components/indicators/utils/population-sql.compiler.test.ts +999 -0
- package/src/components/indicators/utils/population-sql.compiler.ts +858 -0
- package/src/components/landing/landing-action-item.component.tsx +50 -0
- package/src/components/landing/landing-action-list.component.tsx +12 -0
- package/src/components/landing/landing-activity-list.component.tsx +47 -0
- package/src/components/landing/landing-footer-grid.component.tsx +45 -0
- package/src/components/landing/landing-hero.component.tsx +18 -0
- package/src/components/landing/landing-section-card.component.tsx +33 -0
- package/src/components/landing/report-builder-landing-page.component.tsx +186 -0
- package/src/components/landing/report-builder-landing-page.scss +131 -0
- package/src/components/legacy-reports/legacy-report-detail-page.component.tsx +300 -0
- package/src/components/legacy-reports/legacy-report-editor-page.component.tsx +324 -0
- package/src/components/legacy-reports/legacy-report-upload-modal.component.tsx +307 -0
- package/src/components/legacy-reports/legacy-reports-page.component.tsx +206 -0
- package/src/components/legacy-reports/legacy-reports-table.component.tsx +154 -0
- package/src/components/legacy-reports/tabs/legacy-report-datasets-tab.component.tsx +344 -0
- package/src/components/legacy-reports/tabs/legacy-report-dimensions-tab.component.tsx +590 -0
- package/src/components/legacy-reports/tabs/legacy-report-indicators-tab.component.tsx +490 -0
- package/src/components/legacy-reports/tabs/legacy-report-json-preview-tab.component.tsx +272 -0
- package/src/components/legacy-reports/tabs/legacy-report-overview-tab.component.tsx +218 -0
- package/src/components/legacy-reports/tabs/legacy-report-parameters-tab.component.tsx +279 -0
- package/src/components/linelist/definition/indicator-to-sql.utils.ts +281 -0
- package/src/components/linelist/definition/linelist-definition-editor.component.tsx +1676 -0
- package/src/components/linelist/definition/linelist-definition-editor.scss +461 -0
- package/src/components/linelist/definition/linelist-definition.types.ts +108 -0
- package/src/components/linelist/definition/panels/column-properties-panel.component.tsx +328 -0
- package/src/components/linelist/definition/panels/column-selector-panel.component.tsx +1082 -0
- package/src/components/linelist/definition/panels/column-selector-panel.scss +100 -0
- package/src/components/linelist/definition/panels/custom-sql-column-modal.component.tsx +248 -0
- package/src/components/linelist/definition/panels/encounter-diagnosis-column-modal.component.tsx +300 -0
- package/src/components/linelist/definition/panels/encounter-diagnosis-column-modal.scss +21 -0
- package/src/components/linelist/definition/panels/multi-datasource-selector.component.tsx +332 -0
- package/src/components/linelist/definition/panels/multi-datasource-selector.scss +161 -0
- package/src/components/linelist/definition/panels/observation-column-modal.component.tsx +450 -0
- package/src/components/linelist/definition/panels/observation-column-modal.scss +29 -0
- package/src/components/linelist/definition/panels/parameter-panel.component.tsx +659 -0
- package/src/components/linelist/definition/panels/parameter-panel.scss +64 -0
- package/src/components/linelist/definition/panels/population-source-selector.component.tsx +407 -0
- package/src/components/linelist/definition/panels/population-source-selector.scss +187 -0
- package/src/components/linelist/design/panels/column-category-selector.component.tsx +433 -0
- package/src/components/linelist/design/panels/column-category-selector.scss +329 -0
- package/src/components/linelist/design/panels/column-debug-panel.component.tsx +197 -0
- package/src/components/linelist/design/panels/column-debug-panel.scss +141 -0
- package/src/components/linelist/design/panels/data-catalogue.component.tsx +1062 -0
- package/src/components/linelist/design/panels/data-catalogue.scss +263 -0
- package/src/components/linelist/design/panels/display-settings-panel.component.tsx +405 -0
- package/src/components/linelist/design/panels/display-settings-panel.scss +78 -0
- package/src/components/linelist/design/panels/filtermap-editor.component.tsx +434 -0
- package/src/components/linelist/design/panels/filtermap-editor.scss +176 -0
- package/src/components/linelist/design/panels/json-preview.component.tsx +114 -0
- package/src/components/linelist/design/panels/json-preview.scss +85 -0
- package/src/components/linelist/design/panels/query-config-panel.component.tsx +1137 -0
- package/src/components/linelist/design/panels/query-config-panel.scss +342 -0
- package/src/components/linelist/design/panels/sort-config-panel.component.tsx +330 -0
- package/src/components/linelist/design/panels/sort-config-panel.scss +77 -0
- package/src/components/linelist/design/panels/sql-builder-panel.component.tsx +475 -0
- package/src/components/linelist/design/panels/sql-builder-panel.scss +226 -0
- package/src/components/linelist/design/panels/transformation-panel.component.tsx +687 -0
- package/src/components/linelist/design/panels/transformation-panel.scss +81 -0
- package/src/components/linelist/design/panels/visual-filter-panel.component.tsx +595 -0
- package/src/components/linelist/design/panels/visual-filter-panel.scss +76 -0
- package/src/components/linelist/linelist-dashboard.page.component.tsx +756 -0
- package/src/components/linelist/linelist-dashboard.page.scss +240 -0
- package/src/components/linelist/linelist-editor.page.component.tsx +134 -0
- package/src/components/linelist/linelist-editor.page.scss +28 -0
- package/src/components/linelist/linelist-run-report.page.component.tsx +715 -0
- package/src/components/linelist/linelist-run-report.page.scss +136 -0
- package/src/components/linelist/modals/linelist-report-builder-modal.component.tsx +1454 -0
- package/src/components/linelist/modals/linelist-report-builder-modal.scss +164 -0
- package/src/components/linelist/modals/preview-parameter-modal.component.tsx +361 -0
- package/src/components/report/dashboard/reports-table.component.tsx +186 -0
- package/src/components/report/definition/panels/properties-panel.component.tsx +38 -0
- package/src/components/report/definition/panels/report-structure-panel.component.tsx +61 -0
- package/src/components/report/definition/panels/selection-panel.component.tsx +58 -0
- package/src/components/report/definition/report-definition-editor.component.tsx +99 -0
- package/src/components/report/definition/report-definition.types.ts +66 -0
- package/src/components/report/design/panels/IndicatorPropertiesPanel.scss +33 -0
- package/src/components/report/design/panels/IndicatorPropertiesPanel.tsx +389 -0
- package/src/components/report/design/panels/JsonPreviewPanel.scss +32 -0
- package/src/components/report/design/panels/JsonPreviewPanel.tsx +104 -0
- package/src/components/report/design/panels/mapping-preview-panel.component.tsx +82 -0
- package/src/components/report/design/panels/mapping-preview-panel.scss +55 -0
- package/src/components/report/design/panels/template-structure-panel.component.tsx +296 -0
- package/src/components/report/design/panels/template-structure-panel.scss +81 -0
- package/src/components/report/design/report-design-editor.component.tsx +1170 -0
- package/src/components/report/design/report-design.types.ts +48 -0
- package/src/components/report/design/report-design.utils.ts +86 -0
- package/src/components/report/panels/report-detail.component.tsx +71 -0
- package/src/components/report/panels/report-panel-shell.component.tsx +60 -0
- package/src/components/report/report-dashboard.page.component.tsx +554 -0
- package/src/components/report/report-editor.page.component.tsx +590 -0
- package/src/components/report/report.routes.tsx +32 -0
- package/src/components/report-sections/create-report-section-modal.component.tsx +388 -0
- package/src/components/report-sections/hooks/useAgeCategories.ts +25 -0
- package/src/components/report-sections/hooks/useSectionEditorState.ts +449 -0
- package/src/components/report-sections/panels/Dhis2MappingPanel.tsx +157 -0
- package/src/components/report-sections/panels/DisaggregationPanel.tsx +97 -0
- package/src/components/report-sections/panels/IndicatorPickerPanel.tsx +201 -0
- package/src/components/report-sections/panels/SectionBasicsPanel.tsx +27 -0
- package/src/components/report-sections/report-section-preview-modal.component.tsx +548 -0
- package/src/components/report-sections/report-sections-page.component.tsx +325 -0
- package/src/components/report-sections/section-disaggregation.utils.ts +637 -0
- package/src/components/report-sections/section-types.ts +55 -0
- package/src/components/report-sections/section-utils.ts +50 -0
- package/src/components/report-sections/sections-page.scss +79 -0
- package/src/components/run-reports/run-reports-page.component.tsx +8 -0
- package/src/components/security/access-denied-page.component.tsx +69 -0
- package/src/components/security/access-denied-page.scss +39 -0
- package/src/components/security/privilege-route.component.test.tsx +79 -0
- package/src/components/security/privilege-route.component.tsx +39 -0
- package/src/components/shared/compile-setup-modal.component.tsx +182 -0
- package/src/components/shared/concept-selector.component.tsx +245 -0
- package/src/components/shared/concept-selector.scss +121 -0
- package/src/components/shared/header/builder-header-actions.component.tsx +38 -0
- package/src/components/shared/header/header.component.tsx +85 -0
- package/src/components/shared/header/header.scss +150 -0
- package/src/components/shared/header/illustration.component.tsx +34 -0
- package/src/components/shared/indicator-properties-panel/IndicatorPropertiesPanel.scss +33 -0
- package/src/components/shared/indicator-properties-panel/IndicatorPropertiesPanel.tsx +389 -0
- package/src/components/shared/json-preview-panel/JsonPreviewPanel.scss +32 -0
- package/src/components/shared/json-preview-panel/JsonPreviewPanel.tsx +104 -0
- package/src/components/shared/mapping-preview-panel/mapping-preview-panel.component.tsx +82 -0
- package/src/components/shared/mapping-preview-panel/mapping-preview-panel.scss +55 -0
- package/src/components/shared/preview/query-results-preview.component.tsx +204 -0
- package/src/components/shared/template-structure-panel/template-structure-panel.component.tsx +296 -0
- package/src/components/shared/template-structure-panel/template-structure-panel.scss +81 -0
- package/src/config-schema.ts +43 -0
- package/src/constants/privileges.test.ts +53 -0
- package/src/constants/privileges.ts +118 -0
- package/src/features/dashboards/DashboardRendererPage.tsx +174 -0
- package/src/features/dashboards/DashboardsPage.tsx +164 -0
- package/src/features/dashboards/EtlDashboardPage.tsx +32 -0
- package/src/features/dashboards/components/DashboardEmptyState.tsx +41 -0
- package/src/features/dashboards/components/DashboardGrid.tsx +48 -0
- package/src/features/dashboards/components/DashboardHeader.tsx +58 -0
- package/src/features/dashboards/components/DashboardSection.tsx +46 -0
- package/src/features/dashboards/components/ReportLaunchCard.tsx +51 -0
- package/src/features/dashboards/components/WidgetSlotDispatcher.tsx +61 -0
- package/src/features/dashboards/components/WidgetUnavailableCard.tsx +36 -0
- package/src/features/dashboards/dashboards.scss +196 -0
- package/src/features/dashboards/hooks/useDashboard.ts +61 -0
- package/src/features/dashboards/hooks/useDashboardWidgets.ts +74 -0
- package/src/features/dashboards/hooks/useDashboards.ts +56 -0
- package/src/features/dashboards/hooks/useMonitorDataMap.ts +84 -0
- package/src/features/dashboards/index.ts +11 -0
- package/src/features/dashboards/utils/dashboard-config.util.test.ts +255 -0
- package/src/features/dashboards/utils/dashboard-config.util.ts +382 -0
- package/src/features/report-visualizer/ReportVisualizerPage.tsx +448 -0
- package/src/features/report-visualizer/components/Export/ExportMenu.tsx +141 -0
- package/src/features/report-visualizer/components/Export/index.ts +2 -0
- package/src/features/report-visualizer/components/ReportExplorer/QuickFilters.tsx +152 -0
- package/src/features/report-visualizer/components/ReportExplorer/ReportCategoryFilter.tsx +54 -0
- package/src/features/report-visualizer/components/ReportExplorer/ReportExplorerPanel.tsx +201 -0
- package/src/features/report-visualizer/components/ReportExplorer/ReportExplorerRail.tsx +202 -0
- package/src/features/report-visualizer/components/ReportExplorer/ReportList.tsx +287 -0
- package/src/features/report-visualizer/components/ReportExplorer/ReportSearch.tsx +151 -0
- package/src/features/report-visualizer/components/ReportExplorer/ReportTagFilter.tsx +100 -0
- package/src/features/report-visualizer/components/ReportExplorer/RunReportButton.tsx +42 -0
- package/src/features/report-visualizer/components/ReportExplorer/RuntimeParameterForm.tsx +259 -0
- package/src/features/report-visualizer/components/ReportExplorer/index.ts +8 -0
- package/src/features/report-visualizer/components/ReportWorkspace/ReportLayer.tsx +33 -0
- package/src/features/report-visualizer/components/ReportWorkspace/ReportSummary.tsx +88 -0
- package/src/features/report-visualizer/components/ReportWorkspace/ReportViewTabs.tsx +72 -0
- package/src/features/report-visualizer/components/ReportWorkspace/ReportWorkspaceHeader.tsx +92 -0
- package/src/features/report-visualizer/components/ReportWorkspace/ReportWorkspacePanel.tsx +242 -0
- package/src/features/report-visualizer/components/ReportWorkspace/index.ts +5 -0
- package/src/features/report-visualizer/components/TableView/ColumnVisibilityMenu.tsx +206 -0
- package/src/features/report-visualizer/components/TableView/ReportTable.tsx +253 -0
- package/src/features/report-visualizer/components/TableView/index.ts +2 -0
- package/src/features/report-visualizer/hooks/index.ts +4 -0
- package/src/features/report-visualizer/hooks/useExplorerPreferences.ts +103 -0
- package/src/features/report-visualizer/hooks/useReportCapabilities.ts +112 -0
- package/src/features/report-visualizer/hooks/useReportParameters.ts +152 -0
- package/src/features/report-visualizer/hooks/useReports.ts +135 -0
- package/src/features/report-visualizer/index.ts +6 -0
- package/src/features/report-visualizer/report-visualizer.scss +593 -0
- package/src/features/report-visualizer/types/index.ts +1 -0
- package/src/features/report-visualizer/types/report-visualizer.types.ts +104 -0
- package/src/features/report-visualizer/utils/status-colors.ts +117 -0
- package/src/globals.d.ts +14 -0
- package/src/hooks/address-template.ts +86 -0
- package/src/hooks/concept.ts +112 -0
- package/src/hooks/indicator/index.ts +1 -0
- package/src/hooks/indicator/useIndicatorsByTheme.ts +41 -0
- package/src/hooks/openmrs-reference-data.ts +432 -0
- package/src/hooks/theme/index.ts +4 -0
- package/src/hooks/theme/useDataTheme.ts +61 -0
- package/src/hooks/theme/useDataThemes.ts +28 -0
- package/src/hooks/theme/useMambaTableMeta.ts +34 -0
- package/src/hooks/theme/useMambaTables.ts +95 -0
- package/src/hooks/use-report-builder-privileges.test.tsx +98 -0
- package/src/hooks/use-report-builder-privileges.ts +58 -0
- package/src/index.ts +29 -0
- package/src/resources/address-template/address-template.api.ts +180 -0
- package/src/resources/agegroup/agegroups.api.ts +58 -0
- package/src/resources/concept/concept.api.ts +116 -0
- package/src/resources/concepts/concept-mapper.ts +21 -0
- package/src/resources/concepts/concept-types.ts +31 -0
- package/src/resources/concepts/concepts.resource.ts +44 -0
- package/src/resources/concepts/useConceptSearch.ts +41 -0
- package/src/resources/dashboard/dashboard.api.ts +107 -0
- package/src/resources/etl-monitor/etl-monitor-test.api.ts +409 -0
- package/src/resources/etl-monitor/etl-monitor.api.ts +381 -0
- package/src/resources/etl-source/etl-source.api.ts +65 -0
- package/src/resources/etl-task/etl-task.api.ts +96 -0
- package/src/resources/etl-task/etl-task.types.ts +31 -0
- package/src/resources/indicator/indicators.api.ts +125 -0
- package/src/resources/legacy-reports/legacy-reports.api.ts +144 -0
- package/src/resources/linelist/linelist-reports.api.ts +616 -0
- package/src/resources/location/location.api.ts +135 -0
- package/src/resources/openmrs-api.ts +40 -0
- package/src/resources/patient-identifier-type/patient-identifier-type.api.ts +65 -0
- package/src/resources/person-attribute-type/person-attribute-type.api.ts +71 -0
- package/src/resources/preview/sql-preview.api.ts +54 -0
- package/src/resources/report/reports.api.ts +140 -0
- package/src/resources/report-category/report-category.api.ts +41 -0
- package/src/resources/report-import-export/import-export-api.ts +164 -0
- package/src/resources/report-library/report-library.api.ts +63 -0
- package/src/resources/report-section/report-sections.api.ts +73 -0
- package/src/resources/report-section/section-preview.api.ts +25 -0
- package/src/resources/theme/data-theme.api.ts +88 -0
- package/src/resources/theme/etl-schema.api.ts +101 -0
- package/src/resources/theme/etl-table-meta.api.ts +45 -0
- package/src/root.component.tsx +20 -0
- package/src/routes/report-builder.routes.tsx +160 -0
- package/src/routes/report-builder.scss +56 -0
- package/src/routes.json +28 -0
- package/src/sample-template.ts +121 -0
- package/src/template-utils.ts +110 -0
- package/src/types/carbon-react-inert.d.ts +13 -0
- package/src/types/condition-operators.ts +63 -0
- package/src/types/dashboard/dashboard.types.ts +137 -0
- package/src/types/dashboard/index.ts +4 -0
- package/src/types/etl/etl-types.ts +238 -0
- package/src/types/etl-monitor/etl-monitor-builder.types.ts +555 -0
- package/src/types/etl-monitor/etl-monitor-v2.types.ts +397 -0
- package/src/types/etl-monitor/etl-monitor.types.ts +188 -0
- package/src/types/etl-monitor/index.ts +6 -0
- package/src/types/linelist/compile-config.ts +455 -0
- package/src/types/linelist-types.ts +2059 -0
- package/src/types/theme/data-theme.types.ts +88 -0
- package/src/utils/api-error.utils.test.ts +151 -0
- package/src/utils/api-error.utils.ts +131 -0
- package/src/utils/config-enhancer.ts +62 -0
- package/src/utils/etl-browser/etl-browser.util.test.ts +145 -0
- package/src/utils/etl-browser/export-csv.util.ts +43 -0
- package/src/utils/etl-browser/group-tables.util.ts +84 -0
- package/src/utils/etl-browser/index.ts +4 -0
- package/src/utils/etl-browser/summarize-columns.util.ts +27 -0
- package/src/utils/etl-browser/table-sql.util.ts +20 -0
- package/src/utils/etl-monitor/config-v2-adapter.util.ts +460 -0
- package/src/utils/etl-monitor/endpoint-config-normalizer.util.ts +383 -0
- package/src/utils/etl-monitor/index.ts +10 -0
- package/src/utils/etl-monitor/jsonpath-resolver.util.ts +118 -0
- package/src/utils/etl-monitor/normalization-examples.ts +0 -0
- package/src/utils/etl-monitor/schema-inspector.util.ts +76 -0
- package/src/utils/etl-monitor/semantic-type-detector.util.ts +76 -0
- package/src/utils/etl-monitor/status-mapper.util.ts +105 -0
- package/src/utils/etl-monitor/value-formatters.util.ts +167 -0
- package/src/utils/etl-task/etl-task-card.util.test.ts +239 -0
- package/src/utils/etl-task/etl-task-card.util.ts +276 -0
- package/src/utils/etl-task/index.ts +1 -0
- package/src/utils/html-entities.utils.ts +34 -0
- package/src/utils/parameter-resolution.ts +218 -0
- package/src/views/etl-monitor-builder/add-monitor-modal.component.scss +0 -0
- package/src/views/etl-monitor-builder/add-monitor-modal.component.tsx +210 -0
- package/src/views/etl-monitor-builder/etl-monitor-builder.component.scss +157 -0
- package/src/views/etl-monitor-builder/etl-monitor-builder.component.tsx +252 -0
- package/src/views/etl-monitor-builder/etl-monitor-builder.store.ts +126 -0
- package/tools/scss-stub.js +12 -0
- package/tools/setup-tests.ts +1 -0
- package/translations/en.json +65 -0
- package/tsconfig.json +43 -0
- package/webpack.config.js +30 -0
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
import type { IndicatorDto } from '../../../resources/indicator/indicators.api';
|
|
2
|
+
import { resolveIndicatorSql, isSqlResolutionError, resolvePatientIdColumn, type SqlSource } from './indicator-sql-resolution.utils';
|
|
3
|
+
|
|
4
|
+
export type CountSqlSource = SqlSource;
|
|
5
|
+
|
|
6
|
+
export function idFieldForUnit(unit: 'Patients' | 'Encounters', overrideColumn?: string) {
|
|
7
|
+
if (overrideColumn) return overrideColumn;
|
|
8
|
+
return unit === 'Encounters' ? 'encounter_id' : 'client_id';
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Extract patientIdColumn from stored authoring (if present),
|
|
13
|
+
* otherwise fallback to "client_id".
|
|
14
|
+
*
|
|
15
|
+
* @deprecated Use resolvePatientIdColumn from indicator-sql-resolution.utils.ts instead.
|
|
16
|
+
* This function is maintained for backward compatibility but delegates to the centralized implementation.
|
|
17
|
+
*/
|
|
18
|
+
export function tryGetPatientIdColumnFromConfig(ind: IndicatorDto): string {
|
|
19
|
+
return resolvePatientIdColumn(ind);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Check if an indicator is complex (has built-in disaggregation logic).
|
|
24
|
+
* Complex indicators should not be processed through COUNT-to-population conversion
|
|
25
|
+
* because they already have their own disaggregation structure.
|
|
26
|
+
* Note: Similar function exists in section-disaggregation.utils.ts which is actively used.
|
|
27
|
+
*/
|
|
28
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
29
|
+
function isComplexIndicator(sql: string): boolean {
|
|
30
|
+
if (!sql) return false;
|
|
31
|
+
|
|
32
|
+
// Signs of complex indicators with built-in disaggregation:
|
|
33
|
+
// 1. Multiple GROUP BY clauses
|
|
34
|
+
const groupByCount = (sql.match(/GROUP BY/gi) || []).length;
|
|
35
|
+
if (groupByCount > 1) return true;
|
|
36
|
+
|
|
37
|
+
// 2. Has age_group/gender columns in SELECT with aggregation
|
|
38
|
+
if (/age_group.*AS.*aggregat|gender.*AS.*sex|disaggregat/i.test(sql)) return true;
|
|
39
|
+
|
|
40
|
+
// 3. Has complex WITH clauses (multiple CTEs with aggregation)
|
|
41
|
+
const withCount = (sql.match(/WITH.*AS\s*\(/gi) || []).length;
|
|
42
|
+
if (withCount > 1 && /GROUP BY/i.test(sql)) return true;
|
|
43
|
+
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* ✅ Key fix:
|
|
49
|
+
* Many times the backend returns sqlTemplate empty, but configJson contains sqlPreview.
|
|
50
|
+
* We support all known config shapes.
|
|
51
|
+
*
|
|
52
|
+
* @deprecated Use resolveIndicatorSql from indicator-sql-resolution.utils.ts instead.
|
|
53
|
+
* This function is maintained for backward compatibility but delegates to the centralized implementation.
|
|
54
|
+
*/
|
|
55
|
+
export function tryGetCountSqlFromIndicator(ind: IndicatorDto): { sql: string; source: CountSqlSource } {
|
|
56
|
+
const result = resolveIndicatorSql(ind);
|
|
57
|
+
|
|
58
|
+
if (isSqlResolutionError(result)) {
|
|
59
|
+
return { sql: '', source: result.success === false ? 'none(parse-error)' : 'none' };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return { sql: result.sql, source: result.source };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Extract the primary table alias from a SQL query.
|
|
67
|
+
* Looks for patterns like "FROM table_name alias" or "FROM table_name AS alias"
|
|
68
|
+
* Returns the alias or null if not found.
|
|
69
|
+
*/
|
|
70
|
+
function extractTableAlias(sql: string): string | null {
|
|
71
|
+
// Match: FROM table_name alias or FROM table_name AS alias
|
|
72
|
+
// Skip if inside parentheses (subqueries) - we only care about the first FROM
|
|
73
|
+
const fromPattern = /FROM\s+([a-zA-Z_][a-zA-Z0-9_]*)\s+(?:AS\s+)?([a-zA-Z_][a-zA-Z0-9_]*)\b/i;
|
|
74
|
+
const match = sql.match(fromPattern);
|
|
75
|
+
return match ? match[2] : null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Convert a base indicator COUNT sqlTemplate into a population query.
|
|
80
|
+
*
|
|
81
|
+
* Supports BOTH:
|
|
82
|
+
* A) Base-style COUNT SQL:
|
|
83
|
+
* SELECT COUNT(*) AS total
|
|
84
|
+
* FROM <table> a
|
|
85
|
+
* ...
|
|
86
|
+
*
|
|
87
|
+
* B) Composite-style COUNT SQL:
|
|
88
|
+
* WITH A AS (...), B AS (...)
|
|
89
|
+
* SELECT COUNT(*) AS total
|
|
90
|
+
* FROM ( SELECT A.client_id ... ) X;
|
|
91
|
+
*
|
|
92
|
+
* For composite-style, we MUST preserve the WITH ... prefix,
|
|
93
|
+
* otherwise the resulting query references A/B without defining them.
|
|
94
|
+
*
|
|
95
|
+
* Returns population SQL that always exposes the patient ID column.
|
|
96
|
+
*/
|
|
97
|
+
export function countSqlToPopulationSql(sql: string, idColumn: string, unit: 'Patients' | 'Encounters') {
|
|
98
|
+
// Use idColumn (from theme config) as the output field name for consistency
|
|
99
|
+
// This ensures base_pop uses the same column name as the source table
|
|
100
|
+
const idField = idColumn || idFieldForUnit(unit);
|
|
101
|
+
|
|
102
|
+
let raw = (sql ?? '').trim();
|
|
103
|
+
if (!raw) return '';
|
|
104
|
+
|
|
105
|
+
// Handle escaped newlines BEFORE pattern matching
|
|
106
|
+
// This ensures multi-line SQL with GROUP BY, HAVING, etc. on separate lines is preserved
|
|
107
|
+
raw = normalizeEscapedNewlines(raw);
|
|
108
|
+
|
|
109
|
+
// normalize trailing semicolons
|
|
110
|
+
const noSemi = raw.replace(/;+\s*$/, '');
|
|
111
|
+
|
|
112
|
+
// ✅ Case 0a: Base COUNT DISTINCT SQL without AS clause
|
|
113
|
+
// Handle "SELECT COUNT(DISTINCT column) FROM ... WHERE ..." (no AS alias)
|
|
114
|
+
// by converting to "SELECT DISTINCT column AS {idField} FROM ... WHERE ..."
|
|
115
|
+
const distinctPatternNoAlias = /SELECT\s+COUNT\s*\(\s*DISTINCT\s+([a-zA-Z_][a-zA-Z0-9_]*(?:\.[a-zA-Z_][a-zA-Z0-9_]*)?)\s*\)\s+FROM/i;
|
|
116
|
+
const distinctMatchNoAlias = noSemi.match(distinctPatternNoAlias);
|
|
117
|
+
if (distinctMatchNoAlias) {
|
|
118
|
+
const columnRef = distinctMatchNoAlias[1]; // e.g., "a.client_id" or "client_id"
|
|
119
|
+
// Get everything after "SELECT COUNT(DISTINCT ...) FROM " to preserve FROM, WHERE, JOINs, etc.
|
|
120
|
+
const fromIdx = distinctMatchNoAlias[0].length - 5; // -5 to account for " FROM" that we'll replace
|
|
121
|
+
const afterSelect = noSemi.substring(fromIdx).trim();
|
|
122
|
+
|
|
123
|
+
// If column is already qualified (e.g., "a.client_id"), use it as-is
|
|
124
|
+
if (columnRef.includes('.')) {
|
|
125
|
+
const result = `SELECT DISTINCT ${columnRef} AS ${idField} ${afterSelect}`.trim();
|
|
126
|
+
return validatePopulationSqlOutput(result, idField);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Extract the actual table alias from FROM clause instead of assuming "a"
|
|
130
|
+
const tableAlias = extractTableAlias(afterSelect) || 'a';
|
|
131
|
+
const result = `SELECT DISTINCT ${tableAlias}.${columnRef} AS ${idField} ${afterSelect}`.trim();
|
|
132
|
+
return validatePopulationSqlOutput(result, idField);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// ✅ Case 0b: Base COUNT DISTINCT SQL with AS clause
|
|
136
|
+
// Handle "SELECT COUNT(DISTINCT column) AS alias FROM ... WHERE ..."
|
|
137
|
+
// by converting to "SELECT DISTINCT column AS {idField} FROM ... WHERE ..."
|
|
138
|
+
// Preserves FROM, WHERE, JOINs, and all other clauses
|
|
139
|
+
// Enhanced: supports any result alias name (not just "total") and extracts actual table alias
|
|
140
|
+
const distinctPattern = /SELECT\s+COUNT\s*\(\s*DISTINCT\s+([a-zA-Z_][a-zA-Z0-9_]*(?:\.[a-zA-Z_][a-zA-Z0-9_]*)?)\s*\)\s+AS\s+(\w+)\s*/i;
|
|
141
|
+
const distinctMatch = noSemi.match(distinctPattern);
|
|
142
|
+
if (distinctMatch) {
|
|
143
|
+
const columnRef = distinctMatch[1]; // e.g., "a.client_id" or "client_id"
|
|
144
|
+
// Get everything after "SELECT COUNT(DISTINCT ...) AS alias "
|
|
145
|
+
// This preserves FROM, WHERE, JOINs, etc.
|
|
146
|
+
const afterSelect = noSemi.substring(distinctMatch[0].length);
|
|
147
|
+
|
|
148
|
+
// If column is already qualified (e.g., "a.client_id"), use it as-is
|
|
149
|
+
// Always add the AS alias for consistency
|
|
150
|
+
if (columnRef.includes('.')) {
|
|
151
|
+
const result = `SELECT DISTINCT ${columnRef} AS ${idField} ${afterSelect}`.trim();
|
|
152
|
+
return validatePopulationSqlOutput(result, idField);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Extract the actual table alias from FROM clause instead of assuming "a"
|
|
156
|
+
const tableAlias = extractTableAlias(afterSelect) || 'a';
|
|
157
|
+
const result = `SELECT DISTINCT ${tableAlias}.${columnRef} AS ${idField} ${afterSelect}`.trim();
|
|
158
|
+
return validatePopulationSqlOutput(result, idField);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// ✅ Case 1: Composite COUNT SQL
|
|
162
|
+
// Preserve any WITH CTE prefix (e.g. WITH A AS (...), B AS (...))
|
|
163
|
+
// Replace "SELECT COUNT(*) AS total FROM ( <inner select> ) X" with a population select.
|
|
164
|
+
const countIdx = noSemi.search(/SELECT\s+COUNT\s*\(\s*\*\s*\)\s+AS\s+total/i);
|
|
165
|
+
const innerMatch = noSemi.match(/FROM\s*\(\s*(SELECT[\s\S]*?)\)\s*X\b/i);
|
|
166
|
+
|
|
167
|
+
if (countIdx >= 0 && innerMatch?.[1]) {
|
|
168
|
+
const prefix = noSemi.slice(0, countIdx).trim(); // keeps WITH A,B if present
|
|
169
|
+
const inner = innerMatch[1].trim().replace(/;+\s*$/, '');
|
|
170
|
+
|
|
171
|
+
const result = `
|
|
172
|
+
${prefix}
|
|
173
|
+
SELECT DISTINCT pop.${idField}
|
|
174
|
+
FROM (
|
|
175
|
+
${inner}
|
|
176
|
+
) pop
|
|
177
|
+
`.trim();
|
|
178
|
+
return validatePopulationSqlOutput(result, idField);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// ✅ Case 2: Base COUNT SQL
|
|
182
|
+
// Replace the COUNT select with a DISTINCT id select.
|
|
183
|
+
// Extract the actual table alias from FROM clause instead of assuming "a"
|
|
184
|
+
const tableAlias = extractTableAlias(noSemi) || 'a';
|
|
185
|
+
const replaced = noSemi.replace(
|
|
186
|
+
/SELECT\s+([\s\S]*?)COUNT\s*\(\s*\*\s*\)\s+AS\s+total\s*/i,
|
|
187
|
+
`SELECT DISTINCT ${tableAlias}.${idColumn} AS ${idField}\n`,
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
// If it didn't replace, fallback: try simpler replace
|
|
191
|
+
let result = replaced;
|
|
192
|
+
if (result === noSemi) {
|
|
193
|
+
result = noSemi.replace(/COUNT\s*\(\s*\*\s*\)\s+AS\s+total/gi, `DISTINCT ${tableAlias}.${idColumn} AS ${idField}`);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return validatePopulationSqlOutput(result, idField);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Validates that the output of countSqlToPopulationSql is proper population SQL.
|
|
201
|
+
* Logs a warning if the output doesn't start with SELECT DISTINCT.
|
|
202
|
+
*/
|
|
203
|
+
function validatePopulationSqlOutput(sql: string, idField: string): string {
|
|
204
|
+
const trimmed = sql.trim();
|
|
205
|
+
|
|
206
|
+
// Check if output is valid population SQL
|
|
207
|
+
if (!trimmed.startsWith('SELECT DISTINCT')) {
|
|
208
|
+
console.warn(`[countSqlToPopulationSql] Output does not start with SELECT DISTINCT. Output:`, trimmed.substring(0, 100));
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Check if the output exposes the expected ID field
|
|
212
|
+
const idPattern = new RegExp(`(?:SELECT\\s+DISTINCT\\s+(?:[\\w]+\\.)?${idField}\\b|(?:AS\\s+${idField}\\b))`, 'i');
|
|
213
|
+
if (!idPattern.test(trimmed)) {
|
|
214
|
+
console.warn(`[countSqlToPopulationSql] Output may not expose '${idField}' column. Output:`, trimmed.substring(0, 100));
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return trimmed;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Extracts CTEs from a SQL query and renames them to avoid conflicts.
|
|
222
|
+
* Handles nested WITH clauses by recursively extracting inner CTEs to the top level.
|
|
223
|
+
*
|
|
224
|
+
* @param sql - The SQL query that may start WITH CTEs
|
|
225
|
+
* @param prefix - Prefix to add to CTE names (e.g., 'base_' or 'filter_')
|
|
226
|
+
* @returns Object with ctes string and the main query with updated references
|
|
227
|
+
*/
|
|
228
|
+
function extractAndRenameCtes(sql: string, prefix: string): { ctes: string; mainQuery: string } {
|
|
229
|
+
const trimmed = sql.trim();
|
|
230
|
+
|
|
231
|
+
// Check if SQL starts WITH
|
|
232
|
+
const withMatch = trimmed.match(/^\s*WITH\s+/i);
|
|
233
|
+
if (!withMatch) {
|
|
234
|
+
// No CTEs, return as-is
|
|
235
|
+
return { ctes: '', mainQuery: trimmed };
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Find the SELECT that starts the main query (after all CTEs)
|
|
239
|
+
let parenCount = 0;
|
|
240
|
+
let mainSelectIndex = -1;
|
|
241
|
+
let foundWith = false;
|
|
242
|
+
|
|
243
|
+
for (let i = 0; i < trimmed.length; i++) {
|
|
244
|
+
const char = trimmed[i];
|
|
245
|
+
|
|
246
|
+
// Track if we've passed the WITH keyword
|
|
247
|
+
if (!foundWith && i >= withMatch[0].length) {
|
|
248
|
+
foundWith = true;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (char === '(') {
|
|
252
|
+
parenCount++;
|
|
253
|
+
} else if (char === ')') {
|
|
254
|
+
parenCount--;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Look for SELECT when we're not in parentheses
|
|
258
|
+
if (foundWith && parenCount === 0) {
|
|
259
|
+
if (trimmed.substring(i, i + 6).toUpperCase() === 'SELECT') {
|
|
260
|
+
mainSelectIndex = i;
|
|
261
|
+
break;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (mainSelectIndex === -1) {
|
|
267
|
+
// Couldn't find main SELECT, return as-is
|
|
268
|
+
return { ctes: '', mainQuery: trimmed };
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Split into CTEs section and main query
|
|
272
|
+
const ctesSection = trimmed.substring(withMatch[0].length, mainSelectIndex);
|
|
273
|
+
const mainQuery = trimmed.substring(mainSelectIndex);
|
|
274
|
+
|
|
275
|
+
// Process CTEs - handle nested WITH by recursive extraction
|
|
276
|
+
const allCtes: string[] = [];
|
|
277
|
+
let current = '';
|
|
278
|
+
let level = 0;
|
|
279
|
+
|
|
280
|
+
for (let i = 0; i < ctesSection.length; i++) {
|
|
281
|
+
const char = ctesSection[i];
|
|
282
|
+
|
|
283
|
+
if (char === '(') level++;
|
|
284
|
+
else if (char === ')') level--;
|
|
285
|
+
|
|
286
|
+
current += char;
|
|
287
|
+
|
|
288
|
+
// When level returns to 0, check for comma or end
|
|
289
|
+
if (level === 0 && current.trim()) {
|
|
290
|
+
// Look ahead for comma
|
|
291
|
+
let j = i + 1;
|
|
292
|
+
while (j < ctesSection.length && /[\s]/.test(ctesSection[j])) j++;
|
|
293
|
+
|
|
294
|
+
if (j >= ctesSection.length || ctesSection[j] === ',') {
|
|
295
|
+
// Complete CTE
|
|
296
|
+
const cteDef = current.trim().replace(/,$/, '');
|
|
297
|
+
if (cteDef) {
|
|
298
|
+
// Match: NAME AS (body)
|
|
299
|
+
const nameMatch = cteDef.match(/^([A-Za-z_][A-Za-z0-9_]*)\s+AS\s*\((.*)\)\s*$/);
|
|
300
|
+
if (nameMatch) {
|
|
301
|
+
const oldName = nameMatch[1];
|
|
302
|
+
let body = nameMatch[2].trim();
|
|
303
|
+
const newName = prefix + oldName;
|
|
304
|
+
|
|
305
|
+
// Check if body contains nested WITH clause
|
|
306
|
+
const nestedWithMatch = body.match(/^\s*WITH\s+/i);
|
|
307
|
+
if (nestedWithMatch) {
|
|
308
|
+
// Recursively extract nested CTEs
|
|
309
|
+
const nestedResult = extractAndRenameCtes(body, prefix);
|
|
310
|
+
// Add the nested CTEs first (they're referenced by the main query)
|
|
311
|
+
if (nestedResult.ctes) {
|
|
312
|
+
allCtes.push(nestedResult.ctes);
|
|
313
|
+
}
|
|
314
|
+
// Update body to use the extracted main query
|
|
315
|
+
body = nestedResult.mainQuery;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Update references within body:
|
|
319
|
+
// 1. Column references like A.xxx -> base_A.xxx
|
|
320
|
+
let updatedBody = body.replace(/\b([A-D])\.(\w+)/g, `${prefix}$1.$2`);
|
|
321
|
+
// 2. Table references like FROM A, JOIN A -> FROM base_A, JOIN base_A
|
|
322
|
+
// Use negative lookbehind to avoid matching A in base_A
|
|
323
|
+
updatedBody = updatedBody.replace(/(?<![_A-Za-z0-9])([A-D])\b(?!\.)/g, `${prefix}$1`);
|
|
324
|
+
|
|
325
|
+
allCtes.push(`${newName} AS (\n ${updatedBody}\n)`);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
current = '';
|
|
329
|
+
// Skip past comma
|
|
330
|
+
if (j < ctesSection.length && ctesSection[j] === ',') {
|
|
331
|
+
i = j;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Update references in main query
|
|
338
|
+
let updatedMainQuery = mainQuery.replace(/\b([A-D])\.(\w+)/g, `${prefix}$1.$2`);
|
|
339
|
+
// Also update table references like FROM A, JOIN A
|
|
340
|
+
updatedMainQuery = updatedMainQuery.replace(/(?<![_A-Za-z0-9])([A-D])\b(?!\.)/g, `${prefix}$1`);
|
|
341
|
+
|
|
342
|
+
return {
|
|
343
|
+
ctes: allCtes.join(',\n'),
|
|
344
|
+
mainQuery: updatedMainQuery
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Build composite COUNT SQL from two population queries.
|
|
350
|
+
* Population queries MUST return a column named client_id or encounter_id.
|
|
351
|
+
*
|
|
352
|
+
* Handles the case where population queries themselves contain WITH clauses
|
|
353
|
+
* (composite indicators) by extracting and renaming inner CTEs to avoid conflicts.
|
|
354
|
+
*/
|
|
355
|
+
export function buildCompositeCountSql(args: {
|
|
356
|
+
unit: 'Patients' | 'Encounters';
|
|
357
|
+
operator: 'AND' | 'OR' | 'A_AND_NOT_B';
|
|
358
|
+
populationSqlA: string;
|
|
359
|
+
populationSqlB: string;
|
|
360
|
+
}) {
|
|
361
|
+
const idField = idFieldForUnit(args.unit);
|
|
362
|
+
|
|
363
|
+
const rawA = args.populationSqlA.trim().replace(/;+\s*$/, '');
|
|
364
|
+
const rawB = args.populationSqlB.trim().replace(/;+\s*$/, '');
|
|
365
|
+
|
|
366
|
+
if (!rawA || !rawB) return '';
|
|
367
|
+
|
|
368
|
+
// Extract and rename CTEs from population queries (if they are composite indicators)
|
|
369
|
+
const resultA = extractAndRenameCtes(rawA, 'base_');
|
|
370
|
+
const resultB = extractAndRenameCtes(rawB, 'filter_');
|
|
371
|
+
|
|
372
|
+
// Build the flattened WITH clause with all CTEs
|
|
373
|
+
// IMPORTANT: CTEs must be ordered such that inner CTEs are defined before outer CTEs
|
|
374
|
+
// that reference them. The order should be:
|
|
375
|
+
// 1. Inner CTEs from A (base_A, base_B, ...)
|
|
376
|
+
// 2. Inner CTEs from B (filter_A, filter_B, ...)
|
|
377
|
+
// 3. Outer CTE A (which references base_*)
|
|
378
|
+
// 4. Outer CTE B (which references filter_*)
|
|
379
|
+
const withCtes: string[] = [];
|
|
380
|
+
|
|
381
|
+
// Add inner CTEs from A (renamed with base_ prefix) - these come first
|
|
382
|
+
if (resultA.ctes) {
|
|
383
|
+
withCtes.push(resultA.ctes);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// Add inner CTEs from B (renamed with filter_ prefix) - these come second
|
|
387
|
+
if (resultB.ctes) {
|
|
388
|
+
withCtes.push(resultB.ctes);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Add outer CTE declarations for A and B - these come last and reference the inner CTEs
|
|
392
|
+
withCtes.push(`A AS (\n ${resultA.mainQuery}\n)`);
|
|
393
|
+
withCtes.push(`B AS (\n ${resultB.mainQuery}\n)`);
|
|
394
|
+
|
|
395
|
+
// Build the final query based on operator
|
|
396
|
+
let innerSelect = '';
|
|
397
|
+
|
|
398
|
+
if (args.operator === 'AND') {
|
|
399
|
+
innerSelect = `SELECT A.${idField}\n FROM A\n INNER JOIN B ON B.${idField} = A.${idField}`;
|
|
400
|
+
} else if (args.operator === 'OR') {
|
|
401
|
+
innerSelect = `SELECT ${idField} FROM A\n UNION\n SELECT ${idField} FROM B`;
|
|
402
|
+
} else {
|
|
403
|
+
// A_AND_NOT_B
|
|
404
|
+
innerSelect = `SELECT A.${idField}\n FROM A\n LEFT JOIN B ON B.${idField} = A.${idField}\n WHERE B.${idField} IS NULL`;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
return `
|
|
408
|
+
WITH
|
|
409
|
+
${withCtes.join(',\n')}
|
|
410
|
+
SELECT COUNT(*) AS total
|
|
411
|
+
FROM (
|
|
412
|
+
${innerSelect}
|
|
413
|
+
) X;
|
|
414
|
+
`.trim();
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* Normalize SQL by converting escaped newlines to actual newlines.
|
|
419
|
+
* This handles cases where SQL is serialized through JSON in the backend,
|
|
420
|
+
* which can escape newlines as literal \n instead of actual newline characters.
|
|
421
|
+
*/
|
|
422
|
+
function normalizeEscapedNewlines(sql: string): string {
|
|
423
|
+
if (!sql) return sql;
|
|
424
|
+
// Convert literal \n to actual newlines
|
|
425
|
+
return sql.replace(/\\n/g, '\n');
|
|
426
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { omrsGet } from '../../../resources/openmrs-api';
|
|
2
|
+
import type { ConceptSummary } from '../../../resources/concepts/concept-types';
|
|
3
|
+
import type { SelectedConcept } from '../handler/concept-search-multiselect.component';
|
|
4
|
+
|
|
5
|
+
export async function searchConcepts(q: string, signal?: AbortSignal): Promise<ConceptSummary[]> {
|
|
6
|
+
const trimmed = q.trim();
|
|
7
|
+
if (!trimmed) return [];
|
|
8
|
+
const data = await omrsGet<any>(`/concept?q=${encodeURIComponent(trimmed)}&v=default`, signal);
|
|
9
|
+
const results = Array.isArray(data?.results) ? data.results : Array.isArray(data) ? data : [];
|
|
10
|
+
return results as ConceptSummary[];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function toSelectedConcept(c: any): SelectedConcept {
|
|
14
|
+
return {
|
|
15
|
+
id: Number(c.id) || 0,
|
|
16
|
+
uuid: c.uuid,
|
|
17
|
+
display: c.display,
|
|
18
|
+
conceptClass: c.conceptClass?.name,
|
|
19
|
+
datatype: c.datatype?.name,
|
|
20
|
+
};
|
|
21
|
+
}
|