Browse Source

SONAR-12256 Consistent rounding in project overview

tags/8.0
Jeremy Davis 4 years ago
parent
commit
cedf9b4c89

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

@@ -0,0 +1,68 @@
/*
* SonarQube
* Copyright (C) 2009-2019 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 {
mockMeasureEnhanced,
mockMetric,
mockQualityGateStatusCondition
} from '../../../helpers/testMocks';
import { getThreshold } from '../utils';

describe('getThreshold', () => {
it('return undefined if condition is not found', () => {
expect(getThreshold([], '')).toBeUndefined();
expect(getThreshold([mockMeasure()], '')).toBeUndefined();
expect(
getThreshold(
[
{
metric: mockMetric({ key: 'quality_gate_details' }),
value: 'badly typed json should fail'
}
],
''
)
).toBeUndefined();
});

it('should return the threshold for the right metric', () => {
expect(getThreshold([mockMeasure()], 'new_coverage')).toBe(85);
expect(getThreshold([mockMeasure()], 'new_duplicated_lines_density')).toBe(5);
});
});

function mockMeasure() {
return mockMeasureEnhanced({
metric: mockMetric({ key: 'quality_gate_details' }),
value: JSON.stringify({
conditions: [
mockQualityGateStatusCondition({
metric: 'new_coverage',
level: 'ERROR',
error: '85'
}),
mockQualityGateStatusCondition({
metric: 'new_duplicated_lines_density',
level: 'WARNING',
warning: '5'
})
]
})
});
}

+ 11
- 3
server/sonar-web/src/main/js/apps/overview/main/Coverage.tsx View File

@@ -19,12 +19,15 @@
*/
import * as React from 'react';
import { translate } from 'sonar-ui-common/helpers/l10n';
import { formatMeasure } from 'sonar-ui-common/helpers/measures';
import {
formatMeasure,
getMinDecimalsCountToBeDistinctFromThreshold
} from 'sonar-ui-common/helpers/measures';
import DocTooltip from '../../../components/docs/DocTooltip';
import DrilldownLink from '../../../components/shared/DrilldownLink';
import CoverageRating from '../../../components/ui/CoverageRating';
import { getPeriodValue } from '../../../helpers/measures';
import { getMetricName } from '../utils';
import { getMetricName, getThreshold } from '../utils';
import enhance, { ComposedProps } from './enhance';

export class Coverage extends React.PureComponent<ComposedProps> {
@@ -102,7 +105,12 @@ export class Coverage extends React.PureComponent<ComposedProps> {
component={component.key}
metric={newCoverageMeasure.metric.key}>
<span className="js-overview-main-new-coverage">
{formatMeasure(newCoverageValue, 'PERCENT')}
{formatMeasure(newCoverageValue, 'PERCENT', {
decimals: getMinDecimalsCountToBeDistinctFromThreshold(
parseFloat(newCoverageValue),
getThreshold(measures, 'new_coverage')
)
})}
</span>
</DrilldownLink>
</div>

+ 11
- 3
server/sonar-web/src/main/js/apps/overview/main/Duplications.tsx View File

@@ -20,11 +20,14 @@
import * as React from 'react';
import DuplicationsRating from 'sonar-ui-common/components/ui/DuplicationsRating';
import { translate } from 'sonar-ui-common/helpers/l10n';
import { formatMeasure } from 'sonar-ui-common/helpers/measures';
import {
formatMeasure,
getMinDecimalsCountToBeDistinctFromThreshold
} from 'sonar-ui-common/helpers/measures';
import DocTooltip from '../../../components/docs/DocTooltip';
import DrilldownLink from '../../../components/shared/DrilldownLink';
import { getPeriodValue } from '../../../helpers/measures';
import { getMetricName } from '../utils';
import { getMetricName, getThreshold } from '../utils';
import enhance, { ComposedProps } from './enhance';

export class Duplications extends React.PureComponent<ComposedProps> {
@@ -102,7 +105,12 @@ export class Duplications extends React.PureComponent<ComposedProps> {
component={component.key}
metric={newDuplicationsMeasure.metric.key}>
<span className="js-overview-main-new-duplications">
{formatMeasure(newDuplicationsValue, 'PERCENT')}
{formatMeasure(newDuplicationsValue, 'PERCENT', {
decimals: getMinDecimalsCountToBeDistinctFromThreshold(
parseFloat(newDuplicationsValue),
getThreshold(measures, 'new_duplicated_lines_density')
)
})}
</span>
</DrilldownLink>
</div>

+ 8
- 12
server/sonar-web/src/main/js/apps/overview/qualityGate/QualityGateCondition.tsx View File

@@ -22,7 +22,10 @@ import * as React from 'react';
import { Link } from 'react-router';
import IssueTypeIcon from 'sonar-ui-common/components/icons/IssueTypeIcon';
import { translate } from 'sonar-ui-common/helpers/l10n';
import { formatMeasure } from 'sonar-ui-common/helpers/measures';
import {
formatMeasure,
getMinDecimalsCountToBeDistinctFromThreshold
} from 'sonar-ui-common/helpers/measures';
import Measure from '../../../components/measure/Measure';
import DrilldownLink from '../../../components/shared/DrilldownLink';
import { getBranchLikeQuery, isPullRequest, isShortLivingBranch } from '../../../helpers/branches';
@@ -36,16 +39,6 @@ interface Props {
}

export default class QualityGateCondition extends React.PureComponent<Props> {
getDecimalsNumber(threshold: number, value: number) {
const delta = Math.abs(threshold - value);
if (delta < 0.1 && delta > 0) {
const match = delta.toFixed(20).match('[^0.]');
return match && match.index ? match.index - 1 : undefined;
} else {
return undefined;
}
}

getIssuesUrl = (sinceLeakPeriod: boolean, customQuery: T.Dict<string>) => {
const query: T.Dict<string | undefined> = {
resolved: 'false',
@@ -142,7 +135,10 @@ export default class QualityGateCondition extends React.PureComponent<Props> {
if (metric.type === 'RATING') {
operator = translate('quality_gates.operator', condition.op, 'rating');
} else if (metric.type === 'PERCENT') {
decimals = this.getDecimalsNumber(parseFloat(threshold), parseFloat(actual));
decimals = getMinDecimalsCountToBeDistinctFromThreshold(
parseFloat(actual),
parseFloat(threshold)
);
}

return this.wrapWithLink(

+ 0
- 14
server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/QualityGateCondition-test.tsx View File

@@ -131,20 +131,6 @@ it('new_maintainability_rating', () => {
).toMatchSnapshot();
});

it('should be able to correctly decide how much decimals to show', () => {
const condition = mockRatingCondition('new_maintainability_rating');
const instance = shallow(
<QualityGateCondition component={{ key: 'abcd-key' }} condition={condition} />
).instance() as QualityGateCondition;
expect(instance.getDecimalsNumber(85, 80)).toBe(undefined);
expect(instance.getDecimalsNumber(85, 85)).toBe(undefined);
expect(instance.getDecimalsNumber(85, 85.01)).toBe(2);
expect(instance.getDecimalsNumber(85, 84.95)).toBe(2);
expect(instance.getDecimalsNumber(85, 84.999999999999554)).toBe('9999999999995'.length);
expect(instance.getDecimalsNumber(85, 85.0000000000000954)).toBe('00000000000009'.length);
expect(instance.getDecimalsNumber(85, 85.00000000000000009)).toBe(undefined);
});

it('should work with branch', () => {
const condition = mockRatingCondition('new_maintainability_rating');
expect(

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

@@ -153,3 +153,32 @@ export function getMetricName(metricKey: string) {
export function getRatingName(type: IssueType) {
return translate('metric_domain', ISSUETYPE_MAP[type].ratingName);
}

/*
* Extract a specific metric's threshold from the quality gate details
*/
export function getThreshold(measures: T.MeasureEnhanced[], metricKey: string): number | undefined {
const detailsMeasure = measures.find(measure => measure.metric.key === 'quality_gate_details');
if (detailsMeasure && detailsMeasure.value) {
const details = safeParse(detailsMeasure.value);
const conditions: T.QualityGateStatusConditionEnhanced[] = details.conditions || [];

const condition = conditions.find(c => c.metric === metricKey);
if (condition) {
return parseFloat((condition.level === 'ERROR'
? condition.error
: condition.warning) as string);
}
}
return undefined;
}

function safeParse(json: string) {
try {
return JSON.parse(json);
} catch (e) {
// eslint-disable-next-line no-console
console.error(e);
return {};
}
}

Loading…
Cancel
Save