mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +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", | ||||
|     "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", | ||||
|  | ||||
| @ -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 { 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 = ({ | ||||
|             <Grid item xs={12} sm={4}> | ||||
|                 <article className={styles.item}> | ||||
|                     <h3 className={styles.title}>Exposure</h3> | ||||
|                     <p className={styles.value}>{totalYes}</p> | ||||
|                     <p className={styles.value}> | ||||
|                         <PrettifyLargeNumber value={totalYes} /> | ||||
|                     </p> | ||||
|                     <p className={styles.text}> | ||||
|                         Total exposure of the feature in the environment{' '} | ||||
|                         {hoursSuffix}. | ||||
| @ -56,7 +59,9 @@ export const FeatureMetricsStats = ({ | ||||
|             <Grid item xs={12} sm={4}> | ||||
|                 <article className={styles.item}> | ||||
|                     <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}> | ||||
|                         Total requests for the feature in the environment{' '} | ||||
|                         {hoursSuffix}. | ||||
|  | ||||
| @ -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 = ({ | ||||
|                 <p className={styles.percentage}>{percentage}%</p> | ||||
|                 <p className={styles.infoParagraph}> | ||||
|                     The feature has been requested{' '} | ||||
|                     <b>{environmentMetric.yes + environmentMetric.no} times</b>{' '} | ||||
|                     and exposed <b>{environmentMetric.yes} times</b> in the last | ||||
|                     hour | ||||
|                     <b> | ||||
|                         <PrettifyLargeNumber value={total} /> times | ||||
|                     </b>{' '} | ||||
|                     and exposed{' '} | ||||
|                     <b> | ||||
|                         <PrettifyLargeNumber value={environmentMetric.yes} />{' '} | ||||
|                         times | ||||
|                     </b>{' '} | ||||
|                     in the last hour | ||||
|                 </p> | ||||
|             </div> | ||||
|             <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 INSTANCE_STATUS_BAR_ID = 'INSTANCE_STATUS_BAR_ID'; | ||||
| 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" | ||||
|     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" | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user