parseMetricWidgetsQueryParam.spec.tsx 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503
  1. import {
  2. MetricChartOverlayType,
  3. MetricDisplayType,
  4. MetricExpressionType,
  5. type MetricsWidget,
  6. } from 'sentry/utils/metrics/types';
  7. import {parseMetricWidgetsQueryParam} from 'sentry/views/metrics/utils/parseMetricWidgetsQueryParam';
  8. function testParsing(input: any, result: MetricsWidget[]) {
  9. expect(parseMetricWidgetsQueryParam(JSON.stringify(input))).toStrictEqual(result);
  10. }
  11. describe('parseMetricWidgetsQueryParam', () => {
  12. it('returns empty array for invalid param', () => {
  13. testParsing(undefined, []);
  14. testParsing({}, []);
  15. testParsing(true, []);
  16. testParsing(2, []);
  17. testParsing('', []);
  18. testParsing('test', []);
  19. testParsing([], []);
  20. });
  21. it('returns a single widget', () => {
  22. testParsing(
  23. [
  24. // INPUT
  25. {
  26. id: 0,
  27. type: MetricExpressionType.QUERY,
  28. mri: 'd:transactions/duration@millisecond',
  29. aggregation: 'sum',
  30. condition: 1,
  31. query: 'test:query',
  32. groupBy: ['dist'],
  33. displayType: 'line',
  34. focusedSeries: [{id: 'default', groupBy: {dist: 'default'}}],
  35. powerUserMode: true,
  36. sort: {order: 'asc'},
  37. isHidden: true,
  38. },
  39. ],
  40. // RESULT
  41. [
  42. {
  43. id: 0,
  44. type: MetricExpressionType.QUERY,
  45. mri: 'd:transactions/duration@millisecond',
  46. aggregation: 'sum',
  47. condition: 1,
  48. query: 'test:query',
  49. groupBy: ['dist'],
  50. displayType: MetricDisplayType.LINE,
  51. focusedSeries: [{id: 'default', groupBy: {dist: 'default'}}],
  52. powerUserMode: true,
  53. sort: {name: undefined, order: 'asc'},
  54. isHidden: true,
  55. overlays: [MetricChartOverlayType.SAMPLES],
  56. },
  57. ]
  58. );
  59. });
  60. it('returns multiple widgets', () => {
  61. testParsing(
  62. // INPUT
  63. [
  64. {
  65. id: 0,
  66. type: MetricExpressionType.QUERY,
  67. mri: 'd:transactions/duration@millisecond',
  68. aggregation: 'sum',
  69. condition: 1,
  70. query: 'test:query',
  71. groupBy: ['dist'],
  72. displayType: 'line',
  73. focusedSeries: [{id: 'default', groupBy: {dist: 'default'}}],
  74. powerUserMode: true,
  75. sort: {name: 'avg', order: 'desc'},
  76. isHidden: true,
  77. },
  78. {
  79. id: 0,
  80. type: MetricExpressionType.EQUATION,
  81. formula: 'a + b',
  82. displayType: 'line',
  83. sort: {name: 'avg', order: 'desc'},
  84. focusedSeries: [],
  85. isHidden: true,
  86. },
  87. {
  88. id: 1,
  89. type: MetricExpressionType.QUERY,
  90. mri: 'd:custom/sentry.event_manager.save@second',
  91. aggregation: 'avg',
  92. condition: 1,
  93. query: '',
  94. groupBy: ['event_type'],
  95. displayType: 'line',
  96. powerUserMode: false,
  97. focusedSeries: [{id: 'default', groupBy: {event_type: 'default'}}],
  98. sort: {name: 'sum', order: 'asc'},
  99. isHidden: false,
  100. },
  101. ],
  102. // RESULT
  103. [
  104. {
  105. id: 0,
  106. type: MetricExpressionType.QUERY,
  107. mri: 'd:transactions/duration@millisecond',
  108. aggregation: 'sum',
  109. condition: 1,
  110. query: 'test:query',
  111. groupBy: ['dist'],
  112. displayType: MetricDisplayType.LINE,
  113. focusedSeries: [{id: 'default', groupBy: {dist: 'default'}}],
  114. powerUserMode: true,
  115. sort: {name: 'avg', order: 'desc'},
  116. isHidden: true,
  117. overlays: [MetricChartOverlayType.SAMPLES],
  118. },
  119. {
  120. id: 1,
  121. type: MetricExpressionType.QUERY,
  122. mri: 'd:custom/sentry.event_manager.save@second',
  123. aggregation: 'avg',
  124. condition: 1,
  125. query: '',
  126. groupBy: ['event_type'],
  127. displayType: MetricDisplayType.LINE,
  128. powerUserMode: false,
  129. focusedSeries: [{id: 'default', groupBy: {event_type: 'default'}}],
  130. sort: {name: 'sum', order: 'asc'},
  131. isHidden: false,
  132. overlays: [MetricChartOverlayType.SAMPLES],
  133. },
  134. // Formulas should always be at the end
  135. {
  136. id: 0,
  137. type: MetricExpressionType.EQUATION,
  138. formula: 'a + b',
  139. displayType: MetricDisplayType.LINE,
  140. sort: {name: 'avg', order: 'desc'},
  141. focusedSeries: [],
  142. isHidden: true,
  143. overlays: [MetricChartOverlayType.SAMPLES],
  144. },
  145. ]
  146. );
  147. });
  148. it('falls back to defaults', () => {
  149. // Missing values
  150. testParsing(
  151. // INPUT
  152. [
  153. {
  154. id: 0,
  155. type: MetricExpressionType.QUERY,
  156. mri: 'd:transactions/duration@millisecond',
  157. },
  158. {
  159. type: MetricExpressionType.EQUATION,
  160. formula: 'a * 2',
  161. },
  162. ],
  163. // RESULT
  164. [
  165. {
  166. id: 0,
  167. type: MetricExpressionType.QUERY,
  168. mri: 'd:transactions/duration@millisecond',
  169. aggregation: 'avg',
  170. condition: undefined,
  171. query: '',
  172. groupBy: [],
  173. displayType: MetricDisplayType.LINE,
  174. focusedSeries: [],
  175. powerUserMode: false,
  176. sort: {name: undefined, order: 'asc'},
  177. isHidden: false,
  178. overlays: [MetricChartOverlayType.SAMPLES],
  179. },
  180. {
  181. id: 0,
  182. type: MetricExpressionType.EQUATION,
  183. formula: 'a * 2',
  184. displayType: MetricDisplayType.LINE,
  185. focusedSeries: [],
  186. sort: {name: undefined, order: 'asc'},
  187. isHidden: false,
  188. overlays: [MetricChartOverlayType.SAMPLES],
  189. },
  190. ]
  191. );
  192. // Invalid values
  193. testParsing(
  194. // INPUT
  195. [
  196. {
  197. id: 'invalid',
  198. type: 123,
  199. mri: 'd:transactions/duration@millisecond',
  200. aggregation: 1,
  201. condition: 'test',
  202. query: 12,
  203. groupBy: true,
  204. displayType: 'aasfcsdf',
  205. focusedSeries: {},
  206. powerUserMode: 1,
  207. sort: {name: 1, order: 'invalid'},
  208. isHidden: 'foo',
  209. },
  210. ],
  211. // RESULT
  212. [
  213. {
  214. id: 0,
  215. type: MetricExpressionType.QUERY,
  216. mri: 'd:transactions/duration@millisecond',
  217. aggregation: 'avg',
  218. condition: undefined,
  219. query: '',
  220. groupBy: [],
  221. displayType: MetricDisplayType.LINE,
  222. focusedSeries: [],
  223. powerUserMode: false,
  224. sort: {name: undefined, order: 'asc'},
  225. isHidden: false,
  226. overlays: [MetricChartOverlayType.SAMPLES],
  227. },
  228. ]
  229. );
  230. });
  231. it('ignores invalid widgets', () => {
  232. testParsing(
  233. // INPUT
  234. [
  235. {
  236. id: 0,
  237. mri: 'd:transactions/duration@millisecond',
  238. },
  239. {
  240. // Missing MRI
  241. },
  242. {
  243. // Mallformed MRI
  244. mri: 'transactions/duration@millisecond',
  245. },
  246. {
  247. // Duplicate id
  248. id: 0,
  249. mri: 'd:transactions/duration@second',
  250. },
  251. {
  252. // Missing formula
  253. type: MetricExpressionType.EQUATION,
  254. },
  255. ],
  256. // RESULT
  257. [
  258. {
  259. id: 0,
  260. type: MetricExpressionType.QUERY,
  261. mri: 'd:transactions/duration@millisecond',
  262. aggregation: 'avg',
  263. condition: undefined,
  264. query: '',
  265. groupBy: [],
  266. displayType: MetricDisplayType.LINE,
  267. focusedSeries: [],
  268. powerUserMode: false,
  269. sort: {name: undefined, order: 'asc'},
  270. isHidden: false,
  271. overlays: [MetricChartOverlayType.SAMPLES],
  272. },
  273. ]
  274. );
  275. });
  276. it('returns empty array if there is no valid widget', () => {
  277. testParsing(
  278. // INPUT
  279. [
  280. {
  281. // Missing MRI
  282. },
  283. {
  284. // Missing formula
  285. type: MetricExpressionType.EQUATION,
  286. },
  287. ],
  288. // RESULT
  289. []
  290. );
  291. });
  292. it('handles missing array in array params', () => {
  293. testParsing(
  294. // INPUT
  295. [
  296. {
  297. id: 0,
  298. type: MetricExpressionType.QUERY,
  299. mri: 'd:transactions/duration@millisecond',
  300. condition: 1,
  301. aggregation: 'sum',
  302. query: 'test:query',
  303. groupBy: 'dist',
  304. displayType: 'line',
  305. focusedSeries: {id: 'default', groupBy: {dist: 'default'}},
  306. powerUserMode: true,
  307. sort: {order: 'asc'},
  308. isHidden: false,
  309. },
  310. ],
  311. // RESULT
  312. [
  313. {
  314. id: 0,
  315. type: MetricExpressionType.QUERY,
  316. mri: 'd:transactions/duration@millisecond',
  317. condition: 1,
  318. aggregation: 'sum',
  319. query: 'test:query',
  320. groupBy: ['dist'],
  321. displayType: MetricDisplayType.LINE,
  322. focusedSeries: [{id: 'default', groupBy: {dist: 'default'}}],
  323. powerUserMode: true,
  324. sort: {name: undefined, order: 'asc'},
  325. isHidden: false,
  326. overlays: [MetricChartOverlayType.SAMPLES],
  327. },
  328. ]
  329. );
  330. });
  331. it('handles overlays array in array params', () => {
  332. testParsing(
  333. // INPUT
  334. [
  335. {
  336. id: 0,
  337. type: MetricExpressionType.QUERY,
  338. mri: 'd:transactions/duration@millisecond',
  339. condition: 1,
  340. aggregation: 'sum',
  341. query: 'test:query',
  342. groupBy: 'dist',
  343. displayType: 'line',
  344. focusedSeries: {id: 'default', groupBy: {dist: 'default'}},
  345. powerUserMode: true,
  346. sort: {order: 'asc'},
  347. isHidden: false,
  348. overlays: [MetricChartOverlayType.SAMPLES, MetricChartOverlayType.RELEASES],
  349. },
  350. ],
  351. // RESULT
  352. [
  353. {
  354. id: 0,
  355. type: MetricExpressionType.QUERY,
  356. mri: 'd:transactions/duration@millisecond',
  357. condition: 1,
  358. aggregation: 'sum',
  359. query: 'test:query',
  360. groupBy: ['dist'],
  361. displayType: MetricDisplayType.LINE,
  362. focusedSeries: [{id: 'default', groupBy: {dist: 'default'}}],
  363. powerUserMode: true,
  364. sort: {name: undefined, order: 'asc'},
  365. isHidden: false,
  366. overlays: [MetricChartOverlayType.SAMPLES, MetricChartOverlayType.RELEASES],
  367. },
  368. ]
  369. );
  370. });
  371. it('adds missing ids', () => {
  372. function widgetWithId<T extends number | undefined>(id: T) {
  373. return {
  374. id,
  375. type: MetricExpressionType.QUERY as const,
  376. mri: 'd:transactions/duration@millisecond' as const,
  377. aggregation: 'sum' as const,
  378. condition: 1,
  379. query: 'test:query',
  380. groupBy: ['dist'],
  381. displayType: MetricDisplayType.LINE,
  382. focusedSeries: [{id: 'default', groupBy: {dist: 'default'}}],
  383. powerUserMode: true,
  384. sort: {name: 'avg' as const, order: 'desc' as const},
  385. isHidden: false,
  386. overlays: [MetricChartOverlayType.SAMPLES],
  387. };
  388. }
  389. testParsing(
  390. // INPUT
  391. [
  392. widgetWithId(0),
  393. widgetWithId(undefined),
  394. widgetWithId(2),
  395. {
  396. // Invalid widget
  397. },
  398. widgetWithId(undefined),
  399. widgetWithId(3),
  400. ],
  401. // RESULT
  402. [
  403. widgetWithId(0),
  404. widgetWithId(1),
  405. widgetWithId(2),
  406. widgetWithId(4),
  407. widgetWithId(3),
  408. ]
  409. );
  410. });
  411. it('resets the id of a single widget to 0', () => {
  412. testParsing(
  413. // INPUT
  414. [
  415. {
  416. id: 5,
  417. type: MetricExpressionType.QUERY,
  418. mri: 'd:transactions/duration@millisecond',
  419. aggregation: 'sum',
  420. condition: 1,
  421. query: 'test:query',
  422. groupBy: ['dist'],
  423. displayType: 'line',
  424. focusedSeries: [{id: 'default', groupBy: {dist: 'default'}}],
  425. powerUserMode: true,
  426. sort: {name: 'avg', order: 'desc'},
  427. isHidden: false,
  428. },
  429. ],
  430. // RESULT
  431. [
  432. {
  433. id: 0,
  434. type: MetricExpressionType.QUERY,
  435. mri: 'd:transactions/duration@millisecond',
  436. aggregation: 'sum',
  437. condition: 1,
  438. query: 'test:query',
  439. groupBy: ['dist'],
  440. displayType: MetricDisplayType.LINE,
  441. focusedSeries: [{id: 'default', groupBy: {dist: 'default'}}],
  442. powerUserMode: true,
  443. sort: {name: 'avg', order: 'desc'},
  444. isHidden: false,
  445. overlays: [MetricChartOverlayType.SAMPLES],
  446. },
  447. ]
  448. );
  449. });
  450. it('tries to parse op if aggregation is not present', () => {
  451. testParsing(
  452. // INPUT
  453. [
  454. {
  455. id: 0,
  456. type: MetricExpressionType.QUERY,
  457. mri: 'd:transactions/duration@millisecond',
  458. condition: 1,
  459. query: 'test:query',
  460. groupBy: ['dist'],
  461. displayType: 'line',
  462. focusedSeries: [{id: 'default', groupBy: {dist: 'default'}}],
  463. powerUserMode: true,
  464. sort: {name: 'avg', order: 'desc'},
  465. isHidden: false,
  466. op: 'avg',
  467. },
  468. ],
  469. // RESULT
  470. [
  471. {
  472. id: 0,
  473. type: MetricExpressionType.QUERY,
  474. mri: 'd:transactions/duration@millisecond',
  475. aggregation: 'avg',
  476. condition: 1,
  477. query: 'test:query',
  478. groupBy: ['dist'],
  479. displayType: MetricDisplayType.LINE,
  480. focusedSeries: [{id: 'default', groupBy: {dist: 'default'}}],
  481. powerUserMode: true,
  482. sort: {name: 'avg', order: 'desc'},
  483. isHidden: false,
  484. overlays: [MetricChartOverlayType.SAMPLES],
  485. },
  486. ]
  487. );
  488. });
  489. });