results.spec.jsx 47 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706
  1. import {browserHistory} from 'react-router';
  2. import {enforceActOnUseLegacyStoreHook, mountWithTheme} from 'sentry-test/enzyme';
  3. import {initializeOrg} from 'sentry-test/initializeOrg';
  4. import {act} from 'sentry-test/reactTestingLibrary';
  5. import {triggerPress} from 'sentry-test/utils';
  6. import * as PageFilterPersistence from 'sentry/components/organizations/pageFilters/persistence';
  7. import ProjectsStore from 'sentry/stores/projectsStore';
  8. import Results from 'sentry/views/eventsV2/results';
  9. import {OrganizationContext} from 'sentry/views/organizationContext';
  10. const FIELDS = [
  11. {
  12. field: 'title',
  13. },
  14. {
  15. field: 'timestamp',
  16. },
  17. {
  18. field: 'user',
  19. },
  20. {
  21. field: 'count()',
  22. },
  23. ];
  24. const generateFields = () => ({
  25. field: FIELDS.map(i => i.field),
  26. });
  27. describe('Results', function () {
  28. enforceActOnUseLegacyStoreHook();
  29. const eventTitle = 'Oh no something bad';
  30. let eventsResultsMock, eventsv2ResultsMock, mockSaved, eventsStatsMock, mockVisit;
  31. const mountWithThemeAndOrg = (component, opts, organization) =>
  32. mountWithTheme(component, {
  33. ...opts,
  34. wrappingComponent: ({children}) => (
  35. <OrganizationContext.Provider value={organization}>
  36. {children}
  37. </OrganizationContext.Provider>
  38. ),
  39. });
  40. beforeEach(function () {
  41. MockApiClient.addMockResponse({
  42. url: '/organizations/org-slug/projects/',
  43. body: [],
  44. });
  45. MockApiClient.addMockResponse({
  46. url: '/organizations/org-slug/projects-count/',
  47. body: {myProjects: 10, allProjects: 300},
  48. });
  49. MockApiClient.addMockResponse({
  50. url: '/organizations/org-slug/tags/',
  51. body: [],
  52. });
  53. eventsStatsMock = MockApiClient.addMockResponse({
  54. url: '/organizations/org-slug/events-stats/',
  55. body: {data: [[123, []]]},
  56. });
  57. MockApiClient.addMockResponse({
  58. url: '/organizations/org-slug/recent-searches/',
  59. body: [],
  60. });
  61. MockApiClient.addMockResponse({
  62. url: '/organizations/org-slug/recent-searches/',
  63. method: 'POST',
  64. body: [],
  65. });
  66. MockApiClient.addMockResponse({
  67. url: '/organizations/org-slug/releases/stats/',
  68. body: [],
  69. });
  70. const eventsV2ResultsMockBody = {
  71. meta: {
  72. id: 'string',
  73. title: 'string',
  74. 'project.name': 'string',
  75. timestamp: 'date',
  76. 'user.id': 'string',
  77. },
  78. data: [
  79. {
  80. id: 'deadbeef',
  81. 'user.id': 'alberto leal',
  82. title: eventTitle,
  83. 'project.name': 'project-slug',
  84. timestamp: '2019-05-23T22:12:48+00:00',
  85. },
  86. ],
  87. };
  88. const eventsResultsMockBody = {
  89. meta: {
  90. fields: {
  91. id: 'string',
  92. title: 'string',
  93. 'project.name': 'string',
  94. timestamp: 'date',
  95. 'user.id': 'string',
  96. },
  97. },
  98. data: [
  99. {
  100. id: 'deadbeef',
  101. 'user.id': 'alberto leal',
  102. title: eventTitle,
  103. 'project.name': 'project-slug',
  104. timestamp: '2019-05-23T22:12:48+00:00',
  105. },
  106. ],
  107. };
  108. eventsv2ResultsMock = MockApiClient.addMockResponse({
  109. url: '/organizations/org-slug/eventsv2/',
  110. body: eventsV2ResultsMockBody,
  111. });
  112. eventsResultsMock = MockApiClient.addMockResponse({
  113. url: '/organizations/org-slug/events/',
  114. body: eventsResultsMockBody,
  115. });
  116. MockApiClient.addMockResponse({
  117. url: '/organizations/org-slug/events-meta/',
  118. body: {
  119. count: 2,
  120. },
  121. });
  122. MockApiClient.addMockResponse({
  123. url: '/organizations/org-slug/events/project-slug:deadbeef/',
  124. method: 'GET',
  125. body: {
  126. id: '1234',
  127. size: 1200,
  128. eventID: 'deadbeef',
  129. title: 'Oh no something bad',
  130. message: 'It was not good',
  131. dateCreated: '2019-05-23T22:12:48+00:00',
  132. entries: [
  133. {
  134. type: 'message',
  135. message: 'bad stuff',
  136. data: {},
  137. },
  138. ],
  139. tags: [{key: 'browser', value: 'Firefox'}],
  140. },
  141. });
  142. MockApiClient.addMockResponse({
  143. url: '/organizations/org-slug/events-facets/',
  144. body: [
  145. {
  146. key: 'release',
  147. topValues: [{count: 3, value: 'abcd123', name: 'abcd123'}],
  148. },
  149. {
  150. key: 'environment',
  151. topValues: [{count: 2, value: 'dev', name: 'dev'}],
  152. },
  153. {
  154. key: 'foo',
  155. topValues: [{count: 1, value: 'bar', name: 'bar'}],
  156. },
  157. ],
  158. });
  159. mockVisit = MockApiClient.addMockResponse({
  160. url: '/organizations/org-slug/discover/saved/1/visit/',
  161. method: 'POST',
  162. body: [],
  163. statusCode: 200,
  164. });
  165. mockSaved = MockApiClient.addMockResponse({
  166. url: '/organizations/org-slug/discover/saved/1/',
  167. method: 'GET',
  168. statusCode: 200,
  169. body: {
  170. id: '1',
  171. name: 'new',
  172. projects: [],
  173. version: 2,
  174. expired: false,
  175. dateCreated: '2021-04-08T17:53:25.195782Z',
  176. dateUpdated: '2021-04-09T12:13:18.567264Z',
  177. createdBy: {
  178. id: '2',
  179. },
  180. environment: [],
  181. fields: ['title', 'event.type', 'project', 'user.display', 'timestamp'],
  182. widths: ['-1', '-1', '-1', '-1', '-1'],
  183. range: '24h',
  184. orderby: '-user.display',
  185. },
  186. });
  187. });
  188. afterEach(function () {
  189. MockApiClient.clearMockResponses();
  190. act(() => ProjectsStore.reset());
  191. });
  192. describe('EventsV2', function () {
  193. const features = ['discover-basic'];
  194. it('loads data when moving from an invalid to valid EventView', async function () {
  195. const organization = TestStubs.Organization({
  196. features,
  197. });
  198. // Start off with an invalid view (empty is invalid)
  199. const initialData = initializeOrg({
  200. organization,
  201. router: {
  202. location: {query: {query: 'tag:value'}},
  203. },
  204. });
  205. ProjectsStore.loadInitialData([TestStubs.Project()]);
  206. const wrapper = mountWithThemeAndOrg(
  207. <Results
  208. organization={organization}
  209. location={initialData.router.location}
  210. router={initialData.router}
  211. />,
  212. initialData.routerContext,
  213. organization
  214. );
  215. await tick();
  216. wrapper.update();
  217. // No request as eventview was invalid.
  218. expect(eventsv2ResultsMock).not.toHaveBeenCalled();
  219. // Should redirect and retain the old query value..
  220. expect(browserHistory.replace).toHaveBeenCalledWith(
  221. expect.objectContaining({
  222. pathname: '/organizations/org-slug/discover/results/',
  223. query: expect.objectContaining({
  224. query: 'tag:value',
  225. }),
  226. })
  227. );
  228. // Update location simulating a redirect.
  229. wrapper.setProps({location: {query: {...generateFields()}}});
  230. wrapper.update();
  231. // Should load events once
  232. expect(eventsv2ResultsMock).toHaveBeenCalled();
  233. });
  234. it('pagination cursor should be cleared when making a search', async function () {
  235. const organization = TestStubs.Organization({
  236. features,
  237. });
  238. const initialData = initializeOrg({
  239. organization,
  240. router: {
  241. location: {
  242. query: {
  243. ...generateFields(),
  244. cursor: '0%3A50%3A0',
  245. },
  246. },
  247. },
  248. });
  249. ProjectsStore.loadInitialData([TestStubs.Project()]);
  250. const wrapper = mountWithThemeAndOrg(
  251. <Results
  252. organization={organization}
  253. location={initialData.router.location}
  254. router={initialData.router}
  255. />,
  256. initialData.routerContext,
  257. organization
  258. );
  259. await tick();
  260. wrapper.update();
  261. // ensure cursor query string is initially present in the location
  262. expect(initialData.router.location).toEqual({
  263. query: {
  264. ...generateFields(),
  265. cursor: '0%3A50%3A0',
  266. },
  267. });
  268. // perform a search
  269. const search = wrapper.find('#smart-search-input').first();
  270. search.simulate('change', {target: {value: 'geo:canada'}}).simulate('submit', {
  271. preventDefault() {},
  272. });
  273. await tick();
  274. // should only be called with saved queries
  275. expect(mockVisit).not.toHaveBeenCalled();
  276. // cursor query string should be omitted from the query string
  277. expect(initialData.router.push).toHaveBeenCalledWith({
  278. pathname: undefined,
  279. query: {
  280. ...generateFields(),
  281. query: 'geo:canada',
  282. statsPeriod: '14d',
  283. },
  284. });
  285. wrapper.unmount();
  286. });
  287. it('renders a y-axis selector', async function () {
  288. const organization = TestStubs.Organization({
  289. features,
  290. });
  291. const initialData = initializeOrg({
  292. organization,
  293. router: {
  294. location: {query: {...generateFields(), yAxis: 'count()'}},
  295. },
  296. });
  297. ProjectsStore.loadInitialData([TestStubs.Project()]);
  298. const wrapper = mountWithThemeAndOrg(
  299. <Results
  300. organization={organization}
  301. location={initialData.router.location}
  302. router={initialData.router}
  303. />,
  304. initialData.routerContext,
  305. organization
  306. );
  307. // y-axis selector is last.
  308. const selector = wrapper.find('OptionSelector').last();
  309. // Open the selector
  310. act(() => {
  311. triggerPress(selector.find('button[aria-haspopup="listbox"]'));
  312. });
  313. await tick();
  314. wrapper.update();
  315. // Click one of the options.
  316. wrapper.find('Option').first().simulate('click');
  317. await tick();
  318. wrapper.update();
  319. const eventsRequest = wrapper.find('EventsChart');
  320. expect(eventsRequest.props().yAxis).toEqual(['count()']);
  321. wrapper.unmount();
  322. });
  323. it('renders a display selector', async function () {
  324. const organization = TestStubs.Organization({
  325. features,
  326. });
  327. const initialData = initializeOrg({
  328. organization,
  329. router: {
  330. location: {query: {...generateFields(), display: 'default', yAxis: 'count'}},
  331. },
  332. });
  333. const wrapper = mountWithThemeAndOrg(
  334. <Results
  335. organization={organization}
  336. location={initialData.router.location}
  337. router={initialData.router}
  338. />,
  339. initialData.routerContext,
  340. organization
  341. );
  342. act(() => ProjectsStore.loadInitialData([TestStubs.Project()]));
  343. await tick();
  344. wrapper.update();
  345. // display selector is first.
  346. const selector = wrapper.find('OptionSelector').first();
  347. // Open the selector
  348. act(() => {
  349. triggerPress(selector.find('button[aria-haspopup="listbox"]'));
  350. });
  351. await tick();
  352. wrapper.update();
  353. // Click the 'default' option.
  354. wrapper.find('Option').first().simulate('click');
  355. await tick();
  356. wrapper.update();
  357. const eventsRequest = wrapper.find('EventsChart').props();
  358. expect(eventsRequest.disableReleases).toEqual(false);
  359. expect(eventsRequest.disablePrevious).toEqual(true);
  360. wrapper.unmount();
  361. });
  362. it('excludes top5 options when plan does not include discover-query', async function () {
  363. const organization = TestStubs.Organization({
  364. features: ['discover-basic'],
  365. });
  366. const initialData = initializeOrg({
  367. organization,
  368. router: {
  369. location: {query: {...generateFields(), display: 'previous'}},
  370. },
  371. });
  372. ProjectsStore.loadInitialData([TestStubs.Project()]);
  373. const wrapper = mountWithThemeAndOrg(
  374. <Results
  375. organization={organization}
  376. location={initialData.router.location}
  377. router={initialData.router}
  378. />,
  379. initialData.routerContext,
  380. organization
  381. );
  382. // display selector is first.
  383. const selector = wrapper.find('OptionSelector').first();
  384. // Open the selector
  385. act(() => {
  386. triggerPress(selector.find('button[aria-haspopup="listbox"]'));
  387. });
  388. await tick();
  389. wrapper.update();
  390. // Make sure the top5 option isn't present
  391. const options = wrapper
  392. .find('Option [data-test-id]')
  393. .map(item => item.prop('data-test-id'));
  394. expect(options).not.toContain('top5');
  395. expect(options).not.toContain('dailytop5');
  396. expect(options).toContain('default');
  397. wrapper.unmount();
  398. });
  399. it('needs confirmation on long queries', async function () {
  400. const organization = TestStubs.Organization({
  401. features: ['discover-basic'],
  402. });
  403. const initialData = initializeOrg({
  404. organization,
  405. router: {
  406. location: {query: {...generateFields(), statsPeriod: '60d', project: '-1'}},
  407. },
  408. });
  409. const wrapper = mountWithThemeAndOrg(
  410. <Results
  411. organization={organization}
  412. location={initialData.router.location}
  413. router={initialData.router}
  414. />,
  415. initialData.routerContext,
  416. organization
  417. );
  418. await tick();
  419. const results = wrapper.find('Results');
  420. expect(results.state('needConfirmation')).toEqual(true);
  421. wrapper.unmount();
  422. });
  423. it('needs confirmation on long query with explicit projects', async function () {
  424. const organization = TestStubs.Organization({
  425. features: ['discover-basic'],
  426. });
  427. const initialData = initializeOrg({
  428. organization,
  429. router: {
  430. location: {
  431. query: {
  432. ...generateFields(),
  433. statsPeriod: '60d',
  434. project: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
  435. },
  436. },
  437. },
  438. });
  439. const wrapper = mountWithThemeAndOrg(
  440. <Results
  441. organization={organization}
  442. location={initialData.router.location}
  443. router={initialData.router}
  444. />,
  445. initialData.routerContext,
  446. organization
  447. );
  448. await tick();
  449. const results = wrapper.find('Results');
  450. expect(results.state('needConfirmation')).toEqual(true);
  451. wrapper.unmount();
  452. });
  453. it('does not need confirmation on short queries', async function () {
  454. const organization = TestStubs.Organization({
  455. features: ['discover-basic'],
  456. });
  457. const initialData = initializeOrg({
  458. organization,
  459. router: {
  460. location: {query: {...generateFields(), statsPeriod: '30d', project: '-1'}},
  461. },
  462. });
  463. const wrapper = mountWithThemeAndOrg(
  464. <Results
  465. organization={organization}
  466. location={initialData.router.location}
  467. router={initialData.router}
  468. />,
  469. initialData.routerContext,
  470. organization
  471. );
  472. await tick();
  473. const results = wrapper.find('Results');
  474. expect(results.state('needConfirmation')).toEqual(false);
  475. wrapper.unmount();
  476. });
  477. it('does not need confirmation with to few projects', async function () {
  478. const organization = TestStubs.Organization({
  479. features: ['discover-basic'],
  480. });
  481. const initialData = initializeOrg({
  482. organization,
  483. router: {
  484. location: {
  485. query: {...generateFields(), statsPeriod: '90d', project: [1, 2, 3, 4]},
  486. },
  487. },
  488. });
  489. const wrapper = mountWithThemeAndOrg(
  490. <Results
  491. organization={organization}
  492. location={initialData.router.location}
  493. router={initialData.router}
  494. />,
  495. initialData.routerContext,
  496. organization
  497. );
  498. await tick();
  499. const results = wrapper.find('Results');
  500. expect(results.state('needConfirmation')).toEqual(false);
  501. wrapper.unmount();
  502. });
  503. it('retrieves saved query', async function () {
  504. const organization = TestStubs.Organization({
  505. features,
  506. slug: 'org-slug',
  507. });
  508. const initialData = initializeOrg({
  509. organization,
  510. router: {
  511. location: {query: {id: '1', statsPeriod: '24h'}},
  512. },
  513. });
  514. const wrapper = mountWithThemeAndOrg(
  515. <Results
  516. organization={organization}
  517. location={initialData.router.location}
  518. router={initialData.router}
  519. />,
  520. initialData.routerContext,
  521. organization
  522. );
  523. await tick();
  524. const savedQuery = wrapper.find('SavedQueryAPI').state('savedQuery');
  525. expect(savedQuery.name).toEqual('new');
  526. expect(savedQuery.id).toEqual('1');
  527. expect(savedQuery.fields).toEqual([
  528. 'title',
  529. 'event.type',
  530. 'project',
  531. 'user.display',
  532. 'timestamp',
  533. ]);
  534. expect(savedQuery.projects).toEqual([]);
  535. expect(savedQuery.range).toEqual('24h');
  536. expect(mockSaved).toHaveBeenCalled();
  537. expect(mockVisit).toHaveBeenCalledTimes(1);
  538. wrapper.unmount();
  539. });
  540. it('creates event view from saved query', async function () {
  541. const organization = TestStubs.Organization({
  542. features,
  543. slug: 'org-slug',
  544. });
  545. const initialData = initializeOrg({
  546. organization,
  547. router: {
  548. location: {query: {id: '1', statsPeriod: '24h'}},
  549. },
  550. });
  551. const wrapper = mountWithThemeAndOrg(
  552. <Results
  553. organization={organization}
  554. location={initialData.router.location}
  555. router={initialData.router}
  556. />,
  557. initialData.routerContext,
  558. organization
  559. );
  560. await tick();
  561. const eventView = wrapper.find('Results').state('eventView');
  562. expect(eventView.name).toEqual('new');
  563. expect(eventView.id).toEqual('1');
  564. expect(eventView.fields.length).toEqual(5);
  565. expect(eventView.project).toEqual([]);
  566. expect(eventView.statsPeriod).toEqual('24h');
  567. expect(eventView.sorts).toEqual([{field: 'user.display', kind: 'desc'}]);
  568. wrapper.unmount();
  569. });
  570. it('overrides saved query params with location query params', async function () {
  571. const organization = TestStubs.Organization({
  572. features,
  573. slug: 'org-slug',
  574. });
  575. const initialData = initializeOrg({
  576. organization,
  577. router: {
  578. location: {
  579. query: {
  580. id: '1',
  581. statsPeriod: '7d',
  582. project: [2],
  583. environment: ['production'],
  584. },
  585. },
  586. },
  587. });
  588. const wrapper = mountWithThemeAndOrg(
  589. <Results
  590. organization={organization}
  591. location={initialData.router.location}
  592. router={initialData.router}
  593. />,
  594. initialData.routerContext,
  595. organization
  596. );
  597. await tick();
  598. const eventView = wrapper.find('Results').state('eventView');
  599. expect(eventView.name).toEqual('new');
  600. expect(eventView.id).toEqual('1');
  601. expect(eventView.fields.length).toEqual(5);
  602. expect(eventView.project).toEqual([2]);
  603. expect(eventView.statsPeriod).toEqual('7d');
  604. expect(eventView.environment).toEqual(['production']);
  605. expect(mockVisit).toHaveBeenCalledTimes(1);
  606. wrapper.unmount();
  607. });
  608. it('updates chart whenever yAxis parameter changes', async function () {
  609. const organization = TestStubs.Organization({
  610. features,
  611. });
  612. const initialData = initializeOrg({
  613. organization,
  614. router: {
  615. location: {query: {...generateFields(), yAxis: 'count()'}},
  616. },
  617. });
  618. ProjectsStore.loadInitialData([TestStubs.Project()]);
  619. const wrapper = mountWithThemeAndOrg(
  620. <Results
  621. organization={organization}
  622. location={initialData.router.location}
  623. router={initialData.router}
  624. />,
  625. initialData.routerContext,
  626. organization
  627. );
  628. // Should load events once
  629. expect(eventsStatsMock).toHaveBeenCalledTimes(1);
  630. expect(eventsStatsMock).toHaveBeenNthCalledWith(
  631. 1,
  632. '/organizations/org-slug/events-stats/',
  633. expect.objectContaining({
  634. query: expect.objectContaining({
  635. statsPeriod: '14d',
  636. yAxis: ['count()'],
  637. }),
  638. })
  639. );
  640. // Update location simulating a browser back button action
  641. wrapper.setProps({
  642. location: {
  643. query: {...generateFields(), yAxis: 'count_unique(user)'},
  644. },
  645. });
  646. await tick();
  647. wrapper.update();
  648. // Should load events again
  649. expect(eventsStatsMock).toHaveBeenCalledTimes(2);
  650. expect(eventsStatsMock).toHaveBeenNthCalledWith(
  651. 2,
  652. '/organizations/org-slug/events-stats/',
  653. expect.objectContaining({
  654. query: expect.objectContaining({
  655. statsPeriod: '14d',
  656. yAxis: ['count_unique(user)'],
  657. }),
  658. })
  659. );
  660. wrapper.unmount();
  661. });
  662. it('updates chart whenever display parameter changes', async function () {
  663. const organization = TestStubs.Organization({
  664. features,
  665. });
  666. const initialData = initializeOrg({
  667. organization,
  668. router: {
  669. location: {query: {...generateFields(), display: 'default', yAxis: 'count()'}},
  670. },
  671. });
  672. ProjectsStore.loadInitialData([TestStubs.Project()]);
  673. const wrapper = mountWithThemeAndOrg(
  674. <Results
  675. organization={organization}
  676. location={initialData.router.location}
  677. router={initialData.router}
  678. />,
  679. initialData.routerContext,
  680. organization
  681. );
  682. // Should load events once
  683. expect(eventsStatsMock).toHaveBeenCalledTimes(1);
  684. expect(eventsStatsMock).toHaveBeenNthCalledWith(
  685. 1,
  686. '/organizations/org-slug/events-stats/',
  687. expect.objectContaining({
  688. query: expect.objectContaining({
  689. statsPeriod: '14d',
  690. yAxis: ['count()'],
  691. }),
  692. })
  693. );
  694. // Update location simulating a browser back button action
  695. wrapper.setProps({
  696. location: {
  697. query: {...generateFields(), display: 'previous', yAxis: 'count()'},
  698. },
  699. });
  700. await tick();
  701. wrapper.update();
  702. // Should load events again
  703. expect(eventsStatsMock).toHaveBeenCalledTimes(2);
  704. expect(eventsStatsMock).toHaveBeenNthCalledWith(
  705. 2,
  706. '/organizations/org-slug/events-stats/',
  707. expect.objectContaining({
  708. query: expect.objectContaining({
  709. statsPeriod: '28d',
  710. yAxis: ['count()'],
  711. }),
  712. })
  713. );
  714. wrapper.unmount();
  715. });
  716. it('updates chart whenever display and yAxis parameters change', async function () {
  717. const organization = TestStubs.Organization({
  718. features,
  719. });
  720. const initialData = initializeOrg({
  721. organization,
  722. router: {
  723. location: {query: {...generateFields(), display: 'default', yAxis: 'count()'}},
  724. },
  725. });
  726. ProjectsStore.loadInitialData([TestStubs.Project()]);
  727. const wrapper = mountWithThemeAndOrg(
  728. <Results
  729. organization={organization}
  730. location={initialData.router.location}
  731. router={initialData.router}
  732. />,
  733. initialData.routerContext,
  734. organization
  735. );
  736. // Should load events once
  737. expect(eventsStatsMock).toHaveBeenCalledTimes(1);
  738. expect(eventsStatsMock).toHaveBeenNthCalledWith(
  739. 1,
  740. '/organizations/org-slug/events-stats/',
  741. expect.objectContaining({
  742. query: expect.objectContaining({
  743. statsPeriod: '14d',
  744. yAxis: ['count()'],
  745. }),
  746. })
  747. );
  748. // Update location simulating a browser back button action
  749. wrapper.setProps({
  750. location: {
  751. query: {...generateFields(), display: 'previous', yAxis: 'count_unique(user)'},
  752. },
  753. });
  754. await tick();
  755. wrapper.update();
  756. // Should load events again
  757. expect(eventsStatsMock).toHaveBeenCalledTimes(2);
  758. expect(eventsStatsMock).toHaveBeenNthCalledWith(
  759. 2,
  760. '/organizations/org-slug/events-stats/',
  761. expect.objectContaining({
  762. query: expect.objectContaining({
  763. statsPeriod: '28d',
  764. yAxis: ['count_unique(user)'],
  765. }),
  766. })
  767. );
  768. wrapper.unmount();
  769. });
  770. });
  771. describe('Events', function () {
  772. const features = ['discover-basic', 'discover-frontend-use-events-endpoint'];
  773. it('loads data when moving from an invalid to valid EventView', async function () {
  774. const organization = TestStubs.Organization({
  775. features,
  776. });
  777. // Start off with an invalid view (empty is invalid)
  778. const initialData = initializeOrg({
  779. organization,
  780. router: {
  781. location: {query: {query: 'tag:value'}},
  782. },
  783. });
  784. ProjectsStore.loadInitialData([TestStubs.Project()]);
  785. const wrapper = mountWithThemeAndOrg(
  786. <Results
  787. organization={organization}
  788. location={initialData.router.location}
  789. router={initialData.router}
  790. />,
  791. initialData.routerContext,
  792. organization
  793. );
  794. await tick();
  795. wrapper.update();
  796. // No request as eventview was invalid.
  797. expect(eventsResultsMock).not.toHaveBeenCalled();
  798. // Should redirect and retain the old query value..
  799. expect(browserHistory.replace).toHaveBeenCalledWith(
  800. expect.objectContaining({
  801. pathname: '/organizations/org-slug/discover/results/',
  802. query: expect.objectContaining({
  803. query: 'tag:value',
  804. }),
  805. })
  806. );
  807. // Update location simulating a redirect.
  808. wrapper.setProps({location: {query: {...generateFields()}}});
  809. wrapper.update();
  810. // Should load events once
  811. expect(eventsResultsMock).toHaveBeenCalled();
  812. });
  813. it('pagination cursor should be cleared when making a search', async function () {
  814. const organization = TestStubs.Organization({
  815. features,
  816. });
  817. const initialData = initializeOrg({
  818. organization,
  819. router: {
  820. location: {
  821. query: {
  822. ...generateFields(),
  823. cursor: '0%3A50%3A0',
  824. },
  825. },
  826. },
  827. });
  828. ProjectsStore.loadInitialData([TestStubs.Project()]);
  829. const wrapper = mountWithThemeAndOrg(
  830. <Results
  831. organization={organization}
  832. location={initialData.router.location}
  833. router={initialData.router}
  834. />,
  835. initialData.routerContext,
  836. organization
  837. );
  838. await tick();
  839. wrapper.update();
  840. // ensure cursor query string is initially present in the location
  841. expect(initialData.router.location).toEqual({
  842. query: {
  843. ...generateFields(),
  844. cursor: '0%3A50%3A0',
  845. },
  846. });
  847. // perform a search
  848. const search = wrapper.find('#smart-search-input').first();
  849. search.simulate('change', {target: {value: 'geo:canada'}}).simulate('submit', {
  850. preventDefault() {},
  851. });
  852. await tick();
  853. // should only be called with saved queries
  854. expect(mockVisit).not.toHaveBeenCalled();
  855. // cursor query string should be omitted from the query string
  856. expect(initialData.router.push).toHaveBeenCalledWith({
  857. pathname: undefined,
  858. query: {
  859. ...generateFields(),
  860. query: 'geo:canada',
  861. statsPeriod: '14d',
  862. },
  863. });
  864. wrapper.unmount();
  865. });
  866. it('renders a y-axis selector', async function () {
  867. const organization = TestStubs.Organization({
  868. features,
  869. });
  870. const initialData = initializeOrg({
  871. organization,
  872. router: {
  873. location: {query: {...generateFields(), yAxis: 'count()'}},
  874. },
  875. });
  876. ProjectsStore.loadInitialData([TestStubs.Project()]);
  877. const wrapper = mountWithThemeAndOrg(
  878. <Results
  879. organization={organization}
  880. location={initialData.router.location}
  881. router={initialData.router}
  882. />,
  883. initialData.routerContext,
  884. organization
  885. );
  886. // y-axis selector is last.
  887. const selector = wrapper.find('OptionSelector').last();
  888. // Open the selector
  889. act(() => {
  890. triggerPress(selector.find('button[aria-haspopup="listbox"]'));
  891. });
  892. await tick();
  893. wrapper.update();
  894. // Click one of the options.
  895. wrapper.find('Option').first().simulate('click');
  896. await tick();
  897. wrapper.update();
  898. const eventsRequest = wrapper.find('EventsChart');
  899. expect(eventsRequest.props().yAxis).toEqual(['count()']);
  900. wrapper.unmount();
  901. });
  902. it('renders a display selector', async function () {
  903. const organization = TestStubs.Organization({
  904. features,
  905. });
  906. const initialData = initializeOrg({
  907. organization,
  908. router: {
  909. location: {query: {...generateFields(), display: 'default', yAxis: 'count'}},
  910. },
  911. });
  912. const wrapper = mountWithThemeAndOrg(
  913. <Results
  914. organization={organization}
  915. location={initialData.router.location}
  916. router={initialData.router}
  917. />,
  918. initialData.routerContext,
  919. organization
  920. );
  921. act(() => ProjectsStore.loadInitialData([TestStubs.Project()]));
  922. await tick();
  923. wrapper.update();
  924. // display selector is first.
  925. const selector = wrapper.find('OptionSelector').first();
  926. // Open the selector
  927. act(() => {
  928. triggerPress(selector.find('button[aria-haspopup="listbox"]'));
  929. });
  930. await tick();
  931. wrapper.update();
  932. // Click the 'default' option.
  933. wrapper.find('Option').first().simulate('click');
  934. await tick();
  935. wrapper.update();
  936. const eventsRequest = wrapper.find('EventsChart').props();
  937. expect(eventsRequest.disableReleases).toEqual(false);
  938. expect(eventsRequest.disablePrevious).toEqual(true);
  939. wrapper.unmount();
  940. });
  941. it('excludes top5 options when plan does not include discover-query', async function () {
  942. const organization = TestStubs.Organization({
  943. features: ['discover-basic'],
  944. });
  945. const initialData = initializeOrg({
  946. organization,
  947. router: {
  948. location: {query: {...generateFields(), display: 'previous'}},
  949. },
  950. });
  951. ProjectsStore.loadInitialData([TestStubs.Project()]);
  952. const wrapper = mountWithThemeAndOrg(
  953. <Results
  954. organization={organization}
  955. location={initialData.router.location}
  956. router={initialData.router}
  957. />,
  958. initialData.routerContext,
  959. organization
  960. );
  961. // display selector is first.
  962. const selector = wrapper.find('OptionSelector').first();
  963. // Open the selector
  964. act(() => {
  965. triggerPress(selector.find('button[aria-haspopup="listbox"]'));
  966. });
  967. await tick();
  968. wrapper.update();
  969. // Make sure the top5 option isn't present
  970. const options = wrapper
  971. .find('Option [data-test-id]')
  972. .map(item => item.prop('data-test-id'));
  973. expect(options).not.toContain('top5');
  974. expect(options).not.toContain('dailytop5');
  975. expect(options).toContain('default');
  976. wrapper.unmount();
  977. });
  978. it('needs confirmation on long queries', async function () {
  979. const organization = TestStubs.Organization({
  980. features: ['discover-basic'],
  981. });
  982. const initialData = initializeOrg({
  983. organization,
  984. router: {
  985. location: {query: {...generateFields(), statsPeriod: '60d', project: '-1'}},
  986. },
  987. });
  988. const wrapper = mountWithThemeAndOrg(
  989. <Results
  990. organization={organization}
  991. location={initialData.router.location}
  992. router={initialData.router}
  993. />,
  994. initialData.routerContext,
  995. organization
  996. );
  997. await tick();
  998. const results = wrapper.find('Results');
  999. expect(results.state('needConfirmation')).toEqual(true);
  1000. wrapper.unmount();
  1001. });
  1002. it('needs confirmation on long query with explicit projects', async function () {
  1003. const organization = TestStubs.Organization({
  1004. features: ['discover-basic'],
  1005. });
  1006. const initialData = initializeOrg({
  1007. organization,
  1008. router: {
  1009. location: {
  1010. query: {
  1011. ...generateFields(),
  1012. statsPeriod: '60d',
  1013. project: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
  1014. },
  1015. },
  1016. },
  1017. });
  1018. const wrapper = mountWithThemeAndOrg(
  1019. <Results
  1020. organization={organization}
  1021. location={initialData.router.location}
  1022. router={initialData.router}
  1023. />,
  1024. initialData.routerContext,
  1025. organization
  1026. );
  1027. await tick();
  1028. const results = wrapper.find('Results');
  1029. expect(results.state('needConfirmation')).toEqual(true);
  1030. wrapper.unmount();
  1031. });
  1032. it('does not need confirmation on short queries', async function () {
  1033. const organization = TestStubs.Organization({
  1034. features: ['discover-basic'],
  1035. });
  1036. const initialData = initializeOrg({
  1037. organization,
  1038. router: {
  1039. location: {query: {...generateFields(), statsPeriod: '30d', project: '-1'}},
  1040. },
  1041. });
  1042. const wrapper = mountWithThemeAndOrg(
  1043. <Results
  1044. organization={organization}
  1045. location={initialData.router.location}
  1046. router={initialData.router}
  1047. />,
  1048. initialData.routerContext,
  1049. organization
  1050. );
  1051. await tick();
  1052. const results = wrapper.find('Results');
  1053. expect(results.state('needConfirmation')).toEqual(false);
  1054. wrapper.unmount();
  1055. });
  1056. it('does not need confirmation with to few projects', async function () {
  1057. const organization = TestStubs.Organization({
  1058. features: ['discover-basic'],
  1059. });
  1060. const initialData = initializeOrg({
  1061. organization,
  1062. router: {
  1063. location: {
  1064. query: {...generateFields(), statsPeriod: '90d', project: [1, 2, 3, 4]},
  1065. },
  1066. },
  1067. });
  1068. const wrapper = mountWithThemeAndOrg(
  1069. <Results
  1070. organization={organization}
  1071. location={initialData.router.location}
  1072. router={initialData.router}
  1073. />,
  1074. initialData.routerContext,
  1075. organization
  1076. );
  1077. await tick();
  1078. const results = wrapper.find('Results');
  1079. expect(results.state('needConfirmation')).toEqual(false);
  1080. wrapper.unmount();
  1081. });
  1082. it('retrieves saved query', async function () {
  1083. const organization = TestStubs.Organization({
  1084. features,
  1085. slug: 'org-slug',
  1086. });
  1087. const initialData = initializeOrg({
  1088. organization,
  1089. router: {
  1090. location: {query: {id: '1', statsPeriod: '24h'}},
  1091. },
  1092. });
  1093. const wrapper = mountWithThemeAndOrg(
  1094. <Results
  1095. organization={organization}
  1096. location={initialData.router.location}
  1097. router={initialData.router}
  1098. />,
  1099. initialData.routerContext,
  1100. organization
  1101. );
  1102. await tick();
  1103. const savedQuery = wrapper.find('SavedQueryAPI').state('savedQuery');
  1104. expect(savedQuery.name).toEqual('new');
  1105. expect(savedQuery.id).toEqual('1');
  1106. expect(savedQuery.fields).toEqual([
  1107. 'title',
  1108. 'event.type',
  1109. 'project',
  1110. 'user.display',
  1111. 'timestamp',
  1112. ]);
  1113. expect(savedQuery.projects).toEqual([]);
  1114. expect(savedQuery.range).toEqual('24h');
  1115. expect(mockSaved).toHaveBeenCalled();
  1116. expect(mockVisit).toHaveBeenCalledTimes(1);
  1117. wrapper.unmount();
  1118. });
  1119. it('creates event view from saved query', async function () {
  1120. const organization = TestStubs.Organization({
  1121. features,
  1122. slug: 'org-slug',
  1123. });
  1124. const initialData = initializeOrg({
  1125. organization,
  1126. router: {
  1127. location: {query: {id: '1', statsPeriod: '24h'}},
  1128. },
  1129. });
  1130. const wrapper = mountWithThemeAndOrg(
  1131. <Results
  1132. organization={organization}
  1133. location={initialData.router.location}
  1134. router={initialData.router}
  1135. />,
  1136. initialData.routerContext,
  1137. organization
  1138. );
  1139. await tick();
  1140. const eventView = wrapper.find('Results').state('eventView');
  1141. expect(eventView.name).toEqual('new');
  1142. expect(eventView.id).toEqual('1');
  1143. expect(eventView.fields.length).toEqual(5);
  1144. expect(eventView.project).toEqual([]);
  1145. expect(eventView.statsPeriod).toEqual('24h');
  1146. expect(eventView.sorts).toEqual([{field: 'user.display', kind: 'desc'}]);
  1147. wrapper.unmount();
  1148. });
  1149. it('overrides saved query params with location query params', async function () {
  1150. const organization = TestStubs.Organization({
  1151. features,
  1152. slug: 'org-slug',
  1153. });
  1154. const initialData = initializeOrg({
  1155. organization,
  1156. router: {
  1157. location: {
  1158. query: {
  1159. id: '1',
  1160. statsPeriod: '7d',
  1161. project: [2],
  1162. environment: ['production'],
  1163. },
  1164. },
  1165. },
  1166. });
  1167. const wrapper = mountWithThemeAndOrg(
  1168. <Results
  1169. organization={organization}
  1170. location={initialData.router.location}
  1171. router={initialData.router}
  1172. />,
  1173. initialData.routerContext,
  1174. organization
  1175. );
  1176. await tick();
  1177. const eventView = wrapper.find('Results').state('eventView');
  1178. expect(eventView.name).toEqual('new');
  1179. expect(eventView.id).toEqual('1');
  1180. expect(eventView.fields.length).toEqual(5);
  1181. expect(eventView.project).toEqual([2]);
  1182. expect(eventView.statsPeriod).toEqual('7d');
  1183. expect(eventView.environment).toEqual(['production']);
  1184. expect(mockVisit).toHaveBeenCalledTimes(1);
  1185. wrapper.unmount();
  1186. });
  1187. it('updates chart whenever yAxis parameter changes', async function () {
  1188. const organization = TestStubs.Organization({
  1189. features,
  1190. });
  1191. const initialData = initializeOrg({
  1192. organization,
  1193. router: {
  1194. location: {query: {...generateFields(), yAxis: 'count()'}},
  1195. },
  1196. });
  1197. ProjectsStore.loadInitialData([TestStubs.Project()]);
  1198. const wrapper = mountWithThemeAndOrg(
  1199. <Results
  1200. organization={organization}
  1201. location={initialData.router.location}
  1202. router={initialData.router}
  1203. />,
  1204. initialData.routerContext,
  1205. organization
  1206. );
  1207. // Should load events once
  1208. expect(eventsStatsMock).toHaveBeenCalledTimes(1);
  1209. expect(eventsStatsMock).toHaveBeenNthCalledWith(
  1210. 1,
  1211. '/organizations/org-slug/events-stats/',
  1212. expect.objectContaining({
  1213. query: expect.objectContaining({
  1214. statsPeriod: '14d',
  1215. yAxis: ['count()'],
  1216. }),
  1217. })
  1218. );
  1219. // Update location simulating a browser back button action
  1220. wrapper.setProps({
  1221. location: {
  1222. query: {...generateFields(), yAxis: 'count_unique(user)'},
  1223. },
  1224. });
  1225. await tick();
  1226. wrapper.update();
  1227. // Should load events again
  1228. expect(eventsStatsMock).toHaveBeenCalledTimes(2);
  1229. expect(eventsStatsMock).toHaveBeenNthCalledWith(
  1230. 2,
  1231. '/organizations/org-slug/events-stats/',
  1232. expect.objectContaining({
  1233. query: expect.objectContaining({
  1234. statsPeriod: '14d',
  1235. yAxis: ['count_unique(user)'],
  1236. }),
  1237. })
  1238. );
  1239. wrapper.unmount();
  1240. });
  1241. it('updates chart whenever display parameter changes', async function () {
  1242. const organization = TestStubs.Organization({
  1243. features,
  1244. });
  1245. const initialData = initializeOrg({
  1246. organization,
  1247. router: {
  1248. location: {query: {...generateFields(), display: 'default', yAxis: 'count()'}},
  1249. },
  1250. });
  1251. ProjectsStore.loadInitialData([TestStubs.Project()]);
  1252. const wrapper = mountWithThemeAndOrg(
  1253. <Results
  1254. organization={organization}
  1255. location={initialData.router.location}
  1256. router={initialData.router}
  1257. />,
  1258. initialData.routerContext,
  1259. organization
  1260. );
  1261. // Should load events once
  1262. expect(eventsStatsMock).toHaveBeenCalledTimes(1);
  1263. expect(eventsStatsMock).toHaveBeenNthCalledWith(
  1264. 1,
  1265. '/organizations/org-slug/events-stats/',
  1266. expect.objectContaining({
  1267. query: expect.objectContaining({
  1268. statsPeriod: '14d',
  1269. yAxis: ['count()'],
  1270. }),
  1271. })
  1272. );
  1273. // Update location simulating a browser back button action
  1274. wrapper.setProps({
  1275. location: {
  1276. query: {...generateFields(), display: 'previous', yAxis: 'count()'},
  1277. },
  1278. });
  1279. await tick();
  1280. wrapper.update();
  1281. // Should load events again
  1282. expect(eventsStatsMock).toHaveBeenCalledTimes(2);
  1283. expect(eventsStatsMock).toHaveBeenNthCalledWith(
  1284. 2,
  1285. '/organizations/org-slug/events-stats/',
  1286. expect.objectContaining({
  1287. query: expect.objectContaining({
  1288. statsPeriod: '28d',
  1289. yAxis: ['count()'],
  1290. }),
  1291. })
  1292. );
  1293. wrapper.unmount();
  1294. });
  1295. it('updates chart whenever display and yAxis parameters change', async function () {
  1296. const organization = TestStubs.Organization({
  1297. features,
  1298. });
  1299. const initialData = initializeOrg({
  1300. organization,
  1301. router: {
  1302. location: {query: {...generateFields(), display: 'default', yAxis: 'count()'}},
  1303. },
  1304. });
  1305. ProjectsStore.loadInitialData([TestStubs.Project()]);
  1306. const wrapper = mountWithThemeAndOrg(
  1307. <Results
  1308. organization={organization}
  1309. location={initialData.router.location}
  1310. router={initialData.router}
  1311. />,
  1312. initialData.routerContext,
  1313. organization
  1314. );
  1315. // Should load events once
  1316. expect(eventsStatsMock).toHaveBeenCalledTimes(1);
  1317. expect(eventsStatsMock).toHaveBeenNthCalledWith(
  1318. 1,
  1319. '/organizations/org-slug/events-stats/',
  1320. expect.objectContaining({
  1321. query: expect.objectContaining({
  1322. statsPeriod: '14d',
  1323. yAxis: ['count()'],
  1324. }),
  1325. })
  1326. );
  1327. // Update location simulating a browser back button action
  1328. wrapper.setProps({
  1329. location: {
  1330. query: {...generateFields(), display: 'previous', yAxis: 'count_unique(user)'},
  1331. },
  1332. });
  1333. await tick();
  1334. wrapper.update();
  1335. // Should load events again
  1336. expect(eventsStatsMock).toHaveBeenCalledTimes(2);
  1337. expect(eventsStatsMock).toHaveBeenNthCalledWith(
  1338. 2,
  1339. '/organizations/org-slug/events-stats/',
  1340. expect.objectContaining({
  1341. query: expect.objectContaining({
  1342. statsPeriod: '28d',
  1343. yAxis: ['count_unique(user)'],
  1344. }),
  1345. })
  1346. );
  1347. wrapper.unmount();
  1348. });
  1349. it('appends tag value to existing query when clicked', async function () {
  1350. const organization = TestStubs.Organization({
  1351. features,
  1352. });
  1353. const initialData = initializeOrg({
  1354. organization,
  1355. router: {
  1356. location: {query: {...generateFields(), display: 'default', yAxis: 'count'}},
  1357. },
  1358. });
  1359. const wrapper = mountWithThemeAndOrg(
  1360. <Results
  1361. organization={organization}
  1362. location={initialData.router.location}
  1363. router={initialData.router}
  1364. />,
  1365. initialData.routerContext,
  1366. organization
  1367. );
  1368. act(() => ProjectsStore.loadInitialData([TestStubs.Project()]));
  1369. await tick();
  1370. wrapper.update();
  1371. wrapper.find('[data-test-id="toggle-show-tags"]').first().simulate('click');
  1372. await tick();
  1373. wrapper.update();
  1374. // since environment collides with the environment field, it is wrapped with `tags[...]`
  1375. const envSegment = wrapper.find(
  1376. '[data-test-id="tag-environment-segment-dev"] Segment'
  1377. );
  1378. const envTarget = envSegment.props().to;
  1379. expect(envTarget.query.query).toEqual('tags[environment]:dev');
  1380. const fooSegment = wrapper.find('[data-test-id="tag-foo-segment-bar"] Segment');
  1381. const fooTarget = fooSegment.props().to;
  1382. expect(fooTarget.query.query).toEqual('foo:bar');
  1383. });
  1384. it('respects pinned filters for prebuilt queries', async function () {
  1385. const organization = TestStubs.Organization({
  1386. features: [...features, 'global-views'],
  1387. });
  1388. const initialData = initializeOrg({
  1389. organization,
  1390. router: {
  1391. location: {query: {...generateFields(), display: 'default', yAxis: 'count'}},
  1392. },
  1393. });
  1394. jest.spyOn(PageFilterPersistence, 'getPageFilterStorage').mockReturnValue({
  1395. state: {
  1396. project: [1],
  1397. environment: [],
  1398. start: null,
  1399. end: null,
  1400. period: '14d',
  1401. utc: null,
  1402. },
  1403. pinnedFilters: new Set(['projects']),
  1404. });
  1405. const wrapper = mountWithThemeAndOrg(
  1406. <Results
  1407. organization={organization}
  1408. location={initialData.router.location}
  1409. router={initialData.router}
  1410. />,
  1411. initialData.routerContext,
  1412. organization
  1413. );
  1414. act(() =>
  1415. ProjectsStore.loadInitialData([
  1416. TestStubs.Project({id: 1, slug: 'Pinned Project'}),
  1417. ])
  1418. );
  1419. await tick();
  1420. wrapper.update();
  1421. const projectPageFilter = wrapper
  1422. .find('[data-test-id="page-filter-project-selector"]')
  1423. .first();
  1424. expect(projectPageFilter.text()).toEqual('Pinned Project');
  1425. });
  1426. });
  1427. it('renders metric fallback alert', async function () {
  1428. const organization = TestStubs.Organization({
  1429. features: ['discover-basic'],
  1430. });
  1431. const initialData = initializeOrg({
  1432. organization,
  1433. router: {
  1434. location: {query: {fromMetric: true}},
  1435. },
  1436. });
  1437. ProjectsStore.loadInitialData([TestStubs.Project()]);
  1438. const wrapper = mountWithThemeAndOrg(
  1439. <Results
  1440. organization={organization}
  1441. location={initialData.router.location}
  1442. router={initialData.router}
  1443. />,
  1444. initialData.routerContext,
  1445. organization
  1446. );
  1447. await tick();
  1448. wrapper.update();
  1449. expect(wrapper.find('Alert').find('Message').text()).toEqual(
  1450. "You've navigated to this page from a performance metric widget generated from processed events. The results here only show indexed events."
  1451. );
  1452. });
  1453. it('renders unparameterized data banner', async function () {
  1454. const organization = TestStubs.Organization({
  1455. features: ['discover-basic'],
  1456. });
  1457. const initialData = initializeOrg({
  1458. organization,
  1459. router: {
  1460. location: {query: {showUnparameterizedBanner: true}},
  1461. },
  1462. });
  1463. ProjectsStore.loadInitialData([TestStubs.Project()]);
  1464. const wrapper = mountWithThemeAndOrg(
  1465. <Results
  1466. organization={organization}
  1467. location={initialData.router.location}
  1468. router={initialData.router}
  1469. />,
  1470. initialData.routerContext,
  1471. organization
  1472. );
  1473. await tick();
  1474. wrapper.update();
  1475. expect(wrapper.find('Alert').find('Message').text()).toEqual(
  1476. 'These are unparameterized transactions. To better organize your transactions, set transaction names manually.'
  1477. );
  1478. });
  1479. });