Skip to content

Commit 501e099

Browse files
fix(scorecard): status icon rendering issue in cluster env (#2866)
* fix(scorecard): status icon rendering issue in cluster env * reusing existing icons
1 parent 8cee7d5 commit 501e099

3 files changed

Lines changed: 150 additions & 1 deletion

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@red-hat-developer-hub/backstage-plugin-scorecard': patch
3+
---
4+
5+
Removed Backstage registration requirement for default Scorecard icons

workspaces/scorecard/plugins/scorecard/src/components/ScorecardIcon/ScorecardIcon.tsx

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,19 @@
1616
import { useApp } from '@backstage/core-plugin-api';
1717
import Box from '@mui/material/Box';
1818
import MuiIcon from '@mui/material/Icon';
19+
import type { SvgIconComponent } from '@mui/icons-material';
1920
import type { SxProps, Theme } from '@mui/material/styles';
21+
import {
22+
ScorecardSuccessStatusIcon,
23+
ScorecardWarningStatusIcon,
24+
ScorecardErrorStatusIcon,
25+
} from '../..';
26+
27+
const builtInIcons: Record<string, SvgIconComponent> = {
28+
scorecardSuccessStatusIcon: ScorecardSuccessStatusIcon,
29+
scorecardWarningStatusIcon: ScorecardWarningStatusIcon,
30+
scorecardErrorStatusIcon: ScorecardErrorStatusIcon,
31+
};
2032

2133
/**
2234
* @public
@@ -40,7 +52,7 @@ export const ScorecardIcon = ({
4052
return null;
4153
}
4254

43-
const SystemIcon = app.getSystemIcon(icon);
55+
const SystemIcon = app.getSystemIcon(icon) ?? builtInIcons[icon];
4456
if (SystemIcon) {
4557
return (
4658
<Box sx={{ display: 'flex', alignItems: 'center', ...sx }}>
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
/*
2+
* Copyright Red Hat, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import { render } from '@testing-library/react';
18+
19+
import { ScorecardIcon } from '../ScorecardIcon';
20+
21+
const mockGetSystemIcon = jest.fn();
22+
23+
jest.mock('@backstage/core-plugin-api', () => ({
24+
...jest.requireActual('@backstage/core-plugin-api'),
25+
useApp: () => ({
26+
getSystemIcon: mockGetSystemIcon,
27+
}),
28+
}));
29+
30+
describe('ScorecardIcon', () => {
31+
beforeEach(() => {
32+
jest.clearAllMocks();
33+
});
34+
35+
it('should return null for empty icon string', () => {
36+
const { container } = render(<ScorecardIcon icon="" />);
37+
expect(container.firstChild).toBeNull();
38+
});
39+
40+
it('should use system icon when registered', () => {
41+
const MockIcon = (props: any) => (
42+
<span data-testid="system-icon" {...props} />
43+
);
44+
mockGetSystemIcon.mockReturnValue(MockIcon);
45+
46+
const { getByTestId } = render(
47+
<ScorecardIcon icon="scorecardSuccessStatusIcon" />,
48+
);
49+
50+
expect(mockGetSystemIcon).toHaveBeenCalledWith(
51+
'scorecardSuccessStatusIcon',
52+
);
53+
expect(getByTestId('system-icon')).toBeInTheDocument();
54+
});
55+
56+
it('should fall back to built-in icon when system icon is not registered', () => {
57+
mockGetSystemIcon.mockReturnValue(undefined);
58+
59+
const { container } = render(
60+
<ScorecardIcon icon="scorecardSuccessStatusIcon" />,
61+
);
62+
63+
expect(mockGetSystemIcon).toHaveBeenCalledWith(
64+
'scorecardSuccessStatusIcon',
65+
);
66+
expect(
67+
container.querySelector('[data-testid="CheckCircleOutlineIcon"]'),
68+
).toBeInTheDocument();
69+
});
70+
71+
it('should fall back to built-in warning icon', () => {
72+
mockGetSystemIcon.mockReturnValue(undefined);
73+
74+
const { container } = render(
75+
<ScorecardIcon icon="scorecardWarningStatusIcon" />,
76+
);
77+
78+
expect(
79+
container.querySelector('[data-testid="WarningAmberIcon"]'),
80+
).toBeInTheDocument();
81+
});
82+
83+
it('should fall back to built-in error icon', () => {
84+
mockGetSystemIcon.mockReturnValue(undefined);
85+
86+
const { container } = render(
87+
<ScorecardIcon icon="scorecardErrorStatusIcon" />,
88+
);
89+
90+
expect(
91+
container.querySelector('[data-testid="DangerousOutlinedIcon"]'),
92+
).toBeInTheDocument();
93+
});
94+
95+
it('should prefer system icon over built-in icon', () => {
96+
const OverrideIcon = (props: any) => (
97+
<span data-testid="override-icon" {...props} />
98+
);
99+
mockGetSystemIcon.mockReturnValue(OverrideIcon);
100+
101+
const { getByTestId, container } = render(
102+
<ScorecardIcon icon="scorecardSuccessStatusIcon" />,
103+
);
104+
105+
expect(getByTestId('override-icon')).toBeInTheDocument();
106+
expect(
107+
container.querySelector('[data-testid="CheckCircleOutlineIcon"]'),
108+
).not.toBeInTheDocument();
109+
});
110+
111+
it('should render URL-based icon as image', () => {
112+
mockGetSystemIcon.mockReturnValue(undefined);
113+
114+
const { container } = render(
115+
<ScorecardIcon icon="https://example.com/icon.png" />,
116+
);
117+
118+
const img = container.querySelector('img');
119+
expect(img).toBeInTheDocument();
120+
expect(img?.getAttribute('src')).toBe('https://example.com/icon.png');
121+
});
122+
123+
it('should render unknown icon string as Material Design ligature', () => {
124+
mockGetSystemIcon.mockReturnValue(undefined);
125+
126+
const { container } = render(<ScorecardIcon icon="settings" />);
127+
128+
const muiIcon = container.querySelector('.material-icons-outlined');
129+
expect(muiIcon).toBeInTheDocument();
130+
expect(muiIcon?.textContent).toBe('settings');
131+
});
132+
});

0 commit comments

Comments
 (0)