Skip to content

O3-5692: Build a dedicated REST endpoint for queue entries#114

Open
UjjawalPrabhat wants to merge 3 commits into
openmrs:mainfrom
UjjawalPrabhat:O3-5692-queue-entry-endpoint
Open

O3-5692: Build a dedicated REST endpoint for queue entries#114
UjjawalPrabhat wants to merge 3 commits into
openmrs:mainfrom
UjjawalPrabhat:O3-5692-queue-entry-endpoint

Conversation

@UjjawalPrabhat
Copy link
Copy Markdown

@UjjawalPrabhat UjjawalPrabhat commented May 25, 2026

Summary

The Service Queues table currently loads via QueueEntryResource with a deep custom v= representation that pulls every encounter, obs, and diagnosis on each patient's visit. On a 50-entry queue this triggers hundreds of cascading Hibernate loads; ~95% of the bytes are never read by the table.

Adds GET /rest/v1/queue-entry-summary — flat, server-paginated, with previousQueueEntryUuid resolved via a single batch IN-clause query instead of the per-row N+1 path through getPreviousQueueEntry.

Mirrors the three-layer batch pattern from openmrs-module-emrapi PR #250.

Response shape

Before (/queue-entry?v=custom:(…) as requested by the frontend):

{
  "uuid": "...",
  "display": "...",
  "queue":    { "uuid": "...", "display": "...", "name": "...", "description": "...", "service": {...}, "location": {...} },
  "status":   { "uuid": "...", "display": "...", "name": {...}, "datatype": {...}, "conceptClass": {...}, "set": false, "version": "...", "retired": false, "names": [...], "descriptions": [...], "mappings": [...], "answers": [...], "setMembers": [...], ... },
  "priority": { /* same deep Concept shape */ },
  "patient": {
    "uuid": "...", "display": "...",
    "identifiers": [ { "uuid": "...", "identifier": "...", "identifierType": {...}, "location": {...}, "preferred": true, ... } ],
    "person": { /* full person graph */ }
  },
  "visit": {
    "uuid": "...", "startDatetime": "...",
    "encounters": [
      {
        "uuid": "...", "display": "...", "encounterDatetime": "...",
        "encounterType": {...},
        "diagnoses": [ { /* full diagnosis */ } ],
        "obs": [ { /* full obs, recursively */ } ],
        "encounterProviders": [ { /* full provider */ } ],
        "voided": false
      }
      /* …every encounter on the visit, each with its full obs/diagnoses/providers graph */
    ],
    "attributes": [ { /* full visit attributes */ } ]
  },
  "priorityComment": "...",
  "sortWeight": 0.0,
  "startedAt": "...", "endedAt": null,
  "locationWaitingFor": {...}, "providerWaitingFor": {...}, "queueComingFrom": {...},
  "previousQueueEntry": { /* full QueueEntry, recursively */ }
}

After (/queue-entry-summary):

{
  "uuid": "...",
  "patient":  { "uuid": "...", "display": "100GEJ - John Doe" },
  "queue":    { "uuid": "...", "display": "Triage" },
  "status":   { "uuid": "...", "display": "Waiting" },
  "priority": { "uuid": "...", "display": "Not urgent" },
  "priorityComment": "...",
  "startedAt": "2026-05-25T09:00:00.000+0000",
  "visitUuid": "...",
  "visitStartDatetime": "2026-05-25T08:55:00.000+0000",
  "previousQueueEntryUuid": null
}

Relate Issue

O3-5692

@jwnasambu jwnasambu changed the title O3-5692: Add lightweight queue-entry-summary REST endpoint O3-5692: Build a dedicated REST endpoint for queue entries May 25, 2026
Copy link
Copy Markdown

@jwnasambu jwnasambu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@UjjawalPrabhat this is a good progress . Kindly make the following updates here:

  • Update SUMMARY_REPRESENTATION to include patientIdentifier.
  • Add logic to rename patientIdentifier to identifier and flatten the patient object.

Copy link
Copy Markdown

@jwnasambu jwnasambu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also create this file to include test cases for
getQueueEntrySummaries_shouldReturnPaginatedSummariesWithIdentifier,
shouldHandleTotalCountRequest, and shouldReturnEmptyListWhenNoResults. I know the PR originally focuses on DAO tests but without Controller tests there is
no verification that the final JSON keys like identifier or visitUuid are correctly
transformed for the frontend.

* {@code queueComingFrom}. Entries without a resolvable predecessor are absent from the result.
*/
@Authorized({ PrivilegeConstants.GET_QUEUE_ENTRIES })
Map<QueueEntry, String> getPreviousQueueEntryUuids(Collection<QueueEntry> entries);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kindly to follow the 3-layer architectural pattern (Controller -> Service -> DAO) mentioned in the PR description, please expose
this method getPreviousQueueEntryUuids in the Service interface to ensures that the logic is available to the REST
controller while maintaining proper security and transaction boundaries. please don't forget to add the @Authorized annotation for
PrivilegeConstants.GET_QUEUE_ENTRIES."

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is already in place, the method is declared on QueueEntryService at line 146 with @Authorized({ PrivilegeConstants.GET_QUEUE_ENTRIES }) on line 145. Let me know if you're seeing something different.

Comment on lines +228 to +230
public Map<QueueEntry, String> getPreviousQueueEntryUuids(Collection<QueueEntry> entries) {
return dao.getPreviousQueueEntryUuids(entries);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kindly provide the implementation for getPreviousQueueEntryUuids by delegating the call to the DAO layer. This maintains
consistency with the patterns used in emrapi and ensures the service layer remains the primary entry point for business logic.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The implementation at lines 228-230 delegates to the DAO - return dao.getPreviousQueueEntryUuids(entries); with @Override + @Transactional(readOnly = true) on the lines above. Happy to adjust if you'd like a different pattern.


QueueEntryService queueEntryService = services.getQueueEntryService();
List<QueueEntry> entries = queueEntryService.getQueueEntries(criteria, context.getStartIndex(), context.getLimit());
Map<QueueEntry, String> previousUuids = queueEntryService.getPreviousQueueEntryUuids(entries);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the controller, please ensure you are calling queueEntryService.getPreviousQueueEntryUuids() rather than trying to
access the DAO directly. This preserves the architectural integrity of the module.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The controller calls the service, not the DAO — queueEntryService.getPreviousQueueEntryUuids(entries) on line 71, where queueEntryService is obtained from services.getQueueEntryService() on line 69. There's no DAO reference anywhere in the controller.

@sonarqubecloud
Copy link
Copy Markdown

@UjjawalPrabhat UjjawalPrabhat requested a review from jwnasambu May 25, 2026 11:44
@chibongho
Copy link
Copy Markdown
Contributor

Hi @UjjawalPrabhat, some questions I have:

  • Are you sure that visit.encounter.obs and previousQueueEntry are pulled in recursively? Looking that this from the O3 service queues app, I don't think we are doing that.
  • I'm not surprised that the frontend is pulling more data than is needed. That said, can't we fix that by fixing the custom:() rep in the frontend?
  • You're right that previousQueueEntry is not that efficient. If /queueEntry?custom(previousQueueEntry) returns N queue entries, then for each N of them, we make one additional request for its previous queue entry. It looks like this will help reduce it to one query instead. That said, is it that a big performance improvement to warrant having a specialized endpoint?
    • preivousQueueEntry is unfortunately not well implemented, see code. The right way to do it would have been having an actual preivous_queue_entry column in the DB. That way, we could have added the following to QueueEntry.java, which would fix the performance issue:
        @BatchSize(size = 100) // this will automatically batch the queries for previousQueueEntries for a List<QueueEntry> returned by hibernate
        @Column(name = "previous_queue_entry")
        private QueueEntry previousQueueEntry;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants