onboarding.tsx 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  1. import {Fragment, useEffect} 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 tourAlert from 'sentry-images/spot/performance-tour-alert.svg';
  6. import tourCorrelate from 'sentry-images/spot/performance-tour-correlate.svg';
  7. import tourMetrics from 'sentry-images/spot/performance-tour-metrics.svg';
  8. import tourTrace from 'sentry-images/spot/performance-tour-trace.svg';
  9. import {
  10. addErrorMessage,
  11. addLoadingMessage,
  12. clearIndicators,
  13. } from 'sentry/actionCreators/indicator';
  14. import UnsupportedAlert from 'sentry/components/alerts/unsupportedAlert';
  15. import {Button, LinkButton} from 'sentry/components/button';
  16. import ButtonBar from 'sentry/components/buttonBar';
  17. import type {TourStep} from 'sentry/components/modals/featureTourModal';
  18. import FeatureTourModal, {
  19. TourImage,
  20. TourText,
  21. } from 'sentry/components/modals/featureTourModal';
  22. import OnboardingPanel from 'sentry/components/onboardingPanel';
  23. import {filterProjects} from 'sentry/components/performanceOnboarding/utils';
  24. import {SidebarPanelKey} from 'sentry/components/sidebar/types';
  25. import {
  26. withoutPerformanceSupport,
  27. withPerformanceOnboarding,
  28. } from 'sentry/data/platformCategories';
  29. import {t} from 'sentry/locale';
  30. import SidebarPanelStore from 'sentry/stores/sidebarPanelStore';
  31. import type {Organization} from 'sentry/types/organization';
  32. import type {Project} from 'sentry/types/project';
  33. import {trackAnalytics} from 'sentry/utils/analytics';
  34. import {browserHistory} from 'sentry/utils/browserHistory';
  35. import {generateLinkToEventInTraceView} from 'sentry/utils/discover/urls';
  36. import useApi from 'sentry/utils/useApi';
  37. import {useLocation} from 'sentry/utils/useLocation';
  38. import useProjects from 'sentry/utils/useProjects';
  39. const performanceSetupUrl =
  40. 'https://docs.sentry.io/performance-monitoring/getting-started/';
  41. const docsLink = (
  42. <LinkButton external href={performanceSetupUrl}>
  43. {t('Setup')}
  44. </LinkButton>
  45. );
  46. export const PERFORMANCE_TOUR_STEPS: TourStep[] = [
  47. {
  48. title: t('Track Application Metrics'),
  49. image: <TourImage src={tourMetrics} />,
  50. body: (
  51. <TourText>
  52. {t(
  53. 'Monitor your slowest pageloads and APIs to see which users are having the worst time.'
  54. )}
  55. </TourText>
  56. ),
  57. actions: docsLink,
  58. },
  59. {
  60. title: t('Correlate Errors and Traces'),
  61. image: <TourImage src={tourCorrelate} />,
  62. body: (
  63. <TourText>
  64. {t(
  65. 'See what errors occurred within a transaction and the impact of those errors.'
  66. )}
  67. </TourText>
  68. ),
  69. actions: docsLink,
  70. },
  71. {
  72. title: t('Watch and Alert'),
  73. image: <TourImage src={tourAlert} />,
  74. body: (
  75. <TourText>
  76. {t(
  77. 'Highlight mission-critical pages and APIs and set latency alerts to notify you before things go wrong.'
  78. )}
  79. </TourText>
  80. ),
  81. actions: docsLink,
  82. },
  83. {
  84. title: t('Trace Across Systems'),
  85. image: <TourImage src={tourTrace} />,
  86. body: (
  87. <TourText>
  88. {t(
  89. "Follow a trace from a user's session and drill down to identify any bottlenecks that occur."
  90. )}
  91. </TourText>
  92. ),
  93. },
  94. ];
  95. type Props = {
  96. organization: Organization;
  97. project: Project;
  98. };
  99. function Onboarding({organization, project}: Props) {
  100. const api = useApi();
  101. const {projects} = useProjects();
  102. const location = useLocation();
  103. const {projectsForOnboarding} = filterProjects(projects);
  104. const showOnboardingChecklist = organization.features?.includes(
  105. 'performance-onboarding-checklist'
  106. );
  107. useEffect(() => {
  108. if (
  109. showOnboardingChecklist &&
  110. location.hash === '#performance-sidequest' &&
  111. projectsForOnboarding.some(p => p.id === project.id)
  112. ) {
  113. SidebarPanelStore.activatePanel(SidebarPanelKey.PERFORMANCE_ONBOARDING);
  114. }
  115. }, [location.hash, projectsForOnboarding, project.id, showOnboardingChecklist]);
  116. function handleAdvance(step: number, duration: number) {
  117. trackAnalytics('performance_views.tour.advance', {
  118. step,
  119. duration,
  120. organization,
  121. });
  122. }
  123. function handleClose(step: number, duration: number) {
  124. trackAnalytics('performance_views.tour.close', {
  125. step,
  126. duration,
  127. organization,
  128. });
  129. }
  130. const currentPlatform = project.platform;
  131. const hasPerformanceOnboarding = currentPlatform
  132. ? withPerformanceOnboarding.has(currentPlatform)
  133. : false;
  134. const noPerformanceSupport =
  135. currentPlatform && withoutPerformanceSupport.has(currentPlatform);
  136. let setupButton = (
  137. <LinkButton
  138. priority="primary"
  139. href="https://docs.sentry.io/performance-monitoring/getting-started/"
  140. external
  141. >
  142. {t('Start Setup')}
  143. </LinkButton>
  144. );
  145. if (hasPerformanceOnboarding && showOnboardingChecklist) {
  146. setupButton = (
  147. <Button
  148. priority="primary"
  149. onClick={event => {
  150. event.preventDefault();
  151. window.location.hash = 'performance-sidequest';
  152. SidebarPanelStore.activatePanel(SidebarPanelKey.PERFORMANCE_ONBOARDING);
  153. }}
  154. >
  155. {t('Start Checklist')}
  156. </Button>
  157. );
  158. }
  159. return (
  160. <Fragment>
  161. {noPerformanceSupport && (
  162. <UnsupportedAlert projectSlug={project.slug} featureName="Performance" />
  163. )}
  164. <OnboardingPanel image={<PerfImage src={emptyStateImg} />}>
  165. <h3>{t('Pinpoint problems')}</h3>
  166. <p>
  167. {t(
  168. 'Something seem slow? Track down transactions to connect the dots between 10-second page loads and poor-performing API calls or slow database queries.'
  169. )}
  170. </p>
  171. <ButtonList gap={1}>
  172. {setupButton}
  173. <Button
  174. data-test-id="create-sample-transaction-btn"
  175. onClick={async () => {
  176. trackAnalytics('performance_views.create_sample_transaction', {
  177. platform: project.platform,
  178. organization,
  179. });
  180. addLoadingMessage(t('Processing sample event...'), {
  181. duration: 15000,
  182. });
  183. const url = `/projects/${organization.slug}/${project.slug}/create-sample-transaction/`;
  184. try {
  185. const eventData = await api.requestPromise(url, {method: 'POST'});
  186. const traceSlug = eventData.contexts?.trace?.trace_id ?? '';
  187. browserHistory.push(
  188. generateLinkToEventInTraceView({
  189. eventId: eventData.eventID,
  190. location,
  191. projectSlug: project.slug,
  192. organization,
  193. timestamp: eventData.endTimestamp,
  194. traceSlug,
  195. demo: `${project.slug}:${eventData.eventID}`,
  196. })
  197. );
  198. clearIndicators();
  199. } catch (error) {
  200. Sentry.withScope(scope => {
  201. scope.setExtra('error', error);
  202. Sentry.captureException(new Error('Failed to create sample event'));
  203. });
  204. clearIndicators();
  205. addErrorMessage(t('Failed to create a new sample event'));
  206. return;
  207. }
  208. }}
  209. >
  210. {t('View Sample Transaction')}
  211. </Button>
  212. </ButtonList>
  213. <FeatureTourModal
  214. steps={PERFORMANCE_TOUR_STEPS}
  215. onAdvance={handleAdvance}
  216. onCloseModal={handleClose}
  217. doneUrl={performanceSetupUrl}
  218. doneText={t('Start Setup')}
  219. >
  220. {({showModal}) => (
  221. <Button
  222. priority="link"
  223. onClick={() => {
  224. trackAnalytics('performance_views.tour.start', {organization});
  225. showModal();
  226. }}
  227. >
  228. {t('Take a Tour')}
  229. </Button>
  230. )}
  231. </FeatureTourModal>
  232. </OnboardingPanel>
  233. </Fragment>
  234. );
  235. }
  236. const PerfImage = styled('img')`
  237. @media (min-width: ${p => p.theme.breakpoints.small}) {
  238. max-width: unset;
  239. user-select: none;
  240. position: absolute;
  241. top: 75px;
  242. bottom: 0;
  243. width: 450px;
  244. margin-top: auto;
  245. margin-bottom: auto;
  246. }
  247. @media (min-width: ${p => p.theme.breakpoints.medium}) {
  248. width: 480px;
  249. }
  250. @media (min-width: ${p => p.theme.breakpoints.large}) {
  251. width: 600px;
  252. }
  253. `;
  254. const ButtonList = styled(ButtonBar)`
  255. grid-template-columns: repeat(auto-fit, minmax(130px, max-content));
  256. margin-bottom: 16px;
  257. `;
  258. export default Onboarding;