@red-hat-developer-hub/backstage-plugin-scorecard-backend 4.2.0 → 4.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/CHANGELOG.md +88 -0
  2. package/README.md +233 -15
  3. package/config.schema.json +528 -0
  4. package/dist/actions/getEntityMetrics.cjs.js +4 -1
  5. package/dist/actions/getEntityMetrics.cjs.js.map +1 -1
  6. package/dist/actions/listMetrics.cjs.js +4 -1
  7. package/dist/actions/listMetrics.cjs.js.map +1 -1
  8. package/dist/database/DatabaseMetricValues.cjs.js +133 -11
  9. package/dist/database/DatabaseMetricValues.cjs.js.map +1 -1
  10. package/dist/database/utils/buildScalarTimeSeriesPoints.cjs.js +41 -0
  11. package/dist/database/utils/buildScalarTimeSeriesPoints.cjs.js.map +1 -0
  12. package/dist/database/utils/getAggregateExpression.cjs.js +7 -6
  13. package/dist/database/utils/getAggregateExpression.cjs.js.map +1 -1
  14. package/dist/database/utils/mapMetricValueRow.cjs.js +5 -2
  15. package/dist/database/utils/mapMetricValueRow.cjs.js.map +1 -1
  16. package/dist/index.cjs.js +14 -2
  17. package/dist/index.cjs.js.map +1 -1
  18. package/dist/index.d.ts +12 -1
  19. package/dist/middlewares/validateTimeSeriesQueryParams.cjs.js +44 -0
  20. package/dist/middlewares/validateTimeSeriesQueryParams.cjs.js.map +1 -0
  21. package/dist/plugin.cjs.js +2 -1
  22. package/dist/plugin.cjs.js.map +1 -1
  23. package/dist/scheduler/tasks/CleanupExpiredMetricsTask.cjs.js +2 -2
  24. package/dist/scheduler/tasks/CleanupExpiredMetricsTask.cjs.js.map +1 -1
  25. package/dist/service/CatalogMetricService.cjs.js +111 -5
  26. package/dist/service/CatalogMetricService.cjs.js.map +1 -1
  27. package/dist/service/aggregations/AggregatedMetricLoader.cjs.js +16 -0
  28. package/dist/service/aggregations/AggregatedMetricLoader.cjs.js.map +1 -1
  29. package/dist/service/aggregations/AggregationsService.cjs.js +24 -13
  30. package/dist/service/aggregations/AggregationsService.cjs.js.map +1 -1
  31. package/dist/service/aggregations/strategies/ScalarAggregationStrategy.cjs.js +43 -1
  32. package/dist/service/aggregations/strategies/ScalarAggregationStrategy.cjs.js.map +1 -1
  33. package/dist/service/aggregations/strategies/WeightedStatusScoreAggregationStrategy.cjs.js +9 -18
  34. package/dist/service/aggregations/strategies/WeightedStatusScoreAggregationStrategy.cjs.js.map +1 -1
  35. package/dist/service/mappers.cjs.js +26 -0
  36. package/dist/service/mappers.cjs.js.map +1 -1
  37. package/dist/service/router.cjs.js +101 -1
  38. package/dist/service/router.cjs.js.map +1 -1
  39. package/dist/threshold/ThresholdResolver.cjs.js +4 -0
  40. package/dist/threshold/ThresholdResolver.cjs.js.map +1 -1
  41. package/dist/threshold/thresholdAnnotations.cjs.js +25 -0
  42. package/dist/threshold/thresholdAnnotations.cjs.js.map +1 -0
  43. package/dist/utils/aggregation/classifyNumberAgainstThresholds.cjs.js +22 -0
  44. package/dist/utils/aggregation/classifyNumberAgainstThresholds.cjs.js.map +1 -0
  45. package/dist/utils/aggregation/getAggregationChartDisplayColor.cjs.js +19 -0
  46. package/dist/utils/aggregation/getAggregationChartDisplayColor.cjs.js.map +1 -0
  47. package/dist/utils/aggregation/isScalarAggregationType.cjs.js.map +1 -1
  48. package/dist/utils/aggregation/withStandardThresholdDefaults.cjs.js +32 -0
  49. package/dist/utils/aggregation/withStandardThresholdDefaults.cjs.js.map +1 -0
  50. package/dist/utils/metricUtils.cjs.js +6 -0
  51. package/dist/utils/metricUtils.cjs.js.map +1 -1
  52. package/dist/validation/schemas/aggregationConfigSchemas.cjs.js.map +1 -1
  53. package/migrations/20260804123239_add_entity_metric_timestamp_index.js +33 -0
  54. package/package.json +14 -14
  55. package/config.d.ts +0 -141
  56. package/dist/scheduler/tasks/utils.cjs.js +0 -8
  57. package/dist/scheduler/tasks/utils.cjs.js.map +0 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,93 @@
1
1
  # @red-hat-developer-hub/backstage-plugin-scorecard-backend
2
2
 
3
+ ## 4.4.0
4
+
5
+ ### Patch Changes
6
+
7
+ - Updated dependencies [8c690d2]
8
+ - @red-hat-developer-hub/backstage-plugin-scorecard-node@4.4.0
9
+ - @red-hat-developer-hub/backstage-plugin-scorecard-common@4.4.0
10
+
11
+ ## 4.3.1
12
+
13
+ ### Patch Changes
14
+
15
+ - 52e46db: Fix config schema generation during `prepack` by using string literal aggregation types in `config.d.ts` instead of `typeof aggregationTypes.*` references that Backstage cannot resolve.
16
+ - @red-hat-developer-hub/backstage-plugin-scorecard-common@4.3.1
17
+ - @red-hat-developer-hub/backstage-plugin-scorecard-node@4.3.1
18
+
19
+ ## 4.3.0
20
+
21
+ ### Minor Changes
22
+
23
+ - 9c1936e: Add `GET /aggregations/:aggregationId/time-series` for daily scalar portfolio aggregation (`sum`, `average`, `max`, `min`, `count`). Returns aggregated metric values per UTC days. Days with no data are omitted. Aggregation type `statusGrouped` and `weightedStatusScore` return `400`. Sparkline metrics without a KPI block default to aggregation type `average`.
24
+
25
+ Adds `metadata.visualization` type to `GET /aggregations/:aggregationId/metadata` response.
26
+
27
+ - 2bb0ec5: Entity time-series API (`GET /metrics/catalog/:kind/:namespace/:name/time-series`) now returns entity-resolved `thresholds` and per-point `thresholdEvaluation` (classified at read time against those current thresholds) so clients can render sparkline legends and chart colors without a separate snapshot call. Threshold evaluation failures are returned in the existing per-point `error` field. When entity threshold resolution fails (e.g. malformed annotation overrides), the response sets `thresholdsError` and omits `thresholds` instead of silently falling back to config/provider defaults; points are left unclassified (`thresholdEvaluation` null).
28
+ - 485fadb: Persist DORA collector data in the database and sync incrementally from the last watermark, so metrics reuse stored deployments, incidents, and pull requests instead of refetching the full window every time.
29
+
30
+ The Jira `jira:doraIncidents` collector contract now requires `updatedSince` (ISO datetime) in the input and `updatedAt` (ISO datetime) on each incident in the output. Custom incident collector implementations must provide these fields.
31
+
32
+ - c380e6b: Skip scalar aggregation threshold coloring when no successful samples contributed (`total` is 0). Return a null display color and keep the card grey fallback. Scalar aggregation responses now include `aggregationChartDisplayColor` (threshold-derived chart color, or `null` when `total` is 0).
33
+
34
+ **BREAKING**: Changed types in `scorecard-common` module:
35
+
36
+ - `WeightedStatusScoreAggregationResult.aggregationChartDisplayColor` widened from `string` to `string | null`.
37
+ - `ScalarAggregationResult` gained a required `aggregationChartDisplayColor: string | null` property.
38
+
39
+ These changes are intentional: the API can return `null` when no samples contribute, and scalar KPI results now expose the same display-color field as weighted status score aggregations.
40
+
41
+ - fea86e8: Adds new endpoint `GET /metrics/:metricId/collectors` to list collector id and description for a metric. Composite metrics (like DORA) set optional `collectorIds` on `Metric` from config.
42
+
43
+ **BREAKING**: `ScorecardCollectorsService` now includes `getCollectorMetadata`. The default implementation behind `scorecardCollectorsServiceRef` already provides it, so no change is required unless you registered your own factory for that ref — then implement the new method.
44
+
45
+ - a1c3eb3: Updated DORA Mean Time to Restore to Median Time to Restore.
46
+ - ff6683f: Add DORA metrics and a collectors framework for composing datasource data into metrics.
47
+
48
+ - New `@red-hat-developer-hub/backstage-plugin-scorecard-backend-module-dora` with Deployment Frequency, Median Lead Time for Changes, Mean Time to Restore, and Change Failure Rate
49
+ - New data collectors used by DORA: GitHub deployments, deployment workflow runs, and deployment pull requests; Jira incidents
50
+ - Metric time-series API `/metrics/catalog/:kind/:namespace/:name/time-series`
51
+ - Adds `defaultVisualization` to Metric metadata for sparkline
52
+
53
+ - ecb789b: Select one time-series point per UTC day in the database and include error-only days in the response.
54
+
55
+ - Prefer the latest sample of the day, including calculation errors (`value: null` with `error`)
56
+ - Widen `MetricTimeSeriesPoint` so `value` may be null and `error` is optional
57
+
58
+ - c7b7410: Adds an explicit collectors service factory. Updates default export to include it.
59
+ - f3f71a5: Add unit to metric and display it in threshold legend
60
+ - 50e1563: Add `scorecard.entityAnnotations.thresholds` (and global `entityAnnotations.enabled`) so administrators can disable or restrict per-entity threshold override annotations, using the same pattern as disabled-metrics annotation controls.
61
+ - a7a1b4a: Backstage version bump to v1.54.6
62
+
63
+ ### Patch Changes
64
+
65
+ - f2662b4: Rename DORA-specific collector IDs to make their scope explicit and avoid conflicts with potential future generic collectors:
66
+
67
+ - `github:deployments` -> `github:doraDeployments`
68
+ - `github:deploymentWorkflowRuns` -> `github:doraDeploymentWorkflowRuns`
69
+ - `github:deploymentPullRequests` -> `github:doraDeploymentPullRequests`
70
+ - `jira:incidents` -> `jira:doraIncidents`
71
+
72
+ - 47ac76d: Export public `ScalarAggregationType` for scalar aggregation KPI types (`sum`, `average`, `count`, `min`, `max`).
73
+
74
+ Add legacy app E2E coverage for scalar and statusGrouped homepage aggregation KPI cards, including reusable Playwright registration helpers and legacy homepage widget entries for scalar KPIs.
75
+
76
+ - Updated dependencies [9c1936e]
77
+ - Updated dependencies [2bb0ec5]
78
+ - Updated dependencies [485fadb]
79
+ - Updated dependencies [befccc2]
80
+ - Updated dependencies [c380e6b]
81
+ - Updated dependencies [fea86e8]
82
+ - Updated dependencies [ff6683f]
83
+ - Updated dependencies [ecb789b]
84
+ - Updated dependencies [47ac76d]
85
+ - Updated dependencies [c7b7410]
86
+ - Updated dependencies [f3f71a5]
87
+ - Updated dependencies [a7a1b4a]
88
+ - @red-hat-developer-hub/backstage-plugin-scorecard-common@4.3.0
89
+ - @red-hat-developer-hub/backstage-plugin-scorecard-node@4.3.0
90
+
3
91
  ## 4.2.0
4
92
 
5
93
  ### Minor Changes
package/README.md CHANGED
@@ -94,13 +94,14 @@ For more information about schedule configuration options, see the [Metric Colle
94
94
 
95
95
  The following metric providers are available:
96
96
 
97
- | Provider | Metric ID | Title | Description | Type |
98
- | -------------- | ----------------- | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | ------- |
99
- | **GitHub** | `github.openPRs` | GitHub open PRs | Count of open Pull Requests in GitHub | number |
100
- | **Filecheck** | `filecheck.*` | File Checks | Checks whether specific files (e.g., `README.md`, `LICENSE`, `CODEOWNERS`) exist in a repository. | boolean |
101
- | **Jira** | `jira.openIssues` | Jira open issues | The number of opened issues in Jira | number |
102
- | **OpenSSF** | `openssf.*` | OpenSSF Security Scorecards | 18 security metrics from OpenSSF Scorecards (e.g., `openssf.codeReview`, `openssf.maintained`). Each returns a score from 0-10. | number |
103
- | **Dependabot** | `dependabot.*` | Dependabot Alerts | Critical, High, Medium and Low CVE Alerts | number |
97
+ | Provider | Metric ID | Title | Description | Type |
98
+ | -------------- | ----------------------------------------------------------------------------------------------------------------- | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | ------- |
99
+ | **GitHub** | `github.openPRs` | GitHub open PRs | Count of open Pull Requests in GitHub | number |
100
+ | **Filecheck** | `filecheck.*` | File Checks | Checks whether specific files (e.g., `README.md`, `LICENSE`, `CODEOWNERS`) exist in a repository. | boolean |
101
+ | **Jira** | `jira.openIssues` | Jira open issues | The number of opened issues in Jira | number |
102
+ | **OpenSSF** | `openssf.*` | OpenSSF Security Scorecards | 18 security metrics from OpenSSF Scorecards (e.g., `openssf.codeReview`, `openssf.maintained`). Each returns a score from 0-10. | number |
103
+ | **Dependabot** | `dependabot.*` | Dependabot Alerts | Critical, High, Medium and Low CVE Alerts | number |
104
+ | **DORA** | `dora.deploymentFrequency`, `dora.medianLeadTimeForChanges`, `dora.medianTimeToRestore`, `dora.changeFailureRate` | DORA Metrics | Software delivery performance metrics based on DORA (DevOps Research and Assessment) | number |
104
105
 
105
106
  To use these providers, install the corresponding backend modules:
106
107
 
@@ -109,10 +110,11 @@ To use these providers, install the corresponding backend modules:
109
110
  - OpenSSF: [`@red-hat-developer-hub/backstage-plugin-scorecard-backend-module-openssf`](../scorecard-backend-module-openssf/README.md)
110
111
  - Dependabot: [`@red-hat-developer-hub/backstage-plugin-scorecard-backend-module-dependabot`](../scorecard-backend-module-dependabot/README.md)
111
112
  - Filecheck: [`@red-hat-developer-hub/backstage-plugin-scorecard-backend-module-filecheck`](../scorecard-backend-module-filecheck/README.md)
113
+ - DORA: [`@red-hat-developer-hub/backstage-plugin-scorecard-backend-module-dora`](../scorecard-backend-module-dora/README.md)
112
114
 
113
115
  ### Disabling Metrics
114
116
 
115
- Administrators can disable metric checks globally via app-config, and users can disable them for concrete entity via `scorecard.io/disabled-metrics` annotation. For more details, see [disabled-metrics-logic.md](./docs/disabled-metrics-logic.md).
117
+ Administrators can disable metric checks globally via `scorecard.disabledMetrics`, and users can disable them per entity via the `scorecard.io/disabled-metrics` annotation. Whether that annotation is honored is controlled by `scorecard.entityAnnotations.enabled` (global switch for all scorecard entity annotations) and `scorecard.entityAnnotations.disabledMetrics` (`enabled` / `except`). For more details, see [disabled-metrics-logic.md](./docs/disabled-metrics-logic.md).
116
118
 
117
119
  ## Thresholds
118
120
 
@@ -120,7 +122,7 @@ Thresholds define conditions to assign metric values to specific visual categori
120
122
 
121
123
  - **Provider Defaults**: Metric providers define default thresholds
122
124
  - **App Configuration**: Override defaults through `app-config.yaml`
123
- - **Entity Annotations**: Override specific thresholds per entity using catalog annotations
125
+ - **Entity Annotations**: Override specific thresholds per entity using catalog annotations, can be disabled or restricted per metric via `scorecard.entityAnnotations.enabled` and `scorecard.entityAnnotations.thresholds`, see [threshold entity annotations](./docs/thresholds.md#3-entity-annotation-overrides)
124
126
 
125
127
  Thresholds are evaluated in order, and the first matching rule determines the category. The plugin supports various operators for number metrics (`>`, `>=`, `<`, `<=`, `==`, `!=`, `-` (range)) and boolean metrics (`==`, `!=`). For **number** metrics, configurations loaded through validated paths must cover the **entire real line** when two or more rules are defined (no gaps between intervals).
126
128
 
@@ -201,6 +203,11 @@ scorecard:
201
203
  description: Mean open issues count per entity
202
204
  type: average
203
205
  metricId: jira.openIssues
206
+ avgDeploymentFrequency:
207
+ title: Average deployment frequency
208
+ description: Mean weekly production deploys across catalog entities you own.
209
+ type: average
210
+ metricId: dora.deploymentFrequency
204
211
  entitiesWithOpenPrs:
205
212
  title: Entities with Open PRs
206
213
  description: Count of entities with a stored open-prs value
@@ -228,7 +235,7 @@ scorecard:
228
235
  | `options` | **Optional:** extra configuration attributes required to further configure the aggregated card for a specific type |
229
236
 
230
237
  - **Path**: `scorecard.aggregationKPIs.<aggregationId>`.
231
- - 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.
238
+ - If **`aggregationKPIs` is omitted** or a given id is not listed, aggregation KPIs still work, See [Default aggregation](./docs/aggregation.md#default-aggregation).
232
239
  - **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).
233
240
 
234
241
  **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).
@@ -298,16 +305,140 @@ curl -X GET "{{url}}/api/scorecard/metrics/catalog/component/default/my-service?
298
305
  -H "Authorization: Bearer <token>"
299
306
  ```
300
307
 
308
+ ### `GET /metrics/catalog/:kind/:namespace/:name/time-series`
309
+
310
+ Returns daily time-series points for one metric on a catalog entity. Each point is the latest sample (`MAX(id)` among success or calculation-error rows) for that UTC calendar day. On a mixed day the later sample wins, so a later error is returned as `{ "value": null, "error": "..." }` (and clients can gap a sparkline). Days with no rows (or only null without `error_message`) are omitted. Returns `200` with `points: []` when the entity and metric are authorized but no data exists in the range.
311
+
312
+ The response also includes entity-resolved `thresholds` (provider defaults, then app-config, then entity annotation overrides) for sparkline legend rendering. Successful points include `thresholdEvaluation`: the matched threshold rule key from **read-time** evaluation of the point's `value` against those current `thresholds` (e.g. `success`, `warning`, `error`). This keeps legend keys and point classifications consistent when config changes. Calculation-error points omit `thresholdEvaluation`. When no rule matches, `thresholdEvaluation` is `null` (no per-point `error`). Threshold evaluation failures set that point's `error` (with `thresholdEvaluation` `null`).
313
+
314
+ When entity threshold resolution fails (e.g. malformed annotation overrides), the response omits `thresholds`, sets top-level `thresholdsError` with the failure message, and leaves successful points unclassified (`thresholdEvaluation` `null`, no per-point `error`). There is no silent fallback to app-config / provider defaults.
315
+
316
+ #### Path Parameters
317
+
318
+ | Parameter | Type | Required | Description |
319
+ | ----------- | ------ | -------- | ---------------------------------- |
320
+ | `kind` | string | Yes | Entity kind (e.g., `component`) |
321
+ | `namespace` | string | Yes | Entity namespace (e.g., `default`) |
322
+ | `name` | string | Yes | Entity name |
323
+
324
+ #### Query Parameters
325
+
326
+ | Parameter | Type | Required | Description |
327
+ | ---------- | ------ | -------- | -------------------------------------------------------------------- |
328
+ | `metricId` | string | Yes | Metric ID (e.g., `github.openPRs`) |
329
+ | `from` | string | Yes | Inclusive range start (ISO-8601) |
330
+ | `to` | string | Yes | Inclusive range end (ISO-8601); must be `>= from`; max span 365 days |
331
+
332
+ #### Permissions
333
+
334
+ Requires `scorecard.metric.read` permission and `catalog.entity.read` permission for the specific entity.
335
+
336
+ #### Example Request
337
+
338
+ ```bash
339
+ curl -X GET "{{url}}/api/scorecard/metrics/catalog/component/default/my-service/time-series?metricId=github.openPRs&from=2026-04-01T00:00:00.000Z&to=2026-04-30T23:59:59.999Z" \
340
+ -H "Authorization: Bearer <token>"
341
+ ```
342
+
343
+ #### Example Response
344
+
345
+ ```json
346
+ {
347
+ "metricId": "github.openPRs",
348
+ "entityRef": "component:default/my-service",
349
+ "metadata": {
350
+ "title": "GitHub open PRs",
351
+ "description": "The number of open pull requests.",
352
+ "type": "number",
353
+ "history": true,
354
+ "defaultVisualization": "donut"
355
+ },
356
+ "points": [
357
+ {
358
+ "value": 8,
359
+ "timestamp": "2026-04-27T23:10:00.000Z",
360
+ "thresholdEvaluation": "success"
361
+ },
362
+ {
363
+ "value": null,
364
+ "timestamp": "2026-04-28T16:00:00.000Z",
365
+ "error": "GitHub API 500"
366
+ },
367
+ {
368
+ "value": 25,
369
+ "timestamp": "2026-04-29T22:55:00.000Z",
370
+ "thresholdEvaluation": "warning"
371
+ },
372
+ {
373
+ "value": 12,
374
+ "timestamp": "2026-04-30T18:00:00.000Z",
375
+ "thresholdEvaluation": null,
376
+ "error": "Error: Invalid threshold expression"
377
+ }
378
+ ],
379
+ "thresholds": {
380
+ "rules": [
381
+ { "key": "success", "expression": "<10" },
382
+ { "key": "warning", "expression": "10-50" },
383
+ { "key": "error", "expression": ">50" }
384
+ ]
385
+ }
386
+ }
387
+ ```
388
+
389
+ ### `GET /metrics/:metricId/collectors`
390
+
391
+ Returns the collectors used as data sources for a composite metric calculation.
392
+
393
+ #### Path Parameters
394
+
395
+ | Parameter | Type | Required | Description |
396
+ | ---------- | ------ | -------- | ------------------------------------------------------------ |
397
+ | `metricId` | string | Yes | Metric ID (e.g., `dora.changeFailureRate`, `github.openPRs`) |
398
+
399
+ #### Permissions
400
+
401
+ Requires `scorecard.metric.read` permission for the requested metric.
402
+
403
+ #### Behavior
404
+
405
+ - Returns `{ "collectors": [ ... ] }` with one entry per collector ID on the metric.
406
+ - Metrics that do not use collectors (no `collectorIds`) return `{ "collectors": [] }`.
407
+ - Unknown `metricId` returns `404 NotFoundError`.
408
+ - Missing permission for the metric returns `403 NotAllowedError`.
409
+ - A metric that references a collector ID that is not registered returns `500`.
410
+
411
+ #### Example Request
412
+
413
+ ```bash
414
+ curl -X GET "{{url}}/api/scorecard/metrics/dora.changeFailureRate/collectors" \
415
+ -H "Authorization: Bearer <token>"
416
+ ```
417
+
418
+ #### Example Response
419
+
420
+ ```json
421
+ {
422
+ "collectors": [
423
+ {
424
+ "id": "github:doraDeployments",
425
+ "description": "Collects GitHub deployments."
426
+ },
427
+ {
428
+ "id": "jira:doraIncidents",
429
+ "description": "Collects Jira incidents."
430
+ }
431
+ ]
432
+ }
433
+ ```
434
+
301
435
  ### `GET /aggregations/:aggregationId`
302
436
 
303
437
  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)).
304
438
 
305
439
  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.
306
440
 
307
- The **`aggregationId`** is either:
308
-
309
- - A key under **`scorecard.aggregationKPIs`** in app-config (KPI-specific title, description, type, and `metricId`), or
310
- - The **metric id** itself when no KPI entry exists (default **statusGrouped** behavior).
441
+ The **`aggregationId`** is a key under **`scorecard.aggregationKPIs`**, or a metric id when no KPI is configured. See [Default aggregation](#default-aggregation).
311
442
 
312
443
  #### Path Parameters
313
444
 
@@ -330,15 +461,102 @@ curl -X GET "{{url}}/api/scorecard/aggregations/github.openPRs" \
330
461
  -H "Authorization: Bearer <token>"
331
462
  ```
332
463
 
464
+ ### `GET /aggregations/:aggregationId/time-series`
465
+
466
+ Returns a **daily** history of a **scalar** KPI (`sum`, `average`, `max`, `min`, or `count`) across entities you own. Each response point is one UTC day: Scorecard takes **latest stored row** for each owned entity that day (including calculation failures), then rolls successful values up with the KPI’s aggregation type. Optional **`filter.status`** applies only to successes. UTC days with no rows are omitted; a day with only failures is included with **`value: null`**, **`status: error`** and **`errors`** list.
467
+
468
+ Only [scalar](./docs/aggregation.md#scalar-types) aggregation types are supported. **`statusGrouped`** and **`weightedStatusScore`** return **`400 Bad Request`**. See [aggregation.md](./docs/aggregation.md#get-aggregationsaggregationidtime-series) for details.
469
+
470
+ #### Path Parameters
471
+
472
+ | Parameter | Type | Required | Description |
473
+ | --------------- | ------ | -------- | -------------------------------------------------------------------------------------------------------------------- |
474
+ | `aggregationId` | string | Yes | Same as `GET /aggregations/:aggregationId`. Must resolve to a scalar type (`sum`, `average`, `max`, `min`, `count`). |
475
+
476
+ #### Query Parameters
477
+
478
+ | Parameter | Type | Required | Description |
479
+ | --------- | ------ | -------- | -------------------------------------------------------------------- |
480
+ | `from` | string | Yes | Inclusive range start (ISO-8601) |
481
+ | `to` | string | Yes | Inclusive range end (ISO-8601); must be `>= from`; max span 365 days |
482
+
483
+ #### Authentication / permissions
484
+
485
+ Requires user authentication, `scorecard.metric.read` permission, and `catalog.entity.read` permission for each aggregated entity.
486
+
487
+ #### Example Request
488
+
489
+ ```bash
490
+ curl -X GET "{{url}}/api/scorecard/aggregations/avgDeploymentFrequency/time-series?from=2026-08-24T00:00:00.000Z&to=2026-08-24T23:59:59.999Z" \
491
+ -H "Authorization: Bearer <token>"
492
+ ```
493
+
494
+ ### Example Response
495
+
496
+ ```json
497
+ {
498
+ "id": "avgDeploymentFrequency",
499
+ "metricId": "dora.deploymentFrequency",
500
+ "metadata": {
501
+ "title": "Average Deployment Frequency",
502
+ "description": "This KPI provides average weekly production deploys over a 30-day window per entity.",
503
+ "type": "number",
504
+ "unit": "/week",
505
+ "history": true,
506
+ "visualization": "sparkline",
507
+ "aggregationType": "average"
508
+ },
509
+ "points": [
510
+ {
511
+ "value": 6.8,
512
+ "successCount": 4,
513
+ "errorCount": 3,
514
+ "total": 7,
515
+ "status": "success",
516
+ "timestamp": "2026-08-24T00:00:00.000Z",
517
+ "errors": [
518
+ { "message": "GitHub API error", "count": 2 },
519
+ { "message": "timeout", "count": 1 }
520
+ ]
521
+ }
522
+ ],
523
+ "thresholds": {
524
+ "rules": [
525
+ {
526
+ "key": "elite",
527
+ "expression": ">=7",
528
+ "color": "success.main",
529
+ "icon": "scorecardSuccessStatusIcon"
530
+ },
531
+ {
532
+ "key": "medium",
533
+ "expression": "1-7",
534
+ "color": "warning.main",
535
+ "icon": "scorecardWarningStatusIcon"
536
+ },
537
+ {
538
+ "key": "error",
539
+ "expression": "<1",
540
+ "color": "error.main",
541
+ "icon": "scorecardErrorStatusIcon"
542
+ }
543
+ ]
544
+ },
545
+ "aggregationChartDisplayColor": "warning.main"
546
+ }
547
+ ```
548
+
333
549
  ### `GET /aggregations/:aggregationId/metadata`
334
550
 
335
- 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).
551
+ Returns **title**, **description**, **type**, **unit**, **history**, **visualization**, **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).
336
552
 
337
553
  ```bash
338
554
  curl -X GET "{{url}}/api/scorecard/aggregations/openIssuesKpi/metadata" \
339
555
  -H "Authorization: Bearer <token>"
340
556
  ```
341
557
 
558
+ For endpoint details, see [aggregation.md](./docs/aggregation.md#get-aggregationsaggregationidmetadata).
559
+
342
560
  ### `GET /metrics/:metricId/catalog/aggregations` (deprecated; removal planned)
343
561
 
344
562
  This endpoint **remains available** for backward compatibility and behaves like the default case of **`GET /aggregations/:metricId`** (status-grouped aggregation for that metric). **It will be removed in a future major release** of the plugin - migrate to **`GET /aggregations/:aggregationId`**.