fix(ui): stop deep links 404ing on cold boot and fix incident table column sizing - #31301
Conversation
…olumn sizing Three defects surfaced while reviewing the AI-mode Observability pages. All three live in shared UI, so Classic mode benefits from the last two as well. **Deep links / reloads landing on /404** `AppRouter` resolved routes as `ModeRoutes ?? AuthenticatedRoutes`. A non-default app mode registers its routes from the plugin that owns the mode, in an effect gated on `applications` loading, so for the first few renders the registry is legitimately empty. Falling through to the default routes in that window mounts their `path="*"` catch-all, which navigates to /404 and destroys the requested URL before the real routes ever mount — non-deterministically, depending on which request settles first. `useResolvedAppMode` already computes a `registrySettled` flag for exactly this window (it deliberately refuses to clear a valid session before it flips). It now returns that flag, and `AppRouter` holds a loader while a non-default mode is active but unregistered. Once settled and still unregistered — the plugin is genuinely uninstalled — it falls back to the default routes as before. The loader sits INSIDE `AuthenticatedApp` on purpose: `ApplicationsProvider` lives there and is what flips `applicationsLoaded`, so short-circuiting above it would deadlock. **Incident table: a long test case name stretched the name column** The cell declares `w-72`, but the table lays out `auto` and `overflow-wrap: break-word` does not shrink a word's min-content contribution — so the column grew to fit the longest name (~900px on a 1033px container). `wrap-anywhere` does shrink it; the floor is pinned to the same 18rem so auto-layout cannot then collapse the column and wrap every name onto three lines. **Incident table: the Assignee column collapsed when every row was unassigned** antd's `.ant-typography` sets `word-break: break-word`, which drops the "No Assignee" placeholder's min-content contribution to a single character. Owner names already render nowrap + ellipsis, so once every row is unassigned nothing holds the column open and it collapsed until the placeholder stacked one letter per line. The cell is now `whitespace-nowrap`, matching the Last Updated cell. Tests: two new AppRouter cases covering the registration window; both fail without the router change. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The provider derives its filters from the query string, which is global. A host that keeps the page mounted while routing elsewhere — AI mode caches visited routes — leaves a backgrounded Data Quality page re-deriving its filters from whatever route now owns the URL. Its filter keys (`testPlatforms`, `tags`, `serviceName`, …) overlap with the Test Library's, so applying a Test Library filter made the hidden page refetch with another page's values, flip its loading flag, and remount the whole dashboard — 26 stray requests per filter click, plus an error toast from the generic catch when one of them failed. `isActive` defaults to true, so hosts that unmount the page on navigation are unaffected. Re-running the effect on re-activation is intentional: it revalidates against the page's real filters when it comes back. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Added a fourth commit: Problem. const filterParams = omit(QueryString.parse(location.search), ['currentPage','pageSize','searchValue'])
useEffect(() => { fetchTestSummary(filterParams) }, [filterKey])Its filter keys — Captured off the wire — the hidden Data Quality page issuing an ES query built from the Test Library's filter: {"query":{"bool":{"must":[{"terms":{"testPlatforms":"GreatExpectations"}}, …(Note That call also flips Change. An optional Measured on the same repro after wiring it up downstream: 28 requests → 1, no remount, and navigating back still revalidates. |
❌ 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 |
|
| Count | Rule |
|---|---|
| 3 | sonarjs/no-duplicate-string |
| 2 | sonarjs/cyclomatic-complexity |
| 1 | react/no-array-index-key |
| 1 | sonarjs/cognitive-complexity |
| 1 | react-hooks/exhaustive-deps |
All findings
| Location | Rule | Message | |
|---|---|---|---|
| 🟡 | src/components/AppRouter/AppRouter.test.tsx:135:33 |
sonarjs/no-duplicate-string |
Define a constant instead of duplicating this literal 7 times. |
| 🟡 | src/components/AppRouter/AppRouter.test.tsx:137:33 |
sonarjs/no-duplicate-string |
Define a constant instead of duplicating this literal 5 times. |
| 🟡 | src/components/IncidentManager/IncidentManagerTable.component.tsx:114:23 |
sonarjs/no-duplicate-string |
Define a constant instead of duplicating this literal 3 times. |
| 🟡 | src/components/IncidentManager/IncidentManagerTable.component.tsx:145:58 |
react/no-array-index-key |
Do not use Array index in keys |
| 🟡 | src/components/IncidentManager/IncidentManagerTable.component.tsx:152:56 |
sonarjs/cyclomatic-complexity |
{"message":"Function has a complexity of 11 which is greater than 10 authorized.","cost":1,"secondaryLocations":[{"line":152,"column":55,"endLine":152,"endColum |
| 🟡 | src/hooks/useResolvedAppMode.ts:168:16 |
sonarjs/cognitive-complexity |
Refactor this function to reduce its Cognitive Complexity from 20 to the 15 allowed. |
| 🟡 | src/hooks/useResolvedAppMode.ts:168:16 |
sonarjs/cyclomatic-complexity |
{"message":"Function has a complexity of 23 which is greater than 10 authorized.","cost":13,"secondaryLocations":[{"line":168,"column":15,"endLine":168,"endColu |
| 🟡 | src/pages/DataQuality/DataQualityProvider.tsx:177:6 |
react-hooks/exhaustive-deps |
React Hook useEffect has missing dependencies: 'filterParams' and 'testCasePermission'. Either include them or remove the dependency array. |
Fix locally (fast - only checks files changed in this branch):
make ui-checkstyle-changed
|
af573dd
…olumn sizing (#31301) * fix(ui): stop deep links 404ing on cold boot and fix incident table column sizing Three defects surfaced while reviewing the AI-mode Observability pages. All three live in shared UI, so Classic mode benefits from the last two as well. **Deep links / reloads landing on /404** `AppRouter` resolved routes as `ModeRoutes ?? AuthenticatedRoutes`. A non-default app mode registers its routes from the plugin that owns the mode, in an effect gated on `applications` loading, so for the first few renders the registry is legitimately empty. Falling through to the default routes in that window mounts their `path="*"` catch-all, which navigates to /404 and destroys the requested URL before the real routes ever mount — non-deterministically, depending on which request settles first. `useResolvedAppMode` already computes a `registrySettled` flag for exactly this window (it deliberately refuses to clear a valid session before it flips). It now returns that flag, and `AppRouter` holds a loader while a non-default mode is active but unregistered. Once settled and still unregistered — the plugin is genuinely uninstalled — it falls back to the default routes as before. The loader sits INSIDE `AuthenticatedApp` on purpose: `ApplicationsProvider` lives there and is what flips `applicationsLoaded`, so short-circuiting above it would deadlock. **Incident table: a long test case name stretched the name column** The cell declares `w-72`, but the table lays out `auto` and `overflow-wrap: break-word` does not shrink a word's min-content contribution — so the column grew to fit the longest name (~900px on a 1033px container). `wrap-anywhere` does shrink it; the floor is pinned to the same 18rem so auto-layout cannot then collapse the column and wrap every name onto three lines. **Incident table: the Assignee column collapsed when every row was unassigned** antd's `.ant-typography` sets `word-break: break-word`, which drops the "No Assignee" placeholder's min-content contribution to a single character. Owner names already render nowrap + ellipsis, so once every row is unassigned nothing holds the column open and it collapsed until the placeholder stacked one letter per line. The cell is now `whitespace-nowrap`, matching the Last Updated cell. Tests: two new AppRouter cases covering the registration window; both fail without the router change. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(ui): let a host freeze DataQualityProvider's URL tracking The provider derives its filters from the query string, which is global. A host that keeps the page mounted while routing elsewhere — AI mode caches visited routes — leaves a backgrounded Data Quality page re-deriving its filters from whatever route now owns the URL. Its filter keys (`testPlatforms`, `tags`, `serviceName`, …) overlap with the Test Library's, so applying a Test Library filter made the hidden page refetch with another page's values, flip its loading flag, and remount the whole dashboard — 26 stray requests per filter click, plus an error toast from the generic catch when one of them failed. `isActive` defaults to true, so hosts that unmount the page on navigation are unaffected. Re-running the effect on re-activation is intentional: it revalidates against the page's real filters when it comes back. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: Harshit Shah <harshit.shah@getcollate.io>
Code Review 👍 Approved with suggestions 0 resolved / 1 findingsFixes cold-boot 404s on deep links and corrects incident table column sizing with robust wrapping and layout constraints. Consider adding 💡 Bug: getAppDefaultMode() read in effect but not a dependency📄 openmetadata-ui/src/main/resources/ui/src/hooks/useResolvedAppMode.ts:259-263 The effect reads 🤖 Prompt for agentsOptionsDisplay: 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 |
…olumn sizing (open-metadata#31301) * fix(ui): stop deep links 404ing on cold boot and fix incident table column sizing Three defects surfaced while reviewing the AI-mode Observability pages. All three live in shared UI, so Classic mode benefits from the last two as well. **Deep links / reloads landing on /404** `AppRouter` resolved routes as `ModeRoutes ?? AuthenticatedRoutes`. A non-default app mode registers its routes from the plugin that owns the mode, in an effect gated on `applications` loading, so for the first few renders the registry is legitimately empty. Falling through to the default routes in that window mounts their `path="*"` catch-all, which navigates to /404 and destroys the requested URL before the real routes ever mount — non-deterministically, depending on which request settles first. `useResolvedAppMode` already computes a `registrySettled` flag for exactly this window (it deliberately refuses to clear a valid session before it flips). It now returns that flag, and `AppRouter` holds a loader while a non-default mode is active but unregistered. Once settled and still unregistered — the plugin is genuinely uninstalled — it falls back to the default routes as before. The loader sits INSIDE `AuthenticatedApp` on purpose: `ApplicationsProvider` lives there and is what flips `applicationsLoaded`, so short-circuiting above it would deadlock. **Incident table: a long test case name stretched the name column** The cell declares `w-72`, but the table lays out `auto` and `overflow-wrap: break-word` does not shrink a word's min-content contribution — so the column grew to fit the longest name (~900px on a 1033px container). `wrap-anywhere` does shrink it; the floor is pinned to the same 18rem so auto-layout cannot then collapse the column and wrap every name onto three lines. **Incident table: the Assignee column collapsed when every row was unassigned** antd's `.ant-typography` sets `word-break: break-word`, which drops the "No Assignee" placeholder's min-content contribution to a single character. Owner names already render nowrap + ellipsis, so once every row is unassigned nothing holds the column open and it collapsed until the placeholder stacked one letter per line. The cell is now `whitespace-nowrap`, matching the Last Updated cell. Tests: two new AppRouter cases covering the registration window; both fail without the router change. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(ui): let a host freeze DataQualityProvider's URL tracking The provider derives its filters from the query string, which is global. A host that keeps the page mounted while routing elsewhere — AI mode caches visited routes — leaves a backgrounded Data Quality page re-deriving its filters from whatever route now owns the URL. Its filter keys (`testPlatforms`, `tags`, `serviceName`, …) overlap with the Test Library's, so applying a Test Library filter made the hidden page refetch with another page's values, flip its loading flag, and remount the whole dashboard — 26 stray requests per filter click, plus an error toast from the generic catch when one of them failed. `isActive` defaults to true, so hosts that unmount the page on navigation are unaffected. Re-running the effect on re-activation is intentional: it revalidates against the page's real filters when it comes back. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com> Co-authored-by: Harshit Shah <harshit.shah@getcollate.io>



Problem
Three defects found while reviewing the AI-mode Observability pages. All three are in shared UI — the last two fix Classic mode too.
1. Deep links and reloads land on
/404AppRouterresolves routes asModeRoutes ?? AuthenticatedRoutes. A non-default app mode's routes are registered by the plugin that owns the mode, in an effect gated onapplicationsloading — so for the first renders the registry is legitimately empty. Falling through to the default routes in that window mounts theirpath="*"catch-all:which navigates to
/404and destroys the requested URL before the real routes ever mount. It is a race, so it reproduces intermittently — roughly half of hard loads locally.2. A long test case name stretches the Incident Manager's name column
The cell declares
w-72, but the table lays outautoandoverflow-wrap: break-worddoes not reduce a word's min-content contribution. The column grew to the full width of the longest name — 900px on a 1033px container.3. The Assignee column collapses when every row is unassigned
antd's
.ant-typographysetsword-break: break-word, which drops the "No Assignee" placeholder's min-content contribution to a single character. Owner names already render nowrap + ellipsis, so once every row is unassigned (e.g. filtering toNew) nothing holds the column open and the placeholder stacks one letter per line.Solution
Routing.
useResolvedAppModealready computes aregistrySettledflag for exactly this window — it deliberately refuses to clear a valid session before the flag flips. It now returns that flag, andAppRouterholds a loader while a non-default mode is active but unregistered. Once settled and still unregistered (plugin genuinely uninstalled) it falls back to the default routes exactly as before.The loader sits inside
AuthenticatedAppdeliberately:ApplicationsProviderlives there and is what flipsapplicationsLoaded. Short-circuiting above it deadlocks — the routes could never register, so the loader would never clear. There is a comment at the call site to stop that being "simplified" later.Table.
wrap-anywhereon the name link (it does shrink min-content) with the floor pinned to the same 18rem the cell declares, so auto-layout cannot then collapse the column.whitespace-nowrapon the assignee cell, matching the Last Updated cell in the same file.Measurements
Tests
Two new
AppRoutercases covering the registration window. Both were confirmed failing with the router change reverted (2 failed, 5 passed) and passing with it, so they are real regression coverage.yarn jest src/components/AppRouter src/hooks/useResolvedAppMode src/hooks/useAppMode— 11 suites, 112 passedyarn jest src/components/IncidentManager/IncidentManagerTable.component.test.tsx— 10 passedType of change
🤖 Generated with Claude Code
Summary by Gitar
isActiveprop to freeze URL tracking and avoid background refetches against shared query parameters in cached routesThis will update automatically on new commits.