Skip to content

feat(scorecard): add entity-page sparkline charts for time-series metrics - #4573

Merged
Eswaraiahsapram merged 15 commits into
redhat-developer:mainfrom
Eswaraiahsapram:feat/scorecard-entity-sparkline-cards-ui
Sep 17, 2026
Merged

Eswaraiahsapram merged 15 commits into
redhat-developer:mainfrom
Eswaraiahsapram:feat/scorecard-entity-sparkline-cards-ui

Conversation

@Eswaraiahsapram

@Eswaraiahsapram Eswaraiahsapram commented Sep 3, 2026

Copy link
Copy Markdown
Member

Hey, I just made a Pull Request!

Fix - https://redhat.atlassian.net/browse/RHIDP-15576

What

Adds sparkline (area chart) visualization support for entity-page scorecard metrics whose defaultVisualization is sparkline. This is the foundation PR — shared chart components and utilities are included here and will be reused by the homepage sparkline PR that follows.

What changed

New components

  • SparklineChart — Recharts-based area chart with gradient fill, error-dot markers, hover tooltip, and threshold legend
  • SparklineTooltip / SparklineLegend — supporting chart sub-components
  • EntitySparklineCard — entity-page card that fetches time-series data and renders a sparkline with a "View data sources" dialog for collector metadata
  • EntityMetricCard — routing component that renders EntitySparklineCard or the existing Scorecard card based on defaultVisualization

New API methods

  • getMetricTimeSeriesGET /metrics/catalog/:kind/:namespace/:name/time-series
  • getMetricCollectorsGET /metrics/:metricId/collectors

New hooks

  • useMetricTimeSeriesuseQuery-based hook for 30-day entity metric time series
  • useMetricCollectorsuseQuery-based hook for collector metadata (fetched only when the data-sources dialog is open)

New utilities

  • timeSeriesChartData — maps API points to chart-ready data with interpolation for error gaps
  • sparklineLegend — builds threshold legend items with color + line-style pairing
  • sparklineChartModel — shared view-model factory used by both entity and homepage cards
  • metricVisualizationisSparklineVisualization() helper
  • timeSeriesRange — computes the default 30-day ISO-8601 range

Refactors

  • DataSourcesDialog now accepts generic SourceRow[] instead of building rows internally
  • Extracted collectorSourceRows.ts (for sparkline metrics) and metricSourceRows.ts (for existing donut metrics) as separate row builders
  • All collector labels (GitHub, Jira, empty value --, unavailable status N/A) are now translated via i18n keys instead of hardcoded strings

Translations

  • Added 6 new dataSourcesDialog.* keys to ref.ts and all locale files (de, es, fr, it, ja)

Screen Recording

Screen.Recording.2026-09-07.at.3.18.23.PM.mov

How to test

  1. Configure a catalog entity with DORA metric providers (or any metric with defaultVisualization: sparkline)
  2. Navigate to the entity's Scorecard tab
  3. Verify the sparkline chart renders with a 30-day trend line
  4. Click the menu → "View data sources" and verify collectors are listed
  5. Error days should show red dots on the chart with tooltip messages

✔️ Checklist

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

@rhdh-gh-app

rhdh-gh-app Bot commented Sep 3, 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 workspaces/scorecard/plugins/scorecard minor v4.3.1

@fullsend-ai-review

fullsend-ai-review Bot commented Sep 3, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 7:36 PM UTC · Completed 7:44 PM UTC

Commit: b56fa91 · View workflow run →

Runtime: claude · Model: opus → claude-opus-4-6 · Cost: $2.16

@fullsend-ai-review

fullsend-ai-review Bot commented Sep 3, 2026

Copy link
Copy Markdown

Review

Verdict: comment · 1 medium, 5 low findings · no blocking issues

Summary

This PR adds sparkline (area chart) visualization for entity-page scorecard metrics with defaultVisualization: 'sparkline'. The change is well-structured: a new EntityMetricCard routing component selects between the existing Scorecard donut and the new EntitySparklineCard; three new API client methods (getMetricTimeSeries, getMetricCollectors, getAggregationTimeSeries) include thorough runtime response validation with extracted type guards; and shared utility functions (sparklineChartModel, sparklineLegend, timeSeriesChartData) are cleanly separated for reuse by a planned homepage sparkline PR. The DataSourcesDialog refactor from metrics: MetricResult[] to generic rows: SourceRow[] is a sound generalization that enables collector-based data sources. Test coverage is comprehensive across 19 test files. The minor changeset bump is appropriate for a new user-visible feature. No public API contracts are broken — the ScorecardApi interface is internal and the only public surface change is 5 additive translation keys.

Findings

1. README does not document the sparkline feature — medium

File: workspaces/scorecard/plugins/scorecard/README.md
Category: documentation-currency

The Features section lists four bullet points but none mention sparkline, time-series visualization, or DORA metrics. This is a new user-visible capability: metrics with defaultVisualization: 'sparkline' now render a 30-day trend chart with threshold legend and collector data-sources dialog instead of the standard donut card. Consider adding a feature bullet and brief usage guidance.

2. URL path-segment encoding inconsistency — low

File: workspaces/scorecard/plugins/scorecard/src/api/index.ts · getMetricTimeSeries
Category: defense-in-depth

entity.kind, entity.metadata.namespace, and entity.metadata.name are interpolated directly into the URL path without encodeURIComponent(), while getMetricCollectors in the same PR correctly encodes metricId. This follows a pre-existing pattern (getScorecards does the same), and Backstage's catalog naming restrictions mitigate practical exploitation, but the inconsistency is worth noting for defense-in-depth.

3. pluginJira translation key is unused — low

File: workspaces/scorecard/plugins/scorecard/src/translations/ref.ts
Category: unused-code

The key dataSourcesDialog.pluginJira is defined in all 6 locale files but never consumed by production code. In collectorSourceRows.ts, pluginLabelFromCollectorId special-cases only the github prefix via t('dataSourcesDialog.pluginGithub'); the jira prefix falls through to extractPluginName which returns 'Jira' via string manipulation. Either extend the lookup to cover jira (for translation consistency with GitHub) or remove the dead key.

4. useMetricCollectors missing error-wrapping try/catch — low

File: workspaces/scorecard/plugins/scorecard/src/hooks/useMetricCollectors.tsx
Category: style-inconsistency

Every other hook in the package wraps its queryFn in a try/catch to re-throw non-Error rejections with a translated t('errors.fetchError') message. useMetricCollectors delegates directly to the API client without this wrapper. In practice this is safe — the API client already wraps non-Error throws — but the inconsistency may confuse future contributors.

5. Boolean sparkline values display "null" tooltip — low

File: workspaces/scorecard/plugins/scorecard/src/utils/timeSeriesChartData.ts
Category: edge-case

When a MetricTimeSeriesPoint has a boolean value, toMetricSparklinePoints does not flag it as an error (since true !== null). Later, toNumericValue converts it to null, producing a chart point with no error marker and a tooltip showing the literal text "null". This is a latent display bug for any boolean metric given defaultVisualization: 'sparkline' — unlikely for DORA metrics but worth a defensive fix.

6. Duplicate jest.mock in test file — low

File: workspaces/scorecard/plugins/scorecard/src/components/Scorecard/__tests__/ScorecardEntityContentGridView.test.tsx
Category: dead-code

A new jest.mock('../../../utils', ...) block is immediately overridden by the pre-existing mock for the same module. In Jest, the last factory wins, making the new block dead code that may mislead maintainers.

Previous run

Review

Findings

Low

  • [dead code / test integrity] workspaces/scorecard/plugins/scorecard/src/components/Scorecard/__tests__/ScorecardEntityContentGridView.test.tsx — The PR adds a jest.mock('../../../utils', ...) call that is immediately followed by the existing jest.mock('../../../utils', ...) call. Jest hoists all mock calls and the last one for a given module wins, so the newly added mock is dead code — it is completely overridden by the existing mock. The new mock omits getTranslatedTextWithFallback, creating a maintenance trap if someone later removes the existing mock thinking it is the duplicate.
    Remediation: Remove the newly added jest.mock('../../../utils', ...) block (the one without getTranslatedTextWithFallback). The existing mock already provides getStatusConfig and resolveMetricTranslation alongside getTranslatedTextWithFallback.

  • [path traversal / inconsistent URL encoding] workspaces/scorecard/plugins/scorecard/src/api/index.ts:607 — The new getAggregationTimeSeries method interpolates aggregationId directly into the URL path without encodeURIComponent. Similarly, getMetricTimeSeries interpolates entity.kind, entity.metadata.namespace, and entity.metadata.name without encoding. In contrast, the new getMetricCollectors method correctly uses encodeURIComponent(metricId). Risk is mitigated because values come from the Backstage catalog model, but defense-in-depth favors consistent encoding.
    Remediation: Apply encodeURIComponent() to all path segments consistently, as already done in getMetricCollectors.

Previous run (2)

Review

Verdict: Approve

This PR adds sparkline (area chart) visualization support for entity-page scorecard metrics with defaultVisualization: sparkline. The change is well-structured, follows existing codebase patterns, and is thoroughly tested.

What was reviewed

60 files changed across the workspaces/scorecard plugin — new API methods, hooks, components, utilities, fixtures, translations, and tests (~5,471 additions, 426 deletions).

Correctness

  • API client methods (getMetricTimeSeries, getMetricCollectors, getAggregationTimeSeries): Proper input validation (empty IDs, missing entity fields, missing date range) and strict response validation with well-typed guards (isMetricTimeSeriesPoint, isScalarAggregatedTimeSeriesPoint, isCollectorMetadata, isThresholdConfig). URL construction uses URL and searchParams for safe encoding.
  • Hooks (useMetricTimeSeries, useMetricCollectors): Correctly guard fetch with enabled flags, properly cache via React Query with stable keys, and handle non-Error rejections with translated messages.
  • Chart data utilities (timeSeriesChartData.ts): Linear interpolation for error/null gaps is correct — finds nearest numeric neighbours and interpolates proportionally. Edge cases (all errors, trailing errors, single-value series, empty data) are well-handled.
  • Threshold evaluation (getLatestSuccessfulThresholdEvaluation): Correctly walks backward from the latest point, skips null calculation-error days, and stops at the first point with a non-null value — returning undefined if that point has no threshold classification.
  • DataSourcesDialog refactoring: The decoupling from MetricResult[] to generic SourceRow[] is clean. The MetricGroupCard now pre-computes rows via toMetricSourceRows, and EntitySparklineCard uses toCollectorSourceRows. Both paths are tested independently.
  • Test coverage: 523 lines in EntitySparklineCard.test.tsx alone, plus dedicated tests for collectorSourceRows, metricSourceRows, timeSeriesChartData, sparklineLegend, sparklineChartModel, timeSeriesRange, metricVisualization, SparklineChart, SparklineTooltip, and updated tests for DataSourcesDialog, DataSourcesDialogColumns, MetricGroupCard, EntityScorecardContent, ScorecardEntityContentGridView, and ScorecardApiClient.

Security

No concerns. The changes are frontend-only React components. URL construction follows existing patterns (path interpolation for entity coordinates, encodeURIComponent for metricId in getMetricCollectors). No authentication, RBAC, or privilege changes.

Architecture & coherence

The feature follows the established plugin architecture: API client → hooks → components. The EntityMetricCard routing component cleanly dispatches between sparkline and donut visualizations. The toSparklineChartModel view-model factory is designed for reuse by the upcoming homepage sparkline PR.

Style & conventions

Code follows existing patterns. i18n translations are added to all 6 locale files (ref, de, es, fr, it, ja). API reports are updated. The changeset is correctly marked as minor.

Low-severity observations

  1. ScorecardEntityContentGridView empty-group guard removal (ScorecardEntityContentGridView.tsx): The if (metricsInOrder.length > 0) guard was removed, so groups with zero matching metrics are now added to groupedMetrics. This could render an empty group card if a group definition references metric IDs absent from the API response. The rendering chain filters with .filter(Boolean), so the impact is minimal, but it's a subtle behavior change worth noting.

  2. useMetricTimeSeries date range stability: The date range (from/to) is computed inside queryFn at call time, but the query key uses the constant TIME_SERIES_DEFAULT_RANGE_DAYS = 30. If a component stays mounted across midnight, the range shifts but the cache key doesn't, so React Query returns stale data until the stale time expires. This is benign for a 30-day window.

  3. extractPluginName regex change (translationUtils.ts): The split regex changed from .split('.') to .split(/[.:]/) to support collector IDs like github:deploymentWorkflowRuns. This is backward-compatible since existing metric IDs use dots, and the regex handles both separators.

These are not blocking. The PR is clean and ready for merge.

Previous run (3)

Review

Findings

Medium

  • [error-handling-idiom] workspaces/scorecard/plugins/scorecard/src/hooks/useMetricCollectors.tsx:37useMetricCollectors omits the try/catch error-handling wrapper and useTranslation() call that all other hooks in the package use, including useMetricTimeSeries added in this same PR. Errors from the collectors API will bypass the localization system.
    Remediation: Add the same try/catch pattern used in sibling hooks.

  • [API shape] workspaces/scorecard/plugins/scorecard/src/hooks/useMetricCollectors.tsx:30useMetricCollectors(metricId, enabled) uses positional arguments while all other hooks accept a single options object (e.g., useAggregatedScorecard({ aggregationId, enabled })).
    Remediation: Define a UseMetricCollectorsOptions interface and accept a destructured options object.

  • [missing-feature-documentation] workspaces/scorecard/plugins/scorecard/README.md:9 — The Features list does not mention sparkline chart support, despite this PR adding a major new visualization mode across ~60 files.
    Remediation: Add a bullet under Features describing sparkline chart support.

Low

  • [dead test code] workspaces/scorecard/plugins/scorecard/src/components/Scorecard/__tests__/ScorecardEntityContentGridView.test.tsx — Duplicate jest.mock('../../../utils', ...) block; the later mock wins, making the new one dead code.

  • [edge-case] workspaces/scorecard/plugins/scorecard/src/utils/timeSeriesChartData.ts — Boolean MetricTimeSeriesPoint values (allowed by the MetricValue type) are silently converted to null via toNumericValue with no error marker, since toMetricSparklinePoints only sets error strings for null values.

  • [inconsistent input encoding] workspaces/scorecard/plugins/scorecard/src/api/index.tsgetMetricCollectors applies encodeURIComponent to its path segment while getAggregationTimeSeries and getMetricTimeSeries do not, creating an inconsistency within this PR (the non-encoding approach matches pre-existing methods).

  • [error-handling-idiom] workspaces/scorecard/plugins/scorecard/src/hooks/useMetricCollectors.tsx:40 — Custom state-suppression (isLoading: enabled && isLoading) diverges from how other hooks pass isLoading directly from useQuery.

  • [scope authorization] — PR references only an external JIRA ticket (RHIDP-15576) with no linked GitHub issue for scope verification.

  • [naming-convention] workspaces/scorecard/plugins/scorecard/src/translations/ref.tscollectorStatusTooltip is hardcoded to mention "DORA" specifically, coupling the tooltip text to one metric framework.

  • [scope-creep] workspaces/scorecard/plugins/scorecard/dev/unclipMain.ts — Dev-only CSS injection utility addresses a layout issue not specific to sparkline charts (though tangentially related).

  • [scope-creep] workspaces/scorecard/plugins/scorecard/src/components/Scorecard/ScorecardEntityContentGridView.tsx — Empty-group guard if (metricsInOrder.length > 0) was removed; functionally harmless but unrelated to sparkline feature.

Previous run (4)

Review — Approve

Scope: Adds sparkline (area chart) visualization for entity-page scorecard metrics whose defaultVisualization is sparkline. Includes new API client methods, hooks, chart components, shared utilities, a DataSourcesDialog refactor, i18n keys, and comprehensive tests.

This is a well-structured, cleanly decomposed feature PR. The code follows existing project patterns, has thorough input validation and error handling, and ships with excellent test coverage across all new and modified modules.

Correctness

  • API client (ScorecardApiClient): The three new methods (getMetricTimeSeries, getAggregationTimeSeries, getMetricCollectors) validate inputs, check response shapes, and surface clear error messages. Error handling follows the same try/catch pattern as existing methods. getMetricCollectors correctly uses encodeURIComponent for the metric ID in the URL path.
  • Hooks: useMetricTimeSeries properly gates the query on entity presence and non-empty metric ID; range computation happens inside queryFn while the cache key uses a constant (TIME_SERIES_DEFAULT_RANGE_DAYS), avoiding unnecessary refetches. useMetricCollectors cleanly suppresses loading/error state when enabled is false, preventing flash-of-loading when the dialog hasn't been opened yet.
  • DataSourcesDialog refactor: The dialog is now a pure presentation component accepting pre-built SourceRow[] and optional buckets. Row-building responsibility is properly separated into metricSourceRows.ts and collectorSourceRows.ts. The ThresholdLegend is conditionally rendered only when buckets is provided, which is correct for collector-mode usage where threshold filtering doesn't apply.
  • EntitySparklineCard: Correctly memoizes the chart model, lazily fetches collectors only when the dialog opens AND the metric has collectorIds, and handles loading/error/empty states.
  • Chart data utilities: interpolatePlotValue keeps the sparkline continuous across error gaps via linear interpolation — a sensible UX choice. getSparklineYDomain handles edge cases (empty data, single-value series) with appropriate fallback padding.
  • extractPluginName change: The regex update from .split('.') to .split(/[.:]/) is backward-compatible for existing dot-separated metric IDs and correctly handles colon-separated collector IDs like github:deploymentWorkflowRuns.
  • Test coverage: All new components, hooks, and utility functions have dedicated test suites covering happy paths, edge cases (null values, empty responses, disabled state), and error scenarios.

Security

No concerns. No authentication or authorization changes. All new API calls use the existing fetchApi plumbing. Translation strings are rendered via React (no dangerouslySetInnerHTML). Entity metadata values used in URL paths come from the Backstage catalog, not direct user input.

Intent & Coherence

The change matches its stated purpose and is appropriately scoped as a foundation PR. Shared chart components and utilities are designed for reuse by the homepage sparkline PR that follows. The changeset correctly selects a minor bump for a new user-visible feature.

Style & Conventions

  • All new files include the Red Hat license header.
  • Type imports use the import type form consistently.
  • JSDoc comments on public functions and hooks.
  • Test patterns match existing test suites in the scorecard plugin.
  • i18n keys follow the existing dataSourcesDialog.* namespace with translations provided for all five locales.

Documentation

The PR body is thorough with component descriptions, API endpoints, test instructions, and a checklist. In-code documentation (JSDoc, type comments) is present on all public-facing utilities. The checklist items for changeset, docs, tests, and screenshots are tracked but some are unchecked — the author should verify these before merge.

Observations

  • [provenance-warning] Prior review provenance: unverifiable-wrong-app. Prior review was created by a different GitHub App than expected; severity anchoring was skipped for this run.
  • [info] ScorecardEntityContentGridView grouping change: The guard if (metricsInOrder.length > 0) before groupedMetrics.set(...) was removed, meaning empty groups are now added to the map. This is functionally safe because the downstream .filter(Boolean) handles null returns, but it is a subtle behavioral change worth noting.
  • [info] Dev mock duplication: dev/legacy.tsx and dev/mocks.ts contain near-identical mock API implementations for the new methods. This is expected for dev server configuration but adds maintenance surface.

No medium or higher severity findings. The PR is safe to merge.

Previous run (5)

Review

Findings

Medium

  • [error-handling-idiom] workspaces/scorecard/plugins/scorecard/src/hooks/useMetricCollectors.tsx — Every existing hook wraps queryFn in a try-catch that converts non-Error throwables to a translated message (e.g., t('errors.fetchError', { error: String(err) })). useMetricCollectors passes a bare queryFn with no error wrapping, breaking the established error-handling pattern. useMetricTimeSeries in the same PR correctly follows this pattern.

  • [Hook parameter convention] workspaces/scorecard/plugins/scorecard/src/hooks/useMetricCollectors.tsx — All existing hooks that accept an enabled flag use a destructured options-object pattern (e.g., useAggregatedScorecard({ aggregationId, enabled })). useMetricCollectors uses positional parameters (metricId, enabled), diverging from the established convention.

  • [stale-api-report] workspaces/scorecard/plugins/scorecard/report.api.md — The main report.api.md is missing 5 new translation keys (collectorStatusTooltip, collectorEmptyValue, collectorUnavailableStatus, pluginGithub, pluginJira) that were added to report-alpha.api.md and report-legacy.api.md. The report jumps directly from dataSourcesDialog.statusTooltip to dataSourcesDialog.columns.plugin.
    Remediation: Regenerate report.api.md by running the API Extractor.

  • [missing-doc] workspaces/scorecard/plugins/scorecard/README.md — The README Features section lists four features but does not mention sparkline chart visualization. This is a user-visible feature that administrators and integrators should know about.
    Remediation: Add a fifth bullet to the Features list describing sparkline chart support for time-series metrics.

Low

  • [edge-case] workspaces/scorecard/plugins/scorecard/src/utils/timeSeriesChartData.ts:136toAggregationSparklinePoints determines error labels based solely on point.status === 'error'. If a point has status: 'success' but value: null, no error label is set, causing the tooltip to display null. By contrast, toMetricSparklinePoints defensively checks point.value === null as a fallback.

  • [api-surface] workspaces/scorecard/plugins/scorecard/report-alpha.api.mdpluginGithub and pluginJira translation keys are hardcoded to two specific collector providers. Future providers would need new keys, though a fallback to extractPluginName exists for unknown prefixes.

  • [pattern-inconsistency] workspaces/scorecard/plugins/scorecard/src/api/index.tsgetMetricCollectors encodes metricId with encodeURIComponent, but getAggregationTimeSeries and getMetricTimeSeries do not encode their path segments. This is consistent with the pre-existing codebase pattern (most methods do not encode), making getMetricCollectors the outlier.

  • [naming-convention] workspaces/scorecard/plugins/scorecard/dev/mocks.ts — Type-only symbols (ScorecardApi, ScorecardOptions, etc.) imported with value import instead of import type. Sibling file legacy.tsx uses import type for the same symbols.

  • [interface-extension] workspaces/scorecard/plugins/scorecard/src/api/types.ts — Three new mandatory methods added to the ScorecardApi interface. Not part of the declared public API surface; any downstream consumer that deep-imports this internal interface will get clear compile errors at the missing methods. Minor version bump correctly signals additive changes.

  • [behavioral-change] workspaces/scorecard/plugins/scorecard/src/utils/translationUtils.tsextractPluginName regex changed from split('.') to split(/[.:]/) to handle colon-separated collector IDs. Internal function, backward-compatible for dot-separated IDs.

  • [internal-component-contract] workspaces/scorecard/plugins/scorecard/src/components/MetricGroupCard/DataSourcesDialog.tsxDataSourcesDialogProps interface refactored: metrics replaced with rows, isLoading, error, buckets. Internal component, not part of public API. Row computation now happens in callers (MetricGroupCard and EntitySparklineCard).


Labels: Feature PR adding new sparkline chart capability to the scorecard workspace.

Previous run (6)

Review

Verdict: comment

This PR adds sparkline (area chart) visualization support for entity-page scorecard metrics, including new chart components, API methods, hooks, utilities, and i18n strings. The architecture is well-structured: components follow the existing project patterns, hooks use the established useQuery + UseResponseData<T> pattern, and the code includes comprehensive test coverage across all new modules. The security posture is clean — all data rendering goes through React's auto-escaping, API calls use Backstage's authenticated fetch wrapper, and input validation is present on all new API methods.

Two medium-severity findings require attention before merge. Several low-severity items are noted for consideration.


Medium

1. Stale API report — report.api.md not regenerated

File: workspaces/scorecard/plugins/scorecard/report.api.md

The PR updates report-alpha.api.md and report-legacy.api.md with 5 new dataSourcesDialog.* translation keys (collectorStatusTooltip, collectorEmptyValue, collectorUnavailableStatus, pluginGithub, pluginJira), but report.api.md is not updated. This file exports the same scorecardTranslationRef and is now inconsistent with the other two reports.

Remediation: Run the API report generation command (e.g., yarn backstage-repo-tools api-reports) to regenerate report.api.md.

2. Breaking ScorecardApi interface — dev mocks will not compile

File: workspaces/scorecard/plugins/scorecard/src/api/types.ts (line ~105)

Three new required methods are added to the ScorecardApi interface (getAggregationTimeSeries, getMetricTimeSeries, getMetricCollectors). The MockScorecardApi classes in dev/mocks.ts (line 57) and dev/legacy.tsx (line 82) both implements ScorecardApi but are not updated with these methods, causing TypeScript compilation errors in the dev environment.

Remediation: Add stub implementations for the three new methods to both MockScorecardApi classes. Alternatively, consider making the new methods optional on the interface if downstream consumers implement ScorecardApi directly.


Low

3. URL construction inconsistency in getMetricCollectors

getMetricCollectors uses a template literal with explicit encodeURIComponent() while every other method in the class (including the two new time-series methods) uses new URL(). This creates a split pattern for URL construction within the same class.

4. Hardcoded 'DORA' in collectorStatusTooltip translation

The collectorStatusTooltip string hardcodes "DORA" but the tooltip displays for any metric with isCollector: true, not only DORA metrics. Consider using a generic term or a translation interpolation variable.

5. useMetricCollectors hook missing error-wrapping pattern

Unlike the sibling hooks (useMetricTimeSeries, useAggregatedScorecard), useMetricCollectors does not wrap its queryFn in a try/catch with translated error messages. The practical risk is low since the API client handles errors internally, but it breaks the convention.

6. getAggregationTimeSeries has no caller in this PR

The method is defined, tested, and added to the interface, but no hook or component in this PR calls it. The PR body notes a follow-up homepage sparkline PR will use it. Consider whether this uncalled method belongs in this PR or the follow-up.

7. Test gap — no test for collector fetch error

EntitySparklineCard.test.tsx covers loading state, time-series fetch errors, empty data, and successful rendering, but does not test the path where useMetricCollectors returns an error.

8. README not updated with sparkline feature

The README Features section does not mention sparkline time-series charts. The changeset describes the feature, but a README update would help users discover it.

9. extractPluginName regex change

The split regex changed from '.' to /[.:]/ to support colon-delimited collector IDs (e.g., github:deploymentWorkflowRuns). This is functionally correct and tested, but the behavioral change to an existing utility is not called out in the PR description.

10. Hardcoded pluginGithub/pluginJira translation keys

The pluginLabels map must be manually extended for each new collector integration. The fallback (extractPluginName) already capitalizes the first segment, so these keys only improve brand-name casing.

Previous run (7)

Review

Verdict: comment — medium-severity findings worth noting but none that should block.

Summary

This PR adds sparkline chart support to the scorecard entity page, enabling time-series visualization for DORA and other metrics alongside the existing score donut cards. The change spans 52 files (+4101/−315) across API client extensions, new React components, custom hooks, utility functions, translations, and comprehensive tests.

Architecture is clean: EntityMetricCard acts as a visualization router (sparkline vs. donut), EntitySparklineCard wires up data fetching and chart rendering, and SparklineChart is a reusable Recharts wrapper. The DataSourcesDialog refactoring from raw MetricResult[] to pre-built SourceRow[] improves separation of concerns and enables collector-based data source rows.

Test coverage is strong — ~20 new test files cover API client methods, hooks, utility functions, chart components, and the integration between EntitySparklineCard and data source dialogs.

Findings

1. Missing changeset [medium · process]

File: (repository root — no .changeset/*.md file present)

The PR adds a user-visible feature (feat prefix) but includes no changeset. Per CONTRIBUTING.md and .fullsend/AGENTS.md, a changeset with minor bump level is expected for new features. The PR checklist also shows all items unchecked.

Remediation: Add a changeset via npx changeset selecting the @red-hat-developer-hub/backstage-plugin-scorecard package with a minor bump.

2. Entity path segments not URL-encoded in getMetricTimeSeries [low · defense-in-depth]

File: workspaces/scorecard/plugins/scorecard/src/api/index.ts (new method getMetricTimeSeries)

The URL is built with entity kind, namespace, and name interpolated directly into the path:

const url = new URL(
  `${baseUrl}/metrics/catalog/${entity.kind}/${entity.metadata.namespace}/${entity.metadata.name}/time-series`,
);

While entity names from the Backstage catalog are typically safe, this is inconsistent with getMetricCollectors which properly uses encodeURIComponent(metricId). If an entity name contained / or other URL-special characters, the URL would be malformed.

Note: This pattern is consistent with existing methods in the same file (e.g., getScorecards), so it is pre-existing rather than introduced by this PR.

Remediation: Consider wrapping path segments with encodeURIComponent() for defense-in-depth:

`${baseUrl}/metrics/catalog/${encodeURIComponent(entity.kind)}/${encodeURIComponent(entity.metadata.namespace)}/${encodeURIComponent(entity.metadata.name)}/time-series`

3. Hardcoded plugin labels may not scale [low · maintainability]

File: workspaces/scorecard/plugins/scorecard/src/translations/ref.ts and collectorSourceRows.ts

Plugin labels for collectors (pluginGithub, pluginJira) are hardcoded as translation keys and passed via a pluginLabels map. When a new collector provider is added (e.g., PagerDuty, GitLab), a new translation key and mapping would need to be added manually. The fallback to extractPluginName handles unknown providers by capitalizing the prefix from the collector ID, which is reasonable, but the hardcoded map adds ongoing maintenance.

Remediation: Consider whether the extractPluginName fallback alone is sufficient, or document the pattern for adding new collector providers.

What looks good

  • Clean component extraction: EntityMetricCard centralizes the visualization-type routing, eliminating duplicated status/translation logic from both EntityScorecardContent and ScorecardEntityContentGridView.
  • Smart data fetching: useMetricCollectors is gated by enabled so collector data is only fetched when the data-sources dialog opens and the metric has collector IDs — no wasted requests.
  • Robust chart data handling: The interpolatePlotValue function linearly interpolates null/error points to keep the sparkline continuous, with proper edge-case handling (no prev, no next, both missing).
  • Comprehensive i18n: All 5 new translation keys are added across all 7 supported languages (en, de, es, fr, it, ja, ref).
  • Thorough test coverage: API client, hooks, utility functions, and component integration are all well-tested with edge cases.

@fullsend-ai-review fullsend-ai-review Bot added the requires-manual-review Review requires human judgment label Sep 3, 2026
@Eswaraiahsapram
Eswaraiahsapram force-pushed the feat/scorecard-entity-sparkline-cards-ui branch from b56fa91 to 3dd8c8b Compare September 4, 2026 08:51
@fullsend-ai-review

fullsend-ai-review Bot commented Sep 4, 2026

Copy link
Copy Markdown

🤖 Review · ❌ Terminated · Started 8:53 AM UTC · Ended 9:09 AM UTC

Commit: 3dd8c8b · View workflow run →

fullsend-ai-review[bot]

This comment was marked as outdated.

fullsend-ai-review[bot]

This comment was marked as outdated.

@fullsend-ai-review fullsend-ai-review Bot added requires-manual-review Review requires human judgment and removed requires-manual-review Review requires human judgment labels Sep 4, 2026
@fullsend-ai-review

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 8:53 AM UTC · Completed 9:09 AM UTC

Commit: 3dd8c8b · View workflow run →

Runtime: claude · Model: opus → claude-opus-4-6 · Cost: $9.51

@fullsend-ai-review

fullsend-ai-review Bot commented Sep 4, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 9:11 AM UTC · Completed 9:53 AM UTC

Commit: 46c6489 · View workflow run →

Runtime: claude · Model: opus → claude-opus-4-6 · Cost: $13.56

fullsend-ai-review[bot]

This comment was marked as outdated.

@fullsend-ai-review fullsend-ai-review Bot added requires-manual-review Review requires human judgment enhancement New feature or request and removed requires-manual-review Review requires human judgment labels Sep 4, 2026
@Eswaraiahsapram
Eswaraiahsapram force-pushed the feat/scorecard-entity-sparkline-cards-ui branch from 46c6489 to 159c8f3 Compare September 7, 2026 06:10
@fullsend-ai-review

fullsend-ai-review Bot commented Sep 7, 2026

Copy link
Copy Markdown

🤖 Review · ❌ Terminated · Started 6:12 AM UTC · Ended 6:39 AM UTC

Commit: 159c8f3 · View workflow run →

@codecov

codecov Bot commented Sep 7, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 89.68105% with 55 lines in your changes missing coverage. Please review.
✅ Project coverage is 63.23%. Comparing base (ce6f5f5) to head (d78abf0).
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4573      +/-   ##
==========================================
+ Coverage   60.14%   63.23%   +3.08%     
==========================================
  Files        2673     2675       +2     
  Lines      106162   106151      -11     
  Branches    29807    29664     -143     
==========================================
+ Hits        63856    67128    +3272     
+ Misses      41789    38512    -3277     
+ Partials      517      511       -6     
Flag Coverage Δ *Carryforward flag
adoption-insights 84.77% <ø> (ø) Carriedforward from 754ebef
ai-integrations 78.54% <ø> (-1.30%) ⬇️ Carriedforward from 754ebef
app-defaults 54.82% <ø> (ø) Carriedforward from 754ebef
augment 46.67% <ø> (ø) Carriedforward from 754ebef
boost 84.97% <ø> (ø) Carriedforward from 754ebef
bulk-import 73.12% <ø> (ø) Carriedforward from 754ebef
cost-management 13.53% <ø> (ø) Carriedforward from 754ebef
dcm 73.47% <ø> (ø) Carriedforward from 754ebef
e2e-adoption-insights 60.00% <ø> (ø) Carriedforward from 754ebef
e2e-extensions 62.31% <ø> (ø) Carriedforward from 754ebef
e2e-global-header 51.82% <ø> (ø) Carriedforward from 754ebef
e2e-homepage 61.11% <ø> (ø) Carriedforward from 754ebef
e2e-intelligent-assistant 46.01% <ø> (ø) Carriedforward from 754ebef
e2e-orchestrator 49.49% <ø> (ø) Carriedforward from 754ebef
e2e-orchestrator-plugin 49.48% <ø> (ø) Carriedforward from 754ebef
e2e-quickstart 55.21% <ø> (ø) Carriedforward from 754ebef
e2e-scorecard 50.00% <ø> (-0.06%) ⬇️ Carriedforward from 754ebef
e2e-theme 16.36% <ø> (ø) Carriedforward from 754ebef
extensions 58.30% <ø> (ø) Carriedforward from 754ebef
global-floating-action-button 71.18% <ø> (ø) Carriedforward from 754ebef
global-header 67.78% <ø> (+0.02%) ⬆️ Carriedforward from 754ebef
homepage 48.39% <ø> (ø) Carriedforward from 754ebef
install-dynamic-plugins 71.77% <ø> (ø) Carriedforward from 754ebef
intelligent-assistant 77.24% <ø> (-0.75%) ⬇️ Carriedforward from 754ebef
konflux 91.98% <ø> (ø) Carriedforward from 754ebef
lightspeed 69.02% <ø> (ø) Carriedforward from 754ebef
mcp-integrations 84.46% <ø> (ø) Carriedforward from 754ebef
orchestrator 77.32% <ø> (ø) Carriedforward from 754ebef
quickstart 63.74% <ø> (ø) Carriedforward from 754ebef
sandbox 79.56% <ø> (ø) Carriedforward from 754ebef
scorecard 88.71% <89.68%> (+0.26%) ⬆️
theme 87.91% <ø> (ø) Carriedforward from 754ebef
translations 5.12% <ø> (ø) Carriedforward from 754ebef
x2a 78.44% <ø> (+64.57%) ⬆️ Carriedforward from 754ebef

*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 ce6f5f5...d78abf0. 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[bot]

This comment was marked as outdated.

@fullsend-ai-review fullsend-ai-review Bot added ready-for-merge All reviewers approved — ready to merge and removed requires-manual-review Review requires human judgment labels Sep 7, 2026
@fullsend-ai-review

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 6:12 AM UTC · Completed 6:39 AM UTC

Commit: 159c8f3 · View workflow run →

Runtime: claude · Model: opus → claude-opus-4-6 · Cost: $1.78

@Eswaraiahsapram
Eswaraiahsapram force-pushed the feat/scorecard-entity-sparkline-cards-ui branch from 281e14d to d201379 Compare September 16, 2026 09:07
@fullsend-ai-review

fullsend-ai-review Bot commented Sep 16, 2026

Copy link
Copy Markdown

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

Commit: d201379 · View workflow run →

@fullsend-ai-review
fullsend-ai-review Bot dismissed their stale review September 16, 2026 09:30

Superseded by updated review

@fullsend-ai-review fullsend-ai-review Bot added requires-manual-review Review requires human judgment and removed ready-for-merge All reviewers approved — ready to merge labels Sep 16, 2026
@fullsend-ai-review

Copy link
Copy Markdown

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

Commit: d201379 · View workflow run →

Runtime: claude · Model: opus → claude-opus-4-6 · Cost: $14.68

Comment thread workspaces/scorecard/plugins/scorecard/src/translations/de.ts Outdated
@Eswaraiahsapram
Eswaraiahsapram force-pushed the feat/scorecard-entity-sparkline-cards-ui branch from d201379 to c71e26e Compare September 16, 2026 11:14

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

Confirmed that the functionality works as expected:

Single points:

Screen.Recording.2026-09-16.at.15.57.35.mov

Various data with errors:

Screen.Recording.2026-09-16.at.16.03.30.mov

Translation:

Screen.Recording.2026-09-16.at.16.06.16.mov

Translation of collectors description is to be worked within a different ticket.
Adding check description together with collector description to View datasources will be also done within a different ticket, as it was confirmed on today's meeting.

It would be also nice to update all mocked collector ids as they were updated to the narrowed down version, to do within a different PR:

  • github:deployments -> github:doraDeployments
  • github:deploymentWorkflowRuns -> github:doraDeploymentWorkflowRuns
  • github:deploymentPullRequests -> github:doraDeploymentPullRequests
  • jira:incidents -> jira:doraIncidents

@sonarqubecloud

Copy link
Copy Markdown

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

Hi @Eswaraiahsapram , the changes look good to me, thanks for the update. I also agree with @dzemanov 's point about updating the collector IDs in the mocked data. If you'd prefer to handle that in a separate story, we can merge this PR now. Please open a Jira story to track the work and add the link here for reference 🤝

/lgtm

@Eswaraiahsapram

Copy link
Copy Markdown
Member Author

Hi @Eswaraiahsapram , the changes look good to me, thanks for the update. I also agree with @dzemanov 's point about updating the collector IDs in the mocked data. If you'd prefer to handle that in a separate story, we can merge this PR now. Please open a Jira story to track the work and add the link here for reference 🤝

/lgtm

Thanks @ciiay , I can take care of updating the collector IDs in the Homepage sparkline PR #4596.

Merging now

@Eswaraiahsapram
Eswaraiahsapram merged commit aabab35 into redhat-developer:main Sep 17, 2026
26 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request lgtm requires-manual-review Review requires human judgment workspace/scorecard

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants