sections.tsx 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154
  1. import {MouseEvent, useEffect} from 'react';
  2. import queryString from 'query-string';
  3. import ObjectInspector from 'sentry/components/objectInspector';
  4. import {t, tct} from 'sentry/locale';
  5. import {formatBytesBase10} from 'sentry/utils';
  6. import useCrumbHandlers from 'sentry/utils/replays/hooks/useCrumbHandlers';
  7. import {
  8. Indent,
  9. keyValueTableOrNotFound,
  10. SectionItem,
  11. SizeTooltip,
  12. Warning,
  13. } from 'sentry/views/replays/detail/network/details/components';
  14. import {useDismissReqRespBodiesAlert} from 'sentry/views/replays/detail/network/details/onboarding';
  15. import TimestampButton from 'sentry/views/replays/detail/timestampButton';
  16. import type {NetworkSpan} from 'sentry/views/replays/types';
  17. export type SectionProps = {
  18. item: NetworkSpan;
  19. projectId: string;
  20. startTimestampMs: number;
  21. };
  22. export function GeneralSection({item, startTimestampMs}: SectionProps) {
  23. const {handleClick} = useCrumbHandlers(startTimestampMs);
  24. const startMs = item.startTimestamp * 1000;
  25. const endMs = item.endTimestamp * 1000;
  26. const data = {
  27. [t('URL')]: item.description,
  28. [t('Type')]: item.op,
  29. [t('Method')]: item.data?.method ?? '',
  30. [t('Status Code')]: item.data?.statusCode ?? '',
  31. [t('Request Body Size')]: (
  32. <SizeTooltip>{formatBytesBase10(item.data?.request?.size ?? 0)}</SizeTooltip>
  33. ),
  34. [t('Response Body Size')]: (
  35. <SizeTooltip>{formatBytesBase10(item.data?.response?.size ?? 0)}</SizeTooltip>
  36. ),
  37. [t('Duration')]: `${(endMs - startMs).toFixed(2)}ms`,
  38. [t('Timestamp')]: (
  39. <TimestampButton
  40. format="mm:ss.SSS"
  41. onClick={(event: MouseEvent) => {
  42. event.stopPropagation();
  43. handleClick(item);
  44. }}
  45. startTimestampMs={startTimestampMs}
  46. timestampMs={startMs}
  47. />
  48. ),
  49. };
  50. return (
  51. <SectionItem title={t('General')}>
  52. {keyValueTableOrNotFound(data, t('Missing request details'))}
  53. </SectionItem>
  54. );
  55. }
  56. export function RequestHeadersSection({item}: SectionProps) {
  57. return (
  58. <SectionItem title={t('Request Headers')}>
  59. {keyValueTableOrNotFound(item.data?.request?.headers, t('Headers not captured'))}
  60. </SectionItem>
  61. );
  62. }
  63. export function ResponseHeadersSection({item}: SectionProps) {
  64. return (
  65. <SectionItem title={t('Response Headers')}>
  66. {keyValueTableOrNotFound(item.data?.request?.headers, t('Headers not captured'))}
  67. </SectionItem>
  68. );
  69. }
  70. export function QueryParamsSection({item}: SectionProps) {
  71. const queryParams = queryString.parse(item.description?.split('?')?.[1] ?? '');
  72. return (
  73. <SectionItem title={t('Query String Parameters')}>
  74. <Indent>
  75. <ObjectInspector data={queryParams} expandLevel={3} showCopyButton />
  76. </Indent>
  77. </SectionItem>
  78. );
  79. }
  80. export function RequestPayloadSection({item}: SectionProps) {
  81. const {dismiss, isDismissed} = useDismissReqRespBodiesAlert();
  82. const hasRequest = 'request' in item.data;
  83. useEffect(() => {
  84. if (!isDismissed && hasRequest) {
  85. dismiss();
  86. }
  87. }, [dismiss, hasRequest, isDismissed]);
  88. return (
  89. <SectionItem
  90. title={t('Request Body')}
  91. titleExtra={
  92. <SizeTooltip>
  93. {t('Size:')} {formatBytesBase10(item.data?.request?.size ?? 0)}
  94. </SizeTooltip>
  95. }
  96. >
  97. <Indent>
  98. <Warning warnings={item.data?.request?._meta?.warnings} />
  99. {hasRequest ? (
  100. <ObjectInspector data={item.data.request.body} expandLevel={2} showCopyButton />
  101. ) : (
  102. tct('Request body not found.', item.data)
  103. )}
  104. </Indent>
  105. </SectionItem>
  106. );
  107. }
  108. export function ResponsePayloadSection({item}: SectionProps) {
  109. const {dismiss, isDismissed} = useDismissReqRespBodiesAlert();
  110. const hasResponse = 'response' in item.data;
  111. useEffect(() => {
  112. if (!isDismissed && hasResponse) {
  113. dismiss();
  114. }
  115. }, [dismiss, hasResponse, isDismissed]);
  116. return (
  117. <SectionItem
  118. title={t('Response Body')}
  119. titleExtra={
  120. <SizeTooltip>
  121. {t('Size:')} {formatBytesBase10(item.data?.response?.size ?? 0)}
  122. </SizeTooltip>
  123. }
  124. >
  125. <Indent>
  126. <Warning warnings={item.data?.response?._meta?.warnings} />
  127. {hasResponse ? (
  128. <ObjectInspector
  129. data={item.data.response.body}
  130. expandLevel={2}
  131. showCopyButton
  132. />
  133. ) : (
  134. tct('Response body not found.', item.data)
  135. )}
  136. </Indent>
  137. </SectionItem>
  138. );
  139. }