Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions workspaces/scorecard/.changeset/modern-eggs-sink.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@red-hat-developer-hub/backstage-plugin-scorecard': minor
---

Adding scorecardHompage and metric page extension, also added e2e support in nfs
2 changes: 1 addition & 1 deletion workspaces/scorecard/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,4 @@ site
*.session.sql

# E2E test reports
e2e-test-report/
e2e-test-report*/
29 changes: 28 additions & 1 deletion workspaces/scorecard/app-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,13 @@ app:
extensions:
- api:app/app-language:
config:
availableLanguages: ['en', 'de', 'fr']
availableLanguages: ['en', 'de', 'fr', 'it', 'es', 'ja']
defaultLanguage: 'en'
- api:home/visits: true
- app-root-element:home/visit-listener: true
- page:home:
config:
path: /

# Scorecard tab: entity shows tab if it matches any filter below.
- entity-content:catalog/entity-content-scorecard:
Expand All @@ -16,6 +21,28 @@ app:
type: website
- kind: api # e.g. any API entity
- type: service # e.g. Component or System with spec.type: service
- home-page-layout:home/dynamic-homepage-layout:
config:
customizable: true
widgetLayout:
ScorecardJiraHomepage:
priority: 240
breakpoints:
xl: { w: 4, h: 6 }
lg: { w: 4, h: 6 }
md: { w: 4, h: 6 }
sm: { w: 4, h: 6 }
xs: { w: 4, h: 6 }
xxs: { w: 4, h: 6 }
ScorecardGithubHomepage:
priority: 250
breakpoints:
xl: { w: 4, h: 6, x: 4 }
lg: { w: 4, h: 6, x: 4 }
md: { w: 4, h: 6, x: 4 }
sm: { w: 4, h: 6, x: 4 }
xs: { w: 4, h: 6, x: 4 }
xxs: { w: 4, h: 6, x: 4 }
organization:
name: My Company

Expand Down
4 changes: 4 additions & 0 deletions workspaces/scorecard/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@
"test": "NODE_OPTIONS='--experimental-vm-modules' backstage-cli repo test",
"test:all": "NODE_OPTIONS='--experimental-vm-modules' backstage-cli repo test --coverage",
"test:e2e": "playwright test",
"test:e2e:legacy": "APP_MODE=legacy playwright test",
"test:e2e:nfs": "APP_MODE=nfs playwright test",
"test:e2e:all": "yarn test:e2e:legacy && yarn test:e2e:nfs",
"playwright": "bash -c 'if [[ \"$*\" == \"test\" ]]; then yarn test:e2e:all; else npx playwright \"$@\"; fi' _",
"fix": "backstage-cli repo fix",
"lint": "backstage-cli repo lint --since origin/main",
"lint:all": "backstage-cli repo lint",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
} from '../utils/translationUtils';

type ThresholdState = 'success' | 'warning' | 'error';
const escapeRegex = (value: string) =>
value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');

Check warning on line 26 in workspaces/scorecard/packages/app-legacy/e2e-tests/pages/HomePage.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Prefer `String#replaceAll()` over `String#replace()`.

See more on https://sonarcloud.io/project/issues?id=redhat-developer_rhdh-plugins&issues=AZ06jNWSRnJvyhKa905H&open=AZ06jNWSRnJvyhKa905H&pullRequest=2637

Check warning on line 26 in workspaces/scorecard/packages/app-legacy/e2e-tests/pages/HomePage.ts

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

`String.raw` should be used to avoid escaping `\`.

See more on https://sonarcloud.io/project/issues?id=redhat-developer_rhdh-plugins&issues=AZ06jNWSRnJvyhKa905I&open=AZ06jNWSRnJvyhKa905I&pullRequest=2637

export class HomePage {
readonly page: Page;
Expand All @@ -48,7 +50,22 @@

async addCard(cardName: string) {
await this.page.getByRole('button', { name: 'Add widget' }).click();
await this.page.getByRole('button', { name: cardName }).click();
await expect(
this.page.getByRole('heading', { name: 'Add new widget to dashboard' }),
).toBeVisible();

let cardPattern: RegExp;
if (cardName === 'Onboarding section') {
cardPattern = /Onboarding section|RhdhOnboardingSection/i;
} else if (cardName === 'Scorecard: GitHub open PRs') {
cardPattern = /Scorecard:\s*GitHub open PRs|ScorecardGithubHomepage/i;
} else if (cardName === 'Scorecard: Jira open blocking') {
cardPattern = /Scorecard:\s*Jira open blocking|ScorecardJiraHomepage/i;
} else {
cardPattern = new RegExp(escapeRegex(cardName), 'i');
}

await this.page.getByRole('button', { name: cardPattern }).first().click();
}

async saveChanges() {
Expand All @@ -69,8 +86,9 @@

getCard(metricId: 'github.open_prs' | 'jira.open_issues'): Locator {
return this.page
.locator('article')
.filter({ hasText: this.translations.metric[metricId].title });
.getByText(this.translations.metric[metricId].title, { exact: true })
.last()
.locator('xpath=ancestor::article[1]');
}

async verifyThresholdTooltip(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,17 @@ test.describe('Scorecard Plugin Tests', () => {

test.describe('Entity Scorecards', () => {
test('Verify permission required state', async ({ browser }, testInfo) => {
await mockScorecardResponse(
page,
{
error: {
name: 'NotAllowedError',
message: 'Permission denied',
},
},
403,
);

await catalogPage.openCatalog();
await catalogPage.openComponent('Red Hat Developer Hub');
await page.getByText('Scorecard', { exact: true }).click();
Expand Down Expand Up @@ -193,7 +204,30 @@ test.describe('Scorecard Plugin Tests', () => {

test.describe('Aggregated Scorecards', () => {
test('Verify missing permission state', async () => {
await mockAggregatedScorecardResponse(
page,
{
error: {
name: 'NotAllowedError',
message: 'Permission denied',
},
},
{
error: {
name: 'NotAllowedError',
message: 'Permission denied',
},
},
403,
);

await homePage.navigateToHome();
await page.reload();
await homePage.enterEditMode();
await homePage.clearAllCards();
await homePage.addCard('Scorecard: GitHub open PRs');
await homePage.addCard('Scorecard: Jira open blocking');
await homePage.saveChanges();

const entityCount = getEntityCount(translations, currentLocale, '0');

Expand Down Expand Up @@ -277,7 +311,9 @@ test.describe('Scorecard Plugin Tests', () => {
),
);

await runAccessibilityTests(page, testInfo);
await runAccessibilityTests(page, testInfo, undefined, {
includeSelectors: ['[data-chart-container]'],
});
});

test('Verify cards aggregation data is not found when API returns empty aggregated response', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,20 @@ export async function runAccessibilityTests(
page: Page,
testInfo: TestInfo,
attachName = 'accessibility-scan-results.json',
options?: {
includeSelectors?: string[];
},
) {
const accessibilityScanResults = await new AxeBuilder({ page })
.withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa'])
.analyze();
let axeBuilder = new AxeBuilder({ page }).withTags([
'wcag2a',
'wcag2aa',
'wcag21a',
'wcag21aa',
]);
for (const selector of options?.includeSelectors ?? []) {
axeBuilder = axeBuilder.include(selector);
}
const accessibilityScanResults = await axeBuilder.analyze();

await testInfo.attach(attachName, {
body: JSON.stringify(accessibilityScanResults, null, 2),
Expand Down
2 changes: 1 addition & 1 deletion workspaces/scorecard/packages/app-legacy/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,8 @@ import {
DynamicCustomizableHomePage,
OnboardingSection,
HomePageCardMountPoint,
homepageTranslations,
} from '@red-hat-developer-hub/backstage-plugin-dynamic-home-page';
import { homepageTranslations } from '@red-hat-developer-hub/backstage-plugin-dynamic-home-page/alpha';
import { ComponentType } from 'react';

const mountPoints: HomePageCardMountPoint[] = [
Expand Down
2 changes: 2 additions & 0 deletions workspaces/scorecard/packages/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"@backstage/plugin-api-docs": "^0.13.1",
"@backstage/plugin-app-react": "^0.1.0",
"@backstage/plugin-catalog": "^1.32.0",
"@backstage/plugin-home": "^0.9.2",
"@backstage/plugin-org": "^0.6.46",
"@backstage/plugin-scaffolder": "^1.34.3",
"@backstage/plugin-search": "^1.5.0",
Expand All @@ -34,6 +35,7 @@
"@backstage/ui": "^0.9.1",
"@material-ui/core": "^4.12.2",
"@mui/icons-material": "^5.18.0",
"@red-hat-developer-hub/backstage-plugin-dynamic-home-page": "1.11.0",
"@red-hat-developer-hub/backstage-plugin-scorecard": "workspace:^",
"@red-hat-developer-hub/backstage-plugin-theme": "^0.13.0",
"react": "^18.0.2",
Expand Down
8 changes: 8 additions & 0 deletions workspaces/scorecard/packages/app/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ import { createApp } from '@backstage/frontend-defaults';

import { rhdhThemeModule } from '@red-hat-developer-hub/backstage-plugin-theme/alpha';
import {
homePageModule,
homepageTranslationsModule,
} from '@red-hat-developer-hub/backstage-plugin-dynamic-home-page/alpha';
import {
scorecardHomeModule,
scorecardTranslationsModule,
scorecardCatalogModule,
} from '@red-hat-developer-hub/backstage-plugin-scorecard/alpha';
Expand All @@ -30,6 +35,9 @@ import { navModule } from './modules/nav';
const app = createApp({
features: [
rhdhThemeModule,
homePageModule,
homepageTranslationsModule,
scorecardHomeModule,
scorecardCatalogModule,
scorecardTranslationsModule,
signInModule,
Comment on lines 35 to 43
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Action required

1. Scorecard api not wired 🐞 Bug ✓ Correctness

The scorecard NFS app registers Scorecard homepage widgets, but it does not register the Scorecard
frontend plugin that provides the ApiBlueprint for scorecardApiRef. As a result, any widget/page/tab
using useApi(scorecardApiRef) will fail at runtime due to a missing API implementation.
Agent Prompt
### Issue description
`workspaces/scorecard/packages/app/src/App.tsx` registers `scorecardHomeModule` (homepage widgets) and other scorecard modules, but it never registers the Scorecard frontend plugin default export. The Scorecard API implementation (`scorecardApiRef` -> `ScorecardApiClient`) is provided via an `ApiBlueprint` extension that is attached to the default frontend plugin; without adding that plugin to `createApp({ features: [...] })`, `useApi(scorecardApiRef)` calls will fail.

### Issue Context
- `useAggregatedScorecard` calls `useApi(scorecardApiRef)`.
- The API factory is created in `scorecardApi` and registered via the default Scorecard plugin’s `extensions`.

### Fix Focus Areas
- workspaces/scorecard/packages/app/src/App.tsx[17-45]

### Expected change
1. Import the default plugin export from `@red-hat-developer-hub/backstage-plugin-scorecard/alpha` (e.g., `import scorecardPlugin, { ... } from ...`).
2. Add `scorecardPlugin` to the `features` array (typically near other plugins), keeping the existing modules (`scorecardHomeModule`, `scorecardCatalogModule`, `scorecardTranslationsModule`).

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,19 @@ import {

type NavItem = { to: string; text: string; icon: IconComponent; title: string };

/** Main nav order: Catalog, My Groups, APIs, Docs, Create, RBAC */
/** Main nav order: Home, Catalog, My Groups, APIs, Docs, Create, RBAC */
const MAIN_NAV_ORDER: Array<{
match: (path: string) => boolean;
order: number;
}> = [
{
match: p => {
const [pathWithoutQuery] = p.split('?');
const [pathWithoutHash] = pathWithoutQuery.split('#');
return pathWithoutHash === '/' || pathWithoutHash === '/home';
},
order: 0,
},
{ match: p => p.includes('/catalog'), order: 1 },
{ match: p => p.includes('scorecard') || p.includes('my-groups'), order: 2 },
{ match: p => p.includes('/api-docs'), order: 3 },
Expand Down
10 changes: 7 additions & 3 deletions workspaces/scorecard/playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
import { defineConfig } from '@playwright/test';

const LOCALES = ['en', 'fr', 'it', 'ja', 'de', 'es'] as const;
const appMode = process.env.APP_MODE || 'legacy';
const startCommand = appMode === 'legacy' ? 'yarn start:legacy' : 'yarn start';

export default defineConfig({
timeout: 2 * 60 * 1000,
Expand All @@ -28,7 +30,7 @@ export default defineConfig({
webServer: process.env.PLAYWRIGHT_URL
? []
: {
command: 'yarn start:legacy',
command: startCommand,
port: 3000,
reuseExistingServer: true,
env: {
Expand All @@ -39,15 +41,17 @@ export default defineConfig({

retries: process.env.CI ? 2 : 0,

reporter: [['html', { open: 'never', outputFolder: 'e2e-test-report' }]],
reporter: [
['html', { open: 'never', outputFolder: `e2e-test-report-${appMode}` }],
],

use: {
baseURL: process.env.PLAYWRIGHT_URL ?? 'http://localhost:3000',
screenshot: 'only-on-failure',
trace: 'on-first-retry',
},

outputDir: 'node_modules/.cache/e2e-test-results',
outputDir: `node_modules/.cache/e2e-test-results-${appMode}`,

projects: LOCALES.map(locale => ({
name: locale,
Expand Down
Loading
Loading