consoleLogRow.tsx 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. import type {CSSProperties} from 'react';
  2. import {forwardRef, useCallback} from 'react';
  3. import styled from '@emotion/styled';
  4. import classNames from 'classnames';
  5. import ErrorBoundary from 'sentry/components/errorBoundary';
  6. import {Tooltip} from 'sentry/components/tooltip';
  7. import {IconClose, IconInfo, IconWarning} from 'sentry/icons';
  8. import {space} from 'sentry/styles/space';
  9. import {BreadcrumbLevelType} from 'sentry/types/breadcrumbs';
  10. import type useCrumbHandlers from 'sentry/utils/replays/hooks/useCrumbHandlers';
  11. import type {BreadcrumbFrame, ConsoleFrame} from 'sentry/utils/replays/types';
  12. import MessageFormatter from 'sentry/views/replays/detail/console/messageFormatter';
  13. import TimestampButton from 'sentry/views/replays/detail/timestampButton';
  14. interface Props extends ReturnType<typeof useCrumbHandlers> {
  15. currentHoverTime: number | undefined;
  16. currentTime: number;
  17. frame: BreadcrumbFrame;
  18. index: number;
  19. onDimensionChange: (
  20. index: number,
  21. path: string,
  22. expandedState: Record<string, boolean>
  23. ) => void;
  24. startTimestampMs: number;
  25. style: CSSProperties;
  26. expandPaths?: string[];
  27. }
  28. const ConsoleLogRow = forwardRef<HTMLDivElement, Props>(function ConsoleLogRow(
  29. {
  30. currentHoverTime,
  31. currentTime,
  32. expandPaths,
  33. frame,
  34. onMouseEnter,
  35. onMouseLeave,
  36. index,
  37. onClickTimestamp,
  38. onDimensionChange,
  39. startTimestampMs,
  40. style,
  41. },
  42. ref
  43. ) {
  44. const handleDimensionChange = useCallback(
  45. (path: any, expandedState: any) => onDimensionChange?.(index, path, expandedState),
  46. [onDimensionChange, index]
  47. );
  48. const hasOccurred = currentTime >= frame.offsetMs;
  49. const isBeforeHover =
  50. currentHoverTime === undefined || currentHoverTime >= frame.offsetMs;
  51. return (
  52. <ConsoleLog
  53. ref={ref}
  54. className={classNames({
  55. beforeCurrentTime: hasOccurred,
  56. afterCurrentTime: !hasOccurred,
  57. beforeHoverTime: currentHoverTime !== undefined && isBeforeHover,
  58. afterHoverTime: currentHoverTime !== undefined && !isBeforeHover,
  59. })}
  60. hasOccurred={hasOccurred}
  61. level={(frame as ConsoleFrame).level}
  62. onMouseEnter={() => onMouseEnter(frame)}
  63. onMouseLeave={() => onMouseLeave(frame)}
  64. style={style}
  65. >
  66. <ConsoleLevelIcon level={(frame as ConsoleFrame).level} />
  67. <Message>
  68. <ErrorBoundary mini>
  69. <MessageFormatter
  70. expandPaths={expandPaths}
  71. frame={frame}
  72. onExpand={handleDimensionChange}
  73. />
  74. </ErrorBoundary>
  75. </Message>
  76. <TimestampButton
  77. onClick={event => {
  78. event.stopPropagation();
  79. onClickTimestamp(frame);
  80. }}
  81. startTimestampMs={startTimestampMs}
  82. timestampMs={frame.timestampMs}
  83. />
  84. </ConsoleLog>
  85. );
  86. });
  87. export default ConsoleLogRow;
  88. const ConsoleLog = styled('div')<{
  89. hasOccurred: boolean;
  90. level: undefined | string;
  91. }>`
  92. display: grid;
  93. grid-template-columns: 12px 1fr max-content;
  94. gap: ${space(0.75)};
  95. align-items: baseline;
  96. padding: ${space(0.5)} ${space(1)};
  97. font-size: ${p => p.theme.fontSizeSmall};
  98. background-color: ${p =>
  99. ['warning', 'error'].includes(String(p.level))
  100. ? // @ts-expect-error TS(7053): Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
  101. p.theme.alert[String(p.level)].backgroundLight
  102. : 'inherit'};
  103. color: ${p => p.theme.gray400};
  104. /*
  105. Show the timestamp button "Play" icon when we hover the row.
  106. This is a really generic selector that could find many things, but for now it
  107. only targets the one thing that we expect.
  108. */
  109. &:hover button > svg {
  110. visibility: visible;
  111. }
  112. `;
  113. const ICONS = {
  114. [BreadcrumbLevelType.ERROR]: (
  115. <Tooltip title={BreadcrumbLevelType.ERROR}>
  116. <IconClose size="xs" color="red400" isCircled />
  117. </Tooltip>
  118. ),
  119. [BreadcrumbLevelType.WARNING]: (
  120. <Tooltip title={BreadcrumbLevelType.WARNING}>
  121. <IconWarning color="yellow400" size="xs" />
  122. </Tooltip>
  123. ),
  124. [BreadcrumbLevelType.INFO]: (
  125. <Tooltip title={BreadcrumbLevelType.INFO}>
  126. <IconInfo color="gray400" size="xs" />
  127. </Tooltip>
  128. ),
  129. };
  130. const MediumFontSize = styled('span')`
  131. font-size: ${p => p.theme.fontSizeMedium};
  132. `;
  133. function ConsoleLevelIcon({level}: {level: string | undefined}) {
  134. return level && level in ICONS ? (
  135. // @ts-expect-error TS(7053): Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
  136. <MediumFontSize>{ICONS[level]}</MediumFontSize>
  137. ) : (
  138. <i />
  139. );
  140. }
  141. const Message = styled('div')`
  142. font-family: ${p => p.theme.text.familyMono};
  143. white-space: pre-wrap;
  144. word-break: break-word;
  145. `;