results.spec.tsx 52 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957
  1. import {LocationFixture} from 'sentry-fixture/locationFixture';
  2. import {OrganizationFixture} from 'sentry-fixture/organization';
  3. import {ProjectFixture} from 'sentry-fixture/project';
  4. import {initializeOrg} from 'sentry-test/initializeOrg';
  5. import {render, screen, userEvent, waitFor} from 'sentry-test/reactTestingLibrary';
  6. import selectEvent from 'sentry-test/selectEvent';
  7. import * as PageFilterPersistence from 'sentry/components/organizations/pageFilters/persistence';
  8. import ProjectsStore from 'sentry/stores/projectsStore';
  9. import {SavedSearchType} from 'sentry/types/group';
  10. import {browserHistory} from 'sentry/utils/browserHistory';
  11. import EventView from 'sentry/utils/discover/eventView';
  12. import Results from 'sentry/views/discover/results';
  13. import {DEFAULT_EVENT_VIEW, getTransactionViews} from './data';
  14. const FIELDS = [
  15. {
  16. field: 'title',
  17. },
  18. {
  19. field: 'timestamp',
  20. },
  21. {
  22. field: 'user',
  23. },
  24. {
  25. field: 'count()',
  26. },
  27. ];
  28. const generateFields = () => ({
  29. field: FIELDS.map(i => i.field),
  30. });
  31. const eventTitle = 'Oh no something bad';
  32. function renderMockRequests() {
  33. MockApiClient.addMockResponse({
  34. url: '/organizations/org-slug/projects/',
  35. body: [],
  36. });
  37. MockApiClient.addMockResponse({
  38. url: '/organizations/org-slug/projects-count/',
  39. body: {myProjects: 10, allProjects: 300},
  40. });
  41. MockApiClient.addMockResponse({
  42. url: '/organizations/org-slug/tags/',
  43. body: [],
  44. });
  45. const eventsStatsMock = MockApiClient.addMockResponse({
  46. url: '/organizations/org-slug/events-stats/',
  47. body: {data: [[123, []]]},
  48. });
  49. MockApiClient.addMockResponse({
  50. url: '/organizations/org-slug/recent-searches/',
  51. body: [],
  52. });
  53. MockApiClient.addMockResponse({
  54. url: '/organizations/org-slug/recent-searches/',
  55. method: 'POST',
  56. body: [],
  57. });
  58. MockApiClient.addMockResponse({
  59. url: '/organizations/org-slug/releases/stats/',
  60. body: [],
  61. });
  62. const measurementsMetaMock = MockApiClient.addMockResponse({
  63. url: '/organizations/org-slug/measurements-meta/',
  64. method: 'GET',
  65. body: {},
  66. });
  67. const eventsResultsMock = MockApiClient.addMockResponse({
  68. url: '/organizations/org-slug/events/',
  69. body: {
  70. meta: {
  71. fields: {
  72. id: 'string',
  73. title: 'string',
  74. 'project.name': 'string',
  75. timestamp: 'date',
  76. 'user.id': 'string',
  77. },
  78. discoverSplitDecision: 'transaction-like',
  79. },
  80. data: [
  81. {
  82. id: 'deadbeef',
  83. 'user.id': 'alberto leal',
  84. title: eventTitle,
  85. 'project.name': 'project-slug',
  86. timestamp: '2019-05-23T22:12:48+00:00',
  87. },
  88. ],
  89. },
  90. });
  91. const eventsMetaMock = MockApiClient.addMockResponse({
  92. url: '/organizations/org-slug/events-meta/',
  93. body: {
  94. count: 2,
  95. },
  96. });
  97. MockApiClient.addMockResponse({
  98. url: '/organizations/org-slug/events/project-slug:deadbeef/',
  99. method: 'GET',
  100. body: {
  101. id: '1234',
  102. size: 1200,
  103. eventID: 'deadbeef',
  104. title: 'Oh no something bad',
  105. message: 'It was not good',
  106. dateCreated: '2019-05-23T22:12:48+00:00',
  107. entries: [
  108. {
  109. type: 'message',
  110. message: 'bad stuff',
  111. data: {},
  112. },
  113. ],
  114. tags: [{key: 'browser', value: 'Firefox'}],
  115. },
  116. });
  117. const eventFacetsMock = MockApiClient.addMockResponse({
  118. url: '/organizations/org-slug/events-facets/',
  119. body: [
  120. {
  121. key: 'release',
  122. topValues: [{count: 3, value: 'abcd123', name: 'abcd123'}],
  123. },
  124. {
  125. key: 'environment',
  126. topValues: [
  127. {count: 2, value: 'dev', name: 'dev'},
  128. {count: 1, value: 'prod', name: 'prod'},
  129. ],
  130. },
  131. {
  132. key: 'foo',
  133. topValues: [
  134. {count: 2, value: 'bar', name: 'bar'},
  135. {count: 1, value: 'baz', name: 'baz'},
  136. ],
  137. },
  138. ],
  139. });
  140. const mockVisit = MockApiClient.addMockResponse({
  141. url: '/organizations/org-slug/discover/saved/1/visit/',
  142. method: 'POST',
  143. body: [],
  144. statusCode: 200,
  145. });
  146. const mockSaved = MockApiClient.addMockResponse({
  147. url: '/organizations/org-slug/discover/saved/1/',
  148. method: 'GET',
  149. statusCode: 200,
  150. body: {
  151. id: '1',
  152. name: 'new',
  153. projects: [],
  154. version: 2,
  155. expired: false,
  156. dateCreated: '2021-04-08T17:53:25.195782Z',
  157. dateUpdated: '2021-04-09T12:13:18.567264Z',
  158. createdBy: {
  159. id: '2',
  160. },
  161. environment: [],
  162. fields: ['title', 'event.type', 'project', 'user.display', 'timestamp'],
  163. widths: ['-1', '-1', '-1', '-1', '-1'],
  164. range: '24h',
  165. orderby: '-user.display',
  166. queryDataset: 'discover',
  167. },
  168. });
  169. MockApiClient.addMockResponse({
  170. url: '/organizations/org-slug/discover/homepage/',
  171. method: 'GET',
  172. statusCode: 200,
  173. body: {
  174. id: '2',
  175. name: '',
  176. projects: [],
  177. version: 2,
  178. expired: false,
  179. dateCreated: '2021-04-08T17:53:25.195782Z',
  180. dateUpdated: '2021-04-09T12:13:18.567264Z',
  181. createdBy: {
  182. id: '2',
  183. },
  184. environment: [],
  185. fields: ['title', 'event.type', 'project', 'user.display', 'timestamp'],
  186. widths: ['-1', '-1', '-1', '-1', '-1'],
  187. range: '24h',
  188. orderby: '-user.display',
  189. queryDataset: 'discover',
  190. },
  191. });
  192. MockApiClient.addMockResponse({
  193. url: '/organizations/org-slug/dynamic-sampling/custom-rules/',
  194. method: 'GET',
  195. statusCode: 204,
  196. body: '',
  197. });
  198. return {
  199. eventsStatsMock,
  200. eventsMetaMock,
  201. eventsResultsMock,
  202. mockVisit,
  203. mockSaved,
  204. eventFacetsMock,
  205. measurementsMetaMock,
  206. };
  207. }
  208. describe('Results', function () {
  209. afterEach(function () {
  210. MockApiClient.clearMockResponses();
  211. ProjectsStore.reset();
  212. });
  213. describe('Events', function () {
  214. const features = ['discover-basic'];
  215. it('loads data when moving from an invalid to valid EventView', function () {
  216. const organization = OrganizationFixture({
  217. features,
  218. });
  219. // Start off with an invalid view (empty is invalid)
  220. const {router} = initializeOrg({
  221. organization,
  222. router: {
  223. location: {query: {query: 'tag:value'}},
  224. },
  225. });
  226. const mockRequests = renderMockRequests();
  227. ProjectsStore.loadInitialData([ProjectFixture()]);
  228. render(
  229. <Results
  230. location={router.location}
  231. router={router}
  232. loading={false}
  233. setSavedQuery={jest.fn()}
  234. />,
  235. {
  236. router: router,
  237. organization,
  238. }
  239. );
  240. // No request as eventview was invalid.
  241. expect(mockRequests.eventsStatsMock).not.toHaveBeenCalled();
  242. // Should redirect and retain the old query value
  243. expect(browserHistory.replace).toHaveBeenCalledWith(
  244. expect.objectContaining({
  245. pathname: '/organizations/org-slug/discover/results/',
  246. query: expect.objectContaining({
  247. query: 'tag:value',
  248. }),
  249. })
  250. );
  251. });
  252. it('pagination cursor should be cleared when making a search', async function () {
  253. const organization = OrganizationFixture({
  254. features,
  255. });
  256. const {router} = initializeOrg({
  257. organization,
  258. router: {
  259. location: {
  260. query: {
  261. ...generateFields(),
  262. cursor: '0%3A50%3A0',
  263. },
  264. },
  265. },
  266. });
  267. const mockRequests = renderMockRequests();
  268. ProjectsStore.loadInitialData([ProjectFixture()]);
  269. render(
  270. <Results
  271. organization={organization}
  272. location={router.location}
  273. router={router}
  274. loading={false}
  275. setSavedQuery={jest.fn()}
  276. />,
  277. {
  278. router: router,
  279. organization,
  280. }
  281. );
  282. // ensure cursor query string is initially present in the location
  283. expect(router.location).toEqual({
  284. query: {
  285. ...generateFields(),
  286. cursor: '0%3A50%3A0',
  287. },
  288. });
  289. await waitFor(() =>
  290. expect(screen.queryByTestId('loading-indicator')).not.toBeInTheDocument()
  291. );
  292. // perform a search
  293. await userEvent.click(
  294. screen.getByPlaceholderText('Search for events, users, tags, and more')
  295. );
  296. await userEvent.paste('geo:canada');
  297. await userEvent.keyboard('{enter}');
  298. // should only be called with saved queries
  299. expect(mockRequests.mockVisit).not.toHaveBeenCalled();
  300. // cursor query string should be omitted from the query string
  301. expect(router.push).toHaveBeenCalledWith({
  302. pathname: undefined,
  303. query: {
  304. ...generateFields(),
  305. query: 'geo:canada',
  306. statsPeriod: '14d',
  307. },
  308. });
  309. });
  310. it('renders a y-axis selector', async function () {
  311. const organization = OrganizationFixture({
  312. features,
  313. });
  314. const {router} = initializeOrg({
  315. organization,
  316. router: {
  317. location: {query: {...generateFields(), yAxis: 'count()'}},
  318. },
  319. });
  320. renderMockRequests();
  321. ProjectsStore.loadInitialData([ProjectFixture()]);
  322. render(
  323. <Results
  324. organization={organization}
  325. location={router.location}
  326. router={router}
  327. loading={false}
  328. setSavedQuery={jest.fn()}
  329. />,
  330. {
  331. router: router,
  332. organization,
  333. }
  334. );
  335. // Click the 'default' option.
  336. await selectEvent.select(
  337. await screen.findByRole('button', {name: 'Y-Axis count()'}),
  338. 'count_unique(user)'
  339. );
  340. });
  341. it('renders a display selector', async function () {
  342. const organization = OrganizationFixture({
  343. features,
  344. });
  345. const {router} = initializeOrg({
  346. organization,
  347. router: {
  348. location: {query: {...generateFields(), display: 'default', yAxis: 'count'}},
  349. },
  350. });
  351. renderMockRequests();
  352. ProjectsStore.loadInitialData([ProjectFixture()]);
  353. render(
  354. <Results
  355. organization={organization}
  356. location={router.location}
  357. router={router}
  358. loading={false}
  359. setSavedQuery={jest.fn()}
  360. />,
  361. {
  362. router: router,
  363. organization,
  364. }
  365. );
  366. // Click the 'default' option.
  367. await selectEvent.select(
  368. await screen.findByRole('button', {name: /Display/}),
  369. 'Total Period'
  370. );
  371. });
  372. it('excludes top5 options when plan does not include discover-query', async function () {
  373. const organization = OrganizationFixture({
  374. features: ['discover-basic'],
  375. });
  376. const {router} = initializeOrg({
  377. organization,
  378. router: {
  379. location: {query: {...generateFields(), display: 'previous'}},
  380. },
  381. });
  382. ProjectsStore.loadInitialData([ProjectFixture()]);
  383. renderMockRequests();
  384. render(
  385. <Results
  386. organization={organization}
  387. location={router.location}
  388. router={router}
  389. loading={false}
  390. setSavedQuery={jest.fn()}
  391. />,
  392. {
  393. router: router,
  394. organization,
  395. }
  396. );
  397. await userEvent.click(await screen.findByRole('button', {name: /Display/}));
  398. expect(screen.queryByText('Top 5 Daily')).not.toBeInTheDocument();
  399. expect(screen.queryByText('Top 5 Period')).not.toBeInTheDocument();
  400. });
  401. it('needs confirmation on long queries', async function () {
  402. const organization = OrganizationFixture({
  403. features: ['discover-basic'],
  404. });
  405. const {router} = initializeOrg({
  406. organization,
  407. router: {
  408. location: {query: {...generateFields(), statsPeriod: '60d', project: '-1'}},
  409. },
  410. });
  411. ProjectsStore.loadInitialData([ProjectFixture()]);
  412. const mockRequests = renderMockRequests();
  413. render(
  414. <Results
  415. organization={organization}
  416. location={router.location}
  417. router={router}
  418. loading={false}
  419. setSavedQuery={jest.fn()}
  420. />,
  421. {
  422. router: router,
  423. organization,
  424. }
  425. );
  426. expect(mockRequests.eventsResultsMock).toHaveBeenCalledTimes(0);
  427. await waitFor(() => {
  428. expect(mockRequests.measurementsMetaMock).toHaveBeenCalled();
  429. });
  430. });
  431. it('needs confirmation on long query with explicit projects', async function () {
  432. const organization = OrganizationFixture({
  433. features: ['discover-basic'],
  434. });
  435. const {router} = initializeOrg({
  436. organization,
  437. router: {
  438. location: {
  439. query: {
  440. ...generateFields(),
  441. statsPeriod: '60d',
  442. project: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11].map(String),
  443. },
  444. },
  445. },
  446. });
  447. ProjectsStore.loadInitialData([ProjectFixture()]);
  448. const mockRequests = renderMockRequests();
  449. render(
  450. <Results
  451. organization={organization}
  452. location={router.location}
  453. router={router}
  454. loading={false}
  455. setSavedQuery={jest.fn()}
  456. />,
  457. {
  458. router: router,
  459. organization,
  460. }
  461. );
  462. expect(mockRequests.eventsResultsMock).toHaveBeenCalledTimes(0);
  463. await waitFor(() => {
  464. expect(mockRequests.measurementsMetaMock).toHaveBeenCalled();
  465. });
  466. });
  467. it('does not need confirmation on short queries', async function () {
  468. const organization = OrganizationFixture({
  469. features: ['discover-basic'],
  470. });
  471. const {router} = initializeOrg({
  472. organization,
  473. router: {
  474. location: {query: {...generateFields(), statsPeriod: '30d', project: '-1'}},
  475. },
  476. });
  477. ProjectsStore.loadInitialData([ProjectFixture()]);
  478. const mockRequests = renderMockRequests();
  479. render(
  480. <Results
  481. organization={organization}
  482. location={router.location}
  483. router={router}
  484. loading={false}
  485. setSavedQuery={jest.fn()}
  486. />,
  487. {
  488. router: router,
  489. organization,
  490. }
  491. );
  492. await waitFor(() => {
  493. expect(mockRequests.measurementsMetaMock).toHaveBeenCalled();
  494. });
  495. expect(mockRequests.eventsResultsMock).toHaveBeenCalledTimes(1);
  496. });
  497. it('does not need confirmation with to few projects', async function () {
  498. const organization = OrganizationFixture({
  499. features: ['discover-basic'],
  500. });
  501. const {router} = initializeOrg({
  502. organization,
  503. router: {
  504. location: {
  505. query: {
  506. ...generateFields(),
  507. statsPeriod: '90d',
  508. project: [1, 2, 3, 4].map(String),
  509. },
  510. },
  511. },
  512. });
  513. ProjectsStore.loadInitialData([ProjectFixture()]);
  514. const mockRequests = renderMockRequests();
  515. render(
  516. <Results
  517. organization={organization}
  518. location={router.location}
  519. router={router}
  520. loading={false}
  521. setSavedQuery={jest.fn()}
  522. />,
  523. {
  524. router: router,
  525. organization,
  526. }
  527. );
  528. await waitFor(() => {
  529. expect(mockRequests.measurementsMetaMock).toHaveBeenCalled();
  530. });
  531. expect(mockRequests.eventsResultsMock).toHaveBeenCalledTimes(1);
  532. });
  533. it('creates event view from saved query', async function () {
  534. const organization = OrganizationFixture({
  535. features,
  536. slug: 'org-slug',
  537. });
  538. const {router} = initializeOrg({
  539. organization,
  540. router: {
  541. location: {query: {id: '1', statsPeriod: '24h'}},
  542. },
  543. });
  544. ProjectsStore.loadInitialData([ProjectFixture()]);
  545. const mockRequests = renderMockRequests();
  546. render(
  547. <Results
  548. organization={organization}
  549. location={router.location}
  550. router={router}
  551. loading={false}
  552. setSavedQuery={jest.fn()}
  553. />,
  554. {
  555. router: router,
  556. organization,
  557. }
  558. );
  559. await waitFor(() => expect(mockRequests.mockVisit).toHaveBeenCalled());
  560. expect(screen.getByRole('link', {name: 'timestamp'})).toHaveAttribute(
  561. 'href',
  562. 'undefined?field=title&field=event.type&field=project&field=user.display&field=timestamp&id=1&name=new&query=&sort=-timestamp&statsPeriod=24h&topEvents=5'
  563. );
  564. expect(screen.getByRole('link', {name: 'project'})).toHaveAttribute(
  565. 'href',
  566. 'undefined?field=title&field=event.type&field=project&field=user.display&field=timestamp&id=1&name=new&query=&sort=-project&statsPeriod=24h&topEvents=5'
  567. );
  568. // NOTE: This uses a legacy redirect for project event to the issue group event link
  569. expect(screen.getByRole('link', {name: 'deadbeef'})).toHaveAttribute(
  570. 'href',
  571. '/org-slug/project-slug/events/deadbeef/?id=1&referrer=discover-events-table&statsPeriod=24h'
  572. );
  573. expect(screen.getByRole('link', {name: 'user.display'})).toHaveAttribute(
  574. 'href',
  575. 'undefined?field=title&field=event.type&field=project&field=user.display&field=timestamp&id=1&name=new&query=&sort=user.display&statsPeriod=24h&topEvents=5'
  576. );
  577. expect(screen.getByRole('link', {name: 'title'})).toHaveAttribute(
  578. 'href',
  579. 'undefined?field=title&field=event.type&field=project&field=user.display&field=timestamp&id=1&name=new&query=&sort=-title&statsPeriod=24h&topEvents=5'
  580. );
  581. });
  582. it('overrides saved query params with location query params', async function () {
  583. const organization = OrganizationFixture({
  584. features,
  585. slug: 'org-slug',
  586. });
  587. const {router} = initializeOrg({
  588. organization,
  589. router: {
  590. location: {
  591. query: {
  592. id: '1',
  593. statsPeriod: '7d',
  594. project: ['2'],
  595. environment: ['production'],
  596. },
  597. },
  598. },
  599. });
  600. ProjectsStore.loadInitialData([ProjectFixture()]);
  601. const mockRequests = renderMockRequests();
  602. render(
  603. <Results
  604. organization={organization}
  605. location={router.location}
  606. router={router}
  607. loading={false}
  608. setSavedQuery={jest.fn()}
  609. />,
  610. {
  611. router: router,
  612. organization,
  613. }
  614. );
  615. await waitFor(() => expect(mockRequests.mockVisit).toHaveBeenCalled());
  616. expect(screen.getByRole('link', {name: 'timestamp'})).toHaveAttribute(
  617. 'href',
  618. 'undefined?environment=production&field=title&field=event.type&field=project&field=user.display&field=timestamp&id=1&name=new&project=2&query=&sort=-timestamp&statsPeriod=7d&topEvents=5'
  619. );
  620. });
  621. it('updates chart whenever yAxis parameter changes', async function () {
  622. const organization = OrganizationFixture({
  623. features,
  624. });
  625. const {router} = initializeOrg({
  626. organization,
  627. router: {
  628. location: {query: {...generateFields(), yAxis: 'count()'}},
  629. },
  630. });
  631. ProjectsStore.loadInitialData([ProjectFixture()]);
  632. const {eventsStatsMock, measurementsMetaMock} = renderMockRequests();
  633. const {rerender} = render(
  634. <Results
  635. organization={organization}
  636. location={router.location}
  637. router={router}
  638. loading={false}
  639. setSavedQuery={jest.fn()}
  640. />,
  641. {
  642. router: router,
  643. organization,
  644. }
  645. );
  646. // Should load events once
  647. expect(eventsStatsMock).toHaveBeenCalledTimes(1);
  648. expect(eventsStatsMock).toHaveBeenNthCalledWith(
  649. 1,
  650. '/organizations/org-slug/events-stats/',
  651. expect.objectContaining({
  652. query: expect.objectContaining({
  653. statsPeriod: '14d',
  654. yAxis: ['count()'],
  655. }),
  656. })
  657. );
  658. await waitFor(() => {
  659. expect(measurementsMetaMock).toHaveBeenCalled();
  660. });
  661. // Update location simulating a browser back button action
  662. rerender(
  663. <Results
  664. organization={organization}
  665. location={{
  666. ...router.location,
  667. query: {...generateFields(), yAxis: 'count_unique(user)'},
  668. }}
  669. router={router}
  670. loading={false}
  671. setSavedQuery={jest.fn()}
  672. />
  673. );
  674. // Should load events again
  675. expect(eventsStatsMock).toHaveBeenCalledTimes(2);
  676. expect(eventsStatsMock).toHaveBeenNthCalledWith(
  677. 2,
  678. '/organizations/org-slug/events-stats/',
  679. expect.objectContaining({
  680. query: expect.objectContaining({
  681. statsPeriod: '14d',
  682. yAxis: ['count_unique(user)'],
  683. }),
  684. })
  685. );
  686. await waitFor(() => {
  687. expect(measurementsMetaMock).toHaveBeenCalled();
  688. });
  689. });
  690. it('updates chart whenever display parameter changes', async function () {
  691. const organization = OrganizationFixture({
  692. features,
  693. });
  694. const {router} = initializeOrg({
  695. organization,
  696. router: {
  697. location: {query: {...generateFields(), display: 'default', yAxis: 'count()'}},
  698. },
  699. });
  700. const {eventsStatsMock, measurementsMetaMock} = renderMockRequests();
  701. ProjectsStore.loadInitialData([ProjectFixture()]);
  702. const {rerender} = render(
  703. <Results
  704. organization={organization}
  705. location={router.location}
  706. router={router}
  707. loading={false}
  708. setSavedQuery={jest.fn()}
  709. />,
  710. {
  711. router: router,
  712. organization,
  713. }
  714. );
  715. // Should load events once
  716. expect(eventsStatsMock).toHaveBeenCalledTimes(1);
  717. expect(eventsStatsMock).toHaveBeenNthCalledWith(
  718. 1,
  719. '/organizations/org-slug/events-stats/',
  720. expect.objectContaining({
  721. query: expect.objectContaining({
  722. statsPeriod: '14d',
  723. yAxis: ['count()'],
  724. }),
  725. })
  726. );
  727. await waitFor(() => {
  728. expect(measurementsMetaMock).toHaveBeenCalled();
  729. });
  730. // Update location simulating a browser back button action
  731. rerender(
  732. <Results
  733. organization={organization}
  734. location={{
  735. ...router.location,
  736. query: {...generateFields(), display: 'previous', yAxis: 'count()'},
  737. }}
  738. router={router}
  739. loading={false}
  740. setSavedQuery={jest.fn()}
  741. />
  742. );
  743. // Should load events again
  744. expect(eventsStatsMock).toHaveBeenCalledTimes(2);
  745. expect(eventsStatsMock).toHaveBeenNthCalledWith(
  746. 2,
  747. '/organizations/org-slug/events-stats/',
  748. expect.objectContaining({
  749. query: expect.objectContaining({
  750. statsPeriod: '28d',
  751. yAxis: ['count()'],
  752. }),
  753. })
  754. );
  755. await waitFor(() => {
  756. expect(measurementsMetaMock).toHaveBeenCalled();
  757. });
  758. });
  759. it('updates chart whenever display and yAxis parameters change', async function () {
  760. const organization = OrganizationFixture({
  761. features,
  762. });
  763. const {router} = initializeOrg({
  764. organization,
  765. router: {
  766. location: {query: {...generateFields(), display: 'default', yAxis: 'count()'}},
  767. },
  768. });
  769. const {eventsStatsMock, measurementsMetaMock} = renderMockRequests();
  770. ProjectsStore.loadInitialData([ProjectFixture()]);
  771. const {rerender} = render(
  772. <Results
  773. organization={organization}
  774. location={router.location}
  775. router={router}
  776. loading={false}
  777. setSavedQuery={jest.fn()}
  778. />,
  779. {
  780. router: router,
  781. organization,
  782. }
  783. );
  784. // Should load events once
  785. expect(eventsStatsMock).toHaveBeenCalledTimes(1);
  786. expect(eventsStatsMock).toHaveBeenNthCalledWith(
  787. 1,
  788. '/organizations/org-slug/events-stats/',
  789. expect.objectContaining({
  790. query: expect.objectContaining({
  791. statsPeriod: '14d',
  792. yAxis: ['count()'],
  793. }),
  794. })
  795. );
  796. await waitFor(() => {
  797. expect(measurementsMetaMock).toHaveBeenCalled();
  798. });
  799. // Update location simulating a browser back button action
  800. rerender(
  801. <Results
  802. organization={organization}
  803. location={{
  804. ...router.location,
  805. query: {
  806. ...generateFields(),
  807. display: 'previous',
  808. yAxis: 'count_unique(user)',
  809. },
  810. }}
  811. router={router}
  812. loading={false}
  813. setSavedQuery={jest.fn()}
  814. />
  815. );
  816. // Should load events again
  817. expect(eventsStatsMock).toHaveBeenCalledTimes(2);
  818. expect(eventsStatsMock).toHaveBeenNthCalledWith(
  819. 2,
  820. '/organizations/org-slug/events-stats/',
  821. expect.objectContaining({
  822. query: expect.objectContaining({
  823. statsPeriod: '28d',
  824. yAxis: ['count_unique(user)'],
  825. }),
  826. })
  827. );
  828. await waitFor(() => {
  829. expect(measurementsMetaMock).toHaveBeenCalled();
  830. });
  831. });
  832. it('appends tag value to existing query when clicked', async function () {
  833. const organization = OrganizationFixture({
  834. features,
  835. });
  836. const {router} = initializeOrg({
  837. organization,
  838. router: {
  839. location: {query: {...generateFields(), display: 'default', yAxis: 'count'}},
  840. },
  841. });
  842. const mockRequests = renderMockRequests();
  843. ProjectsStore.loadInitialData([ProjectFixture()]);
  844. render(
  845. <Results
  846. organization={organization}
  847. location={router.location}
  848. router={router}
  849. loading={false}
  850. setSavedQuery={jest.fn()}
  851. />,
  852. {
  853. router: router,
  854. organization,
  855. }
  856. );
  857. await userEvent.click(await screen.findByRole('button', {name: 'Show Tags'}));
  858. await waitFor(() => expect(mockRequests.eventFacetsMock).toHaveBeenCalled());
  859. // TODO(edward): update this to be less generic
  860. await userEvent.click(screen.getByText('environment'));
  861. await userEvent.click(screen.getByText('foo'));
  862. // since environment collides with the environment field, it is wrapped with `tags[...]`
  863. expect(
  864. await screen.findByRole('link', {
  865. name: 'environment, dev, 100% of all events. View events with this tag value.',
  866. })
  867. ).toBeInTheDocument();
  868. expect(
  869. screen.getByRole('link', {
  870. name: 'foo, bar, 100% of all events. View events with this tag value.',
  871. })
  872. ).toBeInTheDocument();
  873. });
  874. it('respects pinned filters for prebuilt queries', async function () {
  875. const organization = OrganizationFixture({
  876. features: [...features, 'global-views'],
  877. });
  878. const {router} = initializeOrg({
  879. organization,
  880. router: {
  881. location: {query: {...generateFields(), display: 'default', yAxis: 'count'}},
  882. },
  883. });
  884. renderMockRequests();
  885. jest.spyOn(PageFilterPersistence, 'getPageFilterStorage').mockReturnValue({
  886. state: {
  887. project: [1],
  888. environment: [],
  889. start: null,
  890. end: null,
  891. period: '14d',
  892. utc: null,
  893. },
  894. pinnedFilters: new Set(['projects']),
  895. });
  896. ProjectsStore.loadInitialData([ProjectFixture({id: '1', slug: 'Pinned Project'})]);
  897. render(
  898. <Results
  899. organization={organization}
  900. location={router.location}
  901. router={router}
  902. loading={false}
  903. setSavedQuery={jest.fn()}
  904. />,
  905. {router: router, organization}
  906. );
  907. const projectPageFilter = await screen.findByTestId('page-filter-project-selector');
  908. expect(projectPageFilter).toHaveTextContent('All Projects');
  909. });
  910. it('displays tip when events response contains a tip', async function () {
  911. renderMockRequests();
  912. MockApiClient.addMockResponse({
  913. url: '/organizations/org-slug/events/',
  914. body: {
  915. meta: {
  916. fields: {},
  917. tips: {query: 'this is a tip'},
  918. },
  919. data: [],
  920. },
  921. });
  922. const organization = OrganizationFixture({
  923. features,
  924. });
  925. const {router} = initializeOrg({
  926. organization,
  927. router: {
  928. location: {query: {...generateFields(), yAxis: 'count()'}},
  929. },
  930. });
  931. ProjectsStore.loadInitialData([ProjectFixture()]);
  932. render(
  933. <Results
  934. organization={organization}
  935. location={router.location}
  936. router={router}
  937. loading={false}
  938. setSavedQuery={jest.fn()}
  939. />,
  940. {router: router, organization}
  941. );
  942. expect(await screen.findByText('this is a tip')).toBeInTheDocument();
  943. });
  944. it('renders metric fallback alert', async function () {
  945. const organization = OrganizationFixture({
  946. features: ['discover-basic'],
  947. });
  948. const {router} = initializeOrg({
  949. organization,
  950. router: {
  951. location: {query: {fromMetric: 'true', id: '1'}},
  952. },
  953. });
  954. ProjectsStore.loadInitialData([ProjectFixture()]);
  955. renderMockRequests();
  956. render(
  957. <Results
  958. organization={organization}
  959. location={router.location}
  960. router={router}
  961. loading={false}
  962. setSavedQuery={jest.fn()}
  963. />,
  964. {
  965. router: router,
  966. organization,
  967. }
  968. );
  969. expect(
  970. await screen.findByText(
  971. /You've navigated to this page from a performance metric widget generated from processed events/
  972. )
  973. ).toBeInTheDocument();
  974. });
  975. it('renders unparameterized data banner', async function () {
  976. const organization = OrganizationFixture({
  977. features: ['discover-basic'],
  978. });
  979. const {router} = initializeOrg({
  980. organization,
  981. router: {
  982. location: {query: {showUnparameterizedBanner: 'true', id: '1'}},
  983. },
  984. });
  985. ProjectsStore.loadInitialData([ProjectFixture()]);
  986. renderMockRequests();
  987. render(
  988. <Results
  989. organization={organization}
  990. location={router.location}
  991. router={router}
  992. loading={false}
  993. setSavedQuery={jest.fn()}
  994. />,
  995. {
  996. router: router,
  997. organization,
  998. }
  999. );
  1000. expect(
  1001. await screen.findByText(/These are unparameterized transactions/)
  1002. ).toBeInTheDocument();
  1003. });
  1004. it('updates the homepage query with up to date eventView when Set as Default is clicked', async () => {
  1005. const mockHomepageUpdate = MockApiClient.addMockResponse({
  1006. url: '/organizations/org-slug/discover/homepage/',
  1007. method: 'PUT',
  1008. statusCode: 200,
  1009. });
  1010. const organization = OrganizationFixture({
  1011. features: ['discover-basic', 'discover-query'],
  1012. });
  1013. const {router} = initializeOrg({
  1014. organization,
  1015. router: {
  1016. // These fields take priority and should be sent in the request
  1017. location: {query: {field: ['title', 'user'], id: '1'}},
  1018. },
  1019. });
  1020. ProjectsStore.loadInitialData([ProjectFixture()]);
  1021. renderMockRequests();
  1022. render(
  1023. <Results
  1024. organization={organization}
  1025. location={router.location}
  1026. router={router}
  1027. loading={false}
  1028. setSavedQuery={jest.fn()}
  1029. />,
  1030. {router: router, organization}
  1031. );
  1032. await waitFor(() =>
  1033. expect(screen.getByRole('button', {name: /set as default/i})).toBeEnabled()
  1034. );
  1035. await userEvent.click(screen.getByText('Set as Default'));
  1036. expect(mockHomepageUpdate).toHaveBeenCalledWith(
  1037. '/organizations/org-slug/discover/homepage/',
  1038. expect.objectContaining({
  1039. data: expect.objectContaining({
  1040. fields: ['title', 'user'],
  1041. }),
  1042. })
  1043. );
  1044. });
  1045. it('Changes the Use as Discover button to a reset button for saved query', async () => {
  1046. renderMockRequests();
  1047. MockApiClient.addMockResponse({
  1048. url: '/organizations/org-slug/discover/homepage/',
  1049. method: 'PUT',
  1050. statusCode: 200,
  1051. body: {
  1052. id: '2',
  1053. name: '',
  1054. projects: [],
  1055. version: 2,
  1056. expired: false,
  1057. dateCreated: '2021-04-08T17:53:25.195782Z',
  1058. dateUpdated: '2021-04-09T12:13:18.567264Z',
  1059. createdBy: {
  1060. id: '2',
  1061. },
  1062. environment: [],
  1063. fields: ['title', 'event.type', 'project', 'user.display', 'timestamp'],
  1064. widths: ['-1', '-1', '-1', '-1', '-1'],
  1065. range: '24h',
  1066. orderby: '-user.display',
  1067. queryDataset: 'discover',
  1068. },
  1069. });
  1070. const organization = OrganizationFixture({
  1071. features: ['discover-basic', 'discover-query'],
  1072. });
  1073. const {router} = initializeOrg({
  1074. organization,
  1075. router: {
  1076. location: {query: {id: '1'}},
  1077. },
  1078. });
  1079. ProjectsStore.loadInitialData([ProjectFixture()]);
  1080. renderMockRequests();
  1081. const {rerender} = render(
  1082. <Results
  1083. loading={false}
  1084. setSavedQuery={jest.fn()}
  1085. organization={organization}
  1086. location={router.location}
  1087. router={router}
  1088. />,
  1089. {router: router, organization}
  1090. );
  1091. await waitFor(() =>
  1092. expect(screen.getByRole('button', {name: /set as default/i})).toBeEnabled()
  1093. );
  1094. await userEvent.click(screen.getByText('Set as Default'));
  1095. expect(await screen.findByText('Remove Default')).toBeInTheDocument();
  1096. await userEvent.click(screen.getByText('Total Period'));
  1097. await userEvent.click(screen.getByText('Previous Period'));
  1098. const rerenderData = initializeOrg({
  1099. organization,
  1100. router: {
  1101. location: {query: {...router.location.query, display: 'previous'}},
  1102. },
  1103. });
  1104. rerender(
  1105. <Results
  1106. loading={false}
  1107. setSavedQuery={jest.fn()}
  1108. organization={organization}
  1109. location={rerenderData.router.location}
  1110. router={rerenderData.router}
  1111. />
  1112. );
  1113. screen.getByText('Previous Period');
  1114. expect(await screen.findByText('Set as Default')).toBeInTheDocument();
  1115. });
  1116. it('Changes the Use as Discover button to a reset button for prebuilt query', async () => {
  1117. const organization = OrganizationFixture({
  1118. features: ['discover-basic', 'discover-query'],
  1119. });
  1120. MockApiClient.addMockResponse({
  1121. url: '/organizations/org-slug/discover/homepage/',
  1122. method: 'PUT',
  1123. statusCode: 200,
  1124. body: {...getTransactionViews(organization)[0], name: ''},
  1125. });
  1126. const {router} = initializeOrg({
  1127. organization,
  1128. router: {
  1129. location: {
  1130. ...LocationFixture(),
  1131. query: {
  1132. ...EventView.fromNewQueryWithLocation(
  1133. getTransactionViews(organization)[0],
  1134. LocationFixture()
  1135. ).generateQueryStringObject(),
  1136. },
  1137. },
  1138. },
  1139. });
  1140. ProjectsStore.loadInitialData([ProjectFixture()]);
  1141. renderMockRequests();
  1142. const {rerender} = render(
  1143. <Results
  1144. organization={organization}
  1145. location={router.location}
  1146. router={router}
  1147. loading={false}
  1148. setSavedQuery={jest.fn()}
  1149. />,
  1150. {router: router, organization}
  1151. );
  1152. await screen.findAllByText(getTransactionViews(organization)[0].name);
  1153. await userEvent.click(screen.getByText('Set as Default'));
  1154. expect(await screen.findByText('Remove Default')).toBeInTheDocument();
  1155. await userEvent.click(screen.getByText('Total Period'));
  1156. await userEvent.click(screen.getByText('Previous Period'));
  1157. const rerenderData = initializeOrg({
  1158. organization,
  1159. router: {
  1160. location: {query: {...router.location.query, display: 'previous'}},
  1161. },
  1162. });
  1163. rerender(
  1164. <Results
  1165. organization={organization}
  1166. location={rerenderData.router.location}
  1167. router={rerenderData.router}
  1168. loading={false}
  1169. setSavedQuery={jest.fn()}
  1170. />
  1171. );
  1172. screen.getByText('Previous Period');
  1173. expect(await screen.findByText('Set as Default')).toBeInTheDocument();
  1174. });
  1175. it('links back to the homepage through the Discover breadcrumb', async () => {
  1176. const organization = OrganizationFixture({
  1177. features: ['discover-basic', 'discover-query'],
  1178. });
  1179. const {router} = initializeOrg({
  1180. organization,
  1181. router: {
  1182. location: {query: {id: '1'}},
  1183. },
  1184. });
  1185. ProjectsStore.loadInitialData([ProjectFixture()]);
  1186. const {measurementsMetaMock} = renderMockRequests();
  1187. render(
  1188. <Results
  1189. organization={organization}
  1190. location={router.location}
  1191. router={router}
  1192. loading={false}
  1193. setSavedQuery={jest.fn()}
  1194. />,
  1195. {router: router, organization}
  1196. );
  1197. await waitFor(() => {
  1198. expect(measurementsMetaMock).toHaveBeenCalled();
  1199. });
  1200. expect(screen.getByText('Discover')).toHaveAttribute(
  1201. 'href',
  1202. expect.stringMatching(new RegExp('^/organizations/org-slug/discover/homepage/'))
  1203. );
  1204. });
  1205. it('links back to the Saved Queries through the Saved Queries breadcrumb', async () => {
  1206. const organization = OrganizationFixture({
  1207. features: ['discover-basic', 'discover-query'],
  1208. });
  1209. const {router} = initializeOrg({
  1210. organization,
  1211. router: {
  1212. location: {query: {id: '1'}},
  1213. },
  1214. });
  1215. const {measurementsMetaMock} = renderMockRequests();
  1216. render(
  1217. <Results
  1218. organization={organization}
  1219. location={router.location}
  1220. router={router}
  1221. loading={false}
  1222. setSavedQuery={jest.fn()}
  1223. />,
  1224. {router: router, organization}
  1225. );
  1226. await waitFor(() => {
  1227. expect(measurementsMetaMock).toHaveBeenCalled();
  1228. });
  1229. expect(screen.getByRole('link', {name: 'Saved Queries'})).toHaveAttribute(
  1230. 'href',
  1231. expect.stringMatching(new RegExp('^/organizations/org-slug/discover/queries/'))
  1232. );
  1233. });
  1234. it('allows users to Set As Default on the All Events query', async () => {
  1235. const organization = OrganizationFixture({
  1236. features: ['discover-basic', 'discover-query'],
  1237. });
  1238. const {router} = initializeOrg({
  1239. organization,
  1240. router: {
  1241. location: {
  1242. ...LocationFixture(),
  1243. query: {
  1244. ...EventView.fromNewQueryWithLocation(
  1245. DEFAULT_EVENT_VIEW,
  1246. LocationFixture()
  1247. ).generateQueryStringObject(),
  1248. },
  1249. },
  1250. },
  1251. });
  1252. ProjectsStore.loadInitialData([ProjectFixture()]);
  1253. const {measurementsMetaMock} = renderMockRequests();
  1254. render(
  1255. <Results
  1256. organization={organization}
  1257. location={router.location}
  1258. router={router}
  1259. loading={false}
  1260. setSavedQuery={jest.fn()}
  1261. />,
  1262. {router: router, organization}
  1263. );
  1264. await waitFor(() => {
  1265. expect(measurementsMetaMock).toHaveBeenCalled();
  1266. });
  1267. expect(screen.getByTestId('set-as-default')).toBeEnabled();
  1268. });
  1269. it("doesn't render sample data alert", async function () {
  1270. const organization = OrganizationFixture({
  1271. features: ['discover-basic', 'discover-query'],
  1272. });
  1273. const {router} = initializeOrg({
  1274. organization,
  1275. router: {
  1276. location: {
  1277. ...LocationFixture(),
  1278. query: {
  1279. ...EventView.fromNewQueryWithLocation(
  1280. {...DEFAULT_EVENT_VIEW, query: 'event.type:error'},
  1281. LocationFixture()
  1282. ).generateQueryStringObject(),
  1283. },
  1284. },
  1285. },
  1286. });
  1287. const {measurementsMetaMock} = renderMockRequests();
  1288. render(
  1289. <Results
  1290. organization={organization}
  1291. location={router.location}
  1292. router={router}
  1293. loading={false}
  1294. setSavedQuery={jest.fn()}
  1295. />,
  1296. {router: router, organization}
  1297. );
  1298. await waitFor(() => {
  1299. expect(measurementsMetaMock).toHaveBeenCalled();
  1300. });
  1301. expect(screen.queryByText(/Based on your search criteria/)).not.toBeInTheDocument();
  1302. });
  1303. it('uses split decision to populate dataset selector', async function () {
  1304. const organization = OrganizationFixture({
  1305. features: [
  1306. 'discover-basic',
  1307. 'discover-query',
  1308. 'performance-discover-dataset-selector',
  1309. ],
  1310. });
  1311. const {router} = initializeOrg({
  1312. organization,
  1313. router: {
  1314. location: {query: {id: '1'}},
  1315. },
  1316. });
  1317. ProjectsStore.loadInitialData([ProjectFixture()]);
  1318. const mockRequests = renderMockRequests();
  1319. render(
  1320. <Results
  1321. organization={organization}
  1322. location={router.location}
  1323. router={router}
  1324. loading={false}
  1325. setSavedQuery={jest.fn()}
  1326. />,
  1327. {
  1328. router: router,
  1329. organization,
  1330. }
  1331. );
  1332. await waitFor(() => {
  1333. expect(mockRequests.measurementsMetaMock).toHaveBeenCalled();
  1334. });
  1335. expect(mockRequests.eventsResultsMock).toHaveBeenCalledTimes(1);
  1336. await waitFor(() => {
  1337. expect(
  1338. screen.getByRole('button', {name: 'Dataset Transactions'})
  1339. ).toBeInTheDocument();
  1340. });
  1341. expect(
  1342. screen.getByText(
  1343. "We're splitting our datasets up to make it a bit easier to digest. We defaulted this query to Transactions. Edit as you see fit."
  1344. )
  1345. ).toBeInTheDocument();
  1346. expect(screen.queryByText('Save Changes')).not.toBeInTheDocument();
  1347. });
  1348. it('calls events endpoint with the right dataset', async function () {
  1349. const organization = OrganizationFixture({
  1350. features: [
  1351. 'discover-basic',
  1352. 'discover-query',
  1353. 'performance-discover-dataset-selector',
  1354. ],
  1355. });
  1356. const {router} = initializeOrg({
  1357. organization,
  1358. router: {
  1359. location: {query: {id: '1'}},
  1360. },
  1361. });
  1362. ProjectsStore.loadInitialData([ProjectFixture()]);
  1363. const mockRequests = renderMockRequests();
  1364. MockApiClient.addMockResponse({
  1365. url: '/organizations/org-slug/discover/saved/1/',
  1366. method: 'GET',
  1367. statusCode: 200,
  1368. body: {
  1369. id: '1',
  1370. name: 'new',
  1371. projects: [],
  1372. version: 2,
  1373. expired: false,
  1374. dateCreated: '2021-04-08T17:53:25.195782Z',
  1375. dateUpdated: '2021-04-09T12:13:18.567264Z',
  1376. createdBy: {
  1377. id: '2',
  1378. },
  1379. environment: [],
  1380. fields: ['title', 'event.type', 'project', 'user.display', 'timestamp'],
  1381. widths: ['-1', '-1', '-1', '-1', '-1'],
  1382. range: '24h',
  1383. orderby: '-user.display',
  1384. queryDataset: 'error-events',
  1385. },
  1386. });
  1387. render(
  1388. <Results
  1389. organization={organization}
  1390. location={router.location}
  1391. router={router}
  1392. loading={false}
  1393. setSavedQuery={jest.fn()}
  1394. />,
  1395. {
  1396. router: router,
  1397. organization,
  1398. }
  1399. );
  1400. await waitFor(() => {
  1401. expect(mockRequests.measurementsMetaMock).toHaveBeenCalled();
  1402. });
  1403. expect(mockRequests.eventsResultsMock).toHaveBeenCalledTimes(1);
  1404. expect(screen.getByRole('button', {name: 'Dataset Errors'})).toBeInTheDocument();
  1405. expect(mockRequests.eventsStatsMock).toHaveBeenCalledWith(
  1406. '/organizations/org-slug/events-stats/',
  1407. expect.objectContaining({
  1408. query: expect.objectContaining({
  1409. dataset: 'errors',
  1410. }),
  1411. })
  1412. );
  1413. expect(mockRequests.eventsResultsMock).toHaveBeenCalledWith(
  1414. '/organizations/org-slug/events/',
  1415. expect.objectContaining({
  1416. query: expect.objectContaining({
  1417. dataset: 'errors',
  1418. }),
  1419. })
  1420. );
  1421. expect(mockRequests.eventsMetaMock).toHaveBeenCalledWith(
  1422. '/organizations/org-slug/events-meta/',
  1423. expect.objectContaining({
  1424. query: expect.objectContaining({
  1425. dataset: 'errors',
  1426. }),
  1427. })
  1428. );
  1429. });
  1430. it('does not automatically append dataset with selector feature disabled', async function () {
  1431. const organization = OrganizationFixture({
  1432. features: ['discover-basic', 'discover-query'],
  1433. });
  1434. const {router} = initializeOrg({
  1435. organization,
  1436. router: {
  1437. location: {query: {id: '1'}},
  1438. },
  1439. });
  1440. ProjectsStore.loadInitialData([ProjectFixture()]);
  1441. const mockRequests = renderMockRequests();
  1442. MockApiClient.addMockResponse({
  1443. url: '/organizations/org-slug/discover/saved/1/',
  1444. method: 'GET',
  1445. statusCode: 200,
  1446. body: {
  1447. id: '1',
  1448. name: 'new',
  1449. projects: [],
  1450. version: 2,
  1451. expired: false,
  1452. dateCreated: '2021-04-08T17:53:25.195782Z',
  1453. dateUpdated: '2021-04-09T12:13:18.567264Z',
  1454. createdBy: {
  1455. id: '2',
  1456. },
  1457. environment: [],
  1458. fields: ['title', 'event.type', 'project', 'user.display', 'timestamp'],
  1459. widths: ['-1', '-1', '-1', '-1', '-1'],
  1460. range: '24h',
  1461. orderby: '-user.display',
  1462. queryDataset: 'error-events',
  1463. },
  1464. });
  1465. render(
  1466. <Results
  1467. organization={organization}
  1468. location={router.location}
  1469. router={router}
  1470. loading={false}
  1471. setSavedQuery={jest.fn()}
  1472. />,
  1473. {
  1474. router: router,
  1475. organization,
  1476. }
  1477. );
  1478. await waitFor(() => {
  1479. expect(mockRequests.measurementsMetaMock).toHaveBeenCalled();
  1480. });
  1481. expect(mockRequests.eventsResultsMock).toHaveBeenCalledTimes(1);
  1482. expect(
  1483. screen.queryByRole('button', {name: 'Dataset Errors'})
  1484. ).not.toBeInTheDocument();
  1485. expect(mockRequests.eventsStatsMock).toHaveBeenCalledWith(
  1486. '/organizations/org-slug/events-stats/',
  1487. expect.objectContaining({
  1488. query: expect.not.objectContaining({
  1489. dataset: 'errors',
  1490. }),
  1491. })
  1492. );
  1493. expect(mockRequests.eventsResultsMock).toHaveBeenCalledWith(
  1494. '/organizations/org-slug/events/',
  1495. expect.objectContaining({
  1496. query: expect.not.objectContaining({
  1497. dataset: 'errors',
  1498. }),
  1499. })
  1500. );
  1501. expect(mockRequests.eventsMetaMock).toHaveBeenCalledWith(
  1502. '/organizations/org-slug/events-meta/',
  1503. expect.objectContaining({
  1504. query: expect.not.objectContaining({
  1505. dataset: 'errors',
  1506. }),
  1507. })
  1508. );
  1509. });
  1510. it('shows the search history for the error dataset', async function () {
  1511. const organization = OrganizationFixture({
  1512. features: [
  1513. 'discover-basic',
  1514. 'discover-query',
  1515. 'performance-discover-dataset-selector',
  1516. ],
  1517. });
  1518. const {router} = initializeOrg({
  1519. organization,
  1520. router: {
  1521. location: {query: {id: '1'}},
  1522. },
  1523. });
  1524. ProjectsStore.loadInitialData([ProjectFixture()]);
  1525. renderMockRequests();
  1526. MockApiClient.addMockResponse({
  1527. url: '/organizations/org-slug/recent-searches/',
  1528. body: [
  1529. {
  1530. query: 'event.type:error',
  1531. },
  1532. ],
  1533. match: [
  1534. (_url, options) => {
  1535. return options.query?.type === SavedSearchType.ERROR;
  1536. },
  1537. ],
  1538. });
  1539. MockApiClient.addMockResponse({
  1540. url: '/organizations/org-slug/recent-searches/',
  1541. body: [
  1542. {
  1543. query: 'transaction.status:ok',
  1544. },
  1545. ],
  1546. match: [
  1547. (_url, options) => {
  1548. return options.query?.type === SavedSearchType.TRANSACTION;
  1549. },
  1550. ],
  1551. });
  1552. MockApiClient.addMockResponse({
  1553. url: '/organizations/org-slug/discover/saved/1/',
  1554. method: 'GET',
  1555. statusCode: 200,
  1556. body: {
  1557. id: '1',
  1558. name: 'new',
  1559. projects: [],
  1560. version: 2,
  1561. expired: false,
  1562. dateCreated: '2021-04-08T17:53:25.195782Z',
  1563. dateUpdated: '2021-04-09T12:13:18.567264Z',
  1564. createdBy: {
  1565. id: '2',
  1566. },
  1567. environment: [],
  1568. fields: ['title', 'event.type', 'project', 'user.display', 'timestamp'],
  1569. widths: ['-1', '-1', '-1', '-1', '-1'],
  1570. range: '24h',
  1571. orderby: '-user.display',
  1572. queryDataset: 'error-events',
  1573. },
  1574. });
  1575. render(
  1576. <Results
  1577. organization={organization}
  1578. location={router.location}
  1579. router={router}
  1580. loading={false}
  1581. setSavedQuery={jest.fn()}
  1582. />,
  1583. {
  1584. router: router,
  1585. organization,
  1586. }
  1587. );
  1588. await userEvent.click(
  1589. screen.getByPlaceholderText('Search for events, users, tags, and more')
  1590. );
  1591. expect(screen.getByTestId('filter-token')).toHaveTextContent('event.type:error');
  1592. });
  1593. it('shows the search history for the transaction dataset', async function () {
  1594. const organization = OrganizationFixture({
  1595. features: [
  1596. 'discover-basic',
  1597. 'discover-query',
  1598. 'performance-discover-dataset-selector',
  1599. ],
  1600. });
  1601. const {router} = initializeOrg({
  1602. organization,
  1603. router: {
  1604. location: {query: {id: '1'}},
  1605. },
  1606. });
  1607. ProjectsStore.loadInitialData([ProjectFixture()]);
  1608. renderMockRequests();
  1609. MockApiClient.addMockResponse({
  1610. url: '/organizations/org-slug/recent-searches/',
  1611. body: [
  1612. {
  1613. query: 'event.type:error',
  1614. },
  1615. ],
  1616. match: [
  1617. (_url, options) => {
  1618. return options.query?.type === SavedSearchType.ERROR;
  1619. },
  1620. ],
  1621. });
  1622. MockApiClient.addMockResponse({
  1623. url: '/organizations/org-slug/recent-searches/',
  1624. body: [
  1625. {
  1626. query: 'transaction.status:ok',
  1627. },
  1628. ],
  1629. match: [
  1630. (_url, options) => {
  1631. return options.query?.type === SavedSearchType.TRANSACTION;
  1632. },
  1633. ],
  1634. });
  1635. MockApiClient.addMockResponse({
  1636. url: '/organizations/org-slug/events/',
  1637. body: {
  1638. meta: {
  1639. fields: {
  1640. id: 'string',
  1641. title: 'string',
  1642. 'project.name': 'string',
  1643. timestamp: 'date',
  1644. 'user.id': 'string',
  1645. },
  1646. discoverSplitDecision: 'transaction-like',
  1647. },
  1648. data: [
  1649. {
  1650. trace: 'test',
  1651. id: 'deadbeef',
  1652. 'user.id': 'alberto leal',
  1653. title: eventTitle,
  1654. 'project.name': 'project-slug',
  1655. timestamp: '2019-05-23T22:12:48+00:00',
  1656. },
  1657. ],
  1658. },
  1659. });
  1660. MockApiClient.addMockResponse({
  1661. url: '/organizations/org-slug/discover/saved/1/',
  1662. method: 'GET',
  1663. statusCode: 200,
  1664. body: {
  1665. id: '1',
  1666. name: 'new',
  1667. projects: [],
  1668. version: 2,
  1669. expired: false,
  1670. dateCreated: '2021-04-08T17:53:25.195782Z',
  1671. dateUpdated: '2021-04-09T12:13:18.567264Z',
  1672. createdBy: {
  1673. id: '2',
  1674. },
  1675. environment: [],
  1676. fields: ['title', 'event.type', 'project', 'user.display', 'timestamp'],
  1677. widths: ['-1', '-1', '-1', '-1', '-1'],
  1678. range: '24h',
  1679. orderby: '-user.display',
  1680. queryDataset: 'transaction-like',
  1681. },
  1682. });
  1683. render(
  1684. <Results
  1685. organization={organization}
  1686. location={router.location}
  1687. router={router}
  1688. loading={false}
  1689. setSavedQuery={jest.fn()}
  1690. />,
  1691. {
  1692. router: router,
  1693. organization,
  1694. }
  1695. );
  1696. await userEvent.click(
  1697. screen.getByPlaceholderText('Search for events, users, tags, and more')
  1698. );
  1699. expect(screen.getByTestId('filter-token')).toHaveTextContent(
  1700. 'transaction.status:ok'
  1701. );
  1702. });
  1703. });
  1704. });