@@ -28,6 +28,7 @@ import { ComponentTree } from './components'; | |||
import { IssueData } from './issues'; | |||
import { listAllComponent, listAllComponentTrees } from './utils'; | |||
const MAX_RATING = 5; | |||
export type MeasureRecords = Record<string, Record<string, Measure>>; | |||
export function mockFullMeasureData(tree: ComponentTree, issueList: IssueData[]) { | |||
@@ -68,6 +69,21 @@ function mockComponentMeasure(tree: ComponentTree, issueList: IssueData[], metri | |||
}), | |||
}); | |||
case MetricKey.new_security_issues: | |||
return mockMeasure({ | |||
metric: metricKey, | |||
period: { | |||
index: 1, | |||
value: JSON.stringify({ | |||
total: 3, | |||
[SoftwareImpactSeverity.High]: 2, | |||
[SoftwareImpactSeverity.Medium]: 0, | |||
[SoftwareImpactSeverity.Low]: 1, | |||
}), | |||
}, | |||
value: undefined, | |||
}); | |||
case MetricKey.reliability_issues: | |||
return mockMeasure({ | |||
metric: metricKey, | |||
@@ -79,6 +95,21 @@ function mockComponentMeasure(tree: ComponentTree, issueList: IssueData[], metri | |||
}), | |||
}); | |||
case MetricKey.new_reliability_issues: | |||
return mockMeasure({ | |||
metric: metricKey, | |||
period: { | |||
index: 1, | |||
value: JSON.stringify({ | |||
total: 2, | |||
[SoftwareImpactSeverity.High]: 0, | |||
[SoftwareImpactSeverity.Medium]: 1, | |||
[SoftwareImpactSeverity.Low]: 1, | |||
}), | |||
}, | |||
value: undefined, | |||
}); | |||
case MetricKey.maintainability_issues: | |||
return mockMeasure({ | |||
metric: metricKey, | |||
@@ -89,6 +120,21 @@ function mockComponentMeasure(tree: ComponentTree, issueList: IssueData[], metri | |||
[SoftwareImpactSeverity.Low]: 1, | |||
}), | |||
}); | |||
case MetricKey.new_maintainability_issues: | |||
return mockMeasure({ | |||
metric: metricKey, | |||
period: { | |||
index: 1, | |||
value: JSON.stringify({ | |||
total: 5, | |||
[SoftwareImpactSeverity.High]: 2, | |||
[SoftwareImpactSeverity.Medium]: 2, | |||
[SoftwareImpactSeverity.Low]: 1, | |||
}), | |||
}, | |||
value: undefined, | |||
}); | |||
} | |||
const issues = issueList | |||
@@ -234,13 +280,16 @@ function mockComponentMeasure(tree: ComponentTree, issueList: IssueData[], metri | |||
export function getMetricTypeFromKey(metricKey: string) { | |||
if (/(coverage|duplication)$/.test(metricKey)) { | |||
return MetricType.Percent; | |||
} else if (/_rating$/.test(metricKey)) { | |||
} else if (metricKey.includes('_rating')) { | |||
return MetricType.Rating; | |||
} else if ( | |||
[ | |||
MetricKey.reliability_issues, | |||
MetricKey.new_reliability_issues, | |||
MetricKey.security_issues, | |||
MetricKey.new_security_issues, | |||
MetricKey.maintainability_issues, | |||
MetricKey.new_maintainability_issues, | |||
].includes(metricKey as MetricKey) | |||
) { | |||
return MetricType.Data; | |||
@@ -276,7 +325,7 @@ function isIssueRelatedRating(metricKey: MetricKey) { | |||
* ratio to the LOC. But using the number will suffice as an approximation in our tests. | |||
*/ | |||
function computeRating(issues: RawIssue[], type: IssueType) { | |||
const value = Math.max(Math.min(issues.filter((i) => i.type === type).length, 5), 1); | |||
const value = Math.max(Math.min(issues.filter((i) => i.type === type).length, MAX_RATING), 1); | |||
return { | |||
value: `${value}.0`, | |||
bestValue: value === 1, |
@@ -88,11 +88,11 @@ describe('rendering', () => { | |||
// Check one of the domains. | |||
await user.click(ui.maintainabilityDomainBtn.get()); | |||
[ | |||
'New Code Smells 8', | |||
'component_measures.metric.new_maintainability_issues.name 5', | |||
'Added Technical Debt work_duration.x_minutes.1', | |||
'Technical Debt Ratio on New Code 1.0%', | |||
'Maintainability Rating on New Code metric.has_rating_X.E', | |||
'Code Smells 8', | |||
'component_measures.metric.maintainability_issues.name 2', | |||
'Technical Debt work_duration.x_minutes.1', | |||
'Technical Debt Ratio 1.0%', | |||
'Maintainability Rating metric.has_rating_X.E', | |||
@@ -102,6 +102,32 @@ describe('rendering', () => { | |||
}); | |||
}); | |||
it('should correctly revert to old measures when analysis is missing', async () => { | |||
measuresHandler.deleteComponentMeasure('foo', MetricKey.maintainability_issues); | |||
measuresHandler.deleteComponentMeasure('foo', MetricKey.new_maintainability_issues); | |||
const { ui, user } = getPageObject(); | |||
renderMeasuresApp(); | |||
await ui.appLoaded(); | |||
// Check one of the domains. | |||
await user.click(ui.maintainabilityDomainBtn.get()); | |||
[ | |||
'component_measures.metric.new_code_smells.name 8', | |||
'Added Technical Debt work_duration.x_minutes.1', | |||
'Technical Debt Ratio on New Code 1.0%', | |||
'Maintainability Rating on New Code metric.has_rating_X.E', | |||
'component_measures.metric.code_smells.name 8', | |||
'Technical Debt work_duration.x_minutes.1', | |||
'Technical Debt Ratio 1.0%', | |||
'Maintainability Rating metric.has_rating_X.E', | |||
'Effort to Reach Maintainability Rating A work_duration.x_minutes.1', | |||
].forEach((measure) => { | |||
expect(ui.measureBtn(measure).get()).toBeInTheDocument(); | |||
}); | |||
expect(screen.getByText('overview.missing_project_data.TRK')).toBeInTheDocument(); | |||
}); | |||
it('should correctly render a list view', async () => { | |||
const { ui } = getPageObject(); | |||
renderMeasuresApp('component_measures?id=foo&metric=code_smells&view=list'); | |||
@@ -172,7 +198,17 @@ describe('rendering', () => { | |||
renderMeasuresApp('component_measures?id=foo&metric=open_issues'); | |||
await ui.appLoaded(); | |||
expect(screen.getAllByText('Issues').length).toBeGreaterThan(1); | |||
expect(screen.getAllByText('Issues').length).toEqual(1); | |||
[ | |||
'component_measures.metric.new_violations.name 1', | |||
'component_measures.metric.violations.name 1', | |||
'component_measures.metric.confirmed_issues.name 1', | |||
'component_measures.metric.accepted_issues.name 1', | |||
'component_measures.metric.new_accepted_issues.name 1', | |||
'component_measures.metric.false_positive_issues.name 1', | |||
].forEach((measure) => { | |||
expect(ui.measureBtn(measure).get()).toBeInTheDocument(); | |||
}); | |||
}); | |||
it('should render correctly if there are no measures', async () => { | |||
@@ -267,14 +303,16 @@ describe('rendering', () => { | |||
it('should correctly render a link to the activity page', async () => { | |||
const { ui, user } = getPageObject(); | |||
renderMeasuresApp('component_measures?id=foo&metric=new_code_smells'); | |||
renderMeasuresApp('component_measures?id=foo&metric=new_maintainability_issues'); | |||
await ui.appLoaded(); | |||
expect(ui.goToActivityLink.query()).not.toBeInTheDocument(); | |||
await user.click(ui.measureBtn('Code Smells 8').get()); | |||
await user.click( | |||
ui.measureBtn('component_measures.metric.maintainability_issues.name 2').get(), | |||
); | |||
expect(ui.goToActivityLink.get()).toHaveAttribute( | |||
'href', | |||
'/project/activity?id=foo&graph=custom&custom_metrics=code_smells', | |||
'/project/activity?id=foo&graph=custom&custom_metrics=maintainability_issues', | |||
); | |||
}); | |||
@@ -305,9 +343,11 @@ describe('navigation', () => { | |||
// Drilldown to the file level. | |||
await user.click(ui.maintainabilityDomainBtn.get()); | |||
await user.click(ui.measureBtn('Code Smells 8').get()); | |||
await user.click( | |||
ui.measureBtn('component_measures.metric.maintainability_issues.name 2').get(), | |||
); | |||
expect( | |||
within(ui.measuresRow('folderA').get()).getByRole('cell', { name: '3' }), | |||
within(ui.measuresRow('folderA').get()).getByRole('cell', { name: '2' }), | |||
).toBeInTheDocument(); | |||
expect( | |||
within(ui.measuresRow('test1.js').get()).getByRole('cell', { name: '2' }), | |||
@@ -315,7 +355,7 @@ describe('navigation', () => { | |||
await user.click(ui.fileLink('folderA').get()); | |||
expect( | |||
within(ui.measuresRow('out.tsx').get()).getByRole('cell', { name: '1' }), | |||
within(ui.measuresRow('out.tsx').get()).getByRole('cell', { name: '2' }), | |||
).toBeInTheDocument(); | |||
expect( | |||
within(ui.measuresRow('in.tsx').get()).getByRole('cell', { name: '2' }), | |||
@@ -336,11 +376,13 @@ describe('navigation', () => { | |||
await ui.appLoaded(); | |||
await user.click(ui.maintainabilityDomainBtn.get()); | |||
await user.click(ui.measureBtn('Code Smells 8').get()); | |||
await user.click( | |||
ui.measureBtn('component_measures.metric.maintainability_issues.name 2').get(), | |||
); | |||
await waitFor(() => ui.changeViewToList()); | |||
expect( | |||
within(await ui.measuresRow('out.tsx').find()).getByRole('cell', { name: '1' }), | |||
within(await ui.measuresRow('out.tsx').find()).getByRole('cell', { name: '2' }), | |||
).toBeInTheDocument(); | |||
expect( | |||
within(ui.measuresRow('test1.js').get()).getByRole('cell', { name: '2' }), | |||
@@ -378,13 +420,15 @@ describe('navigation', () => { | |||
// Drilldown to the file level. | |||
await user.click(ui.maintainabilityDomainBtn.get()); | |||
await user.click(ui.measureBtn('Code Smells 8').get()); | |||
await user.click( | |||
ui.measureBtn('component_measures.metric.maintainability_issues.name 2').get(), | |||
); | |||
await ui.arrowDown(); // Select the 1st element ("folderA") | |||
await ui.arrowRight(); // Open "folderA" | |||
expect( | |||
within(ui.measuresRow('out.tsx').get()).getByRole('cell', { name: '1' }), | |||
within(ui.measuresRow('out.tsx').get()).getByRole('cell', { name: '2' }), | |||
).toBeInTheDocument(); | |||
expect( | |||
within(ui.measuresRow('in.tsx').get()).getByRole('cell', { name: '2' }), | |||
@@ -394,7 +438,7 @@ describe('navigation', () => { | |||
await ui.arrowLeft(); // Close "folderA" | |||
expect( | |||
within(ui.measuresRow('folderA').get()).getByRole('cell', { name: '3' }), | |||
within(ui.measuresRow('folderA').get()).getByRole('cell', { name: '2' }), | |||
).toBeInTheDocument(); | |||
await ui.arrowRight(); // Open "folderA" | |||
@@ -416,18 +460,44 @@ describe('redirects', () => { | |||
}); | |||
it('should redirect old metric route', async () => { | |||
measuresHandler.deleteComponentMeasure('foo', MetricKey.maintainability_issues); | |||
measuresHandler.deleteComponentMeasure('foo', MetricKey.new_maintainability_issues); | |||
const { ui } = getPageObject(); | |||
renderMeasuresApp('component_measures/metric/bugs?id=foo'); | |||
await ui.appLoaded(); | |||
expect(ui.measureBtn('Bugs 0').get()).toHaveAttribute('aria-current', 'true'); | |||
expect(ui.measureBtn('component_measures.metric.bugs.name 0').get()).toHaveAttribute( | |||
'aria-current', | |||
'true', | |||
); | |||
}); | |||
it('should redirect old metric route for software qualities', async () => { | |||
const { ui } = getPageObject(); | |||
renderMeasuresApp('component_measures/metric/security_issues?id=foo'); | |||
await ui.appLoaded(); | |||
expect(ui.measureBtn('component_measures.metric.security_issues.name 1').get()).toHaveAttribute( | |||
'aria-current', | |||
'true', | |||
); | |||
}); | |||
it('should redirect old domain route', async () => { | |||
measuresHandler.deleteComponentMeasure('foo', MetricKey.maintainability_issues); | |||
measuresHandler.deleteComponentMeasure('foo', MetricKey.new_maintainability_issues); | |||
const { ui } = getPageObject(); | |||
renderMeasuresApp('component_measures/domain/bugs?id=foo'); | |||
await ui.appLoaded(); | |||
expect(ui.reliabilityDomainBtn.get()).toHaveAttribute('aria-expanded', 'true'); | |||
}); | |||
it('should redirect old domain route for software qualities', async () => { | |||
const { ui } = getPageObject(); | |||
renderMeasuresApp('component_measures/domain/reliability_issues?id=foo'); | |||
await ui.appLoaded(); | |||
expect(ui.reliabilityDomainBtn.get()).toHaveAttribute('aria-expanded', 'true'); | |||
}); | |||
}); | |||
it('should allow to load more components', async () => { |
@@ -21,6 +21,7 @@ import { withTheme } from '@emotion/react'; | |||
import styled from '@emotion/styled'; | |||
import { Spinner } from '@sonarsource/echoes-react'; | |||
import { | |||
FlagMessage, | |||
LargeCenteredLayout, | |||
Note, | |||
PageContentFontWrapper, | |||
@@ -33,12 +34,15 @@ import { Helmet } from 'react-helmet-async'; | |||
import { getMeasuresWithPeriod } from '../../../api/measures'; | |||
import { getAllMetrics } from '../../../api/metrics'; | |||
import { ComponentContext } from '../../../app/components/componentContext/ComponentContext'; | |||
import HelpTooltip from '../../../components/controls/HelpTooltip'; | |||
import Suggestions from '../../../components/embed-docs-modal/Suggestions'; | |||
import { Location, Router, withRouter } from '../../../components/hoc/withRouter'; | |||
import { enhanceMeasure } from '../../../components/measure/utils'; | |||
import '../../../components/search-navigator.css'; | |||
import AnalysisMissingInfoMessage from '../../../components/shared/AnalysisMissingInfoMessage'; | |||
import { getBranchLikeQuery, isPullRequest, isSameBranchLike } from '../../../helpers/branch-like'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import { areLeakAndOverallCCTMeasuresComputed } from '../../../helpers/measures'; | |||
import { WithBranchLikesProps, useBranchesQuery } from '../../../queries/branch'; | |||
import { ComponentQualifier, isPortfolioLike } from '../../../types/component'; | |||
import { MeasurePageView } from '../../../types/measures'; | |||
@@ -134,7 +138,10 @@ class ComponentMeasuresApp extends React.PureComponent<Props, State> { | |||
fetchMeasures(metrics: State['metrics']) { | |||
const { branchLike } = this.props; | |||
const query = parseQuery(this.props.location.query); | |||
const componentKey = query.selected || this.props.component.key; | |||
const componentKey = | |||
query.selected !== undefined && query.selected !== '' | |||
? query.selected | |||
: this.props.component.key; | |||
const filteredKeys = getMeasuresPageMetricKeys(metrics, branchLike); | |||
@@ -285,15 +292,25 @@ class ComponentMeasuresApp extends React.PureComponent<Props, State> { | |||
{measures.length > 0 ? ( | |||
<div className="sw-grid sw-grid-cols-12 sw-w-full"> | |||
<Sidebar | |||
canBrowseAllChildProjects={!!canBrowseAllChildProjects} | |||
measures={measures} | |||
qualifier={qualifier} | |||
selectedMetric={metric ? metric.key : query.metric} | |||
showFullMeasures={showFullMeasures} | |||
updateQuery={this.updateQuery} | |||
/> | |||
<div className="sw-col-span-9 sw-ml-12"> | |||
{!canBrowseAllChildProjects && isPortfolioLike(qualifier) && ( | |||
<FlagMessage className="sw-mb-4 it__portfolio_warning" variant="warning"> | |||
{translate('component_measures.not_all_measures_are_shown')} | |||
<HelpTooltip | |||
className="sw-ml-2" | |||
overlay={translate('component_measures.not_all_measures_are_shown.help')} | |||
/> | |||
</FlagMessage> | |||
)} | |||
{!areLeakAndOverallCCTMeasuresComputed(measures) && ( | |||
<AnalysisMissingInfoMessage className="sw-mb-4" qualifier={qualifier} /> | |||
)} | |||
{this.renderContent(displayOverview, query, metric)} | |||
</div> | |||
</div> |
@@ -29,7 +29,7 @@ import { getBranchLikeQuery, isSameBranchLike } from '../../../helpers/branch-li | |||
import { getComponentMeasureUniqueKey } from '../../../helpers/component'; | |||
import { KeyboardKeys } from '../../../helpers/keycodes'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import { isDiffMetric } from '../../../helpers/measures'; | |||
import { getCCTMeasureValue, isDiffMetric } from '../../../helpers/measures'; | |||
import { RequestData } from '../../../helpers/request'; | |||
import { isDefined } from '../../../helpers/types'; | |||
import { getProjectUrl } from '../../../helpers/urls'; | |||
@@ -94,8 +94,14 @@ export default class MeasureContent extends React.PureComponent<Props, State> { | |||
} | |||
componentDidUpdate(prevProps: Props) { | |||
const prevComponentKey = prevProps.selected || prevProps.rootComponent.key; | |||
const componentKey = this.props.selected || this.props.rootComponent.key; | |||
const prevComponentKey = | |||
prevProps.selected !== undefined && prevProps.selected !== '' | |||
? prevProps.selected | |||
: prevProps.rootComponent.key; | |||
const componentKey = | |||
this.props.selected !== undefined && this.props.selected !== '' | |||
? this.props.selected | |||
: this.props.rootComponent.key; | |||
if ( | |||
prevComponentKey !== componentKey || | |||
!isSameBranchLike(prevProps.branchLike, this.props.branchLike) || | |||
@@ -116,7 +122,7 @@ export default class MeasureContent extends React.PureComponent<Props, State> { | |||
const { metricKeys, opts, strategy } = this.getComponentRequestParams(view, requestedMetric, { | |||
...(asc !== undefined && { asc }), | |||
}); | |||
const componentKey = selected || rootComponent.key; | |||
const componentKey = selected !== undefined && selected !== '' ? selected : rootComponent.key; | |||
const baseComponentMetrics = [requestedMetric.key]; | |||
if (requestedMetric.key === MetricKey.ncloc) { | |||
baseComponentMetrics.push(MetricKey.ncloc_language_distribution); | |||
@@ -347,8 +353,10 @@ export default class MeasureContent extends React.PureComponent<Props, State> { | |||
return null; | |||
} | |||
const measureValue = | |||
const rawMeasureValue = | |||
measure && (isDiffMetric(measure.metric) ? measure.period?.value : measure.value); | |||
const measureValue = getCCTMeasureValue(metric.key, rawMeasureValue); | |||
const isFileComponent = isFile(baseComponent.qualifier); | |||
const selectedIdx = this.getSelectedIndex(); | |||
@@ -17,8 +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 { LinkStandalone } from '@sonarsource/echoes-react'; | |||
import classNames from 'classnames'; | |||
import { Link, MetricsLabel, MetricsRatingBadge } from 'design-system'; | |||
import { MetricsLabel, MetricsRatingBadge } from 'design-system'; | |||
import * as React from 'react'; | |||
import LanguageDistribution from '../../../components/charts/LanguageDistribution'; | |||
import Tooltip from '../../../components/controls/Tooltip'; | |||
@@ -30,7 +31,7 @@ import { BranchLike } from '../../../types/branch-like'; | |||
import { ComponentQualifier } from '../../../types/component'; | |||
import { MetricKey, MetricType } from '../../../types/metrics'; | |||
import { ComponentMeasure, Metric, Period, Measure as TypeMeasure } from '../../../types/types'; | |||
import { hasFullMeasures } from '../utils'; | |||
import { getMetricSubnavigationName, hasFullMeasures } from '../utils'; | |||
import LeakPeriodLegend from './LeakPeriodLegend'; | |||
interface Props { | |||
@@ -42,7 +43,7 @@ interface Props { | |||
secondaryMeasure?: TypeMeasure; | |||
} | |||
export default function MeasureHeader(props: Props) { | |||
export default function MeasureHeader(props: Readonly<Props>) { | |||
const { branchLike, component, leakPeriod, measureValue, metric, secondaryMeasure } = props; | |||
const isDiff = isDiffMetric(metric.key); | |||
const hasHistory = | |||
@@ -53,11 +54,13 @@ export default function MeasureHeader(props: Props) { | |||
ComponentQualifier.Project, | |||
].includes(component.qualifier as ComponentQualifier) && hasFullMeasures(branchLike); | |||
const displayLeak = hasFullMeasures(branchLike); | |||
const title = getMetricSubnavigationName(metric, getLocalizedMetricName, isDiff); | |||
return ( | |||
<div className="sw-mb-4"> | |||
<div className="sw-flex sw-items-center sw-justify-between sw-gap-4"> | |||
<div className="it__measure-details-metric sw-flex sw-items-center sw-gap-1"> | |||
<strong className="sw-body-md-highlight">{getLocalizedMetricName(metric)}</strong> | |||
<strong className="sw-body-md-highlight">{title}</strong> | |||
<div className="sw-flex sw-items-center sw-ml-2"> | |||
<Measure | |||
@@ -84,12 +87,12 @@ export default function MeasureHeader(props: Props) { | |||
{!isDiff && hasHistory && ( | |||
<Tooltip overlay={translate('component_measures.show_metric_history')}> | |||
<span className="sw-ml-4"> | |||
<Link | |||
<LinkStandalone | |||
className="it__show-history-link sw-font-semibold" | |||
to={getMeasureHistoryUrl(component.key, metric.key, branchLike)} | |||
> | |||
{translate('component_measures.see_metric_history')} | |||
</Link> | |||
</LinkStandalone> | |||
</span> | |||
</Tooltip> | |||
)} |
@@ -23,16 +23,21 @@ interface Domains { | |||
[domain: string]: { categories?: string[]; order: string[] }; | |||
} | |||
const NEW_CODE_CATEGORY = 'new_code_category'; | |||
const OVERALL_CATEGORY = 'overall_category'; | |||
export const domains: Domains = { | |||
Reliability: { | |||
categories: ['new_code_category', 'overall_category'], | |||
categories: [NEW_CODE_CATEGORY, OVERALL_CATEGORY], | |||
order: [ | |||
'new_code_category', | |||
NEW_CODE_CATEGORY, | |||
MetricKey.new_reliability_issues, | |||
MetricKey.new_bugs, | |||
MetricKey.new_reliability_rating, | |||
MetricKey.new_reliability_remediation_effort, | |||
'overall_category', | |||
OVERALL_CATEGORY, | |||
MetricKey.reliability_issues, | |||
MetricKey.bugs, | |||
MetricKey.reliability_rating, | |||
MetricKey.reliability_remediation_effort, | |||
@@ -40,14 +45,16 @@ export const domains: Domains = { | |||
}, | |||
Security: { | |||
categories: ['new_code_category', 'overall_category'], | |||
categories: [NEW_CODE_CATEGORY, OVERALL_CATEGORY], | |||
order: [ | |||
'new_code_category', | |||
NEW_CODE_CATEGORY, | |||
MetricKey.new_security_issues, | |||
MetricKey.new_vulnerabilities, | |||
MetricKey.new_security_rating, | |||
MetricKey.new_security_remediation_effort, | |||
'overall_category', | |||
OVERALL_CATEGORY, | |||
MetricKey.security_issues, | |||
MetricKey.vulnerabilities, | |||
MetricKey.security_rating, | |||
MetricKey.security_remediation_effort, | |||
@@ -55,14 +62,14 @@ export const domains: Domains = { | |||
}, | |||
SecurityReview: { | |||
categories: ['new_code_category', 'overall_category'], | |||
categories: [NEW_CODE_CATEGORY, OVERALL_CATEGORY], | |||
order: [ | |||
'new_code_category', | |||
NEW_CODE_CATEGORY, | |||
MetricKey.new_security_hotspots, | |||
MetricKey.new_security_review_rating, | |||
MetricKey.new_security_hotspots_reviewed, | |||
'overall_category', | |||
OVERALL_CATEGORY, | |||
MetricKey.security_hotspots, | |||
MetricKey.security_review_rating, | |||
MetricKey.security_hotspots_reviewed, | |||
@@ -70,15 +77,17 @@ export const domains: Domains = { | |||
}, | |||
Maintainability: { | |||
categories: ['new_code_category', 'overall_category'], | |||
categories: [NEW_CODE_CATEGORY, OVERALL_CATEGORY], | |||
order: [ | |||
'new_code_category', | |||
NEW_CODE_CATEGORY, | |||
MetricKey.new_maintainability_issues, | |||
MetricKey.new_code_smells, | |||
MetricKey.new_technical_debt, | |||
MetricKey.new_sqale_debt_ratio, | |||
MetricKey.new_maintainability_rating, | |||
'overall_category', | |||
OVERALL_CATEGORY, | |||
MetricKey.maintainability_issues, | |||
MetricKey.code_smells, | |||
MetricKey.sqale_index, | |||
MetricKey.sqale_debt_ratio, | |||
@@ -88,9 +97,9 @@ export const domains: Domains = { | |||
}, | |||
Coverage: { | |||
categories: ['new_code_category', 'overall_category', 'tests_category'], | |||
categories: [NEW_CODE_CATEGORY, OVERALL_CATEGORY, 'tests_category'], | |||
order: [ | |||
'new_code_category', | |||
NEW_CODE_CATEGORY, | |||
MetricKey.new_coverage, | |||
MetricKey.new_lines_to_cover, | |||
MetricKey.new_uncovered_lines, | |||
@@ -99,7 +108,7 @@ export const domains: Domains = { | |||
MetricKey.new_uncovered_conditions, | |||
MetricKey.new_branch_coverage, | |||
'overall_category', | |||
OVERALL_CATEGORY, | |||
MetricKey.coverage, | |||
MetricKey.lines_to_cover, | |||
MetricKey.uncovered_lines, | |||
@@ -119,14 +128,14 @@ export const domains: Domains = { | |||
}, | |||
Duplications: { | |||
categories: ['new_code_category', 'overall_category'], | |||
categories: [NEW_CODE_CATEGORY, OVERALL_CATEGORY], | |||
order: [ | |||
'new_code_category', | |||
NEW_CODE_CATEGORY, | |||
MetricKey.new_duplicated_lines_density, | |||
MetricKey.new_duplicated_lines, | |||
MetricKey.new_duplicated_blocks, | |||
'overall_category', | |||
OVERALL_CATEGORY, | |||
MetricKey.duplicated_lines_density, | |||
MetricKey.duplicated_lines, | |||
MetricKey.duplicated_blocks, | |||
@@ -157,23 +166,16 @@ export const domains: Domains = { | |||
}, | |||
Issues: { | |||
categories: [NEW_CODE_CATEGORY, OVERALL_CATEGORY], | |||
order: [ | |||
NEW_CODE_CATEGORY, | |||
MetricKey.new_violations, | |||
MetricKey.new_blocker_violations, | |||
MetricKey.new_critical_violations, | |||
MetricKey.new_major_violations, | |||
MetricKey.new_minor_violations, | |||
MetricKey.new_info_violations, | |||
MetricKey.new_accepted_issues, | |||
OVERALL_CATEGORY, | |||
MetricKey.violations, | |||
MetricKey.blocker_violations, | |||
MetricKey.critical_violations, | |||
MetricKey.major_violations, | |||
MetricKey.minor_violations, | |||
MetricKey.info_violations, | |||
MetricKey.open_issues, | |||
MetricKey.reopened_issues, | |||
MetricKey.confirmed_issues, | |||
MetricKey.accepted_issues, | |||
MetricKey.false_positive_issues, | |||
], | |||
}, |
@@ -21,7 +21,7 @@ import { MetricsLabel, MetricsRatingBadge, NumericalCell } from 'design-system'; | |||
import * as React from 'react'; | |||
import Measure from '../../../components/measure/Measure'; | |||
import { translate, translateWithParameters } from '../../../helpers/l10n'; | |||
import { formatMeasure, isDiffMetric } from '../../../helpers/measures'; | |||
import { formatMeasure, getCCTMeasureValue, isDiffMetric } from '../../../helpers/measures'; | |||
import { MetricType } from '../../../types/metrics'; | |||
import { ComponentMeasureEnhanced, MeasureEnhanced, Metric } from '../../../types/types'; | |||
@@ -35,7 +35,8 @@ export default function MeasureCell({ component, measure, metric }: Props) { | |||
const getValue = (item: { leak?: string; value?: string }) => | |||
isDiffMetric(metric.key) ? item.leak : item.value; | |||
const value = getValue(measure || component); | |||
const rawValue = getValue(measure || component); | |||
const value = getCCTMeasureValue(metric.key, rawValue); | |||
return ( | |||
<NumericalCell className="sw-py-3"> |
@@ -34,7 +34,12 @@ import { | |||
translate, | |||
} from '../../../helpers/l10n'; | |||
import { MeasureEnhanced } from '../../../types/types'; | |||
import { addMeasureCategories, hasBubbleChart, sortMeasures } from '../utils'; | |||
import { | |||
addMeasureCategories, | |||
getMetricSubnavigationName, | |||
hasBubbleChart, | |||
sortMeasures, | |||
} from '../utils'; | |||
import DomainSubnavigationItem from './DomainSubnavigationItem'; | |||
interface Props { | |||
@@ -45,7 +50,7 @@ interface Props { | |||
showFullMeasures: boolean; | |||
} | |||
export default function DomainSubnavigation(props: Props) { | |||
export default function DomainSubnavigation(props: Readonly<Props>) { | |||
const { domain, onChange, open, selected, showFullMeasures } = props; | |||
const helperMessageKey = `component_measures.domain_subnavigation.${domain.name}.help`; | |||
const helper = hasMessage(helperMessageKey) ? translate(helperMessageKey) : undefined; | |||
@@ -100,7 +105,7 @@ export default function DomainSubnavigation(props: Props) { | |||
<DomainSubnavigationItem | |||
key={item.metric.key} | |||
measure={item} | |||
name={translateMetric(item.metric)} | |||
name={getMetricSubnavigationName(item.metric, translateMetric)} | |||
onChange={onChange} | |||
selected={selected} | |||
/> |
@@ -29,7 +29,12 @@ interface Props { | |||
selected: string; | |||
} | |||
export default function DomainSubnavigationItem({ measure, name, onChange, selected }: Props) { | |||
export default function DomainSubnavigationItem({ | |||
measure, | |||
name, | |||
onChange, | |||
selected, | |||
}: Readonly<Props>) { | |||
const { key } = measure.metric; | |||
return ( | |||
<SubnavigationItem active={key === selected} key={key} onClick={onChange} value={key}> |
@@ -21,7 +21,6 @@ import { withTheme } from '@emotion/react'; | |||
import styled from '@emotion/styled'; | |||
import { | |||
BareButton, | |||
FlagMessage, | |||
LAYOUT_FOOTER_HEIGHT, | |||
LAYOUT_GLOBAL_NAV_HEIGHT, | |||
LAYOUT_PROJECT_NAV_HEIGHT, | |||
@@ -32,33 +31,24 @@ import { | |||
} from 'design-system'; | |||
import * as React from 'react'; | |||
import A11ySkipTarget from '../../../components/a11y/A11ySkipTarget'; | |||
import HelpTooltip from '../../../components/controls/HelpTooltip'; | |||
import { translate } from '../../../helpers/l10n'; | |||
import useFollowScroll from '../../../hooks/useFollowScroll'; | |||
import { isPortfolioLike } from '../../../types/component'; | |||
import { MeasureEnhanced } from '../../../types/types'; | |||
import { PROJECT_OVERVEW, Query, groupByDomains, isProjectOverview } from '../utils'; | |||
import { PROJECT_OVERVEW, Query, isProjectOverview, populateDomainsFromMeasures } from '../utils'; | |||
import DomainSubnavigation from './DomainSubnavigation'; | |||
import { Domain } from '../../../types/measures'; | |||
interface Props { | |||
canBrowseAllChildProjects: boolean; | |||
measures: MeasureEnhanced[]; | |||
qualifier: string; | |||
selectedMetric: string; | |||
showFullMeasures: boolean; | |||
updateQuery: (query: Partial<Query>) => void; | |||
} | |||
export default function Sidebar(props: Props) { | |||
const { | |||
showFullMeasures, | |||
canBrowseAllChildProjects, | |||
qualifier, | |||
updateQuery, | |||
selectedMetric, | |||
measures, | |||
} = props; | |||
export default function Sidebar(props: Readonly<Props>) { | |||
const { showFullMeasures, updateQuery, selectedMetric, measures } = props; | |||
const { top: topScroll, scrolledOnce } = useFollowScroll(); | |||
const domains = populateDomainsFromMeasures(measures); | |||
const handleChangeMetric = React.useCallback( | |||
(metric: string) => { | |||
@@ -89,15 +79,6 @@ export default function Sidebar(props: Props) { | |||
)`, | |||
}} | |||
> | |||
{!canBrowseAllChildProjects && isPortfolioLike(qualifier) && ( | |||
<FlagMessage className="sw-mt-4 it__portfolio_warning" variant="warning"> | |||
{translate('component_measures.not_all_measures_are_shown')} | |||
<HelpTooltip | |||
className="sw-ml-2" | |||
overlay={translate('component_measures.not_all_measures_are_shown.help')} | |||
/> | |||
</FlagMessage> | |||
)} | |||
<section | |||
className="sw-flex sw-flex-col sw-gap-4 sw-p-4" | |||
aria-label={translate('component_measures.navigation')} | |||
@@ -118,7 +99,7 @@ export default function Sidebar(props: Props) { | |||
</SubnavigationItem> | |||
</SubnavigationGroup> | |||
{groupByDomains(measures).map((domain: Domain) => ( | |||
{domains.map((domain: Domain) => ( | |||
<DomainSubnavigation | |||
domain={domain} | |||
key={domain.name} | |||
@@ -133,11 +114,6 @@ export default function Sidebar(props: Props) { | |||
); | |||
} | |||
interface Domain { | |||
measures: MeasureEnhanced[]; | |||
name: string; | |||
} | |||
function isDomainSelected(selectedMetric: string, domain: Domain) { | |||
return ( | |||
selectedMetric === domain.name || |
@@ -29,7 +29,7 @@ interface Props { | |||
measure: MeasureEnhanced; | |||
} | |||
export default function SubnavigationMeasureValue({ measure }: Props) { | |||
export default function SubnavigationMeasureValue({ measure }: Readonly<Props>) { | |||
const isDiff = isDiffMetric(measure.metric.key); | |||
const value = isDiff ? measure.leak : measure.value; | |||
const formatted = formatMeasure(value, MetricType.Rating); |
@@ -20,9 +20,22 @@ | |||
import { groupBy, memoize, sortBy, toPairs } from 'lodash'; | |||
import { enhanceMeasure } from '../../components/measure/utils'; | |||
import { isBranch, isPullRequest } from '../../helpers/branch-like'; | |||
import { HIDDEN_METRICS } from '../../helpers/constants'; | |||
import { getLocalizedMetricName } from '../../helpers/l10n'; | |||
import { MEASURES_REDIRECTION, getDisplayMetrics, isDiffMetric } from '../../helpers/measures'; | |||
import { | |||
CCT_SOFTWARE_QUALITY_METRICS, | |||
HIDDEN_METRICS, | |||
LEAK_CCT_SOFTWARE_QUALITY_METRICS, | |||
LEAK_OLD_TAXONOMY_METRICS, | |||
OLD_TAXONOMY_METRICS, | |||
} from '../../helpers/constants'; | |||
import { getLocalizedMetricName, translate } from '../../helpers/l10n'; | |||
import { | |||
MEASURES_REDIRECTION, | |||
areLeakCCTMeasuresComputed, | |||
areCCTMeasuresComputed, | |||
getDisplayMetrics, | |||
isDiffMetric, | |||
getCCTMeasureValue, | |||
} from '../../helpers/measures'; | |||
import { | |||
cleanQuery, | |||
parseAsOptionalBoolean, | |||
@@ -31,7 +44,7 @@ import { | |||
} from '../../helpers/query'; | |||
import { BranchLike } from '../../types/branch-like'; | |||
import { ComponentQualifier } from '../../types/component'; | |||
import { MeasurePageView } from '../../types/measures'; | |||
import { Domain, MeasurePageView } from '../../types/measures'; | |||
import { MetricKey, MetricType } from '../../types/metrics'; | |||
import { | |||
ComponentMeasure, | |||
@@ -51,16 +64,103 @@ export const DEFAULT_VIEW = MeasurePageView.tree; | |||
export const DEFAULT_METRIC = PROJECT_OVERVEW; | |||
export const KNOWN_DOMAINS = [ | |||
'Releasability', | |||
'Reliability', | |||
'Security', | |||
'SecurityReview', | |||
'Reliability', | |||
'Maintainability', | |||
'SecurityReview', | |||
'Coverage', | |||
'Duplications', | |||
'Size', | |||
'Complexity', | |||
]; | |||
const CCT_METRIC_DOMAIN_MAP: Dict<string> = { | |||
[MetricKey.security_issues]: 'Security', | |||
[MetricKey.new_security_issues]: 'Security', | |||
[MetricKey.reliability_issues]: 'Reliability', | |||
[MetricKey.new_reliability_issues]: 'Reliability', | |||
[MetricKey.maintainability_issues]: 'Maintainability', | |||
[MetricKey.new_maintainability_issues]: 'Maintainability', | |||
}; | |||
const DEPRECATED_METRICS = [ | |||
MetricKey.blocker_violations, | |||
MetricKey.new_blocker_violations, | |||
MetricKey.critical_violations, | |||
MetricKey.new_critical_violations, | |||
MetricKey.major_violations, | |||
MetricKey.new_major_violations, | |||
MetricKey.info_violations, | |||
MetricKey.new_info_violations, | |||
MetricKey.minor_violations, | |||
MetricKey.new_minor_violations, | |||
MetricKey.high_impact_accepted_issues, | |||
]; | |||
const ISSUES_METRICS = [ | |||
MetricKey.accepted_issues, | |||
MetricKey.new_accepted_issues, | |||
MetricKey.confirmed_issues, | |||
MetricKey.false_positive_issues, | |||
MetricKey.violations, | |||
MetricKey.new_violations, | |||
]; | |||
export const populateDomainsFromMeasures = memoize((measures: MeasureEnhanced[]): Domain[] => { | |||
let populatedMeasures = measures | |||
.filter((measure) => !DEPRECATED_METRICS.includes(measure.metric.key as MetricKey)) | |||
.map((measure) => { | |||
const isDiff = isDiffMetric(measure.metric.key); | |||
const calculatedValue = getCCTMeasureValue( | |||
measure.metric.key, | |||
isDiff ? measure.leak : measure.value, | |||
); | |||
return { | |||
...measure, | |||
metric: { | |||
...measure.metric, | |||
domain: CCT_METRIC_DOMAIN_MAP[measure.metric.key] ?? measure.metric.domain, | |||
}, | |||
...(!isDiff && { value: calculatedValue }), | |||
...(isDiff && { leak: calculatedValue }), | |||
}; | |||
}); | |||
if (areLeakCCTMeasuresComputed(measures)) { | |||
populatedMeasures = populatedMeasures.filter( | |||
(measure) => !LEAK_OLD_TAXONOMY_METRICS.includes(measure.metric.key as MetricKey), | |||
); | |||
} | |||
if (areCCTMeasuresComputed(measures)) { | |||
populatedMeasures = populatedMeasures.filter( | |||
(measure) => !OLD_TAXONOMY_METRICS.includes(measure.metric.key as MetricKey), | |||
); | |||
} | |||
return groupByDomains(populatedMeasures); | |||
}); | |||
export function getMetricSubnavigationName( | |||
metric: Metric, | |||
translateFn: (metric: Metric) => string, | |||
isDiff = false, | |||
) { | |||
if ( | |||
[ | |||
...LEAK_CCT_SOFTWARE_QUALITY_METRICS, | |||
...CCT_SOFTWARE_QUALITY_METRICS, | |||
...ISSUES_METRICS, | |||
...OLD_TAXONOMY_METRICS, | |||
...LEAK_OLD_TAXONOMY_METRICS, | |||
].includes(metric.key as MetricKey) | |||
) { | |||
return translate( | |||
`component_measures.metric.${metric.key}.${isDiff ? 'detailed_name' : 'name'}`, | |||
); | |||
} | |||
return translateFn(metric); | |||
} | |||
export function filterMeasures(measures: MeasureEnhanced[]): MeasureEnhanced[] { | |||
return measures.filter((measure) => !HIDDEN_METRICS.includes(measure.metric.key as MetricKey)); | |||
} |
@@ -92,12 +92,24 @@ export const CCT_SOFTWARE_QUALITY_METRICS = [ | |||
MetricKey.maintainability_issues, | |||
]; | |||
export const LEAK_CCT_SOFTWARE_QUALITY_METRICS = [ | |||
MetricKey.new_security_issues, | |||
MetricKey.new_reliability_issues, | |||
MetricKey.new_maintainability_issues, | |||
]; | |||
export const OLD_TAXONOMY_METRICS = [ | |||
MetricKey.vulnerabilities, | |||
MetricKey.bugs, | |||
MetricKey.code_smells, | |||
]; | |||
export const LEAK_OLD_TAXONOMY_METRICS = [ | |||
MetricKey.new_vulnerabilities, | |||
MetricKey.new_bugs, | |||
MetricKey.new_code_smells, | |||
]; | |||
export const OLD_TO_NEW_TAXONOMY_METRICS_MAP: { [key in MetricKey]?: MetricKey } = { | |||
[MetricKey.vulnerabilities]: MetricKey.security_issues, | |||
[MetricKey.bugs]: MetricKey.reliability_issues, |
@@ -23,7 +23,11 @@ import { | |||
QualityGateStatusConditionEnhanced, | |||
} from '../types/quality-gates'; | |||
import { Dict, Measure, MeasureEnhanced, Metric } from '../types/types'; | |||
import { CCT_SOFTWARE_QUALITY_METRICS, ONE_SECOND } from './constants'; | |||
import { | |||
CCT_SOFTWARE_QUALITY_METRICS, | |||
LEAK_CCT_SOFTWARE_QUALITY_METRICS, | |||
ONE_SECOND, | |||
} from './constants'; | |||
import { translate, translateWithParameters } from './l10n'; | |||
import { getCurrentLocale } from './l10nBundle'; | |||
import { isDefined } from './types'; | |||
@@ -72,13 +76,28 @@ export function isDiffMetric(metricKey: MetricKey | string): boolean { | |||
} | |||
export function getDisplayMetrics(metrics: Metric[]) { | |||
return metrics.filter((metric) => !metric.hidden && !['DATA', 'DISTRIB'].includes(metric.type)); | |||
return metrics.filter( | |||
(metric) => | |||
!metric.hidden && | |||
([...CCT_SOFTWARE_QUALITY_METRICS, ...LEAK_CCT_SOFTWARE_QUALITY_METRICS].includes( | |||
metric.key as MetricKey, | |||
) || | |||
![MetricType.Data, MetricType.Distribution].includes(metric.type as MetricType)), | |||
); | |||
} | |||
export function findMeasure(measures: MeasureEnhanced[], metric: MetricKey | string) { | |||
return measures.find((measure) => measure.metric.key === metric); | |||
} | |||
export function areLeakCCTMeasuresComputed(measures?: Measure[] | MeasureEnhanced[]) { | |||
return LEAK_CCT_SOFTWARE_QUALITY_METRICS.every((metric) => | |||
measures?.find((measure) => | |||
isMeasureEnhanced(measure) ? measure.metric.key === metric : measure.metric === metric, | |||
), | |||
); | |||
} | |||
export function areCCTMeasuresComputed(measures?: Measure[] | MeasureEnhanced[]) { | |||
return CCT_SOFTWARE_QUALITY_METRICS.every((metric) => | |||
measures?.find((measure) => | |||
@@ -87,10 +106,26 @@ export function areCCTMeasuresComputed(measures?: Measure[] | MeasureEnhanced[]) | |||
); | |||
} | |||
export function areLeakAndOverallCCTMeasuresComputed(measures?: Measure[] | MeasureEnhanced[]) { | |||
return areLeakCCTMeasuresComputed(measures) && areCCTMeasuresComputed(measures); | |||
} | |||
function isMeasureEnhanced(measure: Measure | MeasureEnhanced): measure is MeasureEnhanced { | |||
return (measure.metric as Metric)?.key !== undefined; | |||
} | |||
export const getCCTMeasureValue = (key: string, value?: string) => { | |||
if ( | |||
CCT_SOFTWARE_QUALITY_METRICS.concat(LEAK_CCT_SOFTWARE_QUALITY_METRICS).includes( | |||
key as MetricKey, | |||
) && | |||
value !== undefined | |||
) { | |||
return JSON.parse(value).total; | |||
} | |||
return value; | |||
}; | |||
const HOURS_IN_DAY = 8; | |||
type Formatter = (value: string | number, options?: Dict<unknown>) => string; |
@@ -912,11 +912,20 @@ export const DEFAULT_METRICS: Dict<Metric> = { | |||
}, | |||
reliability_issues: { | |||
key: 'reliability_issues', | |||
type: 'INT', | |||
type: 'DATA', | |||
name: 'Reliability', | |||
description: 'Reliability issues', | |||
direction: -1, | |||
qualitative: true, | |||
direction: 0, | |||
qualitative: false, | |||
hidden: false, | |||
}, | |||
new_reliability_issues: { | |||
key: 'new_reliability_issues', | |||
type: 'DATA', | |||
name: 'New Reliability', | |||
description: 'New Reliability issues', | |||
direction: 0, | |||
qualitative: false, | |||
hidden: false, | |||
}, | |||
reliability_rating: { | |||
@@ -1023,11 +1032,21 @@ export const DEFAULT_METRICS: Dict<Metric> = { | |||
}, | |||
security_issues: { | |||
key: 'security_issues', | |||
type: 'INT', | |||
type: 'DATA', | |||
name: 'Security', | |||
description: 'Security issues', | |||
direction: -1, | |||
qualitative: true, | |||
direction: 0, | |||
qualitative: false, | |||
hidden: false, | |||
}, | |||
new_security_issues: { | |||
key: 'new_security_issues', | |||
type: 'DATA', | |||
name: 'Security', | |||
description: 'New Security issues', | |||
domain: 'Issues', | |||
direction: 0, | |||
qualitative: false, | |||
hidden: false, | |||
}, | |||
security_rating: { | |||
@@ -1192,11 +1211,22 @@ export const DEFAULT_METRICS: Dict<Metric> = { | |||
}, | |||
maintainability_issues: { | |||
key: 'maintainability_issues', | |||
type: 'INT', | |||
type: 'DATA', | |||
name: 'Maintainability', | |||
description: 'Maintainability issues', | |||
direction: -1, | |||
qualitative: true, | |||
domain: 'Issues', | |||
direction: 0, | |||
qualitative: false, | |||
hidden: false, | |||
}, | |||
new_maintainability_issues: { | |||
key: 'new_maintainability_issues', | |||
type: 'DATA', | |||
name: 'Maintainability', | |||
description: 'New Maintainability issues', | |||
domain: 'Issues', | |||
direction: 0, | |||
qualitative: false, | |||
hidden: false, | |||
}, | |||
sqale_index: { | |||
@@ -1389,4 +1419,14 @@ export const DEFAULT_METRICS: Dict<Metric> = { | |||
qualitative: false, | |||
hidden: false, | |||
}, | |||
new_accepted_issues: { | |||
key: 'new_accepted_issues', | |||
type: 'INT', | |||
name: 'New Accepted Issues', | |||
description: 'New Accepted issues', | |||
domain: 'Issues', | |||
direction: -1, | |||
qualitative: false, | |||
hidden: false, | |||
}, | |||
}; |
@@ -17,7 +17,7 @@ | |||
* along with this program; if not, write to the Free Software Foundation, | |||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | |||
*/ | |||
import { ComponentMeasure, Metric, Period, PeriodMeasure } from './types'; | |||
import { ComponentMeasure, MeasureEnhanced, Metric, Period, PeriodMeasure } from './types'; | |||
export interface MeasuresForProjects { | |||
component: string; | |||
@@ -41,3 +41,8 @@ export enum MeasurePageView { | |||
tree = 'tree', | |||
treemap = 'treemap', | |||
} | |||
export interface Domain { | |||
measures: MeasureEnhanced[]; | |||
name: string; | |||
} |
@@ -93,15 +93,18 @@ export enum MetricKey { | |||
new_line_coverage = 'new_line_coverage', | |||
new_lines = 'new_lines', | |||
new_lines_to_cover = 'new_lines_to_cover', | |||
new_maintainability_issues = 'new_maintainability_issues', | |||
new_maintainability_rating = 'new_maintainability_rating', | |||
new_maintainability_rating_distribution = 'new_maintainability_rating_distribution', | |||
new_major_violations = 'new_major_violations', | |||
new_minor_violations = 'new_minor_violations', | |||
new_reliability_issues = 'new_reliability_issues', | |||
new_reliability_rating = 'new_reliability_rating', | |||
new_reliability_remediation_effort = 'new_reliability_remediation_effort', | |||
new_reliability_rating_distribution = 'new_reliability_rating_distribution', | |||
new_security_hotspots = 'new_security_hotspots', | |||
new_security_hotspots_reviewed = 'new_security_hotspots_reviewed', | |||
new_security_issues = 'new_security_issues', | |||
new_security_rating = 'new_security_rating', | |||
new_security_rating_distribution = 'new_security_rating_distribution', | |||
new_security_remediation_effort = 'new_security_remediation_effort', |
@@ -4180,6 +4180,33 @@ component_measures.bubble_chart.zoom_level=Current zoom level. Scroll on the cha | |||
component_measures.not_all_measures_are_shown=Not all projects and applications are included | |||
component_measures.not_all_measures_are_shown.help=You do not have access to all projects and/or applications. Measures are still computed based on all projects and applications. | |||
component_measures.metric.new_security_issues.name=Issues | |||
component_measures.metric.new_security_issues.detailed_name=New Issues | |||
component_measures.metric.new_vulnerabilities.name=Issues | |||
component_measures.metric.new_vulnerabilities.detailed_name=New Issues | |||
component_measures.metric.new_reliability_issues.name=Issues | |||
component_measures.metric.new_reliability_issues.detailed_name=New Issues | |||
component_measures.metric.new_maintainability_issues.name=Issues | |||
component_measures.metric.new_maintainability_issues.detailed_name=New Issues | |||
component_measures.metric.new_code_smells.name=Issues | |||
component_measures.metric.new_code_smells.detailed_name=New Issues | |||
component_measures.metric.new_violations.name=Open Issues | |||
component_measures.metric.new_violations.detailed_name=New Open Issues | |||
component_measures.metric.new_accepted_issues.name=Accepted Issues | |||
component_measures.metric.new_accepted_issues.detailed_name=New Accepted Issues | |||
component_measures.metric.new_bugs.name=Issues | |||
component_measures.metric.new_bugs.detailed_name=New Issues | |||
component_measures.metric.security_issues.name=Issues | |||
component_measures.metric.vulnerabilities.name=Issues | |||
component_measures.metric.reliability_issues.name=Issues | |||
component_measures.metric.bugs.name=Issues | |||
component_measures.metric.maintainability_issues.name=Issues | |||
component_measures.metric.code_smells.name=Issues | |||
component_measures.metric.violations.name=Open Issues | |||
component_measures.metric.accepted_issues.name=Accepted Issues | |||
component_measures.metric.confirmed_issues.name=Confirmed Issues | |||
component_measures.metric.false_positive_issues.name=False Positive Issues | |||
#------------------------------------------------------------------------------ | |||
# | |||
# DOCS |