index.spec.tsx 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387
  1. import {render, screen, userEvent, waitFor} from 'sentry-test/reactTestingLibrary';
  2. import {NewQuery} from 'sentry/types';
  3. import EventView from 'sentry/utils/discover/eventView';
  4. import {DisplayModes} from 'sentry/utils/discover/types';
  5. import {ALL_VIEWS} from 'sentry/views/discover/data';
  6. import SavedQueryButtonGroup from 'sentry/views/discover/savedQuery';
  7. import * as utils from 'sentry/views/discover/savedQuery/utils';
  8. jest.mock('sentry/actionCreators/modal');
  9. function mount(
  10. location,
  11. organization,
  12. router,
  13. eventView,
  14. savedQuery,
  15. yAxis,
  16. disabled = false,
  17. setSavedQuery = jest.fn()
  18. ) {
  19. return render(
  20. <SavedQueryButtonGroup
  21. location={location}
  22. organization={organization}
  23. eventView={eventView}
  24. savedQuery={savedQuery}
  25. disabled={disabled}
  26. updateCallback={() => {}}
  27. yAxis={yAxis}
  28. router={router}
  29. queryDataLoading={false}
  30. setSavedQuery={setSavedQuery}
  31. setHomepageQuery={jest.fn()}
  32. />
  33. );
  34. }
  35. describe('Discover > SaveQueryButtonGroup', function () {
  36. let organization;
  37. const location = {
  38. pathname: '/organization/eventsv2/',
  39. query: {},
  40. };
  41. const router = {
  42. location: {query: {}},
  43. };
  44. const yAxis = ['count()', 'failure_count()'];
  45. const errorsQuery = {
  46. ...(ALL_VIEWS.find(view => view.name === 'Errors by Title') as NewQuery),
  47. yAxis: ['count()'],
  48. display: DisplayModes.DEFAULT,
  49. };
  50. const errorsView = EventView.fromSavedQuery(errorsQuery);
  51. const errorsViewSaved = EventView.fromSavedQuery(errorsQuery);
  52. errorsViewSaved.id = '1';
  53. const errorsViewModified = EventView.fromSavedQuery(errorsQuery);
  54. errorsViewModified.id = '1';
  55. errorsViewModified.name = 'Modified Name';
  56. const savedQuery = {
  57. ...errorsViewSaved.toNewQuery(),
  58. yAxis,
  59. dateCreated: '',
  60. dateUpdated: '',
  61. id: '1',
  62. };
  63. beforeEach(() => {
  64. organization = TestStubs.Organization({
  65. features: ['discover-query', 'dashboards-edit'],
  66. });
  67. });
  68. afterEach(() => {
  69. MockApiClient.clearMockResponses();
  70. jest.clearAllMocks();
  71. });
  72. describe('building on a new query', () => {
  73. const mockUtils = jest
  74. .spyOn(utils, 'handleCreateQuery')
  75. .mockImplementation(() => Promise.resolve(savedQuery));
  76. beforeEach(() => {
  77. mockUtils.mockClear();
  78. });
  79. it('renders disabled buttons when disabled prop is used', () => {
  80. mount(location, organization, router, errorsView, undefined, yAxis, true);
  81. expect(screen.getByRole('button', {name: /save as/i})).toBeDisabled();
  82. });
  83. it('renders the correct set of buttons', () => {
  84. mount(location, organization, router, errorsView, undefined, yAxis);
  85. expect(screen.getByRole('button', {name: /save as/i})).toBeInTheDocument();
  86. expect(
  87. screen.queryByRole('button', {name: /saved for org/i})
  88. ).not.toBeInTheDocument();
  89. expect(
  90. screen.queryByRole('button', {name: /save changes/i})
  91. ).not.toBeInTheDocument();
  92. expect(screen.queryByRole('button', {name: /delete/i})).not.toBeInTheDocument();
  93. });
  94. it('renders the correct set of buttons with the homepage query feature', () => {
  95. organization = TestStubs.Organization({
  96. features: [
  97. 'discover-query',
  98. 'dashboards-edit',
  99. 'discover-query-builder-as-landing-page',
  100. ],
  101. });
  102. mount(location, organization, router, errorsView, undefined, yAxis);
  103. expect(screen.getByRole('button', {name: /save as/i})).toBeInTheDocument();
  104. expect(screen.getByRole('button', {name: /set as default/i})).toBeInTheDocument();
  105. expect(screen.getByRole('button', {name: /saved queries/i})).toBeInTheDocument();
  106. expect(
  107. screen.getByRole('button', {name: /discover context menu/i})
  108. ).toBeInTheDocument();
  109. expect(
  110. screen.queryByRole('button', {name: /saved for org/i})
  111. ).not.toBeInTheDocument();
  112. expect(
  113. screen.queryByRole('button', {name: /save changes/i})
  114. ).not.toBeInTheDocument();
  115. expect(screen.queryByRole('button', {name: /delete/i})).not.toBeInTheDocument();
  116. expect(
  117. screen.queryByRole('button', {name: /add to dashboard/i})
  118. ).not.toBeInTheDocument();
  119. });
  120. it('hides the banner when save is complete.', () => {
  121. mount(location, organization, router, errorsView, undefined, yAxis);
  122. // Click on ButtonSaveAs to open dropdown
  123. userEvent.click(screen.getByRole('button', {name: 'Save as'}));
  124. // Fill in the Input
  125. userEvent.type(screen.getByPlaceholderText('Display name'), 'My New Query Name');
  126. // Click on Save in the Dropdown
  127. userEvent.click(screen.getByRole('button', {name: 'Save for Org'}));
  128. // The banner should not render
  129. expect(screen.queryByText('Discover Trends')).not.toBeInTheDocument();
  130. });
  131. it('saves a well-formed query', () => {
  132. mount(location, organization, router, errorsView, undefined, yAxis);
  133. // Click on ButtonSaveAs to open dropdown
  134. userEvent.click(screen.getByRole('button', {name: 'Save as'}));
  135. // Fill in the Input
  136. userEvent.type(screen.getByPlaceholderText('Display name'), 'My New Query Name');
  137. // Click on Save in the Dropdown
  138. userEvent.click(screen.getByRole('button', {name: 'Save for Org'}));
  139. expect(mockUtils).toHaveBeenCalledWith(
  140. expect.anything(), // api
  141. organization,
  142. expect.objectContaining({
  143. ...errorsView,
  144. name: 'My New Query Name',
  145. }),
  146. yAxis,
  147. true
  148. );
  149. });
  150. it('rejects if query.name is empty', () => {
  151. mount(location, organization, router, errorsView, undefined, yAxis);
  152. // Click on ButtonSaveAs to open dropdown
  153. userEvent.click(screen.getByRole('button', {name: 'Save as'}));
  154. // Do not fill in Input
  155. // Click on Save in the Dropdown
  156. userEvent.click(screen.getByRole('button', {name: 'Save for Org'}));
  157. // Check that EventView has a name
  158. expect(errorsView.name).toBe('Errors by Title');
  159. expect(mockUtils).not.toHaveBeenCalled();
  160. });
  161. });
  162. describe('viewing a saved query', () => {
  163. let mockUtils;
  164. beforeEach(() => {
  165. mockUtils = jest
  166. .spyOn(utils, 'handleDeleteQuery')
  167. .mockImplementation(() => Promise.resolve());
  168. });
  169. afterEach(() => {
  170. mockUtils.mockClear();
  171. });
  172. it('renders the correct set of buttons', () => {
  173. mount(location, organization, router, errorsViewSaved, savedQuery, yAxis);
  174. expect(screen.queryByRole('button', {name: /save as/i})).not.toBeInTheDocument();
  175. expect(screen.getByRole('button', {name: /saved for org/i})).toBeInTheDocument();
  176. expect(
  177. screen.queryByRole('button', {name: /save changes/i})
  178. ).not.toBeInTheDocument();
  179. expect(screen.getByRole('button', {name: /delete/i})).toBeInTheDocument();
  180. });
  181. it('treats undefined yAxis the same as count() when checking for changes', () => {
  182. mount(
  183. location,
  184. organization,
  185. router,
  186. errorsViewSaved,
  187. {...savedQuery, yAxis: undefined},
  188. ['count()']
  189. );
  190. expect(screen.queryByRole('button', {name: /save as/i})).not.toBeInTheDocument();
  191. expect(screen.getByRole('button', {name: /saved for org/i})).toBeInTheDocument();
  192. expect(
  193. screen.queryByRole('button', {name: /save changes/i})
  194. ).not.toBeInTheDocument();
  195. expect(screen.getByRole('button', {name: /delete/i})).toBeInTheDocument();
  196. });
  197. it('converts string yAxis values to array when checking for changes', () => {
  198. mount(
  199. location,
  200. organization,
  201. router,
  202. errorsViewSaved,
  203. {...savedQuery, yAxis: 'count()'},
  204. ['count()']
  205. );
  206. expect(screen.queryByRole('button', {name: /save as/i})).not.toBeInTheDocument();
  207. expect(screen.getByRole('button', {name: /saved for org/i})).toBeInTheDocument();
  208. expect(
  209. screen.queryByRole('button', {name: /save changes/i})
  210. ).not.toBeInTheDocument();
  211. expect(screen.getByRole('button', {name: /delete/i})).toBeInTheDocument();
  212. });
  213. it('deletes the saved query', () => {
  214. mount(location, organization, router, errorsViewSaved, savedQuery, yAxis);
  215. userEvent.click(screen.getByRole('button', {name: /delete/i}));
  216. expect(mockUtils).toHaveBeenCalledWith(
  217. expect.anything(), // api
  218. organization,
  219. expect.objectContaining({id: '1'})
  220. );
  221. });
  222. });
  223. describe('modifying a saved query', () => {
  224. let mockUtils;
  225. it('renders the correct set of buttons', () => {
  226. mount(
  227. location,
  228. organization,
  229. router,
  230. errorsViewModified,
  231. errorsViewSaved.toNewQuery(),
  232. yAxis
  233. );
  234. expect(screen.queryByRole('button', {name: /save as/i})).toBeInTheDocument();
  235. expect(
  236. screen.queryByRole('button', {name: /saved for org/i})
  237. ).not.toBeInTheDocument();
  238. expect(screen.getByRole('button', {name: /save changes/i})).toBeInTheDocument();
  239. expect(screen.getByRole('button', {name: /delete/i})).toBeInTheDocument();
  240. });
  241. describe('updates the saved query', () => {
  242. beforeEach(() => {
  243. mockUtils = jest
  244. .spyOn(utils, 'handleUpdateQuery')
  245. .mockImplementation(() => Promise.resolve(savedQuery));
  246. });
  247. afterEach(() => {
  248. mockUtils.mockClear();
  249. });
  250. it('accepts a well-formed query', async () => {
  251. const mockSetSavedQuery = jest.fn();
  252. mount(
  253. location,
  254. organization,
  255. router,
  256. errorsViewModified,
  257. savedQuery,
  258. yAxis,
  259. false,
  260. mockSetSavedQuery
  261. );
  262. // Click on Save in the Dropdown
  263. userEvent.click(screen.getByRole('button', {name: /save changes/i}));
  264. await waitFor(() => {
  265. expect(mockUtils).toHaveBeenCalledWith(
  266. expect.anything(), // api
  267. organization,
  268. expect.objectContaining({
  269. ...errorsViewModified,
  270. }),
  271. yAxis
  272. );
  273. expect(mockSetSavedQuery).toHaveBeenCalled();
  274. });
  275. });
  276. });
  277. describe('creates a separate query', () => {
  278. beforeEach(() => {
  279. mockUtils = jest
  280. .spyOn(utils, 'handleCreateQuery')
  281. .mockImplementation(() => Promise.resolve(savedQuery));
  282. });
  283. afterEach(() => {
  284. mockUtils.mockClear();
  285. });
  286. it('checks that it is forked from a saved query', () => {
  287. mount(location, organization, router, errorsViewModified, savedQuery, yAxis);
  288. // Click on ButtonSaveAs to open dropdown
  289. userEvent.click(screen.getByRole('button', {name: 'Save as'}));
  290. // Fill in the Input
  291. userEvent.type(screen.getByPlaceholderText('Display name'), 'Forked Query');
  292. // Click on Save in the Dropdown
  293. userEvent.click(screen.getByRole('button', {name: 'Save for Org'}));
  294. expect(mockUtils).toHaveBeenCalledWith(
  295. expect.anything(), // api
  296. organization,
  297. expect.objectContaining({
  298. ...errorsViewModified,
  299. name: 'Forked Query',
  300. }),
  301. yAxis,
  302. false
  303. );
  304. });
  305. });
  306. });
  307. describe('create alert from discover', () => {
  308. it('renders create alert button when metrics alerts is enabled', () => {
  309. const metricAlertOrg = {
  310. ...organization,
  311. features: ['incidents'],
  312. };
  313. mount(location, metricAlertOrg, router, errorsViewModified, savedQuery, yAxis);
  314. expect(screen.getByRole('button', {name: /create alert/i})).toBeInTheDocument();
  315. });
  316. it('does not render create alert button without metric alerts', () => {
  317. mount(location, organization, router, errorsViewModified, savedQuery, yAxis);
  318. expect(
  319. screen.queryByRole('button', {name: /create alert/i})
  320. ).not.toBeInTheDocument();
  321. });
  322. });
  323. });