diff --git a/frontend/src/component/admin/network/NetworkTrafficUsage/NetworkTrafficUsage.tsx b/frontend/src/component/admin/network/NetworkTrafficUsage/NetworkTrafficUsage.tsx
index 428f246ec8..96d9e01d02 100644
--- a/frontend/src/component/admin/network/NetworkTrafficUsage/NetworkTrafficUsage.tsx
+++ b/frontend/src/component/admin/network/NetworkTrafficUsage/NetworkTrafficUsage.tsx
@@ -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 = () => {
{showOverageCalculations && (
({
@@ -70,6 +71,7 @@ export const RequestSummary: FC = ({
period,
usageTotal,
includedTraffic,
+ purchasedTraffic,
}) => {
const { locationSettings } = useLocationSettings();
@@ -104,6 +106,25 @@ export const RequestSummary: FC = ({
)}
+ {purchasedTraffic > 0 && (
+
+ Additional traffic purchased
+
+ {purchasedTraffic.toLocaleString('en-US')} requests
+
+
+ )}
+ {includedTraffic > 0 && (
+
+ Total traffic
+
+ {(
+ includedTraffic + purchasedTraffic
+ ).toLocaleString('en-US')}{' '}
+ requests
+
+
+ )}
);
diff --git a/frontend/src/component/admin/network/NetworkTrafficUsage/hooks/useChartDataSelection.ts b/frontend/src/component/admin/network/NetworkTrafficUsage/hooks/useChartDataSelection.ts
index 6c317c8639..2cb12bb0b9 100644
--- a/frontend/src/component/admin/network/NetworkTrafficUsage/hooks/useChartDataSelection.ts
+++ b/frontend/src/component/admin/network/NetworkTrafficUsage/hooks/useChartDataSelection.ts
@@ -51,7 +51,7 @@ export const useChartDataSelection = (includedTraffic?: number) => {
},
includedTraffic,
);
- }, [theme, chartDataSelection]);
+ }, [theme, chartDataSelection, includedTraffic]);
return { chartDataSelection, setChartDataSelection, options };
};
diff --git a/frontend/src/component/admin/network/NetworkTrafficUsage/hooks/useTrafficLimit.test.ts b/frontend/src/component/admin/network/NetworkTrafficUsage/hooks/useTrafficLimit.test.ts
deleted file mode 100644
index f9dacbe66c..0000000000
--- a/frontend/src/component/admin/network/NetworkTrafficUsage/hooks/useTrafficLimit.test.ts
+++ /dev/null
@@ -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);
- });
-});
diff --git a/frontend/src/component/admin/network/NetworkTrafficUsage/hooks/useTrafficLimit.ts b/frontend/src/component/admin/network/NetworkTrafficUsage/hooks/useTrafficLimit.ts
deleted file mode 100644
index 688b78b7dd..0000000000
--- a/frontend/src/component/admin/network/NetworkTrafficUsage/hooks/useTrafficLimit.ts
+++ /dev/null
@@ -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;
-};
diff --git a/frontend/src/hooks/api/getters/useTrafficBundles/useTrafficBundles.ts b/frontend/src/hooks/api/getters/useTrafficBundles/useTrafficBundles.ts
new file mode 100644
index 0000000000..55ffe89208
--- /dev/null
+++ b/frontend/src/hooks/api/getters/useTrafficBundles/useTrafficBundles.ts
@@ -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(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());
+};
diff --git a/frontend/src/interfaces/trafficBundles.ts b/frontend/src/interfaces/trafficBundles.ts
new file mode 100644
index 0000000000..e5bb235db6
--- /dev/null
+++ b/frontend/src/interfaces/trafficBundles.ts
@@ -0,0 +1,4 @@
+export interface ITrafficBundles {
+ includedTraffic: number;
+ purchasedTraffic: number;
+}