From db30b020a1361c2bbb4c65d4c93f9bb682a163e0 Mon Sep 17 00:00:00 2001 From: Eswaraiahsapram Date: Wed, 22 Apr 2026 16:53:19 +0530 Subject: [PATCH 1/2] fix(scorecard): status icon rendering issue in cluster env --- .../scorecard/.changeset/many-frogs-count.md | 5 + .../ScorecardIcon/ScorecardIcon.tsx | 13 +- .../__tests__/ScorecardIcon.test.tsx | 132 ++++++++++++++++++ 3 files changed, 149 insertions(+), 1 deletion(-) create mode 100644 workspaces/scorecard/.changeset/many-frogs-count.md create mode 100644 workspaces/scorecard/plugins/scorecard/src/components/ScorecardIcon/__tests__/ScorecardIcon.test.tsx diff --git a/workspaces/scorecard/.changeset/many-frogs-count.md b/workspaces/scorecard/.changeset/many-frogs-count.md new file mode 100644 index 00000000000..d0932343b24 --- /dev/null +++ b/workspaces/scorecard/.changeset/many-frogs-count.md @@ -0,0 +1,5 @@ +--- +'@red-hat-developer-hub/backstage-plugin-scorecard': patch +--- + +Fix status icon rendering issue in cluster environment diff --git a/workspaces/scorecard/plugins/scorecard/src/components/ScorecardIcon/ScorecardIcon.tsx b/workspaces/scorecard/plugins/scorecard/src/components/ScorecardIcon/ScorecardIcon.tsx index 106ab438045..2afa775137e 100644 --- a/workspaces/scorecard/plugins/scorecard/src/components/ScorecardIcon/ScorecardIcon.tsx +++ b/workspaces/scorecard/plugins/scorecard/src/components/ScorecardIcon/ScorecardIcon.tsx @@ -16,8 +16,19 @@ import { useApp } from '@backstage/core-plugin-api'; import Box from '@mui/material/Box'; import MuiIcon from '@mui/material/Icon'; +import type { SvgIconComponent } from '@mui/icons-material'; import type { SxProps, Theme } from '@mui/material/styles'; +import CheckCircleOutline from '@mui/icons-material/CheckCircleOutline'; +import WarningAmber from '@mui/icons-material/WarningAmber'; +import DangerousOutlined from '@mui/icons-material/DangerousOutlined'; + +const builtInIcons: Record = { + scorecardSuccessStatusIcon: CheckCircleOutline, + scorecardWarningStatusIcon: WarningAmber, + scorecardErrorStatusIcon: DangerousOutlined, +}; + /** * @public */ @@ -40,7 +51,7 @@ export const ScorecardIcon = ({ return null; } - const SystemIcon = app.getSystemIcon(icon); + const SystemIcon = app.getSystemIcon(icon) ?? builtInIcons[icon]; if (SystemIcon) { return ( diff --git a/workspaces/scorecard/plugins/scorecard/src/components/ScorecardIcon/__tests__/ScorecardIcon.test.tsx b/workspaces/scorecard/plugins/scorecard/src/components/ScorecardIcon/__tests__/ScorecardIcon.test.tsx new file mode 100644 index 00000000000..c1bcd972a60 --- /dev/null +++ b/workspaces/scorecard/plugins/scorecard/src/components/ScorecardIcon/__tests__/ScorecardIcon.test.tsx @@ -0,0 +1,132 @@ +/* + * Copyright Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { render } from '@testing-library/react'; + +import { ScorecardIcon } from '../ScorecardIcon'; + +const mockGetSystemIcon = jest.fn(); + +jest.mock('@backstage/core-plugin-api', () => ({ + ...jest.requireActual('@backstage/core-plugin-api'), + useApp: () => ({ + getSystemIcon: mockGetSystemIcon, + }), +})); + +describe('ScorecardIcon', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it('should return null for empty icon string', () => { + const { container } = render(); + expect(container.firstChild).toBeNull(); + }); + + it('should use system icon when registered', () => { + const MockIcon = (props: any) => ( + + ); + mockGetSystemIcon.mockReturnValue(MockIcon); + + const { getByTestId } = render( + , + ); + + expect(mockGetSystemIcon).toHaveBeenCalledWith( + 'scorecardSuccessStatusIcon', + ); + expect(getByTestId('system-icon')).toBeInTheDocument(); + }); + + it('should fall back to built-in icon when system icon is not registered', () => { + mockGetSystemIcon.mockReturnValue(undefined); + + const { container } = render( + , + ); + + expect(mockGetSystemIcon).toHaveBeenCalledWith( + 'scorecardSuccessStatusIcon', + ); + expect( + container.querySelector('[data-testid="CheckCircleOutlineIcon"]'), + ).toBeInTheDocument(); + }); + + it('should fall back to built-in warning icon', () => { + mockGetSystemIcon.mockReturnValue(undefined); + + const { container } = render( + , + ); + + expect( + container.querySelector('[data-testid="WarningAmberIcon"]'), + ).toBeInTheDocument(); + }); + + it('should fall back to built-in error icon', () => { + mockGetSystemIcon.mockReturnValue(undefined); + + const { container } = render( + , + ); + + expect( + container.querySelector('[data-testid="DangerousOutlinedIcon"]'), + ).toBeInTheDocument(); + }); + + it('should prefer system icon over built-in icon', () => { + const OverrideIcon = (props: any) => ( + + ); + mockGetSystemIcon.mockReturnValue(OverrideIcon); + + const { getByTestId, container } = render( + , + ); + + expect(getByTestId('override-icon')).toBeInTheDocument(); + expect( + container.querySelector('[data-testid="CheckCircleOutlineIcon"]'), + ).not.toBeInTheDocument(); + }); + + it('should render URL-based icon as image', () => { + mockGetSystemIcon.mockReturnValue(undefined); + + const { container } = render( + , + ); + + const img = container.querySelector('img'); + expect(img).toBeInTheDocument(); + expect(img?.getAttribute('src')).toBe('https://example.com/icon.png'); + }); + + it('should render unknown icon string as Material Design ligature', () => { + mockGetSystemIcon.mockReturnValue(undefined); + + const { container } = render(); + + const muiIcon = container.querySelector('.material-icons-outlined'); + expect(muiIcon).toBeInTheDocument(); + expect(muiIcon?.textContent).toBe('settings'); + }); +}); From 9fb3f2a3535321786a5e8ec8c6a3cb6d30cd79a8 Mon Sep 17 00:00:00 2001 From: Eswaraiahsapram Date: Thu, 23 Apr 2026 14:31:26 +0530 Subject: [PATCH 2/2] reusing existing icons --- .../scorecard/.changeset/many-frogs-count.md | 2 +- .../components/ScorecardIcon/ScorecardIcon.tsx | 15 ++++++++------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/workspaces/scorecard/.changeset/many-frogs-count.md b/workspaces/scorecard/.changeset/many-frogs-count.md index d0932343b24..b8b1f37aedd 100644 --- a/workspaces/scorecard/.changeset/many-frogs-count.md +++ b/workspaces/scorecard/.changeset/many-frogs-count.md @@ -2,4 +2,4 @@ '@red-hat-developer-hub/backstage-plugin-scorecard': patch --- -Fix status icon rendering issue in cluster environment +Removed Backstage registration requirement for default Scorecard icons diff --git a/workspaces/scorecard/plugins/scorecard/src/components/ScorecardIcon/ScorecardIcon.tsx b/workspaces/scorecard/plugins/scorecard/src/components/ScorecardIcon/ScorecardIcon.tsx index 2afa775137e..59239a917a6 100644 --- a/workspaces/scorecard/plugins/scorecard/src/components/ScorecardIcon/ScorecardIcon.tsx +++ b/workspaces/scorecard/plugins/scorecard/src/components/ScorecardIcon/ScorecardIcon.tsx @@ -18,15 +18,16 @@ import Box from '@mui/material/Box'; import MuiIcon from '@mui/material/Icon'; import type { SvgIconComponent } from '@mui/icons-material'; import type { SxProps, Theme } from '@mui/material/styles'; - -import CheckCircleOutline from '@mui/icons-material/CheckCircleOutline'; -import WarningAmber from '@mui/icons-material/WarningAmber'; -import DangerousOutlined from '@mui/icons-material/DangerousOutlined'; +import { + ScorecardSuccessStatusIcon, + ScorecardWarningStatusIcon, + ScorecardErrorStatusIcon, +} from '../..'; const builtInIcons: Record = { - scorecardSuccessStatusIcon: CheckCircleOutline, - scorecardWarningStatusIcon: WarningAmber, - scorecardErrorStatusIcon: DangerousOutlined, + scorecardSuccessStatusIcon: ScorecardSuccessStatusIcon, + scorecardWarningStatusIcon: ScorecardWarningStatusIcon, + scorecardErrorStatusIcon: ScorecardErrorStatusIcon, }; /**