Skip to content

feat(scorecard): implement blueprint architecture for grouped layouts#3282

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

feat(scorecard): implement blueprint architecture for grouped layouts#3282
Eswaraiahsapram wants to merge 4 commits into
redhat-developer:mainfrom
Eswaraiahsapram:group-metrics-RHIDP-13955

Conversation

@Eswaraiahsapram
Copy link
Copy Markdown
Member

@Eswaraiahsapram Eswaraiahsapram commented Jun 3, 2026

Hey, I just made a Pull Request!

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

Description:

Add blueprint pattern for scorecard metric grouping with dynamic layout extensions discoverable through Backstage NFS.

Changes:

  • Create ScorecardEntityContentLayoutBlueprint with groups config schema
  • Add Grid and List layout extension instances via .make()
  • Update entity tab to declare layouts input and render ScorecardLayoutSwitcher
  • Implement draft ScoreEntityContentGridView component
  • Add ScorecardLayoutSwitcher for toggling between enabled layouts
  • Bundle layout extensions in scorecardCatalogModule for auto-discovery
  • Support enable/disable of individual layouts via app-config.yaml

Screenshot

Screenshot 2026-06-03 at 5 11 41 PM

How to Test

  1. cd workspaces/scorecard
  2. yarn install
  3. yarn start
  4. Navigate to an entity with scorecard metrics (Component with SonarQube, GitHub, filecheck data).
Config to Enable Both Layouts
app:
  extensions:
    # Scorecard tab: entity shows tab if it matches any filter below.
    - entity-content:catalog/entity-content-scorecard:
        config:
          allowedFilters:
            - kind: component # e.g. Component with spec.type: website
              type: website
            - kind: api # e.g. any API entity
            - type: service # e.g. Component or System with spec.type: service
            
    # Grid layout – enabled with metric grouping
    - scorecard-layout:catalog/scorecard-entity-layout-grid:
        config:
          groups:
            code-quality:
              title: Code Quality
              description: Code quality and coverage metrics from SonarQube
              metrics:
                - sonarqube.quality_gate
                - sonarqube.code_coverage
                - sonarqube.code_duplications
                - sonarqube.maintainability_issues
                - sonarqube.security_hotspots
                - sonarqube.reliability_rating
                - sonarqube.reliability_issues

            repository-standards:
              title: Repository Standards
              description: Essential repository files and documentation
              metrics:
                - filecheck.license
                - filecheck.codeowners
                - filecheck.readme
                - filecheck.contributing

            security:
              title: Security Compliance
              description: Security scans and vulnerability checks
              metrics:
                - dependabot.vulnerabilities
                - ossf.scorecard
                - snyk.vulnerabilities

    # List layout – disabled
    - scorecard-layout:catalog/scorecard-entity-layout-list:
        config:
          groups:
            code-quality:
              title: Code Quality
              description: Code quality and coverage metrics from SonarQube
              metrics:
                - sonarqube.quality_gate
                - sonarqube.code_coverage
                - sonarqube.code_duplications
                - sonarqube.maintainability_issues
                - sonarqube.security_hotspots
                - sonarqube.reliability_rating
                - sonarqube.reliability_issues

            repository-standards:
              title: Repository Standards
              description: Essential repository files and documentation
              metrics:
                - filecheck.license
                - filecheck.codeowners
                - filecheck.readme
                - filecheck.contributing

            security:
              title: Security Compliance
              description: Security scans and vulnerability checks
              metrics:
                - dependabot.vulnerabilities
                - ossf.scorecard
                - snyk.vulnerabilities

✔️ 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
Copy link
Copy Markdown

rhdh-gh-app Bot commented Jun 3, 2026

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 major v2.7.7

@fullsend-ai-review
Copy link
Copy Markdown

fullsend-ai-review Bot commented Jun 3, 2026

Review

Findings

Low

  • [edge-case] workspaces/scorecard/plugins/scorecard/src/components/Scorecard/ScorecardLayoutSwitcher.tsx:27 — The fallback layouts[selectedIndex] ?? layouts[0] masks a potential stale-index state when selectedIndex exceeds layouts.length. In practice, layouts is resolved statically at framework boot time and does not change between renders, making this a theoretical concern. However, adding a useEffect to clamp selectedIndex when it falls out of bounds would make the component more robust for future use cases where layouts might be dynamically toggled.

  • [test-inadequate] workspaces/scorecard/plugins/scorecard/src/alpha/blueprints/ScorecardLayoutBlueprint.test.tsx — Tests use two different loader patterns: async () => () => <MockLayout /> (wrapping component, ignoring groups prop) vs async () => MockLayout (direct component, receiving groups). The production code uses the direct pattern, so this doesn't mask a real bug, but standardizing test loaders would prevent confusion and improve test reliability.

  • [error-handling] workspaces/scorecard/plugins/scorecard/src/components/Scorecard/ScorecardLayoutSwitcher.tsx:50 — Toggle buttons use layout.title as the React key. If two layouts shared the same title, React would produce duplicate key warnings and potentially incorrect reconciliation. Currently mitigated by hardcoded titles ('Grid', 'List').

Info

  • [sub-agent-failure] N/A — The style-conventions, intent-coherence, and docs-currency sub-agents could not complete due to model availability issues on this deployment. These dimensions were not evaluated in this review cycle.
Previous run

Review

Findings

High

  • [missing-test] workspaces/scorecard/plugins/scorecard/src/components/Scorecard/ScoreEntityContentGridView.tsx — This PR introduces a major new feature (blueprint architecture, grid view, layout switcher, blueprint factory) with zero test files. The existing codebase has thorough test coverage for comparable components (EntityScorecardContent.test.tsx, Scorecard.test.tsx, CustomLegend.test.tsx). ScoreEntityContentGridView contains substantial logic (metric grouping, status color resolution, ungrouped-metric detection, multiple rendering branches) that is untested. ScorecardLayoutSwitcher has state management and edge-case behavior that also lacks tests.
    Remediation: Add tests for (1) ScoreEntityContentGridView covering grouping logic, ungrouped metric fallback, error/loading/empty states, and permission error; (2) ScorecardLayoutSwitcher covering layout switching and single-layout behavior; (3) ScorecardLayoutBlueprint verifying the factory yields expected extension data.

  • [type-safety] workspaces/scorecard/plugins/scorecard/src/alpha/extensions/entityTab.tsx(inputs as any).layouts bypasses all TypeScript type checking. No other file in src/alpha/ uses as any. The subsequent .map((l: any) => ...) also uses any, meaning l.get() calls have no compile-time verification. The root cause is that the blueprint omits the dataRefs field (both existing blueprints in this repo — GlobalHeaderComponentBlueprint, AppDrawerContentBlueprint — declare dataRefs), which would enable type-safe access to extension data.
    Remediation: Add a dataRefs field to ScorecardEntityContentLayoutBlueprint and type the inputs parameter properly. If the upstream Blueprint API genuinely lacks the types, add focused runtime guards and a TODO comment explaining the gap.

  • [incomplete-work] workspaces/scorecard/plugins/scorecard/src/components/Scorecard/ScorecardEntityListView.tsxScorecardEntityListView is a non-functional stub (return <div>ScorecardEntityListView</div>) registered as a first-class layout extension via the blueprint and wired into scorecardCatalogModule. It will be discoverable and selectable by platform engineers. Shipping a placeholder as a registered extension in a versioned release creates a confusing UX if toggled on.
    Remediation: Either remove the list layout extension and add it in a follow-up PR when implemented, or gate it behind an explicit experimental flag.

  • [naming-convention] workspaces/scorecard/plugins/scorecard/src/components/Scorecard/ScoreEntityContentGridView.tsx — The component uses the prefix Score instead of Scorecard. Every other public component in the codebase uses the Scorecard prefix (ScorecardPage, ScorecardIcon, ScorecardHomepageCard, EntityScorecardContent). Additionally, the naming pattern is asymmetric with its sibling: ScoreEntityContentGridView vs ScorecardEntityListView (extra Content word in grid).
    Remediation: Rename to a consistent pattern, e.g., ScorecardEntityGridView / ScorecardEntityListView.

Medium

  • [behavioral-contract-change] workspaces/scorecard/plugins/scorecard/src/alpha/index.tsx:68 — scorecardCatalogModule now registers two additional extensions (scorecardEntityLayoutGrid, scorecardEntityLayoutList). Because the entity tab checks layouts.length > 0 to decide whether to render ScorecardLayoutSwitcher or the legacy EntityScorecardContent, existing consumers who import scorecardCatalogModule will get a different UI without any code change. This is a behavioral breaking change for the @alpha export.
    Remediation: Ship layout extensions in a separate opt-in module (e.g., scorecardLayoutsModule) so existing consumers retain the legacy view by default, or ensure layout extensions are disabled by default and must be explicitly enabled in app-config.yaml.

  • [stale-doc] workspaces/scorecard/plugins/scorecard/README.md:225 — The Modules table describes scorecardCatalogModule as only "Registers the Scorecard entity tab" — after this PR it also registers grid and list layout extensions. The Extensions list (lines 229-238) omits both new scorecard-layout extensions. The NFS configuration section documents allowedFilters but not the new groups config schema for layout extensions.
    Remediation: Update the module description, add extension entries, and document the groups config schema with examples.

  • [hardcoded-colors] workspaces/scorecard/plugins/scorecard/src/components/Scorecard/ScoreEntityContentGridView.tsx:48 — statusTileBg and statusTileText maps use hardcoded hex color literals (#e8f5e9, #2e7d32, etc.). The rest of the codebase resolves status colors through the theme via resolveStatusColor(). The component already imports useTheme and calls resolveStatusColor for the donut color but bypasses it for tile backgrounds and text. These values will not adapt to dark mode or custom themes.
    Remediation: Derive colors from the MUI theme palette or use alpha(resolvedColor, 0.12) from @mui/material/styles for background tints.

  • [edge-case] workspaces/scorecard/plugins/scorecard/src/components/Scorecard/ScoreEntityContentGridView.tsx — If the same metric ID appears in multiple groups (which the config schema does not prevent), the metric will be rendered in multiple group cards. This is likely unintended duplication that could confuse users.
    Remediation: Either validate at config parse time that metric IDs are unique across groups, or assign each metric to only the first matching group.

  • [stale-doc] workspaces/scorecard/plugins/scorecard/report-alpha.api.md — The auto-generated API report does not include the new alpha exports (ScorecardEntityContentLayoutBlueprint, scorecardLayoutTitleDataRef, ScorecardLayoutProps). Needs regeneration.
    Remediation: Run the API extractor to regenerate report-alpha.api.md.

Low

  • [pattern-inconsistency] workspaces/scorecard/plugins/scorecard/src/components/Scorecard/index.ts:18 — The original barrel file uses import-then-re-export pattern; the three new lines use inline re-exports. Mixing styles in one file is inconsistent.

  • [i18n-gap] workspaces/scorecard/plugins/scorecard/src/components/Scorecard/ScoreEntityContentGridView.tsx:176 — Hardcoded English strings ('No metric data available yet', metric/metrics pluralization) bypass the useTranslation hook used elsewhere in the codebase.

Info

  • [incomplete-checklist] All PR checklist items are unchecked (changeset review, documentation, tests, screenshots). The do-not-merge/hold label is appropriately applied.
Previous run (2)

Review

Reason: stale-head

The review agent reviewed commit 204a6f50c836ff85ca3db2a5ba879aea7df84f1a but the PR HEAD is now d1c2822915ed8399970ff75bbd8b200d0f7db58c. This review was discarded to avoid approving unreviewed code.

Copy link
Copy Markdown

@fullsend-ai-review fullsend-ai-review Bot left a comment

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.

* extensions (grid + list) into the Catalog entity pages.
*
* The plugin ships both layouts; platform engineers enable/disable individual
* layouts via app-config.yaml. No separate module registration is needed
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] behavioral-contract-change

scorecardCatalogModule now bundles layout extensions that change the default UI for existing consumers. When layouts are registered, the entity tab renders ScorecardLayoutSwitcher instead of the legacy EntityScorecardContent.

Suggested fix: Ship layout extensions in a separate opt-in module or ensure they are disabled by default.

success: '#e8f5e9',
warning: '#fff3e0',
error: '#fce4ec',
};
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] hardcoded-colors

statusTileBg and statusTileText use hardcoded hex colors that will not adapt to dark mode or custom themes. Rest of codebase uses resolveStatusColor() through the theme.

Suggested fix: Derive colors from MUI theme palette or use alpha(resolvedColor, 0.12) for background tints.

@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud Bot commented Jun 3, 2026

@codecov
Copy link
Copy Markdown

codecov Bot commented Jun 3, 2026

Codecov Report

❌ Patch coverage is 29.29293% with 70 lines in your changes missing coverage. Please review.
✅ Project coverage is 53.59%. Comparing base (d49a228) to head (58c014f).
⚠️ Report is 3 commits behind head on main.
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #3282      +/-   ##
==========================================
- Coverage   53.62%   53.59%   -0.03%     
==========================================
  Files        2409     2414       +5     
  Lines       86633    86731      +98     
  Branches    23997    24031      +34     
==========================================
+ Hits        46457    46486      +29     
- Misses      39894    39963      +69     
  Partials      282      282              
Flag Coverage Δ *Carryforward flag
adoption-insights 83.58% <ø> (ø) Carriedforward from d49a228
ai-integrations 70.03% <ø> (ø) Carriedforward from d49a228
app-defaults 69.60% <ø> (ø) Carriedforward from d49a228
augment 46.39% <ø> (ø) Carriedforward from d49a228
bulk-import 72.86% <ø> (ø) Carriedforward from d49a228
cost-management 16.49% <ø> (ø) Carriedforward from d49a228
dcm 45.40% <ø> (ø) Carriedforward from d49a228
extensions 61.79% <ø> (ø) Carriedforward from d49a228
global-floating-action-button 74.30% <ø> (ø) Carriedforward from d49a228
global-header 61.63% <ø> (ø) Carriedforward from d49a228
homepage 51.60% <ø> (ø) Carriedforward from d49a228
konflux 91.01% <ø> (ø) Carriedforward from d49a228
lightspeed 68.50% <ø> (ø) Carriedforward from d49a228
mcp-integrations 85.46% <ø> (ø) Carriedforward from d49a228
orchestrator 37.34% <ø> (ø) Carriedforward from d49a228
quickstart 62.88% <ø> (ø) Carriedforward from d49a228
sandbox 79.56% <ø> (ø) Carriedforward from d49a228
scorecard 82.28% <29.29%> (-1.57%) ⬇️
theme 64.54% <ø> (ø) Carriedforward from d49a228
translations 8.49% <ø> (ø) Carriedforward from d49a228
x2a 78.79% <ø> (ø) Carriedforward from d49a228

*This pull request uses carry forward flags. Click here to find out more.


Continue to review full report in Codecov by Sentry.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update d49a228...58c014f. 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.


/** @alpha */
export interface ScorecardLayoutItem {
title: string;
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

The fallback layouts[selectedIndex] ?? layouts[0] masks a potential stale-index state. In practice layouts are static extension inputs resolved at boot time, but adding a useEffect to clamp selectedIndex would make the component more robust.

Suggested fix: Add useEffect(() => { if (selectedIndex >= layouts.length) setSelectedIndex(0); }, [layouts.length, selectedIndex]);

return (
<Box>
{layouts.length > 1 && (
<Box display="flex" justifyContent="flex-end" mb={2}>
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[low] error-handling

Toggle buttons use layout.title as React key. Duplicate titles would cause reconciliation issues. Currently mitigated by hardcoded unique titles.

@fullsend-ai-review fullsend-ai-review Bot added the ready-for-merge All reviewers approved — ready to merge label Jun 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready-for-merge All reviewers approved — ready to merge workspace/scorecard

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants