Browse Source

SONAR-21808 Add the failed badge on software quality cards

tags/10.5.0.89998
Mathieu Suen 1 month ago
parent
commit
a43209f6b8

+ 3
- 4
server/sonar-web/design-system/src/components/Badge.tsx View File

@@ -32,8 +32,7 @@ const variantList: Record<BadgeVariant, ThemeColors> = {
counterFailed: 'badgeCounterFailed',
};

interface BadgeProps {
children: string | number;
interface BadgeProps extends React.PropsWithChildren {
className?: string;
title?: string;
variant?: BadgeVariant;
@@ -41,9 +40,9 @@ interface BadgeProps {

export function Badge({ className, children, title, variant = 'default' }: BadgeProps) {
const commonProps = {
'aria-label': title ?? children.toString(),
'aria-label': title,
className,
role: 'status',
role: title ? 'img' : 'presentation',
title,
};


+ 7
- 3
server/sonar-web/design-system/src/components/__tests__/Badge-test.tsx View File

@@ -23,10 +23,14 @@ import { Badge } from '../Badge';

it('renders badge correctly', () => {
render(<Badge>foo</Badge>);
expect(screen.getByRole('status')).toBeInTheDocument();
expect(screen.getByText('foo')).toBeInTheDocument();
});

it('renders counter correctly', () => {
render(<Badge variant="counter">23</Badge>);
expect(screen.getByRole('status')).toHaveAttribute('aria-label', '23');
render(
<Badge title="This 23" variant="counter">
23
</Badge>,
);
expect(screen.getByRole('img')).toHaveAccessibleName('This 23');
});

+ 3
- 0
server/sonar-web/src/main/js/apps/overview/branches/OverallCodeMeasuresPanel.tsx View File

@@ -70,6 +70,7 @@ export default function OverallCodeMeasuresPanel(props: Readonly<OverallCodeMeas
<SoftwareImpactMeasureCard
branch={branch}
component={component}
conditions={conditions}
softwareQuality={SoftwareQuality.Security}
ratingMetricKey={MetricKey.security_rating}
measures={measures}
@@ -77,6 +78,7 @@ export default function OverallCodeMeasuresPanel(props: Readonly<OverallCodeMeas
<SoftwareImpactMeasureCard
branch={branch}
component={component}
conditions={conditions}
softwareQuality={SoftwareQuality.Reliability}
ratingMetricKey={MetricKey.reliability_rating}
measures={measures}
@@ -84,6 +86,7 @@ export default function OverallCodeMeasuresPanel(props: Readonly<OverallCodeMeas
<SoftwareImpactMeasureCard
branch={branch}
component={component}
conditions={conditions}
softwareQuality={SoftwareQuality.Maintainability}
ratingMetricKey={MetricKey.sqale_rating}
measures={measures}

+ 16
- 5
server/sonar-web/src/main/js/apps/overview/branches/SoftwareImpactMeasureCard.tsx View File

@@ -20,9 +20,9 @@
import styled from '@emotion/styled';
import { LinkHighlight, LinkStandalone } from '@sonarsource/echoes-react';
import classNames from 'classnames';
import { BasicSeparator, LightGreyCard, TextBold, TextSubdued } from 'design-system';
import { Badge, BasicSeparator, LightGreyCard, TextBold, TextSubdued } from 'design-system';
import * as React from 'react';
import { useIntl } from 'react-intl';
import { FormattedMessage, useIntl } from 'react-intl';
import Tooltip from '../../../components/controls/Tooltip';
import { DEFAULT_ISSUES_QUERY } from '../../../components/shared/utils';
import {
@@ -39,14 +39,16 @@ import {
SoftwareQuality,
} from '../../../types/clean-code-taxonomy';
import { MetricKey, MetricType } from '../../../types/metrics';
import { QualityGateStatusConditionEnhanced } from '../../../types/quality-gates';
import { Component, MeasureEnhanced } from '../../../types/types';
import { OverviewDisabledLinkTooltip } from '../components/OverviewDisabledLinkTooltip';
import { softwareQualityToMeasure } from '../utils';
import { Status, softwareQualityToMeasure } from '../utils';
import SoftwareImpactMeasureBreakdownCard from './SoftwareImpactMeasureBreakdownCard';
import SoftwareImpactMeasureRating from './SoftwareImpactMeasureRating';

export interface SoftwareImpactBreakdownCardProps {
component: Component;
conditions: QualityGateStatusConditionEnhanced[];
softwareQuality: SoftwareQuality;
ratingMetricKey: MetricKey;
measures: MeasureEnhanced[];
@@ -54,7 +56,7 @@ export interface SoftwareImpactBreakdownCardProps {
}

export function SoftwareImpactMeasureCard(props: Readonly<SoftwareImpactBreakdownCardProps>) {
const { component, softwareQuality, ratingMetricKey, measures, branch } = props;
const { component, conditions, softwareQuality, ratingMetricKey, measures, branch } = props;

const intl = useIntl();

@@ -92,12 +94,21 @@ export function SoftwareImpactMeasureCard(props: Readonly<SoftwareImpactBreakdow
intl.formatMessage({ id: 'overview.measures.software_impact.count_tooltip' })
);

const failed = conditions.some((c) => c.level === Status.ERROR && c.metric === ratingMetricKey);

return (
<LightGreyCard
data-testid={`overview__software-impact-card-${softwareQuality}`}
className="sw-w-1/3 sw-overflow-hidden sw-rounded-2 sw-p-4 sw-flex-col"
>
<TextBold name={intl.formatMessage({ id: `software_quality.${softwareQuality}` })} />
<div className="sw-flex sw-justify-between">
<TextBold name={intl.formatMessage({ id: `software_quality.${softwareQuality}` })} />
{failed && (
<Badge className="sw-h-fit" variant="deleted">
<FormattedMessage id="overview.measures.failed_badge" />
</Badge>
)}
</div>
<BasicSeparator className="sw--mx-4" />
<div className="sw-flex sw-flex-col sw-gap-3">
<div className="sw-flex sw-mt-2">

+ 1
- 1
server/sonar-web/src/main/js/apps/overview/branches/__tests__/ActivityPanel-it.tsx View File

@@ -46,7 +46,7 @@ it('should render correctly', async () => {

expect(await screen.findAllByText('metric.level.ERROR')).toHaveLength(2);
expect(screen.getAllByText('metric.level.OK')).toHaveLength(2);
expect(screen.getByRole('status', { name: 'v1.0' })).toBeInTheDocument();
expect(screen.getByText('v1.0')).toBeInTheDocument();
expect(screen.getByText(/event.category.OTHER/)).toBeInTheDocument();
expect(screen.getByText(/event.category.DEFINITION_CHANGE/)).toBeInTheDocument();
expect(screen.getByText('event.sqUpgrade10.2')).toBeInTheDocument();

+ 17
- 0
server/sonar-web/src/main/js/apps/overview/branches/__tests__/BranchOverview-it.tsx View File

@@ -262,6 +262,21 @@ describe('project overview', () => {

// eslint-disable-next-line jest/expect-expect
it('should render software impact measure cards', async () => {
qualityGatesHandler.setQualityGateProjectStatus(
mockQualityGateProjectStatus({
status: 'ERROR',
conditions: [
{
actualValue: '2',
comparator: 'GT',
errorThreshold: '1',
metricKey: MetricKey.reliability_rating,
periodIndex: 1,
status: 'ERROR',
},
],
}),
);
const { user, ui } = getPageObjects();
renderBranchOverview();

@@ -288,6 +303,8 @@ describe('project overview', () => {
[SoftwareImpactSeverity.Low]: 1,
},
[false, true, false],
undefined,
true,
);
ui.expectSoftwareImpactMeasureCard(
SoftwareQuality.Maintainability,

+ 9
- 0
server/sonar-web/src/main/js/apps/overview/branches/test-utils.ts View File

@@ -40,7 +40,16 @@ export const getPageObjects = () => {
data?: SoftwareImpactMeasureData,
severitiesActiveState?: boolean[],
branch = 'master',
failed = false,
) => {
if (failed) {
expect(
byTestId(`overview__software-impact-card-${softwareQuality}`)
.byText('overview.measures.failed_badge')
.get(),
).toBeInTheDocument();
}

if (typeof rating === 'string') {
expect(
byText(rating, { exact: true }).get(ui.softwareImpactMeasureCard(softwareQuality).get()),

+ 14
- 14
server/sonar-web/src/main/js/apps/projects/components/project-card/__tests__/ProjectCard-test.tsx View File

@@ -65,7 +65,7 @@ it('should display tags', async () => {
it('should display private badge', () => {
const project: Project = { ...PROJECT, visibility: Visibility.Private };
renderProjectCard(project);
expect(screen.getByLabelText('visibility.private')).toBeInTheDocument();
expect(screen.getByText('visibility.private')).toBeInTheDocument();
});

it('should display configure analysis button for logged in user and scan rights', () => {
@@ -81,7 +81,7 @@ it('should not display configure analysis button for logged in user and without

it('should display applications', () => {
renderProjectCard({ ...PROJECT, qualifier: ComponentQualifier.Application });
expect(screen.getByLabelText('qualifier.APP')).toBeInTheDocument();
expect(screen.getAllByText('qualifier.APP')).toHaveLength(2);
});

it('should not display awaiting analysis badge and do not display old measures', () => {
@@ -97,7 +97,7 @@ it('should not display awaiting analysis badge and do not display old measures',
[MetricKey.vulnerabilities]: '6',
},
});
expect(screen.queryByRole('status', { name: 'projects.awaiting_scan' })).not.toBeInTheDocument();
expect(screen.queryByText('projects.awaiting_scan')).not.toBeInTheDocument();
expect(screen.getByText('1')).toBeInTheDocument();
expect(screen.getByText('2')).toBeInTheDocument();
expect(screen.getByText('3')).toBeInTheDocument();
@@ -116,10 +116,10 @@ it('should display awaiting analysis badge and show the old measures', async ()
[MetricKey.vulnerabilities]: '6',
},
});
expect(screen.getByRole('status', { name: 'projects.awaiting_scan' })).toBeInTheDocument();
await expect(
screen.getByRole('status', { name: 'projects.awaiting_scan' }),
).toHaveATooltipWithContent('projects.awaiting_scan.description.TRK');
expect(screen.getByText('projects.awaiting_scan')).toBeInTheDocument();
await expect(screen.getByText('projects.awaiting_scan')).toHaveATooltipWithContent(
'projects.awaiting_scan.description.TRK',
);
expect(screen.getByText('4')).toBeInTheDocument();
expect(screen.getByText('5')).toBeInTheDocument();
expect(screen.getByText('6')).toBeInTheDocument();
@@ -136,10 +136,10 @@ it('should display awaiting analysis badge and show the old measures for Applica
[MetricKey.vulnerabilities]: '6',
},
});
expect(screen.getByRole('status', { name: 'projects.awaiting_scan' })).toBeInTheDocument();
await expect(
screen.getByRole('status', { name: 'projects.awaiting_scan' }),
).toHaveATooltipWithContent('projects.awaiting_scan.description.APP');
expect(screen.getByText('projects.awaiting_scan')).toBeInTheDocument();
await expect(screen.getByText('projects.awaiting_scan')).toHaveATooltipWithContent(
'projects.awaiting_scan.description.APP',
);
expect(screen.getByText('4')).toBeInTheDocument();
expect(screen.getByText('5')).toBeInTheDocument();
expect(screen.getByText('6')).toBeInTheDocument();
@@ -150,7 +150,7 @@ it('should not display awaiting analysis badge if project is not analyzed', () =
...PROJECT,
analysisDate: undefined,
});
expect(screen.queryByRole('status', { name: 'projects.awaiting_scan' })).not.toBeInTheDocument();
expect(screen.queryByText('projects.awaiting_scan')).not.toBeInTheDocument();
});

it('should not display awaiting analysis badge if project does not have lines of code', () => {
@@ -160,12 +160,12 @@ it('should not display awaiting analysis badge if project does not have lines of
...(({ [MetricKey.ncloc]: _, ...rest }) => rest)(MEASURES),
},
});
expect(screen.queryByRole('status', { name: 'projects.awaiting_scan' })).not.toBeInTheDocument();
expect(screen.queryByText('projects.awaiting_scan')).not.toBeInTheDocument();
});

it('should not display awaiting analysis badge if it is a new code filter', () => {
renderProjectCard(PROJECT, undefined, 'leak');
expect(screen.queryByRole('status', { name: 'projects.awaiting_scan' })).not.toBeInTheDocument();
expect(screen.queryByText('projects.awaiting_scan')).not.toBeInTheDocument();
});

it('should display 3 aplication', () => {

+ 1
- 1
server/sonar-web/src/main/js/components/issue/__tests__/Issue-it.tsx View File

@@ -71,7 +71,7 @@ describe('rendering', () => {

it('should render correctly for external rule engines', () => {
renderIssue({ issue: mockIssue(true, { externalRuleEngine: 'ESLINT' }) });
expect(screen.getByRole('status', { name: 'ESLINT' })).toBeInTheDocument();
expect(screen.getByText('ESLINT')).toBeInTheDocument();
});

it('should render the SonarLint icon correctly', async () => {

+ 2
- 2
sonar-core/src/main/resources/org/sonar/l10n/core.properties View File

@@ -3930,7 +3930,7 @@ overview.accepted_issues.total=Total accepted issues
overview.high_impact_accepted_issues=High impact accepted issues
overview.measures.empty_explanation=Measures on New Code will appear after the second analysis of this branch.
overview.measures.empty_link={learn_more_link} about the Clean as You Code approach.
overview.measures.same_reference.explanation=This branch is configured to use itself as reference branch. It will never have New Code.
overview.measures.same_reference.explanation=This branch is configured to use itself as a reference branch. It will never have New Code.
overview.measures.bad_reference.explanation=This branch could not be compared to its reference branch. See the SCM or analysis report for more details.
overview.measures.bad_setting.link=This can be fixed in the {setting_link} setting.
overview.measures.security_hotspots_reviewed=Reviewed
@@ -4018,7 +4018,7 @@ overview.sonarlint_ad.details_3=Repair flagged issues in real-time with quick fi
overview.sonarlint_ad.details_4=12 major IDE's supported (including key JetBrains and Microsoft IDE's
overview.sonarlint_ad.details_5=Free forever
overview.sonarlint_ad.learn_more=Learn more about SonarLint
overview.sonarlint_ad.close_promotion=Close SonarLint romotion
overview.sonarlint_ad.close_promotion=Close SonarLint promotion

overview.badges.get_badge=Badges
overview.badges.title=Get project badges

Loading…
Cancel
Save