issueWidgetCard.spec.tsx 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. import {OrganizationFixture} from 'sentry-fixture/organization';
  2. import {initializeOrg} from 'sentry-test/initializeOrg';
  3. import {render, screen, userEvent} from 'sentry-test/reactTestingLibrary';
  4. import MemberListStore from 'sentry/stores/memberListStore';
  5. import type {Widget} from 'sentry/views/dashboards/types';
  6. import {DisplayType, WidgetType} from 'sentry/views/dashboards/types';
  7. import WidgetCard from 'sentry/views/dashboards/widgetCard';
  8. import {IssueSortOptions} from 'sentry/views/issueList/utils';
  9. describe('Dashboards > IssueWidgetCard', function () {
  10. const {router, organization, routerContext} = initializeOrg({
  11. organization: OrganizationFixture({
  12. features: ['dashboards-edit'],
  13. }),
  14. router: {orgId: 'orgId'},
  15. } as Parameters<typeof initializeOrg>[0]);
  16. const widget: Widget = {
  17. title: 'Issues',
  18. interval: '5m',
  19. displayType: DisplayType.TABLE,
  20. widgetType: WidgetType.ISSUE,
  21. queries: [
  22. {
  23. conditions: 'event.type:default',
  24. fields: ['issue', 'assignee', 'title'],
  25. columns: ['issue', 'assignee', 'title'],
  26. aggregates: [],
  27. name: '',
  28. orderby: IssueSortOptions.FREQ,
  29. },
  30. ],
  31. };
  32. const selection = {
  33. projects: [1],
  34. environments: ['prod'],
  35. datetime: {
  36. period: '14d',
  37. start: null,
  38. end: null,
  39. utc: false,
  40. },
  41. };
  42. const api = new MockApiClient();
  43. beforeEach(function () {
  44. MockApiClient.addMockResponse({
  45. url: '/organizations/org-slug/issues/',
  46. body: [
  47. {
  48. id: '44444444',
  49. title: 'ChunkLoadError: Loading chunk app_bootstrap_index_tsx failed.',
  50. shortId: 'ISSUE',
  51. assignedTo: {
  52. type: 'user',
  53. id: '2222222',
  54. name: 'dashboard user',
  55. email: 'dashboarduser@sentry.io',
  56. },
  57. lifetime: {count: 10, userCount: 5},
  58. count: 6,
  59. userCount: 3,
  60. project: {id: 1},
  61. },
  62. ],
  63. });
  64. MockApiClient.addMockResponse({
  65. url: '/organizations/org-slug/users/',
  66. method: 'GET',
  67. body: [],
  68. });
  69. });
  70. afterEach(function () {
  71. MockApiClient.clearMockResponses();
  72. });
  73. it('renders with title and issues chart', async function () {
  74. MemberListStore.loadInitialData([]);
  75. render(
  76. <WidgetCard
  77. api={api}
  78. organization={organization}
  79. widget={widget}
  80. selection={selection}
  81. isEditingDashboard={false}
  82. onDelete={() => undefined}
  83. onEdit={() => undefined}
  84. onDuplicate={() => undefined}
  85. renderErrorMessage={() => undefined}
  86. showContextMenu
  87. widgetLimitReached={false}
  88. />
  89. );
  90. expect(await screen.findByText('Issues')).toBeInTheDocument();
  91. expect(screen.getByText('assignee')).toBeInTheDocument();
  92. expect(screen.getByText('title')).toBeInTheDocument();
  93. expect(screen.getByText('issue')).toBeInTheDocument();
  94. expect(screen.getByText('DU')).toBeInTheDocument();
  95. expect(screen.getByText('ISSUE')).toBeInTheDocument();
  96. expect(
  97. screen.getByText('ChunkLoadError: Loading chunk app_bootstrap_index_tsx failed.')
  98. ).toBeInTheDocument();
  99. await userEvent.hover(screen.getByTitle('dashboard user'));
  100. expect(await screen.findByText('Assigned to dashboard user')).toBeInTheDocument();
  101. });
  102. it('opens in issues page', async function () {
  103. render(
  104. <WidgetCard
  105. api={api}
  106. organization={organization}
  107. widget={widget}
  108. selection={selection}
  109. isEditingDashboard={false}
  110. onDelete={() => undefined}
  111. onEdit={() => undefined}
  112. onDuplicate={() => undefined}
  113. renderErrorMessage={() => undefined}
  114. showContextMenu
  115. widgetLimitReached={false}
  116. />,
  117. {context: routerContext}
  118. );
  119. await userEvent.click(await screen.findByLabelText('Widget actions'));
  120. expect(screen.getByText('Duplicate Widget')).toBeInTheDocument();
  121. expect(screen.getByText('Open in Issues')).toBeInTheDocument();
  122. await userEvent.click(screen.getByText('Open in Issues'));
  123. expect(router.push).toHaveBeenCalledWith(
  124. '/organizations/org-slug/issues/?environment=prod&project=1&query=event.type%3Adefault&sort=freq&statsPeriod=14d'
  125. );
  126. });
  127. it('calls onDuplicate when Duplicate Widget is clicked', async function () {
  128. const mock = jest.fn();
  129. render(
  130. <WidgetCard
  131. api={api}
  132. organization={organization}
  133. widget={widget}
  134. selection={selection}
  135. isEditingDashboard={false}
  136. onDelete={() => undefined}
  137. onEdit={() => undefined}
  138. onDuplicate={mock}
  139. renderErrorMessage={() => undefined}
  140. showContextMenu
  141. widgetLimitReached={false}
  142. />
  143. );
  144. await userEvent.click(await screen.findByLabelText('Widget actions'));
  145. expect(screen.getByText('Duplicate Widget')).toBeInTheDocument();
  146. await userEvent.click(screen.getByText('Duplicate Widget'));
  147. expect(mock).toHaveBeenCalledTimes(1);
  148. });
  149. it('disables the duplicate widget button if max widgets is reached', async function () {
  150. const mock = jest.fn();
  151. render(
  152. <WidgetCard
  153. api={api}
  154. organization={organization}
  155. widget={widget}
  156. selection={selection}
  157. isEditingDashboard={false}
  158. onDelete={() => undefined}
  159. onEdit={() => undefined}
  160. onDuplicate={mock}
  161. renderErrorMessage={() => undefined}
  162. showContextMenu
  163. widgetLimitReached
  164. />
  165. );
  166. await userEvent.click(await screen.findByLabelText('Widget actions'));
  167. expect(screen.getByText('Duplicate Widget')).toBeInTheDocument();
  168. await userEvent.click(screen.getByText('Duplicate Widget'));
  169. expect(mock).toHaveBeenCalledTimes(0);
  170. });
  171. it('maps lifetimeEvents and lifetimeUsers headers to more human readable', async function () {
  172. MemberListStore.loadInitialData([]);
  173. render(
  174. <WidgetCard
  175. api={api}
  176. organization={organization}
  177. widget={{
  178. ...widget,
  179. queries: [
  180. {
  181. ...widget.queries[0],
  182. fields: ['issue', 'assignee', 'title', 'lifetimeEvents', 'lifetimeUsers'],
  183. },
  184. ],
  185. }}
  186. selection={selection}
  187. isEditingDashboard={false}
  188. onDelete={() => undefined}
  189. onEdit={() => undefined}
  190. onDuplicate={() => undefined}
  191. renderErrorMessage={() => undefined}
  192. showContextMenu
  193. widgetLimitReached={false}
  194. />
  195. );
  196. expect(await screen.findByText('Lifetime Events')).toBeInTheDocument();
  197. expect(screen.getByText('Lifetime Users')).toBeInTheDocument();
  198. });
  199. });