parseMetricWidgetsQueryParam.spec.tsx 14 KB

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