onboarding.tsx 5.9 KB

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