eventEntries.tsx 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. import {Fragment} from 'react';
  2. import styled from '@emotion/styled';
  3. import {CommitRow} from 'sentry/components/commitRow';
  4. import {EventEvidence} from 'sentry/components/events/eventEvidence';
  5. import EventReplay from 'sentry/components/events/eventReplay';
  6. import {ActionableItems} from 'sentry/components/events/interfaces/crashContent/exception/actionableItems';
  7. import {actionableItemsEnabled} from 'sentry/components/events/interfaces/crashContent/exception/useActionableItems';
  8. import {t} from 'sentry/locale';
  9. import {space} from 'sentry/styles/space';
  10. import type {
  11. Entry,
  12. Event,
  13. Group,
  14. Organization,
  15. Project,
  16. SharedViewOrganization,
  17. } from 'sentry/types';
  18. import {EntryType, EventOrGroupType} from 'sentry/types';
  19. import {isNotSharedOrganization} from 'sentry/types/utils';
  20. import {objectIsEmpty} from 'sentry/utils';
  21. import {CustomMetricsEventData} from 'sentry/views/ddm/customMetricsEventData';
  22. import {EventContexts} from './contexts';
  23. import {EventDevice} from './device';
  24. import {EventAttachments} from './eventAttachments';
  25. import {EventDataSection} from './eventDataSection';
  26. import {EventEntry} from './eventEntry';
  27. import {EventExtraData} from './eventExtraData';
  28. import {EventSdk} from './eventSdk';
  29. import {EventTagsAndScreenshot} from './eventTagsAndScreenshot';
  30. import {EventViewHierarchy} from './eventViewHierarchy';
  31. import {EventGroupingInfo} from './groupingInfo';
  32. import {EventPackageData} from './packageData';
  33. import {EventRRWebIntegration} from './rrwebIntegration';
  34. import {DataSection} from './styles';
  35. import {SuspectCommits} from './suspectCommits';
  36. import {EventUserFeedback} from './userFeedback';
  37. type Props = {
  38. /**
  39. * The organization can be the shared view on a public issue view.
  40. */
  41. organization: Organization | SharedViewOrganization;
  42. project: Project;
  43. className?: string;
  44. event?: Event;
  45. group?: Group;
  46. isShare?: boolean;
  47. showTagSummary?: boolean;
  48. };
  49. function EventEntries({
  50. organization,
  51. project,
  52. event,
  53. group,
  54. className,
  55. isShare = false,
  56. showTagSummary = true,
  57. }: Props) {
  58. const orgSlug = organization.slug;
  59. const projectSlug = project.slug;
  60. const orgFeatures = organization?.features ?? [];
  61. if (!event) {
  62. return (
  63. <LatestEventNotAvailable>
  64. <h3>{t('Latest Event Not Available')}</h3>
  65. </LatestEventNotAvailable>
  66. );
  67. }
  68. const hasContext = !objectIsEmpty(event.user ?? {}) || !objectIsEmpty(event.contexts);
  69. const hasActionableItems = actionableItemsEnabled({
  70. eventId: event.id,
  71. organization,
  72. projectSlug,
  73. });
  74. return (
  75. <div className={className}>
  76. {hasActionableItems && (
  77. <ActionableItems event={event} project={project} isShare={isShare} />
  78. )}
  79. {!isShare && isNotSharedOrganization(organization) && (
  80. <SuspectCommits
  81. project={project}
  82. eventId={event.id}
  83. group={group}
  84. commitRow={CommitRow}
  85. />
  86. )}
  87. {event.userReport && group && (
  88. <EventDataSection title="User Feedback" type="user-feedback">
  89. <EventUserFeedback
  90. report={event.userReport}
  91. orgSlug={orgSlug}
  92. issueId={group.id}
  93. />
  94. </EventDataSection>
  95. )}
  96. {showTagSummary && (
  97. <EventTagsAndScreenshot
  98. event={event}
  99. projectSlug={projectSlug}
  100. isShare={isShare}
  101. />
  102. )}
  103. <EventEvidence event={event} project={project} />
  104. <Entries
  105. definedEvent={event}
  106. projectSlug={projectSlug}
  107. group={group}
  108. organization={organization}
  109. isShare={isShare}
  110. />
  111. {hasContext && <EventContexts group={group} event={event} />}
  112. <EventExtraData event={event} />
  113. <EventPackageData event={event} />
  114. <EventDevice event={event} />
  115. {!isShare && <EventViewHierarchy event={event} project={project} />}
  116. {!isShare && <EventAttachments event={event} projectSlug={projectSlug} />}
  117. <EventSdk sdk={event.sdk} meta={event._meta?.sdk} />
  118. {event.type === EventOrGroupType.TRANSACTION && event._metrics_summary && (
  119. <CustomMetricsEventData
  120. metricsSummary={event._metrics_summary}
  121. startTimestamp={event.startTimestamp}
  122. />
  123. )}
  124. {!isShare && event.groupID && (
  125. <EventGroupingInfo
  126. projectSlug={projectSlug}
  127. event={event}
  128. showGroupingConfig={
  129. orgFeatures.includes('set-grouping-config') && 'groupingConfig' in event
  130. }
  131. group={group}
  132. />
  133. )}
  134. {!isShare && (
  135. <EventRRWebIntegration event={event} orgId={orgSlug} projectSlug={projectSlug} />
  136. )}
  137. </div>
  138. );
  139. }
  140. // The ordering for event entries is owned by the interface serializers on the backend.
  141. // Because replays are not an interface, we need to manually insert the replay section
  142. // into the array of entries. The long-term solution here is to move the ordering
  143. // logic to this component, similar to how GroupEventDetailsContent works.
  144. function partitionEntriesForReplay(entries: Entry[]) {
  145. let replayIndex = 0;
  146. for (const [i, entry] of entries.entries()) {
  147. if (
  148. [
  149. // The following entry types should be placed before the replay
  150. // This is similar to the ordering in GroupEventDetailsContent
  151. EntryType.MESSAGE,
  152. EntryType.STACKTRACE,
  153. EntryType.EXCEPTION,
  154. EntryType.THREADS,
  155. EntryType.SPANS,
  156. ].includes(entry.type)
  157. ) {
  158. replayIndex = i + 1;
  159. }
  160. }
  161. return [entries.slice(0, replayIndex), entries.slice(replayIndex)];
  162. }
  163. export function Entries({
  164. definedEvent,
  165. projectSlug,
  166. isShare,
  167. group,
  168. organization,
  169. hideBeforeReplayEntries = false,
  170. hideBreadCrumbs = false,
  171. }: {
  172. definedEvent: Event;
  173. projectSlug: string;
  174. hideBeforeReplayEntries?: boolean;
  175. hideBreadCrumbs?: boolean;
  176. isShare?: boolean;
  177. } & Pick<Props, 'group' | 'organization'>) {
  178. if (!Array.isArray(definedEvent.entries)) {
  179. return null;
  180. }
  181. const [beforeReplayEntries, afterReplayEntries] = partitionEntriesForReplay(
  182. definedEvent.entries
  183. );
  184. const eventEntryProps = {
  185. projectSlug,
  186. group,
  187. organization,
  188. event: definedEvent,
  189. isShare,
  190. };
  191. return (
  192. <Fragment>
  193. {!hideBeforeReplayEntries &&
  194. beforeReplayEntries.map((entry, entryIdx) => (
  195. <EventEntry key={entryIdx} entry={entry} {...eventEntryProps} />
  196. ))}
  197. {!isShare && <EventReplay {...eventEntryProps} />}
  198. {afterReplayEntries.map((entry, entryIdx) => {
  199. if (hideBreadCrumbs && entry.type === EntryType.BREADCRUMBS) {
  200. return null;
  201. }
  202. return <EventEntry key={entryIdx} entry={entry} {...eventEntryProps} />;
  203. })}
  204. </Fragment>
  205. );
  206. }
  207. const LatestEventNotAvailable = styled('div')`
  208. padding: ${space(2)} ${space(4)};
  209. `;
  210. const BorderlessEventEntries = styled(EventEntries)`
  211. & ${DataSection} {
  212. margin-left: 0 !important;
  213. margin-right: 0 !important;
  214. padding: ${space(3)} 0 0 0;
  215. }
  216. & ${DataSection}:first-child {
  217. padding-top: 0;
  218. border-top: 0;
  219. }
  220. `;
  221. export {EventEntries, BorderlessEventEntries};