ChartDataTransform.utils.ts 2.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889
  1. import dayjs from 'dayjs'
  2. import type { LogsBarChartDatum } from './ProjectUsage.metrics'
  3. /**
  4. * Configuration for chart bucket sizes based on time interval
  5. */
  6. const BUCKET_CONFIG = {
  7. '1hr': {
  8. bucketMinutes: 2, // 2-minute buckets
  9. expectedBuckets: 30, // 60 minutes / 2 = 30 buckets
  10. },
  11. '1day': {
  12. bucketMinutes: 60, // 1-hour buckets
  13. expectedBuckets: 24, // 24 hours
  14. },
  15. '7day': {
  16. bucketMinutes: 360, // 6-hour buckets
  17. expectedBuckets: 28, // 168 hours / 6 = 28 buckets
  18. },
  19. } as const
  20. type IntervalKey = keyof typeof BUCKET_CONFIG
  21. /**
  22. * Normalizes chart data to consistent bucket sizes regardless of backend data density.
  23. *
  24. * For 1hr interval: Creates 30 buckets of 2 minutes each
  25. * For 1day interval: Creates 24 buckets of 1 hour each
  26. * For 7day interval: Creates 28 buckets of 6 hours each
  27. *
  28. * This ensures consistent bar width in charts and proper data aggregation.
  29. *
  30. * @param data - Raw chart data from backend
  31. * @param interval - Time interval key ('1hr', '1day', '7day')
  32. * @param endDate - End date for the chart (defaults to now)
  33. * @returns Array of exactly the expected number of buckets with aggregated data
  34. */
  35. export function normalizeChartBuckets(
  36. data: LogsBarChartDatum[],
  37. interval: IntervalKey,
  38. endDate: Date = new Date()
  39. ): LogsBarChartDatum[] {
  40. const config = BUCKET_CONFIG[interval]
  41. const { bucketMinutes, expectedBuckets } = config
  42. // Calculate start time based on expected buckets
  43. const end = dayjs(endDate)
  44. const start = end.subtract(expectedBuckets * bucketMinutes, 'minute')
  45. // Create empty buckets
  46. const buckets: LogsBarChartDatum[] = []
  47. let currentBucketStart = start
  48. for (let i = 0; i < expectedBuckets; i++) {
  49. buckets.push({
  50. timestamp: currentBucketStart.toISOString(),
  51. ok_count: 0,
  52. warning_count: 0,
  53. error_count: 0,
  54. })
  55. currentBucketStart = currentBucketStart.add(bucketMinutes, 'minute')
  56. }
  57. // If no data, return empty buckets
  58. if (!data || data.length === 0) {
  59. return buckets
  60. }
  61. // Aggregate data into buckets
  62. for (const datum of data) {
  63. const datumTime = dayjs(datum.timestamp)
  64. // Find which bucket this datum belongs to
  65. const bucketIndex = Math.floor(datumTime.diff(start, 'minute') / bucketMinutes)
  66. // Skip data points outside our time range
  67. if (bucketIndex < 0 || bucketIndex >= expectedBuckets) {
  68. continue
  69. }
  70. // Aggregate counts into the appropriate bucket
  71. buckets[bucketIndex].ok_count += datum.ok_count || 0
  72. buckets[bucketIndex].warning_count += datum.warning_count || 0
  73. buckets[bucketIndex].error_count += datum.error_count || 0
  74. }
  75. return buckets
  76. }