createAlertButton.spec.tsx 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. import {OrganizationFixture} from 'sentry-fixture/organization';
  2. import {ProjectFixture} from 'sentry-fixture/project';
  3. import {RouterFixture} from 'sentry-fixture/routerFixture';
  4. import {render, screen, userEvent} from 'sentry-test/reactTestingLibrary';
  5. import {navigateTo} from 'sentry/actionCreators/navigation';
  6. import CreateAlertButton, {
  7. CreateAlertFromViewButton,
  8. } from 'sentry/components/createAlertButton';
  9. import GuideStore from 'sentry/stores/guideStore';
  10. import EventView from 'sentry/utils/discover/eventView';
  11. import useProjects from 'sentry/utils/useProjects';
  12. import {DEFAULT_EVENT_VIEW} from 'sentry/views/discover/data';
  13. const onClickMock = jest.fn();
  14. jest.mock('sentry/utils/useProjects');
  15. jest.mock('sentry/actionCreators/navigation');
  16. describe('CreateAlertFromViewButton', () => {
  17. const organization = OrganizationFixture();
  18. beforeEach(() => {
  19. jest.mocked(useProjects).mockReturnValue({
  20. projects: [],
  21. onSearch: jest.fn(),
  22. reloadProjects: jest.fn(),
  23. placeholders: [],
  24. fetching: false,
  25. hasMore: null,
  26. fetchError: null,
  27. initiallyLoaded: false,
  28. });
  29. });
  30. afterEach(() => {
  31. jest.resetAllMocks();
  32. });
  33. it('should trigger onClick callback', async () => {
  34. const eventView = EventView.fromSavedQuery({
  35. ...DEFAULT_EVENT_VIEW,
  36. query: 'event.type:error',
  37. projects: [2],
  38. });
  39. render(
  40. <CreateAlertFromViewButton
  41. organization={organization}
  42. eventView={eventView}
  43. projects={[ProjectFixture()]}
  44. onClick={onClickMock}
  45. />
  46. );
  47. await userEvent.click(screen.getByRole('button', {name: 'Create Alert'}));
  48. expect(onClickMock).toHaveBeenCalledTimes(1);
  49. });
  50. it('disables the button for org-member', () => {
  51. const eventView = EventView.fromSavedQuery({
  52. ...DEFAULT_EVENT_VIEW,
  53. });
  54. const noAccessOrg = {
  55. ...organization,
  56. access: [],
  57. };
  58. const projects = [
  59. {
  60. ...ProjectFixture(),
  61. access: [],
  62. },
  63. ];
  64. jest.mocked(useProjects).mockReturnValue({
  65. projects,
  66. onSearch: jest.fn(),
  67. reloadProjects: jest.fn(),
  68. placeholders: [],
  69. fetching: false,
  70. hasMore: null,
  71. fetchError: null,
  72. initiallyLoaded: false,
  73. });
  74. render(
  75. <CreateAlertFromViewButton
  76. organization={noAccessOrg}
  77. eventView={eventView}
  78. projects={projects}
  79. onClick={onClickMock}
  80. />,
  81. {
  82. organization: noAccessOrg,
  83. }
  84. );
  85. expect(screen.getByRole('button', {name: 'Create Alert'})).toBeDisabled();
  86. });
  87. it('enables the button for org-owner/manager', () => {
  88. const eventView = EventView.fromSavedQuery({
  89. ...DEFAULT_EVENT_VIEW,
  90. });
  91. const projects = [
  92. {
  93. ...ProjectFixture(),
  94. access: [],
  95. },
  96. ];
  97. jest.mocked(useProjects).mockReturnValue({
  98. projects,
  99. onSearch: jest.fn(),
  100. reloadProjects: jest.fn(),
  101. placeholders: [],
  102. fetching: false,
  103. hasMore: null,
  104. fetchError: null,
  105. initiallyLoaded: false,
  106. });
  107. render(
  108. <CreateAlertFromViewButton
  109. organization={organization}
  110. eventView={eventView}
  111. projects={projects}
  112. onClick={onClickMock}
  113. />,
  114. {
  115. organization,
  116. }
  117. );
  118. expect(screen.getByRole('button', {name: 'Create Alert'})).toBeEnabled();
  119. });
  120. it('enables the button for team-admin', () => {
  121. const eventView = EventView.fromSavedQuery({
  122. ...DEFAULT_EVENT_VIEW,
  123. });
  124. const noAccessOrg = {
  125. ...organization,
  126. access: [],
  127. };
  128. const projects = [
  129. {
  130. ...ProjectFixture(),
  131. id: '1',
  132. slug: 'admin-team',
  133. access: ['alerts:write' as const],
  134. },
  135. {
  136. ...ProjectFixture(),
  137. id: '2',
  138. slug: 'contributor-team',
  139. access: ['alerts:read' as const],
  140. },
  141. ];
  142. jest.mocked(useProjects).mockReturnValue({
  143. projects,
  144. onSearch: jest.fn(),
  145. reloadProjects: jest.fn(),
  146. placeholders: [],
  147. fetching: false,
  148. hasMore: null,
  149. fetchError: null,
  150. initiallyLoaded: false,
  151. });
  152. render(
  153. <CreateAlertFromViewButton
  154. organization={noAccessOrg}
  155. eventView={eventView}
  156. projects={projects}
  157. onClick={onClickMock}
  158. />,
  159. {
  160. organization: noAccessOrg,
  161. }
  162. );
  163. expect(screen.getByRole('button', {name: 'Create Alert'})).toBeEnabled();
  164. });
  165. it('shows a guide for org-member', () => {
  166. const noAccessOrg = {
  167. ...organization,
  168. access: [],
  169. };
  170. render(
  171. <CreateAlertButton
  172. aria-label="Create Alert"
  173. organization={noAccessOrg}
  174. showPermissionGuide
  175. />,
  176. {
  177. organization: noAccessOrg,
  178. }
  179. );
  180. expect(GuideStore.state.anchors).toEqual(new Set(['alerts_write_member']));
  181. });
  182. it('shows a guide for org-owner/manager', () => {
  183. const adminAccessOrg = OrganizationFixture({
  184. ...organization,
  185. access: ['org:write'],
  186. });
  187. render(
  188. <CreateAlertButton
  189. aria-label="Create Alert"
  190. organization={adminAccessOrg}
  191. showPermissionGuide
  192. />,
  193. {
  194. organization: adminAccessOrg,
  195. }
  196. );
  197. expect(GuideStore.state.anchors).toEqual(new Set(['alerts_write_owner']));
  198. });
  199. it('redirects to alert wizard with no project', async () => {
  200. render(<CreateAlertButton aria-label="Create Alert" organization={organization} />, {
  201. organization,
  202. });
  203. await userEvent.click(screen.getByRole('button'));
  204. expect(navigateTo).toHaveBeenCalledWith(
  205. `/organizations/org-slug/alerts/wizard/?`,
  206. expect.objectContaining({
  207. params: expect.objectContaining({
  208. orgId: 'org-slug',
  209. }),
  210. })
  211. );
  212. });
  213. it('redirects to alert wizard with a project', () => {
  214. render(
  215. <CreateAlertButton
  216. aria-label="Create Alert"
  217. organization={organization}
  218. projectSlug="proj-slug"
  219. />,
  220. {
  221. organization,
  222. }
  223. );
  224. expect(screen.getByRole('button')).toHaveAttribute(
  225. 'href',
  226. '/organizations/org-slug/alerts/wizard/?project=proj-slug'
  227. );
  228. });
  229. it('removes a duplicate project filter', async () => {
  230. const router = RouterFixture();
  231. const projects = [ProjectFixture()];
  232. jest.mocked(useProjects).mockReturnValue({
  233. projects,
  234. onSearch: jest.fn(),
  235. reloadProjects: jest.fn(),
  236. placeholders: [],
  237. fetching: false,
  238. hasMore: null,
  239. fetchError: null,
  240. initiallyLoaded: false,
  241. });
  242. const eventView = EventView.fromSavedQuery({
  243. ...DEFAULT_EVENT_VIEW,
  244. query: 'event.type:error project:project-slug',
  245. projects: [2],
  246. });
  247. render(
  248. <CreateAlertFromViewButton
  249. organization={organization}
  250. eventView={eventView}
  251. projects={projects}
  252. onClick={onClickMock}
  253. />,
  254. {router}
  255. );
  256. await userEvent.click(screen.getByRole('button'));
  257. expect(router.push).toHaveBeenCalledWith({
  258. pathname: `/organizations/org-slug/alerts/new/metric/`,
  259. query: expect.objectContaining({
  260. query: 'event.type:error ',
  261. project: 'project-slug',
  262. }),
  263. });
  264. });
  265. });