heatmap_data_converter.test.ts 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375
  1. import _ from 'lodash';
  2. import { describe, beforeEach, it, expect } from '../../../../../test/lib/common';
  3. import TimeSeries from 'app/core/time_series2';
  4. import {
  5. convertToHeatMap,
  6. convertToCards,
  7. histogramToHeatmap,
  8. calculateBucketSize,
  9. isHeatmapDataEqual,
  10. } from '../heatmap_data_converter';
  11. import { HeatmapData } from '../types';
  12. describe('isHeatmapDataEqual', () => {
  13. const ctx: any = {};
  14. beforeEach(() => {
  15. ctx.heatmapA = {
  16. '1422774000000': {
  17. x: 1422774000000,
  18. buckets: {
  19. '1': { y: 1, values: [1, 1.5] },
  20. '2': { y: 2, values: [1] },
  21. },
  22. },
  23. };
  24. ctx.heatmapB = {
  25. '1422774000000': {
  26. x: 1422774000000,
  27. buckets: {
  28. '1': { y: 1, values: [1.5, 1] },
  29. '2': { y: 2, values: [1] },
  30. },
  31. },
  32. };
  33. });
  34. it('should proper compare objects', () => {
  35. const heatmapC = _.cloneDeep(ctx.heatmapA);
  36. heatmapC['1422774000000'].buckets['1'].values = [1, 1.5];
  37. const heatmapD = _.cloneDeep(ctx.heatmapA);
  38. heatmapD['1422774000000'].buckets['1'].values = [1.5, 1, 1.6];
  39. const heatmapE = _.cloneDeep(ctx.heatmapA);
  40. heatmapE['1422774000000'].buckets['1'].values = [1, 1.6];
  41. const empty = {};
  42. const emptyValues = _.cloneDeep(ctx.heatmapA);
  43. emptyValues['1422774000000'].buckets['1'].values = [];
  44. expect(isHeatmapDataEqual(ctx.heatmapA, ctx.heatmapB)).toBe(true);
  45. expect(isHeatmapDataEqual(ctx.heatmapB, ctx.heatmapA)).toBe(true);
  46. expect(isHeatmapDataEqual(ctx.heatmapA, heatmapC)).toBe(true);
  47. expect(isHeatmapDataEqual(heatmapC, ctx.heatmapA)).toBe(true);
  48. expect(isHeatmapDataEqual(ctx.heatmapA, heatmapD)).toBe(false);
  49. expect(isHeatmapDataEqual(heatmapD, ctx.heatmapA)).toBe(false);
  50. expect(isHeatmapDataEqual(ctx.heatmapA, heatmapE)).toBe(false);
  51. expect(isHeatmapDataEqual(heatmapE, ctx.heatmapA)).toBe(false);
  52. expect(isHeatmapDataEqual(empty, ctx.heatmapA)).toBe(false);
  53. expect(isHeatmapDataEqual(ctx.heatmapA, empty)).toBe(false);
  54. expect(isHeatmapDataEqual(emptyValues, ctx.heatmapA)).toBe(false);
  55. expect(isHeatmapDataEqual(ctx.heatmapA, emptyValues)).toBe(false);
  56. });
  57. });
  58. describe('calculateBucketSize', () => {
  59. const ctx: any = {};
  60. describe('when logBase is 1 (linear scale)', () => {
  61. beforeEach(() => {
  62. ctx.logBase = 1;
  63. ctx.bounds_set = [
  64. { bounds: [], size: 0 },
  65. { bounds: [0], size: 0 },
  66. { bounds: [4], size: 4 },
  67. { bounds: [0, 1, 2, 3, 4], size: 1 },
  68. { bounds: [0, 1, 3, 5, 7], size: 1 },
  69. { bounds: [0, 3, 7, 9, 15], size: 2 },
  70. { bounds: [0, 7, 3, 15, 9], size: 2 },
  71. { bounds: [0, 5, 10, 15, 50], size: 5 },
  72. ];
  73. });
  74. it('should properly calculate bucket size', () => {
  75. _.each(ctx.bounds_set, b => {
  76. const bucketSize = calculateBucketSize(b.bounds, ctx.logBase);
  77. expect(bucketSize).toBe(b.size);
  78. });
  79. });
  80. });
  81. describe('when logBase is 2', () => {
  82. beforeEach(() => {
  83. ctx.logBase = 2;
  84. ctx.bounds_set = [
  85. { bounds: [], size: 0 },
  86. { bounds: [0], size: 0 },
  87. { bounds: [4], size: 4 },
  88. { bounds: [1, 2, 4, 8], size: 1 },
  89. { bounds: [1, Math.SQRT2, 2, 8, 16], size: 0.5 },
  90. ];
  91. });
  92. it('should properly calculate bucket size', () => {
  93. _.each(ctx.bounds_set, b => {
  94. const bucketSize = calculateBucketSize(b.bounds, ctx.logBase);
  95. expect(isEqual(bucketSize, b.size)).toBe(true);
  96. });
  97. });
  98. });
  99. });
  100. describe('HeatmapDataConverter', () => {
  101. const ctx: any = {};
  102. beforeEach(() => {
  103. ctx.series = [];
  104. ctx.series.push(
  105. new TimeSeries({
  106. datapoints: [[1, 1422774000000], [1, 1422774000010], [2, 1422774060000]],
  107. alias: 'series1',
  108. })
  109. );
  110. ctx.series.push(
  111. new TimeSeries({
  112. datapoints: [[2, 1422774000000], [2, 1422774000010], [3, 1422774060000]],
  113. alias: 'series2',
  114. })
  115. );
  116. ctx.series.push(
  117. new TimeSeries({
  118. datapoints: [[5, 1422774000000], [3, 1422774000010], [4, 1422774060000]],
  119. alias: 'series3',
  120. })
  121. );
  122. ctx.xBucketSize = 60000; // 60s
  123. ctx.yBucketSize = 2;
  124. ctx.logBase = 1;
  125. });
  126. describe('when logBase is 1 (linear scale)', () => {
  127. beforeEach(() => {
  128. ctx.logBase = 1;
  129. });
  130. it('should build proper heatmap data', () => {
  131. const expectedHeatmap = {
  132. '1422774000000': {
  133. x: 1422774000000,
  134. buckets: {
  135. '0': {
  136. y: 0,
  137. values: [1, 1],
  138. count: 2,
  139. bounds: { bottom: 0, top: 2 },
  140. },
  141. '2': {
  142. y: 2,
  143. values: [2, 2, 3],
  144. count: 3,
  145. bounds: { bottom: 2, top: 4 },
  146. },
  147. '4': { y: 4, values: [5], count: 1, bounds: { bottom: 4, top: 6 } },
  148. },
  149. },
  150. '1422774060000': {
  151. x: 1422774060000,
  152. buckets: {
  153. '2': {
  154. y: 2,
  155. values: [2, 3],
  156. count: 3,
  157. bounds: { bottom: 2, top: 4 },
  158. },
  159. '4': { y: 4, values: [4], count: 1, bounds: { bottom: 4, top: 6 } },
  160. },
  161. },
  162. };
  163. const heatmap = convertToHeatMap(ctx.series, ctx.yBucketSize, ctx.xBucketSize, ctx.logBase);
  164. expect(isHeatmapDataEqual(heatmap, expectedHeatmap)).toBe(true);
  165. });
  166. });
  167. describe.skip('when logBase is 2', () => {
  168. beforeEach(() => {
  169. ctx.logBase = 2;
  170. });
  171. it('should build proper heatmap data', () => {
  172. const expectedHeatmap = {
  173. '1422774000000': {
  174. x: 1422774000000,
  175. buckets: {
  176. '1': { y: 1, values: [1] },
  177. '2': { y: 2, values: [2] },
  178. },
  179. },
  180. '1422774060000': {
  181. x: 1422774060000,
  182. buckets: {
  183. '2': { y: 2, values: [2, 3] },
  184. },
  185. },
  186. };
  187. const heatmap = convertToHeatMap(ctx.series, ctx.yBucketSize, ctx.xBucketSize, ctx.logBase);
  188. expect(isHeatmapDataEqual(heatmap, expectedHeatmap)).toBe(true);
  189. });
  190. });
  191. });
  192. describe('Histogram converter', () => {
  193. const ctx: any = {};
  194. beforeEach(() => {
  195. ctx.series = [];
  196. ctx.series.push(
  197. new TimeSeries({
  198. datapoints: [[1, 1422774000000], [0, 1422774060000]],
  199. alias: '1',
  200. label: '1',
  201. })
  202. );
  203. ctx.series.push(
  204. new TimeSeries({
  205. datapoints: [[5, 1422774000000], [3, 1422774060000]],
  206. alias: '2',
  207. label: '2',
  208. })
  209. );
  210. ctx.series.push(
  211. new TimeSeries({
  212. datapoints: [[0, 1422774000000], [1, 1422774060000]],
  213. alias: '3',
  214. label: '3',
  215. })
  216. );
  217. });
  218. describe('when converting histogram', () => {
  219. beforeEach(() => {});
  220. it('should build proper heatmap data', () => {
  221. const expectedHeatmap: HeatmapData = {
  222. '1422774000000': {
  223. x: 1422774000000,
  224. buckets: {
  225. '0': {
  226. y: 0,
  227. count: 1,
  228. bounds: { bottom: 0, top: null },
  229. values: [],
  230. points: [],
  231. },
  232. '1': {
  233. y: 1,
  234. count: 5,
  235. bounds: { bottom: 1, top: null },
  236. values: [],
  237. points: [],
  238. },
  239. '2': {
  240. y: 2,
  241. count: 0,
  242. bounds: { bottom: 2, top: null },
  243. values: [],
  244. points: [],
  245. },
  246. },
  247. },
  248. '1422774060000': {
  249. x: 1422774060000,
  250. buckets: {
  251. '0': {
  252. y: 0,
  253. count: 0,
  254. bounds: { bottom: 0, top: null },
  255. values: [],
  256. points: [],
  257. },
  258. '1': {
  259. y: 1,
  260. count: 3,
  261. bounds: { bottom: 1, top: null },
  262. values: [],
  263. points: [],
  264. },
  265. '2': {
  266. y: 2,
  267. count: 1,
  268. bounds: { bottom: 2, top: null },
  269. values: [],
  270. points: [],
  271. },
  272. },
  273. },
  274. };
  275. const heatmap = histogramToHeatmap(ctx.series);
  276. expect(heatmap).toEqual(expectedHeatmap);
  277. });
  278. it('should use bucket index as a bound', () => {
  279. const heatmap = histogramToHeatmap(ctx.series);
  280. const bucketLabels = _.map(heatmap['1422774000000'].buckets, (b, label) => label);
  281. const bucketYs = _.map(heatmap['1422774000000'].buckets, 'y');
  282. const bucketBottoms = _.map(heatmap['1422774000000'].buckets, b => b.bounds.bottom);
  283. const expectedBounds = [0, 1, 2];
  284. expect(bucketLabels).toEqual(_.map(expectedBounds, b => b.toString()));
  285. expect(bucketYs).toEqual(expectedBounds);
  286. expect(bucketBottoms).toEqual(expectedBounds);
  287. });
  288. });
  289. });
  290. describe('convertToCards', () => {
  291. let buckets: HeatmapData = {};
  292. beforeEach(() => {
  293. buckets = {
  294. '1422774000000': {
  295. x: 1422774000000,
  296. buckets: {
  297. '1': { y: 1, values: [1], count: 1, bounds: {} },
  298. '2': { y: 2, values: [2], count: 1, bounds: {} },
  299. },
  300. },
  301. '1422774060000': {
  302. x: 1422774060000,
  303. buckets: {
  304. '2': { y: 2, values: [2, 3], count: 2, bounds: {} },
  305. },
  306. },
  307. };
  308. });
  309. it('should build proper cards data', () => {
  310. const expectedCards = [
  311. { x: 1422774000000, y: 1, count: 1, values: [1], yBounds: {} },
  312. { x: 1422774000000, y: 2, count: 1, values: [2], yBounds: {} },
  313. { x: 1422774060000, y: 2, count: 2, values: [2, 3], yBounds: {} },
  314. ];
  315. const res = convertToCards(buckets);
  316. expect(res.cards).toMatchObject(expectedCards);
  317. });
  318. it('should build proper cards stats', () => {
  319. const expectedStats = { min: 1, max: 2 };
  320. const res = convertToCards(buckets);
  321. expect(res.cardStats).toMatchObject(expectedStats);
  322. });
  323. });
  324. /**
  325. * Compare two numbers with given precision. Suitable for compare float numbers after conversions with precision loss.
  326. * @param a
  327. * @param b
  328. * @param precision
  329. */
  330. function isEqual(a: number, b: number, precision = 0.000001): boolean {
  331. if (a === b) {
  332. return true;
  333. } else {
  334. return Math.abs(1 - a / b) <= precision;
  335. }
  336. }