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:
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