]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-9026 Increase precision for coverage QG conditions
authorGrégoire Aubert <gregoire.aubert@sonarsource.com>
Fri, 12 May 2017 10:47:28 +0000 (12:47 +0200)
committerGrégoire Aubert <gregoire.aubert@sonarsource.com>
Fri, 12 May 2017 14:14:43 +0000 (16:14 +0200)
server/sonar-web/src/main/js/apps/component-measures/components/Measure.js
server/sonar-web/src/main/js/apps/component-measures/utils.js
server/sonar-web/src/main/js/apps/overview/qualityGate/QualityGateCondition.js
server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/QualityGateCondition-test.js
server/sonar-web/src/main/js/apps/overview/qualityGate/__tests__/__snapshots__/QualityGateCondition-test.js.snap
server/sonar-web/src/main/js/helpers/__tests__/measures-test.js
server/sonar-web/src/main/js/helpers/measures.js

index 4700219e15ba83f1c886daf7e733a953315b782e..520852b4367fae2ec4afcaabfe926c55990b1fbd 100644 (file)
@@ -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 (
       <span>
index 6b3abc7eda4846b45a12bc377eb73bcf0dc4b9fd..91c3e3f92b21d12e83fe47cc57948d63be57852f 100644 (file)
@@ -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);
   }
 }
 
index 1c0d73f31cb47bcef0c1b98849094bada45d5650..d2ac1128fcea488f437859843381acb73ddd9c19 100644 (file)
@@ -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(
       <div className="overview-quality-gate-condition-container">
         <div className="overview-quality-gate-condition-value">
-          <Measure measure={{ value: actual, leak: actual }} metric={metric} />
+          <Measure measure={{ value: actual, leak: actual }} metric={metric} decimals={decimals} />
         </div>
 
         <div>
index 1b711f7c60c0c56f946ab8b1afc19535ebb1c199..449ceff035b53cbdc3046a4ecfb53db813c7af91 100644 (file)
@@ -130,3 +130,17 @@ it('new_maintainability_rating', () => {
     shallow(<QualityGateCondition component={{ key: 'abcd-key' }} condition={condition} />)
   ).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();
+  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);
+});
index b392a10f2244eb3d2c63e812b36ac3800af706aa..f9e6d025176f3b76d52ddbf3179e4fcf2a0ed9ca 100644 (file)
@@ -24,6 +24,7 @@ exports[`new_maintainability_rating 1`] = `
       className="overview-quality-gate-condition-value"
     >
       <Measure
+        decimals={null}
         measure={
           Object {
             "leak": "3",
@@ -75,6 +76,7 @@ exports[`new_open_issues 1`] = `
       className="overview-quality-gate-condition-value"
     >
       <Measure
+        decimals={null}
         measure={
           Object {
             "leak": "10",
@@ -137,6 +139,7 @@ exports[`new_reliability_rating 1`] = `
       className="overview-quality-gate-condition-value"
     >
       <Measure
+        decimals={null}
         measure={
           Object {
             "leak": "3",
@@ -199,6 +202,7 @@ exports[`new_security_rating 1`] = `
       className="overview-quality-gate-condition-value"
     >
       <Measure
+        decimals={null}
         measure={
           Object {
             "leak": "3",
@@ -250,6 +254,7 @@ exports[`open_issues 1`] = `
       className="overview-quality-gate-condition-value"
     >
       <Measure
+        decimals={null}
         measure={
           Object {
             "leak": "10",
@@ -311,6 +316,7 @@ exports[`reliability_rating 1`] = `
       className="overview-quality-gate-condition-value"
     >
       <Measure
+        decimals={null}
         measure={
           Object {
             "leak": "3",
@@ -372,6 +378,7 @@ exports[`security_rating 1`] = `
       className="overview-quality-gate-condition-value"
     >
       <Measure
+        decimals={null}
         measure={
           Object {
             "leak": "3",
@@ -432,6 +439,7 @@ exports[`sqale_rating 1`] = `
       className="overview-quality-gate-condition-value"
     >
       <Measure
+        decimals={null}
         measure={
           Object {
             "leak": "3",
index bc621a44fec7ef0657006beb1efc49c095c799a8..46d81f7f0a6933e510af4f36d8caf562d97815cb 100644 (file)
@@ -47,6 +47,8 @@ describe('#formatMeasure()', () => {
     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', () => {
index e665b5a809b6886e7299c24cd5afde3d2086bcf2..01c3c43a3ee336fce8974a814c5d7d5b175234c1 100644 (file)
@@ -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) {