parseMetricWidgetsQueryParam.spec.tsx 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399
  1. import {emptyMetricsQueryWidget} from 'sentry/utils/metrics/constants';
  2. import {
  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('parseMetricWidgetQueryParam', () => {
  12. const defaultState = [{...emptyMetricsQueryWidget, id: 0}];
  13. it('returns default widget for invalid param', () => {
  14. testParsing(undefined, defaultState);
  15. testParsing({}, defaultState);
  16. testParsing(true, defaultState);
  17. testParsing(2, defaultState);
  18. testParsing('', defaultState);
  19. testParsing('test', defaultState);
  20. // empty array is not valid
  21. testParsing([], defaultState);
  22. });
  23. it('returns a single widget', () => {
  24. testParsing(
  25. [
  26. // INPUT
  27. {
  28. id: 0,
  29. type: MetricExpressionType.QUERY,
  30. mri: 'd:transactions/duration@millisecond',
  31. op: 'sum',
  32. query: 'test:query',
  33. groupBy: ['dist'],
  34. displayType: 'line',
  35. focusedSeries: [{id: 'default', groupBy: {dist: 'default'}}],
  36. powerUserMode: true,
  37. sort: {order: 'asc'},
  38. isHidden: true,
  39. },
  40. ],
  41. // RESULT
  42. [
  43. {
  44. id: 0,
  45. type: MetricExpressionType.QUERY,
  46. mri: 'd:transactions/duration@millisecond',
  47. op: 'sum',
  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. },
  56. ]
  57. );
  58. });
  59. it('returns multiple widgets', () => {
  60. testParsing(
  61. // INPUT
  62. [
  63. {
  64. id: 0,
  65. type: MetricExpressionType.QUERY,
  66. mri: 'd:transactions/duration@millisecond',
  67. op: 'sum',
  68. query: 'test:query',
  69. groupBy: ['dist'],
  70. displayType: 'line',
  71. focusedSeries: [{id: 'default', groupBy: {dist: 'default'}}],
  72. powerUserMode: true,
  73. sort: {name: 'avg', order: 'desc'},
  74. isHidden: true,
  75. },
  76. {
  77. id: 0,
  78. type: MetricExpressionType.EQUATION,
  79. formula: 'a + b',
  80. displayType: 'line',
  81. sort: {name: 'avg', order: 'desc'},
  82. focusedSeries: [],
  83. isHidden: true,
  84. },
  85. {
  86. id: 1,
  87. type: MetricExpressionType.QUERY,
  88. mri: 'd:custom/sentry.event_manager.save@second',
  89. op: 'avg',
  90. query: '',
  91. groupBy: ['event_type'],
  92. displayType: 'line',
  93. powerUserMode: false,
  94. focusedSeries: [{id: 'default', groupBy: {event_type: 'default'}}],
  95. sort: {name: 'sum', order: 'asc'},
  96. isHidden: false,
  97. },
  98. ],
  99. // RESULT
  100. [
  101. {
  102. id: 0,
  103. type: MetricExpressionType.QUERY,
  104. mri: 'd:transactions/duration@millisecond',
  105. op: 'sum',
  106. query: 'test:query',
  107. groupBy: ['dist'],
  108. displayType: MetricDisplayType.LINE,
  109. focusedSeries: [{id: 'default', groupBy: {dist: 'default'}}],
  110. powerUserMode: true,
  111. sort: {name: 'avg', order: 'desc'},
  112. isHidden: true,
  113. },
  114. {
  115. id: 1,
  116. type: MetricExpressionType.QUERY,
  117. mri: 'd:custom/sentry.event_manager.save@second',
  118. op: 'avg',
  119. query: '',
  120. groupBy: ['event_type'],
  121. displayType: MetricDisplayType.LINE,
  122. powerUserMode: false,
  123. focusedSeries: [{id: 'default', groupBy: {event_type: 'default'}}],
  124. sort: {name: 'sum', order: 'asc'},
  125. isHidden: false,
  126. },
  127. // Formulas should always be at the end
  128. {
  129. id: 0,
  130. type: MetricExpressionType.EQUATION,
  131. formula: 'a + b',
  132. displayType: MetricDisplayType.LINE,
  133. sort: {name: 'avg', order: 'desc'},
  134. focusedSeries: [],
  135. isHidden: true,
  136. },
  137. ]
  138. );
  139. });
  140. it('falls back to defaults', () => {
  141. // Missing values
  142. testParsing(
  143. // INPUT
  144. [
  145. {
  146. id: 0,
  147. type: MetricExpressionType.QUERY,
  148. mri: 'd:transactions/duration@millisecond',
  149. },
  150. {
  151. type: MetricExpressionType.EQUATION,
  152. formula: 'a * 2',
  153. },
  154. ],
  155. // RESULT
  156. [
  157. {
  158. id: 0,
  159. type: MetricExpressionType.QUERY,
  160. mri: 'd:transactions/duration@millisecond',
  161. op: 'avg',
  162. query: '',
  163. groupBy: [],
  164. displayType: MetricDisplayType.LINE,
  165. focusedSeries: [],
  166. powerUserMode: false,
  167. sort: {name: undefined, order: 'asc'},
  168. isHidden: false,
  169. },
  170. {
  171. id: 0,
  172. type: MetricExpressionType.EQUATION,
  173. formula: 'a * 2',
  174. displayType: MetricDisplayType.LINE,
  175. focusedSeries: [],
  176. sort: {name: undefined, order: 'asc'},
  177. isHidden: false,
  178. },
  179. ]
  180. );
  181. // Invalid values
  182. testParsing(
  183. // INPUT
  184. [
  185. {
  186. id: 'invalid',
  187. type: 123,
  188. mri: 'd:transactions/duration@millisecond',
  189. op: 1,
  190. query: 12,
  191. groupBy: true,
  192. displayType: 'aasfcsdf',
  193. focusedSeries: {},
  194. powerUserMode: 1,
  195. sort: {name: 1, order: 'invalid'},
  196. isHidden: 'foo',
  197. },
  198. ],
  199. // RESULT
  200. [
  201. {
  202. id: 0,
  203. type: MetricExpressionType.QUERY,
  204. mri: 'd:transactions/duration@millisecond',
  205. op: 'avg',
  206. query: '',
  207. groupBy: [],
  208. displayType: MetricDisplayType.LINE,
  209. focusedSeries: [],
  210. powerUserMode: false,
  211. sort: {name: undefined, order: 'asc'},
  212. isHidden: false,
  213. },
  214. ]
  215. );
  216. });
  217. it('ignores invalid widgets', () => {
  218. testParsing(
  219. // INPUT
  220. [
  221. {
  222. id: 0,
  223. mri: 'd:transactions/duration@millisecond',
  224. },
  225. {
  226. // Missing MRI
  227. },
  228. {
  229. // Mallformed MRI
  230. mri: 'transactions/duration@millisecond',
  231. },
  232. {
  233. // Duplicate id
  234. id: 0,
  235. mri: 'd:transactions/duration@second',
  236. },
  237. {
  238. // Missing formula
  239. type: MetricExpressionType.EQUATION,
  240. },
  241. ],
  242. // RESULT
  243. [
  244. {
  245. id: 0,
  246. type: MetricExpressionType.QUERY,
  247. mri: 'd:transactions/duration@millisecond',
  248. op: 'avg',
  249. query: '',
  250. groupBy: [],
  251. displayType: MetricDisplayType.LINE,
  252. focusedSeries: [],
  253. powerUserMode: false,
  254. sort: {name: undefined, order: 'asc'},
  255. isHidden: false,
  256. },
  257. ]
  258. );
  259. });
  260. it('returns default widget if there is no valid widget', () => {
  261. testParsing(
  262. // INPUT
  263. [
  264. {
  265. // Missing MRI
  266. },
  267. {
  268. // Missing formula
  269. type: MetricExpressionType.EQUATION,
  270. },
  271. ],
  272. // RESULT
  273. defaultState
  274. );
  275. });
  276. it('handles missing array in array params', () => {
  277. testParsing(
  278. // INPUT
  279. [
  280. {
  281. id: 0,
  282. type: MetricExpressionType.QUERY,
  283. mri: 'd:transactions/duration@millisecond',
  284. op: 'sum',
  285. query: 'test:query',
  286. groupBy: 'dist',
  287. displayType: 'line',
  288. focusedSeries: {id: 'default', groupBy: {dist: 'default'}},
  289. powerUserMode: true,
  290. sort: {order: 'asc'},
  291. isHidden: false,
  292. },
  293. ],
  294. // RESULT
  295. [
  296. {
  297. id: 0,
  298. type: MetricExpressionType.QUERY,
  299. mri: 'd:transactions/duration@millisecond',
  300. op: 'sum',
  301. query: 'test:query',
  302. groupBy: ['dist'],
  303. displayType: MetricDisplayType.LINE,
  304. focusedSeries: [{id: 'default', groupBy: {dist: 'default'}}],
  305. powerUserMode: true,
  306. sort: {name: undefined, order: 'asc'},
  307. isHidden: false,
  308. },
  309. ]
  310. );
  311. });
  312. it('adds missing ids', () => {
  313. function widgetWithId<T extends number | undefined>(id: T) {
  314. return {
  315. id,
  316. type: MetricExpressionType.QUERY as const,
  317. mri: 'd:transactions/duration@millisecond' as const,
  318. op: 'sum' as const,
  319. query: 'test:query',
  320. groupBy: ['dist'],
  321. displayType: MetricDisplayType.LINE,
  322. focusedSeries: [{id: 'default', groupBy: {dist: 'default'}}],
  323. powerUserMode: true,
  324. sort: {name: 'avg' as const, order: 'desc' as const},
  325. isHidden: false,
  326. };
  327. }
  328. testParsing(
  329. // INPUT
  330. [
  331. widgetWithId(0),
  332. widgetWithId(undefined),
  333. widgetWithId(2),
  334. {
  335. // Invalid widget
  336. },
  337. widgetWithId(undefined),
  338. widgetWithId(3),
  339. ],
  340. // RESULT
  341. [
  342. widgetWithId(0),
  343. widgetWithId(1),
  344. widgetWithId(2),
  345. widgetWithId(4),
  346. widgetWithId(3),
  347. ]
  348. );
  349. });
  350. it('resets the id of a single widget to 0', () => {
  351. testParsing(
  352. // INPUT
  353. [
  354. {
  355. id: 5,
  356. type: MetricExpressionType.QUERY,
  357. mri: 'd:transactions/duration@millisecond',
  358. op: 'sum',
  359. query: 'test:query',
  360. groupBy: ['dist'],
  361. displayType: 'line',
  362. focusedSeries: [{id: 'default', groupBy: {dist: 'default'}}],
  363. powerUserMode: true,
  364. sort: {name: 'avg', order: 'desc'},
  365. isHidden: false,
  366. },
  367. ],
  368. // RESULT
  369. [
  370. {
  371. id: 0,
  372. type: MetricExpressionType.QUERY,
  373. mri: 'd:transactions/duration@millisecond',
  374. op: 'sum',
  375. query: 'test:query',
  376. groupBy: ['dist'],
  377. displayType: MetricDisplayType.LINE,
  378. focusedSeries: [{id: 'default', groupBy: {dist: 'default'}}],
  379. powerUserMode: true,
  380. sort: {name: 'avg', order: 'desc'},
  381. isHidden: false,
  382. },
  383. ]
  384. );
  385. });
  386. });