issueWidgetCard.spec.tsx 6.6 KB

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