mirror of
https://github.com/Unleash/unleash.git
synced 2025-11-10 01:19:53 +01:00
feat: requests consumption UI for frontend (#9550)
Switching frontend traffic tab to use the requests consumption API:
This commit is contained in:
parent
f093a3f4b3
commit
6d6a4290fe
@ -18,7 +18,7 @@ import annotationPlugin from 'chartjs-plugin-annotation';
|
|||||||
import { customHighlightPlugin } from 'component/common/Chart/customHighlightPlugin';
|
import { customHighlightPlugin } from 'component/common/Chart/customHighlightPlugin';
|
||||||
import { PeriodSelector } from './PeriodSelector';
|
import { PeriodSelector } from './PeriodSelector';
|
||||||
import { getChartLabel } from './chart-functions';
|
import { getChartLabel } from './chart-functions';
|
||||||
import { useTrafficStats } from './hooks/useStats';
|
import { useRequestsStats } from './hooks/useStats';
|
||||||
import { StyledBox, TopRow } from './SharedComponents';
|
import { StyledBox, TopRow } from './SharedComponents';
|
||||||
import { useChartDataSelection } from './hooks/useChartDataSelection';
|
import { useChartDataSelection } from './hooks/useChartDataSelection';
|
||||||
|
|
||||||
@ -30,12 +30,7 @@ const FrontendNetworkTrafficUsage: FC = () => {
|
|||||||
const { chartDataSelection, setChartDataSelection, options } =
|
const { chartDataSelection, setChartDataSelection, options } =
|
||||||
useChartDataSelection();
|
useChartDataSelection();
|
||||||
|
|
||||||
const includedTraffic = 0;
|
const { chartData } = useRequestsStats(chartDataSelection);
|
||||||
const { chartData } = useTrafficStats(
|
|
||||||
includedTraffic,
|
|
||||||
chartDataSelection,
|
|
||||||
'/api/frontend',
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ConditionallyRender
|
<ConditionallyRender
|
||||||
|
|||||||
@ -1,9 +1,11 @@
|
|||||||
import type {
|
import type {
|
||||||
MeteredConnectionsSchema,
|
MeteredConnectionsSchema,
|
||||||
|
MeteredRequestsSchema,
|
||||||
TrafficUsageDataSegmentedCombinedSchema,
|
TrafficUsageDataSegmentedCombinedSchema,
|
||||||
} from 'openapi';
|
} from 'openapi';
|
||||||
import {
|
import {
|
||||||
toConnectionChartData,
|
toConnectionChartData,
|
||||||
|
toRequestChartData,
|
||||||
toTrafficUsageChartData,
|
toTrafficUsageChartData,
|
||||||
} from './chart-functions';
|
} from './chart-functions';
|
||||||
import { endpointsInfo } from './endpoint-info';
|
import { endpointsInfo } from './endpoint-info';
|
||||||
@ -251,3 +253,92 @@ describe('toConnectionChartData', () => {
|
|||||||
expect(toConnectionChartData(input)).toMatchObject(expectedOutput);
|
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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import type { ChartDataset } from 'chart.js';
|
import type { ChartDataset } from 'chart.js';
|
||||||
import type {
|
import type {
|
||||||
MeteredConnectionsSchema,
|
MeteredConnectionsSchema,
|
||||||
|
MeteredRequestsSchema,
|
||||||
TrafficUsageDataSegmentedCombinedSchema,
|
TrafficUsageDataSegmentedCombinedSchema,
|
||||||
} from 'openapi';
|
} from 'openapi';
|
||||||
import { endpointsInfo } from './endpoint-info';
|
import { endpointsInfo } from './endpoint-info';
|
||||||
@ -73,6 +74,34 @@ export const toConnectionChartData = (
|
|||||||
return { datasets, labels };
|
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 = (
|
const getLabelsAndRecords = (
|
||||||
traffic: Pick<
|
traffic: Pick<
|
||||||
TrafficUsageDataSegmentedCombinedSchema,
|
TrafficUsageDataSegmentedCombinedSchema,
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import { currentDate } from '../dates';
|
|||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import {
|
import {
|
||||||
toConnectionChartData,
|
toConnectionChartData,
|
||||||
|
toRequestChartData,
|
||||||
toTrafficUsageChartData,
|
toTrafficUsageChartData,
|
||||||
} from '../chart-functions';
|
} from '../chart-functions';
|
||||||
import {
|
import {
|
||||||
@ -14,6 +15,7 @@ import {
|
|||||||
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';
|
import { useConnectionsConsumption } from 'hooks/api/getters/useConnectionsConsumption/useConnectionsConsumption';
|
||||||
|
import { useRequestsConsumption } from 'hooks/api/getters/useRequestsConsumption/useRequestsConsumption';
|
||||||
|
|
||||||
export const useTrafficStats = (
|
export const useTrafficStats = (
|
||||||
includedTraffic: number,
|
includedTraffic: number,
|
||||||
@ -94,3 +96,26 @@ export const useConsumptionStats = (chartDataSelection: ChartDataSelection) => {
|
|||||||
|
|
||||||
return results;
|
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;
|
||||||
|
};
|
||||||
|
|||||||
@ -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());
|
||||||
|
};
|
||||||
@ -135,7 +135,7 @@ const sidebars: SidebarsConfig = {
|
|||||||
type: 'doc',
|
type: 'doc',
|
||||||
label: 'Security and Compliance',
|
label: 'Security and Compliance',
|
||||||
id: 'feature-flag-tutorials/use-cases/security-compliance',
|
id: 'feature-flag-tutorials/use-cases/security-compliance',
|
||||||
}
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user