index.tsx 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. import {useCallback, useMemo} from 'react';
  2. import styled from '@emotion/styled';
  3. import Breadcrumbs from 'sentry/components/breadcrumbs';
  4. import ButtonBar from 'sentry/components/buttonBar';
  5. import DiscoverButton from 'sentry/components/discoverButton';
  6. import {HighlightsIconSummary} from 'sentry/components/events/highlights/highlightsIconSummary';
  7. import FeedbackWidgetButton from 'sentry/components/feedback/widget/feedbackWidgetButton';
  8. import * as Layout from 'sentry/components/layouts/thirds';
  9. import Placeholder from 'sentry/components/placeholder';
  10. import {t} from 'sentry/locale';
  11. import {space} from 'sentry/styles/space';
  12. import type {EventTransaction} from 'sentry/types/event';
  13. import type {Organization} from 'sentry/types/organization';
  14. import {trackAnalytics} from 'sentry/utils/analytics';
  15. import type EventView from 'sentry/utils/discover/eventView';
  16. import {SavedQueryDatasets} from 'sentry/utils/discover/types';
  17. import type {UseApiQueryResult} from 'sentry/utils/queryClient';
  18. import type RequestError from 'sentry/utils/requestError/requestError';
  19. import {useLocation} from 'sentry/utils/useLocation';
  20. import {hasDatasetSelector} from 'sentry/views/dashboards/utils';
  21. import {ProjectsRenderer} from 'sentry/views/explore/tables/tracesTable/fieldRenderers';
  22. import {useModuleURLBuilder} from 'sentry/views/insights/common/utils/useModuleURL';
  23. import {useDomainViewFilters} from 'sentry/views/insights/pages/useFilters';
  24. import {useTraceStateDispatch} from 'sentry/views/performance/newTraceDetails/traceState/traceStateProvider';
  25. import type {TraceMetaQueryResults} from '../traceApi/useTraceMeta';
  26. import TraceConfigurations from '../traceConfigurations';
  27. import type {TraceTree} from '../traceModels/traceTree';
  28. import {useHasTraceNewUi} from '../useHasTraceNewUi';
  29. import {getTraceViewBreadcrumbs} from './breadcrumbs';
  30. import {Meta} from './meta';
  31. import {Title} from './title';
  32. interface TraceMetadataHeaderProps {
  33. metaResults: TraceMetaQueryResults;
  34. organization: Organization;
  35. rootEventResults: UseApiQueryResult<EventTransaction, RequestError>;
  36. traceEventView: EventView;
  37. traceSlug: string;
  38. tree: TraceTree;
  39. }
  40. function PlaceHolder({organization}: {organization: Organization}) {
  41. const {view} = useDomainViewFilters();
  42. const moduleURLBuilder = useModuleURLBuilder(true);
  43. const location = useLocation();
  44. return (
  45. <Layout.Header>
  46. <HeaderContent>
  47. <HeaderRow>
  48. <Breadcrumbs
  49. crumbs={getTraceViewBreadcrumbs(
  50. organization,
  51. location,
  52. moduleURLBuilder,
  53. view
  54. )}
  55. />
  56. </HeaderRow>
  57. <HeaderRow>
  58. <PlaceHolderTitleWrapper>
  59. <StyledPlaceholder _width={300} _height={20} />
  60. <StyledPlaceholder _width={200} _height={18} />
  61. </PlaceHolderTitleWrapper>
  62. <PlaceHolderTitleWrapper>
  63. <StyledPlaceholder _width={300} _height={18} />
  64. <StyledPlaceholder _width={300} _height={24} />
  65. </PlaceHolderTitleWrapper>
  66. </HeaderRow>
  67. <StyledBreak />
  68. <HeaderRow>
  69. <PlaceHolderHighlightWrapper>
  70. <StyledPlaceholder _width={150} _height={20} />
  71. <StyledPlaceholder _width={150} _height={20} />
  72. <StyledPlaceholder _width={150} _height={20} />
  73. </PlaceHolderHighlightWrapper>
  74. <StyledPlaceholder _width={50} _height={28} />
  75. </HeaderRow>
  76. </HeaderContent>
  77. </Layout.Header>
  78. );
  79. }
  80. const PlaceHolderTitleWrapper = styled('div')`
  81. display: flex;
  82. flex-direction: column;
  83. gap: ${space(0.5)};
  84. `;
  85. const PlaceHolderHighlightWrapper = styled('div')`
  86. display: flex;
  87. align-items: center;
  88. gap: ${space(1)};
  89. `;
  90. const StyledPlaceholder = styled(Placeholder)<{_height: number; _width: number}>`
  91. border-radius: ${p => p.theme.borderRadius};
  92. height: ${p => p._height}px;
  93. width: ${p => p._width}px;
  94. `;
  95. function LegacyTraceMetadataHeader(props: TraceMetadataHeaderProps) {
  96. const location = useLocation();
  97. const {view} = useDomainViewFilters();
  98. const moduleURLBuilder = useModuleURLBuilder(true);
  99. const trackOpenInDiscover = useCallback(() => {
  100. trackAnalytics('performance_views.trace_view.open_in_discover', {
  101. organization: props.organization,
  102. });
  103. }, [props.organization]);
  104. return (
  105. <Layout.Header>
  106. <Layout.HeaderContent>
  107. <Breadcrumbs
  108. crumbs={getTraceViewBreadcrumbs(
  109. props.organization,
  110. location,
  111. moduleURLBuilder,
  112. view
  113. )}
  114. />
  115. </Layout.HeaderContent>
  116. <Layout.HeaderActions>
  117. <ButtonBar gap={1}>
  118. <TraceConfigurations rootEventResults={props.rootEventResults} />
  119. <DiscoverButton
  120. size="sm"
  121. to={props.traceEventView.getResultsViewUrlTarget(
  122. props.organization.slug,
  123. false,
  124. hasDatasetSelector(props.organization)
  125. ? SavedQueryDatasets.ERRORS
  126. : undefined
  127. )}
  128. onClick={trackOpenInDiscover}
  129. >
  130. {t('Open in Discover')}
  131. </DiscoverButton>
  132. <FeedbackWidgetButton />
  133. </ButtonBar>
  134. </Layout.HeaderActions>
  135. </Layout.Header>
  136. );
  137. }
  138. export function TraceMetaDataHeader(props: TraceMetadataHeaderProps) {
  139. const location = useLocation();
  140. const hasNewTraceViewUi = useHasTraceNewUi();
  141. const {view} = useDomainViewFilters();
  142. const moduleURLBuilder = useModuleURLBuilder(true);
  143. const dispatch = useTraceStateDispatch();
  144. const onProjectClick = useCallback(
  145. (projectSlug: string) => {
  146. dispatch({type: 'set query', query: `project:${projectSlug}`, source: 'external'});
  147. },
  148. [dispatch]
  149. );
  150. const projectSlugs = useMemo(() => {
  151. return Array.from(props.tree.projects).map(p => p.slug);
  152. }, [props.tree]);
  153. if (!hasNewTraceViewUi) {
  154. return <LegacyTraceMetadataHeader {...props} />;
  155. }
  156. const isLoading =
  157. props.metaResults.status === 'pending' ||
  158. props.rootEventResults.isPending ||
  159. props.tree.type === 'loading';
  160. if (isLoading) {
  161. return <PlaceHolder organization={props.organization} />;
  162. }
  163. return (
  164. <HeaderLayout>
  165. <HeaderContent>
  166. <HeaderRow>
  167. <Breadcrumbs
  168. crumbs={getTraceViewBreadcrumbs(
  169. props.organization,
  170. location,
  171. moduleURLBuilder,
  172. view
  173. )}
  174. />
  175. </HeaderRow>
  176. <HeaderRow>
  177. <Title traceSlug={props.traceSlug} tree={props.tree} />
  178. <Meta
  179. organization={props.organization}
  180. rootEventResults={props.rootEventResults}
  181. tree={props.tree}
  182. meta={props.metaResults.data}
  183. />
  184. </HeaderRow>
  185. <StyledBreak />
  186. {props.rootEventResults.data ? (
  187. <HeaderRow>
  188. <StyledWrapper>
  189. <HighlightsIconSummary event={props.rootEventResults.data} />
  190. </StyledWrapper>
  191. <ProjectsRendererWrapper>
  192. <ProjectsRenderer
  193. disableLink
  194. onProjectClick={onProjectClick}
  195. projectSlugs={projectSlugs}
  196. visibleAvatarSize={24}
  197. maxVisibleProjects={3}
  198. />
  199. </ProjectsRendererWrapper>
  200. </HeaderRow>
  201. ) : null}
  202. </HeaderContent>
  203. </HeaderLayout>
  204. );
  205. }
  206. // We cannot change the cursor of the ProjectBadge component so we need to wrap it in a div
  207. const ProjectsRendererWrapper = styled('div')`
  208. img {
  209. cursor: pointer;
  210. }
  211. `;
  212. const HeaderLayout = styled(Layout.Header)`
  213. padding: ${space(2)} ${space(2)} 0 !important;
  214. `;
  215. const HeaderRow = styled('div')`
  216. display: flex;
  217. justify-content: space-between;
  218. &:not(:first-child) {
  219. margin: ${space(1)} 0;
  220. }
  221. @media (max-width: ${p => p.theme.breakpoints.small}) {
  222. flex-direction: column;
  223. }
  224. `;
  225. const HeaderContent = styled('div')`
  226. display: flex;
  227. flex-direction: column;
  228. `;
  229. const StyledBreak = styled('hr')`
  230. margin: 0;
  231. border-color: ${p => p.theme.border};
  232. `;
  233. const StyledWrapper = styled('span')`
  234. display: flex;
  235. align-items: center;
  236. & > div {
  237. padding: 0;
  238. }
  239. `;