productSelectionAvailability.spec.tsx 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629
  1. import {BillingConfigFixture} from 'getsentry-test/fixtures/billingConfig';
  2. import {SubscriptionFixture} from 'getsentry-test/fixtures/subscription';
  3. import {initializeOrg} from 'sentry-test/initializeOrg';
  4. import {
  5. act,
  6. render,
  7. renderGlobalModal,
  8. screen,
  9. userEvent,
  10. waitFor,
  11. } from 'sentry-test/reactTestingLibrary';
  12. import {ProductSolution} from 'sentry/components/onboarding/gettingStartedDoc/types';
  13. import type {Organization} from 'sentry/types/organization';
  14. import {PlanFixture} from 'getsentry/__fixtures__/plan';
  15. import {PreviewDataFixture} from 'getsentry/__fixtures__/previewData';
  16. import {ProductSelectionAvailability} from 'getsentry/components/productSelectionAvailability';
  17. import type {Reservations} from 'getsentry/components/upgradeNowModal/types';
  18. import usePreviewData from 'getsentry/components/upgradeNowModal/usePreviewData';
  19. import SubscriptionStore from 'getsentry/stores/subscriptionStore';
  20. import {PlanTier} from 'getsentry/types';
  21. jest.mock('getsentry/components/upgradeNowModal/usePreviewData');
  22. function renderMockRequests({
  23. planTier,
  24. organization,
  25. canSelfServe,
  26. }: {
  27. organization: Organization;
  28. planTier: PlanTier;
  29. canSelfServe?: boolean;
  30. }) {
  31. const subscription = SubscriptionFixture({
  32. organization,
  33. planTier,
  34. canSelfServe,
  35. });
  36. act(() => SubscriptionStore.set(organization.slug, subscription));
  37. MockApiClient.addMockResponse({
  38. url: `/subscriptions/org-slug/`,
  39. body: {
  40. planTier,
  41. canSelfServe,
  42. },
  43. });
  44. MockApiClient.addMockResponse({
  45. url: `/customers/${organization.slug}/billing-config/`,
  46. body: BillingConfigFixture(planTier),
  47. });
  48. }
  49. describe('ProductSelectionAvailability', function () {
  50. describe('with no billing access', function () {
  51. it('with performance and session replay', async function () {
  52. const {organization, router} = initializeOrg({
  53. organization: {
  54. features: ['performance-view', 'session-replay'],
  55. },
  56. router: {
  57. location: {
  58. query: {
  59. product: [
  60. ProductSolution.PERFORMANCE_MONITORING,
  61. ProductSolution.SESSION_REPLAY,
  62. ],
  63. },
  64. },
  65. },
  66. });
  67. renderMockRequests({planTier: PlanTier.AM2, organization});
  68. render(
  69. <ProductSelectionAvailability
  70. organization={organization}
  71. platform="javascript-react"
  72. />,
  73. {
  74. router,
  75. }
  76. );
  77. // Error Monitoring
  78. // disabled: false - it's not disabled because of the styles, but it behaves as if it were disabled
  79. // checked: true
  80. expect(await screen.findByRole('button', {name: 'Error Monitoring'})).toBeEnabled();
  81. expect(screen.getByRole('checkbox', {name: 'Error Monitoring'})).toBeChecked();
  82. // Tooltip
  83. await userEvent.hover(screen.getByRole('button', {name: 'Error Monitoring'}));
  84. expect(
  85. await screen.findByText(/let's admit it, we all have errors/i)
  86. ).toBeInTheDocument();
  87. // Tracing
  88. // disabled: false
  89. // checked: true - by default, it's checked
  90. expect(screen.getByRole('button', {name: 'Tracing'})).toBeEnabled();
  91. expect(screen.getByRole('checkbox', {name: 'Tracing'})).toBeChecked();
  92. // Tooltip
  93. await userEvent.hover(screen.getByRole('button', {name: 'Tracing'}));
  94. expect(
  95. await screen.findByText(/automatic performance issue detection/i)
  96. ).toBeInTheDocument();
  97. // Session Replay
  98. // disabled: false
  99. // checked: true - by default, it's checked
  100. expect(screen.getByRole('button', {name: 'Session Replay'})).toBeEnabled();
  101. expect(screen.getByRole('checkbox', {name: 'Session Replay'})).toBeChecked();
  102. // Tooltip
  103. await userEvent.hover(screen.getByRole('button', {name: 'Session Replay'}));
  104. expect(
  105. await screen.findByText(/video-like reproductions of user sessions/i)
  106. ).toBeInTheDocument();
  107. });
  108. it('without performance and session replay', async function () {
  109. const {organization, router} = initializeOrg();
  110. renderMockRequests({planTier: PlanTier.MM2, organization, canSelfServe: true});
  111. render(
  112. <ProductSelectionAvailability
  113. organization={organization}
  114. platform="javascript-react"
  115. />,
  116. {
  117. router,
  118. }
  119. );
  120. // Error Monitoring
  121. // disabled: false - it's not disabled because of the styles, but it behaves as if it were disabled
  122. // checked: true
  123. expect(await screen.findByRole('button', {name: 'Error Monitoring'})).toBeEnabled();
  124. expect(screen.getByRole('checkbox', {name: 'Error Monitoring'})).toBeChecked();
  125. // Tracing
  126. // disabled: true
  127. // checked: false
  128. expect(screen.getByRole('button', {name: 'Tracing'})).toBeDisabled();
  129. expect(screen.getByRole('checkbox', {name: 'Tracing'})).not.toBeChecked();
  130. // Tooltip
  131. await userEvent.hover(screen.getByRole('button', {name: 'Tracing'}));
  132. expect(
  133. await screen.findByText(/to use performance, request an owner/i)
  134. ).toBeInTheDocument();
  135. // Session Replay
  136. // disabled: true
  137. // checked: false
  138. expect(screen.getByRole('button', {name: 'Session Replay'})).toBeDisabled();
  139. expect(screen.getByRole('checkbox', {name: 'Session Replay'})).not.toBeChecked();
  140. // Tooltip
  141. await userEvent.hover(screen.getByRole('button', {name: 'Session Replay'}));
  142. expect(
  143. await screen.findByText(/to use session replay, request an owner/i)
  144. ).toBeInTheDocument();
  145. });
  146. it('without session replay', async function () {
  147. const {organization, router} = initializeOrg({
  148. organization: {
  149. features: ['performance-view'],
  150. },
  151. router: {
  152. location: {
  153. query: {
  154. product: [ProductSolution.PERFORMANCE_MONITORING],
  155. },
  156. },
  157. },
  158. });
  159. renderMockRequests({planTier: PlanTier.AM1, organization});
  160. render(
  161. <ProductSelectionAvailability
  162. organization={organization}
  163. platform="javascript-react"
  164. />,
  165. {
  166. router,
  167. }
  168. );
  169. // Error Monitoring
  170. // disabled: false - it's not disabled because of the styles, but it behaves as if it were disabled
  171. // checked: true
  172. expect(await screen.findByRole('button', {name: 'Error Monitoring'})).toBeEnabled();
  173. expect(screen.getByRole('checkbox', {name: 'Error Monitoring'})).toBeChecked();
  174. // Tracing
  175. // disabled: false
  176. // checked: true - by default, it's checked
  177. expect(screen.getByRole('button', {name: 'Tracing'})).toBeEnabled();
  178. expect(screen.getByRole('checkbox', {name: 'Tracing'})).toBeChecked();
  179. // Session Replay
  180. // disabled: true
  181. // checked: false
  182. expect(screen.getByRole('button', {name: 'Session Replay'})).toBeDisabled();
  183. expect(screen.getByRole('checkbox', {name: 'Session Replay'})).not.toBeChecked();
  184. });
  185. });
  186. describe('with billing access', function () {
  187. it('with performance and session replay', async function () {
  188. const {organization, router} = initializeOrg({
  189. organization: {
  190. features: ['performance-view', 'session-replay'],
  191. access: ['org:billing'] as any, // TODO(ts): Fix this type for organizations on a plan
  192. },
  193. router: {
  194. location: {
  195. query: {
  196. product: [
  197. ProductSolution.PERFORMANCE_MONITORING,
  198. ProductSolution.SESSION_REPLAY,
  199. ],
  200. },
  201. },
  202. },
  203. });
  204. renderMockRequests({planTier: PlanTier.AM2, organization});
  205. render(
  206. <ProductSelectionAvailability
  207. organization={organization}
  208. platform="javascript-react"
  209. />,
  210. {
  211. router,
  212. }
  213. );
  214. // Error Monitoring
  215. // disabled: false - it's not disabled because of the styles, but it behaves as if it were disabled
  216. // checked: true
  217. expect(await screen.findByRole('button', {name: 'Error Monitoring'})).toBeEnabled();
  218. expect(screen.getByRole('checkbox', {name: 'Error Monitoring'})).toBeChecked();
  219. // Tooltip
  220. await userEvent.hover(screen.getByRole('button', {name: 'Error Monitoring'}));
  221. expect(
  222. await screen.findByText(/let's admit it, we all have errors/i)
  223. ).toBeInTheDocument();
  224. // Tracing
  225. // disabled: false
  226. // checked: true - by default, it's checked
  227. expect(screen.getByRole('button', {name: 'Tracing'})).toBeEnabled();
  228. expect(screen.getByRole('checkbox', {name: 'Tracing'})).toBeChecked();
  229. // Tooltip
  230. await userEvent.hover(screen.getByRole('button', {name: 'Tracing'}));
  231. expect(
  232. await screen.findByText(/automatic performance issue detection/i)
  233. ).toBeInTheDocument();
  234. // Session Replay
  235. // disabled: false
  236. // checked: true - by default, it's checked
  237. expect(screen.getByRole('button', {name: 'Session Replay'})).toBeEnabled();
  238. expect(screen.getByRole('checkbox', {name: 'Session Replay'})).toBeChecked();
  239. // Tooltip
  240. await userEvent.hover(screen.getByRole('button', {name: 'Session Replay'}));
  241. expect(
  242. await screen.findByText(/video-like reproductions of user sessions/i)
  243. ).toBeInTheDocument();
  244. });
  245. it('without performance, session replay and profiling', async function () {
  246. const {organization, router} = initializeOrg({
  247. organization: {
  248. access: ['org:billing'] as any, // TODO(ts): Fix this type for organizations on a plan
  249. },
  250. });
  251. renderMockRequests({planTier: PlanTier.MM2, organization});
  252. render(
  253. <ProductSelectionAvailability
  254. organization={organization}
  255. platform="javascript-react"
  256. />,
  257. {
  258. router,
  259. }
  260. );
  261. // Error Monitoring
  262. // disabled: false - it's not disabled because of the styles, but it behaves as if it were disabled
  263. // checked: true
  264. expect(await screen.findByRole('button', {name: 'Error Monitoring'})).toBeEnabled();
  265. expect(screen.getByRole('checkbox', {name: 'Error Monitoring'})).toBeChecked();
  266. // Tracing
  267. // disabled: true
  268. // checked: false
  269. expect(screen.getByRole('button', {name: 'Tracing'})).toBeDisabled();
  270. expect(screen.getByRole('checkbox', {name: 'Tracing'})).not.toBeChecked();
  271. // Tooltip
  272. await userEvent.hover(screen.getByRole('button', {name: 'Tracing'}));
  273. expect(
  274. await screen.findByText(/to use performance, update your organization's plan/i)
  275. ).toBeInTheDocument();
  276. // Session Replay
  277. // disabled: true - We don't display an upsell modal to users on MM* plans
  278. // checked: false
  279. expect(screen.getByRole('button', {name: 'Session Replay'})).toBeDisabled();
  280. expect(screen.getByRole('checkbox', {name: 'Session Replay'})).not.toBeChecked();
  281. expect(screen.getByRole('checkbox', {name: 'Session Replay'})).toBeDisabled();
  282. // Tooltip
  283. await userEvent.hover(screen.getByRole('button', {name: 'Session Replay'}));
  284. expect(
  285. await screen.findByText(/to use session replay, update your organization's plan/i)
  286. ).toBeInTheDocument();
  287. });
  288. it('without session replay', async function () {
  289. const {organization, router} = initializeOrg({
  290. organization: {
  291. access: ['org:billing'] as any, // TODO(ts): Fix this type for organizations on a plan
  292. features: ['performance-view'],
  293. },
  294. router: {
  295. location: {
  296. query: {
  297. product: [ProductSolution.PERFORMANCE_MONITORING],
  298. },
  299. },
  300. },
  301. });
  302. const MockUsePreviewData = usePreviewData as jest.MockedFunction<
  303. typeof usePreviewData
  304. >;
  305. const mockReservations: Reservations = {
  306. reservedErrors: 50000,
  307. reservedTransactions: 0,
  308. reservedReplays: 500,
  309. reservedAttachments: 0,
  310. reservedMonitorSeats: 0,
  311. reservedUptime: 0,
  312. };
  313. const mockPlan = PlanFixture({});
  314. const mockPreview = PreviewDataFixture({});
  315. MockUsePreviewData.mockReturnValue({
  316. loading: false,
  317. error: false,
  318. plan: mockPlan,
  319. previewData: mockPreview,
  320. reservations: mockReservations,
  321. });
  322. // can self-serve
  323. renderMockRequests({planTier: PlanTier.AM1, organization, canSelfServe: true});
  324. const {rerender} = render(
  325. <ProductSelectionAvailability
  326. organization={organization}
  327. platform="javascript-react"
  328. />,
  329. {
  330. router,
  331. }
  332. );
  333. // Error Monitoring
  334. // disabled: false - it's not disabled because of the styles, but it behaves as if it were disabled
  335. // checked: true
  336. expect(await screen.findByRole('button', {name: 'Error Monitoring'})).toBeEnabled();
  337. expect(screen.getByRole('checkbox', {name: 'Error Monitoring'})).toBeChecked();
  338. // Tracing
  339. // disabled: false
  340. // checked: true - by default, it's checked
  341. expect(screen.getByRole('button', {name: 'Tracing'})).toBeEnabled();
  342. expect(screen.getByRole('checkbox', {name: 'Tracing'})).toBeChecked();
  343. // Session Replay
  344. // disabled: false - By clicking on the button, an upsell modal is shown
  345. // checked: false
  346. expect(screen.getByRole('button', {name: 'Session Replay'})).toBeEnabled();
  347. expect(screen.getByRole('checkbox', {name: 'Session Replay'})).not.toBeChecked();
  348. expect(screen.getByRole('checkbox', {name: 'Session Replay'})).toBeDisabled();
  349. // Tooltip
  350. await userEvent.hover(screen.getByRole('button', {name: 'Session Replay'}));
  351. expect(
  352. await screen.findByText(/to use session replay, update your organization's plan/i)
  353. ).toBeInTheDocument();
  354. renderGlobalModal();
  355. // Modal
  356. await userEvent.click(screen.getByRole('button', {name: 'Session Replay'}));
  357. expect(
  358. await screen.findByRole('heading', {name: /enable session replays now/i})
  359. ).toBeInTheDocument();
  360. // can't self-serve
  361. renderMockRequests({planTier: PlanTier.AM1, organization, canSelfServe: false});
  362. rerender(
  363. <ProductSelectionAvailability
  364. organization={organization}
  365. platform="javascript-react"
  366. />
  367. );
  368. // Session Replay
  369. // disabled: true - We don't display an upsell modal to users who has access to billing but cannot self-serve
  370. // checked: false
  371. expect(await screen.findByRole('button', {name: 'Session Replay'})).toBeDisabled();
  372. // Tooltip
  373. await userEvent.hover(screen.getByRole('button', {name: 'Session Replay'}));
  374. expect(await screen.findByText(/Manage Subscription/i)).toBeInTheDocument();
  375. });
  376. it('with profiling and without session replay', async function () {
  377. const {organization, router} = initializeOrg({
  378. organization: {
  379. features: ['performance-view', 'profiling-view'],
  380. },
  381. router: {
  382. location: {
  383. query: {
  384. product: [
  385. ProductSolution.PERFORMANCE_MONITORING,
  386. ProductSolution.PROFILING,
  387. ],
  388. },
  389. },
  390. },
  391. });
  392. renderMockRequests({planTier: PlanTier.AM2, organization});
  393. render(
  394. <ProductSelectionAvailability
  395. organization={organization}
  396. platform="python-django"
  397. />,
  398. {
  399. router,
  400. }
  401. );
  402. // Error Monitoring
  403. // disabled: false - it's not disabled because of the styles, but it behaves as if it were disabled
  404. // checked: true
  405. expect(await screen.findByRole('button', {name: 'Error Monitoring'})).toBeEnabled();
  406. expect(screen.getByRole('checkbox', {name: 'Error Monitoring'})).toBeChecked();
  407. // Tracing
  408. // disabled: false
  409. // checked: true - by default, it's checked
  410. expect(screen.getByRole('button', {name: 'Tracing'})).toBeEnabled();
  411. expect(screen.getByRole('checkbox', {name: 'Tracing'})).toBeChecked();
  412. // Session Replay (not rendered)
  413. expect(
  414. screen.queryByRole('button', {name: 'Session Replay'})
  415. ).not.toBeInTheDocument();
  416. // Profiling
  417. // disabled: false
  418. // checked: true - by default, it's checked
  419. expect(screen.getByRole('button', {name: 'Profiling'})).toBeEnabled();
  420. expect(screen.getByRole('checkbox', {name: 'Profiling'})).toBeChecked();
  421. });
  422. it('without profiling and without session replay', async function () {
  423. const {organization, router} = initializeOrg({
  424. organization: {
  425. features: ['performance-view'],
  426. },
  427. router: {
  428. location: {
  429. query: {
  430. product: [ProductSolution.PERFORMANCE_MONITORING],
  431. },
  432. },
  433. },
  434. });
  435. renderMockRequests({planTier: PlanTier.AM2, organization});
  436. render(
  437. <ProductSelectionAvailability
  438. organization={organization}
  439. platform="python-django"
  440. />,
  441. {
  442. router,
  443. }
  444. );
  445. // Error Monitoring
  446. // disabled: false - it's not disabled because of the styles, but it behaves as if it were disabled
  447. // checked: true
  448. expect(await screen.findByRole('button', {name: 'Error Monitoring'})).toBeEnabled();
  449. expect(screen.getByRole('checkbox', {name: 'Error Monitoring'})).toBeChecked();
  450. // Tracing
  451. // disabled: false
  452. // checked: true - by default, it's checked
  453. expect(screen.getByRole('button', {name: 'Tracing'})).toBeEnabled();
  454. expect(screen.getByRole('checkbox', {name: 'Tracing'})).toBeChecked();
  455. // Session Replay (not rendered)
  456. expect(
  457. screen.queryByRole('button', {name: 'Session Replay'})
  458. ).not.toBeInTheDocument();
  459. // Profiling
  460. // disabled: false
  461. // checked: false
  462. expect(screen.getByRole('button', {name: 'Profiling'})).toBeDisabled();
  463. expect(screen.getByRole('checkbox', {name: 'Profiling'})).not.toBeChecked();
  464. expect(screen.getByRole('checkbox', {name: 'Profiling'})).toBeDisabled();
  465. // Tooltip
  466. await userEvent.hover(screen.getByRole('button', {name: 'Profiling'}));
  467. expect(
  468. await screen.findByText(/to use profiling, update your organization's plan/i)
  469. ).toBeInTheDocument();
  470. });
  471. // TODO: This test does not play well with deselected products by default
  472. // eslint-disable-next-line jest/no-disabled-tests
  473. it.skip('enabling Profiling, shall check and "disabled" Tracing', async function () {
  474. const {router, organization} = initializeOrg({
  475. organization: {
  476. features: ['performance-view', 'profiling-view'],
  477. },
  478. router: {
  479. location: {
  480. query: {
  481. product: [ProductSolution.PROFILING],
  482. },
  483. },
  484. },
  485. });
  486. renderMockRequests({planTier: PlanTier.AM2, organization});
  487. render(
  488. <ProductSelectionAvailability
  489. organization={organization}
  490. platform="python-django"
  491. />,
  492. {
  493. router,
  494. }
  495. );
  496. // Performance is added to the query string, so it will be checked
  497. await waitFor(() => {
  498. expect(router.replace).toHaveBeenCalledWith(
  499. expect.objectContaining({
  500. query: expect.objectContaining({
  501. product: [
  502. ProductSolution.PERFORMANCE_MONITORING,
  503. ProductSolution.PROFILING,
  504. ],
  505. }),
  506. })
  507. );
  508. });
  509. // Tracing
  510. // disabled: false - it's not disabled because of the styles, but it behaves as if it were disabled
  511. expect(screen.getByRole('button', {name: 'Tracing'})).toBeEnabled();
  512. });
  513. it('with Profiling and Tracing', async function () {
  514. const {router, organization} = initializeOrg({
  515. organization: {
  516. features: ['performance-view', 'profiling-view'],
  517. },
  518. router: {
  519. location: {
  520. query: {
  521. product: [
  522. ProductSolution.PROFILING,
  523. ProductSolution.PERFORMANCE_MONITORING,
  524. ],
  525. },
  526. },
  527. },
  528. });
  529. renderMockRequests({planTier: PlanTier.AM2, organization});
  530. render(
  531. <ProductSelectionAvailability
  532. organization={organization}
  533. platform="python-django"
  534. />,
  535. {
  536. router,
  537. }
  538. );
  539. // Tracing
  540. expect(screen.getByRole('button', {name: 'Tracing'})).toBeEnabled();
  541. expect(screen.getByRole('checkbox', {name: 'Tracing'})).toBeChecked();
  542. // Profiling
  543. expect(screen.getByRole('button', {name: 'Profiling'})).toBeEnabled();
  544. expect(screen.getByRole('checkbox', {name: 'Profiling'})).toBeChecked();
  545. await userEvent.click(screen.getByRole('button', {name: 'Profiling'}));
  546. // profiling is removed from the query string
  547. await waitFor(() => {
  548. expect(router.replace).toHaveBeenCalledWith(
  549. expect.objectContaining({
  550. query: expect.objectContaining({
  551. product: [ProductSolution.PERFORMANCE_MONITORING],
  552. }),
  553. })
  554. );
  555. });
  556. });
  557. });
  558. });