consoleLogRow.tsx 4.5 KB

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