diff --git a/frontend/src/component/admin/network/NetworkTrafficUsage/NetworkTrafficUsage.tsx b/frontend/src/component/admin/network/NetworkTrafficUsage/NetworkTrafficUsage.tsx
index 113ccd5926..6691b5448a 100644
--- a/frontend/src/component/admin/network/NetworkTrafficUsage/NetworkTrafficUsage.tsx
+++ b/frontend/src/component/admin/network/NetworkTrafficUsage/NetworkTrafficUsage.tsx
@@ -20,7 +20,10 @@ import {
} from 'chart.js';
import { Bar } from 'react-chartjs-2';
-import { useInstanceTrafficMetrics } from 'hooks/api/getters/useInstanceTrafficMetrics/useInstanceTrafficMetrics';
+import {
+ useInstanceTrafficMetrics,
+ useInstanceTrafficMetrics2,
+} from 'hooks/api/getters/useInstanceTrafficMetrics/useInstanceTrafficMetrics';
import type { Theme } from '@mui/material/styles/createTheme';
import Grid from '@mui/material/Grid';
import { NetworkTrafficUsagePlanSummary } from './NetworkTrafficUsagePlanSummary';
@@ -148,6 +151,21 @@ const NewHeader = styled('div')(() => ({
alignItems: 'flex-start',
}));
+const NewNetworkTrafficUsage: FC = () => {
+ usePageTitle('Network - Data Usage');
+ const theme = useTheme();
+
+ const { isOss } = useUiConfig();
+
+ const selection = { format: 'daily' as const, month: '2025-01' };
+
+ const incoming = useInstanceTrafficMetrics2(selection);
+
+ // do the mapping here somehow
+
+ return
Network Traffic Usage
;
+};
+
export const NetworkTrafficUsage: FC = () => {
usePageTitle('Network - Data Usage');
const theme = useTheme();
@@ -155,6 +173,8 @@ export const NetworkTrafficUsage: FC = () => {
const { isOss } = useUiConfig();
+ // what do we do here? if we're cutting the traffic usage box, might be best to split it into multiple components?
+
const { locationSettings } = useLocationSettings();
const {
record,
@@ -215,6 +235,15 @@ export const NetworkTrafficUsage: FC = () => {
setDatasets(toChartData(labels, traffic, endpointsInfo));
}, [labels, traffic]);
+ // console.log(
+ // 'data',
+ // data,
+ // 'traffic',
+ // traffic,
+ // 'endpointsInfo',
+ // endpointsInfo,
+ // );
+
useEffect(() => {
if (record && period) {
const periodData = record[period];
@@ -224,6 +253,7 @@ export const NetworkTrafficUsage: FC = () => {
useEffect(() => {
if (data) {
+ // if daily, there is a sum. if monthly, use the count from the current month
const usage = toTrafficUsageSum(data.datasets);
setUsageTotal(usage);
if (includedTraffic > 0) {
@@ -247,6 +277,18 @@ export const NetworkTrafficUsage: FC = () => {
}
}, [data]);
+ // if single month:
+ // overage warning
+ // num requests to unleash this month
+ // selector
+ // chart
+ //
+ // if multi month:
+ // overage warning
+ // avg requests per month for preceding n months
+ // selector
+ // char
+
return (
{
+ const fmt = (date: Date) => format(date, 'YYYY-MM-dd');
+ if (selection.format === 'daily') {
+ const month = new Date(selection.month);
+ const from = fmt(month);
+ const to = fmt(endOfMonth(month));
+ return { from, to };
+ } else {
+ const now = new Date();
+ const from = fmt(startOfMonth(subMonths(now, selection.monthsBack)));
+ const to = fmt(endOfMonth(now));
+ return { from, to };
+ }
+};
+
+export type SegmentedSchema = {
+ format: 'monthly' | 'daily';
+ period: { from: string; to: string };
+ apiData: [
+ {
+ apiPath: string;
+ dataPoints: [
+ {
+ // other options: period? time? interval? for?
+ when: string; // in API: string formatted as full date or YYYY-MM, depending on monthly/daily
+ trafficTypes: [
+ {
+ group: string; // we could do 'successful-requests', but that might constrain us in the future
+ count: number; // natural number
+ },
+ ];
+ },
+ ];
+ },
+ ];
+};
+
+export type SegmentedSchemaApiData = SegmentedSchema['apiData'][number];
+
+export type InstanceTrafficMetricsResponse2 = {
+ usage: SegmentedSchema;
+
+ refetch: () => void;
+
+ loading: boolean;
+
+ error?: Error;
+};
+
+export const useInstanceTrafficMetrics2 = (
+ selection: Selection,
+): InstanceTrafficMetricsResponse2 => {
+ const { from, to } = fromSelection(selection);
+ console.log('would use these from and to dates', from, to);
+
+ const apiPath =
+ selection.format === 'daily'
+ ? `api/admin/metrics/traffic2?format=daily&month=${selection.month}`
+ : `api/admin/metrics/traffic2?format=monthly&monthsBack=${selection.monthsBack}`;
+
+ const { data, error, mutate } = useSWR(formatApiPath(apiPath), fetcher);
+
+ return useMemo(
+ () => ({
+ usage: data,
+ loading: !error && !data,
+ refetch: () => mutate(),
+ error,
+ }),
+ [data, error, mutate],
+ );
+};
+
const fetcher = (path: string) => {
return fetch(path)
.then(handleErrorResponses('Instance Metrics'))
diff --git a/frontend/src/hooks/useTrafficData.ts b/frontend/src/hooks/useTrafficData.ts
index b2aefe0598..f47ec98a98 100644
--- a/frontend/src/hooks/useTrafficData.ts
+++ b/frontend/src/hooks/useTrafficData.ts
@@ -1,6 +1,17 @@
import { useState } from 'react';
-import type { IInstanceTrafficMetricsResponse } from './api/getters/useInstanceTrafficMetrics/useInstanceTrafficMetrics';
+import type {
+ IInstanceTrafficMetricsResponse,
+ InstanceTrafficMetricsResponse2,
+ SegmentedSchemaApiData,
+} from './api/getters/useInstanceTrafficMetrics/useInstanceTrafficMetrics';
import type { ChartDataset } from 'chart.js';
+import {
+ addDays,
+ addMonths,
+ differenceInCalendarDays,
+ differenceInCalendarMonths,
+ format,
+} from 'date-fns';
const DEFAULT_TRAFFIC_DATA_UNIT_COST = 5;
const DEFAULT_TRAFFIC_DATA_UNIT_SIZE = 1_000_000;
@@ -104,36 +115,145 @@ const toPeriodsRecord = (
);
};
-const toChartData = (
- days: number[],
- traffic: IInstanceTrafficMetricsResponse,
+const toMonthlyChartData = (
+ traffic: InstanceTrafficMetricsResponse2,
endpointsInfo: Record,
-): ChartDatasetType[] => {
+): { datasets: ChartDatasetType[]; labels: string[] } => {
if (!traffic || !traffic.usage || !traffic.usage.apiData) {
- return [];
+ return { labels: [], datasets: [] };
}
- const data = traffic.usage.apiData
+ const from = new Date(traffic.usage.period.from);
+ const to = new Date(traffic.usage.period.to);
+ const numMonths = Math.abs(differenceInCalendarMonths(to, from));
+ const formatMonth = (date: Date) => format(date, 'yyyy-MM');
+
+ const datasets = traffic.usage.apiData
.filter((item) => !!endpointsInfo[item.apiPath])
.sort(
- (item1: any, item2: any) =>
+ (item1: SegmentedSchemaApiData, item2: SegmentedSchemaApiData) =>
endpointsInfo[item1.apiPath].order -
endpointsInfo[item2.apiPath].order,
)
- .map((item: any) => {
- const daysRec = days.reduce(
- (acc, day: number) => {
- acc[`d${day}`] = 0;
- return acc;
- },
- {} as Record,
- );
-
- for (const dayKey in item.days) {
- const day = item.days[dayKey];
- const dayNum = new Date(Date.parse(day.day)).getUTCDate();
- daysRec[`d${dayNum}`] = day.trafficTypes[0].count;
+ .map((item: SegmentedSchemaApiData) => {
+ const monthsRec: { [month: string]: number } = {};
+ for (let i = 0; i <= numMonths; i++) {
+ monthsRec[formatMonth(addMonths(from, i))] = 0;
}
+
+ for (const month of Object.values(item.dataPoints)) {
+ monthsRec[month.when] = month.trafficTypes[0].count;
+ }
+
+ const epInfo = endpointsInfo[item.apiPath];
+
+ return {
+ label: epInfo.label,
+ data: Object.values(monthsRec),
+ backgroundColor: epInfo.color,
+ hoverBackgroundColor: epInfo.color,
+ };
+ });
+
+ const labels = Array.from({ length: numMonths + 1 }).map((_, index) =>
+ formatMonth(addMonths(from, index)),
+ );
+
+ return { datasets, labels };
+};
+
+// const getDailyChartDataRec = (period: { from: string; to: string }) => {
+// const from = new Date(period.from);
+// const to = new Date(period.to);
+// const numDays = Math.abs(differenceInCalendarDays(to, from));
+// const formatDay = (date: Date) => format(date, 'yyyy-MM-dd');
+
+// const daysRec: { [day: string]: number } = {};
+// for (let i = 0; i <= numDays; i++) {
+// daysRec[formatDay(addDays(from, i))] = 0;
+// }
+
+// return () => ({
+// ...daysRec,
+// });
+// };
+
+// const toAnyChartData =
+// (getDataRec: () => { [key: string]: number }) =>
+// (
+// traffic: InstanceTrafficMetricsResponse2,
+// endpointsInfo: Record,
+// ): ChartDatasetType[] => {
+// if (!traffic || !traffic.usage || !traffic.usage.apiData) {
+// return [];
+// }
+
+// const data = traffic.usage.apiData
+// .filter((item) => !!endpointsInfo[item.apiPath])
+// .sort(
+// (
+// item1: SegmentedSchemaApiData,
+// item2: SegmentedSchemaApiData,
+// ) =>
+// endpointsInfo[item1.apiPath].order -
+// endpointsInfo[item2.apiPath].order,
+// )
+// .map((item: SegmentedSchemaApiData) => {
+// const entries = getDataRec();
+
+// for (const day of Object.values(item.dataPoints)) {
+// entries[day.when] = day.trafficTypes[0].count;
+// }
+
+// const epInfo = endpointsInfo[item.apiPath];
+
+// return {
+// label: epInfo.label,
+// data: Object.values(entries),
+// backgroundColor: epInfo.color,
+// hoverBackgroundColor: epInfo.color,
+// };
+// });
+
+// return data;
+// };
+
+const toDailyChartData = (
+ traffic: InstanceTrafficMetricsResponse2,
+ endpointsInfo: Record,
+): { datasets: ChartDatasetType[]; labels: number[] } => {
+ if (!traffic || !traffic.usage || !traffic.usage.apiData) {
+ return { datasets: [], labels: [] };
+ }
+
+ const from = new Date(traffic.usage.period.from);
+ const to = new Date(traffic.usage.period.to);
+ const numDays = Math.abs(differenceInCalendarDays(to, from));
+ const formatDay = (date: Date) => format(date, 'yyyy-MM-dd');
+
+ const daysRec: { [day: string]: number } = {};
+ for (let i = 0; i <= numDays; i++) {
+ daysRec[formatDay(addDays(from, i))] = 0;
+ }
+
+ const getDaysRec = () => ({
+ ...daysRec,
+ });
+
+ const datasets = traffic.usage.apiData
+ .filter((item) => !!endpointsInfo[item.apiPath])
+ .sort(
+ (item1: SegmentedSchemaApiData, item2: SegmentedSchemaApiData) =>
+ endpointsInfo[item1.apiPath].order -
+ endpointsInfo[item2.apiPath].order,
+ )
+ .map((item: SegmentedSchemaApiData) => {
+ const daysRec = getDaysRec();
+
+ for (const day of Object.values(item.dataPoints)) {
+ daysRec[day.when] = day.trafficTypes[0].count;
+ }
+
const epInfo = endpointsInfo[item.apiPath];
return {
@@ -144,6 +264,72 @@ const toChartData = (
};
});
+ // simplification: assumings days run in a single month from the 1st onwards
+ const labels = Array.from({ length: numDays }).map((_, index) => index + 1);
+
+ return { datasets, labels };
+};
+
+const toChartData = (
+ days: number[],
+ traffic: IInstanceTrafficMetricsResponse,
+ endpointsInfo: Record,
+): ChartDatasetType[] => {
+ if (!traffic || !traffic.usage || !traffic.usage.apiData) {
+ return [];
+ }
+
+ // days contains all the days of the month because the usage data may not have entries for all days. so it
+
+ const data = traffic.usage.apiData
+ .filter((item) => !!endpointsInfo[item.apiPath]) // ignore /edge and unknown endpoints
+ .sort(
+ // sort the data such that admin goes before frontend goes before client
+ (item1: any, item2: any) =>
+ endpointsInfo[item1.apiPath].order -
+ endpointsInfo[item2.apiPath].order,
+ )
+ .map((item: any) => {
+ // generate a list of 0s for each day of the month
+ const daysRec = days.reduce(
+ (acc, day: number) => {
+ acc[`d${day}`] = 0;
+ return acc;
+ },
+ {} as Record,
+ );
+
+ console.log(item, daysRec);
+
+ // for each day in the usage data
+ for (const dayKey in item.days) {
+ const day = item.days[dayKey];
+ // get the day of the month (probably don't need the Date parse)
+ const dayNum = new Date(Date.parse(day.day)).getUTCDate();
+ // add the count to the record for that day
+ daysRec[`d${dayNum}`] = day.trafficTypes[0].count;
+ }
+ const epInfo = endpointsInfo[item.apiPath];
+
+ console.log(daysRec, Object.values(daysRec));
+ return {
+ label: epInfo.label,
+ // traversal order is well-defined
+ data: Object.values(daysRec),
+ backgroundColor: epInfo.color,
+ hoverBackgroundColor: epInfo.color,
+ };
+ });
+
+ console.log(
+ 'traffic data to chart data',
+ days,
+ traffic.usage,
+ endpointsInfo,
+ 'result:',
+ data,
+ );
+
return data;
};
@@ -234,6 +420,17 @@ export const calculateEstimatedMonthlyCost = (
export const useTrafficDataEstimation = () => {
const selectablePeriods = getSelectablePeriods();
const record = toPeriodsRecord(selectablePeriods);
+ console.log('RECORD', record); // Contains each month of the past year:
+ // {
+ // // ... other props
+ // "2024-12": {
+ // "key": "2024-12",
+ // "year": 2024,
+ // "month": 11,
+ // "dayCount": 31,
+ // "label": "December 2024"
+ // }
+ // }
const [period, setPeriod] = useState(selectablePeriods[0].key);
return {
diff --git a/src/lib/features/traffic-data-usage/traffic-data-usage-store.test.ts b/src/lib/features/traffic-data-usage/traffic-data-usage-store.test.ts
index 3d40f19c40..ea1953e802 100644
--- a/src/lib/features/traffic-data-usage/traffic-data-usage-store.test.ts
+++ b/src/lib/features/traffic-data-usage/traffic-data-usage-store.test.ts
@@ -237,6 +237,7 @@ test('can query for monthly aggregation of data for a specified range', async ()
const result =
await trafficDataUsageStore.getTrafficDataForMonthRange(monthsBack);
+ console.log(result);
// should have the current month and the preceding n months (one entry per group)
expect(result.length).toBe((monthsBack + 1) * 2);