onboarding.tsx 5.9 KB

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