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';
render() {
const { statistic } = this.props;
- const formattedString = formatMeasure(statistic.value, 'SHORT_INT');
- const value = parseFloat(formattedString.slice(0, -1));
- const suffix = formattedString.substr(-1);
+ const formattedString = formatMeasure(statistic.value, 'SHORT_INT', {
+ roundingFunc: Math.floor
+ });
+ const value = parseFloat(formattedString);
+ let suffix = formattedString.replace(value.toString(), '');
+ if (suffix === translate('short_number_suffix.g')) {
+ suffix = ' ' + translate('billion');
+ }
return (
<div className="sc-stat-card sc-big-spacer-top" ref={node => (this.container = node)}>
<div className="sc-stat-icon">
* 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(<Statistics statistics={[STATISTICS]} />)).toMatchSnapshot();
});
it('should render StatisticCard', () => {
- expect(shallow(<StatisticCard statistic={STATISTICS} />)).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<StatisticCard['props']> = {}) {
+ const wrapper = shallow(<StatisticCard statistic={STATISTICS} {...props} />);
+ wrapper.setState({ viewable: true });
+ return wrapper;
+}
<div
className="sc-stat-content"
>
+ <CountUp
+ delay={0}
+ duration={4}
+ end={26}
+ suffix="short_number_suffix.k"
+ >
+ <Component />
+ </CountUp>
<span>
my stat
</span>
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', () => {
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', () => {
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 {
ascending=Ascending
assignee=Assignee
author=Author
+billion=Billion
bitbucket=Bitbucket
back=Back
backup=Backup