Skip to content

perf(ui): lazy load entity detail components#29179

Open
shah-harshit wants to merge 1 commit into
feat/dashboard-lcp-parentfrom
feat/dashboard-lcp-03-entity-details
Open

perf(ui): lazy load entity detail components#29179
shah-harshit wants to merge 1 commit into
feat/dashboard-lcp-parentfrom
feat/dashboard-lcp-03-entity-details

Conversation

@shah-harshit

@shah-harshit shah-harshit commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Splits entity detail/page utilities and components so detail-heavy modules are not eagerly pulled into landing flows.
  • Split from the dashboard LCP optimization work so it can be reviewed independently.

Testing

  • Not run; tests will be fixed separately for this PR.

Ref: https://github.com/open-metadata/openmetadata-collate/issues/4230

Greptile Summary

This PR reduces startup bundle size by converting eager imports of heavy entity-detail page components and shared UI building blocks into React.lazy + withSuspenseFallback wrappers across a wide set of utility files. It also splits display-only helpers from EntityDisplayUtils.tsx into a new EntityDisplayPureUtils.tsx so lightweight consumers stop pulling in the heavier module graph.

  • Lazy registry for entity detail pages (EntityDetailComponentUtils.tsx): replaces the large switch-case in EntityUtilClassBase that eagerly imported every page component; each page is now code-split behind a Suspense boundary.
  • Pure utility split: getCountBadge, errorMsg, getEntityMissingError, and related helpers moved to EntityDisplayPureUtils.tsx; EntityDisplayUtils.tsx retains only getServiceLogo; all import sites updated.
  • Per-util-file lazification: TabsLabel, GenericTab, CommonWidgets, ActivityFeedTab, ErrorPlaceHolder, and others converted from static imports to lazy() wrappers in TableTabsUtils, DashboardDetailsUtils, MetricUtils, TopicDetailsUtils, APICollectionUtils, and APIEndpointUtils.
  • VersionButton extracted into its own file with a new summary prop so EntityVersionTimeLine.tsx no longer depends on getSummary/useUserProfile transitively; import sites and tests updated accordingly.

Confidence Score: 2/5

Not safe to merge — the UI build will fail as written.

TaskTabNew.component.tsx imports four utility modules (TaskNavigationUtils, TaskActionUtils, TaskAssigneeUtils, TaskPayloadUtils) that do not exist anywhere in the repository; all the symbols they are expected to export currently live in TasksUtils.ts. The TypeScript compiler will reject these module resolutions outright, preventing a successful build. Separately, EntityDetailComponentUtils allocates a new wrapper function on every call to getEntityDetailComponent, which causes React to treat the component as a new type on each render in EntityVersionPage (the one call site that lacks memoization), triggering full unmount/remount cycles for the default entity-version view.

TaskTabNew.component.tsx (missing utility file targets) and EntityDetailComponentUtils.tsx (unstable component references).

Important Files Changed

Filename Overview
openmetadata-ui/src/main/resources/ui/src/components/Entity/Task/TaskTab/TaskTabNew.component.tsx Imports from four non-existent utility files (TaskNavigationUtils, TaskActionUtils, TaskAssigneeUtils, TaskPayloadUtils), making this a build-breaking change.
openmetadata-ui/src/main/resources/ui/src/utils/EntityDetailComponentUtils.tsx New lazy registry for entity detail pages; creates a fresh WrappedComponent function on every call, causing component remounting when used without memoization (e.g., EntityVersionPage).
openmetadata-ui/src/main/resources/ui/src/utils/EntityDisplayPureUtils.tsx New file extracting pure display utilities (getCountBadge, errorMsg, getEntityMissingError, etc.) from EntityDisplayUtils.tsx to break heavy UI imports from lightweight consumers.
openmetadata-ui/src/main/resources/ui/src/utils/EntityDisplayUtils.tsx Trimmed down to only the service-logo utility; all pure display helpers moved to EntityDisplayPureUtils.tsx; import sites across the codebase updated correctly.
openmetadata-ui/src/main/resources/ui/src/utils/EntityUtilClassBase.ts getEntityDetailComponent switch-case removed and delegated to the new lazy registry; all page imports replaced with type-only imports to cut bundle weight.
openmetadata-ui/src/main/resources/ui/src/components/Entity/EntityVersionTimeLine/VersionButton.tsx VersionButton extracted from EntityVersionTimeLine.tsx into its own file; summary prop now passed from parent rather than computed inline; missing displayName on the forwardRef.
openmetadata-ui/src/main/resources/ui/src/utils/TableTabsUtils.tsx TabsLabel, ErrorPlaceHolder, GenericTab, CommonWidgets, QueryViewer, FrequentlyJoinedTables, and PartitionedKeys converted from eager imports to lazy/withSuspenseFallback wrappers.
openmetadata-ui/src/main/resources/ui/src/utils/DashboardDetailsUtils.tsx TabsLabel, GenericTab, CommonWidgets lazified; ChartType import source corrected to DashboardDetails.interface; no issues found.
openmetadata-ui/src/main/resources/ui/src/utils/EntityRightPanelClassBase.ts KnowledgePages component lazified with withSuspenseFallback; straightforward change with no issues.
openmetadata-ui/src/main/resources/ui/src/utils/EntityVersionUtils.tsx VersionButton import updated to new file location; summary prop now pre-computed at the renderVersionButton call site instead of inside VersionButton itself.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    ER[EntityRouter\nuseMemo] -->|calls| EUC[EntityUtilClassBase\ngetEntityDetailComponent]
    EVP[EntityVersionPage\nno memo] -->|calls| EVC[EntityVersionClassBase\ngetEntityDetailComponent]
    EVC --> EUC
    EUC -->|delegates| EDCU[EntityDetailComponentUtils\ngetEntityDetailComponent]
    EDCU -->|looks up| LCM[lazyComponentMap\nstable module-level map]
    LCM -->|lazy import| Pages["Detail Pages\n(DatabaseDetailsPage, TableDetailsPageV1, ...)"]
    EDCU -->|creates NEW fn each call| WC[WrappedComponent\nnew reference on every call]
    WC -->|wraps with| SB[Suspense boundary\nfallback=null]
    subgraph Split [EntityDisplayUtils split]
      EDU[EntityDisplayUtils\ngetServiceLogo only]
      EDPU[EntityDisplayPureUtils\ngetCountBadge, errorMsg, getEntityMissingError, ...]
    end
    subgraph LazyUtils [Lazified util files]
      TTU[TableTabsUtils]
      DDU[DashboardDetailsUtils]
      MU[MetricUtils]
      TDU[TopicDetailsUtils]
      ACU[APICollectionUtils]
      AEU[APIEndpointUtils]
    end
    LazyUtils -->|lazy + withSuspenseFallback| HeavyComponents[TabsLabel, GenericTab, CommonWidgets, ActivityFeedTab, ErrorPlaceHolder, ...]
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    ER[EntityRouter\nuseMemo] -->|calls| EUC[EntityUtilClassBase\ngetEntityDetailComponent]
    EVP[EntityVersionPage\nno memo] -->|calls| EVC[EntityVersionClassBase\ngetEntityDetailComponent]
    EVC --> EUC
    EUC -->|delegates| EDCU[EntityDetailComponentUtils\ngetEntityDetailComponent]
    EDCU -->|looks up| LCM[lazyComponentMap\nstable module-level map]
    LCM -->|lazy import| Pages["Detail Pages\n(DatabaseDetailsPage, TableDetailsPageV1, ...)"]
    EDCU -->|creates NEW fn each call| WC[WrappedComponent\nnew reference on every call]
    WC -->|wraps with| SB[Suspense boundary\nfallback=null]
    subgraph Split [EntityDisplayUtils split]
      EDU[EntityDisplayUtils\ngetServiceLogo only]
      EDPU[EntityDisplayPureUtils\ngetCountBadge, errorMsg, getEntityMissingError, ...]
    end
    subgraph LazyUtils [Lazified util files]
      TTU[TableTabsUtils]
      DDU[DashboardDetailsUtils]
      MU[MetricUtils]
      TDU[TopicDetailsUtils]
      ACU[APICollectionUtils]
      AEU[APIEndpointUtils]
    end
    LazyUtils -->|lazy + withSuspenseFallback| HeavyComponents[TabsLabel, GenericTab, CommonWidgets, ActivityFeedTab, ErrorPlaceHolder, ...]
Loading

Reviews (1): Last reviewed commit: "perf(ui): lazy load entity detail compon..." | Re-trigger Greptile

Greptile also left 2 inline comments on this PR.

@shah-harshit shah-harshit requested a review from a team as a code owner June 18, 2026 10:32
@shah-harshit shah-harshit added UI UI specific issues safe to test Add this label to run secure Github workflows on PRs skip-pr-checks Bypass PR metadata validation check labels Jun 18, 2026
@shah-harshit shah-harshit self-assigned this Jun 18, 2026
import { CurrentState } from 'Models';
import { lazy, ReactNode, Suspense } from 'react';
import { EntityType, FqnPart } from '../enums/entity.enum';
import { isNull } from 'lodash';

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: Unused isNull import in EntityDisplayUtils.tsx

EntityDisplayUtils.tsx adds import { isNull } from 'lodash'; but after the refactor the file only contains getServiceLogo, which does not use isNull. This unused import will trigger lint/build warnings (and the project's no-unused rules). Remove it if getServiceLogo does not reference isNull.

Drop the unused lodash import.:

// Remove the unused import if getServiceLogo does not use isNull
-import { isNull } from 'lodash';
  • Apply fix

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

@gitar-bot

gitar-bot Bot commented Jun 18, 2026

Copy link
Copy Markdown
Code Review 🚫 Blocked 0 resolved / 2 findings

Lazy loading implementation for entity detail components is blocked because the removal of multiple utility functions from EntityDisplayUtils breaks the build. Additionally, remove the unused isNull import in EntityDisplayUtils.tsx.

🚨 Bug: Moved functions still imported from EntityDisplayUtils break build

📄 openmetadata-ui/src/main/resources/ui/src/utils/EntityDisplayUtils.tsx

This PR removed getCountBadge, errorMsg, requiredField, getEntityMissingError, getEntityDeleteMessage, getEntityPlaceHolder, getLoadingStatus, and prepareLabel from utils/EntityDisplayUtils.tsx (which now only exports getServiceLogo) and relocated them to the new utils/EntityDisplayPureUtils.tsx. However, only a handful of callers were updated in this PR (e.g. APICollectionPage, MetricDetailsPage, FrequentlyJoinedTables, TabsLabel). Many other production modules still import these now-removed symbols from the old EntityDisplayUtils path, which will fail TypeScript compilation (module has no exported member ...) and break at runtime (undefined).

Confirmed stale imports of removed functions include:

  • getEntityMissingError: ServiceDetailsPage.tsx, SpreadsheetDetailsPage.tsx, DomainDetailPage.component.tsx, AddIngestionPage.component.tsx, GlossaryPage.component.tsx (plus test files)
  • getEntityDeleteMessage: GlossaryHeader.component.tsx, DataProductsDetailsPage.component.tsx
  • requiredField: ConnectionStepCard.tsx
  • getLoadingStatus: PipelineActions.tsx, PipelineActionsDropdown.tsx
  • getEntityPlaceHolder: FeedUtils.tsx
  • getCountBadge: AdvancedSearchUtils.tsx, UserTeamSelectableList.component.tsx

These must be repointed to EntityDisplayPureUtils. Note getServiceLogo importers (DataAssetCard, LineageTabContent, AddServicePage, etc.) are fine since that symbol stayed. The PR summary mentions only tests will be fixed separately, but the affected set includes production code, so this is a full build break, not just test breakage. Either update all remaining call sites or re-export the moved symbols from EntityDisplayUtils for backward compatibility.

Re-export the relocated functions from EntityDisplayUtils so existing import sites keep working (callers can be migrated later). Alternatively, update every stale import path to point at EntityDisplayPureUtils.
// In utils/EntityDisplayUtils.tsx, re-export moved helpers for backward compatibility:
export {
  getCountBadge,
  errorMsg,
  requiredField,
  getEntityMissingError,
  getEntityDeleteMessage,
  getEntityPlaceHolder,
  getLoadingStatus,
  prepareLabel,
} from './EntityDisplayPureUtils';
💡 Quality: Unused isNull import in EntityDisplayUtils.tsx

📄 openmetadata-ui/src/main/resources/ui/src/utils/EntityDisplayUtils.tsx:14

EntityDisplayUtils.tsx adds import { isNull } from 'lodash'; but after the refactor the file only contains getServiceLogo, which does not use isNull. This unused import will trigger lint/build warnings (and the project's no-unused rules). Remove it if getServiceLogo does not reference isNull.

Drop the unused lodash import.
// Remove the unused import if getServiceLogo does not use isNull
-import { isNull } from 'lodash';
🤖 Prompt for agents
Code Review: Lazy loading implementation for entity detail components is blocked because the removal of multiple utility functions from EntityDisplayUtils breaks the build. Additionally, remove the unused isNull import in EntityDisplayUtils.tsx.

1. 🚨 Bug: Moved functions still imported from EntityDisplayUtils break build
   Files: openmetadata-ui/src/main/resources/ui/src/utils/EntityDisplayUtils.tsx

   This PR removed `getCountBadge`, `errorMsg`, `requiredField`, `getEntityMissingError`, `getEntityDeleteMessage`, `getEntityPlaceHolder`, `getLoadingStatus`, and `prepareLabel` from `utils/EntityDisplayUtils.tsx` (which now only exports `getServiceLogo`) and relocated them to the new `utils/EntityDisplayPureUtils.tsx`. However, only a handful of callers were updated in this PR (e.g. APICollectionPage, MetricDetailsPage, FrequentlyJoinedTables, TabsLabel). Many other production modules still import these now-removed symbols from the old `EntityDisplayUtils` path, which will fail TypeScript compilation (`module has no exported member ...`) and break at runtime (`undefined`).
   
   Confirmed stale imports of removed functions include:
   - `getEntityMissingError`: ServiceDetailsPage.tsx, SpreadsheetDetailsPage.tsx, DomainDetailPage.component.tsx, AddIngestionPage.component.tsx, GlossaryPage.component.tsx (plus test files)
   - `getEntityDeleteMessage`: GlossaryHeader.component.tsx, DataProductsDetailsPage.component.tsx
   - `requiredField`: ConnectionStepCard.tsx
   - `getLoadingStatus`: PipelineActions.tsx, PipelineActionsDropdown.tsx
   - `getEntityPlaceHolder`: FeedUtils.tsx
   - `getCountBadge`: AdvancedSearchUtils.tsx, UserTeamSelectableList.component.tsx
   
   These must be repointed to `EntityDisplayPureUtils`. Note `getServiceLogo` importers (DataAssetCard, LineageTabContent, AddServicePage, etc.) are fine since that symbol stayed. The PR summary mentions only tests will be fixed separately, but the affected set includes production code, so this is a full build break, not just test breakage. Either update all remaining call sites or re-export the moved symbols from `EntityDisplayUtils` for backward compatibility.

   Fix (Re-export the relocated functions from EntityDisplayUtils so existing import sites keep working (callers can be migrated later). Alternatively, update every stale import path to point at EntityDisplayPureUtils.):
   // In utils/EntityDisplayUtils.tsx, re-export moved helpers for backward compatibility:
   export {
     getCountBadge,
     errorMsg,
     requiredField,
     getEntityMissingError,
     getEntityDeleteMessage,
     getEntityPlaceHolder,
     getLoadingStatus,
     prepareLabel,
   } from './EntityDisplayPureUtils';

2. 💡 Quality: Unused isNull import in EntityDisplayUtils.tsx
   Files: openmetadata-ui/src/main/resources/ui/src/utils/EntityDisplayUtils.tsx:14

   `EntityDisplayUtils.tsx` adds `import { isNull } from 'lodash';` but after the refactor the file only contains `getServiceLogo`, which does not use `isNull`. This unused import will trigger lint/build warnings (and the project's no-unused rules). Remove it if `getServiceLogo` does not reference `isNull`.

   Fix (Drop the unused lodash import.):
   // Remove the unused import if getServiceLogo does not use isNull
   -import { isNull } from 'lodash';

Options

Display: compact → Showing less information.

Comment with these commands to change:

Compact
gitar display:verbose         

Was this helpful? React with 👍 / 👎 | Gitar

Comment on lines 113 to +128
getTaskDetailPathFromTask,
getTaskDisplayId,
GLOSSARY_TASK_ACTION_LIST,
INCIDENT_TASK_ACTION_LIST,
isTaskPendingFurtherApproval,
isTaskTerminalStatus,
} from '../../../../utils/TaskNavigationUtils';
import {
GLOSSARY_TASK_ACTION_LIST,
INCIDENT_TASK_ACTION_LIST,
TASK_ACTION_COMMON_ITEM,
TASK_ACTION_LIST,
} from '../../../../utils/TasksUtils';
} from '../../../../utils/TaskActionUtils';
import {
fetchOptions,
generateOptions,
} from '../../../../utils/TaskAssigneeUtils';
import { getNormalizedTaskPayload } from '../../../../utils/TaskPayloadUtils';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P0 Imports reference non-existent utility files — build will fail

TaskTabNew.component.tsx imports from four utility modules (TaskNavigationUtils, TaskActionUtils, TaskAssigneeUtils, TaskPayloadUtils) that do not exist anywhere in the repository. All of the exported symbols being consumed (getTaskDetailPathFromTask, GLOSSARY_TASK_ACTION_LIST, fetchOptions, getNormalizedTaskPayload, etc.) currently live in TasksUtils.ts. The compiler will reject the module resolution immediately, so the entire UI build is broken by this change as written. These new utility files need to be created (or the imports reverted to TasksUtils) before this PR can be merged.

Comment on lines +26 to +33
export const VersionButton = forwardRef<
HTMLDivElement,
EntityVersionButtonProps
>(
(
{ version, onVersionSelect, selected, isMajorVersion, className, summary },
ref
) => {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Add a displayName to the extracted forwardRef component so it shows as VersionButton in React DevTools and error stack traces rather than the generic ForwardRef.

Suggested change
export const VersionButton = forwardRef<
HTMLDivElement,
EntityVersionButtonProps
>(
(
{ version, onVersionSelect, selected, isMajorVersion, className, summary },
ref
) => {
export const VersionButton = forwardRef<
HTMLDivElement,
EntityVersionButtonProps
>(
(
{ version, onVersionSelect, selected, isMajorVersion, className, summary },
ref
) => {
VersionButton.displayName = 'VersionButton';

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@shah-harshit shah-harshit changed the base branch from main to feat/dashboard-lcp-parent June 18, 2026 10:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

safe to test Add this label to run secure Github workflows on PRs skip-pr-checks Bypass PR metadata validation check UI UI specific issues

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant