Skip to content

Remove n+1 fields types from Repositories#28150

Open
mohityadav766 wants to merge 6 commits into
mainfrom
fix-nplusone-listings
Open

Remove n+1 fields types from Repositories#28150
mohityadav766 wants to merge 6 commits into
mainfrom
fix-nplusone-listings

Conversation

@mohityadav766
Copy link
Copy Markdown
Member

@mohityadav766 mohityadav766 commented May 15, 2026

Describe your changes:

Fixes #

I worked on ... because ...

Type of change:

  • Bug fix
  • Improvement
  • New feature
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation

High-level design:

N/A — small change.

Tests:

Use cases covered

Unit tests

Backend integration tests

Ingestion integration tests

Playwright (UI) tests

Manual testing performed

UI screen recording / screenshots:

Not applicable.

Checklist:

  • I have read the CONTRIBUTING document.
  • My PR title is Fixes <issue-number>: <short explanation>
  • My PR is linked to a GitHub issue via Fixes #<issue-number> above.
  • I have commented on my code, particularly in hard-to-understand areas.
  • For JSON Schema changes: I updated the migration scripts or explained why it is not needed.
  • For UI changes: I attached a screen recording and/or screenshots above.
  • I have added tests (unit / integration / Playwright as applicable) and listed them above.

Summary by Gitar

  • API Optimization:
    • Removed N+1 field materialization for child entities (e.g., users, apiEndpoints, dashboards, charts) in parent repositories to prevent OOM errors.
    • Deprecated and removed bulk count fields (e.g., schemaCount, chartCount) in favor of paginated listing endpoints.
  • Refactored Repositories:
    • Updated Team, APICollection, Chart, and Dashboard repositories to return null for previously embedded child collections.
    • Enforced strict reliance on dedicated list endpoints (e.g., GET /v1/users?team={fqn}) for all child entity access.
  • Schema & UI Updates:
    • Updated JSON schemas to mark collection fields as deprecated and removed unnecessary count fields.
    • Refactored DashboardChartTable to fetch charts via getChartsByDashboard rather than reading from dashboard.charts.
  • Testing:
    • Updated integration tests to assert that child collections are never materialized on parent entities, even when explicitly requested.

This will update automatically on new commits.

@mohityadav766 mohityadav766 self-assigned this May 15, 2026
Copilot AI review requested due to automatic review settings May 15, 2026 13:27
@mohityadav766 mohityadav766 requested a review from a team as a code owner May 15, 2026 13:27
@github-actions github-actions Bot added backend safe to test Add this label to run secure Github workflows on PRs labels May 15, 2026
Comment on lines +6719 to +6733
private java.util.Set<String> expandableFields() {
Set<String> childFields = childCollectionFields();
if (childFields == null || childFields.isEmpty()) {
return allowedFields;
}
return allowedFields.stream()
.filter(f -> !childFields.contains(f))
.collect(java.util.stream.Collectors.toCollection(java.util.LinkedHashSet::new));
}

/**
* Repositories override to declare fields that represent unbounded child-entity collections
* (e.g. {@code tables} on {@code DatabaseSchema}, {@code apiEndpoints} on
* {@code APICollection}). These are excluded from `fields=*` expansion to prevent OOMs on
* parents with very large child counts. Returns an empty set by default.
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Quality: Fully qualified class names used despite existing imports

Multiple new methods use fully qualified class names (java.util.Set, java.util.Collections, java.util.stream.Collectors, java.util.LinkedHashSet) even though these are already imported in the respective files. This applies to expandableFields() and childCollectionFields() in EntityRepository.java, the overrides in ChartRepository, ServiceEntityRepository, TeamRepository, and the fetchChildRefsForIndexing method in VectorDocBuilder.java. Per project rules: 'No fully qualified names.'

Use the already-imported class names (Set, Collections, Collectors, LinkedHashSet) instead of fully qualified references.:

// In EntityRepository.java:
private Set<String> expandableFields() {
    Set<String> childFields = childCollectionFields();
    if (childFields == null || childFields.isEmpty()) {
      return allowedFields;
    }
    return allowedFields.stream()
        .filter(f -> !childFields.contains(f))
        .collect(Collectors.toCollection(LinkedHashSet::new));
}

protected Set<String> childCollectionFields() {
    return Collections.emptySet();
}
  • Apply fix

Check the box to apply the fix or reply for a change | Was this helpful? React with 👍 / 👎

Comment on lines +595 to +602
} catch (Exception ex) {
LOG.warn(
"Failed to fetch child refs for semantic indexing of {} {}: {}",
entity.getEntityReference() != null ? entity.getEntityReference().getType() : "?",
entity.getFullyQualifiedName(),
ex.getMessage());
return Collections.emptyList();
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Edge Case: VectorDocBuilder swallows all exceptions silently during indexing

The fetchChildRefsForIndexing method catches Exception broadly and returns an empty list with only a WARN log. If the repository or filter is misconfigured (e.g., a typo in parentFilterKey), the semantic indexing will silently produce incomplete embeddings for all affected entities with no clear signal to operators. This can degrade search quality without any visibility. At minimum, this should log at ERROR level for unexpected exceptions (not just entity-not-found scenarios), or rethrow non-recoverable errors.

Distinguish expected (entity not found) from unexpected exceptions, logging the latter at ERROR with full stack trace for operator visibility.:

} catch (EntityNotFoundException ex) {
  LOG.debug("No children found for {} {}: {}",
      entity.getEntityReference() != null ? entity.getEntityReference().getType() : "?",
      entity.getFullyQualifiedName(), ex.getMessage());
  return Collections.emptyList();
} catch (Exception ex) {
  LOG.error("Failed to fetch child refs for semantic indexing of {} {}",
      entity.getEntityReference() != null ? entity.getEntityReference().getType() : "?",
      entity.getFullyQualifiedName(), ex);
  return Collections.emptyList();
}
  • Apply fix

Check the box to apply the fix or reply for a change | Was this helpful? React with 👍 / 👎

@github-actions
Copy link
Copy Markdown
Contributor

✅ TypeScript Types Auto-Updated

The generated TypeScript types have been automatically updated based on JSON schema changes in this PR.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR aims to prevent OOMs and reduce unbounded payload materialization by deprecating embedded “child collection” fields on several entities (e.g., schemas on databases, tables on schemas, charts on dashboards) and replacing them with lightweight *Count fields plus paginated listing endpoints/filters.

Changes:

  • Updates JSON schemas to deprecate embedded child lists and introduces computed *Count fields (e.g., schemaCount, tableCount, chartCount, endpointCount).
  • Changes backend fields=* expansion to exclude repository-declared child-collection fields, adds new list filters (e.g., charts by dashboard), and updates repositories/resources to serve counts instead of embedded lists.
  • Updates/extends integration tests to assert wildcard fetches do not materialize large child collections.

Reviewed changes

Copilot reviewed 32 out of 38 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
openmetadata-ui/src/main/resources/ui/src/utils/DashboardDetailsUtils.tsx Updates dashboard default fields to request counts instead of embedded chart/dataModel lists.
openmetadata-ui/src/main/resources/ui/src/enums/entity.enum.ts Adds TabSpecificField enum entries for new *Count fields.
openmetadata-spec/src/main/resources/json/schema/entity/data/databaseSchema.json Deprecates tables[], adds tableCount.
openmetadata-spec/src/main/resources/json/schema/entity/data/database.json Deprecates databaseSchemas[], adds schemaCount.
openmetadata-spec/src/main/resources/json/schema/entity/data/dashboard.json Limits embedded lists to explicit requests and adds chartCount/dataModelCount.
openmetadata-spec/src/main/resources/json/schema/entity/data/container.json Deprecates children[], adds childrenCount.
openmetadata-spec/src/main/resources/json/schema/entity/data/chart.json Deprecates dashboards[], adds dashboardCount.
openmetadata-spec/src/main/resources/json/schema/entity/data/apiCollection.json Limits embedded endpoints to explicit requests and adds endpointCount.
openmetadata-service/src/main/java/org/openmetadata/service/search/vector/VectorDocBuilder.java Switches semantic child context to fetch children via repositories/filters rather than embedded lists.
openmetadata-service/src/main/java/org/openmetadata/service/resources/storages/ContainerResource.java Adds parent query param for container listing to support child pagination.
openmetadata-service/src/main/java/org/openmetadata/service/resources/domains/DomainResource.java Adds parent query param to domain listing.
openmetadata-service/src/main/java/org/openmetadata/service/resources/databases/DatabaseSchemaResource.java Replaces default FIELDS/view ops from tables to tableCount.
openmetadata-service/src/main/java/org/openmetadata/service/resources/databases/DatabaseResource.java Replaces default FIELDS/view ops from databaseSchemas to schemaCount.
openmetadata-service/src/main/java/org/openmetadata/service/resources/dashboards/DashboardResource.java Replaces default FIELDS/view ops from embedded lists to counts.
openmetadata-service/src/main/java/org/openmetadata/service/resources/charts/ChartResource.java Updates default FIELDS and adds dashboard filter for listing charts by dashboard.
openmetadata-service/src/main/java/org/openmetadata/service/resources/apis/APICollectionResource.java Replaces default FIELDS/view ops from apiEndpoints to endpointCount.
openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/TeamRepository.java Excludes users from fields=* expansion.
openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/ServiceEntityRepository.java Excludes pipelines from fields=* expansion.
openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/ListFilter.java Adds dashboard→charts filtering condition.
openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/EntityRepository.java Implements fields=* expansion control via childCollectionFields().
openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/DatabaseSchemaRepository.java Stops materializing tables[]; computes tableCount.
openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/DatabaseRepository.java Stops materializing databaseSchemas[]; computes schemaCount.
openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/DashboardRepository.java Excludes embedded lists from * and adds count computation.
openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/ContainerRepository.java Adds childrenCount and prevents children[] materialization.
openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/CollectionDAO.java Adds relationship count helpers used by new count fields.
openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/ChartRepository.java Stops materializing dashboards[]; computes dashboardCount.
openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/APICollectionRepository.java Adds endpointCount computation and excludes endpoints from fields=*.
openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/DatabaseSchemaResourceIT.java Updates bulk-fetch tests and adds wildcard regression test for tables[] OOM.
openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/DatabaseResourceIT.java Adds wildcard regression test for databaseSchemas[] OOM.
openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/DashboardResourceIT.java Adds wildcard regression test to exclude embedded lists and validate counts.
openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/ChartResourceIT.java Adds wildcard regression test for excluding dashboards[] and validating dashboardCount.
openmetadata-integration-tests/src/test/java/org/openmetadata/it/tests/APICollectionResourceIT.java Adds wildcard regression test for excluding apiEndpoints[] and validating endpointCount.
Comments suppressed due to low confidence (2)

openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/APICollectionRepository.java:171

  • endpointCount is only set in setFields(...), but list responses go through setFieldsInBulk(...)/fetchAndSetFields(...). Since this repository does not register a bulk field fetcher for endpointCount, GET /v1/apiCollections?fields=endpointCount (and the default FIELDS value) will return null counts. Add a fieldFetchers.put("endpointCount", ...) implementation (ideally using a batched relationship count) or override setFieldsInBulk to populate it.
  @Override
  public void setFieldsInBulk(Fields fields, List<APICollection> entities) {
    if (entities == null || entities.isEmpty()) {
      return;
    }
    // Bulk fetch and set service for all API collections first
    fetchAndSetServices(entities);

    // Then call parent's implementation which handles standard fields
    super.setFieldsInBulk(fields, entities);
  }

openmetadata-service/src/main/java/org/openmetadata/service/search/vector/VectorDocBuilder.java:588

  • fetchChildRefsForIndexing calls repo.listAfter(...), which always computes a listCount(...) total before fetching the page. On the indexing path this adds an extra COUNT query per parent entity (and pollutes ListCountCache) even though you only need up to MAX_CHILD_NAMES_IN_CONTEXT refs. Consider using the DAO's listAfter(...) directly (no total) or adding a lightweight repository method that skips the total-count calculation for this indexing use case.
      org.openmetadata.service.jdbi3.EntityRepository<?> repo =
          Entity.getEntityRepository(spec.childEntityType());
      org.openmetadata.service.jdbi3.ListFilter filter =
          new org.openmetadata.service.jdbi3.ListFilter(
                  org.openmetadata.schema.type.Include.NON_DELETED)
              .addQueryParam(spec.parentFilterKey(), entity.getFullyQualifiedName());
      var page = repo.listAfter(null, repo.getFields(""), filter, MAX_CHILD_NAMES_IN_CONTEXT, null);
      List<EntityReference> refs = new ArrayList<>(page.getData().size());


// eslint-disable-next-line max-len
export const defaultFields = `${TabSpecificField.DOMAINS},${TabSpecificField.OWNERS}, ${TabSpecificField.FOLLOWERS}, ${TabSpecificField.TAGS}, ${TabSpecificField.CHARTS},${TabSpecificField.VOTES},${TabSpecificField.DATA_PRODUCTS},${TabSpecificField.EXTENSION}`;
export const defaultFields = `${TabSpecificField.DOMAINS},${TabSpecificField.OWNERS},${TabSpecificField.FOLLOWERS},${TabSpecificField.TAGS},${TabSpecificField.CHART_COUNT},${TabSpecificField.DATA_MODEL_COUNT},${TabSpecificField.VOTES},${TabSpecificField.DATA_PRODUCTS},${TabSpecificField.EXTENSION}`;
String fieldsParam,
@Parameter(
description =
"Filter Domains by parent Domain FQN. Returns only direct sub-domains of the given parent. Omit to list all root-level domains plus their descendants (the legacy behavior).",
Comment on lines +64 to +66
* direct children via the child DAO, identified by {@code childEntityType} filtered by
* {@code parentFilterKey} = parent's FQN. {@code DATA_PRODUCT} assets remain on the entity
* (many-to-many, bounded) and use {@code embeddedGetter}.
Comment on lines +83 to +97
"chartCount": {
"description": "Number of charts linked to this dashboard. Computed on demand when `chartCount` is requested in `fields`.",
"type": "integer",
"default": null
},
"dataModels": {
"description": "List of data models used by this dashboard or the charts contained on it.",
"description": "Dashboard data models on this Dashboard. Populated only when `fields=dataModels` is explicitly requested. Excluded from `fields=*` expansion.",
"$ref": "../../type/entityReferenceList.json",
"default": null
},
"dataModelCount": {
"description": "Number of dashboard data models linked to this dashboard. Computed on demand when `dataModelCount` is requested in `fields`.",
"type": "integer",
"default": null
},
Comment on lines +194 to +210
private void fetchAndSetChartCounts(List<Dashboard> dashboards, Fields fields) {
if (!fields.contains("chartCount") || dashboards == null || dashboards.isEmpty()) {
return;
}
for (Dashboard dashboard : dashboards) {
dashboard.setChartCount(getChartCount(dashboard));
}
}

private void fetchAndSetDataModelCounts(List<Dashboard> dashboards, Fields fields) {
if (!fields.contains("dataModelCount") || dashboards == null || dashboards.isEmpty()) {
return;
}
for (Dashboard dashboard : dashboards) {
dashboard.setDataModelCount(getDataModelCount(dashboard));
}
}
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 15, 2026

🔴 Playwright Results — 19 failure(s), 12 flaky

✅ 4096 passed · ❌ 19 failed · 🟡 12 flaky · ⏭️ 107 skipped

Shard Passed Failed Flaky Skipped
🔴 Shard 1 298 1 0 4
🟡 Shard 2 777 0 4 8
🔴 Shard 3 798 1 2 10
✅ Shard 4 752 0 0 12
🟡 Shard 5 771 0 2 47
🔴 Shard 6 700 17 4 26

Genuine Failures (failed on all attempts)

Features/Dashboards.spec.ts › should be able to toggle between deleted and non-deleted charts (shard 1)
Error: �[2mexpect(�[22m�[31mlocator�[39m�[2m).not.�[22mtoBeVisible�[2m(�[22m�[2m)�[22m failed

Locator:  getByTestId('charts-table').getByTestId('no-data-placeholder')
Expected: not visible
Received: visible
Timeout:  15000ms

Call log:
�[2m  - Expect "not toBeVisible" with timeout 15000ms�[22m
�[2m  - waiting for getByTestId('charts-table').getByTestId('no-data-placeholder')�[22m
�[2m    18 × locator resolved to <div data-testid="no-data-placeholder" class="border-none mt-0-important flex-center flex-col w-full h-full">…</div>�[22m
�[2m       - unexpected value "visible"�[22m

Flow/PersonaFlow.spec.ts › Set default persona for team should work properly (shard 3)
�[31mTest timeout of 180000ms exceeded.�[39m
Pages/Glossary.spec.ts › Glossary & terms creation for reviewer as team (shard 6)
Error: To get the last run execution status as success

�[2mexpect(�[22m�[31mreceived�[39m�[2m).�[22mtoContain�[2m(�[22m�[32mexpected�[39m�[2m) // indexOf�[22m

Expected value: �[32m"PW.536c1473%Falcon384dfe73"�[39m
Received array: �[31m[]�[39m

Call Log:
- Test timeout of 180000ms exceeded
Pages/Lineage/DataAssetLineage.spec.ts › Column lineage for table -> dashboard (shard 6)
Error: �[2mexpect(�[22m�[31mreceived�[39m�[2m).�[22mtoBeDefined�[2m()�[22m

Received: �[31mundefined�[39m
Pages/Lineage/DataAssetLineage.spec.ts › Column lineage for container -> dashboard (shard 6)
Error: �[2mexpect(�[22m�[31mreceived�[39m�[2m).�[22mtoBeDefined�[2m()�[22m

Received: �[31mundefined�[39m
Pages/Lineage/DataAssetLineage.spec.ts › Column lineage for topic -> dashboard (shard 6)
Error: �[2mexpect(�[22m�[31mreceived�[39m�[2m).�[22mtoBeDefined�[2m()�[22m

Received: �[31mundefined�[39m
Pages/Lineage/DataAssetLineage.spec.ts › Column lineage for apiEndpoint -> dashboard (shard 6)
Error: �[2mexpect(�[22m�[31mreceived�[39m�[2m).�[22mtoBeDefined�[2m()�[22m

Received: �[31mundefined�[39m
Pages/Lineage/DataAssetLineage.spec.ts › Column lineage for dashboard -> table (shard 6)
Error: �[2mexpect(�[22m�[31mreceived�[39m�[2m).�[22mtoBeDefined�[2m()�[22m

Received: �[31mundefined�[39m
Pages/Lineage/DataAssetLineage.spec.ts › Column lineage for dashboard -> container (shard 6)
Error: �[2mexpect(�[22m�[31mreceived�[39m�[2m).�[22mtoBeDefined�[2m()�[22m

Received: �[31mundefined�[39m
Pages/Lineage/DataAssetLineage.spec.ts › Column lineage for dashboard -> topic (shard 6)
Error: �[2mexpect(�[22m�[31mreceived�[39m�[2m).�[22mtoBeDefined�[2m()�[22m

Received: �[31mundefined�[39m
Pages/Lineage/DataAssetLineage.spec.ts › Column lineage for dashboard -> apiEndpoint (shard 6)
Error: �[2mexpect(�[22m�[31mreceived�[39m�[2m).�[22mtoBeDefined�[2m()�[22m

Received: �[31mundefined�[39m
Pages/Lineage/DataAssetLineage.spec.ts › Column lineage for dashboard -> dashboard (shard 6)
Error: �[2mexpect(�[22m�[31mreceived�[39m�[2m).�[22mtoBeDefined�[2m()�[22m

Received: �[31mundefined�[39m
Pages/Lineage/DataAssetLineage.spec.ts › Column lineage for dashboard -> dashboardDataModel (shard 6)
Error: �[2mexpect(�[22m�[31mreceived�[39m�[2m).�[22mtoBeDefined�[2m()�[22m

Received: �[31mundefined�[39m
Pages/Lineage/DataAssetLineage.spec.ts › Column lineage for dashboard -> searchIndex (shard 6)
Error: �[2mexpect(�[22m�[31mreceived�[39m�[2m).�[22mtoBeDefined�[2m()�[22m

Received: �[31mundefined�[39m
Pages/Lineage/DataAssetLineage.spec.ts › Column lineage for dashboard -> mlModel (shard 6)
Error: �[2mexpect(�[22m�[31mreceived�[39m�[2m).�[22mtoBeDefined�[2m()�[22m

Received: �[31mundefined�[39m
Pages/Lineage/DataAssetLineage.spec.ts › Column lineage for dashboardDataModel -> dashboard (shard 6)
Error: �[2mexpect(�[22m�[31mreceived�[39m�[2m).�[22mtoBeDefined�[2m()�[22m

Received: �[31mundefined�[39m
Pages/Lineage/DataAssetLineage.spec.ts › Column lineage for searchIndex -> dashboard (shard 6)
Error: �[2mexpect(�[22m�[31mreceived�[39m�[2m).�[22mtoBeDefined�[2m()�[22m

Received: �[31mundefined�[39m
Pages/Lineage/DataAssetLineage.spec.ts › Column lineage for mlModel -> dashboard (shard 6)
Error: �[2mexpect(�[22m�[31mreceived�[39m�[2m).�[22mtoBeDefined�[2m()�[22m

Received: �[31mundefined�[39m
Pages/Teams.spec.ts › Teams Page Flow (shard 6)
�[31mTest timeout of 180000ms exceeded.�[39m
🟡 12 flaky test(s) (passed on retry)
  • Features/BulkEditEntity.spec.ts › Glossary (shard 2, 1 retry)
  • Features/Glossary/GlossaryWorkflow.spec.ts › should start term as Draft when glossary has reviewers (shard 2, 2 retries)
  • Features/KnowledgeCenter.spec.ts › Article mentions in description should working for Knowledge Center (shard 2, 1 retry)
  • Features/KnowledgeCenterTextEditor.spec.ts › Rich Text Editor - Advanced Blocks (shard 2, 1 retry)
  • Features/RTL.spec.ts › Verify Following widget functionality (shard 3, 1 retry)
  • Flow/CustomizeWidgets.spec.ts › My Data Widget (shard 3, 1 retry)
  • Pages/Entity.spec.ts › Tag and Glossary Selector should close vice versa (shard 5, 1 retry)
  • Pages/ExplorePageRightPanel_KnowledgeCenter.spec.ts › Should remove user owner for knowledgeCenter (shard 5, 2 retries)
  • Pages/GlossaryImportExport.spec.ts › Glossary CSV import preserves typed relations (shard 6, 1 retry)
  • Pages/InputOutputPorts.spec.ts › Lineage section collapse/expand (shard 6, 1 retry)
  • Pages/Lineage/LineageFilters.spec.ts › Verify Impact Analysis service filter selection (shard 6, 1 retry)
  • Pages/Lineage/LineageFilters.spec.ts › Verify lineage schema filter selection (shard 6, 1 retry)

📦 Download artifacts

How to debug locally
# Download playwright-test-results-<shard> artifact and unzip
npx playwright show-trace path/to/trace.zip    # view trace

Copilot AI review requested due to automatic review settings May 17, 2026 11:25
Comment on lines 129 to +143
const initializeCharts = useCallback(async () => {
if (!dashboardDetails?.fullyQualifiedName) {
return;
}
try {
const res = await fetchCharts(
listChartIds,
chartFilters.showDeletedCharts
// Charts are no longer embedded on the dashboard entity — fetch via the dedicated
// listing endpoint filtered by dashboard FQN. The full chart objects (incl. tags)
// are returned in one paginated call instead of N individual lookups.
const res = await getChartsByDashboard(
dashboardDetails.fullyQualifiedName,
TabSpecificField.TAGS,
undefined,
chartFilters.showDeletedCharts ? Include.Deleted : Include.NonDeleted
);
setCharts(res);
setCharts((res.data as unknown as ChartType[]) ?? []);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🚨 Bug: DashboardChartTable only loads first 10 charts, no pagination

The initializeCharts function calls getChartsByDashboard with the default limit = PAGE_SIZE (10) and never fetches subsequent pages. The Table component is rendered with pagination={false}. Previously, the component received all chart references from the embedded dashboard.charts list and fetched each one individually (N+1 but complete). Now, dashboards with more than 10 charts will silently truncate the chart table to only 10 entries.

This is a user-visible data loss regression — any dashboard with >10 charts will appear incomplete.

Paginate through all chart pages using the after cursor returned by the API, with a larger per-page limit to reduce round-trips.:

const initializeCharts = useCallback(async () => {
  if (!dashboardDetails?.fullyQualifiedName) {
    return;
  }
  try {
    let allCharts: ChartType[] = [];
    let after: string | undefined;
    do {
      const res = await getChartsByDashboard(
        dashboardDetails.fullyQualifiedName,
        TabSpecificField.TAGS,
        after ? { after } : undefined,
        chartFilters.showDeletedCharts ? Include.Deleted : Include.NonDeleted,
        100
      );
      allCharts = [...allCharts, ...(res.data as unknown as ChartType[])];
      after = res.paging?.after;
    } while (after);
    setCharts(allCharts);
  } catch (error) {
    showErrorToast(error as AxiosError, ...);
  }
}, [dashboardDetails?.fullyQualifiedName, chartFilters.showDeletedCharts]);
  • Apply fix

Check the box to apply the fix or reply for a change | Was this helpful? React with 👍 / 👎

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 17, 2026

❌ UI Checkstyle Failed

❌ ESLint + Prettier + Organise Imports (src)

One or more source files have linting or formatting issues.

Affected files
  • openmetadata-ui/src/main/resources/ui/src/components/Dashboard/DashboardChartTable/DashboardChartTable.tsx

Fix locally (fast — only checks files changed in this branch):

make ui-checkstyle-changed

@github-actions
Copy link
Copy Markdown
Contributor

✅ TypeScript Types Auto-Updated

The generated TypeScript types have been automatically updated based on JSON schema changes in this PR.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 33 out of 39 changed files in this pull request and generated 4 comments.

Comment on lines +137 to 144
const res = await getChartsByDashboard(
dashboardDetails.fullyQualifiedName,
TabSpecificField.TAGS,
undefined,
chartFilters.showDeletedCharts ? Include.Deleted : Include.NonDeleted
);
setCharts(res);
setCharts((res.data as unknown as ChartType[]) ?? []);
} catch (error) {
Comment on lines 44 to 46
// eslint-disable-next-line max-len
export const defaultFields = `${TabSpecificField.DOMAINS},${TabSpecificField.OWNERS}, ${TabSpecificField.FOLLOWERS}, ${TabSpecificField.TAGS}, ${TabSpecificField.CHARTS},${TabSpecificField.VOTES},${TabSpecificField.DATA_PRODUCTS},${TabSpecificField.EXTENSION}`;
export const defaultFields = `${TabSpecificField.DOMAINS},${TabSpecificField.OWNERS},${TabSpecificField.FOLLOWERS},${TabSpecificField.TAGS},${TabSpecificField.VOTES},${TabSpecificField.DATA_PRODUCTS},${TabSpecificField.EXTENSION}`;

Comment on lines 131 to +156
@@ -143,8 +149,11 @@ public ResultList<Domain> list(
schema = @Schema(type = "string"))
@QueryParam("after")
String after) {
return listInternal(
uriInfo, securityContext, fieldsParam, new ListFilter(null), limitParam, before, after);
ListFilter filter = new ListFilter(null);
if (parent != null && !parent.isBlank()) {
filter.addQueryParam("parent", parent);
}
return listInternal(uriInfo, securityContext, fieldsParam, filter, limitParam, before, after);
Comment on lines +64 to +65
* direct children via the child DAO, identified by {@code childEntityType} filtered by
* {@code parentFilterKey} = parent's FQN. {@code DATA_PRODUCT} assets remain on the entity
@gitar-bot
Copy link
Copy Markdown

gitar-bot Bot commented May 18, 2026

Code Review 🚫 Blocked 1 resolved / 4 findings

Refactors repositories to remove N+1 field materialization and bulk counts, addressing inefficient data loading. However, the implementation is blocked by a critical pagination defect in DashboardChartTable and unchecked exception handling in VectorDocBuilder.

🚨 Bug: DashboardChartTable only loads first 10 charts, no pagination

📄 openmetadata-ui/src/main/resources/ui/src/components/Dashboard/DashboardChartTable/DashboardChartTable.tsx:129-143 📄 openmetadata-ui/src/main/resources/ui/src/rest/chartsAPI.ts:86-100

The initializeCharts function calls getChartsByDashboard with the default limit = PAGE_SIZE (10) and never fetches subsequent pages. The Table component is rendered with pagination={false}. Previously, the component received all chart references from the embedded dashboard.charts list and fetched each one individually (N+1 but complete). Now, dashboards with more than 10 charts will silently truncate the chart table to only 10 entries.

This is a user-visible data loss regression — any dashboard with >10 charts will appear incomplete.

Paginate through all chart pages using the `after` cursor returned by the API, with a larger per-page limit to reduce round-trips.
const initializeCharts = useCallback(async () => {
  if (!dashboardDetails?.fullyQualifiedName) {
    return;
  }
  try {
    let allCharts: ChartType[] = [];
    let after: string | undefined;
    do {
      const res = await getChartsByDashboard(
        dashboardDetails.fullyQualifiedName,
        TabSpecificField.TAGS,
        after ? { after } : undefined,
        chartFilters.showDeletedCharts ? Include.Deleted : Include.NonDeleted,
        100
      );
      allCharts = [...allCharts, ...(res.data as unknown as ChartType[])];
      after = res.paging?.after;
    } while (after);
    setCharts(allCharts);
  } catch (error) {
    showErrorToast(error as AxiosError, ...);
  }
}, [dashboardDetails?.fullyQualifiedName, chartFilters.showDeletedCharts]);
⚠️ Edge Case: VectorDocBuilder swallows all exceptions silently during indexing

📄 openmetadata-service/src/main/java/org/openmetadata/service/search/vector/VectorDocBuilder.java:595-602

The fetchChildRefsForIndexing method catches Exception broadly and returns an empty list with only a WARN log. If the repository or filter is misconfigured (e.g., a typo in parentFilterKey), the semantic indexing will silently produce incomplete embeddings for all affected entities with no clear signal to operators. This can degrade search quality without any visibility. At minimum, this should log at ERROR level for unexpected exceptions (not just entity-not-found scenarios), or rethrow non-recoverable errors.

Distinguish expected (entity not found) from unexpected exceptions, logging the latter at ERROR with full stack trace for operator visibility.
} catch (EntityNotFoundException ex) {
  LOG.debug("No children found for {} {}: {}",
      entity.getEntityReference() != null ? entity.getEntityReference().getType() : "?",
      entity.getFullyQualifiedName(), ex.getMessage());
  return Collections.emptyList();
} catch (Exception ex) {
  LOG.error("Failed to fetch child refs for semantic indexing of {} {}",
      entity.getEntityReference() != null ? entity.getEntityReference().getType() : "?",
      entity.getFullyQualifiedName(), ex);
  return Collections.emptyList();
}
💡 Quality: Fully qualified class names used despite existing imports

📄 openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/EntityRepository.java:6719-6733 📄 openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/ChartRepository.java:85-86 📄 openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/ServiceEntityRepository.java:96-97 📄 openmetadata-service/src/main/java/org/openmetadata/service/search/vector/VectorDocBuilder.java:581-586

Multiple new methods use fully qualified class names (java.util.Set, java.util.Collections, java.util.stream.Collectors, java.util.LinkedHashSet) even though these are already imported in the respective files. This applies to expandableFields() and childCollectionFields() in EntityRepository.java, the overrides in ChartRepository, ServiceEntityRepository, TeamRepository, and the fetchChildRefsForIndexing method in VectorDocBuilder.java. Per project rules: 'No fully qualified names.'

Use the already-imported class names (Set, Collections, Collectors, LinkedHashSet) instead of fully qualified references.
// In EntityRepository.java:
private Set<String> expandableFields() {
    Set<String> childFields = childCollectionFields();
    if (childFields == null || childFields.isEmpty()) {
      return allowedFields;
    }
    return allowedFields.stream()
        .filter(f -> !childFields.contains(f))
        .collect(Collectors.toCollection(LinkedHashSet::new));
}

protected Set<String> childCollectionFields() {
    return Collections.emptySet();
}
✅ 1 resolved
Performance: N+1 queries in bulk count fetchers defeat the purpose of batching

📄 openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/ChartRepository.java:166-173
The fetchAndSetDashboardCounts method (and similar fetchAndSetChartCounts, fetchAndSetSchemaCounts in other repositories) is registered as a bulk field fetcher via fieldFetchers, but it simply loops through each entity and issues an individual COUNT query per entity. This is the exact N+1 pattern the PR title claims to fix. For a listing of 100 charts, this fires 100 separate COUNT queries.

A proper fix would use a single batch query (e.g., GROUP BY toId or GROUP BY fromId) to fetch all counts in one round-trip, similar to how batchFetchDashboards previously batched relationship lookups.

🤖 Prompt for agents
Code Review: Refactors repositories to remove N+1 field materialization and bulk counts, addressing inefficient data loading. However, the implementation is blocked by a critical pagination defect in `DashboardChartTable` and unchecked exception handling in `VectorDocBuilder`.

1. 💡 Quality: Fully qualified class names used despite existing imports
   Files: openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/EntityRepository.java:6719-6733, openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/ChartRepository.java:85-86, openmetadata-service/src/main/java/org/openmetadata/service/jdbi3/ServiceEntityRepository.java:96-97, openmetadata-service/src/main/java/org/openmetadata/service/search/vector/VectorDocBuilder.java:581-586

   Multiple new methods use fully qualified class names (`java.util.Set`, `java.util.Collections`, `java.util.stream.Collectors`, `java.util.LinkedHashSet`) even though these are already imported in the respective files. This applies to `expandableFields()` and `childCollectionFields()` in EntityRepository.java, the overrides in ChartRepository, ServiceEntityRepository, TeamRepository, and the `fetchChildRefsForIndexing` method in VectorDocBuilder.java. Per project rules: 'No fully qualified names.'

   Fix (Use the already-imported class names (Set, Collections, Collectors, LinkedHashSet) instead of fully qualified references.):
   // In EntityRepository.java:
   private Set<String> expandableFields() {
       Set<String> childFields = childCollectionFields();
       if (childFields == null || childFields.isEmpty()) {
         return allowedFields;
       }
       return allowedFields.stream()
           .filter(f -> !childFields.contains(f))
           .collect(Collectors.toCollection(LinkedHashSet::new));
   }
   
   protected Set<String> childCollectionFields() {
       return Collections.emptySet();
   }

2. ⚠️ Edge Case: VectorDocBuilder swallows all exceptions silently during indexing
   Files: openmetadata-service/src/main/java/org/openmetadata/service/search/vector/VectorDocBuilder.java:595-602

   The `fetchChildRefsForIndexing` method catches `Exception` broadly and returns an empty list with only a WARN log. If the repository or filter is misconfigured (e.g., a typo in `parentFilterKey`), the semantic indexing will silently produce incomplete embeddings for all affected entities with no clear signal to operators. This can degrade search quality without any visibility. At minimum, this should log at ERROR level for unexpected exceptions (not just entity-not-found scenarios), or rethrow non-recoverable errors.

   Fix (Distinguish expected (entity not found) from unexpected exceptions, logging the latter at ERROR with full stack trace for operator visibility.):
   } catch (EntityNotFoundException ex) {
     LOG.debug("No children found for {} {}: {}",
         entity.getEntityReference() != null ? entity.getEntityReference().getType() : "?",
         entity.getFullyQualifiedName(), ex.getMessage());
     return Collections.emptyList();
   } catch (Exception ex) {
     LOG.error("Failed to fetch child refs for semantic indexing of {} {}",
         entity.getEntityReference() != null ? entity.getEntityReference().getType() : "?",
         entity.getFullyQualifiedName(), ex);
     return Collections.emptyList();
   }

3. 🚨 Bug: DashboardChartTable only loads first 10 charts, no pagination
   Files: openmetadata-ui/src/main/resources/ui/src/components/Dashboard/DashboardChartTable/DashboardChartTable.tsx:129-143, openmetadata-ui/src/main/resources/ui/src/rest/chartsAPI.ts:86-100

   The `initializeCharts` function calls `getChartsByDashboard` with the default `limit = PAGE_SIZE` (10) and never fetches subsequent pages. The `Table` component is rendered with `pagination={false}`. Previously, the component received all chart references from the embedded `dashboard.charts` list and fetched each one individually (N+1 but complete). Now, dashboards with more than 10 charts will silently truncate the chart table to only 10 entries.
   
   This is a user-visible data loss regression — any dashboard with >10 charts will appear incomplete.

   Fix (Paginate through all chart pages using the `after` cursor returned by the API, with a larger per-page limit to reduce round-trips.):
   const initializeCharts = useCallback(async () => {
     if (!dashboardDetails?.fullyQualifiedName) {
       return;
     }
     try {
       let allCharts: ChartType[] = [];
       let after: string | undefined;
       do {
         const res = await getChartsByDashboard(
           dashboardDetails.fullyQualifiedName,
           TabSpecificField.TAGS,
           after ? { after } : undefined,
           chartFilters.showDeletedCharts ? Include.Deleted : Include.NonDeleted,
           100
         );
         allCharts = [...allCharts, ...(res.data as unknown as ChartType[])];
         after = res.paging?.after;
       } while (after);
       setCharts(allCharts);
     } catch (error) {
       showErrorToast(error as AxiosError, ...);
     }
   }, [dashboardDetails?.fullyQualifiedName, chartFilters.showDeletedCharts]);

Options

Display: compact → Showing less information.

Comment with these commands to change:

Compact
gitar display:verbose         

Was this helpful? React with 👍 / 👎 | Gitar

@sonarqubecloud
Copy link
Copy Markdown

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

Labels

backend safe to test Add this label to run secure Github workflows on PRs

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants