Skip to content

feat(#3468): add new GitHub metrics to scorecard module#3472

Open
fullsend-ai-coder[bot] wants to merge 2 commits into
mainfrom
agent/3468-extend-github-metrics
Open

feat(#3468): add new GitHub metrics to scorecard module#3472
fullsend-ai-coder[bot] wants to merge 2 commits into
mainfrom
agent/3468-extend-github-metrics

Conversation

@fullsend-ai-coder

Copy link
Copy Markdown
Contributor

Extend the scorecard GitHub module with 13 new metrics across four domains:

Issue/PR counts (5 metrics):

  • Currently open issues
  • Opened issues in last 7 days
  • Opened PRs in last 7 days
  • Closed issues in last 7 days
  • Closed PRs in last 7 days

PR lifecycle timing (3 metrics, batch provider):

  • Time to first review (average hours)
  • Time to first approval (average hours)
  • Time to merge (average hours)
    All computed from PRs updated in the last 7 days.

GitHub Actions (5 metrics, batch provider):

  • Started workflow runs in last 7 days
  • Successfully completed runs in last 7 days
  • Failed runs in last 7 days
  • Success ratio for 7 days (percentage)
  • Success ratio for 24 hours (percentage)
    Non-terminal runs (pending/running/cancelled) are excluded from success/failure counts and ratio calculations.

CI pass rate (2 metrics, batch provider):

  • First-time CI pass rate for 7 days (percentage)
  • First-time CI pass rate for 24 hours (percentage)
    Checks CI status on the last commit of the first push to each PR. PRs without CI checks are excluded.

New GithubClient methods use GraphQL for issue/PR queries and REST API (fetch) for workflow runs. All providers follow the existing MetricProvider pattern and are registered in the module init. Batch providers use getMetrics/calculateMetrics for efficient multi-metric computation.


Closes #3468

Post-script verification

  • Branch is not main/master (agent/3468-extend-github-metrics)
  • Secret scan passed (gitleaks — 7ccaff17753df64c7ab288cdcba34cee5a657254..HEAD)
  • Pre-commit hooks passed (authoritative run on runner)
  • Tests ran inside sandbox

Extend the scorecard GitHub module with 13 new metrics across
four domains:

Issue/PR counts (5 metrics):
- Currently open issues
- Opened issues in last 7 days
- Opened PRs in last 7 days
- Closed issues in last 7 days
- Closed PRs in last 7 days

PR lifecycle timing (3 metrics, batch provider):
- Time to first review (average hours)
- Time to first approval (average hours)
- Time to merge (average hours)
All computed from PRs updated in the last 7 days.

GitHub Actions (5 metrics, batch provider):
- Started workflow runs in last 7 days
- Successfully completed runs in last 7 days
- Failed runs in last 7 days
- Success ratio for 7 days (percentage)
- Success ratio for 24 hours (percentage)
Non-terminal runs (pending/running/cancelled) are excluded
from success/failure counts and ratio calculations.

CI pass rate (2 metrics, batch provider):
- First-time CI pass rate for 7 days (percentage)
- First-time CI pass rate for 24 hours (percentage)
Checks CI status on the last commit of the first push to
each PR. PRs without CI checks are excluded.

New GithubClient methods use GraphQL for issue/PR queries
and REST API (fetch) for workflow runs. All providers follow
the existing MetricProvider pattern and are registered in the
module init. Batch providers use getMetrics/calculateMetrics
for efficient multi-metric computation.

Closes #3468
@rhdh-gh-app

rhdh-gh-app Bot commented Jun 19, 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-github

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-github workspaces/scorecard/plugins/scorecard-backend-module-github none v2.7.9

@codecov

codecov Bot commented Jun 19, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 65.29680% with 152 lines in your changes missing coverage. Please review.
✅ Project coverage is 53.65%. Comparing base (7ccaff1) to head (006906c).
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3472      +/-   ##
==========================================
+ Coverage   53.59%   53.65%   +0.05%     
==========================================
  Files        2252     2261       +9     
  Lines       85789    86227     +438     
  Branches    24121    24140      +19     
==========================================
+ Hits        45982    46268     +286     
- Misses      38246    38398     +152     
  Partials     1561     1561              
Flag Coverage Δ *Carryforward flag
adoption-insights 83.70% <ø> (ø) Carriedforward from 6d22a2d
ai-integrations 67.95% <ø> (ø) Carriedforward from 6d22a2d
app-defaults 69.79% <ø> (ø) Carriedforward from 6d22a2d
augment 46.39% <ø> (ø) Carriedforward from 6d22a2d
boost 100.00% <ø> (ø) Carriedforward from 6d22a2d
bulk-import 72.46% <ø> (ø) Carriedforward from 6d22a2d
cost-management 14.10% <ø> (ø) Carriedforward from 6d22a2d
dcm 61.79% <ø> (ø) Carriedforward from 6d22a2d
extensions 61.53% <ø> (ø) Carriedforward from 6d22a2d
global-floating-action-button 71.18% <ø> (ø) Carriedforward from 6d22a2d
global-header 59.71% <ø> (ø) Carriedforward from 6d22a2d
homepage 49.84% <ø> (ø) Carriedforward from 6d22a2d
install-dynamic-plugins 56.23% <ø> (ø) Carriedforward from 6d22a2d
konflux 91.49% <ø> (ø) Carriedforward from 6d22a2d
lightspeed 68.57% <ø> (ø) Carriedforward from 6d22a2d
mcp-integrations 85.46% <ø> (ø) Carriedforward from 6d22a2d
orchestrator 37.75% <ø> (ø) Carriedforward from 6d22a2d
quickstart 63.76% <ø> (ø) Carriedforward from 6d22a2d
sandbox 79.56% <ø> (ø) Carriedforward from 6d22a2d
scorecard 81.60% <65.29%> (-2.24%) ⬇️
theme 61.26% <ø> (ø) Carriedforward from 6d22a2d
translations 6.55% <ø> (ø) Carriedforward from 6d22a2d
x2a 78.68% <ø> (ø) Carriedforward from 6d22a2d

*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 7ccaff1...006906c. 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 19, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 11:49 AM UTC · Completed 12:02 PM UTC
Commit: 7ccaff1 · View workflow run →

@fullsend-ai-review

fullsend-ai-review Bot commented Jun 19, 2026

Copy link
Copy Markdown

Review

Findings

Medium

  • [logic-error] workspaces/scorecard/plugins/scorecard-backend-module-github/src/metricProviders/GithubActionsRatioProvider.ts:48computeSuccessRatio uses Math.round(x * 1000) / 10 producing one-decimal percentages (e.g., 79.6), but threshold expressions use integer boundaries (>=80, 50-79, <50). Verified against ThresholdEvaluator: the range operator uses a >= min && a <= max, so a value of 79.6 fails both 50-79 (79.6 > 79) and >=80 (79.6 < 80), falling through all rules and returning undefined status. Same issue exists in GithubPRPassRateProvider.ts computePassRate.
    Remediation: Use Math.round(x * 100) for integer percentages, or adjust threshold expressions to cover the full range (e.g., 50-79.9).

  • [logic-error] workspaces/scorecard/plugins/scorecard-backend-module-github/src/github/GithubClient.ts:166getWorkflowRuns paginates through all GitHub Actions workflow runs without an upper bound on page count. While the loop does terminate when allRuns.length >= data.total_count, very active repositories could trigger many REST API calls per metric calculation, risking rate limit exhaustion and long response times.
    Remediation: Add a maximum page count (e.g., const MAX_PAGES = 10) and break out of the loop when reached.

Low

  • [logic-error] workspaces/scorecard/plugins/scorecard-backend-module-github/src/github/GithubClient.ts:126getPullRequestsWithReviews and getPullRequestsWithCommitStatuses GraphQL queries use first: 100 without cursor-based pagination, silently dropping PRs beyond the first 100. For high-activity repos this means metrics are computed from an incomplete sample with no warning. This follows the existing pattern in getOpenPullRequestsCount.

  • [edge-case] workspaces/scorecard/plugins/scorecard-backend-module-github/src/github/GithubClient.ts:248 — The heuristic to identify first-push commits in getPullRequestsWithCommitStatuses uses a 1-minute tolerance on committedDate, which can differ from push time for long-lived branches. This is a known limitation of the GitHub GraphQL API with no better alternative.

  • [api-contract] workspaces/scorecard/plugins/scorecard-backend-module-github/src/metricProviders/GithubActionsCountProvider.ts:41 — Multi-metric providers implement optional MetricProvider interface methods (getMetricIds, getMetrics, calculateMetrics) as required. Tests use non-null assertions (provider.calculateMetrics!(mockEntity)). Valid TypeScript but a minor style concern.

  • [error-handling-gap] workspaces/scorecard/plugins/scorecard-backend-module-github/src/github/GithubClient.ts:55getRestConfig duplicates the credential-fetching logic from getOctokitClient. Both create a new DefaultGithubCredentialsProvider per call, consistent with existing patterns but increasingly visible with 10+ providers.

  • [test-inadequate] workspaces/scorecard/plugins/scorecard-backend-module-github/src/metricProviders/GithubPRLifecycleProvider.test.ts — All test durations are exact hour multiples (12h, 24h, etc.). No test exercises the fractional-hour rounding path in computeAverageHours.

  • [test-inadequate] workspaces/scorecard/plugins/scorecard-backend-module-github/src/metricProviders/GithubActionsRatioProvider.test.tsfilterRunsByWindow uses new Date() internally. Tests create timestamps relative to their own new Date(), creating a theoretical (but practically unlikely) race between the two calls. Consider jest.useFakeTimers().

Info

  • [scope-alignment] All 13 new metrics match the scope requested in issue Extend the scorecard github module with some new metrics #3468. No scope creep detected.
  • [injection] All interpolated values in GraphQL queries are passed as variables (not inline). REST URL uses encodeURIComponent for owner/repo. No injection vectors found.
Previous run

Review

Findings

High

  • [logic-error] workspaces/scorecard/plugins/scorecard-backend-module-github/src/metricProviders/GithubActionsProvider.ts:137GithubActionsProvider.getMetricThresholds() returns COUNT_THRESHOLDS (e.g., <10 = success, >50 = error), but this single threshold config is applied to ALL metrics exposed by the batch provider, including the ratio metrics (actions_success_ratio_7d and actions_success_ratio_24h) which produce percentage values (0–100). A healthy 80% success ratio would be evaluated as "error" (>50) under count thresholds. Verified against ThresholdResolver.ts and PullMetricsByProviderTask.ts: the framework resolves one ThresholdConfig per provider and applies it uniformly to all metrics in the batch. RATIO_THRESHOLDS is defined at line 38 but never returned by getMetricThresholds().
    Remediation: Split GithubActionsProvider into two separate providers (one for counts using COUNT_THRESHOLDS, one for ratios using RATIO_THRESHOLDS), or extend the MetricProvider interface to support per-metric thresholds.

Medium

  • [unbounded-loop] workspaces/scorecard/plugins/scorecard-backend-module-github/src/github/GithubClient.ts:165getWorkflowRuns() paginates through ALL workflow runs with no upper bound. For very active repositories (CI-heavy monorepos) this could cause excessive API calls and memory consumption. The 7-day since parameter provides some natural bound but is insufficient for high-volume repos.
    Remediation: Add a maximum page count or total results limit.

  • [data-truncation] workspaces/scorecard/plugins/scorecard-backend-module-github/src/github/GithubClient.ts:125getPullRequestsWithReviews() uses first: 100 in the GraphQL search query but does not paginate. If a repository has more than 100 PRs updated in the last 7 days, results will be silently truncated, producing inaccurate metrics. Same issue in getPullRequestsWithCommitStatuses() at line 202.
    Remediation: Implement cursor-based pagination or document the 100-PR cap as a known limitation.

  • [ssrf] workspaces/scorecard/plugins/scorecard-backend-module-github/src/github/GithubClient.ts:166 — In getWorkflowRuns(), repository.owner and repository.repo are interpolated directly into a REST API URL path without encoding. A malicious catalog entity could supply a slug with path traversal characters to redirect the REST request to an unintended GitHub API endpoint, sending configured credentials to the manipulated path. See also: [unbounded-loop] and [architectural-coherence] findings at this location.
    Remediation: Apply encodeURIComponent() to both repository.owner and repository.repo.

  • [architectural-coherence] workspaces/scorecard/plugins/scorecard-backend-module-github/src/github/GithubClient.ts:160getWorkflowRuns uses raw fetch() while every other method uses Octokit GraphQL. This creates two distinct credential resolution paths (getRestConfig vs getOctokitClient), introducing maintenance risk in security-adjacent code. See also: [ssrf] finding at this location.
    Remediation: Use Octokit REST API client (which shares credential management) or document why the raw fetch path is necessary.

Low

  • [logic-error] workspaces/scorecard/plugins/scorecard-backend-module-github/src/metricProviders/GithubActionsProvider.ts:63computeSuccessRatio() excludes runs with conclusions other than success or failure from the denominator. This is explicitly tested and intentional, but the metric description could clarify that cancelled/skipped runs are excluded.

  • [error-handling] workspaces/scorecard/plugins/scorecard-backend-module-github/src/github/GithubClient.ts:167getWorkflowRuns() only checks response.ok. Does not handle rate limiting (HTTP 403 with X-RateLimit headers) or guard against non-JSON responses.

  • [edge-case] workspaces/scorecard/plugins/scorecard-backend-module-github/src/github/GithubClient.ts:243getPullRequestsWithCommitStatuses() uses a 1-minute tolerance to identify "first push" commits and assumes GitHub returns commits in chronological order (not guaranteed by API docs).

  • [test-inadequate] workspaces/scorecard/plugins/scorecard-backend-module-github/src/metricProviders/GithubActionsProvider.test.ts:120 — Test "should return 100% ratio when no completed runs" only tests 7d ratio but not 24h ratio.

  • [scope-accuracy] PR body claims "13 new metrics" but the change actually adds 15.

  • [naming-convention] workspaces/scorecard/plugins/scorecard-backend-module-github/src/github/types.ts:31WorkflowRun type uses snake_case field created_at while all other types use camelCase.

  • [api-shape-pattern] workspaces/scorecard/plugins/scorecard-backend-module-github/src/metricProviders/GithubActionsProvider.ts:88getProviderId() returns a metric ID rather than a provider-scoped identifier. Matches existing convention but could be documented.

  • [code-organization] workspaces/scorecard/plugins/scorecard-backend-module-github/src/metricProviders/GithubActionsProvider.ts:196RATIO_THRESHOLDS and COUNT_THRESHOLDS exported but not imported anywhere. GithubCIPassRateProvider redefines RATIO_THRESHOLDS locally with identical values.

  • [code-organization] workspaces/scorecard/plugins/scorecard-backend-module-github/src/metricProviders/GithubClosedIssuesProvider.ts — Four search-count providers are nearly identical ~87-line files. A factory function could reduce duplication.

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

title: 'GitHub Actions success ratio (7d)',
description:
'Ratio of successful to successful+failed GitHub Actions workflow runs in the last 7 days (percentage).',
type: this.getMetricType(),

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

GithubActionsProvider.getMetricThresholds() returns COUNT_THRESHOLDS but this applies to ALL metrics including ratio metrics producing percentage values (0-100). A healthy 80% success ratio would be evaluated as error (>50) under count thresholds. RATIO_THRESHOLDS is defined but never used.

Suggested fix: Split GithubActionsProvider into two separate providers (one for counts, one for ratios), or extend the MetricProvider interface to support per-metric thresholds.

}>(query, {
q: fullQuery,
});

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] unbounded-loop

getWorkflowRuns() paginates through ALL workflow runs with no upper bound. For very active repositories this could cause excessive API calls and memory consumption.

Suggested fix: Add a maximum page count or total results limit.

}
}
}
`;

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] data-truncation

getPullRequestsWithReviews() and getPullRequestsWithCommitStatuses() use first:100 without pagination. More than 100 PRs updated in 7 days causes silent truncation.

Suggested fix: Implement cursor-based pagination or document the 100-PR cap.

q: fullQuery,
});

return response.search.issueCount;

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] ssrf

repository.owner and repository.repo are interpolated directly into a REST API URL path without encoding. A malicious catalog entity could redirect the request to an unintended API endpoint.

Suggested fix: Apply encodeURIComponent() to both repository.owner and repository.repo.


const response = await octokit<{
search: {
issueCount: number;

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] architectural-coherence

getWorkflowRuns uses raw fetch() while every other method uses Octokit GraphQL, creating two distinct credential resolution paths.

Suggested fix: Use Octokit REST API client or document why raw fetch is necessary.

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

Address the following issues:

  1. GitHubActionsProvider needs to be splitted because it includes metrics that counts actions and then metrics that creates a ratio. But the MetricProvider API only supports one ThresholdConfig for all metrics, at the moment. So splitting this into two providers would allow to define the right ThresholdConfig for these metrics.
  2. GithubCIPassRateProvider should be renamed to GitHubPRPassRateProvider and the metrics should be named something like "GitHub PR CI first time pass rate (7d)". The description should also include that this is about FTPR (first time pass rate).
  3. Add unit tests for GitHubClosedIssuesProvider.ts, GitHubClosedPRsProvider.ts, GitHubOpenedIssuesProvider.ts and GitHubOpenedPRsProvider.ts

@christoph-jerolimov

Copy link
Copy Markdown
Member

/fs-fix

@fullsend-ai-coder

fullsend-ai-coder Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor Author

🤖 Finished Fix · ✅ Success · Started 12:51 PM UTC · Completed 1:10 PM UTC
Commit: 7ccaff1 · View workflow run →

- Split GithubActionsProvider into GithubActionsCountProvider (with
  COUNT_THRESHOLDS) and GithubActionsRatioProvider (with
  RATIO_THRESHOLDS) so each provider gets the correct ThresholdConfig
- Rename GithubCIPassRateProvider to GithubPRPassRateProvider with
  updated metric IDs (github.pr_ci_first_time_pass_rate_*) and titles
  mentioning FTPR (first time pass rate)
- Add individual unit test files for GithubClosedIssuesProvider,
  GithubClosedPRsProvider, GithubOpenedIssuesProvider, and
  GithubOpenedPRsProvider
- Apply encodeURIComponent to owner/repo in REST URL construction

Addresses review feedback on #3472
@fullsend-ai-coder

Copy link
Copy Markdown
Contributor Author

🔧 Fix agent — iteration 1 (human-triggered)

Addressed all 3 human review requests: (1) split GithubActionsProvider into count and ratio providers with correct thresholds, (2) renamed GithubCIPassRateProvider to GithubPRPassRateProvider with FTPR naming, (3) added individual unit tests for 4 search-count providers. Also fixed SSRF vulnerability with encodeURIComponent. Disagreed with 4 bot-only findings as out of scope or unnecessary.

Fixed (4):

  1. Split GithubActionsProvider into count and ratio providers (workspaces/scorecard/plugins/scorecard-backend-module-github/src/metricProviders/GithubActionsProvider.ts): Split GithubActionsProvider into GithubActionsCountProvider (with COUNT_THRESHOLDS for started/successful/failed metrics) and GithubActionsRatioProvider (with RATIO_THRESHOLDS for success ratio metrics). This fixes the logic error where ratio metrics producing percentage values were evaluated against count thresholds.
  2. Rename GithubCIPassRateProvider to GithubPRPassRateProvider with FTPR naming (workspaces/scorecard/plugins/scorecard-backend-module-github/src/metricProviders/GithubCIPassRateProvider.ts): Renamed GithubCIPassRateProvider to GithubPRPassRateProvider. Updated metric IDs to github.pr_ci_first_time_pass_rate_7d/24h, titles to 'GitHub PR CI first time pass rate', and descriptions to mention FTPR (first time pass rate).
  3. Add unit tests for search-count providers (workspaces/scorecard/plugins/scorecard-backend-module-github/src/metricProviders/GithubClosedIssuesProvider.test.ts): Added individual unit test files for GithubClosedIssuesProvider, GithubClosedPRsProvider, GithubOpenedIssuesProvider, and GithubOpenedPRsProvider. Tests cover fromConfig with default thresholds and calculateMetric with mocked GithubClient.
  4. SSRF via unencoded owner/repo in REST URL (workspaces/scorecard/plugins/scorecard-backend-module-github/src/github/GithubClient.ts): Applied encodeURIComponent() to repository.owner and repository.repo in the getWorkflowRuns REST URL construction to prevent path traversal.

Disagreed (4):

  1. Unbounded pagination in getWorkflowRuns: The 7-day since parameter provides a natural bound. Adding an artificial page limit could silently truncate real data. This is a minor concern for a scorecard metrics provider and can be addressed in a follow-up if needed.
  2. Data truncation with first:100 in GraphQL queries: Implementing cursor-based pagination would significantly increase complexity. The 100-PR cap is a reasonable default for most repositories. This is better addressed as a separate enhancement.
  3. Architectural coherence - raw fetch vs Octokit: The GitHub Actions workflow runs API is a REST endpoint not available via GraphQL. Using raw fetch with getRestConfig is the appropriate approach. Both paths use the same credential provider.
  4. Factory function for search-count providers to reduce duplication: Out of scope for this PR. The current pattern with individual provider files is explicit and follows existing conventions in the codebase.

Tests: passed

Updated by fullsend fix agent

@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
7.4% Duplication on New Code (required ≤ 3%)

See analysis details on SonarQube Cloud

@fullsend-ai-review

fullsend-ai-review Bot commented Jun 19, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 1:13 PM UTC · Completed 1:26 PM UTC
Commit: 7ccaff1 · View workflow run →

@fullsend-ai-review fullsend-ai-review Bot added the requires-manual-review Review requires human judgment label Jun 19, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

requires-manual-review Review requires human judgment workspace/scorecard

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Extend the scorecard github module with some new metrics

1 participant