From 77805ef32399c243d8a53610878497ab11a06b48 Mon Sep 17 00:00:00 2001 From: platex-rehor-bot Date: Fri, 21 Aug 2026 13:50:39 +0000 Subject: [PATCH] fix(list-page): exclude optional resources from fatal loadError OCPBUGS-111081 When the Firehose component was replaced by hooks in MultiListPage (CONSOLE-5026), the loadError computation lost the check that excluded resources declared with optional: true from contributing to the page's fatal error. This caused the Installed Operators page to show a "catalogsources forbidden" error for users whose RBAC is delivered via namespace-scoped RoleBindings only, since the cluster-scoped CatalogSource fetch (declared optional) would fail and its error would be promoted to a fatal page error. The fix cross-references each watched resource's key against the watchResources input config to check the optional flag, restoring the 4.21 behavior where optional resource errors are silently tolerated. Co-Authored-By: Claude Opus 4.6 --- .../factory/__tests__/list-page.spec.tsx | 54 +++++++++++++++++++ .../public/components/factory/list-page.tsx | 11 ++-- 2 files changed, 61 insertions(+), 4 deletions(-) diff --git a/frontend/public/components/factory/__tests__/list-page.spec.tsx b/frontend/public/components/factory/__tests__/list-page.spec.tsx index 0f383c88d66..5dcf568980e 100644 --- a/frontend/public/components/factory/__tests__/list-page.spec.tsx +++ b/frontend/public/components/factory/__tests__/list-page.spec.tsx @@ -221,4 +221,58 @@ describe('MultiListPage component', () => { expect(watchConfig?.Pod?.kind).toBe('Pod'); expect(watchConfig?.Pod?.name).toBe('example-pod'); }); + + it('does not treat errors from optional resources as fatal page errors', () => { + let receivedLoadError: unknown; + const ListComponent = (props: { loadError?: unknown }) => { + receivedLoadError = props.loadError; + return
List Content
; + }; + + mockUseK8sWatchResources.mockReturnValue({ + Pod: { data: [{ kind: 'Pod' }], loaded: true, loadError: undefined }, + CatalogSource: { + data: [], + loaded: true, + loadError: 'catalogsources.operators.coreos.com is forbidden', + }, + }); + + renderWithProviders( + , + ); + + expect(screen.getByText('List Content')).toBeVisible(); + expect(receivedLoadError).toBeUndefined(); + }); + + it('treats errors from non-optional resources as fatal page errors', () => { + let receivedLoadError: unknown; + const ListComponent = (props: { loadError?: unknown }) => { + receivedLoadError = props.loadError; + return
List Content
; + }; + + const forbiddenError = 'pods is forbidden'; + mockUseK8sWatchResources.mockReturnValue({ + Pod: { data: [], loaded: true, loadError: forbiddenError }, + }); + + renderWithProviders( + , + ); + + expect(receivedLoadError).toBe(forbiddenError); + }); }); diff --git a/frontend/public/components/factory/list-page.tsx b/frontend/public/components/factory/list-page.tsx index 0050c5a1e54..e4331efdc95 100644 --- a/frontend/public/components/factory/list-page.tsx +++ b/frontend/public/components/factory/list-page.tsx @@ -604,10 +604,13 @@ export const MultiListPage: FC = (props) => { const loadError = useMemo( () => - Object.values(watchedResources).find( - (r) => r.loadError && !(r.loadError instanceof NoModelError), - )?.loadError, - [watchedResources], + Object.entries(watchedResources).find( + ([key, r]) => + r.loadError && + !(r.loadError instanceof NoModelError) && + !watchResources[key]?.optional, + )?.[1]?.loadError, + [watchedResources, watchResources], ); const reduxIDs = useMemo(