issueWidgetCard.spec.tsx 6.5 KB

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