ChartDataTransform.utils.test.ts 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. import dayjs from 'dayjs'
  2. import { describe, expect, it } from 'vitest'
  3. import { normalizeChartBuckets } from './ChartDataTransform.utils'
  4. import type { LogsBarChartDatum } from './ProjectUsage.metrics'
  5. describe('normalizeChartBuckets', () => {
  6. const now = dayjs('2024-01-28T12:00:00.000Z')
  7. describe('1hr interval', () => {
  8. it('should create exactly 30 buckets with 2-minute intervals', () => {
  9. const result = normalizeChartBuckets([], '1hr', now.toDate())
  10. expect(result).toHaveLength(30)
  11. // Check first bucket
  12. expect(result[0].timestamp).toBe(now.subtract(60, 'minute').toISOString())
  13. // Check last bucket
  14. expect(result[29].timestamp).toBe(now.subtract(2, 'minute').toISOString())
  15. // Check all buckets are 2 minutes apart
  16. for (let i = 0; i < result.length - 1; i++) {
  17. const diff = dayjs(result[i + 1].timestamp).diff(dayjs(result[i].timestamp), 'minute')
  18. expect(diff).toBe(2)
  19. }
  20. })
  21. it('should aggregate data points into correct 2-minute buckets', () => {
  22. const data: LogsBarChartDatum[] = [
  23. {
  24. // First bucket starts at -60 minutes, so -60 to -59 minutes is in bucket 0
  25. timestamp: now.subtract(60, 'minute').toISOString(),
  26. ok_count: 10,
  27. warning_count: 1,
  28. error_count: 2,
  29. },
  30. {
  31. timestamp: now.subtract(59, 'minute').add(30, 'second').toISOString(),
  32. ok_count: 5,
  33. warning_count: 0,
  34. error_count: 1,
  35. },
  36. {
  37. timestamp: now.subtract(30, 'minute').toISOString(),
  38. ok_count: 20,
  39. warning_count: 2,
  40. error_count: 0,
  41. },
  42. ]
  43. const result = normalizeChartBuckets(data, '1hr', now.toDate())
  44. // First bucket (60-58 minutes ago) should contain aggregated data
  45. const firstBucket = result[0]
  46. expect(firstBucket.ok_count).toBe(15) // 10 + 5
  47. expect(firstBucket.warning_count).toBe(1) // 1 + 0
  48. expect(firstBucket.error_count).toBe(3) // 2 + 1
  49. // Bucket at 30 minutes ago
  50. const bucket15 = result[15] // 30 minutes / 2 minutes per bucket = bucket 15
  51. expect(bucket15.ok_count).toBe(20)
  52. expect(bucket15.warning_count).toBe(2)
  53. expect(bucket15.error_count).toBe(0)
  54. })
  55. it('should return empty buckets when no data provided', () => {
  56. const result = normalizeChartBuckets([], '1hr', now.toDate())
  57. expect(result).toHaveLength(30)
  58. result.forEach((bucket) => {
  59. expect(bucket.ok_count).toBe(0)
  60. expect(bucket.warning_count).toBe(0)
  61. expect(bucket.error_count).toBe(0)
  62. })
  63. })
  64. })
  65. describe('1day interval', () => {
  66. it('should create exactly 24 buckets with 1-hour intervals', () => {
  67. const result = normalizeChartBuckets([], '1day', now.toDate())
  68. expect(result).toHaveLength(24)
  69. // Check first bucket
  70. expect(result[0].timestamp).toBe(now.subtract(24, 'hour').toISOString())
  71. // Check last bucket
  72. expect(result[23].timestamp).toBe(now.subtract(1, 'hour').toISOString())
  73. // Check all buckets are 1 hour apart
  74. for (let i = 0; i < result.length - 1; i++) {
  75. const diff = dayjs(result[i + 1].timestamp).diff(dayjs(result[i].timestamp), 'hour')
  76. expect(diff).toBe(1)
  77. }
  78. })
  79. it('should aggregate multiple data points into hourly buckets', () => {
  80. const data: LogsBarChartDatum[] = [
  81. {
  82. timestamp: now.subtract(23, 'hour').subtract(30, 'minute').toISOString(),
  83. ok_count: 100,
  84. warning_count: 5,
  85. error_count: 3,
  86. },
  87. {
  88. timestamp: now.subtract(23, 'hour').subtract(15, 'minute').toISOString(),
  89. ok_count: 50,
  90. warning_count: 2,
  91. error_count: 1,
  92. },
  93. ]
  94. const result = normalizeChartBuckets(data, '1day', now.toDate())
  95. // First bucket should contain aggregated data
  96. expect(result[0].ok_count).toBe(150)
  97. expect(result[0].warning_count).toBe(7)
  98. expect(result[0].error_count).toBe(4)
  99. })
  100. })
  101. describe('7day interval', () => {
  102. it('should create exactly 28 buckets with 6-hour intervals', () => {
  103. const result = normalizeChartBuckets([], '7day', now.toDate())
  104. expect(result).toHaveLength(28)
  105. // Check first bucket (7 days = 168 hours ago)
  106. expect(result[0].timestamp).toBe(now.subtract(168, 'hour').toISOString())
  107. // Check last bucket
  108. expect(result[27].timestamp).toBe(now.subtract(6, 'hour').toISOString())
  109. // Check all buckets are 6 hours apart
  110. for (let i = 0; i < result.length - 1; i++) {
  111. const diff = dayjs(result[i + 1].timestamp).diff(dayjs(result[i].timestamp), 'hour')
  112. expect(diff).toBe(6)
  113. }
  114. })
  115. it('should aggregate data points into 6-hour buckets', () => {
  116. const data: LogsBarChartDatum[] = [
  117. {
  118. timestamp: now.subtract(167, 'hour').toISOString(),
  119. ok_count: 1000,
  120. warning_count: 10,
  121. error_count: 5,
  122. },
  123. {
  124. timestamp: now.subtract(165, 'hour').toISOString(),
  125. ok_count: 500,
  126. warning_count: 5,
  127. error_count: 2,
  128. },
  129. ]
  130. const result = normalizeChartBuckets(data, '7day', now.toDate())
  131. // First bucket should contain aggregated data
  132. expect(result[0].ok_count).toBe(1500)
  133. expect(result[0].warning_count).toBe(15)
  134. expect(result[0].error_count).toBe(7)
  135. })
  136. })
  137. describe('edge cases', () => {
  138. it('should handle data points outside the time range', () => {
  139. const data: LogsBarChartDatum[] = [
  140. {
  141. timestamp: now.subtract(120, 'minute').toISOString(), // Outside 1hr range
  142. ok_count: 100,
  143. warning_count: 10,
  144. error_count: 5,
  145. },
  146. {
  147. timestamp: now.add(10, 'minute').toISOString(), // Future data
  148. ok_count: 50,
  149. warning_count: 5,
  150. error_count: 2,
  151. },
  152. {
  153. timestamp: now.subtract(30, 'minute').toISOString(), // Within range
  154. ok_count: 25,
  155. warning_count: 2,
  156. error_count: 1,
  157. },
  158. ]
  159. const result = normalizeChartBuckets(data, '1hr', now.toDate())
  160. // Should only include the data within range
  161. const validBucket = result[15] // 30 minutes ago
  162. expect(validBucket.ok_count).toBe(25)
  163. expect(validBucket.warning_count).toBe(2)
  164. expect(validBucket.error_count).toBe(1)
  165. // Other buckets should be empty
  166. expect(result[0].ok_count).toBe(0)
  167. expect(result[29].ok_count).toBe(0)
  168. })
  169. it('should handle undefined/null values in data', () => {
  170. const data: LogsBarChartDatum[] = [
  171. {
  172. timestamp: now.subtract(30, 'minute').toISOString(),
  173. ok_count: undefined as any,
  174. warning_count: null as any,
  175. error_count: 5,
  176. },
  177. ]
  178. const result = normalizeChartBuckets(data, '1hr', now.toDate())
  179. const bucket = result[15]
  180. expect(bucket.ok_count).toBe(0)
  181. expect(bucket.warning_count).toBe(0)
  182. expect(bucket.error_count).toBe(5)
  183. })
  184. })
  185. })