@@ -172,6 +172,7 @@ export default function NewCodeMeasuresPanel(props: Readonly<Props>) { | |||
: 'issue.type.SECURITY_HOTSPOT.plural' | |||
} | |||
url={getComponentSecurityHotspotsUrl(component.key, { | |||
inNewCodePeriod: 'true', | |||
...getBranchLikeQuery(branch), | |||
})} | |||
value={newSecurityHotspots} |
@@ -42,7 +42,6 @@ import { QualityGateStatus } from '../../../types/quality-gates'; | |||
import { Component, MeasureEnhanced } from '../../../types/types'; | |||
import MeasuresCard from '../components/MeasuresCard'; | |||
import MeasuresCardNumber from '../components/MeasuresCardNumber'; | |||
import { OverviewDisabledLinkTooltip } from '../components/OverviewDisabledLinkTooltip'; | |||
import MeasuresPanelPercentCards from './MeasuresPanelPercentCards'; | |||
import SoftwareImpactMeasureCard from './SoftwareImpactMeasureCard'; | |||
@@ -108,8 +107,6 @@ export default function OverallCodeMeasuresPanel(props: Readonly<OverallCodeMeas | |||
color={acceptedIssues === '0' ? 'overviewCardDefaultIcon' : 'overviewCardWarningIcon'} | |||
/> | |||
} | |||
disabled={component.needIssueSync} | |||
tooltip={component.needIssueSync ? <OverviewDisabledLinkTooltip /> : null} | |||
> | |||
<TextSubdued className="sw-body-xs sw-mt-3"> | |||
{intl.formatMessage({ |
@@ -19,7 +19,6 @@ | |||
*/ | |||
import styled from '@emotion/styled'; | |||
import { LinkHighlight, LinkStandalone } from '@sonarsource/echoes-react'; | |||
import classNames from 'classnames'; | |||
import { Badge, LightGreyCard, LightGreyCardTitle, TextBold, TextSubdued } from 'design-system'; | |||
import * as React from 'react'; | |||
import { FormattedMessage, useIntl } from 'react-intl'; | |||
@@ -41,7 +40,6 @@ import { | |||
import { MetricKey, MetricType } from '../../../types/metrics'; | |||
import { QualityGateStatusConditionEnhanced } from '../../../types/quality-gates'; | |||
import { Component, MeasureEnhanced } from '../../../types/types'; | |||
import { OverviewDisabledLinkTooltip } from '../components/OverviewDisabledLinkTooltip'; | |||
import { Status, softwareQualityToMeasure } from '../utils'; | |||
import SoftwareImpactMeasureBreakdownCard from './SoftwareImpactMeasureBreakdownCard'; | |||
import SoftwareImpactMeasureRating from './SoftwareImpactMeasureRating'; | |||
@@ -88,11 +86,9 @@ export function SoftwareImpactMeasureCard(props: Readonly<SoftwareImpactBreakdow | |||
(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); | |||
@@ -111,11 +107,7 @@ export function SoftwareImpactMeasureCard(props: Readonly<SoftwareImpactBreakdow | |||
</LightGreyCardTitle> | |||
<div className="sw-flex sw-flex-col sw-gap-3"> | |||
<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 ? ( | |||
<Tooltip overlay={countTooltipOverlay}> | |||
<LinkStandalone |
@@ -250,6 +250,11 @@ describe('project overview', () => { | |||
expect(await screen.findByText('metric.level.ERROR')).toBeInTheDocument(); | |||
expect(screen.getByText(/overview.X_conditions_failed/)).toBeInTheDocument(); | |||
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 () => { | |||
@@ -437,39 +442,6 @@ describe('project overview', () => { | |||
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', () => { |
@@ -18,15 +18,14 @@ | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import styled from '@emotion/styled'; | |||
import { LinkHighlight, LinkStandalone } from '@sonarsource/echoes-react'; | |||
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 { Path } from 'react-router-dom'; | |||
import Tooltip from '../../../components/controls/Tooltip'; | |||
import { translate, translateWithParameters } from '../../../helpers/l10n'; | |||
import { localizeMetric } from '../../../helpers/measures'; | |||
import { MetricKey } from '../../../types/metrics'; | |||
import { OverviewDisabledLinkTooltip } from './OverviewDisabledLinkTooltip'; | |||
interface IssueMeasuresCardInnerProps extends React.HTMLAttributes<HTMLDivElement> { | |||
metric: MetricKey; | |||
@@ -60,27 +59,22 @@ export function IssueMeasuresCardInner(props: Readonly<IssueMeasuresCardInnerPro | |||
</ColorBold> | |||
<div className="sw-flex sw-justify-between sw-items-center sw-h-9"> | |||
<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> | |||
{value ? icon : <NoDataIcon size="md" />} | |||
</div> |
@@ -18,7 +18,8 @@ | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
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 { To } from 'react-router-dom'; | |||
import { translate, translateWithParameters } from '../../../helpers/l10n'; | |||
@@ -32,14 +33,12 @@ export interface MeasuresCardProps { | |||
label: string; | |||
failed?: boolean; | |||
icon?: React.ReactElement; | |||
disabled?: boolean; | |||
tooltip?: React.ReactNode | null; | |||
} | |||
export default function MeasuresCard( | |||
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 ( | |||
<StyledCard className="sw-p-6 sw-rounded-2 sw-text-base" {...rest}> | |||
@@ -50,24 +49,23 @@ export default function MeasuresCard( | |||
</Badge> | |||
)} | |||
<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} | |||
</div> | |||
{children && <div className="sw-flex sw-flex-col">{children}</div>} |
@@ -1,42 +0,0 @@ | |||
/* | |||
* 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> | |||
); | |||
} |
@@ -155,7 +155,17 @@ it('should render links correctly', async () => { | |||
}).get(), | |||
).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 () => { |
@@ -310,6 +310,7 @@ more_information=More information | |||
new_violations=New violations | |||
new_window=New window | |||
no_data=No data | |||
no_measure_value_x=No measure value for {0} | |||
no_results=No results | |||
no_results_for_x=No results for "{0}" | |||
no_results_search=We couldn't find any results matching selected criteria. | |||
@@ -5165,7 +5166,6 @@ maintenance.sonarqube_is_offline.text=The connection to SonarQube is lost. Pleas | |||
#------------------------------------------------------------------------------ | |||
indexation.in_progress=Reindexing in progress. | |||
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.link=More info | |||
indexation.progression={0} out of {1} projects reindexed. |