1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-08-04 13:48:56 +02:00

feat: read backend connections UI (#9526)

This commit is contained in:
Mateusz Kwasniewski 2025-03-13 09:56:29 +01:00 committed by GitHub
parent 22f51df76c
commit 1b7f91cd4b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 100 additions and 69 deletions

View File

@ -21,6 +21,7 @@ import {
import annotationPlugin from 'chartjs-plugin-annotation'; import annotationPlugin from 'chartjs-plugin-annotation';
import { useChartDataSelection } from './hooks/useChartDataSelection'; import { useChartDataSelection } from './hooks/useChartDataSelection';
// TODO: consider renaming to Connection Consumption
export const BackendConnections: FC = () => { export const BackendConnections: FC = () => {
usePageTitle('Network - Backend Connections'); usePageTitle('Network - Backend Connections');

View File

@ -1,4 +1,7 @@
import type { TrafficUsageDataSegmentedCombinedSchema } from 'openapi'; import type {
MeteredConnectionsSchema,
TrafficUsageDataSegmentedCombinedSchema,
} from 'openapi';
import { import {
toConnectionChartData, toConnectionChartData,
toTrafficUsageChartData, toTrafficUsageChartData,
@ -152,9 +155,9 @@ describe('toTrafficUsageChartData', () => {
}); });
describe('toConnectionChartData', () => { describe('toConnectionChartData', () => {
const dataPoint = (period: string, count: number) => ({ const dataPoint = (period: string, connections: number) => ({
period, period,
trafficTypes: [{ count, group: 'successful-requests' }], connections,
}); });
const fromEndpointInfo = (endpoint: keyof typeof endpointsInfo) => { const fromEndpointInfo = (endpoint: keyof typeof endpointsInfo) => {
@ -167,7 +170,7 @@ describe('toConnectionChartData', () => {
}; };
test('monthly data conversion', () => { test('monthly data conversion', () => {
const input: TrafficUsageDataSegmentedCombinedSchema = { const input: MeteredConnectionsSchema = {
grouping: 'monthly', grouping: 'monthly',
dateRange: { dateRange: {
from: '2025-01-01', from: '2025-01-01',
@ -175,16 +178,12 @@ describe('toConnectionChartData', () => {
}, },
apiData: [ apiData: [
{ {
apiPath: '/api/admin', // filter out meteredGroup: 'default',
dataPoints: [dataPoint('2025-06', 5)],
},
{
apiPath: '/api/client',
dataPoints: [ dataPoints: [
dataPoint('2025-06', 10 * 5 * 60 * 24 * 30), dataPoint('2025-06', 10),
dataPoint('2025-01', 7 * 5 * 60 * 24 * 31), dataPoint('2025-01', 7),
dataPoint('2025-03', 11 * 5 * 60 * 24 * 31), dataPoint('2025-03', 11),
dataPoint('2025-04', 13 * 5 * 60 * 24 * 30), dataPoint('2025-04', 13),
], ],
}, },
], ],
@ -194,7 +193,9 @@ describe('toConnectionChartData', () => {
datasets: [ datasets: [
{ {
data: [7, 0, 11, 13, 0, 10], data: [7, 0, 11, 13, 0, 10],
...fromEndpointInfo('/api/client'), hoverBackgroundColor: '#6D66D9',
label: 'Connections',
backgroundColor: '#6D66D9',
}, },
], ],
labels: [ labels: [
@ -211,7 +212,7 @@ describe('toConnectionChartData', () => {
}); });
test('daily data conversion', () => { test('daily data conversion', () => {
const input: TrafficUsageDataSegmentedCombinedSchema = { const input: MeteredConnectionsSchema = {
grouping: 'daily', grouping: 'daily',
dateRange: { dateRange: {
from: '2025-01-01', from: '2025-01-01',
@ -219,16 +220,12 @@ describe('toConnectionChartData', () => {
}, },
apiData: [ apiData: [
{ {
apiPath: '/api/admin', // filter out meteredGroup: 'default',
dataPoints: [dataPoint('2025-01-01', 5)],
},
{
apiPath: '/api/client',
dataPoints: [ dataPoints: [
dataPoint('2025-01-02', 2 * 5 * 60 * 24), dataPoint('2025-01-02', 2),
dataPoint('2025-01-17', 6 * 5 * 60 * 24), dataPoint('2025-01-17', 6),
dataPoint('2025-01-19', 4 * 5 * 60 * 24), dataPoint('2025-01-19', 4),
dataPoint('2025-01-06', 8 * 5 * 60 * 24), dataPoint('2025-01-06', 8),
], ],
}, },
], ],
@ -241,7 +238,9 @@ describe('toConnectionChartData', () => {
0, 2, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 4, 0, 2, 0, 0, 0, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 4,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
], ],
...fromEndpointInfo('/api/client'), hoverBackgroundColor: '#6D66D9',
label: 'Connections',
backgroundColor: '#6D66D9',
}, },
], ],
labels: Array.from({ length: 31 }).map((_, index) => labels: Array.from({ length: 31 }).map((_, index) =>

View File

@ -1,13 +1,14 @@
import type { ChartDataset } from 'chart.js'; import type { ChartDataset } from 'chart.js';
import type { TrafficUsageDataSegmentedCombinedSchema } from 'openapi'; import type {
MeteredConnectionsSchema,
TrafficUsageDataSegmentedCombinedSchema,
} from 'openapi';
import { endpointsInfo } from './endpoint-info'; import { endpointsInfo } from './endpoint-info';
import { import {
addDays, addDays,
addMonths, addMonths,
differenceInCalendarDays, differenceInCalendarDays,
differenceInCalendarMonths, differenceInCalendarMonths,
getDaysInMonth,
parseISO,
} from 'date-fns'; } from 'date-fns';
import { formatDay, formatMonth } from './dates'; import { formatDay, formatMonth } from './dates';
import type { ChartDataSelection } from './chart-data-selection'; import type { ChartDataSelection } from './chart-data-selection';
@ -45,37 +46,21 @@ export const toTrafficUsageChartData = (
}; };
export const toConnectionChartData = ( export const toConnectionChartData = (
traffic: TrafficUsageDataSegmentedCombinedSchema, traffic: MeteredConnectionsSchema,
): { 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.map((item) => {
.filter((apiData) => apiData.apiPath === '/api/client')
.sort(
(item1, item2) =>
endpointsInfo[item1.apiPath].order -
endpointsInfo[item2.apiPath].order,
)
.map((item) => {
const record = newRecord(); const record = newRecord();
for (const dataPoint of Object.values(item.dataPoints)) { for (const dataPoint of Object.values(item.dataPoints)) {
const date = parseISO(dataPoint.period); const requestCount = dataPoint.connections;
const requestCount = dataPoint.trafficTypes[0].count; record[dataPoint.period] = requestCount;
if (traffic.grouping === 'monthly') {
// 1 connections = 7200 * days in month requests per day
const daysInMonth = getDaysInMonth(date);
record[dataPoint.period] = Number(
(requestCount / (daysInMonth * 7200)).toFixed(1),
);
} else {
// 1 connection = 7200 requests per day
record[dataPoint.period] = Number(
(requestCount / 7200).toFixed(1),
);
}
} }
const epInfo = endpointsInfo[item.apiPath]; const epInfo = {
label: 'Connections',
color: '#6D66D9',
order: 1,
};
return { return {
label: epInfo.label, label: epInfo.label,
@ -89,7 +74,10 @@ export const toConnectionChartData = (
}; };
const getLabelsAndRecords = ( const getLabelsAndRecords = (
traffic: TrafficUsageDataSegmentedCombinedSchema, traffic: Pick<
TrafficUsageDataSegmentedCombinedSchema,
'dateRange' | 'grouping'
>,
) => { ) => {
if (traffic.grouping === 'monthly') { if (traffic.grouping === 'monthly') {
const from = new Date(traffic.dateRange.from); const from = new Date(traffic.dateRange.from);

View File

@ -13,6 +13,7 @@ import {
} from 'utils/traffic-calculations'; } from 'utils/traffic-calculations';
import { BILLING_TRAFFIC_BUNDLE_PRICE } from '../../../billing/BillingDashboard/BillingPlan/BillingPlan'; import { BILLING_TRAFFIC_BUNDLE_PRICE } from '../../../billing/BillingDashboard/BillingPlan/BillingPlan';
import { averageTrafficPreviousMonths } from '../average-traffic-previous-months'; import { averageTrafficPreviousMonths } from '../average-traffic-previous-months';
import { useConnectionsConsumption } from 'hooks/api/getters/useConnectionsConsumption/useConnectionsConsumption';
export const useTrafficStats = ( export const useTrafficStats = (
includedTraffic: number, includedTraffic: number,
@ -72,7 +73,7 @@ export const useTrafficStats = (
}; };
export const useConsumptionStats = (chartDataSelection: ChartDataSelection) => { export const useConsumptionStats = (chartDataSelection: ChartDataSelection) => {
const { result } = useTrafficSearch( const { result } = useConnectionsConsumption(
chartDataSelection.grouping, chartDataSelection.grouping,
toDateRange(chartDataSelection, currentDate), toDateRange(chartDataSelection, currentDate),
); );
@ -80,10 +81,6 @@ export const useConsumptionStats = (chartDataSelection: ChartDataSelection) => {
if (result.state !== 'success') { if (result.state !== 'success') {
return { return {
chartData: { datasets: [], labels: [] }, chartData: { datasets: [], labels: [] },
usageTotal: 0,
overageCost: 0,
estimatedMonthlyCost: 0,
requestSummaryUsage: 0,
}; };
} }
const traffic = result.data; const traffic = result.data;

View File

@ -0,0 +1,46 @@
import useSWR from 'swr';
import { useMemo } from 'react';
import { formatApiPath } from 'utils/formatPath';
import handleErrorResponses from '../httpErrorResponseHandler';
import type { MeteredConnectionsSchema } from 'openapi';
export type MeteredConnectionsResponse = {
refetch: () => void;
result:
| { state: 'success'; data: MeteredConnectionsSchema }
| { state: 'error'; error: Error }
| { state: 'loading' };
};
export const useConnectionsConsumption = (
grouping: 'monthly' | 'daily',
{
from,
to,
}: {
from: string;
to: string;
},
): MeteredConnectionsResponse => {
const apiPath = `api/admin/metrics/connection?grouping=${grouping}&from=${from}&to=${to}`;
const { data, error, mutate } = useSWR(formatApiPath(apiPath), fetcher);
return useMemo(() => {
const result = data
? { state: 'success' as const, data: data }
: error
? { state: 'error' as const, error }
: { state: 'loading' as const };
return {
refetch: () => mutate(),
result,
};
}, [data, error, mutate]);
};
const fetcher = (path: string) => {
return fetch(path)
.then(handleErrorResponses('Metered Connections Metrics'))
.then((res) => res.json());
};