spanSamplesPanel.tsx 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. import {useCallback} from 'react';
  2. import styled from '@emotion/styled';
  3. import omit from 'lodash/omit';
  4. import * as qs from 'query-string';
  5. import ProjectAvatar from 'sentry/components/avatar/projectAvatar';
  6. import Link from 'sentry/components/links/link';
  7. import {t} from 'sentry/locale';
  8. import {space} from 'sentry/styles/space';
  9. import {trackAnalytics} from 'sentry/utils/analytics';
  10. import {PageAlert, PageAlertProvider} from 'sentry/utils/performance/contexts/pageAlert';
  11. import {useLocation} from 'sentry/utils/useLocation';
  12. import useOrganization from 'sentry/utils/useOrganization';
  13. import useRouter from 'sentry/utils/useRouter';
  14. import {normalizeUrl} from 'sentry/utils/withDomainRequired';
  15. import {SpanSamplesContainer} from 'sentry/views/performance/mobile/components/spanSamplesPanelContainer';
  16. import useCrossPlatformProject from 'sentry/views/performance/mobile/useCrossPlatformProject';
  17. import DetailPanel from 'sentry/views/starfish/components/detailPanel';
  18. import {useReleaseSelection} from 'sentry/views/starfish/queries/useReleases';
  19. import type {ModuleName} from 'sentry/views/starfish/types';
  20. type Props = {
  21. groupId: string;
  22. moduleName: ModuleName;
  23. transactionName: string;
  24. additionalFilters?: Record<string, string>;
  25. onClose?: () => void;
  26. spanDescription?: string;
  27. spanOp?: string;
  28. transactionMethod?: string;
  29. transactionRoute?: string;
  30. };
  31. const PRIMARY_SPAN_QUERY_KEY = 'primarySpanSearchQuery';
  32. const SECONDARY_SPAN_QUERY_KEY = 'secondarySpanSearchQuery';
  33. export function SpanSamplesPanel({
  34. groupId,
  35. moduleName,
  36. transactionName,
  37. transactionMethod,
  38. spanDescription,
  39. onClose,
  40. transactionRoute = '/performance/summary/',
  41. spanOp,
  42. additionalFilters,
  43. }: Props) {
  44. const router = useRouter();
  45. const {primaryRelease, secondaryRelease} = useReleaseSelection();
  46. // A a transaction name is required to show the panel, but a transaction
  47. // method is not
  48. const detailKey = transactionName
  49. ? [groupId, transactionName, transactionMethod].filter(Boolean).join(':')
  50. : undefined;
  51. const organization = useOrganization();
  52. const {query} = useLocation();
  53. const {project} = useCrossPlatformProject();
  54. const onOpenDetailPanel = useCallback(() => {
  55. if (query.transaction) {
  56. trackAnalytics('performance_views.sample_spans.opened', {
  57. organization,
  58. source: moduleName,
  59. });
  60. }
  61. }, [organization, query.transaction, moduleName]);
  62. const label =
  63. transactionMethod && !transactionName.startsWith(transactionMethod)
  64. ? `${transactionMethod} ${transactionName}`
  65. : transactionName;
  66. const link = normalizeUrl(
  67. `/organizations/${organization.slug}${transactionRoute}?${qs.stringify({
  68. project: query.project,
  69. transaction: transactionName,
  70. })}`
  71. );
  72. function defaultOnClose() {
  73. router.replace({
  74. pathname: router.location.pathname,
  75. query: omit(router.location.query, 'transaction', 'transactionMethod'),
  76. });
  77. }
  78. return (
  79. <PageAlertProvider>
  80. <DetailPanel
  81. detailKey={detailKey}
  82. onClose={() => {
  83. onClose ? onClose() : defaultOnClose();
  84. }}
  85. onOpen={onOpenDetailPanel}
  86. >
  87. <HeaderContainer>
  88. {project && (
  89. <SpanSummaryProjectAvatar
  90. project={project}
  91. direction="left"
  92. size={40}
  93. hasTooltip
  94. tooltip={project.slug}
  95. />
  96. )}
  97. <TitleContainer>
  98. {spanDescription && <SpanDescription>{spanDescription}</SpanDescription>}
  99. <Title>
  100. <Link to={link}>{label}</Link>
  101. </Title>
  102. </TitleContainer>
  103. </HeaderContainer>
  104. <PageAlert />
  105. <ChartsContainer>
  106. <ChartsContainerItem key="release1">
  107. <SpanSamplesContainer
  108. groupId={groupId}
  109. moduleName={moduleName}
  110. transactionName={transactionName}
  111. transactionMethod={transactionMethod}
  112. release={primaryRelease}
  113. sectionTitle={t('Release 1')}
  114. searchQueryKey={PRIMARY_SPAN_QUERY_KEY}
  115. spanOp={spanOp}
  116. additionalFilters={additionalFilters}
  117. />
  118. </ChartsContainerItem>
  119. <ChartsContainerItem key="release2">
  120. <SpanSamplesContainer
  121. groupId={groupId}
  122. moduleName={moduleName}
  123. transactionName={transactionName}
  124. transactionMethod={transactionMethod}
  125. release={secondaryRelease}
  126. sectionTitle={t('Release 2')}
  127. searchQueryKey={SECONDARY_SPAN_QUERY_KEY}
  128. spanOp={spanOp}
  129. additionalFilters={additionalFilters}
  130. />
  131. </ChartsContainerItem>
  132. </ChartsContainer>
  133. </DetailPanel>
  134. </PageAlertProvider>
  135. );
  136. }
  137. const SpanSummaryProjectAvatar = styled(ProjectAvatar)`
  138. padding-right: ${space(1)};
  139. `;
  140. const HeaderContainer = styled('div')`
  141. width: 100%;
  142. padding-bottom: ${space(2)};
  143. padding-top: ${space(1)};
  144. display: grid;
  145. grid-template-rows: auto auto auto;
  146. @media (min-width: ${p => p.theme.breakpoints.small}) {
  147. grid-template-rows: auto;
  148. grid-template-columns: auto 1fr auto;
  149. }
  150. `;
  151. const TitleContainer = styled('div')`
  152. width: 100%;
  153. position: relative;
  154. height: 40px;
  155. `;
  156. const Title = styled('h4')`
  157. position: absolute;
  158. bottom: 0;
  159. margin-bottom: 0;
  160. `;
  161. const SpanDescription = styled('div')`
  162. display: inline-block;
  163. white-space: nowrap;
  164. overflow: hidden;
  165. text-overflow: ellipsis;
  166. max-width: 35vw;
  167. `;
  168. const ChartsContainer = styled('div')`
  169. display: flex;
  170. flex-direction: row;
  171. gap: ${space(2)};
  172. align-items: top;
  173. `;
  174. const ChartsContainerItem = styled('div')`
  175. flex: 1;
  176. `;