issueWidgetCard.spec.tsx 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226
  1. import {initializeOrg} from 'sentry-test/initializeOrg';
  2. import {render, screen, userEvent} from 'sentry-test/reactTestingLibrary';
  3. import {Client} from 'sentry/api';
  4. import MemberListStore from 'sentry/stores/memberListStore';
  5. import {DisplayType, Widget, WidgetType} from 'sentry/views/dashboardsV2/types';
  6. import WidgetCard from 'sentry/views/dashboardsV2/widgetCard';
  7. import {IssueSortOptions} from 'sentry/views/issueList/utils';
  8. describe('Dashboards > IssueWidgetCard', function () {
  9. const {router, organization, routerContext} = initializeOrg({
  10. organization: TestStubs.Organization({
  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 Client();
  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. isEditing={false}
  81. onDelete={() => undefined}
  82. onEdit={() => undefined}
  83. onDuplicate={() => undefined}
  84. renderErrorMessage={() => undefined}
  85. isSorting={false}
  86. currentWidgetDragging={false}
  87. showContextMenu
  88. widgetLimitReached={false}
  89. />
  90. );
  91. expect(await screen.findByText('Issues')).toBeInTheDocument();
  92. expect(screen.getByText('assignee')).toBeInTheDocument();
  93. expect(screen.getByText('title')).toBeInTheDocument();
  94. expect(screen.getByText('issue')).toBeInTheDocument();
  95. expect(screen.getByText('DU')).toBeInTheDocument();
  96. expect(screen.getByText('ISSUE')).toBeInTheDocument();
  97. expect(
  98. screen.getByText('ChunkLoadError: Loading chunk app_bootstrap_index_tsx failed.')
  99. ).toBeInTheDocument();
  100. userEvent.hover(screen.getByTitle('dashboard user'));
  101. expect(await screen.findByText('Assigned to')).toBeInTheDocument();
  102. expect(await screen.findByText('dashboard user')).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. isEditing={false}
  112. onDelete={() => undefined}
  113. onEdit={() => undefined}
  114. onDuplicate={() => undefined}
  115. renderErrorMessage={() => undefined}
  116. isSorting={false}
  117. currentWidgetDragging={false}
  118. showContextMenu
  119. widgetLimitReached={false}
  120. />,
  121. {context: routerContext}
  122. );
  123. userEvent.click(await screen.findByLabelText('Widget actions'));
  124. expect(screen.getByText('Duplicate Widget')).toBeInTheDocument();
  125. expect(screen.getByText('Open in Issues')).toBeInTheDocument();
  126. userEvent.click(screen.getByText('Open in Issues'));
  127. expect(router.push).toHaveBeenCalledWith(
  128. '/organizations/org-slug/issues/?environment=prod&project=1&query=event.type%3Adefault&sort=freq&statsPeriod=14d'
  129. );
  130. });
  131. it('calls onDuplicate when Duplicate Widget is clicked', async function () {
  132. const mock = jest.fn();
  133. render(
  134. <WidgetCard
  135. api={api}
  136. organization={organization}
  137. widget={widget}
  138. selection={selection}
  139. isEditing={false}
  140. onDelete={() => undefined}
  141. onEdit={() => undefined}
  142. onDuplicate={mock}
  143. renderErrorMessage={() => undefined}
  144. isSorting={false}
  145. currentWidgetDragging={false}
  146. showContextMenu
  147. widgetLimitReached={false}
  148. />
  149. );
  150. userEvent.click(await screen.findByLabelText('Widget actions'));
  151. expect(screen.getByText('Duplicate Widget')).toBeInTheDocument();
  152. userEvent.click(screen.getByText('Duplicate Widget'));
  153. expect(mock).toHaveBeenCalledTimes(1);
  154. });
  155. it('disables the duplicate widget button if max widgets is reached', async function () {
  156. const mock = jest.fn();
  157. render(
  158. <WidgetCard
  159. api={api}
  160. organization={organization}
  161. widget={widget}
  162. selection={selection}
  163. isEditing={false}
  164. onDelete={() => undefined}
  165. onEdit={() => undefined}
  166. onDuplicate={mock}
  167. renderErrorMessage={() => undefined}
  168. isSorting={false}
  169. currentWidgetDragging={false}
  170. showContextMenu
  171. widgetLimitReached
  172. />
  173. );
  174. userEvent.click(await screen.findByLabelText('Widget actions'));
  175. expect(screen.getByText('Duplicate Widget')).toBeInTheDocument();
  176. userEvent.click(screen.getByText('Duplicate Widget'));
  177. expect(mock).toHaveBeenCalledTimes(0);
  178. });
  179. it('maps lifetimeEvents and lifetimeUsers headers to more human readable', async function () {
  180. MemberListStore.loadInitialData([]);
  181. render(
  182. <WidgetCard
  183. api={api}
  184. organization={organization}
  185. widget={{
  186. ...widget,
  187. queries: [
  188. {
  189. ...widget.queries[0],
  190. fields: ['issue', 'assignee', 'title', 'lifetimeEvents', 'lifetimeUsers'],
  191. },
  192. ],
  193. }}
  194. selection={selection}
  195. isEditing={false}
  196. onDelete={() => undefined}
  197. onEdit={() => undefined}
  198. onDuplicate={() => undefined}
  199. renderErrorMessage={() => undefined}
  200. isSorting={false}
  201. currentWidgetDragging={false}
  202. showContextMenu
  203. widgetLimitReached={false}
  204. />
  205. );
  206. expect(await screen.findByText('Lifetime Events')).toBeInTheDocument();
  207. expect(screen.getByText('Lifetime Users')).toBeInTheDocument();
  208. });
  209. });