eventMetas.tsx 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  1. import * as React from 'react';
  2. import styled from '@emotion/styled';
  3. import {Location} from 'history';
  4. import Clipboard from 'sentry/components/clipboard';
  5. import DateTime from 'sentry/components/dateTime';
  6. import ProjectBadge from 'sentry/components/idBadge/projectBadge';
  7. import TimeSince from 'sentry/components/timeSince';
  8. import Tooltip from 'sentry/components/tooltip';
  9. import {IconCopy} from 'sentry/icons';
  10. import {t} from 'sentry/locale';
  11. import space from 'sentry/styles/space';
  12. import {OrganizationSummary} from 'sentry/types';
  13. import {Event} from 'sentry/types/event';
  14. import {getShortEventId} from 'sentry/utils/events';
  15. import {getDuration} from 'sentry/utils/formatters';
  16. import getDynamicText from 'sentry/utils/getDynamicText';
  17. import {
  18. QuickTraceQueryChildrenProps,
  19. TraceMeta,
  20. } from 'sentry/utils/performance/quickTrace/types';
  21. import {isTransaction} from 'sentry/utils/performance/quickTrace/utils';
  22. import Projects from 'sentry/utils/projects';
  23. import theme from 'sentry/utils/theme';
  24. import QuickTraceMeta from './quickTraceMeta';
  25. import {MetaData} from './styles';
  26. type Props = Pick<
  27. React.ComponentProps<typeof QuickTraceMeta>,
  28. 'errorDest' | 'transactionDest'
  29. > & {
  30. event: Event;
  31. location: Location;
  32. meta: TraceMeta | null;
  33. organization: OrganizationSummary;
  34. projectId: string;
  35. quickTrace: QuickTraceQueryChildrenProps | null;
  36. };
  37. type State = {
  38. isLargeScreen: boolean;
  39. };
  40. /**
  41. * This should match the breakpoint chosen for the `EventDetailHeader` below
  42. */
  43. const BREAKPOINT_MEDIA_QUERY = `(min-width: ${theme.breakpoints[2]})`;
  44. class EventMetas extends React.Component<Props, State> {
  45. state: State = {
  46. isLargeScreen: window.matchMedia?.(BREAKPOINT_MEDIA_QUERY)?.matches,
  47. };
  48. componentDidMount() {
  49. if (this.mq) {
  50. this.mq.addListener(this.handleMediaQueryChange);
  51. }
  52. }
  53. componentWillUnmount() {
  54. if (this.mq) {
  55. this.mq.removeListener(this.handleMediaQueryChange);
  56. }
  57. }
  58. mq = window.matchMedia?.(BREAKPOINT_MEDIA_QUERY);
  59. handleMediaQueryChange = (changed: MediaQueryListEvent) => {
  60. this.setState({
  61. isLargeScreen: changed.matches,
  62. });
  63. };
  64. render() {
  65. const {
  66. event,
  67. organization,
  68. projectId,
  69. location,
  70. quickTrace,
  71. meta,
  72. errorDest,
  73. transactionDest,
  74. } = this.props;
  75. const {isLargeScreen} = this.state;
  76. const type = isTransaction(event) ? 'transaction' : 'event';
  77. const timestamp = (
  78. <TimeSince date={event.dateCreated || (event.endTimestamp || 0) * 1000} />
  79. );
  80. const httpStatus = <HttpStatus event={event} />;
  81. return (
  82. <Projects orgId={organization.slug} slugs={[projectId]}>
  83. {({projects}) => {
  84. const project = projects.find(p => p.slug === projectId);
  85. return (
  86. <EventDetailHeader type={type}>
  87. <MetaData
  88. headingText={t('Event ID')}
  89. tooltipText={t('The unique ID assigned to this %s.', type)}
  90. bodyText={<EventID event={event} />}
  91. subtext={
  92. <ProjectBadge
  93. project={project ? project : {slug: projectId}}
  94. avatarSize={16}
  95. />
  96. }
  97. />
  98. {isTransaction(event) ? (
  99. <MetaData
  100. headingText={t('Event Duration')}
  101. tooltipText={t(
  102. 'The time elapsed between the start and end of this transaction.'
  103. )}
  104. bodyText={getDuration(
  105. event.endTimestamp - event.startTimestamp,
  106. 2,
  107. true
  108. )}
  109. subtext={timestamp}
  110. />
  111. ) : (
  112. <MetaData
  113. headingText={t('Created')}
  114. tooltipText={t('The time at which this event was created.')}
  115. bodyText={timestamp}
  116. subtext={getDynamicText({
  117. value: <DateTime date={event.dateCreated} />,
  118. fixed: 'May 6, 2021 3:27:01 UTC',
  119. })}
  120. />
  121. )}
  122. {isTransaction(event) && (
  123. <MetaData
  124. headingText={t('Status')}
  125. tooltipText={t(
  126. 'The status of this transaction indicating if it succeeded or otherwise.'
  127. )}
  128. bodyText={event.contexts?.trace?.status ?? '\u2014'}
  129. subtext={httpStatus}
  130. />
  131. )}
  132. <QuickTraceContainer>
  133. <QuickTraceMeta
  134. event={event}
  135. project={project}
  136. location={location}
  137. quickTrace={quickTrace}
  138. traceMeta={meta}
  139. anchor={isLargeScreen ? 'right' : 'left'}
  140. errorDest={errorDest}
  141. transactionDest={transactionDest}
  142. />
  143. </QuickTraceContainer>
  144. </EventDetailHeader>
  145. );
  146. }}
  147. </Projects>
  148. );
  149. }
  150. }
  151. const EventDetailHeader = styled('div')<{type?: 'transaction' | 'event'}>`
  152. display: grid;
  153. grid-template-columns: repeat(${p => (p.type === 'transaction' ? 3 : 2)}, 1fr);
  154. grid-template-rows: repeat(2, auto);
  155. gap: ${space(2)};
  156. margin-bottom: ${space(2)};
  157. @media (min-width: ${p => p.theme.breakpoints[1]}) {
  158. margin-bottom: 0;
  159. }
  160. /* This should match the breakpoint chosen for BREAKPOINT_MEDIA_QUERY above. */
  161. @media (min-width: ${p => p.theme.breakpoints[2]}) {
  162. ${p =>
  163. p.type === 'transaction'
  164. ? 'grid-template-columns: minmax(160px, 1fr) minmax(160px, 1fr) minmax(160px, 1fr) 6fr;'
  165. : 'grid-template-columns: minmax(160px, 1fr) minmax(200px, 1fr) 6fr;'};
  166. grid-row-gap: 0;
  167. }
  168. `;
  169. const QuickTraceContainer = styled('div')`
  170. grid-column: 1/4;
  171. @media (min-width: ${p => p.theme.breakpoints[2]}) {
  172. justify-self: flex-end;
  173. min-width: 325px;
  174. grid-column: unset;
  175. }
  176. `;
  177. function EventID({event}: {event: Event}) {
  178. return (
  179. <Clipboard value={event.eventID}>
  180. <EventIDContainer>
  181. <EventIDWrapper>{getShortEventId(event.eventID)}</EventIDWrapper>
  182. <Tooltip title={event.eventID} position="top">
  183. <IconCopy color="subText" />
  184. </Tooltip>
  185. </EventIDContainer>
  186. </Clipboard>
  187. );
  188. }
  189. const EventIDContainer = styled('div')`
  190. display: flex;
  191. align-items: center;
  192. cursor: pointer;
  193. `;
  194. const EventIDWrapper = styled('span')`
  195. margin-right: ${space(1)};
  196. `;
  197. function HttpStatus({event}: {event: Event}) {
  198. const {tags} = event;
  199. const emptyStatus = <React.Fragment>{'\u2014'}</React.Fragment>;
  200. if (!Array.isArray(tags)) {
  201. return emptyStatus;
  202. }
  203. const tag = tags.find(tagObject => tagObject.key === 'http.status_code');
  204. if (!tag) {
  205. return emptyStatus;
  206. }
  207. return <React.Fragment>HTTP {tag.value}</React.Fragment>;
  208. }
  209. export default EventMetas;