homepage.spec.tsx 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506
  1. import {browserHistory} from 'react-router';
  2. import {LocationFixture} from 'sentry-fixture/locationFixture';
  3. import {OrganizationFixture} from 'sentry-fixture/organization';
  4. import {ProjectFixture} from 'sentry-fixture/project';
  5. import {initializeOrg} from 'sentry-test/initializeOrg';
  6. import {
  7. render,
  8. renderGlobalModal,
  9. screen,
  10. userEvent,
  11. waitFor,
  12. } from 'sentry-test/reactTestingLibrary';
  13. import * as pageFilterUtils from 'sentry/components/organizations/pageFilters/persistence';
  14. import ProjectsStore from 'sentry/stores/projectsStore';
  15. import EventView from 'sentry/utils/discover/eventView';
  16. import {DEFAULT_EVENT_VIEW} from './data';
  17. import Homepage from './homepage';
  18. describe('Discover > Homepage', () => {
  19. const features = ['global-views', 'discover-query'];
  20. let initialData, organization, mockHomepage, measurementsMetaMock;
  21. beforeEach(() => {
  22. organization = OrganizationFixture({
  23. features,
  24. });
  25. initialData = initializeOrg({
  26. organization,
  27. router: {
  28. location: LocationFixture(),
  29. },
  30. });
  31. ProjectsStore.loadInitialData(organization.projects);
  32. MockApiClient.addMockResponse({
  33. url: '/organizations/org-slug/events/',
  34. body: [],
  35. });
  36. MockApiClient.addMockResponse({
  37. url: '/organizations/org-slug/events-meta/',
  38. body: {
  39. count: 2,
  40. },
  41. });
  42. MockApiClient.addMockResponse({
  43. url: '/organizations/org-slug/events-stats/',
  44. body: {data: [[123, []]]},
  45. });
  46. MockApiClient.addMockResponse({
  47. url: '/organizations/org-slug/tags/',
  48. body: [],
  49. });
  50. MockApiClient.addMockResponse({
  51. url: '/organizations/org-slug/releases/stats/',
  52. body: [],
  53. });
  54. mockHomepage = MockApiClient.addMockResponse({
  55. url: '/organizations/org-slug/discover/homepage/',
  56. method: 'GET',
  57. statusCode: 200,
  58. body: {
  59. id: '2',
  60. name: 'homepage query',
  61. projects: [],
  62. version: 2,
  63. expired: false,
  64. dateCreated: '2021-04-08T17:53:25.195782Z',
  65. dateUpdated: '2021-04-09T12:13:18.567264Z',
  66. createdBy: {
  67. id: '2',
  68. },
  69. environment: ['alpha'],
  70. fields: ['environment'],
  71. widths: ['-1'],
  72. range: '24h',
  73. orderby: '-environment',
  74. display: 'previous',
  75. query: 'event.type:error',
  76. },
  77. });
  78. measurementsMetaMock = MockApiClient.addMockResponse({
  79. url: '/organizations/org-slug/measurements-meta/',
  80. method: 'GET',
  81. body: {},
  82. });
  83. });
  84. it('renders the Discover banner', async () => {
  85. render(
  86. <Homepage
  87. organization={organization}
  88. location={initialData.router.location}
  89. router={initialData.router}
  90. setSavedQuery={jest.fn()}
  91. loading={false}
  92. />,
  93. {context: initialData.routerContext, organization: initialData.organization}
  94. );
  95. await screen.findByText('Discover Trends');
  96. screen.getByText('Get a Tour');
  97. expect(screen.queryByText('Build a new query')).not.toBeInTheDocument();
  98. });
  99. it('fetches from the homepage URL and renders fields, async page filters, async and chart information', async () => {
  100. render(
  101. <Homepage
  102. organization={organization}
  103. location={initialData.router.location}
  104. router={initialData.router}
  105. setSavedQuery={jest.fn()}
  106. loading={false}
  107. />,
  108. {context: initialData.routerContext, organization: initialData.organization}
  109. );
  110. expect(mockHomepage).toHaveBeenCalled();
  111. await screen.findByText('environment');
  112. // Only the environment field
  113. expect(screen.getAllByTestId('grid-head-cell').length).toEqual(1);
  114. screen.getByText('Previous Period');
  115. screen.getByText('event.type:error');
  116. });
  117. it('renders event view from URL params over homepage query', async () => {
  118. initialData = initializeOrg({
  119. organization,
  120. router: {
  121. location: {
  122. ...LocationFixture(),
  123. query: {
  124. ...EventView.fromSavedQuery(DEFAULT_EVENT_VIEW).generateQueryStringObject(),
  125. field: ['project'],
  126. },
  127. },
  128. },
  129. });
  130. render(
  131. <Homepage
  132. organization={organization}
  133. location={initialData.router.location}
  134. router={initialData.router}
  135. setSavedQuery={jest.fn()}
  136. loading={false}
  137. />,
  138. {context: initialData.routerContext, organization: initialData.organization}
  139. );
  140. expect(mockHomepage).toHaveBeenCalled();
  141. await screen.findByText('project');
  142. // This is the field in the mocked response for the homepage
  143. expect(screen.queryByText('environment')).not.toBeInTheDocument();
  144. });
  145. it('applies URL changes with the homepage pathname', async () => {
  146. render(
  147. <Homepage
  148. organization={organization}
  149. location={initialData.router.location}
  150. router={initialData.router}
  151. setSavedQuery={jest.fn()}
  152. loading={false}
  153. />,
  154. {context: initialData.routerContext, organization: initialData.organization}
  155. );
  156. renderGlobalModal();
  157. await userEvent.click(await screen.findByText('Columns'));
  158. await userEvent.click(screen.getByTestId('label'));
  159. await userEvent.click(screen.getByText('event.type'));
  160. await userEvent.click(screen.getByText('Apply'));
  161. expect(browserHistory.push).toHaveBeenCalledWith(
  162. expect.objectContaining({
  163. pathname: '/organizations/org-slug/discover/homepage/',
  164. query: expect.objectContaining({
  165. field: ['event.type'],
  166. }),
  167. })
  168. );
  169. });
  170. it('does not show an editable header or author information', async () => {
  171. render(
  172. <Homepage
  173. organization={organization}
  174. location={initialData.router.location}
  175. router={initialData.router}
  176. setSavedQuery={jest.fn()}
  177. loading={false}
  178. />,
  179. {context: initialData.routerContext, organization: initialData.organization}
  180. );
  181. await waitFor(() => {
  182. expect(measurementsMetaMock).toHaveBeenCalled();
  183. });
  184. // 'Discover' is the header for the homepage
  185. expect(screen.getByText('Discover')).toBeInTheDocument();
  186. expect(screen.queryByText(/Created by:/)).not.toBeInTheDocument();
  187. expect(screen.queryByText(/Last edited:/)).not.toBeInTheDocument();
  188. });
  189. it('shows the Remove Default button on initial load', async () => {
  190. MockApiClient.addMockResponse({
  191. url: '/organizations/org-slug/discover/homepage/',
  192. method: 'GET',
  193. statusCode: 200,
  194. body: {
  195. id: '2',
  196. name: 'homepage query',
  197. projects: [],
  198. version: 2,
  199. expired: false,
  200. dateCreated: '2021-04-08T17:53:25.195782Z',
  201. dateUpdated: '2021-04-09T12:13:18.567264Z',
  202. createdBy: {
  203. id: '2',
  204. },
  205. environment: [],
  206. fields: ['environment'],
  207. widths: ['-1'],
  208. range: '14d',
  209. orderby: '-environment',
  210. display: 'previous',
  211. query: 'event.type:error',
  212. topEvents: '5',
  213. },
  214. });
  215. render(
  216. <Homepage
  217. organization={organization}
  218. location={initialData.router.location}
  219. router={initialData.router}
  220. setSavedQuery={jest.fn()}
  221. loading={false}
  222. />,
  223. {context: initialData.routerContext, organization: initialData.organization}
  224. );
  225. expect(await screen.findByText('Remove Default')).toBeInTheDocument();
  226. expect(screen.queryByText('Set as Default')).not.toBeInTheDocument();
  227. });
  228. it('Disables the Set as Default button when no saved homepage', async () => {
  229. initialData = initializeOrg({
  230. organization,
  231. router: {
  232. location: {
  233. ...LocationFixture(),
  234. query: {
  235. ...EventView.fromSavedQuery(DEFAULT_EVENT_VIEW).generateQueryStringObject(),
  236. },
  237. },
  238. },
  239. });
  240. mockHomepage = MockApiClient.addMockResponse({
  241. url: '/organizations/org-slug/discover/homepage/',
  242. method: 'GET',
  243. statusCode: 200,
  244. });
  245. render(
  246. <Homepage
  247. organization={organization}
  248. location={initialData.router.location}
  249. router={initialData.router}
  250. setSavedQuery={jest.fn()}
  251. loading={false}
  252. />,
  253. {context: initialData.routerContext, organization: initialData.organization}
  254. );
  255. expect(mockHomepage).toHaveBeenCalled();
  256. expect(screen.getByRole('button', {name: /set as default/i})).toBeDisabled();
  257. await waitFor(() => {
  258. expect(measurementsMetaMock).toHaveBeenCalled();
  259. });
  260. });
  261. it('follows absolute date selection', async () => {
  262. initialData = initializeOrg({
  263. organization,
  264. router: {
  265. location: {
  266. ...LocationFixture(),
  267. query: {
  268. ...EventView.fromSavedQuery(DEFAULT_EVENT_VIEW).generateQueryStringObject(),
  269. },
  270. },
  271. },
  272. });
  273. MockApiClient.addMockResponse({
  274. url: '/organizations/org-slug/discover/homepage/',
  275. method: 'GET',
  276. statusCode: 200,
  277. });
  278. render(
  279. <Homepage
  280. organization={organization}
  281. location={initialData.router.location}
  282. router={initialData.router}
  283. setSavedQuery={jest.fn()}
  284. loading={false}
  285. />,
  286. {context: initialData.routerContext, organization: initialData.organization}
  287. );
  288. await userEvent.click(await screen.findByText('24H'));
  289. await userEvent.click(await screen.findByText('Absolute date'));
  290. await userEvent.click(screen.getByText('Apply'));
  291. expect(screen.queryByText('14D')).not.toBeInTheDocument();
  292. });
  293. it('renders changes to the discover query when no homepage', async () => {
  294. initialData = initializeOrg({
  295. organization,
  296. router: {
  297. location: {
  298. ...LocationFixture(),
  299. query: {
  300. ...EventView.fromSavedQuery(DEFAULT_EVENT_VIEW).generateQueryStringObject(),
  301. field: ['title'],
  302. },
  303. },
  304. },
  305. });
  306. MockApiClient.addMockResponse({
  307. url: '/organizations/org-slug/discover/homepage/',
  308. method: 'GET',
  309. statusCode: 200,
  310. body: '',
  311. });
  312. const {rerender} = render(
  313. <Homepage
  314. organization={organization}
  315. location={initialData.router.location}
  316. router={initialData.router}
  317. setSavedQuery={jest.fn()}
  318. loading={false}
  319. />,
  320. {context: initialData.routerContext, organization: initialData.organization}
  321. );
  322. renderGlobalModal();
  323. // Simulate an update to the columns by changing the URL params
  324. const rerenderData = initializeOrg({
  325. organization,
  326. router: {
  327. location: {
  328. ...LocationFixture(),
  329. query: {
  330. ...EventView.fromSavedQuery(DEFAULT_EVENT_VIEW).generateQueryStringObject(),
  331. field: ['event.type'],
  332. },
  333. },
  334. },
  335. });
  336. rerender(
  337. <Homepage
  338. organization={organization}
  339. location={rerenderData.router.location}
  340. router={rerenderData.router}
  341. setSavedQuery={jest.fn()}
  342. loading={false}
  343. />
  344. );
  345. await waitFor(() => {
  346. expect(measurementsMetaMock).toHaveBeenCalled();
  347. });
  348. expect(screen.getByText('event.type')).toBeInTheDocument();
  349. });
  350. it('renders changes to the discover query when loaded with valid event view in url params', async () => {
  351. initialData = initializeOrg({
  352. organization,
  353. router: {
  354. location: {
  355. ...LocationFixture(),
  356. query: {
  357. ...EventView.fromSavedQuery(DEFAULT_EVENT_VIEW).generateQueryStringObject(),
  358. field: ['title'],
  359. },
  360. },
  361. },
  362. });
  363. const {rerender} = render(
  364. <Homepage
  365. organization={organization}
  366. location={initialData.router.location}
  367. router={initialData.router}
  368. setSavedQuery={jest.fn()}
  369. loading={false}
  370. />,
  371. {context: initialData.routerContext, organization: initialData.organization}
  372. );
  373. renderGlobalModal();
  374. // Simulate an update to the columns by changing the URL params
  375. const rerenderData = initializeOrg({
  376. organization,
  377. router: {
  378. location: {
  379. ...LocationFixture(),
  380. query: {
  381. ...EventView.fromSavedQuery(DEFAULT_EVENT_VIEW).generateQueryStringObject(),
  382. field: ['event.type'],
  383. },
  384. },
  385. },
  386. });
  387. rerender(
  388. <Homepage
  389. organization={organization}
  390. location={rerenderData.router.location}
  391. router={rerenderData.router}
  392. setSavedQuery={jest.fn()}
  393. loading={false}
  394. />
  395. );
  396. await waitFor(() => {
  397. expect(measurementsMetaMock).toHaveBeenCalled();
  398. });
  399. expect(screen.getByText('event.type')).toBeInTheDocument();
  400. });
  401. it('overrides homepage filters with pinned filters if they exist', async () => {
  402. ProjectsStore.loadInitialData([ProjectFixture({id: '1'}), ProjectFixture({id: '2'})]);
  403. jest.spyOn(pageFilterUtils, 'getPageFilterStorage').mockReturnValueOnce({
  404. pinnedFilters: new Set(['projects']),
  405. state: {
  406. project: [2],
  407. environment: [],
  408. start: null,
  409. end: null,
  410. period: '14d',
  411. utc: null,
  412. },
  413. });
  414. render(
  415. <Homepage
  416. organization={organization}
  417. location={initialData.router.location}
  418. router={initialData.router}
  419. setSavedQuery={jest.fn()}
  420. loading={false}
  421. />,
  422. {context: initialData.routerContext, organization: initialData.organization}
  423. );
  424. await waitFor(() => {
  425. expect(measurementsMetaMock).toHaveBeenCalled();
  426. });
  427. expect(screen.getByText('project-slug')).toBeInTheDocument();
  428. });
  429. it('allows users to set the All Events query as default', async () => {
  430. initialData = initializeOrg({
  431. organization,
  432. router: {
  433. location: {
  434. ...LocationFixture(),
  435. query: {
  436. ...EventView.fromSavedQuery(DEFAULT_EVENT_VIEW).generateQueryStringObject(),
  437. },
  438. },
  439. },
  440. });
  441. mockHomepage = MockApiClient.addMockResponse({
  442. url: '/organizations/org-slug/discover/homepage/',
  443. method: 'GET',
  444. statusCode: 200,
  445. });
  446. render(
  447. <Homepage
  448. organization={organization}
  449. location={initialData.router.location}
  450. router={initialData.router}
  451. setSavedQuery={jest.fn()}
  452. loading={false}
  453. />,
  454. {context: initialData.routerContext, organization: initialData.organization}
  455. );
  456. await waitFor(() => expect(screen.getByTestId('set-as-default')).toBeEnabled());
  457. });
  458. });