eventsTableRow.tsx 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. import AttachmentUrl from 'sentry/components/attachmentUrl';
  2. import UserAvatar from 'sentry/components/avatar/userAvatar';
  3. import Button from 'sentry/components/button';
  4. import DateTime from 'sentry/components/dateTime';
  5. import {DeviceName} from 'sentry/components/deviceName';
  6. import FileSize from 'sentry/components/fileSize';
  7. import GlobalSelectionLink from 'sentry/components/globalSelectionLink';
  8. import {IconPlay} from 'sentry/icons';
  9. import {t} from 'sentry/locale';
  10. import {AvatarUser, Organization, Tag} from 'sentry/types';
  11. import {Event} from 'sentry/types/event';
  12. import getRouteStringFromRoutes from 'sentry/utils/getRouteStringFromRoutes';
  13. import {useRoutes} from 'sentry/utils/useRoutes';
  14. import withOrganization from 'sentry/utils/withOrganization';
  15. type Props = {
  16. event: Event;
  17. groupId: string;
  18. orgId: string;
  19. organization: Organization;
  20. projectId: string;
  21. tagList: Tag[];
  22. className?: string;
  23. hasUser?: boolean;
  24. };
  25. function EventsTableRow({
  26. className,
  27. event,
  28. projectId,
  29. orgId,
  30. groupId,
  31. tagList,
  32. hasUser,
  33. organization,
  34. }: Props) {
  35. const routes = useRoutes();
  36. const crashFileLink = !event.crashFile ? null : (
  37. <AttachmentUrl projectId={projectId} eventId={event.id} attachment={event.crashFile}>
  38. {url =>
  39. url ? (
  40. <small>
  41. {event.crashFile?.type === 'event.minidump' ? 'Minidump' : 'Crash file'}:{' '}
  42. <a href={`${url}?download=1`}>{event.crashFile?.name}</a> (
  43. <FileSize bytes={event.crashFile?.size || 0} />)
  44. </small>
  45. ) : null
  46. }
  47. </AttachmentUrl>
  48. );
  49. const tagMap = Object.fromEntries(event.tags.map(tag => [tag.key, tag.value]));
  50. const hasReplay = Boolean(tagMap.replayId);
  51. const fullReplayUrl = {
  52. pathname: `/organizations/${organization.slug}/replays/${projectId}:${tagMap.replayId}/`,
  53. query: {
  54. referrer: getRouteStringFromRoutes(routes),
  55. event_t: event.dateCreated ? new Date(event.dateCreated).getTime() : undefined,
  56. },
  57. };
  58. return (
  59. <tr key={event.id} className={className}>
  60. <td>
  61. <h5>
  62. <GlobalSelectionLink
  63. to={`/organizations/${orgId}/issues/${groupId}/events/${event.id}/?referrer=events-table`}
  64. >
  65. <DateTime date={event.dateCreated} year seconds timeZone />
  66. </GlobalSelectionLink>
  67. <small>{event.title.substr(0, 100)}</small>
  68. {crashFileLink}
  69. </h5>
  70. </td>
  71. {hasUser && (
  72. <td className="event-user table-user-info">
  73. {event.user ? (
  74. <div>
  75. <UserAvatar
  76. // TODO(ts): Some of the user fields are optional from event,
  77. // this cast can probably be removed in the future
  78. user={event.user as AvatarUser}
  79. size={24}
  80. className="avatar"
  81. gravatar={false}
  82. />
  83. {event.user.email}
  84. </div>
  85. ) : (
  86. <span>—</span>
  87. )}
  88. </td>
  89. )}
  90. {tagList.map(tag => (
  91. <td key={tag.key}>
  92. <div>
  93. {tag.key === 'device' ? (
  94. <DeviceName value={tagMap[tag.key]} />
  95. ) : tag.key === 'replayId' ? (
  96. hasReplay ? (
  97. <Button
  98. to={fullReplayUrl}
  99. size="sm"
  100. icon={<IconPlay size="sm" />}
  101. aria-label={t('View Full Replay')}
  102. />
  103. ) : null
  104. ) : (
  105. tagMap[tag.key]
  106. )}
  107. </div>
  108. </td>
  109. ))}
  110. </tr>
  111. );
  112. }
  113. export default withOrganization(EventsTableRow);