@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,190 @@
|
|
|
1
|
+
import type { IndicatorDto } from '../../../resources/indicator/indicators.api';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
countSqlToPopulationSql,
|
|
5
|
+
tryGetPatientIdColumnFromConfig,
|
|
6
|
+
tryGetCountSqlFromIndicator,
|
|
7
|
+
} from './composite-indicator-sql.utils';
|
|
8
|
+
import {
|
|
9
|
+
compilePopulationSql,
|
|
10
|
+
generateAgeSexDisaggregationSql,
|
|
11
|
+
clearCompilationCache,
|
|
12
|
+
type CompilerOptions
|
|
13
|
+
} from './population-sql.compiler';
|
|
14
|
+
|
|
15
|
+
export type FinalIndicatorAuthoringV1 = {
|
|
16
|
+
version: 1;
|
|
17
|
+
baseIndicatorId: string;
|
|
18
|
+
|
|
19
|
+
// Backward/forward compatibility
|
|
20
|
+
ageGroupSetCode?: string;
|
|
21
|
+
ageCategoryCode?: string;
|
|
22
|
+
|
|
23
|
+
genders: Array<'F' | 'M'>;
|
|
24
|
+
sqlPreview: string;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
type BuildFinalSqlArgs = {
|
|
28
|
+
baseIndicator: IndicatorDto;
|
|
29
|
+
ageCategoryCode: string;
|
|
30
|
+
genders: Array<'F' | 'M'>;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
type BuildFinalSqlAsyncArgs = BuildFinalSqlArgs & {
|
|
34
|
+
getIndicator: (uuid: string) => Promise<IndicatorDto | null>;
|
|
35
|
+
compilerOptions?: CompilerOptions;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Builds a Final Indicator query that ALWAYS returns all age groups (even if value = 0),
|
|
40
|
+
* and for each selected gender.
|
|
41
|
+
*
|
|
42
|
+
* Strategy:
|
|
43
|
+
* - base_pop: patient population set from base indicator
|
|
44
|
+
* - ag: all age groups for the selected category (active only)
|
|
45
|
+
* - genders: the selected genders as rows
|
|
46
|
+
* - cnt: computed counts per (age_group_id, gender)
|
|
47
|
+
* - final: ag CROSS JOIN genders LEFT JOIN cnt, COALESCE(value,0)
|
|
48
|
+
*/
|
|
49
|
+
export function buildFinalIndicatorSql({ baseIndicator, ageCategoryCode, genders }: BuildFinalSqlArgs): string {
|
|
50
|
+
const { sql: countSql } = tryGetCountSqlFromIndicator(baseIndicator);
|
|
51
|
+
const pidCol = tryGetPatientIdColumnFromConfig(baseIndicator);
|
|
52
|
+
|
|
53
|
+
const populationSql = countSql ? countSqlToPopulationSql(countSql, pidCol, 'Patients') : '';
|
|
54
|
+
|
|
55
|
+
if (!populationSql.trim()) {
|
|
56
|
+
return `-- Unable to build final indicator SQL: base indicator does not contain usable count SQL.\n-- Ensure the base indicator has a valid sqlTemplate or configJson.sqlPreview.`;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Default to both genders if none selected (for backward compatibility)
|
|
60
|
+
// TODO: Remove this default in a future version and require explicit gender selection
|
|
61
|
+
const selectedGenders = (genders ?? []).length ? genders : (['F', 'M'] as Array<'F' | 'M'>);
|
|
62
|
+
|
|
63
|
+
// Log deprecation warning when defaulting
|
|
64
|
+
if (!genders || genders.length === 0) {
|
|
65
|
+
console.warn('[buildFinalIndicatorSql] No genders specified, defaulting to both F and M. This default will be removed in a future version.');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const gendersCte = buildGenderCte(selectedGenders);
|
|
69
|
+
|
|
70
|
+
// NOTE: adjust these table names if your DB uses different names.
|
|
71
|
+
// If you adopted the new naming, use report_builder_dim_age_category/group.
|
|
72
|
+
const AGE_CATEGORY_TABLE = 'report_builder_dim_age_category';
|
|
73
|
+
const AGE_GROUP_TABLE = 'report_builder_dim_age_group';
|
|
74
|
+
|
|
75
|
+
return `
|
|
76
|
+
WITH base_pop AS (
|
|
77
|
+
${indent(populationSql.trim(), 2)}
|
|
78
|
+
),
|
|
79
|
+
ag AS (
|
|
80
|
+
SELECT
|
|
81
|
+
ag.age_group_id,
|
|
82
|
+
ag.label,
|
|
83
|
+
ag.min_age_days,
|
|
84
|
+
ag.max_age_days,
|
|
85
|
+
ag.sort_order
|
|
86
|
+
FROM ${AGE_GROUP_TABLE} ag
|
|
87
|
+
JOIN ${AGE_CATEGORY_TABLE} ac
|
|
88
|
+
ON ac.age_category_id = ag.age_category_id
|
|
89
|
+
WHERE ac.code = '${escapeSqlLiteral(ageCategoryCode)}'
|
|
90
|
+
AND ag.is_active = 1
|
|
91
|
+
),
|
|
92
|
+
genders AS (
|
|
93
|
+
${indent(gendersCte, 2)}
|
|
94
|
+
),
|
|
95
|
+
cnt AS (
|
|
96
|
+
SELECT
|
|
97
|
+
ag.age_group_id AS age_group_id,
|
|
98
|
+
mdp.gender AS gender,
|
|
99
|
+
COUNT(DISTINCT base_pop.${pidCol}) AS value
|
|
100
|
+
FROM base_pop
|
|
101
|
+
JOIN mamba_fact_patients_latest_patient_demographics mdp
|
|
102
|
+
ON mdp.patient_id = base_pop.${pidCol}
|
|
103
|
+
JOIN ag
|
|
104
|
+
ON TIMESTAMPDIFF(DAY, mdp.birthdate, :endDate)
|
|
105
|
+
BETWEEN ag.min_age_days AND ag.max_age_days
|
|
106
|
+
WHERE mdp.birthdate IS NOT NULL
|
|
107
|
+
AND mdp.gender IS NOT NULL
|
|
108
|
+
AND mdp.gender IN (${selectedGenders.map((g) => `'${g}'`).join(',')})
|
|
109
|
+
GROUP BY ag.age_group_id, mdp.gender
|
|
110
|
+
)
|
|
111
|
+
SELECT
|
|
112
|
+
ag.label AS age_group,
|
|
113
|
+
g.gender AS gender,
|
|
114
|
+
COALESCE(cnt.value, 0) AS value
|
|
115
|
+
FROM ag
|
|
116
|
+
CROSS JOIN genders g
|
|
117
|
+
LEFT JOIN cnt
|
|
118
|
+
ON cnt.age_group_id = ag.age_group_id
|
|
119
|
+
AND cnt.gender = g.gender
|
|
120
|
+
ORDER BY ag.sort_order, g.gender;
|
|
121
|
+
`.trim();
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function buildGenderCte(genders: Array<'F' | 'M'>): string {
|
|
125
|
+
// Produce rows:
|
|
126
|
+
// SELECT 'F' AS gender UNION ALL SELECT 'M' AS gender
|
|
127
|
+
const parts = genders.map((g, i) => (i === 0 ? `SELECT '${g}' AS gender` : `UNION ALL SELECT '${g}' AS gender`));
|
|
128
|
+
return parts.join('\n');
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function indent(s: string, spaces: number) {
|
|
132
|
+
const pad = ' '.repeat(spaces);
|
|
133
|
+
return s
|
|
134
|
+
.split('\n')
|
|
135
|
+
.map((l) => (l.trim().length ? pad + l : l))
|
|
136
|
+
.join('\n');
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function escapeSqlLiteral(s: string) {
|
|
140
|
+
return String(s).replace(/'/g, "''");
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Async version of final indicator SQL builder.
|
|
145
|
+
*
|
|
146
|
+
* This function uses the recursive population SQL compiler to properly handle
|
|
147
|
+
* composite base indicators. For simple base indicators, it falls back to the
|
|
148
|
+
* synchronous logic.
|
|
149
|
+
*
|
|
150
|
+
* Use this version when the base indicator might be a composite indicator.
|
|
151
|
+
*/
|
|
152
|
+
export async function buildFinalIndicatorSqlAsync({
|
|
153
|
+
baseIndicator,
|
|
154
|
+
ageCategoryCode,
|
|
155
|
+
genders,
|
|
156
|
+
getIndicator,
|
|
157
|
+
compilerOptions = {}
|
|
158
|
+
}: BuildFinalSqlAsyncArgs): Promise<string> {
|
|
159
|
+
try {
|
|
160
|
+
// Clear cache to ensure fresh compilation
|
|
161
|
+
clearCompilationCache();
|
|
162
|
+
|
|
163
|
+
// If base indicator is composite, use the recursive compiler
|
|
164
|
+
if (baseIndicator.kind === 'COMPOSITE') {
|
|
165
|
+
const result = await compilePopulationSql(baseIndicator, getIndicator, new Set(), compilerOptions);
|
|
166
|
+
|
|
167
|
+
// Check for compilation errors
|
|
168
|
+
if (result.error) {
|
|
169
|
+
return `-- Error: ${result.error.message}\n-- Base indicator: ${baseIndicator.name} (${baseIndicator.code || baseIndicator.uuid})\n-- Error Code: ${result.error.code}`;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const populationSql = result.sql;
|
|
173
|
+
|
|
174
|
+
// Generate the disaggregation SQL using the compiled population SQL
|
|
175
|
+
// Note: population SQL is normalized to patient_id, so no need to specify patientIdColumn
|
|
176
|
+
return generateAgeSexDisaggregationSql({
|
|
177
|
+
populationSql,
|
|
178
|
+
ageCategoryCode,
|
|
179
|
+
genders
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// For BASE indicators, use the synchronous logic
|
|
184
|
+
return buildFinalIndicatorSql({ baseIndicator, ageCategoryCode, genders });
|
|
185
|
+
} catch (error) {
|
|
186
|
+
// Return an error comment in the SQL
|
|
187
|
+
const errorMsg = error instanceof Error ? error.message : String(error);
|
|
188
|
+
return `-- Error building final indicator SQL: ${errorMsg}\n-- Base indicator: ${baseIndicator.name} (${baseIndicator.code || baseIndicator.uuid})`;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
// src/report-builder/components/indicators/utils/indicator-conditions-hydration.utils.ts
|
|
2
|
+
|
|
3
|
+
import type { ThemeCondition } from '../types/data-theme-config.types';
|
|
4
|
+
import type { IndicatorCondition } from '../types/indicator-types';
|
|
5
|
+
|
|
6
|
+
import { getConceptByUuid } from '../../../resources/concepts/concepts.resource';
|
|
7
|
+
import type { ConceptSummary } from '../../../resources/concepts/concept-types';
|
|
8
|
+
|
|
9
|
+
import { toSelectedConcept, type SelectedConcept } from '../handler/concept-search-multiselect.component';
|
|
10
|
+
import type { ConceptUiMap, QaUiMap } from '../types/condition-ui.types';
|
|
11
|
+
|
|
12
|
+
export type HydrationOptions = {
|
|
13
|
+
force?: boolean;
|
|
14
|
+
dedupe?: boolean;
|
|
15
|
+
preserveUnknownKeys?: boolean;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export type HydrationResult = {
|
|
19
|
+
conceptUi: ConceptUiMap;
|
|
20
|
+
qaUi: QaUiMap;
|
|
21
|
+
stats: {
|
|
22
|
+
themeConditionsCount: number;
|
|
23
|
+
pickedConditionsCount: number;
|
|
24
|
+
conceptConditionsHydrated: number;
|
|
25
|
+
qaConditionsHydrated: number;
|
|
26
|
+
tokensHydrated: number;
|
|
27
|
+
tokensMissed: number;
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
// Stored value shape in pickedConditions for QA handler
|
|
32
|
+
// (support both legacy single-question and current multi-question formats)
|
|
33
|
+
type QAValue = {
|
|
34
|
+
question?: string | number | null;
|
|
35
|
+
questions?: Array<string | number>;
|
|
36
|
+
answers?: Array<string | number>;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
function uniq(tokens: string[]) {
|
|
40
|
+
const out: string[] = [];
|
|
41
|
+
const seen = new Set<string>();
|
|
42
|
+
for (const t of tokens) {
|
|
43
|
+
const k = String(t ?? '').trim();
|
|
44
|
+
if (!k) continue;
|
|
45
|
+
if (seen.has(k)) continue;
|
|
46
|
+
seen.add(k);
|
|
47
|
+
out.push(k);
|
|
48
|
+
}
|
|
49
|
+
return out;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Hydrate ONE token into ConceptSummary (internal).
|
|
54
|
+
* We now persist UUIDs, so we call direct endpoint by UUID.
|
|
55
|
+
*/
|
|
56
|
+
async function hydrateConceptSummaryToken(token: string, signal?: AbortSignal): Promise<ConceptSummary | null> {
|
|
57
|
+
const tok = String(token ?? '').trim();
|
|
58
|
+
if (!tok) return null;
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
const concept = await getConceptByUuid(tok, signal);
|
|
62
|
+
if (!concept?.uuid) return null;
|
|
63
|
+
return concept;
|
|
64
|
+
} catch {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Hydrate ONE token into SelectedConcept (UI shape).
|
|
71
|
+
* ✅ Uses toSelectedConcept so ICD mappings are preserved.
|
|
72
|
+
*/
|
|
73
|
+
async function hydrateConceptToken(token: string, signal?: AbortSignal): Promise<SelectedConcept | null> {
|
|
74
|
+
const cs = await hydrateConceptSummaryToken(token, signal);
|
|
75
|
+
return cs ? toSelectedConcept(cs) : null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Hydrate MANY tokens into SelectedConcept[].
|
|
80
|
+
* ✅ for-loop; preserves order; returns only valid concepts.
|
|
81
|
+
*/
|
|
82
|
+
async function hydrateConceptTokens(tokens: string[], signal?: AbortSignal, dedupe = true): Promise<SelectedConcept[]> {
|
|
83
|
+
const clean = dedupe ? uniq(tokens) : tokens.map((t) => String(t ?? '').trim()).filter(Boolean);
|
|
84
|
+
if (!clean.length) return [];
|
|
85
|
+
|
|
86
|
+
const results: SelectedConcept[] = [];
|
|
87
|
+
|
|
88
|
+
for (const tok of clean) {
|
|
89
|
+
if (signal?.aborted) break;
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
const concept = await hydrateConceptToken(tok, signal);
|
|
93
|
+
if (concept) results.push(concept);
|
|
94
|
+
} catch {
|
|
95
|
+
// ignore individual token failures
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return results;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Hydrates UI state for concept-based conditions (edit mode):
|
|
104
|
+
* - iterate picked first (source of truth)
|
|
105
|
+
* - match to themeByKey for handler
|
|
106
|
+
* - ensure defaults for theme conditions not saved
|
|
107
|
+
*/
|
|
108
|
+
export async function hydrateConditionUiState(
|
|
109
|
+
themeConditions: ThemeCondition[] | undefined,
|
|
110
|
+
pickedConditions: IndicatorCondition[] | undefined,
|
|
111
|
+
existingConceptUi: ConceptUiMap | undefined,
|
|
112
|
+
existingQaUi: QaUiMap | undefined,
|
|
113
|
+
signal?: AbortSignal,
|
|
114
|
+
options?: HydrationOptions,
|
|
115
|
+
): Promise<HydrationResult> {
|
|
116
|
+
const opt: Required<HydrationOptions> = {
|
|
117
|
+
force: options?.force ?? true,
|
|
118
|
+
dedupe: options?.dedupe ?? true,
|
|
119
|
+
preserveUnknownKeys: options?.preserveUnknownKeys ?? true,
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const themeList = themeConditions ?? [];
|
|
123
|
+
const pickedList = pickedConditions ?? [];
|
|
124
|
+
|
|
125
|
+
const themeByKey = new Map<string, ThemeCondition>();
|
|
126
|
+
for (const tc of themeList) {
|
|
127
|
+
if (tc?.key) themeByKey.set(tc.key, tc);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const nextConceptUi: ConceptUiMap = { ...(existingConceptUi ?? {}) };
|
|
131
|
+
const nextQaUi: QaUiMap = { ...(existingQaUi ?? {}) };
|
|
132
|
+
|
|
133
|
+
let conceptConditionsHydrated = 0;
|
|
134
|
+
let qaConditionsHydrated = 0;
|
|
135
|
+
let tokensHydrated = 0;
|
|
136
|
+
let tokensMissed = 0;
|
|
137
|
+
|
|
138
|
+
const seenKeys = new Set<string>();
|
|
139
|
+
|
|
140
|
+
// 1) hydrate from picked conditions (source of truth)
|
|
141
|
+
for (const pc of pickedList) {
|
|
142
|
+
if (!pc?.key) continue;
|
|
143
|
+
seenKeys.add(pc.key);
|
|
144
|
+
|
|
145
|
+
const tc = themeByKey.get(pc.key);
|
|
146
|
+
if (!tc) {
|
|
147
|
+
if (!opt.preserveUnknownKeys) {
|
|
148
|
+
delete nextConceptUi[pc.key];
|
|
149
|
+
delete nextQaUi[pc.key];
|
|
150
|
+
}
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// -----------------------------
|
|
155
|
+
// CONCEPT_SEARCH
|
|
156
|
+
// -----------------------------
|
|
157
|
+
if (tc.handler === 'CONCEPT_SEARCH') {
|
|
158
|
+
const shouldHydrate =
|
|
159
|
+
opt.force || !Array.isArray(nextConceptUi[tc.key]) || nextConceptUi[tc.key].length === 0;
|
|
160
|
+
|
|
161
|
+
if (!shouldHydrate) continue;
|
|
162
|
+
|
|
163
|
+
const raw = pc.value;
|
|
164
|
+
|
|
165
|
+
if (Array.isArray(raw) && raw.length) {
|
|
166
|
+
const tokens = raw.map((x) => String(x)).filter(Boolean);
|
|
167
|
+
const selected = await hydrateConceptTokens(tokens, signal, opt.dedupe);
|
|
168
|
+
|
|
169
|
+
tokensHydrated += selected.length;
|
|
170
|
+
tokensMissed += Math.max(0, tokens.length - selected.length);
|
|
171
|
+
|
|
172
|
+
nextConceptUi[tc.key] = selected;
|
|
173
|
+
} else {
|
|
174
|
+
nextConceptUi[tc.key] = [];
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
conceptConditionsHydrated += 1;
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// -----------------------------
|
|
182
|
+
// QUESTION_ANSWER_CONCEPT_SEARCH
|
|
183
|
+
// -----------------------------
|
|
184
|
+
if (tc.handler === 'QUESTION_ANSWER_CONCEPT_SEARCH') {
|
|
185
|
+
const existing = nextQaUi[tc.key];
|
|
186
|
+
|
|
187
|
+
const shouldHydrate =
|
|
188
|
+
opt.force ||
|
|
189
|
+
!existing ||
|
|
190
|
+
((existing.questions?.length ?? 0) === 0 && (existing.answers?.length ?? 0) === 0 && !existing.error);
|
|
191
|
+
|
|
192
|
+
if (!shouldHydrate) continue;
|
|
193
|
+
|
|
194
|
+
const raw: any = pc.value;
|
|
195
|
+
|
|
196
|
+
const qa: QAValue | null = raw && typeof raw === 'object' ? (raw as QAValue) : null;
|
|
197
|
+
|
|
198
|
+
// ✅ support both shapes: { question } OR { questions: [] }
|
|
199
|
+
const qTokens: string[] = Array.isArray(qa?.questions)
|
|
200
|
+
? (qa?.questions ?? []).map((x) => String(x).trim()).filter(Boolean)
|
|
201
|
+
: qa?.question !== null && qa?.question !== undefined
|
|
202
|
+
? [String(qa.question).trim()].filter(Boolean)
|
|
203
|
+
: [];
|
|
204
|
+
|
|
205
|
+
const aTokens: string[] = Array.isArray(qa?.answers)
|
|
206
|
+
? (qa?.answers ?? []).map((x) => String(x).trim()).filter(Boolean)
|
|
207
|
+
: [];
|
|
208
|
+
|
|
209
|
+
const questions: SelectedConcept[] = qTokens.length
|
|
210
|
+
? await hydrateConceptTokens(qTokens, signal, opt.dedupe)
|
|
211
|
+
: [];
|
|
212
|
+
|
|
213
|
+
const answers: SelectedConcept[] = aTokens.length
|
|
214
|
+
? await hydrateConceptTokens(aTokens, signal, opt.dedupe)
|
|
215
|
+
: [];
|
|
216
|
+
|
|
217
|
+
tokensHydrated += questions.length + answers.length;
|
|
218
|
+
tokensMissed += Math.max(0, qTokens.length - questions.length) + Math.max(0, aTokens.length - answers.length);
|
|
219
|
+
|
|
220
|
+
nextQaUi[tc.key] = { questions, answers };
|
|
221
|
+
qaConditionsHydrated += 1;
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// other handlers ignored
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// 2) ensure all theme conditions have UI defaults
|
|
229
|
+
for (const tc of themeList) {
|
|
230
|
+
if (!tc?.key) continue;
|
|
231
|
+
|
|
232
|
+
if (!seenKeys.has(tc.key)) {
|
|
233
|
+
if (tc.handler === 'CONCEPT_SEARCH') {
|
|
234
|
+
if (!Array.isArray(nextConceptUi[tc.key])) nextConceptUi[tc.key] = [];
|
|
235
|
+
}
|
|
236
|
+
if (tc.handler === 'QUESTION_ANSWER_CONCEPT_SEARCH') {
|
|
237
|
+
if (!nextQaUi[tc.key]) nextQaUi[tc.key] = { questions: [], answers: [] };
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
return {
|
|
243
|
+
conceptUi: nextConceptUi,
|
|
244
|
+
qaUi: nextQaUi,
|
|
245
|
+
stats: {
|
|
246
|
+
themeConditionsCount: themeList.length,
|
|
247
|
+
pickedConditionsCount: pickedList.length,
|
|
248
|
+
conceptConditionsHydrated,
|
|
249
|
+
qaConditionsHydrated,
|
|
250
|
+
tokensHydrated,
|
|
251
|
+
tokensMissed,
|
|
252
|
+
},
|
|
253
|
+
};
|
|
254
|
+
}
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Centralized Indicator SQL Resolution
|
|
3
|
+
*
|
|
4
|
+
* This module provides a single canonical way to resolve SQL from indicators
|
|
5
|
+
* across all compilation paths (Indicator Preview, Section Preview, etc.).
|
|
6
|
+
*
|
|
7
|
+
* Instead of duplicating the fallback chain in multiple components,
|
|
8
|
+
* use these functions to reliably locate and extract SQL from indicator configurations.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import type { IndicatorDto } from '../../../resources/indicator/indicators.api';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* SQL extraction source for debugging and error reporting
|
|
15
|
+
*/
|
|
16
|
+
export type SqlSource =
|
|
17
|
+
| 'indicator.sqlTemplate'
|
|
18
|
+
| 'configJson.sqlPreview'
|
|
19
|
+
| 'configJson.sqlTemplate'
|
|
20
|
+
| 'configJson.base.sqlPreview'
|
|
21
|
+
| 'configJson.base.sqlTemplate'
|
|
22
|
+
| 'configJson.authoring.base.sqlPreview'
|
|
23
|
+
| 'configJson.authoring.base.sqlTemplate'
|
|
24
|
+
| 'configJson.baseIndicator.sqlPreview'
|
|
25
|
+
| 'configJson.baseIndicator.sqlTemplate'
|
|
26
|
+
| 'none'
|
|
27
|
+
| 'none(parse-error)'
|
|
28
|
+
| 'none(empty-config)';
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Result of resolving indicator SQL with source tracking for debugging
|
|
32
|
+
*/
|
|
33
|
+
export type ResolvedIndicatorSql = {
|
|
34
|
+
/** The extracted SQL */
|
|
35
|
+
sql: string;
|
|
36
|
+
/** Where the SQL was found (for debugging) */
|
|
37
|
+
source: SqlSource;
|
|
38
|
+
/** The config JSON that was parsed (for reference) */
|
|
39
|
+
parsedConfig?: any;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Structured error when SQL cannot be resolved
|
|
44
|
+
*/
|
|
45
|
+
export type SqlResolutionError = {
|
|
46
|
+
success: false;
|
|
47
|
+
error: string;
|
|
48
|
+
indicator: {
|
|
49
|
+
uuid: string;
|
|
50
|
+
code?: string;
|
|
51
|
+
name?: string;
|
|
52
|
+
kind?: string;
|
|
53
|
+
};
|
|
54
|
+
attemptedSources: SqlSource[];
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Resolve SQL from an indicator with comprehensive fallback chain.
|
|
59
|
+
*
|
|
60
|
+
* This is the canonical way to extract SQL from indicators across all compilation paths.
|
|
61
|
+
*
|
|
62
|
+
* Priority order (as per the current config structure):
|
|
63
|
+
* 1. indicator.sqlTemplate (direct field)
|
|
64
|
+
* 2. configJson.sqlPreview (flat config)
|
|
65
|
+
* 3. configJson.sqlTemplate (flat config)
|
|
66
|
+
* 4. configJson.base.sqlPreview (nested base config)
|
|
67
|
+
* 5. configJson.base.sqlTemplate (nested base config)
|
|
68
|
+
* 6. configJson.authoring.base.sqlPreview (authoring structure)
|
|
69
|
+
* 7. configJson.authoring.base.sqlTemplate (authoring structure)
|
|
70
|
+
* 8. configJson.baseIndicator.sqlPreview (wrapped indicator config)
|
|
71
|
+
* 9. configJson.baseIndicator.sqlTemplate (wrapped indicator config)
|
|
72
|
+
*
|
|
73
|
+
* @param indicator - The indicator to extract SQL from
|
|
74
|
+
* @returns Resolved SQL with source tracking, or structured error
|
|
75
|
+
*/
|
|
76
|
+
export function resolveIndicatorSql(indicator: IndicatorDto): ResolvedIndicatorSql | SqlResolutionError {
|
|
77
|
+
const attemptedSources: SqlSource[] = [];
|
|
78
|
+
const direct = (indicator?.sqlTemplate ?? '').trim();
|
|
79
|
+
|
|
80
|
+
if (direct) {
|
|
81
|
+
return {
|
|
82
|
+
sql: normalizeEscapedNewlines(direct),
|
|
83
|
+
source: 'indicator.sqlTemplate'
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
attemptedSources.push('indicator.sqlTemplate');
|
|
87
|
+
|
|
88
|
+
// Try to parse config JSON
|
|
89
|
+
let parsed: any = null;
|
|
90
|
+
try {
|
|
91
|
+
parsed = indicator?.configJson ? JSON.parse(indicator.configJson) : null;
|
|
92
|
+
} catch (e) {
|
|
93
|
+
// Invalid JSON - will return error at end
|
|
94
|
+
parsed = null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (!parsed) {
|
|
98
|
+
return createSqlResolutionError(indicator, attemptedSources, 'configJson is null or invalid JSON');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Helper to try a path and return early if found
|
|
102
|
+
const tryPath = (source: SqlSource, getValue: (p: any) => string): ResolvedIndicatorSql | null => {
|
|
103
|
+
attemptedSources.push(source);
|
|
104
|
+
const value = (getValue(parsed) ?? '').trim();
|
|
105
|
+
if (value) {
|
|
106
|
+
return {
|
|
107
|
+
sql: normalizeEscapedNewlines(value),
|
|
108
|
+
source,
|
|
109
|
+
parsedConfig: parsed
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
return null;
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
// Try all known paths in priority order
|
|
116
|
+
const result =
|
|
117
|
+
tryPath('configJson.sqlPreview', (p) => p.sqlPreview) ||
|
|
118
|
+
tryPath('configJson.sqlTemplate', (p) => p.sqlTemplate) ||
|
|
119
|
+
tryPath('configJson.base.sqlPreview', (p) => p.base?.sqlPreview) ||
|
|
120
|
+
tryPath('configJson.base.sqlTemplate', (p) => p.base?.sqlTemplate) ||
|
|
121
|
+
tryPath('configJson.authoring.base.sqlPreview', (p) => p.authoring?.base?.sqlPreview) ||
|
|
122
|
+
tryPath('configJson.authoring.base.sqlTemplate', (p) => p.authoring?.base?.sqlTemplate) ||
|
|
123
|
+
tryPath('configJson.baseIndicator.sqlPreview', (p) => p.baseIndicator?.sqlPreview) ||
|
|
124
|
+
tryPath('configJson.baseIndicator.sqlTemplate', (p) => p.baseIndicator?.sqlTemplate);
|
|
125
|
+
|
|
126
|
+
if (result) {
|
|
127
|
+
return result;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
attemptedSources.push('none');
|
|
131
|
+
return createSqlResolutionError(indicator, attemptedSources, 'No SQL found in any known config location');
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Create a structured SQL resolution error
|
|
136
|
+
*/
|
|
137
|
+
function createSqlResolutionError(
|
|
138
|
+
indicator: IndicatorDto,
|
|
139
|
+
attemptedSources: SqlSource[],
|
|
140
|
+
reason: string
|
|
141
|
+
): SqlResolutionError {
|
|
142
|
+
return {
|
|
143
|
+
success: false,
|
|
144
|
+
error: `SQL_RESOLUTION_FAILED: ${reason}`,
|
|
145
|
+
indicator: {
|
|
146
|
+
uuid: indicator.uuid,
|
|
147
|
+
code: indicator.code,
|
|
148
|
+
name: indicator.name,
|
|
149
|
+
kind: indicator.kind
|
|
150
|
+
},
|
|
151
|
+
attemptedSources
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Check if a resolved SQL result is an error
|
|
157
|
+
*/
|
|
158
|
+
export function isSqlResolutionError(result: ResolvedIndicatorSql | SqlResolutionError): result is SqlResolutionError {
|
|
159
|
+
return (result as any).success === false;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Resolve patient ID column from indicator config.
|
|
164
|
+
*
|
|
165
|
+
* Centralized way to extract the patient ID column across all compilation paths.
|
|
166
|
+
*
|
|
167
|
+
* Priority order:
|
|
168
|
+
* 1. parsed.themeConfig.patientIdColumn
|
|
169
|
+
* 2. parsed.base.themeConfig.patientIdColumn
|
|
170
|
+
* 3. parsed.authoring.base.themeConfig.patientIdColumn
|
|
171
|
+
* 4. parsed.baseIndicator.themeConfig.patientIdColumn
|
|
172
|
+
* 5. Default: 'client_id'
|
|
173
|
+
*
|
|
174
|
+
* @param indicator - The indicator to extract patient ID column from
|
|
175
|
+
* @returns The patient ID column name (defaults to 'client_id')
|
|
176
|
+
*/
|
|
177
|
+
export function resolvePatientIdColumn(indicator: IndicatorDto): string {
|
|
178
|
+
try {
|
|
179
|
+
const parsed: any = indicator?.configJson ? JSON.parse(indicator.configJson) : null;
|
|
180
|
+
|
|
181
|
+
const cfg =
|
|
182
|
+
parsed?.themeConfig ? { ...parsed.themeConfig, _source: 'themeConfig' } :
|
|
183
|
+
parsed?.base?.themeConfig ? { ...parsed.base.themeConfig, _source: 'base.themeConfig' } :
|
|
184
|
+
parsed?.authoring?.base?.themeConfig ? { ...parsed.authoring.base.themeConfig, _source: 'authoring.base.themeConfig' } :
|
|
185
|
+
parsed?.baseIndicator?.themeConfig ? { ...parsed.baseIndicator.themeConfig, _source: 'baseIndicator.themeConfig' } :
|
|
186
|
+
null;
|
|
187
|
+
|
|
188
|
+
const pid = cfg?.patientIdColumn;
|
|
189
|
+
return pid ? String(pid) : 'client_id';
|
|
190
|
+
} catch {
|
|
191
|
+
return 'client_id';
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Normalize escaped newlines in SQL.
|
|
197
|
+
*
|
|
198
|
+
* SQL stored in JSON may have literal \n instead of actual newlines.
|
|
199
|
+
* This converts them back.
|
|
200
|
+
*/
|
|
201
|
+
export function normalizeEscapedNewlines(sql: string): string {
|
|
202
|
+
if (!sql) return sql;
|
|
203
|
+
return sql.replace(/\\n/g, '\n');
|
|
204
|
+
}
|