setupDocs.tsx 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  1. import {Fragment, useCallback, useEffect, useMemo, useState} from 'react';
  2. import {browserHistory} from 'react-router';
  3. import styled from '@emotion/styled';
  4. import {motion} from 'framer-motion';
  5. import {loadDocs} from 'sentry/actionCreators/projects';
  6. import {Alert} from 'sentry/components/alert';
  7. import LoadingError from 'sentry/components/loadingError';
  8. import {DocumentationWrapper} from 'sentry/components/onboarding/documentationWrapper';
  9. import {
  10. migratedDocs,
  11. SdkDocumentation,
  12. } from 'sentry/components/onboarding/gettingStartedDoc/sdkDocumentation';
  13. import {MissingExampleWarning} from 'sentry/components/onboarding/missingExampleWarning';
  14. import {ProductSolution} from 'sentry/components/onboarding/productSelection';
  15. import {PlatformKey} from 'sentry/data/platformCategories';
  16. import platforms from 'sentry/data/platforms';
  17. import {t} from 'sentry/locale';
  18. import {space} from 'sentry/styles/space';
  19. import {Organization, Project} from 'sentry/types';
  20. import {OnboardingPlatformDoc} from 'sentry/types/onboarding';
  21. import {trackAnalytics} from 'sentry/utils/analytics';
  22. import getDynamicText from 'sentry/utils/getDynamicText';
  23. import {platformToIntegrationMap} from 'sentry/utils/integrationUtil';
  24. import useApi from 'sentry/utils/useApi';
  25. import useOrganization from 'sentry/utils/useOrganization';
  26. import SetupIntroduction from 'sentry/views/onboarding/components/setupIntroduction';
  27. import {SetupDocsLoader} from 'sentry/views/onboarding/setupDocsLoader';
  28. import FirstEventFooter from './components/firstEventFooter';
  29. import IntegrationSetup from './integrationSetup';
  30. import {StepProps} from './types';
  31. function ProjectDocs(props: {
  32. hasError: boolean;
  33. onRetry: () => void;
  34. organization: Organization;
  35. platform: PlatformKey | null;
  36. platformDocs: OnboardingPlatformDoc | null;
  37. project: Project;
  38. }) {
  39. const currentPlatform = props.platform ?? props.project?.platform ?? 'other';
  40. return (
  41. <Fragment>
  42. <SetupIntroduction
  43. stepHeaderText={t(
  44. 'Configure %s SDK',
  45. platforms.find(p => p.id === currentPlatform)?.name ?? ''
  46. )}
  47. platform={currentPlatform}
  48. />
  49. {getDynamicText({
  50. value: !props.hasError ? (
  51. props.platformDocs !== null && (
  52. <DocsWrapper key={props.platformDocs.html}>
  53. <DocumentationWrapper
  54. dangerouslySetInnerHTML={{__html: props.platformDocs.html}}
  55. />
  56. <MissingExampleWarning
  57. platform={props.platform}
  58. platformDocs={props.platformDocs}
  59. />
  60. </DocsWrapper>
  61. )
  62. ) : (
  63. <LoadingError
  64. message={t(
  65. 'Failed to load documentation for the %s platform.',
  66. props.project?.platform
  67. )}
  68. onRetry={props.onRetry}
  69. />
  70. ),
  71. fixed: (
  72. <Alert type="warning">
  73. Platform documentation is not rendered in for tests in CI
  74. </Alert>
  75. ),
  76. })}
  77. </Fragment>
  78. );
  79. }
  80. function SetupDocs({location, recentCreatedProject: project}: StepProps) {
  81. const api = useApi();
  82. const organization = useOrganization();
  83. const products = useMemo<ProductSolution[]>(
  84. () => (location.query.product ?? []) as ProductSolution[],
  85. [location.query.product]
  86. );
  87. // SDK instrumentation
  88. const [hasError, setHasError] = useState(false);
  89. const [platformDocs, setPlatformDocs] = useState<OnboardingPlatformDoc | null>(null);
  90. const [loadedPlatform, setLoadedPlatform] = useState<PlatformKey | null>(null);
  91. const currentPlatformKey = loadedPlatform ?? project?.platform ?? 'other';
  92. const [showLoaderOnboarding, setShowLoaderOnboarding] = useState(
  93. currentPlatformKey === 'javascript'
  94. );
  95. useEffect(() => {
  96. setShowLoaderOnboarding(currentPlatformKey === 'javascript');
  97. }, [currentPlatformKey]);
  98. const integrationSlug = project?.platform && platformToIntegrationMap[project.platform];
  99. const [integrationUseManualSetup, setIntegrationUseManualSetup] = useState(false);
  100. const showIntegrationOnboarding = integrationSlug && !integrationUseManualSetup;
  101. const showDocsWithProductSelection =
  102. currentPlatformKey.match('^javascript-([A-Za-z]+)$') ??
  103. (showLoaderOnboarding === false && currentPlatformKey === 'javascript');
  104. const hideLoaderOnboarding = useCallback(() => {
  105. setShowLoaderOnboarding(false);
  106. if (!project?.id) {
  107. return;
  108. }
  109. trackAnalytics('onboarding.js_loader_npm_docs_shown', {
  110. organization,
  111. platform: currentPlatformKey,
  112. project_id: project?.id,
  113. });
  114. }, [organization, currentPlatformKey, project?.id]);
  115. const fetchData = useCallback(async () => {
  116. // TODO: add better error handling logic
  117. if (!project?.platform) {
  118. return;
  119. }
  120. // this will be fetched in the DocWithProductSelection component
  121. if (showDocsWithProductSelection) {
  122. return;
  123. }
  124. // Show loader setup for base javascript platform
  125. if (showLoaderOnboarding) {
  126. return;
  127. }
  128. if (showIntegrationOnboarding) {
  129. setLoadedPlatform(project.platform);
  130. setPlatformDocs(null);
  131. setHasError(false);
  132. return;
  133. }
  134. // There are no docs for the other platform
  135. if (project.platform === 'other') {
  136. return;
  137. }
  138. try {
  139. const loadedDocs = await loadDocs({
  140. api,
  141. orgSlug: organization.slug,
  142. projectSlug: project.slug,
  143. platform: project.platform as PlatformKey,
  144. });
  145. setPlatformDocs(loadedDocs);
  146. setLoadedPlatform(project.platform);
  147. setHasError(false);
  148. } catch (error) {
  149. setHasError(error);
  150. throw error;
  151. }
  152. }, [
  153. project?.slug,
  154. project?.platform,
  155. api,
  156. organization.slug,
  157. showDocsWithProductSelection,
  158. showIntegrationOnboarding,
  159. showLoaderOnboarding,
  160. ]);
  161. useEffect(() => {
  162. fetchData();
  163. }, [fetchData, location.query.product, project?.platform]);
  164. if (!project) {
  165. return null;
  166. }
  167. const currentPlatform = platforms.find(p => p.id === currentPlatformKey);
  168. const platformName = currentPlatform?.name ?? '';
  169. const loadLocalSdkDocumentation =
  170. currentPlatform && migratedDocs.includes(currentPlatformKey);
  171. return (
  172. <Fragment>
  173. <Wrapper>
  174. <MainContent>
  175. {showIntegrationOnboarding ? (
  176. <IntegrationSetup
  177. integrationSlug={integrationSlug}
  178. project={project}
  179. onClickManualSetup={() => {
  180. setIntegrationUseManualSetup(true);
  181. }}
  182. />
  183. ) : showLoaderOnboarding ? (
  184. <Fragment>
  185. <SetupIntroduction
  186. stepHeaderText={t(
  187. 'Configure %s SDK',
  188. platforms.find(p => p.id === currentPlatformKey)?.name ?? ''
  189. )}
  190. platform={currentPlatformKey}
  191. />
  192. <SetupDocsLoader
  193. organization={organization}
  194. project={project}
  195. location={location}
  196. platform={loadedPlatform}
  197. close={hideLoaderOnboarding}
  198. />
  199. </Fragment>
  200. ) : loadLocalSdkDocumentation ? (
  201. <Fragment>
  202. <SetupIntroduction
  203. stepHeaderText={t('Configure %s SDK', platformName)}
  204. platform={currentPlatformKey}
  205. />
  206. <SdkDocumentation
  207. platform={currentPlatform}
  208. organization={organization}
  209. projectSlug={project.slug}
  210. projectId={project.id}
  211. activeProductSelection={products}
  212. newOrg
  213. />
  214. </Fragment>
  215. ) : (
  216. <ProjectDocs
  217. platform={loadedPlatform}
  218. project={project}
  219. hasError={hasError}
  220. platformDocs={platformDocs}
  221. onRetry={fetchData}
  222. organization={organization}
  223. />
  224. )}
  225. </MainContent>
  226. </Wrapper>
  227. <FirstEventFooter
  228. project={project}
  229. organization={organization}
  230. isLast
  231. onClickSetupLater={() => {
  232. const orgIssuesURL = `/organizations/${organization.slug}/issues/?project=${project.id}&referrer=onboarding-setup-docs`;
  233. trackAnalytics('growth.onboarding_clicked_setup_platform_later', {
  234. organization,
  235. platform: currentPlatformKey,
  236. project_id: project.id,
  237. });
  238. browserHistory.push(orgIssuesURL);
  239. }}
  240. />
  241. </Fragment>
  242. );
  243. }
  244. export default SetupDocs;
  245. const AnimatedContentWrapper = styled(motion.div)`
  246. overflow: hidden;
  247. `;
  248. AnimatedContentWrapper.defaultProps = {
  249. initial: {
  250. height: 0,
  251. },
  252. animate: {
  253. height: 'auto',
  254. },
  255. exit: {
  256. height: 0,
  257. },
  258. };
  259. const DocsWrapper = styled(motion.div)``;
  260. DocsWrapper.defaultProps = {
  261. initial: {opacity: 0, y: 40},
  262. animate: {opacity: 1, y: 0},
  263. exit: {opacity: 0},
  264. };
  265. const Wrapper = styled('div')`
  266. display: flex;
  267. flex-direction: row;
  268. margin: ${space(2)};
  269. justify-content: center;
  270. `;
  271. const MainContent = styled('div')`
  272. max-width: 850px;
  273. min-width: 0;
  274. flex-grow: 1;
  275. `;