issueWidgetCard.spec.tsx 7.8 KB

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