index.tsx 5.0 KB

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