mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	feat: added network traffic bundle (#9691)
This commit is contained in:
		
							parent
							
								
									07d11c7a87
								
							
						
					
					
						commit
						f20ea86c61
					
				| @ -18,7 +18,6 @@ import { | ||||
| import { Bar } from 'react-chartjs-2'; | ||||
| import annotationPlugin from 'chartjs-plugin-annotation'; | ||||
| import { customHighlightPlugin } from 'component/common/Chart/customHighlightPlugin'; | ||||
| import { useTrafficLimit } from './hooks/useTrafficLimit'; | ||||
| import { PeriodSelector } from './PeriodSelector'; | ||||
| import { useUiFlag } from 'hooks/useUiFlag'; | ||||
| import { OverageInfo, RequestSummary } from './RequestSummary'; | ||||
| @ -27,6 +26,7 @@ import { getChartLabel } from './chart-functions'; | ||||
| import { useTrafficStats } from './hooks/useStats'; | ||||
| import { BoldText, StyledBox, TopRow } from './SharedComponents'; | ||||
| import { useChartDataSelection } from './hooks/useChartDataSelection'; | ||||
| import { useTrafficBundles } from '../../../../hooks/api/getters/useTrafficBundles/useTrafficBundles'; | ||||
| 
 | ||||
| const TrafficInfoBoxes = styled('div')(({ theme }) => ({ | ||||
|     display: 'grid', | ||||
| @ -42,10 +42,13 @@ const NetworkTrafficUsage: FC = () => { | ||||
| 
 | ||||
|     const { isOss } = useUiConfig(); | ||||
| 
 | ||||
|     const includedTraffic = useTrafficLimit(); | ||||
|     const { trafficBundles } = useTrafficBundles(); | ||||
| 
 | ||||
|     const totalTraffic = | ||||
|         trafficBundles.includedTraffic + trafficBundles.purchasedTraffic; | ||||
| 
 | ||||
|     const { chartDataSelection, setChartDataSelection, options } = | ||||
|         useChartDataSelection(includedTraffic); | ||||
|         useChartDataSelection(totalTraffic); | ||||
| 
 | ||||
|     const { | ||||
|         chartData, | ||||
| @ -53,19 +56,19 @@ const NetworkTrafficUsage: FC = () => { | ||||
|         overageCost, | ||||
|         estimatedMonthlyCost, | ||||
|         requestSummaryUsage, | ||||
|     } = useTrafficStats(includedTraffic, chartDataSelection); | ||||
|     } = useTrafficStats(totalTraffic, chartDataSelection); | ||||
| 
 | ||||
|     const showOverageCalculations = | ||||
|         chartDataSelection.grouping === 'daily' && | ||||
|         chartDataSelection.month === currentMonth && | ||||
|         includedTraffic > 0 && | ||||
|         usageTotal - includedTraffic > 0 && | ||||
|         totalTraffic > 0 && | ||||
|         usageTotal - totalTraffic > 0 && | ||||
|         estimateTrafficDataCost; | ||||
| 
 | ||||
|     const showConsumptionBillingWarning = | ||||
|         (chartDataSelection.grouping === 'monthly' || | ||||
|             chartDataSelection.month === currentMonth) && | ||||
|         includedTraffic > 0 && | ||||
|         totalTraffic > 0 && | ||||
|         overageCost > 0; | ||||
| 
 | ||||
|     return ( | ||||
| @ -100,12 +103,17 @@ const NetworkTrafficUsage: FC = () => { | ||||
|                                 <RequestSummary | ||||
|                                     period={chartDataSelection} | ||||
|                                     usageTotal={requestSummaryUsage} | ||||
|                                     includedTraffic={includedTraffic} | ||||
|                                     includedTraffic={ | ||||
|                                         trafficBundles.includedTraffic | ||||
|                                     } | ||||
|                                     purchasedTraffic={ | ||||
|                                         trafficBundles.purchasedTraffic | ||||
|                                     } | ||||
|                                 /> | ||||
|                                 {showOverageCalculations && ( | ||||
|                                     <OverageInfo | ||||
|                                         overageCost={overageCost} | ||||
|                                         overages={usageTotal - includedTraffic} | ||||
|                                         overages={usageTotal - totalTraffic} | ||||
|                                         estimatedMonthlyCost={ | ||||
|                                             estimatedMonthlyCost | ||||
|                                         } | ||||
|  | ||||
| @ -10,6 +10,7 @@ type Props = { | ||||
|     period: ChartDataSelection; | ||||
|     usageTotal: number; | ||||
|     includedTraffic: number; | ||||
|     purchasedTraffic: number; | ||||
| }; | ||||
| 
 | ||||
| const Container = styled('article')(({ theme }) => ({ | ||||
| @ -70,6 +71,7 @@ export const RequestSummary: FC<Props> = ({ | ||||
|     period, | ||||
|     usageTotal, | ||||
|     includedTraffic, | ||||
|     purchasedTraffic, | ||||
| }) => { | ||||
|     const { locationSettings } = useLocationSettings(); | ||||
| 
 | ||||
| @ -104,6 +106,25 @@ export const RequestSummary: FC<Props> = ({ | ||||
|                         </dd> | ||||
|                     </Row> | ||||
|                 )} | ||||
|                 {purchasedTraffic > 0 && ( | ||||
|                     <Row> | ||||
|                         <dt>Additional traffic purchased</dt> | ||||
|                         <dd> | ||||
|                             {purchasedTraffic.toLocaleString('en-US')} requests | ||||
|                         </dd> | ||||
|                     </Row> | ||||
|                 )} | ||||
|                 {includedTraffic > 0 && ( | ||||
|                     <Row> | ||||
|                         <dt>Total traffic</dt> | ||||
|                         <dd> | ||||
|                             {( | ||||
|                                 includedTraffic + purchasedTraffic | ||||
|                             ).toLocaleString('en-US')}{' '} | ||||
|                             requests | ||||
|                         </dd> | ||||
|                     </Row> | ||||
|                 )} | ||||
|             </List> | ||||
|         </Container> | ||||
|     ); | ||||
|  | ||||
| @ -51,7 +51,7 @@ export const useChartDataSelection = (includedTraffic?: number) => { | ||||
|             }, | ||||
|             includedTraffic, | ||||
|         ); | ||||
|     }, [theme, chartDataSelection]); | ||||
|     }, [theme, chartDataSelection, includedTraffic]); | ||||
| 
 | ||||
|     return { chartDataSelection, setChartDataSelection, options }; | ||||
| }; | ||||
|  | ||||
| @ -1,77 +0,0 @@ | ||||
| import { renderHook } from '@testing-library/react'; | ||||
| import { useTrafficLimit } from './useTrafficLimit'; | ||||
| import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; | ||||
| import { useUiFlag } from 'hooks/useUiFlag'; | ||||
| import { vi, describe, it, expect } from 'vitest'; | ||||
| 
 | ||||
| vi.mock('hooks/api/getters/useUiConfig/useUiConfig'); | ||||
| vi.mock('hooks/useUiFlag'); | ||||
| 
 | ||||
| describe('useTrafficLimit', () => { | ||||
|     it('should return requests limit if user is on pro plan', () => { | ||||
|         vi.mocked(useUiConfig).mockReturnValue({ | ||||
|             isPro: () => true, | ||||
|             isEnterprise: () => false, | ||||
|             uiConfig: { | ||||
|                 billing: 'pay-as-you-go', | ||||
|             }, | ||||
|         } as any); | ||||
|         vi.mocked(useUiFlag).mockReturnValue(false); | ||||
| 
 | ||||
|         const { result } = renderHook(() => useTrafficLimit()); | ||||
| 
 | ||||
|         expect(result.current).toBe(53_000_000); | ||||
|     }); | ||||
| 
 | ||||
|     it('should return PAYG plan requests limit if enterprise-payg is enabled and billing is pay-as-you-go', () => { | ||||
|         vi.mocked(useUiConfig).mockReturnValue({ | ||||
|             isPro: () => false, | ||||
|             isEnterprise: () => true, | ||||
|             uiConfig: { billing: 'pay-as-you-go' }, | ||||
|         } as any); | ||||
|         vi.mocked(useUiFlag).mockReturnValue(true); | ||||
| 
 | ||||
|         const { result } = renderHook(() => useTrafficLimit()); | ||||
| 
 | ||||
|         expect(result.current).toBe(53_000_000); | ||||
|     }); | ||||
| 
 | ||||
|     it('should return 0 if user is not on pro plan and pay-as-you-go conditions are not met', () => { | ||||
|         vi.mocked(useUiConfig).mockReturnValue({ | ||||
|             isPro: () => false, | ||||
|             isEnterprise: () => false, | ||||
|             uiConfig: {}, | ||||
|         } as any); | ||||
|         vi.mocked(useUiFlag).mockReturnValue(false); | ||||
| 
 | ||||
|         const { result } = renderHook(() => useTrafficLimit()); | ||||
| 
 | ||||
|         expect(result.current).toBe(0); | ||||
|     }); | ||||
| 
 | ||||
|     it('should return 0 if user is not on pro plan and flag is disabled', () => { | ||||
|         vi.mocked(useUiConfig).mockReturnValue({ | ||||
|             isPro: () => false, | ||||
|             isEnterprise: () => true, | ||||
|             uiConfig: { billing: 'pay-as-you-go' }, | ||||
|         } as any); | ||||
|         vi.mocked(useUiFlag).mockReturnValue(false); | ||||
| 
 | ||||
|         const { result } = renderHook(() => useTrafficLimit()); | ||||
| 
 | ||||
|         expect(result.current).toBe(0); | ||||
|     }); | ||||
| 
 | ||||
|     it('should return 0 if enterprise PAYG is enabled but billing is not pay-as-you-go', () => { | ||||
|         vi.mocked(useUiConfig).mockReturnValue({ | ||||
|             isPro: () => false, | ||||
|             isEnterprise: () => false, | ||||
|             uiConfig: { billing: 'subscription' }, | ||||
|         } as any); | ||||
|         vi.mocked(useUiFlag).mockReturnValue(true); | ||||
| 
 | ||||
|         const { result } = renderHook(() => useTrafficLimit()); | ||||
| 
 | ||||
|         expect(result.current).toBe(0); | ||||
|     }); | ||||
| }); | ||||
| @ -1,24 +0,0 @@ | ||||
| import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig'; | ||||
| import { useUiFlag } from 'hooks/useUiFlag'; | ||||
| 
 | ||||
| const proPlanIncludedRequests = 53_000_000; | ||||
| const paygPlanIncludedRequests = proPlanIncludedRequests; | ||||
| 
 | ||||
| export const useTrafficLimit = () => { | ||||
|     const { isPro, isEnterprise, uiConfig } = useUiConfig(); | ||||
|     const isEnterprisePaygEnabled = useUiFlag('enterprise-payg'); | ||||
| 
 | ||||
|     if (isPro()) { | ||||
|         return proPlanIncludedRequests; | ||||
|     } | ||||
| 
 | ||||
|     if ( | ||||
|         isEnterprisePaygEnabled && | ||||
|         isEnterprise() && | ||||
|         uiConfig.billing === 'pay-as-you-go' | ||||
|     ) { | ||||
|         return paygPlanIncludedRequests; | ||||
|     } | ||||
| 
 | ||||
|     return 0; | ||||
| }; | ||||
| @ -0,0 +1,76 @@ | ||||
| import { formatApiPath } from '../../../../utils/formatPath'; | ||||
| import type { ITrafficBundles } from '../../../../interfaces/trafficBundles'; | ||||
| import useSWR from 'swr'; | ||||
| import handleErrorResponses from '../httpErrorResponseHandler'; | ||||
| import { useMemo } from 'react'; | ||||
| import useUiConfig from '../useUiConfig/useUiConfig'; | ||||
| 
 | ||||
| type IUseTrafficBundlesOutput = { | ||||
|     trafficBundles: ITrafficBundles; | ||||
|     loading: boolean; | ||||
|     error?: Error; | ||||
|     refetch: () => void; | ||||
| }; | ||||
| 
 | ||||
| const DEFAULT_TRAFFIC_INCLUDED_PAYG = 53; | ||||
| const DEFAULT_TRAFFIC_INCLUDED_ENTERPRISE = 259; | ||||
| const DEFAULT_TRAFFIC_MULTIPLIER = 1_000_000; | ||||
| 
 | ||||
| const defaultIncludedTraffic = ( | ||||
|     isEnterprise: boolean, | ||||
|     billing?: string, | ||||
| ): number => { | ||||
|     if (!isEnterprise || billing === 'pay-as-you-go') { | ||||
|         return DEFAULT_TRAFFIC_INCLUDED_PAYG; | ||||
|     } | ||||
|     return DEFAULT_TRAFFIC_INCLUDED_ENTERPRISE; | ||||
| }; | ||||
| 
 | ||||
| function includedTraffic( | ||||
|     includedTraffic: number | undefined, | ||||
|     isEnterprise: boolean, | ||||
|     billing?: string, | ||||
| ): number { | ||||
|     if (includedTraffic === undefined) { | ||||
|         return defaultIncludedTraffic(isEnterprise, billing); | ||||
|     } | ||||
|     return includedTraffic; | ||||
| } | ||||
| 
 | ||||
| function purchasedTraffic(purchasedTraffic: number | undefined): number { | ||||
|     if (purchasedTraffic === undefined) { | ||||
|         return 0; | ||||
|     } | ||||
|     return purchasedTraffic; | ||||
| } | ||||
| 
 | ||||
| export const useTrafficBundles = (): IUseTrafficBundlesOutput => { | ||||
|     const { isEnterprise, uiConfig } = useUiConfig(); | ||||
|     const path = formatApiPath('/api/instance/trafficBundles'); | ||||
|     const { data, error, mutate } = useSWR<ITrafficBundles>(path, fetcher); | ||||
| 
 | ||||
|     const trafficBundles = useMemo(() => { | ||||
|         return { | ||||
|             includedTraffic: | ||||
|                 includedTraffic( | ||||
|                     data?.includedTraffic, | ||||
|                     isEnterprise(), | ||||
|                     uiConfig.billing, | ||||
|                 ) * DEFAULT_TRAFFIC_MULTIPLIER, | ||||
|             purchasedTraffic: | ||||
|                 purchasedTraffic(data?.purchasedTraffic) * | ||||
|                 DEFAULT_TRAFFIC_MULTIPLIER, | ||||
|         }; | ||||
|     }, [data]); | ||||
|     return { | ||||
|         trafficBundles, | ||||
|         loading: !error && !data, | ||||
|         refetch: mutate, | ||||
|     }; | ||||
| }; | ||||
| 
 | ||||
| const fetcher = (path: string) => { | ||||
|     return fetch(path) | ||||
|         .then(handleErrorResponses('configuration')) | ||||
|         .then((res) => res.json()); | ||||
| }; | ||||
							
								
								
									
										4
									
								
								frontend/src/interfaces/trafficBundles.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								frontend/src/interfaces/trafficBundles.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,4 @@ | ||||
| export interface ITrafficBundles { | ||||
|     includedTraffic: number; | ||||
|     purchasedTraffic: number; | ||||
| } | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user