Skip to content

Commit dd295a2

Browse files
authored
Merge pull request #2 from djanickova/scorecard-file-checks-frontend
Scorecard file checks - frontend, aggregated card
2 parents 5cbd460 + 3d0cc7a commit dd295a2

File tree

32 files changed

+696
-74
lines changed

32 files changed

+696
-74
lines changed

workspaces/scorecard/app-config.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,3 +133,11 @@ scorecard:
133133
frequency: { minutes: 5 }
134134
timeout: { minutes: 10 }
135135
initialDelay: { seconds: 5 }
136+
files_check:
137+
files:
138+
- readme: 'README.md'
139+
- i18guide: 'docs/backstage-i18n-guide.md'
140+
schedule:
141+
frequency: { minutes: 5 }
142+
timeout: { minutes: 10 }
143+
initialDelay: { seconds: 5 }

workspaces/scorecard/packages/app/e2e-tests/scorecard.test.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import {
2727
emptyScorecardResponse,
2828
unavailableMetricResponse,
2929
invalidThresholdResponse,
30+
fileCheckScorecardResponse,
3031
githubAggregatedResponse,
3132
jiraAggregatedResponse,
3233
emptyGithubAggregatedResponse,
@@ -188,6 +189,67 @@ test.describe('Scorecard Plugin Tests', () => {
188189

189190
await runAccessibilityTests(page, testInfo);
190191
});
192+
193+
test('Verify file check metrics display correctly', async ({
194+
browser,
195+
}, testInfo) => {
196+
await mockScorecardResponse(page, fileCheckScorecardResponse);
197+
198+
await catalogPage.openCatalog();
199+
await catalogPage.openComponent('Red Hat Developer Hub');
200+
await scorecardPage.openTab();
201+
202+
const existLabel = translations.thresholds.exist ?? 'Exist';
203+
const missingLabel = translations.thresholds.missing ?? 'Missing';
204+
205+
const readmeTitle = evaluateMessage(
206+
translations.metric['github.files_check'].title,
207+
'readme',
208+
);
209+
const readmeDescription = evaluateMessage(
210+
translations.metric['github.files_check'].description,
211+
'readme',
212+
);
213+
214+
const readmeCard = page
215+
.locator('[role="article"]')
216+
.filter({ hasText: readmeTitle })
217+
.first();
218+
await expect(readmeCard).toBeVisible();
219+
await expect(readmeCard.getByText(readmeDescription)).toBeVisible();
220+
await expect(
221+
readmeCard.getByText(existLabel, { exact: true }),
222+
).toBeVisible();
223+
await expect(
224+
readmeCard.getByText(missingLabel, { exact: true }),
225+
).toBeVisible();
226+
227+
const codeownersTitle = evaluateMessage(
228+
translations.metric['github.files_check'].title,
229+
'codeowners',
230+
);
231+
const codeownersDescription = evaluateMessage(
232+
translations.metric['github.files_check'].description,
233+
'codeowners',
234+
);
235+
236+
const codeownersCard = page
237+
.locator('[role="article"]')
238+
.filter({ hasText: codeownersTitle })
239+
.first();
240+
await expect(codeownersCard).toBeVisible();
241+
await expect(
242+
codeownersCard.getByText(codeownersDescription),
243+
).toBeVisible();
244+
await expect(
245+
codeownersCard.getByText(existLabel, { exact: true }),
246+
).toBeVisible();
247+
await expect(
248+
codeownersCard.getByText(missingLabel, { exact: true }),
249+
).toBeVisible();
250+
251+
await runAccessibilityTests(page, testInfo);
252+
});
191253
});
192254

193255
test.describe('Aggregated Scorecards', () => {

workspaces/scorecard/packages/app/e2e-tests/utils/scorecardResponseUtils.ts

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,10 @@
1313
* See the License for the specific language governing permissions and
1414
* limitations under the License.
1515
*/
16-
import { DEFAULT_NUMBER_THRESHOLDS } from '@red-hat-developer-hub/backstage-plugin-scorecard-common';
16+
import {
17+
DEFAULT_NUMBER_THRESHOLDS,
18+
ScorecardThresholdRuleColors,
19+
} from '@red-hat-developer-hub/backstage-plugin-scorecard-common';
1720

1821
export const customScorecardResponse = [
1922
{
@@ -260,3 +263,70 @@ export const emptyGithubAggregatedResponse = {
260263
thresholds: DEFAULT_NUMBER_THRESHOLDS,
261264
},
262265
};
266+
267+
export const fileCheckScorecardResponse = [
268+
{
269+
id: 'github.files_check.readme',
270+
status: 'success',
271+
metadata: {
272+
title: 'GitHub File: README.md',
273+
description: 'Checks if README.md exists in the repository.',
274+
type: 'boolean',
275+
history: true,
276+
},
277+
result: {
278+
value: true,
279+
timestamp: '2025-09-08T09:08:55.629Z',
280+
thresholdResult: {
281+
definition: {
282+
rules: [
283+
{
284+
key: 'exist',
285+
expression: '==true',
286+
color: ScorecardThresholdRuleColors.SUCCESS,
287+
},
288+
{
289+
key: 'missing',
290+
expression: '==false',
291+
color: ScorecardThresholdRuleColors.ERROR,
292+
},
293+
],
294+
},
295+
status: 'success',
296+
evaluation: 'exist',
297+
},
298+
},
299+
},
300+
{
301+
id: 'github.files_check.codeowners',
302+
status: 'success',
303+
metadata: {
304+
title: 'GitHub File: CODEOWNERS',
305+
description: 'Checks if CODEOWNERS exists in the repository.',
306+
type: 'boolean',
307+
history: true,
308+
},
309+
result: {
310+
value: false,
311+
timestamp: '2025-09-08T09:08:55.629Z',
312+
thresholdResult: {
313+
definition: {
314+
rules: [
315+
{
316+
key: 'exist',
317+
expression: '==true',
318+
color: ScorecardThresholdRuleColors.SUCCESS,
319+
},
320+
{
321+
key: 'missing',
322+
expression: '==false',
323+
color: ScorecardThresholdRuleColors.ERROR,
324+
},
325+
],
326+
},
327+
status: 'success',
328+
evaluation: 'missing',
329+
},
330+
},
331+
},
332+
];

workspaces/scorecard/packages/app/src/App.tsx

Lines changed: 72 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,66 @@ const mountPoints: HomePageCardMountPoint[] = [
146146
},
147147
},
148148
},
149+
{
150+
Component: ScorecardHomepageCard as ComponentType,
151+
config: {
152+
id: 'scorecard-github.files_check.readme',
153+
title: 'Scorecard: README file exists',
154+
cardLayout: {
155+
width: {
156+
minColumns: 3,
157+
maxColumns: 12,
158+
defaultColumns: 4,
159+
},
160+
height: {
161+
minRows: 5,
162+
maxRows: 12,
163+
defaultRows: 6,
164+
},
165+
},
166+
layouts: {
167+
xl: { w: 4, h: 6 },
168+
lg: { w: 4, h: 6 },
169+
md: { w: 4, h: 6 },
170+
sm: { w: 4, h: 6 },
171+
xs: { w: 4, h: 6 },
172+
xxs: { w: 4, h: 6 },
173+
},
174+
props: {
175+
metricId: 'github.files_check.readme',
176+
},
177+
},
178+
},
179+
{
180+
Component: ScorecardHomepageCard as ComponentType,
181+
config: {
182+
id: 'scorecard-github.files_check.i18guide',
183+
title: 'Scorecard: i18n guide file exists',
184+
cardLayout: {
185+
width: {
186+
minColumns: 3,
187+
maxColumns: 12,
188+
defaultColumns: 4,
189+
},
190+
height: {
191+
minRows: 5,
192+
maxRows: 12,
193+
defaultRows: 6,
194+
},
195+
},
196+
layouts: {
197+
xl: { w: 4, h: 6, x: 4 },
198+
lg: { w: 4, h: 6, x: 4 },
199+
md: { w: 4, h: 6, x: 4 },
200+
sm: { w: 4, h: 6, x: 4 },
201+
xs: { w: 4, h: 6, x: 4 },
202+
xxs: { w: 4, h: 6, x: 4 },
203+
},
204+
props: {
205+
metricId: 'github.files_check.i18guide',
206+
},
207+
},
208+
},
149209
{
150210
Component: ScorecardHomepageCard as ComponentType,
151211
config: {
@@ -180,14 +240,24 @@ const mountPoints: HomePageCardMountPoint[] = [
180240
title: 'Metric (Needs currently a page reload after change!)',
181241
type: 'string',
182242
default: 'jira.open_issues',
183-
enum: ['jira.open_issues', 'github.open_prs'],
243+
enum: [
244+
'jira.open_issues',
245+
'github.open_prs',
246+
'github.files_check.readme',
247+
'github.files_check.i18guide',
248+
],
184249
},
185250
},
186251
},
187252
uiSchema: {
188253
metricId: {
189254
'ui:widget': 'RadioWidget',
190-
'ui:enumNames': ['Jira Open Issues', 'GitHub Open PRs'],
255+
'ui:enumNames': [
256+
'Jira Open Issues',
257+
'GitHub Open PRs',
258+
'README file exists',
259+
'i18n guide file exists',
260+
],
191261
},
192262
},
193263
},
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
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 {
18+
type ThresholdConfig,
19+
ScorecardThresholdRuleColors,
20+
} from '@red-hat-developer-hub/backstage-plugin-scorecard-common';
21+
22+
export const DEFAULT_FILE_CHECK_THRESHOLDS: ThresholdConfig = {
23+
rules: [
24+
{
25+
key: 'exist',
26+
expression: '==true',
27+
color: ScorecardThresholdRuleColors.SUCCESS,
28+
},
29+
{
30+
key: 'missing',
31+
expression: '==false',
32+
color: ScorecardThresholdRuleColors.ERROR,
33+
},
34+
],
35+
};

workspaces/scorecard/plugins/scorecard-backend-module-github/src/metricProviders/GithubFilesProvider.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import { ConfigReader } from '@backstage/config';
1818
import type { Entity } from '@backstage/catalog-model';
1919
import { GithubFilesProvider } from './GithubFilesProvider';
2020
import { GithubClient } from '../github/GithubClient';
21-
import { DEFAULT_BOOLEAN_THRESHOLDS } from '@red-hat-developer-hub/backstage-plugin-scorecard-common';
21+
import { DEFAULT_FILE_CHECK_THRESHOLDS } from './GithubConfig';
2222

2323
jest.mock('@backstage/catalog-model', () => ({
2424
...jest.requireActual('@backstage/catalog-model'),
@@ -138,9 +138,9 @@ describe('GithubFilesProvider', () => {
138138
]);
139139
});
140140

141-
it('should return default boolean thresholds', () => {
141+
it('should return default file check thresholds', () => {
142142
expect(provider.getMetricThresholds()).toEqual(
143-
DEFAULT_BOOLEAN_THRESHOLDS,
143+
DEFAULT_FILE_CHECK_THRESHOLDS,
144144
);
145145
});
146146

workspaces/scorecard/plugins/scorecard-backend-module-github/src/metricProviders/GithubFilesProvider.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@ import type { Config } from '@backstage/config';
1818
import { getEntitySourceLocation, type Entity } from '@backstage/catalog-model';
1919
import { CATALOG_FILTER_EXISTS } from '@backstage/catalog-client';
2020
import {
21-
DEFAULT_BOOLEAN_THRESHOLDS,
2221
Metric,
2322
ThresholdConfig,
2423
} from '@red-hat-developer-hub/backstage-plugin-scorecard-common';
24+
import { DEFAULT_FILE_CHECK_THRESHOLDS } from './GithubConfig';
2525
import { MetricProvider } from '@red-hat-developer-hub/backstage-plugin-scorecard-node';
2626
import { GithubClient } from '../github/GithubClient';
2727
import { getRepositoryInformationFromEntity } from '../github/utils';
@@ -35,7 +35,7 @@ export class GithubFilesProvider implements MetricProvider<'boolean'> {
3535
private constructor(config: Config, filesConfig: GithubFilesConfig) {
3636
this.githubClient = new GithubClient(config);
3737
this.filesConfig = filesConfig;
38-
this.thresholds = DEFAULT_BOOLEAN_THRESHOLDS;
38+
this.thresholds = DEFAULT_FILE_CHECK_THRESHOLDS;
3939
}
4040

4141
getProviderDatasourceId(): string {

workspaces/scorecard/plugins/scorecard-backend/__fixtures__/mockMetricProvidersRegistry.ts

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -41,15 +41,6 @@ export const buildMockMetricProvidersRegistry = ({
4141
const getProvider = provider
4242
? jest.fn().mockReturnValue(provider)
4343
: jest.fn();
44-
const getMetric = provider
45-
? jest.fn().mockImplementation((metricId: string) => {
46-
if (provider.getMetrics) {
47-
const metric = provider.getMetrics().find(m => m.id === metricId);
48-
if (metric) return metric;
49-
}
50-
return provider.getMetric();
51-
})
52-
: jest.fn();
5344
const listMetrics = metricsList
5445
? jest.fn().mockImplementation((metricIds?: string[]) => {
5546
if (metricIds && metricIds.length !== 0) {
@@ -61,6 +52,11 @@ export const buildMockMetricProvidersRegistry = ({
6152
const getMetric =
6253
provider || metricsList
6354
? jest.fn().mockImplementation((metricId: string) => {
55+
if (provider?.getMetrics) {
56+
const found = provider.getMetrics().find(m => m.id === metricId);
57+
if (found) return found;
58+
}
59+
6460
const pMetric = provider?.getMetric();
6561
if (pMetric && pMetric.id === metricId) return pMetric;
6662

@@ -78,6 +74,5 @@ export const buildMockMetricProvidersRegistry = ({
7874
getProvider,
7975
getMetric,
8076
listMetrics,
81-
getMetric,
8277
} as unknown as jest.Mocked<MetricProvidersRegistry>;
8378
};

workspaces/scorecard/plugins/scorecard-backend/src/service/CatalogMetricService.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -502,6 +502,7 @@ describe('CatalogMetricService', () => {
502502
auth: mockedAuth,
503503
registry: mockedRegistry,
504504
database: mockedDatabase,
505+
logger: mockedLogger,
505506
});
506507

507508
const results = await service.getLatestEntityMetrics(

0 commit comments

Comments
 (0)