Skip to content

Commit 4ecaacd

Browse files
authored
feat(scorecard): File level checks - all providers (#2913)
1 parent dbc4e81 commit 4ecaacd

54 files changed

Lines changed: 3020 additions & 142 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
'@red-hat-developer-hub/backstage-plugin-scorecard-backend-module-filecheck': minor
3+
'@red-hat-developer-hub/backstage-plugin-scorecard-backend': minor
4+
'@red-hat-developer-hub/backstage-plugin-scorecard-node': minor
5+
'@red-hat-developer-hub/backstage-plugin-scorecard': minor
6+
---
7+
8+
Add support for batch metric providers, allowing a single provider to handle multiple metrics efficiently. Introduce a new backend module for configurable file existence checks (filecheck.\*) that verify whether required files (like README, LICENSE, or CODEOWNERS) are present in a repository.

workspaces/scorecard/app-config.yaml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,24 @@ app:
6464
sm: { w: 4, h: 6, x: 4 }
6565
xs: { w: 4, h: 6, x: 4 }
6666
xxs: { w: 4, h: 6, x: 4 }
67+
AggregatedCardWithGithubFilecheckLicense:
68+
priority: 450
69+
breakpoints:
70+
xl: { w: 4, h: 6 }
71+
lg: { w: 4, h: 6 }
72+
md: { w: 4, h: 6 }
73+
sm: { w: 4, h: 6 }
74+
xs: { w: 4, h: 6 }
75+
xxs: { w: 4, h: 6 }
76+
AggregatedCardWithGithubFilecheckCodeowners:
77+
priority: 460
78+
breakpoints:
79+
xl: { w: 4, h: 6 }
80+
lg: { w: 4, h: 6 }
81+
md: { w: 4, h: 6 }
82+
sm: { w: 4, h: 6 }
83+
xs: { w: 4, h: 6 }
84+
xxs: { w: 4, h: 6 }
6785

6886
organization:
6987
name: My Company
@@ -198,6 +216,11 @@ scorecard:
198216
type: statusGrouped
199217
description: This KPI is provide information about Jira open issues grouped by status.
200218
metricId: jira.open_issues
219+
licenseFileExistsKpi:
220+
title: License File Exists KPI
221+
type: statusGrouped
222+
description: This KPI is provide information about whether the license file exists in the repository.
223+
metricId: filecheck.license
201224
plugins:
202225
jira:
203226
open_issues:
@@ -211,3 +234,11 @@ scorecard:
211234
frequency: { minutes: 5 }
212235
timeout: { minutes: 10 }
213236
initialDelay: { seconds: 5 }
237+
filecheck:
238+
files:
239+
license: 'LICENSE'
240+
codeowners: 'CODEOWNERS'
241+
schedule:
242+
frequency: { minutes: 5 }
243+
timeout: { minutes: 10 }
244+
initialDelay: { seconds: 5 }

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

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ import {
4242
jiraEntitiesDrillDownResponse,
4343
jiraEntitiesDrillDownNoDataResponse,
4444
jiraMetricMetadataResponse,
45+
fileCheckScorecardResponse,
4546
} from './utils/scorecardResponseUtils';
4647
import {
4748
ScorecardMessages,
@@ -254,6 +255,71 @@ test.describe('Scorecard Plugin Tests', () => {
254255

255256
await runAccessibilityTests(page, testInfo);
256257
});
258+
259+
test('Verify file check metrics display correctly', async ({
260+
browser,
261+
}, testInfo) => {
262+
await mockApiResponse(
263+
page,
264+
ScorecardRoutes.SCORECARD_API_ROUTE,
265+
fileCheckScorecardResponse,
266+
);
267+
268+
await catalogPage.openCatalog();
269+
await catalogPage.openComponent('Red Hat Developer Hub');
270+
await scorecardPage.openTab();
271+
272+
const existLabel = translations.thresholds.exist ?? 'Exist';
273+
const missingLabel = translations.thresholds.missing ?? 'Missing';
274+
275+
const readmeTitle = evaluateMessage(
276+
translations.metric.filecheck.title,
277+
'readme',
278+
);
279+
const readmeDescription = evaluateMessage(
280+
translations.metric.filecheck.description,
281+
'readme',
282+
);
283+
284+
const readmeCard = page
285+
.locator('[role="article"]')
286+
.filter({ hasText: readmeTitle })
287+
.first();
288+
await expect(readmeCard).toBeVisible();
289+
await expect(readmeCard.getByText(readmeDescription)).toBeVisible();
290+
await expect(
291+
readmeCard.getByText(existLabel, { exact: true }),
292+
).toBeVisible();
293+
await expect(
294+
readmeCard.getByText(missingLabel, { exact: true }),
295+
).toBeVisible();
296+
297+
const codeownersTitle = evaluateMessage(
298+
translations.metric.filecheck.title,
299+
'codeowners',
300+
);
301+
const codeownersDescription = evaluateMessage(
302+
translations.metric.filecheck.description,
303+
'codeowners',
304+
);
305+
306+
const codeownersCard = page
307+
.locator('[role="article"]')
308+
.filter({ hasText: codeownersTitle })
309+
.first();
310+
await expect(codeownersCard).toBeVisible();
311+
await expect(
312+
codeownersCard.getByText(codeownersDescription),
313+
).toBeVisible();
314+
await expect(
315+
codeownersCard.getByText(existLabel, { exact: true }),
316+
).toBeVisible();
317+
await expect(
318+
codeownersCard.getByText(missingLabel, { exact: true }),
319+
).toBeVisible();
320+
321+
await runAccessibilityTests(page, testInfo);
322+
});
257323
});
258324

259325
test.describe('Homepage aggregated scorecards', () => {

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

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -506,3 +506,74 @@ export const jiraEntitiesDrillDownNoDataResponse = {
506506
isCapped: false,
507507
},
508508
};
509+
510+
export const fileCheckScorecardResponse = [
511+
{
512+
id: 'filecheck.readme',
513+
status: 'success',
514+
metadata: {
515+
title: 'GitHub File: README.md',
516+
description: 'Checks if README.md exists in the repository.',
517+
type: 'boolean',
518+
history: true,
519+
},
520+
result: {
521+
value: true,
522+
timestamp: '2025-09-08T09:08:55.629Z',
523+
thresholdResult: {
524+
definition: {
525+
rules: [
526+
{
527+
key: 'exist',
528+
expression: '==true',
529+
color: 'success.main',
530+
icon: 'scorecardSuccessStatusIcon',
531+
},
532+
{
533+
key: 'missing',
534+
expression: '==false',
535+
color: 'error.main',
536+
icon: 'scorecardErrorStatusIcon',
537+
},
538+
],
539+
},
540+
status: 'success',
541+
evaluation: 'exist',
542+
},
543+
},
544+
},
545+
{
546+
id: 'filecheck.codeowners',
547+
status: 'success',
548+
metadata: {
549+
title: 'GitHub File: CODEOWNERS',
550+
description: 'Checks if CODEOWNERS exists in the repository.',
551+
type: 'boolean',
552+
history: true,
553+
},
554+
result: {
555+
value: false,
556+
timestamp: '2025-09-08T09:08:55.629Z',
557+
thresholdResult: {
558+
definition: {
559+
rules: [
560+
{
561+
key: 'exist',
562+
expression: '==true',
563+
color: 'success.main',
564+
icon: 'scorecardSuccessStatusIcon',
565+
},
566+
{
567+
key: 'missing',
568+
expression: '==false',
569+
color: 'error.main',
570+
icon: 'scorecardErrorStatusIcon',
571+
},
572+
],
573+
},
574+
status: 'success',
575+
evaluation: 'missing',
576+
},
577+
},
578+
},
579+
];

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

Lines changed: 72 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,66 @@ const mountPoints: HomePageCardMountPoint[] = [
217217
},
218218
},
219219
},
220+
{
221+
Component: ScorecardHomepageCard as ComponentType,
222+
config: {
223+
id: 'scorecard-filecheck.license',
224+
title: 'Scorecard: LICENSE file exists',
225+
cardLayout: {
226+
width: {
227+
minColumns: 3,
228+
maxColumns: 12,
229+
defaultColumns: 4,
230+
},
231+
height: {
232+
minRows: 5,
233+
maxRows: 12,
234+
defaultRows: 6,
235+
},
236+
},
237+
layouts: {
238+
xl: { w: 4, h: 6 },
239+
lg: { w: 4, h: 6 },
240+
md: { w: 4, h: 6 },
241+
sm: { w: 4, h: 6 },
242+
xs: { w: 4, h: 6 },
243+
xxs: { w: 4, h: 6 },
244+
},
245+
props: {
246+
aggregationId: 'licenseFileExistsKpi',
247+
},
248+
},
249+
},
250+
{
251+
Component: ScorecardHomepageCard as ComponentType,
252+
config: {
253+
id: 'scorecard-filecheck.codeowners',
254+
title: 'Scorecard: CODEOWNERS file exists',
255+
cardLayout: {
256+
width: {
257+
minColumns: 3,
258+
maxColumns: 12,
259+
defaultColumns: 4,
260+
},
261+
height: {
262+
minRows: 5,
263+
maxRows: 12,
264+
defaultRows: 6,
265+
},
266+
},
267+
layouts: {
268+
xl: { w: 4, h: 6, x: 4 },
269+
lg: { w: 4, h: 6, x: 4 },
270+
md: { w: 4, h: 6, x: 4 },
271+
sm: { w: 4, h: 6, x: 4 },
272+
xs: { w: 4, h: 6, x: 4 },
273+
xxs: { w: 4, h: 6, x: 4 },
274+
},
275+
props: {
276+
aggregationId: 'filecheck.codeowners',
277+
},
278+
},
279+
},
220280
{
221281
Component: ScorecardHomepageCard as ComponentType,
222282
config: {
@@ -251,14 +311,24 @@ const mountPoints: HomePageCardMountPoint[] = [
251311
title: 'Metric (Needs currently a page reload after change!)',
252312
type: 'string',
253313
default: 'jira.open_issues',
254-
enum: ['jira.open_issues', 'github.open_prs'],
314+
enum: [
315+
'jira.open_issues',
316+
'github.open_prs',
317+
'filecheck.license',
318+
'filecheck.codeowners',
319+
],
255320
},
256321
},
257322
},
258323
uiSchema: {
259324
metricId: {
260325
'ui:widget': 'RadioWidget',
261-
'ui:enumNames': ['Jira Open Issues', 'GitHub Open PRs'],
326+
'ui:enumNames': [
327+
'Jira Open Issues',
328+
'GitHub Open PRs',
329+
'LICENSE file exists',
330+
'CODEOWNERS file exists',
331+
],
262332
},
263333
},
264334
},

workspaces/scorecard/packages/backend/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
"@backstage/plugin-techdocs-backend": "^2.1.6",
4848
"@red-hat-developer-hub/backstage-plugin-scorecard-backend": "workspace:^",
4949
"@red-hat-developer-hub/backstage-plugin-scorecard-backend-module-dependabot": "workspace:^",
50+
"@red-hat-developer-hub/backstage-plugin-scorecard-backend-module-filecheck": "workspace:^",
5051
"@red-hat-developer-hub/backstage-plugin-scorecard-backend-module-github": "workspace:^",
5152
"@red-hat-developer-hub/backstage-plugin-scorecard-backend-module-jira": "workspace:^",
5253
"@red-hat-developer-hub/backstage-plugin-scorecard-backend-module-openssf": "workspace:^",

workspaces/scorecard/packages/backend/src/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,11 @@ backend.add(
7272
'@red-hat-developer-hub/backstage-plugin-scorecard-backend-module-jira'
7373
),
7474
);
75+
backend.add(
76+
import(
77+
'@red-hat-developer-hub/backstage-plugin-scorecard-backend-module-filecheck'
78+
),
79+
);
7580
backend.add(
7681
import(
7782
'@red-hat-developer-hub/backstage-plugin-scorecard-backend-module-openssf'
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);

0 commit comments

Comments
 (0)