Skip to content

Fixes #25878: listBots API does not return botUser field#27527

Open
kratipaliwal wants to merge 1 commit intoopen-metadata:mainfrom
kratipaliwal:ISSUE-25878
Open

Fixes #25878: listBots API does not return botUser field#27527
kratipaliwal wants to merge 1 commit intoopen-metadata:mainfrom
kratipaliwal:ISSUE-25878

Conversation

@kratipaliwal
Copy link
Copy Markdown

Describe your changes:

Fixes #25878

GET /v1/bots (list) was never returning the botUser field, even when callers explicitly asked for it with ?fields=botUser. Existing GET /v1/bots/{id} and GET /v1/bots/name/{name} continued to return botUser as before.

Two independent bugs produced this:

  1. BotResource did not expose a fields query parameter. list(), get(), and getByName() hardcoded "" for fields, so ?fields=botUser was silently ignored.
  2. BotRepository only populated botUser via setFields (single-entity path). The list endpoint uses setFieldsInBulkfetchAndSetFields, which dispatches through registered fieldFetchers. Combined with getFieldsStrippedFromStorageJson() stripping botUser from stored JSON, listed bots came back without it.

Fix

  • BotResource: added a FIELDS = "botUser" constant and a @QueryParam("fields") on list, get, and getByName, forwarded to the internal handlers (previously the param was silently dropped).
  • BotRepository: overrode setFieldsInBulk to batch-fetch botUser via relationshipDAO.findToBatch(..., CONTAINS, USER) when fields.contains("botUser"). Single-entity setFields remains unconditional to preserve backward compatibility for existing UI and SDK callers of GET /bots/{id} and GET /bots/name/{name}.

Final behavior

Endpoint ?fields not passed ?fields=botUser
GET /v1/bots/{id} botUser returned (unchanged) botUser returned
GET /v1/bots/name/{name} botUser returned (unchanged) botUser returned
GET /v1/bots (list) botUser omitted botUser returned

No breaking change for existing consumers of the single-entity GET endpoints. The list endpoint gains the missing ?fields=botUser support described in the Swagger docs.

Tests

Added regression integration tests in BotResourceIT covering the list, get, create, and patch paths:

  • test_listBots_withoutFieldsParam_omitsBotUser — list without ?fields does not populate botUser.
  • test_listBots_withFieldsParam_returnsBotUser — list with ?fields=botUser returns the relationship.
  • test_listBots_multipleBots_allHaveBotUser — exercises the bulk-fetch code path with multiple bots.
  • test_getBotById_alwaysReturnsBotUser / test_getBotByName_alwaysReturnsBotUser — backward-compat guards: single-entity GETs must continue to return botUser without ?fields.
  • test_getBotById_withFieldsParam_returnsBotUser / test_getBotByName_withFieldsParam_returnsBotUser — explicit ?fields=botUser still works on single-entity GETs.
  • test_createBot_responseIncludesBotUser — POST response continues to include botUser.
  • test_patchBotDescription_preservesBotUser — patching description does not drop the botUser relationship.
  • test_patchBot_cannotChangeBotUser — guards the immutability contract in restorePatchAttributes.

Type of change:

  • Bug fix

Checklist:

  • I have read the CONTRIBUTING document.
  • My PR title is Fixes <issue-number>: <short explanation>
  • I have commented on my code, particularly in hard-to-understand areas.
  • I have added a test that covers the exact scenario we are fixing. For complex issues, comment the issue number in the test for future reference.

@github-actions
Copy link
Copy Markdown
Contributor

Hi there 👋 Thanks for your contribution!

The OpenMetadata team will review the PR shortly! Once it has been labeled as safe to test, the CI workflows
will start executing and we'll be able to make sure everything is working as expected.

Let us know if you need any help!

Comment on lines +88 to +94
for (CollectionDAO.EntityRelationshipObject record : records) {
UUID botId = UUID.fromString(record.getFromId());
EntityReference userRef =
Entity.getEntityReferenceById(
Entity.USER, UUID.fromString(record.getToId()), Include.NON_DELETED);
botUserMap.put(botId, userRef);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Performance: batchFetchBotUsers still does N individual DB lookups per user

The batchFetchBotUsers method correctly fetches all bot→user relationships in one query via findToBatch, but then calls Entity.getEntityReferenceById(Entity.USER, ...) inside the loop for each record (line 91-92). This results in N individual database queries to resolve each user entity reference, partially defeating the purpose of the batch optimization.

For small bot counts this is fine, but for large deployments the list endpoint could issue hundreds of individual queries.

Consider collecting all toId UUIDs first, then using a bulk fetch (e.g., Entity.getEntityReferencesByIds or a single IN-clause query) to resolve all user references in one round-trip.

Suggested fix:

List<UUID> userIds = records.stream()
    .map(r -> UUID.fromString(r.getToId()))
    .collect(Collectors.toList());
Map<UUID, EntityReference> userRefs = Entity.getEntityReferencesByIds(Entity.USER, userIds, Include.NON_DELETED);
for (CollectionDAO.EntityRelationshipObject record : records) {
  UUID botId = UUID.fromString(record.getFromId());
  UUID userId = UUID.fromString(record.getToId());
  botUserMap.put(botId, userRefs.get(userId));
}

Was this helpful? React with 👍 / 👎 | Reply gitar fix to apply this suggestion

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@kratipaliwal can you please address this

@gitar-bot
Copy link
Copy Markdown

gitar-bot bot commented Apr 19, 2026

Code Review 👍 Approved with suggestions 0 resolved / 1 findings

Updates the listBots API to include the missing botUser field. Refactor batchFetchBotUsers to eliminate N+1 database lookups to optimize performance.

💡 Performance: batchFetchBotUsers still does N individual DB lookups per user

📄 openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/BotRepository.java:88-94

The batchFetchBotUsers method correctly fetches all bot→user relationships in one query via findToBatch, but then calls Entity.getEntityReferenceById(Entity.USER, ...) inside the loop for each record (line 91-92). This results in N individual database queries to resolve each user entity reference, partially defeating the purpose of the batch optimization.

For small bot counts this is fine, but for large deployments the list endpoint could issue hundreds of individual queries.

Consider collecting all toId UUIDs first, then using a bulk fetch (e.g., Entity.getEntityReferencesByIds or a single IN-clause query) to resolve all user references in one round-trip.

Suggested fix
List<UUID> userIds = records.stream()
    .map(r -> UUID.fromString(r.getToId()))
    .collect(Collectors.toList());
Map<UUID, EntityReference> userRefs = Entity.getEntityReferencesByIds(Entity.USER, userIds, Include.NON_DELETED);
for (CollectionDAO.EntityRelationshipObject record : records) {
  UUID botId = UUID.fromString(record.getFromId());
  UUID userId = UUID.fromString(record.getToId());
  botUserMap.put(botId, userRefs.get(userId));
}
🤖 Prompt for agents
Code Review: Updates the listBots API to include the missing botUser field. Refactor batchFetchBotUsers to eliminate N+1 database lookups to optimize performance.

1. 💡 Performance: batchFetchBotUsers still does N individual DB lookups per user
   Files: openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/BotRepository.java:88-94

   The `batchFetchBotUsers` method correctly fetches all bot→user relationships in one query via `findToBatch`, but then calls `Entity.getEntityReferenceById(Entity.USER, ...)` inside the loop for each record (line 91-92). This results in N individual database queries to resolve each user entity reference, partially defeating the purpose of the batch optimization.
   
   For small bot counts this is fine, but for large deployments the list endpoint could issue hundreds of individual queries.
   
   Consider collecting all `toId` UUIDs first, then using a bulk fetch (e.g., `Entity.getEntityReferencesByIds` or a single IN-clause query) to resolve all user references in one round-trip.

   Suggested fix:
   List<UUID> userIds = records.stream()
       .map(r -> UUID.fromString(r.getToId()))
       .collect(Collectors.toList());
   Map<UUID, EntityReference> userRefs = Entity.getEntityReferencesByIds(Entity.USER, userIds, Include.NON_DELETED);
   for (CollectionDAO.EntityRelationshipObject record : records) {
     UUID botId = UUID.fromString(record.getFromId());
     UUID userId = UUID.fromString(record.getToId());
     botUserMap.put(botId, userRefs.get(userId));
   }

Options

Display: compact → Showing less information.

Comment with these commands to change:

Compact
gitar display:verbose         

Was this helpful? React with 👍 / 👎 | Gitar

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.

listBots API does not return botUser field

2 participants