Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
07c5289
feat: implement batch provider for file checks
djanickova Feb 24, 2026
6a60828
feat: display correct file check scorecards
djanickova Mar 23, 2026
f0385fd
Merge branch 'main' into scorecard-file-level-checks
djanickova Apr 13, 2026
155643b
ref: use resolveMetricTranslation helper
djanickova Apr 14, 2026
4c87647
chore: re-add changes not solved in conflicts
djanickova Apr 14, 2026
154747e
chore: fix yarn tsc
djanickova Apr 14, 2026
3956b85
fix: do not show icon for boolean metric type
djanickova Apr 14, 2026
b6b3404
fix: pass extra props to db in pullProviderMetrics
djanickova Apr 14, 2026
269ca6d
fix: aggregations endpoint for batch providers
djanickova Apr 14, 2026
31c2393
chore: fix prettier
djanickova Apr 14, 2026
5e21972
ref: fix sonarqube issues
djanickova Apr 14, 2026
768ad4d
fix: broken test
djanickova Apr 15, 2026
ae8c0a8
feat: show aggregated cards for file checks
djanickova Apr 15, 2026
554dd8a
ref: fix sonarqube issue
djanickova Apr 15, 2026
4c04759
chore: generate changeset, update readme
djanickova Apr 15, 2026
87d44b2
ref: use license and codeowners as examples
djanickova Apr 15, 2026
3383dfa
fix: implement qodo suggestions
djanickova Apr 15, 2026
56289e1
fix: return empty map for no files
djanickova Apr 15, 2026
4bbe474
ref: fix sonarqube issues
djanickova Apr 15, 2026
9b4f0d1
feat: add aggregation card with custom title
djanickova Apr 16, 2026
671e690
Merge branch 'main' into scorecard-file-level-checks
djanickova Apr 16, 2026
d680d66
Merge branch 'main' into scorecard-file-level-checks
djanickova Apr 20, 2026
7df0227
fix: use ScorecardHomepageCardWithProvider
djanickova Apr 20, 2026
1dad625
wip: migrate functionality to new module
djanickova Apr 23, 2026
e32203c
wip: rename files_check to filecheck
djanickova Apr 23, 2026
fb11af5
fix: translation cascading lookup, remove Github keyword
djanickova Apr 24, 2026
7a85748
ref: restructure based on other modules
djanickova Apr 24, 2026
2fd3e75
ref: use readTree instead of readUrl
djanickova Apr 24, 2026
daffa57
fix: app config and tests
djanickova Apr 24, 2026
aa463fe
chore: regenerate api reports
djanickova Apr 24, 2026
d4b709c
fix: sonarqube issues, package.json
djanickova Apr 24, 2026
0c6612a
fix: CI failures
djanickova Apr 24, 2026
3e1a608
fix: add Jira open issues to App.tsx
djanickova Apr 24, 2026
b094d5f
doc: update READMEs
djanickova Apr 24, 2026
7a663f6
fix: add filecheck module to index.ts
djanickova Apr 24, 2026
f4a71cd
chore: generate changeset
djanickova Apr 24, 2026
e7a855b
fix: use correct aggregationId in legacy app
djanickova Apr 24, 2026
48492c0
ref: implement feedback from reviews
djanickova Apr 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions workspaces/scorecard/.changeset/bright-candies-roll.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
'@red-hat-developer-hub/backstage-plugin-scorecard-backend-module-filecheck': minor
'@red-hat-developer-hub/backstage-plugin-scorecard-backend': minor
'@red-hat-developer-hub/backstage-plugin-scorecard-node': minor
'@red-hat-developer-hub/backstage-plugin-scorecard': minor
---

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.
31 changes: 31 additions & 0 deletions workspaces/scorecard/app-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,24 @@ app:
sm: { w: 4, h: 6, x: 4 }
xs: { w: 4, h: 6, x: 4 }
xxs: { w: 4, h: 6, x: 4 }
AggregatedCardWithGithubFilecheckLicense:
priority: 450
breakpoints:
xl: { w: 4, h: 6 }
lg: { w: 4, h: 6 }
md: { w: 4, h: 6 }
sm: { w: 4, h: 6 }
xs: { w: 4, h: 6 }
xxs: { w: 4, h: 6 }
AggregatedCardWithGithubFilecheckCodeowners:
priority: 460
breakpoints:
xl: { w: 4, h: 6 }
lg: { w: 4, h: 6 }
md: { w: 4, h: 6 }
sm: { w: 4, h: 6 }
xs: { w: 4, h: 6 }
xxs: { w: 4, h: 6 }

organization:
name: My Company
Expand Down Expand Up @@ -198,6 +216,11 @@ scorecard:
type: statusGrouped
description: This KPI is provide information about Jira open issues grouped by status.
metricId: jira.open_issues
licenseFileExistsKpi:
title: License File Exists KPI
type: statusGrouped
description: This KPI is provide information about whether the license file exists in the repository.
metricId: filecheck.license
plugins:
jira:
open_issues:
Expand All @@ -211,3 +234,11 @@ scorecard:
frequency: { minutes: 5 }
timeout: { minutes: 10 }
initialDelay: { seconds: 5 }
filecheck:
files:
license: 'LICENSE'
codeowners: 'CODEOWNERS'
schedule:
frequency: { minutes: 5 }
timeout: { minutes: 10 }
initialDelay: { seconds: 5 }
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import {
jiraEntitiesDrillDownResponse,
jiraEntitiesDrillDownNoDataResponse,
jiraMetricMetadataResponse,
fileCheckScorecardResponse,
} from './utils/scorecardResponseUtils';
import {
ScorecardMessages,
Expand Down Expand Up @@ -254,6 +255,71 @@ test.describe('Scorecard Plugin Tests', () => {

await runAccessibilityTests(page, testInfo);
});

test('Verify file check metrics display correctly', async ({
browser,
}, testInfo) => {
await mockApiResponse(
page,
ScorecardRoutes.SCORECARD_API_ROUTE,
fileCheckScorecardResponse,
);

await catalogPage.openCatalog();
await catalogPage.openComponent('Red Hat Developer Hub');
await scorecardPage.openTab();

const existLabel = translations.thresholds.exist ?? 'Exist';
const missingLabel = translations.thresholds.missing ?? 'Missing';

const readmeTitle = evaluateMessage(
translations.metric.filecheck.title,
'readme',
);
const readmeDescription = evaluateMessage(
translations.metric.filecheck.description,
'readme',
);

const readmeCard = page
.locator('[role="article"]')
.filter({ hasText: readmeTitle })
.first();
await expect(readmeCard).toBeVisible();
await expect(readmeCard.getByText(readmeDescription)).toBeVisible();
await expect(
readmeCard.getByText(existLabel, { exact: true }),
).toBeVisible();
await expect(
readmeCard.getByText(missingLabel, { exact: true }),
).toBeVisible();

const codeownersTitle = evaluateMessage(
translations.metric.filecheck.title,
'codeowners',
);
const codeownersDescription = evaluateMessage(
translations.metric.filecheck.description,
'codeowners',
);

const codeownersCard = page
.locator('[role="article"]')
.filter({ hasText: codeownersTitle })
.first();
await expect(codeownersCard).toBeVisible();
await expect(
codeownersCard.getByText(codeownersDescription),
).toBeVisible();
await expect(
codeownersCard.getByText(existLabel, { exact: true }),
).toBeVisible();
await expect(
codeownersCard.getByText(missingLabel, { exact: true }),
).toBeVisible();

await runAccessibilityTests(page, testInfo);
});
});

test.describe('Homepage aggregated scorecards', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -506,3 +506,74 @@ export const jiraEntitiesDrillDownNoDataResponse = {
isCapped: false,
},
};

export const fileCheckScorecardResponse = [
{
id: 'filecheck.readme',
status: 'success',
metadata: {
title: 'GitHub File: README.md',
description: 'Checks if README.md exists in the repository.',
type: 'boolean',
history: true,
},
result: {
value: true,
timestamp: '2025-09-08T09:08:55.629Z',
thresholdResult: {
definition: {
rules: [
{
key: 'exist',
expression: '==true',
color: 'success.main',
icon: 'scorecardSuccessStatusIcon',
},
{
key: 'missing',
expression: '==false',
color: 'error.main',
icon: 'scorecardErrorStatusIcon',
},
],
},
status: 'success',
evaluation: 'exist',
},
},
},
{
id: 'filecheck.codeowners',
status: 'success',
metadata: {
title: 'GitHub File: CODEOWNERS',
description: 'Checks if CODEOWNERS exists in the repository.',
type: 'boolean',
history: true,
},
result: {
value: false,
timestamp: '2025-09-08T09:08:55.629Z',
thresholdResult: {
definition: {
rules: [
{
key: 'exist',
expression: '==true',
color: 'success.main',
icon: 'scorecardSuccessStatusIcon',
},
{
key: 'missing',
expression: '==false',
color: 'error.main',
icon: 'scorecardErrorStatusIcon',
},
],
},
status: 'success',
evaluation: 'missing',
},
},
},
];
74 changes: 72 additions & 2 deletions workspaces/scorecard/packages/app-legacy/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,66 @@ const mountPoints: HomePageCardMountPoint[] = [
},
},
},
{
Component: ScorecardHomepageCard as ComponentType,
config: {
id: 'scorecard-filecheck.license',
title: 'Scorecard: LICENSE file exists',
cardLayout: {
width: {
minColumns: 3,
maxColumns: 12,
defaultColumns: 4,
},
height: {
minRows: 5,
maxRows: 12,
defaultRows: 6,
},
},
layouts: {
xl: { w: 4, h: 6 },
lg: { w: 4, h: 6 },
md: { w: 4, h: 6 },
sm: { w: 4, h: 6 },
xs: { w: 4, h: 6 },
xxs: { w: 4, h: 6 },
},
props: {
aggregationId: 'licenseFileExistsKpi',
},
},
},
{
Component: ScorecardHomepageCard as ComponentType,
config: {
id: 'scorecard-filecheck.codeowners',
title: 'Scorecard: CODEOWNERS file exists',
cardLayout: {
width: {
minColumns: 3,
maxColumns: 12,
defaultColumns: 4,
},
height: {
minRows: 5,
maxRows: 12,
defaultRows: 6,
},
},
layouts: {
xl: { w: 4, h: 6, x: 4 },
lg: { w: 4, h: 6, x: 4 },
md: { w: 4, h: 6, x: 4 },
sm: { w: 4, h: 6, x: 4 },
xs: { w: 4, h: 6, x: 4 },
xxs: { w: 4, h: 6, x: 4 },
},
props: {
aggregationId: 'filecheck.codeowners',
},
},
},
{
Component: ScorecardHomepageCard as ComponentType,
config: {
Expand Down Expand Up @@ -251,14 +311,24 @@ const mountPoints: HomePageCardMountPoint[] = [
title: 'Metric (Needs currently a page reload after change!)',
type: 'string',
default: 'jira.open_issues',
enum: ['jira.open_issues', 'github.open_prs'],
enum: [
'jira.open_issues',
'github.open_prs',
'filecheck.license',
'filecheck.codeowners',
],
},
},
},
uiSchema: {
metricId: {
'ui:widget': 'RadioWidget',
'ui:enumNames': ['Jira Open Issues', 'GitHub Open PRs'],
'ui:enumNames': [
'Jira Open Issues',
'GitHub Open PRs',
'LICENSE file exists',
'CODEOWNERS file exists',
],
},
},
},
Expand Down
1 change: 1 addition & 0 deletions workspaces/scorecard/packages/backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
"@backstage/plugin-techdocs-backend": "^2.1.6",
"@red-hat-developer-hub/backstage-plugin-scorecard-backend": "workspace:^",
"@red-hat-developer-hub/backstage-plugin-scorecard-backend-module-dependabot": "workspace:^",
"@red-hat-developer-hub/backstage-plugin-scorecard-backend-module-filecheck": "workspace:^",
"@red-hat-developer-hub/backstage-plugin-scorecard-backend-module-github": "workspace:^",
"@red-hat-developer-hub/backstage-plugin-scorecard-backend-module-jira": "workspace:^",
"@red-hat-developer-hub/backstage-plugin-scorecard-backend-module-openssf": "workspace:^",
Expand Down
5 changes: 5 additions & 0 deletions workspaces/scorecard/packages/backend/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,11 @@ backend.add(
'@red-hat-developer-hub/backstage-plugin-scorecard-backend-module-jira'
),
);
backend.add(
import(
'@red-hat-developer-hub/backstage-plugin-scorecard-backend-module-filecheck'
),
);
backend.add(
import(
'@red-hat-developer-hub/backstage-plugin-scorecard-backend-module-openssf'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = require('@backstage/cli/config/eslint-factory')(__dirname);
Loading
Loading