dashboardList.spec.jsx 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294
  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. organization={organization}
  126. dashboards={[]}
  127. pageLinks=""
  128. location={router.location}
  129. />
  130. );
  131. expect(screen.getByTestId('empty-state')).toBeInTheDocument();
  132. });
  133. it('renders dashboard list', function () {
  134. render(
  135. <DashboardList
  136. organization={organization}
  137. dashboards={dashboards}
  138. pageLinks=""
  139. location={router.location}
  140. />
  141. );
  142. expect(screen.getByText('Dashboard 1')).toBeInTheDocument();
  143. expect(screen.getByText('Dashboard 2')).toBeInTheDocument();
  144. });
  145. it('returns landing page url for dashboards', function () {
  146. render(
  147. <DashboardList
  148. organization={organization}
  149. dashboards={dashboards}
  150. pageLinks=""
  151. location={router.location}
  152. />,
  153. {context: routerContext}
  154. );
  155. expect(screen.getByRole('link', {name: 'Dashboard 1'})).toHaveAttribute(
  156. 'href',
  157. '/organizations/org-slug/dashboard/1/?'
  158. );
  159. expect(screen.getByRole('link', {name: 'Dashboard 2'})).toHaveAttribute(
  160. 'href',
  161. '/organizations/org-slug/dashboard/2/?'
  162. );
  163. });
  164. it('persists global selection headers', function () {
  165. render(
  166. <DashboardList
  167. organization={organization}
  168. dashboards={dashboards}
  169. pageLinks=""
  170. location={{query: {statsPeriod: '7d'}}}
  171. />,
  172. {context: routerContext}
  173. );
  174. expect(screen.getByRole('link', {name: 'Dashboard 1'})).toHaveAttribute(
  175. 'href',
  176. '/organizations/org-slug/dashboard/1/?statsPeriod=7d'
  177. );
  178. });
  179. it('can delete dashboards', async function () {
  180. render(
  181. <DashboardList
  182. organization={organization}
  183. dashboards={dashboards}
  184. pageLinks=""
  185. location={{query: {}}}
  186. onDashboardsChange={dashboardUpdateMock}
  187. />,
  188. {context: routerContext}
  189. );
  190. renderGlobalModal();
  191. await userEvent.click(screen.getAllByRole('button', {name: /dashboard actions/i})[1]);
  192. await userEvent.click(screen.getByTestId('dashboard-delete'));
  193. expect(deleteMock).not.toHaveBeenCalled();
  194. await userEvent.click(
  195. within(screen.getByRole('dialog')).getByRole('button', {name: /confirm/i})
  196. );
  197. await waitFor(() => {
  198. expect(deleteMock).toHaveBeenCalled();
  199. expect(dashboardUpdateMock).toHaveBeenCalled();
  200. });
  201. });
  202. it('cannot delete last dashboard', async function () {
  203. const singleDashboard = [
  204. TestStubs.Dashboard([], {
  205. id: '1',
  206. title: 'Dashboard 1',
  207. dateCreated: '2021-04-19T13:13:23.962105Z',
  208. createdBy: {id: '1'},
  209. widgetPreview: [],
  210. }),
  211. ];
  212. render(
  213. <DashboardList
  214. organization={organization}
  215. dashboards={singleDashboard}
  216. pageLinks=""
  217. location={{query: {}}}
  218. onDashboardsChange={dashboardUpdateMock}
  219. />
  220. );
  221. await userEvent.click(screen.getByRole('button', {name: /dashboard actions/i}));
  222. expect(screen.getByTestId('dashboard-delete')).toHaveAttribute(
  223. 'aria-disabled',
  224. 'true'
  225. );
  226. });
  227. it('can duplicate dashboards', async function () {
  228. render(
  229. <DashboardList
  230. organization={organization}
  231. dashboards={dashboards}
  232. pageLinks=""
  233. location={{query: {}}}
  234. onDashboardsChange={dashboardUpdateMock}
  235. />
  236. );
  237. await userEvent.click(screen.getAllByRole('button', {name: /dashboard actions/i})[1]);
  238. await userEvent.click(screen.getByTestId('dashboard-duplicate'));
  239. await waitFor(() => {
  240. expect(createMock).toHaveBeenCalled();
  241. expect(dashboardUpdateMock).toHaveBeenCalled();
  242. });
  243. });
  244. it('does not throw an error if the POST fails during duplication', async function () {
  245. const postMock = MockApiClient.addMockResponse({
  246. url: '/organizations/org-slug/dashboards/',
  247. method: 'POST',
  248. statusCode: 404,
  249. });
  250. render(
  251. <DashboardList
  252. organization={organization}
  253. dashboards={dashboards}
  254. pageLinks=""
  255. location={{query: {}}}
  256. onDashboardsChange={dashboardUpdateMock}
  257. />
  258. );
  259. await userEvent.click(screen.getAllByRole('button', {name: /dashboard actions/i})[1]);
  260. await userEvent.click(screen.getByTestId('dashboard-duplicate'));
  261. await waitFor(() => {
  262. expect(postMock).toHaveBeenCalled();
  263. // Should not update, and not throw error
  264. expect(dashboardUpdateMock).not.toHaveBeenCalled();
  265. });
  266. });
  267. });