setupDocs.spec.tsx 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585
  1. import {Location} from 'history';
  2. import {initializeOrg} from 'sentry-test/initializeOrg';
  3. import {render, screen} from 'sentry-test/reactTestingLibrary';
  4. import {PRODUCT} from 'sentry/components/onboarding/productSelection';
  5. import {ReactDocVariant} from 'sentry/data/platforms';
  6. import {PersistedStoreContext} from 'sentry/stores/persistedStore';
  7. import ProjectsStore from 'sentry/stores/projectsStore';
  8. import {Organization, Project} from 'sentry/types';
  9. import SetupDocs from 'sentry/views/onboarding/setupDocs';
  10. const PROJECT_KEY = TestStubs.ProjectKeys()[0];
  11. function renderMockRequests({
  12. project,
  13. orgSlug,
  14. location,
  15. }: {
  16. orgSlug: Organization['slug'];
  17. project: Project;
  18. location?: Location;
  19. }) {
  20. MockApiClient.addMockResponse({
  21. url: `/projects/${orgSlug}/${project.slug}/`,
  22. body: project,
  23. });
  24. if (project.slug === 'javascript-browser') {
  25. MockApiClient.addMockResponse({
  26. url: `/projects/${orgSlug}/${project.slug}/keys/`,
  27. body: [PROJECT_KEY],
  28. });
  29. }
  30. MockApiClient.addMockResponse({
  31. url: `/projects/${orgSlug}/${project.slug}/issues/`,
  32. body: [],
  33. });
  34. if (project.slug === 'javascript-react') {
  35. const products = location?.query.product ?? [];
  36. if (
  37. products.includes(PRODUCT.PERFORMANCE_MONITORING) &&
  38. products.includes(PRODUCT.SESSION_REPLAY)
  39. ) {
  40. MockApiClient.addMockResponse({
  41. url: `/projects/${orgSlug}/${project.slug}/docs/${ReactDocVariant.ErrorMonitoringPerformanceAndReplay}/`,
  42. body: {html: ReactDocVariant.ErrorMonitoringPerformanceAndReplay},
  43. });
  44. } else if (products.includes(PRODUCT.PERFORMANCE_MONITORING)) {
  45. MockApiClient.addMockResponse({
  46. url: `/projects/${orgSlug}/${project.slug}/docs/${ReactDocVariant.ErrorMonitoringAndPerformance}/`,
  47. body: {html: ReactDocVariant.ErrorMonitoringAndPerformance},
  48. });
  49. } else if (products.includes(PRODUCT.SESSION_REPLAY)) {
  50. MockApiClient.addMockResponse({
  51. url: `/projects/${orgSlug}/${project.slug}/docs/${ReactDocVariant.ErrorMonitoringAndSessionReplay}/`,
  52. body: {html: ReactDocVariant.ErrorMonitoringAndSessionReplay},
  53. });
  54. } else {
  55. MockApiClient.addMockResponse({
  56. url: `/projects/${orgSlug}/${project.slug}/docs/${ReactDocVariant.ErrorMonitoring}/`,
  57. body: {html: ReactDocVariant.ErrorMonitoring},
  58. });
  59. }
  60. } else {
  61. MockApiClient.addMockResponse({
  62. url: `/projects/${orgSlug}/${project.slug}/docs/${project.platform}/`,
  63. body: {html: ''},
  64. });
  65. }
  66. }
  67. describe('Onboarding Setup Docs', function () {
  68. it('does not render Product Selection', async function () {
  69. const {router, route, routerContext, organization, project} = initializeOrg({
  70. ...initializeOrg(),
  71. organization: {
  72. ...initializeOrg().organization,
  73. features: [
  74. 'onboarding-remove-multiselect-platform',
  75. 'onboarding-docs-with-product-selection',
  76. ],
  77. },
  78. projects: [
  79. {
  80. ...initializeOrg().project,
  81. slug: 'python',
  82. platform: 'python',
  83. },
  84. ],
  85. });
  86. ProjectsStore.init();
  87. ProjectsStore.loadInitialData([project]);
  88. renderMockRequests({project, orgSlug: organization.slug});
  89. render(
  90. <PersistedStoreContext.Provider
  91. value={[
  92. {
  93. onboarding: {
  94. selectedPlatforms: ['python'],
  95. platformToProjectIdMap: {
  96. python: 'python',
  97. },
  98. },
  99. },
  100. jest.fn(),
  101. ]}
  102. >
  103. <SetupDocs
  104. active
  105. onComplete={() => {}}
  106. stepIndex={2}
  107. router={router}
  108. route={route}
  109. location={router.location}
  110. genSkipOnboardingLink={() => ''}
  111. orgId={organization.slug}
  112. jumpToSetupProject={() => {}}
  113. search=""
  114. />
  115. </PersistedStoreContext.Provider>,
  116. {
  117. context: routerContext,
  118. organization,
  119. }
  120. );
  121. expect(
  122. await screen.findByRole('heading', {name: 'Configure Python SDK'})
  123. ).toBeInTheDocument();
  124. expect(
  125. screen.queryByTestId(
  126. `product-${PRODUCT.ERROR_MONITORING}-${PRODUCT.PERFORMANCE_MONITORING}-${PRODUCT.SESSION_REPLAY}`
  127. )
  128. ).not.toBeInTheDocument();
  129. });
  130. describe('renders Product Selection', function () {
  131. it('all products checked', async function () {
  132. const {router, route, routerContext, organization, project} = initializeOrg({
  133. ...initializeOrg(),
  134. organization: {
  135. ...initializeOrg().organization,
  136. features: [
  137. 'onboarding-remove-multiselect-platform',
  138. 'onboarding-docs-with-product-selection',
  139. ],
  140. },
  141. router: {
  142. location: {
  143. query: {product: [PRODUCT.PERFORMANCE_MONITORING, PRODUCT.SESSION_REPLAY]},
  144. },
  145. },
  146. projects: [
  147. {
  148. ...initializeOrg().project,
  149. slug: 'javascript-react',
  150. platform: 'javascript-react',
  151. },
  152. ],
  153. });
  154. ProjectsStore.init();
  155. ProjectsStore.loadInitialData([project]);
  156. renderMockRequests({
  157. project,
  158. orgSlug: organization.slug,
  159. location: router.location,
  160. });
  161. render(
  162. <PersistedStoreContext.Provider
  163. value={[
  164. {
  165. onboarding: {
  166. selectedPlatforms: ['javascript-react'],
  167. platformToProjectIdMap: {
  168. 'javascript-react': 'javascript-react',
  169. },
  170. },
  171. },
  172. jest.fn(),
  173. ]}
  174. >
  175. <SetupDocs
  176. active
  177. onComplete={() => {}}
  178. stepIndex={2}
  179. router={router}
  180. route={route}
  181. location={router.location}
  182. genSkipOnboardingLink={() => ''}
  183. orgId={organization.slug}
  184. jumpToSetupProject={() => {}}
  185. search=""
  186. />
  187. </PersistedStoreContext.Provider>,
  188. {
  189. context: routerContext,
  190. organization,
  191. }
  192. );
  193. expect(
  194. await screen.findByRole('heading', {name: 'Configure React SDK'})
  195. ).toBeInTheDocument();
  196. // Render variation of docs - default (all checked)
  197. expect(
  198. await screen.findByText(ReactDocVariant.ErrorMonitoringPerformanceAndReplay)
  199. ).toBeInTheDocument();
  200. });
  201. it('only performance checked', async function () {
  202. const {router, route, routerContext, organization, project} = initializeOrg({
  203. ...initializeOrg(),
  204. organization: {
  205. ...initializeOrg().organization,
  206. features: [
  207. 'onboarding-remove-multiselect-platform',
  208. 'onboarding-docs-with-product-selection',
  209. ],
  210. },
  211. router: {
  212. location: {
  213. query: {product: [PRODUCT.PERFORMANCE_MONITORING]},
  214. },
  215. },
  216. projects: [
  217. {
  218. ...initializeOrg().project,
  219. slug: 'javascript-react',
  220. platform: 'javascript-react',
  221. },
  222. ],
  223. });
  224. ProjectsStore.init();
  225. ProjectsStore.loadInitialData([project]);
  226. renderMockRequests({
  227. project,
  228. orgSlug: organization.slug,
  229. location: router.location,
  230. });
  231. render(
  232. <PersistedStoreContext.Provider
  233. value={[
  234. {
  235. onboarding: {
  236. selectedPlatforms: ['javascript-react'],
  237. platformToProjectIdMap: {
  238. 'javascript-react': 'javascript-react',
  239. },
  240. },
  241. },
  242. jest.fn(),
  243. ]}
  244. >
  245. <SetupDocs
  246. active
  247. onComplete={() => {}}
  248. stepIndex={2}
  249. router={router}
  250. route={route}
  251. location={router.location}
  252. genSkipOnboardingLink={() => ''}
  253. orgId={organization.slug}
  254. jumpToSetupProject={() => {}}
  255. search=""
  256. />
  257. </PersistedStoreContext.Provider>,
  258. {
  259. context: routerContext,
  260. organization,
  261. }
  262. );
  263. // Render variation of docs - error monitoring and performance doc
  264. expect(
  265. await screen.findByText(ReactDocVariant.ErrorMonitoringAndPerformance)
  266. ).toBeInTheDocument();
  267. });
  268. it('only session replay checked', async function () {
  269. const {router, route, routerContext, organization, project} = initializeOrg({
  270. ...initializeOrg(),
  271. organization: {
  272. ...initializeOrg().organization,
  273. features: [
  274. 'onboarding-remove-multiselect-platform',
  275. 'onboarding-docs-with-product-selection',
  276. ],
  277. },
  278. router: {
  279. location: {
  280. query: {product: [PRODUCT.SESSION_REPLAY]},
  281. },
  282. },
  283. projects: [
  284. {
  285. ...initializeOrg().project,
  286. slug: 'javascript-react',
  287. platform: 'javascript-react',
  288. },
  289. ],
  290. });
  291. ProjectsStore.init();
  292. ProjectsStore.loadInitialData([project]);
  293. renderMockRequests({
  294. project,
  295. orgSlug: organization.slug,
  296. location: router.location,
  297. });
  298. render(
  299. <PersistedStoreContext.Provider
  300. value={[
  301. {
  302. onboarding: {
  303. selectedPlatforms: ['javascript-react'],
  304. platformToProjectIdMap: {
  305. 'javascript-react': 'javascript-react',
  306. },
  307. },
  308. },
  309. jest.fn(),
  310. ]}
  311. >
  312. <SetupDocs
  313. active
  314. onComplete={() => {}}
  315. stepIndex={2}
  316. router={router}
  317. route={route}
  318. location={router.location}
  319. genSkipOnboardingLink={() => ''}
  320. orgId={organization.slug}
  321. jumpToSetupProject={() => {}}
  322. search=""
  323. />
  324. </PersistedStoreContext.Provider>,
  325. {
  326. context: routerContext,
  327. organization,
  328. }
  329. );
  330. // Render variation of docs - error monitoring and replay doc
  331. expect(
  332. await screen.findByText(ReactDocVariant.ErrorMonitoringAndSessionReplay)
  333. ).toBeInTheDocument();
  334. });
  335. it('only error monitoring checked', async function () {
  336. const {router, route, routerContext, organization, project} = initializeOrg({
  337. ...initializeOrg(),
  338. organization: {
  339. ...initializeOrg().organization,
  340. features: [
  341. 'onboarding-remove-multiselect-platform',
  342. 'onboarding-docs-with-product-selection',
  343. ],
  344. },
  345. router: {
  346. location: {
  347. query: {product: []},
  348. },
  349. },
  350. projects: [
  351. {
  352. ...initializeOrg().project,
  353. slug: 'javascript-react',
  354. platform: 'javascript-react',
  355. },
  356. ],
  357. });
  358. ProjectsStore.init();
  359. ProjectsStore.loadInitialData([project]);
  360. renderMockRequests({
  361. project,
  362. orgSlug: organization.slug,
  363. location: router.location,
  364. });
  365. render(
  366. <PersistedStoreContext.Provider
  367. value={[
  368. {
  369. onboarding: {
  370. selectedPlatforms: ['javascript-react'],
  371. platformToProjectIdMap: {
  372. 'javascript-react': 'javascript-react',
  373. },
  374. },
  375. },
  376. jest.fn(),
  377. ]}
  378. >
  379. <SetupDocs
  380. active
  381. onComplete={() => {}}
  382. stepIndex={2}
  383. router={router}
  384. route={route}
  385. location={router.location}
  386. genSkipOnboardingLink={() => ''}
  387. orgId={organization.slug}
  388. jumpToSetupProject={() => {}}
  389. search=""
  390. />
  391. </PersistedStoreContext.Provider>,
  392. {
  393. context: routerContext,
  394. organization,
  395. }
  396. );
  397. // Render variation of docs - error monitoring doc
  398. expect(
  399. await screen.findByText(ReactDocVariant.ErrorMonitoring)
  400. ).toBeInTheDocument();
  401. });
  402. });
  403. describe('JS Loader Script', function () {
  404. it('renders Loader Script setup', async function () {
  405. const {router, route, routerContext, organization, project} = initializeOrg({
  406. ...initializeOrg(),
  407. organization: {
  408. ...initializeOrg().organization,
  409. features: [
  410. 'onboarding-remove-multiselect-platform',
  411. 'onboarding-docs-with-product-selection',
  412. 'onboarding-project-loader',
  413. 'js-sdk-dynamic-loader',
  414. ],
  415. },
  416. router: {
  417. location: {
  418. query: {product: [PRODUCT.PERFORMANCE_MONITORING, PRODUCT.SESSION_REPLAY]},
  419. },
  420. },
  421. projects: [
  422. {
  423. ...initializeOrg().project,
  424. slug: 'javascript-browser',
  425. platform: 'javascript',
  426. },
  427. ],
  428. });
  429. const updateLoaderMock = MockApiClient.addMockResponse({
  430. url: `/projects/${organization.slug}/${project.slug}/keys/${PROJECT_KEY.id}/`,
  431. method: 'PUT',
  432. body: PROJECT_KEY,
  433. });
  434. ProjectsStore.init();
  435. ProjectsStore.loadInitialData([project]);
  436. renderMockRequests({
  437. project,
  438. orgSlug: organization.slug,
  439. location: router.location,
  440. });
  441. const {rerender} = render(
  442. <PersistedStoreContext.Provider
  443. value={[
  444. {
  445. onboarding: {
  446. selectedPlatforms: ['javascript'],
  447. platformToProjectIdMap: {
  448. javascript: 'javascript-browser',
  449. },
  450. },
  451. },
  452. jest.fn(),
  453. ]}
  454. >
  455. <SetupDocs
  456. active
  457. onComplete={() => {}}
  458. stepIndex={2}
  459. router={router}
  460. route={route}
  461. location={router.location}
  462. genSkipOnboardingLink={() => ''}
  463. orgId={organization.slug}
  464. jumpToSetupProject={() => {}}
  465. search=""
  466. />
  467. </PersistedStoreContext.Provider>,
  468. {
  469. context: routerContext,
  470. organization,
  471. }
  472. );
  473. expect(
  474. await screen.findByRole('heading', {name: 'Configure JavaScript SDK'})
  475. ).toBeInTheDocument();
  476. expect(updateLoaderMock).toHaveBeenCalledTimes(1);
  477. expect(updateLoaderMock).toHaveBeenCalledWith(
  478. expect.any(String), // The URL
  479. {
  480. data: {
  481. dynamicSdkLoaderOptions: {
  482. hasDebug: false,
  483. hasPerformance: true,
  484. hasReplay: true,
  485. },
  486. },
  487. error: expect.any(Function),
  488. method: 'PUT',
  489. success: expect.any(Function),
  490. }
  491. );
  492. // update query in URL
  493. router.location.query = {
  494. product: [PRODUCT.SESSION_REPLAY],
  495. };
  496. rerender(
  497. <PersistedStoreContext.Provider
  498. value={[
  499. {
  500. onboarding: {
  501. selectedPlatforms: ['javascript'],
  502. platformToProjectIdMap: {
  503. javascript: 'javascript-browser',
  504. },
  505. },
  506. },
  507. jest.fn(),
  508. ]}
  509. >
  510. <SetupDocs
  511. active
  512. onComplete={() => {}}
  513. stepIndex={2}
  514. router={router}
  515. route={route}
  516. location={router.location}
  517. genSkipOnboardingLink={() => ''}
  518. orgId={organization.slug}
  519. jumpToSetupProject={() => {}}
  520. search=""
  521. />
  522. </PersistedStoreContext.Provider>
  523. );
  524. expect(updateLoaderMock).toHaveBeenCalledTimes(2);
  525. expect(updateLoaderMock).toHaveBeenLastCalledWith(
  526. expect.any(String), // The URL
  527. {
  528. data: {
  529. dynamicSdkLoaderOptions: {
  530. hasDebug: false,
  531. hasPerformance: false,
  532. hasReplay: true,
  533. },
  534. },
  535. error: expect.any(Function),
  536. method: 'PUT',
  537. success: expect.any(Function),
  538. }
  539. );
  540. });
  541. });
  542. });