mirror of
https://github.com/Unleash/unleash.git
synced 2025-06-04 01:18:20 +02:00
feat: backend connections tab (#9381)
This commit is contained in:
parent
359b7cc4c0
commit
f46ec293df
@ -14,6 +14,9 @@ const NetworkTraffic = lazy(() => import('./NetworkTraffic/NetworkTraffic'));
|
|||||||
const NetworkTrafficUsage = lazy(
|
const NetworkTrafficUsage = lazy(
|
||||||
() => import('./NetworkTrafficUsage/NetworkTrafficUsage'),
|
() => import('./NetworkTrafficUsage/NetworkTrafficUsage'),
|
||||||
);
|
);
|
||||||
|
const BackendConnections = lazy(
|
||||||
|
() => import('./NetworkTrafficUsage/BackendConnections'),
|
||||||
|
);
|
||||||
|
|
||||||
const tabs = [
|
const tabs = [
|
||||||
{
|
{
|
||||||
@ -28,17 +31,31 @@ const tabs = [
|
|||||||
label: 'Connected Edges',
|
label: 'Connected Edges',
|
||||||
path: '/admin/network/connected-edges',
|
path: '/admin/network/connected-edges',
|
||||||
},
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const seatModelTabs = [
|
||||||
{
|
{
|
||||||
label: 'Data Usage',
|
label: 'Data Usage',
|
||||||
path: '/admin/network/data-usage',
|
path: '/admin/network/data-usage',
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const consumptionModelTabs = [
|
||||||
|
{
|
||||||
|
label: 'Backend Connections',
|
||||||
|
path: '/admin/network/backend-connections',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
export const Network = () => {
|
export const Network = () => {
|
||||||
const { pathname } = useLocation();
|
const { pathname } = useLocation();
|
||||||
const edgeObservabilityEnabled = useUiFlag('edgeObservability');
|
const edgeObservabilityEnabled = useUiFlag('edgeObservability');
|
||||||
|
const consumptionModelEnabled = useUiFlag('consumptionModel');
|
||||||
|
const allTabs = consumptionModelEnabled
|
||||||
|
? [...tabs, ...consumptionModelTabs]
|
||||||
|
: [...tabs, ...seatModelTabs];
|
||||||
|
|
||||||
const filteredTabs = tabs.filter(
|
const filteredTabs = allTabs.filter(
|
||||||
({ label }) => label !== 'Connected Edges' || edgeObservabilityEnabled,
|
({ label }) => label !== 'Connected Edges' || edgeObservabilityEnabled,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -82,6 +99,10 @@ export const Network = () => {
|
|||||||
path='data-usage'
|
path='data-usage'
|
||||||
element={<NetworkTrafficUsage />}
|
element={<NetworkTrafficUsage />}
|
||||||
/>
|
/>
|
||||||
|
<Route
|
||||||
|
path='backend-connections'
|
||||||
|
element={<BackendConnections />}
|
||||||
|
/>
|
||||||
</Routes>
|
</Routes>
|
||||||
</PageContent>
|
</PageContent>
|
||||||
</div>
|
</div>
|
||||||
|
@ -0,0 +1,75 @@
|
|||||||
|
import type { FC } from 'react';
|
||||||
|
import { usePageTitle } from 'hooks/usePageTitle';
|
||||||
|
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||||
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
|
import { Alert, Box } from '@mui/material';
|
||||||
|
import { PeriodSelector } from './PeriodSelector';
|
||||||
|
import { Bar } from 'react-chartjs-2';
|
||||||
|
import { customHighlightPlugin } from 'component/common/Chart/customHighlightPlugin';
|
||||||
|
import { getChartLabel } from './chart-functions';
|
||||||
|
import { useConsumptionStats } from './hooks/useStats';
|
||||||
|
import { StyledBox, TopRow } from './SharedComponents';
|
||||||
|
import {
|
||||||
|
BarElement,
|
||||||
|
CategoryScale,
|
||||||
|
Chart as ChartJS,
|
||||||
|
Legend,
|
||||||
|
LinearScale,
|
||||||
|
Title,
|
||||||
|
Tooltip,
|
||||||
|
} from 'chart.js';
|
||||||
|
import annotationPlugin from 'chartjs-plugin-annotation';
|
||||||
|
import { useChartDataSelection } from './hooks/useChartDataSelection';
|
||||||
|
|
||||||
|
export const BackendConnections: FC = () => {
|
||||||
|
usePageTitle('Network - Backend Connections');
|
||||||
|
|
||||||
|
const { isOss } = useUiConfig();
|
||||||
|
|
||||||
|
const { chartDataSelection, setChartDataSelection, options } =
|
||||||
|
useChartDataSelection();
|
||||||
|
|
||||||
|
const { chartData } = useConsumptionStats(chartDataSelection);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ConditionallyRender
|
||||||
|
condition={isOss()}
|
||||||
|
show={<Alert severity='warning'>Not enabled.</Alert>}
|
||||||
|
elseShow={
|
||||||
|
<>
|
||||||
|
<StyledBox>
|
||||||
|
<TopRow>
|
||||||
|
<Box>
|
||||||
|
1 connection = 7200 backend SDK requests per day
|
||||||
|
</Box>
|
||||||
|
<PeriodSelector
|
||||||
|
selectedPeriod={chartDataSelection}
|
||||||
|
setPeriod={setChartDataSelection}
|
||||||
|
/>
|
||||||
|
</TopRow>
|
||||||
|
<Bar
|
||||||
|
data={chartData}
|
||||||
|
plugins={[customHighlightPlugin()]}
|
||||||
|
options={options}
|
||||||
|
aria-label={getChartLabel(chartDataSelection)}
|
||||||
|
/>
|
||||||
|
</StyledBox>
|
||||||
|
</>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Register dependencies that we need to draw the chart.
|
||||||
|
ChartJS.register(
|
||||||
|
annotationPlugin,
|
||||||
|
CategoryScale,
|
||||||
|
LinearScale,
|
||||||
|
BarElement,
|
||||||
|
Title,
|
||||||
|
Tooltip,
|
||||||
|
Legend,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Use a default export to lazy-load the charting library.
|
||||||
|
export default BackendConnections;
|
@ -1,172 +1,45 @@
|
|||||||
import { useMemo, useState, useEffect, type FC } from 'react';
|
import { type FC, useEffect, useMemo, useState } from 'react';
|
||||||
import useTheme from '@mui/material/styles/useTheme';
|
import useTheme from '@mui/material/styles/useTheme';
|
||||||
import styled from '@mui/material/styles/styled';
|
import styled from '@mui/material/styles/styled';
|
||||||
import { usePageTitle } from 'hooks/usePageTitle';
|
import { usePageTitle } from 'hooks/usePageTitle';
|
||||||
import Select from 'component/common/select';
|
import Select from 'component/common/select';
|
||||||
import Box from '@mui/system/Box';
|
|
||||||
import { Link as RouterLink } from 'react-router-dom';
|
import { Link as RouterLink } from 'react-router-dom';
|
||||||
import { Alert, Link } from '@mui/material';
|
import { Alert, Link } from '@mui/material';
|
||||||
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
import { ConditionallyRender } from 'component/common/ConditionallyRender/ConditionallyRender';
|
||||||
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
import useUiConfig from 'hooks/api/getters/useUiConfig/useUiConfig';
|
||||||
import {
|
import {
|
||||||
Chart as ChartJS,
|
|
||||||
type ChartOptions,
|
|
||||||
CategoryScale,
|
|
||||||
LinearScale,
|
|
||||||
BarElement,
|
BarElement,
|
||||||
|
CategoryScale,
|
||||||
|
Chart as ChartJS,
|
||||||
|
Legend,
|
||||||
|
LinearScale,
|
||||||
Title,
|
Title,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
Legend,
|
|
||||||
} from 'chart.js';
|
} from 'chart.js';
|
||||||
|
|
||||||
import { Bar } from 'react-chartjs-2';
|
import { Bar } from 'react-chartjs-2';
|
||||||
import {
|
import { useInstanceTrafficMetrics } from 'hooks/api/getters/useInstanceTrafficMetrics/useInstanceTrafficMetrics';
|
||||||
useInstanceTrafficMetrics,
|
|
||||||
useTrafficSearch,
|
|
||||||
} from 'hooks/api/getters/useInstanceTrafficMetrics/useInstanceTrafficMetrics';
|
|
||||||
import type { Theme } from '@mui/material/styles/createTheme';
|
|
||||||
import Grid from '@mui/material/Grid';
|
import Grid from '@mui/material/Grid';
|
||||||
import { NetworkTrafficUsagePlanSummary } from './NetworkTrafficUsagePlanSummary';
|
import { NetworkTrafficUsagePlanSummary } from './NetworkTrafficUsagePlanSummary';
|
||||||
import annotationPlugin from 'chartjs-plugin-annotation';
|
import annotationPlugin from 'chartjs-plugin-annotation';
|
||||||
import {
|
import {
|
||||||
useTrafficDataEstimation,
|
|
||||||
calculateEstimatedMonthlyCost as deprecatedCalculateEstimatedMonthlyCost,
|
calculateEstimatedMonthlyCost as deprecatedCalculateEstimatedMonthlyCost,
|
||||||
|
useTrafficDataEstimation,
|
||||||
} from 'hooks/useTrafficData';
|
} from 'hooks/useTrafficData';
|
||||||
import { customHighlightPlugin } from 'component/common/Chart/customHighlightPlugin';
|
import { customHighlightPlugin } from 'component/common/Chart/customHighlightPlugin';
|
||||||
import { formatTickValue } from 'component/common/Chart/formatTickValue';
|
|
||||||
import { useTrafficLimit } from './hooks/useTrafficLimit';
|
import { useTrafficLimit } from './hooks/useTrafficLimit';
|
||||||
import { BILLING_TRAFFIC_BUNDLE_PRICE } from 'component/admin/billing/BillingDashboard/BillingPlan/BillingPlan';
|
import { BILLING_TRAFFIC_BUNDLE_PRICE } from 'component/admin/billing/BillingDashboard/BillingPlan/BillingPlan';
|
||||||
import { useLocationSettings } from 'hooks/useLocationSettings';
|
import { useLocationSettings } from 'hooks/useLocationSettings';
|
||||||
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';
|
||||||
import { averageTrafficPreviousMonths } from './average-traffic-previous-months';
|
import { calculateOverageCost } from 'utils/traffic-calculations';
|
||||||
import {
|
import { currentMonth } from './dates';
|
||||||
calculateTotalUsage,
|
import { type ChartDatasetType, getChartLabel } from './chart-functions';
|
||||||
calculateOverageCost,
|
import { createBarChartOptions } from './bar-chart-options';
|
||||||
calculateEstimatedMonthlyCost,
|
import { useTrafficStats } from './hooks/useStats';
|
||||||
} from 'utils/traffic-calculations';
|
import { BoldText, StyledBox, TopRow } from './SharedComponents';
|
||||||
import { currentDate, currentMonth } from './dates';
|
import { useChartDataSelection } from './hooks/useChartDataSelection';
|
||||||
import { type ChartDataSelection, toDateRange } from './chart-data-selection';
|
|
||||||
import {
|
|
||||||
type ChartDatasetType,
|
|
||||||
getChartLabel,
|
|
||||||
toChartData as newToChartData,
|
|
||||||
toConnectionChartData,
|
|
||||||
} from './chart-functions';
|
|
||||||
import { periodsRecord, selectablePeriods } from './selectable-periods';
|
|
||||||
|
|
||||||
const StyledBox = styled(Box)(({ theme }) => ({
|
|
||||||
display: 'grid',
|
|
||||||
gap: theme.spacing(5),
|
|
||||||
}));
|
|
||||||
|
|
||||||
const createBarChartOptions = (
|
|
||||||
theme: Theme,
|
|
||||||
tooltipTitleCallback: (tooltipItems: any) => string,
|
|
||||||
includedTraffic?: number,
|
|
||||||
): ChartOptions<'bar'> => ({
|
|
||||||
plugins: {
|
|
||||||
annotation: {
|
|
||||||
clip: false,
|
|
||||||
annotations: {
|
|
||||||
line: {
|
|
||||||
type: 'line',
|
|
||||||
borderDash: [5, 5],
|
|
||||||
yMin: includedTraffic ? includedTraffic / 30 : 0,
|
|
||||||
yMax: includedTraffic ? includedTraffic / 30 : 0,
|
|
||||||
borderColor: 'gray',
|
|
||||||
borderWidth: 1,
|
|
||||||
display: !!includedTraffic,
|
|
||||||
|
|
||||||
label: {
|
|
||||||
backgroundColor: 'rgba(192, 192, 192, 0.8)',
|
|
||||||
color: 'black',
|
|
||||||
padding: {
|
|
||||||
top: 10,
|
|
||||||
bottom: 10,
|
|
||||||
left: 10,
|
|
||||||
right: 10,
|
|
||||||
},
|
|
||||||
content: 'Average daily requests included in your plan',
|
|
||||||
display: !!includedTraffic,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
legend: {
|
|
||||||
position: 'bottom',
|
|
||||||
labels: {
|
|
||||||
color: theme.palette.text.primary,
|
|
||||||
pointStyle: 'circle',
|
|
||||||
usePointStyle: true,
|
|
||||||
boxHeight: 6,
|
|
||||||
padding: 15,
|
|
||||||
boxPadding: 5,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
tooltip: {
|
|
||||||
backgroundColor: theme.palette.background.paper,
|
|
||||||
titleColor: theme.palette.text.primary,
|
|
||||||
bodyColor: theme.palette.text.primary,
|
|
||||||
bodySpacing: 6,
|
|
||||||
padding: {
|
|
||||||
top: 20,
|
|
||||||
bottom: 20,
|
|
||||||
left: 30,
|
|
||||||
right: 30,
|
|
||||||
},
|
|
||||||
borderColor: 'rgba(0, 0, 0, 0.05)',
|
|
||||||
borderWidth: 3,
|
|
||||||
usePointStyle: true,
|
|
||||||
caretSize: 0,
|
|
||||||
boxPadding: 10,
|
|
||||||
callbacks: {
|
|
||||||
title: tooltipTitleCallback,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
responsive: true,
|
|
||||||
scales: {
|
|
||||||
x: {
|
|
||||||
stacked: true,
|
|
||||||
ticks: {
|
|
||||||
color: theme.palette.text.secondary,
|
|
||||||
},
|
|
||||||
grid: {
|
|
||||||
display: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
y: {
|
|
||||||
stacked: true,
|
|
||||||
ticks: {
|
|
||||||
color: theme.palette.text.secondary,
|
|
||||||
maxTicksLimit: 5,
|
|
||||||
callback: formatTickValue,
|
|
||||||
},
|
|
||||||
grid: {
|
|
||||||
drawBorder: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
elements: {
|
|
||||||
bar: {
|
|
||||||
borderRadius: 5,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
interaction: {
|
|
||||||
mode: 'index',
|
|
||||||
intersect: false,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const TopRow = styled('div')(({ theme }) => ({
|
|
||||||
display: 'flex',
|
|
||||||
flexFlow: 'row wrap',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
gap: theme.spacing(2, 4),
|
|
||||||
alignItems: 'start',
|
|
||||||
}));
|
|
||||||
|
|
||||||
const TrafficInfoBoxes = styled('div')(({ theme }) => ({
|
const TrafficInfoBoxes = styled('div')(({ theme }) => ({
|
||||||
display: 'grid',
|
display: 'grid',
|
||||||
@ -175,123 +48,17 @@ const TrafficInfoBoxes = styled('div')(({ theme }) => ({
|
|||||||
gap: theme.spacing(2, 4),
|
gap: theme.spacing(2, 4),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const BoldText = styled('span')(({ theme }) => ({
|
|
||||||
fontWeight: 'bold',
|
|
||||||
}));
|
|
||||||
|
|
||||||
const useTrafficStats = (
|
|
||||||
includedTraffic: number,
|
|
||||||
chartDataSelection: ChartDataSelection,
|
|
||||||
) => {
|
|
||||||
const consumptionModelEnabled = useUiFlag('consumptionModel');
|
|
||||||
const { result } = useTrafficSearch(
|
|
||||||
chartDataSelection.grouping,
|
|
||||||
toDateRange(chartDataSelection, currentDate),
|
|
||||||
);
|
|
||||||
const results = useMemo(() => {
|
|
||||||
if (result.state !== 'success') {
|
|
||||||
return {
|
|
||||||
chartData: { datasets: [], labels: [] },
|
|
||||||
usageTotal: 0,
|
|
||||||
overageCost: 0,
|
|
||||||
estimatedMonthlyCost: 0,
|
|
||||||
requestSummaryUsage: 0,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
const traffic = result.data;
|
|
||||||
|
|
||||||
const chartData = consumptionModelEnabled
|
|
||||||
? toConnectionChartData(traffic)
|
|
||||||
: newToChartData(traffic);
|
|
||||||
const usageTotal = calculateTotalUsage(traffic);
|
|
||||||
const overageCost = calculateOverageCost(
|
|
||||||
usageTotal,
|
|
||||||
includedTraffic,
|
|
||||||
BILLING_TRAFFIC_BUNDLE_PRICE,
|
|
||||||
);
|
|
||||||
|
|
||||||
const estimatedMonthlyCost = calculateEstimatedMonthlyCost(
|
|
||||||
traffic.apiData,
|
|
||||||
includedTraffic,
|
|
||||||
currentDate,
|
|
||||||
BILLING_TRAFFIC_BUNDLE_PRICE,
|
|
||||||
);
|
|
||||||
|
|
||||||
const requestSummaryUsage =
|
|
||||||
chartDataSelection.grouping === 'daily'
|
|
||||||
? usageTotal
|
|
||||||
: averageTrafficPreviousMonths(traffic);
|
|
||||||
|
|
||||||
return {
|
|
||||||
chartData,
|
|
||||||
usageTotal,
|
|
||||||
overageCost,
|
|
||||||
estimatedMonthlyCost,
|
|
||||||
requestSummaryUsage,
|
|
||||||
};
|
|
||||||
}, [
|
|
||||||
JSON.stringify(result),
|
|
||||||
includedTraffic,
|
|
||||||
JSON.stringify(chartDataSelection),
|
|
||||||
]);
|
|
||||||
|
|
||||||
return results;
|
|
||||||
};
|
|
||||||
|
|
||||||
const NewNetworkTrafficUsage: FC = () => {
|
const NewNetworkTrafficUsage: FC = () => {
|
||||||
usePageTitle('Network - Data Usage');
|
usePageTitle('Network - Data Usage');
|
||||||
const theme = useTheme();
|
|
||||||
|
|
||||||
const estimateTrafficDataCost = useUiFlag('estimateTrafficDataCost');
|
const estimateTrafficDataCost = useUiFlag('estimateTrafficDataCost');
|
||||||
|
|
||||||
const { isOss } = useUiConfig();
|
const { isOss } = useUiConfig();
|
||||||
|
|
||||||
const { locationSettings } = useLocationSettings();
|
|
||||||
|
|
||||||
const [chartDataSelection, setChartDataSelection] =
|
|
||||||
useState<ChartDataSelection>({
|
|
||||||
grouping: 'daily',
|
|
||||||
month: selectablePeriods[0].key,
|
|
||||||
});
|
|
||||||
|
|
||||||
const includedTraffic = useTrafficLimit();
|
const includedTraffic = useTrafficLimit();
|
||||||
|
|
||||||
const options = useMemo(() => {
|
const { chartDataSelection, setChartDataSelection, options } =
|
||||||
return createBarChartOptions(
|
useChartDataSelection(includedTraffic);
|
||||||
theme,
|
|
||||||
(tooltipItems: any) => {
|
|
||||||
if (chartDataSelection.grouping === 'daily') {
|
|
||||||
const periodItem = periodsRecord[chartDataSelection.month];
|
|
||||||
const tooltipDate = new Date(
|
|
||||||
periodItem.year,
|
|
||||||
periodItem.month,
|
|
||||||
Number.parseInt(tooltipItems[0].label),
|
|
||||||
);
|
|
||||||
return tooltipDate.toLocaleDateString(
|
|
||||||
locationSettings?.locale ?? 'en-US',
|
|
||||||
{
|
|
||||||
month: 'long',
|
|
||||||
day: 'numeric',
|
|
||||||
year: 'numeric',
|
|
||||||
},
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
const timestamp = Date.parse(tooltipItems[0].label);
|
|
||||||
if (Number.isNaN(timestamp)) {
|
|
||||||
return 'Current month to date';
|
|
||||||
}
|
|
||||||
return new Date(timestamp).toLocaleDateString(
|
|
||||||
locationSettings?.locale ?? 'en-US',
|
|
||||||
{
|
|
||||||
month: 'long',
|
|
||||||
year: 'numeric',
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
includedTraffic,
|
|
||||||
);
|
|
||||||
}, [theme, chartDataSelection]);
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
chartData,
|
chartData,
|
||||||
|
@ -0,0 +1,19 @@
|
|||||||
|
import styled from '@mui/material/styles/styled';
|
||||||
|
import Box from '@mui/system/Box';
|
||||||
|
|
||||||
|
export const StyledBox = styled(Box)(({ theme }) => ({
|
||||||
|
display: 'grid',
|
||||||
|
gap: theme.spacing(5),
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const TopRow = styled('div')(({ theme }) => ({
|
||||||
|
display: 'flex',
|
||||||
|
flexFlow: 'row wrap',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
gap: theme.spacing(2, 4),
|
||||||
|
alignItems: 'start',
|
||||||
|
}));
|
||||||
|
|
||||||
|
export const BoldText = styled('span')(({ theme }) => ({
|
||||||
|
fontWeight: 'bold',
|
||||||
|
}));
|
@ -0,0 +1,102 @@
|
|||||||
|
import type { Theme } from '@mui/material/styles/createTheme';
|
||||||
|
import type { ChartOptions } from 'chart.js';
|
||||||
|
import { formatTickValue } from 'component/common/Chart/formatTickValue';
|
||||||
|
|
||||||
|
export const createBarChartOptions = (
|
||||||
|
theme: Theme,
|
||||||
|
tooltipTitleCallback: (tooltipItems: any) => string,
|
||||||
|
includedTraffic?: number,
|
||||||
|
): ChartOptions<'bar'> => ({
|
||||||
|
plugins: {
|
||||||
|
annotation: {
|
||||||
|
clip: false,
|
||||||
|
annotations: {
|
||||||
|
line: {
|
||||||
|
type: 'line',
|
||||||
|
borderDash: [5, 5],
|
||||||
|
yMin: includedTraffic ? includedTraffic / 30 : 0,
|
||||||
|
yMax: includedTraffic ? includedTraffic / 30 : 0,
|
||||||
|
borderColor: 'gray',
|
||||||
|
borderWidth: 1,
|
||||||
|
display: !!includedTraffic,
|
||||||
|
|
||||||
|
label: {
|
||||||
|
backgroundColor: 'rgba(192, 192, 192, 0.8)',
|
||||||
|
color: 'black',
|
||||||
|
padding: {
|
||||||
|
top: 10,
|
||||||
|
bottom: 10,
|
||||||
|
left: 10,
|
||||||
|
right: 10,
|
||||||
|
},
|
||||||
|
content: 'Average daily requests included in your plan',
|
||||||
|
display: !!includedTraffic,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
position: 'bottom',
|
||||||
|
labels: {
|
||||||
|
color: theme.palette.text.primary,
|
||||||
|
pointStyle: 'circle',
|
||||||
|
usePointStyle: true,
|
||||||
|
boxHeight: 6,
|
||||||
|
padding: 15,
|
||||||
|
boxPadding: 5,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
backgroundColor: theme.palette.background.paper,
|
||||||
|
titleColor: theme.palette.text.primary,
|
||||||
|
bodyColor: theme.palette.text.primary,
|
||||||
|
bodySpacing: 6,
|
||||||
|
padding: {
|
||||||
|
top: 20,
|
||||||
|
bottom: 20,
|
||||||
|
left: 30,
|
||||||
|
right: 30,
|
||||||
|
},
|
||||||
|
borderColor: 'rgba(0, 0, 0, 0.05)',
|
||||||
|
borderWidth: 3,
|
||||||
|
usePointStyle: true,
|
||||||
|
caretSize: 0,
|
||||||
|
boxPadding: 10,
|
||||||
|
callbacks: {
|
||||||
|
title: tooltipTitleCallback,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
responsive: true,
|
||||||
|
scales: {
|
||||||
|
x: {
|
||||||
|
stacked: true,
|
||||||
|
ticks: {
|
||||||
|
color: theme.palette.text.secondary,
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
display: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
y: {
|
||||||
|
stacked: true,
|
||||||
|
ticks: {
|
||||||
|
color: theme.palette.text.secondary,
|
||||||
|
maxTicksLimit: 5,
|
||||||
|
callback: formatTickValue,
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
drawBorder: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
elements: {
|
||||||
|
bar: {
|
||||||
|
borderRadius: 5,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
interaction: {
|
||||||
|
mode: 'index',
|
||||||
|
intersect: false,
|
||||||
|
},
|
||||||
|
});
|
@ -1,5 +1,5 @@
|
|||||||
import type { TrafficUsageDataSegmentedCombinedSchema } from 'openapi';
|
import type { TrafficUsageDataSegmentedCombinedSchema } from 'openapi';
|
||||||
import { toChartData } from './chart-functions';
|
import { toTrafficUsageChartData } from './chart-functions';
|
||||||
import { endpointsInfo } from './endpoint-info';
|
import { endpointsInfo } from './endpoint-info';
|
||||||
|
|
||||||
describe('toChartData', () => {
|
describe('toChartData', () => {
|
||||||
@ -67,7 +67,7 @@ describe('toChartData', () => {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(toChartData(input)).toMatchObject(expectedOutput);
|
expect(toTrafficUsageChartData(input)).toMatchObject(expectedOutput);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('daily data conversion', () => {
|
test('daily data conversion', () => {
|
||||||
@ -121,7 +121,7 @@ describe('toChartData', () => {
|
|||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(toChartData(input)).toMatchObject(expectedOutput);
|
expect(toTrafficUsageChartData(input)).toMatchObject(expectedOutput);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('sorts endpoints according to endpoint data spec', () => {
|
test('sorts endpoints according to endpoint data spec', () => {
|
||||||
@ -146,6 +146,6 @@ describe('toChartData', () => {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(toChartData(input)).toMatchObject(expectedOutput);
|
expect(toTrafficUsageChartData(input)).toMatchObject(expectedOutput);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -13,7 +13,7 @@ import { formatDay, formatMonth } from './dates';
|
|||||||
import type { ChartDataSelection } from './chart-data-selection';
|
import type { ChartDataSelection } from './chart-data-selection';
|
||||||
export type ChartDatasetType = ChartDataset<'bar'>;
|
export type ChartDatasetType = ChartDataset<'bar'>;
|
||||||
|
|
||||||
export const toChartData = (
|
export const toTrafficUsageChartData = (
|
||||||
traffic: TrafficUsageDataSegmentedCombinedSchema,
|
traffic: TrafficUsageDataSegmentedCombinedSchema,
|
||||||
): { datasets: ChartDatasetType[]; labels: string[] } => {
|
): { datasets: ChartDatasetType[]; labels: string[] } => {
|
||||||
const { newRecord, labels } = getLabelsAndRecords(traffic);
|
const { newRecord, labels } = getLabelsAndRecords(traffic);
|
||||||
@ -47,6 +47,7 @@ export const toConnectionChartData = (
|
|||||||
): { datasets: ChartDatasetType[]; labels: string[] } => {
|
): { datasets: ChartDatasetType[]; labels: string[] } => {
|
||||||
const { newRecord, labels } = getLabelsAndRecords(traffic);
|
const { newRecord, labels } = getLabelsAndRecords(traffic);
|
||||||
const datasets = traffic.apiData
|
const datasets = traffic.apiData
|
||||||
|
.filter((apiData) => apiData.apiPath === '/api/client')
|
||||||
.sort(
|
.sort(
|
||||||
(item1, item2) =>
|
(item1, item2) =>
|
||||||
endpointsInfo[item1.apiPath].order -
|
endpointsInfo[item1.apiPath].order -
|
||||||
@ -61,11 +62,14 @@ export const toConnectionChartData = (
|
|||||||
if (traffic.grouping === 'monthly') {
|
if (traffic.grouping === 'monthly') {
|
||||||
// 1 connections = 7200 * days in month requests per day
|
// 1 connections = 7200 * days in month requests per day
|
||||||
const daysInMonth = getDaysInMonth(date);
|
const daysInMonth = getDaysInMonth(date);
|
||||||
record[dataPoint.period] =
|
record[dataPoint.period] = Number(
|
||||||
requestCount / (daysInMonth * 7200);
|
(requestCount / (daysInMonth * 7200)).toFixed(1),
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
// 1 connection = 7200 requests per day
|
// 1 connection = 7200 requests per day
|
||||||
record[dataPoint.period] = requestCount / 7200;
|
record[dataPoint.period] = Number(
|
||||||
|
(requestCount / 7200).toFixed(1),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,56 @@
|
|||||||
|
import { useMemo, useState } from 'react';
|
||||||
|
import type { ChartDataSelection } from '../chart-data-selection';
|
||||||
|
import { periodsRecord, selectablePeriods } from '../selectable-periods';
|
||||||
|
import { createBarChartOptions } from '../bar-chart-options';
|
||||||
|
import useTheme from '@mui/material/styles/useTheme';
|
||||||
|
import { useLocationSettings } from 'hooks/useLocationSettings';
|
||||||
|
|
||||||
|
export const useChartDataSelection = (includedTraffic?: number) => {
|
||||||
|
const theme = useTheme();
|
||||||
|
const { locationSettings } = useLocationSettings();
|
||||||
|
|
||||||
|
const [chartDataSelection, setChartDataSelection] =
|
||||||
|
useState<ChartDataSelection>({
|
||||||
|
grouping: 'daily',
|
||||||
|
month: selectablePeriods[0].key,
|
||||||
|
});
|
||||||
|
|
||||||
|
const options = useMemo(() => {
|
||||||
|
return createBarChartOptions(
|
||||||
|
theme,
|
||||||
|
(tooltipItems: any) => {
|
||||||
|
if (chartDataSelection.grouping === 'daily') {
|
||||||
|
const periodItem = periodsRecord[chartDataSelection.month];
|
||||||
|
const tooltipDate = new Date(
|
||||||
|
periodItem.year,
|
||||||
|
periodItem.month,
|
||||||
|
Number.parseInt(tooltipItems[0].label),
|
||||||
|
);
|
||||||
|
return tooltipDate.toLocaleDateString(
|
||||||
|
locationSettings?.locale ?? 'en-US',
|
||||||
|
{
|
||||||
|
month: 'long',
|
||||||
|
day: 'numeric',
|
||||||
|
year: 'numeric',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
const timestamp = Date.parse(tooltipItems[0].label);
|
||||||
|
if (Number.isNaN(timestamp)) {
|
||||||
|
return 'Current month to date';
|
||||||
|
}
|
||||||
|
return new Date(timestamp).toLocaleDateString(
|
||||||
|
locationSettings?.locale ?? 'en-US',
|
||||||
|
{
|
||||||
|
month: 'long',
|
||||||
|
year: 'numeric',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
includedTraffic,
|
||||||
|
);
|
||||||
|
}, [theme, chartDataSelection]);
|
||||||
|
|
||||||
|
return { chartDataSelection, setChartDataSelection, options };
|
||||||
|
};
|
@ -0,0 +1,98 @@
|
|||||||
|
import { type ChartDataSelection, toDateRange } from '../chart-data-selection';
|
||||||
|
import { useTrafficSearch } from 'hooks/api/getters/useInstanceTrafficMetrics/useInstanceTrafficMetrics';
|
||||||
|
import { currentDate } from '../dates';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
import {
|
||||||
|
toTrafficUsageChartData as newToChartData,
|
||||||
|
toConnectionChartData,
|
||||||
|
} from '../chart-functions';
|
||||||
|
import {
|
||||||
|
calculateEstimatedMonthlyCost,
|
||||||
|
calculateOverageCost,
|
||||||
|
calculateTotalUsage,
|
||||||
|
} from 'utils/traffic-calculations';
|
||||||
|
import { BILLING_TRAFFIC_BUNDLE_PRICE } from '../../../billing/BillingDashboard/BillingPlan/BillingPlan';
|
||||||
|
import { averageTrafficPreviousMonths } from '../average-traffic-previous-months';
|
||||||
|
|
||||||
|
export const useTrafficStats = (
|
||||||
|
includedTraffic: number,
|
||||||
|
chartDataSelection: ChartDataSelection,
|
||||||
|
) => {
|
||||||
|
const { result } = useTrafficSearch(
|
||||||
|
chartDataSelection.grouping,
|
||||||
|
toDateRange(chartDataSelection, currentDate),
|
||||||
|
);
|
||||||
|
const results = useMemo(() => {
|
||||||
|
if (result.state !== 'success') {
|
||||||
|
return {
|
||||||
|
chartData: { datasets: [], labels: [] },
|
||||||
|
usageTotal: 0,
|
||||||
|
overageCost: 0,
|
||||||
|
estimatedMonthlyCost: 0,
|
||||||
|
requestSummaryUsage: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const traffic = result.data;
|
||||||
|
|
||||||
|
const chartData = newToChartData(traffic);
|
||||||
|
const usageTotal = calculateTotalUsage(traffic);
|
||||||
|
const overageCost = calculateOverageCost(
|
||||||
|
usageTotal,
|
||||||
|
includedTraffic,
|
||||||
|
BILLING_TRAFFIC_BUNDLE_PRICE,
|
||||||
|
);
|
||||||
|
|
||||||
|
const estimatedMonthlyCost = calculateEstimatedMonthlyCost(
|
||||||
|
traffic.apiData,
|
||||||
|
includedTraffic,
|
||||||
|
currentDate,
|
||||||
|
BILLING_TRAFFIC_BUNDLE_PRICE,
|
||||||
|
);
|
||||||
|
|
||||||
|
const requestSummaryUsage =
|
||||||
|
chartDataSelection.grouping === 'daily'
|
||||||
|
? usageTotal
|
||||||
|
: averageTrafficPreviousMonths(traffic);
|
||||||
|
|
||||||
|
return {
|
||||||
|
chartData,
|
||||||
|
usageTotal,
|
||||||
|
overageCost,
|
||||||
|
estimatedMonthlyCost,
|
||||||
|
requestSummaryUsage,
|
||||||
|
};
|
||||||
|
}, [
|
||||||
|
JSON.stringify(result),
|
||||||
|
includedTraffic,
|
||||||
|
JSON.stringify(chartDataSelection),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return results;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useConsumptionStats = (chartDataSelection: ChartDataSelection) => {
|
||||||
|
const { result } = useTrafficSearch(
|
||||||
|
chartDataSelection.grouping,
|
||||||
|
toDateRange(chartDataSelection, currentDate),
|
||||||
|
);
|
||||||
|
const results = useMemo(() => {
|
||||||
|
if (result.state !== 'success') {
|
||||||
|
return {
|
||||||
|
chartData: { datasets: [], labels: [] },
|
||||||
|
usageTotal: 0,
|
||||||
|
overageCost: 0,
|
||||||
|
estimatedMonthlyCost: 0,
|
||||||
|
requestSummaryUsage: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const traffic = result.data;
|
||||||
|
|
||||||
|
const chartData = toConnectionChartData(traffic);
|
||||||
|
|
||||||
|
return {
|
||||||
|
chartData,
|
||||||
|
};
|
||||||
|
}, [JSON.stringify(result), JSON.stringify(chartDataSelection)]);
|
||||||
|
|
||||||
|
return results;
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user