onboarding.tsx 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. import styled from '@emotion/styled';
  2. import Alert from 'sentry/components/alert';
  3. import {CodeSnippet} from 'sentry/components/codeSnippet';
  4. import ExternalLink from 'sentry/components/links/externalLink';
  5. import {useReplayContext} from 'sentry/components/replays/replayContext';
  6. import TextCopyInput from 'sentry/components/textCopyInput';
  7. import {t, tct} from 'sentry/locale';
  8. import {space} from 'sentry/styles/space';
  9. import type {SpanFrame} from 'sentry/utils/replays/types';
  10. import useDismissAlert from 'sentry/utils/useDismissAlert';
  11. import useOrganization from 'sentry/utils/useOrganization';
  12. import useProjectSdkNeedsUpdate from 'sentry/utils/useProjectSdkNeedsUpdate';
  13. import {Output} from 'sentry/views/replays/detail/network/details/getOutputType';
  14. import type {TabKey} from 'sentry/views/replays/detail/network/details/tabs';
  15. export const useDismissReqRespBodiesAlert = () => {
  16. const organization = useOrganization();
  17. return useDismissAlert({
  18. key: `${organization.id}:replay-network-bodies-alert-dismissed`,
  19. });
  20. };
  21. export function UnsupportedOp({type}: {type: 'headers' | 'bodies'}) {
  22. const title =
  23. type === 'bodies'
  24. ? t('Capture Request and Response Bodies')
  25. : t('Capture Request and Response Headers');
  26. return (
  27. <StyledInstructions data-test-id="network-op-unsupported">
  28. <h1>{title}</h1>
  29. <p>
  30. {tct(
  31. `This feature is only compatible with [fetch] and [xhr] request types. [link].`,
  32. {
  33. fetch: <code>fetch</code>,
  34. xhr: <code>xhr</code>,
  35. link: (
  36. <ExternalLink href="https://docs.sentry.io/platforms/javascript/session-replay/configuration/#network-details">
  37. {t('Learn more')}
  38. </ExternalLink>
  39. ),
  40. }
  41. )}
  42. </p>
  43. </StyledInstructions>
  44. );
  45. }
  46. export function Setup({
  47. item,
  48. projectId,
  49. showSnippet,
  50. visibleTab,
  51. }: {
  52. item: SpanFrame;
  53. projectId: string;
  54. showSnippet: Output;
  55. visibleTab: TabKey;
  56. }) {
  57. const organization = useOrganization();
  58. const {isFetching, needsUpdate} = useProjectSdkNeedsUpdate({
  59. // Only show update instructions if not >= 7.50.0, but our instructions
  60. // will show a different min version as there are known bugs in 7.50 ->
  61. // 7.53
  62. minVersion: '7.50.0',
  63. organization,
  64. projectId: [projectId],
  65. });
  66. const sdkNeedsUpdate = !isFetching && Boolean(needsUpdate);
  67. const {replay} = useReplayContext();
  68. const isVideoReplay = replay?.isVideoReplay();
  69. const url = item.description || 'http://example.com';
  70. return isVideoReplay ? (
  71. visibleTab === 'request' || visibleTab === 'response' ? (
  72. <StyledAlert type="info" showIcon>
  73. {tct(
  74. 'Request and response headers or bodies are currently not available for mobile platforms. Track this [link:GitHub issue] to get progress on support for this feature.',
  75. {
  76. link: (
  77. <ExternalLink href="https://github.com/getsentry/sentry-react-native/issues/4106" />
  78. ),
  79. }
  80. )}
  81. </StyledAlert>
  82. ) : null
  83. ) : (
  84. <SetupInstructions
  85. minVersion="7.53.1"
  86. sdkNeedsUpdate={sdkNeedsUpdate}
  87. showSnippet={showSnippet}
  88. url={url}
  89. visibleTab={visibleTab}
  90. />
  91. );
  92. }
  93. function SetupInstructions({
  94. minVersion,
  95. sdkNeedsUpdate,
  96. showSnippet,
  97. url,
  98. visibleTab,
  99. }: {
  100. minVersion: string;
  101. sdkNeedsUpdate: boolean;
  102. showSnippet: Output;
  103. url: string;
  104. visibleTab: TabKey;
  105. }) {
  106. if (showSnippet === Output.DATA && visibleTab === 'details') {
  107. return (
  108. <NoMarginAlert type="muted" system data-test-id="network-setup-steps">
  109. {tct(
  110. 'You can capture additional headers by adding them to the [requestConfig] and [responseConfig] lists in your SDK config.',
  111. {
  112. requestConfig: <code>networkRequestHeaders</code>,
  113. responseConfig: <code>networkResponseHeaders</code>,
  114. }
  115. )}
  116. </NoMarginAlert>
  117. );
  118. }
  119. function trimUrl(oldUrl: string): string {
  120. const end = oldUrl.indexOf('?') > 0 ? oldUrl.indexOf('?') : oldUrl.length;
  121. return oldUrl.substring(0, end);
  122. }
  123. const urlSnippet = `
  124. networkDetailAllowUrls: ['${trimUrl(url)}'],`;
  125. const headersSnippet = `
  126. networkRequestHeaders: ['X-Custom-Header'],
  127. networkResponseHeaders: ['X-Custom-Header'],`;
  128. const includeHeadersSnippet =
  129. showSnippet === Output.SETUP ||
  130. ([Output.URL_SKIPPED, Output.DATA].includes(showSnippet) && visibleTab === 'details');
  131. const code = `Sentry.init({
  132. integrations: [
  133. Sentry.replayIntegration({${urlSnippet + (includeHeadersSnippet ? headersSnippet : '')}
  134. }),
  135. ],
  136. })`;
  137. const title =
  138. showSnippet === Output.SETUP
  139. ? t('Capture Request and Response Headers and Bodies')
  140. : visibleTab === 'details'
  141. ? t('Capture Request and Response Headers')
  142. : t('Capture Request and Response Bodies');
  143. return (
  144. <StyledInstructions data-test-id="network-setup-steps">
  145. <h1>{title}</h1>
  146. <p>
  147. {tct(
  148. `To protect user privacy, Session Replay defaults to not capturing the request or response headers. However, we provide the option to do so, if it’s critical to your debugging process. [link].`,
  149. {
  150. link: (
  151. <ExternalLink href="https://docs.sentry.io/platforms/javascript/session-replay/configuration/#network-details">
  152. {t('Learn More')}
  153. </ExternalLink>
  154. ),
  155. }
  156. )}
  157. </p>
  158. <NetworkUrlWrapper>
  159. {showSnippet === Output.URL_SKIPPED &&
  160. url !== '[Filtered]' &&
  161. tct(
  162. 'Add the following to your [field] list to start capturing data: [alert] ',
  163. {
  164. field: <code>networkDetailAllowUrls</code>,
  165. alert: <StyledTextCopyInput>{trimUrl(url)}</StyledTextCopyInput>,
  166. }
  167. )}
  168. </NetworkUrlWrapper>
  169. {showSnippet === Output.BODY_SKIPPED && (
  170. <Alert type="warning">
  171. {tct('Enable [field] to capture both Request and Response bodies.', {
  172. field: <code>networkCaptureBodies: true</code>,
  173. })}
  174. </Alert>
  175. )}
  176. <h1>{t('Prerequisites')}</h1>
  177. <ol>
  178. {sdkNeedsUpdate ? (
  179. <li>
  180. {tct('Update your SDK version to >= [minVersion]', {
  181. minVersion,
  182. })}
  183. </li>
  184. ) : null}
  185. <li>{t('Edit the Replay integration configuration to allow this URL.')}</li>
  186. <li>{t('That’s it!')}</li>
  187. </ol>
  188. {url !== '[Filtered]' && (
  189. <CodeSnippet filename="JavaScript" language="javascript">
  190. {code}
  191. </CodeSnippet>
  192. )}
  193. </StyledInstructions>
  194. );
  195. }
  196. const StyledTextCopyInput = styled(TextCopyInput)`
  197. margin-top: ${space(0.5)};
  198. `;
  199. const NetworkUrlWrapper = styled('div')`
  200. margin: ${space(1)} 0 ${space(1.5)} 0;
  201. `;
  202. const NoMarginAlert = styled(Alert)`
  203. margin: 0;
  204. border-width: 1px 0 0 0;
  205. `;
  206. const StyledInstructions = styled('div')`
  207. font-size: ${p => p.theme.fontSizeSmall};
  208. margin-top: ${space(1)};
  209. border-top: 1px solid ${p => p.theme.border};
  210. padding: ${space(2)};
  211. &:first-child {
  212. margin-top: 0;
  213. border-top: none;
  214. }
  215. h1 {
  216. font-size: inherit;
  217. margin-bottom: ${space(1)};
  218. }
  219. p {
  220. margin-bottom: ${space(2)};
  221. }
  222. p:last-child {
  223. margin-bottom: 0;
  224. }
  225. `;
  226. const StyledAlert = styled(Alert)`
  227. margin: ${space(1)};
  228. `;