mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-25 00:07:47 +01:00
chore: PAYG traffic bundles (#8805)
https://linear.app/unleash/issue/2-2989/unleash-payg-auto-traffic-billing Integrates auto traffic bundle billing with PAYG. Currently assumes the PAYG traffic bundle will have the same `$5/1_000_000` cost as the existing Pro traffic bundle, with the same `53_000_000` included requests. However some adjustments are included so it's easier to change this in the future.
This commit is contained in:
parent
332440491a
commit
b7af9b7ec3
@ -6,9 +6,15 @@ import { GridColLink } from './GridColLink/GridColLink';
|
||||
import type { IInstanceStatus } from 'interfaces/instance';
|
||||
import { useUsers } from 'hooks/api/getters/useUsers/useUsers';
|
||||
import {
|
||||
BILLING_INCLUDED_REQUESTS,
|
||||
BILLING_PAYG_DEFAULT_MINIMUM_SEATS,
|
||||
BILLING_PAYG_USER_PRICE,
|
||||
BILLING_TRAFFIC_BUNDLE_PRICE,
|
||||
} from './BillingPlan';
|
||||
import { useTrafficDataEstimation } from 'hooks/useTrafficData';
|
||||
import { useInstanceTrafficMetrics } from 'hooks/api/getters/useInstanceTrafficMetrics/useInstanceTrafficMetrics';
|
||||
import { useMemo } from 'react';
|
||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||
|
||||
const StyledInfoLabel = styled(Typography)(({ theme }) => ({
|
||||
fontSize: theme.fontSizes.smallBody,
|
||||
@ -27,6 +33,14 @@ export const BillingDetailsPAYG = ({
|
||||
instanceStatus,
|
||||
}: IBillingDetailsPAYGProps) => {
|
||||
const { users, loading } = useUsers();
|
||||
const {
|
||||
currentPeriod,
|
||||
toChartData,
|
||||
toTrafficUsageSum,
|
||||
endpointsInfo,
|
||||
getDayLabels,
|
||||
calculateOverageCost,
|
||||
} = useTrafficDataEstimation();
|
||||
|
||||
const eligibleUsers = users.filter((user) => user.email);
|
||||
|
||||
@ -36,7 +50,27 @@ export const BillingDetailsPAYG = ({
|
||||
const billableUsers = Math.max(eligibleUsers.length, minSeats);
|
||||
const usersCost = BILLING_PAYG_USER_PRICE * billableUsers;
|
||||
|
||||
const totalCost = usersCost;
|
||||
const includedTraffic = BILLING_INCLUDED_REQUESTS;
|
||||
const traffic = useInstanceTrafficMetrics(currentPeriod.key);
|
||||
|
||||
const overageCost = useMemo(() => {
|
||||
if (!includedTraffic) {
|
||||
return 0;
|
||||
}
|
||||
const trafficData = toChartData(
|
||||
getDayLabels(currentPeriod.dayCount),
|
||||
traffic,
|
||||
endpointsInfo,
|
||||
);
|
||||
const totalTraffic = toTrafficUsageSum(trafficData);
|
||||
return calculateOverageCost(
|
||||
totalTraffic,
|
||||
includedTraffic,
|
||||
BILLING_TRAFFIC_BUNDLE_PRICE,
|
||||
);
|
||||
}, [includedTraffic, traffic, currentPeriod, endpointsInfo]);
|
||||
|
||||
const totalCost = usersCost + overageCost;
|
||||
|
||||
if (loading) return null;
|
||||
|
||||
@ -72,6 +106,36 @@ export const BillingDetailsPAYG = ({
|
||||
</Typography>
|
||||
</GridCol>
|
||||
</GridRow>
|
||||
<ConditionallyRender
|
||||
condition={overageCost > 0}
|
||||
show={
|
||||
<GridRow>
|
||||
<GridCol vertical>
|
||||
<Typography>
|
||||
<strong>Accrued traffic charges</strong>
|
||||
<GridColLink>
|
||||
<Link to='/admin/network/data-usage'>
|
||||
view details
|
||||
</Link>
|
||||
</GridColLink>
|
||||
</Typography>
|
||||
<StyledInfoLabel>
|
||||
${BILLING_TRAFFIC_BUNDLE_PRICE} per 1
|
||||
million started above included data
|
||||
</StyledInfoLabel>
|
||||
</GridCol>
|
||||
<GridCol>
|
||||
<Typography
|
||||
sx={(theme) => ({
|
||||
fontSize: theme.fontSizes.mainHeader,
|
||||
})}
|
||||
>
|
||||
${overageCost.toFixed(2)}
|
||||
</Typography>
|
||||
</GridCol>
|
||||
</GridRow>
|
||||
}
|
||||
/>
|
||||
</Grid>
|
||||
<StyledDivider />
|
||||
<Grid container>
|
||||
|
@ -14,6 +14,7 @@ import {
|
||||
BILLING_PLAN_PRICES,
|
||||
BILLING_PRO_DEFAULT_INCLUDED_SEATS,
|
||||
BILLING_PRO_USER_PRICE,
|
||||
BILLING_TRAFFIC_BUNDLE_PRICE,
|
||||
} from './BillingPlan';
|
||||
import { useInstanceTrafficMetrics } from 'hooks/api/getters/useInstanceTrafficMetrics/useInstanceTrafficMetrics';
|
||||
|
||||
@ -70,7 +71,11 @@ export const BillingDetailsPro = ({
|
||||
endpointsInfo,
|
||||
);
|
||||
const totalTraffic = toTrafficUsageSum(trafficData);
|
||||
return calculateOverageCost(totalTraffic, includedTraffic);
|
||||
return calculateOverageCost(
|
||||
totalTraffic,
|
||||
includedTraffic,
|
||||
BILLING_TRAFFIC_BUNDLE_PRICE,
|
||||
);
|
||||
}, [includedTraffic, traffic, currentPeriod, endpointsInfo]);
|
||||
|
||||
const totalCost = planPrice + paidAssignedPrice + overageCost;
|
||||
@ -146,8 +151,8 @@ export const BillingDetailsPro = ({
|
||||
</GridColLink>
|
||||
</Typography>
|
||||
<StyledInfoLabel>
|
||||
$5 dollar per 1 million started above
|
||||
included data
|
||||
${BILLING_TRAFFIC_BUNDLE_PRICE} per 1
|
||||
million started above included data
|
||||
</StyledInfoLabel>
|
||||
</GridCol>
|
||||
<GridCol>
|
||||
|
@ -18,6 +18,7 @@ export const BILLING_PAYG_DEFAULT_MINIMUM_SEATS = 5;
|
||||
export const BILLING_PRO_USER_PRICE = 15;
|
||||
export const BILLING_PRO_DEFAULT_INCLUDED_SEATS = 5;
|
||||
export const BILLING_INCLUDED_REQUESTS = 53_000_000;
|
||||
export const BILLING_TRAFFIC_BUNDLE_PRICE = 5;
|
||||
|
||||
const StyledPlanBox = styled('aside')(({ theme }) => ({
|
||||
padding: theme.spacing(2.5),
|
||||
|
@ -32,6 +32,7 @@ import {
|
||||
import { customHighlightPlugin } from 'component/common/Chart/customHighlightPlugin';
|
||||
import { formatTickValue } from 'component/common/Chart/formatTickValue';
|
||||
import { useTrafficLimit } from './hooks/useTrafficLimit';
|
||||
import { BILLING_TRAFFIC_BUNDLE_PRICE } from 'component/admin/billing/BillingDashboard/BillingPlan/BillingPlan';
|
||||
|
||||
const StyledBox = styled(Box)(({ theme }) => ({
|
||||
display: 'grid',
|
||||
@ -214,6 +215,7 @@ export const NetworkTrafficUsage: VFC = () => {
|
||||
const calculatedOverageCost = calculateOverageCost(
|
||||
usage,
|
||||
includedTraffic,
|
||||
BILLING_TRAFFIC_BUNDLE_PRICE,
|
||||
);
|
||||
setOverageCost(calculatedOverageCost);
|
||||
|
||||
@ -223,6 +225,7 @@ export const NetworkTrafficUsage: VFC = () => {
|
||||
data.datasets,
|
||||
includedTraffic,
|
||||
new Date(),
|
||||
BILLING_TRAFFIC_BUNDLE_PRICE,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -101,4 +101,39 @@ describe('traffic overage calculation', () => {
|
||||
// 22_500_000 * 3 * 30 = 2_025_000_000
|
||||
expect(result).toBe(2_025_000_000);
|
||||
});
|
||||
|
||||
it('supports custom price and unit size', () => {
|
||||
const dataUsage = 54_000_000;
|
||||
const includedTraffic = 53_000_000;
|
||||
const result = calculateOverageCost(
|
||||
dataUsage,
|
||||
includedTraffic,
|
||||
10,
|
||||
500_000,
|
||||
);
|
||||
expect(result).toBe(20);
|
||||
});
|
||||
|
||||
it('estimates based on custom price and unit size', () => {
|
||||
const testData = testData4Days;
|
||||
testData[0].data.push(22_500_000);
|
||||
testData[1].data.push(22_500_000);
|
||||
testData[2].data.push(22_500_000);
|
||||
const now = new Date();
|
||||
const period = toSelectablePeriod(now);
|
||||
const testNow = new Date(now.getFullYear(), now.getMonth(), 5);
|
||||
const result = calculateEstimatedMonthlyCost(
|
||||
period.key,
|
||||
testData,
|
||||
53_000_000,
|
||||
testNow,
|
||||
10,
|
||||
500_000,
|
||||
);
|
||||
// 22_500_000 * 3 * 30 = 2_025_000_000 total usage
|
||||
// 2_025_000_000 - 53_000_000 = 1_972_000_000 overage
|
||||
// 1_972_000_000 / 500_000 = 3_944 overage units
|
||||
// 3_944 * 10 = 39_440
|
||||
expect(result).toBe(39_440);
|
||||
});
|
||||
});
|
||||
|
@ -2,8 +2,8 @@ import { useState } from 'react';
|
||||
import type { IInstanceTrafficMetricsResponse } from './api/getters/useInstanceTrafficMetrics/useInstanceTrafficMetrics';
|
||||
import type { ChartDataset } from 'chart.js';
|
||||
|
||||
const TRAFFIC_DATA_UNIT_COST = 5;
|
||||
const TRAFFIC_DATA_UNIT_SIZE = 1_000_000;
|
||||
const DEFAULT_TRAFFIC_DATA_UNIT_COST = 5;
|
||||
const DEFAULT_TRAFFIC_DATA_UNIT_SIZE = 1_000_000;
|
||||
|
||||
export type SelectablePeriod = {
|
||||
key: string;
|
||||
@ -39,9 +39,13 @@ const endpointsInfo: Record<string, EndpointInfo> = {
|
||||
},
|
||||
};
|
||||
|
||||
const calculateTrafficDataCost = (trafficData: number) => {
|
||||
const unitCount = Math.ceil(trafficData / TRAFFIC_DATA_UNIT_SIZE);
|
||||
return unitCount * TRAFFIC_DATA_UNIT_COST;
|
||||
const calculateTrafficDataCost = (
|
||||
trafficData: number,
|
||||
trafficUnitCost = DEFAULT_TRAFFIC_DATA_UNIT_COST,
|
||||
trafficUnitSize = DEFAULT_TRAFFIC_DATA_UNIT_SIZE,
|
||||
) => {
|
||||
const unitCount = Math.ceil(trafficData / trafficUnitSize);
|
||||
return unitCount * trafficUnitCost;
|
||||
};
|
||||
|
||||
const padMonth = (month: number): string =>
|
||||
@ -167,6 +171,8 @@ const getDayLabels = (dayCount: number): number[] => {
|
||||
export const calculateOverageCost = (
|
||||
dataUsage: number,
|
||||
includedTraffic: number,
|
||||
trafficUnitCost = DEFAULT_TRAFFIC_DATA_UNIT_COST,
|
||||
trafficUnitSize = DEFAULT_TRAFFIC_DATA_UNIT_SIZE,
|
||||
): number => {
|
||||
if (dataUsage === 0) {
|
||||
return 0;
|
||||
@ -174,7 +180,9 @@ export const calculateOverageCost = (
|
||||
|
||||
const overage =
|
||||
Math.floor((dataUsage - includedTraffic) / 1_000_000) * 1_000_000;
|
||||
return overage > 0 ? calculateTrafficDataCost(overage) : 0;
|
||||
return overage > 0
|
||||
? calculateTrafficDataCost(overage, trafficUnitCost, trafficUnitSize)
|
||||
: 0;
|
||||
};
|
||||
|
||||
export const calculateProjectedUsage = (
|
||||
@ -203,6 +211,8 @@ export const calculateEstimatedMonthlyCost = (
|
||||
trafficData: ChartDatasetType[],
|
||||
includedTraffic: number,
|
||||
currentDate: Date,
|
||||
trafficUnitCost = DEFAULT_TRAFFIC_DATA_UNIT_COST,
|
||||
trafficUnitSize = DEFAULT_TRAFFIC_DATA_UNIT_SIZE,
|
||||
) => {
|
||||
if (period !== currentPeriod.key) {
|
||||
return 0;
|
||||
@ -214,7 +224,12 @@ export const calculateEstimatedMonthlyCost = (
|
||||
trafficData,
|
||||
currentPeriod.dayCount,
|
||||
);
|
||||
return calculateOverageCost(projectedUsage, includedTraffic);
|
||||
return calculateOverageCost(
|
||||
projectedUsage,
|
||||
includedTraffic,
|
||||
trafficUnitCost,
|
||||
trafficUnitSize,
|
||||
);
|
||||
};
|
||||
|
||||
export const useTrafficDataEstimation = () => {
|
||||
|
Loading…
Reference in New Issue
Block a user