mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	feat: read backend connections UI (#9526)
This commit is contained in:
		
							parent
							
								
									22f51df76c
								
							
						
					
					
						commit
						1b7f91cd4b
					
				@ -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');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -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) =>
 | 
				
			||||||
 | 
				
			|||||||
@ -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,51 +46,38 @@ 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')
 | 
					        const record = newRecord();
 | 
				
			||||||
        .sort(
 | 
					        for (const dataPoint of Object.values(item.dataPoints)) {
 | 
				
			||||||
            (item1, item2) =>
 | 
					            const requestCount = dataPoint.connections;
 | 
				
			||||||
                endpointsInfo[item1.apiPath].order -
 | 
					            record[dataPoint.period] = requestCount;
 | 
				
			||||||
                endpointsInfo[item2.apiPath].order,
 | 
					        }
 | 
				
			||||||
        )
 | 
					 | 
				
			||||||
        .map((item) => {
 | 
					 | 
				
			||||||
            const record = newRecord();
 | 
					 | 
				
			||||||
            for (const dataPoint of Object.values(item.dataPoints)) {
 | 
					 | 
				
			||||||
                const date = parseISO(dataPoint.period);
 | 
					 | 
				
			||||||
                const requestCount = dataPoint.trafficTypes[0].count;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                if (traffic.grouping === 'monthly') {
 | 
					        const epInfo = {
 | 
				
			||||||
                    // 1 connections = 7200 * days in month requests per day
 | 
					            label: 'Connections',
 | 
				
			||||||
                    const daysInMonth = getDaysInMonth(date);
 | 
					            color: '#6D66D9',
 | 
				
			||||||
                    record[dataPoint.period] = Number(
 | 
					            order: 1,
 | 
				
			||||||
                        (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];
 | 
					        return {
 | 
				
			||||||
 | 
					            label: epInfo.label,
 | 
				
			||||||
            return {
 | 
					            data: Object.values(record),
 | 
				
			||||||
                label: epInfo.label,
 | 
					            backgroundColor: epInfo.color,
 | 
				
			||||||
                data: Object.values(record),
 | 
					            hoverBackgroundColor: epInfo.color,
 | 
				
			||||||
                backgroundColor: epInfo.color,
 | 
					        };
 | 
				
			||||||
                hoverBackgroundColor: epInfo.color,
 | 
					    });
 | 
				
			||||||
            };
 | 
					 | 
				
			||||||
        });
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return { datasets, labels };
 | 
					    return { datasets, labels };
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
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);
 | 
				
			||||||
 | 
				
			|||||||
@ -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;
 | 
				
			||||||
 | 
				
			|||||||
@ -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());
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
		Loading…
	
		Reference in New Issue
	
	Block a user