onboarding.tsx 7.5 KB

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