homepage.spec.tsx 14 KB

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