From 218182c4c4d7ebbbf41e9cbfd8d7688cd411a82f Mon Sep 17 00:00:00 2001 From: =?utf8?q?Gr=C3=A9goire=20Aubert?= Date: Fri, 12 May 2017 12:47:28 +0200 Subject: [PATCH] SONAR-9026 Increase precision for coverage QG conditions --- .../component-measures/components/Measure.js | 9 +-- .../main/js/apps/component-measures/utils.js | 6 +- .../qualityGate/QualityGateCondition.js | 23 +++++-- .../__tests__/QualityGateCondition-test.js | 14 +++++ .../QualityGateCondition-test.js.snap | 8 +++ .../js/helpers/__tests__/measures-test.js | 18 ++++++ .../sonar-web/src/main/js/helpers/measures.js | 63 ++++++++++++------- 7 files changed, 106 insertions(+), 35 deletions(-) diff --git a/server/sonar-web/src/main/js/apps/component-measures/components/Measure.js b/server/sonar-web/src/main/js/apps/component-measures/components/Measure.js index 4700219e15b..520852b4367 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/components/Measure.js +++ b/server/sonar-web/src/main/js/apps/component-measures/components/Measure.js @@ -27,7 +27,8 @@ import { formatLeak, isDiffMetric, getRatingTooltip } from '../utils'; export default class Measure extends React.PureComponent { static propTypes = { measure: React.PropTypes.object, - metric: React.PropTypes.object + metric: React.PropTypes.object, + decimals: React.PropTypes.number }; renderRating(measure, metric) { @@ -51,7 +52,7 @@ export default class Measure extends React.PureComponent { } render() { - const { measure, metric } = this.props; + const { measure, metric, decimals } = this.props; const finalMetric = metric || measure.metric; if (finalMetric.type === 'RATING') { @@ -63,8 +64,8 @@ export default class Measure extends React.PureComponent { } const formattedValue = isDiffMetric(finalMetric) - ? formatLeak(measure.leak, finalMetric) - : formatMeasure(measure.value, finalMetric.type); + ? formatLeak(measure.leak, finalMetric, { decimals }) + : formatMeasure(measure.value, finalMetric.type, { decimals }); return ( diff --git a/server/sonar-web/src/main/js/apps/component-measures/utils.js b/server/sonar-web/src/main/js/apps/component-measures/utils.js index 6b3abc7eda4..91c3e3f92b2 100644 --- a/server/sonar-web/src/main/js/apps/component-measures/utils.js +++ b/server/sonar-web/src/main/js/apps/component-measures/utils.js @@ -62,11 +62,11 @@ export function getSingleLeakValue(measures, periodIndex = 1) { return period ? period.value : null; } -export function formatLeak(value, metric) { +export function formatLeak(value, metric, options) { if (isDiffMetric(metric)) { - return formatMeasure(value, metric.type); + return formatMeasure(value, metric.type, options); } else { - return formatMeasureVariation(value, metric.type); + return formatMeasureVariation(value, metric.type, options); } } diff --git a/server/sonar-web/src/main/js/apps/overview/qualityGate/QualityGateCondition.js b/server/sonar-web/src/main/js/apps/overview/qualityGate/QualityGateCondition.js index 1c0d73f31cb..d2ac1128fce 100644 --- a/server/sonar-web/src/main/js/apps/overview/qualityGate/QualityGateCondition.js +++ b/server/sonar-web/src/main/js/apps/overview/qualityGate/QualityGateCondition.js @@ -49,6 +49,14 @@ export default class QualityGateCondition extends React.PureComponent { } }; + getDecimalsNumber(threshold: number, value: number) { + const delta = Math.abs(threshold - value); + if (delta < 0.1 && delta > 0) { + //$FlowFixMe The matching result can't null because of the previous check + return delta.toFixed(20).match('[^0\.]').index - 1; + } + } + getIssuesUrl(sinceLeakPeriod: boolean, customQuery: {}) { const query: Object = { resolved: 'false', @@ -126,21 +134,24 @@ export default class QualityGateCondition extends React.PureComponent { const { measure } = condition; const { metric } = measure; - const isRating = metric.type === 'RATING'; const isDiff = isDiffMetric(metric.key); const threshold = condition.level === 'ERROR' ? condition.error : condition.warning; - const actual = condition.period ? getPeriodValue(measure, condition.period) : measure.value; - const operator = isRating - ? translate('quality_gates.operator', condition.op, 'rating') - : translate('quality_gates.operator', condition.op); + let operator = translate('quality_gates.operator', condition.op); + let decimals = null; + + if (metric.type === 'RATING') { + operator = translate('quality_gates.operator', condition.op, 'rating'); + } else if (metric.type === 'PERCENT') { + decimals = this.getDecimalsNumber(parseFloat(condition.error), parseFloat(actual)); + } return this.wrapWithLink(
- +
diff --git a/server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/QualityGateCondition-test.js b/server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/QualityGateCondition-test.js index 1b711f7c60c..449ceff035b 100644 --- a/server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/QualityGateCondition-test.js +++ b/server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/QualityGateCondition-test.js @@ -130,3 +130,17 @@ it('new_maintainability_rating', () => { shallow() ).toMatchSnapshot(); }); + +it('should be able to correctly decide how much decimals to show', () => { + const condition = mockRatingCondition('new_maintainability_rating'); + const instance = shallow( + + ).instance(); + 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); +}); diff --git a/server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/__snapshots__/QualityGateCondition-test.js.snap b/server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/__snapshots__/QualityGateCondition-test.js.snap index b392a10f224..f9e6d025176 100644 --- a/server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/__snapshots__/QualityGateCondition-test.js.snap +++ b/server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/__snapshots__/QualityGateCondition-test.js.snap @@ -24,6 +24,7 @@ exports[`new_maintainability_rating 1`] = ` className="overview-quality-gate-condition-value" > { expect(formatMeasure(1529, 'INT')).toBe('1,529'); expect(formatMeasure(10000, 'INT')).toBe('10,000'); expect(formatMeasure(1234567890, 'INT')).toBe('1,234,567,890'); + expect(formatMeasure(1028.5, 'INT', { round: Math.floor })).toBe('1,028'); + expect(formatMeasure(1028.5, 'INT', { round: Math.ceil })).toBe('1,029'); }); it('should format SHORT_INT', () => { @@ -58,6 +60,8 @@ describe('#formatMeasure()', () => { expect(formatMeasure(10000, 'SHORT_INT')).toBe('10k'); expect(formatMeasure(10678, 'SHORT_INT')).toBe('11k'); expect(formatMeasure(1234567890, 'SHORT_INT')).toBe('1b'); + expect(formatMeasure(1529, 'SHORT_INT', { round: Math.ceil })).toBe('1.6k'); + expect(formatMeasure(1589, 'SHORT_INT', { round: Math.floor })).toBe('1.5k'); }); it('should format FLOAT', () => { @@ -77,6 +81,14 @@ describe('#formatMeasure()', () => { expect(formatMeasure(0.12, 'FLOAT')).toBe('0.12'); expect(formatMeasure(0.12345, 'FLOAT')).toBe('0.12345'); expect(formatMeasure(0.123456, 'FLOAT')).toBe('0.12346'); + expect(formatMeasure(0.123451, 'FLOAT', { round: Math.ceil })).toBe('0.12346'); + expect(formatMeasure(0.123458, 'FLOAT', { round: Math.floor })).toBe('0.12345'); + expect(formatMeasure(0.12345, 'FLOAT', { decimals: 1 })).toBe('0.1'); + expect(formatMeasure(0.16789, 'FLOAT', { decimals: 1 })).toBe('0.2'); + expect(formatMeasure(0.123456, 'FLOAT', { decimals: 5 })).toBe('0.12346'); + expect(formatMeasure(0.1234567, 'FLOAT', { decimals: 6 })).toBe('0.123457'); + expect(formatMeasure(0.12345, 'FLOAT', { decimals: 0 })).toBe('0.12345'); + expect(formatMeasure(0.16789, 'FLOAT', { decimals: 1, round: Math.floor })).toBe('0.1'); }); it('should format PERCENT', () => { @@ -86,6 +98,12 @@ describe('#formatMeasure()', () => { expect(formatMeasure(1.34, 'PERCENT')).toBe('1.3%'); expect(formatMeasure(50.89, 'PERCENT')).toBe('50.9%'); expect(formatMeasure(100.0, 'PERCENT')).toBe('100%'); + expect(formatMeasure(50.89, 'PERCENT', { round: Math.floor })).toBe('50.8%'); + expect(formatMeasure(50.83, 'PERCENT', { round: Math.ceil })).toBe('50.9%'); + expect(formatMeasure(50.89, 'PERCENT', { decimals: 1 })).toBe('50.9%'); + expect(formatMeasure(50.89, 'PERCENT', { decimals: 1, round: Math.floor })).toBe('50.8%'); + expect(formatMeasure(50.89, 'PERCENT', { decimals: 2 })).toBe('50.89%'); + expect(formatMeasure(50.89, 'PERCENT', { decimals: 3 })).toBe('50.890%'); }); it('should format WORK_DUR', () => { diff --git a/server/sonar-web/src/main/js/helpers/measures.js b/server/sonar-web/src/main/js/helpers/measures.js index e665b5a809b..01c3c43a3ee 100644 --- a/server/sonar-web/src/main/js/helpers/measures.js +++ b/server/sonar-web/src/main/js/helpers/measures.js @@ -27,9 +27,9 @@ const HOURS_IN_DAY = 8; * @param {string|number} value * @param {string} type */ -export function formatMeasure(value, type) { +export function formatMeasure(value, type, options) { const formatter = getFormatter(type); - return useFormatter(value, formatter); + return useFormatter(value, formatter, options); } /** @@ -37,9 +37,9 @@ export function formatMeasure(value, type) { * @param {string|number} value * @param {string} type */ -export function formatMeasureVariation(value, type) { +export function formatMeasureVariation(value, type, options) { const formatter = getVariationFormatter(type); - return useFormatter(value, formatter); + return useFormatter(value, formatter, options); } /** @@ -102,8 +102,8 @@ export function isDiffMetric(metricKey) { * Helpers */ -function useFormatter(value, formatter) { - return value != null && value !== '' && formatter != null ? formatter(value) : null; +function useFormatter(value, formatter, options) { + return value != null && value !== '' && formatter != null ? formatter(value, options) : null; } function getFormatter(type) { @@ -140,6 +140,13 @@ function getVariationFormatter(type) { * Formatters */ +function genericFormatter(value, formatValue, options = {}) { + if (options.round) { + return numeral(value).format(formatValue, options.round); + } + return numeral(value).format(formatValue); +} + function noFormatter(value) { return value; } @@ -148,15 +155,15 @@ function emptyFormatter() { return null; } -function intFormatter(value) { - return numeral(value).format('0,0'); +function intFormatter(value, options) { + return genericFormatter(value, '0,0', options); } -function intVariationFormatter(value) { - return numeral(value).format('+0,0'); +function intVariationFormatter(value, options) { + return genericFormatter(value, '+0,0', options); } -function shortIntFormatter(value) { +function shortIntFormatter(value, options) { let format = '0,0'; if (value >= 1000) { format = '0.[0]a'; @@ -164,30 +171,42 @@ function shortIntFormatter(value) { if (value >= 10000) { format = '0a'; } - return numeral(value).format(format); + return genericFormatter(value, format, options); } -function shortIntVariationFormatter(value) { - const formatted = shortIntFormatter(Math.abs(value)); +function shortIntVariationFormatter(value, options) { + const formatted = shortIntFormatter(Math.abs(value), options); return value < 0 ? `-${formatted}` : `+${formatted}`; } -function floatFormatter(value) { - return numeral(value).format('0,0.0[0000]'); +function floatFormatter(value, options = {}) { + if (options.decimals) { + return genericFormatter(value, `0,0.${'0'.repeat(options.decimals)}`, options); + } + return genericFormatter(value, '0,0.0[0000]', options); } -function floatVariationFormatter(value) { - return value === 0 ? '+0.0' : numeral(value).format('+0,0.0[0000]'); +function floatVariationFormatter(value, options = {}) { + if (options.decimals) { + return genericFormatter(value, `+0,0.${'0'.repeat(options.decimals)}`, options); + } + return value === 0 ? '+0.0' : genericFormatter(value, '+0,0.0[0000]', options); } -function percentFormatter(value) { +function percentFormatter(value, options = {}) { value = parseFloat(value); - return value === 100 ? '100%' : numeral(value / 100).format('0,0.0%'); + if (options.decimals) { + return genericFormatter(value / 100, `0,0.${'0'.repeat(options.decimals)}%`, options); + } + return value === 100 ? '100%' : genericFormatter(value / 100, '0,0.0%', options); } -function percentVariationFormatter(value) { +function percentVariationFormatter(value, options = {}) { value = parseFloat(value); - return value === 0 ? '+0.0%' : numeral(value / 100).format('+0,0.0%'); + if (options.decimals) { + return genericFormatter(value / 100, `+0,0.${'0'.repeat(options.decimals)}%`, options); + } + return value === 0 ? '+0.0%' : genericFormatter(value / 100, '+0,0.0%', options); } function ratingFormatter(value) { -- 2.39.5