Charts.utils.test.ts 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. import { describe, expect, it } from 'vitest'
  2. import { computeYAxisDomain, normalizeStackedSeriesData } from './Charts.utils'
  3. const IOPS_DATA = [
  4. { timestamp: 1, disk_iops_write: 1200, disk_iops_read: 24203, disk_iops_max: 25000 },
  5. { timestamp: 2, disk_iops_write: 400, disk_iops_read: 3200, disk_iops_max: 25000 },
  6. { timestamp: 3, disk_iops_write: 100, disk_iops_read: 900, disk_iops_max: 25000 },
  7. ]
  8. const IOPS_VISIBLE = ['disk_iops_write', 'disk_iops_read']
  9. describe('computeYAxisDomain', () => {
  10. describe('percentage charts with max line hidden', () => {
  11. it('returns [0, yMaxFromVisible] to zoom in on the data', () => {
  12. expect(
  13. computeYAxisDomain({
  14. isPercentage: true,
  15. showMaxValue: false,
  16. yMaxFromVisible: 75,
  17. maxAttributeKey: 'cpu_usage_max',
  18. showMaxLine: false,
  19. data: [{ cpu_busy: 75, cpu_usage_max: 100 }],
  20. visibleAttributeNames: ['cpu_busy'],
  21. })
  22. ).toEqual([0, 75])
  23. })
  24. it('still zooms in even when a maxAttributeKey is present', () => {
  25. expect(
  26. computeYAxisDomain({
  27. isPercentage: true,
  28. showMaxValue: false,
  29. yMaxFromVisible: 60,
  30. maxAttributeKey: 'cpu_usage_max',
  31. showMaxLine: true,
  32. data: [{ cpu_busy: 60, cpu_usage_max: 100 }],
  33. visibleAttributeNames: ['cpu_busy'],
  34. })
  35. ).toEqual([0, 60])
  36. })
  37. })
  38. describe('no max reference line', () => {
  39. it('returns auto when maxAttributeKey is undefined', () => {
  40. expect(
  41. computeYAxisDomain({
  42. isPercentage: false,
  43. showMaxValue: false,
  44. yMaxFromVisible: 5000,
  45. maxAttributeKey: undefined,
  46. showMaxLine: false,
  47. data: IOPS_DATA,
  48. visibleAttributeNames: IOPS_VISIBLE,
  49. })
  50. ).toEqual(['auto', 'auto'])
  51. })
  52. it('returns auto when showMaxLine is false', () => {
  53. expect(
  54. computeYAxisDomain({
  55. isPercentage: false,
  56. showMaxValue: true,
  57. yMaxFromVisible: 5000,
  58. maxAttributeKey: 'disk_iops_max',
  59. showMaxLine: false,
  60. data: IOPS_DATA,
  61. visibleAttributeNames: IOPS_VISIBLE,
  62. })
  63. ).toEqual(['auto', 'auto'])
  64. })
  65. })
  66. describe('max reference line not yet loaded (value is 0)', () => {
  67. it('returns auto when diskConfig has not loaded and reference line value is 0', () => {
  68. const dataWithZeroMax = IOPS_DATA.map((p) => ({ ...p, disk_iops_max: 0 }))
  69. expect(
  70. computeYAxisDomain({
  71. isPercentage: false,
  72. showMaxValue: true,
  73. yMaxFromVisible: 5000,
  74. maxAttributeKey: 'disk_iops_max',
  75. showMaxLine: true,
  76. data: dataWithZeroMax,
  77. visibleAttributeNames: IOPS_VISIBLE,
  78. })
  79. ).toEqual(['auto', 'auto'])
  80. })
  81. })
  82. describe('explicit domain with reference line', () => {
  83. it('uses the reference line value when bars stay below it', () => {
  84. // All stacked bar totals (1000, 500) are well below maxRefValue (25000)
  85. expect(
  86. computeYAxisDomain({
  87. isPercentage: false,
  88. showMaxValue: true,
  89. yMaxFromVisible: 800,
  90. maxAttributeKey: 'disk_iops_max',
  91. showMaxLine: true,
  92. data: [
  93. { disk_iops_write: 400, disk_iops_read: 600, disk_iops_max: 25000 },
  94. { disk_iops_write: 200, disk_iops_read: 300, disk_iops_max: 25000 },
  95. ],
  96. visibleAttributeNames: IOPS_VISIBLE,
  97. })
  98. ).toEqual([0, 25000])
  99. })
  100. it('uses the stacked bar total when it exceeds the reference line', () => {
  101. // Stacked total at first point: 24203 + 1200 = 25403 > 25000
  102. expect(
  103. computeYAxisDomain({
  104. isPercentage: false,
  105. showMaxValue: true,
  106. yMaxFromVisible: 24203,
  107. maxAttributeKey: 'disk_iops_max',
  108. showMaxLine: true,
  109. data: IOPS_DATA,
  110. visibleAttributeNames: IOPS_VISIBLE,
  111. })
  112. ).toEqual([0, 25403])
  113. })
  114. it('domain min is always 0', () => {
  115. const [min] = computeYAxisDomain({
  116. isPercentage: false,
  117. showMaxValue: true,
  118. yMaxFromVisible: 100,
  119. maxAttributeKey: 'disk_iops_max',
  120. showMaxLine: true,
  121. data: IOPS_DATA,
  122. visibleAttributeNames: IOPS_VISIBLE,
  123. }) as [number, number]
  124. expect(min).toBe(0)
  125. })
  126. it('works for database connections chart (single bar series, no stacking)', () => {
  127. const data = [
  128. { pg_stat_database_num_backends: 45, max_db_connections: 60 },
  129. { pg_stat_database_num_backends: 52, max_db_connections: 60 },
  130. ]
  131. expect(
  132. computeYAxisDomain({
  133. isPercentage: false,
  134. showMaxValue: true,
  135. yMaxFromVisible: 52,
  136. maxAttributeKey: 'max_db_connections',
  137. showMaxLine: true,
  138. data,
  139. visibleAttributeNames: ['pg_stat_database_num_backends'],
  140. })
  141. ).toEqual([0, 60])
  142. })
  143. it('handles non-numeric values in data gracefully', () => {
  144. const data = [
  145. { disk_iops_write: 'bad', disk_iops_read: null, disk_iops_max: 25000 },
  146. { disk_iops_write: 500, disk_iops_read: 1000, disk_iops_max: 25000 },
  147. ]
  148. expect(
  149. computeYAxisDomain({
  150. isPercentage: false,
  151. showMaxValue: true,
  152. yMaxFromVisible: 1000,
  153. maxAttributeKey: 'disk_iops_max',
  154. showMaxLine: true,
  155. data: data as Record<string, unknown>[],
  156. visibleAttributeNames: IOPS_VISIBLE,
  157. })
  158. ).toEqual([0, 25000])
  159. })
  160. })
  161. })
  162. describe('normalizeStackedSeriesData', () => {
  163. it('normalizes each stacked point to 100%', () => {
  164. const normalized = normalizeStackedSeriesData({
  165. data: [{ system: 25, user: 25, idle: 100 }],
  166. attributeNames: ['system', 'user', 'idle'],
  167. })
  168. expect(normalized[0].system).toBeCloseTo(16.6666666667)
  169. expect(normalized[0].user).toBeCloseTo(16.6666666667)
  170. expect(normalized[0].idle).toBeCloseTo(66.6666666666)
  171. expect(
  172. Number(normalized[0].system) + Number(normalized[0].user) + Number(normalized[0].idle)
  173. ).toBeCloseTo(100)
  174. })
  175. it('leaves empty stacks unchanged', () => {
  176. const empty = [{ system: 0, user: 0, idle: 0 }]
  177. expect(
  178. normalizeStackedSeriesData({
  179. data: empty,
  180. attributeNames: ['system', 'user', 'idle'],
  181. })
  182. ).toEqual(empty)
  183. })
  184. it('leaves data unchanged when attributeNames is empty', () => {
  185. const data = [{ system: 50, user: 30, timestamp: 1000 }]
  186. expect(
  187. normalizeStackedSeriesData({
  188. data,
  189. attributeNames: [],
  190. })
  191. ).toEqual(data)
  192. })
  193. it('preserves non-stacked keys (timestamps, metadata) unchanged', () => {
  194. const data = [{ system: 60, user: 80, period_start: '2024-01-01', timestamp: 1000 }]
  195. const normalized = normalizeStackedSeriesData({
  196. data,
  197. attributeNames: ['system', 'user'],
  198. })
  199. expect(normalized[0].period_start).toBe('2024-01-01')
  200. expect(normalized[0].timestamp).toBe(1000)
  201. expect(Number(normalized[0].system) + Number(normalized[0].user)).toBeCloseTo(100)
  202. })
  203. })