@@ -19,21 +19,20 @@ | |||
*/ | |||
import { | |||
BasicSeparator, | |||
FlagMessage, | |||
LargeCenteredLayout, | |||
LightGreyCard, | |||
PageContentFontWrapper, | |||
} from 'design-system'; | |||
import * as React from 'react'; | |||
import { useIntl } from 'react-intl'; | |||
import A11ySkipTarget from '../../../components/a11y/A11ySkipTarget'; | |||
import { useLocation, useRouter } from '../../../components/hoc/withRouter'; | |||
import AnalysisMissingInfoMessage from '../../../components/shared/AnalysisMissingInfoMessage'; | |||
import { parseDate } from '../../../helpers/dates'; | |||
import { isDiffMetric } from '../../../helpers/measures'; | |||
import { CodeScope } from '../../../helpers/urls'; | |||
import { ApplicationPeriod } from '../../../types/application'; | |||
import { Branch } from '../../../types/branch-like'; | |||
import { ComponentQualifier, isApplication } from '../../../types/component'; | |||
import { ComponentQualifier } from '../../../types/component'; | |||
import { MetricKey } from '../../../types/metrics'; | |||
import { Analysis, GraphType, MeasureHistory } from '../../../types/project-activity'; | |||
import { QualityGateStatus } from '../../../types/quality-gates'; | |||
@@ -94,18 +93,17 @@ export default function BranchOverviewRenderer(props: BranchOverviewRendererProp | |||
const { query } = useLocation(); | |||
const router = useRouter(); | |||
const intl = useIntl(); | |||
const tab = query.codeScope === CodeScope.Overall ? CodeScope.Overall : CodeScope.New; | |||
const leakPeriod = component.qualifier === ComponentQualifier.Application ? appLeak : period; | |||
const isNewCodeTab = tab === CodeScope.New; | |||
const hasNewCodeMeasures = measures.some((m) => isDiffMetric(m.metric.key)); | |||
// Check if any potentially missing uncomputed measure is not present | |||
const isMissingMeasures = ( | |||
isNewCodeTab | |||
? [MetricKey.new_accepted_issues] | |||
: [MetricKey.security_issues, MetricKey.maintainability_issues, MetricKey.reliability_issues] | |||
).some((key) => !measures.find((measure) => measure.metric.key === key)); | |||
const isMissingMeasures = [ | |||
MetricKey.security_issues, | |||
MetricKey.maintainability_issues, | |||
MetricKey.reliability_issues, | |||
].some((key) => !measures.find((measure) => measure.metric.key === key)); | |||
const selectTab = (tab: CodeScope) => { | |||
router.replace({ query: { ...query, codeScope: tab } }); | |||
@@ -121,14 +119,9 @@ export default function BranchOverviewRenderer(props: BranchOverviewRendererProp | |||
/* eslint-disable-next-line react-hooks/exhaustive-deps */ | |||
}, [loadingStatus, hasNewCodeMeasures]); | |||
const appReanalysisWarning = | |||
isMissingMeasures && isApplication(component.qualifier) ? ( | |||
<FlagMessage variant="warning" className="sw-my-4"> | |||
{intl.formatMessage({ | |||
id: 'overview.missing_project_data.APP', | |||
})} | |||
</FlagMessage> | |||
) : null; | |||
const analysisMissingInfo = isMissingMeasures && ( | |||
<AnalysisMissingInfoMessage qualifier={component.qualifier} className="sw-mt-6" /> | |||
); | |||
return ( | |||
<> | |||
@@ -184,15 +177,12 @@ export default function BranchOverviewRenderer(props: BranchOverviewRendererProp | |||
{isNewCodeTab && ( | |||
<> | |||
{hasNewCodeMeasures ? ( | |||
<> | |||
{appReanalysisWarning} | |||
<NewCodeMeasuresPanel | |||
qgStatuses={qgStatuses} | |||
branch={branch} | |||
component={component} | |||
measures={measures} | |||
/> | |||
</> | |||
<NewCodeMeasuresPanel | |||
qgStatuses={qgStatuses} | |||
branch={branch} | |||
component={component} | |||
measures={measures} | |||
/> | |||
) : ( | |||
<MeasuresPanelNoNewCode | |||
branch={branch} | |||
@@ -205,7 +195,7 @@ export default function BranchOverviewRenderer(props: BranchOverviewRendererProp | |||
{!isNewCodeTab && ( | |||
<> | |||
{appReanalysisWarning} | |||
{analysisMissingInfo} | |||
<OverallCodeMeasuresPanel | |||
branch={branch} | |||
qgStatuses={qgStatuses} |
@@ -17,16 +17,10 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { LinkHighlight, LinkStandalone } from '@sonarsource/echoes-react'; | |||
import styled from '@emotion/styled'; | |||
import classNames from 'classnames'; | |||
import { | |||
BasicSeparator, | |||
LightGreyCard, | |||
NakedLink, | |||
TextBold, | |||
TextSubdued, | |||
themeColor, | |||
} from 'design-system'; | |||
import { BasicSeparator, LightGreyCard, TextBold, TextSubdued } from 'design-system'; | |||
import * as React from 'react'; | |||
import { useIntl } from 'react-intl'; | |||
import Tooltip from '../../../components/controls/Tooltip'; | |||
@@ -45,6 +39,11 @@ import { OverviewDisabledLinkTooltip } from '../components/OverviewDisabledLinkT | |||
import { softwareQualityToMeasure } from '../utils'; | |||
import SoftwareImpactMeasureBreakdownCard from './SoftwareImpactMeasureBreakdownCard'; | |||
import SoftwareImpactMeasureRating from './SoftwareImpactMeasureRating'; | |||
import { isDefined } from '../../../helpers/types'; | |||
import { | |||
getIssueTypeBySoftwareQuality, | |||
SOFTWARE_QUALITIES_METRIC_KEYS_MAP, | |||
} from '../../../helpers/issues'; | |||
export interface SoftwareImpactBreakdownCardProps { | |||
component: Component; | |||
@@ -63,15 +62,20 @@ export function SoftwareImpactMeasureCard(props: Readonly<SoftwareImpactBreakdow | |||
const metricKey = softwareQualityToMeasure(softwareQuality); | |||
const measureRaw = measures.find((m) => m.metric.key === metricKey); | |||
const measure = JSON.parse(measureRaw?.value ?? 'null') as SoftwareImpactMeasureData; | |||
const renderDisabled = !measure || component.needIssueSync; | |||
const alternativeMeasure = measures.find( | |||
(m) => m.metric.key === SOFTWARE_QUALITIES_METRIC_KEYS_MAP[softwareQuality].deprecatedMetric, | |||
); | |||
// Find rating measure | |||
const ratingMeasure = measures.find((m) => m.metric.key === ratingMetricKey); | |||
const count = measure?.total ?? alternativeMeasure?.value; | |||
const totalLinkHref = getComponentIssuesUrl(component.key, { | |||
...DEFAULT_ISSUES_QUERY, | |||
impactSoftwareQualities: softwareQuality, | |||
...(isDefined(measure) | |||
? { impactSoftwareQualities: softwareQuality } | |||
: { types: getIssueTypeBySoftwareQuality(softwareQuality) }), | |||
branch: branch?.name, | |||
}); | |||
@@ -99,30 +103,30 @@ export function SoftwareImpactMeasureCard(props: Readonly<SoftwareImpactBreakdow | |||
<div className="sw-flex sw-mt-2"> | |||
<div | |||
className={classNames('sw-flex sw-gap-1 sw-items-center', { | |||
'sw-opacity-60': renderDisabled, | |||
'sw-opacity-60': component.needIssueSync, | |||
})} | |||
> | |||
{measure ? ( | |||
{count ? ( | |||
<Tooltip overlay={countTooltipOverlay}> | |||
<NakedLink | |||
<LinkStandalone | |||
data-testid={`overview__software-impact-${softwareQuality}`} | |||
aria-label={intl.formatMessage( | |||
{ | |||
id: `overview.measures.software_impact.see_list_of_x_open_issues`, | |||
}, | |||
{ | |||
count: measure.total, | |||
count, | |||
softwareQuality: intl.formatMessage({ | |||
id: `software_quality.${softwareQuality}`, | |||
}), | |||
}, | |||
)} | |||
className="sw-text-lg" | |||
className="sw-text-lg sw-font-semibold" | |||
highlight={LinkHighlight.CurrentColor} | |||
to={totalLinkHref} | |||
disabled={component.needIssueSync} | |||
> | |||
{formatMeasure(measure.total, MetricType.ShortInteger)} | |||
</NakedLink> | |||
{formatMeasure(count, MetricType.ShortInteger)} | |||
</LinkStandalone> | |||
</Tooltip> | |||
) : ( | |||
<StyledDash className="sw-font-bold" name="-" /> | |||
@@ -139,36 +143,26 @@ export function SoftwareImpactMeasureCard(props: Readonly<SoftwareImpactBreakdow | |||
/> | |||
</div> | |||
</div> | |||
<div className="sw-flex sw-gap-2"> | |||
{[ | |||
SoftwareImpactSeverity.High, | |||
SoftwareImpactSeverity.Medium, | |||
SoftwareImpactSeverity.Low, | |||
].map((severity) => ( | |||
<SoftwareImpactMeasureBreakdownCard | |||
branch={branch} | |||
key={severity} | |||
component={component} | |||
softwareQuality={softwareQuality} | |||
value={measure?.[severity]?.toString()} | |||
severity={severity} | |||
active={highlightedSeverity === severity} | |||
/> | |||
))} | |||
</div> | |||
{measure && ( | |||
<div className="sw-flex sw-gap-2"> | |||
{[ | |||
SoftwareImpactSeverity.High, | |||
SoftwareImpactSeverity.Medium, | |||
SoftwareImpactSeverity.Low, | |||
].map((severity) => ( | |||
<SoftwareImpactMeasureBreakdownCard | |||
branch={branch} | |||
key={severity} | |||
component={component} | |||
softwareQuality={softwareQuality} | |||
value={measure?.[severity]?.toString()} | |||
severity={severity} | |||
active={highlightedSeverity === severity} | |||
/> | |||
))} | |||
</div> | |||
)} | |||
</div> | |||
{!measure && ( | |||
<> | |||
<BasicSeparator className="sw--mx-4 sw-mb-0 sw-mt-3" /> | |||
<StyledInfoSection className="sw--ml-4 sw--mr-4 sw--mb-4 sw-text-xs sw-p-4 sw-flex sw-gap-1 sw-flex-wrap"> | |||
<span> | |||
{intl.formatMessage({ | |||
id: `overview.run_analysis_to_compute.${component.qualifier}`, | |||
})} | |||
</span> | |||
</StyledInfoSection> | |||
</> | |||
)} | |||
</LightGreyCard> | |||
); | |||
} | |||
@@ -177,8 +171,4 @@ const StyledDash = styled(TextBold)` | |||
font-size: 36px; | |||
`; | |||
const StyledInfoSection = styled.div` | |||
background-color: ${themeColor('overviewSoftwareImpactSeverityNeutral')}; | |||
`; | |||
export default SoftwareImpactMeasureCard; |
@@ -17,16 +17,9 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { Spinner } from '@sonarsource/echoes-react'; | |||
import { isBefore, sub } from 'date-fns'; | |||
import { | |||
BasicSeparator, | |||
ButtonLink, | |||
FlagMessage, | |||
LightLabel, | |||
PageTitle, | |||
Spinner, | |||
Tabs, | |||
} from 'design-system'; | |||
import { BasicSeparator, ButtonLink, FlagMessage, LightLabel, Tabs } from 'design-system'; | |||
import * as React from 'react'; | |||
import { FormattedMessage } from 'react-intl'; | |||
import DocumentationLink from '../../../components/common/DocumentationLink'; | |||
@@ -131,15 +124,14 @@ export function TabsPanel(props: React.PropsWithChildren<MeasuresPanelProps>) { | |||
return ( | |||
<div data-testid="overview__measures-panel"> | |||
<div className="sw-flex sw-justify-between sw-items-center sw-mb-4"> | |||
<PageTitle as="h2" text={translate('overview.measures')} /> | |||
<div className="sw-flex sw-justify-end sw-items-center sw-mb-4"> | |||
<LastAnalysisLabel analysisDate={branch?.analysisDate} /> | |||
</div> | |||
<BasicSeparator className="sw--mx-6 sw-mb-3" /> | |||
{loading ? ( | |||
<div> | |||
<Spinner loading={loading} /> | |||
<Spinner isLoading={loading} /> | |||
</div> | |||
) : ( | |||
<> |
@@ -37,7 +37,7 @@ import { mockAnalysis, mockAnalysisEvent } from '../../../../helpers/mocks/proje | |||
import { mockQualityGateProjectStatus } from '../../../../helpers/mocks/quality-gates'; | |||
import { mockLoggedInUser, mockMeasure, mockPaging } from '../../../../helpers/testMocks'; | |||
import { renderComponent } from '../../../../helpers/testReactTestingUtils'; | |||
import { byRole, byText } from '../../../../helpers/testSelector'; | |||
import { byLabelText, byRole, byText } from '../../../../helpers/testSelector'; | |||
import { SoftwareImpactSeverity, SoftwareQuality } from '../../../../types/clean-code-taxonomy'; | |||
import { ComponentQualifier } from '../../../../types/component'; | |||
import { MetricKey } from '../../../../types/metrics'; | |||
@@ -323,9 +323,11 @@ describe('project overview', () => { | |||
); | |||
}); | |||
it('should render missing software impact measure cards', async () => { | |||
// Make as if reliability_issues was not computed | |||
it('should render old measures if software impact are missing', async () => { | |||
// Make as if new analysis after upgrade is missing | |||
measuresHandler.deleteComponentMeasure('foo', MetricKey.maintainability_issues); | |||
measuresHandler.deleteComponentMeasure('foo', MetricKey.security_issues); | |||
measuresHandler.deleteComponentMeasure('foo', MetricKey.reliability_issues); | |||
const { user, ui } = getPageObjects(); | |||
renderBranchOverview(); | |||
@@ -334,43 +336,91 @@ describe('project overview', () => { | |||
expect(await ui.softwareImpactMeasureCard(SoftwareQuality.Security).find()).toBeInTheDocument(); | |||
ui.expectSoftwareImpactMeasureCard( | |||
ui.expectSoftwareImpactMeasureCard(SoftwareQuality.Security); | |||
ui.expectSoftwareImpactMeasureCardToHaveOldMeasures( | |||
SoftwareQuality.Security, | |||
'B', | |||
{ | |||
total: 1, | |||
[SoftwareImpactSeverity.High]: 0, | |||
[SoftwareImpactSeverity.Medium]: 1, | |||
[SoftwareImpactSeverity.Low]: 0, | |||
}, | |||
[false, true, false], | |||
2, | |||
'VULNERABILITY', | |||
); | |||
ui.expectSoftwareImpactMeasureCard( | |||
SoftwareQuality.Reliability, | |||
'A', | |||
{ | |||
total: 3, | |||
[SoftwareImpactSeverity.High]: 0, | |||
[SoftwareImpactSeverity.Medium]: 2, | |||
[SoftwareImpactSeverity.Low]: 1, | |||
}, | |||
[false, true, false], | |||
ui.expectSoftwareImpactMeasureCard(SoftwareQuality.Reliability); | |||
ui.expectSoftwareImpactMeasureCardToHaveOldMeasures(SoftwareQuality.Reliability, 'A', 0, 'BUG'); | |||
ui.expectSoftwareImpactMeasureCard(SoftwareQuality.Maintainability); | |||
ui.expectSoftwareImpactMeasureCardToHaveOldMeasures( | |||
SoftwareQuality.Maintainability, | |||
'E', | |||
8, | |||
'CODE_SMELL', | |||
); | |||
}); | |||
it('should render missing software impact measure cards if both software qualities and old measures are missing', async () => { | |||
// Make as if no measures at all | |||
measuresHandler.deleteComponentMeasure('foo', MetricKey.maintainability_issues); | |||
measuresHandler.deleteComponentMeasure('foo', MetricKey.code_smells); | |||
measuresHandler.deleteComponentMeasure('foo', MetricKey.security_issues); | |||
measuresHandler.deleteComponentMeasure('foo', MetricKey.vulnerabilities); | |||
measuresHandler.deleteComponentMeasure('foo', MetricKey.reliability_issues); | |||
measuresHandler.deleteComponentMeasure('foo', MetricKey.bugs); | |||
const { user, ui } = getPageObjects(); | |||
renderBranchOverview(); | |||
await user.click(await ui.overallCodeButton.find()); | |||
expect(await ui.softwareImpactMeasureCard(SoftwareQuality.Security).find()).toBeInTheDocument(); | |||
expect(byText('-', { exact: true }).getAll()).toHaveLength(3); | |||
ui.expectSoftwareImpactMeasureCard(SoftwareQuality.Security); | |||
expect( | |||
byLabelText( | |||
`overview.project.software_impact.has_rating.software_quality.${SoftwareQuality.Security}.B`, | |||
).get(), | |||
).toBeInTheDocument(); | |||
ui.expectSoftwareImpactMeasureCard(SoftwareQuality.Reliability); | |||
expect( | |||
byLabelText( | |||
`overview.project.software_impact.has_rating.software_quality.${SoftwareQuality.Reliability}.A`, | |||
).get(), | |||
).toBeInTheDocument(); | |||
// Maintainability is not computed | |||
ui.expectSoftwareImpactMeasureCard(SoftwareQuality.Maintainability); | |||
expect( | |||
byText('overview.run_analysis_to_compute.TRK').get( | |||
ui.softwareImpactMeasureCard(SoftwareQuality.Maintainability).get(), | |||
), | |||
byLabelText( | |||
`overview.project.software_impact.has_rating.software_quality.${SoftwareQuality.Maintainability}.E`, | |||
).get(), | |||
).toBeInTheDocument(); | |||
ui.expectSoftwareImpactMeasureCard(SoftwareQuality.Maintainability, undefined, undefined, [ | |||
false, | |||
false, | |||
false, | |||
]); | |||
}); | |||
it.each([ | |||
['security_issues', MetricKey.security_issues], | |||
['reliability_issues', MetricKey.reliability_issues], | |||
['maintainability_issues', MetricKey.maintainability_issues], | |||
])( | |||
'should display info about missing analysis if a project is not computed for %s', | |||
async (missingMetricKey) => { | |||
measuresHandler.deleteComponentMeasure('foo', missingMetricKey as MetricKey); | |||
const { user, ui } = getPageObjects(); | |||
renderBranchOverview(); | |||
await user.click(await ui.overallCodeButton.find()); | |||
expect( | |||
await ui.softwareImpactMeasureCard(SoftwareQuality.Security).find(), | |||
).toBeInTheDocument(); | |||
await user.click(await ui.overallCodeButton.find()); | |||
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({ | |||
@@ -528,17 +578,6 @@ describe('application overview', () => { | |||
expect(await screen.findByText('portfolio.app.empty')).toBeInTheDocument(); | |||
}); | |||
it.each([['new_accepted_issues', MetricKey.new_accepted_issues]])( | |||
'should ask to reanalyze all projects if a project is not computed for %s', | |||
async (missingMetricKey) => { | |||
measuresHandler.deleteComponentMeasure('foo', missingMetricKey as MetricKey); | |||
renderBranchOverview({ component }); | |||
expect(await screen.findByText('overview.missing_project_data.APP')).toBeInTheDocument(); | |||
}, | |||
); | |||
it.each([ | |||
['security_issues', MetricKey.security_issues], | |||
['reliability_issues', MetricKey.reliability_issues], |
@@ -100,6 +100,26 @@ export const getPageObjects = () => { | |||
); | |||
} | |||
}, | |||
expectSoftwareImpactMeasureCardToHaveOldMeasures: ( | |||
softwareQuality: SoftwareQuality, | |||
rating: string, | |||
total: number, | |||
oldMetric: string, | |||
branch = 'master', | |||
) => { | |||
const branchQuery = branch ? `&branch=${branch}` : ''; | |||
expect( | |||
byText(rating, { exact: true }).get(ui.softwareImpactMeasureCard(softwareQuality).get()), | |||
).toBeInTheDocument(); | |||
expect( | |||
byRole('link', { | |||
name: `overview.measures.software_impact.see_list_of_x_open_issues.${total}.software_quality.${softwareQuality}`, | |||
}).get(), | |||
).toHaveAttribute( | |||
'href', | |||
`/project/issues?issueStatuses=OPEN%2CCONFIRMED&types=${oldMetric}${branchQuery}&id=foo`, | |||
); | |||
}, | |||
expectSoftwareImpactMeasureBreakdownCard: ( | |||
softwareQuality: SoftwareQuality, | |||
severity: SoftwareImpactSeverity, |
@@ -0,0 +1,54 @@ | |||
/* | |||
* 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 { FlagMessage } from 'design-system'; | |||
import * as React from 'react'; | |||
import { FormattedMessage, useIntl } from 'react-intl'; | |||
import DocumentationLink from '../common/DocumentationLink'; | |||
interface AnalysisMissingInfoMessageProps { | |||
qualifier: string; | |||
className?: string; | |||
} | |||
export default function AnalysisMissingInfoMessage({ | |||
qualifier, | |||
className, | |||
}: Readonly<AnalysisMissingInfoMessageProps>) { | |||
const intl = useIntl(); | |||
return ( | |||
<FlagMessage variant="info" className={className}> | |||
<FormattedMessage | |||
id={`overview.missing_project_data.${qualifier}`} | |||
tagName="div" | |||
values={{ | |||
learn_more: ( | |||
<DocumentationLink | |||
className="sw-ml-2 sw-whitespace-nowrap" | |||
to="/user-guide/clean-code/code-analysis/" | |||
> | |||
{intl.formatMessage({ id: 'learn_more' })} | |||
</DocumentationLink> | |||
), | |||
}} | |||
/> | |||
</FlagMessage> | |||
); | |||
} |
@@ -24,6 +24,7 @@ import { MetricKey } from '../types/metrics'; | |||
import { Dict, Flow, FlowLocation, FlowType, Issue, TextRange } from '../types/types'; | |||
import { UserBase } from '../types/users'; | |||
import { ISSUE_TYPES } from './constants'; | |||
import { SoftwareQuality } from '../types/clean-code-taxonomy'; | |||
interface Rule {} | |||
@@ -163,6 +164,37 @@ export function parseIssueFromResponse( | |||
} as Issue; | |||
} | |||
export function getIssueTypeBySoftwareQuality(quality: SoftwareQuality): IssueType { | |||
const map = { | |||
[SoftwareQuality.Maintainability]: IssueType.CodeSmell, | |||
[SoftwareQuality.Security]: IssueType.Vulnerability, | |||
[SoftwareQuality.Reliability]: IssueType.Bug, | |||
}; | |||
return map[quality]; | |||
} | |||
export const SOFTWARE_QUALITIES_METRIC_KEYS_MAP = { | |||
[SoftwareQuality.Security]: { | |||
metric: MetricKey.security_issues, | |||
deprecatedMetric: MetricKey.vulnerabilities, | |||
rating: MetricKey.security_rating, | |||
newRating: MetricKey.new_security_rating, | |||
}, | |||
[SoftwareQuality.Reliability]: { | |||
metric: MetricKey.reliability_issues, | |||
deprecatedMetric: MetricKey.bugs, | |||
rating: MetricKey.reliability_rating, | |||
newRating: MetricKey.new_reliability_rating, | |||
}, | |||
[SoftwareQuality.Maintainability]: { | |||
metric: MetricKey.maintainability_issues, | |||
deprecatedMetric: MetricKey.code_smells, | |||
rating: MetricKey.sqale_rating, | |||
newRating: MetricKey.new_maintainability_rating, | |||
}, | |||
}; | |||
export const ISSUETYPE_METRIC_KEYS_MAP = { | |||
[IssueType.CodeSmell]: { | |||
metric: MetricKey.code_smells, |
@@ -3929,7 +3929,6 @@ overview.accepted_issues=Accepted issues | |||
overview.accepted_issues.description=Issues that are valid, but were not fixed and represent accepted technical debt. | |||
overview.accepted_issues.total=Total accepted issues | |||
overview.high_impact_accepted_issues=High impact accepted issues | |||
overview.measures=Measures | |||
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. | |||
@@ -3969,7 +3968,8 @@ overview.project.next_steps.links.set_up_ci=set up analysis in your favorite CI | |||
overview.project.software_impact.has_rating=Software Quality {softwareQuality} has rating {rating} | |||
overview.run_analysis_to_compute.TRK=Run new analysis to compute the missing data. | |||
overview.run_analysis_to_compute.APP=Analyse all projects to compute the missing data. | |||
overview.missing_project_data.APP=Some projects are missing data. All projects in the application need to be analysed to compute the missing data. | |||
overview.missing_project_data.APP=The way Security, Reliability, and Maintainability are calculated has changed. These values may change after all projects in this application have been analyzed again. {learn_more} | |||
overview.missing_project_data.TRK=The way Security, Reliability, and Maintainability are calculated has changed. These values may change after the next analysis. {learn_more} | |||
overview.coverage_on=Coverage on | |||
overview.coverage_on_X_lines=Coverage on {count} Lines to cover |