index.tsx 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. import {isValidElement} from 'react';
  2. import styled from '@emotion/styled';
  3. import isArray from 'lodash/isArray';
  4. import isNumber from 'lodash/isNumber';
  5. import isString from 'lodash/isString';
  6. import {AnnotatedText} from 'sentry/components/events/meta/annotatedText';
  7. import ExternalLink from 'sentry/components/links/externalLink';
  8. import {IconOpen} from 'sentry/icons';
  9. import {t} from 'sentry/locale';
  10. import {isUrl} from 'sentry/utils';
  11. import Toggle from './toggle';
  12. import {analyzeStringForRepr, naturalCaseInsensitiveSort} from './utils';
  13. type Props = React.HTMLAttributes<HTMLPreElement> & {
  14. data?: React.ReactNode;
  15. jsonConsts?: boolean;
  16. maxDefaultDepth?: number;
  17. meta?: Record<any, any>;
  18. preserveQuotes?: boolean;
  19. withAnnotatedText?: boolean;
  20. };
  21. function walk({
  22. depth,
  23. value = null,
  24. maxDefaultDepth: maxDepth = 2,
  25. preserveQuotes,
  26. withAnnotatedText,
  27. jsonConsts,
  28. meta,
  29. }: {
  30. depth: number;
  31. value?: React.ReactNode;
  32. } & Pick<
  33. Props,
  34. 'withAnnotatedText' | 'preserveQuotes' | 'jsonConsts' | 'meta' | 'maxDefaultDepth'
  35. >) {
  36. let i = 0;
  37. const children: React.ReactNode[] = [];
  38. if (value === null) {
  39. return (
  40. <span className="val-null">
  41. <AnnotatedText value={jsonConsts ? 'null' : 'None'} meta={meta?.[''] ?? meta} />
  42. </span>
  43. );
  44. }
  45. if (value === true || value === false) {
  46. return (
  47. <span className="val-bool">
  48. <AnnotatedText
  49. value={jsonConsts ? (value ? 'true' : 'false') : value ? 'True' : 'False'}
  50. meta={meta?.[''] ?? meta}
  51. />
  52. </span>
  53. );
  54. }
  55. if (isString(value)) {
  56. const valueInfo = analyzeStringForRepr(value);
  57. const valueToBeReturned = withAnnotatedText ? (
  58. <AnnotatedText value={valueInfo.repr} meta={meta?.[''] ?? meta} />
  59. ) : (
  60. valueInfo.repr
  61. );
  62. const out = [
  63. <span
  64. key="value"
  65. className={
  66. (valueInfo.isString ? 'val-string' : '') +
  67. (valueInfo.isStripped ? ' val-stripped' : '') +
  68. (valueInfo.isMultiLine ? ' val-string-multiline' : '')
  69. }
  70. >
  71. {preserveQuotes ? `"${valueToBeReturned}"` : valueToBeReturned}
  72. </span>,
  73. ];
  74. if (valueInfo.isString && isUrl(value)) {
  75. out.push(
  76. <ExternalLink key="external" href={value} className="external-icon">
  77. <StyledIconOpen size="xs" aria-label={t('Open link')} />
  78. </ExternalLink>
  79. );
  80. }
  81. return out;
  82. }
  83. if (isNumber(value)) {
  84. const valueToBeReturned =
  85. withAnnotatedText && meta ? (
  86. <AnnotatedText value={value} meta={meta?.[''] ?? meta} />
  87. ) : (
  88. value
  89. );
  90. return <span>{valueToBeReturned}</span>;
  91. }
  92. if (isArray(value)) {
  93. for (i = 0; i < value.length; i++) {
  94. children.push(
  95. <span className="val-array-item" key={i}>
  96. {walk({
  97. value: value[i],
  98. depth: depth + 1,
  99. preserveQuotes,
  100. withAnnotatedText,
  101. jsonConsts,
  102. meta: meta?.[i]?.[''] ?? meta?.[i] ?? meta?.[''] ?? meta,
  103. })}
  104. {i < value.length - 1 ? <span className="val-array-sep">{', '}</span> : null}
  105. </span>
  106. );
  107. }
  108. return (
  109. <span className="val-array">
  110. <span className="val-array-marker">{'['}</span>
  111. <Toggle highUp={depth <= maxDepth} wrapClassName="val-array-items">
  112. {children}
  113. </Toggle>
  114. <span className="val-array-marker">{']'}</span>
  115. </span>
  116. );
  117. }
  118. if (isValidElement(value)) {
  119. return value;
  120. }
  121. const keys = Object.keys(value);
  122. keys.sort(naturalCaseInsensitiveSort);
  123. for (i = 0; i < keys.length; i++) {
  124. const key = keys[i];
  125. children.push(
  126. <span className="val-dict-pair" key={key}>
  127. <span className="val-dict-key">
  128. <span className="val-string">{preserveQuotes ? `"${key}"` : key}</span>
  129. </span>
  130. <span className="val-dict-col">{': '}</span>
  131. <span className="val-dict-value">
  132. {walk({
  133. value: value[key],
  134. depth: depth + 1,
  135. preserveQuotes,
  136. withAnnotatedText,
  137. jsonConsts,
  138. meta: meta?.[key]?.[''] ?? meta?.[key] ?? meta?.[''] ?? meta,
  139. })}
  140. {i < keys.length - 1 ? <span className="val-dict-sep">{', '}</span> : null}
  141. </span>
  142. </span>
  143. );
  144. }
  145. return (
  146. <span className="val-dict">
  147. <span className="val-dict-marker">{'{'}</span>
  148. <Toggle highUp={depth <= maxDepth - 1} wrapClassName="val-dict-items">
  149. {children}
  150. </Toggle>
  151. <span className="val-dict-marker">{'}'}</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. `;