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 { Bar } from 'react-chartjs-2';
 | 
				
			||||||
import annotationPlugin from 'chartjs-plugin-annotation';
 | 
					import annotationPlugin from 'chartjs-plugin-annotation';
 | 
				
			||||||
import { customHighlightPlugin } from 'component/common/Chart/customHighlightPlugin';
 | 
					import { customHighlightPlugin } from 'component/common/Chart/customHighlightPlugin';
 | 
				
			||||||
import { useTrafficLimit } from './hooks/useTrafficLimit';
 | 
					 | 
				
			||||||
import { PeriodSelector } from './PeriodSelector';
 | 
					import { PeriodSelector } from './PeriodSelector';
 | 
				
			||||||
import { useUiFlag } from 'hooks/useUiFlag';
 | 
					import { useUiFlag } from 'hooks/useUiFlag';
 | 
				
			||||||
import { OverageInfo, RequestSummary } from './RequestSummary';
 | 
					import { OverageInfo, RequestSummary } from './RequestSummary';
 | 
				
			||||||
@ -27,6 +26,7 @@ import { getChartLabel } from './chart-functions';
 | 
				
			|||||||
import { useTrafficStats } from './hooks/useStats';
 | 
					import { useTrafficStats } from './hooks/useStats';
 | 
				
			||||||
import { BoldText, StyledBox, TopRow } from './SharedComponents';
 | 
					import { BoldText, StyledBox, TopRow } from './SharedComponents';
 | 
				
			||||||
import { useChartDataSelection } from './hooks/useChartDataSelection';
 | 
					import { useChartDataSelection } from './hooks/useChartDataSelection';
 | 
				
			||||||
 | 
					import { useTrafficBundles } from '../../../../hooks/api/getters/useTrafficBundles/useTrafficBundles';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const TrafficInfoBoxes = styled('div')(({ theme }) => ({
 | 
					const TrafficInfoBoxes = styled('div')(({ theme }) => ({
 | 
				
			||||||
    display: 'grid',
 | 
					    display: 'grid',
 | 
				
			||||||
@ -42,10 +42,13 @@ const NetworkTrafficUsage: FC = () => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    const { isOss } = useUiConfig();
 | 
					    const { isOss } = useUiConfig();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const includedTraffic = useTrafficLimit();
 | 
					    const { trafficBundles } = useTrafficBundles();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const totalTraffic =
 | 
				
			||||||
 | 
					        trafficBundles.includedTraffic + trafficBundles.purchasedTraffic;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const { chartDataSelection, setChartDataSelection, options } =
 | 
					    const { chartDataSelection, setChartDataSelection, options } =
 | 
				
			||||||
        useChartDataSelection(includedTraffic);
 | 
					        useChartDataSelection(totalTraffic);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const {
 | 
					    const {
 | 
				
			||||||
        chartData,
 | 
					        chartData,
 | 
				
			||||||
@ -53,19 +56,19 @@ const NetworkTrafficUsage: FC = () => {
 | 
				
			|||||||
        overageCost,
 | 
					        overageCost,
 | 
				
			||||||
        estimatedMonthlyCost,
 | 
					        estimatedMonthlyCost,
 | 
				
			||||||
        requestSummaryUsage,
 | 
					        requestSummaryUsage,
 | 
				
			||||||
    } = useTrafficStats(includedTraffic, chartDataSelection);
 | 
					    } = useTrafficStats(totalTraffic, chartDataSelection);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const showOverageCalculations =
 | 
					    const showOverageCalculations =
 | 
				
			||||||
        chartDataSelection.grouping === 'daily' &&
 | 
					        chartDataSelection.grouping === 'daily' &&
 | 
				
			||||||
        chartDataSelection.month === currentMonth &&
 | 
					        chartDataSelection.month === currentMonth &&
 | 
				
			||||||
        includedTraffic > 0 &&
 | 
					        totalTraffic > 0 &&
 | 
				
			||||||
        usageTotal - includedTraffic > 0 &&
 | 
					        usageTotal - totalTraffic > 0 &&
 | 
				
			||||||
        estimateTrafficDataCost;
 | 
					        estimateTrafficDataCost;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const showConsumptionBillingWarning =
 | 
					    const showConsumptionBillingWarning =
 | 
				
			||||||
        (chartDataSelection.grouping === 'monthly' ||
 | 
					        (chartDataSelection.grouping === 'monthly' ||
 | 
				
			||||||
            chartDataSelection.month === currentMonth) &&
 | 
					            chartDataSelection.month === currentMonth) &&
 | 
				
			||||||
        includedTraffic > 0 &&
 | 
					        totalTraffic > 0 &&
 | 
				
			||||||
        overageCost > 0;
 | 
					        overageCost > 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return (
 | 
					    return (
 | 
				
			||||||
@ -100,12 +103,17 @@ const NetworkTrafficUsage: FC = () => {
 | 
				
			|||||||
                                <RequestSummary
 | 
					                                <RequestSummary
 | 
				
			||||||
                                    period={chartDataSelection}
 | 
					                                    period={chartDataSelection}
 | 
				
			||||||
                                    usageTotal={requestSummaryUsage}
 | 
					                                    usageTotal={requestSummaryUsage}
 | 
				
			||||||
                                    includedTraffic={includedTraffic}
 | 
					                                    includedTraffic={
 | 
				
			||||||
 | 
					                                        trafficBundles.includedTraffic
 | 
				
			||||||
 | 
					                                    }
 | 
				
			||||||
 | 
					                                    purchasedTraffic={
 | 
				
			||||||
 | 
					                                        trafficBundles.purchasedTraffic
 | 
				
			||||||
 | 
					                                    }
 | 
				
			||||||
                                />
 | 
					                                />
 | 
				
			||||||
                                {showOverageCalculations && (
 | 
					                                {showOverageCalculations && (
 | 
				
			||||||
                                    <OverageInfo
 | 
					                                    <OverageInfo
 | 
				
			||||||
                                        overageCost={overageCost}
 | 
					                                        overageCost={overageCost}
 | 
				
			||||||
                                        overages={usageTotal - includedTraffic}
 | 
					                                        overages={usageTotal - totalTraffic}
 | 
				
			||||||
                                        estimatedMonthlyCost={
 | 
					                                        estimatedMonthlyCost={
 | 
				
			||||||
                                            estimatedMonthlyCost
 | 
					                                            estimatedMonthlyCost
 | 
				
			||||||
                                        }
 | 
					                                        }
 | 
				
			||||||
 | 
				
			|||||||
@ -10,6 +10,7 @@ type Props = {
 | 
				
			|||||||
    period: ChartDataSelection;
 | 
					    period: ChartDataSelection;
 | 
				
			||||||
    usageTotal: number;
 | 
					    usageTotal: number;
 | 
				
			||||||
    includedTraffic: number;
 | 
					    includedTraffic: number;
 | 
				
			||||||
 | 
					    purchasedTraffic: number;
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const Container = styled('article')(({ theme }) => ({
 | 
					const Container = styled('article')(({ theme }) => ({
 | 
				
			||||||
@ -70,6 +71,7 @@ export const RequestSummary: FC<Props> = ({
 | 
				
			|||||||
    period,
 | 
					    period,
 | 
				
			||||||
    usageTotal,
 | 
					    usageTotal,
 | 
				
			||||||
    includedTraffic,
 | 
					    includedTraffic,
 | 
				
			||||||
 | 
					    purchasedTraffic,
 | 
				
			||||||
}) => {
 | 
					}) => {
 | 
				
			||||||
    const { locationSettings } = useLocationSettings();
 | 
					    const { locationSettings } = useLocationSettings();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -104,6 +106,25 @@ export const RequestSummary: FC<Props> = ({
 | 
				
			|||||||
                        </dd>
 | 
					                        </dd>
 | 
				
			||||||
                    </Row>
 | 
					                    </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>
 | 
					            </List>
 | 
				
			||||||
        </Container>
 | 
					        </Container>
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
				
			|||||||
@ -51,7 +51,7 @@ export const useChartDataSelection = (includedTraffic?: number) => {
 | 
				
			|||||||
            },
 | 
					            },
 | 
				
			||||||
            includedTraffic,
 | 
					            includedTraffic,
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
    }, [theme, chartDataSelection]);
 | 
					    }, [theme, chartDataSelection, includedTraffic]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return { chartDataSelection, setChartDataSelection, options };
 | 
					    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