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