Skip to content

feat(#3529): move ThresholdConfig from MetricProvider to individual Metrics#3560

Open
fullsend-ai-coder[bot] wants to merge 1 commit into
mainfrom
agent/3529-refactor-metric-provider
Open

feat(#3529): move ThresholdConfig from MetricProvider to individual Metrics#3560
fullsend-ai-coder[bot] wants to merge 1 commit into
mainfrom
agent/3529-refactor-metric-provider

Conversation

@fullsend-ai-coder

Copy link
Copy Markdown
Contributor

Refactor the scorecard MetricProvider interface to move threshold configuration from provider-level methods to individual Metric objects.

Core interface changes:

  • Add threshold: ThresholdConfig field to the Metric type
  • Remove getMetricType(), getMetric(), getMetricThresholds(),
    calculateMetric(), and getMetricIds() from MetricProvider
  • Make getMetrics() and calculateMetrics() required on MetricProvider
  • Consolidate single/batch provider code paths into batch-only

Provider migrations:

  • All providers (dependabot, filecheck, github, jira, openssf, sonarqube)
    updated to the new interface with thresholds on metric objects
  • ThresholdResolver API: resolveProviderThresholds(provider)
    resolveMetricThresholds(metric, providerId)
  • ThresholdResolver API: resolveEntityThresholds(entity, provider)
    resolveEntityThresholds(entity, metric, providerId)
  • mergeEntityAndProviderThresholds signature updated similarly
  • PullMetricsByProviderTask consolidated to always use calculateMetrics()
  • MetricProvidersRegistry uses provider.getMetrics() for all operations

All tests updated. API reports regenerated.

Note: DatabaseMetricValues tests could not run (missing better-sqlite3 native binding in sandbox). All other 91 test suites (976 tests) passed.

Co-Authored-By: Claude Opus 4.6 noreply@anthropic.com


Closes #3529

Post-script verification

  • Branch is not main/master (agent/3529-refactor-metric-provider)
  • Secret scan passed (gitleaks — b57a430ea0c48b4dff885dc812072d5eccb6e0a2..HEAD)
  • Pre-commit hooks passed (authoritative run on runner)
  • Tests ran inside sandbox

@rhdh-gh-app

rhdh-gh-app Bot commented Jun 24, 2026

Copy link
Copy Markdown

Missing Changesets

The following package(s) are changed by this PR but do not have a changeset:

  • @red-hat-developer-hub/backstage-plugin-scorecard-backend-module-dependabot
  • @red-hat-developer-hub/backstage-plugin-scorecard-backend-module-filecheck
  • @red-hat-developer-hub/backstage-plugin-scorecard-backend-module-github
  • @red-hat-developer-hub/backstage-plugin-scorecard-backend-module-jira
  • @red-hat-developer-hub/backstage-plugin-scorecard-backend-module-openssf
  • @red-hat-developer-hub/backstage-plugin-scorecard-backend-module-sonarqube
  • @red-hat-developer-hub/backstage-plugin-scorecard-backend
  • @red-hat-developer-hub/backstage-plugin-scorecard-common
  • @red-hat-developer-hub/backstage-plugin-scorecard-node

See CONTRIBUTING.md for more information about how to add changesets.

Changed Packages

Package Name Package Path Changeset Bump Current Version
@red-hat-developer-hub/backstage-plugin-scorecard-backend-module-dependabot workspaces/scorecard/plugins/scorecard-backend-module-dependabot none v0.2.13
@red-hat-developer-hub/backstage-plugin-scorecard-backend-module-filecheck workspaces/scorecard/plugins/scorecard-backend-module-filecheck none v0.1.11
@red-hat-developer-hub/backstage-plugin-scorecard-backend-module-github workspaces/scorecard/plugins/scorecard-backend-module-github none v2.7.9
@red-hat-developer-hub/backstage-plugin-scorecard-backend-module-jira workspaces/scorecard/plugins/scorecard-backend-module-jira none v2.7.9
@red-hat-developer-hub/backstage-plugin-scorecard-backend-module-openssf workspaces/scorecard/plugins/scorecard-backend-module-openssf none v0.2.13
@red-hat-developer-hub/backstage-plugin-scorecard-backend-module-sonarqube workspaces/scorecard/plugins/scorecard-backend-module-sonarqube none v0.1.8
@red-hat-developer-hub/backstage-plugin-scorecard-backend workspaces/scorecard/plugins/scorecard-backend none v2.7.9
@red-hat-developer-hub/backstage-plugin-scorecard-common workspaces/scorecard/plugins/scorecard-common none v2.7.9
@red-hat-developer-hub/backstage-plugin-scorecard-node workspaces/scorecard/plugins/scorecard-node none v2.7.9

@codecov

codecov Bot commented Jun 24, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.00000% with 5 lines in your changes missing coverage. Please review.
✅ Project coverage is 51.06%. Comparing base (0186e63) to head (dbcc8c6).
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3560      +/-   ##
==========================================
- Coverage   51.07%   51.06%   -0.01%     
==========================================
  Files        2346     2346              
  Lines       89269    89236      -33     
  Branches    25020    25001      -19     
==========================================
- Hits        45590    45566      -24     
+ Misses      43507    43498       -9     
  Partials      172      172              
Flag Coverage Δ *Carryforward flag
adoption-insights 83.70% <ø> (ø) Carriedforward from 0186e63
ai-integrations 67.95% <ø> (ø) Carriedforward from 0186e63
app-defaults 69.79% <ø> (ø) Carriedforward from 0186e63
augment 46.39% <ø> (ø) Carriedforward from 0186e63
boost 74.68% <ø> (ø) Carriedforward from 0186e63
bulk-import 72.46% <ø> (ø) Carriedforward from 0186e63
cost-management 14.10% <ø> (ø) Carriedforward from 0186e63
dcm 61.81% <ø> (ø) Carriedforward from 0186e63
extensions 61.53% <ø> (ø) Carriedforward from 0186e63
global-floating-action-button 71.18% <ø> (ø) Carriedforward from 0186e63
global-header 59.71% <ø> (ø) Carriedforward from 0186e63
homepage 50.23% <ø> (ø) Carriedforward from 0186e63
install-dynamic-plugins 56.77% <ø> (ø) Carriedforward from 0186e63
konflux 91.49% <ø> (ø) Carriedforward from 0186e63
lightspeed 68.81% <ø> (ø) Carriedforward from 0186e63
mcp-integrations 85.46% <ø> (ø) Carriedforward from 0186e63
orchestrator 39.51% <ø> (ø) Carriedforward from 0186e63
quickstart 65.63% <ø> (ø) Carriedforward from 0186e63
sandbox 79.56% <ø> (ø) Carriedforward from 0186e63
scorecard 82.77% <95.00%> (+0.09%) ⬆️
theme 61.26% <ø> (ø) Carriedforward from 0186e63
translations 7.25% <ø> (ø) Carriedforward from 0186e63
x2a 13.78% <ø> (ø) Carriedforward from 0186e63

*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 0186e63...dbcc8c6. 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 Jun 24, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 12:49 AM UTC · Completed 1:03 AM UTC
Commit: b57a430 · View workflow run →

@fullsend-ai-review

fullsend-ai-review Bot commented Jun 24, 2026

Copy link
Copy Markdown

Review

Findings

High

  • [breaking-public-api] workspaces/scorecard/plugins/scorecard-common/src/types/Metric.ts:40 — The threshold field is added as a required (non-optional) property on the public Metric type (@public in report.api.md). Any external consumers constructing Metric objects will get compile errors after upgrading. Additionally, MetricProvider has five methods removed (getMetricType, getMetric, getMetricThresholds, getMetricIds, calculateMetric) and two previously optional methods made required (getMetrics, calculateMetrics). This is a semver-breaking change.
    Remediation: Ensure this ships with a major or minor version bump (per project semver policy) with appropriate changelog entries and document the migration path for third-party MetricProvider implementors.

  • [stale-doc] workspaces/scorecard/plugins/scorecard-backend/docs/providers.md:52 — The example MetricProvider implementation uses four removed methods: getMetricType() (line 52), getMetric() (line 57), getMetricThresholds() (line 66), and calculateMetric() (line 85). Convention text at lines 95-100 also references getMetric().id and getMetricType(). All these methods no longer exist on the MetricProvider interface.
    Remediation: Update the example code to use getMetrics() returning an array with threshold inline, and calculateMetrics() returning a Map. Update convention text to reference the new API shape.

  • [stale-doc] workspaces/scorecard/plugins/scorecard-backend/docs/thresholds.md:54 — Text says "Metric providers must define default thresholds ... in getMetricThresholds" and example code (lines 60-72) shows a getMetricThresholds() implementation. This method has been removed; thresholds are now defined inline within each Metric object's threshold property.
    Remediation: Update text and code example to show thresholds defined inline in Metric objects returned by getMetrics().

Medium

  • [stale-test-data] workspaces/scorecard/plugins/scorecard-backend/src/service/aggregations/strategies/statusGroupedAggregationStrategy.test.ts:33 — This file (not in the PR diff) constructs a Metric object using as Metric without the newly required threshold field. Same issue in averageAggregationStrategy.test.ts (line 33) and AggregationsService.test.ts (line 46). The as Metric cast suppresses TypeScript errors, but the objects will lack the threshold property at runtime.

Low

  • [logic-error] workspaces/scorecard/plugins/scorecard-backend/src/threshold/ThresholdResolver.ts:57 — In setConfiguredThresholds, config-level threshold override is validated using metrics[0].type. Config overrides are stored per providerId and applied to all metrics, but validated only against the first metric's type. Practically, the generic constraint MetricProvider<T> ensures type homogeneity within a provider, so this is a theoretical concern. Consider adding a code comment documenting this assumption.

  • [edge-case] workspaces/scorecard/plugins/scorecard-backend/src/providers/MetricProvidersRegistry.tscalculateMetric() calls provider.calculateMetrics(entity) which returns a Map. A Map.get() returns undefined both when the key is missing and when the value is explicitly undefined. Current providers always use concrete values.

  • [scope-creep] workspaces/scorecard/plugins/scorecard-backend/src/threshold/ThresholdResolver.ts — The PR renames resolveProviderThresholds to resolveMetricThresholds and changes method signatures in ThresholdResolver and mergeEntityAndProviderThresholds. These are logical mechanical consequences of the authorized refactor.

  • [architectural-coherence] workspaces/scorecard/plugins/scorecard-backend/src/threshold/ThresholdResolver.ts — Config overrides are stored per providerId, not per metricId, creating an asymmetry with the new per-metric thresholds. This is a pre-existing design pattern, not introduced by this PR.

  • [naming-consistency] workspaces/scorecard/plugins/scorecard-backend/__fixtures__/mockProviders.ts:52 — The abstract method getDefaultThresholds() in MockMetricProvider introduces a novel naming pattern; production providers inline threshold constants directly in getMetrics().

  • [error-message-consistency] workspaces/scorecard/plugins/scorecard-backend/src/providers/MetricProvidersRegistry.ts:68 — The test regex for threshold validation error does not verify the new , metric '...' suffix.

  • [fallback-pattern] workspaces/scorecard/plugins/scorecard/dev/mocks.ts:83 — The threshold field uses m.result.thresholdResult.definition ?? { rules: [] }, producing empty threshold rules as a fallback in dev/mock code. Same pattern in legacy.tsx.

Previous run

Review

Findings

Critical

  • [breaking-change] workspaces/scorecard/plugins/scorecard-node/src/api/MetricProvider.ts — Five required/optional methods removed from the public MetricProvider interface without deprecation: getMetricType(), getMetric(), getMetricThresholds(), calculateMetric(), and getMetricIds(). Additionally, getMetrics() and calculateMetrics() changed from optional to required. Any downstream plugin implementing MetricProvider will fail to compile.
    Remediation: Either keep removed methods as deprecated optional members with default implementations and remove in a future major version, or treat this as a major/breaking semver bump for @red-hat-developer-hub/backstage-plugin-scorecard-node.

  • [breaking-change] workspaces/scorecard/plugins/scorecard-common/src/types/Metric.ts — A new required property threshold: ThresholdConfig was added to the public Metric type. All existing code that constructs Metric objects without supplying threshold will fail to compile. This affects every downstream consumer of @red-hat-developer-hub/backstage-plugin-scorecard-common.
    Remediation: Either make threshold optional (threshold?: ThresholdConfig) so existing code remains compatible, or perform a major semver bump for @red-hat-developer-hub/backstage-plugin-scorecard-common.

  • [missing-version-bump] workspaces/scorecard/plugins/scorecard-node/report.api.md — No changeset for the breaking changes to @red-hat-developer-hub/backstage-plugin-scorecard-node and @red-hat-developer-hub/backstage-plugin-scorecard-common. These are backward-incompatible changes that require at minimum a major semver bump.
    Remediation: Add a changeset with major bump for both packages and include migration notes describing how downstream providers should update.

Medium

  • [logic-error] workspaces/scorecard/plugins/scorecard-backend/src/threshold/ThresholdResolver.ts:47 — In setConfiguredThresholds, configured thresholds are stored per providerId (not per metricId) and validated using metrics[0].type. For batch providers, a single config-override threshold applies to all metrics uniformly. This is a pre-existing limitation preserved by the PR, but it is now more visible since individual metrics carry their own defaults that could differ.

  • [breaking-change] workspaces/scorecard/plugins/scorecard-backend/src/threshold/ThresholdResolver.tsresolveProviderThresholds(provider) renamed to resolveMetricThresholds(metric, providerId) and resolveEntityThresholds signature changed. These are internal to the scorecard-backend plugin (not exported from the public API surface), so the impact is limited to in-repo consumers.

  • [breaking-change] workspaces/scorecard/plugins/scorecard-backend/src/utils/mergeEntityAndProviderThresholds.ts — Signature changed from (entity, provider, baseThresholds?) to (entity, metric, providerId, baseThresholds?). Also internal to scorecard-backend, not exported publicly.

Low

  • [missing-deprecation] workspaces/scorecard/plugins/scorecard-node/src/api/MetricProvider.ts — No deprecation period for removed interface members with @public JSDoc tags. Best practice is to deprecate for at least one release before removal.

  • [naming-consistency] workspaces/scorecard/plugins/scorecard-backend/__fixtures__/mockProviders.ts:52 — The abstract method getDefaultThresholds() diverges from production provider patterns where thresholds are inlined into getMetrics().

  • [api-shape-consistency] workspaces/scorecard/plugins/scorecard-backend/src/scheduler/tasks/PullMetricsByProviderTask.ts — The logger.warn call for per-entity calculation failures was removed during code path consolidation. Errors are still recorded in the database but no longer logged.

  • [edge-case] workspaces/scorecard/plugins/scorecard-backend/src/providers/MetricProvidersRegistry.ts:39 — If provider.getMetrics() returns an empty array, no metrics are registered and no validation occurs. The provider is silently ignored.

  • [error-handling-idiom] workspaces/scorecard/plugins/scorecard-backend/src/providers/MetricProvidersRegistry.ts:108calculateMetric throws plain Error while getMetric uses NotFoundError from @backstage/errors for the same class of condition.

  • [code-organization] workspaces/scorecard/plugins/scorecard/dev/legacy.tsx — The fallback { rules: [] } is repeated in 5 files. Consider extracting an EMPTY_THRESHOLD_CONFIG constant.

  • [test-weakened] workspaces/scorecard/plugins/scorecard-backend/src/providers/MetricProvidersRegistry.test.ts:125 — Error assertion loosened from exact string match to regex.

Info

fullsend-ai-review[bot]

This comment was marked as outdated.

@christoph-jerolimov

Copy link
Copy Markdown
Member

/fs-fix

@fullsend-ai-coder

fullsend-ai-coder Bot commented Jun 24, 2026

Copy link
Copy Markdown
Contributor Author

🤖 Finished Fix · ❌ Failure · Started 2:42 AM UTC · Completed 4:16 AM UTC
Commit: b57a430 · View workflow run →

@dzemanov dzemanov 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.

I went briefly through the code and looks very nice, I have only 1 small comment.

I have tested all metrics except for sonarqube, for that one I tested that metric errors correctly display as I don't have it setup.
I have tested that metrics endpoint works correctly when filtered by datasource or metric ids.
Custom thresholds in app config work. Invalid thresholds in entity annotation or app config work. Custom thresholds in entity annotations work. Aggregation cards work. Custom thresholds for statusWeight (average) aggregation cards work correctly.

Can you please add changeset with major breaking change?
Posting this as a comment to avoid blocking this as I will be on PTO.

);
}

async calculateMetric(

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.

Is calculateMetric needed? Can it be removed?

@christoph-jerolimov

Copy link
Copy Markdown
Member

/fs-fix

  1. Remove calculateMetric from @workspaces/scorecard/plugins/scorecard-backend/src/providers/MetricProvidersRegistry.ts
  2. Add a major changeset for the changed plugins

@fullsend-ai-coder

fullsend-ai-coder Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor Author

🤖 Fix · ❌ Terminated · Started 2:13 PM UTC · Ended 3:49 PM UTC
Commit: 0b92af1 · View workflow run →

@fullsend-ai-coder fullsend-ai-coder Bot force-pushed the agent/3529-refactor-metric-provider branch from 1874c31 to 8fb5093 Compare June 30, 2026 14:13
@fullsend-ai-coder

Copy link
Copy Markdown
Contributor Author

🤖 Finished Fix · ❌ Failure · Started 2:13 PM UTC · Completed 3:49 PM UTC
Commit: 0b92af1 · View workflow run →

@fullsend-ai-review

fullsend-ai-review Bot commented Jun 30, 2026

Copy link
Copy Markdown

🤖 Finished Review · ❌ Failure · Started 3:52 PM UTC · Completed 3:59 PM UTC
Commit: 0b92af1 · View workflow run →

@christoph-jerolimov

Copy link
Copy Markdown
Member

/fs-fix

revert the changes in workspaces/extensions/plugins/extensions/src/components/TabPanel.tsx

@fullsend-ai-coder

fullsend-ai-coder Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor Author

🤖 Finished Fix · ✅ Success · Started 8:59 AM UTC · Completed 9:04 AM UTC
Commit: 0186e63 · View workflow run →

…etrics

Refactor the scorecard MetricProvider interface to move threshold
configuration from provider-level methods to individual Metric objects.

Core interface changes:
- Add `threshold: ThresholdConfig` field to the `Metric` type
- Remove `getMetricType()`, `getMetric()`, `getMetricThresholds()`,
  `calculateMetric()`, and `getMetricIds()` from MetricProvider
- Make `getMetrics()` and `calculateMetrics()` required on MetricProvider
- Consolidate single/batch provider code paths into batch-only

Provider migrations:
- All providers (dependabot, filecheck, github, jira, openssf, sonarqube)
  updated to the new interface with thresholds on metric objects
- ThresholdResolver API: `resolveProviderThresholds(provider)` →
  `resolveMetricThresholds(metric, providerId)`
- ThresholdResolver API: `resolveEntityThresholds(entity, provider)` →
  `resolveEntityThresholds(entity, metric, providerId)`
- mergeEntityAndProviderThresholds signature updated similarly
- PullMetricsByProviderTask consolidated to always use calculateMetrics()
- MetricProvidersRegistry uses provider.getMetrics() for all operations

All tests updated. API reports regenerated.

Note: DatabaseMetricValues tests could not run (missing better-sqlite3
native binding in sandbox). All other 91 test suites (976 tests) passed.

Closes #3529

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@fullsend-ai-coder fullsend-ai-coder Bot force-pushed the agent/3529-refactor-metric-provider branch from 8fb5093 to dbcc8c6 Compare July 3, 2026 09:00
@sonarqubecloud

sonarqubecloud Bot commented Jul 3, 2026

Copy link
Copy Markdown

@fullsend-ai-coder

Copy link
Copy Markdown
Contributor Author

🔧 Fix agent — iteration 1 (bot-triggered)

No review feedback was available to address. The review body file was empty, no PR_NUMBER was set, and GH CLI was not authenticated. No code changes were made.

Disagreed (1):

  1. No review feedback provided: The review body file at /sandbox/workspace/review-body.txt is empty (contains only a newline). No PR_NUMBER or TRIGGER_SOURCE environment variables were set, and GH CLI is not authenticated. Without review feedback to act on, no code changes can be made.

Tests: passed

Updated by fullsend fix agent

@fullsend-ai-review

fullsend-ai-review Bot commented Jul 3, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 9:07 AM UTC · Completed 9:22 AM UTC
Commit: 0186e63 · View workflow run →

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

@@ -38,6 +38,7 @@ export type Metric<T extends MetricType = MetricType> = {
title: string;
description: string;
type: T;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[high] breaking-public-api

The threshold field is added as a required (non-optional) property on the public Metric type. Any external consumers constructing Metric objects will get compile errors. Additionally, MetricProvider has five methods removed and two previously optional methods made required. This is a semver-breaking change.

Suggested fix: Ensure this ships with a major or minor version bump with appropriate changelog entries and document the migration path for third-party MetricProvider implementors.


private setConfiguredThresholds(provider: MetricProvider): void {
const providerId = provider.getProviderId();
const metrics = provider.getMetrics();

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] logic-error

In setConfiguredThresholds, config-level threshold override is validated using metrics[0].type. Config overrides stored per providerId apply to all metrics from that provider, validated only against the first metric's type. The generic constraint MetricProvider ensures type homogeneity, making this theoretical.

) {}

abstract getMetricThresholds(): ThresholdConfig;
abstract getDefaultThresholds(): ThresholdConfig;

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-consistency

The abstract method getDefaultThresholds() in MockMetricProvider introduces a novel naming pattern; production providers inline threshold constants directly in getMetrics().

`Invalid default thresholds for metric provider '${providerId}', metric '${metricId}'`,
error,
);
}

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] error-message-consistency

The test regex for threshold validation error does not verify the new ', metric ...' suffix.

title: m.metadata.title,
description: m.metadata.description,
type: m.metadata.type,
threshold: m.result.thresholdResult.definition ?? { rules: [] },

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] fallback-pattern

The threshold field uses m.result.thresholdResult.definition ?? { rules: [] }, producing empty threshold rules as a fallback in dev/mock code. Same pattern in legacy.tsx.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Refactor Scorecard: move ThresholdConfig from MetricProvider to individual Metrics

2 participants