dashboardList.spec.jsx 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298
  1. import {act} from 'react-dom/test-utils';
  2. import {selectDropdownMenuItem} from 'sentry-test/dropdownMenu';
  3. import {mountWithTheme} from 'sentry-test/enzyme';
  4. import {mountGlobalModal} from 'sentry-test/modal';
  5. import {triggerPress} from 'sentry-test/utils';
  6. import DashboardList from 'sentry/views/dashboardsV2/manage/dashboardList';
  7. describe('Dashboards > DashboardList', function () {
  8. let dashboards, widgets, deleteMock, dashboardUpdateMock, createMock;
  9. const organization = TestStubs.Organization({
  10. features: ['global-views', 'dashboards-basic', 'dashboards-edit', 'discover-query'],
  11. projects: [TestStubs.Project()],
  12. });
  13. beforeEach(function () {
  14. MockApiClient.addMockResponse({
  15. url: '/organizations/org-slug/projects/',
  16. body: [],
  17. });
  18. widgets = [
  19. TestStubs.Widget(
  20. [{name: '', conditions: 'event.type:error', fields: ['count()']}],
  21. {
  22. title: 'Errors',
  23. interval: '1d',
  24. id: '1',
  25. }
  26. ),
  27. TestStubs.Widget(
  28. [{name: '', conditions: 'event.type:transaction', fields: ['count()']}],
  29. {
  30. title: 'Transactions',
  31. interval: '1d',
  32. id: '2',
  33. }
  34. ),
  35. TestStubs.Widget(
  36. [
  37. {
  38. name: '',
  39. conditions: 'event.type:transaction transaction:/api/cats',
  40. fields: ['p50()'],
  41. },
  42. ],
  43. {
  44. title: 'p50 of /api/cats',
  45. interval: '1d',
  46. id: '3',
  47. }
  48. ),
  49. ];
  50. dashboards = [
  51. TestStubs.Dashboard([], {
  52. id: '1',
  53. title: 'Dashboard 1',
  54. dateCreated: '2021-04-19T13:13:23.962105Z',
  55. createdBy: {id: '1'},
  56. widgetPreview: [],
  57. }),
  58. TestStubs.Dashboard(widgets, {
  59. id: '2',
  60. title: 'Dashboard 2',
  61. dateCreated: '2021-04-19T13:13:23.962105Z',
  62. createdBy: {id: '1'},
  63. widgetPreview: [
  64. {
  65. displayType: 'line',
  66. layout: {},
  67. },
  68. {
  69. displayType: 'table',
  70. layout: {},
  71. },
  72. ],
  73. }),
  74. ];
  75. deleteMock = MockApiClient.addMockResponse({
  76. url: '/organizations/org-slug/dashboards/2/',
  77. method: 'DELETE',
  78. statusCode: 200,
  79. });
  80. MockApiClient.addMockResponse({
  81. url: '/organizations/org-slug/dashboards/2/',
  82. method: 'GET',
  83. statusCode: 200,
  84. body: {
  85. id: '2',
  86. title: 'Dashboard Demo',
  87. widgets: [
  88. {
  89. id: '1',
  90. title: 'Errors',
  91. displayType: 'big_number',
  92. interval: '5m',
  93. },
  94. {
  95. id: '2',
  96. title: 'Transactions',
  97. displayType: 'big_number',
  98. interval: '5m',
  99. },
  100. {
  101. id: '3',
  102. title: 'p50 of /api/cat',
  103. displayType: 'big_number',
  104. interval: '5m',
  105. },
  106. ],
  107. },
  108. });
  109. createMock = MockApiClient.addMockResponse({
  110. url: '/organizations/org-slug/dashboards/',
  111. method: 'POST',
  112. statusCode: 200,
  113. });
  114. dashboardUpdateMock = jest.fn();
  115. });
  116. afterEach(function () {
  117. MockApiClient.clearMockResponses();
  118. });
  119. it('renders an empty list', function () {
  120. const wrapper = mountWithTheme(
  121. <DashboardList
  122. organization={organization}
  123. dashboards={[]}
  124. pageLinks=""
  125. location={{query: {}}}
  126. />
  127. );
  128. const content = wrapper.find('DashboardCard');
  129. // No dashboards
  130. expect(content).toHaveLength(0);
  131. expect(wrapper.find('EmptyStateWarning')).toHaveLength(1);
  132. });
  133. it('renders dashboard list', function () {
  134. const wrapper = mountWithTheme(
  135. <DashboardList
  136. organization={organization}
  137. dashboards={dashboards}
  138. pageLinks=""
  139. location={{query: {}}}
  140. />
  141. );
  142. const content = wrapper.find('DashboardCard');
  143. expect(content).toHaveLength(2);
  144. });
  145. it('returns landing page url for dashboards', function () {
  146. const wrapper = mountWithTheme(
  147. <DashboardList
  148. organization={organization}
  149. dashboards={dashboards}
  150. pageLinks=""
  151. location={{query: {}}}
  152. />
  153. );
  154. const card = wrapper.find('DashboardCard').last();
  155. const link = card.find('Link').last().prop('to');
  156. expect(link.pathname).toEqual(`/organizations/org-slug/dashboard/2/`);
  157. });
  158. it('persists global selection headers', function () {
  159. const wrapper = mountWithTheme(
  160. <DashboardList
  161. organization={organization}
  162. dashboards={dashboards}
  163. pageLinks=""
  164. location={{query: {statsPeriod: '7d'}}}
  165. />
  166. );
  167. const card = wrapper.find('DashboardCard').last();
  168. const link = card.find('Link').last().prop('to');
  169. expect(link.pathname).toEqual(`/organizations/org-slug/dashboard/2/`);
  170. expect(link.query).toEqual({statsPeriod: '7d'});
  171. });
  172. it('can delete dashboards', async function () {
  173. const wrapper = mountWithTheme(
  174. <DashboardList
  175. organization={organization}
  176. dashboards={dashboards}
  177. pageLinks=""
  178. location={{query: {}}}
  179. onDashboardsChange={dashboardUpdateMock}
  180. />
  181. );
  182. const card = wrapper.find('DashboardCard').last();
  183. expect(card.find('Title').text()).toEqual(dashboards[1].title);
  184. await selectDropdownMenuItem({
  185. wrapper,
  186. specifiers: {prefix: 'DashboardCard', last: true},
  187. itemKey: 'dashboard-delete',
  188. });
  189. expect(deleteMock).not.toHaveBeenCalled();
  190. // Confirm
  191. const modal = await mountGlobalModal();
  192. modal.find('Button').last().simulate('click');
  193. await tick();
  194. expect(deleteMock).toHaveBeenCalled();
  195. expect(dashboardUpdateMock).toHaveBeenCalled();
  196. });
  197. it('cannot delete last dashboard', async function () {
  198. const singleDashboard = [
  199. TestStubs.Dashboard([], {
  200. id: '1',
  201. title: 'Dashboard 1',
  202. dateCreated: '2021-04-19T13:13:23.962105Z',
  203. createdBy: {id: '1'},
  204. widgetPreview: [],
  205. }),
  206. ];
  207. const wrapper = mountWithTheme(
  208. <DashboardList
  209. organization={organization}
  210. dashboards={singleDashboard}
  211. pageLinks=""
  212. location={{query: {}}}
  213. onDashboardsChange={dashboardUpdateMock}
  214. />
  215. );
  216. let card = wrapper.find('DashboardCard').last();
  217. expect(card.find('Title').text()).toEqual(singleDashboard[0].title);
  218. await act(async () => {
  219. triggerPress(card.find('DropdownTrigger'));
  220. await tick();
  221. wrapper.update();
  222. });
  223. card = wrapper.find('DashboardCard').last();
  224. const dashboardDelete = card.find(`MenuItemWrap[data-test-id="dashboard-delete"]`);
  225. expect(dashboardDelete.prop('aria-disabled')).toBe(true);
  226. });
  227. it('can duplicate dashboards', async function () {
  228. const wrapper = mountWithTheme(
  229. <DashboardList
  230. organization={organization}
  231. dashboards={dashboards}
  232. pageLinks=""
  233. location={{query: {}}}
  234. onDashboardsChange={dashboardUpdateMock}
  235. />
  236. );
  237. const card = wrapper.find('DashboardCard').last();
  238. expect(card.find('Title').text()).toEqual(dashboards[1].title);
  239. await selectDropdownMenuItem({
  240. wrapper,
  241. specifiers: {prefix: 'DashboardCard', last: true},
  242. itemKey: 'dashboard-duplicate',
  243. });
  244. expect(createMock).toHaveBeenCalled();
  245. expect(dashboardUpdateMock).toHaveBeenCalled();
  246. });
  247. it('does not throw an error if the POST fails during duplication', async function () {
  248. MockApiClient.addMockResponse({
  249. url: '/organizations/org-slug/dashboards/',
  250. method: 'POST',
  251. statusCode: 404,
  252. });
  253. const wrapper = mountWithTheme(
  254. <DashboardList
  255. organization={organization}
  256. dashboards={dashboards}
  257. pageLinks=""
  258. location={{query: {}}}
  259. onDashboardsChange={dashboardUpdateMock}
  260. />
  261. );
  262. const card = wrapper.find('DashboardCard').last();
  263. expect(card.find('Title').text()).toEqual(dashboards[1].title);
  264. await selectDropdownMenuItem({
  265. wrapper,
  266. specifiers: {prefix: 'DashboardCard', last: true},
  267. itemKey: 'dashboard-duplicate',
  268. });
  269. // Should not update, and not throw error
  270. expect(dashboardUpdateMock).not.toHaveBeenCalled();
  271. });
  272. });