eventContext.tsx 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. import {Fragment, useEffect} from 'react';
  2. import styled from '@emotion/styled';
  3. import type {Location} from 'history';
  4. import {
  5. getStacktrace,
  6. StackTracePreviewContent,
  7. } from 'sentry/components/groupPreviewTooltip/stackTracePreview';
  8. import {t} from 'sentry/locale';
  9. import {space} from 'sentry/styles/space';
  10. import type {Event} from 'sentry/types/event';
  11. import type {Project} from 'sentry/types/project';
  12. import {trackAnalytics} from 'sentry/utils/analytics';
  13. import type EventView from 'sentry/utils/discover/eventView';
  14. import getDuration from 'sentry/utils/duration/getDuration';
  15. import {useApiQuery} from 'sentry/utils/queryClient';
  16. import {
  17. getStatusBodyText,
  18. HttpStatus,
  19. } from 'sentry/views/performance/transactionDetails/eventMetas';
  20. import ActionDropDown, {ContextValueType} from './actionDropdown';
  21. import {NoContext} from './quickContextWrapper';
  22. import {
  23. ContextBody,
  24. ContextContainer,
  25. ContextHeader,
  26. ContextRow,
  27. ContextTitle,
  28. NoContextWrapper,
  29. Wrapper,
  30. } from './styles';
  31. import type {BaseContextProps} from './utils';
  32. import {ContextType, tenSecondInMs} from './utils';
  33. interface EventContextProps extends BaseContextProps {
  34. eventView?: EventView;
  35. location?: Location;
  36. projects?: Project[];
  37. }
  38. function EventContext(props: EventContextProps) {
  39. const {organization, dataRow, eventView, location} = props;
  40. const {isPending, isError, data} = useApiQuery<Event>(
  41. [
  42. `/organizations/${organization.slug}/events/${dataRow['project.name']}:${dataRow.id}/`,
  43. ],
  44. {
  45. staleTime: tenSecondInMs,
  46. }
  47. );
  48. useEffect(() => {
  49. if (data) {
  50. trackAnalytics('discover_v2.quick_context_hover_contexts', {
  51. organization,
  52. contextType: ContextType.EVENT,
  53. eventType: data.type,
  54. });
  55. }
  56. }, [data, organization]);
  57. if (isPending || isError) {
  58. return <NoContext isLoading={isPending} />;
  59. }
  60. if (data.type === 'transaction') {
  61. const transactionDuration = getDuration(
  62. data.endTimestamp - data.startTimestamp,
  63. 2,
  64. true
  65. );
  66. const status = getStatusBodyText(data);
  67. return (
  68. <Wrapper data-test-id="quick-context-hover-body">
  69. <EventContextContainer>
  70. <ContextHeader>
  71. <ContextTitle>{t('Transaction Duration')}</ContextTitle>
  72. {location && eventView && (
  73. <ActionDropDown
  74. dataRow={dataRow}
  75. contextValueType={ContextValueType.DURATION}
  76. location={location}
  77. eventView={eventView}
  78. organization={organization}
  79. queryKey="transaction.duration"
  80. value={transactionDuration}
  81. />
  82. )}
  83. </ContextHeader>
  84. <EventContextBody>{transactionDuration}</EventContextBody>
  85. </EventContextContainer>
  86. {location && (
  87. <EventContextContainer>
  88. <Fragment>
  89. <ContextHeader>
  90. <ContextTitle>{t('Status')}</ContextTitle>
  91. {location && eventView && (
  92. <ActionDropDown
  93. dataRow={dataRow}
  94. contextValueType={ContextValueType.STRING}
  95. location={location}
  96. eventView={eventView}
  97. organization={organization}
  98. queryKey="transaction.status"
  99. value={status}
  100. />
  101. )}
  102. </ContextHeader>
  103. <EventContextBody>
  104. <ContextRow>
  105. {status}
  106. <HttpStatusWrapper>
  107. (<HttpStatus event={data} />)
  108. </HttpStatusWrapper>
  109. </ContextRow>
  110. </EventContextBody>
  111. </Fragment>
  112. </EventContextContainer>
  113. )}
  114. </Wrapper>
  115. );
  116. }
  117. const stackTrace = getStacktrace(data);
  118. return stackTrace ? (
  119. <Fragment>
  120. {!dataRow.title && (
  121. <ErrorTitleContainer>
  122. <ContextHeader>
  123. <ContextTitle>{t('Title')}</ContextTitle>
  124. {location && eventView && (
  125. <ActionDropDown
  126. dataRow={dataRow}
  127. contextValueType={ContextValueType.STRING}
  128. location={location}
  129. eventView={eventView}
  130. organization={organization}
  131. queryKey="title"
  132. value={data.title}
  133. />
  134. )}
  135. </ContextHeader>
  136. <ErrorTitleBody>{data.title}</ErrorTitleBody>
  137. </ErrorTitleContainer>
  138. )}
  139. <StackTraceWrapper>
  140. <StackTracePreviewContent event={data} stacktrace={stackTrace} />
  141. </StackTraceWrapper>
  142. </Fragment>
  143. ) : (
  144. <NoContextWrapper>
  145. {t('There is no stack trace available for this event.')}
  146. </NoContextWrapper>
  147. );
  148. }
  149. const ErrorTitleContainer = styled(ContextContainer)`
  150. padding: ${space(1.5)};
  151. `;
  152. const ErrorTitleBody = styled(ContextBody)`
  153. margin: 0;
  154. max-width: 450px;
  155. ${p => p.theme.overflowEllipsis}
  156. `;
  157. const EventContextBody = styled(ContextBody)`
  158. font-size: ${p => p.theme.fontSizeExtraLarge};
  159. margin: 0;
  160. align-items: flex-start;
  161. flex-direction: column;
  162. `;
  163. const EventContextContainer = styled(ContextContainer)`
  164. & + & {
  165. margin-top: ${space(2)};
  166. }
  167. `;
  168. const StackTraceWrapper = styled('div')`
  169. overflow: hidden;
  170. max-height: 300px;
  171. width: 500px;
  172. overflow-y: auto;
  173. .traceback {
  174. margin-bottom: 0;
  175. border: 0;
  176. }
  177. border-radius: ${p => p.theme.borderRadius};
  178. `;
  179. const HttpStatusWrapper = styled('span')`
  180. margin-left: ${space(0.5)};
  181. `;
  182. export default EventContext;