/****************************************************************************** * Copyright (c) 2026 Contributors to the Eclipse Foundation. * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License 2.0 which is available at * https://www.eclipse.org/legal/epl-2.0. * * SPDX-License-Identifier: EPL-2.0 *****************************************************************************/ import { FC, useMemo } from "react"; import { Box, Paper, Typography, Alert, Stack, IconButton } from "@mui/material"; import { BarPlot } from "@mui/x-charts/BarChart"; import { DatePicker } from "@mui/x-date-pickers/DatePicker"; import { LocalizationProvider } from "@mui/x-date-pickers/LocalizationProvider"; import { AdapterLuxon } from "@mui/x-date-pickers/AdapterLuxon"; import ChevronLeftIcon from "@mui/icons-material/ChevronLeft"; import ChevronRightIcon from "@mui/icons-material/ChevronRight"; import type { Customer, UsageStats } from "../../../extension-registry-types"; import { ChartsReferenceLine, ChartsTooltip, ChartsXAxis, ChartsYAxis, ChartsContainer, } from "@mui/x-charts"; import { DateTime } from "luxon"; interface UsageStatsChartProps { usageStats: readonly UsageStats[]; dailyP95?: number; customer: Customer | null; startDate: DateTime; onStartDateChange: (date: DateTime) => void; embedded?: boolean; compact?: boolean; } export const UsageStatsChart: FC = ({ usageStats, dailyP95, customer, startDate, onStartDateChange, embedded = false, compact = false }) => { const dayStart = startDate.startOf('day').toMillis() / 1000; const dayEnd = startDate.endOf('day').toMillis() / 1000; const formatter = Intl.NumberFormat('en', { notation: 'compact' }); // we have 5min steps const step = 5 * 60; const tierCapacity = customer?.tier !== undefined ? customer.tier.capacity * step / customer.tier.duration : 0; const tierRps = customer?.tier !== undefined ? customer.tier.capacity / customer.tier.duration : 0; const tierRpsRounded = Math.round(tierRps * 100) / 100; const data: UsageStats[] = useMemo( () => { const arr: UsageStats[] = []; for (let idx = dayStart; idx <= dayEnd; idx += step) { arr.push({ windowStart: idx, duration: step, count: 0 }); } for (const stat of usageStats) { const idx = (stat.windowStart - dayStart) / step; if (idx >= 0 && idx < arr.length) { arr[idx].count = stat.count; } } return arr; }, [usageStats] ); const maxDataValue: number = useMemo( () => { if (usageStats.length === 0) { return 0; } else { return Math.max(...usageStats.map(v => v.count)); } }, [usageStats] ); const totalRequests = useMemo( () => usageStats.reduce((sum, d) => sum + d.count, 0), [usageStats] ); const Wrapper: typeof Box = embedded ? Box : Paper; return ( Filters onStartDateChange(startDate.minus({ days: 1 }))}> onStartDateChange(startDate.plus({ days: 1 }))}> {usageStats.length === 0 ? No usage data available for this customer. : <> d.count), label: 'Request Count', color: 'lightgray', }]} height={compact ? 300 : 400} margin={{ top: 30 }} xAxis={[ { id: 'date', data: data.map((value) => value.windowStart * 1000), valueFormatter: (value) => DateTime.fromMillis(value, { zone: 'UTC' }).toLocaleString(DateTime.TIME_24_SIMPLE), scaleType: 'band', height: 50, }, ]} yAxis={[ { id: 'requests', scaleType: 'linear', min: 0, max: Math.max(tierCapacity, maxDataValue) * 1.2, valueFormatter: (value) => formatter.format(value), width: 50 }, ]} > {tierCapacity > 0 && } {dailyP95 !== undefined && } { const d = new Date(value); return d.getMinutes() === 0 && (!compact || d.getHours() % 3 === 0); }} tickLabelPlacement='middle' tickLabelStyle={{ fontSize: 10, }} /> Total requests in selected range: {totalRequests.toLocaleString()} {usageStats.length > 0 && <> ({usageStats.length} data points)} } ); };