onboarding.tsx 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. import {useEffect} from 'react';
  2. import {browserHistory} from 'react-router';
  3. import styled from '@emotion/styled';
  4. import * as Sentry from '@sentry/react';
  5. import emptyStateImg from 'sentry-images/spot/performance-empty-state.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 {Button} 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 {withPerformanceOnboarding} from 'sentry/data/platformCategories';
  26. import {t} from 'sentry/locale';
  27. import SidebarPanelStore from 'sentry/stores/sidebarPanelStore';
  28. import type {Organization} from 'sentry/types/organization';
  29. import type {Project} from 'sentry/types/project';
  30. import {trackAnalytics} from 'sentry/utils/analytics';
  31. import useApi from 'sentry/utils/useApi';
  32. import {useLocation} from 'sentry/utils/useLocation';
  33. import useProjects from 'sentry/utils/useProjects';
  34. import {normalizeUrl} from 'sentry/utils/withDomainRequired';
  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. browserHistory.push(
  177. normalizeUrl(
  178. `/organizations/${organization.slug}/performance/${project.slug}:${eventData.eventID}/`
  179. )
  180. );
  181. clearIndicators();
  182. } catch (error) {
  183. Sentry.withScope(scope => {
  184. scope.setExtra('error', error);
  185. Sentry.captureException(new Error('Failed to create sample event'));
  186. });
  187. clearIndicators();
  188. addErrorMessage(t('Failed to create a new sample event'));
  189. return;
  190. }
  191. }}
  192. >
  193. {t('View Sample Transaction')}
  194. </Button>
  195. </ButtonList>
  196. <FeatureTourModal
  197. steps={PERFORMANCE_TOUR_STEPS}
  198. onAdvance={handleAdvance}
  199. onCloseModal={handleClose}
  200. doneUrl={performanceSetupUrl}
  201. doneText={t('Start Setup')}
  202. >
  203. {({showModal}) => (
  204. <Button
  205. priority="link"
  206. onClick={() => {
  207. trackAnalytics('performance_views.tour.start', {organization});
  208. showModal();
  209. }}
  210. >
  211. {t('Take a Tour')}
  212. </Button>
  213. )}
  214. </FeatureTourModal>
  215. </OnboardingPanel>
  216. );
  217. }
  218. const PerfImage = styled('img')`
  219. @media (min-width: ${p => p.theme.breakpoints.small}) {
  220. max-width: unset;
  221. user-select: none;
  222. position: absolute;
  223. top: 75px;
  224. bottom: 0;
  225. width: 450px;
  226. margin-top: auto;
  227. margin-bottom: auto;
  228. }
  229. @media (min-width: ${p => p.theme.breakpoints.medium}) {
  230. width: 480px;
  231. }
  232. @media (min-width: ${p => p.theme.breakpoints.large}) {
  233. width: 600px;
  234. }
  235. `;
  236. const ButtonList = styled(ButtonBar)`
  237. grid-template-columns: repeat(auto-fit, minmax(130px, max-content));
  238. margin-bottom: 16px;
  239. `;
  240. export default Onboarding;