parseMetricWidgetsQueryParam.spec.tsx 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450
  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('parseMetricWidgetQueryParam', () => {
  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. op: 'sum',
  33. query: 'test:query',
  34. groupBy: ['dist'],
  35. displayType: 'line',
  36. focusedSeries: [{id: 'default', groupBy: {dist: 'default'}}],
  37. powerUserMode: true,
  38. sort: {order: 'asc'},
  39. isHidden: true,
  40. },
  41. ],
  42. // RESULT
  43. [
  44. {
  45. id: 0,
  46. type: MetricExpressionType.QUERY,
  47. mri: 'd:transactions/duration@millisecond',
  48. op: 'sum',
  49. query: 'test:query',
  50. groupBy: ['dist'],
  51. displayType: MetricDisplayType.LINE,
  52. focusedSeries: [{id: 'default', groupBy: {dist: 'default'}}],
  53. powerUserMode: true,
  54. sort: {name: undefined, order: 'asc'},
  55. isHidden: true,
  56. overlays: [MetricChartOverlayType.SAMPLES],
  57. },
  58. ]
  59. );
  60. });
  61. it('returns multiple widgets', () => {
  62. testParsing(
  63. // INPUT
  64. [
  65. {
  66. id: 0,
  67. type: MetricExpressionType.QUERY,
  68. mri: 'd:transactions/duration@millisecond',
  69. op: 'sum',
  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. op: 'avg',
  92. query: '',
  93. groupBy: ['event_type'],
  94. displayType: 'line',
  95. powerUserMode: false,
  96. focusedSeries: [{id: 'default', groupBy: {event_type: 'default'}}],
  97. sort: {name: 'sum', order: 'asc'},
  98. isHidden: false,
  99. },
  100. ],
  101. // RESULT
  102. [
  103. {
  104. id: 0,
  105. type: MetricExpressionType.QUERY,
  106. mri: 'd:transactions/duration@millisecond',
  107. op: 'sum',
  108. query: 'test:query',
  109. groupBy: ['dist'],
  110. displayType: MetricDisplayType.LINE,
  111. focusedSeries: [{id: 'default', groupBy: {dist: 'default'}}],
  112. powerUserMode: true,
  113. sort: {name: 'avg', order: 'desc'},
  114. isHidden: true,
  115. overlays: [MetricChartOverlayType.SAMPLES],
  116. },
  117. {
  118. id: 1,
  119. type: MetricExpressionType.QUERY,
  120. mri: 'd:custom/sentry.event_manager.save@second',
  121. op: 'avg',
  122. query: '',
  123. groupBy: ['event_type'],
  124. displayType: MetricDisplayType.LINE,
  125. powerUserMode: false,
  126. focusedSeries: [{id: 'default', groupBy: {event_type: 'default'}}],
  127. sort: {name: 'sum', order: 'asc'},
  128. isHidden: false,
  129. overlays: [MetricChartOverlayType.SAMPLES],
  130. },
  131. // Formulas should always be at the end
  132. {
  133. id: 0,
  134. type: MetricExpressionType.EQUATION,
  135. formula: 'a + b',
  136. displayType: MetricDisplayType.LINE,
  137. sort: {name: 'avg', order: 'desc'},
  138. focusedSeries: [],
  139. isHidden: true,
  140. overlays: [MetricChartOverlayType.SAMPLES],
  141. },
  142. ]
  143. );
  144. });
  145. it('falls back to defaults', () => {
  146. // Missing values
  147. testParsing(
  148. // INPUT
  149. [
  150. {
  151. id: 0,
  152. type: MetricExpressionType.QUERY,
  153. mri: 'd:transactions/duration@millisecond',
  154. },
  155. {
  156. type: MetricExpressionType.EQUATION,
  157. formula: 'a * 2',
  158. },
  159. ],
  160. // RESULT
  161. [
  162. {
  163. id: 0,
  164. type: MetricExpressionType.QUERY,
  165. mri: 'd:transactions/duration@millisecond',
  166. op: 'avg',
  167. query: '',
  168. groupBy: [],
  169. displayType: MetricDisplayType.LINE,
  170. focusedSeries: [],
  171. powerUserMode: false,
  172. sort: {name: undefined, order: 'asc'},
  173. isHidden: false,
  174. overlays: [MetricChartOverlayType.SAMPLES],
  175. },
  176. {
  177. id: 0,
  178. type: MetricExpressionType.EQUATION,
  179. formula: 'a * 2',
  180. displayType: MetricDisplayType.LINE,
  181. focusedSeries: [],
  182. sort: {name: undefined, order: 'asc'},
  183. isHidden: false,
  184. overlays: [MetricChartOverlayType.SAMPLES],
  185. },
  186. ]
  187. );
  188. // Invalid values
  189. testParsing(
  190. // INPUT
  191. [
  192. {
  193. id: 'invalid',
  194. type: 123,
  195. mri: 'd:transactions/duration@millisecond',
  196. op: 1,
  197. query: 12,
  198. groupBy: true,
  199. displayType: 'aasfcsdf',
  200. focusedSeries: {},
  201. powerUserMode: 1,
  202. sort: {name: 1, order: 'invalid'},
  203. isHidden: 'foo',
  204. },
  205. ],
  206. // RESULT
  207. [
  208. {
  209. id: 0,
  210. type: MetricExpressionType.QUERY,
  211. mri: 'd:transactions/duration@millisecond',
  212. op: 'avg',
  213. query: '',
  214. groupBy: [],
  215. displayType: MetricDisplayType.LINE,
  216. focusedSeries: [],
  217. powerUserMode: false,
  218. sort: {name: undefined, order: 'asc'},
  219. isHidden: false,
  220. overlays: [MetricChartOverlayType.SAMPLES],
  221. },
  222. ]
  223. );
  224. });
  225. it('ignores invalid widgets', () => {
  226. testParsing(
  227. // INPUT
  228. [
  229. {
  230. id: 0,
  231. mri: 'd:transactions/duration@millisecond',
  232. },
  233. {
  234. // Missing MRI
  235. },
  236. {
  237. // Mallformed MRI
  238. mri: 'transactions/duration@millisecond',
  239. },
  240. {
  241. // Duplicate id
  242. id: 0,
  243. mri: 'd:transactions/duration@second',
  244. },
  245. {
  246. // Missing formula
  247. type: MetricExpressionType.EQUATION,
  248. },
  249. ],
  250. // RESULT
  251. [
  252. {
  253. id: 0,
  254. type: MetricExpressionType.QUERY,
  255. mri: 'd:transactions/duration@millisecond',
  256. op: 'avg',
  257. query: '',
  258. groupBy: [],
  259. displayType: MetricDisplayType.LINE,
  260. focusedSeries: [],
  261. powerUserMode: false,
  262. sort: {name: undefined, order: 'asc'},
  263. isHidden: false,
  264. overlays: [MetricChartOverlayType.SAMPLES],
  265. },
  266. ]
  267. );
  268. });
  269. it('returns default widget if there is no valid widget', () => {
  270. testParsing(
  271. // INPUT
  272. [
  273. {
  274. // Missing MRI
  275. },
  276. {
  277. // Missing formula
  278. type: MetricExpressionType.EQUATION,
  279. },
  280. ],
  281. // RESULT
  282. defaultState
  283. );
  284. });
  285. it('handles missing array in array params', () => {
  286. testParsing(
  287. // INPUT
  288. [
  289. {
  290. id: 0,
  291. type: MetricExpressionType.QUERY,
  292. mri: 'd:transactions/duration@millisecond',
  293. op: 'sum',
  294. query: 'test:query',
  295. groupBy: 'dist',
  296. displayType: 'line',
  297. focusedSeries: {id: 'default', groupBy: {dist: 'default'}},
  298. powerUserMode: true,
  299. sort: {order: 'asc'},
  300. isHidden: false,
  301. },
  302. ],
  303. // RESULT
  304. [
  305. {
  306. id: 0,
  307. type: MetricExpressionType.QUERY,
  308. mri: 'd:transactions/duration@millisecond',
  309. op: 'sum',
  310. query: 'test:query',
  311. groupBy: ['dist'],
  312. displayType: MetricDisplayType.LINE,
  313. focusedSeries: [{id: 'default', groupBy: {dist: 'default'}}],
  314. powerUserMode: true,
  315. sort: {name: undefined, order: 'asc'},
  316. isHidden: false,
  317. overlays: [MetricChartOverlayType.SAMPLES],
  318. },
  319. ]
  320. );
  321. });
  322. it('handles overlays array in array params', () => {
  323. testParsing(
  324. // INPUT
  325. [
  326. {
  327. id: 0,
  328. type: MetricExpressionType.QUERY,
  329. mri: 'd:transactions/duration@millisecond',
  330. op: 'sum',
  331. query: 'test:query',
  332. groupBy: 'dist',
  333. displayType: 'line',
  334. focusedSeries: {id: 'default', groupBy: {dist: 'default'}},
  335. powerUserMode: true,
  336. sort: {order: 'asc'},
  337. isHidden: false,
  338. overlays: [MetricChartOverlayType.SAMPLES, MetricChartOverlayType.RELEASES],
  339. },
  340. ],
  341. // RESULT
  342. [
  343. {
  344. id: 0,
  345. type: MetricExpressionType.QUERY,
  346. mri: 'd:transactions/duration@millisecond',
  347. op: 'sum',
  348. query: 'test:query',
  349. groupBy: ['dist'],
  350. displayType: MetricDisplayType.LINE,
  351. focusedSeries: [{id: 'default', groupBy: {dist: 'default'}}],
  352. powerUserMode: true,
  353. sort: {name: undefined, order: 'asc'},
  354. isHidden: false,
  355. overlays: [MetricChartOverlayType.SAMPLES, MetricChartOverlayType.RELEASES],
  356. },
  357. ]
  358. );
  359. });
  360. it('adds missing ids', () => {
  361. function widgetWithId<T extends number | undefined>(id: T) {
  362. return {
  363. id,
  364. type: MetricExpressionType.QUERY as const,
  365. mri: 'd:transactions/duration@millisecond' as const,
  366. op: 'sum' as const,
  367. query: 'test:query',
  368. groupBy: ['dist'],
  369. displayType: MetricDisplayType.LINE,
  370. focusedSeries: [{id: 'default', groupBy: {dist: 'default'}}],
  371. powerUserMode: true,
  372. sort: {name: 'avg' as const, order: 'desc' as const},
  373. isHidden: false,
  374. overlays: [MetricChartOverlayType.SAMPLES],
  375. };
  376. }
  377. testParsing(
  378. // INPUT
  379. [
  380. widgetWithId(0),
  381. widgetWithId(undefined),
  382. widgetWithId(2),
  383. {
  384. // Invalid widget
  385. },
  386. widgetWithId(undefined),
  387. widgetWithId(3),
  388. ],
  389. // RESULT
  390. [
  391. widgetWithId(0),
  392. widgetWithId(1),
  393. widgetWithId(2),
  394. widgetWithId(4),
  395. widgetWithId(3),
  396. ]
  397. );
  398. });
  399. it('resets the id of a single widget to 0', () => {
  400. testParsing(
  401. // INPUT
  402. [
  403. {
  404. id: 5,
  405. type: MetricExpressionType.QUERY,
  406. mri: 'd:transactions/duration@millisecond',
  407. op: 'sum',
  408. query: 'test:query',
  409. groupBy: ['dist'],
  410. displayType: 'line',
  411. focusedSeries: [{id: 'default', groupBy: {dist: 'default'}}],
  412. powerUserMode: true,
  413. sort: {name: 'avg', order: 'desc'},
  414. isHidden: false,
  415. },
  416. ],
  417. // RESULT
  418. [
  419. {
  420. id: 0,
  421. type: MetricExpressionType.QUERY,
  422. mri: 'd:transactions/duration@millisecond',
  423. op: 'sum',
  424. query: 'test:query',
  425. groupBy: ['dist'],
  426. displayType: MetricDisplayType.LINE,
  427. focusedSeries: [{id: 'default', groupBy: {dist: 'default'}}],
  428. powerUserMode: true,
  429. sort: {name: 'avg', order: 'desc'},
  430. isHidden: false,
  431. overlays: [MetricChartOverlayType.SAMPLES],
  432. },
  433. ]
  434. );
  435. });
  436. });