1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-07-26 13:48:33 +02:00

feat: requests consumption UI for frontend (#9550)

Switching frontend traffic tab to use the requests consumption API:
This commit is contained in:
Jaanus Sellin 2025-03-17 12:55:01 +02:00 committed by GitHub
parent f093a3f4b3
commit 6d6a4290fe
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 194 additions and 8 deletions

View File

@ -18,7 +18,7 @@ import annotationPlugin from 'chartjs-plugin-annotation';
import { customHighlightPlugin } from 'component/common/Chart/customHighlightPlugin';
import { PeriodSelector } from './PeriodSelector';
import { getChartLabel } from './chart-functions';
import { useTrafficStats } from './hooks/useStats';
import { useRequestsStats } from './hooks/useStats';
import { StyledBox, TopRow } from './SharedComponents';
import { useChartDataSelection } from './hooks/useChartDataSelection';
@ -30,12 +30,7 @@ const FrontendNetworkTrafficUsage: FC = () => {
const { chartDataSelection, setChartDataSelection, options } =
useChartDataSelection();
const includedTraffic = 0;
const { chartData } = useTrafficStats(
includedTraffic,
chartDataSelection,
'/api/frontend',
);
const { chartData } = useRequestsStats(chartDataSelection);
return (
<ConditionallyRender

View File

@ -1,9 +1,11 @@
import type {
MeteredConnectionsSchema,
MeteredRequestsSchema,
TrafficUsageDataSegmentedCombinedSchema,
} from 'openapi';
import {
toConnectionChartData,
toRequestChartData,
toTrafficUsageChartData,
} from './chart-functions';
import { endpointsInfo } from './endpoint-info';
@ -251,3 +253,92 @@ describe('toConnectionChartData', () => {
expect(toConnectionChartData(input)).toMatchObject(expectedOutput);
});
});
describe('toRequestChartData', () => {
const dataPoint = (period: string, requests: number) => ({
period,
requests,
});
test('monthly data conversion', () => {
const input: MeteredRequestsSchema = {
grouping: 'monthly',
dateRange: {
from: '2025-01-01',
to: '2025-06-30',
},
apiData: [
{
meteredGroup: 'default',
dataPoints: [
dataPoint('2025-06', 15),
dataPoint('2025-01', 9),
dataPoint('2025-03', 14),
dataPoint('2025-04', 18),
],
},
],
};
const expectedOutput = {
datasets: [
{
data: [9, 0, 14, 18, 0, 15],
hoverBackgroundColor: '#A39EFF',
label: 'Frontend requests',
backgroundColor: '#A39EFF',
},
],
labels: [
'2025-01',
'2025-02',
'2025-03',
'2025-04',
'2025-05',
'Current month',
],
};
expect(toRequestChartData(input)).toMatchObject(expectedOutput);
});
test('daily data conversion', () => {
const input: MeteredRequestsSchema = {
grouping: 'daily',
dateRange: {
from: '2025-01-01',
to: '2025-01-31',
},
apiData: [
{
meteredGroup: 'default',
dataPoints: [
dataPoint('2025-01-02', 3),
dataPoint('2025-01-17', 7),
dataPoint('2025-01-19', 5),
dataPoint('2025-01-06', 10),
],
},
],
};
const expectedOutput = {
datasets: [
{
data: [
0, 3, 0, 0, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0,
5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
],
hoverBackgroundColor: '#A39EFF',
label: 'Frontend requests',
backgroundColor: '#A39EFF',
},
],
labels: Array.from({ length: 31 }).map((_, index) =>
(index + 1).toString(),
),
};
expect(toRequestChartData(input)).toMatchObject(expectedOutput);
});
});

View File

@ -1,6 +1,7 @@
import type { ChartDataset } from 'chart.js';
import type {
MeteredConnectionsSchema,
MeteredRequestsSchema,
TrafficUsageDataSegmentedCombinedSchema,
} from 'openapi';
import { endpointsInfo } from './endpoint-info';
@ -73,6 +74,34 @@ export const toConnectionChartData = (
return { datasets, labels };
};
export const toRequestChartData = (
traffic: MeteredRequestsSchema,
): { datasets: ChartDatasetType[]; labels: string[] } => {
const { newRecord, labels } = getLabelsAndRecords(traffic);
const datasets = traffic.apiData.map((item) => {
const record = newRecord();
for (const dataPoint of Object.values(item.dataPoints)) {
const requestCount = dataPoint.requests;
record[dataPoint.period] = requestCount;
}
const epInfo = {
label: 'Frontend requests',
color: '#A39EFF',
order: 1,
};
return {
label: epInfo.label,
data: Object.values(record),
backgroundColor: epInfo.color,
hoverBackgroundColor: epInfo.color,
};
});
return { datasets, labels };
};
const getLabelsAndRecords = (
traffic: Pick<
TrafficUsageDataSegmentedCombinedSchema,

View File

@ -4,6 +4,7 @@ import { currentDate } from '../dates';
import { useMemo } from 'react';
import {
toConnectionChartData,
toRequestChartData,
toTrafficUsageChartData,
} from '../chart-functions';
import {
@ -14,6 +15,7 @@ import {
import { BILLING_TRAFFIC_BUNDLE_PRICE } from '../../../billing/BillingDashboard/BillingPlan/BillingPlan';
import { averageTrafficPreviousMonths } from '../average-traffic-previous-months';
import { useConnectionsConsumption } from 'hooks/api/getters/useConnectionsConsumption/useConnectionsConsumption';
import { useRequestsConsumption } from 'hooks/api/getters/useRequestsConsumption/useRequestsConsumption';
export const useTrafficStats = (
includedTraffic: number,
@ -94,3 +96,26 @@ export const useConsumptionStats = (chartDataSelection: ChartDataSelection) => {
return results;
};
export const useRequestsStats = (chartDataSelection: ChartDataSelection) => {
const { result } = useRequestsConsumption(
chartDataSelection.grouping,
toDateRange(chartDataSelection, currentDate),
);
const results = useMemo(() => {
if (result.state !== 'success') {
return {
chartData: { datasets: [], labels: [] },
};
}
const traffic = result.data;
const chartData = toRequestChartData(traffic);
return {
chartData,
};
}, [JSON.stringify(result), JSON.stringify(chartDataSelection)]);
return results;
};

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 { MeteredRequestsSchema } from 'openapi';
export type MeteredRequestsResponse = {
refetch: () => void;
result:
| { state: 'success'; data: MeteredRequestsSchema }
| { state: 'error'; error: Error }
| { state: 'loading' };
};
export const useRequestsConsumption = (
grouping: 'monthly' | 'daily',
{
from,
to,
}: {
from: string;
to: string;
},
): MeteredRequestsResponse => {
const apiPath = `api/admin/metrics/request?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('Consumption Requests Metrics'))
.then((res) => res.json());
};

View File

@ -135,7 +135,7 @@ const sidebars: SidebarsConfig = {
type: 'doc',
label: 'Security and Compliance',
id: 'feature-flag-tutorials/use-cases/security-compliance',
}
},
],
},
{