index.spec.tsx 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776
  1. import {OrganizationFixture} from 'sentry-fixture/organization';
  2. import {ProjectFixture} from 'sentry-fixture/project';
  3. import {RouterFixture} from 'sentry-fixture/routerFixture';
  4. import {
  5. render,
  6. screen,
  7. userEvent,
  8. waitFor,
  9. within,
  10. } from 'sentry-test/reactTestingLibrary';
  11. import {openAddToDashboardModal} from 'sentry/actionCreators/modal';
  12. import ProjectsStore from 'sentry/stores/projectsStore';
  13. import {DiscoverDatasets} from 'sentry/utils/discover/types';
  14. import {
  15. PageParamsProvider,
  16. useExploreDataset,
  17. useExploreFields,
  18. useExploreGroupBys,
  19. useExploreMode,
  20. useExplorePageParams,
  21. useExploreSortBys,
  22. useExploreVisualizes,
  23. } from 'sentry/views/explore/contexts/pageParamsContext';
  24. import {Mode} from 'sentry/views/explore/contexts/pageParamsContext/mode';
  25. import {ExploreToolbar} from 'sentry/views/explore/toolbar';
  26. import {ChartType} from 'sentry/views/insights/common/components/chart';
  27. import {SpanTagsProvider} from '../contexts/spanTagsContext';
  28. jest.mock('sentry/actionCreators/modal');
  29. describe('ExploreToolbar', function () {
  30. const organization = OrganizationFixture({
  31. features: ['alerts-eap', 'dashboards-eap', 'dashboards-edit', 'explore-multi-query'],
  32. });
  33. beforeEach(function () {
  34. // without this the `CompactSelect` component errors with a bunch of async updates
  35. jest.spyOn(console, 'error').mockImplementation();
  36. const project = ProjectFixture({
  37. id: '1',
  38. slug: 'proj-slug',
  39. organization,
  40. });
  41. ProjectsStore.loadInitialData([project]);
  42. MockApiClient.addMockResponse({
  43. url: `/organizations/${organization.slug}/spans/fields/`,
  44. method: 'GET',
  45. body: [],
  46. });
  47. });
  48. it('should not render dataset selector', function () {
  49. function Component() {
  50. return <ExploreToolbar />;
  51. }
  52. render(
  53. <PageParamsProvider>
  54. <SpanTagsProvider dataset={DiscoverDatasets.SPANS_EAP} enabled>
  55. <Component />
  56. </SpanTagsProvider>
  57. </PageParamsProvider>,
  58. {disableRouterMocks: true}
  59. );
  60. const section = screen.queryByTestId('section-dataset');
  61. expect(section).not.toBeInTheDocument();
  62. });
  63. it('allows changing datasets', async function () {
  64. let dataset: any;
  65. function Component() {
  66. dataset = useExploreDataset();
  67. return <ExploreToolbar extras={['dataset toggle']} />;
  68. }
  69. render(
  70. <PageParamsProvider>
  71. <SpanTagsProvider dataset={DiscoverDatasets.SPANS_EAP} enabled>
  72. <Component />
  73. </SpanTagsProvider>
  74. </PageParamsProvider>,
  75. {disableRouterMocks: true}
  76. );
  77. const section = screen.getByTestId('section-dataset');
  78. const eapSpans = within(section).getByRole('radio', {name: 'EAP Spans'});
  79. const rpcSpans = within(section).getByRole('radio', {name: 'EAP RPC Spans'});
  80. const indexedSpans = within(section).getByRole('radio', {name: 'Indexed Spans'});
  81. expect(eapSpans).toBeChecked();
  82. expect(rpcSpans).not.toBeChecked();
  83. expect(indexedSpans).not.toBeChecked();
  84. expect(dataset).toEqual(DiscoverDatasets.SPANS_EAP);
  85. await userEvent.click(rpcSpans);
  86. expect(eapSpans).not.toBeChecked();
  87. expect(rpcSpans).toBeChecked();
  88. expect(indexedSpans).not.toBeChecked();
  89. expect(dataset).toEqual(DiscoverDatasets.SPANS_EAP_RPC);
  90. await userEvent.click(indexedSpans);
  91. expect(eapSpans).not.toBeChecked();
  92. expect(rpcSpans).not.toBeChecked();
  93. expect(indexedSpans).toBeChecked();
  94. expect(dataset).toEqual(DiscoverDatasets.SPANS_INDEXED);
  95. });
  96. it('allows changing mode', async function () {
  97. let mode: any;
  98. function Component() {
  99. mode = useExploreMode();
  100. return <ExploreToolbar extras={['dataset toggle']} />;
  101. }
  102. render(
  103. <PageParamsProvider>
  104. <SpanTagsProvider dataset={DiscoverDatasets.SPANS_EAP} enabled>
  105. <Component />
  106. </SpanTagsProvider>
  107. </PageParamsProvider>,
  108. {disableRouterMocks: true}
  109. );
  110. const section = screen.getByTestId('section-mode');
  111. const samples = within(section).getByRole('radio', {name: 'Samples'});
  112. const aggregates = within(section).getByRole('radio', {name: 'Aggregates'});
  113. expect(samples).toBeChecked();
  114. expect(aggregates).not.toBeChecked();
  115. expect(mode).toEqual(Mode.SAMPLES);
  116. await userEvent.click(aggregates);
  117. expect(samples).not.toBeChecked();
  118. expect(aggregates).toBeChecked();
  119. expect(mode).toEqual(Mode.AGGREGATE);
  120. await userEvent.click(samples);
  121. expect(samples).toBeChecked();
  122. expect(aggregates).not.toBeChecked();
  123. expect(mode).toEqual(Mode.SAMPLES);
  124. });
  125. it('inserts group bys from aggregate mode as fields in samples mode', async function () {
  126. let fields, groupBys;
  127. function Component() {
  128. fields = useExploreFields();
  129. groupBys = useExploreGroupBys();
  130. return <ExploreToolbar extras={['dataset toggle']} />;
  131. }
  132. render(
  133. <PageParamsProvider>
  134. <SpanTagsProvider dataset={DiscoverDatasets.SPANS_EAP} enabled>
  135. <Component />
  136. </SpanTagsProvider>
  137. </PageParamsProvider>,
  138. {disableRouterMocks: true}
  139. );
  140. const section = screen.getByTestId('section-mode');
  141. const samples = within(section).getByRole('radio', {name: 'Samples'});
  142. const aggregates = within(section).getByRole('radio', {name: 'Aggregates'});
  143. expect(fields).toEqual([
  144. 'id',
  145. 'span.op',
  146. 'span.description',
  147. 'span.duration',
  148. 'transaction',
  149. 'timestamp',
  150. ]); // default
  151. // Add a group by, and leave one unselected
  152. await userEvent.click(aggregates);
  153. const groupBy = screen.getByTestId('section-group-by');
  154. await userEvent.click(within(groupBy).getByRole('button', {name: 'span.op'}));
  155. await userEvent.click(within(groupBy).getByRole('option', {name: 'release'}));
  156. expect(groupBys).toEqual(['release']);
  157. await userEvent.click(within(groupBy).getByRole('button', {name: 'Add Group'}));
  158. expect(groupBys).toEqual(['release', '']);
  159. await userEvent.click(samples);
  160. expect(fields).toEqual([
  161. 'id',
  162. 'span.op',
  163. 'span.description',
  164. 'span.duration',
  165. 'transaction',
  166. 'timestamp',
  167. 'release',
  168. ]);
  169. });
  170. it('allows changing visualizes', async function () {
  171. let fields, visualizes: any;
  172. function Component() {
  173. fields = useExploreFields();
  174. visualizes = useExploreVisualizes();
  175. return <ExploreToolbar />;
  176. }
  177. render(
  178. <PageParamsProvider>
  179. <SpanTagsProvider dataset={DiscoverDatasets.SPANS_EAP} enabled>
  180. <Component />
  181. </SpanTagsProvider>
  182. </PageParamsProvider>,
  183. {disableRouterMocks: true}
  184. );
  185. const section = screen.getByTestId('section-visualizes');
  186. // this is the default
  187. expect(visualizes).toEqual([
  188. {
  189. chartType: ChartType.LINE,
  190. label: 'A',
  191. yAxes: ['avg(span.duration)'],
  192. },
  193. ]);
  194. expect(fields).toEqual([
  195. 'id',
  196. 'span.op',
  197. 'span.description',
  198. 'span.duration',
  199. 'transaction',
  200. 'timestamp',
  201. ]); // default
  202. // try changing the field
  203. await userEvent.click(within(section).getByRole('button', {name: 'span.duration'}));
  204. await userEvent.click(within(section).getByRole('option', {name: 'span.self_time'}));
  205. expect(visualizes).toEqual([
  206. {
  207. chartType: ChartType.LINE,
  208. label: 'A',
  209. yAxes: ['avg(span.self_time)'],
  210. },
  211. ]);
  212. expect(fields).toEqual([
  213. 'id',
  214. 'span.op',
  215. 'span.description',
  216. 'span.duration',
  217. 'transaction',
  218. 'timestamp',
  219. 'span.self_time',
  220. ]);
  221. // try changing the aggregate
  222. await userEvent.click(within(section).getByRole('button', {name: 'avg'}));
  223. await userEvent.click(within(section).getByRole('option', {name: 'count'}));
  224. expect(visualizes).toEqual([
  225. {
  226. chartType: ChartType.LINE,
  227. label: 'A',
  228. yAxes: ['count(span.self_time)'],
  229. },
  230. ]);
  231. // try adding an overlay
  232. await userEvent.click(within(section).getByRole('button', {name: 'Add Series'}));
  233. await userEvent.click(within(section).getByRole('button', {name: 'span.duration'}));
  234. await userEvent.click(within(section).getByRole('option', {name: 'span.self_time'}));
  235. expect(visualizes).toEqual([
  236. {
  237. chartType: ChartType.LINE,
  238. label: 'A',
  239. yAxes: ['count(span.self_time)', 'avg(span.self_time)'],
  240. },
  241. ]);
  242. // try adding a new chart
  243. await userEvent.click(within(section).getByRole('button', {name: 'Add Chart'}));
  244. expect(visualizes).toEqual([
  245. {
  246. chartType: ChartType.LINE,
  247. label: 'A',
  248. yAxes: ['count(span.self_time)', 'avg(span.self_time)'],
  249. },
  250. {
  251. chartType: ChartType.LINE,
  252. label: 'B',
  253. yAxes: ['avg(span.duration)'],
  254. },
  255. ]);
  256. // delete first overlay
  257. await userEvent.click(within(section).getAllByLabelText('Remove Overlay')[0]!);
  258. expect(visualizes).toEqual([
  259. {
  260. chartType: ChartType.LINE,
  261. label: 'A',
  262. yAxes: ['avg(span.self_time)'],
  263. },
  264. {
  265. chartType: ChartType.LINE,
  266. label: 'B',
  267. yAxes: ['avg(span.duration)'],
  268. },
  269. ]);
  270. // delete second chart
  271. await userEvent.click(within(section).getAllByLabelText('Remove Overlay')[1]!);
  272. expect(visualizes).toEqual([
  273. {
  274. chartType: ChartType.LINE,
  275. label: 'A',
  276. yAxes: ['avg(span.self_time)'],
  277. },
  278. ]);
  279. // only one left so cant be deleted
  280. expect(within(section).getByLabelText('Remove Overlay')).toBeDisabled();
  281. });
  282. it('allows changing visualizes equations', async function () {
  283. let fields, visualizes: any;
  284. function Component() {
  285. fields = useExploreFields();
  286. visualizes = useExploreVisualizes();
  287. return <ExploreToolbar extras={['equations']} />;
  288. }
  289. render(
  290. <PageParamsProvider>
  291. <SpanTagsProvider dataset={DiscoverDatasets.SPANS_EAP} enabled>
  292. <Component />
  293. </SpanTagsProvider>
  294. </PageParamsProvider>,
  295. {disableRouterMocks: true}
  296. );
  297. const section = screen.getByTestId('section-visualizes');
  298. // this is the default
  299. expect(visualizes).toEqual([
  300. {
  301. chartType: ChartType.LINE,
  302. label: 'A',
  303. yAxes: ['avg(span.duration)'],
  304. },
  305. ]);
  306. expect(fields).toEqual([
  307. 'id',
  308. 'span.op',
  309. 'span.description',
  310. 'span.duration',
  311. 'transaction',
  312. 'timestamp',
  313. ]); // default
  314. let input;
  315. // try changing the field
  316. input = within(section).getByRole('combobox', {
  317. name: 'Select an attribute',
  318. });
  319. await userEvent.click(input);
  320. await userEvent.click(within(section).getByRole('option', {name: 'span.self_time'}));
  321. expect(fields).toEqual([
  322. 'id',
  323. 'span.op',
  324. 'span.description',
  325. 'span.duration',
  326. 'transaction',
  327. 'timestamp',
  328. 'span.self_time',
  329. ]);
  330. await userEvent.click(input);
  331. await userEvent.keyboard('{Backspace}');
  332. await userEvent.click(within(section).getByRole('option', {name: 'count(\u2026)'}));
  333. await userEvent.click(within(section).getByRole('option', {name: 'span.self_time'}));
  334. expect(visualizes).toEqual([
  335. {
  336. chartType: ChartType.LINE,
  337. label: 'A',
  338. yAxes: ['count(span.self_time)'],
  339. },
  340. ]);
  341. await userEvent.keyboard('{Escape}');
  342. // try adding an overlay
  343. await userEvent.click(within(section).getByRole('button', {name: 'Add Series'}));
  344. input = within(section)
  345. .getAllByRole('combobox', {
  346. name: 'Select an attribute',
  347. })
  348. .at(-1)!;
  349. await userEvent.click(input);
  350. await userEvent.click(within(section).getByRole('option', {name: 'span.self_time'}));
  351. expect(visualizes).toEqual([
  352. {
  353. chartType: ChartType.LINE,
  354. label: 'A',
  355. yAxes: ['count(span.self_time)', 'avg(span.self_time)'],
  356. },
  357. ]);
  358. await userEvent.keyboard('{Escape}');
  359. // try adding a new chart
  360. await userEvent.click(within(section).getByRole('button', {name: 'Add Chart'}));
  361. expect(visualizes).toEqual([
  362. {
  363. chartType: ChartType.LINE,
  364. label: 'A',
  365. yAxes: ['count(span.self_time)', 'avg(span.self_time)'],
  366. },
  367. {
  368. chartType: ChartType.LINE,
  369. label: 'B',
  370. yAxes: ['avg(span.duration)'],
  371. },
  372. ]);
  373. // delete first overlay
  374. await userEvent.click(within(section).getAllByLabelText('Remove Overlay')[0]!);
  375. expect(visualizes).toEqual([
  376. {
  377. chartType: ChartType.LINE,
  378. label: 'A',
  379. yAxes: ['avg(span.self_time)'],
  380. },
  381. {
  382. chartType: ChartType.LINE,
  383. label: 'B',
  384. yAxes: ['avg(span.duration)'],
  385. },
  386. ]);
  387. // delete second chart
  388. await userEvent.click(within(section).getAllByLabelText('Remove Overlay')[1]!);
  389. expect(visualizes).toEqual([
  390. {
  391. chartType: ChartType.LINE,
  392. label: 'A',
  393. yAxes: ['avg(span.self_time)'],
  394. },
  395. ]);
  396. // only one left so cant be deleted
  397. expect(within(section).getByLabelText('Remove Overlay')).toBeDisabled();
  398. });
  399. it('allows changing group bys', async function () {
  400. let groupBys: any;
  401. function Component() {
  402. groupBys = useExploreGroupBys();
  403. return <ExploreToolbar />;
  404. }
  405. render(
  406. <PageParamsProvider>
  407. <SpanTagsProvider dataset={DiscoverDatasets.SPANS_EAP} enabled>
  408. <Component />
  409. </SpanTagsProvider>
  410. </PageParamsProvider>,
  411. {disableRouterMocks: true}
  412. );
  413. expect(screen.queryByTestId('section-group-by')).not.toBeInTheDocument();
  414. // click the aggregates mode to enable
  415. await userEvent.click(
  416. within(screen.getByTestId('section-mode')).getByRole('radio', {
  417. name: 'Aggregates',
  418. })
  419. );
  420. const section = screen.getByTestId('section-group-by');
  421. expect(within(section).getByRole('button', {name: 'span.op'})).toBeEnabled();
  422. await userEvent.click(within(section).getByRole('button', {name: 'span.op'}));
  423. const groupByOptions1 = await within(section).findAllByRole('option');
  424. expect(groupByOptions1.length).toBeGreaterThan(0);
  425. await userEvent.click(within(section).getByRole('option', {name: 'project'}));
  426. expect(groupBys).toEqual(['project']);
  427. await userEvent.click(within(section).getByRole('button', {name: 'Add Group'}));
  428. expect(groupBys).toEqual(['project', '']);
  429. await userEvent.click(within(section).getByRole('button', {name: 'None'}));
  430. const groupByOptions2 = await within(section).findAllByRole('option');
  431. expect(groupByOptions2.length).toBeGreaterThan(0);
  432. await userEvent.click(
  433. within(section).getByRole('option', {name: 'span.description'})
  434. );
  435. expect(groupBys).toEqual(['project', 'span.description']);
  436. await userEvent.click(within(section).getAllByLabelText('Remove Column')[0]!);
  437. expect(groupBys).toEqual(['span.description']);
  438. // only 1 left but it's not empty
  439. expect(within(section).getByLabelText('Remove Column')).toBeEnabled();
  440. await userEvent.click(within(section).getByLabelText('Remove Column'));
  441. expect(groupBys).toEqual(['']);
  442. // last one and it's empty
  443. expect(within(section).getByLabelText('Remove Column')).toBeDisabled();
  444. });
  445. it('allows changing sort by', async function () {
  446. let sortBys: any;
  447. function Component() {
  448. sortBys = useExploreSortBys();
  449. return <ExploreToolbar />;
  450. }
  451. render(
  452. <PageParamsProvider>
  453. <SpanTagsProvider dataset={DiscoverDatasets.SPANS_EAP} enabled>
  454. <Component />
  455. </SpanTagsProvider>
  456. </PageParamsProvider>,
  457. {disableRouterMocks: true}
  458. );
  459. const section = screen.getByTestId('section-sort-by');
  460. // this is the default
  461. expect(within(section).getByRole('button', {name: 'timestamp'})).toBeInTheDocument();
  462. expect(within(section).getByRole('button', {name: 'Desc'})).toBeInTheDocument();
  463. expect(sortBys).toEqual([{field: 'timestamp', kind: 'desc'}]);
  464. // check the default field options
  465. const fields = [
  466. 'id',
  467. 'span.description',
  468. 'span.duration',
  469. 'span.op',
  470. 'timestamp',
  471. 'transaction',
  472. ];
  473. await userEvent.click(within(section).getByRole('button', {name: 'timestamp'}));
  474. const fieldOptions = await within(section).findAllByRole('option');
  475. expect(fieldOptions).toHaveLength(fields.length);
  476. fieldOptions.forEach((option, i) => {
  477. expect(option).toHaveTextContent(fields[i]!);
  478. });
  479. // try changing the field
  480. await userEvent.click(within(section).getByRole('option', {name: 'span.op'}));
  481. expect(within(section).getByRole('button', {name: 'span.op'})).toBeInTheDocument();
  482. expect(within(section).getByRole('button', {name: 'Desc'})).toBeInTheDocument();
  483. expect(sortBys).toEqual([{field: 'span.op', kind: 'desc'}]);
  484. // check the kind options
  485. await userEvent.click(within(section).getByRole('button', {name: 'Desc'}));
  486. const kindOptions = await within(section).findAllByRole('option');
  487. expect(kindOptions).toHaveLength(2);
  488. expect(kindOptions[0]).toHaveTextContent('Desc');
  489. expect(kindOptions[1]).toHaveTextContent('Asc');
  490. // try changing the kind
  491. await userEvent.click(within(section).getByRole('option', {name: 'Asc'}));
  492. expect(within(section).getByRole('button', {name: 'span.op'})).toBeInTheDocument();
  493. expect(within(section).getByRole('button', {name: 'Asc'})).toBeInTheDocument();
  494. expect(sortBys).toEqual([{field: 'span.op', kind: 'asc'}]);
  495. });
  496. it('takes you to suggested query', async function () {
  497. let pageParams: any;
  498. function Component() {
  499. pageParams = useExplorePageParams();
  500. return <ExploreToolbar />;
  501. }
  502. render(
  503. <PageParamsProvider>
  504. <SpanTagsProvider dataset={DiscoverDatasets.SPANS_EAP} enabled>
  505. <Component />
  506. </SpanTagsProvider>
  507. </PageParamsProvider>,
  508. {disableRouterMocks: true}
  509. );
  510. const section = screen.getByTestId('section-suggested-queries');
  511. await userEvent.click(within(section).getByText('Slowest Ops'));
  512. expect(pageParams).toEqual(
  513. expect.objectContaining({
  514. fields: [
  515. 'id',
  516. 'project',
  517. 'span.op',
  518. 'span.description',
  519. 'span.duration',
  520. 'timestamp',
  521. ],
  522. groupBys: ['span.op'],
  523. mode: Mode.AGGREGATE,
  524. query: '',
  525. sortBys: [{field: 'avg(span.duration)', kind: 'desc'}],
  526. visualizes: [
  527. {
  528. chartType: ChartType.LINE,
  529. label: 'A',
  530. yAxes: ['avg(span.duration)'],
  531. },
  532. {
  533. chartType: ChartType.LINE,
  534. label: 'B',
  535. yAxes: ['p50(span.duration)'],
  536. },
  537. ],
  538. })
  539. );
  540. });
  541. it('opens compare queries', async function () {
  542. const router = RouterFixture({
  543. location: {
  544. pathname: '/traces/',
  545. query: {
  546. visualize: encodeURIComponent('{"chartType":1,"yAxes":["p95(span.duration)"]}'),
  547. },
  548. },
  549. });
  550. function Component() {
  551. return <ExploreToolbar />;
  552. }
  553. render(
  554. <PageParamsProvider>
  555. <SpanTagsProvider dataset={DiscoverDatasets.SPANS_EAP} enabled>
  556. <Component />
  557. </SpanTagsProvider>
  558. </PageParamsProvider>,
  559. {router, organization}
  560. );
  561. const section = screen.getByTestId('section-save-as');
  562. await userEvent.click(within(section).getByText(/Compare/));
  563. expect(router.push).toHaveBeenCalledWith({
  564. pathname: '/organizations/org-slug/traces/compare/',
  565. query: expect.objectContaining({
  566. queries: [
  567. '{"groupBys":[],"query":"","sortBys":["-timestamp"],"yAxes":["avg(span.duration)"]}',
  568. '{"chartType":1,"fields":["id","span.duration"],"groupBys":[],"query":"","sortBys":["-span.duration"],"yAxes":["avg(span.duration)"]}',
  569. ],
  570. }),
  571. });
  572. });
  573. it('opens the right alert', async function () {
  574. const router = RouterFixture({
  575. location: {
  576. pathname: '/traces/',
  577. query: {
  578. visualize: encodeURIComponent('{"chartType":1,"yAxes":["avg(span.duration)"]}'),
  579. },
  580. },
  581. });
  582. function Component() {
  583. return <ExploreToolbar />;
  584. }
  585. render(
  586. <PageParamsProvider>
  587. <SpanTagsProvider dataset={DiscoverDatasets.SPANS_EAP} enabled>
  588. <Component />
  589. </SpanTagsProvider>
  590. </PageParamsProvider>,
  591. {router, organization}
  592. );
  593. const section = screen.getByTestId('section-save-as');
  594. await userEvent.click(within(section).getByText(/Save as/));
  595. await userEvent.hover(within(section).getByText('An Alert for'));
  596. await userEvent.click(screen.getByText('avg(span.duration)'));
  597. expect(router.push).toHaveBeenCalledWith({
  598. pathname: '/organizations/org-slug/alerts/new/metric/',
  599. query: expect.objectContaining({
  600. aggregate: 'avg(span.duration)',
  601. dataset: 'events_analytics_platform',
  602. }),
  603. });
  604. });
  605. it('add to dashboard options correctly', async function () {
  606. const router = RouterFixture({
  607. location: {
  608. pathname: '/traces/',
  609. query: {
  610. visualize: encodeURIComponent('{"chartType":1,"yAxes":["avg(span.duration)"]}'),
  611. },
  612. },
  613. });
  614. function Component() {
  615. return <ExploreToolbar />;
  616. }
  617. render(
  618. <PageParamsProvider>
  619. <SpanTagsProvider dataset={DiscoverDatasets.SPANS_EAP} enabled>
  620. <Component />
  621. </SpanTagsProvider>
  622. </PageParamsProvider>,
  623. {router, organization}
  624. );
  625. const section = screen.getByTestId('section-save-as');
  626. await userEvent.click(within(section).getByText(/Save as/));
  627. await userEvent.click(within(section).getByText('A Dashboard widget'));
  628. await waitFor(() => {
  629. expect(openAddToDashboardModal).toHaveBeenCalledWith(
  630. expect.objectContaining({
  631. widget: expect.objectContaining({
  632. displayType: 'line',
  633. queries: [
  634. {
  635. aggregates: ['avg(span.duration)'],
  636. columns: [],
  637. conditions: '',
  638. fields: ['avg(span.duration)'],
  639. name: '',
  640. orderby: '-timestamp',
  641. },
  642. ],
  643. title: 'Custom Widget',
  644. widgetType: 'spans',
  645. }),
  646. widgetAsQueryParams: expect.objectContaining({
  647. dataset: 'spans',
  648. defaultTableColumns: [
  649. 'id',
  650. 'span.op',
  651. 'span.description',
  652. 'span.duration',
  653. 'transaction',
  654. 'timestamp',
  655. ],
  656. defaultTitle: 'Custom Widget',
  657. defaultWidgetQuery:
  658. 'name=&aggregates=avg(span.duration)&columns=&fields=avg(span.duration)&conditions=&orderby=-timestamp',
  659. displayType: 'line',
  660. end: undefined,
  661. field: [
  662. 'id',
  663. 'span.op',
  664. 'span.description',
  665. 'span.duration',
  666. 'transaction',
  667. 'timestamp',
  668. ],
  669. limit: undefined,
  670. source: 'traceExplorer',
  671. start: undefined,
  672. statsPeriod: '14d',
  673. }),
  674. })
  675. );
  676. });
  677. });
  678. });