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
7 changes: 7 additions & 0 deletions workspaces/scorecard/.changeset/green-cats-care.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@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
---

Implemented filter by `status` for scalar aggregation types (`sum`, `average`, `count`, `min`, `max`).
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`, `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) |
| Topic | Location |
| ------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------- |
| Aggregation KPIs (`statusGrouped`, `weightedStatusScore`, scalar `sum`/`average`/`max`/`min`/`count`, **`filter.status`**), 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** and **scalar** KPI result thresholds, **`filter.status`** for scalar KPIs | [plugins/scorecard-backend/docs/thresholds.md](plugins/scorecard-backend/docs/thresholds.md) |
| Frontend (homepage cards, NFS) | [plugins/scorecard/README.md](plugins/scorecard/README.md) |
19 changes: 12 additions & 7 deletions workspaces/scorecard/app-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -274,20 +274,25 @@ scorecard:
type: statusGrouped
description: This KPI is provide information about whether the license file exists in the repository.
metricId: filecheck.license
totalOpenPrs:
title: Total Open PRs
description: Sum of open PRs across owned entities
totalCriticalPRs:
title: Total Critical PRs
description: Sum of open PRs for entities in error status
type: sum
metricId: github.openPRs
filter:
status: error
options:
thresholds:
rules:
- key: success
expression: '>=80'
color: '#6bb300' # green
expression: '<30'
color: '#6bb300'
- key: warning
expression: '<80'
color: 'rgb(224, 189, 108)' # light orange
expression: '30-80'
color: 'rgb(224, 189, 108)'
- key: error
expression: '>80'
color: '#be1ec7'
avgOpenIssues:
title: Average Open Issues
description: Mean open issues count per entity
Expand Down
60 changes: 50 additions & 10 deletions workspaces/scorecard/plugins/scorecard-backend/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,19 +177,59 @@ scorecard:
- key: error
expression: '<10'
color: error.main
totalCriticalPRs:
title: Total Critical PRs
description: Sum of open PRs for entities in error status
type: sum
metricId: github.openPRs
filter:
status: error
options:
thresholds:
rules:
- key: success
expression: '<30'
color: '#6bb300'
- key: warning
expression: '30-80'
color: 'rgb(224, 189, 108)'
- key: error
expression: '>80'
color: '#be1ec7'
avgOpenIssues:
title: Average Open Issues
description: Mean open issues count per entity
type: average
metricId: jira.openIssues
entitiesWithOpenPrs:
title: Entities with Open PRs
description: Count of entities with a stored open-prs value
type: count
metricId: github.openPRs
maxOpenPrs:
title: Maximum Open PRs
description: Maximum open PR count per entity
type: max
metricId: github.openPRs
minOpenIssues:
title: Minimum Open Issues
description: Minimum open issues count per entity
type: min
metricId: jira.openIssues
```

| Field | Description |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `title` | Display title for this aggregation (returned in API metadata). |
| `description` | Display description for this aggregation. |
| `type` | Aggregation algorithm: `statusGrouped` (counts per threshold status), `weightedStatusScore` (normalized weighted score), or a **scalar** type — `sum`, `average`, `max`, `min`, `count` (rollup of latest numeric metric values; see [aggregation.md](./docs/aggregation.md#aggregation-types)). Scalar types require a **number** metric. |
| `metricId` | Metric provider id used to load thresholds and compute counts or scalar values. |
| `options` | **Optional:** extra configuration attributes required to further configure the aggregated card for a specific type |
| Field | Description |
| ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `title` | Display title for this aggregation (returned in API metadata). |
| `description` | Display description for this aggregation. |
| `type` | Aggregation algorithm: `statusGrouped` (counts per threshold status), `weightedStatusScore` (normalized weighted score), or a **scalar** type — `sum`, `average`, `max`, `min`, `count` (rollup of latest numeric metric values; see [aggregation.md](./docs/aggregation.md#aggregation-types)). Scalar types require a **number** metric. |
| `metricId` | Metric provider id used to load thresholds and compute counts or values. |
| `filter` | Optional for **scalar types** only (silently ignored on **`statusGrouped`** / **`weightedStatusScore`**). **`filter.status`**: threshold status key (case-sensitive). Only latest rows with that status contribute to **`value`** and **`total`**. Must match a threshold rule **`key`** for **`metricId`** from provider defaults or app-config overrides at **`scorecard.metricProviders.<datasource>.<providerName>.metrics.<metricName>.thresholds`** (or provider-level **`scorecard.metricProviders.<datasource>.<providerName>.thresholds`**) (validated at startup for scalar types). **`entitiesConsidered`** and **`calculationErrorCount`** stay full-portfolio. Returned in API **`metadata.filter`** when set. |
| `options` | **Optional:** extra configuration attributes required to further configure the aggregated card for a specific type |

- **Path**: `scorecard.aggregationKPIs.<aggregationId>`.
- If **`aggregationKPIs` is omitted** or a given id is not listed, **`GET /aggregations/:aggregationId`** still works when **`aggregationId` equals the metric id** (e.g. `github.openPRs`): the backend uses that metric with the default `statusGrouped` aggregation and metric-defined title/description.
- **Startup validation**: the backend validates every **`scorecard.aggregationKPIs`** entry when the plugin loads. Invalid configuration (including **`weightedStatusScore`** KPIs without **`options.statusScores`**, bad expressions, or unregistered **`metricId`**) causes the backend to **fail to start** with a clear error. At runtime, some edge cases may still be logged (for example skipping a KPI with unusable weights); prefer correcting app-config. See [aggregation.md](./docs/aggregation.md#configuration-validation).
- **Startup validation**: the backend validates every **`scorecard.aggregationKPIs`** entry when the plugin loads. Invalid configuration (including **`weightedStatusScore`** KPIs without **`options.statusScores`**, non-count scalar types on boolean metrics, invalid **`filter.status`** keys on scalar types, bad threshold expressions, or unregistered **`metricId`**) causes the backend to **fail to start** with a clear error. At runtime, some edge cases may still be logged (for example skipping a KPI with unusable weights); prefer correcting app-config. See [aggregation.md](./docs/aggregation.md#configuration-validation).

**Homepage cards** are configured in the app (for example Dynamic Home Page mount points). They should pass **`aggregationId`** matching a key in `aggregationKPIs` or the metric id for the default case. See the [Scorecard frontend plugin README](../scorecard/README.md#homepage-scorecard-cards).

Expand Down Expand Up @@ -262,7 +302,7 @@ curl -X GET "{{url}}/api/scorecard/metrics/catalog/component/default/my-service?

Returns aggregated metrics for the authenticated user across all catalog entities they own (same ownership rules as the legacy route; see [aggregation.md](./docs/aggregation.md)).

Response **`result`** shape depends on **`metadata.aggregationType`**: status counts for **`statusGrouped`**, weighted score fields for **`weightedStatusScore`**, or scalar fields for **`sum`** / **`average`** / **`max`** / **`min`** / **`count`** — see [Scalar result fields](./docs/aggregation.md#scalar-result-fields).
Response **`result`** shape depends on **`metadata.aggregationType`**: status counts for **`statusGrouped`**, weighted score fields for **`weightedStatusScore`**, or scalar fields for **`sum`** / **`average`** / **`max`** / **`min`** / **`count`** — see [Scalar result fields](./docs/aggregation.md#scalar-result-fields). Scalar KPIs may also return **`metadata.filter`** when **`filter.status`** is configured.

The **`aggregationId`** is either:

Expand Down Expand Up @@ -292,7 +332,7 @@ curl -X GET "{{url}}/api/scorecard/aggregations/github.openPRs" \

### `GET /aggregations/:aggregationId/metadata`

Returns **title**, **description**, **type**, **history**, and **aggregationType** for the aggregation without computing full aggregate counts. Uses the same resolution rules as `GET /aggregations/:aggregationId` (KPI config vs metric id fallback).
Returns **title**, **description**, **type**, **history**, and **aggregationType** for the aggregation without computing full aggregate counts. Includes **`filter`** when the KPI is a scalar type with **`filter.status`** configured. Uses the same resolution rules as `GET /aggregations/:aggregationId` (KPI config vs metric id fallback).

```bash
curl -X GET "{{url}}/api/scorecard/aggregations/openIssuesKpi/metadata" \
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ export const buildMockMetricProvidersRegistry = ({
const getProvider = provider
? jest.fn().mockReturnValue(provider)
: jest.fn();
const listProviders = provider
? jest.fn().mockReturnValue([provider])
: jest.fn().mockReturnValue([]);
const listMetrics = metricsList
? jest.fn().mockImplementation((metricIds?: string[]) => {
if (metricIds && metricIds.length !== 0) {
Expand Down Expand Up @@ -72,5 +75,6 @@ export const buildMockMetricProvidersRegistry = ({
hasProvider,
getMetric,
listMetrics,
listProviders,
} as unknown as jest.Mocked<MetricProvidersRegistry>;
};
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@

import type { ThresholdConfig } from '@red-hat-developer-hub/backstage-plugin-scorecard-common';

export const mockFirstThresholds = {
/** Success when value is >= 80; error otherwise. */
export const mockHigherIsBetterThresholds = {
rules: [
{
key: 'success',
Expand All @@ -31,7 +32,8 @@ export const mockFirstThresholds = {
],
} as ThresholdConfig;

export const mockSecondThresholds = {
/** Error when value is > 50; warning 10–50; success when < 10. */
export const mockLowerIsBetterThresholds = {
rules: [
{
key: 'error',
Expand Down
5 changes: 5 additions & 0 deletions workspaces/scorecard/plugins/scorecard-backend/config.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,11 @@ export interface Config {
| typeof aggregationTypes.max
| typeof aggregationTypes.min
| typeof aggregationTypes.count;
/** Optional: filter applied when aggregating scalar KPI values */
filter?: {
/** Threshold status key to include (e.g. success, warning, error) */
status?: string;
};
/** Options specific to the scalar aggregation type */
options?: {
/** Optional: threshold rules for coloring the KPI headline value from the aggregation result */
Expand Down
Loading
Loading