tableCell.tsx 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. import styled from '@emotion/styled';
  2. import Duration from 'sentry/components/duration';
  3. import ProjectBadge from 'sentry/components/idBadge/projectBadge';
  4. import UserBadge from 'sentry/components/idBadge/userBadge';
  5. import Link from 'sentry/components/links/link';
  6. import {StringWalker} from 'sentry/components/replays/walker/urlWalker';
  7. import ScoreBar from 'sentry/components/scoreBar';
  8. import TimeSince from 'sentry/components/timeSince';
  9. import CHART_PALETTE from 'sentry/constants/chartPalette';
  10. import {IconCalendar} from 'sentry/icons';
  11. import space from 'sentry/styles/space';
  12. import type {Organization} from 'sentry/types';
  13. import EventView from 'sentry/utils/discover/eventView';
  14. import {spanOperationRelativeBreakdownRenderer} from 'sentry/utils/discover/fieldRenderers';
  15. import {useLocation} from 'sentry/utils/useLocation';
  16. import useProjects from 'sentry/utils/useProjects';
  17. import type {ReplayListRecordWithTx} from 'sentry/views/performance/transactionSummary/transactionReplays/useReplaysFromTransaction';
  18. import type {ReplayListRecord} from 'sentry/views/replays/types';
  19. type Props = {
  20. replay: ReplayListRecord | ReplayListRecordWithTx;
  21. };
  22. export function SessionCell({
  23. eventView,
  24. organization,
  25. referrer,
  26. replay,
  27. }: Props & {eventView: EventView; organization: Organization; referrer: string}) {
  28. const {projects} = useProjects();
  29. const project = projects.find(p => p.id === replay.projectId);
  30. return (
  31. <UserBadge
  32. avatarSize={32}
  33. displayName={
  34. <Link
  35. to={{
  36. pathname: `/organizations/${organization.slug}/replays/${project?.slug}:${replay.id}/`,
  37. query: {
  38. referrer,
  39. ...eventView.generateQueryStringObject(),
  40. },
  41. }}
  42. >
  43. {replay.user.displayName || ''}
  44. </Link>
  45. }
  46. user={{
  47. username: replay.user.displayName || '',
  48. email: replay.user.email || '',
  49. id: replay.user.id || '',
  50. ip_address: replay.user.ip_address || '',
  51. name: replay.user.name || '',
  52. }}
  53. // this is the subheading for the avatar, so displayEmail in this case is a misnomer
  54. displayEmail={<StringWalker urls={replay.urls} />}
  55. />
  56. );
  57. }
  58. export function ProjectCell({replay}: Props) {
  59. const {projects} = useProjects();
  60. const project = projects.find(p => p.id === replay.projectId);
  61. return (
  62. <Item>{project ? <ProjectBadge project={project} avatarSize={16} /> : null}</Item>
  63. );
  64. }
  65. export function TransactionCell({
  66. organization,
  67. replay,
  68. }: Props & {organization: Organization}) {
  69. const location = useLocation();
  70. const hasTxEvent = 'txEvent' in replay;
  71. const txDuration = hasTxEvent ? replay.txEvent?.['transaction.duration'] : undefined;
  72. return hasTxEvent ? (
  73. <SpanOperationBreakdown>
  74. {txDuration ? <div>{txDuration}ms</div> : null}
  75. {spanOperationRelativeBreakdownRenderer(
  76. replay.txEvent,
  77. {
  78. organization,
  79. location,
  80. },
  81. {
  82. enableOnClick: false,
  83. }
  84. )}
  85. </SpanOperationBreakdown>
  86. ) : null;
  87. }
  88. export function StartedAtCell({replay}: Props) {
  89. return (
  90. <Item>
  91. <IconCalendar color="gray500" size="sm" />
  92. <TimeSince date={replay.startedAt} />
  93. </Item>
  94. );
  95. }
  96. export function DurationCell({replay}: Props) {
  97. return (
  98. <Item>
  99. <Duration seconds={replay.duration.asSeconds()} exact abbreviation />
  100. </Item>
  101. );
  102. }
  103. export function ErrorCountCell({replay}: Props) {
  104. return <Item data-test-id="replay-table-count-errors">{replay.countErrors || 0}</Item>;
  105. }
  106. export function ActivityCell({replay}: Props) {
  107. const scoreBarPalette = new Array(10).fill([CHART_PALETTE[0][0]]);
  108. return (
  109. <Item>
  110. <ScoreBar
  111. size={20}
  112. score={replay?.activity ?? 1}
  113. palette={scoreBarPalette}
  114. radius={0}
  115. />
  116. </Item>
  117. );
  118. }
  119. const Item = styled('div')`
  120. display: flex;
  121. align-items: center;
  122. gap: ${space(1)};
  123. `;
  124. const SpanOperationBreakdown = styled('div')`
  125. width: 100%;
  126. display: flex;
  127. flex-direction: column;
  128. gap: ${space(0.5)};
  129. color: ${p => p.theme.gray500};
  130. font-size: ${p => p.theme.fontSizeMedium};
  131. text-align: right;
  132. `;