issueWidgetCard.spec.tsx 7.1 KB

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