dashboardList.spec.tsx 7.8 KB

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