onboarding.tsx 7.4 KB

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