Skip to content

feat(scorecard): add GitLab module with issues, MRs, pipelines, and jobs metrics#3478

Open
fullsend-ai-coder[bot] wants to merge 4 commits into
mainfrom
feat/scorecard-gitlab-module-3475
Open

feat(scorecard): add GitLab module with issues, MRs, pipelines, and jobs metrics#3478
fullsend-ai-coder[bot] wants to merge 4 commits into
mainfrom
feat/scorecard-gitlab-module-3475

Conversation

@fullsend-ai-coder

Copy link
Copy Markdown
Contributor

Implements a new scorecard-backend-module-gitlab plugin that provides 16 metrics for GitLab projects via the GitLab REST API. The module uses Backstage SCM integration for authentication and filters entities by the gitlab.com/project-slug annotation.

Metrics: open/opened/closed issues (7d), open/opened/closed MRs (7d), started/successful/failed pipelines (7d), started/successful/failed jobs (7d), pipeline and job success ratios (7d and 24h).

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


Closes #3475

Post-script verification

  • Branch is not main/master (feat/scorecard-gitlab-module-3475)
  • Secret scan passed (gitleaks — 7ccaff17753df64c7ab288cdcba34cee5a657254..HEAD)
  • Pre-commit hooks passed (authoritative run on runner)
  • Tests ran inside sandbox

@rhdh-gh-app

rhdh-gh-app Bot commented Jun 19, 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-gitlab workspaces/scorecard/plugins/scorecard-backend-module-gitlab minor v0.0.0

@codecov

codecov Bot commented Jun 19, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 83.87097% with 40 lines in your changes missing coverage. Please review.
✅ Project coverage is 53.71%. Comparing base (c3c2966) to head (e104c81).
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3478      +/-   ##
==========================================
+ Coverage   53.62%   53.71%   +0.08%     
==========================================
  Files        2256     2264       +8     
  Lines       85870    86118     +248     
  Branches    24186    24202      +16     
==========================================
+ Hits        46046    46254     +208     
- Misses      38333    38373      +40     
  Partials     1491     1491              
Flag Coverage Δ *Carryforward flag
adoption-insights 83.70% <ø> (ø) Carriedforward from 6f8afcc
ai-integrations 67.95% <ø> (ø) Carriedforward from 6f8afcc
app-defaults 69.79% <ø> (ø) Carriedforward from 6f8afcc
augment 46.39% <ø> (ø) Carriedforward from 6f8afcc
boost 100.00% <ø> (ø) Carriedforward from 6f8afcc
bulk-import 72.46% <ø> (ø) Carriedforward from 6f8afcc
cost-management 14.10% <ø> (ø) Carriedforward from 6f8afcc
dcm 61.79% <ø> (ø) Carriedforward from 6f8afcc
extensions 61.53% <ø> (ø) Carriedforward from 6f8afcc
global-floating-action-button 71.18% <ø> (ø) Carriedforward from 6f8afcc
global-header 59.71% <ø> (ø) Carriedforward from 6f8afcc
homepage 49.84% <ø> (ø) Carriedforward from 6f8afcc
install-dynamic-plugins 56.23% <ø> (ø) Carriedforward from 6f8afcc
konflux 91.49% <ø> (ø) Carriedforward from 6f8afcc
lightspeed 68.57% <ø> (ø) Carriedforward from 6f8afcc
mcp-integrations 85.46% <ø> (ø) Carriedforward from 6f8afcc
orchestrator 37.75% <ø> (ø) Carriedforward from 6f8afcc
quickstart 63.76% <ø> (ø) Carriedforward from 6f8afcc
sandbox 79.56% <ø> (ø) Carriedforward from 6f8afcc
scorecard 83.95% <83.87%> (-0.01%) ⬇️
theme 61.26% <ø> (ø) Carriedforward from 6f8afcc
translations 7.25% <ø> (ø) Carriedforward from 6f8afcc
x2a 78.68% <ø> (ø) Carriedforward from 6f8afcc

*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 c3c2966...e104c81. 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 1:54 PM UTC · Completed 2:08 PM UTC
Commit: 1912534 · View workflow run →

@fullsend-ai-review

fullsend-ai-review Bot commented Jun 19, 2026

Copy link
Copy Markdown

Review

Findings

Medium

  • [edge-case] workspaces/scorecard/plugins/scorecard-backend-module-gitlab/src/gitlab/GitlabClient.ts:122fetchTotalCount falls back to counting response body items when the x-total header is absent, but since the request uses per_page=1, the fallback returns 0 or 1. GitLab omits x-total for resources exceeding 10,000 items, which would produce silently incorrect counts.
    Remediation: When x-total is absent, either paginate through all results to get an accurate count or throw an error indicating the count could not be determined.

  • [api-contract] workspaces/scorecard/plugins/scorecard-backend-module-gitlab/src/gitlab/GitlabClient.ts:150getClosedIssuesCount uses updated_after to filter closed issues, but GitLab's updated_after filters by the updated_at timestamp, not closed_at. An issue closed months ago but commented on recently would be counted; an issue closed within the window but not otherwise updated could be missed. The same approximation applies to getClosedMergeRequestsCount.
    Remediation: Add a code comment documenting this as a known approximation, or fetch items and client-side filter by the closed_at field for accurate counts.

  • [logic-error] workspaces/scorecard/plugins/scorecard-backend-module-gitlab/src/gitlab/GitlabClient.ts:68getApiBaseUrl hardcodes host = 'gitlab.com'. Self-hosted GitLab instances are not supported despite the README stating that standard Backstage GitLab integration configuration is used. The gitlab.com/project-slug annotation contains no host information.
    Remediation: Support self-hosted instances by iterating configured GitLab integrations or adding a host annotation, or explicitly document the gitlab.com-only limitation in the README.

Low

  • [api-contract] workspaces/scorecard/plugins/scorecard-backend-module-gitlab/src/gitlab/GitlabClient.ts:118getJobsCount encodes scope[] as params['scope[]'] = scope.join(','), producing ?scope[]=success,failed instead of GitLab's expected ?scope[]=success&scope[]=failed. Currently a latent bug since callers only pass single-element arrays.

  • [logic-error] workspaces/scorecard/plugins/scorecard-backend-module-gitlab/src/gitlab/GitlabClient.ts:162fetchAllPages assumes descending created_at ordering for early termination but never specifies order_by or sort parameters. The per-item filter preserves correctness, but the optimization may fetch extra pages if ordering differs.

  • [logic-error] workspaces/scorecard/plugins/scorecard-backend-module-gitlab/src/metricProviders/GitlabPipelinesProvider.ts:90PERCENTAGE_THRESHOLDS is defined and exported but never used. getMetricThresholds() returns DEFAULT_NUMBER_THRESHOLDS for all metrics including ratio percentages.

  • [test-inadequate] workspaces/scorecard/plugins/scorecard-backend-module-gitlab/src/gitlab/GitlabClient.test.ts:226getJobsCount tests do not cover multi-page pagination, early termination, or scope parameter encoding.

  • [test-inadequate] workspaces/scorecard/plugins/scorecard-backend-module-gitlab/src/gitlab/GitlabClient.test.ts:275getClosedMergeRequestsCount test does not assert which states (closed vs merged) are queried in each API call.

  • [architectural-coherence] workspaces/scorecard/plugins/scorecard-backend-module-gitlab/package.json — Missing config.d.ts unlike the GitHub module which declares configurable thresholds/schedules via config schema.

  • [docs-completeness] workspaces/scorecard/plugins/scorecard-backend/README.md:87 — The "Available Metric Providers" table and module install list do not include the new GitLab module. The docs/providers.md example providers section is also missing a GitLab entry.

Previous run

Review

Findings

High

  • [logic-error] workspaces/scorecard/plugins/scorecard-backend-module-gitlab/src/metricProviders/GitlabPipelinesProvider.ts:37PERCENTAGE_THRESHOLDS is defined with correct semantics for ratio metrics (error <50, warning 50-80, success >80) but is never used. getMetricThresholds() returns DEFAULT_NUMBER_THRESHOLDS for all providers, including ratio metrics. DEFAULT_NUMBER_THRESHOLDS classifies values >50 as error and <10 as success, so a healthy 80% success ratio would be classified as error (semantically inverted). Same problem affects GitlabJobsProvider.
    Remediation: Have getMetricThresholds() return PERCENTAGE_THRESHOLDS in both GitlabPipelinesProvider and GitlabJobsProvider, or split ratio metrics into separate providers that return the correct thresholds.

Medium

  • [logic-error] workspaces/scorecard/plugins/scorecard-backend-module-gitlab/src/gitlab/GitlabClient.ts:30getApiBaseUrl hard-codes host to gitlab.com, making self-hosted GitLab instances inaccessible. The annotation gitlab.com/project-slug does not encode the host, so entities on gitlab.example.com cannot be resolved.
    Remediation: Accept GitLab host from entity (via additional annotation or source location parsing) rather than hard-coding gitlab.com.

  • [stale-doc] workspaces/scorecard/plugins/scorecard-backend/README.md:91 — The "Available Metric Providers" table lists GitHub, Filecheck, Jira, OpenSSF, and Dependabot but does not include the new GitLab provider. The installation links section (~line 99) is also missing the GitLab module.
    Remediation: Add a GitLab row to the providers table and a bullet to the installation links section.

Low

  • [api-contract] workspaces/scorecard/plugins/scorecard-backend-module-gitlab/src/gitlab/GitlabClient.ts:165getJobsCount sets params['scope[]'] = scope.join(',') which would produce incorrect query parameters for multi-element arrays. Current callers only pass single-element arrays so the bug is latent.
    Remediation: Use url.searchParams.append('scope[]', value) in a loop, or document the single-scope limitation.

  • [logic-error] workspaces/scorecard/plugins/scorecard-backend-module-gitlab/src/gitlab/GitlabClient.ts:94getClosedIssuesCount uses updated_after to filter closed issues, but this returns issues with any update after the date, not issues specifically closed after it. The GitLab API lacks a closed_after parameter, making this an approximation.
    Remediation: Document as approximation in metric description, or client-side filter by closed_at field.

  • [fail-open] workspaces/scorecard/plugins/scorecard-backend-module-gitlab/src/gitlab/GitlabClient.ts:45 — When the GitLab integration has no token configured, the PRIVATE-TOKEN header is omitted and requests proceed unauthenticated silently.
    Remediation: Emit a warning log via the Backstage logger service when operating without a token.

  • [code-organization] workspaces/scorecard/plugins/scorecard-backend-module-gitlab/src/metricProviders/GitlabPipelinesProvider.ts:45calculateRatio helper is defined in GitlabPipelinesProvider.ts and imported by GitlabJobsProvider.ts. Shared utilities should live in src/gitlab/utils.ts.

  • [edge-case] workspaces/scorecard/plugins/scorecard-backend-module-gitlab/src/gitlab/GitlabClient.ts:170fetchTotalCount fallback counts items in response body when x-total header is null. Since per_page=1, this returns 0 or 1, not the actual total. GitLab omits x-total for large result sets (>10,000).

  • [edge-case] workspaces/scorecard/plugins/scorecard-backend-module-gitlab/src/gitlab/GitlabClient.ts:189fetchAllPages has no upper bound on pages fetched and relies on descending chronological order without explicitly requesting it.
    Remediation: Add order_by=created_at&sort=desc to API parameters and a maximum page limit.

Info

  • [pattern-inconsistency] workspaces/scorecard/plugins/scorecard-backend-module-gitlab/src/module.ts:38 — Each of four providers instantiates its own GitlabClient (and ScmIntegrations) from config. Minor optimization opportunity to share a single instance.

  • [pattern-inconsistency] workspaces/scorecard/plugins/scorecard-backend-module-gitlab/src/gitlab/types.tsGitlabPipelineStatus and GitlabJobStatus types are defined but never used in method signatures. Consider using them for type safety or removing them.

Previous run (2)

Review

Findings

Medium

  • [logic-error] workspaces/scorecard/plugins/scorecard-backend-module-gitlab/src/gitlab/GitlabClient.ts:34 — The getApiBaseUrl method hardcodes the host to gitlab.com regardless of the entity's actual GitLab instance. Self-hosted GitLab instances will never resolve to the correct SCM integration, and the wrong token could be selected. The comment says "Derive the host from the project slug" but the code always uses gitlab.com.
    Remediation: Extract the host from the entity annotation or use getEntitySourceLocation(entity).target as the existing GitHub module does.

  • [logic-error] workspaces/scorecard/plugins/scorecard-backend-module-gitlab/src/gitlab/GitlabClient.ts:86fetchTotalCount falls back to counting items.length from the response body when x-total header is absent, but the request uses per_page=1. This means the fallback returns 0 or 1 regardless of actual count. The GitLab Pipelines API omits x-total for performance reasons on large result sets, so pipeline count metrics may silently return incorrect values.
    Remediation: For endpoints that may omit x-total, use fetchAllPages for accurate counts, or increase per_page in the fallback path.

  • [api-contract] workspaces/scorecard/plugins/scorecard-backend-module-gitlab/src/gitlab/GitlabClient.ts:197 — The scope[] parameter is set as params['scope[]'] = scope.join(','), producing scope[]=success,failed. The GitLab Jobs API expects repeated parameters (scope[]=success&scope[]=failed). Currently all callers pass single-element arrays so this works in practice, but it is a latent bug that will surface when multi-scope filtering is needed.
    Remediation: Use url.searchParams.append('scope[]', value) for each scope element instead of joining with commas.

  • [logic-error] workspaces/scorecard/plugins/scorecard-backend-module-gitlab/src/gitlab/GitlabClient.ts:128getClosedIssuesCount and getClosedMergeRequestsCount use updated_after to approximate items closed within a time window. However, updated_after includes items updated for any reason (comments, labels, etc.), not just closure. This will over-count closed items.
    Remediation: Document as a known approximation, or fetch paginated results and filter by closed_at for accurate counts.

  • [edge-case] workspaces/scorecard/plugins/scorecard-backend-module-gitlab/src/gitlab/GitlabClient.ts:191fetchAllPages has no upper bound on page count and assumes descending created_at ordering for early termination. The GitLab Jobs API sorts by id by default, not created_at. While the 7-day/24-hour time windows provide a practical bound, a project with very high job volume could trigger excessive API calls.
    Remediation: Add order_by=id&sort=desc to lock down ordering and a maximum page limit (e.g., 100 pages) with a warning log when hit.

  • [pattern-violation] workspaces/scorecard/plugins/scorecard-backend-module-gitlab/src/metricProviders/GitlabPipelinesProvider.ts:137PERCENTAGE_THRESHOLDS is defined with appropriate rules for ratio metrics (error <50, warning 50-80, success >80) but is never used. All providers return DEFAULT_NUMBER_THRESHOLDS from getMetricThresholds(), which means ratio metrics (0-100%) will be evaluated against count-oriented thresholds, producing nonsensical health indicators.
    Remediation: The MetricProvider interface supports only a single getMetricThresholds(). Consider splitting ratio metrics into separate providers, or use PERCENTAGE_THRESHOLDS as the return value for ratio-focused providers.

Low

  • [test-inadequate] workspaces/scorecard/plugins/scorecard-backend-module-gitlab/src/gitlab/GitlabClient.test.ts:159 — Test mocks use new Map() for response headers instead of the real Headers object. While both have .get(), Headers is case-insensitive. More importantly, there are no tests for the multi-page pagination logic in fetchAllPages, which is the most complex code path.
    Remediation: Use new Headers() in mocks and add tests for multi-page pagination, early termination, and empty page edge cases.

  • [edge-case] workspaces/scorecard/plugins/scorecard-backend-module-gitlab/src/gitlab/GitlabClient.ts:95parseInt(total, 10) can return NaN if the x-total header contains a non-numeric string. The NaN propagates to callers without validation.
    Remediation: Add a Number.isNaN() guard after parsing; fall through to the fallback path or throw if the value is not a valid integer.

Previous run (3)

Review

Findings

High

  • [logic-error] workspaces/scorecard/plugins/scorecard-backend-module-gitlab/src/metricProviders/GitlabPipelinesProvider.tsPERCENTAGE_THRESHOLDS is defined with correct rules (<50 error, 50-80 warning, >80 success) but is never used by any provider's getMetricThresholds(). All providers return DEFAULT_NUMBER_THRESHOLDS which treats <10 as success and >50 as error. For ratio metrics (pipeline_success_ratio, job_success_ratio), this means a 90% success ratio displays as "error" and a 5% ratio displays as "success" — the thresholds are semantically inverted.
    Remediation: GitlabPipelinesProvider and GitlabJobsProvider should return PERCENTAGE_THRESHOLDS from their getMetricThresholds() method instead of DEFAULT_NUMBER_THRESHOLDS. Note that each provider can only return one ThresholdConfig, so the ratio providers may need to be split from the count providers, or the interface needs to support per-metric thresholds.

Medium

  • [DoS] workspaces/scorecard/plugins/scorecard-backend-module-gitlab/src/gitlab/GitlabClient.ts — The fetchAllPages method has no upper bound on pages fetched. A project with extensive job history could trigger hundreds of sequential HTTP requests. The early-exit condition relies on created_at descending order, which GitLab provides by default but does not explicitly guarantee.
    Remediation: Add a maximum page limit (e.g., MAX_PAGES = 100) to the while (hasMore) loop and log a warning when the limit is reached.

  • [architectural-conflict] workspaces/scorecard/plugins/scorecard-backend-module-gitlab/src/gitlab/GitlabClient.tsgetApiBaseUrl hardcodes gitlab.com as the host. Self-hosted GitLab instances configured in Backstage SCM integrations will not be resolved. The GitHub module resolves this by accepting the entity source URL and calling integrations.github.byUrl(url). Issue Create new gitlab module for scorecard #3475 specifies "using Backstage SCM configuration for gitlab," implying self-hosted support.
    Remediation: Pass entity or host information through to GitlabClient so it can resolve the correct integration by URL, similar to GithubClient.

Low

  • [api-contract] workspaces/scorecard/plugins/scorecard-backend-module-gitlab/src/gitlab/GitlabClient.ts — The scope[] parameter is set via params['scope[]'] = scope.join(','). GitLab expects repeated query params (scope[]=success&scope[]=failed), not comma-separated. Current callers only pass single-element arrays so the bug does not manifest, but the API is incorrect for multi-value use.

  • [edge-case] workspaces/scorecard/plugins/scorecard-backend-module-gitlab/src/gitlab/GitlabClient.tsfetchTotalCount falls back to counting response body items when x-total header is missing, but since per_page=1, the fallback returns at most 1 (not the actual total). The header is present for the endpoints used here, so this is a low-probability edge case.

  • [DoS] workspaces/scorecard/plugins/scorecard-backend-module-gitlab/src/gitlab/GitlabClient.tsfetch() calls lack request timeouts (AbortController/signal). If GitLab is unresponsive, the backend hangs indefinitely.

  • [code-organization] workspaces/scorecard/plugins/scorecard-backend-module-gitlab/src/metricProviders/GitlabPipelinesProvider.tscalculateRatio is a shared utility defined in GitlabPipelinesProvider.ts and imported by GitlabJobsProvider.ts. Shared utilities should live in gitlab/utils.ts per established module conventions.

  • [api-shape] workspaces/scorecard/plugins/scorecard-backend-module-gitlab/src/metricProviders/GitlabIssuesProvider.ts — Existing providers reference this.getProviderId() and this.getMetricType() in getMetric(). GitLab providers hardcode METRIC_IDS constants instead, diverging from the established pattern.

  • [api-shape] workspaces/scorecard/plugins/scorecard-backend-module-gitlab/src/metricProviders/GitlabIssuesProvider.ts — All GitLab providers add explicit : string return types on getProviderId(), while existing providers rely on type inference.

  • [auth] workspaces/scorecard/plugins/scorecard-backend-module-gitlab/src/gitlab/GitlabClient.ts — When no token is configured, unauthenticated requests proceed silently without logging a warning. Operators may not realize they are running without auth (reduced rate limits, no private project access).

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

@christoph-jerolimov

Copy link
Copy Markdown
Member

/fs-fix regenerate the api reports and commit the changes (or new files)

@fullsend-ai-coder

fullsend-ai-coder Bot commented Jun 20, 2026

Copy link
Copy Markdown
Contributor Author

🤖 Finished Fix · ✅ Success · Started 6:44 AM UTC · Completed 6:57 AM UTC
Commit: c0783eb · View workflow run →

@fullsend-ai-coder fullsend-ai-coder Bot force-pushed the feat/scorecard-gitlab-module-3475 branch from ec0bd06 to 4a05a43 Compare June 20, 2026 06:44
fullsend-ai-coder Bot added a commit that referenced this pull request Jun 20, 2026
Generate the report.api.md file for the new GitLab module plugin
to match the pattern used by other scorecard backend modules.

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

Copy link
Copy Markdown
Contributor Author

🔧 Fix agent — iteration 1 (human-triggered)

Generated the missing report.api.md API report file for the new scorecard-backend-module-gitlab plugin by running yarn tsc followed by yarn build:api-reports. The generated report matches the expected format used by other scorecard backend modules.

Fixed (1):

  1. missing API report for scorecard-backend-module-gitlab (workspaces/scorecard/plugins/scorecard-backend-module-gitlab/report.api.md): Generated report.api.md using yarn build:api-reports to match the pattern used by all other scorecard backend modules (e.g., scorecard-backend-module-github).

Tests: passed

Updated by fullsend fix agent

@fullsend-ai-review

fullsend-ai-review Bot commented Jun 20, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 7:00 AM UTC · Completed 7:09 AM UTC
Commit: c0783eb · 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.

} {
// Derive the host from the project slug or default to gitlab.com
const host = 'gitlab.com';
const url = `https://${host}/${projectSlug}`;

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

getApiBaseUrl hardcodes host to gitlab.com. Self-hosted GitLab instances will never resolve to the correct SCM integration, and the wrong token could be selected.

Suggested fix: Extract the host from the entity annotation or use getEntitySourceLocation(entity).target as the existing GitHub module does.


const total = response.headers.get('x-total');
if (total !== null) {
return parseInt(total, 10);

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

fetchTotalCount falls back to counting items.length with per_page=1 when x-total header is absent. The GitLab Pipelines API omits x-total for large result sets, so pipeline metrics may silently return 0 or 1.

Suggested fix: For endpoints that may omit x-total, use fetchAllPages for accurate counts, or increase per_page in the fallback path.

since: Date,
): Promise<Array<{ created_at: string }>> {
const allItems: Array<{ created_at: string }> = [];
let page = 1;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[medium] api-contract

scope[] parameter uses comma-joined string instead of repeated query parameters. Latent bug — all current callers pass single-element arrays, but multi-scope filtering will fail.

Suggested fix: Use url.searchParams.append for each scope element instead of joining with commas.

}

async getOpenedMergeRequestsCount(
projectSlug: GitlabProjectSlug,

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

getClosedIssuesCount and getClosedMergeRequestsCount use updated_after to approximate items closed within a time window. This over-counts because updated_after includes items updated for any reason, not just closure.

Suggested fix: Document as a known approximation, or fetch paginated results and filter by closed_at for accurate counts.

}

private async fetchAllPages(
projectSlug: GitlabProjectSlug,

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] edge-case

fetchAllPages has no upper bound on page count and assumes descending created_at ordering for early termination. A project with high job volume could trigger excessive API calls.

Suggested fix: Add order_by and sort parameters to lock down ordering and a maximum page limit with a warning log when hit.

type: 'number',
history: true,
},
{

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

PERCENTAGE_THRESHOLDS is defined but never used. All providers return DEFAULT_NUMBER_THRESHOLDS, so ratio metrics will be evaluated against count-oriented thresholds, producing nonsensical health indicators.

Suggested fix: Split ratio metrics into separate providers, or use PERCENTAGE_THRESHOLDS as the return value for ratio-focused providers.

projectSlug,
since,
);

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] test-inadequate

Test mocks use Map instead of Headers object, and there are no tests for the multi-page pagination logic in fetchAllPages.

Suggested fix: Use new Headers() in mocks and add tests for multi-page pagination, early termination, and empty page edge cases.

}

async getOpenIssuesCount(projectSlug: GitlabProjectSlug): Promise<number> {
return this.fetchTotalCount(projectSlug, '/issues', {

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] edge-case

parseInt(total, 10) can return NaN if x-total header contains a non-numeric string. NaN propagates without validation.

Suggested fix: Add Number.isNaN() guard after parsing; fall through to fallback or throw.

@christoph-jerolimov

Copy link
Copy Markdown
Member

/fs-fix

  1. Add a readme, with installation instructions, notes on the necessary annotation and table with all metrics
  2. change the package.json version to 0.0.0
  3. add a changeset

@fullsend-ai-coder

fullsend-ai-coder Bot commented Jun 20, 2026

Copy link
Copy Markdown
Contributor Author

🤖 Finished Fix · ✅ Success · Started 8:01 AM UTC · Completed 8:05 AM UTC
Commit: c3c2966 · View workflow run →

fullsend-ai-coder Bot and others added 2 commits June 20, 2026 08:01
…obs metrics

Implements a new scorecard-backend-module-gitlab plugin that provides 16
metrics for GitLab projects via the GitLab REST API. The module uses
Backstage SCM integration for authentication and filters entities by the
gitlab.com/project-slug annotation.

Metrics: open/opened/closed issues (7d), open/opened/closed MRs (7d),
started/successful/failed pipelines (7d), started/successful/failed
jobs (7d), pipeline and job success ratios (7d and 24h).

Closes #3475

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Generate the report.api.md file for the new GitLab module plugin
to match the pattern used by other scorecard backend modules.

Addresses review feedback on #3478
@fullsend-ai-coder fullsend-ai-coder Bot force-pushed the feat/scorecard-gitlab-module-3475 branch from 3cbefe0 to 6f8afcc Compare June 20, 2026 08:01
- Add README.md with installation instructions, annotation requirements,
  and a table of all 16 available metrics
- Add changeset for the new package
- Change package.json version from 1.0.0 to 0.0.0

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

Copy link
Copy Markdown
Contributor Author

🔧 Fix agent — iteration 2 (human-triggered)

Addressed all 3 human review instructions: added README.md with full documentation, changed version to 0.0.0, and added a changeset.

Fixed (3):

  1. Add a README with installation instructions, annotation notes, and metrics table: Created README.md following the pattern from scorecard-backend-module-github, with installation instructions, entity annotation requirements (gitlab.com/project-slug), and a table listing all 16 available metrics with their IDs, descriptions, types, and time windows
  2. Change package.json version to 0.0.0: Changed version field in package.json from 1.0.0 to 0.0.0
  3. Add a changeset: Added workspaces/scorecard/.changeset/add-gitlab-scorecard-module.md with a minor changeset for the new package

Tests: passed

Updated by fullsend fix agent

@fullsend-ai-review

fullsend-ai-review Bot commented Jun 20, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 8:08 AM UTC · Completed 8:22 AM UTC
Commit: c3c2966 · 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.

SUCCESS_RATIO_7D: 'gitlab.pipeline_success_ratio_7d',
SUCCESS_RATIO_24H: 'gitlab.pipeline_success_ratio_24h',
} as const;

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

PERCENTAGE_THRESHOLDS is defined with correct semantics for ratio metrics but is never used. getMetricThresholds() returns DEFAULT_NUMBER_THRESHOLDS which classifies values >50 as error, so a healthy 80% success ratio would be classified as error (semantically inverted). Same problem affects GitlabJobsProvider.

Suggested fix: Have getMetricThresholds() return PERCENTAGE_THRESHOLDS in both GitlabPipelinesProvider and GitlabJobsProvider, or split ratio metrics into separate providers that return the correct thresholds.


private getApiBaseUrl(projectSlug: string): {
apiBaseUrl: string;
token: string | undefined;

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

getApiBaseUrl hard-codes host to gitlab.com, making self-hosted GitLab instances inaccessible.

Suggested fix: Accept GitLab host from entity (via additional annotation or source location parsing) rather than hard-coding gitlab.com.

status?: string,
): Promise<number> {
const params: Record<string, string> = {
updated_after: since.toISOString(),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[low] api-contract

getJobsCount sets params scope[] = scope.join(',') which would produce incorrect query parameters for multi-element arrays. Current callers only pass single-element arrays so the bug is latent.

Suggested fix: Use url.searchParams.append in a loop for each scope value.

return items.length;
}

async getOpenIssuesCount(projectSlug: GitlabProjectSlug): Promise<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.

[low] logic-error

getClosedIssuesCount uses updated_after which returns issues with any update after the date, not issues specifically closed after it. This is an approximation due to GitLab API limitations.

Suggested fix: Document as approximation in metric description, or client-side filter by closed_at field.

apiBaseUrl: integration.config.apiBaseUrl ?? `https://${host}/api/v4`,
token: integration.config.token,
};
}

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] fail-open

When GitLab integration has no token configured, requests proceed unauthenticated silently without warning.

Suggested fix: Emit a warning log via the Backstage logger service when operating without a token.

{ key: 'success', expression: '>80' },
],
};

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] code-organization

calculateRatio helper is defined in GitlabPipelinesProvider.ts and imported by GitlabJobsProvider.ts, creating cross-provider coupling. Shared utilities should live in src/gitlab/utils.ts.

Suggested fix: Move calculateRatio and PERCENTAGE_THRESHOLDS to src/gitlab/utils.ts.

if (status) {
params.status = status;
}
return this.fetchTotalCount(projectSlug, '/pipelines', params);

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] edge-case

fetchTotalCount fallback with per_page=1 returns 0 or 1 when x-total header is absent. GitLab omits x-total for large result sets (>10,000).

Suggested fix: Paginate to get accurate count when x-total is absent, or log a warning.

const jobs = await this.fetchAllPages(projectSlug, '/jobs', params, since);
return jobs.length;
}

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] edge-case

fetchAllPages has no upper bound on pages fetched and relies on descending chronological order without explicitly requesting it.

Suggested fix: Add order_by=created_at and sort=desc to API parameters and add a maximum page limit.

@christoph-jerolimov

Copy link
Copy Markdown
Member

/fs-fix run prettier:fix

@fullsend-ai-coder

fullsend-ai-coder Bot commented Jun 20, 2026

Copy link
Copy Markdown
Contributor Author

🤖 Finished Fix · ✅ Success · Started 8:56 AM UTC · Completed 9:09 AM UTC
Commit: c3c2966 · View workflow run →

Addresses formatting issues in README.md table and import statement
per human instruction to run prettier:fix.

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

Copy link
Copy Markdown
Contributor Author

🔧 Fix agent — iteration 3 (human-triggered)

Ran prettier:fix per human instruction. Only README.md required formatting changes (table alignment and import formatting).

Fixed (1):

  1. run prettier:fix to fix formatting: Ran prettier --write on all plugin files. Only README.md had formatting changes: table alignment was normalized and a multi-line import statement was collapsed to a single line.

Tests: passed

Updated by fullsend fix agent

@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

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

See analysis details on SonarQube Cloud

@fullsend-ai-review

fullsend-ai-review Bot commented Jun 20, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 9:12 AM UTC · Completed 9:26 AM UTC
Commit: c3c2966 · View workflow run →

@fullsend-ai-review fullsend-ai-review Bot added the requires-manual-review Review requires human judgment label Jun 20, 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.

Create new gitlab module for scorecard

4 participants