Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export const AGGREGATED_CARDS_METRIC_IDS = {
githubOpenPrsKpi: 'openPrsKpi',
jiraOpenIssuesKpi: 'openIssuesKpi',
gitHubOpenPrsWeightedKpi: 'openPrsWeightedKpi',
licenseFileExistsKpi: 'licenseFileExistsKpi',
} as const;

/** Must match `title` in App.tsx homepage widget config (Add widget picker). */
Expand All @@ -29,6 +30,7 @@ export const AGGREGATED_CARDS_WIDGET_TITLES = {
githubOpenPrsKpi: 'Scorecard: GitHub open PRs',
jiraOpenIssuesKpi: 'Scorecard: Jira open blocking tickets',
gitHubOpenPrsWeightedKpi: 'Scorecard: GitHub open PRs (weighted health)',
licenseFileExistsKpi: 'Scorecard: LICENSE file exists',
} as const;

export const AGGREGATED_CARDS_METADATA = {
Expand Down Expand Up @@ -57,4 +59,9 @@ export const AGGREGATED_CARDS_METADATA = {
title: 'Scorecard: GitHub open PRs (weighted health)',
metricId: 'github.openPRs',
},
licenseFileExistsKpi: {
id: AGGREGATED_CARDS_METRIC_IDS.licenseFileExistsKpi,
title: 'Scorecard: LICENSE file exists',
metricId: 'filecheck.license',
},
} as const;
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,6 @@ export const ScorecardRoutes = {
'**/api/scorecard/aggregations/jira.openIssues',
GITHUB_OPEN_PRS_METRIC_AGGREGATION_ROUTE:
'**/api/scorecard/aggregations/github.openPRs',
LICENSE_FILE_EXISTS_KPI_AGGREGATION_ROUTE:
'**/api/scorecard/aggregations/licenseFileExistsKpi',
} as const;
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,11 @@ export class HomePage {
) {
cardPattern =
/Scorecard:\s*GitHub open PRs \(weighted health\)|ScorecardGitHubOpenPrsWeightedKpi/i;
} else if (
cardName === AGGREGATED_CARDS_WIDGET_TITLES.licenseFileExistsKpi
) {
cardPattern =
/Scorecard:\s*LICENSE file exists|scorecard-filecheck\.license/i;
} else {
cardPattern = new RegExp(escapeRegex(cardName), 'i');
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import {
getSomeEntitiesNotReportingTooltip,
} from '../utils/translationUtils';

Comment thread
imykhno marked this conversation as resolved.
type MetricId = 'github.openPRs' | 'jira.openIssues';
type MetricId = 'github.openPRs' | 'jira.openIssues' | 'filecheck.license';
Comment thread
imykhno marked this conversation as resolved.

export type DrillDownCardLocatorOptions = {
aggregationId?: string;
Expand Down Expand Up @@ -167,19 +167,30 @@ export class ScorecardDrillDownPage {
* When mocks report no calculation failures, the drill-down must not show the
* calculation-warning icon next to the Entities heading.
*/
async expectNoDrillDownCalculationErrorWarningIcon() {
const heading = this.page.getByRole('heading', {
level: 3,
name: this.translations.entitiesPage.entitiesTable.title,
private getEntitiesTableHeading(): Locator {
const titlePrefix = this.translations.entitiesPage.entitiesTable.title;
return this.page.getByRole('heading', { level: 3 }).filter({
hasText: titlePrefix,
});
await expect(heading.locator('svg.MuiSvgIcon-colorWarning')).toHaveCount(0);
}

/** Verifies the "some entities not reporting" icon tooltip on the drill-down card. */
async verifySomeEntitiesNotReportingTooltip() {
const icon = this.page.getByTestId('ReportProblemOutlinedIcon');
await expect(icon).toBeVisible();
await icon.hover();
private getEntitiesTableCalculationWarningIcon(): Locator {
return this.getEntitiesTableHeading().getByTestId(
'entities-table-calculation-warning-icon',
);
}

async expectNoDrillDownCalculationErrorWarningIcon() {
await expect(this.getEntitiesTableCalculationWarningIcon()).toHaveCount(0);
}

Comment thread
imykhno marked this conversation as resolved.
async expectDrillDownCalculationErrorWarningIcon() {
await expect(this.getEntitiesTableCalculationWarningIcon()).toBeVisible();
}

/** Verifies the calculation-error tooltip on the Entities table heading icon. */
async verifyEntitiesTableCalculationErrorTooltip() {
await this.getEntitiesTableCalculationWarningIcon().hover();
const tooltipText = getSomeEntitiesNotReportingTooltip(this.translations);
Comment thread
imykhno marked this conversation as resolved.
await expect(this.page.getByRole('tooltip')).toContainText(tooltipText);
}
Expand Down Expand Up @@ -220,6 +231,25 @@ export class ScorecardDrillDownPage {
}
}
Comment thread
imykhno marked this conversation as resolved.

private getEntityLink(entitySlug: string): Locator {
const slug = encodeURIComponent(entitySlug);
return this.getEntitiesTable()
.locator('tbody')
.locator(`a[href*="/catalog/default/component/${slug}"]`)
.first();
}

async clickEntityLink(entitySlug: string) {
await this.getEntityLink(entitySlug).click();
}

async expectOnEntityPage(entitySlug: string) {
const slug = encodeURIComponent(entitySlug);
await expect(this.page).toHaveURL(
new RegExp(`/catalog/default/component/${escapeRegex(slug)}`),
);
}

async verifyMetricColumnSort() {
const tableHeaders = getEntitiesTableHeaderLabels(this.translations);
const entitiesTable = this.getEntitiesTable();
Expand Down
119 changes: 119 additions & 0 deletions workspaces/scorecard/packages/app-legacy/e2e-tests/scorecard.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ import {
githubCustomAggregatedResponse,
gitHubPartiallyAggregatedResponse,
gitHubWeightedPartiallyAggregatedResponse,
licenseFileExistsAggregatedResponse,
githubEntitiesDrillDownWithCalculationErrorsResponse,
weightedKpiEntitiesDrillDownResponse,
} from './utils/scorecardResponseUtils';
import {
ScorecardMessages,
Expand Down Expand Up @@ -471,6 +474,52 @@ test.describe('Scorecard Plugin Tests', () => {
}
});

test.describe('Filecheck homepage KPI - licenseFileExistsKpi', () => {
let card: Locator;
const aggregationMetadata =
AGGREGATED_CARDS_METADATA.licenseFileExistsKpi;
const aggregatedResponse = licenseFileExistsAggregatedResponse;

test.beforeAll(async () => {
await setupHomepageAggregationCard(page, homePage, {
aggregationMetadata,
route: ScorecardRoutes.LICENSE_FILE_EXISTS_KPI_AGGREGATION_ROUTE,
response: aggregatedResponse,
});
card = homePage.getCard(aggregationMetadata.id);
});

test('Verify title and description', async () => {
await expect(card).toBeVisible();
await expect(card).toContainText(aggregatedResponse.metadata.title);
await expect(card).toContainText(
aggregatedResponse.metadata.description,
);
});

test('Verify exist and missing threshold buckets', async ({}, testInfo) => {
const existLabel = translations.thresholds.exist ?? 'Exist';
const missingLabel = translations.thresholds.missing ?? 'Missing';
await expect(card.getByText(existLabel, { exact: true })).toBeVisible();
await expect(
card.getByText(missingLabel, { exact: true }),
).toBeVisible();

await runAccessibilityTests(page, testInfo);
});

test('Verify drill-down link', async () => {
await homePage.clickDrillDownLink(card);
await scorecardDrillDownPage.expectOnPage('filecheck.license', {
aggregationId: aggregationMetadata.id,
});
await scorecardDrillDownPage.expectPageTitle(
'filecheck.license',
aggregatedResponse.metadata.title,
);
});
});

test.describe('Deprecated homepage card (metricId only)', () => {
let card: Locator;
const aggregationMetadata =
Expand Down Expand Up @@ -789,6 +838,26 @@ test.describe('Scorecard Plugin Tests', () => {
openPrsWeightedAggregatedResponse.metadata.title,
);
});

test('Verify drill-down entities table', async () => {
await mockScorecardEntitiesDrillDown(
page,
weightedKpiEntitiesDrillDownResponse,
'github.openPRs',
);

await page.goto(
`/scorecard/aggregations/${aggregationMetadata.id}/metrics/github.openPRs`,
);

await scorecardDrillDownPage.expectOnPage('github.openPRs', {
aggregationId: aggregationMetadata.id,
});
await scorecardDrillDownPage.expectTableHeadersVisible();
await scorecardDrillDownPage.expectEntityNamesVisible([
'red-hat-developer-hub',
]);
});
});

test('Verify empty aggregated response shows no data', async () => {
Expand Down Expand Up @@ -953,6 +1022,56 @@ test.describe('Scorecard Plugin Tests', () => {
await test.step('Verify metric column sort', async () => {
await scorecardDrillDownPage.verifyMetricColumnSort();
});

await test.step('Navigate to entity from drill-down table', async () => {
await scorecardDrillDownPage.clickEntityLink('red-hat-developer-hub');
await scorecardDrillDownPage.expectOnEntityPage(
'red-hat-developer-hub',
);
});
});

test('GitHub drill-down: calculation errors show warning icon and tooltip', async () => {
const aggregationMetadata =
AGGREGATED_CARDS_METADATA.githubDefaultAggregation;
const partialAggregationResponse = {
...githubAggregatedResponse,
result: {
...githubAggregatedResponse.result,
calculationErrorCount: 2,
},
};

await mockApiResponse(
page,
ScorecardRoutes.GITHUB_OPEN_PRS_METRIC_AGGREGATION_ROUTE,
partialAggregationResponse,
);
await mockScorecardEntitiesDrillDown(
page,
githubEntitiesDrillDownWithCalculationErrorsResponse,
'github.openPRs',
);

const entitiesResponse = page.waitForResponse(
res =>
res
.url()
.includes(
'/api/scorecard/metrics/github.openPRs/catalog/aggregations/entities',
) && res.status() === 200,
);

await page.goto(
`/scorecard/aggregations/${aggregationMetadata.id}/metrics/github.openPRs`,
);
await entitiesResponse;

await scorecardDrillDownPage.expectOnPage('github.openPRs', {
aggregationId: aggregationMetadata.id,
});
await scorecardDrillDownPage.expectDrillDownCalculationErrorWarningIcon();
await scorecardDrillDownPage.verifyEntitiesTableCalculationErrorTooltip();
});

test('Jira scorecard: tooltips, entity drill-down, and metric sort', async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,14 @@ import {
import { mockApiResponse, waitForAggregationResponse } from './apiUtils';
import { mockAggregationNoDataFound } from './mockHomepageAggregations';

type AggregatedCardWidgetKey = keyof typeof AGGREGATED_CARDS_METRIC_IDS;

function isAggregatedCardWidgetKey(
key: string,
): key is AggregatedCardWidgetKey {
return key in AGGREGATED_CARDS_WIDGET_TITLES;
}

type SetupHomepageAggregationCardOptions = {
aggregationMetadata: { id: string; title: string };
route: string;
Expand All @@ -38,12 +46,20 @@ async function addWidget(homePage: HomePage, widgetTitle: string) {
await homePage.saveChanges();
}

export async function addAggregatedScorecardWidgets(homePage: HomePage) {
export async function addAggregatedScorecardWidgets(
homePage: HomePage,
widgetIds: Partial<
typeof AGGREGATED_CARDS_METRIC_IDS
> = AGGREGATED_CARDS_METRIC_IDS,
) {
await homePage.navigateToHome();
await homePage.enterEditMode();
await homePage.clearAllCards();
Comment thread
imykhno marked this conversation as resolved.

for (const instanceId of Object.keys(AGGREGATED_CARDS_METRIC_IDS)) {
for (const instanceId of Object.keys(widgetIds)) {
if (!isAggregatedCardWidgetKey(instanceId)) {
throw new Error(`Unknown homepage scorecard widget id: ${instanceId}`);
}
await homePage.addCard(AGGREGATED_CARDS_WIDGET_TITLES[instanceId]);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,11 @@ import { ScorecardRoutes } from '../constants/routes';
import {
emptyGithubAggregatedResponse,
emptyJiraAggregatedResponse,
emptyLicenseFileExistsAggregatedResponse,
emptyOpenPrsWeightedAggregatedResponse,
githubAggregatedResponse,
jiraAggregatedResponse,
licenseFileExistsKpiMetadataResponse,
notAllowedAggregationErrorBody,
openIssuesKpiMetadataResponse,
openPrsKpiMetadataResponse,
Expand All @@ -41,6 +43,9 @@ function aggregationMetadataForRequestUrl(url: string): object {
if (url.includes('openPrsWeightedKpi')) {
return openPrsWeightedKpiMetadataResponse;
}
if (url.includes('licenseFileExistsKpi')) {
return licenseFileExistsKpiMetadataResponse;
}
if (url.includes('jira.openIssues')) {
return jiraAggregatedResponse.metadata;
}
Expand Down Expand Up @@ -80,7 +85,7 @@ export async function mockAggregationNoDataFound(page: Page): Promise<void> {
await page.route('**/api/scorecard/aggregations/**', async route => {
const url = route.request().url();

if (url.includes('metadata')) {
if (url.includes('/metadata')) {
Comment thread
imykhno marked this conversation as resolved.
await route.fulfill({
status: 200,
contentType: 'application/json',
Expand All @@ -98,6 +103,15 @@ export async function mockAggregationNoDataFound(page: Page): Promise<void> {
return;
}

if (url.includes(AGGREGATED_CARDS_METRIC_IDS.licenseFileExistsKpi)) {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify(emptyLicenseFileExistsAggregatedResponse),
});
return;
}

const githubAggregations = [
AGGREGATED_CARDS_METRIC_IDS.githubOpenPrsKpi,
AGGREGATED_CARDS_METRIC_IDS.githubMetricId,
Expand Down
Loading
Loading