diff --git a/frontend/package.json b/frontend/package.json index f7328578942..de67b9794e1 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -69,6 +69,7 @@ "immer": "9.0.15", "jsdom": "20.0.1", "lodash.clonedeep": "4.5.0", + "millify": "^5.0.1", "msw": "0.47.4", "pkginfo": "0.4.1", "plausible-tracker": "0.3.8", diff --git a/frontend/src/component/common/PrettifyLargeNumber/PrettifyLargeNumber.test.tsx b/frontend/src/component/common/PrettifyLargeNumber/PrettifyLargeNumber.test.tsx new file mode 100644 index 00000000000..1b845b519e0 --- /dev/null +++ b/frontend/src/component/common/PrettifyLargeNumber/PrettifyLargeNumber.test.tsx @@ -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(); + + 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(); + + 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(); + + 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(); + + 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( + + ); + + const prettifiedText = await screen.getByTestId( + LARGE_NUMBER_PRETTIFIED + ); + + expect(prettifiedText.textContent).toBe('12.3457M'); + }); +}); diff --git a/frontend/src/component/common/PrettifyLargeNumber/PrettifyLargeNumber.tsx b/frontend/src/component/common/PrettifyLargeNumber/PrettifyLargeNumber.tsx new file mode 100644 index 00000000000..50e42095b96 --- /dev/null +++ b/frontend/src/component/common/PrettifyLargeNumber/PrettifyLargeNumber.tsx @@ -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 = ({ + 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 = ( + {prettyValue} + ); + + return ( + + {valueSpan} + + } + elseShow={valueSpan} + /> + ); +}; diff --git a/frontend/src/component/feature/FeatureView/FeatureMetrics/FeatureMetricsStats/FeatureMetricsStats.tsx b/frontend/src/component/feature/FeatureView/FeatureMetrics/FeatureMetricsStats/FeatureMetricsStats.tsx index 03e49fa4268..4ebd0881afe 100644 --- a/frontend/src/component/feature/FeatureView/FeatureMetrics/FeatureMetricsStats/FeatureMetricsStats.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureMetrics/FeatureMetricsStats/FeatureMetricsStats.tsx @@ -1,6 +1,7 @@ import { calculatePercentage } from 'utils/calculatePercentage'; import { useStyles } from './FeatureMetricsStats.styles'; import { Grid } from '@mui/material'; +import { PrettifyLargeNumber } from 'component/common/PrettifyLargeNumber/PrettifyLargeNumber'; export interface IFeatureMetricsStatsProps { totalYes: number; @@ -34,7 +35,9 @@ export const FeatureMetricsStats = ({

Exposure

-

{totalYes}

+

+ +

Total exposure of the feature in the environment{' '} {hoursSuffix}. @@ -56,7 +59,9 @@ export const FeatureMetricsStats = ({

Requests

-

{totalYes + totalNo}

+

+ +

Total requests for the feature in the environment{' '} {hoursSuffix}. diff --git a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironmentMetrics/FeatureOverviewEnvironmentMetrics.tsx b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironmentMetrics/FeatureOverviewEnvironmentMetrics.tsx index 3e542e350a3..d15c0e67605 100644 --- a/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironmentMetrics/FeatureOverviewEnvironmentMetrics.tsx +++ b/frontend/src/component/feature/FeatureView/FeatureOverview/FeatureOverviewEnvironments/FeatureOverviewEnvironment/FeatureOverviewEnvironmentMetrics/FeatureOverviewEnvironmentMetrics.tsx @@ -4,6 +4,7 @@ import { IFeatureEnvironmentMetrics } from 'interfaces/featureToggle'; import { calculatePercentage } from 'utils/calculatePercentage'; import PercentageCircle from 'component/common/PercentageCircle/PercentageCircle'; import { useStyles } from './FeatureOverviewEnvironmentMetrics.styles'; +import { PrettifyLargeNumber } from 'component/common/PrettifyLargeNumber/PrettifyLargeNumber'; interface IFeatureOverviewEnvironmentMetrics { environmentMetric?: IFeatureEnvironmentMetrics; @@ -68,9 +69,15 @@ const FeatureOverviewEnvironmentMetrics = ({

{percentage}%

The feature has been requested{' '} - {environmentMetric.yes + environmentMetric.no} times{' '} - and exposed {environmentMetric.yes} times in the last - hour + + times + {' '} + and exposed{' '} + + {' '} + times + {' '} + in the last hour

diff --git a/frontend/src/utils/testIds.ts b/frontend/src/utils/testIds.ts index 7f455e4e2e8..c443f62c5f1 100644 --- a/frontend/src/utils/testIds.ts +++ b/frontend/src/utils/testIds.ts @@ -73,3 +73,4 @@ export const AUTH_PAGE_ID = 'AUTH_PAGE_ID'; export const ANNOUNCER_ELEMENT_TEST_ID = 'ANNOUNCER_ELEMENT_TEST_ID'; export const INSTANCE_STATUS_BAR_ID = 'INSTANCE_STATUS_BAR_ID'; export const TOAST_TEXT = 'TOAST_TEXT'; +export const LARGE_NUMBER_PRETTIFIED = 'LARGE_NUMBER_PRETTIFIED'; diff --git a/frontend/yarn.lock b/frontend/yarn.lock index d8b32fd8b7c..f4e8af72fd7 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -3319,6 +3319,15 @@ cliui@^7.0.2: strip-ansi "^6.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: version "1.0.4" 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" 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: version "1.52.0" 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" 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: version "17.4.1" resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.4.1.tgz#ebe23284207bb75cee7c408c33e722bfb27b5284"