From 436296b4cdf93e71a6195ab116853504202cab08 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Gr=C3=A9goire=20Aubert?= Date: Wed, 19 Jun 2019 12:06:37 +0200 Subject: [PATCH] SONAR-12219 Fix formatting of rounded short numbers --- .../sonarcloud/components/Statistics.tsx | 12 +++-- .../components/__tests__/Statistics-test.tsx | 35 +++++++++++--- .../__snapshots__/Statistics-test.tsx.snap | 8 ++++ .../js/helpers/__tests__/measures-test.ts | 11 +++-- .../sonar-web/src/main/js/helpers/measures.ts | 48 ++++++++++++++----- .../resources/org/sonar/l10n/core.properties | 1 + 6 files changed, 91 insertions(+), 24 deletions(-) diff --git a/server/sonar-web/src/main/js/apps/about/sonarcloud/components/Statistics.tsx b/server/sonar-web/src/main/js/apps/about/sonarcloud/components/Statistics.tsx index 8f220c26e35..91be927f973 100644 --- a/server/sonar-web/src/main/js/apps/about/sonarcloud/components/Statistics.tsx +++ b/server/sonar-web/src/main/js/apps/about/sonarcloud/components/Statistics.tsx @@ -21,6 +21,7 @@ import * as React from 'react'; import { throttle } from 'lodash'; import CountUp from 'react-countup'; import { formatMeasure } from '../../../../helpers/measures'; +import { translate } from '../../../../helpers/l10n'; import { getBaseUrl } from '../../../../helpers/urls'; import './Statistics.css'; @@ -83,9 +84,14 @@ export class StatisticCard extends React.PureComponent (this.container = node)}>
diff --git a/server/sonar-web/src/main/js/apps/about/sonarcloud/components/__tests__/Statistics-test.tsx b/server/sonar-web/src/main/js/apps/about/sonarcloud/components/__tests__/Statistics-test.tsx index 9387c2f9cec..52626e30552 100644 --- a/server/sonar-web/src/main/js/apps/about/sonarcloud/components/__tests__/Statistics-test.tsx +++ b/server/sonar-web/src/main/js/apps/about/sonarcloud/components/__tests__/Statistics-test.tsx @@ -18,19 +18,40 @@ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import * as React from 'react'; -import { shallow } from 'enzyme'; +import { shallow, ShallowWrapper } from 'enzyme'; import Statistics, { StatisticCard } from '../Statistics'; -const STATISTICS = { - icon: 'stat-icon', - text: 'my stat', - value: 26666 -}; +const STATISTICS = { icon: 'stat-icon', text: 'my stat', value: 26666 }; it('should render', () => { expect(shallow()).toMatchSnapshot(); }); it('should render StatisticCard', () => { - expect(shallow()).toMatchSnapshot(); + expect(shallowRender()).toMatchSnapshot(); }); + +it('should render big numbers correctly', () => { + function checkCountUp(wrapper: ShallowWrapper, end: number, suffix: string) { + expect(wrapper.find('CountUp').prop('end')).toBe(end); + expect(wrapper.find('CountUp').prop('suffix')).toBe(suffix); + } + + checkCountUp( + shallowRender({ statistic: { ...STATISTICS, value: 999003632 } }), + 999, + 'short_number_suffix.m' + ); + checkCountUp( + shallowRender({ statistic: { ...STATISTICS, value: 999861538 } }), + 999, + 'short_number_suffix.m' + ); + checkCountUp(shallowRender({ statistic: { ...STATISTICS, value: 1100021731 } }), 1.1, ' billion'); +}); + +function shallowRender(props: Partial = {}) { + const wrapper = shallow(); + wrapper.setState({ viewable: true }); + return wrapper; +} diff --git a/server/sonar-web/src/main/js/apps/about/sonarcloud/components/__tests__/__snapshots__/Statistics-test.tsx.snap b/server/sonar-web/src/main/js/apps/about/sonarcloud/components/__tests__/__snapshots__/Statistics-test.tsx.snap index 32a75b43212..0e1f5dc9dc9 100644 --- a/server/sonar-web/src/main/js/apps/about/sonarcloud/components/__tests__/__snapshots__/Statistics-test.tsx.snap +++ b/server/sonar-web/src/main/js/apps/about/sonarcloud/components/__tests__/__snapshots__/Statistics-test.tsx.snap @@ -33,6 +33,14 @@ exports[`should render StatisticCard 1`] = `
+ + + my stat diff --git a/server/sonar-web/src/main/js/helpers/__tests__/measures-test.ts b/server/sonar-web/src/main/js/helpers/__tests__/measures-test.ts index 0cc6bc6f10a..6cb21fc5aa7 100644 --- a/server/sonar-web/src/main/js/helpers/__tests__/measures-test.ts +++ b/server/sonar-web/src/main/js/helpers/__tests__/measures-test.ts @@ -60,7 +60,12 @@ describe('#formatMeasure()', () => { expect(formatMeasure(1529, 'SHORT_INT')).toBe('1.5k'); expect(formatMeasure(10000, 'SHORT_INT')).toBe('10k'); expect(formatMeasure(10678, 'SHORT_INT')).toBe('11k'); - expect(formatMeasure(1234567890, 'SHORT_INT')).toBe('1G'); + expect(formatMeasure(9467890, 'SHORT_INT')).toBe('9.5M'); + expect(formatMeasure(994567890, 'SHORT_INT')).toBe('995M'); + expect(formatMeasure(999000001, 'SHORT_INT')).toBe('999M'); + expect(formatMeasure(999567890, 'SHORT_INT')).toBe('1G'); + expect(formatMeasure(1234567890, 'SHORT_INT')).toBe('1.2G'); + expect(formatMeasure(11234567890, 'SHORT_INT')).toBe('11G'); }); it('should format FLOAT', () => { @@ -130,8 +135,8 @@ describe('#formatMeasure()', () => { expect(formatMeasure(-1 * ONE_MINUTE, 'SHORT_WORK_DUR')).toBe('-1min'); expect(formatMeasure(1529 * ONE_DAY, 'SHORT_WORK_DUR')).toBe('1.5kd'); - expect(formatMeasure(1234567 * ONE_DAY, 'SHORT_WORK_DUR')).toBe('1Md'); - expect(formatMeasure(1234567 * ONE_DAY + 2 * ONE_HOUR, 'SHORT_WORK_DUR')).toBe('1Md'); + expect(formatMeasure(1234567 * ONE_DAY, 'SHORT_WORK_DUR')).toBe('1.2Md'); + expect(formatMeasure(12345670 * ONE_DAY + 4 * ONE_HOUR, 'SHORT_WORK_DUR')).toBe('12Md'); }); it('should format RATING', () => { diff --git a/server/sonar-web/src/main/js/helpers/measures.ts b/server/sonar-web/src/main/js/helpers/measures.ts index f270294757e..a66fbd8999f 100644 --- a/server/sonar-web/src/main/js/helpers/measures.ts +++ b/server/sonar-web/src/main/js/helpers/measures.ts @@ -130,18 +130,44 @@ function intFormatter(value: number): string { return numberFormatter(value); } -function shortIntFormatter(value: number): string { - if (value >= 1e9) { - return numberFormatter(value / 1e9) + translate('short_number_suffix.g'); - } else if (value >= 1e6) { - return numberFormatter(value / 1e6) + translate('short_number_suffix.m'); - } else if (value >= 1e4) { - return numberFormatter(value / 1e3) + translate('short_number_suffix.k'); - } else if (value >= 1e3) { - return numberFormatter(value / 1e3, 0, 1) + translate('short_number_suffix.k'); - } else { - return numberFormatter(value); +const shortIntFormats = [ + { unit: 1e10, formatUnit: 1e9, fraction: 0, suffix: 'short_number_suffix.g' }, + { unit: 1e9, formatUnit: 1e9, fraction: 1, suffix: 'short_number_suffix.g' }, + { unit: 1e7, formatUnit: 1e6, fraction: 0, suffix: 'short_number_suffix.m' }, + { unit: 1e6, formatUnit: 1e6, fraction: 1, suffix: 'short_number_suffix.m' }, + { unit: 1e4, formatUnit: 1e3, fraction: 0, suffix: 'short_number_suffix.k' }, + { unit: 1e3, formatUnit: 1e3, fraction: 1, suffix: 'short_number_suffix.k' } +]; + +function shortIntFormatter( + value: number, + option?: { roundingFunc?: (x: number) => number } +): string { + const roundingFunc = (option && option.roundingFunc) || undefined; + for (let i = 0; i < shortIntFormats.length; i++) { + const { unit, formatUnit, fraction, suffix } = shortIntFormats[i]; + const nextFraction = unit / (shortIntFormats[i + 1] ? shortIntFormats[i + 1].unit / 10 : 1); + const roundedValue = numberRound(value / unit, nextFraction, roundingFunc); + if (roundedValue >= 1) { + return ( + numberFormatter( + numberRound(value / formatUnit, Math.pow(10, fraction), roundingFunc), + 0, + fraction + ) + translate(suffix) + ); + } } + + return numberFormatter(value); +} + +function numberRound( + value: number, + fraction: number = 1000, + roundingFunc: (x: number) => number = Math.round +) { + return roundingFunc(value * fraction) / fraction; } function floatFormatter(value: number): string { diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties index ea4344eb9bb..065d5e7e04b 100644 --- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties +++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties @@ -18,6 +18,7 @@ any=Any ascending=Ascending assignee=Assignee author=Author +billion=Billion bitbucket=Bitbucket back=Back backup=Backup -- 2.39.5