Radiology/image link column in the Patient List - #2939
Merged
Conversation
- Add @img placeholder->table mapping (image_occurrence joined to person via person_id) in query-gen-svc and analytics-svc, so image_occurrence can be configured as a patient-list interaction with no query-engine code change. - Allow an optional patientlist.link { label } in the PA config schema.
Patient-list cells whose value is a valid http(s) URL render as a new-tab link (rel=noopener noreferrer) with an external-link icon; other values render as plain text. Enables the radiology 'Action' column to open an image viewer. - urlUtils.isValidHttpUrl: http/https-only URL validation - PatientListLinkData.vue + ExternalLinkIcon.vue: the link cell - PatientListControl: per-cell dispatch (link vs text) by value - MriConfigAttribute: getPatientListLink/isPatientListLink for the config label
khairul-syazwan
requested review from
LSriragavan,
brandantck,
jerome-ng,
maggie-li-yd and
p-hoffmann
as code owners
July 22, 2026 07:41
Contributor
There was a problem hiding this comment.
Pull request overview
Adds radiology/image support to the Patient List by enabling an @IMG placeholder for OMOP image_occurrence in backend default mappings and introducing a reusable frontend “link cell” renderer that safely turns valid http(s) values into new-tab links.
Changes:
- Backend: add
@IMGplaceholder →image_occurrencedefault table mappings in bothquery-gen-svcandanalytics-svc. - Frontend: add URL validation utility + new patient-list link cell component (with external-link icon) and wire it into patient-list rendering.
- Config + tests: extend config schema for
patientlist.link.labeland add unit tests for URL validation, config accessor, and link cell rendering.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| plugins/ui/apps/vue-mri-ui-lib/src/utils/urlUtils.ts | Adds isValidHttpUrl helper for safe http/https detection. |
| plugins/ui/apps/vue-mri-ui-lib/src/utils/tests/urlUtils.test.ts | Unit tests for URL validation edge cases. |
| plugins/ui/apps/vue-mri-ui-lib/src/lib/MriConfigAttribute.ts | Adds accessor for optional patientlist.link config. |
| plugins/ui/apps/vue-mri-ui-lib/src/lib/tests/MriConfigAttribute.test.ts | Tests for the new link-config accessor. |
| plugins/ui/apps/vue-mri-ui-lib/src/components/PatientListLinkData.vue | New patient-list cell renderer that displays safe new-tab links. |
| plugins/ui/apps/vue-mri-ui-lib/src/components/PatientListControl.vue | Dispatches between text vs. link cell rendering and carries link label into column meta. |
| plugins/ui/apps/vue-mri-ui-lib/src/components/icons/ExternalLinkIcon.vue | Adds external-link icon used by link cells. |
| plugins/ui/apps/vue-mri-ui-lib/src/components/tests/PatientListLinkData.test.ts | Tests link rendering, label fallback, and unsafe/missing value behavior. |
| plugins/functions/query-gen-svc/src/qe/settings/OldSettings.ts | Adds default @IMG placeholder mapping to image_occurrence. |
| plugins/functions/mri-pa-config/src/config/configDefinition.ts | Extends config schema to allow optional patientlist.link.label. |
| plugins/functions/analytics-svc/src/qe/settings/OldSettings.ts | Mirrors the @IMG default mapping addition. |
maggie-li-yd
approved these changes
Jul 27, 2026
github-merge-queue
Bot
removed this pull request from the merge queue due to failed status checks
Jul 28, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds radiology / image data support to the Patient List (issue #2849). Researchers can now see imaging attributes (Study name, Modality, Date) alongside a patient and click straight through to an image viewer — without leaving the analytics workflow.
Two capabilities, both generic and reusable:
image_occurrencein the Patient List via a new@IMGquery placeholder — so an "imaging"/"Radiology" interaction can be configured with no query-engine code change.http(s)URL becomes a safe, new-tab link (with an external-link icon) instead of raw text. The radiology "Action" column uses this to open the viewer.The PR is the plumbing. Getting DICOM into
image_occurrence/Orthanc (ETL) and deploying a viewer are separate tracks; see Out of scope and Manual setup.Architecture & how it works
flowchart TD C[(OMOP image_occurrence)] --> E["PA config: @IMG maps to a<br/>Radiology interaction<br/>Study / Modality / Date / Action"] E --> F["query-gen builds SQL<br/>joins patient + concept (modality)"] F --> G["result rows include a viewer URL"] G --> H{"Cell value is a<br/>valid http/https URL?"} H -->|yes| I["render link (new tab,<br/>rel=noopener) + icon"] H -->|no| J["render plain text"] I -->|click| K["image viewer opens the study"] classDef pr fill:#7c3aed,stroke:#a78bfa,color:#fff; classDef ctx fill:#3f3f46,stroke:#71717a,color:#e4e4e7; class E,F,G,H,I,J pr; class C,K ctx;Purple = changed by this PR. Grey = surrounding context (data + viewer).
Backend — why no query-engine change is needed. The SQL generator is table-agnostic: it resolves interaction placeholders (
@COND,@VISIT, …) against a per-dataset mapping. This PR adds@IMG→image_occurrence(joined topersonviaperson_id), mirroring@CONDexactly (8 keys). Modality resolvesmodality_concept_id→concept.concept_namethrough the existing@REFvocabulary-join pattern.Frontend — the link cell. Each cell asks "is my value a valid
http/httpsURL?" (isValidHttpUrl). If yes → an anchor opening in a new tab (rel="noopener noreferrer") with a config-driven label (falls back to a generic "Open"). If no → plain text, exactly as before. Dangerous schemes (javascript:,data:) are rejected and shown as text.Changes (11 files, 2 commits)
Backend — surface
image_occurrencequery-gen-svc/…/qe/settings/OldSettings.ts@IMGplaceholder→table mapping (default).analytics-svc/…/qe/settings/OldSettings.tsmri-pa-config/…/config/configDefinition.tspatientlist.link { label }in the config schema.Frontend — render URL/link columns
vue-mri-ui-lib/…/utils/urlUtils.ts(new)isValidHttpUrl()— http/https only; rejects empty/non-string/javascript:/data:/malformed.vue-mri-ui-lib/…/components/PatientListLinkData.vue(new)vue-mri-ui-lib/…/components/icons/ExternalLinkIcon.vue(new)vue-mri-ui-lib/…/components/PatientListControl.vuevue-mri-ui-lib/…/lib/MriConfigAttribute.tsgetPatientListLink()accessor for the config link label.…/__tests__/*.test.ts(3 new)Design decisions
wadors_urivs. constructing an OHIF URL from the study UID) is a deployment decision — the UI is viewer-agnostic.Manual setup for existing datasets (config)
New datasets can have this generated from the data model; existing datasets need a one-time, targeted config patch (via the config service
save/activate, not raw SQL). Add the following to the dataset's config:1. CDW config —
@IMGmapping inadvancedSettings.tableMapping(mirror the existing@CONDquoting for your dialect):2.⚠️ Register
@IMGas a dim placeholder inadvancedSettings.tableTypePlaceholderMap.dimTables(without this, query-gen returns a 500):{ "placeholder": "@IMG", "attributeTables": [], "hierarchy": true, "time": true, "oneToN": false, "condition": false }3. Add the
imageoccurrenceinteraction (patient.interactions.imageoccurrence,defaultPlaceholder: "@IMG") with attributes:studyname(@IMG."IMAGE_STUDY_UID"),modality(@REF.concept_name, filter@REF.concept_id = @IMG.MODALITY_CONCEPT_ID),imagedate(@IMG."IMAGE_OCCURRENCE_DATE", typetime),action(@IMG."WADORS_URI"or a constructed viewer URL), andpid(@IMG.person_id) — required for row grouping.4. Frontend config — a matching
patient.interactions.imageoccurrencefiltercard with each attributepatientlist.visible: true, and theactionattribute carryingpatientlist.link: { label: "OHIF viewer" }.5. Apply —
save+activateviamri-pa-configconfig.xsjs(needsmri-admin), then add the columns from the patient list's column menu (or add them tochartOptions.list.initialColumnsto auto-show).Gotchas
@IMGmissing fromtableTypePlaceholderMap.dimTables(step 2)pidattribute (step 3)image_occurrence does not existat query time