eventContext.tsx 5.4 KB

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