diff --git a/frontend/src/component/App.tsx b/frontend/src/component/App.tsx
index 96c813919c11..ac65ed379b97 100644
--- a/frontend/src/component/App.tsx
+++ b/frontend/src/component/App.tsx
@@ -23,6 +23,7 @@ import { LicenseBanner } from './banners/internalBanners/LicenseBanner';
import { Demo } from './demo/Demo';
import { LoginRedirect } from './common/LoginRedirect/LoginRedirect';
import { SecurityBanner } from './banners/internalBanners/SecurityBanner';
+import { TrafficOverageBanner } from './banners/TrafficOverageBanner';
const StyledContainer = styled('div')(() => ({
'& ul': {
@@ -68,6 +69,7 @@ export const App = () => {
/>
+
diff --git a/frontend/src/component/admin/network/NetworkTrafficUsage/NetworkTrafficUsage.tsx b/frontend/src/component/admin/network/NetworkTrafficUsage/NetworkTrafficUsage.tsx
index 3c6ab14fb920..7beb26d7f8ef 100644
--- a/frontend/src/component/admin/network/NetworkTrafficUsage/NetworkTrafficUsage.tsx
+++ b/frontend/src/component/admin/network/NetworkTrafficUsage/NetworkTrafficUsage.tsx
@@ -239,20 +239,11 @@ export const NetworkTrafficUsage: VFC = () => {
elseShow={
<>
0 && overageCost > 0}
+ condition={includedTraffic > 0}
show={
-
- Heads up! You are currently consuming
- more requests than your plan includes and will
- be billed according to our terms. Please see{' '}
-
- this page
- {' '}
- for more information. In order to reduce your
- traffic consumption, you may configure an{' '}
+
+ In order to reduce your traffic consumption,
+ consider setting up an{' '}
{
+ const period = toSelectablePeriod(new Date()).key;
+
+ testServerRoute(server, '/api/admin/ui-config', {
+ environment: 'Enterprise',
+ versionInfo: {
+ current: {
+ enterprise: 'Enterprise',
+ },
+ },
+ billing: 'pay-as-you-go',
+ flags: {
+ 'enterprise-payg': true,
+ estimateTrafficDataCost: true,
+ },
+ });
+
+ testServerRoute(server, `/api/admin/metrics/traffic/${period}`, {
+ period,
+ apiData: [
+ {
+ apiPath: '/api/client',
+ days: [
+ {
+ day: `${period}-01T00:00:00.000Z`,
+ trafficTypes: [
+ {
+ group: 'successful-requests',
+ count: totalRequests,
+ },
+ ],
+ },
+ ],
+ },
+ ],
+ });
+};
+
+test('Displays overage banner when overage cost is calculated', async () => {
+ setupApi(BILLING_INCLUDED_REQUESTS + 1_000_000);
+
+ render();
+
+ const bannerMessage = await screen.findByText(
+ /You're using more requests than your plan/,
+ );
+ expect(bannerMessage).toBeInTheDocument();
+});
+
+test('Displays estimated monthly cost banner when usage is projected to exceed', async () => {
+ setupApi(BILLING_INCLUDED_REQUESTS - 1_000_000);
+
+ render();
+
+ const bannerMessage = await screen.findByText(
+ /Based on your current usage, you're projected to exceed your plan/,
+ );
+ expect(bannerMessage).toBeInTheDocument();
+});
+
+test('Does not display banner when no overage or estimated cost', async () => {
+ setupApi();
+
+ render();
+
+ expect(screen.queryByText('Heads up!')).not.toBeInTheDocument();
+});
diff --git a/frontend/src/component/banners/TrafficOverageBanner.tsx b/frontend/src/component/banners/TrafficOverageBanner.tsx
new file mode 100644
index 000000000000..e31bb2046d43
--- /dev/null
+++ b/frontend/src/component/banners/TrafficOverageBanner.tsx
@@ -0,0 +1,84 @@
+import { useUiFlag } from 'hooks/useUiFlag';
+import { Banner } from './Banner/Banner';
+import { useTrafficDataEstimation } from 'hooks/useTrafficData';
+import { useTrafficLimit } from 'component/admin/network/NetworkTrafficUsage/hooks/useTrafficLimit';
+import { useInstanceTrafficMetrics } from 'hooks/api/getters/useInstanceTrafficMetrics/useInstanceTrafficMetrics';
+import { BILLING_TRAFFIC_BUNDLE_PRICE } from 'component/admin/billing/BillingDashboard/BillingPlan/BillingPlan';
+import { useMemo } from 'react';
+
+export const TrafficOverageBanner = () => {
+ const estimateTrafficDataCostEnabled = useUiFlag('estimateTrafficDataCost');
+ const includedTraffic = useTrafficLimit();
+ const {
+ currentPeriod,
+ toChartData,
+ toTrafficUsageSum,
+ endpointsInfo,
+ getDayLabels,
+ calculateOverageCost,
+ calculateEstimatedMonthlyCost,
+ } = useTrafficDataEstimation();
+ const traffic = useInstanceTrafficMetrics(currentPeriod.key);
+
+ const trafficData = useMemo(
+ () =>
+ toChartData(
+ getDayLabels(currentPeriod.dayCount),
+ traffic,
+ endpointsInfo,
+ ),
+ [traffic, currentPeriod, endpointsInfo],
+ );
+
+ const calculatedOverageCost = useMemo(
+ () =>
+ includedTraffic
+ ? calculateOverageCost(
+ toTrafficUsageSum(trafficData),
+ includedTraffic,
+ BILLING_TRAFFIC_BUNDLE_PRICE,
+ )
+ : 0,
+ [includedTraffic, trafficData],
+ );
+
+ const estimatedMonthlyCost = useMemo(
+ () =>
+ includedTraffic && estimateTrafficDataCostEnabled
+ ? calculateEstimatedMonthlyCost(
+ currentPeriod.key,
+ trafficData,
+ includedTraffic,
+ new Date(),
+ BILLING_TRAFFIC_BUNDLE_PRICE,
+ )
+ : 0,
+ [
+ includedTraffic,
+ estimateTrafficDataCostEnabled,
+ trafficData,
+ currentPeriod,
+ ],
+ );
+
+ const overageMessage =
+ calculatedOverageCost > 0
+ ? `**Heads up!** You're using more requests than your plan [includes](https://www.getunleash.io/pricing), and additional charges will apply per our [terms](https://www.getunleash.io/fair-use-policy).`
+ : estimatedMonthlyCost > 0
+ ? `**Heads up!** Based on your current usage, you're projected to exceed your plan's [limit](https://www.getunleash.io/pricing), and additional charges may apply per our [terms](https://www.getunleash.io/fair-use-policy).`
+ : undefined;
+
+ if (!overageMessage) return null;
+
+ return (
+
+ );
+};
diff --git a/frontend/src/hooks/api/getters/useInstanceTrafficMetrics/useInstanceTrafficMetrics.ts b/frontend/src/hooks/api/getters/useInstanceTrafficMetrics/useInstanceTrafficMetrics.ts
index b95d82d68c34..aa81b12be3fe 100644
--- a/frontend/src/hooks/api/getters/useInstanceTrafficMetrics/useInstanceTrafficMetrics.ts
+++ b/frontend/src/hooks/api/getters/useInstanceTrafficMetrics/useInstanceTrafficMetrics.ts
@@ -1,8 +1,14 @@
-import useSWR from 'swr';
import { useMemo } from 'react';
import { formatApiPath } from 'utils/formatPath';
import handleErrorResponses from '../httpErrorResponseHandler';
import type { TrafficUsageDataSegmentedSchema } from 'openapi';
+import { useConditionalSWR } from '../useConditionalSWR/useConditionalSWR';
+import useUiConfig from '../useUiConfig/useUiConfig';
+
+const DEFAULT_DATA: TrafficUsageDataSegmentedSchema = {
+ apiData: [],
+ period: '',
+};
export interface IInstanceTrafficMetricsResponse {
usage: TrafficUsageDataSegmentedSchema;
@@ -17,14 +23,21 @@ export interface IInstanceTrafficMetricsResponse {
export const useInstanceTrafficMetrics = (
period: string,
): IInstanceTrafficMetricsResponse => {
- const { data, error, mutate } = useSWR(
- formatApiPath(`api/admin/metrics/traffic/${period}`),
- fetcher,
- );
+ const {
+ isPro,
+ uiConfig: { billing },
+ } = useUiConfig();
+ const { data, error, mutate } =
+ useConditionalSWR(
+ isPro() || billing === 'pay-as-you-go',
+ DEFAULT_DATA,
+ formatApiPath(`api/admin/metrics/traffic/${period}`),
+ fetcher,
+ );
return useMemo(
() => ({
- usage: data,
+ usage: data ?? DEFAULT_DATA,
loading: !error && !data,
refetch: () => mutate(),
error,