onboarding.tsx 7.9 KB

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