Skip to content

feat(scorecard): add MetricGroupCard with threshold buckets and data sources dialog#3667

Open
Eswaraiahsapram wants to merge 4 commits into
redhat-developer:mainfrom
Eswaraiahsapram:scorecard-group-metrics-ui-RHIDP-14897
Open

feat(scorecard): add MetricGroupCard with threshold buckets and data sources dialog#3667
Eswaraiahsapram wants to merge 4 commits into
redhat-developer:mainfrom
Eswaraiahsapram:scorecard-group-metrics-ui-RHIDP-14897

Conversation

@Eswaraiahsapram

@Eswaraiahsapram Eswaraiahsapram commented Jul 2, 2026

Copy link
Copy Markdown
Member

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

📋 Summary

Implements the Grid View component for the Scorecard grouped layouts feature as part of RHIDP-14897.

This PR introduces a new MetricGroupCard component that aggregates related metrics into visual threshold-based bucket tiles, allowing platform engineers to quickly understand metric status across categories at a glance.

🎯 What Changed

New Components

  • MetricGroupCard - Displays grouped metrics as threshold bucket tiles (e.g., "3 Critical", "5 High", "12 Passed")

    • Three-column tile layout showing count + status for each threshold evaluation

    • Interactive tiles that open filtered data sources dialog

    • Menu with "View data sources" action

    • DataSourcesDialog - Modal for viewing detailed metric information

      • Sortable table showing Plugin, Check, Value, Status, Last Synced
      • Filterable by threshold status (click bucket tiles to filter)
      • Interactive threshold legend for toggling filters
      • Responsive design with proper i18n support
  • Supporting Components:

    • ThresholdBucketTile - Individual threshold status tile with click/keyboard support
    • ThresholdLegend - Filter chips for threshold status in dialog footer
    • StatusIcon - Reusable status icon component
    • MetricGroupCardMenu - Overflow menu for card actions

Refactored Components

  • ScorecardEntityContentGridView - Migrated to Masonry layout
    • Removed inline component definitions (143 lines → extracted to dedicated files)
    • Now renders MetricGroupCard for grouped metrics + individual Scorecard cards for ungrouped
    • Responsive grid: 1 column (mobile) → 2 (tablet) → 3 (desktop)

Infrastructure

  • Layout Extension Cleanup - Removed duplicate scorecardEntityLayoutList extension that was incorrectly pointing to GridView
  • Translation Support - Added i18n keys across all 6 locales (en, de, es, fr, it, ja):
    • dataSourcesDialog.* - Dialog UI strings
    • metricGroupCard.* - Menu and card strings
  • Utility Functions:
    • buildThresholdBuckets - Groups metrics by threshold evaluation
    • extractPluginName - Parses plugin identifier from metric ID
    • resolveMetricTranslation - Handles metric-specific i18n

Dependencies

  • Added @mui/lab (5.0.0-alpha.177) for Masonry layout
  • Moved @backstage/ui from devDependencies → dependencies

🧪 Testing

Comprehensive test coverage

  • DataSourcesDialog.test.tsx - Dialog behavior, sorting, filtering, keyboard nav
  • MetricGroupCard.test.tsx - Card rendering, menu actions, tile clicks
  • thresholdBucketUtils.test.tsx - Bucket aggregation logic
  • ThresholdBucketTile.test.tsx - Accessibility, click/keyboard handlers
  • ThresholdLegend.test.tsx - Filter toggle interactions
  • ScorecardEntityContentGridView.test.tsx - Layout rendering, grouping logic

📸 Screenshots

Screen.Recording.2026-07-06.at.11.25.28.AM.mov

📝 Configuration Example

# Enable Grid layout and configure metric groups in app-config.yaml
app:
  extensions:
    - scorecard-layout:catalog/scorecard-entity-layout-grid:
        disabled: false
        config:
          groups:
            security-vulnerabilities:
              title: Security Vulnerabilities
              description: Track security issues across your repositories
              metrics:
                - sonarqube.security_rating
                - dependabot.alerts_critical
                - dependabot.alerts_high

            code-quality:
              title: Code Quality
              description: Code quality and maintainability metrics
              metrics:
                - sonarqube.quality_gate
                - sonarqube.code_coverage
                - sonarqube.maintainability_rating

Hey, I just made a Pull Request!

✔️ 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 Jul 2, 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 v2.7.9

@fullsend-ai-review

fullsend-ai-review Bot commented Jul 2, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 4:34 PM UTC · Completed 4:50 PM UTC
Commit: de71949 · View workflow run →

@codecov

codecov Bot commented Jul 2, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 82.30453% with 43 lines in your changes missing coverage. Please review.
✅ Project coverage is 54.43%. Comparing base (00f80fc) to head (02ee320).
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3667      +/-   ##
==========================================
+ Coverage   54.30%   54.43%   +0.13%     
==========================================
  Files        2349     2354       +5     
  Lines       89718    89802      +84     
  Branches    25119    25069      -50     
==========================================
+ Hits        48722    48885     +163     
- Misses      40648    40656       +8     
+ Partials      348      261      -87     
Flag Coverage Δ *Carryforward flag
adoption-insights 83.70% <ø> (ø) Carriedforward from b81e311
ai-integrations 68.35% <ø> (ø) Carriedforward from b81e311
app-defaults 69.79% <ø> (ø) Carriedforward from b81e311
augment 46.39% <ø> (ø) Carriedforward from b81e311
boost 74.68% <ø> (ø) Carriedforward from b81e311
bulk-import 72.46% <ø> (ø) Carriedforward from b81e311
cost-management 14.10% <ø> (ø) Carriedforward from b81e311
dcm 61.81% <ø> (ø) Carriedforward from b81e311
extensions 61.53% <ø> (ø) Carriedforward from b81e311
global-floating-action-button 71.18% <ø> (ø) Carriedforward from b81e311
global-header 59.71% <ø> (ø) Carriedforward from b81e311
homepage 50.23% <ø> (ø) Carriedforward from b81e311
install-dynamic-plugins 56.77% <ø> (ø) Carriedforward from b81e311
konflux 91.49% <ø> (ø) Carriedforward from b81e311
lightspeed 69.01% <ø> (+0.19%) ⬆️ Carriedforward from b81e311
mcp-integrations 85.46% <ø> (ø) Carriedforward from b81e311
orchestrator 39.69% <ø> (-0.12%) ⬇️ Carriedforward from b81e311
quickstart 65.63% <ø> (ø) Carriedforward from b81e311
sandbox 79.56% <ø> (ø) Carriedforward from b81e311
scorecard 83.98% <82.30%> (+1.31%) ⬆️
theme 61.26% <ø> (ø) Carriedforward from b81e311
translations 7.25% <ø> (ø) Carriedforward from b81e311
x2a 78.68% <ø> (ø) Carriedforward from b81e311

*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 00f80fc...02ee320. 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 Jul 2, 2026

Copy link
Copy Markdown

Review — comment

This PR introduces a well-structured MetricGroupCard system for the Scorecard plugin's grid layout, including threshold-based bucket tiles, a filterable data sources dialog, a Masonry layout, and comprehensive i18n support across 6 locales. The code is well-organized with proper component decomposition, good accessibility patterns (keyboard support, aria attributes), and thorough test coverage. The removal of the duplicate scorecardEntityLayoutList extension is a reasonable cleanup.

Three medium-severity findings are noted below — none are blocking on their own but are worth addressing to improve robustness.


Findings

1. Global CSS injection via unscoped <style> tags — medium

File: src/components/MetricGroupCard/DataSourcesDialog.tsx

When the dialog is open, global <style> tags are injected with broad attribute selectors:

[class*="DialogOverlay"] { background: rgba(0, 0, 0, 0.5); }
[slot="title"] { font-size: 1.25rem; font-weight: 700; }
[slot="close"] svg { width: 1.5rem; height: 1.5rem; }

These selectors are not scoped to this component instance. Any element on the page whose class contains DialogOverlay, or any element with slot="title", will be styled — including elements from other plugins or Backstage components. This can cause visual regressions that are difficult to debug.

Remediation: Use MUI's sx prop, scoped CSS modules, or apply styles via a ref/container selector that targets only this dialog instance (e.g., wrap the dialog in a container with a unique class and scope all selectors under it).

2. Manual overlay-click handler conflicts with Dialog built-in behavior — medium

File: src/components/MetricGroupCard/DataSourcesDialog.tsx

The component adds a mousedown document listener (handleOverlayClick) that uses document.querySelector('[role="dialog"]') to detect clicks outside the dialog. This approach has two issues:

  • Fragile selector: querySelector('[role="dialog"]') returns the first matching element in DOM order. If any other dialog exists (from Backstage core, another plugin, or a nested dialog), this will match the wrong element.
  • Duplicated behavior: The @backstage/ui Dialog component already accepts onOpenChange (which is wired up: onOpenChange={isOpen => !isOpen && onClose()}). This should already handle overlay dismiss. The custom handler may conflict with, or double-fire alongside, the built-in mechanism.

Remediation: Remove the custom handleOverlayClick effect and rely on Dialog's native onOpenChange callback for close-on-overlay-click behavior. If the built-in behavior is insufficient, scope the selector (e.g., use a React ref on the dialog container).

3. as any type assertions bypass translation parameter safety — medium

File: src/components/MetricGroupCard/DataSourcesDialog.tsx

Several translation calls use as any to bypass TypeScript:

t('dataSourcesDialog.title', { title } as any)
t('dataSourcesDialog.statusTooltip', { value: ..., status: ..., expression: ... } as any)

These assertions suppress compile-time checks on interpolation parameter names. If a parameter name is mistyped (e.g., titl instead of title), the translation will render the raw {{title}} placeholder at runtime with no compiler warning. Given that i18n keys are already defined in the translation ref, the proper types should be available.

Remediation: Add proper type definitions for translation keys that accept interpolation parameters, or use a typed wrapper. If the translation framework doesn't support parameterized key types natively, consider a utility type that maps keys to their expected parameter shapes.


Additional observations (low severity)

  • cx={82} hardcoded in Scorecard.tsx: The pie chart center changed from cx="22%" (responsive) to cx={82} (fixed pixels). At extreme container widths this may clip or misalign the chart. Verify the rendering remains acceptable across the responsive grid's breakpoints.

  • @mui/lab at alpha version (5.0.0-alpha.177): The Masonry component comes from an alpha-versioned package. The API surface may change between alpha releases. Consider documenting this dependency risk or pinning to an exact version range.

Previous run

Review

Findings

Medium

  • [logic-error] workspaces/scorecard/plugins/scorecard/src/components/MetricGroupCard/DataSourcesDialog.tsx:601 — The handleOverlayClick callback uses document.querySelector('[role="dialog"]') to find the dialog element, which returns the first element in DOM order matching that selector. If any other dialog is present in the DOM (e.g., from Backstage or another plugin), this check may match the wrong element, causing clicks inside the real DataSourcesDialog to be misidentified as outside clicks (closing the dialog unexpectedly) or clicks outside to be ignored.
    Remediation: Use a React ref attached to the Dialog's content container and check ref.current.contains(e.target) instead of querying the DOM by role.

Low

  • [edge-case] workspaces/scorecard/plugins/scorecard/src/components/MetricGroupCard/thresholdBucketUtils.ts:49collectThresholdRules uses a first-seen-wins strategy: when multiple metrics have threshold rules with the same key but different expressions, only the first metric's expression is kept. The resulting ThresholdBucket.expression shown in the legend may be misleading when a group contains metrics with heterogeneous threshold definitions.

  • [pattern-violation] workspaces/scorecard/plugins/scorecard/src/components/MetricGroupCard/MetricGroupCard.tsx:42buildThresholdBuckets(metrics, t) is called directly in the render body without useMemo, so it re-runs on every render. The same function call in DataSourcesDialog.tsx is correctly wrapped in useMemo. While not a functional bug, this is inconsistent and causes unnecessary recomputation.

  • [edge-case] workspaces/scorecard/plugins/scorecard/src/components/MetricGroupCard/DataSourcesDialog.tsx:580 — The sort comparator for the 'value' column treats non-numeric values (like the em-dash '—') as equal to each other (return 0), meaning their relative order is unstable across sort toggles. This is a minor UX inconsistency rather than a crash.

Previous run (2)

Review

Findings

Medium

  • [logic-error] workspaces/scorecard/plugins/scorecard/src/components/MetricGroupCard/DataSourcesDialog.tsx — The handleOverlayClick handler uses document.querySelector('[role="dialog"]') to detect clicks outside the dialog. This returns the first matching element in document order, which will malfunction if any other dialog/modal with role="dialog" is present in the DOM. Additionally, this manual dismiss logic is redundant with the Dialog component's own onOpenChange handler (already wired at line 559), creating a potential double-close race.
    Remediation: Remove the handleOverlayClick callback and its associated useEffect. Rely solely on the Dialog component's built-in onOpenChange prop for dismiss-on-overlay-click behavior.

  • [logic-error] workspaces/scorecard/plugins/scorecard/src/components/MetricGroupCard/DataSourcesDialog.tsx — Global <style> tags with broad selectors ([class*="DialogOverlay"], [slot="title"], [slot="close"] svg) are injected whenever the dialog is open. These selectors match any element on the page with those attributes/class substrings, not just elements within this dialog instance. This can unintentionally restyle other Backstage UI Dialog instances or slot-based components rendered elsewhere in the application.
    Remediation: Scope these styles to a wrapper element using a unique CSS class or use the Dialog component's built-in styling props/slots instead of global <style> injection.

Low

  • [missing-authorization] No linked issue — This PR introduces a substantial new feature (MetricGroupCard component family, DataSourcesDialog, Masonry layout, removal of scorecardEntityLayoutList, new @mui/lab dependency, i18n keys across 6 locales) but has no linked issue. The PR carries the do-not-merge/work-in-progress label, so this may be addressed before the PR is ready for merge.

  • [breaking-api] workspaces/scorecard/plugins/scorecard/src/alpha/index.tsx — The scorecardEntityLayoutList extension is removed from scorecardCatalogModule.extensions. This extension was @alpha and disabled: true by default, limiting the blast radius to alpha consumers who explicitly opted in. Consider documenting this removal in the changeset.

  • [scope-creep] workspaces/scorecard/plugins/scorecard/src/components/Scorecard/Scorecard.tsx — The Scorecard component's width is changed from 371px to 100%. While this is necessary for the new Masonry layout, it also affects the default EntityScorecardContent view. Documenting why this change is necessary would improve reviewability.

  • [scope-creep] workspaces/scorecard/plugins/scorecard/src/components/Scorecard/EntityScorecardContent.tsx — Layout change from flex-wrap to CSS grid and donut chart cx change from 22% to 82px. These are related to the width change above but should be documented.

  • [scope-creep] workspaces/scorecard/plugins/scorecard/src/alpha/extensions/scorecardLayoutExtensions.tsx — Removing scorecardEntityLayoutList is a natural cleanup alongside the ScorecardEntityContentGridView refactor, but the PR description should mention this removal.

  • [component-declaration-style] workspaces/scorecard/plugins/scorecard/src/components/MetricGroupCard/StatusIcon.tsx — Declared as export function while the codebase convention for exported components is export const ComponentName = (...) =>.

  • [component-declaration-style] workspaces/scorecard/plugins/scorecard/src/components/MetricGroupCard/ThresholdLegend.tsx — Same as above: uses export function instead of export const arrow.

  • [type-definition-style] workspaces/scorecard/plugins/scorecard/src/components/MetricGroupCard/types.tsMetricGroupCardProps uses export interface while all other exported types in types.ts files use export type. The same file uses export type for ThresholdBucket, creating an internal inconsistency.

  • [edge-case] workspaces/scorecard/plugins/scorecard/src/components/MetricGroupCard/thresholdBucketUtils.tscollectThresholdRules deduplicates rules by key, keeping only the first occurrence. If metrics within a group have different threshold definitions for the same key, the bucket's expression will reflect whichever metric appears first.

  • [dependency-coherence] workspaces/scorecard/plugins/scorecard/package.json@mui/lab@5.0.0-alpha.177 is added as a production dependency. Only used for the Masonry component. While @mui/lab is widely used in the MUI ecosystem despite its alpha versioning, confirm this is acceptable per project standards.

  • [dependency-promotion] workspaces/scorecard/plugins/scorecard/package.json@backstage/ui moved from devDependencies to dependencies (correct since new components import from it at runtime). @mui/lab is a new transitive dependency for consumers.

@fullsend-ai-review fullsend-ai-review Bot added the requires-manual-review Review requires human judgment label Jul 2, 2026
@Eswaraiahsapram Eswaraiahsapram changed the title feat(scorecard): add MetricGroupCard with threshold buckets and data … feat(scorecard): add MetricGroupCard with threshold buckets and data sources dialog Jul 6, 2026
@Eswaraiahsapram Eswaraiahsapram force-pushed the scorecard-group-metrics-ui-RHIDP-14897 branch from 8c6e821 to ed5a9ce Compare July 6, 2026 06:13
@fullsend-ai-review

fullsend-ai-review Bot commented Jul 6, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 6:15 AM UTC · Completed 6:25 AM UTC
Commit: 12b0c0b · View workflow run →

@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 Jul 6, 2026
@ShiranHi

ShiranHi commented Jul 6, 2026

Copy link
Copy Markdown

@Eswaraiahsapram Looks great! Just one small comment regarding the footer: when the list gets too long, we need the text to wrap to a new line so it doesn't scroll beneath the "Close" button. Can we update that behavior?

@Eswaraiahsapram

Copy link
Copy Markdown
Member Author

@Eswaraiahsapram Looks great! Just one small comment regarding the footer: when the list gets too long, we need the text to wrap to a new line so it doesn't scroll beneath the "Close" button. Can we update that behavior?

Thanks for the feedback @ShiranHi , I initially added horizontal scrolling for the threshold legend so users could scroll through and select/filter rules. But I agree wrapping to a new line would make it much easier for users to see all the rules at a glance and navigate/filter without needing to scroll. I'll update the legend to wrap instead.

@fullsend-ai-review

fullsend-ai-review Bot commented Jul 6, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 11:22 AM UTC · Completed 11:26 AM UTC
Commit: 12b0c0b · View workflow run →

@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 Jul 6, 2026

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

/lgtm

Looks good to me. Tested locally.

Screen.Recording.2026-07-07.at.4.51.11.PM.mov

@openshift-ci openshift-ci Bot removed the lgtm label Jul 7, 2026
@teknaS47

teknaS47 commented Jul 7, 2026

Copy link
Copy Markdown
Member

/lgtm

@openshift-ci openshift-ci Bot added the lgtm label Jul 7, 2026
@sonarqubecloud

sonarqubecloud Bot commented Jul 7, 2026

Copy link
Copy Markdown

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants