Browse Source

SONAR-21765 Fix security hotspot link in the new code overview tab

tags/10.5.0.89998
Mathieu Suen 1 month ago
parent
commit
1a0f4685e9

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

: 'issue.type.SECURITY_HOTSPOT.plural' : 'issue.type.SECURITY_HOTSPOT.plural'
} }
url={getComponentSecurityHotspotsUrl(component.key, { url={getComponentSecurityHotspotsUrl(component.key, {
inNewCodePeriod: 'true',
...getBranchLikeQuery(branch), ...getBranchLikeQuery(branch),
})} })}
value={newSecurityHotspots} value={newSecurityHotspots}

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

import { Component, MeasureEnhanced } from '../../../types/types'; import { Component, MeasureEnhanced } from '../../../types/types';
import MeasuresCard from '../components/MeasuresCard'; import MeasuresCard from '../components/MeasuresCard';
import MeasuresCardNumber from '../components/MeasuresCardNumber'; import MeasuresCardNumber from '../components/MeasuresCardNumber';
import { OverviewDisabledLinkTooltip } from '../components/OverviewDisabledLinkTooltip';
import MeasuresPanelPercentCards from './MeasuresPanelPercentCards'; import MeasuresPanelPercentCards from './MeasuresPanelPercentCards';
import SoftwareImpactMeasureCard from './SoftwareImpactMeasureCard'; import SoftwareImpactMeasureCard from './SoftwareImpactMeasureCard';


color={acceptedIssues === '0' ? 'overviewCardDefaultIcon' : 'overviewCardWarningIcon'} color={acceptedIssues === '0' ? 'overviewCardDefaultIcon' : 'overviewCardWarningIcon'}
/> />
} }
disabled={component.needIssueSync}
tooltip={component.needIssueSync ? <OverviewDisabledLinkTooltip /> : null}
> >
<TextSubdued className="sw-body-xs sw-mt-3"> <TextSubdued className="sw-body-xs sw-mt-3">
{intl.formatMessage({ {intl.formatMessage({

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

*/ */
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { LinkHighlight, LinkStandalone } from '@sonarsource/echoes-react'; import { LinkHighlight, LinkStandalone } from '@sonarsource/echoes-react';
import classNames from 'classnames';
import { Badge, LightGreyCard, LightGreyCardTitle, TextBold, TextSubdued } from 'design-system'; import { Badge, LightGreyCard, LightGreyCardTitle, TextBold, TextSubdued } from 'design-system';
import * as React from 'react'; import * as React from 'react';
import { FormattedMessage, useIntl } from 'react-intl'; import { FormattedMessage, useIntl } from 'react-intl';
import { MetricKey, MetricType } from '../../../types/metrics'; import { MetricKey, MetricType } from '../../../types/metrics';
import { QualityGateStatusConditionEnhanced } from '../../../types/quality-gates'; import { QualityGateStatusConditionEnhanced } from '../../../types/quality-gates';
import { Component, MeasureEnhanced } from '../../../types/types'; import { Component, MeasureEnhanced } from '../../../types/types';
import { OverviewDisabledLinkTooltip } from '../components/OverviewDisabledLinkTooltip';
import { Status, softwareQualityToMeasure } from '../utils'; import { Status, softwareQualityToMeasure } from '../utils';
import SoftwareImpactMeasureBreakdownCard from './SoftwareImpactMeasureBreakdownCard'; import SoftwareImpactMeasureBreakdownCard from './SoftwareImpactMeasureBreakdownCard';
import SoftwareImpactMeasureRating from './SoftwareImpactMeasureRating'; import SoftwareImpactMeasureRating from './SoftwareImpactMeasureRating';
(severity) => measure[severity] > 0, (severity) => measure[severity] > 0,
); );


const countTooltipOverlay = component.needIssueSync ? (
<OverviewDisabledLinkTooltip />
) : (
intl.formatMessage({ id: 'overview.measures.software_impact.count_tooltip' })
);
const countTooltipOverlay = intl.formatMessage({
id: 'overview.measures.software_impact.count_tooltip',
});


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


</LightGreyCardTitle> </LightGreyCardTitle>
<div className="sw-flex sw-flex-col sw-gap-3"> <div className="sw-flex sw-flex-col sw-gap-3">
<div className="sw-flex sw-mt-4"> <div className="sw-flex sw-mt-4">
<div
className={classNames('sw-flex sw-gap-1 sw-items-center', {
'sw-opacity-60': component.needIssueSync,
})}
>
<div className="sw-flex sw-gap-1 sw-items-center">
{count ? ( {count ? (
<Tooltip overlay={countTooltipOverlay}> <Tooltip overlay={countTooltipOverlay}>
<LinkStandalone <LinkStandalone

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

expect(await screen.findByText('metric.level.ERROR')).toBeInTheDocument(); expect(await screen.findByText('metric.level.ERROR')).toBeInTheDocument();
expect(screen.getByText(/overview.X_conditions_failed/)).toBeInTheDocument(); expect(screen.getByText(/overview.X_conditions_failed/)).toBeInTheDocument();
expect(screen.getAllByText(/overview.quality_gate.required_x/)).toHaveLength(3); expect(screen.getAllByText(/overview.quality_gate.required_x/)).toHaveLength(3);
expect(
screen.getByRole('link', {
name: '1 1 metric.new_security_hotspots_reviewed.name quality_gates.operator.GT 2',
}),
).toHaveAttribute('href', '/security_hotspots?id=foo&inNewCodePeriod=true');
}); });


it('should correctly show a project as empty', async () => { it('should correctly show a project as empty', async () => {
expect(await screen.findByText('overview.missing_project_data.TRK')).toBeInTheDocument(); expect(await screen.findByText('overview.missing_project_data.TRK')).toBeInTheDocument();
}, },
); );

it('should disable software impact measure card links during reindexing', async () => {
const { user, ui } = getPageObjects();
renderBranchOverview({
component: mockComponent({
breadcrumbs: [mockComponent({ key: 'foo' })],
key: 'foo',
needIssueSync: true,
}),
});

await user.click(await ui.overallCodeButton.find());

expect(await ui.softwareImpactMeasureCard(SoftwareQuality.Security).find()).toBeInTheDocument();

ui.expectSoftwareImpactMeasureCard(
SoftwareQuality.Security,
'B',
{
total: 1,
[SoftwareImpactSeverity.High]: 0,
[SoftwareImpactSeverity.Medium]: 1,
[SoftwareImpactSeverity.Low]: 0,
},
[false, true, false],
);

await expect(
byRole('link', {
name: `overview.measures.software_impact.see_list_of_x_open_issues.${1}.software_quality.${SoftwareQuality.Security}`,
}).get(),
).toHaveATooltipWithContent('indexation.in_progress');
});
}); });


describe('application overview', () => { describe('application overview', () => {

+ 17
- 23
server/sonar-web/src/main/js/apps/overview/components/IssueMeasuresCardInner.tsx View File

* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/ */
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { LinkHighlight, LinkStandalone } from '@sonarsource/echoes-react';
import classNames from 'classnames'; import classNames from 'classnames';
import { Badge, ContentLink, NoDataIcon, themeColor } from 'design-system';
import { Badge, NoDataIcon, themeColor } from 'design-system';
import * as React from 'react'; import * as React from 'react';
import { Path } from 'react-router-dom'; import { Path } from 'react-router-dom';
import Tooltip from '../../../components/controls/Tooltip';
import { translate, translateWithParameters } from '../../../helpers/l10n'; import { translate, translateWithParameters } from '../../../helpers/l10n';
import { localizeMetric } from '../../../helpers/measures'; import { localizeMetric } from '../../../helpers/measures';
import { MetricKey } from '../../../types/metrics'; import { MetricKey } from '../../../types/metrics';
import { OverviewDisabledLinkTooltip } from './OverviewDisabledLinkTooltip';


interface IssueMeasuresCardInnerProps extends React.HTMLAttributes<HTMLDivElement> { interface IssueMeasuresCardInnerProps extends React.HTMLAttributes<HTMLDivElement> {
metric: MetricKey; metric: MetricKey;
</ColorBold> </ColorBold>
<div className="sw-flex sw-justify-between sw-items-center sw-h-9"> <div className="sw-flex sw-justify-between sw-items-center sw-h-9">
<div className="sw-h-fit"> <div className="sw-h-fit">
<Tooltip
classNameSpace={disabled ? 'tooltip' : 'sw-hidden'}
overlay={value && <OverviewDisabledLinkTooltip />}
<LinkStandalone
highlight={LinkHighlight.Default}
aria-label={
value
? translateWithParameters(
'overview.see_more_details_on_x_of_y',
value,
localizeMetric(metric),
)
: translateWithParameters('no_measure_value_x', localizeMetric(metric))
}
className="it__overview-measures-value sw-w-fit sw-text-lg"
to={url}
> >
<ContentLink
disabled={disabled || !value}
aria-label={
value
? translateWithParameters(
'overview.see_more_details_on_x_of_y',
value,
localizeMetric(metric),
)
: translate('no_data')
}
className="it__overview-measures-value sw-w-fit sw-text-lg"
to={url}
>
{value ? value : '-'}
</ContentLink>
</Tooltip>
{value ?? '-'}
</LinkStandalone>
</div> </div>
{value ? icon : <NoDataIcon size="md" />} {value ? icon : <NoDataIcon size="md" />}
</div> </div>

+ 20
- 22
server/sonar-web/src/main/js/apps/overview/components/MeasuresCard.tsx View File

* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/ */
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { Badge, Card, ContentLink, Tooltip, themeBorder, themeColor } from 'design-system';
import { LinkHighlight, LinkStandalone } from '@sonarsource/echoes-react';
import { Badge, Card, themeBorder, themeColor } from 'design-system';
import * as React from 'react'; import * as React from 'react';
import { To } from 'react-router-dom'; import { To } from 'react-router-dom';
import { translate, translateWithParameters } from '../../../helpers/l10n'; import { translate, translateWithParameters } from '../../../helpers/l10n';
label: string; label: string;
failed?: boolean; failed?: boolean;
icon?: React.ReactElement; icon?: React.ReactElement;
disabled?: boolean;
tooltip?: React.ReactNode | null;
} }


export default function MeasuresCard( export default function MeasuresCard(
props: React.PropsWithChildren<MeasuresCardProps & React.HTMLAttributes<HTMLDivElement>>, props: React.PropsWithChildren<MeasuresCardProps & React.HTMLAttributes<HTMLDivElement>>,
) { ) {
const { failed, children, metric, icon, value, url, label, disabled, tooltip, ...rest } = props;
const { failed, children, metric, icon, value, url, label, ...rest } = props;


return ( return (
<StyledCard className="sw-p-6 sw-rounded-2 sw-text-base" {...rest}> <StyledCard className="sw-p-6 sw-rounded-2 sw-text-base" {...rest}>
</Badge> </Badge>
)} )}
<div className="sw-flex sw-items-center sw-mt-1 sw-justify-between sw-font-semibold"> <div className="sw-flex sw-items-center sw-mt-1 sw-justify-between sw-font-semibold">
{value ? (
<Tooltip overlay={tooltip}>
<ContentLink
aria-label={translateWithParameters(
'overview.see_more_details_on_x_of_y',
value,
localizeMetric(metric),
)}
className="it__overview-measures-value sw-text-lg"
to={url}
disabled={disabled}
>
{value}
</ContentLink>
</Tooltip>
) : (
<ColorBold> — </ColorBold>
)}
<LinkStandalone
highlight={LinkHighlight.Default}
aria-label={
value
? translateWithParameters(
'overview.see_more_details_on_x_of_y',
value,
localizeMetric(metric),
)
: translateWithParameters('no_measure_value_x', localizeMetric(metric))
}
className="it__overview-measures-value sw-text-lg"
to={url}
>
{value ?? '-'}
</LinkStandalone>

{icon} {icon}
</div> </div>
{children && <div className="sw-flex sw-flex-col">{children}</div>} {children && <div className="sw-flex sw-flex-col">{children}</div>}

+ 0
- 42
server/sonar-web/src/main/js/apps/overview/components/OverviewDisabledLinkTooltip.tsx View File

/*
* SonarQube
* Copyright (C) 2009-2024 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
import DocumentationLink from '../../../components/common/DocumentationLink';
import { translate } from '../../../helpers/l10n';

export function OverviewDisabledLinkTooltip() {
return (
<div className="sw-body-sm sw-w-[280px]">
{translate('indexation.in_progress')}

<br />

{translate('indexation.link_unavailable')}

<hr className="sw-mx-0 sw-my-3 sw-p-0 sw-w-full" />

<span className="sw-body-sm-highlight">{translate('indexation.learn_more')}</span>

<DocumentationLink className="sw-ml-1" to="/instance-administration/reindexing/">
{translate('indexation.reindexing')}
</DocumentationLink>
</div>
);
}

+ 11
- 1
server/sonar-web/src/main/js/apps/overview/pullRequests/__tests__/PullRequestOverview-it.tsx View File

}).get(), }).get(),
).toHaveAttribute('href', '/project/issues?fixedInPullRequest=1001&id=foo'); ).toHaveAttribute('href', '/project/issues?fixedInPullRequest=1001&id=foo');


expect(screen.getByLabelText('no_data')).toBeInTheDocument();
expect(
screen.getByRole('link', { name: 'no_measure_value_x.metric.new_security_hotspots.name' }),
).toBeInTheDocument();
expect(
screen.getByRole('link', { name: 'no_measure_value_x.metric.new_accepted_issues.name' }),
).toBeInTheDocument();
expect(
screen.getByRole('link', {
name: 'no_measure_value_x.metric.new_duplicated_lines_density.name',
}),
).toBeInTheDocument();
}); });


it('should render correctly for a passed QG', async () => { it('should render correctly for a passed QG', async () => {

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

new_violations=New violations new_violations=New violations
new_window=New window new_window=New window
no_data=No data no_data=No data
no_measure_value_x=No measure value for {0}
no_results=No results no_results=No results
no_results_for_x=No results for "{0}" no_results_for_x=No results for "{0}"
no_results_search=We couldn't find any results matching selected criteria. no_results_search=We couldn't find any results matching selected criteria.
#------------------------------------------------------------------------------ #------------------------------------------------------------------------------
indexation.in_progress=Reindexing in progress. indexation.in_progress=Reindexing in progress.
indexation.details_unavailable=Details are unavailable until this process is complete. indexation.details_unavailable=Details are unavailable until this process is complete.
indexation.link_unavailable=The link to these results is unavailable until this process is complete.
indexation.features_partly_available=Most features are available. Some details only show upon completion. {link} indexation.features_partly_available=Most features are available. Some details only show upon completion. {link}
indexation.features_partly_available.link=More info indexation.features_partly_available.link=More info
indexation.progression={0} out of {1} projects reindexed. indexation.progression={0} out of {1} projects reindexed.

Loading…
Cancel
Save