import {Fragment, useEffect, useState} from 'react'; import styled from '@emotion/styled'; import * as Sentry from '@sentry/react'; import emptyStateImg from 'sentry-images/spot/performance-empty-state.svg'; import emptyTraceImg from 'sentry-images/spot/performance-empty-trace.svg'; import tourAlert from 'sentry-images/spot/performance-tour-alert.svg'; import tourCorrelate from 'sentry-images/spot/performance-tour-correlate.svg'; import tourMetrics from 'sentry-images/spot/performance-tour-metrics.svg'; import tourTrace from 'sentry-images/spot/performance-tour-trace.svg'; import { addErrorMessage, addLoadingMessage, clearIndicators, } from 'sentry/actionCreators/indicator'; import type {Client} from 'sentry/api'; import UnsupportedAlert from 'sentry/components/alerts/unsupportedAlert'; import {Button, LinkButton} from 'sentry/components/button'; import ButtonBar from 'sentry/components/buttonBar'; import {GuidedSteps} from 'sentry/components/guidedSteps/guidedSteps'; import LoadingIndicator from 'sentry/components/loadingIndicator'; import type {TourStep} from 'sentry/components/modals/featureTourModal'; import FeatureTourModal, { TourImage, TourText, } from 'sentry/components/modals/featureTourModal'; import {AuthTokenGeneratorProvider} from 'sentry/components/onboarding/gettingStartedDoc/authTokenGenerator'; import {OnboardingCodeSnippet} from 'sentry/components/onboarding/gettingStartedDoc/onboardingCodeSnippet'; import { type Configuration, TabbedCodeSnippet, } from 'sentry/components/onboarding/gettingStartedDoc/step'; import { type DocsParams, ProductSolution, } from 'sentry/components/onboarding/gettingStartedDoc/types'; import {useLoadGettingStarted} from 'sentry/components/onboarding/gettingStartedDoc/utils/useLoadGettingStarted'; import LegacyOnboardingPanel from 'sentry/components/onboardingPanel'; import Panel from 'sentry/components/panels/panel'; import PanelBody from 'sentry/components/panels/panelBody'; import {filterProjects} from 'sentry/components/performanceOnboarding/utils'; import {SidebarPanelKey} from 'sentry/components/sidebar/types'; import { withoutPerformanceSupport, withPerformanceOnboarding, } from 'sentry/data/platformCategories'; import platforms, {otherPlatform} from 'sentry/data/platforms'; import {t, tct} from 'sentry/locale'; import ConfigStore from 'sentry/stores/configStore'; import SidebarPanelStore from 'sentry/stores/sidebarPanelStore'; import {useLegacyStore} from 'sentry/stores/useLegacyStore'; import pulsingIndicatorStyles from 'sentry/styles/pulsingIndicator'; import {space} from 'sentry/styles/space'; import type {Organization} from 'sentry/types/organization'; import type {Project} from 'sentry/types/project'; import {trackAnalytics} from 'sentry/utils/analytics'; import {browserHistory} from 'sentry/utils/browserHistory'; import {generateLinkToEventInTraceView} from 'sentry/utils/discover/urls'; import EventWaiter from 'sentry/utils/eventWaiter'; import useApi from 'sentry/utils/useApi'; import {useLocation} from 'sentry/utils/useLocation'; import useProjects from 'sentry/utils/useProjects'; import {traceAnalytics} from './newTraceDetails/traceAnalytics'; const performanceSetupUrl = 'https://docs.sentry.io/performance-monitoring/getting-started/'; const docsLink = ( {t('Setup')} ); export const PERFORMANCE_TOUR_STEPS: TourStep[] = [ { title: t('Track Application Metrics'), image: , body: ( {t( 'Monitor your slowest pageloads and APIs to see which users are having the worst time.' )} ), actions: docsLink, }, { title: t('Correlate Errors and Traces'), image: , body: ( {t( 'See what errors occurred within a transaction and the impact of those errors.' )} ), actions: docsLink, }, { title: t('Watch and Alert'), image: , body: ( {t( 'Highlight mission-critical pages and APIs and set latency alerts to notify you before things go wrong.' )} ), actions: docsLink, }, { title: t('Trace Across Systems'), image: , body: ( {t( "Follow a trace from a user's session and drill down to identify any bottlenecks that occur." )} ), }, ]; type SampleButtonProps = { api: Client; errorMessage: React.ReactNode; loadingMessage: React.ReactNode; organization: Organization; project: Project; triggerText: React.ReactNode; }; function SampleButton({ triggerText, loadingMessage, errorMessage, project, organization, api, }: SampleButtonProps) { const location = useLocation(); return ( ); } type OnboardingProps = { organization: Organization; project: Project; }; export function LegacyOnboarding({organization, project}: OnboardingProps) { const api = useApi(); const {projects} = useProjects(); const location = useLocation(); const {projectsForOnboarding} = filterProjects(projects); const showOnboardingChecklist = organization.features.includes( 'performance-onboarding-checklist' ); useEffect(() => { if ( showOnboardingChecklist && location.hash === '#performance-sidequest' && projectsForOnboarding.some(p => p.id === project.id) ) { SidebarPanelStore.activatePanel(SidebarPanelKey.PERFORMANCE_ONBOARDING); } }, [location.hash, projectsForOnboarding, project.id, showOnboardingChecklist]); function handleAdvance(step: number, duration: number) { trackAnalytics('performance_views.tour.advance', { step, duration, organization, }); } function handleClose(step: number, duration: number) { trackAnalytics('performance_views.tour.close', { step, duration, organization, }); } const currentPlatform = project.platform; const hasPerformanceOnboarding = currentPlatform ? withPerformanceOnboarding.has(currentPlatform) : false; const noPerformanceSupport = currentPlatform && withoutPerformanceSupport.has(currentPlatform); let setupButton = ( {t('Start Setup')} ); if (hasPerformanceOnboarding && showOnboardingChecklist) { setupButton = ( ); } return ( {noPerformanceSupport && ( )} }>

{t('Pinpoint problems')}

{t( 'Something seem slow? Track down transactions to connect the dots between 10-second page loads and poor-performing API calls or slow database queries.' )}

{setupButton} {({showModal}) => ( )}
); } const PerfImage = styled('img')` @media (min-width: ${p => p.theme.breakpoints.small}) { max-width: unset; user-select: none; position: absolute; top: 75px; bottom: 0; width: 450px; margin-top: auto; margin-bottom: auto; } @media (min-width: ${p => p.theme.breakpoints.medium}) { width: 480px; } @media (min-width: ${p => p.theme.breakpoints.large}) { width: 600px; } `; const ButtonList = styled(ButtonBar)` grid-template-columns: repeat(auto-fit, minmax(130px, max-content)); margin-bottom: 16px; `; function WaitingIndicator({ api, organization, project, }: { api: Client; organization: Organization; project: Project; }) { const [received, setReceived] = useState(false); return ( { setReceived(true); }} > {() => (received ? : )} ); } type ConfigurationStepProps = { api: Client; configuration: Configuration; organization: Organization; project: Project; showWaitingIndicator: boolean; stepKey: string; title: React.ReactNode; }; function ConfigurationStep({ stepKey, title, api, organization, project, configuration, showWaitingIndicator, }: ConfigurationStepProps) { return (
{configuration.description} {configuration.code ? ( Array.isArray(configuration.code) ? ( ) : ( {configuration.code} ) ) : null} {configuration.configurations && configuration.configurations.length > 0 ? ( Array.isArray(configuration.configurations[0]!.code) ? ( ) : null ) : null} {configuration.additionalInfo} {showWaitingIndicator ? ( ) : null}
); } function OnboardingPanel({ project, children, }: { children: React.ReactNode; project: Project; }) { return (
{t('Query for Traces, Get Answers')} {t( 'You can query and aggregate spans to create metrics that help you debug busted API calls, slow image loads, or any other metrics you’d like to track.' )}
  • {t( 'Find traces tied to a user complaint and pinpoint exactly what broke' )}
  • {t( 'Debug persistent issues by investigating API payloads, cache sizes, user tokens, and more' )}
  • {t( 'Track any span attribute as a metric to catch slowdowns before they escalate' )}
  • {children} {t('Preview a Sentry Trace')}
    ); } export function Onboarding({organization, project}: OnboardingProps) { const api = useApi(); const {isSelfHosted, urlPrefix} = useLegacyStore(ConfigStore); const [received, setReceived] = useState(false); const showNewUi = organization.features.includes('tracing-onboarding-new-ui'); const currentPlatform = project.platform ? platforms.find(p => p.id === project.platform) : undefined; const {isLoading, docs, dsn, projectKeyId} = useLoadGettingStarted({ platform: currentPlatform || otherPlatform, orgSlug: organization.slug, projSlug: project.slug, productType: 'performance', }); const doesNotSupportPerformance = project.platform ? withoutPerformanceSupport.has(project.platform) : false; useEffect(() => { if (isLoading || !currentPlatform || !dsn || !projectKeyId) { return; } traceAnalytics.trackTracingOnboarding( organization, currentPlatform.id, !doesNotSupportPerformance, withPerformanceOnboarding.has(currentPlatform.id) ); }, [ currentPlatform, isLoading, dsn, projectKeyId, organization, doesNotSupportPerformance, ]); if (!showNewUi) { return ; } const performanceDocs = docs?.performanceOnboarding; if (isLoading) { return ; } if (doesNotSupportPerformance) { return (
    {tct( 'Fiddlesticks. Performance isn’t available for your [platform] project yet but we’re definitely still working on it. Stay tuned.', {platform: currentPlatform?.name || project.slug} )}

    { traceAnalytics.trackPlatformDocsViewed( organization, currentPlatform?.id ?? project.platform ?? 'unknown' ); }} > {t('Go to Documentation')}
    ); } if (!currentPlatform || !performanceDocs || !dsn || !projectKeyId) { return (
    {tct( 'Fiddlesticks. The tracing onboarding checklist isn’t available for your [project] project yet, but for now, go to Sentry docs for installation details.', {project: project.slug} )}

    { traceAnalytics.trackPerformanceSetupDocsViewed( organization, currentPlatform?.id ?? project.platform ?? 'unknown' ); }} > {t('Go to Documentation')}
    ); } const docParams: DocsParams = { api, projectKeyId, dsn, organization, platformKey: project.platform || 'other', projectId: project.id, projectSlug: project.slug, isFeedbackSelected: false, isPerformanceSelected: true, isProfilingSelected: false, isReplaySelected: false, sourcePackageRegistries: { isLoading: false, data: undefined, }, platformOptions: [ProductSolution.PERFORMANCE_MONITORING], newOrg: false, feedbackOptions: {}, urlPrefix, isSelfHosted, }; const installStep = performanceDocs.install(docParams)[0]!; const configureStep = performanceDocs.configure(docParams)[0]!; const [sentryConfiguration, addingDistributedTracing] = configureStep.configurations!; const verifyStep = performanceDocs.verify(docParams)[0]!; const hasVerifyStep = !!(verifyStep.configurations || verifyStep.description); const eventWaitingIndicator = ( { setReceived(true); }} > {() => (received ? : )} ); return ( {t('Set up the Sentry SDK')}
    {installStep.description} {installStep.configurations?.map((configuration, index) => (
    {configuration.description} {configuration.code ? ( Array.isArray(configuration.code) ? ( ) : ( {configuration.code} ) ) : null}
    ))} {!configureStep.configurations && !verifyStep.configurations ? eventWaitingIndicator : null}
    {sentryConfiguration ? ( ) : null} {addingDistributedTracing ? ( , })} configuration={addingDistributedTracing} api={api} organization={organization} project={project} showWaitingIndicator={!hasVerifyStep} /> ) : null} {verifyStep.configurations || verifyStep.description ? (
    {verifyStep.description} {verifyStep.configurations?.map((configuration, index) => (
    {configuration.description} {configuration.code ? ( Array.isArray(configuration.code) ? ( ) : ( {configuration.code} ) ) : null}
    ))} {eventWaitingIndicator}
    ) : ( )}
    ); } const EventWaitingIndicator = styled((p: React.HTMLAttributes) => (
    {t("Waiting for this project's first trace")}
    ))` display: flex; align-items: center; position: relative; z-index: 10; flex-grow: 1; font-size: ${p => p.theme.fontSizeMedium}; color: ${p => p.theme.pink400}; `; const PulsingIndicator = styled('div')` ${pulsingIndicatorStyles}; margin-left: ${space(1)}; `; const OptionalText = styled('span')` color: ${p => p.theme.purple300}; font-weight: ${p => p.theme.fontWeightNormal}; `; const EventReceivedIndicator = styled((p: React.HTMLAttributes) => (
    {'🎉 '} {t("We've received this project's first trace!")}
    ))` display: flex; align-items: center; flex-grow: 1; font-size: ${p => p.theme.fontSizeMedium}; color: ${p => p.theme.successText}; `; const SubTitle = styled('div')` margin-bottom: ${space(1)}; `; const Title = styled('div')` font-size: 26px; font-weight: ${p => p.theme.fontWeightBold}; `; const BulletList = styled('ul')` list-style-type: disc; padding-left: 20px; margin-bottom: ${space(2)}; li { margin-bottom: ${space(1)}; } `; const HeaderWrapper = styled('div')` display: flex; justify-content: space-between; gap: ${space(3)}; border-radius: ${p => p.theme.borderRadius}; padding: ${space(4)}; `; const HeaderText = styled('div')` flex: 0.65; @media (max-width: ${p => p.theme.breakpoints.small}) { flex: 1; } `; const BodyTitle = styled('div')` font-size: ${p => p.theme.fontSizeExtraLarge}; font-weight: ${p => p.theme.fontWeightBold}; margin-bottom: ${space(1)}; `; const Setup = styled('div')` padding: ${space(4)}; &:after { content: ''; position: absolute; right: 50%; top: 2.5%; height: 95%; border-right: 1px ${p => p.theme.border} solid; } `; const Preview = styled('div')` padding: ${space(4)}; `; const Body = styled('div')` display: grid; position: relative; grid-auto-columns: minmax(0, 1fr); grid-auto-flow: column; h4 { margin-bottom: 0; } `; const Image = styled('img')` display: block; pointer-events: none; height: 120px; overflow: hidden; @media (max-width: ${p => p.theme.breakpoints.small}) { display: none; } `; const Divider = styled('hr')` height: 1px; width: 95%; background: ${p => p.theme.border}; border: none; margin-top: 0; margin-bottom: 0; `; const Arcade = styled('iframe')` width: 750px; max-width: 100%; margin-top: ${space(3)}; height: 522px; border: 0; `; const CodeSnippetWrapper = styled('div')` margin-bottom: ${space(2)}; `; const DescriptionWrapper = styled('div')` margin-bottom: ${space(1)}; code { color: ${p => p.theme.pink400}; } `;