Feat/queue entry metrics endpoint#95
Conversation
- Replace Criteria API with HQL and explicit fetch joins - Only fetch necessary relationships (queue, patient, priority, status, visit, queueComingFrom) - Eliminate eager loading of 13 user objects, 4 locations, and unnecessary concept variants - Reduces query from 59 joins to 11 joins (81% reduction) - Expected performance improvement: 60-80% faster response times Fixes performance issue reported on Talk: https://talk.openmrs.org/t/patient-queue-module-performance-issue/47627
- Remove DISTINCT from HQL SELECT clause - Add setResultTransformer(DISTINCT_ROOT_ENTITY) to deduplicate results - Fixes test failures where query returned 4 results instead of expected counts
- Remove DISTINCT from HQL SELECT clause - Use LinkedHashSet to remove duplicates while preserving order - Avoids deprecated setResultTransformer method - Fixes test failures expecting specific result counts
…alified references
|
Two things here:
|
|
Also the change here listed a 3 actually makes this PR perform incorrectly. |
|
Hi @ibacher, apologies for the delayed response. I was away for the |
|
Could you clarify which specific change in point 3 is causing incorrect behaviour? I want to make sure I fix it properly rather than guess. Are you referring to the getFirstConceptName() helper replacing Concept.getName()? |
|
As of now, reverted the unrelated DAO changes. This PR now only adds the new metrics endpoint (3 files: QueueEntryMetricsResponse, QueueEntryMetricsController, and its test). |
|
Good idea to create a dedicated metrics endpoint to reduce over-fetching. A few observations: The flat DTO approach is clean and the constructor injection pattern makes the controller testable without PowerMock, which is a good design choice. |
Description
The Service Queues UI currently fetches active queue entries using generic custom representations:
This forces the REST layer to traverse a deep object graph on every request:
For a queue board with 50+ patients this causes unnecessary Hibernate
fetches on every page load. Additionally, wait time is currently derived
client-side from
startedAt, adding frontend logic that belongs on theserver.
Solution
New endpoint:
Returns a flat JSON array with only what the UI renders:
[ { "uuid": "abc-123", "patientUuid": "pat-456", "patientName": "John Doe", "patientIdentifier": "OM-10045", "patientAge": 34, "patientGender": "M", "queueUuid": "q-789", "queueName": "Triage", "locationUuid": "loc-001", "locationName": "Outpatient Clinic", "statusUuid": "s-111", "statusDisplay": "Waiting", "priorityUuid": "p-222", "priorityDisplay": "Normal", "startedAt": "2025-03-01T08:30:00.000+0000", "waitTimeMinutes": 34, "sortWeight": 0.0 } ]Files Changed
omod/src/main/java/.../web/dto/QueueEntryMetricsResponse.javaomod/src/main/java/.../web/resources/controller/QueueEntryMetricsController.javaomod/src/test/java/.../web/resources/controller/QueueEntryMetricsControllerTest.javaKey Technical Decisions
1. Flat DTO instead of custom representation
Every field in
QueueEntryMetricsResponseis a primitive, String, orDate. No nested objects. The server resolves
patient.person.personNameonce and returns a plain
patientNamestring.2. Constructor injection instead of static Context
The controller receives
LocationServiceandQueueEntryServicevia@Autowiredconstructor injection instead ofContext.getService()static calls. This makes the controller fully testable without PowerMock
or a Spring context.
3.
getFirstConceptName()instead ofConcept.getName()Concept.getName()internally callsContext.getAdministrationService()to resolve locale — which requires a full Spring context and causes
unnecessary overhead in production. Replaced with a helper that calls
concept.getNames().iterator().next()directly, bypassing the localelookup entirely.
4. Wait time computed server-side
waitTimeMinutesis computed once on the server fromstartedAt,removing the need for the frontend to derive it per patient.
5. Active entries only
criteria.setIsEnded(false)ensures only active queue entries arereturned — no ended entries are fetched or mapped.
Query Parameters
locationstatusqueueTests
7 unit tests covering:
All tests use plain
MockitoJUnitRunner— no PowerMock, no Springcontext required.
How to Test Manually
curl -u admin:Admin123 \ "http://localhost:8080/openmrs/ws/rest/v1/queue/metrics?location=<locationUuid>"Related
discussion that the frontend relies on unwieldy custom representations and
that a purpose-built endpoint returning only the required data should be
built instead.