columnEditModal.spec.jsx 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754
  1. import {enforceActOnUseLegacyStoreHook, mountWithTheme} from 'sentry-test/enzyme';
  2. import {initializeOrg} from 'sentry-test/initializeOrg';
  3. import {render, screen, userEvent} from 'sentry-test/reactTestingLibrary';
  4. import {changeInputValue, openMenu, selectByLabel} from 'sentry-test/select-new';
  5. import TagStore from 'sentry/stores/tagStore';
  6. import ColumnEditModal from 'sentry/views/eventsV2/table/columnEditModal';
  7. const stubEl = props => <div>{props.children}</div>;
  8. function mountModal({columns, onApply, customMeasurements}, initialData) {
  9. return mountWithTheme(
  10. <ColumnEditModal
  11. Header={stubEl}
  12. Footer={stubEl}
  13. Body={stubEl}
  14. organization={initialData.organization}
  15. columns={columns}
  16. onApply={onApply}
  17. closeModal={() => void 0}
  18. customMeasurements={customMeasurements}
  19. />,
  20. initialData.routerContext
  21. );
  22. }
  23. describe('EventsV2 -> ColumnEditModal', function () {
  24. enforceActOnUseLegacyStoreHook();
  25. beforeEach(() => {
  26. TagStore.reset();
  27. TagStore.loadTagsSuccess([
  28. {name: 'browser.name', key: 'browser.name', count: 1},
  29. {name: 'custom-field', key: 'custom-field', count: 1},
  30. {name: 'user', key: 'user', count: 1},
  31. ]);
  32. });
  33. const initialData = initializeOrg({
  34. organization: {
  35. features: ['performance-view', 'dashboards-mep'],
  36. },
  37. });
  38. const columns = [
  39. {
  40. kind: 'field',
  41. field: 'event.type',
  42. },
  43. {
  44. kind: 'field',
  45. field: 'browser.name',
  46. },
  47. {
  48. kind: 'function',
  49. function: ['count', 'id'],
  50. },
  51. {
  52. kind: 'function',
  53. function: ['count_unique', 'title'],
  54. },
  55. {
  56. kind: 'function',
  57. function: ['p95', ''],
  58. },
  59. {
  60. kind: 'field',
  61. field: 'issue.id',
  62. },
  63. {
  64. kind: 'function',
  65. function: ['count_unique', 'issue.id'],
  66. },
  67. ];
  68. describe('basic rendering', function () {
  69. const wrapper = mountModal(
  70. {
  71. columns,
  72. onApply: () => void 0,
  73. },
  74. initialData
  75. );
  76. it('renders fields and basic controls', function () {
  77. // Should have fields equal to the columns.
  78. expect(wrapper.find('QueryField')).toHaveLength(columns.length);
  79. expect(wrapper.find('button[aria-label="Apply"]')).toHaveLength(1);
  80. expect(wrapper.find('button[aria-label="Add a Column"]')).toHaveLength(1);
  81. });
  82. it('renders delete and grab buttons', function () {
  83. expect(
  84. wrapper.find('RowContainer button[aria-label="Remove column"]').length
  85. ).toEqual(columns.length);
  86. expect(
  87. wrapper.find('RowContainer button[aria-label="Drag to reorder"]').length
  88. ).toEqual(columns.length);
  89. });
  90. });
  91. describe('rendering unknown fields', function () {
  92. const wrapper = mountModal(
  93. {
  94. columns: [
  95. {kind: 'function', function: ['count_unique', 'user-defined']},
  96. {kind: 'field', field: 'user-def'},
  97. ],
  98. onApply: () => void 0,
  99. },
  100. initialData
  101. );
  102. it('renders unknown fields in field and field parameter controls', function () {
  103. const funcRow = wrapper.find('QueryField').first();
  104. expect(
  105. funcRow.find('SelectControl[name="field"] [data-test-id="label"]').text()
  106. ).toBe('count_unique(\u2026)');
  107. expect(
  108. funcRow
  109. .find('SelectControl[name="parameter"] SingleValue span[data-test-id="label"]')
  110. .text()
  111. ).toBe('user-defined');
  112. const fieldRow = wrapper.find('QueryField').last();
  113. expect(
  114. fieldRow.find('SelectControl[name="field"] span[data-test-id="label"]').text()
  115. ).toBe('user-def');
  116. expect(fieldRow.find('SelectControl[name="field"] Tag')).toHaveLength(1);
  117. expect(fieldRow.find('BlankSpace')).toHaveLength(1);
  118. });
  119. });
  120. describe('rendering tags that overlap fields & functions', function () {
  121. const wrapper = mountModal(
  122. {
  123. columns: [
  124. {kind: 'field', field: 'tags[project]'},
  125. {kind: 'field', field: 'tags[count]'},
  126. ],
  127. onApply: () => void 0,
  128. },
  129. initialData
  130. );
  131. beforeEach(() => {
  132. TagStore.reset();
  133. TagStore.loadTagsSuccess([
  134. {name: 'project', key: 'project', count: 1},
  135. {name: 'count', key: 'count', count: 1},
  136. ]);
  137. });
  138. it('selects tag expressions that overlap fields', function () {
  139. const funcRow = wrapper.find('QueryField').first();
  140. expect(
  141. funcRow.find('SelectControl[name="field"] span[data-test-id="label"]').text()
  142. ).toBe('project');
  143. expect(funcRow.find('SelectControl[name="field"] Tag')).toHaveLength(1);
  144. });
  145. it('selects tag expressions that overlap functions', function () {
  146. const funcRow = wrapper.find('QueryField').last();
  147. expect(
  148. funcRow.find('SelectControl[name="field"] span[data-test-id="label"]').text()
  149. ).toBe('count');
  150. expect(funcRow.find('SelectControl[name="field"] Tag')).toHaveLength(1);
  151. });
  152. });
  153. describe('rendering functions', function () {
  154. const wrapper = mountModal(
  155. {
  156. columns: [
  157. {kind: 'function', function: ['count', 'id']},
  158. {kind: 'function', function: ['count_unique', 'title']},
  159. {kind: 'function', function: ['percentile', 'transaction.duration', '0.66']},
  160. ],
  161. onApply: () => void 0,
  162. },
  163. initialData
  164. );
  165. it('renders three columns when needed', function () {
  166. const countRow = wrapper.find('QueryField').first();
  167. // Has a select and 2 disabled inputs
  168. expect(countRow.find('SelectControl')).toHaveLength(1);
  169. expect(countRow.find('BlankSpace')).toHaveLength(2);
  170. const percentileRow = wrapper.find('QueryField').last();
  171. // two select fields, and one number input.
  172. expect(percentileRow.find('SelectControl')).toHaveLength(2);
  173. expect(percentileRow.find('BlankSpace')).toHaveLength(0);
  174. expect(percentileRow.find('StyledInput[inputMode="numeric"]')).toHaveLength(1);
  175. });
  176. });
  177. describe('function & column selection', function () {
  178. let onApply, wrapper;
  179. beforeEach(function () {
  180. onApply = jest.fn();
  181. wrapper = mountModal(
  182. {
  183. columns: [columns[0]],
  184. onApply,
  185. },
  186. initialData
  187. );
  188. });
  189. it('restricts column choices', function () {
  190. selectByLabel(wrapper, 'avg(\u2026)', {name: 'field', at: 0, control: true});
  191. openMenu(wrapper, {name: 'parameter', at: 0, control: true});
  192. const options = wrapper
  193. .find('QueryField SelectControl[name="parameter"] Option')
  194. .map(option => option.props().label);
  195. expect(options).not.toContain('title');
  196. expect(options).toContain('transaction.duration');
  197. });
  198. it('shows no options for parameterless functions', function () {
  199. selectByLabel(wrapper, 'last_seen()', {name: 'field', at: 0, control: true});
  200. expect(wrapper.find('QueryField BlankSpace')).toHaveLength(1);
  201. });
  202. it('shows additional inputs for multi-parameter functions', function () {
  203. selectByLabel(wrapper, 'percentile(\u2026)', {name: 'field', at: 0, control: true});
  204. // Parameter select should display and use the default value.
  205. const field = wrapper.find('QueryField SelectControl[name="parameter"]');
  206. expect(field.find('SingleValue span[data-test-id="label"]').text()).toBe(
  207. 'transaction.duration'
  208. );
  209. // Input should show and have default value.
  210. const refinement = wrapper.find('QueryField input[inputMode="numeric"]');
  211. expect(refinement.props().value).toBe('0.5');
  212. });
  213. it('handles scalar field parameters', function () {
  214. selectByLabel(wrapper, 'apdex(\u2026)', {name: 'field', at: 0, control: true});
  215. // Parameter select should display and use the default value.
  216. const field = wrapper.find('QueryField input[name="refinement"]');
  217. expect(field.props().value).toBe('300');
  218. // Trigger a blur and make sure the column is not wrong.
  219. field.simulate('blur');
  220. // Apply the changes so we can see the new columns.
  221. wrapper.find('Button[priority="primary"]').simulate('click');
  222. expect(onApply).toHaveBeenCalledWith([
  223. {kind: 'function', function: ['apdex', '300', undefined, undefined]},
  224. ]);
  225. });
  226. it('handles parameter overrides', function () {
  227. selectByLabel(wrapper, 'apdex(\u2026)', {name: 'field', at: 0, control: true});
  228. // Parameter select should display and use the default value.
  229. const field = wrapper.find('QueryField input[name="refinement"]');
  230. expect(field.props().value).toBe('300');
  231. expect(field.prop('placeholder')).toBe(undefined);
  232. // Trigger a blur and make sure the column is not wrong.
  233. field.simulate('blur');
  234. });
  235. it('clears unused parameters', function () {
  236. // Choose percentile, then apdex which has fewer parameters and different types.
  237. selectByLabel(wrapper, 'percentile(\u2026)', {name: 'field', at: 0, control: true});
  238. selectByLabel(wrapper, 'apdex(\u2026)', {name: 'field', at: 0, control: true});
  239. // Apply the changes so we can see the new columns.
  240. wrapper.find('Button[priority="primary"]').simulate('click');
  241. expect(onApply).toHaveBeenCalledWith([
  242. {kind: 'function', function: ['apdex', '300', undefined, undefined]},
  243. ]);
  244. });
  245. it('clears all unused parameters', function () {
  246. // Choose percentile, then failure_rate which has no parameters.
  247. selectByLabel(wrapper, 'percentile(\u2026)', {name: 'field', at: 0, control: true});
  248. selectByLabel(wrapper, 'failure_rate()', {name: 'field', at: 0, control: true});
  249. // Apply the changes so we can see the new columns.
  250. wrapper.find('Button[priority="primary"]').simulate('click');
  251. expect(onApply).toHaveBeenCalledWith([
  252. {kind: 'function', function: ['failure_rate', '', undefined, undefined]},
  253. ]);
  254. });
  255. it('clears all unused parameters with count_if to two parameter function', function () {
  256. // Choose percentile, then failure_rate which has no parameters.
  257. selectByLabel(wrapper, 'count_if(\u2026)', {name: 'field', at: 0, control: true});
  258. selectByLabel(wrapper, 'user', {name: 'parameter', at: 0, control: true});
  259. selectByLabel(wrapper, 'count_miserable(\u2026)', {
  260. name: 'field',
  261. at: 0,
  262. control: true,
  263. });
  264. // Apply the changes so we can see the new columns.
  265. wrapper.find('Button[priority="primary"]').simulate('click');
  266. expect(onApply).toHaveBeenCalledWith([
  267. {kind: 'function', function: ['count_miserable', 'user', '300', undefined]},
  268. ]);
  269. });
  270. it('clears all unused parameters with count_if to one parameter function', function () {
  271. // Choose percentile, then failure_rate which has no parameters.
  272. selectByLabel(wrapper, 'count_if(\u2026)', {name: 'field', at: 0, control: true});
  273. selectByLabel(wrapper, 'user', {name: 'parameter', at: 0, control: true});
  274. selectByLabel(wrapper, 'count_unique(\u2026)', {
  275. name: 'field',
  276. at: 0,
  277. control: true,
  278. });
  279. // Apply the changes so we can see the new columns.
  280. wrapper.find('Button[priority="primary"]').simulate('click');
  281. expect(onApply).toHaveBeenCalledWith([
  282. {kind: 'function', function: ['count_unique', 'user', undefined, undefined]},
  283. ]);
  284. });
  285. it('clears all unused parameters with count_if to parameterless function', function () {
  286. // Choose percentile, then failure_rate which has no parameters.
  287. selectByLabel(wrapper, 'count_if(\u2026)', {name: 'field', at: 0, control: true});
  288. selectByLabel(wrapper, 'count()', {
  289. name: 'field',
  290. at: 0,
  291. control: true,
  292. });
  293. // Apply the changes so we can see the new columns.
  294. wrapper.find('Button[priority="primary"]').simulate('click');
  295. expect(onApply).toHaveBeenCalledWith([
  296. {kind: 'function', function: ['count', '', undefined, undefined]},
  297. ]);
  298. });
  299. it('updates equation errors when they change', function () {
  300. const newWrapper = mountModal(
  301. {
  302. columns: [
  303. {
  304. kind: 'equation',
  305. field: '1 / 0',
  306. },
  307. ],
  308. onApply,
  309. },
  310. initialData
  311. );
  312. expect(newWrapper.find('QueryField ArithmeticError')).toHaveLength(1);
  313. expect(newWrapper.find('QueryField ArithmeticError').prop('title')).toBe(
  314. 'Division by 0 is not allowed'
  315. );
  316. const field = newWrapper.find('QueryField input[type="text"]');
  317. changeInputValue(field, '1+1+1+1+1+1+1+1+1+1+1+1');
  318. newWrapper.update();
  319. field.simulate('blur');
  320. expect(newWrapper.find('QueryField ArithmeticError').prop('title')).toBe(
  321. 'Maximum operators exceeded'
  322. );
  323. });
  324. it('resets required field to previous value if cleared', function () {
  325. const initialColumnVal = '0.6';
  326. const newWrapper = mountModal(
  327. {
  328. columns: [
  329. {
  330. kind: 'function',
  331. function: [
  332. 'percentile',
  333. 'transaction.duration',
  334. initialColumnVal,
  335. undefined,
  336. ],
  337. },
  338. ],
  339. onApply,
  340. },
  341. initialData
  342. );
  343. const field = newWrapper.find('QueryField input[name="refinement"]');
  344. changeInputValue(field, '');
  345. newWrapper.update();
  346. field.simulate('blur');
  347. expect(newWrapper.find('QueryField input[name="refinement"]').prop('value')).toBe(
  348. initialColumnVal
  349. );
  350. newWrapper.find('Button[priority="primary"]').simulate('click');
  351. expect(onApply).toHaveBeenCalledWith([
  352. {
  353. kind: 'function',
  354. function: ['percentile', 'transaction.duration', initialColumnVal, undefined],
  355. },
  356. ]);
  357. });
  358. });
  359. describe('equation automatic update', function () {
  360. let onApply;
  361. beforeEach(function () {
  362. onApply = jest.fn();
  363. });
  364. it('update simple equation columns when they change', function () {
  365. const newWrapper = mountModal(
  366. {
  367. columns: [
  368. {
  369. kind: 'function',
  370. function: ['count_unique', 'user'],
  371. },
  372. {
  373. kind: 'function',
  374. function: ['p95', ''],
  375. },
  376. {
  377. kind: 'equation',
  378. field: '(p95() / count_unique(user) ) * 100',
  379. },
  380. ],
  381. onApply,
  382. },
  383. initialData
  384. );
  385. selectByLabel(newWrapper, 'count_if(\u2026)', {
  386. name: 'field',
  387. at: 0,
  388. control: true,
  389. });
  390. // Apply the changes so we can see the new columns.
  391. newWrapper.find('Button[priority="primary"]').simulate('click');
  392. expect(onApply).toHaveBeenCalledWith([
  393. {kind: 'function', function: ['count_if', 'user', 'equals', '300']},
  394. {kind: 'function', function: ['p95', '']},
  395. {kind: 'equation', field: '(p95() / count_if(user,equals,300) ) * 100'},
  396. ]);
  397. });
  398. it('update equation with repeated columns when they change', function () {
  399. const newWrapper = mountModal(
  400. {
  401. columns: [
  402. {
  403. kind: 'function',
  404. function: ['count_unique', 'user'],
  405. },
  406. {
  407. kind: 'equation',
  408. field:
  409. 'count_unique(user) + (count_unique(user) - count_unique(user)) * 5',
  410. },
  411. ],
  412. onApply,
  413. },
  414. initialData
  415. );
  416. selectByLabel(newWrapper, 'count()', {
  417. name: 'field',
  418. at: 0,
  419. control: true,
  420. });
  421. // Apply the changes so we can see the new columns.
  422. newWrapper.find('Button[priority="primary"]').simulate('click');
  423. expect(onApply).toHaveBeenCalledWith([
  424. {kind: 'function', function: ['count', '', undefined, undefined]},
  425. {kind: 'equation', field: 'count() + (count() - count()) * 5'},
  426. ]);
  427. });
  428. it('handles equations with duplicate fields', function () {
  429. const newWrapper = mountModal(
  430. {
  431. columns: [
  432. {
  433. kind: 'field',
  434. field: 'spans.db',
  435. },
  436. {
  437. kind: 'field',
  438. field: 'spans.db',
  439. },
  440. {
  441. kind: 'equation',
  442. field: 'spans.db - spans.db',
  443. },
  444. ],
  445. onApply,
  446. },
  447. initialData
  448. );
  449. selectByLabel(newWrapper, 'count()', {
  450. name: 'field',
  451. at: 0,
  452. control: true,
  453. });
  454. // Apply the changes so we can see the new columns.
  455. newWrapper.find('Button[priority="primary"]').simulate('click');
  456. // Because spans.db is still a selected column it isn't swapped
  457. expect(onApply).toHaveBeenCalledWith([
  458. {kind: 'function', function: ['count', '', undefined, undefined]},
  459. {kind: 'field', field: 'spans.db'},
  460. {kind: 'equation', field: 'spans.db - spans.db'},
  461. ]);
  462. });
  463. it('handles equations with duplicate functions', function () {
  464. const newWrapper = mountModal(
  465. {
  466. columns: [
  467. {
  468. kind: 'function',
  469. function: ['count', '', undefined, undefined],
  470. },
  471. {
  472. kind: 'function',
  473. function: ['count', '', undefined, undefined],
  474. },
  475. {
  476. kind: 'equation',
  477. field: 'count() - count()',
  478. },
  479. ],
  480. onApply,
  481. },
  482. initialData
  483. );
  484. selectByLabel(newWrapper, 'count_unique(\u2026)', {
  485. name: 'field',
  486. at: 0,
  487. control: true,
  488. });
  489. // Apply the changes so we can see the new columns.
  490. newWrapper.find('Button[priority="primary"]').simulate('click');
  491. expect(onApply).toHaveBeenCalledWith([
  492. {kind: 'function', function: ['count_unique', 'user', undefined, undefined]},
  493. {kind: 'function', function: ['count', '', undefined, undefined]},
  494. {kind: 'equation', field: 'count() - count()'},
  495. ]);
  496. });
  497. it('handles incomplete equations', function () {
  498. const newWrapper = mountModal(
  499. {
  500. columns: [
  501. {
  502. kind: 'function',
  503. function: ['count', '', undefined, undefined],
  504. },
  505. {
  506. kind: 'equation',
  507. field: 'count() - count() arst count() ',
  508. },
  509. ],
  510. onApply,
  511. },
  512. initialData
  513. );
  514. expect(newWrapper.find('QueryField ArithmeticError')).toHaveLength(1);
  515. selectByLabel(newWrapper, 'count_unique(\u2026)', {
  516. name: 'field',
  517. at: 0,
  518. control: true,
  519. });
  520. // Apply the changes so we can see the new columns.
  521. newWrapper.find('Button[priority="primary"]').simulate('click');
  522. // With the way the parser works only tokens up to the error will be updated
  523. expect(onApply).toHaveBeenCalledWith([
  524. {kind: 'function', function: ['count_unique', 'user', undefined, undefined]},
  525. {
  526. kind: 'equation',
  527. field: 'count_unique(user) - count_unique(user) arst count() ',
  528. },
  529. ]);
  530. });
  531. });
  532. describe('adding rows', function () {
  533. const wrapper = mountModal(
  534. {
  535. columns: [columns[0]],
  536. onApply: () => void 0,
  537. },
  538. initialData
  539. );
  540. it('allows rows to be added, but only up to 20', function () {
  541. for (let i = 2; i <= 20; i++) {
  542. wrapper.find('button[aria-label="Add a Column"]').simulate('click');
  543. expect(wrapper.find('QueryField')).toHaveLength(i);
  544. }
  545. expect(
  546. wrapper.find('button[aria-label="Add a Column"]').prop('aria-disabled')
  547. ).toBe(true);
  548. });
  549. });
  550. describe('removing rows', function () {
  551. const wrapper = mountModal(
  552. {
  553. columns: [columns[0], columns[1]],
  554. onApply: () => void 0,
  555. },
  556. initialData
  557. );
  558. it('allows rows to be removed, but not the last one', function () {
  559. expect(wrapper.find('QueryField')).toHaveLength(2);
  560. wrapper
  561. .find('RowContainer button[aria-label="Remove column"]')
  562. .first()
  563. .simulate('click');
  564. expect(wrapper.find('QueryField')).toHaveLength(1);
  565. // Last row cannot be removed or dragged.
  566. expect(
  567. wrapper.find('RowContainer button[aria-label="Remove column"]')
  568. ).toHaveLength(0);
  569. expect(
  570. wrapper.find('RowContainer button[aria-label="Drag to reorder"]')
  571. ).toHaveLength(0);
  572. });
  573. it('does not count equations towards the count of rows', function () {
  574. const newWrapper = mountModal(
  575. {
  576. columns: [
  577. columns[0],
  578. columns[1],
  579. {
  580. kind: 'equation',
  581. field: '5 + 5',
  582. },
  583. ],
  584. onApply: () => void 0,
  585. },
  586. initialData
  587. );
  588. expect(newWrapper.find('QueryField')).toHaveLength(3);
  589. newWrapper
  590. .find('RowContainer button[aria-label="Remove column"]')
  591. .first()
  592. .simulate('click');
  593. expect(newWrapper.find('QueryField')).toHaveLength(2);
  594. // Can still remove the equation
  595. expect(
  596. newWrapper.find('RowContainer button[aria-label="Remove column"]')
  597. ).toHaveLength(1);
  598. // And both are draggable
  599. expect(
  600. newWrapper.find('RowContainer button[aria-label="Drag to reorder"]')
  601. ).toHaveLength(2);
  602. });
  603. it('handles equations being deleted', function () {
  604. const newWrapper = mountModal(
  605. {
  606. columns: [
  607. {
  608. kind: 'equation',
  609. field: '1 / 0',
  610. },
  611. columns[0],
  612. columns[1],
  613. ],
  614. onApply: () => void 0,
  615. },
  616. initialData
  617. );
  618. expect(newWrapper.find('QueryField ArithmeticError')).toHaveLength(1);
  619. expect(newWrapper.find('QueryField')).toHaveLength(3);
  620. newWrapper
  621. .find('RowContainer button[aria-label="Remove column"]')
  622. .first()
  623. .simulate('click');
  624. expect(newWrapper.find('QueryField')).toHaveLength(2);
  625. expect(newWrapper.find('ArithmeticError')).toHaveLength(0);
  626. });
  627. });
  628. describe('apply action', function () {
  629. const onApply = jest.fn();
  630. const wrapper = mountModal(
  631. {
  632. columns: [columns[0], columns[1]],
  633. onApply,
  634. },
  635. initialData
  636. );
  637. it('reflects added and removed columns', function () {
  638. // Remove a column, then add a blank one an select a value in it.
  639. wrapper.find('button[aria-label="Remove column"]').first().simulate('click');
  640. wrapper.find('button[aria-label="Add a Column"]').simulate('click');
  641. wrapper.update();
  642. selectByLabel(wrapper, 'title', {name: 'field', at: 1, control: true});
  643. wrapper.find('button[aria-label="Apply"]').simulate('click');
  644. expect(onApply).toHaveBeenCalledWith([columns[1], {kind: 'field', field: 'title'}]);
  645. });
  646. });
  647. describe('custom performance metrics', function () {
  648. it('allows selecting custom performance metrics in dropdown', function () {
  649. render(
  650. <ColumnEditModal
  651. Header={stubEl}
  652. Footer={stubEl}
  653. Body={stubEl}
  654. organization={initialData.organization}
  655. columns={[columns[0]]}
  656. onApply={() => undefined}
  657. closeModal={() => undefined}
  658. customMeasurements={{
  659. 'measurements.custom.kibibyte': {
  660. key: 'measurements.custom.kibibyte',
  661. name: 'measurements.custom.kibibyte',
  662. functions: ['p99'],
  663. },
  664. 'measurements.custom.minute': {
  665. key: 'measurements.custom.minute',
  666. name: 'measurements.custom.minute',
  667. functions: ['p99'],
  668. },
  669. 'measurements.custom.ratio': {
  670. key: 'measurements.custom.ratio',
  671. name: 'measurements.custom.ratio',
  672. functions: ['p99'],
  673. },
  674. }}
  675. />
  676. );
  677. expect(screen.getByText('event.type')).toBeInTheDocument();
  678. userEvent.click(screen.getByText('event.type'));
  679. userEvent.type(screen.getAllByText('event.type')[0], 'custom');
  680. expect(screen.getByText('measurements.custom.kibibyte')).toBeInTheDocument();
  681. });
  682. });
  683. });