issueWidgetCard.spec.tsx 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218
  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} = 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. {router}
  120. );
  121. await userEvent.click(await screen.findByLabelText('Widget actions'));
  122. expect(screen.getByText('Duplicate Widget')).toBeInTheDocument();
  123. expect(screen.getByRole('link', {name: 'Open in Issues'})).toHaveAttribute(
  124. 'href',
  125. '/organizations/org-slug/issues/?environment=prod&project=1&query=event.type%3Adefault&sort=freq&statsPeriod=14d'
  126. );
  127. });
  128. it('calls onDuplicate when Duplicate Widget is clicked', async function () {
  129. const mock = jest.fn();
  130. render(
  131. <WidgetCard
  132. api={api}
  133. organization={organization}
  134. widget={widget}
  135. selection={selection}
  136. isEditingDashboard={false}
  137. onDelete={() => undefined}
  138. onEdit={() => undefined}
  139. onDuplicate={mock}
  140. renderErrorMessage={() => undefined}
  141. showContextMenu
  142. widgetLimitReached={false}
  143. />
  144. );
  145. await userEvent.click(await screen.findByLabelText('Widget actions'));
  146. expect(screen.getByText('Duplicate Widget')).toBeInTheDocument();
  147. await userEvent.click(screen.getByText('Duplicate Widget'));
  148. expect(mock).toHaveBeenCalledTimes(1);
  149. });
  150. it('disables the duplicate widget button if max widgets is reached', async function () {
  151. const mock = jest.fn();
  152. render(
  153. <WidgetCard
  154. api={api}
  155. organization={organization}
  156. widget={widget}
  157. selection={selection}
  158. isEditingDashboard={false}
  159. onDelete={() => undefined}
  160. onEdit={() => undefined}
  161. onDuplicate={mock}
  162. renderErrorMessage={() => undefined}
  163. showContextMenu
  164. widgetLimitReached
  165. />
  166. );
  167. await userEvent.click(await screen.findByLabelText('Widget actions'));
  168. expect(screen.getByText('Duplicate Widget')).toBeInTheDocument();
  169. await userEvent.click(screen.getByText('Duplicate Widget'));
  170. expect(mock).toHaveBeenCalledTimes(0);
  171. });
  172. it('maps lifetimeEvents and lifetimeUsers headers to more human readable', async function () {
  173. MemberListStore.loadInitialData([user]);
  174. render(
  175. <WidgetCard
  176. api={api}
  177. organization={organization}
  178. widget={{
  179. ...widget,
  180. queries: [
  181. {
  182. ...widget.queries[0],
  183. fields: ['issue', 'assignee', 'title', 'lifetimeEvents', 'lifetimeUsers'],
  184. },
  185. ],
  186. }}
  187. selection={selection}
  188. isEditingDashboard={false}
  189. onDelete={() => undefined}
  190. onEdit={() => undefined}
  191. onDuplicate={() => undefined}
  192. renderErrorMessage={() => undefined}
  193. showContextMenu
  194. widgetLimitReached={false}
  195. />
  196. );
  197. expect(await screen.findByText('Lifetime Events')).toBeInTheDocument();
  198. expect(screen.getByText('Lifetime Users')).toBeInTheDocument();
  199. });
  200. });