index.spec.tsx 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454
  1. import {OrganizationFixture} from 'sentry-fixture/organization';
  2. import {render, screen, userEvent, within} from 'sentry-test/reactTestingLibrary';
  3. import {DiscoverDatasets} from 'sentry/utils/discover/types';
  4. import {
  5. PageParamsProvider,
  6. useExploreDataset,
  7. useExploreFields,
  8. useExploreGroupBys,
  9. useExploreMode,
  10. useExplorePageParams,
  11. useExploreSortBys,
  12. useExploreVisualizes,
  13. } from 'sentry/views/explore/contexts/pageParamsContext';
  14. import {Mode} from 'sentry/views/explore/contexts/pageParamsContext/mode';
  15. import {ExploreToolbar} from 'sentry/views/explore/toolbar';
  16. import {ChartType} from 'sentry/views/insights/common/components/chart';
  17. import {SpanTagsProvider} from '../contexts/spanTagsContext';
  18. describe('ExploreToolbar', function () {
  19. const organization = OrganizationFixture();
  20. beforeEach(function () {
  21. // without this the `CompactSelect` component errors with a bunch of async updates
  22. jest.spyOn(console, 'error').mockImplementation();
  23. MockApiClient.addMockResponse({
  24. url: `/organizations/${organization.slug}/spans/fields/`,
  25. method: 'GET',
  26. body: [],
  27. });
  28. });
  29. it('should not render dataset selector', function () {
  30. function Component() {
  31. return <ExploreToolbar />;
  32. }
  33. render(
  34. <PageParamsProvider>
  35. <SpanTagsProvider dataset={DiscoverDatasets.SPANS_EAP} enabled>
  36. <Component />
  37. </SpanTagsProvider>
  38. </PageParamsProvider>,
  39. {disableRouterMocks: true}
  40. );
  41. const section = screen.queryByTestId('section-dataset');
  42. expect(section).not.toBeInTheDocument();
  43. });
  44. it('allows changing datasets', async function () {
  45. let dataset;
  46. function Component() {
  47. dataset = useExploreDataset();
  48. return <ExploreToolbar extras={['dataset toggle']} />;
  49. }
  50. render(
  51. <PageParamsProvider>
  52. <SpanTagsProvider dataset={DiscoverDatasets.SPANS_EAP} enabled>
  53. <Component />
  54. </SpanTagsProvider>
  55. </PageParamsProvider>,
  56. {disableRouterMocks: true}
  57. );
  58. const section = screen.getByTestId('section-dataset');
  59. const eapSpans = within(section).getByRole('radio', {name: 'EAP Spans'});
  60. const rpcSpans = within(section).getByRole('radio', {name: 'EAP RPC Spans'});
  61. const indexedSpans = within(section).getByRole('radio', {name: 'Indexed Spans'});
  62. expect(eapSpans).toBeChecked();
  63. expect(rpcSpans).not.toBeChecked();
  64. expect(indexedSpans).not.toBeChecked();
  65. expect(dataset).toEqual(DiscoverDatasets.SPANS_EAP);
  66. await userEvent.click(rpcSpans);
  67. expect(eapSpans).not.toBeChecked();
  68. expect(rpcSpans).toBeChecked();
  69. expect(indexedSpans).not.toBeChecked();
  70. expect(dataset).toEqual(DiscoverDatasets.SPANS_EAP_RPC);
  71. await userEvent.click(indexedSpans);
  72. expect(eapSpans).not.toBeChecked();
  73. expect(rpcSpans).not.toBeChecked();
  74. expect(indexedSpans).toBeChecked();
  75. expect(dataset).toEqual(DiscoverDatasets.SPANS_INDEXED);
  76. });
  77. it('allows changing mode', async function () {
  78. let mode;
  79. function Component() {
  80. mode = useExploreMode();
  81. return <ExploreToolbar extras={['dataset toggle']} />;
  82. }
  83. render(
  84. <PageParamsProvider>
  85. <SpanTagsProvider dataset={DiscoverDatasets.SPANS_EAP} enabled>
  86. <Component />
  87. </SpanTagsProvider>
  88. </PageParamsProvider>,
  89. {disableRouterMocks: true}
  90. );
  91. const section = screen.getByTestId('section-mode');
  92. const samples = within(section).getByRole('radio', {name: 'Samples'});
  93. const aggregates = within(section).getByRole('radio', {name: 'Aggregates'});
  94. expect(samples).toBeChecked();
  95. expect(aggregates).not.toBeChecked();
  96. expect(mode).toEqual(Mode.SAMPLES);
  97. await userEvent.click(aggregates);
  98. expect(samples).not.toBeChecked();
  99. expect(aggregates).toBeChecked();
  100. expect(mode).toEqual(Mode.AGGREGATE);
  101. await userEvent.click(samples);
  102. expect(samples).toBeChecked();
  103. expect(aggregates).not.toBeChecked();
  104. expect(mode).toEqual(Mode.SAMPLES);
  105. });
  106. it('inserts group bys from aggregate mode as fields in samples mode', async function () {
  107. let fields, groupBys;
  108. function Component() {
  109. fields = useExploreFields();
  110. groupBys = useExploreGroupBys();
  111. return <ExploreToolbar extras={['dataset toggle']} />;
  112. }
  113. render(
  114. <PageParamsProvider>
  115. <SpanTagsProvider dataset={DiscoverDatasets.SPANS_EAP} enabled>
  116. <Component />
  117. </SpanTagsProvider>
  118. </PageParamsProvider>,
  119. {disableRouterMocks: true}
  120. );
  121. const section = screen.getByTestId('section-mode');
  122. const samples = within(section).getByRole('radio', {name: 'Samples'});
  123. const aggregates = within(section).getByRole('radio', {name: 'Aggregates'});
  124. expect(fields).toEqual([
  125. 'id',
  126. 'project',
  127. 'span.op',
  128. 'span.description',
  129. 'span.duration',
  130. 'timestamp',
  131. ]); // default
  132. // Add a group by, and leave one unselected
  133. await userEvent.click(aggregates);
  134. const groupBy = screen.getByTestId('section-group-by');
  135. await userEvent.click(within(groupBy).getByRole('button', {name: 'None'}));
  136. await userEvent.click(within(groupBy).getByRole('option', {name: 'release'}));
  137. expect(groupBys).toEqual(['release']);
  138. await userEvent.click(within(groupBy).getByRole('button', {name: 'Add Group'}));
  139. expect(groupBys).toEqual(['release', '']);
  140. await userEvent.click(samples);
  141. expect(fields).toEqual([
  142. 'id',
  143. 'project',
  144. 'span.op',
  145. 'span.description',
  146. 'span.duration',
  147. 'timestamp',
  148. 'release',
  149. ]); // default
  150. });
  151. it('allows changing visualizes', async function () {
  152. let visualizes;
  153. function Component() {
  154. visualizes = useExploreVisualizes();
  155. return <ExploreToolbar />;
  156. }
  157. render(
  158. <PageParamsProvider>
  159. <SpanTagsProvider dataset={DiscoverDatasets.SPANS_EAP} enabled>
  160. <Component />
  161. </SpanTagsProvider>
  162. </PageParamsProvider>,
  163. {disableRouterMocks: true}
  164. );
  165. const section = screen.getByTestId('section-visualizes');
  166. // this is the default
  167. expect(visualizes).toEqual([
  168. {
  169. chartType: ChartType.LINE,
  170. label: 'A',
  171. yAxes: ['avg(span.duration)'],
  172. },
  173. ]);
  174. // try changing the field
  175. await userEvent.click(within(section).getByRole('button', {name: 'span.duration'}));
  176. await userEvent.click(within(section).getByRole('option', {name: 'span.self_time'}));
  177. expect(visualizes).toEqual([
  178. {
  179. chartType: ChartType.LINE,
  180. label: 'A',
  181. yAxes: ['avg(span.self_time)'],
  182. },
  183. ]);
  184. // try changing the aggregate
  185. await userEvent.click(within(section).getByRole('button', {name: 'avg'}));
  186. await userEvent.click(within(section).getByRole('option', {name: 'count'}));
  187. expect(visualizes).toEqual([
  188. {
  189. chartType: ChartType.LINE,
  190. label: 'A',
  191. yAxes: ['count(span.self_time)'],
  192. },
  193. ]);
  194. // try adding an overlay
  195. await userEvent.click(within(section).getByRole('button', {name: 'Add Series'}));
  196. await userEvent.click(within(section).getByRole('button', {name: 'span.duration'}));
  197. await userEvent.click(within(section).getByRole('option', {name: 'span.self_time'}));
  198. expect(visualizes).toEqual([
  199. {
  200. chartType: ChartType.LINE,
  201. label: 'A',
  202. yAxes: ['count(span.self_time)', 'avg(span.self_time)'],
  203. },
  204. ]);
  205. // try adding a new chart
  206. await userEvent.click(within(section).getByRole('button', {name: 'Add Chart'}));
  207. expect(visualizes).toEqual([
  208. {
  209. chartType: ChartType.LINE,
  210. label: 'A',
  211. yAxes: ['count(span.self_time)', 'avg(span.self_time)'],
  212. },
  213. {
  214. chartType: ChartType.LINE,
  215. label: 'B',
  216. yAxes: ['avg(span.duration)'],
  217. },
  218. ]);
  219. // delete first overlay
  220. await userEvent.click(within(section).getAllByLabelText('Remove Overlay')[0]!);
  221. expect(visualizes).toEqual([
  222. {
  223. chartType: ChartType.LINE,
  224. label: 'A',
  225. yAxes: ['avg(span.self_time)'],
  226. },
  227. {
  228. chartType: ChartType.LINE,
  229. label: 'B',
  230. yAxes: ['avg(span.duration)'],
  231. },
  232. ]);
  233. // delete second chart
  234. await userEvent.click(within(section).getAllByLabelText('Remove Overlay')[1]!);
  235. expect(visualizes).toEqual([
  236. {
  237. chartType: ChartType.LINE,
  238. label: 'A',
  239. yAxes: ['avg(span.self_time)'],
  240. },
  241. ]);
  242. // only one left so cant be deleted
  243. expect(within(section).getByLabelText('Remove Overlay')).toBeDisabled();
  244. });
  245. it('allows changing group bys', async function () {
  246. let groupBys;
  247. function Component() {
  248. groupBys = useExploreGroupBys();
  249. return <ExploreToolbar />;
  250. }
  251. render(
  252. <PageParamsProvider>
  253. <SpanTagsProvider dataset={DiscoverDatasets.SPANS_EAP} enabled>
  254. <Component />
  255. </SpanTagsProvider>
  256. </PageParamsProvider>,
  257. {disableRouterMocks: true}
  258. );
  259. const section = screen.getByTestId('section-group-by');
  260. expect(within(section).getByRole('button', {name: 'None'})).toBeInTheDocument();
  261. expect(groupBys).toEqual(['']);
  262. // disabled in the samples mode
  263. expect(within(section).getByRole('button', {name: 'None'})).toBeDisabled();
  264. // click the aggregates mode to enable
  265. await userEvent.click(
  266. within(screen.getByTestId('section-mode')).getByRole('radio', {
  267. name: 'Aggregates',
  268. })
  269. );
  270. expect(within(section).getByRole('button', {name: 'None'})).toBeEnabled();
  271. await userEvent.click(within(section).getByRole('button', {name: 'None'}));
  272. const groupByOptions1 = await within(section).findAllByRole('option');
  273. expect(groupByOptions1.length).toBeGreaterThan(0);
  274. await userEvent.click(within(section).getByRole('option', {name: 'span.op'}));
  275. expect(groupBys).toEqual(['span.op']);
  276. await userEvent.click(within(section).getByRole('button', {name: 'Add Group'}));
  277. expect(groupBys).toEqual(['span.op', '']);
  278. await userEvent.click(within(section).getByRole('button', {name: 'None'}));
  279. const groupByOptions2 = await within(section).findAllByRole('option');
  280. expect(groupByOptions2.length).toBeGreaterThan(0);
  281. await userEvent.click(
  282. within(section).getByRole('option', {name: 'span.description'})
  283. );
  284. expect(groupBys).toEqual(['span.op', 'span.description']);
  285. await userEvent.click(within(section).getAllByLabelText('Remove Column')[0]!);
  286. expect(groupBys).toEqual(['span.description']);
  287. // only 1 left but it's not empty
  288. expect(within(section).getByLabelText('Remove Column')).toBeEnabled();
  289. await userEvent.click(within(section).getByLabelText('Remove Column'));
  290. expect(groupBys).toEqual(['']);
  291. // last one and it's empty
  292. expect(within(section).getByLabelText('Remove Column')).toBeDisabled();
  293. });
  294. it('allows changing sort by', async function () {
  295. let sortBys;
  296. function Component() {
  297. sortBys = useExploreSortBys();
  298. return <ExploreToolbar />;
  299. }
  300. render(
  301. <PageParamsProvider>
  302. <SpanTagsProvider dataset={DiscoverDatasets.SPANS_EAP} enabled>
  303. <Component />
  304. </SpanTagsProvider>
  305. </PageParamsProvider>,
  306. {disableRouterMocks: true}
  307. );
  308. const section = screen.getByTestId('section-sort-by');
  309. // this is the default
  310. expect(within(section).getByRole('button', {name: 'timestamp'})).toBeInTheDocument();
  311. expect(within(section).getByRole('button', {name: 'Desc'})).toBeInTheDocument();
  312. expect(sortBys).toEqual([{field: 'timestamp', kind: 'desc'}]);
  313. // check the default field options
  314. const fields = [
  315. 'id',
  316. 'project',
  317. 'span.description',
  318. 'span.duration',
  319. 'span.op',
  320. 'timestamp',
  321. ];
  322. await userEvent.click(within(section).getByRole('button', {name: 'timestamp'}));
  323. const fieldOptions = await within(section).findAllByRole('option');
  324. expect(fieldOptions).toHaveLength(fields.length);
  325. fieldOptions.forEach((option, i) => {
  326. expect(option).toHaveTextContent(fields[i]!);
  327. });
  328. // try changing the field
  329. await userEvent.click(within(section).getByRole('option', {name: 'span.op'}));
  330. expect(within(section).getByRole('button', {name: 'span.op'})).toBeInTheDocument();
  331. expect(within(section).getByRole('button', {name: 'Desc'})).toBeInTheDocument();
  332. expect(sortBys).toEqual([{field: 'span.op', kind: 'desc'}]);
  333. // check the kind options
  334. await userEvent.click(within(section).getByRole('button', {name: 'Desc'}));
  335. const kindOptions = await within(section).findAllByRole('option');
  336. expect(kindOptions).toHaveLength(2);
  337. expect(kindOptions[0]).toHaveTextContent('Desc');
  338. expect(kindOptions[1]).toHaveTextContent('Asc');
  339. // try changing the kind
  340. await userEvent.click(within(section).getByRole('option', {name: 'Asc'}));
  341. expect(within(section).getByRole('button', {name: 'span.op'})).toBeInTheDocument();
  342. expect(within(section).getByRole('button', {name: 'Asc'})).toBeInTheDocument();
  343. expect(sortBys).toEqual([{field: 'span.op', kind: 'asc'}]);
  344. });
  345. it('takes you to suggested query', async function () {
  346. let pageParams;
  347. function Component() {
  348. pageParams = useExplorePageParams();
  349. return <ExploreToolbar />;
  350. }
  351. render(
  352. <PageParamsProvider>
  353. <SpanTagsProvider dataset={DiscoverDatasets.SPANS_EAP} enabled>
  354. <Component />
  355. </SpanTagsProvider>
  356. </PageParamsProvider>,
  357. {disableRouterMocks: true}
  358. );
  359. const section = screen.getByTestId('section-suggested-queries');
  360. await userEvent.click(within(section).getByText('Slowest Ops'));
  361. expect(pageParams).toEqual(
  362. expect.objectContaining({
  363. fields: [
  364. 'id',
  365. 'project',
  366. 'span.op',
  367. 'span.description',
  368. 'span.duration',
  369. 'timestamp',
  370. ],
  371. groupBys: ['span.op'],
  372. mode: Mode.AGGREGATE,
  373. query: '',
  374. sortBys: [{field: 'avg(span.duration)', kind: 'desc'}],
  375. visualizes: [
  376. {
  377. chartType: ChartType.LINE,
  378. label: 'A',
  379. yAxes: ['avg(span.duration)'],
  380. },
  381. {
  382. chartType: ChartType.LINE,
  383. label: 'B',
  384. yAxes: ['p50(span.duration)'],
  385. },
  386. ],
  387. })
  388. );
  389. });
  390. });