1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-05-12 01:17:04 +02:00

feat: added network traffic bundle (#9691)

This commit is contained in:
Christopher Kolstad 2025-04-03 14:37:07 +02:00 committed by GitHub
parent 07d11c7a87
commit f20ea86c61
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 119 additions and 111 deletions

View File

@ -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
}

View File

@ -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>
);

View File

@ -51,7 +51,7 @@ export const useChartDataSelection = (includedTraffic?: number) => {
},
includedTraffic,
);
}, [theme, chartDataSelection]);
}, [theme, chartDataSelection, includedTraffic]);
return { chartDataSelection, setChartDataSelection, options };
};

View File

@ -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);
});
});

View File

@ -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;
};

View File

@ -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());
};

View File

@ -0,0 +1,4 @@
export interface ITrafficBundles {
includedTraffic: number;
purchasedTraffic: number;
}