From 76c7a5bc1f766e8ba00dceca1320847e176e17f1 Mon Sep 17 00:00:00 2001 From: "gandalf-the-greybeard[bot]" <266323920+gandalf-the-greybeard[bot]@users.noreply.github.com> Date: Tue, 17 Mar 2026 17:06:14 +0000 Subject: [PATCH] fix: badge navigation uses window.location.pathname for cluster extraction * fix: badge navigation uses window.location + correct settings plugin name - AppBarScoreBadge: Read cluster from window.location.pathname instead of useCluster() (returns null in AppBar context) or useLocation() (may not reflect cluster prefix outside cluster route context) - registerPluginSettings: Use 'polaris' to match the deployed directory name (plugin is at static-plugins/polaris, not headlamp-polaris) - Add unit test for no-cluster fallback navigation Supersedes the source-code fixes from PR #55 without the workflow/deploy script changes that broke CI. Co-Authored-By: Paperclip * fix: use Object.defineProperty for window.location in test Replace `as Location` cast with Object.defineProperty to match the existing beforeEach pattern and fix TypeScript strict mode error. Co-Authored-By: Paperclip --------- Co-authored-by: Gandalf the Greybeard Co-authored-by: Paperclip --- src/components/AppBarScoreBadge.test.tsx | 46 +++++++++++++++++++----- src/components/AppBarScoreBadge.tsx | 18 ++++++++-- src/index.tsx | 2 +- 3 files changed, 54 insertions(+), 12 deletions(-) diff --git a/src/components/AppBarScoreBadge.test.tsx b/src/components/AppBarScoreBadge.test.tsx index 75616ca..dd55067 100644 --- a/src/components/AppBarScoreBadge.test.tsx +++ b/src/components/AppBarScoreBadge.test.tsx @@ -7,13 +7,7 @@ import { makeAuditData, makeResult } from '../test-utils'; // Mock Headlamp lib vi.mock('@kinvolk/headlamp-plugin/lib', () => ({ ApiProxy: { request: vi.fn() }, - K8s: { - useCluster: () => 'test-cluster', - }, - Router: { - createRouteURL: (name: string, params?: { cluster?: string }) => - `/c/${params?.cluster ?? 'default'}/${name}`, - }, + K8s: {}, })); vi.mock('@mui/material/styles', () => ({ @@ -31,6 +25,15 @@ vi.mock('react-router-dom', () => ({ useHistory: () => ({ push: mockPush }), })); +// Set window.location.pathname for cluster extraction +beforeEach(() => { + Object.defineProperty(window, 'location', { + value: { pathname: '/c/test-cluster/some-page' }, + writable: true, + }); + mockPush.mockClear(); +}); + const mockUsePolarisDataContext = vi.fn(); vi.mock('../api/PolarisDataContext', () => ({ usePolarisDataContext: () => mockUsePolarisDataContext(), @@ -97,7 +100,7 @@ describe('AppBarScoreBadge', () => { expect(button.style.backgroundColor).toBe('rgb(244, 67, 54)'); }); - it('navigates to /polaris on click', async () => { + it('navigates to /c//polaris on click', async () => { const user = userEvent.setup(); const data = makeAuditData([ makeResult({ @@ -120,6 +123,33 @@ describe('AppBarScoreBadge', () => { expect(mockPush).toHaveBeenCalledWith('/c/test-cluster/polaris'); }); + it('navigates to /polaris when no cluster in URL', async () => { + Object.defineProperty(window, 'location', { + value: { pathname: '/settings' }, + writable: true, + }); + const user = userEvent.setup(); + const data = makeAuditData([ + makeResult({ + Results: { + c1: { + ID: 'c1', + Message: '', + Details: [], + Success: true, + Severity: 'warning', + Category: 'X', + }, + }, + }), + ]); + mockUsePolarisDataContext.mockReturnValue({ data, loading: false }); + + render(); + await user.click(screen.getByRole('button')); + expect(mockPush).toHaveBeenCalledWith('/polaris'); + }); + it('has correct aria-label', () => { const data = makeAuditData([ makeResult({ diff --git a/src/components/AppBarScoreBadge.tsx b/src/components/AppBarScoreBadge.tsx index 02b6bbb..5da51a6 100644 --- a/src/components/AppBarScoreBadge.tsx +++ b/src/components/AppBarScoreBadge.tsx @@ -1,10 +1,21 @@ -import { K8s, Router } from '@kinvolk/headlamp-plugin/lib'; import { useTheme } from '@mui/material/styles'; import React from 'react'; import { useHistory } from 'react-router-dom'; import { computeScore, countResults } from '../api/polaris'; import { usePolarisDataContext } from '../api/PolarisDataContext'; +/** + * Extract the cluster name from the current browser URL. + * Headlamp cluster routes follow the pattern /c//... + * We read window.location.pathname directly because the AppBar renders + * outside the cluster route context, so useCluster() returns null and + * React Router's useLocation() may not reflect the cluster prefix. + */ +function getClusterFromUrl(): string | null { + const match = window.location.pathname.match(/\/c\/([^/]+)/); + return match ? match[1] : null; +} + /** * App bar badge showing cluster Polaris score * Clicking navigates to the overview dashboard @@ -13,7 +24,6 @@ export default function AppBarScoreBadge() { const theme = useTheme(); const { data, loading } = usePolarisDataContext(); const history = useHistory(); - const cluster = K8s.useCluster(); if (loading || !data) { return null; // Graceful degradation when Polaris unavailable @@ -36,7 +46,9 @@ export default function AppBarScoreBadge() { }; const handleClick = () => { - history.push(Router.createRouteURL('polaris', { cluster: cluster ?? '' })); + const cluster = getClusterFromUrl(); + const prefix = cluster ? `/c/${cluster}` : ''; + history.push(`${prefix}/polaris`); }; return ( diff --git a/src/index.tsx b/src/index.tsx index 5a90bbe..e5d068f 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -99,7 +99,7 @@ registerRoute({ }); // Register plugin settings -registerPluginSettings('headlamp-polaris', PolarisSettings, true); +registerPluginSettings('polaris', PolarisSettings, true); // Register details view section for supported controller types registerDetailsViewSection(({ resource }) => {