dashboardList.spec.tsx 7.8 KB

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