Skip to content

feat(scorecard): scorecard timeseries scalar aggregation - #4476

Open
dzemanov wants to merge 10 commits into
redhat-developer:mainfrom
dzemanov:scorecard/timeseries-aggregation-RHIDP-14402
Open

feat(scorecard): scorecard timeseries scalar aggregation#4476
dzemanov wants to merge 10 commits into
redhat-developer:mainfrom
dzemanov:scorecard/timeseries-aggregation-RHIDP-14402

Conversation

@dzemanov

@dzemanov dzemanov commented Aug 26, 2026

Copy link
Copy Markdown
Member

Hey, I just made a Pull Request!

  • Adds new endpoint GET /aggregations/:aggregationId/time-series
  • Endpoint GET /aggregations/:aggregationId/metadata now includes metadata.visualization, so frontend can determine what endpoint to call for aggregation data - use new endpoint GET /aggregations/:aggregationId/time-series if visualization = sparkline, use GET /aggregations/:aggregationId otherwise.
  • Removes MetricDefaultVisualizationType type in favor of ScorecardVisualizationType. The default visualization changes from 'value' to 'donut'.
  • When using default aggregation (no aggregation KPI block specified in app-config), the aggregation type is then:
    • average when the metric’s defaultVisualization is sparkline
    • statusGrouped otherwise
  • Updated .warning log to .info log when no aggregation KPI is configured and scorecard uses default aggregation, as it is not an invalid state, it's normal operation. It also reduces log noise.

GET /aggregations/:aggregationId/time-series:

  • Only scalar aggregation types (sum, average, max, min, count) are supported.

  • AggregationTypes statusGrouped and weightedStatusScore are not supported and return 400.

  • aggregationChartDisplayColor (in line with naming in weightedStatusScore aggregation result) comes from value of the last successful point classified against KPI thresholds.

  • No data UTC days are omitted from response.

  • Aggregation uses aggregationKPIs.<aggregationId>.options.thresholds if configured in app-config or by default DEFAULT_NUMBER_THRESHOLDS. This is in line with how scalar aggregation works. For me it would make sense to load default thresholds of a metric instead of using DEFAULT_NUMBER_THRESHOLDS to avoid redefining them in app-config and confusion with filter filtering by original threshold names, to be worked on in a separate ticket.

  • Supports default aggregation that uses :metricId, when no aggregation config is defined.

  • Example request

curl -X GET "{{url}}/api/scorecard/aggregations/avgDeploymentFrequency/time-series?from=2026-08-24T00:00:00.000Z&to=2026-08-24T23:59:59.999Z" \
  -H "Authorization: Bearer <token>"
  • Example response:
{
  "id": "avgDeploymentFrequency",
  "metricId": "dora.deploymentFrequency",
  "metadata": {
    "title": "Average Deployment Frequency",
    "description": "This KPI provides average weekly production deploys over a 30-day window per entity.",
    "type": "number",
    "unit": "/week",
    "history": true,
    "visualization": "sparkline",
    "aggregationType": "average"
  },
  "points": [
    {
      "value": 6.8,
      "successCount": 4,
      "errorCount": 3,
      "total": 7,
      "status": "success",
      "timestamp": "2026-08-24T00:00:00.000Z",
      "errors": [
        { "message": "GitHub API error", "count": 2 },
        { "message": "timeout", "count": 1 }
      ]
    }
  ],
  "thresholds": {
    "rules": [
      {
        "key": "elite",
        "expression": ">=7",
        "color": "success.main",
        "icon": "scorecardSuccessStatusIcon"
      },
      {
        "key": "medium",
        "expression": "1-7",
        "color": "warning.main",
        "icon": "scorecardWarningStatusIcon"
      },
      {
        "key": "error",
        "expression": "<1",
        "color": "error.main",
        "icon": "scorecardErrorStatusIcon"
      }
    ]
  },
  "aggregationChartDisplayColor": "warning.main"
}
  • Example KPI config
scorecard:
  aggregationKPIs:
    avgDeploymentFrequency:
      title: Average Deployment Frequency
      description: This KPI provides average weekly production deploys over a 30-day window per entity.
      type: average
      metricId: dora.deploymentFrequency
      options:
        thresholds:
          rules:
            - key: elite
              expression: '>=7'
              color: success.main
              icon: scorecardSuccessStatusIcon
            - key: medium
              expression: '1-7'
              color: warning.main
              icon: scorecardWarningStatusIcon
            - key: error
              expression: '<1'
              color: error.main
              icon: scorecardErrorStatusIcon
    avgEliteDeploymentFrequency:
      title: Average Elite Deployment Frequency
      description: This KPI provides average elite weekly production deploys over a 30-day window per entity.
      type: average
      metricId: dora.deploymentFrequency
      options:
        thresholds:
          rules:
            - key: elite
              expression: '>=7'
              color: success.main
              icon: scorecardSuccessStatusIcon
            - key: medium
              expression: '1-7'
              color: warning.main
              icon: scorecardWarningStatusIcon
            - key: error
              expression: '<1'
              color: error.main
              icon: scorecardErrorStatusIcon
      filter:
        status: elite

Fixes

Fixes https://redhat.atlassian.net/browse/RHIDP-14402

How to test

Requirements: jq, podman, sqlite3

1. Create test data

a) postgres

App-config

backend:
  database:
    client: pg
    connection:
      host: localhost
      port: 5432
      user: postgres
      password: backstage
podman run --name backstage-psql -e POSTGRES_PASSWORD=backstage -it -d -p 127.0.0.1:5432:5432 postgres

yarn start

podman exec -it backstage-psql psql -U postgres -d backstage_plugin_scorecard
INSERT INTO metric_values
  (metric_id, catalog_entity_ref, value, timestamp, error_message, status,
   entity_kind, entity_namespace, entity_owner)
VALUES
  -- 2026-08-23: all elite (filter elite == unfiltered), max timestamp is github-scorecard-only
  ('dora.deploymentFrequency', 'component:default/dora-scorecard', '8',  '2026-08-23 12:00:00+00', NULL, 'elite',  'component', 'default', 'user:development/guest'),
  ('dora.deploymentFrequency', 'component:default/all-scorecards', '9',  '2026-08-23 12:00:00+00', NULL, 'elite',  'component', 'default', 'user:development/guest'),
  ('dora.deploymentFrequency', 'component:default/github-scorecard-only', '10', '2026-08-23 14:00:00+00', NULL, 'elite',  'component', 'default', 'user:development/guest'),
  ('dora.deploymentFrequency', 'component:default/jira-scorecard-only', '11', '2026-08-23 12:00:00+00', NULL, 'elite',  'component', 'default', 'user:development/guest'),
  ('dora.deploymentFrequency', 'component:default/dependabot-scorecard-only', '12', '2026-08-23 12:00:00+00', NULL, 'elite',  'component', 'default', 'user:development/guest'),

-- ignored, different owner
  ('dora.deploymentFrequency', 'component:default/all-scorecards-service-different-owner', '50', '2026-08-23 12:00:00+00', NULL, 'elite',  'component', 'default', 'user:development/guest'),

  -- 2026-08-24 mixed: 4 successes + 2 errors; older 99 on dora-scorecard is not used, newer 10 is used, max timestamp is error dependabot
  ('dora.deploymentFrequency', 'component:default/dora-scorecard', '99',  '2026-08-24 08:00:00+00', NULL, 'elite',  'component', 'default', 'user:development/guest'),
  ('dora.deploymentFrequency', 'component:default/dora-scorecard', '10',  '2026-08-24 18:00:00+00', NULL, 'elite',  'component', 'default', 'user:development/guest'),
  ('dora.deploymentFrequency', 'component:default/github-scorecard-only', '14',  '2026-08-24 18:00:00+00', NULL, 'elite',  'component', 'default', 'user:development/guest'),
  ('dora.deploymentFrequency', 'component:default/jira-scorecard-only', '3',   '2026-08-24 18:00:00+00', NULL, 'medium', 'component', 'default', 'user:development/guest'),
  ('dora.deploymentFrequency', 'component:default/all-scorecards', '0.2', '2026-08-24 18:00:00+00', NULL, 'low',    'component', 'default', 'user:development/guest'),
  ('dora.deploymentFrequency', 'component:default/openssf-scorecard-only', NULL,  '2026-08-24 18:00:00+00', 'timeout',         NULL, 'component', 'default', 'user:development/guest'),
  ('dora.deploymentFrequency', 'component:default/dependabot-scorecard-only', NULL,  '2026-08-24 20:00:00+00', 'GitHub API error', NULL, 'component', 'default', 'user:development/guest'),
  ('dora.deploymentFrequency', 'component:default/sonarqube-scorecard-only', NULL,  '2026-08-24 18:00:00+00', 'GitHub API error', NULL, 'component', 'default', 'user:development/guest'),

  -- 2026-08-25: errors only
  ('dora.deploymentFrequency', 'component:default/dora-scorecard', NULL, '2026-08-25 12:00:00+00', 'timeout',         NULL, 'component', 'default', 'user:development/guest'),
  ('dora.deploymentFrequency', 'component:default/all-scorecards', NULL, '2026-08-25 13:00:00+00', 'GitHub API error', NULL, 'component', 'default', 'user:development/guest');
b) sqlite

App-config

backend:
  database:
    client: better-sqlite3
    connection:
      directory: './db'
yarn start

sqlite3 /Path/to/rhdh-plugins/workspaces/scorecard/packages/backend/db/scorecard.sqlite
INSERT INTO metric_values
  (metric_id, catalog_entity_ref, value, timestamp, error_message, status,
   entity_kind, entity_namespace, entity_owner)
VALUES
  -- 2026-08-23: all elite (filter elite == unfiltered), github-scorecard-only latest timestamp
  ('dora.deploymentFrequency', 'component:default/dora-scorecard', '8',  1787486400000, NULL, 'elite',  'component', 'default', 'user:development/guest'),
  ('dora.deploymentFrequency', 'component:default/all-scorecards', '9',  1787486400000, NULL, 'elite',  'component', 'default', 'user:development/guest'),
  ('dora.deploymentFrequency', 'component:default/github-scorecard-only', '10', 1787493600000, NULL, 'elite',  'component', 'default', 'user:development/guest'),
  ('dora.deploymentFrequency', 'component:default/jira-scorecard-only', '11', 1787486400000, NULL, 'elite',  'component', 'default', 'user:development/guest'),
  ('dora.deploymentFrequency', 'component:default/dependabot-scorecard-only', '12', 1787486400000, NULL, 'elite',  'component', 'default', 'user:development/guest'),

-- ignored, different owner
  ('dora.deploymentFrequency', 'component:default/all-scorecards-service-different-owner', '50', 1787486400000, NULL, 'elite',  'component', 'default', 'user:development/guest'),

  -- 2026-08-24 mixed: 4 successes + 2 errors; older 99 on dora-scorecard is not used, newer 10 is used; latest timestamp is error dependabot
  ('dora.deploymentFrequency', 'component:default/dora-scorecard', '99',  1787558400000, NULL, 'elite',  'component', 'default', 'user:development/guest'),
  ('dora.deploymentFrequency', 'component:default/dora-scorecard', '10',  1787594400000, NULL, 'elite',  'component', 'default', 'user:development/guest'),
  ('dora.deploymentFrequency', 'component:default/github-scorecard-only', '14',  1787594400000, NULL, 'elite',  'component', 'default', 'user:development/guest'),
  ('dora.deploymentFrequency', 'component:default/jira-scorecard-only', '3',   1787594400000, NULL, 'medium', 'component', 'default', 'user:development/guest'),
  ('dora.deploymentFrequency', 'component:default/all-scorecards', '0.2', 1787594400000, NULL, 'low',    'component', 'default', 'user:development/guest'),
  ('dora.deploymentFrequency', 'component:default/openssf-scorecard-only', NULL,  1787594400000, 'timeout',         NULL, 'component', 'default', 'user:development/guest'),
  ('dora.deploymentFrequency', 'component:default/dependabot-scorecard-only', NULL,  1787594400000, 'GitHub API error', NULL, 'component', 'default', 'user:development/guest'),
  ('dora.deploymentFrequency', 'component:default/sonarqube-scorecard-only', NULL,  1787594400000, 'GitHub API error', NULL, 'component', 'default', 'user:development/guest'),

  -- 2026-08-25: errors only
  ('dora.deploymentFrequency', 'component:default/dora-scorecard', NULL, 1787659200000, 'timeout',         NULL, 'component', 'default', 'user:development/guest'),
  ('dora.deploymentFrequency', 'component:default/all-scorecards', NULL, 1787659200000, 'GitHub API error', NULL, 'component', 'default', 'user:development/guest');

To view data, you can use SQLite Viewer VS Code Extension. Or you can use any preferred DBMS.

2. Test scalar time-series aggregations

Results for points for different scalar operations:

Point timestamp (max) Original values in aggregation Error entities Not in aggregation Aggregation result value successCount ErrorCount total
2026-08-23T14:00:00.000Z 8, 9, 10, 11, 12 - 'different owner entity with value 50' AVG 10, SUM 50, MIN 8, MAX 12, COUNT 5 5 0 5
2026-08-24T20:00:00.000Z 10, 14, 3, 0.2, 1x timeout, 2x GH API error 'older dora-scorecard result with value 100' AVG 6.8, SUM 27.2, MIN 0.2, MAX 14, COUNT 4 4 3 7
2026-08-25T13:00:00.000Z - 1x timeout, 2x GH API error - null 0 2 2

Results for points for different scalar operations with elite filter KPI:

Point timestamp Original values in aggregation Error entities Not in aggregation Aggregation result value successCount ErrorCount total
2026-08-23T14:00:00.000Z 8, 9, 10, 11, 12 - 'different owner entity with value 50' AVG 10, SUM 50, MIN 8, MAX 12, COUNT 5 5 0 5
2026-08-25T13:00:00.000Z 10, 14 1x timeout, 2x GH API error 'older dora-scorecard result with value 100', 3 and 0.2 values that are not 'elite' status AVG 12, SUM 24, MIN 10, MAX 14, COUNT 2 2 3 5
2026-08-25T00:00:00.000Z - 1x timeout, 2x GH API error - null 0 2 2
FROM=2026-08-22T00:00:00.000Z
TO=2026-08-25T23:59:59.000Z
BASE=http://localhost:7007
TOKEN=$(curl "$BASE/api/auth/guest/refresh" | jq -r '.backstageIdentity.token')

Custom KPI

curl -H "Authorization: Bearer $TOKEN" \
 "$BASE/api/scorecard/aggregations/avgDeploymentFrequency/time-series?from=$FROM&to=$TO" | jq

Custom filter KPI

curl -H "Authorization: Bearer $TOKEN" \
 "$BASE/api/scorecard/aggregations/avgEliteDeploymentFrequency/time-series?from=$FROM&to=$TO" | jq

Default KPI (metricId) - uses metric title and description

curl -H "Authorization: Bearer $TOKEN" \
 "$BASE/api/scorecard/aggregations/dora.deploymentFrequency/time-series?from=$FROM&to=$TO" | jq

empty points

curl -H "Authorization: Bearer $TOKEN" \
 "$BASE/api/scorecard/aggregations/countDeploymentFrequency/time-series?from=2026-08-01T00:00:00.000Z&to=2026-08-11T00:00:00.000Z" | jq

only errors

curl -H "Authorization: Bearer $TOKEN" \
 "$BASE/api/scorecard/aggregations/avgDeploymentFrequency/time-series?from=2026-08-25T00:00:00.000Z&to=$TO" | jq

You can test out different scalar aggregation types by updating app-config KPI name and type:

  1. avgDeploymentFrequency, type: average (default)
  2. sumDeploymentFrequency, type: sum
  3. countDeploymentFrequency, type: count
  4. minDeploymentFrequency, type: min
  5. maxDeploymentFrequency, type: max
    For elite filter:
  6. avgEliteDeploymentFrequency, type: average (default)
  7. sumEliteDeploymentFrequency, type: sum
  8. countEliteDeploymentFrequency, type: count
  9. minEliteDeploymentFrequency, type: min
  10. maxEliteDeploymentFrequency, type: max
    Note: you can update app-config.yaml without stopping running instance, make sure when using different custom KPI, to also update :aggregationId in URL from avgDeploymentFrequency or avgEliteDeploymentFrequency

Test cases covered by test data:

  • different owner entity is filtered out
  • aggregation of success values
  • unique errors messages are aggregated with correct count, sorted by their count
  • entity latest value is used
  • no data days are not included in result
  • filter works fine
  • aggregationChartDisplayColor is classified against latest successful value of metric in time range

3. Test invalid inputs

No provider registered

curl -H "Authorization: Bearer $TOKEN" \  
 "$BASE/api/scorecard/aggregations/invalid/time-series?from=$FROM&to=$TO" | jq 

Invalid from

curl -H "Authorization: Bearer $TOKEN" \
 "$BASE/api/scorecard/aggregations/avgDeploymentFrequency/time-series?from=2026invalid&to=$TO" | jq

Invalid to

curl -H "Authorization: Bearer $TOKEN" \
 "$BASE/api/scorecard/aggregations/avgDeploymentFrequency/time-series?from=$FROM&to=2026invalid" | jq

invalid range

curl -H "Authorization: Bearer $TOKEN" \
 "$BASE/api/scorecard/aggregations/avgDeploymentFrequency/time-series?from=2024-08-22T00:00:00.000Z&to=2026-08-22T00:00:00.000Z" | jq

invalid from>to

curl -H "Authorization: Bearer $TOKEN" \
 "$BASE/api/scorecard/aggregations/avgDeploymentFrequency/time-series?from=2024-08-22T00:00:00.000Z&to=2024-08-20T00:00:00.000Z" | jq

No permissions:

permission:
  enabled: true
curl -H "Authorization: Bearer $TOKEN" \
 "$BASE/api/scorecard/aggregations/avgDeploymentFrequency/time-series?from=$FROM&to=$TO" | jq

✔️ Checklist

  • A changeset describing the change and affected packages. (more info)
  • Added or Updated documentation
  • Tests for new functionality and regression tests for bug fixes
  • Screenshots attached (for UI changes)

@rhdh-qodo-merge

Copy link
Copy Markdown

PR Summary by Qodo

Add scalar aggregation time-series support

✨ Enhancement 🧪 Tests 📝 Documentation ⚙️ Configuration changes 🕐 40+ Minutes

Grey Divider

AI Description

• Adds authenticated daily scalar aggregation histories across owned entities.
• Reports sparse UTC points, calculation errors, thresholds, and chart color.
• Unifies visualization types and selects aggregation defaults from metric visualization.
Diagram

graph TD
  Client["Scorecard client"] --> Route["Time-series route"] --> Auth["Access checks"] --> Service["Aggregation service"] --> Strategy["Scalar strategy"] --> Loader["Metric loader"] --> Database["Metric database"]
  Database --> Loader --> Strategy --> Service --> Route --> Client
Loading
High-Level Assessment

The chosen layered approach is appropriate: it extends the existing aggregation strategy abstraction, performs daily rollups in one database round-trip, and reuses existing authorization and metadata mapping. Loading raw samples for application-side aggregation was considered but would increase transfer volume and memory use, while generating empty calendar days would conflict with the intentionally sparse response contract.

Files changed (42) +3426 / -109

Enhancement (21) +744 / -71
getEntityMetrics.tsAccept donut visualization in entity metrics action +1/-1

Accept donut visualization in entity metrics action

• Replaces the obsolete value visualization enum with donut while retaining sparkline support.

workspaces/scorecard/plugins/scorecard-backend/src/actions/getEntityMetrics.ts

listMetrics.tsExpose donut visualization in metric listings +1/-1

Expose donut visualization in metric listings

• Updates the list action schema to return donut or sparkline visualization values.

workspaces/scorecard/plugins/scorecard-backend/src/actions/listMetrics.ts

DatabaseMetricValues.tsQuery scalar aggregates per UTC day +151/-15

Query scalar aggregates per UTC day

• Adds a single-round-trip CTE query that selects each entity's latest daily row and computes scalar values, success/error counts, and grouped error messages. Centralizes database-specific timestamp and numeric expressions.

workspaces/scorecard/plugins/scorecard-backend/src/database/DatabaseMetricValues.ts

types.tsDefine database time-series point types +14/-0

Define database time-series point types

• Introduces internal scalar daily point and grouped calculation-error models.

workspaces/scorecard/plugins/scorecard-backend/src/database/types.ts

buildScalarTimeSeriesPoints.tsBuild daily points from joined query rows +78/-0

Build daily points from joined query rows

• Consolidates joined error rows by UTC day, coerces driver numeric values, sorts errors, and removes empty points.

workspaces/scorecard/plugins/scorecard-backend/src/database/utils/buildScalarTimeSeriesPoints.ts

getAggregateExpression.tsSupport conditional scalar SQL aggregation +19/-5

Support conditional scalar SQL aggregation

• Allows aggregate expressions to include only rows matching a supplied SQL predicate, including conditional counts.

workspaces/scorecard/plugins/scorecard-backend/src/database/utils/getAggregateExpression.ts

validateTimeSeriesQueryParams.tsValidate aggregation time-series ranges +37/-18

Validate aggregation time-series ranges

• Extracts shared date-range validation and adds middleware for aggregation requests that do not require a metricId query parameter.

workspaces/scorecard/plugins/scorecard-backend/src/middlewares/validateTimeSeriesQueryParams.ts

AggregatedMetricLoader.tsLoad scalar aggregation history +28/-0

Load scalar aggregation history

• Adds a loader path that reads daily database aggregates and maps them into public time-series points.

workspaces/scorecard/plugins/scorecard-backend/src/service/aggregations/AggregatedMetricLoader.ts

AggregationsService.tsDispatch time series and derive default aggregation type +38/-15

Dispatch time series and derive default aggregation type

• Adds time-series strategy dispatch with InputError handling for unsupported types. Defaults sparkline metrics to average, other metrics to statusGrouped, and logs fallback resolution at info level.

workspaces/scorecard/plugins/scorecard-backend/src/service/aggregations/AggregationsService.ts

ScalarAggregationStrategy.tsProduce threshold-classified scalar histories +52/-1

Produce threshold-classified scalar histories

• Loads daily scalar points, selects KPI or default number thresholds, and derives chart color from the last successful point.

workspaces/scorecard/plugins/scorecard-backend/src/service/aggregations/strategies/ScalarAggregationStrategy.ts

types.tsExtend aggregation strategies with optional history +14/-2

Extend aggregation strategies with optional history

• Adds an optional daily time-series operation to the aggregation strategy interface.

workspaces/scorecard/plugins/scorecard-backend/src/service/aggregations/strategies/types.ts

types.tsDefine aggregation time-range options +5/-0

Define aggregation time-range options

• Extends aggregation inputs with required from and to dates for history requests.

workspaces/scorecard/plugins/scorecard-backend/src/service/aggregations/types.ts

mappers.tsMap scalar histories into API responses +45/-1

Map scalar histories into API responses

• Adds metric visualization to aggregation metadata and maps database daily points into sparse API responses with thresholds and chart color.

workspaces/scorecard/plugins/scorecard-backend/src/service/mappers.ts

router.tsExpose authenticated aggregation time-series endpoint +62/-1

Expose authenticated aggregation time-series endpoint

• Adds GET /aggregations/:aggregationId/time-series with range validation, user and entity authorization, metric permission filtering, and service dispatch.

workspaces/scorecard/plugins/scorecard-backend/src/service/router.ts

classifyNumberAgainstThresholds.tsClassify scalar values against KPI thresholds +42/-0

Classify scalar values against KPI thresholds

• Finds the first matching numeric threshold rule and enriches standard rules with presentation defaults.

workspaces/scorecard/plugins/scorecard-backend/src/utils/aggregation/classifyNumberAgainstThresholds.ts

withStandardThresholdDefaults.tsSupply standard threshold colors and icons +53/-0

Supply standard threshold colors and icons

• Fills missing presentation fields for success, warning, and error threshold rules without overriding explicit values.

workspaces/scorecard/plugins/scorecard-backend/src/utils/aggregation/withStandardThresholdDefaults.ts

formatUtcDate.tsFormat UTC days as midnight timestamps +7/-0

Format UTC days as midnight timestamps

• Adds a helper that converts YYYY-MM-DD values into start-of-day UTC ISO timestamps.

workspaces/scorecard/plugins/scorecard-backend/src/utils/formatUtcDate.ts

Metric.tsAdopt shared scorecard visualization type +4/-11

Adopt shared scorecard visualization type

• Replaces MetricDefaultVisualization with ScorecardVisualizationType across metric result models, removing the value option.

workspaces/scorecard/plugins/scorecard-common/src/types/Metric.ts

aggregation.tsDefine public scalar time-series contracts +65/-0

Define public scalar time-series contracts

• Adds visualization metadata plus public daily point, error, response, threshold, and chart color types.

workspaces/scorecard/plugins/scorecard-common/src/types/aggregation.ts

index.tsExport scorecard visualization types +1/-0

Export scorecard visualization types

• Re-exports the new shared scorecard type module from the common package.

workspaces/scorecard/plugins/scorecard-common/src/types/index.ts

scorecard.tsIntroduce shared visualization constants and type +27/-0

Introduce shared visualization constants and type

• Defines donut and sparkline visualization constants and the public ScorecardVisualizationType union.

workspaces/scorecard/plugins/scorecard-common/src/types/scorecard.ts

Tests (16) +2249 / -20
mockDatabaseMetricValues.tsMock scalar time-series database reads +3/-0

Mock scalar time-series database reads

• Extends the database fixture with the new scalar aggregation history method.

workspaces/scorecard/plugins/scorecard-backend/fixtures/mockDatabaseMetricValues.ts

listMetrics.test.tsUpdate list metrics visualization expectation +1/-1

Update list metrics visualization expectation

• Changes the action fixture to expect donut as the non-sparkline visualization.

workspaces/scorecard/plugins/scorecard-backend/src/actions/listMetrics.test.ts

DatabaseMetricValues.test.tsCover daily scalar aggregation database behavior +991/-1

Cover daily scalar aggregation database behavior

• Adds PostgreSQL and SQLite coverage for UTC bucketing, latest daily samples, all scalar functions, filters, errors, empty data, and numeric edge cases.

workspaces/scorecard/plugins/scorecard-backend/src/database/DatabaseMetricValues.test.ts

buildScalarTimeSeriesPoints.test.tsTest scalar query row consolidation +90/-0

Test scalar query row consolidation

• Verifies daily error grouping and omission of rows without successes or calculation failures.

workspaces/scorecard/plugins/scorecard-backend/src/database/utils/buildScalarTimeSeriesPoints.test.ts

getAggregateExpression.test.tsTest conditional scalar SQL expressions +15/-0

Test conditional scalar SQL expressions

• Covers row inclusion predicates for sum, average, maximum, minimum, and count expressions.

workspaces/scorecard/plugins/scorecard-backend/src/database/utils/getAggregateExpression.test.ts

validateQueryAndParams.test.tsTest aggregation date-range validation +81/-1

Test aggregation date-range validation

• Covers required dates, ordering, equal timestamps, and the inclusive 365-day range limit.

workspaces/scorecard/plugins/scorecard-backend/src/middlewares/validateQueryAndParams.test.ts

plugin.api.test.tsExercise time-series API end to end +199/-2

Exercise time-series API end to end

• Adds backend integration coverage for scalar results, empty ranges, unsupported types, authentication, missing aggregations, and owned-entity aggregation.

workspaces/scorecard/plugins/scorecard-backend/src/plugin.api.test.ts

CatalogMetricService.test.tsReformat metric history assertion +3/-6

Reformat metric history assertion

• Simplifies an existing database call assertion without changing behavior.

workspaces/scorecard/plugins/scorecard-backend/src/service/CatalogMetricService.test.ts

AggregatedMetricLoader.test.tsTest scalar time-series loading and mapping +89/-0

Test scalar time-series loading and mapping

• Verifies empty-entity short-circuiting, database arguments, status filter forwarding, and point mapping.

workspaces/scorecard/plugins/scorecard-backend/src/service/aggregations/AggregatedMetricLoader.test.ts

AggregationsService.test.tsTest time-series dispatch and visualization defaults +107/-3

Test time-series dispatch and visualization defaults

• Covers scalar strategy dispatch, unsupported aggregation errors, sparkline-to-average fallback, status-grouped fallback, caching, and informational logging.

workspaces/scorecard/plugins/scorecard-backend/src/service/aggregations/AggregationsService.test.ts

scalarAggregationStrategy.test.tsTest scalar history thresholds and chart color +193/-2

Test scalar history thresholds and chart color

• Covers custom and default thresholds, latest successful point classification, empty histories, invalid configs, and filter propagation.

workspaces/scorecard/plugins/scorecard-backend/src/service/aggregations/strategies/scalarAggregationStrategy.test.ts

mappers.test.tsTest visualization and time-series response mapping +161/-1

Test visualization and time-series response mapping

• Verifies metadata visualization, daily status and error mapping, UTC timestamps, thresholds, and chart color response fields.

workspaces/scorecard/plugins/scorecard-backend/src/service/mappers.test.ts

router.test.tsTest time-series routing, validation, and authorization +190/-2

Test time-series routing, validation, and authorization

• Covers invalid ranges, missing metrics, denied access, missing identity, unsupported types, and successful responses for every scalar aggregation type.

workspaces/scorecard/plugins/scorecard-backend/src/service/router.test.ts

classifyNumberAgainstThresholds.test.tsTest numeric threshold classification defaults +34/-0

Test numeric threshold classification defaults

• Verifies matching standard rules receive their default chart color and icon.

workspaces/scorecard/plugins/scorecard-backend/src/utils/aggregation/classifyNumberAgainstThresholds.test.ts

withStandardThresholdDefaults.test.tsTest standard threshold presentation defaults +83/-0

Test standard threshold presentation defaults

• Covers default colors and icons, explicit overrides, partial defaults, and unchanged custom rules.

workspaces/scorecard/plugins/scorecard-backend/src/utils/aggregation/withStandardThresholdDefaults.test.ts

formatUtcDate.test.tsTest UTC day to ISO conversion +9/-1

Test UTC day to ISO conversion

• Verifies a UTC calendar day is formatted as midnight ISO-8601.

workspaces/scorecard/plugins/scorecard-backend/src/utils/formatUtcDate.test.ts

Documentation (4) +391 / -18
aggregation-time-series.mdDeclare minor releases and visualization breaking change +10/-0

Declare minor releases and visualization breaking change

• Adds release notes for scalar aggregation time series and metadata visualization. Documents removal of the old visualization type and the new donut default.

workspaces/scorecard/.changeset/aggregation-time-series.md

README.mdDocument time-series aggregation API and defaults +125/-7

Document time-series aggregation API and defaults

• Explains visualization-based default aggregation selection, the new endpoint contract, permissions, parameters, and response example. Updates visualization examples from value to donut.

workspaces/scorecard/plugins/scorecard-backend/README.md

aggregation.mdExpand aggregation behavior and endpoint reference +211/-5

Expand aggregation behavior and endpoint reference

• Documents daily scalar computation, filtering, errors, sparse days, thresholds, chart color, permissions, and default aggregation behavior.

workspaces/scorecard/plugins/scorecard-backend/docs/aggregation.md

report.api.mdPublish time-series and visualization API surface +45/-6

Publish time-series and visualization API surface

• Updates the generated API report with scalar history response models, grouped errors, metadata visualization, and the shared donut/sparkline type.

workspaces/scorecard/plugins/scorecard-common/report.api.md

Other (1) +42 / -0
app-config.yamlAdd deployment-frequency aggregation examples +42/-0

Add deployment-frequency aggregation examples

• Configures average deployment-frequency KPIs with explicit thresholds, including a status-filtered variant.

workspaces/scorecard/app-config.yaml

@rhdh-qodo-merge

rhdh-qodo-merge Bot commented Aug 26, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (2) 📘 Rule violations (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

1. UTC grouping shifts days 🐞 Bug ≡ Correctness
Description
getUtcDayExpr converts the UTC-assumed, timezone-less PostgreSQL column to timestamptz and then
formats it in the session timezone, so non-UTC PostgreSQL sessions assign samples near midnight to
the preceding or following day. This returns incorrect daily points and can merge or split entity
samples across UTC days.
Code

workspaces/scorecard/plugins/scorecard-backend/src/database/DatabaseMetricValues.ts[95]

+      ? "TO_CHAR(timestamp AT TIME ZONE 'UTC', 'YYYY-MM-DD')"
Relevance

●●● Strong

This is a concrete timezone correctness bug in new UTC grouping logic; no close rejection precedent
was found.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The new expression explicitly applies AT TIME ZONE 'UTC' before formatting, while the schema
defines the column as a timezone-less dateTime; therefore rendering the resulting timezone-aware
value is sensitive to the PostgreSQL session timezone despite the method's UTC contract.

workspaces/scorecard/plugins/scorecard-backend/src/database/DatabaseMetricValues.ts[89-96]
workspaces/scorecard/plugins/scorecard-backend/migrations/20250915113801_init.js[30-37]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
PostgreSQL UTC-day grouping currently depends on the session timezone because `AT TIME ZONE 'UTC'` turns the timezone-less timestamp into a timezone-aware value before `TO_CHAR` formats it.

## Issue Context
The migration creates `timestamp` with `dateTime` and no timezone option, while the database code documents that this column is treated as UTC. Formatting the stored UTC wall-clock timestamp directly avoids a second session-timezone conversion.

## Fix Focus Areas
- workspaces/scorecard/plugins/scorecard-backend/src/database/DatabaseMetricValues.ts[89-96]
- workspaces/scorecard/plugins/scorecard-backend/src/database/DatabaseMetricValues.test.ts[1928-1930]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational

2. Breaking change marked minor 🐞 Bug ⚙ Maintainability
Description
The changeset requests minor releases even though this PR removes an exported type, removes the
accepted 'value' member, and explicitly labels those changes as breaking. Publishing this under a
minor version violates the stable packages' compatibility contract and can break consumers that
accept minor upgrades.
Code

workspaces/scorecard/.changeset/aggregation-time-series.md[R2-3]

+'@red-hat-developer-hub/backstage-plugin-scorecard-common': minor
+'@red-hat-developer-hub/backstage-plugin-scorecard-backend': minor
Relevance

● Weak

Recent PR #4379 rejected the same request: changing minor changesets to major for a breaking public
interface change.

PR-#4379

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The same changeset requests minor bumps and explicitly states that the visualization type removal
and loss of 'value' support are breaking; the packages are on stable major version 4, and the
changed public type/schema lines demonstrate the incompatibility.

workspaces/scorecard/.changeset/aggregation-time-series.md[1-10]
workspaces/scorecard/plugins/scorecard-common/src/types/Metric.ts[17-45]
workspaces/scorecard/plugins/scorecard-backend/src/actions/getEntityMetrics.ts[62-65]
workspaces/scorecard/plugins/scorecard-common/package.json[1-4]
workspaces/scorecard/plugins/scorecard-backend/package.json[1-4]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The release metadata marks explicitly breaking public API changes as minor releases.

## Issue Context
Both affected packages are currently version 4.2.0. The common package removes `MetricDefaultVisualizationType` and contracts the visualization union, while the backend output schemas stop accepting `value`.

## Fix Focus Areas
- workspaces/scorecard/.changeset/aggregation-time-series.md[1-4]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

Context sources
✅ Compliance rules (platform): 11 rules
✅ Cross-repo context — repo relationships
  Explored: repo: redhat-developer/rhdh (sha: d814f4b0)

Grey Divider

Tip of the day
💡 Did you know, you can describe a rule in plain language on the Rules page and Qodo drafts it for you

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

@rhdh-qodo-merge rhdh-qodo-merge Bot added documentation Improvements or additions to documentation enhancement New feature or request Tests labels Aug 26, 2026
@fullsend-ai-review

fullsend-ai-review Bot commented Aug 26, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 4:35 PM UTC · Completed 4:55 PM UTC

Commit: b9acf33 · View workflow run →

Runtime: claude · Model: opus → claude-opus-4-6 · Effort: high · Cost: $15.88

@fullsend-ai-review

fullsend-ai-review Bot commented Aug 26, 2026

Copy link
Copy Markdown

Review

Findings

Critical

  • [breaking-change-semver-mismatch] workspaces/scorecard/.changeset/aggregation-time-series.md:8 — The changeset marks @red-hat-developer-hub/backstage-plugin-scorecard-common as a minor bump, but the changeset body explicitly states BREAKING: MetricDefaultVisualizationType is removed, 'value' is no longer supported, and the default visualization changed from 'value' to 'donut'. Removing a public exported type and dropping an enum member are backward-incompatible changes per semver. Any downstream consumer importing MetricDefaultVisualizationType or using 'value' will fail to compile after upgrading.
    Remediation: Change the changeset to '@red-hat-developer-hub/backstage-plugin-scorecard-common': major, or preserve MetricDefaultVisualizationType as a deprecated type alias (export type MetricDefaultVisualizationType = ScorecardVisualizationType) and keep 'value' in the union to maintain backward compatibility.

Medium

  • [breaking-type-removal] workspaces/scorecard/plugins/scorecard-common/src/types/Metric.ts — The publicly exported type MetricDefaultVisualization = 'value' | 'sparkline' is deleted without a deprecation path. The replacement ScorecardVisualizationType is not a drop-in substitute (different name, 'value' replaced by 'donut'). See also: [breaking-change-semver-mismatch] finding above.
    Remediation: Add a deprecated re-export: /** @deprecated Use ScorecardVisualizationType instead */ export type MetricDefaultVisualization = ScorecardVisualizationType;

  • [stale-reference] workspaces/scorecard/plugins/scorecard-backend/docs/providers.md:72 — The provider example comment says // Optional. Omit / undefined => 'value', but this PR removes the 'value' visualization type. The new default when omitted is 'donut'. This file was not updated in the PR.
    Remediation: Change to // Optional. Omit / undefined => 'donut'. Use 'sparkline' when a time-series UI is intended.

  • [behavioral-contract-change] workspaces/scorecard/plugins/scorecard-backend/src/service/aggregations/AggregationsService.ts:78 — Default aggregation type for metrics without a KPI block changed from always statusGrouped to average when metric.defaultVisualization === 'sparkline'. This affects the existing GET /aggregations/:aggregationId endpoint — consumers relying on the statusGrouped response shape for sparkline metrics will receive a differently-shaped scalar response. The log level also changed from warn to info, reducing operator visibility during the transition.

  • [api-validation-change] workspaces/scorecard/plugins/scorecard-backend/src/actions/getEntityMetrics.ts:65 — Zod validation for defaultVisualization changes from z.enum(['value', 'sparkline']) to z.enum(['donut', 'sparkline']). Any metric provider still returning defaultVisualization: 'value' will now fail validation at the API boundary with a 400 error. Same change in listMetrics.ts.
    Remediation: Ensure all metric providers have been updated, or accept both 'value' and 'donut' during a transition period: z.enum(['value', 'donut', 'sparkline']).

Low

  • [api-response-schema-change] workspaces/scorecard/plugins/scorecard-common/src/types/aggregation.ts:82AggregationMetadata gains a new optional field visualization?: ScorecardVisualizationType. This is backward-compatible per semver but may break strict schema validators or snapshot tests in consumers.

  • [stale-reference] workspaces/scorecard/plugins/scorecard/README.md:527 — The frontend plugin README states the default is always statusGrouped aggregation. This PR changes the fallback to average for sparkline metrics. The README was not updated.

  • [naming-convention] workspaces/scorecard/plugins/scorecard-backend/src/database/DatabaseMetricValues.ts:496 — Variable latestIdsPerEntityPerUTCDay uses UTC in all-caps while the method getLatestIdsPerUtcDaySubquery uses mixed-case Utc. Minor casing inconsistency.


Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR
Previous run

Review

Findings

Critical

  • [breaking-api] workspaces/scorecard/plugins/scorecard-common/src/types/Metric.ts — The exported public type MetricDefaultVisualization ('value' | 'sparkline') has been removed from the package and replaced by ScorecardVisualizationType ('donut' | 'sparkline'). Any downstream consumer importing MetricDefaultVisualization will break. The changeset bumps minor, not major, which is not semver-compliant for a type removal.
    Remediation: Either bump scorecard-common to a new major version, or preserve MetricDefaultVisualization as a deprecated alias (e.g., export type MetricDefaultVisualization = ScorecardVisualizationType;) and include 'value' in the union or document migration guidance for the 'value''donut' rename.

Medium

  • [breaking-api] workspaces/scorecard/plugins/scorecard-common/src/types/Metric.ts — The Metric.defaultVisualization field type changed from MetricDefaultVisualization ('value' | 'sparkline') to ScorecardVisualizationType ('donut' | 'sparkline'). The 'value' literal is dropped. Downstream consumers checking for 'value' will silently stop matching.
    Remediation: Add runtime migration to map 'value' to 'donut', or keep 'value' in the union with a deprecation JSDoc.

  • [scope-creep] workspaces/scorecard/plugins/scorecard-common/src/types/scorecard.ts — The PR introduces a breaking change (removing MetricDefaultVisualization, renaming to ScorecardVisualizationType, changing default from 'value' to 'donut') that affects all consumers of scorecard-common, not just the time-series feature. This widens the PR's blast radius beyond what RHIDP-14402 suggests.

  • [stale-doc] workspaces/scorecard/plugins/scorecard-backend/docs/providers.md:72 — The comment says // Optional. Omit / undefined => 'value'. Use 'sparkline' when a time-series UI is intended. but the PR removes the 'value' option entirely; the default is now 'donut'.
    Remediation: Update to // Optional. Omit / undefined => 'donut'. Use 'sparkline' when a time-series UI is intended.

  • [api-shape] workspaces/scorecard/plugins/scorecard-backend/src/service/mappers.ts — The snapshot route GET /aggregations/:aggregationId sets id: metric.id in the response via toAggregatedMetricResult(). The new time-series route sets id: aggregationConfig.id via toScalarAggregatedMetricTimeSeriesResponse(). When a KPI config exists these differ (e.g., 'totalOpenPrs' vs 'github.openPRs'). Verify this is intentional.

  • [changeset-text] workspaces/scorecard/.changeset/aggregation-time-series.md — The changeset says MetricDefaultVisualizationType was removed but the actual type name on main is MetricDefaultVisualization (no Type suffix).

Low

  • [naming-convention] workspaces/scorecard/plugins/scorecard-backend/src/database/DatabaseMetricValues.ts — The local variable latestIdsPerEntityPerUTCDay uses uppercase UTC while all related identifiers use title-case Utc (getUtcDayExpr(), getLatestIdsPerUtcDaySubquery(), utcDay field).

  • [heading-level] workspaces/scorecard/plugins/scorecard-backend/README.md — The new ### Example Response heading uses h3 but all other example response headings use #### (h4).

  • [error-handling] workspaces/scorecard/plugins/scorecard-backend/src/service/aggregations/AggregationsService.tsgetStrategy() throws bare new Error() for unsupported types, but getAggregatedMetricTimeSeries() throws InputError. A bare Error from getStrategy() could surface as a 500 instead of a 400.

  • [scope-creep] workspaces/scorecard/plugins/scorecard-backend/src/service/aggregations/AggregationsService.ts:78 — The PR changes the default aggregation type for sparkline metrics from statusGrouped to average and the log level from warn to info. These behavioral changes affect the existing GET /aggregations/:aggregationId endpoint, not just the new time-series endpoint.

  • [edge-case] workspaces/scorecard/plugins/scorecard-backend/src/database/utils/buildScalarTimeSeriesPoints.ts:38Number(row.success_count) || 0 treats 0 and NaN identically. While unlikely to cause issues with SQL COUNT results, the || 0 pattern is fragile compared to explicit finite-check.

  • [api-contract] workspaces/scorecard/plugins/scorecard-backend/src/service/mappers.ts — Redundant null guard: buildScalarTimeSeriesPoints already sets value: null when successCount === 0, then the mapper re-checks the same condition. Defense-in-depth, but worth noting.

  • [unused-parameter] workspaces/scorecard/plugins/scorecard-backend/src/service/router.ts:461 — The thresholds field from thresholdResolver.resolveMetricThresholds(metric) is passed to getAggregatedMetricTimeSeries() but the scalar strategy reads thresholds from aggregationConfig.options?.thresholds instead. Consistent with the snapshot path but the unused field adds confusion.

  • [data-exposure] workspaces/scorecard/plugins/scorecard-backend/src/database/utils/buildScalarTimeSeriesPoints.ts:54 — Time-series response exposes raw error_message strings from metric calculation failures (e.g., 'GitHub API error', 'timeout'). These are operational details exposed to authenticated users. Consistent with existing entity-level metric views.


Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR
Previous run (2)

Review

Findings

High

  • [Breaking type removal] workspaces/scorecard/plugins/scorecard-common/src/types/Metric.ts — The exported public type MetricDefaultVisualization = 'value' | 'sparkline' is removed from scorecard-common (v4.2.0). Any downstream consumer importing MetricDefaultVisualization by name will fail to compile. The replacement type ScorecardVisualizationType is semantically different: it replaces 'value' with 'donut'. This is a breaking change to the package's public API surface.
    Remediation: The changeset is marked minor but declares itself BREAKING. At v4.2.0 (post-1.0), breaking type removals require a major bump per semver. Consider re-exporting MetricDefaultVisualization as a deprecated type alias (export type MetricDefaultVisualization = ScorecardVisualizationType) for one release cycle, or bump to major.

  • [Breaking enum value change in REST API response] workspaces/scorecard/plugins/scorecard-backend/src/actions/getEntityMetrics.ts:65 — The zod response schema for GET /metrics/:metricId/catalog/:kind/:namespace/:name changes the defaultVisualization enum from ['value', 'sparkline'] to ['donut', 'sparkline']. The same change occurs in listMetrics.ts. Any existing metric provider returning defaultVisualization: 'value' will fail zod response validation at runtime, causing a 500 error.
    Remediation: Accept both values during a transition period: z.enum(['value', 'donut', 'sparkline']). Map 'value' to 'donut' in the response mapper so existing providers continue to work. Alternatively, verify that no in-tree or documented out-of-tree provider sets defaultVisualization: 'value' explicitly.

Medium

  • [breaking-change-in-minor-bump] workspaces/scorecard/.changeset/aggregation-time-series.md:8 — The changeset marks both scorecard-common and scorecard-backend as minor, but the body explicitly declares a BREAKING change (type removal, enum value change, default behavior change). At v4.2.0, semver requires a major bump for breaking changes.

  • [Default aggregation behavior change] workspaces/scorecard/plugins/scorecard-backend/src/service/aggregations/AggregationsService.ts:78 — The default aggregation type was always statusGrouped when no KPI block existed. Now it is average when the metric's defaultVisualization is sparkline. This changes runtime behavior of the existing GET /aggregations/:aggregationId route — sparkline metrics without KPI config will return scalar average results instead of statusGrouped results, which is a different response shape that could break existing consumers.

Low

  • [stale-doc-reference] workspaces/scorecard/plugins/scorecard-backend/docs/providers.md:72 — Example provider comment says Omit / undefined => 'value', but 'value' is removed from the visualization type. The default is now 'donut'.

  • [stale-doc-reference] workspaces/scorecard/plugins/scorecard/README.md:472 — The frontend plugin README says the default aggregation is always statusGrouped, but this PR makes it conditional (average for sparkline metrics, statusGrouped otherwise).

  • [scope-creep] workspaces/scorecard/plugins/scorecard-backend/src/service/aggregations/AggregationsService.ts:78 — The log-level change from logger.warn to logger.info for the default-aggregation fallback is tangential to the time-series feature. Operators filtering on WARN to detect missing KPI config will silently lose that signal.

  • [edge-case] workspaces/scorecard/plugins/scorecard-backend/src/database/DatabaseMetricValues.ts:131getLatestIdsPerUtcDaySubquery picks MAX(id) unconditionally, including rows where value is null with no error_message. This is tested and intentional but represents a behavioral divergence from the single-entity readLatestEntityMetricValuesPerUtcDay method that is undocumented.


Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR
Previous run (3)

Review

Findings

High

  • [breaking-change-vs-semver] workspaces/scorecard/.changeset/aggregation-time-series.md:8 — The changeset marks both scorecard-common and scorecard-backend as minor, but the body explicitly declares a BREAKING change: the public type MetricDefaultVisualization is removed and the 'value' enum member is dropped from the public API. Per semver, removing a public exported type requires a major bump for scorecard-common. Additionally, a deprecated type alias could be provided for a migration period.
    Remediation: Change the changeset version for @red-hat-developer-hub/backstage-plugin-scorecard-common from minor to major. Alternatively, keep MetricDefaultVisualization as a deprecated alias for ScorecardVisualizationType for at least one release cycle.

  • [breaking-enum-value-change] workspaces/scorecard/plugins/scorecard-backend/src/actions/getEntityMetrics.ts:65 — The Zod validation schema for defaultVisualization changed from z.enum(['value', 'sparkline']) to z.enum(['donut', 'sparkline']). The same change applies to listMetrics.ts. Any existing metric provider that explicitly returns defaultVisualization: 'value' will fail Zod validation at runtime. Since defaultVisualization is optional, providers that omit it are unaffected, but those explicitly returning 'value' will encounter runtime errors.
    Remediation: Accept both values during a deprecation period (z.enum(['value', 'donut', 'sparkline'])) and normalize 'value' to 'donut' in the response, or make this a major release.

Medium

  • [stale-reference] workspaces/scorecard/plugins/scorecard-backend/docs/providers.md:72 — The PR removes the 'value' visualization option (replaced by 'donut'), but providers.md still references the old default: // Optional. Omit / undefined => 'value'. Use 'sparkline' when a time-series UI is intended. This is now stale — the default is 'donut', and 'value' is no longer a valid option.
    Remediation: Update the comment to: // Optional. Omit / undefined => 'donut'. Use 'sparkline' when a time-series UI is intended.

Low

  • [scope-creep] workspaces/scorecard/plugins/scorecard-backend/src/service/aggregations/AggregationsService.ts:75 — The default aggregation type for metrics without a KPI block changed from always statusGrouped to conditionally average when defaultVisualization === 'sparkline'. The log level also changed from .warn to .info. These behavioral changes to the existing GET /aggregations/:aggregationId endpoint are related to the time-series feature and are documented, but they alter the response shape for sparkline metrics on pre-existing endpoints.

  • [scope-creep] workspaces/scorecard/plugins/scorecard-common/src/types/Metric.ts:42 — The removal of MetricDefaultVisualization and introduction of ScorecardVisualizationType is tightly coupled to the feature but represents a breaking type refactor bundled into this feature PR.

  • [naming-coherence] workspaces/scorecard/.changeset/aggregation-time-series.md:16 — The changeset body says MetricDefaultVisualizationType was removed, but the actual removed type is MetricDefaultVisualization (no Type suffix). This typo in user-facing changelog text could confuse consumers.
    Remediation: Correct the changeset to reference MetricDefaultVisualization.

  • [naming-consistency] workspaces/scorecard/plugins/scorecard-backend/src/database/DatabaseMetricValues.ts:135 — Parameter order inconsistency between getLatestIdsSubquery(metricId, catalogEntityRefs) and the new getLatestIdsPerUtcDaySubquery(catalogEntityRefs, metricId). The new method follows the public API convention; the older private method does not.


Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR
Previous run (4)

Review

Findings

High

  • [breaking-api] workspaces/scorecard/.changeset/aggregation-time-series.md:2 — The changeset bumps both scorecard-common and scorecard-backend as minor, but the PR contains breaking changes explicitly labeled BREAKING in the changeset body: (1) The publicly exported type MetricDefaultVisualization is removed — downstream consumers importing it will get a compile error. (2) The literal 'value' is replaced by 'donut' — code comparing === 'value' will silently stop matching, and backend Zod schemas now reject 'value' at runtime. (3) The default aggregation type for metric IDs without KPI config changes from always statusGrouped to average for sparkline metrics, altering the REST API response shape. Per semver, these require a major bump.
    Remediation: Change the changeset for scorecard-common from minor to major. Consider whether scorecard-backend also needs a major bump for the behavioral REST API change. Alternatively, re-export MetricDefaultVisualization as a deprecated alias and keep 'value' in the union to preserve backward compatibility under minor.

Medium

  • [stale-doc] workspaces/scorecard/plugins/scorecard-backend/docs/providers.md:72 — The example metric provider comment says // Optional. Omit / undefined => 'value', but the 'value' visualization option has been removed and replaced with 'donut'. This file is not modified in the PR.
    Remediation: Change to // Optional. Omit / undefined => 'donut'. Use 'sparkline' when a time-series UI is intended.

Low

  • [scope-creep] workspaces/scorecard/plugins/scorecard-common/src/types/Metric.ts:30 — The PR bundles breaking type/enum changes (removing MetricDefaultVisualizationType, replacing 'value' with 'donut', changing default aggregation logic) with the time-series feature. While these may be prerequisites for the feature, they are logically separable concerns that affect downstream consumers independently.

  • [stale-doc] workspaces/scorecard/plugins/scorecard/README.md:472 — The frontend plugin README states metric IDs without a KPI block use "default statusGrouped aggregation", but sparkline metrics now default to average. The backend docs were updated but this file was not.

  • [naming-convention] workspaces/scorecard/plugins/scorecard-backend/src/database/DatabaseMetricValues.ts:72 — The constructor parameter type was widened from Knex<any, any[]> to plain Knex during the refactoring to hoist isPostgres to a class field.

  • [test-inadequate] workspaces/scorecard/plugins/scorecard-backend/src/service/router.test.ts:1758 — The router integration test for GET /aggregations/:aggregationId/time-series does not cover the filter.status case. Filter forwarding is tested at the unit level but not end-to-end through the router.


Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR
Previous run (5)

Review

Findings

Critical

  • [breaking-type-removal] workspaces/scorecard/plugins/scorecard-common/src/types/Metric.ts — The exported public type MetricDefaultVisualization (union 'value' | 'sparkline') is removed from scorecard-common. Any downstream repository or plugin that imports MetricDefaultVisualization will fail to compile. The replacement type ScorecardVisualizationType uses 'donut' | 'sparkline' — a different union that drops the 'value' member and adds 'donut'. This is a breaking change to the public API surface of an npm package, yet the changeset marks it as minor.
    Remediation: Either (1) bump scorecard-common to a new major version since this removes an exported type and changes the value domain, or (2) keep MetricDefaultVisualization as a deprecated re-export alias of ScorecardVisualizationType for at least one minor release cycle to give consumers time to migrate.

High

  • [breaking-value-change] workspaces/scorecard/plugins/scorecard-common/src/types/scorecard.ts:17 — The 'value' literal that was previously valid for Metric.defaultVisualization is no longer part of the replacement union ScorecardVisualizationType ('donut' | 'sparkline'). Any existing metric provider that returns defaultVisualization: 'value' in its metric definition will fail runtime Zod validation on the backend (z.enum(['donut', 'sparkline']) in getEntityMetrics.ts and listMetrics.ts). Existing stored/cached data or configuration referencing 'value' will break.
    Remediation: Add a backward-compatible migration path: either (a) include 'value' in ScorecardVisualizationType as a deprecated alias that maps to 'donut' at the API layer, or (b) document the migration and bump to major.

  • [semver-mismatch] workspaces/scorecard/.changeset/aggregation-time-series.md:2 — The changeset marks @red-hat-developer-hub/backstage-plugin-scorecard-common as a minor release, but the change removes a public exported type (MetricDefaultVisualization), removes a valid union member ('value'), and replaces it with a differently-named type having different members. The changeset's own description says BREAKING while marking minor — a self-contradiction. Per semver and the project's own .fullsend/AGENTS.md instructions (major for breaking API changes, removed exports, changed interfaces), this requires a major bump.
    Remediation: Change the changeset to major for scorecard-common, or preserve backward compatibility to keep the bump at minor.

Medium

  • [breaking-behavioral-change] workspaces/scorecard/plugins/scorecard-backend/src/service/aggregations/AggregationsService.ts:78 — The default aggregation type for metrics without a KPI config block changed from always statusGrouped to conditionally average (when defaultVisualization === 'sparkline'). Consumers relying on the implicit statusGrouped default for sparkline metrics will now receive a different aggregation shape (scalar vs status-grouped). The log level also changed from warn to info, so operators monitoring for warnings about missing KPI config will stop seeing alerts.
    Remediation: Document this behavioral change prominently in the changelog.

  • [stale-documentation] workspaces/scorecard/plugins/scorecard-backend/docs/providers.md:72 — An inline comment in the code sample states // Optional. Omit / undefined => 'value'. Use 'sparkline' when a time-series UI is intended. but this PR removes 'value' as a valid defaultVisualization option. The default when omitted is now 'donut', not 'value'. This file is not modified by the PR, leaving stale documentation that will mislead developers writing metric providers.
    Remediation: Update the comment to: // Optional. Omit / undefined => 'donut'. Use 'sparkline' when a time-series UI is intended.

Low

  • [scope-creep] workspaces/scorecard/plugins/scorecard-backend/src/service/aggregations/AggregationsService.ts:75 — The default aggregation type change (sparkline → average instead of statusGrouped) is architecturally motivated by the time-series feature but represents a separate behavioral change. See also: [breaking-behavioral-change] finding at this location.

  • [scope-creep] workspaces/scorecard/plugins/scorecard-common/src/types/Metric.ts:42 — The MetricDefaultVisualizationType removal is architecturally linked to the new visualization field on AggregationMetadata but represents a distinct breaking change. See also: [breaking-type-removal] finding at this location.

  • [naming-convention] workspaces/scorecard/plugins/scorecard-backend/src/database/DatabaseMetricValues.ts:165 — The new method getLatestIdsPerUTCDaySubquery uses uppercase UTC, which is inconsistent with getUtcDayExpr() in the same class.

  • [code-organization] workspaces/scorecard/plugins/scorecard-backend/src/service/aggregations/strategies/ScalarAggregationStrategy.ts:109[...points].reverse().find(...) creates a full copy of the points array. points.findLast(...) would be more concise (max 365 elements, no real performance concern).

  • [new-api-surface] workspaces/scorecard/plugins/scorecard-common/src/types/aggregation.ts:79AggregationMetadata gains a new optional visualization field. Additive and backward-compatible.

  • [naming-coherence] workspaces/scorecard/plugins/scorecard-common/src/types/aggregation.ts:153AggregatedMetricTimeSeriesResponse uses 'Response' while the snapshot equivalent uses 'Result' (AggregatedMetricResult). Minor naming asymmetry.

  • [architecture-coherence] workspaces/scorecard/plugins/scorecard-backend/src/service/aggregations/strategies/types.ts:26aggregateTimeSeries? is optional on the AggregationStrategy interface. Deliberate design choice since not all strategies support time-series, with runtime check in AggregationsService.


Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR
Previous run (6)

Review

Findings

Critical

  • [semver-mismatch] workspaces/scorecard/.changeset/aggregation-time-series.md — The changeset marks @red-hat-developer-hub/backstage-plugin-scorecard-common as minor, but the changeset body itself declares BREAKING and the diff confirms removal of a public exported type (MetricDefaultVisualization) and removal of the 'value' union member. Per semver and the project's own policy in .fullsend/AGENTS.md ("major for breaking API changes: removed exports, changed interfaces"), this should be a major bump. Publishing as minor means consumers on ^current will auto-upgrade and break at compile time.
    Remediation: Change the changeset bump for scorecard-common from minor to major, or provide a backward-compatible re-export alias so no consumer breaks on upgrade.

  • [breaking-type-removal] workspaces/scorecard/plugins/scorecard-common/src/types/Metric.ts — The exported public type MetricDefaultVisualization = 'value' | 'sparkline' has been removed and replaced by ScorecardVisualizationType = 'donut' | 'sparkline' in a new file. This is a compile-time breaking change for any downstream consumer that imports MetricDefaultVisualization by name. Additionally, the literal 'value' is no longer a valid member of the replacement union.
    Remediation: Re-export MetricDefaultVisualization as a deprecated alias mapped to ScorecardVisualizationType for at least one release cycle, and document that 'value' should be replaced with 'donut'.

High

  • [breaking-default-change] workspaces/scorecard/plugins/scorecard-common/src/types/scorecard.ts — The default visualization value changed from 'value' to 'donut', and 'value' is no longer accepted. The Zod schemas in getEntityMetrics.ts and listMetrics.ts now validate z.enum(['donut', 'sparkline']) instead of z.enum(['value', 'sparkline']), meaning existing API clients sending 'value' will receive validation errors at runtime. See also: [breaking-type-removal] finding at Metric.ts.
    Remediation: Either treat 'value' as a deprecated-but-accepted alias (map to 'donut' at the API boundary) until consumers migrate, or ensure the semver bump is major and coordinate the migration.

Medium

  • [stale-documentation] workspaces/scorecard/plugins/scorecard-backend/docs/providers.md:72 — The inline code comment states Omit / undefined => 'value', but this PR removes the 'value' visualization option and replaces the default with 'donut'. The type was also renamed from MetricDefaultVisualization to ScorecardVisualizationType.
    Remediation: Update line 72 to: // Optional. Omit / undefined => 'donut'. Use 'sparkline' when a time-series UI is intended.

Low

  • [scope-creep] workspaces/scorecard/plugins/scorecard-backend/src/service/aggregations/AggregationsService.ts:75 — The default aggregation type fallback changed from always statusGrouped to conditionally average for sparkline metrics, and the log level changed from .warn to .info. Both changes are documented in the changeset and are prerequisites for the time-series feature.

  • [edge-case] workspaces/scorecard/plugins/scorecard-backend/src/database/utils/buildScalarTimeSeriesPoints.ts:57Number(row.success_count) || 0 coercion correctly handles legitimate 0 but would silently mask NaN from corrupt DB data as 0, reducing observability of query issues.

  • [error-handling-gap] workspaces/scorecard/plugins/scorecard-backend/src/service/aggregations/AggregationsService.ts:75 — When an aggregation ID has no KPI config, the fallback calls getMetric(aggregationId). If the metric is also not found, the resulting NotFoundError lacks context about the fallback attempt.

  • [naming-consistency] workspaces/scorecard/plugins/scorecard-backend/src/database/DatabaseMetricValues.ts:70 — Constructor parameter type changed from Knex<any, any[]> to Knex. Both are functionally equivalent since Knex defaults to <any, any[]>.

  • [naming-convention] workspaces/scorecard/plugins/scorecard-backend/src/database/DatabaseMetricValues.ts:132getLatestIdsPerUTCDaySubquery uses uppercase UTC while the SQL alias uses utc_day. Internally consistent within the PR.

  • [code-organization] workspaces/scorecard/plugins/scorecard-backend/src/service/aggregations/strategies/ScalarAggregationStrategy.ts:30ThresholdEvaluator uses constructor injection with default, while WeightedStatusScoreAggregationStrategy creates it inline. Minor inconsistency; the PR approach is better for testability.


Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR
Previous run (7)

Review

Findings

Medium

  • [breaking-api] workspaces/scorecard/plugins/scorecard-common/src/types/Metric.ts — The exported public type MetricDefaultVisualization = 'value' | 'sparkline' is removed and replaced by ScorecardVisualizationType = 'donut' | 'sparkline'. This is a breaking change: the type name changes and the literal 'value' is removed. The changeset correctly labels this as BREAKING but uses a minor version bump. The project has precedent for shipping BREAKING changes as minor bumps (e.g., v2.3.0), so this may be intentional — but downstream consumers relying on semver ranges like ^x.y.z could be affected.
    Remediation: Consider bumping to major, or document explicitly why a minor bump is acceptable for this breaking change.

  • [breaking-api] workspaces/scorecard/plugins/scorecard-common/src/types/scorecard.ts — The new ScorecardVisualizationTypes replaces 'value' with 'donut', and the default visualization for metrics now returns 'donut'. Existing API consumers reading metadata.defaultVisualization and comparing against 'value' will no longer match. The Zod schema was changed from z.enum(['value', 'sparkline']) to z.enum(['donut', 'sparkline']).
    Remediation: Coordinate the migration with downstream consumers. Consider supporting both values during a transition period, or bump to major version.

  • [breaking-api] workspaces/scorecard/plugins/scorecard-backend/src/service/aggregations/AggregationsService.ts:78 — The default aggregation type for metrics without a KPI config block changed from always statusGrouped to conditionally average when defaultVisualization === 'sparkline'. This changes the shape of the GET /aggregations/:aggregationId response for existing consumers who rely on the fallback default.
    Remediation: Document this behavioral change prominently in the migration guide.

  • [api-contract] workspaces/scorecard/plugins/scorecard-backend/src/service/mappers.ts:90 — The snapshot endpoint's toAggregatedMetricResult sets id: metric.id (the backing metric id), while the new time-series endpoint's toScalarAggregatedMetricTimeSeriesResponse sets id: aggregationConfig.id (the KPI/aggregation id). When a KPI id differs from its metric id (e.g., KPI totalOpenPrs backed by metric github.openPRs), the two endpoints return different values in the top-level id field.
    Remediation: Align the id field semantics or document the intentional difference.

  • [stale-doc] workspaces/scorecard/plugins/scorecard-backend/docs/providers.md:72 — Stale default-visualization comment references removed option 'value'. The inline comment says Omit / undefined => 'value' but this PR removes 'value' from the ScorecardVisualizationType union and changes the default to 'donut'.
    Remediation: Update the comment to reference 'donut' instead of 'value'.

Low

  • [api-report-inconsistency] workspaces/scorecard/plugins/scorecard-common/report.api.mdCollectorMetadata type is removed from report.api.md but the source file collector.ts still exports it and src/types/index.ts still re-exports ./collector. This may be an API extractor tooling quirk; verify by regenerating the report.

  • [scope-creep] workspaces/scorecard/.changeset/aggregation-time-series.md:16 — The BREAKING change notice references MetricDefaultVisualizationType but the actual removed type is named MetricDefaultVisualization (without the trailing Type). Fix the changeset to reference the correct type name.

  • [edge-case] workspaces/scorecard/plugins/scorecard-backend/src/database/utils/buildScalarTimeSeriesPoints.ts:27Number(row.success_count) || 0 uses the falsy-or pattern. While correct for 0, it would coerce NaN to 0, silently hiding data integrity issues. Low risk since the database query always returns numeric aggregates.

Previous run (8)

Review

Findings

Critical

  • [breaking-type-removal] workspaces/scorecard/plugins/scorecard-common/src/types/Metric.ts — The exported type MetricDefaultVisualization ('value' | 'sparkline') has been deleted from scorecard-common@4.2.0. Any downstream consumer that imports this type will get a compile-time error. It is replaced by ScorecardVisualizationType ('donut' | 'sparkline') from the new scorecard.ts file, but there is no re-export alias to maintain backward compatibility.
    Remediation: Either keep MetricDefaultVisualization as a deprecated re-export alias (export type MetricDefaultVisualization = ScorecardVisualizationType;), or bump scorecard-common to a new major version.

  • [breaking-enum-value-change] workspaces/scorecard/plugins/scorecard-common/src/types/scorecard.ts — The defaultVisualization field previously accepted 'value' as a valid literal. The new ScorecardVisualizationType replaces 'value' with 'donut'. Any existing consumer code or persisted data using 'value' will no longer type-check, and runtime comparisons (=== 'value') will fail. The Zod schemas in getEntityMetrics.ts and listMetrics.ts also now reject 'value'.
    Remediation: Consider keeping 'value' as a deprecated alias in ScorecardVisualizationType for one release cycle, or bump to a major version.

  • [semver-mismatch] workspaces/scorecard/.changeset/aggregation-time-series.md — The changeset bumps scorecard-common and scorecard-backend as minor, but the body explicitly states BREAKING. The package is at v4.2.0 (post-1.0), so semver requires a major bump for backward-incompatible changes.
    Remediation: Change the changeset to major for scorecard-common, or convert the type removal into a non-breaking deprecation.

Medium

  • [breaking-api] workspaces/scorecard/plugins/scorecard-common/report.api.md — The CollectorMetadata type was removed from the API report, but the type still exists in collector.ts (lines 27–34) and is still exported from types/index.ts via export * from './collector'. This appears to be an api-extractor regeneration artifact rather than an intentional removal.
    Remediation: Regenerate the API report so CollectorMetadata reappears, or explicitly remove it from collector.ts if the removal is intentional.

  • [stale-doc] workspaces/scorecard/plugins/scorecard-backend/docs/aggregation.md — The GET /aggregations/:aggregationId section says "A warning is logged on the server" but the code was changed from logger.warn to logger.info. The new "Default aggregation" section in the same file correctly says "logs an info", creating an internal contradiction.
    Remediation: Change "A warning is logged" to "An info message is logged" in the GET /aggregations/:aggregationId section.

Low

  • [scope-creep] workspaces/scorecard/plugins/scorecard-common/src/types/Metric.ts — The breaking type change (MetricDefaultVisualization removal, 'value''donut') is bundled with the time-series feature. While the visualization type change supports the new default aggregation logic, these are separate concerns.

  • [scope-creep] workspaces/scorecard/plugins/scorecard-backend/src/service/aggregations/AggregationsService.ts:78 — Default aggregation type for sparkline metrics changed from statusGrouped to average. This behavioral change is documented in the changeset but bundled with the time-series feature.

  • [getter-caching-pattern] workspaces/scorecard/plugins/scorecard-backend/src/database/DatabaseMetricValues.ts:80 — The new isPostgres getter re-evaluates (this.dbClient as any).client?.config?.client on every access. Computing once in the constructor as a private readonly field would be cleaner, though the performance impact is negligible.

  • [unused parameter] workspaces/scorecard/plugins/scorecard-backend/src/service/aggregations/strategies/ScalarAggregationStrategy.ts:83options.thresholds (metric-level thresholds) is accepted via AggregationTimeSeriesOptions but never read in aggregateTimeSeries(). The method uses aggregationConfig.options?.thresholds instead. This is a pre-existing pattern from the snapshot aggregate() method.

  • [jsdoc-style] workspaces/scorecard/plugins/scorecard-backend/src/database/DatabaseMetricValues.ts — Typo: "aggrefated" should be "aggregated" in JSDoc comment on readScalarAggregatedMetricTimeSeriesByEntityRefs.

  • [missing-doc] workspaces/scorecard/plugins/scorecard-backend/README.md — Missing space: `catalog.entity.read`permission should be `catalog.entity.read` permission.

  • [log-level-change] workspaces/scorecard/plugins/scorecard-backend/src/service/aggregations/AggregationsService.ts:81 — Log level for missing KPI config changed from warn to info. The deprecated route handler still uses logger.warn for its deprecation notice — semantically different but a minor inconsistency in log-level conventions.


Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR
Previous run (9)

Review

Findings

Critical

  • [breaking-api] workspaces/scorecard/plugins/scorecard-common/src/types/Metric.ts — The exported MetricDefaultVisualizationType type (previously MetricDefaultVisualization = 'value' | 'sparkline') is completely removed from scorecard-common. Any downstream consumer that imports this type will get a compile-time error. The changeset itself explicitly labels this as BREAKING, yet the changeset declares only a minor bump for scorecard-common, not a major bump.
    Remediation: Either bump scorecard-common to a major version, or re-export MetricDefaultVisualization as a deprecated alias of ScorecardVisualizationType to preserve backward compatibility under a minor bump.

  • [breaking-api] workspaces/scorecard/plugins/scorecard-common/src/types/scorecard.ts — The 'value' visualization option has been removed and replaced by 'donut'. Any consumer that was setting defaultVisualization: 'value' on a Metric will now fail type-checking and potentially at runtime. Combined with the minor changeset bump, this is a silent breaking change for consumers on caret ranges.
    Remediation: If backward compatibility is required under a minor bump, add 'value' to ScorecardVisualizationTypes as a deprecated alias. Alternatively, bump to a major version.

Medium

  • [missing-version-bump] workspaces/scorecard/.changeset/aggregation-time-series.md — The changeset declares minor bumps for both scorecard-common and scorecard-backend, but the changeset body itself states BREAKING: "The MetricDefaultVisualizationType type has been removed." A self-declared breaking change with a minor bump is contradictory and violates the project's own changeset guidance ("major for breaking API changes — removed exports, changed interfaces, dropped support").
    Remediation: Either change the changeset to major for scorecard-common, or remove the BREAKING label and provide the deprecated alias.

  • [breaking-config] workspaces/scorecard/plugins/scorecard-backend/src/service/aggregations/AggregationsService.ts:78 — When no KPI config block exists for an aggregationId, the default aggregation type changed from always statusGrouped to conditionally average (when metric.defaultVisualization === 'sparkline'). This changes runtime behavior for existing deployments that rely on the implicit default — an integration that previously received a statusGrouped response shape for a sparkline metric will now receive a scalar response shape without any config change.
    Remediation: Document this behavioral change prominently in the changeset. Consider logging when the fallback type differs from the pre-change default.

  • [stale-doc] workspaces/scorecard/plugins/scorecard-backend/docs/providers.md:72 — Comment says Omit / undefined => 'value' but the 'value' visualization type has been removed by this PR. The new default is 'donut' (via ScorecardVisualizationTypes.DONUT).
    Remediation: Change the comment from 'value' to 'donut'.

Low

  • [doc-style] workspaces/scorecard/plugins/scorecard-backend/src/database/DatabaseMetricValues.ts:442 — JSDoc comment has typo: aggrefated should be aggregated.

  • [doc-style] workspaces/scorecard/plugins/scorecard-backend/README.md:497 — Example JSON response includes a JavaScript-style comment (// from value of last successful point classified againts KPI thresholds) inside a JSON code block. JSON does not support comments, and againts is a typo for against.

  • [stale-doc] workspaces/scorecard/plugins/scorecard/README.md:472 — States that a metric id without a KPI row uses "default statusGrouped aggregation". This PR changes the default so that sparkline metrics now default to average instead. The statement is no longer unconditionally true.

  • [data-exposure] workspaces/scorecard/plugins/scorecard-backend/src/database/DatabaseMetricValues.ts:494error_message field from stored metric_values rows is returned verbatim in the time-series response errors array. If a metric provider inadvertently includes sensitive details (API keys, internal hostnames, stack traces) in error messages, those would be exposed. This is the same exposure model as existing endpoints and is not a regression introduced by this PR.


Next steps:

  • /fs-fix — agent addresses review findings automatically
  • /fs-fix <your instruction> — agent fixes with your specific guidance
  • Push commits directly — review re-runs automatically on push
  • /fs-fix-stop — disable automatic fix runs for this PR

fullsend-ai-review[bot]

This comment was marked as outdated.

@dzemanov
dzemanov force-pushed the scorecard/timeseries-aggregation-RHIDP-14402 branch from b9acf33 to 6b9c6c1 Compare August 27, 2026 08:56
@rhdh-gh-app

rhdh-gh-app Bot commented Aug 27, 2026

Copy link
Copy Markdown

Important

This PR includes changes that affect public-facing API. Please ensure you are adding/updating documentation for new features or behavior.

Changed Packages

Package Name Package Path Changeset Bump Current Version
@red-hat-developer-hub/backstage-plugin-scorecard-backend workspaces/scorecard/plugins/scorecard-backend minor v4.2.0
@red-hat-developer-hub/backstage-plugin-scorecard-common workspaces/scorecard/plugins/scorecard-common minor v4.2.0

@fullsend-ai-review

fullsend-ai-review Bot commented Aug 27, 2026

Copy link
Copy Markdown

🤖 Review · ❌ Terminated · Started 8:58 AM UTC · Ended 9:16 AM UTC

Commit: 6b9c6c1 · View workflow run →

fullsend-ai-review[bot]

This comment was marked as outdated.

@fullsend-ai-review

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 8:58 AM UTC · Completed 9:16 AM UTC

Commit: 6b9c6c1 · View workflow run →

Runtime: claude · Model: opus → claude-opus-4-6 · Effort: high · Cost: $11.75

@dzemanov

Copy link
Copy Markdown
Member Author

QODO: UTC grouping shifts days: getUtcDayExpr converts the UTC-assumed, timezone-less PostgreSQL column to timestamptz.

Scorecard uses timestamp with timezone, code.
From https://knexjs.org/guide/schema-builder.html#timestamp: 'By default PostgreSQL creates column with timezone (timestamptz type).'
You would need to pass .timestamp('timestamp', { useTz: false, precision: 0 }) for timezone-less.

@fullsend-ai-review

fullsend-ai-review Bot commented Aug 27, 2026

Copy link
Copy Markdown

🤖 Review · ❌ Terminated · Started 11:19 AM UTC · Ended 11:56 AM UTC

Commit: 1be72f1 · View workflow run →

@fullsend-ai-review
fullsend-ai-review Bot dismissed stale reviews from themself August 27, 2026 11:56

Superseded by updated review

fullsend-ai-review[bot]

This comment was marked as outdated.

fullsend-ai-review[bot]

This comment was marked as outdated.

@fullsend-ai-review fullsend-ai-review Bot added the requires-manual-review Review requires human judgment label Aug 27, 2026
@fullsend-ai-review

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 11:19 AM UTC · Completed 11:56 AM UTC

Commit: 1be72f1 · View workflow run →

Runtime: claude · Model: opus → claude-opus-4-6 · Effort: high · Cost: $13.78

@fullsend-ai-review

fullsend-ai-review Bot commented Aug 27, 2026

Copy link
Copy Markdown

🤖 Review · ❌ Terminated · Started 12:16 PM UTC · Ended 12:31 PM UTC

Commit: 69e09fb · View workflow run →

@fullsend-ai-review

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 1:45 PM UTC · Completed 2:04 PM UTC

Commit: efc855b · View workflow run →

Runtime: claude · Model: opus → claude-opus-4-6 · Effort: high · Cost: $12.27

@fullsend-ai-review

fullsend-ai-review Bot commented Aug 31, 2026

Copy link
Copy Markdown

🤖 Review · ❌ Terminated · Started 7:04 AM UTC · Ended 7:22 AM UTC

Commit: 54ce9c7 · View workflow run →

fullsend-ai-review[bot]

This comment was marked as outdated.

fullsend-ai-review[bot]

This comment was marked as outdated.

@fullsend-ai-review

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 7:04 AM UTC · Completed 7:22 AM UTC

Commit: 54ce9c7 · View workflow run →

Runtime: claude · Model: opus → claude-opus-4-6 · Effort: high · Cost: $12.55

dzemanov and others added 8 commits August 31, 2026 14:28
Co-authored-by: Cursor <cursoragent@cursor.com>
Signed-off-by: Dominika Zemanovicova <dzemanov@redhat.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Signed-off-by: Dominika Zemanovicova <dzemanov@redhat.com>
Signed-off-by: Dominika Zemanovicova <dzemanov@redhat.com>
Signed-off-by: Dominika Zemanovicova <dzemanov@redhat.com>
Signed-off-by: Dominika Zemanovicova <dzemanov@redhat.com>
Signed-off-by: Dominika Zemanovicova <dzemanov@redhat.com>
Signed-off-by: Dominika Zemanovicova <dzemanov@redhat.com>
Signed-off-by: Dominika Zemanovicova <dzemanov@redhat.com>
@dzemanov
dzemanov force-pushed the scorecard/timeseries-aggregation-RHIDP-14402 branch from 54ce9c7 to e967e83 Compare August 31, 2026 12:47
@dzemanov

Copy link
Copy Markdown
Member Author

Rebased to fix conflicts

@fullsend-ai-review

fullsend-ai-review Bot commented Aug 31, 2026

Copy link
Copy Markdown

🤖 Review · ❌ Terminated · Started 12:48 PM UTC · Ended 1:07 PM UTC

Commit: e967e83 · View workflow run →

fullsend-ai-review[bot]

This comment was marked as outdated.

@fullsend-ai-review

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 12:48 PM UTC · Completed 1:07 PM UTC

Commit: e967e83 · View workflow run →

Runtime: claude · Model: opus → claude-opus-4-6 · Effort: high · Cost: $12.42

Signed-off-by: Dominika Zemanovicova <dzemanov@redhat.com>
@fullsend-ai-review

fullsend-ai-review Bot commented Aug 31, 2026

Copy link
Copy Markdown

🤖 Review · ❌ Terminated · Started 2:15 PM UTC · Ended 2:58 PM UTC

Commit: b8aff57 · View workflow run →

fullsend-ai-review[bot]

This comment was marked as outdated.

@fullsend-ai-review

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 2:15 PM UTC · Completed 2:58 PM UTC

Commit: b8aff57 · View workflow run →

Runtime: claude · Model: opus → claude-opus-4-6 · Effort: high · Cost: $18.59

Co-authored-by: Cursor <cursoragent@cursor.com>
Signed-off-by: Dominika Zemanovicova <dzemanov@redhat.com>
@fullsend-ai-review

fullsend-ai-review Bot commented Sep 1, 2026

Copy link
Copy Markdown

🤖 Review · ❌ Terminated · Started 9:18 AM UTC · Ended 9:35 AM UTC

Commit: 512aa89 · View workflow run →

@sonarqubecloud

sonarqubecloud Bot commented Sep 1, 2026

Copy link
Copy Markdown

@fullsend-ai-review fullsend-ai-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Note: The following review comments could not be posted on the diff (GitHub returned 422) and are included here instead:

  • workspaces/scorecard/plugins/scorecard-common/src/types/Metric.ts (file-level): Line 30 · [medium] breaking-type-removal

The publicly exported type MetricDefaultVisualization = 'value' | 'sparkline' is deleted without a deprecation path. The replacement ScorecardVisualizationType is not a drop-in substitute: different name and 'value' replaced by 'donut'. See also: [breaking-change-semver-mismatch] finding.

Suggested fix: Add a deprecated re-export: export type MetricDefaultVisualization = ScorecardVisualizationType; and consider keeping 'value' as a member until a major release.

@fullsend-ai-review fullsend-ai-review Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

See the review comment for full details.


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`.

Adds `metadata.visualization` type to `GET /aggregations/:aggregationId/metadata` response.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[critical] breaking-change-semver-mismatch

The changeset marks @red-hat-developer-hub/backstage-plugin-scorecard-common as a minor bump, but the changeset body explicitly states BREAKING: MetricDefaultVisualizationType is removed, 'value' is no longer supported, and the default visualization changed from 'value' to 'donut'. Removing a public exported type and dropping an enum member are backward-incompatible changes per semver. Any downstream consumer importing MetricDefaultVisualizationType or using 'value' will fail to compile after upgrading. This requires a major bump for scorecard-common.

Suggested fix: Change the changeset to '@red-hat-developer-hub/backstage-plugin-scorecard-common': major, or preserve MetricDefaultVisualizationType as a deprecated type alias and keep 'value' in the union to maintain backward compatibility.


if (!config) {
this.logger.warn(
const metric = metricProviderRegistry.getMetric(aggregationId);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[medium] behavioral-contract-change

Default aggregation type for metrics without a KPI block changed from always statusGrouped to average when metric.defaultVisualization === 'sparkline'. This affects the existing GET /aggregations/:aggregationId endpoint: consumers relying on the statusGrouped response shape for sparkline metrics will receive a differently-shaped scalar response. Log level also changed from warn to info, reducing operator visibility during the transition.

Suggested fix: Document this behavioral change prominently. Consider whether the log level change from warn to info might reduce visibility for operators during the transition.

unit: z.string().optional(),
history: z.boolean().optional(),
defaultVisualization: z.enum(['value', 'sparkline']).optional(),
defaultVisualization: z.enum(['donut', 'sparkline']).optional(),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[medium] api-validation-change

Zod validation for defaultVisualization changes from z.enum(['value', 'sparkline']) to z.enum(['donut', 'sparkline']). Any metric provider still returning defaultVisualization: 'value' will now fail validation at the API boundary with a 400 error. Same change in listMetrics.ts.

Suggested fix: Ensure all metric providers have been updated, or accept both 'value' and 'donut' during a transition period: z.enum(['value', 'donut', 'sparkline']).

type: MetricType;
unit?: string;
history?: boolean;
visualization?: ScorecardVisualizationType;

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] api-response-schema-change

AggregationMetadata gains a new optional field visualization?: ScorecardVisualizationType. While backward-compatible per semver, any strict schema validators or snapshot tests in downstream consumers may break.

);

const latestIdsPerEntityPerUTCDay = this.getLatestIdsPerUtcDaySubquery(
catalogEntityRefs,

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] naming-convention

Variable latestIdsPerEntityPerUTCDay uses UTC in all-caps while the method getLatestIdsPerUtcDaySubquery uses mixed case Utc. Minor casing inconsistency.

@fullsend-ai-review

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 9:18 AM UTC · Completed 9:35 AM UTC

Commit: 512aa89 · View workflow run →

Runtime: claude · Model: opus → claude-opus-4-6 · Effort: high · Cost: $12.62

@dzemanov
dzemanov requested a review from djanickova September 1, 2026 12:26

@djanickova djanickova left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thank you for the PR! I tested it locally and everything worked as expected. I only noticed two small things in docs, other than that, I haven't found any problems.

- If **`aggregationKPIs` is omitted** or a given id is not listed, aggregation KPIs still work, See [Default aggregation](#default-aggregation).
- **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).

### Default aggregation

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I am wondering whether we have to add this section to this README, or we could just provide a link to the aggregation.md file? I believe most of this is information is now duplicated across both of these files


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.

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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I believe the link should be only ./docs/aggregation.md#scalar-types

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation enhancement New feature or request Tests workspace/scorecard

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants