onboarding.tsx 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875
  1. import {Fragment, useEffect, useState} from 'react';
  2. import styled from '@emotion/styled';
  3. import * as Sentry from '@sentry/react';
  4. import emptyStateImg from 'sentry-images/spot/performance-empty-state.svg';
  5. import emptyTraceImg from 'sentry-images/spot/performance-empty-trace.svg';
  6. import tourAlert from 'sentry-images/spot/performance-tour-alert.svg';
  7. import tourCorrelate from 'sentry-images/spot/performance-tour-correlate.svg';
  8. import tourMetrics from 'sentry-images/spot/performance-tour-metrics.svg';
  9. import tourTrace from 'sentry-images/spot/performance-tour-trace.svg';
  10. import {
  11. addErrorMessage,
  12. addLoadingMessage,
  13. clearIndicators,
  14. } from 'sentry/actionCreators/indicator';
  15. import type {Client} from 'sentry/api';
  16. import UnsupportedAlert from 'sentry/components/alerts/unsupportedAlert';
  17. import {Button, LinkButton} from 'sentry/components/button';
  18. import ButtonBar from 'sentry/components/buttonBar';
  19. import {GuidedSteps} from 'sentry/components/guidedSteps/guidedSteps';
  20. import LoadingIndicator from 'sentry/components/loadingIndicator';
  21. import type {TourStep} from 'sentry/components/modals/featureTourModal';
  22. import FeatureTourModal, {
  23. TourImage,
  24. TourText,
  25. } from 'sentry/components/modals/featureTourModal';
  26. import {AuthTokenGeneratorProvider} from 'sentry/components/onboarding/gettingStartedDoc/authTokenGenerator';
  27. import {OnboardingCodeSnippet} from 'sentry/components/onboarding/gettingStartedDoc/onboardingCodeSnippet';
  28. import {
  29. type Configuration,
  30. TabbedCodeSnippet,
  31. } from 'sentry/components/onboarding/gettingStartedDoc/step';
  32. import {
  33. type DocsParams,
  34. ProductSolution,
  35. } from 'sentry/components/onboarding/gettingStartedDoc/types';
  36. import {useLoadGettingStarted} from 'sentry/components/onboarding/gettingStartedDoc/utils/useLoadGettingStarted';
  37. import LegacyOnboardingPanel from 'sentry/components/onboardingPanel';
  38. import Panel from 'sentry/components/panels/panel';
  39. import PanelBody from 'sentry/components/panels/panelBody';
  40. import {filterProjects} from 'sentry/components/performanceOnboarding/utils';
  41. import {SidebarPanelKey} from 'sentry/components/sidebar/types';
  42. import {
  43. withoutPerformanceSupport,
  44. withPerformanceOnboarding,
  45. } from 'sentry/data/platformCategories';
  46. import platforms, {otherPlatform} from 'sentry/data/platforms';
  47. import {t, tct} from 'sentry/locale';
  48. import ConfigStore from 'sentry/stores/configStore';
  49. import SidebarPanelStore from 'sentry/stores/sidebarPanelStore';
  50. import {useLegacyStore} from 'sentry/stores/useLegacyStore';
  51. import pulsingIndicatorStyles from 'sentry/styles/pulsingIndicator';
  52. import {space} from 'sentry/styles/space';
  53. import type {Organization} from 'sentry/types/organization';
  54. import type {Project} from 'sentry/types/project';
  55. import {trackAnalytics} from 'sentry/utils/analytics';
  56. import {browserHistory} from 'sentry/utils/browserHistory';
  57. import {generateLinkToEventInTraceView} from 'sentry/utils/discover/urls';
  58. import EventWaiter from 'sentry/utils/eventWaiter';
  59. import useApi from 'sentry/utils/useApi';
  60. import {useLocation} from 'sentry/utils/useLocation';
  61. import useProjects from 'sentry/utils/useProjects';
  62. import {traceAnalytics} from './newTraceDetails/traceAnalytics';
  63. const performanceSetupUrl =
  64. 'https://docs.sentry.io/performance-monitoring/getting-started/';
  65. const docsLink = (
  66. <LinkButton external href={performanceSetupUrl}>
  67. {t('Setup')}
  68. </LinkButton>
  69. );
  70. export const PERFORMANCE_TOUR_STEPS: TourStep[] = [
  71. {
  72. title: t('Track Application Metrics'),
  73. image: <TourImage src={tourMetrics} />,
  74. body: (
  75. <TourText>
  76. {t(
  77. 'Monitor your slowest pageloads and APIs to see which users are having the worst time.'
  78. )}
  79. </TourText>
  80. ),
  81. actions: docsLink,
  82. },
  83. {
  84. title: t('Correlate Errors and Traces'),
  85. image: <TourImage src={tourCorrelate} />,
  86. body: (
  87. <TourText>
  88. {t(
  89. 'See what errors occurred within a transaction and the impact of those errors.'
  90. )}
  91. </TourText>
  92. ),
  93. actions: docsLink,
  94. },
  95. {
  96. title: t('Watch and Alert'),
  97. image: <TourImage src={tourAlert} />,
  98. body: (
  99. <TourText>
  100. {t(
  101. 'Highlight mission-critical pages and APIs and set latency alerts to notify you before things go wrong.'
  102. )}
  103. </TourText>
  104. ),
  105. actions: docsLink,
  106. },
  107. {
  108. title: t('Trace Across Systems'),
  109. image: <TourImage src={tourTrace} />,
  110. body: (
  111. <TourText>
  112. {t(
  113. "Follow a trace from a user's session and drill down to identify any bottlenecks that occur."
  114. )}
  115. </TourText>
  116. ),
  117. },
  118. ];
  119. type SampleButtonProps = {
  120. api: Client;
  121. errorMessage: React.ReactNode;
  122. loadingMessage: React.ReactNode;
  123. organization: Organization;
  124. project: Project;
  125. triggerText: React.ReactNode;
  126. };
  127. function SampleButton({
  128. triggerText,
  129. loadingMessage,
  130. errorMessage,
  131. project,
  132. organization,
  133. api,
  134. }: SampleButtonProps) {
  135. const location = useLocation();
  136. return (
  137. <Button
  138. data-test-id="create-sample-transaction-btn"
  139. onClick={async () => {
  140. trackAnalytics('performance_views.create_sample_transaction', {
  141. platform: project.platform,
  142. organization,
  143. });
  144. addLoadingMessage(loadingMessage, {
  145. duration: 15000,
  146. });
  147. const url = `/projects/${organization.slug}/${project.slug}/create-sample-transaction/`;
  148. try {
  149. const eventData = await api.requestPromise(url, {method: 'POST'});
  150. const traceSlug = eventData.contexts?.trace?.trace_id ?? '';
  151. browserHistory.push(
  152. generateLinkToEventInTraceView({
  153. eventId: eventData.eventID,
  154. location,
  155. projectSlug: project.slug,
  156. organization,
  157. timestamp: eventData.endTimestamp,
  158. traceSlug,
  159. demo: `${project.slug}:${eventData.eventID}`,
  160. })
  161. );
  162. clearIndicators();
  163. } catch (error) {
  164. Sentry.withScope(scope => {
  165. scope.setExtra('error', error);
  166. Sentry.captureException(new Error('Failed to create sample event'));
  167. });
  168. clearIndicators();
  169. addErrorMessage(errorMessage);
  170. return;
  171. }
  172. }}
  173. >
  174. {triggerText}
  175. </Button>
  176. );
  177. }
  178. type OnboardingProps = {
  179. organization: Organization;
  180. project: Project;
  181. };
  182. export function LegacyOnboarding({organization, project}: OnboardingProps) {
  183. const api = useApi();
  184. const {projects} = useProjects();
  185. const location = useLocation();
  186. const {projectsForOnboarding} = filterProjects(projects);
  187. const showOnboardingChecklist = organization.features.includes(
  188. 'performance-onboarding-checklist'
  189. );
  190. useEffect(() => {
  191. if (
  192. showOnboardingChecklist &&
  193. location.hash === '#performance-sidequest' &&
  194. projectsForOnboarding.some(p => p.id === project.id)
  195. ) {
  196. SidebarPanelStore.activatePanel(SidebarPanelKey.PERFORMANCE_ONBOARDING);
  197. }
  198. }, [location.hash, projectsForOnboarding, project.id, showOnboardingChecklist]);
  199. function handleAdvance(step: number, duration: number) {
  200. trackAnalytics('performance_views.tour.advance', {
  201. step,
  202. duration,
  203. organization,
  204. });
  205. }
  206. function handleClose(step: number, duration: number) {
  207. trackAnalytics('performance_views.tour.close', {
  208. step,
  209. duration,
  210. organization,
  211. });
  212. }
  213. const currentPlatform = project.platform;
  214. const hasPerformanceOnboarding = currentPlatform
  215. ? withPerformanceOnboarding.has(currentPlatform)
  216. : false;
  217. const noPerformanceSupport =
  218. currentPlatform && withoutPerformanceSupport.has(currentPlatform);
  219. let setupButton = (
  220. <LinkButton
  221. priority="primary"
  222. href="https://docs.sentry.io/performance-monitoring/getting-started/"
  223. external
  224. >
  225. {t('Start Setup')}
  226. </LinkButton>
  227. );
  228. if (hasPerformanceOnboarding && showOnboardingChecklist) {
  229. setupButton = (
  230. <Button
  231. priority="primary"
  232. onClick={event => {
  233. event.preventDefault();
  234. window.location.hash = 'performance-sidequest';
  235. SidebarPanelStore.activatePanel(SidebarPanelKey.PERFORMANCE_ONBOARDING);
  236. }}
  237. >
  238. {t('Set Up Tracing')}
  239. </Button>
  240. );
  241. }
  242. return (
  243. <Fragment>
  244. {noPerformanceSupport && (
  245. <UnsupportedAlert projectSlug={project.slug} featureName="Performance" />
  246. )}
  247. <LegacyOnboardingPanel image={<PerfImage src={emptyStateImg} />}>
  248. <h3>{t('Pinpoint problems')}</h3>
  249. <p>
  250. {t(
  251. 'Something seem slow? Track down transactions to connect the dots between 10-second page loads and poor-performing API calls or slow database queries.'
  252. )}
  253. </p>
  254. <ButtonList gap={1}>
  255. {setupButton}
  256. <SampleButton
  257. triggerText={t('View Sample Transaction')}
  258. loadingMessage={t('Processing sample transaction...')}
  259. errorMessage={t('Failed to create sample transaction')}
  260. organization={organization}
  261. project={project}
  262. api={api}
  263. />
  264. </ButtonList>
  265. <FeatureTourModal
  266. steps={PERFORMANCE_TOUR_STEPS}
  267. onAdvance={handleAdvance}
  268. onCloseModal={handleClose}
  269. doneUrl={performanceSetupUrl}
  270. doneText={t('Start Setup')}
  271. >
  272. {({showModal}) => (
  273. <Button
  274. priority="link"
  275. onClick={() => {
  276. trackAnalytics('performance_views.tour.start', {organization});
  277. showModal();
  278. }}
  279. >
  280. {t('Take a Tour')}
  281. </Button>
  282. )}
  283. </FeatureTourModal>
  284. </LegacyOnboardingPanel>
  285. </Fragment>
  286. );
  287. }
  288. const PerfImage = styled('img')`
  289. @media (min-width: ${p => p.theme.breakpoints.small}) {
  290. max-width: unset;
  291. user-select: none;
  292. position: absolute;
  293. top: 75px;
  294. bottom: 0;
  295. width: 450px;
  296. margin-top: auto;
  297. margin-bottom: auto;
  298. }
  299. @media (min-width: ${p => p.theme.breakpoints.medium}) {
  300. width: 480px;
  301. }
  302. @media (min-width: ${p => p.theme.breakpoints.large}) {
  303. width: 600px;
  304. }
  305. `;
  306. const ButtonList = styled(ButtonBar)`
  307. grid-template-columns: repeat(auto-fit, minmax(130px, max-content));
  308. margin-bottom: 16px;
  309. `;
  310. function WaitingIndicator({
  311. api,
  312. organization,
  313. project,
  314. }: {
  315. api: Client;
  316. organization: Organization;
  317. project: Project;
  318. }) {
  319. const [received, setReceived] = useState<boolean>(false);
  320. return (
  321. <EventWaiter
  322. api={api}
  323. organization={organization}
  324. project={project}
  325. eventType="transaction"
  326. onIssueReceived={() => {
  327. setReceived(true);
  328. }}
  329. >
  330. {() => (received ? <EventReceivedIndicator /> : <EventWaitingIndicator />)}
  331. </EventWaiter>
  332. );
  333. }
  334. type ConfigurationStepProps = {
  335. api: Client;
  336. configuration: Configuration;
  337. organization: Organization;
  338. project: Project;
  339. showWaitingIndicator: boolean;
  340. stepKey: string;
  341. title: React.ReactNode;
  342. };
  343. function ConfigurationStep({
  344. stepKey,
  345. title,
  346. api,
  347. organization,
  348. project,
  349. configuration,
  350. showWaitingIndicator,
  351. }: ConfigurationStepProps) {
  352. return (
  353. <GuidedSteps.Step stepKey={stepKey} title={title}>
  354. <div>
  355. <div>
  356. <DescriptionWrapper>{configuration.description}</DescriptionWrapper>
  357. <CodeSnippetWrapper>
  358. {configuration.code ? (
  359. Array.isArray(configuration.code) ? (
  360. <TabbedCodeSnippet tabs={configuration.code} />
  361. ) : (
  362. <OnboardingCodeSnippet language={configuration.language}>
  363. {configuration.code}
  364. </OnboardingCodeSnippet>
  365. )
  366. ) : null}
  367. </CodeSnippetWrapper>
  368. <CodeSnippetWrapper>
  369. {configuration.configurations && configuration.configurations.length > 0 ? (
  370. Array.isArray(configuration.configurations[0]!.code) ? (
  371. <TabbedCodeSnippet tabs={configuration.configurations[0]!.code} />
  372. ) : null
  373. ) : null}
  374. </CodeSnippetWrapper>
  375. <DescriptionWrapper>{configuration.additionalInfo}</DescriptionWrapper>
  376. {showWaitingIndicator ? (
  377. <WaitingIndicator api={api} organization={organization} project={project} />
  378. ) : null}
  379. </div>
  380. <GuidedSteps.ButtonWrapper>
  381. <GuidedSteps.BackButton size="md" />
  382. <GuidedSteps.NextButton size="md" />
  383. </GuidedSteps.ButtonWrapper>
  384. </div>
  385. </GuidedSteps.Step>
  386. );
  387. }
  388. function OnboardingPanel({
  389. project,
  390. children,
  391. }: {
  392. children: React.ReactNode;
  393. project: Project;
  394. }) {
  395. return (
  396. <Panel>
  397. <PanelBody>
  398. <AuthTokenGeneratorProvider projectSlug={project?.slug}>
  399. <div>
  400. <HeaderWrapper>
  401. <HeaderText>
  402. <Title>{t('Query for Traces, Get Answers')}</Title>
  403. <SubTitle>
  404. {t(
  405. '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.'
  406. )}
  407. </SubTitle>
  408. <BulletList>
  409. <li>
  410. {t(
  411. 'Find traces tied to a user complaint and pinpoint exactly what broke'
  412. )}
  413. </li>
  414. <li>
  415. {t(
  416. 'Debug persistent issues by investigating API payloads, cache sizes, user tokens, and more'
  417. )}
  418. </li>
  419. <li>
  420. {t(
  421. 'Track any span attribute as a metric to catch slowdowns before they escalate'
  422. )}
  423. </li>
  424. </BulletList>
  425. </HeaderText>
  426. <Image src={emptyTraceImg} />
  427. </HeaderWrapper>
  428. <Divider />
  429. <Body>
  430. <Setup>{children}</Setup>
  431. <Preview>
  432. <BodyTitle>{t('Preview a Sentry Trace')}</BodyTitle>
  433. <Arcade
  434. src="https://demo.arcade.software/BPVB65UiYCxixEw8bnmj?embed"
  435. loading="lazy"
  436. allowFullScreen
  437. />
  438. </Preview>
  439. </Body>
  440. </div>
  441. </AuthTokenGeneratorProvider>
  442. </PanelBody>
  443. </Panel>
  444. );
  445. }
  446. export function Onboarding({organization, project}: OnboardingProps) {
  447. const api = useApi();
  448. const {isSelfHosted, urlPrefix} = useLegacyStore(ConfigStore);
  449. const [received, setReceived] = useState<boolean>(false);
  450. const showNewUi = organization.features.includes('tracing-onboarding-new-ui');
  451. const currentPlatform = project.platform
  452. ? platforms.find(p => p.id === project.platform)
  453. : undefined;
  454. const {isLoading, docs, dsn, projectKeyId} = useLoadGettingStarted({
  455. platform: currentPlatform || otherPlatform,
  456. orgSlug: organization.slug,
  457. projSlug: project.slug,
  458. productType: 'performance',
  459. });
  460. const doesNotSupportPerformance = project.platform
  461. ? withoutPerformanceSupport.has(project.platform)
  462. : false;
  463. useEffect(() => {
  464. if (isLoading || !currentPlatform || !dsn || !projectKeyId) {
  465. return;
  466. }
  467. traceAnalytics.trackTracingOnboarding(
  468. organization,
  469. currentPlatform.id,
  470. !doesNotSupportPerformance,
  471. withPerformanceOnboarding.has(currentPlatform.id)
  472. );
  473. }, [
  474. currentPlatform,
  475. isLoading,
  476. dsn,
  477. projectKeyId,
  478. organization,
  479. doesNotSupportPerformance,
  480. ]);
  481. if (!showNewUi) {
  482. return <LegacyOnboarding organization={organization} project={project} />;
  483. }
  484. const performanceDocs = docs?.performanceOnboarding;
  485. if (isLoading) {
  486. return <LoadingIndicator />;
  487. }
  488. if (doesNotSupportPerformance) {
  489. return (
  490. <OnboardingPanel project={project}>
  491. <div>
  492. {tct(
  493. 'Fiddlesticks. Performance isn’t available for your [platform] project yet but we’re definitely still working on it. Stay tuned.',
  494. {platform: currentPlatform?.name || project.slug}
  495. )}
  496. </div>
  497. <br />
  498. <div>
  499. <LinkButton
  500. size="sm"
  501. href="https://docs.sentry.io/platforms/"
  502. external
  503. onClick={() => {
  504. traceAnalytics.trackPlatformDocsViewed(
  505. organization,
  506. currentPlatform?.id ?? project.platform ?? 'unknown'
  507. );
  508. }}
  509. >
  510. {t('Go to Documentation')}
  511. </LinkButton>
  512. </div>
  513. </OnboardingPanel>
  514. );
  515. }
  516. if (!currentPlatform || !performanceDocs || !dsn || !projectKeyId) {
  517. return (
  518. <OnboardingPanel project={project}>
  519. <div>
  520. {tct(
  521. 'Fiddlesticks. The tracing onboarding checklist isn’t available for your [project] project yet, but for now, go to Sentry docs for installation details.',
  522. {project: project.slug}
  523. )}
  524. </div>
  525. <br />
  526. <div>
  527. <LinkButton
  528. size="sm"
  529. href="https://docs.sentry.io/product/performance/getting-started/"
  530. external
  531. onClick={() => {
  532. traceAnalytics.trackPerformanceSetupDocsViewed(
  533. organization,
  534. currentPlatform?.id ?? project.platform ?? 'unknown'
  535. );
  536. }}
  537. >
  538. {t('Go to Documentation')}
  539. </LinkButton>
  540. </div>
  541. </OnboardingPanel>
  542. );
  543. }
  544. const docParams: DocsParams<any> = {
  545. api,
  546. projectKeyId,
  547. dsn,
  548. organization,
  549. platformKey: project.platform || 'other',
  550. projectId: project.id,
  551. projectSlug: project.slug,
  552. isFeedbackSelected: false,
  553. isPerformanceSelected: true,
  554. isProfilingSelected: false,
  555. isReplaySelected: false,
  556. sourcePackageRegistries: {
  557. isLoading: false,
  558. data: undefined,
  559. },
  560. platformOptions: [ProductSolution.PERFORMANCE_MONITORING],
  561. newOrg: false,
  562. feedbackOptions: {},
  563. urlPrefix,
  564. isSelfHosted,
  565. };
  566. const installStep = performanceDocs.install(docParams)[0]!;
  567. const configureStep = performanceDocs.configure(docParams)[0]!;
  568. const [sentryConfiguration, addingDistributedTracing] = configureStep.configurations!;
  569. const verifyStep = performanceDocs.verify(docParams)[0]!;
  570. const hasVerifyStep = !!(verifyStep.configurations || verifyStep.description);
  571. const eventWaitingIndicator = (
  572. <EventWaiter
  573. api={api}
  574. organization={organization}
  575. project={project}
  576. eventType="transaction"
  577. onIssueReceived={() => {
  578. setReceived(true);
  579. }}
  580. >
  581. {() => (received ? <EventReceivedIndicator /> : <EventWaitingIndicator />)}
  582. </EventWaiter>
  583. );
  584. return (
  585. <OnboardingPanel project={project}>
  586. <BodyTitle>{t('Set up the Sentry SDK')}</BodyTitle>
  587. <GuidedSteps>
  588. <GuidedSteps.Step stepKey="install-sentry" title={t('Install Sentry')}>
  589. <div>
  590. <div>
  591. <DescriptionWrapper>{installStep.description}</DescriptionWrapper>
  592. {installStep.configurations?.map((configuration, index) => (
  593. <div key={index}>
  594. <DescriptionWrapper>{configuration.description}</DescriptionWrapper>
  595. <CodeSnippetWrapper>
  596. {configuration.code ? (
  597. Array.isArray(configuration.code) ? (
  598. <TabbedCodeSnippet tabs={configuration.code} />
  599. ) : (
  600. <OnboardingCodeSnippet language={configuration.language}>
  601. {configuration.code}
  602. </OnboardingCodeSnippet>
  603. )
  604. ) : null}
  605. </CodeSnippetWrapper>
  606. </div>
  607. ))}
  608. {!configureStep.configurations && !verifyStep.configurations
  609. ? eventWaitingIndicator
  610. : null}
  611. </div>
  612. <GuidedSteps.ButtonWrapper>
  613. <GuidedSteps.BackButton size="md" />
  614. <GuidedSteps.NextButton size="md" />
  615. </GuidedSteps.ButtonWrapper>
  616. </div>
  617. </GuidedSteps.Step>
  618. {sentryConfiguration ? (
  619. <ConfigurationStep
  620. stepKey={'configure-sentry'}
  621. title={t('Configure Sentry')}
  622. configuration={sentryConfiguration}
  623. api={api}
  624. organization={organization}
  625. project={project}
  626. showWaitingIndicator={!hasVerifyStep}
  627. />
  628. ) : null}
  629. {addingDistributedTracing ? (
  630. <ConfigurationStep
  631. stepKey={'add-distributed-tracing'}
  632. title={tct('Add Distributed Tracing [optional:(Optional)]', {
  633. optional: <OptionalText />,
  634. })}
  635. configuration={addingDistributedTracing}
  636. api={api}
  637. organization={organization}
  638. project={project}
  639. showWaitingIndicator={!hasVerifyStep}
  640. />
  641. ) : null}
  642. {verifyStep.configurations || verifyStep.description ? (
  643. <GuidedSteps.Step stepKey="verify-sentry" title={t('Verify')}>
  644. <div>
  645. <DescriptionWrapper>{verifyStep.description}</DescriptionWrapper>
  646. {verifyStep.configurations?.map((configuration, index) => (
  647. <div key={index}>
  648. <DescriptionWrapper>{configuration.description}</DescriptionWrapper>
  649. <CodeSnippetWrapper>
  650. {configuration.code ? (
  651. Array.isArray(configuration.code) ? (
  652. <TabbedCodeSnippet tabs={configuration.code} />
  653. ) : (
  654. <OnboardingCodeSnippet language={configuration.language}>
  655. {configuration.code}
  656. </OnboardingCodeSnippet>
  657. )
  658. ) : null}
  659. </CodeSnippetWrapper>
  660. </div>
  661. ))}
  662. {eventWaitingIndicator}
  663. </div>
  664. <GuidedSteps.ButtonWrapper>
  665. <GuidedSteps.BackButton size="md" />
  666. <SampleButton
  667. triggerText={t('Take me to an example')}
  668. loadingMessage={t('Processing sample trace...')}
  669. errorMessage={t('Failed to create sample trace')}
  670. organization={organization}
  671. project={project}
  672. api={api}
  673. />
  674. </GuidedSteps.ButtonWrapper>
  675. </GuidedSteps.Step>
  676. ) : (
  677. <Fragment />
  678. )}
  679. </GuidedSteps>
  680. </OnboardingPanel>
  681. );
  682. }
  683. const EventWaitingIndicator = styled((p: React.HTMLAttributes<HTMLDivElement>) => (
  684. <div {...p}>
  685. {t("Waiting for this project's first trace")}
  686. <PulsingIndicator />
  687. </div>
  688. ))`
  689. display: flex;
  690. align-items: center;
  691. position: relative;
  692. z-index: 10;
  693. flex-grow: 1;
  694. font-size: ${p => p.theme.fontSizeMedium};
  695. color: ${p => p.theme.pink400};
  696. `;
  697. const PulsingIndicator = styled('div')`
  698. ${pulsingIndicatorStyles};
  699. margin-left: ${space(1)};
  700. `;
  701. const OptionalText = styled('span')`
  702. color: ${p => p.theme.purple300};
  703. font-weight: ${p => p.theme.fontWeightNormal};
  704. `;
  705. const EventReceivedIndicator = styled((p: React.HTMLAttributes<HTMLDivElement>) => (
  706. <div {...p}>
  707. {'🎉 '}
  708. {t("We've received this project's first trace!")}
  709. </div>
  710. ))`
  711. display: flex;
  712. align-items: center;
  713. flex-grow: 1;
  714. font-size: ${p => p.theme.fontSizeMedium};
  715. color: ${p => p.theme.successText};
  716. `;
  717. const SubTitle = styled('div')`
  718. margin-bottom: ${space(1)};
  719. `;
  720. const Title = styled('div')`
  721. font-size: 26px;
  722. font-weight: ${p => p.theme.fontWeightBold};
  723. `;
  724. const BulletList = styled('ul')`
  725. list-style-type: disc;
  726. padding-left: 20px;
  727. margin-bottom: ${space(2)};
  728. li {
  729. margin-bottom: ${space(1)};
  730. }
  731. `;
  732. const HeaderWrapper = styled('div')`
  733. display: flex;
  734. justify-content: space-between;
  735. gap: ${space(3)};
  736. border-radius: ${p => p.theme.borderRadius};
  737. padding: ${space(4)};
  738. `;
  739. const HeaderText = styled('div')`
  740. flex: 0.65;
  741. @media (max-width: ${p => p.theme.breakpoints.small}) {
  742. flex: 1;
  743. }
  744. `;
  745. const BodyTitle = styled('div')`
  746. font-size: ${p => p.theme.fontSizeExtraLarge};
  747. font-weight: ${p => p.theme.fontWeightBold};
  748. margin-bottom: ${space(1)};
  749. `;
  750. const Setup = styled('div')`
  751. padding: ${space(4)};
  752. &:after {
  753. content: '';
  754. position: absolute;
  755. right: 50%;
  756. top: 2.5%;
  757. height: 95%;
  758. border-right: 1px ${p => p.theme.border} solid;
  759. }
  760. `;
  761. const Preview = styled('div')`
  762. padding: ${space(4)};
  763. `;
  764. const Body = styled('div')`
  765. display: grid;
  766. position: relative;
  767. grid-auto-columns: minmax(0, 1fr);
  768. grid-auto-flow: column;
  769. h4 {
  770. margin-bottom: 0;
  771. }
  772. `;
  773. const Image = styled('img')`
  774. display: block;
  775. pointer-events: none;
  776. height: 120px;
  777. overflow: hidden;
  778. @media (max-width: ${p => p.theme.breakpoints.small}) {
  779. display: none;
  780. }
  781. `;
  782. const Divider = styled('hr')`
  783. height: 1px;
  784. width: 95%;
  785. background: ${p => p.theme.border};
  786. border: none;
  787. margin-top: 0;
  788. margin-bottom: 0;
  789. `;
  790. const Arcade = styled('iframe')`
  791. width: 750px;
  792. max-width: 100%;
  793. margin-top: ${space(3)};
  794. height: 522px;
  795. border: 0;
  796. `;
  797. const CodeSnippetWrapper = styled('div')`
  798. margin-bottom: ${space(2)};
  799. `;
  800. const DescriptionWrapper = styled('div')`
  801. margin-bottom: ${space(1)};
  802. code {
  803. color: ${p => p.theme.pink400};
  804. }
  805. `;