import {Fragment, useCallback, useEffect, useMemo, useState} from 'react'; import {browserHistory} from 'react-router'; import styled from '@emotion/styled'; import {motion} from 'framer-motion'; import {Location} from 'history'; import {loadDocs} from 'sentry/actionCreators/projects'; import {Alert} from 'sentry/components/alert'; import LoadingError from 'sentry/components/loadingError'; import LoadingIndicator from 'sentry/components/loadingIndicator'; import {DocumentationWrapper} from 'sentry/components/onboarding/documentationWrapper'; import {Footer} from 'sentry/components/onboarding/footer'; import {FooterWithViewSampleErrorButton} from 'sentry/components/onboarding/footerWithViewSampleErrorButton'; import {MissingExampleWarning} from 'sentry/components/onboarding/missingExampleWarning'; import {PRODUCT, ProductSelection} from 'sentry/components/onboarding/productSelection'; import {PlatformKey} from 'sentry/data/platformCategories'; import platforms from 'sentry/data/platforms'; import {t} from 'sentry/locale'; import {space} from 'sentry/styles/space'; import {Organization, Project} from 'sentry/types'; import {OnboardingPlatformDoc} from 'sentry/types/onboarding'; import {trackAnalytics} from 'sentry/utils/analytics'; import getDynamicText from 'sentry/utils/getDynamicText'; import {platformToIntegrationMap} from 'sentry/utils/integrationUtil'; import {useApiQuery} from 'sentry/utils/queryClient'; import useApi from 'sentry/utils/useApi'; import {useExperiment} from 'sentry/utils/useExperiment'; import useOrganization from 'sentry/utils/useOrganization'; import SetupIntroduction from 'sentry/views/onboarding/components/setupIntroduction'; import {SetupDocsLoader} from 'sentry/views/onboarding/setupDocsLoader'; import FirstEventFooter from './components/firstEventFooter'; import IntegrationSetup from './integrationSetup'; import {StepProps} from './types'; export function DocWithProductSelection({ organization, location, projectSlug, newOrg, currentPlatform, }: { currentPlatform: PlatformKey; location: Location; organization: Organization; projectSlug: Project['slug']; newOrg?: boolean; }) { const loadPlatform = useMemo(() => { const products = location.query.product ?? []; return products.includes(PRODUCT.PERFORMANCE_MONITORING) && products.includes(PRODUCT.SESSION_REPLAY) ? `${currentPlatform}-with-error-monitoring-performance-and-replay` : products.includes(PRODUCT.PERFORMANCE_MONITORING) ? `${currentPlatform}-with-error-monitoring-and-performance` : products.includes(PRODUCT.SESSION_REPLAY) ? `${currentPlatform}-with-error-monitoring-and-replay` : `${currentPlatform}-with-error-monitoring`; }, [location.query.product, currentPlatform]); const {data, isLoading, isError, refetch} = useApiQuery( [`/projects/${organization.slug}/${projectSlug}/docs/${loadPlatform}/`], { staleTime: Infinity, enabled: !!projectSlug && !!organization.slug && !!loadPlatform, } ); const platformName = platforms.find(p => p.id === currentPlatform)?.name ?? ''; return ( {newOrg && ( )} {isLoading ? ( ) : isError ? ( ) : ( getDynamicText({ value: ( ), fixed: ( Platform documentation is not rendered in for tests in CI ), }) )} ); } function ProjectDocs(props: { hasError: boolean; onRetry: () => void; organization: Organization; platform: PlatformKey | null; platformDocs: OnboardingPlatformDoc | null; project: Project; }) { const currentPlatform = props.platform ?? props.project?.platform ?? 'other'; return ( p.id === currentPlatform)?.name ?? '' )} platform={currentPlatform} /> {getDynamicText({ value: !props.hasError ? ( props.platformDocs !== null && ( ) ) : ( ), fixed: ( Platform documentation is not rendered in for tests in CI ), })} ); } function SetupDocs({route, router, location, recentCreatedProject: project}: StepProps) { const api = useApi(); const organization = useOrganization(); const { logExperiment: newFooterLogExperiment, experimentAssignment: newFooterAssignment, } = useExperiment('OnboardingNewFooterExperiment', { logExperimentOnMount: false, }); const heartbeatFooter = !!organization?.features.includes( 'onboarding-heartbeat-footer' ); // SDK instrumentation const [hasError, setHasError] = useState(false); const [platformDocs, setPlatformDocs] = useState(null); const [loadedPlatform, setLoadedPlatform] = useState(null); const currentPlatform = loadedPlatform ?? project?.platform ?? 'other'; const [showLoaderOnboarding, setShowLoaderOnboarding] = useState( currentPlatform === 'javascript' ); const integrationSlug = project?.platform && platformToIntegrationMap[project.platform]; const [integrationUseManualSetup, setIntegrationUseManualSetup] = useState(false); const showIntegrationOnboarding = integrationSlug && !integrationUseManualSetup; const showDocsWithProductSelection = currentPlatform.match('^javascript-([A-Za-z]+)$') ?? (showLoaderOnboarding === false && currentPlatform === 'javascript'); const hideLoaderOnboarding = useCallback(() => { setShowLoaderOnboarding(false); if (!project?.id) { return; } trackAnalytics('onboarding.js_loader_npm_docs_shown', { organization, platform: currentPlatform, project_id: project?.id, }); }, [organization, currentPlatform, project?.id]); const fetchData = useCallback(async () => { // TODO: add better error handling logic if (!project?.platform) { return; } // this will be fetched in the DocWithProductSelection component if (showDocsWithProductSelection) { return; } // Show loader setup for base javascript platform if (showLoaderOnboarding) { return; } if (showIntegrationOnboarding) { setLoadedPlatform(project.platform); setPlatformDocs(null); setHasError(false); return; } try { const loadedDocs = await loadDocs({ api, orgSlug: organization.slug, projectSlug: project.slug, platform: project.platform as PlatformKey, }); setPlatformDocs(loadedDocs); setLoadedPlatform(project.platform); setHasError(false); } catch (error) { setHasError(error); throw error; } }, [ project?.slug, project?.platform, api, organization.slug, showDocsWithProductSelection, showIntegrationOnboarding, showLoaderOnboarding, ]); useEffect(() => { fetchData(); }, [fetchData, location.query.product, project?.platform]); // log experiment on mount if feature enabled useEffect(() => { if (heartbeatFooter) { newFooterLogExperiment(); } }, [newFooterLogExperiment, heartbeatFooter]); if (!project) { return null; } return ( {showIntegrationOnboarding ? ( { setIntegrationUseManualSetup(true); }} /> ) : showDocsWithProductSelection ? ( ) : showLoaderOnboarding ? ( p.id === currentPlatform)?.name ?? '' )} platform={currentPlatform} /> ) : ( )} {heartbeatFooter ? ( newFooterAssignment === 'variant2' ? ( ) : newFooterAssignment === 'variant1' ? (