index.tsx 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  1. import {isValidElement} from 'react';
  2. import styled from '@emotion/styled';
  3. import isNumber from 'lodash/isNumber';
  4. import {AnnotatedText} from 'sentry/components/events/meta/annotatedText';
  5. import ExternalLink from 'sentry/components/links/externalLink';
  6. import {IconOpen} from 'sentry/icons';
  7. import {t} from 'sentry/locale';
  8. import {isUrl} from 'sentry/utils';
  9. import Toggle from './toggle';
  10. import {analyzeStringForRepr, naturalCaseInsensitiveSort} from './utils';
  11. type Props = React.HTMLAttributes<HTMLPreElement> & {
  12. data?: React.ReactNode;
  13. jsonConsts?: boolean;
  14. maxDefaultDepth?: number;
  15. meta?: Record<any, any>;
  16. preserveQuotes?: boolean;
  17. withAnnotatedText?: boolean;
  18. };
  19. function LinkHint({value}: {value: string}) {
  20. if (!isUrl(value)) {
  21. return null;
  22. }
  23. return (
  24. <ExternalLink href={value} className="external-icon">
  25. <StyledIconOpen size="xs" aria-label={t('Open link')} />
  26. </ExternalLink>
  27. );
  28. }
  29. function walk({
  30. depth,
  31. value = null,
  32. maxDefaultDepth: maxDepth = 2,
  33. preserveQuotes,
  34. withAnnotatedText,
  35. jsonConsts,
  36. meta,
  37. }: {
  38. depth: number;
  39. value?: React.ReactNode;
  40. } & Pick<
  41. Props,
  42. 'withAnnotatedText' | 'preserveQuotes' | 'jsonConsts' | 'meta' | 'maxDefaultDepth'
  43. >) {
  44. let i = 0;
  45. const children: React.ReactNode[] = [];
  46. if (value === null) {
  47. return (
  48. <ValueNull>
  49. <AnnotatedText value={jsonConsts ? 'null' : 'None'} meta={meta?.[''] ?? meta} />
  50. </ValueNull>
  51. );
  52. }
  53. if (value === true || value === false) {
  54. return (
  55. <ValueBoolean>
  56. <AnnotatedText
  57. value={jsonConsts ? (value ? 'true' : 'false') : value ? 'True' : 'False'}
  58. meta={meta?.[''] ?? meta}
  59. />
  60. </ValueBoolean>
  61. );
  62. }
  63. if (typeof value === 'string') {
  64. const valueInfo = analyzeStringForRepr(value);
  65. const annotatedValue = withAnnotatedText ? (
  66. <AnnotatedText value={valueInfo.repr} meta={meta?.[''] ?? meta} />
  67. ) : (
  68. valueInfo.repr
  69. );
  70. const printedValue = preserveQuotes ? `"${annotatedValue}"` : annotatedValue;
  71. if (valueInfo.isStripped) {
  72. return <ValueStrippedString>{printedValue}</ValueStrippedString>;
  73. }
  74. if (valueInfo.isMultiLine) {
  75. return (
  76. <ValueMultiLineString>
  77. {printedValue}
  78. <LinkHint value={value} />
  79. </ValueMultiLineString>
  80. );
  81. }
  82. return (
  83. <span>
  84. {printedValue}
  85. <LinkHint value={value} />
  86. </span>
  87. );
  88. }
  89. if (isNumber(value)) {
  90. const valueToBeReturned =
  91. withAnnotatedText && meta ? (
  92. <AnnotatedText value={value} meta={meta?.[''] ?? meta} />
  93. ) : (
  94. value
  95. );
  96. return <span>{valueToBeReturned}</span>;
  97. }
  98. if (Array.isArray(value)) {
  99. for (i = 0; i < value.length; i++) {
  100. children.push(
  101. <div key={i}>
  102. {walk({
  103. value: value[i],
  104. depth: depth + 1,
  105. preserveQuotes,
  106. withAnnotatedText,
  107. jsonConsts,
  108. meta: meta?.[i]?.[''] ?? meta?.[i] ?? meta?.[''] ?? meta,
  109. })}
  110. {i < value.length - 1 ? <span>{', '}</span> : null}
  111. </div>
  112. );
  113. }
  114. return (
  115. <span>
  116. <span>{'['}</span>
  117. <Toggle highUp={depth <= maxDepth}>{children}</Toggle>
  118. <span>{']'}</span>
  119. </span>
  120. );
  121. }
  122. if (isValidElement(value)) {
  123. return value;
  124. }
  125. const keys = Object.keys(value);
  126. keys.sort(naturalCaseInsensitiveSort);
  127. for (i = 0; i < keys.length; i++) {
  128. const key = keys[i];
  129. children.push(
  130. <div key={key}>
  131. <ValueObjectKey>{preserveQuotes ? `"${key}"` : key}</ValueObjectKey>
  132. <span>{': '}</span>
  133. <span>
  134. {walk({
  135. value: value[key],
  136. depth: depth + 1,
  137. preserveQuotes,
  138. withAnnotatedText,
  139. jsonConsts,
  140. meta: meta?.[key]?.[''] ?? meta?.[key] ?? meta?.[''] ?? meta,
  141. })}
  142. {i < keys.length - 1 ? <span>{', '}</span> : null}
  143. </span>
  144. </div>
  145. );
  146. }
  147. return (
  148. <span>
  149. <span>{'{'}</span>
  150. <Toggle highUp={depth <= maxDepth - 1}>{children}</Toggle>
  151. <span>{'}'}</span>
  152. </span>
  153. );
  154. }
  155. function ContextData({
  156. children,
  157. meta,
  158. jsonConsts,
  159. maxDefaultDepth,
  160. data = null,
  161. preserveQuotes = false,
  162. withAnnotatedText = false,
  163. ...props
  164. }: Props) {
  165. return (
  166. <pre {...props}>
  167. {walk({
  168. value: data,
  169. depth: 0,
  170. maxDefaultDepth,
  171. meta,
  172. jsonConsts,
  173. withAnnotatedText,
  174. preserveQuotes,
  175. })}
  176. {children}
  177. </pre>
  178. );
  179. }
  180. export default ContextData;
  181. const StyledIconOpen = styled(IconOpen)`
  182. position: relative;
  183. top: 1px;
  184. `;
  185. const ValueNull = styled('span')`
  186. font-weight: bold;
  187. color: var(--prism-property);
  188. `;
  189. const ValueBoolean = styled('span')`
  190. font-weight: bold;
  191. color: var(--prism-property);
  192. `;
  193. const ValueMultiLineString = styled('span')`
  194. color: var(--prism-selector);
  195. display: block;
  196. overflow: auto;
  197. border-radius: 4px;
  198. padding: 2px 4px;
  199. `;
  200. const ValueStrippedString = styled('span')`
  201. font-weight: bold;
  202. color: var(--prism-keyword);
  203. `;
  204. const ValueObjectKey = styled('span')`
  205. color: var(--prism-keyword);
  206. `;