Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
13 changes: 13 additions & 0 deletions workspaces/scorecard/.changeset/afraid-lies-punch.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
'@red-hat-developer-hub/backstage-plugin-scorecard-backend': minor
'@red-hat-developer-hub/backstage-plugin-scorecard-common': minor
'@red-hat-developer-hub/backstage-plugin-scorecard': minor
---

This update introduces new scalar aggregation KPIs in the scorecard configuration, including:

- **`sum`**: Single numeric total of latest metric values across owned entities
- **`average`**: Mean of latest metric values across owned entities
- **`max`**: Maximum latest metric value across owned entities
- **`min`**: Minimum latest metric value across owned entities
- **`count`**: Number of entities with a non-null latest stored value
18 changes: 18 additions & 0 deletions workspaces/scorecard/.changeset/stupid-knives-wonder.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
---
'@red-hat-developer-hub/backstage-plugin-scorecard-backend': major
'@red-hat-developer-hub/backstage-plugin-scorecard-common': major
'@red-hat-developer-hub/backstage-plugin-scorecard': major
---

**BREAKING**: Rename aggregation KPI type `average` to `weightedStatusScore`.

### App config

- `scorecard.aggregationKPIs.*.type`: `average` → `weightedStatusScore`

### `GET /aggregations/:aggregationId` API

- `metadata.aggregationType`: `average` → `weightedStatusScore`
- `result.averageScore` → `result.weightedStatusScore`
- `result.averageWeightedSum` → `result.weightedStatusSum`
- `result.averageMaxPossible` → `result.weightedStatusMaxPossible`
14 changes: 7 additions & 7 deletions workspaces/scorecard/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ yarn install

## Documentation

| Topic | Location |
| ----------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- |
| Aggregation KPIs (`statusGrouped`, `average`), API, ownership | [plugins/scorecard-backend/docs/aggregation.md](plugins/scorecard-backend/docs/aggregation.md) |
| Backend installation and RBAC, **`scorecard.aggregationKPIs`** examples | [plugins/scorecard-backend/README.md](plugins/scorecard-backend/README.md) |
| Drill-down (entity list for a metric) | [plugins/scorecard-backend/docs/drill-down.md](plugins/scorecard-backend/docs/drill-down.md) |
| Metric thresholds, annotations, **average KPI result colors** | [plugins/scorecard-backend/docs/thresholds.md](plugins/scorecard-backend/docs/thresholds.md) |
| Frontend (homepage cards, NFS) | [plugins/scorecard/README.md](plugins/scorecard/README.md) |
| Topic | Location |
| --------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------- |
| Aggregation KPIs (`statusGrouped`, `weightedStatusScore`, scalar `sum`/`average`/`max`/`min`/`count`), API, ownership | [plugins/scorecard-backend/docs/aggregation.md](plugins/scorecard-backend/docs/aggregation.md) |
| Backend installation and RBAC, **`scorecard.aggregationKPIs`** examples | [plugins/scorecard-backend/README.md](plugins/scorecard-backend/README.md) |
| Drill-down (entity list for a metric) | [plugins/scorecard-backend/docs/drill-down.md](plugins/scorecard-backend/docs/drill-down.md) |
| Metric thresholds, annotations, **weightedStatusScore KPI result colors** | [plugins/scorecard-backend/docs/thresholds.md](plugins/scorecard-backend/docs/thresholds.md) |
| Frontend (homepage cards, NFS) | [plugins/scorecard/README.md](plugins/scorecard/README.md) |
4 changes: 2 additions & 2 deletions workspaces/scorecard/app-config.local.EXAMPLE.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ jira:
scorecard:
openPrsWeightedKpi:
title: GitHub Open PRs (weighted health)
type: average
description: Weighted health average for open PRs by threshold status across your entities.
type: weightedStatusScore
description: Weighted health score for open PRs by threshold status across your entities.
metricId: github.open_prs
options:
statusScores:
Expand Down
38 changes: 36 additions & 2 deletions workspaces/scorecard/app-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -246,8 +246,8 @@ scorecard:
metricId: github.open_prs
openPrsWeightedKpi:
title: GitHub Open PRs (weighted health)
type: average
description: Weighted health average for open PRs by threshold status across your entities.
type: weightedStatusScore
description: Weighted health score for open PRs by threshold status across your entities.
metricId: github.open_prs
options:
statusScores:
Expand Down Expand Up @@ -275,6 +275,40 @@ scorecard:
type: statusGrouped
description: This KPI is provide information about whether the license file exists in the repository.
metricId: filecheck.license
totalOpenBugs:
title: Total Open Bugs
description: Sum of open issues across owned entities
type: sum
metricId: jira.open_issues
options:
thresholds:
rules:
- key: success
expression: '>=80'
color: '#6bb300' # green
- key: warning
expression: '<80'
color: 'rgb(224, 189, 108)' # light orange
avgOpenPrs:
title: Average Open PRs
description: Mean open PR count per entity
type: average
metricId: github.open_prs
entitiesWithOpenIssues:
title: Entities with Open Issues
description: Count of entities with a stored open-issues value
type: count
metricId: jira.open_issues
maxOpenPrs:
title: Maximum Open PRs
description: Maximum open PR count per entity
type: max
metricId: github.open_prs
minOpenPrs:
title: Minimum Open PRs
description: Minimum open PR count per entity
type: min
metricId: github.open_prs
plugins:
jira:
open_issues:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,11 @@ export class HomePage {
cardPattern = /Scorecard:\s*GitHub open PRs|ScorecardGithubHomepage/i;
} else if (cardName === 'Scorecard: Jira open blocking') {
cardPattern = /Scorecard:\s*Jira open blocking|ScorecardJiraHomepage/i;
} else if (cardName === AGGREGATED_CARDS_WIDGET_TITLES.openPrsWeightedKpi) {
} else if (
cardName === AGGREGATED_CARDS_WIDGET_TITLES.gitHubOpenPrsWeightedKpi
) {
cardPattern =
/Scorecard:\s*GitHub open PRs \(weighted health\)|ScorecardOpenPrsWeightedKpi/i;
/Scorecard:\s*GitHub open PRs \(weighted health\)|ScorecardGitHubOpenPrsWeightedKpi/i;
} else {
cardPattern = new RegExp(escapeRegex(cardName), 'i');
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ import {
getTranslations,
getEntityCount,
getStatusGroupedCardSnapshot,
getAverageCardSnapshot,
getWeightedStatusScoreCardSnapshot,
getTableFooterSnapshot,
getEntitiesTableFooterRowsLabel,
} from './utils/translationUtils';
Expand All @@ -73,10 +73,10 @@ import {
setupHomepageAllCardsNoData,
} from './utils/homepageWidgetUtils';
import {
expectAverageCardCenterPercent,
verifyAverageDonutCenterTooltip,
verifyAverageCenterTooltipBreakdownRows,
} from './utils/averageCardAssertions';
expectWeightedStatusScoreCardCenterPercent,
verifyWeightedStatusScoreDonutCenterTooltip,
verifyWeightedStatusScoreCenterTooltipBreakdownRows,
} from './utils/weightedStatusScoreCardAssertions';
import { runAccessibilityTests } from './utils/accessibility';
import { ScorecardRoutes } from './constants/routes';
import {
Expand Down Expand Up @@ -714,7 +714,7 @@ test.describe('Scorecard Plugin Tests', () => {
});
});

test.describe('Configured aggregation KPI - "average" type', () => {
test.describe('Configured aggregation KPI - "weightedStatusScore" type', () => {
const aggregationMetadata =
AGGREGATED_CARDS_METADATA.githubOpenPrsWeightedKpi;

Expand All @@ -726,7 +726,7 @@ test.describe('Scorecard Plugin Tests', () => {
});
});

test.describe('Validate "average" type card content', () => {
test.describe('Validate "weightedStatusScore" type card content', () => {
let card: Locator;

test.beforeAll(async () => {
Expand Down Expand Up @@ -755,19 +755,19 @@ test.describe('Scorecard Plugin Tests', () => {

test('Verify center score percentage', async () => {
await expect(card).toBeVisible();
await expectAverageCardCenterPercent(card, '51.5%');
await expectWeightedStatusScoreCardCenterPercent(card, '51.5%');
});

test('Verify center tooltip', async () => {
await expect(card).toBeVisible();
await verifyAverageDonutCenterTooltip(
await verifyWeightedStatusScoreDonutCenterTooltip(
page,
card,
translations,
openPrsWeightedAggregatedResponse.result.averageWeightedSum,
openPrsWeightedAggregatedResponse.result.averageMaxPossible,
openPrsWeightedAggregatedResponse.result.weightedStatusSum,
openPrsWeightedAggregatedResponse.result.weightedStatusMaxPossible,
);
await verifyAverageCenterTooltipBreakdownRows(
await verifyWeightedStatusScoreCenterTooltipBreakdownRows(
page,
card,
translations,
Expand Down Expand Up @@ -814,12 +814,12 @@ test.describe('Scorecard Plugin Tests', () => {

await expect(card).toBeVisible();
await expect(card).toMatchAriaSnapshot(
getAverageCardSnapshot(translations, {
getWeightedStatusScoreCardSnapshot(translations, {
drillDownMetricId: aggregationMetadata.metricId,
drillDownAggregationId: aggregationMetadata.id,
cardTitle: partialResponse.metadata.title,
cardDescription: partialResponse.metadata.description,
averageScoreLabel: `${partialResponse.result.averageScore}%`,
weightedStatusScoreLabel: `${partialResponse.result.weightedStatusScore}%`,
homepageCalculationHealth: {
healthy: String(entitiesConsidered - calculationErrorCount),
total: String(entitiesConsidered),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@ export function waitForAggregationResponse(
const result = json?.result;

return (
result?.averageScore !== undefined || result?.total !== undefined
result?.weightedStatusScore !== undefined ||
result?.total !== undefined
);
} catch {
return false;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -207,14 +207,14 @@ export const openIssuesKpiMetadataResponse = {
export const openPrsWeightedKpiMetadataResponse = {
title: 'GitHub Open PRs (weighted health)',
description:
'Weighted health average for open PRs by threshold status across your entities.',
'Weighted health score for open PRs by threshold status across your entities.',
type: 'number',
history: true,
aggregationType: aggregationTypes.average,
aggregationType: aggregationTypes.weightedStatusScore,
};

/**
* Average KPI: 3×100 + 5×40 + 1×15 + 1×0 = 515 weighted sum; max 100×10 entities → 51.5% score.
* WeightedStatusScore KPI: 3×100 + 5×40 + 1×15 + 1×0 = 515 weighted sum; max 100×10 entities → 51.5% score.
* Includes `critical` as a non-threshold status name (no `thresholds.critical` copy).
* Colors align with aggregation KPI `options.thresholds` warning band (30–79%) in app-config.
*/
Expand All @@ -236,9 +236,9 @@ export const openPrsWeightedAggregatedResponse = {
calculationErrorCount: 0,
timestamp: '2026-01-24T14:10:32.858Z',
thresholds: DEFAULT_NUMBER_THRESHOLDS,
averageScore: 51.5,
averageWeightedSum: 515,
averageMaxPossible: 1000,
weightedStatusScore: 51.5,
weightedStatusSum: 515,
weightedStatusMaxPossible: 1000,
aggregationChartDisplayColor: 'rgb(224, 189, 108)',
},
};
Expand All @@ -259,8 +259,8 @@ export const gitHubWeightedPartiallyAggregatedResponse = {
total: 8,
entitiesConsidered: 6,
calculationErrorCount: 2,
averageScore: 46.7,
averageWeightedSum: 466.67,
weightedStatusScore: 46.7,
weightedStatusSum: 466.67,
},
};

Expand All @@ -279,9 +279,9 @@ export const emptyOpenPrsWeightedAggregatedResponse = {
],
timestamp: '2026-01-24T14:10:32.858Z',
thresholds: DEFAULT_NUMBER_THRESHOLDS,
averageScore: 0,
averageWeightedSum: 0,
averageMaxPossible: 0,
weightedStatusScore: 0,
weightedStatusSum: 0,
weightedStatusMaxPossible: 0,
aggregationChartDisplayColor: '#6bb300',
},
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ export function getSomeEntitiesNotReportingTooltip(
);
}

/** Flat metric-namespace string by key (e.g. averageCenterTooltipTotalLabel). */
/** Flat metric-namespace string by key (e.g. weightedStatusScoreCenterTooltipTotalLabel). */
export function getMetricTranslation(
translations: ScorecardMessages,
key: string,
Expand Down Expand Up @@ -451,24 +451,24 @@ export function getStatusGroupedCardSnapshot(
`;
}

/** Snapshot for average-type homepage KPI cards (donut gauge, no threshold legend). */
export function getAverageCardSnapshot(
/** Snapshot for weightedStatusScore-type homepage KPI cards (donut gauge, no threshold legend). */
export function getWeightedStatusScoreCardSnapshot(
translations: ScorecardMessages,
options: {
drillDownMetricId: 'jira.open_issues' | 'github.open_prs';
drillDownAggregationId?: string;
homepageCalculationHealth?: { healthy: string; total: string };
cardTitle: string;
cardDescription: string;
averageScoreLabel: string;
weightedStatusScoreLabel: string;
},
): string {
const {
drillDownMetricId,
drillDownAggregationId,
cardTitle,
cardDescription,
averageScoreLabel,
weightedStatusScoreLabel,
} = options;
const aggregationSegment = drillDownAggregationId ?? drillDownMetricId;
const { healthy, total } = options.homepageCalculationHealth ?? {
Expand All @@ -488,7 +488,7 @@ export function getAverageCardSnapshot(
- button
- separator
- paragraph: ${cardDescription}
- application: ${averageScoreLabel}
- application: ${weightedStatusScoreLabel}
`;
}

Expand Down
Loading
Loading