Skip to content

feat(scorecard): add DORA database - #4319

Open
dzemanov wants to merge 17 commits into
redhat-developer:mainfrom
dzemanov:scorecard-dora-database
Open

feat(scorecard): add DORA database#4319
dzemanov wants to merge 17 commits into
redhat-developer:mainfrom
dzemanov:scorecard-dora-database

Conversation

@dzemanov

Copy link
Copy Markdown
Member

Hey, I just made a Pull Request!

  • Persist DORA source data in the scorecard DB with tables for deployments, incidents, pull requests, and last sync
  • Persist only successful deployments
  • Sync and read through DoraSyncService / DoraDataService
  • Concurrent provider runs for the same entity/collector share one collector fetch via coalesceInFlight
  • Add daily cleanup task scorecard-dora:cleanup-expired-data with retention via scorecard.plugins.dora.dataRetentionDays (default 365)
  • Add freshness threshold in milliseconds under scorecard.plugins.dora.staleAfterMs for DORA deployment and incident collector refresh - if last successful deployments or incidents sync for a collector is within this value, data refresh is skipped and existing database data is reused.

Fixes

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

✔️ 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)

dzemanov and others added 14 commits August 14, 2026 18:04
Assisted-By: Claude <noreply@anthropic.com>
Signed-off-by: Dominika Zemanovicova <dzemanov@redhat.com>
Assisted-By: Cursor Desktop
Signed-off-by: Dominika Zemanovicova <dzemanov@redhat.com>
Assisted-By: Cursor Desktop
Signed-off-by: Dominika Zemanovicova <dzemanov@redhat.com>
Assisted-By: Cursor Desktop
Signed-off-by: Dominika Zemanovicova <dzemanov@redhat.com>
Assisted-By: Cursor Desktop
Signed-off-by: Dominika Zemanovicova <dzemanov@redhat.com>
Assisted-By: Cursor Desktop
Signed-off-by: Dominika Zemanovicova <dzemanov@redhat.com>
Assisted-By: Cursor Desktop
Signed-off-by: Dominika Zemanovicova <dzemanov@redhat.com>
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>
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>
Co-authored-by: Cursor <cursoragent@cursor.com>
Signed-off-by: Dominika Zemanovicova <dzemanov@redhat.com>
Signed-off-by: Dominika Zemanovicova <dzemanov@redhat.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Signed-off-by: Dominika Zemanovicova <dzemanov@redhat.com>
@rhdh-gh-app

rhdh-gh-app Bot commented Aug 14, 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-module-dora workspaces/scorecard/plugins/scorecard-backend-module-dora minor v0.0.0
@red-hat-developer-hub/backstage-plugin-scorecard-backend-module-jira workspaces/scorecard/plugins/scorecard-backend-module-jira minor v4.2.0

@fullsend-ai-review

fullsend-ai-review Bot commented Aug 14, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 4:49 PM UTC · Completed 5:08 PM UTC

Commit: 0c43921 · View workflow run →

@rhdh-qodo-merge

Copy link
Copy Markdown

PR Summary by Qodo

feat(scorecard): persist DORA source data in the database

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

Grey Divider

AI Description

• Persist DORA deployments, incidents, pull requests, and sync watermarks in Scorecard storage.
• Rework DORA metrics to sync incrementally and calculate from persisted source data.
• Add freshness, retention cleanup, configuration validation, documentation, and comprehensive
 database/service tests.
Diagram

graph TD
  A["DORA Metrics"] --> B["Sync Service"] --> C["Collectors"]
  B --> D[("DORA Database")] --> E["Data Service"] --> A
  F["Daily Cleanup"] --> D
Loading
High-Level Assessment

The database-backed synchronization layer is the appropriate approach: it eliminates repeated full-window collector calls while preserving provider-specific metric calculation. Direct per-provider collector reads were considered implicitly by the prior design, but cannot provide durable incremental watermarks, shared concurrent fetches, or retention management.

Files changed (69) +4628 / -1047

Enhancement (27) +1376 / -223
constants.tsDefine persistence defaults +4/-0

Define persistence defaults

• Adds retention, freshness, and cleanup-task constants for DORA persistence.

workspaces/scorecard/plugins/scorecard-backend-module-dora/src/constants.ts

DatabaseDoraDeployments.tsAdd deployment database store +84/-0

Add deployment database store

• Implements idempotent deployment persistence, ordered window queries, and expired-row deletion.

workspaces/scorecard/plugins/scorecard-backend-module-dora/src/database/DatabaseDoraDeployments.ts

DatabaseDoraIncidents.tsAdd incident database store +84/-0

Add incident database store

• Implements idempotent incident persistence, ordered window queries, and expired-row deletion.

workspaces/scorecard/plugins/scorecard-backend-module-dora/src/database/DatabaseDoraIncidents.ts

DatabaseDoraLastSync.tsAdd sync watermark store +75/-0

Add sync watermark store

• Persists last successful synchronization times and prevents watermark regression.

workspaces/scorecard/plugins/scorecard-backend-module-dora/src/database/DatabaseDoraLastSync.ts

DatabaseDoraPullRequests.tsAdd pull request database store +82/-0

Add pull request database store

• Implements idempotent, deployment-scoped pull request persistence and reads.

workspaces/scorecard/plugins/scorecard-backend-module-dora/src/database/DatabaseDoraPullRequests.ts

mappers.tsMap DORA database records +138/-0

Map DORA database records

• Maps deployment, incident, and pull request records between domain and database representations.

workspaces/scorecard/plugins/scorecard-backend-module-dora/src/database/mappers.ts

types.tsDefine persisted DORA types +70/-0

Define persisted DORA types

• Defines creation and persisted types for deployments, incidents, and pull requests.

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

DoraChangeFailureRateProvider.tsRead change-failure-rate inputs from storage +59/-76

Read change-failure-rate inputs from storage

• Synchronizes deployments and incidents concurrently, then calculates change failure rate from persisted data.

workspaces/scorecard/plugins/scorecard-backend-module-dora/src/metricProviders/DoraChangeFailureRateProvider.ts

DoraDeploymentFrequencyProvider.tsRead deployment frequency from storage +34/-39

Read deployment frequency from storage

• Synchronizes deployments and calculates frequency from persisted successful deployment records.

workspaces/scorecard/plugins/scorecard-backend-module-dora/src/metricProviders/DoraDeploymentFrequencyProvider.ts

DoraMeanTimeToRestoreProvider.tsRead MTTR incidents from storage +33/-37

Read MTTR incidents from storage

• Synchronizes incidents and calculates recovery time from persisted incident records.

workspaces/scorecard/plugins/scorecard-backend-module-dora/src/metricProviders/DoraMeanTimeToRestoreProvider.ts

DoraMedianLeadTimeForChangesProvider.tsRead lead-time inputs from storage +51/-64

Read lead-time inputs from storage

• Synchronizes successful deployments and caches pull requests per deployment before calculating lead time.

workspaces/scorecard/plugins/scorecard-backend-module-dora/src/metricProviders/DoraMedianLeadTimeForChangesProvider.ts

incidentSchemas.tsSupport incremental incident collection +2/-0

Support incremental incident collection

• Extends incident collector input schema with an updatedSince watermark.

workspaces/scorecard/plugins/scorecard-backend-module-dora/src/metricProviders/schemas/incidentSchemas.ts

module.tsWire DORA persistence services +62/-5

Wire DORA persistence services

• Migrates the database, constructs stores and services, injects them into metric providers, and starts retention cleanup.

workspaces/scorecard/plugins/scorecard-backend-module-dora/src/module.ts

CleanupExpiredDataTask.tsAdd daily DORA cleanup task +102/-0

Add daily DORA cleanup task

• Schedules daily deletion of expired deployments, incidents, and pull requests using configured retention.

workspaces/scorecard/plugins/scorecard-backend-module-dora/src/scheduler/CleanupExpiredDataTask.ts

utils.tsAdd retention time utility +19/-0

Add retention time utility

• Converts configured retention days into millisecond cutoffs.

workspaces/scorecard/plugins/scorecard-backend-module-dora/src/scheduler/utils.ts

DoraDataService.tsAdd DORA data read service +90/-0

Add DORA data read service

• Provides metric providers with entity-, collector-, and window-scoped persisted DORA data.

workspaces/scorecard/plugins/scorecard-backend-module-dora/src/service/DoraDataService.ts

DoraSyncService.tsAdd incremental DORA synchronization +292/-0

Add incremental DORA synchronization

• Fetches and persists incremental collector data, stores successful deployments only, and shares matching in-flight requests.

workspaces/scorecard/plugins/scorecard-backend-module-dora/src/service/DoraSyncService.ts

syncUtils.tsAdd synchronization utilities +58/-0

Add synchronization utilities

• Implements incremental watermark selection, freshness checks, and keyed in-flight promise sharing.

workspaces/scorecard/plugins/scorecard-backend-module-dora/src/service/syncUtils.ts

types.tsDefine synchronization options +26/-0

Define synchronization options

• Defines shared collector and computation-window option types for DORA services.

workspaces/scorecard/plugins/scorecard-backend-module-dora/src/service/types.ts

mappers.tsMap Jira issue update timestamps +1/-0

Map Jira issue update timestamps

• Maps Jira update metadata required for persisted incident refreshes.

workspaces/scorecard/plugins/scorecard-backend-module-jira/src/clients/mappers.ts

jiraSearchIssue.tsExpose Jira issue update field +1/-0

Expose Jira issue update field

• Adds the Jira updated field to the search issue schema.

workspaces/scorecard/plugins/scorecard-backend-module-jira/src/clients/schemas/jiraSearchIssue.ts

types.tsType Jira issue updates +1/-0

Type Jira issue updates

• Adds update timestamp typing to Jira search issue data.

workspaces/scorecard/plugins/scorecard-backend-module-jira/src/clients/types.ts

JiraIncidentsCollector.tsForward incident update watermark +1/-0

Forward incident update watermark

• Passes the incremental updatedSince watermark into Jira incident queries.

workspaces/scorecard/plugins/scorecard-backend-module-jira/src/collectors/JiraIncidentsCollector.ts

incidentJql.tsFilter Jira incidents by updates +3/-0

Filter Jira incidents by updates

• Adds an updated timestamp condition so existing incidents can be incrementally refreshed.

workspaces/scorecard/plugins/scorecard-backend-module-jira/src/collectors/incidentJql.ts

incidentSchemas.tsAccept incident update watermark +2/-0

Accept incident update watermark

• Adds updatedSince to the Jira incident collector input schema.

workspaces/scorecard/plugins/scorecard-backend-module-jira/src/collectors/schemas/incidentSchemas.ts

JiraCloudClientStrategy.tsRequest Jira update metadata +1/-1

Request Jira update metadata

• Includes Jira update metadata in Cloud incident search results.

workspaces/scorecard/plugins/scorecard-backend-module-jira/src/strategies/JiraCloudClientStrategy.ts

JiraDataCenterClientStrategy.tsRequest Jira update metadata +1/-1

Request Jira update metadata

• Includes Jira update metadata in Data Center incident search results.

workspaces/scorecard/plugins/scorecard-backend-module-jira/src/strategies/JiraDataCenterClientStrategy.ts

Refactor (1) +3 / -17
deploymentFilterUtils.tsFilter persisted production deployments +3/-17

Filter persisted production deployments

• Keeps environment filtering while removing redundant success-result filtering.

workspaces/scorecard/plugins/scorecard-backend-module-dora/src/metricProviders/utils/deploymentFilterUtils.ts

Documentation (5) +30 / -2
evil-turtles-return.mdPublish DORA and Jira minor releases +6/-0

Publish DORA and Jira minor releases

• Adds release metadata describing persisted, incremental DORA collector data.

workspaces/scorecard/.changeset/evil-turtles-return.md

README.mdDocument retention and staleness controls +16/-0

Document retention and staleness controls

• Documents dataRetentionDays and staleAfterMs configuration, defaults, and retention constraints.

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

change-failure-rate.mdClarify successful deployment handling +2/-0

Clarify successful deployment handling

• Documents that only successful deployments participate in DORA calculations.

workspaces/scorecard/plugins/scorecard-backend-module-dora/docs/metrics/change-failure-rate.md

deployment-frequency.mdClarify successful deployment handling +2/-0

Clarify successful deployment handling

• Documents that failed deployments are excluded from deployment-frequency calculations.

workspaces/scorecard/plugins/scorecard-backend-module-dora/docs/metrics/deployment-frequency.md

median-lead-time-for-changes.mdDocument successful deployment lead time +4/-2

Document successful deployment lead time

• Clarifies that lead-time calculation uses chronological successful production deployments.

workspaces/scorecard/plugins/scorecard-backend-module-dora/docs/metrics/median-lead-time-for-changes.md

Other (36) +3219 / -805
app-config.yamlDocument DORA persistence settings +6/-0

Document DORA persistence settings

• Adds commented examples for DORA data retention and collector freshness configuration.

workspaces/scorecard/app-config.yaml

config.d.tsExpose DORA database configuration +24/-1

Expose DORA database configuration

• Adds typed configuration for source-data retention and collector freshness thresholds.

workspaces/scorecard/plugins/scorecard-backend-module-dora/config.d.ts

knexfile.jsAdd Knex migration environments +56/-0

Add Knex migration environments

• Defines SQLite and PostgreSQL migration settings with a dedicated DORA migration history table.

workspaces/scorecard/plugins/scorecard-backend-module-dora/knexfile.js

20260723000000_init.jsCreate DORA persistence schema +101/-0

Create DORA persistence schema

• Creates indexed tables for deployments, incidents, pull requests, and per-collector sync watermarks. Preserves millisecond timestamps and cascades deployment-owned pull requests.

workspaces/scorecard/plugins/scorecard-backend-module-dora/migrations/20260723000000_init.js

package.jsonPackage DORA migrations +3/-1

Package DORA migrations

• Adds Knex and includes migration files in the published module package.

workspaces/scorecard/plugins/scorecard-backend-module-dora/package.json

DatabaseDoraDeployments.test.tsTest deployment store +276/-0

Test deployment store

• Covers deployment upserts, window reads, conflict updates, and retention deletion.

workspaces/scorecard/plugins/scorecard-backend-module-dora/src/database/DatabaseDoraDeployments.test.ts

DatabaseDoraIncidents.test.tsTest incident store +226/-0

Test incident store

• Covers incident upserts, window reads, conflict updates, and retention deletion.

workspaces/scorecard/plugins/scorecard-backend-module-dora/src/database/DatabaseDoraIncidents.test.ts

DatabaseDoraLastSync.test.tsTest sync watermark store +145/-0

Test sync watermark store

• Verifies watermark reads and monotonic updates for entity and collector pairs.

workspaces/scorecard/plugins/scorecard-backend-module-dora/src/database/DatabaseDoraLastSync.test.ts

DatabaseDoraPullRequests.test.tsTest pull request store +223/-0

Test pull request store

• Covers deployment-scoped pull request upserts, reads, and retention deletion.

workspaces/scorecard/plugins/scorecard-backend-module-dora/src/database/DatabaseDoraPullRequests.test.ts

index.tsExport database test fixtures +17/-0

Export database test fixtures

• Exports shared database fixture helpers for persistence tests.

workspaces/scorecard/plugins/scorecard-backend-module-dora/src/database/fixtures/index.ts

testDatabase.tsProvide test database helper +42/-0

Provide test database helper

• Creates migrated in-memory database fixtures for DORA store tests.

workspaces/scorecard/plugins/scorecard-backend-module-dora/src/database/fixtures/testDatabase.ts

mappers.test.tsTest database mappers +209/-0

Test database mappers

• Verifies record mapping and timestamp validation between database and domain types.

workspaces/scorecard/plugins/scorecard-backend-module-dora/src/database/mappers.test.ts

migration.tsRun module-specific migrations +37/-0

Run module-specific migrations

• Runs packaged DORA migrations using a dedicated history table in the Scorecard database.

workspaces/scorecard/plugins/scorecard-backend-module-dora/src/database/migration.ts

DoraChangeFailureRateProvider.test.tsAdapt change-failure-rate tests +142/-299

Adapt change-failure-rate tests

• Updates provider coverage for database-backed synchronization and persisted inputs.

workspaces/scorecard/plugins/scorecard-backend-module-dora/src/metricProviders/DoraChangeFailureRateProvider.test.ts

DoraConfig.test.tsTest persistence configuration parsing +105/-0

Test persistence configuration parsing

• Covers retention-window validation and stale-refresh threshold parsing.

workspaces/scorecard/plugins/scorecard-backend-module-dora/src/metricProviders/DoraConfig.test.ts

DoraConfig.tsParse persistence configuration +36/-0

Parse persistence configuration

• Parses retention and stale-refresh settings and validates safe values against the DORA window.

workspaces/scorecard/plugins/scorecard-backend-module-dora/src/metricProviders/DoraConfig.ts

DoraDeploymentFrequencyProvider.test.tsAdapt deployment-frequency tests +76/-108

Adapt deployment-frequency tests

• Updates coverage to use synchronization and database-read service doubles.

workspaces/scorecard/plugins/scorecard-backend-module-dora/src/metricProviders/DoraDeploymentFrequencyProvider.test.ts

DoraMeanTimeToRestoreProvider.test.tsAdapt MTTR tests +97/-107

Adapt MTTR tests

• Updates coverage to use synchronization and database-read service doubles.

workspaces/scorecard/plugins/scorecard-backend-module-dora/src/metricProviders/DoraMeanTimeToRestoreProvider.test.ts

DoraMedianLeadTimeForChangesProvider.test.tsAdapt lead-time tests +204/-250

Adapt lead-time tests

• Updates coverage for persisted deployments and deployment-scoped pull requests.

workspaces/scorecard/plugins/scorecard-backend-module-dora/src/metricProviders/DoraMedianLeadTimeForChangesProvider.test.ts

index.tsExport provider test fixtures +3/-0

Export provider test fixtures

• Exports shared service and store fixtures for DORA provider tests.

workspaces/scorecard/plugins/scorecard-backend-module-dora/src/metricProviders/fixtures/index.ts

mockDoraDataService.tsMock persisted DORA reads +23/-0

Mock persisted DORA reads

• Provides a DoraDataService double for metric provider tests.

workspaces/scorecard/plugins/scorecard-backend-module-dora/src/metricProviders/fixtures/mockDoraDataService.ts

mockDoraStores.tsMock DORA persistence stores +106/-0

Mock DORA persistence stores

• Provides deployment, incident, pull request, and watermark store doubles.

workspaces/scorecard/plugins/scorecard-backend-module-dora/src/metricProviders/fixtures/mockDoraStores.ts

mockDoraSyncService.tsMock DORA synchronization +23/-0

Mock DORA synchronization

• Provides a DoraSyncService double for metric provider tests.

workspaces/scorecard/plugins/scorecard-backend-module-dora/src/metricProviders/fixtures/mockDoraSyncService.ts

deploymentSchemas.test.tsTest deployment collector schema +72/-0

Test deployment collector schema

• Adds schema coverage for deployment data used by persistence synchronization.

workspaces/scorecard/plugins/scorecard-backend-module-dora/src/metricProviders/schemas/deploymentSchemas.test.ts

deploymentFilterUtils.test.tsSimplify deployment filter tests +2/-28

Simplify deployment filter tests

• Removes result filtering coverage because only successful deployments are persisted.

workspaces/scorecard/plugins/scorecard-backend-module-dora/src/metricProviders/utils/deploymentFilterUtils.test.ts

CleanupExpiredDataTask.test.tsTest retention cleanup task +122/-0

Test retention cleanup task

• Covers daily task registration, expiration cutoff handling, deletion calls, and failure logging.

workspaces/scorecard/plugins/scorecard-backend-module-dora/src/scheduler/CleanupExpiredDataTask.test.ts

utils.test.tsTest scheduler time conversion +23/-0

Test scheduler time conversion

• Covers conversion of retention days into milliseconds.

workspaces/scorecard/plugins/scorecard-backend-module-dora/src/scheduler/utils.test.ts

DoraDataService.test.tsTest persisted DORA reads +184/-0

Test persisted DORA reads

• Verifies service delegation for deployment, incident, and pull request reads.

workspaces/scorecard/plugins/scorecard-backend-module-dora/src/service/DoraDataService.test.ts

DoraSyncService.test.tsTest DORA synchronization service +478/-0

Test DORA synchronization service

• Covers incremental fetch windows, successful-deployment persistence, watermarks, staleness, and coalesced concurrent calls.

workspaces/scorecard/plugins/scorecard-backend-module-dora/src/service/DoraSyncService.test.ts

syncUtils.test.tsTest synchronization utilities +127/-0

Test synchronization utilities

• Covers watermark selection, freshness checks, and in-flight request coalescing.

workspaces/scorecard/plugins/scorecard-backend-module-dora/src/service/syncUtils.test.ts

mappers.test.tsTest Jira updated timestamp mapping +4/-0

Test Jira updated timestamp mapping

• Adds coverage for Jira issue update timestamps used by incremental incident sync.

workspaces/scorecard/plugins/scorecard-backend-module-jira/src/clients/mappers.test.ts

JiraIncidentsCollector.test.tsTest incremental Jira incident collection +6/-4

Test incremental Jira incident collection

• Verifies updatedSince is passed through Jira incident collection.

workspaces/scorecard/plugins/scorecard-backend-module-jira/src/collectors/JiraIncidentsCollector.test.ts

incidentJql.test.tsTest incremental incident JQL +6/-5

Test incremental incident JQL

• Updates JQL expectations to include the updated-since predicate.

workspaces/scorecard/plugins/scorecard-backend-module-jira/src/collectors/incidentJql.test.ts

JiraCloudClient.test.tsTest Jira Cloud updated-field requests +7/-1

Test Jira Cloud updated-field requests

• Updates Jira Cloud client expectations for updated incident data.

workspaces/scorecard/plugins/scorecard-backend-module-jira/src/strategies/JiraCloudClient.test.ts

JiraDataCenterClientStrategy.test.tsTest Jira Data Center updated-field requests +7/-1

Test Jira Data Center updated-field requests

• Updates Jira Data Center client expectations for updated incident data.

workspaces/scorecard/plugins/scorecard-backend-module-jira/src/strategies/JiraDataCenterClientStrategy.test.ts

yarn.lockLock Knex dependency +1/-0

Lock Knex dependency

• Records the resolved dependency graph for the new Knex database dependency.

workspaces/scorecard/yarn.lock

@rhdh-qodo-merge

rhdh-qodo-merge Bot commented Aug 14, 2026

Copy link
Copy Markdown

Code Review by Qodo

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

Grey Divider


Action required

1. PR cleanup breaks lead-time ✓ Resolved 🐞 Bug ≡ Correctness
Description
CleanupExpiredDataTask deletes dora_pull_requests rows by first_commit_at, which can remove PR data
needed to compute lead time for deployments that are still retained (deployment created_at) and
within the metric window. This can lead to missing/incorrect median lead-time results and
unnecessary re-collection attempts on subsequent runs.
Code

workspaces/scorecard/plugins/scorecard-backend-module-dora/src/scheduler/CleanupExpiredDataTask.ts[R89-95]

+    const deletedPullRequests = await this.pullRequests.deleteOlderThan(
+      olderThan,
+    );
+    const deletedDeployments = await this.deployments.deleteOlderThan(
+      olderThan,
+    );
+    const deletedIncidents = await this.incidents.deleteOlderThan(olderThan);
Relevance

●●● Strong

Likely real correctness issue: retention cleanup can delete PR rows still needed for lead-time
calculations; teams usually fix metric integrity.

PR-#4235

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The cleanup task deletes PRs by age of PR first commit, while lead time uses that first commit
timestamp for deployments and the PR rows are tied to deployments via FK cascade; therefore PRs can
be deleted even when their deployments are still retained and needed for lead-time computation.

workspaces/scorecard/plugins/scorecard-backend-module-dora/src/scheduler/CleanupExpiredDataTask.ts[84-96]
workspaces/scorecard/plugins/scorecard-backend-module-dora/src/database/DatabaseDoraPullRequests.ts[77-80]
workspaces/scorecard/plugins/scorecard-backend-module-dora/src/metricProviders/DoraMedianLeadTimeForChangesProvider.ts[165-189]
workspaces/scorecard/plugins/scorecard-backend-module-dora/migrations/20260723000000_init.js[67-72]
workspaces/scorecard/plugins/scorecard-backend-module-dora/src/metricProviders/DoraConfig.ts[276-291]

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

## Issue description
`CleanupExpiredDataTask` deletes pull requests using `first_commit_at < olderThan`, but PRs are used to compute lead time for deployments (`deployment.createdAt - pullRequest.firstCommitAt`). A PR’s first commit can be older than the retention cutoff even when its associated deployment is recent and should still be used for metric calculation.

Because `dora_pull_requests.deployment_id` has `ON DELETE CASCADE`, deleting expired deployments already deletes PRs for those deployments; the extra PR cleanup can delete PRs for deployments that are still retained.

## Issue Context
- PR cleanup currently happens before deployment cleanup.
- Lead-time metrics depend on PR first commit timestamps, not just deployment age.

## Fix Focus Areas
- workspaces/scorecard/plugins/scorecard-backend-module-dora/src/scheduler/CleanupExpiredDataTask.ts[89-96]
- workspaces/scorecard/plugins/scorecard-backend-module-dora/src/database/DatabaseDoraPullRequests.ts[77-81]
- workspaces/scorecard/plugins/scorecard-backend-module-dora/migrations/20260723000000_init.js[61-85]

## Suggested approach
- Remove the standalone PR delete and rely on deployment deletion + FK cascade, OR
- Change PR cleanup to delete PRs whose *associated deployment* is older than `olderThan` (e.g., `DELETE FROM dora_pull_requests WHERE deployment_id IN (SELECT id FROM dora_deployments WHERE created_at < ?)`), ensuring you never delete PRs for retained deployments.

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



Remediation recommended

2. Missing cleanup timestamp indexes ⊘ Outdated 🐞 Bug ➹ Performance
Description
The daily cleanup deletes rows using timestamp-only predicates (created_at / first_commit_at), but
the migration only adds read-path composite indexes (entity/collector-first) that may not support
these deletes efficiently. As DORA tables grow, cleanup may become increasingly expensive and can
cause avoidable DB load/locking.
Code

workspaces/scorecard/plugins/scorecard-backend-module-dora/migrations/20260723000000_init.js[R34-37]

+    table.index(
+      ['catalog_entity_ref', 'collector_id', 'created_at'],
+      'dora_deployments_entity_collector_created_at_idx',
+    );
Relevance

●● Moderate

Perf tuning depends on DB/retention scale; no close historical precedent for timestamp-only cleanup
indexes in this repo.

PR-#4235

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Cleanup uses timestamp-only delete predicates, while the migration’s indexes are keyed for
entity/collector read patterns, so the DB may not be able to use them effectively for global
time-based deletes.

workspaces/scorecard/plugins/scorecard-backend-module-dora/src/scheduler/CleanupExpiredDataTask.ts[84-96]
workspaces/scorecard/plugins/scorecard-backend-module-dora/src/database/DatabaseDoraDeployments.ts[79-82]
workspaces/scorecard/plugins/scorecard-backend-module-dora/src/database/DatabaseDoraIncidents.ts[79-82]
workspaces/scorecard/plugins/scorecard-backend-module-dora/src/database/DatabaseDoraPullRequests.ts[77-80]
workspaces/scorecard/plugins/scorecard-backend-module-dora/migrations/20260723000000_init.js[33-37]
workspaces/scorecard/plugins/scorecard-backend-module-dora/migrations/20260723000000_init.js[55-58]
workspaces/scorecard/plugins/scorecard-backend-module-dora/migrations/20260723000000_init.js[80-84]

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

## Issue description
Cleanup deletes are driven by timestamp-only filters:
- deployments/incidents: `WHERE created_at < olderThan`
- pull requests: `WHERE first_commit_at < olderThan`

The migration only creates composite indexes with `catalog_entity_ref, collector_id` leading, which are great for per-entity reads but may not help global timestamp-only deletes. This can make the daily cleanup increasingly expensive as data volume grows.

## Issue Context
Cleanup runs daily and affects all entities/collectors.

## Fix Focus Areas
- workspaces/scorecard/plugins/scorecard-backend-module-dora/migrations/20260723000000_init.js[17-85]
- workspaces/scorecard/plugins/scorecard-backend-module-dora/src/database/DatabaseDoraDeployments.ts[79-82]
- workspaces/scorecard/plugins/scorecard-backend-module-dora/src/database/DatabaseDoraIncidents.ts[79-82]
- workspaces/scorecard/plugins/scorecard-backend-module-dora/src/database/DatabaseDoraPullRequests.ts[77-80]

## Suggested approach
In the migration, add standalone indexes aligned to the cleanup predicates, e.g.:
- `dora_deployments(created_at)`
- `dora_incidents(created_at)`
- `dora_pull_requests(first_commit_at)`

Optionally validate with `EXPLAIN` on Postgres/SQLite to confirm the planner uses these indexes for the cleanup queries.

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


Grey Divider

Context
⚠️ Tickets: not configured — ticket URL found in PR but could not be fetched — check ticket provider credentials
✅ Compliance rules (platform): 11 rules
✅ Cross-repo context
  Explored: repo: redhat-developer/rhdh (sha: 88d4ad1d)
  Explored: repo: redhat-developer/rhdh-operator (sha: a425373c)
  Explored: repo: redhat-developer/rhdh-local (sha: a1776caa)

Grey Divider

Tip of the day
💡 Did you know, you can route each action level your way: inline, summary, both, or drop

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 14, 2026
@codecov

codecov Bot commented Aug 14, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 92.38754% with 22 lines in your changes missing coverage. Please review.
✅ Project coverage is 61.35%. Comparing base (f3f71a5) to head (2cbe4c4).
⚠️ Report is 22 commits behind head on main.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4319      +/-   ##
==========================================
+ Coverage   61.21%   61.35%   +0.13%     
==========================================
  Files        2507     2521      +14     
  Lines      100348   100786     +438     
  Branches    28086    28183      +97     
==========================================
+ Hits        61430    61834     +404     
- Misses      38371    38404      +33     
- Partials      547      548       +1     
Flag Coverage Δ *Carryforward flag
adoption-insights 84.55% <ø> (ø) Carriedforward from 0c43921
ai-integrations 71.04% <ø> (ø) Carriedforward from 0c43921
app-defaults 69.79% <ø> (ø) Carriedforward from 0c43921
augment 46.67% <ø> (ø) Carriedforward from 0c43921
boost 78.41% <ø> (ø) Carriedforward from 0c43921
bulk-import 72.79% <ø> (ø) Carriedforward from 0c43921
cost-management 13.55% <ø> (ø) Carriedforward from 0c43921
dcm 67.21% <ø> (ø) Carriedforward from 0c43921
e2e-adoption-insights 60.00% <ø> (ø) Carriedforward from 0c43921
e2e-extensions 62.13% <ø> (ø) Carriedforward from 0c43921
e2e-global-header 49.45% <ø> (ø) Carriedforward from 0c43921
e2e-homepage 43.49% <ø> (ø) Carriedforward from 0c43921
e2e-intelligent-assistant 46.74% <ø> (ø) Carriedforward from 0c43921
e2e-orchestrator 50.42% <ø> (ø) Carriedforward from 0c43921
e2e-quickstart 55.21% <ø> (ø) Carriedforward from 0c43921
e2e-scorecard 50.21% <ø> (ø) Carriedforward from 0c43921
e2e-theme 17.11% <ø> (ø) Carriedforward from 0c43921
extensions 56.59% <ø> (ø) Carriedforward from 0c43921
global-floating-action-button 71.18% <ø> (ø) Carriedforward from 0c43921
global-header 66.50% <ø> (ø) Carriedforward from 0c43921
homepage 47.50% <ø> (ø) Carriedforward from 0c43921
install-dynamic-plugins 59.95% <ø> (ø) Carriedforward from 0c43921
intelligent-assistant 75.42% <ø> (ø) Carriedforward from 0c43921
konflux 91.98% <ø> (ø) Carriedforward from 0c43921
lightspeed 69.02% <ø> (ø) Carriedforward from 0c43921
mcp-integrations 83.40% <ø> (ø) Carriedforward from 0c43921
orchestrator 70.87% <ø> (ø) Carriedforward from 0c43921
quickstart 63.74% <ø> (ø) Carriedforward from 0c43921
sandbox 79.56% <ø> (ø) Carriedforward from 0c43921
scorecard 87.52% <92.38%> (+0.43%) ⬆️
theme 88.14% <ø> (ø) Carriedforward from 0c43921
translations 5.12% <ø> (ø) Carriedforward from 0c43921
x2a 79.20% <ø> (ø) Carriedforward from 0c43921

*This pull request uses carry forward flags. Click here to find out more.


Continue to review full report in Codecov by Harness.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update f3f71a5...2cbe4c4. Read the comment docs.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@fullsend-ai-review

fullsend-ai-review Bot commented Aug 14, 2026

Copy link
Copy Markdown

Review

Findings

High

  • [semver-breaking-change] workspaces/scorecard/.changeset/evil-turtles-return.md — The changeset marks @red-hat-developer-hub/backstage-plugin-scorecard-backend-module-jira as a minor bump, but the changeset body declares a BREAKING change. The Jira module is at version 4.2.0 (post-1.0), so per semver this should be a major bump. Custom collectors implementing the jira:incidents contract will fail Zod validation after upgrading because the input schema now requires updatedSince and the output schema now requires updatedAt.
    Remediation: Change the changeset bump type for the Jira module from minor to major, or make the new fields backward-compatible by marking them as optional with fallback behavior.

  • [stale-collector-contract] workspaces/scorecard/plugins/scorecard-backend-module-jira/README.md:204 — The jira:incidents collector contract documentation is stale after this PR's BREAKING change. The input schema section is missing the new required field updatedSince. The output schema still shows the old format without updatedAt. This is the primary documentation consumers consult when implementing custom incident collectors.
    Remediation: Update the collector contract section in the Jira README to include updatedSince in Input and updatedAt in Output.

Medium

  • [collector-contract-compatibility] workspaces/scorecard/plugins/scorecard-backend-module-dora/src/metricProviders/schemas/incidentSchemas.ts:23 — The DORA module's incident collector input schema adds a mandatory updatedSince field. This is the contract the DORA module enforces on ANY incident collector via collectorsService.collect(). The breaking change note only mentions the Jira collector, but the DORA-side schema change affects all incident collectors generically.
    Remediation: Clarify in the breaking-change notice that the contract change applies to ALL incident collectors registered for DORA metrics, not only jira:incidents. Consider making updatedAt optional with a fallback to createdAt for backward compatibility.

Low

  • [error-handling] workspaces/scorecard/plugins/scorecard-backend-module-dora/src/service/DoraSyncService.ts:144 — In doSyncDeployments, the lastSyncDb.setLastSyncedAt watermark is advanced to options.windowTo after deploymentsDb.upsert succeeds. In practice, upsert failures with Knex are all-or-nothing per statement, so the described partial-failure scenario cannot occur. However, if batching is introduced later, this could become a data loss bug. The same pattern exists in doSyncIncidents.

  • [edge-case] workspaces/scorecard/plugins/scorecard-backend-module-dora/src/service/DoraSyncService.ts:257 — In doSyncPullRequestsForDeployment, the guard if (existing.length > 0) return prevents re-syncing pull requests once any PRs exist for a deployment. If the initial PR sync returns partial results, they will never be picked up. Unlike deployments and incidents which use a watermark, PRs have no mechanism to refresh stale data.

  • [unbounded-batch-insert] workspaces/scorecard/plugins/scorecard-backend-module-dora/src/database/DatabaseDoraDeployments.ts:52 — The upsert methods in all Database*Store classes insert the entire array in a single INSERT statement without any batch-size limit. Practical record counts are bounded by collectors' own limits, but the contract does not enforce an upper bound.

  • [function-declaration-style] workspaces/scorecard/plugins/scorecard-backend-module-dora/src/scheduler/utils.ts:17 — The daysToMilliseconds function uses arrow-function-assigned-to-const style, while every other exported utility function in this codebase uses the export function declaration style.

  • [import-style] workspaces/scorecard/plugins/scorecard-backend-module-dora/src/database/DatabaseDoraDeployments.ts:17 — Uses value import import { Knex } from 'knex' while Knex is only used as a type annotation. Same pattern in all four Database*Store files.

  • [schema-strictness-asymmetry] workspaces/scorecard/plugins/scorecard-backend-module-jira/src/collectors/schemas/incidentSchemas.ts:32 — The Jira module's incident output schema uses plain z.object() (default strip mode) while the DORA module's uses .strict(). This asymmetry is pre-existing and not 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
Previous run

Review

Findings

High

  • [stale-collector-contract] docs/metrics/mean-time-to-restore.md:71 — The incidents collector contract documents the required output as incidents: Array<{ id: string; createdAt: string; resolutionAt: string | null }> but this PR adds a new required updatedAt field to the incident schema. The required input also only documents from and to, but this PR adds updatedSince as a required input field. Custom collector implementors relying on this documentation will produce incompatible collectors.
    Remediation: Update the incidents collector contract section to include updatedAt: string in the output schema and updatedSince: string (ISO datetime) in the required input.

  • [stale-collector-contract] docs/metrics/change-failure-rate.md:130 — Same issue: the incidents collector contract in this doc page is stale — missing the new required updatedSince input field and updatedAt output field added by this PR.
    Remediation: Update the incidents collector contract to include updatedAt in output and updatedSince in input.

Medium

  • [data-loss-on-cleanup] src/scheduler/CleanupExpiredDataTask.ts:85 — The cleanup task deletes pull requests by first_commit_at < cutoff, then deployments by created_at < cutoff. The dora_pull_requests table has deployment_id as a FK with ON DELETE CASCADE to dora_deployments. If a deployment is old (created before cutoff) but has a PR with a recent first_commit_at (after the cutoff), the PR survives the explicit PR cleanup but is then cascade-deleted when the old deployment is removed. PRs newer than the retention cutoff can be silently deleted via cascade.
    Remediation: Consider deleting deployments before pull requests and relying on CASCADE for PR cleanup, or delete pull requests joined with their deployment's created_at rather than using first_commit_at independently.

  • [missing-doc] README.md — The README does not mention the new database requirement. The module now requires database access and runs migrations creating four tables (dora_deployments, dora_incidents, dora_pull_requests, dora_last_sync). Users upgrading should be aware of this new dependency.
    Remediation: Add a note to the Prerequisites or Installation section indicating the module now requires database access and will automatically run migrations.

Low

  • [test-removed] src/metricProviders/DoraChangeFailureRateProvider.test.ts — Two test scenarios were removed during the refactor to the sync/data service pattern: (1) "should return 0 when evaluated intervals have no incidents" and (2) "should attribute an incident after last successful production deployment to the following DORA interval". These validated important CFR business rules.

  • [missing-test] src/service/DoraSyncService.test.ts — No tests exist for syncPullRequestsForDeployment. The method has non-trivial logic (checks existing PRs, coalesces in-flight, calls collector, persists) but is untested.

  • [missing-doc] README.md — The README does not mention the scorecard-dora:cleanup-expired-data background task that runs daily to enforce data retention.

  • [scope-adjacent-change] src/collectors/incidentJql.ts — The Jira module changes add updatedAt/updatedSince as required fields to the incident collector schemas. This is a breaking change for any custom incident collector implementations.

  • [authorization-tier-mismatch] src/metricProviders/utils/deploymentFilterUtils.tsisSuccessfulProductionDeployment removed and replaced with isProductionEnvironment. Success filtering moved to the sync/persistence layer. This is an intentional architectural shift documented in the code comment, but represents an implicit behavioral contract change.

  • [edge-case] src/service/DoraSyncService.ts:104 — The isWithinStaleWindow check uses new Date() independently from the watermark timestamp. With very small staleAfterMs values (e.g., 1ms), the staleness check could be flaky. Minimal practical impact given the 60-second default.

  • [import-ordering] src/metricProviders/DoraChangeFailureRateProvider.ts — Import ordering shifted from the pre-existing convention. The new ordering is internally consistent across all changed files.


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 and others added 3 commits August 17, 2026 10:49
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>
Co-authored-by: Cursor <cursoragent@cursor.com>
Signed-off-by: Dominika Zemanovicova <dzemanov@redhat.com>
@fullsend-ai-review

fullsend-ai-review Bot commented Aug 17, 2026

Copy link
Copy Markdown

🤖 Review · ❌ Terminated · Started 9:30 AM UTC · Ended 9:50 AM UTC

Commit: 2cbe4c4 · View workflow run →

@sonarqubecloud

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.

See the review comment for full details.


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

  • workspaces/scorecard/.changeset/evil-turtles-return.md (file-level): Line 9 · [high] semver-breaking-change

The changeset marks @red-hat-developer-hub/backstage-plugin-scorecard-backend-module-jira as a minor bump, but the changeset body declares a BREAKING change. The Jira module is at version 4.2.0 (post-1.0), so per semver this should be a major bump. Custom collectors implementing the jira:incidents contract will fail Zod validation after upgrading because the input schema now requires updatedSince and the output schema now requires updatedAt.

Suggested fix: Change the changeset bump type for the Jira module from minor to major, or make the new fields backward-compatible by marking them as optional with fallback behavior.

  • workspaces/scorecard/plugins/scorecard-backend-module-dora/src/metricProviders/schemas/incidentSchemas.ts:23: [medium] collector-contract-compatibility

The DORA module incident collector input schema adds a mandatory updatedSince field. This is the contract the DORA module enforces on ANY incident collector via collectorsService.collect(). The breaking change note only mentions the Jira collector, but the DORA-side schema change affects all incident collectors generically.

Suggested fix: Clarify in the breaking-change notice that the contract change applies to ALL incident collectors registered for DORA metrics, not only jira:incidents. Consider making updatedAt optional with a fallback to createdAt for backward compatibility.

  • workspaces/scorecard/plugins/scorecard-backend-module-dora/src/service/DoraSyncService.ts:144: [low] error handling

In doSyncDeployments, the lastSyncDb.setLastSyncedAt watermark is advanced to options.windowTo after deploymentsDb.upsert succeeds. In practice, upsert failures with Knex are all-or-nothing per statement, so the described partial-failure scenario cannot occur. However, if batching is introduced later, this could become a data loss bug. The same pattern exists in doSyncIncidents.

  • workspaces/scorecard/plugins/scorecard-backend-module-dora/src/service/DoraSyncService.ts:257: [low] edge-case

In doSyncPullRequestsForDeployment, the guard if existing.length > 0 return prevents re-syncing pull requests once any PRs exist for a deployment. If the initial PR sync returns partial results, they will never be picked up. Unlike deployments and incidents which use a watermark, PRs have no mechanism to refresh stale data.

  • workspaces/scorecard/plugins/scorecard-backend-module-dora/src/database/DatabaseDoraDeployments.ts:52: [low] unbounded-batch-insert

The upsert methods in all Database*Store classes insert the entire array in a single INSERT statement without any batch-size limit. Practical record counts are bounded by collectors own limits, but the contract does not enforce an upper bound.

  • workspaces/scorecard/plugins/scorecard-backend-module-dora/src/scheduler/utils.ts:17: [low] function-declaration-style

The daysToMilliseconds function uses arrow-function-assigned-to-const style, while every other exported utility function in this codebase uses the export function declaration style.

  • workspaces/scorecard/plugins/scorecard-backend-module-dora/src/database/DatabaseDoraDeployments.ts:17: [low] import-style

Uses value import import { Knex } from knex while Knex is only used as a type annotation. Same pattern in all four Database*Store files.

  • workspaces/scorecard/plugins/scorecard-backend-module-jira/src/collectors/schemas/incidentSchemas.ts:32: [low] schema-strictness-asymmetry

The Jira module incident output schema uses plain z.object() (default strip mode) while the DORA module uses .strict(). This asymmetry is pre-existing and not introduced by this PR.

@fullsend-ai-review

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 9:30 AM UTC · Completed 9:50 AM UTC

Commit: 2cbe4c4 · View workflow run →

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.

1 participant