trace.tsx 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. import {Fragment, useMemo} from 'react';
  2. import styled from '@emotion/styled';
  3. import type {Location} from 'history';
  4. import {SectionHeading} from 'sentry/components/charts/styles';
  5. import EventVitals from 'sentry/components/events/eventVitals';
  6. import Panel from 'sentry/components/panels/panel';
  7. import Placeholder from 'sentry/components/placeholder';
  8. import {t} from 'sentry/locale';
  9. import {space} from 'sentry/styles/space';
  10. import type {EventTransaction, Organization} from 'sentry/types';
  11. import {generateQueryWithTag} from 'sentry/utils';
  12. import type EventView from 'sentry/utils/discover/eventView';
  13. import {formatTagKey} from 'sentry/utils/discover/fields';
  14. import type {
  15. TraceFullDetailed,
  16. TraceSplitResults,
  17. } from 'sentry/utils/performance/quickTrace/types';
  18. import {WEB_VITAL_DETAILS} from 'sentry/utils/performance/vitals/constants';
  19. import type {UseApiQueryResult} from 'sentry/utils/queryClient';
  20. import type RequestError from 'sentry/utils/requestError/requestError';
  21. import Tags from 'sentry/views/discover/tags';
  22. import {isTraceNode} from '../../guards';
  23. import type {TraceTree, TraceTreeNode} from '../../traceTree';
  24. import {IssueList} from '../details/issues/issues';
  25. const WEB_VITALS = [
  26. WEB_VITAL_DETAILS['measurements.cls'],
  27. WEB_VITAL_DETAILS['measurements.lcp'],
  28. WEB_VITAL_DETAILS['measurements.ttfb'],
  29. WEB_VITAL_DETAILS['measurements.fcp'],
  30. WEB_VITAL_DETAILS['measurements.fid'],
  31. ];
  32. type TraceFooterProps = {
  33. location: Location;
  34. node: TraceTreeNode<TraceTree.NodeValue>;
  35. organization: Organization;
  36. rootEventResults: UseApiQueryResult<EventTransaction, RequestError>;
  37. traceEventView: EventView;
  38. traces: TraceSplitResults<TraceFullDetailed> | null;
  39. tree: TraceTree;
  40. };
  41. function NoWebVitals() {
  42. return (
  43. <div style={{flex: 1}}>
  44. <SectionHeading>{t('WebVitals')}</SectionHeading>
  45. <WebVitalsWrapper>
  46. {WEB_VITALS.map(detail => (
  47. <StyledPanel key={detail.name}>
  48. <div>{detail.name}</div>
  49. <div>{' \u2014 '}</div>
  50. </StyledPanel>
  51. ))}
  52. </WebVitalsWrapper>
  53. </div>
  54. );
  55. }
  56. function TraceDataLoading() {
  57. return (
  58. <WebVitalsAndTags>
  59. <div style={{flex: 1}}>
  60. <SectionHeading>{t('WebVitals')}</SectionHeading>
  61. <Fragment>
  62. <StyledPlaceholderVital key="title-1" />
  63. <StyledPlaceholderVital key="title-2" />
  64. <StyledPlaceholderVital key="title-3" />
  65. <StyledPlaceholderVital key="title-4" />
  66. <StyledPlaceholderVital key="title-5" />
  67. </Fragment>
  68. </div>
  69. <div style={{flex: 1}}>
  70. <SectionHeading>{t('Tag Summary')}</SectionHeading>
  71. <Fragment>
  72. <StyledPlaceholderTagTitle key="title-1" />
  73. <StyledPlaceholderTag key="bar-1" />
  74. <StyledPlaceholderTagTitle key="title-2" />
  75. <StyledPlaceholderTag key="bar-2" />
  76. <StyledPlaceholderTagTitle key="title-3" />
  77. <StyledPlaceholderTag key="bar-3" />
  78. </Fragment>
  79. </div>
  80. </WebVitalsAndTags>
  81. );
  82. }
  83. export function TraceLevelDetails(props: TraceFooterProps) {
  84. const issues = useMemo(() => {
  85. return [...props.node.errors, ...props.node.performance_issues];
  86. }, [props.node.errors, props.node.performance_issues]);
  87. if (!(props.node && isTraceNode(props.node))) {
  88. throw new Error('Expected a trace node');
  89. }
  90. if (!props.traces) {
  91. return <TraceDataLoading />;
  92. }
  93. const {data: rootEvent} = props.rootEventResults;
  94. const webVitals = Object.keys(rootEvent?.measurements ?? {})
  95. .filter(name => Boolean(WEB_VITAL_DETAILS[`measurements.${name}`]))
  96. .sort();
  97. return (
  98. <Wrapper>
  99. <IssueList issues={issues} node={props.node} organization={props.organization} />
  100. {rootEvent ? (
  101. <WebVitalsAndTags>
  102. {webVitals.length > 0 ? (
  103. <div style={{flex: 1}}>
  104. <EventVitals event={rootEvent} />
  105. </div>
  106. ) : (
  107. <NoWebVitals />
  108. )}
  109. <div style={{flex: 1}}>
  110. <Tags
  111. generateUrl={(key: string, value: string) => {
  112. const url = props.traceEventView.getResultsViewUrlTarget(
  113. props.organization.slug,
  114. false
  115. );
  116. url.query = generateQueryWithTag(url.query, {
  117. key: formatTagKey(key),
  118. value,
  119. });
  120. return url;
  121. }}
  122. totalValues={props.tree.eventsCount}
  123. eventView={props.traceEventView}
  124. organization={props.organization}
  125. location={props.location}
  126. />
  127. </div>
  128. </WebVitalsAndTags>
  129. ) : null}
  130. </Wrapper>
  131. );
  132. }
  133. const Wrapper = styled('div')`
  134. display: flex;
  135. flex-direction: column;
  136. gap: ${space(2)};
  137. `;
  138. const WebVitalsAndTags = styled('div')`
  139. display: flex;
  140. gap: ${space(2)};
  141. `;
  142. const StyledPlaceholderTag = styled(Placeholder)`
  143. border-radius: ${p => p.theme.borderRadius};
  144. height: 16px;
  145. margin-bottom: ${space(1.5)};
  146. `;
  147. const StyledPlaceholderTagTitle = styled(Placeholder)`
  148. width: 100px;
  149. height: 12px;
  150. margin-bottom: ${space(0.5)};
  151. `;
  152. const StyledPlaceholderVital = styled(StyledPlaceholderTagTitle)`
  153. width: 100%;
  154. height: 50px;
  155. margin-bottom: ${space(0.5)};
  156. `;
  157. const StyledPanel = styled(Panel)`
  158. padding: ${space(1)} ${space(1.5)};
  159. margin-bottom: ${space(1)};
  160. width: 100%;
  161. `;
  162. const WebVitalsWrapper = styled('div')`
  163. display: flex;
  164. align-items: center;
  165. flex-direction: column;
  166. `;