mirror of
https://github.com/Unleash/unleash.git
synced 2025-02-04 00:18:01 +01:00
feat: prettify large numbers in metrics (#2176)
* prettify large numbers * add tooltip for larger numbers * add test ids, add unit test * move dependency to devDependency * remove unused import * use conditional render component * use prettify large number component for feature overview metrics
This commit is contained in:
parent
e1b903a36c
commit
1a09d1778b
@ -69,6 +69,7 @@
|
|||||||
"immer": "9.0.15",
|
"immer": "9.0.15",
|
||||||
"jsdom": "20.0.1",
|
"jsdom": "20.0.1",
|
||||||
"lodash.clonedeep": "4.5.0",
|
"lodash.clonedeep": "4.5.0",
|
||||||
|
"millify": "^5.0.1",
|
||||||
"msw": "0.47.4",
|
"msw": "0.47.4",
|
||||||
"pkginfo": "0.4.1",
|
"pkginfo": "0.4.1",
|
||||||
"plausible-tracker": "0.3.8",
|
"plausible-tracker": "0.3.8",
|
||||||
|
@ -0,0 +1,62 @@
|
|||||||
|
import { render } from 'utils/testRenderer';
|
||||||
|
import { screen } from '@testing-library/react';
|
||||||
|
import { PrettifyLargeNumber } from './PrettifyLargeNumber';
|
||||||
|
import { LARGE_NUMBER_PRETTIFIED } from 'utils/testIds';
|
||||||
|
|
||||||
|
describe('PrettifyLargeNumber', () => {
|
||||||
|
it('should render number with separator for value less than threshold', async () => {
|
||||||
|
render(<PrettifyLargeNumber value={999999} threshold={1000000} />);
|
||||||
|
|
||||||
|
const prettifiedText = await screen.getByTestId(
|
||||||
|
LARGE_NUMBER_PRETTIFIED
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(prettifiedText.textContent).toBe('999,999');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render prettified number for value equal to the threshold', async () => {
|
||||||
|
render(<PrettifyLargeNumber value={1000000} threshold={1000000} />);
|
||||||
|
|
||||||
|
const prettifiedText = await screen.getByTestId(
|
||||||
|
LARGE_NUMBER_PRETTIFIED
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(prettifiedText.textContent).toBe('1M');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render prettified number for value greater than threshold', async () => {
|
||||||
|
render(<PrettifyLargeNumber value={12345678} threshold={1000000} />);
|
||||||
|
|
||||||
|
const prettifiedText = await screen.getByTestId(
|
||||||
|
LARGE_NUMBER_PRETTIFIED
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(prettifiedText.textContent).toBe('12.35M');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render prettified number with tooltip having raw value for value greater than threshold', async () => {
|
||||||
|
render(<PrettifyLargeNumber value={12345678} threshold={1000000} />);
|
||||||
|
|
||||||
|
const prettifiedText = await screen.getByTestId(
|
||||||
|
LARGE_NUMBER_PRETTIFIED
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(prettifiedText.getAttribute('aria-label')).toBe('12,345,678');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should render prettified number with provided significant figures for value greater than threshold', async () => {
|
||||||
|
render(
|
||||||
|
<PrettifyLargeNumber
|
||||||
|
value={12345678}
|
||||||
|
threshold={1000000}
|
||||||
|
precision={4}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
const prettifiedText = await screen.getByTestId(
|
||||||
|
LARGE_NUMBER_PRETTIFIED
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(prettifiedText.textContent).toBe('12.3457M');
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,54 @@
|
|||||||
|
import { FC } from 'react';
|
||||||
|
import millify from 'millify';
|
||||||
|
import { Tooltip } from '@mui/material';
|
||||||
|
import { LARGE_NUMBER_PRETTIFIED } from 'utils/testIds';
|
||||||
|
import { ConditionallyRender } from '../ConditionallyRender/ConditionallyRender';
|
||||||
|
|
||||||
|
interface IPrettifyLargeNumberProps {
|
||||||
|
/**
|
||||||
|
* Value to prettify
|
||||||
|
*/
|
||||||
|
value: number;
|
||||||
|
/**
|
||||||
|
* Threshold above which the number will be prettified. Values lower than this will just have comma separators added
|
||||||
|
* @default 1_000_000
|
||||||
|
*/
|
||||||
|
threshold?: number;
|
||||||
|
/**
|
||||||
|
* The number of significant figures
|
||||||
|
* @default 2
|
||||||
|
*/
|
||||||
|
precision?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const PrettifyLargeNumber: FC<IPrettifyLargeNumberProps> = ({
|
||||||
|
value,
|
||||||
|
threshold = 1_000_000,
|
||||||
|
precision = 2,
|
||||||
|
}) => {
|
||||||
|
let prettyValue: string;
|
||||||
|
let showTooltip = false;
|
||||||
|
|
||||||
|
if (value < threshold) {
|
||||||
|
prettyValue = value.toLocaleString();
|
||||||
|
} else {
|
||||||
|
prettyValue = millify(value, { precision });
|
||||||
|
showTooltip = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const valueSpan = (
|
||||||
|
<span data-testid={LARGE_NUMBER_PRETTIFIED}>{prettyValue}</span>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={showTooltip}
|
||||||
|
show={
|
||||||
|
<Tooltip title={value.toLocaleString()} arrow>
|
||||||
|
{valueSpan}
|
||||||
|
</Tooltip>
|
||||||
|
}
|
||||||
|
elseShow={valueSpan}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
@ -1,6 +1,7 @@
|
|||||||
import { calculatePercentage } from 'utils/calculatePercentage';
|
import { calculatePercentage } from 'utils/calculatePercentage';
|
||||||
import { useStyles } from './FeatureMetricsStats.styles';
|
import { useStyles } from './FeatureMetricsStats.styles';
|
||||||
import { Grid } from '@mui/material';
|
import { Grid } from '@mui/material';
|
||||||
|
import { PrettifyLargeNumber } from 'component/common/PrettifyLargeNumber/PrettifyLargeNumber';
|
||||||
|
|
||||||
export interface IFeatureMetricsStatsProps {
|
export interface IFeatureMetricsStatsProps {
|
||||||
totalYes: number;
|
totalYes: number;
|
||||||
@ -34,7 +35,9 @@ export const FeatureMetricsStats = ({
|
|||||||
<Grid item xs={12} sm={4}>
|
<Grid item xs={12} sm={4}>
|
||||||
<article className={styles.item}>
|
<article className={styles.item}>
|
||||||
<h3 className={styles.title}>Exposure</h3>
|
<h3 className={styles.title}>Exposure</h3>
|
||||||
<p className={styles.value}>{totalYes}</p>
|
<p className={styles.value}>
|
||||||
|
<PrettifyLargeNumber value={totalYes} />
|
||||||
|
</p>
|
||||||
<p className={styles.text}>
|
<p className={styles.text}>
|
||||||
Total exposure of the feature in the environment{' '}
|
Total exposure of the feature in the environment{' '}
|
||||||
{hoursSuffix}.
|
{hoursSuffix}.
|
||||||
@ -56,7 +59,9 @@ export const FeatureMetricsStats = ({
|
|||||||
<Grid item xs={12} sm={4}>
|
<Grid item xs={12} sm={4}>
|
||||||
<article className={styles.item}>
|
<article className={styles.item}>
|
||||||
<h3 className={styles.title}>Requests</h3>
|
<h3 className={styles.title}>Requests</h3>
|
||||||
<p className={styles.value}>{totalYes + totalNo}</p>
|
<p className={styles.value}>
|
||||||
|
<PrettifyLargeNumber value={totalYes + totalNo} />
|
||||||
|
</p>
|
||||||
<p className={styles.text}>
|
<p className={styles.text}>
|
||||||
Total requests for the feature in the environment{' '}
|
Total requests for the feature in the environment{' '}
|
||||||
{hoursSuffix}.
|
{hoursSuffix}.
|
||||||
|
@ -4,6 +4,7 @@ import { IFeatureEnvironmentMetrics } from 'interfaces/featureToggle';
|
|||||||
import { calculatePercentage } from 'utils/calculatePercentage';
|
import { calculatePercentage } from 'utils/calculatePercentage';
|
||||||
import PercentageCircle from 'component/common/PercentageCircle/PercentageCircle';
|
import PercentageCircle from 'component/common/PercentageCircle/PercentageCircle';
|
||||||
import { useStyles } from './FeatureOverviewEnvironmentMetrics.styles';
|
import { useStyles } from './FeatureOverviewEnvironmentMetrics.styles';
|
||||||
|
import { PrettifyLargeNumber } from 'component/common/PrettifyLargeNumber/PrettifyLargeNumber';
|
||||||
|
|
||||||
interface IFeatureOverviewEnvironmentMetrics {
|
interface IFeatureOverviewEnvironmentMetrics {
|
||||||
environmentMetric?: IFeatureEnvironmentMetrics;
|
environmentMetric?: IFeatureEnvironmentMetrics;
|
||||||
@ -68,9 +69,15 @@ const FeatureOverviewEnvironmentMetrics = ({
|
|||||||
<p className={styles.percentage}>{percentage}%</p>
|
<p className={styles.percentage}>{percentage}%</p>
|
||||||
<p className={styles.infoParagraph}>
|
<p className={styles.infoParagraph}>
|
||||||
The feature has been requested{' '}
|
The feature has been requested{' '}
|
||||||
<b>{environmentMetric.yes + environmentMetric.no} times</b>{' '}
|
<b>
|
||||||
and exposed <b>{environmentMetric.yes} times</b> in the last
|
<PrettifyLargeNumber value={total} /> times
|
||||||
hour
|
</b>{' '}
|
||||||
|
and exposed{' '}
|
||||||
|
<b>
|
||||||
|
<PrettifyLargeNumber value={environmentMetric.yes} />{' '}
|
||||||
|
times
|
||||||
|
</b>{' '}
|
||||||
|
in the last hour
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.percentageCircle} data-loading>
|
<div className={styles.percentageCircle} data-loading>
|
||||||
|
@ -73,3 +73,4 @@ export const AUTH_PAGE_ID = 'AUTH_PAGE_ID';
|
|||||||
export const ANNOUNCER_ELEMENT_TEST_ID = 'ANNOUNCER_ELEMENT_TEST_ID';
|
export const ANNOUNCER_ELEMENT_TEST_ID = 'ANNOUNCER_ELEMENT_TEST_ID';
|
||||||
export const INSTANCE_STATUS_BAR_ID = 'INSTANCE_STATUS_BAR_ID';
|
export const INSTANCE_STATUS_BAR_ID = 'INSTANCE_STATUS_BAR_ID';
|
||||||
export const TOAST_TEXT = 'TOAST_TEXT';
|
export const TOAST_TEXT = 'TOAST_TEXT';
|
||||||
|
export const LARGE_NUMBER_PRETTIFIED = 'LARGE_NUMBER_PRETTIFIED';
|
||||||
|
@ -3319,6 +3319,15 @@ cliui@^7.0.2:
|
|||||||
strip-ansi "^6.0.0"
|
strip-ansi "^6.0.0"
|
||||||
wrap-ansi "^7.0.0"
|
wrap-ansi "^7.0.0"
|
||||||
|
|
||||||
|
cliui@^8.0.1:
|
||||||
|
version "8.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/cliui/-/cliui-8.0.1.tgz#0c04b075db02cbfe60dc8e6cf2f5486b1a3608aa"
|
||||||
|
integrity sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==
|
||||||
|
dependencies:
|
||||||
|
string-width "^4.2.0"
|
||||||
|
strip-ansi "^6.0.1"
|
||||||
|
wrap-ansi "^7.0.0"
|
||||||
|
|
||||||
clone@^1.0.2:
|
clone@^1.0.2:
|
||||||
version "1.0.4"
|
version "1.0.4"
|
||||||
resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e"
|
resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e"
|
||||||
@ -5513,6 +5522,13 @@ micromatch@^4.0.2, micromatch@^4.0.4:
|
|||||||
braces "^3.0.2"
|
braces "^3.0.2"
|
||||||
picomatch "^2.3.1"
|
picomatch "^2.3.1"
|
||||||
|
|
||||||
|
millify@^5.0.1:
|
||||||
|
version "5.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/millify/-/millify-5.0.1.tgz#f3f2cebf4d3071e127c05942f827c49754a03754"
|
||||||
|
integrity sha512-1IacXjRDMbRevt2++mBnrI2iFxljWlQapMUT9Bs+uAF3o/TrYdE46Uf6CqlmoOWMX1JDAlMorXPv4/hM1eE/kw==
|
||||||
|
dependencies:
|
||||||
|
yargs "^17.0.1"
|
||||||
|
|
||||||
mime-db@1.52.0:
|
mime-db@1.52.0:
|
||||||
version "1.52.0"
|
version "1.52.0"
|
||||||
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
|
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
|
||||||
@ -7217,6 +7233,19 @@ yargs@^16.2.0:
|
|||||||
y18n "^5.0.5"
|
y18n "^5.0.5"
|
||||||
yargs-parser "^20.2.2"
|
yargs-parser "^20.2.2"
|
||||||
|
|
||||||
|
yargs@^17.0.1:
|
||||||
|
version "17.6.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.6.0.tgz#e134900fc1f218bc230192bdec06a0a5f973e46c"
|
||||||
|
integrity sha512-8H/wTDqlSwoSnScvV2N/JHfLWOKuh5MVla9hqLjK3nsfyy6Y4kDSYSvkU5YCUEPOSnRXfIyx3Sq+B/IWudTo4g==
|
||||||
|
dependencies:
|
||||||
|
cliui "^8.0.1"
|
||||||
|
escalade "^3.1.1"
|
||||||
|
get-caller-file "^2.0.5"
|
||||||
|
require-directory "^2.1.1"
|
||||||
|
string-width "^4.2.3"
|
||||||
|
y18n "^5.0.5"
|
||||||
|
yargs-parser "^21.0.0"
|
||||||
|
|
||||||
yargs@^17.3.1:
|
yargs@^17.3.1:
|
||||||
version "17.4.1"
|
version "17.4.1"
|
||||||
resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.4.1.tgz#ebe23284207bb75cee7c408c33e722bfb27b5284"
|
resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.4.1.tgz#ebe23284207bb75cee7c408c33e722bfb27b5284"
|
||||||
|
Loading…
Reference in New Issue
Block a user