perf(ui): deduplicate docStore persona requests via shared React Query key - #31300
perf(ui): deduplicate docStore persona requests via shared React Query key#31300Rohit0301 wants to merge 5 commits into
Conversation
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>
❌ PR checklist incompleteThis PR cannot be merged until the following are addressed on its linked issue:
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 |
…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
left a comment
There was a problem hiding this comment.
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.
🔴 Playwright Results — workflow failedValidated commit ✅ 876 passed · ❌ 1 failed · 🟡 1 flaky · ⏭️ 1 skipped · 🧰 0 lifecycle flaky PerformanceBlocking 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) 58m 7s ⏱️ Max setup 3m 12s · max shard execution 19m 36s · max shard-job elapsed before upload 25m 2s · reporting 6s 🌐 198.87 requests/attempt · 2.44 app boots/UI scenario · 38.69% common-shard skew Optimization targets still in progress:
Genuine Failures (failed on all attempts)❌
|
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>
Code Review ✅ Approved 1 resolved / 1 findingsCentralizes persona FQN construction in a shared helper and migrates docStore requests to React Query to eliminate duplicate network calls, addressing the duplicate persona FQN construction finding. ✅ 1 resolved✅ Quality: Persona FQN construction duplicated across two consumers
OptionsDisplay: compact → Showing less information. Comment with these commands to change the behavior for this request:
Was this helpful? React with 👍 / 👎 | Powered by Gitar — free for open source |
|
| Count | Rule |
|---|---|
| 16 | i18next/no-literal-string |
| 10 | sonarjs/no-duplicate-string |
| 1 | jsx-a11y/anchor-is-valid |
| 1 | react-hooks/exhaustive-deps |
| 1 | jsx-a11y/click-events-have-key-events |
| 1 | jsx-a11y/no-static-element-interactions |
All findings
| Location | Rule | Message | |
|---|---|---|---|
| 🟡 | src/components/Glossary/GlossaryDetails/GlossaryDetails.test.tsx:24:39 |
i18next/no-literal-string |
disallow literal string: GlossaryTermTab.component |
| 🟡 | src/components/Glossary/GlossaryDetails/GlossaryDetails.test.tsx:27:39 |
i18next/no-literal-string |
disallow literal string: GlossaryHeader.component |
| 🟡 | src/components/Glossary/GlossaryDetails/GlossaryDetails.test.tsx:47:36 |
i18next/no-literal-string |
disallow literal string: testActivityFeedTab |
| 🟡 | src/components/Glossary/GlossaryDetails/GlossaryDetails.test.tsx:52:43 |
i18next/no-literal-string |
disallow literal string: Description |
| 🟡 | src/components/Glossary/GlossaryDetails/GlossaryDetails.test.tsx:95:55 |
i18next/no-literal-string |
disallow literal string: GenericTab |
| 🟡 | 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/components/Glossary/GlossaryV1.test.tsx:83:38 |
i18next/no-literal-string |
disallow literal string: <>Glossary-Details component</> |
| 🟡 | src/components/Glossary/GlossaryV1.test.tsx:87:38 |
i18next/no-literal-string |
disallow literal string: <>Glossary-Term component</> |
| 🟡 | src/components/Glossary/GlossaryV1.test.tsx:91:38 |
i18next/no-literal-string |
disallow literal string: <>TitleBreadcrumb</> |
| 🟡 | src/components/Glossary/GlossaryV1.test.tsx:95:34 |
i18next/no-literal-string |
disallow literal string: Breadcrumb |
| 🟡 | src/components/Glossary/GlossaryV1.test.tsx:103:39 |
i18next/no-literal-string |
disallow literal string: FeedEditor |
| 🟡 | src/hooks/useCustomPages.test.ts:48:25 |
sonarjs/no-duplicate-string |
Define a constant instead of duplicating this literal 3 times. |
| 🟡 | src/hooks/useCustomPages.test.ts:97:55 |
sonarjs/no-duplicate-string |
Define a constant instead of duplicating this literal 3 times. |
| 🟡 | src/pages/MyDataPage/MyDataPage.component.tsx:169: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:49:50 |
i18next/no-literal-string |
disallow literal string: Loader |
| 🟡 | src/pages/MyDataPage/MyDataPage.test.tsx:61:50 |
i18next/no-literal-string |
disallow literal string: MyDataPageSkeleton |
| 🟡 | 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 |
| 🟡 | src/pages/MyDataPage/MyDataPage.test.tsx:82:32 |
i18next/no-literal-string |
disallow literal string: WelcomeScreen |
| 🟡 | src/pages/MyDataPage/MyDataPage.test.tsx:93:58 |
i18next/no-literal-string |
disallow literal string: CustomiseLandingPageHeader |
| 🟡 | src/pages/MyDataPage/MyDataPage.test.tsx:153:11 |
sonarjs/no-duplicate-string |
Define a constant instead of duplicating this literal 7 times. |
| 🟡 | src/pages/MyDataPage/MyDataPage.test.tsx:171:45 |
i18next/no-literal-string |
disallow literal string: <>LimitWrapper{children}</> |
| 🟡 | src/pages/MyDataPage/MyDataPage.test.tsx:202:49 |
i18next/no-literal-string |
disallow literal string: Link |
| 🟡 | src/pages/MyDataPage/MyDataPage.test.tsx:271:26 |
sonarjs/no-duplicate-string |
Define a constant instead of duplicating this literal 6 times. |
| 🟡 | src/pages/MyDataPage/MyDataPage.test.tsx:288:31 |
sonarjs/no-duplicate-string |
Define a constant instead of duplicating this literal 4 times. |
| 🟡 | src/pages/MyDataPage/MyDataPage.test.tsx:291:31 |
sonarjs/no-duplicate-string |
Define a constant instead of duplicating this literal 4 times. |
| 🟡 | src/pages/MyDataPage/MyDataPage.test.tsx:298:33 |
sonarjs/no-duplicate-string |
Define a constant instead of duplicating this literal 4 times. |
| 🟡 | src/pages/MyDataPage/MyDataPage.test.tsx:299:33 |
sonarjs/no-duplicate-string |
Define a constant instead of duplicating this literal 4 times. |
| 🟡 | src/pages/MyDataPage/MyDataPage.test.tsx:300:33 |
sonarjs/no-duplicate-string |
Define a constant instead of duplicating this literal 4 times. |
| 🟡 | src/pages/MyDataPage/MyDataPage.test.tsx:363:31 |
sonarjs/no-duplicate-string |
Define a constant instead of duplicating this literal 5 times. |
Fix locally (fast - only checks files changed in this branch):
make ui-checkstyle-changed
Describe your changes:
I worked on eliminating three redundant identical GET requests to
/api/v1/docStore/name/persona.*that fired on every/my-datanavigation (543ms total wasted, all 404s) becauseMyDataPageand multipleuseCustomPagesconsumers each fetched independently with no cache coordination.Root cause:
useCustomPagesused a manualuseState/useCallback/useEffectpattern, andMyDataPagehad its own separatefetchDocument()+useEffect. Neither used React Query, so concurrent subscribers for the same persona FQN each fired an independent network request.Fix:
rest/queries/docStoreQuery.ts— shareddocStoreQueryKey(fqn)+docStoreQueryFn(fqn)following the existingtableQuery.tspatternuseCustomPagesfrom manual fetch touseQuerywith the shared key;pageTypefiltering moves into the return value (no longer triggers a re-fetch on pageType change — it filters from the cached doc)MyDataPage'sfetchDocument()+useEffectwithuseQueryusing the same key;layoutandpersonaPreferencesderived viauseMemoWith React Query's in-flight deduplication, all concurrent subscribers to
['docStore', 'persona.X']share exactly one network request.Type of change:
High-level design:
The existing
rest/queries/pattern (e.g.tableQuery.ts,dashboardQuery.ts) exports a canonicalqueryKey+queryFnpair so any consumer — detail page, sidebar widget, hover prefetch — hits the same normalised cache slot.docStoreQuery.tsadds the same plumbing for DocStore documents.useCustomPagespreviously re-fetched the full persona document on everypageTypechange 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.MyDataPagepreviously had a duplicate fetch path independent ofuseCustomPages. Both now share the same React Query cache slot (['docStore', 'persona.X']), so on/my-datanavigation the document is fetched exactly once regardless of how many consumers are mounted.Tests:
Use cases covered
/my-datapage loads with a persona selected — one GET todocStore/name/persona.*instead of threeuseSidebarItems→useCustomPages('Navigation')) and page body share the cached response[], customizedPage resets tonull— same contract as beforepageTypebetween renders filters from the cache without a network round-tripUnit tests
useCustomPages.test.ts— wrapped withQueryClientProvider, updated "pageType changes" test to assert one fetch (not two), all other assertions preservedopenmetadata-ui/src/main/resources/ui/src/hooks/useCustomPages.test.tsBackend integration tests
Ingestion integration tests
Playwright (UI) tests
Manual testing performed
/my-datawith a persona assigneddocStore/api/v1/docStore/name/persona.*fires (was 3 before this change)UI screen recording / screenshots:
Not applicable — no visual change; the fix is a network deduplication at the data-fetching layer.
Checklist:
Fixes <issue-number>: <short explanation>Fixes #<issue-number>above.