dashboardTable.spec.tsx 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  1. import {DashboardListItemFixture} from 'sentry-fixture/dashboard';
  2. import {LocationFixture} from 'sentry-fixture/locationFixture';
  3. import {OrganizationFixture} from 'sentry-fixture/organization';
  4. import {UserFixture} from 'sentry-fixture/user';
  5. import {initializeOrg} from 'sentry-test/initializeOrg';
  6. import {
  7. render,
  8. renderGlobalModal,
  9. screen,
  10. userEvent,
  11. waitFor,
  12. within,
  13. } from 'sentry-test/reactTestingLibrary';
  14. import DashboardTable from 'sentry/views/dashboards/manage/dashboardTable';
  15. import {type DashboardListItem, DisplayType} from 'sentry/views/dashboards/types';
  16. describe('Dashboards - DashboardTable', function () {
  17. let dashboards: DashboardListItem[];
  18. let deleteMock: jest.Mock;
  19. let dashboardUpdateMock: jest.Mock;
  20. let createMock: jest.Mock;
  21. const organization = OrganizationFixture({
  22. features: [
  23. 'global-views',
  24. 'dashboards-basic',
  25. 'dashboards-edit',
  26. 'discover-query',
  27. 'dashboards-table-view',
  28. ],
  29. });
  30. const {router} = initializeOrg();
  31. beforeEach(function () {
  32. MockApiClient.clearMockResponses();
  33. MockApiClient.addMockResponse({
  34. url: '/organizations/org-slug/projects/',
  35. body: [],
  36. });
  37. dashboards = [
  38. DashboardListItemFixture({
  39. id: '1',
  40. title: 'Dashboard 1',
  41. dateCreated: '2021-04-19T13:13:23.962105Z',
  42. createdBy: UserFixture({id: '1'}),
  43. permissions: {
  44. isEditableByEveryone: false,
  45. teamsWithEditAccess: [1],
  46. },
  47. isFavorited: true,
  48. }),
  49. DashboardListItemFixture({
  50. id: '2',
  51. title: 'Dashboard 2',
  52. dateCreated: '2021-04-19T13:13:23.962105Z',
  53. createdBy: UserFixture({id: '1'}),
  54. widgetPreview: [
  55. {
  56. displayType: DisplayType.LINE,
  57. layout: null,
  58. },
  59. {
  60. displayType: DisplayType.TABLE,
  61. layout: null,
  62. },
  63. ],
  64. }),
  65. ];
  66. deleteMock = MockApiClient.addMockResponse({
  67. url: '/organizations/org-slug/dashboards/2/',
  68. method: 'DELETE',
  69. statusCode: 200,
  70. });
  71. MockApiClient.addMockResponse({
  72. url: '/organizations/org-slug/dashboards/2/',
  73. method: 'GET',
  74. statusCode: 200,
  75. body: {
  76. id: '2',
  77. title: 'Dashboard Demo',
  78. widgets: [
  79. {
  80. id: '1',
  81. title: 'Errors',
  82. displayType: 'big_number',
  83. interval: '5m',
  84. },
  85. {
  86. id: '2',
  87. title: 'Transactions',
  88. displayType: 'big_number',
  89. interval: '5m',
  90. },
  91. {
  92. id: '3',
  93. title: 'p50 of /api/cat',
  94. displayType: 'big_number',
  95. interval: '5m',
  96. },
  97. ],
  98. },
  99. });
  100. createMock = MockApiClient.addMockResponse({
  101. url: '/organizations/org-slug/dashboards/',
  102. method: 'POST',
  103. statusCode: 200,
  104. });
  105. dashboardUpdateMock = jest.fn();
  106. });
  107. it('renders an empty list', async function () {
  108. render(
  109. <DashboardTable
  110. onDashboardsChange={jest.fn()}
  111. organization={organization}
  112. dashboards={[]}
  113. location={router.location}
  114. />
  115. );
  116. expect(screen.getByTestId('empty-state')).toBeInTheDocument();
  117. expect(
  118. await screen.findByText('Sorry, no Dashboards match your filters.')
  119. ).toBeInTheDocument();
  120. });
  121. it('renders dashboard list', async function () {
  122. render(
  123. <DashboardTable
  124. onDashboardsChange={jest.fn()}
  125. organization={organization}
  126. dashboards={dashboards}
  127. location={router.location}
  128. />
  129. );
  130. expect(await screen.findByText('Dashboard 1')).toBeInTheDocument();
  131. expect(await screen.findByText('Dashboard 2')).toBeInTheDocument();
  132. });
  133. it('returns landing page url for dashboards', async function () {
  134. render(
  135. <DashboardTable
  136. onDashboardsChange={jest.fn()}
  137. organization={organization}
  138. dashboards={dashboards}
  139. location={router.location}
  140. />,
  141. {router}
  142. );
  143. expect(await screen.findByRole('link', {name: 'Dashboard 1'})).toHaveAttribute(
  144. 'href',
  145. '/organizations/org-slug/dashboard/1/'
  146. );
  147. expect(await screen.findByRole('link', {name: 'Dashboard 2'})).toHaveAttribute(
  148. 'href',
  149. '/organizations/org-slug/dashboard/2/'
  150. );
  151. });
  152. it('persists global selection headers', async function () {
  153. render(
  154. <DashboardTable
  155. onDashboardsChange={jest.fn()}
  156. organization={organization}
  157. dashboards={dashboards}
  158. location={{...LocationFixture(), query: {statsPeriod: '7d'}}}
  159. />,
  160. {router}
  161. );
  162. expect(await screen.findByRole('link', {name: 'Dashboard 1'})).toHaveAttribute(
  163. 'href',
  164. '/organizations/org-slug/dashboard/1/?statsPeriod=7d'
  165. );
  166. });
  167. it('can delete dashboards', async function () {
  168. render(
  169. <DashboardTable
  170. organization={organization}
  171. dashboards={dashboards}
  172. location={{...LocationFixture(), query: {}}}
  173. onDashboardsChange={dashboardUpdateMock}
  174. />,
  175. {router}
  176. );
  177. renderGlobalModal();
  178. await userEvent.click(screen.getAllByTestId('dashboard-delete')[1]!);
  179. expect(deleteMock).not.toHaveBeenCalled();
  180. await userEvent.click(
  181. within(screen.getByRole('dialog')).getByRole('button', {name: /confirm/i})
  182. );
  183. await waitFor(() => {
  184. expect(deleteMock).toHaveBeenCalled();
  185. });
  186. expect(dashboardUpdateMock).toHaveBeenCalled();
  187. });
  188. it('cannot delete last dashboard', async function () {
  189. const singleDashboard = [
  190. DashboardListItemFixture({
  191. id: '1',
  192. title: 'Dashboard 1',
  193. dateCreated: '2021-04-19T13:13:23.962105Z',
  194. createdBy: UserFixture({id: '1'}),
  195. widgetPreview: [],
  196. }),
  197. ];
  198. render(
  199. <DashboardTable
  200. organization={organization}
  201. dashboards={singleDashboard}
  202. location={LocationFixture()}
  203. onDashboardsChange={dashboardUpdateMock}
  204. />
  205. );
  206. expect((await screen.findAllByTestId('dashboard-delete'))[0]).toHaveAttribute(
  207. 'aria-disabled',
  208. 'true'
  209. );
  210. });
  211. it('can duplicate dashboards', async function () {
  212. render(
  213. <DashboardTable
  214. organization={organization}
  215. dashboards={dashboards}
  216. location={{...LocationFixture(), query: {}}}
  217. onDashboardsChange={dashboardUpdateMock}
  218. />
  219. );
  220. renderGlobalModal();
  221. await userEvent.click(screen.getAllByTestId('dashboard-duplicate')[1]!);
  222. expect(createMock).not.toHaveBeenCalled();
  223. await userEvent.click(
  224. within(screen.getByRole('dialog')).getByRole('button', {name: /confirm/i})
  225. );
  226. await waitFor(() => {
  227. expect(createMock).toHaveBeenCalled();
  228. });
  229. expect(dashboardUpdateMock).toHaveBeenCalled();
  230. });
  231. it('does not throw an error if the POST fails during duplication', async function () {
  232. const postMock = MockApiClient.addMockResponse({
  233. url: '/organizations/org-slug/dashboards/',
  234. method: 'POST',
  235. statusCode: 404,
  236. });
  237. render(
  238. <DashboardTable
  239. organization={organization}
  240. dashboards={dashboards}
  241. location={{...LocationFixture(), query: {}}}
  242. onDashboardsChange={dashboardUpdateMock}
  243. />
  244. );
  245. renderGlobalModal();
  246. await userEvent.click(screen.getAllByTestId('dashboard-duplicate')[1]!);
  247. expect(postMock).not.toHaveBeenCalled();
  248. await userEvent.click(
  249. within(screen.getByRole('dialog')).getByRole('button', {name: /confirm/i})
  250. );
  251. await waitFor(() => {
  252. expect(postMock).toHaveBeenCalled();
  253. });
  254. // Should not update, and not throw error
  255. expect(dashboardUpdateMock).not.toHaveBeenCalled();
  256. });
  257. it('renders access column', async function () {
  258. const organizationWithEditAccess = OrganizationFixture({
  259. features: [
  260. 'global-views',
  261. 'dashboards-basic',
  262. 'dashboards-edit',
  263. 'discover-query',
  264. 'dashboards-table-view',
  265. ],
  266. });
  267. render(
  268. <DashboardTable
  269. onDashboardsChange={jest.fn()}
  270. organization={organizationWithEditAccess}
  271. dashboards={dashboards}
  272. location={router.location}
  273. />
  274. );
  275. expect(await screen.findAllByTestId('grid-head-cell')).toHaveLength(5);
  276. expect(screen.getByText('Access')).toBeInTheDocument();
  277. await userEvent.click((await screen.findAllByTestId('edit-access-dropdown'))[0]!);
  278. expect(screen.getAllByPlaceholderText('Search Teams')[0]).toBeInTheDocument();
  279. });
  280. it('renders favorite column', async function () {
  281. MockApiClient.addMockResponse({
  282. url: '/organizations/org-slug/dashboards/2/favorite/',
  283. method: 'PUT',
  284. body: {isFavorited: false},
  285. });
  286. const organizationWithFavorite = OrganizationFixture({
  287. features: [
  288. 'global-views',
  289. 'dashboards-basic',
  290. 'dashboards-edit',
  291. 'discover-query',
  292. 'dashboards-table-view',
  293. 'dashboards-favourite',
  294. ],
  295. });
  296. render(
  297. <DashboardTable
  298. onDashboardsChange={jest.fn()}
  299. organization={organizationWithFavorite}
  300. dashboards={dashboards}
  301. location={router.location}
  302. />,
  303. {
  304. organization: organizationWithFavorite,
  305. }
  306. );
  307. expect(screen.getByLabelText('Favorite Column')).toBeInTheDocument();
  308. expect(screen.queryAllByLabelText('Favorite')).toHaveLength(1);
  309. expect(screen.queryAllByLabelText('UnFavorite')).toHaveLength(1);
  310. await userEvent.click(screen.queryAllByLabelText('Favorite')[0]!);
  311. expect(screen.queryAllByLabelText('UnFavorite')).toHaveLength(2);
  312. });
  313. });