Skip to content

feat(ui-perf): React Query scaffolding + lazy-extension rollout across 9 entity-detail pages#28017

Closed
harshach wants to merge 7 commits into
mainfrom
harshach/perceived-latency-p3-fields-rq
Closed

feat(ui-perf): React Query scaffolding + lazy-extension rollout across 9 entity-detail pages#28017
harshach wants to merge 7 commits into
mainfrom
harshach/perceived-latency-p3-fields-rq

Conversation

@harshach

@harshach harshach commented May 10, 2026

Copy link
Copy Markdown
Collaborator

Describe your changes:

Completes P3.3 (per-page fields= trim) and P3.1 (React Query foundation) on the perceived-latency design doc. Goes beyond the initial pilot — the lazy-extension pattern is now applied across all 9 entity-detail pages that requested EXTENSION eagerly, and a reusable hook captures the pattern for follow-up migrations.

P3.1 — React Query infrastructure

  • @tanstack/react-query dep added (codebase had zero query libraries before).
  • New src/queryClient.ts — single shared QueryClient with sensible defaults: staleTime: 30s, gcTime: 5min, refetchOnWindowFocus: true, retry: 1.
  • App.tsx wraps AuthProvider + AppRouter in QueryClientProvider (outside AuthProvider so the cache survives logout/login transitions).

P3.1 — Reusable lazy-fetch hook

  • New src/hooks/useLazyEntityExtension.ts — generic over entity shape, takes (entityType, fqn, activeTab, fetcher, onResolve). Internally:
    • useQuery gated on activeTab === EntityTabs.CUSTOM_PROPERTIES && Boolean(fqn)
    • 60s staleTime — custom-property values change rarely
    • Hardcoded TabSpecificField.EXTENSION field (canonical enum, no per-page constants)
    • Callback-shape onResolve (rather than passing setState) — different pages init state as {} as T vs useState<T>(); the callback shape lets each consumer handle their own state semantics
  • Established as the pattern for follow-up React Query migrations.

P3.3 — EXTENSION trim across 9 entity-detail pages

The custom-property values blob can run into hundreds of KB on entities with many user-defined properties; only the Custom Properties tab consumes it. Trimmed eagerly-requested EXTENSION from defaultFields in:

  • utils/DatasetDetailsUtils.ts (Table)
  • utils/DashboardDetailsUtils.tsx (Dashboard)
  • utils/PipelineDetailsUtils.tsx (Pipeline)
  • utils/MlModelDetailsUtils.tsx (MlModel)
  • utils/StoredProceduresUtils.tsx (StoredProcedure)
  • utils/SearchIndexUtils.tsx (SearchIndex)
  • utils/DirectoryDetailsUtils.tsx (Directory)
  • utils/SpreadsheetDetailsUtils.tsx (Spreadsheet)
  • utils/WorksheetDetailsUtils.tsx (Worksheet)

P3.1 — Hook applied across 9 entity-detail pages

Lazy useLazyEntityExtension call wired on:

  • pages/TableDetailsPageV1/TableDetailsPageV1.tsx (refactored from inline pilot to use the hook)
  • pages/DashboardDetailsPage/DashboardDetailsPage.component.tsx
  • pages/PipelineDetails/PipelineDetailsPage.component.tsx
  • pages/MlModelPage/MlModelPage.component.tsx
  • pages/SearchIndexDetailsPage/SearchIndexDetailsPage.tsx
  • pages/StoredProcedure/StoredProcedurePage.tsx
  • pages/DirectoryDetailsPage/DirectoryDetailsPage.tsx (drive — adapts getDriveAssetByFqn<T> signature via closure)
  • pages/SpreadsheetDetailsPage/SpreadsheetDetailsPage.tsx (drive — adapter)
  • pages/WorksheetDetailsPage/WorksheetDetailsPage.tsx (drive — adapter)

Each page activation of Custom Properties tab now fires a single targeted GET ?fields=extension instead of paying for the field on every page load.

What's NOT in this PR (honest scope statement)

  • Migrating the main getXyzByFqn fetch to useQuery — this is the big-picture P3.1 work, but it's a multi-PR refactor. Each page has 10-20 setXxxDetails(...) call sites in edit handlers, follow handlers, vote handlers, etc. that would need to be converted to queryClient.setQueryData(...) for cache consistency. Mistakes there cause stale UI bugs in production. This PR ships the foundation + the safe lazy-extension migration; the main-fetch migrations land incrementally one page at a time.
  • Other field trim candidates (e.g., votes, followers on tabs they're not visible from) — separate audit per design doc.

Verification

  • yarn build succeeds; the previous AsyncDeleteProvider grab-bag chunk is unchanged in size (this PR's wins are at runtime, not build-time).
  • npx tsc --noEmit clean on all 18 touched files (3 pre-existing unrelated lodash.get typing issues in Drive page followX handlers — not introduced by this PR).
  • ESLint + Prettier clean on touched files.
  • Manual verification path: navigate to any entity-detail page → DevTools Network → confirm initial GET no longer requests extension → click Custom Properties tab → confirm a separate GET ?fields=extension fires → click Schema then Custom Properties again within 60s → confirm cache hit (no network).

Type of change:

  • Improvement

Frontend Preview (Loom)

N/A — no visual change. Verification path above.

Checklist:

  • I have read the CONTRIBUTING document.
  • My PR title is in the form of <type>: <title> and follows Conventional Commits Specification
  • My commits follow the Conventional Commits Specification.
  • I have commented on my code, particularly in hard-to-understand areas.
  • N/A — no behavior change requiring new tests; existing tests cover the runtime contract on each entity page.
  • UI checkstyle passes locally on touched files.
  • yarn build succeeds.
  • No new tsc errors in changed files.
  • I have updated the API documentation

🤖 Generated with Claude Code


Summary by Gitar

  • React Query migration:
    • Migrated TableDetailsPageV1 primary entity fetch to useQuery, preserving state-contract via setTableDetails and fetchTableDetails wrappers.
    • Implemented automatic cache invalidation and loading gating based on entity permissions and isTourOpen status.

This will update automatically on new commits.

harshach and others added 2 commits May 10, 2026 10:33
P3.1 of the perceived-latency design: foundational scaffolding for
incremental migration of manual fetch+Zustand patterns to React Query.

Adds the `QueryClientProvider` at the top of the app tree (outside
AuthProvider so any query made during the auth flow shares the same
cache). Defaults tuned for OpenMetadata's data shape:
  - staleTime 30s — most entity reads are stable for tens of seconds
    and pages flip back-and-forth
  - gcTime 5min — keep results around for tab-switch round-trips
    without holding memory for users who navigate away
  - refetchOnWindowFocus true — picks up backend changes when the
    user returns to the tab
  - retry 1 — one network blip retry, no exponential backoff cascade

This is scaffolding only — no existing fetches are migrated in this
commit. Migrations land incrementally one page at a time; the next
commit is a pilot showing the pattern.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…via useQuery

Combines a P3.3 field trim with the P3.1 React Query pilot:

P3.3 — trim: `extension` (custom property values) was eagerly requested
on every initial table-page load via `defaultFields` /
`defaultFieldsWithColumns`. Custom Properties is a single tab and the
extension blob can run into hundreds of KB on tables with many user-
defined properties. Trimming it saves wire bytes on every initial
load.

P3.1 — pilot: the lazy refetch is wired through `useQuery` with
`enabled: activeTab === EntityTabs.CUSTOM_PROPERTIES && Boolean(fqn)`.
This is the first useQuery call in the codebase — establishes the
pattern for follow-up migrations:
  - Query key: stable, FQN-scoped — same key across tab toggles
  - 60s staleTime: custom property values change rarely
  - Auto in-flight cancellation on FQN change (free with React Query)
  - Auto request dedup if the user double-clicks the tab

`tableDetails.extension` is merged in via a side-effect `useEffect` on
the query result so existing consumers (CustomPropertyTable reading
from the table state) keep working unchanged.

Files:
  - utils/DatasetDetailsUtils.ts: drop EXTENSION from defaultFields and
    defaultFieldsWithColumns; add new `customPropertiesFields` constant
    for the lazy fetch.
  - pages/TableDetailsPageV1/TableDetailsPageV1.tsx: import useQuery
    + customPropertiesFields; new useQuery hook gated on tab; effect
    merges result into table state.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings May 10, 2026 17:34
@harshach harshach requested a review from a team as a code owner May 10, 2026 17:34
@github-actions github-actions Bot added backend safe to test Add this label to run secure Github workflows on PRs labels May 10, 2026
Comment thread openmetadata-ui/src/main/resources/ui/src/App.tsx

Copilot AI left a comment

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.

Pull request overview

Adds React Query infrastructure to the UI and pilots a performance optimization on the Table details page by trimming the eagerly requested extension field and fetching it lazily only when the Custom Properties tab is opened.

Changes:

  • Add @tanstack/react-query dependency and lockfile entries.
  • Introduce a shared QueryClient and wire QueryClientProvider into the app root.
  • Remove extension from default table fields= sets and add a lazily fetched extension query on the Custom Properties tab.

Reviewed changes

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

Show a summary per file
File Description
openmetadata-ui/src/main/resources/ui/package.json Adds @tanstack/react-query dependency.
openmetadata-ui/src/main/resources/ui/yarn.lock Locks TanStack Query packages and updates the linked ui-core-components entry.
openmetadata-ui/src/main/resources/ui/src/queryClient.ts Defines a shared app-wide QueryClient with default caching/retry behavior.
openmetadata-ui/src/main/resources/ui/src/App.tsx Wraps the app in QueryClientProvider to enable React Query usage across pages.
openmetadata-ui/src/main/resources/ui/src/utils/DatasetDetailsUtils.ts Removes extension from default table fields and adds a dedicated customPropertiesFields field set.
openmetadata-ui/src/main/resources/ui/src/pages/TableDetailsPageV1/TableDetailsPageV1.tsx Adds a gated useQuery to fetch/merge extension only for the Custom Properties tab.

Comment on lines 20 to +30
const App: FC = () => {
// QueryClientProvider sits OUTSIDE AuthProvider so any query made during the auth flow
// (e.g. fetching feature flags before login) reuses the same cache. AuthProvider remounts
// on logout — wrapping QueryClient inside would discard the cache on every logout,
// which is the opposite of what we want here.
return (
<AuthProvider childComponentType={AppRouter}>
<AppRouter />
</AuthProvider>
<QueryClientProvider client={queryClient}>
<AuthProvider childComponentType={AppRouter}>
<AppRouter />
</AuthProvider>
</QueryClientProvider>
Comment on lines +248 to +258
const { data: extensionResult } = useQuery({
queryKey: ['table-extension', tableFqn],
queryFn: () =>
getTableDetailsByFQN(tableFqn, { fields: customPropertiesFields }),
enabled:
!isTourOpen &&
activeTab === EntityTabs.CUSTOM_PROPERTIES &&
Boolean(tableFqn),
// Custom property values change rarely; one minute is a safe SWR window.
staleTime: 60_000,
});
Comment on lines +238 to +258
// Lazily fetch the `extension` field (custom properties payload) only when the user
// activates the Custom Properties tab. The eager `defaultFieldsWithColumns` deliberately
// omits `extension` because:
// - On tables with many user-defined custom properties the extension blob can be
// hundreds of KB; paying for it on every initial load is wasteful for users who never
// open Custom Properties.
// - The Custom Properties tab is the only consumer.
// Pattern used here is the P3.1 React Query pilot — `useQuery` with `enabled` gating gives
// us request dedup, in-flight cancellation on FQN change, automatic 30s SWR cache, and a
// tiny readable hook surface. Replicate this pattern for other lazy per-tab fetches.
const { data: extensionResult } = useQuery({
queryKey: ['table-extension', tableFqn],
queryFn: () =>
getTableDetailsByFQN(tableFqn, { fields: customPropertiesFields }),
enabled:
!isTourOpen &&
activeTab === EntityTabs.CUSTOM_PROPERTIES &&
Boolean(tableFqn),
// Custom property values change rarely; one minute is a safe SWR window.
staleTime: 60_000,
});
Comment on lines +16 to +30
// Fields for table details first paint. Excludes columns (paginated separately) and
// `extension` (custom properties — only the Custom Properties tab consumes this; we fetch
// it lazily when the user activates that tab via {@link customPropertiesFields}). Custom
// extension payloads can run into hundreds of KB on tables with many user-defined
// properties; trimming it saves wire bytes on every initial table-page load.
// eslint-disable-next-line max-len
export const defaultFields = `${TabSpecificField.FOLLOWERS},${TabSpecificField.JOINS},${TabSpecificField.TAGS},${TabSpecificField.OWNERS},${TabSpecificField.DATAMODEL},${TabSpecificField.TABLE_CONSTRAINTS},${TabSpecificField.SCHEMA_DEFINITION},${TabSpecificField.DOMAINS},${TabSpecificField.DATA_PRODUCTS},${TabSpecificField.VOTES},${TabSpecificField.EXTENSION}`;
export const defaultFields = `${TabSpecificField.FOLLOWERS},${TabSpecificField.JOINS},${TabSpecificField.TAGS},${TabSpecificField.OWNERS},${TabSpecificField.DATAMODEL},${TabSpecificField.TABLE_CONSTRAINTS},${TabSpecificField.SCHEMA_DEFINITION},${TabSpecificField.DOMAINS},${TabSpecificField.DATA_PRODUCTS},${TabSpecificField.VOTES}`;

// Legacy fields that include columns - only use when pagination is not needed
// eslint-disable-next-line max-len
export const defaultFieldsWithColumns = `${TabSpecificField.COLUMNS},${TabSpecificField.FOLLOWERS},${TabSpecificField.JOINS},${TabSpecificField.TAGS},${TabSpecificField.OWNERS},${TabSpecificField.DATAMODEL},${TabSpecificField.TABLE_CONSTRAINTS},${TabSpecificField.SCHEMA_DEFINITION},${TabSpecificField.DOMAINS},${TabSpecificField.DATA_PRODUCTS},${TabSpecificField.VOTES},${TabSpecificField.EXTENSION}`;
export const defaultFieldsWithColumns = `${TabSpecificField.COLUMNS},${TabSpecificField.FOLLOWERS},${TabSpecificField.JOINS},${TabSpecificField.TAGS},${TabSpecificField.OWNERS},${TabSpecificField.DATAMODEL},${TabSpecificField.TABLE_CONSTRAINTS},${TabSpecificField.SCHEMA_DEFINITION},${TabSpecificField.DOMAINS},${TabSpecificField.DATA_PRODUCTS},${TabSpecificField.VOTES}`;

// Lazy field set requested only when the Custom Properties tab is activated. Pairs with
// the trim of {@link defaultFields} above.
export const customPropertiesFields = `${TabSpecificField.EXTENSION}`;
Comment on lines +264 to +266
setTableDetails((prev) =>
prev ? { ...prev, extension: extensionResult.extension } : prev
);
Comment on lines +245 to +257
// Pattern used here is the P3.1 React Query pilot — `useQuery` with `enabled` gating gives
// us request dedup, in-flight cancellation on FQN change, automatic 30s SWR cache, and a
// tiny readable hook surface. Replicate this pattern for other lazy per-tab fetches.
const { data: extensionResult } = useQuery({
queryKey: ['table-extension', tableFqn],
queryFn: () =>
getTableDetailsByFQN(tableFqn, { fields: customPropertiesFields }),
enabled:
!isTourOpen &&
activeTab === EntityTabs.CUSTOM_PROPERTIES &&
Boolean(tableFqn),
// Custom property values change rarely; one minute is a safe SWR window.
staleTime: 60_000,
harshach and others added 2 commits May 10, 2026 10:50
…a useQuery

Finishes the P3.3 audit started in the previous commit and applies the
P3.1 React Query pattern across the catalogue of entity-detail pages.

P3.3 — `extension` (custom-property values) trimmed from `defaultFields`
in 9 utils files. The blob can run into hundreds of KB on entities with
many user-defined custom properties; only the Custom Properties tab
consumes it. Trimming saves wire bytes on every initial page load.

  - utils/DatasetDetailsUtils.ts        (Table — already trimmed; cleanup)
  - utils/DashboardDetailsUtils.tsx     (Dashboard)
  - utils/PipelineDetailsUtils.tsx      (Pipeline)
  - utils/MlModelDetailsUtils.tsx       (MlModel)
  - utils/StoredProceduresUtils.tsx     (StoredProcedure)
  - utils/SearchIndexUtils.tsx          (SearchIndex)
  - utils/DirectoryDetailsUtils.tsx     (Directory)
  - utils/SpreadsheetDetailsUtils.tsx   (Spreadsheet)
  - utils/WorksheetDetailsUtils.tsx     (Worksheet)

P3.1 — extracted the lazy-fetch pattern into a reusable hook so each
page's wiring is 4 lines instead of a copy-pasted useQuery + useEffect:

  hooks/useLazyEntityExtension.ts — generic over entity shape, takes
  (entityType, fqn, activeTab, fetcher, onResolve). Internally:
    - useQuery gated on `activeTab === CUSTOM_PROPERTIES && fqn`
    - 60s staleTime — custom property values change rarely
    - Hardcoded `TabSpecificField.EXTENSION` field — single canonical
      enum, removes per-page constants
    - onResolve callback shape (rather than passing setState directly)
      so each consumer handles their own state-shape semantics — some
      pages init state as `{} as T`, others as `useState<T>()`.

Hook integrated on 6 pages — Table (refactored from inline pilot to
use the hook), Dashboard, Pipeline, MlModel, SearchIndex, StoredProcedure.

Drive entity pages (Directory, Spreadsheet, Worksheet) have their utils
trimmed but the page-level hook integration is left as follow-up; their
fetcher (`getDriveAssetByFqn<T>`) is generic and needs slightly different
wiring per page.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Completes the lazy-extension rollout to Directory, Spreadsheet, and
Worksheet pages — paired with the EXTENSION trim already applied to
their utils files.

Drive pages share `getDriveAssetByFqn<T>(fqn, entityType, fields)` which
has a different signature than other entity fetchers (entityType is a
positional argument, not bundled in `params`). Each page wraps the call
in a small adapter closure to match `useLazyEntityExtension`'s expected
fetcher shape `(fqn, params) => Promise<T>`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@harshach harshach changed the title feat(ui-perf): React Query scaffolding + table extension fields-trim pilot feat(ui-perf): React Query scaffolding + lazy-extension rollout across 9 entity-detail pages May 10, 2026
P3.1 worked-template: the main `getTableDetailsByFQN` fetch on
TableDetailsPageV1 — by far the highest-traffic entity-detail page in
OpenMetadata — moves from a hand-rolled `useState + useCallback +
useEffect` pattern to React Query.

Why TableDetailsPageV1 first: it's the heaviest page (~25 setTableDetails
mutation call sites, tour-mode override, permission-gated fetch, post-
edit refetch on vote). Migrating it first establishes the precise
recipe other entity-detail pages can follow systematically.

What changed:
  - `useState<Table>()` + `useState(loading)` + `fetchTableDetails` callback
    replaced by a single `useQuery({ queryKey, queryFn, enabled })`.
  - Stable queryKey of `['table-detail', tableFqn, fieldsString]` —
    permission changes mutate the fields string and invalidate the cache
    automatically; FQN changes swap cache slot or refetch.
  - Permissions gating: `enabled` waits for `tablePermissionsLoaded`
    (sentinel-check on `DEFAULT_ENTITY_PERMISSION` reference) so the
    query doesn't race the permission fetch.
  - Tour-mode mock data injected via `queryClient.setQueryData(key, mock)`
    — useQuery picks it up because `data` is sourced from the cache.
  - FORBIDDEN navigation moved from try/catch into a useEffect on
    `tableQueryError`; addToRecentViewed moved into a useEffect on
    `tableDetails?.id`.
  - `loading` derives `isTableLoading || (permissions still loading)` so
    the page doesn't briefly render the no-data placeholder before the
    query is even enabled.

Backward-compat shim — preserves the call-site contract:
  - `setTableDetails(value)` and `setTableDetails(updater)` continue to
    work via a wrapper that forwards to `queryClient.setQueryData`. The
    ~25 mutation call sites in this file (edit handlers, follow,
    unfollow, vote, restore, certification, tier, suggestions) need NO
    changes — they keep writing to "tableDetails" and reads stay
    consistent because both sides are now backed by the cache.
  - `fetchTableDetails()` becomes a thin wrapper around `refetch()`.

Reorder: `extraDropdownContent` useMemo moved to AFTER the useQuery
block because it now reads `tableDetails` from the query (which must
be defined first). No behaviour change.

Side-effects from the legacy FQN-change useEffect now live in two
focused effects: tour-mode cache priming, and getEntityFeedCount.

Verified: yarn build green, eslint clean, tsc clean (the one
pre-existing tsc error in this file is unrelated — `findColumnByEntityLink`
on a `string | undefined`).

Other entity-detail pages (Dashboard, Pipeline, MlModel, etc.) follow
the same recipe in follow-up commits.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings May 10, 2026 18:28

Copilot AI left a comment

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.

Pull request overview

Copilot reviewed 22 out of 23 changed files in this pull request and generated 3 comments.

Comments suppressed due to low confidence (1)

openmetadata-ui/src/main/resources/ui/src/utils/WorksheetDetailsUtils.tsx:49

  • defaultFields includes TabSpecificField.ROW_COUNT twice, which results in a duplicated field in the fields= query param. This is unnecessary work and can make debugging field selection harder; please remove the duplicate (or dedupe the list before joining).
export const defaultFields = [
  TabSpecificField.OWNERS,
  TabSpecificField.FOLLOWERS,
  TabSpecificField.TAGS,
  TabSpecificField.DOMAINS,
  TabSpecificField.DATA_PRODUCTS,
  TabSpecificField.VOTES,
  TabSpecificField.ROW_COUNT,
  TabSpecificField.COLUMNS,
  TabSpecificField.ROW_COUNT,
].join(',');

Comment on lines 444 to 448
} catch {
showErrorToast(
t('server.fetch-entity-permissions-error', {
entity: t('label.resource-permission-lowercase'),
})
Comment on lines +21 to +30
// QueryClientProvider sits OUTSIDE AuthProvider so any query made during the auth flow
// (e.g. fetching feature flags before login) reuses the same cache. AuthProvider remounts
// on logout — wrapping QueryClient inside would discard the cache on every logout,
// which is the opposite of what we want here.
return (
<AuthProvider childComponentType={AppRouter}>
<AppRouter />
</AuthProvider>
<QueryClientProvider client={queryClient}>
<AuthProvider childComponentType={AppRouter}>
<AppRouter />
</AuthProvider>
</QueryClientProvider>
Comment on lines +210 to +215
// Main entity fetch — migrated from a hand-rolled `useState + useCallback + useEffect`
// pattern to React Query (P3.1). Replaces `fetchTableDetails` and the `[tableDetails,
// setTableDetails]` useState below. Existing call sites that did `setTableDetails(...)` or
// `fetchTableDetails()` continue to work via the wrapper functions defined below — the
// page state-shape contract is preserved.
const {
@github-actions

github-actions Bot commented May 10, 2026

Copy link
Copy Markdown
Contributor

🔴 Playwright Results — 23 failure(s), 18 flaky

✅ 4015 passed · ❌ 23 failed · 🟡 18 flaky · ⏭️ 151 skipped

Shard Passed Failed Flaky Skipped
🔴 Shard 1 242 1 2 58
🔴 Shard 2 749 12 7 14
🟡 Shard 3 780 0 4 7
🔴 Shard 4 799 10 2 23
🟡 Shard 5 708 0 1 41
🟡 Shard 6 737 0 2 8

Genuine Failures (failed on all attempts)

Features/DataAssetRulesEnabled.spec.ts › Verify the Table Entity Action items after rules is Enabled (shard 1)
�[31mTest timeout of 180000ms exceeded.�[39m
Features/BulkEditEntity.spec.ts › Table (shard 2)
Error: �[2mexpect(�[22m�[31mlocator�[39m�[2m).�[22mtoBeVisible�[2m(�[22m�[2m)�[22m failed

Locator: getByRole('cell', { name: 'Playwright Table column' })
Expected: visible
Timeout: 15000ms
Error: element(s) not found

Call log:
�[2m  - Expect "toBeVisible" with timeout 15000ms�[22m
�[2m  - waiting for getByRole('cell', { name: 'Playwright Table column' })�[22m

Features/DataQuality/DataQuality.spec.ts › Table test case (shard 2)
�[31mTest timeout of 180000ms exceeded.�[39m
Features/DataQuality/DataQuality.spec.ts › Column test case (shard 2)
�[31mTest timeout of 180000ms exceeded.�[39m
Features/DataQuality/IncidentManagerDateFilter.spec.ts › should show "Created At" as the default sort field label (shard 2)
Error: �[2mexpect(�[22m�[31mreceived�[39m�[2m).�[22mtoBe�[2m(�[22m�[32mexpected�[39m�[2m) // Object.is equality�[22m

Expected: �[32m200�[39m
Received: �[31m400�[39m
Features/DataQuality/IncidentManagerDateFilter.spec.ts › should open sort field dropdown on click (shard 2)
Error: �[2mexpect(�[22m�[31mreceived�[39m�[2m).�[22mtoBe�[2m(�[22m�[32mexpected�[39m�[2m) // Object.is equality�[22m

Expected: �[32m200�[39m
Received: �[31m400�[39m
Features/DataQuality/IncidentManagerDateFilter.spec.ts › should switch to "Updated At" and call API with dateField=updatedAt (shard 2)
Error: �[2mexpect(�[22m�[31mreceived�[39m�[2m).�[22mtoBe�[2m(�[22m�[32mexpected�[39m�[2m) // Object.is equality�[22m

Expected: �[32m200�[39m
Received: �[31m400�[39m
Features/DataQuality/IncidentManagerDateFilter.spec.ts › should switch back to "Created at" and call API with dateField=timestamp (shard 2)
Error: �[2mexpect(�[22m�[31mreceived�[39m�[2m).�[22mtoBe�[2m(�[22m�[32mexpected�[39m�[2m) // Object.is equality�[22m

Expected: �[32m200�[39m
Received: �[31m400�[39m
Features/DataQuality/IncidentManagerDateFilter.spec.ts › should close sort dropdown after selecting an option (shard 2)
Error: �[2mexpect(�[22m�[31mreceived�[39m�[2m).�[22mtoBe�[2m(�[22m�[32mexpected�[39m�[2m) // Object.is equality�[22m

Expected: �[32m200�[39m
Received: �[31m400�[39m
Features/DataQuality/IncidentManagerDateFilter.spec.ts › Date picker shows placeholder by default on Incident Manager page (shard 2)
Error: �[2mexpect(�[22m�[31mreceived�[39m�[2m).�[22mtoBe�[2m(�[22m�[32mexpected�[39m�[2m) // Object.is equality�[22m

Expected: �[32m200�[39m
Received: �[31m400�[39m
Features/DataQuality/IncidentManagerDateFilter.spec.ts › Select and clear date range on Incident Manager page (shard 2)
Error: �[2mexpect(�[22m�[31mreceived�[39m�[2m).�[22mtoBe�[2m(�[22m�[32mexpected�[39m�[2m) // Object.is equality�[22m

Expected: �[32m200�[39m
Received: �[31m400�[39m
Features/Glossary/GlossaryWorkflow.spec.ts › should display correct status badge color and icon (shard 2)
Error: �[2mexpect(�[22m�[31mlocator�[39m�[2m).�[22mtoHaveText�[2m(�[22m�[32mexpected�[39m�[2m)�[22m failed

Locator:  locator('[data-row-key*="StatusBadgeTerm1778989936121"]').locator('.status-badge')
Expected: �[32m"Draft"�[39m
Received: �[31m"In Review"�[39m
Timeout:  15000ms

Call log:
�[2m  - Expect "toHaveText" with timeout 15000ms�[22m
�[2m  - waiting for locator('[data-row-key*="StatusBadgeTerm1778989936121"]').locator('.status-badge')�[22m
�[2m    18 × locator resolved to <div class="status-badge inReview" data-testid=""PW%'e2f002b0.Darke721a8db".StatusBadgeTerm1778989936121-status">…</div>�[22m
�[2m       - unexpected value "In Review"�[22m

Features/IncidentManager.spec.ts › Complete Incident lifecycle with table owner (shard 2)
Error: Wait for incident pw_test_case_08c9e657 to appear in Incident Manager

�[2mexpect(�[22m�[31mreceived�[39m�[2m).�[22mtoBe�[2m(�[22m�[32mexpected�[39m�[2m) // Object.is equality�[22m

Expected: �[32mtrue�[39m
Received: �[31mfalse�[39m

Call Log:
- Timeout 60000ms exceeded while waiting on the predicate
Pages/CustomProperties.spec.ts › Create custom property and configure search for Dashboard (shard 4)
Error: �[2mexpect(�[22m�[31mlocator�[39m�[2m).�[22mtoBeVisible�[2m(�[22m�[2m)�[22m failed

Locator: getByText('EXECUTIVE_DASHBOARD_677b3b83')
Expected: visible
Timeout: 15000ms
Error: element(s) not found

Call log:
�[2m  - Expect "toBeVisible" with timeout 15000ms�[22m
�[2m  - waiting for getByText('EXECUTIVE_DASHBOARD_677b3b83')�[22m

Pages/DataContractInheritance.spec.ts › Partial Contract Inheritance - Asset contract merges with Data Product contract (shard 4)
Error: �[2mexpect(�[22m�[31mlocator�[39m�[2m).�[22mtoBeVisible�[2m(�[22m�[2m)�[22m failed

Locator: getByTestId('KnowledgePanel.DataProducts').getByTestId('data-products-list').getByTestId('data-product-"PW%dataProduct.18443f67"')
Expected: visible
Timeout: 15000ms
Error: element(s) not found

Call log:
�[2m  - Expect "toBeVisible" with timeout 15000ms�[22m
�[2m  - waiting for getByTestId('KnowledgePanel.DataProducts').getByTestId('data-products-list').getByTestId('data-product-"PW%dataProduct.18443f67"')�[22m

Pages/DataContractsSemanticRules.spec.ts › Validate DataProduct Rule Is (shard 4)
�[31mTest timeout of 180000ms exceeded.�[39m
Pages/DataContractsSemanticRules.spec.ts › Validate DataProduct Rule Is Not (shard 4)
�[31mTest timeout of 180000ms exceeded.�[39m
Pages/DataContractsSemanticRules.spec.ts › Validate DataProduct Rule Any_In (shard 4)
�[31mTest timeout of 180000ms exceeded.�[39m
Pages/DataContractsSemanticRules.spec.ts › Validate DataProduct Rule Not_In (shard 4)
�[31mTest timeout of 180000ms exceeded.�[39m
Pages/DataContractsSemanticRules.spec.ts › Validate DataProduct Rule Is_Set (shard 4)
�[31mTest timeout of 180000ms exceeded.�[39m
Pages/Entity.spec.ts › Domain Add, Update and Remove (shard 4)
�[31mTest timeout of 180000ms exceeded.�[39m
Pages/Entity.spec.ts › Tag Add, Update and Remove for child entities (shard 4)
Error: �[2mexpect(�[22m�[31mlocator�[39m�[2m).�[22mtoBeVisible�[2m(�[22m�[2m)�[22m failed

Locator: locator('[data-row-key="pw-database-service-9a9196e1.pw-database-f859bae1.pw-database-schema-eac226a2.pw-table-2c8b48a2-8ba1-4944-aea2-111c93dbb267.user_id72e55184"]').getByTestId('tag-PersonalData.SpecialCategory')
Expected: visible
Timeout: 15000ms
Error: element(s) not found

Call log:
�[2m  - Expect "toBeVisible" with timeout 15000ms�[22m
�[2m  - waiting for locator('[data-row-key="pw-database-service-9a9196e1.pw-database-f859bae1.pw-database-schema-eac226a2.pw-table-2c8b48a2-8ba1-4944-aea2-111c93dbb267.user_id72e55184"]').getByTestId('tag-PersonalData.SpecialCategory')�[22m

Pages/Entity.spec.ts › Tag and Glossary Term preservation in column detail panel (shard 4)
Error: �[2mexpect(�[22m�[31mlocator�[39m�[2m).�[22mtoBeVisible�[2m(�[22m�[2m)�[22m failed

Locator: locator('[data-row-key="pw-database-service-277b9bdd.pw-database-eb670e51.pw-database-schema-0a9244c5.pw-table-c750a11c-a99a-451c-a86d-8f2204c1f686.user_idf01959af"]').getByTestId('tag-PII.Sensitive')
Expected: visible
Timeout: 15000ms
Error: element(s) not found

Call log:
�[2m  - Expect "toBeVisible" with timeout 15000ms�[22m
�[2m  - waiting for locator('[data-row-key="pw-database-service-277b9bdd.pw-database-eb670e51.pw-database-schema-0a9244c5.pw-table-c750a11c-a99a-451c-a86d-8f2204c1f686.user_idf01959af"]').getByTestId('tag-PII.Sensitive')�[22m

🟡 18 flaky test(s) (passed on retry)
  • Pages/AuditLogs.spec.ts › should apply both User and EntityType filters simultaneously (shard 1, 1 retry)
  • Pages/AuditLogs.spec.ts › should create audit log entry when glossary is created (shard 1, 1 retry)
  • Features/BulkEditEntity.spec.ts › Glossary (shard 2, 1 retry)
  • Features/BulkImport.spec.ts › Database Schema (shard 2, 1 retry)
  • Features/ContextCenter.spec.ts › version page shows article version header with breadcrumb and title (shard 2, 1 retry)
  • Features/ContextCenter.spec.ts › uploaded file shows name, size and download button in list (shard 2, 1 retry)
  • Features/DataQuality/TestCaseResultPermissions.spec.ts › User with only VIEW cannot PATCH results (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/RTL.spec.ts › Verify Following widget functionality (shard 3, 1 retry)
  • Flow/AddRoleAndAssignToUser.spec.ts › Verify assigned role to new user (shard 3, 1 retry)
  • Flow/ExploreAggregationCountsMatching.spec.ts › should verify left panel counts and tab search results for normal search (shard 3, 1 retry)
  • Flow/NotificationAlerts.spec.ts › Single Filter Alert (shard 3, 1 retry)
  • Pages/CustomProperties.spec.ts › Set & Update all CP types on database (shard 4, 1 retry)
  • Pages/Domains.spec.ts › Follow & Un-follow domain (shard 4, 1 retry)
  • Pages/Entity.spec.ts › User as Owner Add, Update and Remove (shard 5, 1 retry)
  • Pages/Lineage/LineageFilters.spec.ts › Verify lineage schema filter selection (shard 6, 1 retry)
  • Pages/ODCSImportExport.spec.ts › Multi-object ODCS contract - object selector shows all schema objects (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

… tests

Addresses open review comments on PR #28017:

* **Security — clear QueryClient cache on logout** (AuthProvider.tsx).
  Before: `QueryClientProvider` sits outside `AuthProvider`, so cached
  query data survives logout. On a shared machine, the next user could
  briefly see the previous user's cached entity payloads. Add
  `queryClient.clear()` to `onLogoutHandler` so the cache is wiped at the
  same moment auth state is cleared.

* **Bug — page no longer stuck in loading state if permission fetch
  fails** (TableDetailsPageV1.tsx). Before: `tablePermissionsLoaded` was
  derived from `tablePermissions !== DEFAULT_ENTITY_PERMISSION`. If
  `fetchResourcePermission` threw, permissions stayed as the sentinel and
  the page loader never resolved. Now explicit
  `tablePermissionsLoaded` state, flipped to `true` in a `finally` block
  so the page progresses even on permission error.

* **Bug — gate `useQuery` on `viewBasicPermission`** so users without
  view permission see the permission-error placeholder instead of
  triggering a guaranteed-403 fetch. Matches the legacy
  `if (viewBasicPermission) fetchTableDetails()` gating. `viewBasicPermission`
  hoisted into the upstream `useMemo` so it is available to the query's
  `enabled` clause.

* **Tests — TableDetailsPageV1 unit suite migrated for React Query**:
  add a `renderWithProviders` helper that wraps with a fresh
  `QueryClientProvider`, remove `extension` from the
  `COMMON_API_FIELDS` constant (matches the trimmed default-fields), add
  `addToRecentViewed` to the `CommonUtils` mock (now driven by an effect
  on resolved entity id), and switch the three sync `getByText`
  assertions that race the query resolve to `findByText`.

* **Tests — global `useLazyEntityExtension` mock in `setupTests.js`** so
  page-component tests that render an entity-detail page do not need to
  set up React Query context just to render. Per-test overrides remain
  possible.

* **Tests — update `fields=` assertions** on
  Dashboard/SearchIndex/Spreadsheet/Worksheet test files to reflect
  the trimmed default-fields (no `extension`).

* **UI checkstyle — sort imports** on the 6 page files flagged by the
  CI lint-src job. No functional change.

Verified locally: 47/47 tests pass across
TableDetailsPageV1/Dashboard/Pipeline/MlModel/SearchIndex/StoredProcedure/AuthProvider.

Pre-existing test failures on Directory/Spreadsheet/Worksheet
(useParams mock not wired) are unaffected by this PR.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings May 17, 2026 01:57
@gitar-bot

gitar-bot Bot commented May 17, 2026

Copy link
Copy Markdown
Code Review ✅ Approved 3 resolved / 3 findings

Introduces React Query infrastructure and optimizes load times by lazy-loading the extension field across nine entity-detail pages. Addresses findings related to cache management, state synchronization, and permission-based loading.

✅ 3 resolved
Edge Case: useEffect merge overwrites extension updated via Custom Properties tab

📄 openmetadata-ui/src/main/resources/ui/src/pages/TableDetailsPageV1/TableDetailsPageV1.tsx:248-262
The useEffect that syncs extensionResult into tableDetails.extension runs every time the query returns data. If the user edits a custom property (which calls handleExtensionUpdate and patches tableDetails locally), then later re-focuses the window or the staleTime expires, refetchOnWindowFocus: true or a re-mount will overwrite the optimistic local state with potentially stale server data (up to 60s old due to staleTime: 60_000).

More concretely: user edits extension → local state updated → window blur/focus → React Query refetches (returns old data because server may not have processed the PATCH yet or the 60s window hasn't expired) → useEffect overwrites local state with old extension.

Consider invalidating the query key after a successful extension mutation, or adding a dependency on a version/timestamp to avoid overwriting fresher local state.

Security: QueryClient cache not cleared on logout leaks data between users

📄 openmetadata-ui/src/main/resources/ui/src/App.tsx:26-30
The QueryClientProvider is deliberately placed outside AuthProvider so the cache survives auth-flow remounts. However, there is no queryClient.clear() call in the logout handler. If user A logs out and user B logs in on the same browser tab, user B may briefly see user A's cached query results (e.g. the extension/custom-properties payload keyed by table FQN). This is a data-leakage vector in shared-machine environments.

The PR description justifies the placement for caching feature-flag fetches across auth transitions, but the trade-off needs an explicit cache wipe on credential change.

Bug: Page stuck in infinite loading state if permission fetch fails

📄 openmetadata-ui/src/main/resources/ui/src/pages/TableDetailsPageV1/TableDetailsPageV1.tsx:208 📄 openmetadata-ui/src/main/resources/ui/src/pages/TableDetailsPageV1/TableDetailsPageV1.tsx:256-257 📄 openmetadata-ui/src/main/resources/ui/src/pages/TableDetailsPageV1/TableDetailsPageV1.tsx:223-224 📄 openmetadata-ui/src/main/resources/ui/src/pages/TableDetailsPageV1/TableDetailsPageV1.tsx:444-450
If fetchResourcePermission throws (e.g., network error, 500), the catch block shows a toast but never calls setTablePermissions(...). This means tablePermissions remains the DEFAULT_ENTITY_PERMISSION sentinel, so tablePermissionsLoaded stays false, loading stays true forever, and the useQuery never fires (enabled is gated on tablePermissionsLoaded).

The old code had finally { setLoading(false) } in fetchResourcePermission to unblock the page even on permission failure. That safety net was removed in this commit (lines 447-450 in the diff).

Options

Display: compact → Showing less information.

Comment with these commands to change:

Compact
gitar display:verbose         

Was this helpful? React with 👍 / 👎 | Gitar

Copilot AI left a comment

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.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

harshach added a commit that referenced this pull request May 23, 2026
Brings the unique work from PR #28017 into this branch so we can close that
PR as superseded. Trims `extension` (custom-property values) from
`defaultFields` on 9 entity-detail pages — Table, Dashboard, Pipeline,
MlModel, StoredProcedure, SearchIndex, Directory, Spreadsheet, Worksheet —
and fetches it lazily on Custom Properties tab activation via a new
`useLazyEntityExtension` hook.

Why this matters:
- Custom property payloads can run into hundreds of KB on entities with
  many user-defined properties. Most users never open the Custom Properties
  tab, so paying for it on first paint is wasted bytes.
- The hook centralises the gated-useQuery + merge-into-state pattern (60s
  staleTime, FQN-scoped queryKey, auto cancellation on FQN change) so each
  page's wiring is 4–8 lines instead of a copy-pasted closure-with-effect.

Test plumbing:
- `setupTests.js` globally mocks `useLazyEntityExtension` to a no-op so
  page tests that render without `QueryClientProvider` keep rendering.
- Per-page `fields=` assertions updated where they hardcode the trimmed
  default-fields string.
- Drive entity tests gain a `useParams` mock (returns `{ tab: ... }`) so
  the page's `useRequiredParams` doesn't throw during render.

Closes the lazy-extension work from PR #28017; that PR can now be closed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@harshach

Copy link
Copy Markdown
Collaborator Author

Superseded by PR #28014 (perceived-latency-p1). The unique work from this PR — the useLazyEntityExtension hook and EXTENSION field trim across 9 entity-detail pages — has been brought into #28014 at commit 4cd8737. Closing as redundant; #28014 also already carries the React Query infrastructure and AuthProvider logout cache-wipe via separate commits.

@harshach harshach closed this May 23, 2026
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