Skip to content

perf(ui): deduplicate docStore persona requests via shared React Query key - #31300

Merged
Rohit0301 merged 29 commits into
mainfrom
duplicate-persona-onboarding-requests
Aug 19, 2026
Merged

perf(ui): deduplicate docStore persona requests via shared React Query key#31300
Rohit0301 merged 29 commits into
mainfrom
duplicate-persona-onboarding-requests

Conversation

@Rohit0301

@Rohit0301 Rohit0301 commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

Describe your changes:

I worked on eliminating three redundant identical GET requests to /api/v1/docStore/name/persona.* that fired on every /my-data navigation (543ms total wasted, all 404s) because MyDataPage and multiple useCustomPages consumers each fetched independently with no cache coordination.

Root cause: useCustomPages used a manual useState/useCallback/useEffect pattern, and MyDataPage had its own separate fetchDocument() + useEffect. Neither used React Query, so concurrent subscribers for the same persona FQN each fired an independent network request.

Fix:

  • Added rest/queries/docStoreQuery.ts — shared docStoreQueryKey(fqn) + docStoreQueryFn(fqn) following the existing tableQuery.ts pattern
  • Migrated useCustomPages from manual fetch to useQuery with the shared key; pageType filtering moves into the return value (no longer triggers a re-fetch on pageType change — it filters from the cached doc)
  • Replaced MyDataPage's fetchDocument() + useEffect with useQuery using the same key; layout and personaPreferences derived via useMemo

With React Query's in-flight deduplication, all concurrent subscribers to ['docStore', 'persona.X'] share exactly one network request.

Type of change:

  • Improvement

High-level design:

The existing rest/queries/ pattern (e.g. tableQuery.ts, dashboardQuery.ts) exports a canonical queryKey + queryFn pair so any consumer — detail page, sidebar widget, hover prefetch — hits the same normalised cache slot. docStoreQuery.ts adds the same plumbing for DocStore documents.

useCustomPages previously re-fetched the full persona document on every pageType change even though the document contains all page types. The new implementation fetches once per persona FQN and filters locally, reducing N fetches to 1 per persona.

MyDataPage previously had a duplicate fetch path independent of useCustomPages. Both now share the same React Query cache slot (['docStore', 'persona.X']), so on /my-data navigation the document is fetched exactly once regardless of how many consumers are mounted.

Tests:

Use cases covered

  • /my-data page loads with a persona selected — one GET to docStore/name/persona.* instead of three
  • Sidebar navigation (useSidebarItemsuseCustomPages('Navigation')) and page body share the cached response
  • Error case (404): navigation resets to [], customizedPage resets to null — same contract as before
  • Changing pageType between renders filters from the cache without a network round-trip
  • Switching selected personas triggers a new fetch for the new FQN

Unit tests

  • Updated useCustomPages.test.ts — wrapped with QueryClientProvider, updated "pageType changes" test to assert one fetch (not two), all other assertions preserved
  • Files updated: openmetadata-ui/src/main/resources/ui/src/hooks/useCustomPages.test.ts

Backend integration tests

  • Not applicable (no backend API changes).

Ingestion integration tests

  • Not applicable (no ingestion changes).

Playwright (UI) tests

  • Not applicable — no visible UI behaviour change; this is a network-layer deduplication fix.

Manual testing performed

  1. Navigate to /my-data with a persona assigned
  2. Open browser DevTools → Network tab, filter by docStore
  3. Confirm exactly 1 GET to /api/v1/docStore/name/persona.* fires (was 3 before this change)
  4. Verify page layout and sidebar navigation render correctly
  5. Switch personas — confirm a new request fires for the new FQN

UI screen recording / screenshots:

Not applicable — no visual change; the fix is a network deduplication at the data-fetching layer.

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. — N/A
  • For UI changes: I attached a screen recording and/or screenshots above. — N/A (no visual change)
  • I have added tests (unit / integration / Playwright as applicable) and listed them above.

Greptile Summary

The PR consolidates persona DocStore reads under a shared React Query key and updates that cache after successful customization saves.

  • Deduplicates concurrent persona document requests across My Data, marketplace, navigation, and app-mode consumers.
  • Derives page-specific layouts and preferences from the shared cached document.
  • Synchronizes all current persona customization save paths with the shared query cache.

Confidence Score: 5/5

The PR appears safe to merge.

No blocking failure remains.

Important Files Changed

Filename Overview
openmetadata-ui/src/main/resources/ui/src/rest/queries/docStoreQuery.ts Defines the canonical persona DocStore query key, fetch function, FQN helper, and shared freshness window.
openmetadata-ui/src/main/resources/ui/src/pages/CustomizablePage/CustomizablePage.tsx Updates the canonical query cache after each successful persona customization mutation, resolving the previously reported stale-cache path.
openmetadata-ui/src/main/resources/ui/src/hooks/useCustomPages.ts Replaces independent fetching with a shared cached document and local page-type filtering.
openmetadata-ui/src/main/resources/ui/src/pages/MyDataPage/MyDataPage.component.tsx Reads landing-page layout and persona preferences from the shared persona document query.
openmetadata-ui/src/main/resources/ui/src/pages/DataMarketplacePage/DataMarketplacePage.component.tsx Reads marketplace customization through the shared query while preserving default-layout and error behavior.
openmetadata-ui/src/main/resources/ui/src/hooks/useResolvedAppMode.ts Reuses the shared persona document cache for app-mode resolution and guards personas without an FQN.

Sequence Diagram

sequenceDiagram
  participant UI as Persona UI consumers
  participant RQ as React Query cache
  participant API as DocStore API
  participant Editor as CustomizablePage
  UI->>RQ: "Read ["docStore", "persona.<fqn>"]"
  alt Cache miss
    RQ->>API: GET persona document
    API-->>RQ: Document
  end
  RQ-->>UI: Shared document
  Editor->>API: Create or update customization
  API-->>Editor: Updated document
  Editor->>RQ: setQueryData(shared key, response)
  RQ-->>UI: Updated document
Loading

Reviews (9): Last reviewed commit: "addressed gitar comment" | Re-trigger Greptile

Context used:

Three identical GET /api/v1/docStore/name/persona.* requests fired on
every /my-data navigation because MyDataPage and multiple useCustomPages
consumers each fetched independently with no cache coordination.

Introduce docStoreQuery.ts (shared queryKey + queryFn), migrate
useCustomPages to useQuery, and rewrite MyDataPage's manual fetch/effect
to useQuery with the same key. React Query's in-flight deduplication
collapses N concurrent subscribers to one network request.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@Rohit0301
Rohit0301 requested a review from a team as a code owner August 10, 2026 14:52
@github-actions

Copy link
Copy Markdown
Contributor

❌ PR checklist incomplete

This PR cannot be merged until the following are addressed on its linked issue:

  • No GitHub issue is linked. Link an issue in the Development section of the PR (or add Fixes #12345 to the description). For a same-org cross-repo issue, add Fixes open-metadata/<repo>#123 to the description.

The fields live on the linked issue in the Shipping project (open the issue → right sidebar → Projects). After you set them, re-run this check (or push a commit) — issue/project changes do not re-trigger it automatically.

Maintainers can bypass this check by adding the skip-pr-checks label.

@Rohit0301 Rohit0301 self-assigned this Aug 10, 2026
@Rohit0301 Rohit0301 added the safe to test Add this label to run secure Github workflows on PRs label Aug 10, 2026
@github-actions github-actions Bot added the UI UI specific issues label Aug 10, 2026
…lper

Both useCustomPages and MyDataPage were independently building the
persona docStore FQN string. Extract to personaDocFqn() in
docStoreQuery.ts so the cache key derivation has a single definition
and consumers can't silently drift apart.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

@Rohit0301 Rohit0301 left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good call — addressed in dcb5759. Extracted the FQN construction to personaDocFqn() in docStoreQuery.ts so both useCustomPages and MyDataPage derive the cache key from a single definition. Both consumers now import and call personaDocFqn(selectedPersona) and the inline template literals are gone.

@github-actions

github-actions Bot commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

✅ Playwright Results — workflow succeeded

Validated commit 446985daa2c91aec4baff8c28153b7036d523f0b in Playwright run 32116215033, attempt 1.

✅ 922 passed · ❌ 0 failed · 🟡 3 flaky · ⏭️ 1 skipped · 🧰 0 lifecycle flaky

Performance

Blocking targets: ✅ met · Optimization targets: 🟡 in progress

Shard-job maxima below are not the full workflow wall time; the linked run includes build, fixture, planning, and reporting.

🕒 Full workflow signal wall (to summary) 36m 6s

⏱️ Max setup 3m 8s · max shard execution 18m 13s · max shard-job elapsed before upload 22m 8s · reporting 8s

🌐 214.64 requests/attempt · 2.50 app boots/UI scenario · 13.27% common-shard skew

Optimization targets still in progress:

  • Browser traffic was 214.64 requests per attempt (convergence target: fewer than 200).
  • Application boot ratio was 2.5 per UI scenario (2433 boots / 974 scenarios; convergence target: at most 1).
Shard Passed Failed Flaky Skipped Lifecycle failed Lifecycle flaky
🟡 Shard chromium-01 151 0 1 0 0 0
🟡 Shard chromium-02 149 0 1 0 0 0
✅ Shard chromium-03 151 0 0 0 0 0
✅ Shard chromium-04 158 0 0 1 0 0
🟡 Shard chromium-05 152 0 1 0 0 0
✅ Shard data-asset-rules-01 61 0 0 0 0 0
✅ Shard domain-isolation-01 14 0 0 0 0 0
✅ Shard global-state-01 34 0 0 0 0 0
✅ Shard import-export-01 9 0 0 0 0 0
✅ Shard ingestion-01 1 0 0 0 0 0
✅ Shard reindex-01 2 0 0 0 0 0
✅ Shard search-01 11 0 0 0 0 0
✅ Shard search-rbac-01 29 0 0 0 0 0
🟡 3 flaky test(s) (passed on retry)
  • Flow/CustomizeWidgets.spec.tsKPI Widget (shard chromium-01, 1 retry)
  • Pages/Glossary.spec.tsApprove and reject glossary term from Glossary Listing (shard chromium-02, 1 retry)
  • Features/Glossary/GlossaryHierarchy.spec.tsshould cancel move operation (shard chromium-05, 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

useCustomPages now uses useQuery internally, so components that call it
require a QueryClientProvider ancestor. Fix each test with the
appropriate strategy:

- GlossaryV1, GlossaryDetails, LeftSidebar: add jest.mock for
  useCustomPages (same pattern used by 20+ other component tests)
- MyDataPage: add QueryClientProvider wrapper via a renderMyDataPage()
  helper + fresh QueryClient per test to avoid cache pollution

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

🚦 Removed from the merge queue — failed_checks (2026-08-14T08:45:40Z)

Blocked the queue: playwright-summary

  • Postgresql PR Playwright E2E Tests — playwright-summary, playwright-ci-postgresql (chromium-17), playwright-ci-postgresql (chromium-09), playwright-ci-postgresql (chromium-23), playwright-ci-postgresql (chromium-01), playwright-ci-postgresql (chromium-18), playwright-ci-postgresql (chromium-16), playwright-ci-postgresql (chromium-08), playwright-ci-postgresql (chromium-11), playwright-ci-postgresql (chromium-12), playwright-ci-postgresql (chromium-07), playwright-ci-postgresql (chromium-10), playwright-ci-postgresql (chromium-03)

@github-actions

Copy link
Copy Markdown
Contributor

🚦 Removed from the merge queue — failed_checks (2026-08-14T10:38:11Z)

Blocked the queue: playwright-summary

  • Postgresql PR Playwright E2E Tests — playwright-summary, playwright-ci-postgresql (chromium-19), playwright-ci-postgresql (chromium-05), playwright-ci-postgresql (chromium-18), playwright-ci-postgresql (chromium-12), playwright-ci-postgresql (chromium-04), playwright-ci-postgresql (chromium-22), playwright-ci-postgresql (chromium-08), playwright-ci-postgresql (chromium-11), playwright-ci-postgresql (chromium-15), playwright-ci-postgresql (chromium-01), playwright-ci-postgresql (chromium-06)

@github-actions

Copy link
Copy Markdown
Contributor

🚦 Removed from the merge queue — failed_checks (2026-08-14T14:23:54Z)

Blocked the queue: playwright-summary

  • Postgresql PR Playwright E2E Tests — playwright-summary, playwright-ci-postgresql (chromium-07), playwright-ci-postgresql (chromium-05), playwright-ci-postgresql (chromium-11), playwright-ci-postgresql (chromium-14), playwright-ci-postgresql (chromium-01), playwright-ci-postgresql (chromium-20), playwright-ci-postgresql (chromium-21), playwright-ci-postgresql (chromium-06), playwright-ci-postgresql (chromium-18), playwright-ci-postgresql (chromium-04), playwright-ci-postgresql (chromium-03), playwright-ci-postgresql (chromium-08)

…right timeout

Without this, a user with no persona gets isLoading=false on the very first
render (React Query computes it synchronously), widgets mount immediately, and
their loaders appear before waitForAllLoadersToDisappear starts polling in the
data-contract Playwright test — causing a 30 s timeout.

Restores the pre-React-Query invariant: useState(true) ensures the skeleton
always renders on the first paint; a useEffect syncs isLoading to the actual
query state after that, so widgets are only deferred by one effect flush rather
than a full network round-trip.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Rohit0301 and others added 3 commits August 15, 2026 11:29
The previous fix used useState+useEffect to mirror isQueryLoading into an
isLoading state variable, which is the classic derived-state anti-pattern:
extra render on every query transition and a stale window between renders.

Replace with a one-shot hasMounted flag that flips true after the first paint.
isLoading is now fully derived: !hasMounted forces skeleton on first render;
after mount it equals the actual query loading expression with no lag.

Addresses gitar-bot review comment on PR #31300.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…laywright timeout

The "Data Contracts With Persona" Playwright tests all fail at
waitForAllLoadersToDisappear after visitEntityPage. Every failing entity detail
component (Topic, Dashboard, MlModel, Pipeline, StoredProcedure, SearchIndex,
Container, APICollection, etc.) gates its loader on isLoading from
useCustomPages:

  if (isLoading || permissionsLoading || ...) return <PageLoader />;

Before this PR, isLoading = useState(true) always started true and quickly
resolved. After the React Query migration, isLoading = !!fqn && isPending
starts false when selectedPersona is not yet in the Zustand store, then jumps
to true when the persona arrives asynchronously — after waitForAllLoadersToDisappear
may have already returned count=0, leaving the test interacting with a page
covered by PageLoader.

Apply the same hasMounted pattern already used in MyDataPage: isLoading is
true on the first render regardless of persona state, then derives from the
query after the mount effect fires. This restores the well-defined
always-loading-on-first-render invariant that Playwright tests relied on.

Also update the no-persona unit test to await isLoading=false (hasMounted now
makes the initial value true for one effect tick).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Drop `!!fqn && isPending` from `useCustomPages.isLoading`.

Root cause of remaining test failures: the old `fetchDocument()` inside
`useCustomPages` never called `setIsLoading(true)` — it only called
`setIsLoading(false)` in the finally block.  So when `selectedPersona`
arrived asynchronously after the initial render, `isLoading` stayed false
while the background API fetch ran.  `waitForAllLoadersToDisappear` returned
cleanly and the entity page stayed visible throughout.

The previous hasMounted fix used `isLoading = !hasMounted || (!!fqn && isPending)`.
When `selectedPersona` arrives asynchronously (`fqn` transitions null→non-null
after mount), `isPending=true` makes `isLoading` jump back to true — a second
loader wave AFTER `waitForAllLoadersToDisappear` returned, covering the entity
page and blocking all subsequent test interactions.

Fix: `isLoading = !hasMounted` only.  The persona doc still fetches in the
background via React Query; `customizedPage`/`navigation` update when data
arrives, entity page re-renders without a loader — exactly matching old behaviour.

Also drop the two sync `expect(isLoading).toBe(true)` assertions in the unit
tests: RTL flushes the mount effect synchronously, so `hasMounted` is already
`true` by the time the first assertion runs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Comment thread openmetadata-ui/src/main/resources/ui/src/hooks/useCustomPages.ts Outdated
Rohit0301 and others added 2 commits August 15, 2026 20:53
isPending was left dangling after !!fqn&&isPending was removed from the
isLoading expression — dead binding that would fail no-unused-vars lint.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

⚠️ UI Checkstyle passed — lint findings in changed files

🔍 ESLint findings in this PR's files — 0 error(s), 25 warning(s)

Errors block the build. Warnings do not yet — they are rules whose backlog is still
being worked down, listed so this PR does not add to it. See docs/ui-code-quality-gate.md.

0 error(s), 25 warning(s) across 7 changed file(s).

Count Rule
7 openmetadata-imports/no-cross-page-imports
6 sonarjs/no-duplicate-string
3 sonarjs/cyclomatic-complexity
2 jsx-a11y/click-events-have-key-events
2 jsx-a11y/no-static-element-interactions
2 react-hooks/exhaustive-deps
1 jsx-a11y/anchor-is-valid
1 sonarjs/cognitive-complexity
1 openmetadata-imports/review-sequential-api-calls
All findings
Location Rule Message
🟡 src/components/Glossary/GlossaryV1.test.tsx:77:56 jsx-a11y/anchor-is-valid The href attribute is required for an anchor to be keyboard accessible. Provide a valid, navigable address as the href value. If you cannot provide an href, but
🟡 src/hooks/useResolvedAppMode.ts:194:16 sonarjs/cognitive-complexity Refactor this function to reduce its Cognitive Complexity from 22 to the 15 allowed.
🟡 src/hooks/useResolvedAppMode.ts:194:16 sonarjs/cyclomatic-complexity {"message":"Function has a complexity of 22 which is greater than 10 authorized.","cost":12,"secondaryLocations":[{"line":194,"column":15,"endLine":194,"endColu
🟡 src/pages/CustomizablePage/CustomizablePage.test.tsx:48:13 jsx-a11y/click-events-have-key-events Visible, non-interactive elements with click handlers must have at least one keyboard listener.
🟡 src/pages/CustomizablePage/CustomizablePage.test.tsx:48:13 jsx-a11y/no-static-element-interactions Avoid non-native interactive elements. If using native HTML is not possible, add an appropriate role and support for tabbing, mouse, keyboard, and touch inputs
🟡 src/pages/CustomizablePage/CustomizablePage.tsx:53:1 openmetadata-imports/no-cross-page-imports Page features must not import another page feature. Move shared code to components, hooks, interfaces, or pure utilities.
🟡 src/pages/CustomizablePage/CustomizablePage.tsx:54:1 openmetadata-imports/no-cross-page-imports Page features must not import another page feature. Move shared code to components, hooks, interfaces, or pure utilities.
🟡 src/pages/CustomizablePage/CustomizablePage.tsx:55:1 openmetadata-imports/no-cross-page-imports Page features must not import another page feature. Move shared code to components, hooks, interfaces, or pure utilities.
🟡 src/pages/CustomizablePage/CustomizablePage.tsx:56:1 openmetadata-imports/no-cross-page-imports Page features must not import another page feature. Move shared code to components, hooks, interfaces, or pure utilities.
🟡 src/pages/CustomizablePage/CustomizablePage.tsx:57:1 openmetadata-imports/no-cross-page-imports Page features must not import another page feature. Move shared code to components, hooks, interfaces, or pure utilities.
🟡 src/pages/CustomizablePage/CustomizablePage.tsx:77:36 sonarjs/cyclomatic-complexity {"message":"Function has a complexity of 31 which is greater than 10 authorized.","cost":21,"secondaryLocations":[{"line":77,"column":35,"endLine":77,"endColumn
🟡 src/pages/CustomizablePage/CustomizablePage.tsx:138:11 sonarjs/no-duplicate-string Define a constant instead of duplicating this literal 4 times.
🟡 src/pages/CustomizablePage/CustomizablePage.tsx:140:17 sonarjs/no-duplicate-string Define a constant instead of duplicating this literal 4 times.
🟡 src/pages/CustomizablePage/CustomizablePage.tsx:141:17 sonarjs/no-duplicate-string Define a constant instead of duplicating this literal 4 times.
🟡 src/pages/CustomizablePage/CustomizablePage.tsx:146:11 sonarjs/no-duplicate-string Define a constant instead of duplicating this literal 4 times.
🟡 src/pages/CustomizablePage/CustomizablePage.tsx:148:17 sonarjs/no-duplicate-string Define a constant instead of duplicating this literal 4 times.
🟡 src/pages/CustomizablePage/CustomizablePage.tsx:149:17 sonarjs/no-duplicate-string Define a constant instead of duplicating this literal 4 times.
🟡 src/pages/CustomizablePage/CustomizablePage.tsx:276:54 sonarjs/cyclomatic-complexity {"message":"Function has a complexity of 11 which is greater than 10 authorized.","cost":1,"secondaryLocations":[{"line":276,"column":53,"endLine":276,"endColum
🟡 src/pages/CustomizablePage/CustomizablePage.tsx:348:28 openmetadata-imports/review-sequential-api-calls Review these sequential API requests. If they are independent, start them together with Promise.all/Promise.allSettled; keep sequencing only when data-dependent
🟡 src/pages/CustomizablePage/CustomizablePage.tsx:380:6 react-hooks/exhaustive-deps React Hook useEffect has a missing dependency: 'initializeCustomizeStore'. Either include it or remove the dependency array.
🟡 src/pages/DataMarketplacePage/DataMarketplacePage.component.tsx:39:1 openmetadata-imports/no-cross-page-imports Page features must not import another page feature. Move shared code to components, hooks, interfaces, or pure utilities.
🟡 src/pages/MyDataPage/MyDataPage.component.tsx:50:1 openmetadata-imports/no-cross-page-imports Page features must not import another page feature. Move shared code to components, hooks, interfaces, or pure utilities.
🟡 src/pages/MyDataPage/MyDataPage.component.tsx:182:6 react-hooks/exhaustive-deps React Hook useEffect has missing dependencies: 'isWelcomeVisible', 'updateWelcomeScreen', and 'usernameExistsInCookie'. Either include them or remove the depend
🟡 src/pages/MyDataPage/MyDataPage.test.tsx:82:9 jsx-a11y/click-events-have-key-events Visible, non-interactive elements with click handlers must have at least one keyboard listener.
🟡 src/pages/MyDataPage/MyDataPage.test.tsx:82:9 jsx-a11y/no-static-element-interactions Avoid non-native interactive elements. If using native HTML is not possible, add an appropriate role and support for tabbing, mouse, keyboard, and touch inputs

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

make ui-checkstyle-changed

With isLoading = !hasMounted, RTL flushes the mount effect synchronously
so isLoading resolves to false before the async React Query fetch completes.
waitFor(() => isLoading === false) passed immediately, leaving customizedPage
and navigation still null when the assertions ran.

Gate the waitFor on the data assertions instead, which correctly block until
the mock promise resolves. Also remove a stale comment in the no-persona test.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Rohit0301 and others added 6 commits August 17, 2026 16:42
When a persona layout, navigation, background colour, or app mode is saved
via CustomizablePage, the write reaches the docStore API but the shared
['docStore', fqn] React Query cache was never updated. Consumers such as
useCustomPages and MyDataPage kept serving the pre-save data for up to
five minutes (PERSONA_DOC_STALE_TIME).

After each successful write, call queryClient.setQueryData with the fresh
API response so all cache subscribers immediately see the updated doc.
Using setQueryData avoids an extra round-trip because we already hold the
authoritative response from the mutation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Comment thread openmetadata-ui/src/main/resources/ui/src/hooks/useResolvedAppMode.ts Outdated
@sonarqubecloud

Copy link
Copy Markdown

@github-actions

Copy link
Copy Markdown
Contributor

🚦 Removed from the merge queue — manual (2026-08-18T20:04:29Z)

Blocked the queue: ui-coverage

@gitar-bot

gitar-bot Bot commented Aug 19, 2026

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

Deduplicates persona docStore requests across UI components using a shared React Query cache key and proper staleTime configuration, successfully resolving all redundant network calls. No issues found.

✅ 7 resolved
Quality: Persona FQN construction duplicated across two consumers

📄 openmetadata-ui/src/main/resources/ui/src/pages/MyDataPage/MyDataPage.component.tsx:88-90 📄 openmetadata-ui/src/main/resources/ui/src/hooks/useCustomPages.ts:26-28 📄 openmetadata-ui/src/main/resources/ui/src/rest/queries/docStoreQuery.ts:28-31
Both MyDataPage.component.tsx (lines 88-90) and useCustomPages.ts (lines 26-28) rebuild the persona FQN with the identical ${EntityType.PERSONA}${FQN_SEPARATOR_CHAR}${...} expression. Since the whole point of docStoreQuery.ts is a single normalized cache slot, the key derivation should also be centralized there (e.g. a personaDocFqn(persona) helper) so both consumers can't drift apart and produce mismatched keys that would silently break the intended request deduplication.

Quality: useQuery docStore block duplicated across two hooks

📄 openmetadata-ui/src/main/resources/ui/src/hooks/useSidebarItems.ts:26-34 📄 openmetadata-ui/src/main/resources/ui/src/hooks/useCustomPages.ts:24-36
The exact useQuery config for the shared docStore key (queryKey/queryFn/enabled/retry plus the personaDocFqn(selectedPersona) derivation) is now copy-pasted between useSidebarItems.ts (lines 26-34) and useCustomPages.ts (lines 24-36). This is functionally correct and dedupes as intended, but the two blocks can drift (e.g. one adding staleTime and the other not, which would change caching behavior). Consider extracting a small shared hook like usePersonaDoc() returning { doc, isPending, isError } so both consumers stay in lockstep.

Performance: No staleTime means staggered mounts still refetch docStore

📄 openmetadata-ui/src/main/resources/ui/src/hooks/useCustomPages.ts:31-36 📄 openmetadata-ui/src/main/resources/ui/src/pages/MyDataPage/MyDataPage.component.tsx:89-94
React Query's guaranteed deduplication only covers requests that overlap in-flight. Since neither useQuery config sets staleTime, the default of 0 marks the cached persona doc stale immediately, so any consumer that mounts after the first request settles (e.g. sidebar vs. page body mounting at slightly different times, or a remount within the same session) triggers a fresh background refetch rather than reusing the cache. This weakens the PR's "exactly one request per /my-data navigation" guarantee to "one request only while mounts overlap." Consider adding a shared staleTime (e.g. a few minutes) in docStoreQuery.ts or both configs so the cached document is reused across non-concurrent subscribers.

Quality: Unused isPending destructured after isLoading simplification

📄 openmetadata-ui/src/main/resources/ui/src/hooks/useCustomPages.ts:31 📄 openmetadata-ui/src/main/resources/ui/src/hooks/useCustomPages.ts:66
isPending is still destructured from useQuery at line 31 but the only remaining reference is inside a comment — the code that used it (!!fqn && isPending) was removed from isLoading. This leaves a dead binding that will trip @typescript-eslint/no-unused-vars and can fail lint/CI. Remove isPending from the destructure.

Edge Case: App mode never resolves if defaultPersona lacks FQN

📄 openmetadata-ui/src/main/resources/ui/src/hooks/useResolvedAppMode.ts:166-180 📄 openmetadata-ui/src/main/resources/ui/src/hooks/useResolvedAppMode.ts:195-197
In useResolvedAppMode, the query is now enabled: hasDefaultPersona && !!personaFqn, but hasDefaultPersona is still Boolean(defaultPersonaId && defaultPersonaName) and the resolve effect early-returns while hasDefaultPersona && isPersonaPending. In React Query v5 a disabled query keeps isPending === true. So if a user's defaultPersona has an id and name but no fullyQualifiedName, the query stays disabled/pending forever and the effect returns on every run — app mode is never written (resolver deadlocks). Previously the query was enabled purely on hasDefaultPersona and derived the FQN from name, so this case resolved. Gate the effect on the same condition, e.g. treat the persona as absent when personaFqn is null (make hasDefaultPersona require personaFqn) or skip the pending wait when the query is disabled.

...and 2 more resolved from earlier reviews

Options

Display: compact → Showing less information.

Comment with these commands to change the behavior for this request:

Compact
gitar display:verbose         

Was this helpful? React with 👍 / 👎 | Powered by Gitar — free for open source

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 UI UI specific issues

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants