diff options
author | Jeremy Davis <jeremy.davis@sonarsource.com> | 2019-09-25 18:26:05 +0200 |
---|---|---|
committer | SonarTech <sonartech@sonarsource.com> | 2019-10-01 11:45:54 +0200 |
commit | cedf9b4c89f3cb6351c65e85d6bc3f5edf11a466 (patch) | |
tree | a7fed25a881ddc9a3891c710597f8936cc2057af /server/sonar-web | |
parent | 9a40a5174209cd6642c667714cc86781c7650609 (diff) | |
download | sonarqube-cedf9b4c89f3cb6351c65e85d6bc3f5edf11a466.tar.gz sonarqube-cedf9b4c89f3cb6351c65e85d6bc3f5edf11a466.zip |
SONAR-12256 Consistent rounding in project overview
Diffstat (limited to 'server/sonar-web')
6 files changed, 127 insertions, 32 deletions
diff --git a/server/sonar-web/src/main/js/apps/overview/__tests__/utils-test.ts b/server/sonar-web/src/main/js/apps/overview/__tests__/utils-test.ts new file mode 100644 index 00000000000..5e47552c751 --- /dev/null +++ b/server/sonar-web/src/main/js/apps/overview/__tests__/utils-test.ts @@ -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' + }) + ] + }) + }); +} diff --git a/server/sonar-web/src/main/js/apps/overview/main/Coverage.tsx b/server/sonar-web/src/main/js/apps/overview/main/Coverage.tsx index dac39392ff8..380543baa55 100644 --- a/server/sonar-web/src/main/js/apps/overview/main/Coverage.tsx +++ b/server/sonar-web/src/main/js/apps/overview/main/Coverage.tsx @@ -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> diff --git a/server/sonar-web/src/main/js/apps/overview/main/Duplications.tsx b/server/sonar-web/src/main/js/apps/overview/main/Duplications.tsx index 8b3d03f5662..026eb2b0d37 100644 --- a/server/sonar-web/src/main/js/apps/overview/main/Duplications.tsx +++ b/server/sonar-web/src/main/js/apps/overview/main/Duplications.tsx @@ -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> diff --git a/server/sonar-web/src/main/js/apps/overview/qualityGate/QualityGateCondition.tsx b/server/sonar-web/src/main/js/apps/overview/qualityGate/QualityGateCondition.tsx index ed38b125e69..cdad14829fb 100644 --- a/server/sonar-web/src/main/js/apps/overview/qualityGate/QualityGateCondition.tsx +++ b/server/sonar-web/src/main/js/apps/overview/qualityGate/QualityGateCondition.tsx @@ -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( diff --git a/server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/QualityGateCondition-test.tsx b/server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/QualityGateCondition-test.tsx index 13e6469e540..ad2e8b7c4cc 100644 --- a/server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/QualityGateCondition-test.tsx +++ b/server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/QualityGateCondition-test.tsx @@ -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( diff --git a/server/sonar-web/src/main/js/apps/overview/utils.ts b/server/sonar-web/src/main/js/apps/overview/utils.ts index 034bca95fe9..e8259aeba55 100644 --- a/server/sonar-web/src/main/js/apps/overview/utils.ts +++ b/server/sonar-web/src/main/js/apps/overview/utils.ts @@ -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 {}; + } +} |