Skip to content
Open
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
20 changes: 20 additions & 0 deletions workspaces/scorecard/.changeset/stupid-knives-wonder.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
'@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
---

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[low] scope-creep

The changeset declares major bumps for scorecard-backend-module-github, scorecard-backend-module-jira, and scorecard-node, but the diff contains zero code changes in those three packages and they do not re-export any renamed identifiers.

Suggested fix: Remove those three packages from the changeset unless they re-export renamed types from scorecard-common.


Rename aggregation KPI type `average` to `weightedStatusScore`.

**Breaking changes**

### 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`), 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
4 changes: 2 additions & 2 deletions workspaces/scorecard/app-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -235,8 +235,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
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
Original file line number Diff line number Diff line change
Expand Up @@ -28,30 +28,33 @@ function interpolate(template: string, vars: Record<string, string>): string {
);
}

function averageCenterTooltipBreakdownTemplateKey(
function weightedStatusScoreCenterTooltipBreakdownTemplateKey(
locale: string,
count: number,
):
| 'averageCenterTooltipBreakdownRow_one'
| 'averageCenterTooltipBreakdownRow_other' {
| 'weightedStatusScoreCenterTooltipBreakdownRow_one'
| 'weightedStatusScoreCenterTooltipBreakdownRow_other' {
if (Number.isNaN(count)) {
return 'averageCenterTooltipBreakdownRow_other';
return 'weightedStatusScoreCenterTooltipBreakdownRow_other';
}
const category = new Intl.PluralRules(locale).select(count);
return category === 'one'
? 'averageCenterTooltipBreakdownRow_one'
: 'averageCenterTooltipBreakdownRow_other';
? 'weightedStatusScoreCenterTooltipBreakdownRow_one'
: 'weightedStatusScoreCenterTooltipBreakdownRow_other';
}

function expectedAverageCenterTooltipBreakdownLine(
function expectedWeightedStatusScoreCenterTooltipBreakdownLine(
translations: ScorecardMessages,
locale: string,
statusKey: string,
count: string,
score: string,
): string {
const n = Number.parseInt(count, 10);
const templateKey = averageCenterTooltipBreakdownTemplateKey(locale, n);
const templateKey = weightedStatusScoreCenterTooltipBreakdownTemplateKey(
locale,
n,
);
const template = getMetricTranslation(translations, templateKey);
const status =
statusKey in translations.thresholds
Expand All @@ -60,32 +63,40 @@ function expectedAverageCenterTooltipBreakdownLine(
return interpolate(template, { status, count, score });
}

export async function expectAverageCardCenterPercent(
export async function expectWeightedStatusScoreCardCenterPercent(
card: Locator,
percentLabel: string,
): Promise<void> {
await expect(card.getByTestId('average-card-center-percent')).toHaveText(
percentLabel,
);
await expect(
card.getByTestId('weighted-status-score-card-center-percent'),
).toHaveText(percentLabel);
}

export async function verifyAverageDonutCenterTooltip(
export async function verifyWeightedStatusScoreDonutCenterTooltip(
page: Page,
card: Locator,
translations: ScorecardMessages,
weightedSum: number,
maxPossible: number,
): Promise<void> {
await card.getByTestId('average-card-center-percent-hit-area').hover();
await card
.getByTestId('weighted-status-score-card-center-percent-hit-area')
.hover();
await expect(
page.getByText(
getMetricTranslation(translations, 'averageCenterTooltipTotalLabel'),
getMetricTranslation(
translations,
'weightedStatusScoreCenterTooltipTotalLabel',
),
{ exact: true },
),
).toBeVisible();
await expect(
page.getByText(
getMetricTranslation(translations, 'averageCenterTooltipMaxLabel'),
getMetricTranslation(
translations,
'weightedStatusScoreCenterTooltipMaxLabel',
),
{ exact: true },
),
).toBeVisible();
Expand All @@ -112,15 +123,17 @@ const OPEN_PRS_WEIGHTED_MOCK_BREAKDOWN: Array<{
/**
* Per-status lines under total/max in the center donut tooltip (replaces old side-legend tooltips).
*/
export async function verifyAverageCenterTooltipBreakdownRows(
export async function verifyWeightedStatusScoreCenterTooltipBreakdownRows(
page: Page,
card: Locator,
translations: ScorecardMessages,
locale: string,
): Promise<void> {
await card.getByTestId('average-card-center-percent-hit-area').hover();
await card
.getByTestId('weighted-status-score-card-center-percent-hit-area')
.hover();
for (const row of OPEN_PRS_WEIGHTED_MOCK_BREAKDOWN) {
const line = expectedAverageCenterTooltipBreakdownLine(
const line = expectedWeightedStatusScoreCenterTooltipBreakdownLine(
translations,
locale,
row.statusKey,
Expand Down
Loading
Loading