queryList.spec.jsx 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413
  1. import {act} from 'react-dom/test-utils';
  2. import {browserHistory} from 'react-router';
  3. import {selectDropdownMenuItem} from 'sentry-test/dropdownMenu';
  4. import {mountWithTheme} from 'sentry-test/enzyme';
  5. import {triggerPress} from 'sentry-test/utils';
  6. import {openAddToDashboardModal} from 'sentry/actionCreators/modal';
  7. import {DisplayModes} from 'sentry/utils/discover/types';
  8. import {DashboardWidgetSource, DisplayType} from 'sentry/views/dashboardsV2/types';
  9. import QueryList from 'sentry/views/eventsV2/queryList';
  10. jest.mock('sentry/actionCreators/modal');
  11. jest.mock('sentry/components/charts/eventsRequest');
  12. describe('EventsV2 > QueryList', function () {
  13. let location, savedQueries, organization, deleteMock, duplicateMock, queryChangeMock;
  14. beforeAll(async function () {
  15. await import('sentry/components/modals/widgetBuilder/addToDashboardModal');
  16. });
  17. beforeEach(function () {
  18. organization = TestStubs.Organization();
  19. savedQueries = [
  20. TestStubs.DiscoverSavedQuery(),
  21. TestStubs.DiscoverSavedQuery({name: 'saved query 2', id: '2'}),
  22. ];
  23. MockApiClient.addMockResponse({
  24. url: '/organizations/org-slug/events-stats/',
  25. method: 'GET',
  26. statusCode: 200,
  27. body: {data: []},
  28. });
  29. deleteMock = MockApiClient.addMockResponse({
  30. url: '/organizations/org-slug/discover/saved/2/',
  31. method: 'DELETE',
  32. statusCode: 200,
  33. body: {},
  34. });
  35. duplicateMock = MockApiClient.addMockResponse({
  36. url: '/organizations/org-slug/discover/saved/',
  37. method: 'POST',
  38. body: {
  39. id: '3',
  40. name: 'Saved query copy',
  41. },
  42. });
  43. location = {
  44. pathname: '/organizations/org-slug/discover/queries/',
  45. query: {cursor: '0:1:1', statsPeriod: '14d'},
  46. };
  47. queryChangeMock = jest.fn();
  48. });
  49. afterEach(() => {
  50. jest.clearAllMocks();
  51. });
  52. it('renders an empty list', function () {
  53. const wrapper = mountWithTheme(
  54. <QueryList
  55. organization={organization}
  56. savedQueries={[]}
  57. savedQuerySearchQuery="no matches"
  58. pageLinks=""
  59. onQueryChange={queryChangeMock}
  60. location={location}
  61. />
  62. );
  63. const content = wrapper.find('QueryCard');
  64. // No queries
  65. expect(content).toHaveLength(0);
  66. expect(wrapper.find('EmptyStateWarning')).toHaveLength(1);
  67. });
  68. it('renders pre-built queries and saved ones', function () {
  69. const wrapper = mountWithTheme(
  70. <QueryList
  71. organization={organization}
  72. savedQueries={savedQueries}
  73. renderPrebuilt
  74. pageLinks=""
  75. onQueryChange={queryChangeMock}
  76. location={location}
  77. />
  78. );
  79. const content = wrapper.find('QueryCard');
  80. // pre built + saved queries
  81. expect(content).toHaveLength(5);
  82. });
  83. it('can duplicate and trigger change callback', async function () {
  84. const wrapper = mountWithTheme(
  85. <QueryList
  86. organization={organization}
  87. savedQueries={savedQueries}
  88. pageLinks=""
  89. onQueryChange={queryChangeMock}
  90. location={location}
  91. />
  92. );
  93. const card = wrapper.find('QueryCard').last();
  94. expect(card.find('QueryCardContent').text()).toEqual(savedQueries[1].name);
  95. await selectDropdownMenuItem({
  96. wrapper,
  97. specifiers: {prefix: 'QueryCard', last: true},
  98. itemKey: 'duplicate',
  99. });
  100. expect(duplicateMock).toHaveBeenCalled();
  101. expect(queryChangeMock).toHaveBeenCalled();
  102. });
  103. it('can delete and trigger change callback', async function () {
  104. const wrapper = mountWithTheme(
  105. <QueryList
  106. organization={organization}
  107. savedQueries={savedQueries}
  108. pageLinks=""
  109. onQueryChange={queryChangeMock}
  110. location={location}
  111. />
  112. );
  113. const card = wrapper.find('QueryCard').last();
  114. expect(card.find('QueryCardContent').text()).toEqual(savedQueries[1].name);
  115. await selectDropdownMenuItem({
  116. wrapper,
  117. specifiers: {prefix: 'QueryCard', last: true},
  118. itemKey: 'delete',
  119. });
  120. expect(deleteMock).toHaveBeenCalled();
  121. expect(queryChangeMock).toHaveBeenCalled();
  122. });
  123. it('returns short url location for saved query', function () {
  124. const wrapper = mountWithTheme(
  125. <QueryList
  126. organization={organization}
  127. savedQueries={savedQueries}
  128. pageLinks=""
  129. onQueryChange={queryChangeMock}
  130. location={location}
  131. />
  132. );
  133. const card = wrapper.find('QueryCard').last();
  134. const link = card.find('Link').last().prop('to');
  135. expect(link.pathname).toEqual('/organizations/org-slug/discover/results/');
  136. expect(link.query).toEqual({
  137. id: '2',
  138. statsPeriod: '14d',
  139. });
  140. });
  141. it('can redirect on last query deletion', async function () {
  142. const wrapper = mountWithTheme(
  143. <QueryList
  144. organization={organization}
  145. savedQueries={savedQueries.slice(1)}
  146. pageLinks=""
  147. onQueryChange={queryChangeMock}
  148. location={location}
  149. />
  150. );
  151. const card = wrapper.find('QueryCard').last();
  152. expect(card.find('QueryCardContent').text()).toEqual(savedQueries[1].name);
  153. await selectDropdownMenuItem({
  154. wrapper,
  155. specifiers: {prefix: 'QueryCard', last: true},
  156. itemKey: 'delete',
  157. });
  158. expect(deleteMock).toHaveBeenCalled();
  159. expect(queryChangeMock).not.toHaveBeenCalled();
  160. expect(browserHistory.push).toHaveBeenCalledWith({
  161. pathname: location.pathname,
  162. query: {cursor: undefined, statsPeriod: '14d'},
  163. });
  164. });
  165. it('renders Add to Dashboard in context menu with feature flag', async function () {
  166. const featuredOrganization = TestStubs.Organization({
  167. features: ['dashboards-edit'],
  168. });
  169. const wrapper = mountWithTheme(
  170. <QueryList
  171. organization={featuredOrganization}
  172. savedQueries={savedQueries.slice(1)}
  173. pageLinks=""
  174. onQueryChange={queryChangeMock}
  175. location={location}
  176. />
  177. );
  178. let card = wrapper.find('QueryCard').last();
  179. await act(async () => {
  180. triggerPress(card.find('DropdownTrigger'));
  181. await tick();
  182. wrapper.update();
  183. });
  184. card = wrapper.find('QueryCard').last();
  185. const menuItems = card.find('MenuItemWrap');
  186. expect(menuItems.length).toEqual(3);
  187. expect(menuItems.at(0).text()).toEqual('Add to Dashboard');
  188. expect(menuItems.at(1).text()).toEqual('Duplicate Query');
  189. expect(menuItems.at(2).text()).toEqual('Delete Query');
  190. });
  191. it('only renders Delete Query and Duplicate Query in context menu', async function () {
  192. const wrapper = mountWithTheme(
  193. <QueryList
  194. organization={organization}
  195. savedQueries={savedQueries.slice(1)}
  196. pageLinks=""
  197. onQueryChange={queryChangeMock}
  198. location={location}
  199. />
  200. );
  201. let card = wrapper.find('QueryCard').last();
  202. await act(async () => {
  203. triggerPress(card.find('DropdownTrigger'));
  204. await tick();
  205. wrapper.update();
  206. });
  207. card = wrapper.find('QueryCard').last();
  208. const menuItems = card.find('MenuItemWrap');
  209. expect(menuItems.length).toEqual(2);
  210. expect(menuItems.at(0).text()).toEqual('Duplicate Query');
  211. expect(menuItems.at(1).text()).toEqual('Delete Query');
  212. });
  213. it('passes yAxis from the savedQuery to MiniGraph', function () {
  214. const featuredOrganization = TestStubs.Organization({
  215. features: ['dashboards-edit'],
  216. });
  217. const yAxis = ['count()', 'failure_count()'];
  218. const savedQueryWithMultiYAxis = {
  219. ...savedQueries.slice(1)[0],
  220. yAxis,
  221. };
  222. const wrapper = mountWithTheme(
  223. <QueryList
  224. organization={featuredOrganization}
  225. savedQueries={[savedQueryWithMultiYAxis]}
  226. pageLinks=""
  227. onQueryChange={queryChangeMock}
  228. location={location}
  229. />
  230. );
  231. const miniGraph = wrapper.find('MiniGraph');
  232. expect(miniGraph.props().yAxis).toEqual(['count()', 'failure_count()']);
  233. });
  234. describe('Add to Dashboard modal', () => {
  235. it('opens a modal with the correct params for Top 5 chart', async function () {
  236. const featuredOrganization = TestStubs.Organization({
  237. features: ['dashboards-edit'],
  238. });
  239. const wrapper = mountWithTheme(
  240. <QueryList
  241. organization={featuredOrganization}
  242. savedQueries={[
  243. TestStubs.DiscoverSavedQuery({
  244. display: DisplayModes.TOP5,
  245. orderby: 'test',
  246. fields: ['test', 'count()'],
  247. yAxis: ['count()'],
  248. }),
  249. ]}
  250. pageLinks=""
  251. onQueryChange={queryChangeMock}
  252. location={location}
  253. />
  254. );
  255. let card = wrapper.find('QueryCard').last();
  256. await act(async () => {
  257. triggerPress(card.find('DropdownTrigger'));
  258. await tick();
  259. wrapper.update();
  260. });
  261. card = wrapper.find('QueryCard').last();
  262. const menuItems = card.find('MenuItemWrap');
  263. expect(menuItems.length).toEqual(3);
  264. expect(menuItems.at(0).text()).toEqual('Add to Dashboard');
  265. await act(async () => {
  266. triggerPress(menuItems.at(0));
  267. await tick();
  268. wrapper.update();
  269. });
  270. expect(openAddToDashboardModal).toHaveBeenCalledWith(
  271. expect.objectContaining({
  272. widget: {
  273. title: 'Saved query #1',
  274. displayType: DisplayType.AREA,
  275. limit: 5,
  276. queries: [
  277. {
  278. aggregates: ['count()'],
  279. columns: ['test'],
  280. conditions: '',
  281. fields: ['test', 'count()', 'count()'],
  282. name: '',
  283. orderby: 'test',
  284. },
  285. ],
  286. },
  287. widgetAsQueryParams: expect.objectContaining({
  288. defaultTableColumns: ['test', 'count()'],
  289. defaultTitle: 'Saved query #1',
  290. defaultWidgetQuery:
  291. 'name=&aggregates=count()&columns=test&fields=test%2Ccount()%2Ccount()&conditions=&orderby=test',
  292. displayType: DisplayType.AREA,
  293. source: DashboardWidgetSource.DISCOVERV2,
  294. }),
  295. })
  296. );
  297. });
  298. it('opens a modal with the correct params for other chart', async function () {
  299. const featuredOrganization = TestStubs.Organization({
  300. features: ['dashboards-edit'],
  301. });
  302. const wrapper = mountWithTheme(
  303. <QueryList
  304. organization={featuredOrganization}
  305. savedQueries={[
  306. TestStubs.DiscoverSavedQuery({
  307. display: DisplayModes.DEFAULT,
  308. orderby: 'count()',
  309. fields: ['test', 'count()'],
  310. yAxis: ['count()'],
  311. }),
  312. ]}
  313. pageLinks=""
  314. onQueryChange={queryChangeMock}
  315. location={location}
  316. />
  317. );
  318. let card = wrapper.find('QueryCard').last();
  319. await act(async () => {
  320. triggerPress(card.find('DropdownTrigger'));
  321. await tick();
  322. wrapper.update();
  323. });
  324. card = wrapper.find('QueryCard').last();
  325. const menuItems = card.find('MenuItemWrap');
  326. expect(menuItems.length).toEqual(3);
  327. expect(menuItems.at(0).text()).toEqual('Add to Dashboard');
  328. await act(async () => {
  329. triggerPress(menuItems.at(0));
  330. await tick();
  331. wrapper.update();
  332. });
  333. expect(openAddToDashboardModal).toHaveBeenCalledWith(
  334. expect.objectContaining({
  335. widget: {
  336. title: 'Saved query #1',
  337. displayType: DisplayType.LINE,
  338. queries: [
  339. {
  340. aggregates: ['count()'],
  341. columns: [],
  342. conditions: '',
  343. fields: ['count()'],
  344. name: '',
  345. // Orderby gets dropped because ordering only applies to
  346. // Top-N and tables
  347. orderby: '',
  348. },
  349. ],
  350. },
  351. widgetAsQueryParams: expect.objectContaining({
  352. defaultTableColumns: ['test', 'count()'],
  353. defaultTitle: 'Saved query #1',
  354. defaultWidgetQuery:
  355. 'name=&aggregates=count()&columns=&fields=count()&conditions=&orderby=',
  356. displayType: DisplayType.LINE,
  357. source: DashboardWidgetSource.DISCOVERV2,
  358. }),
  359. })
  360. );
  361. });
  362. });
  363. });