spanSamplesPanel.tsx 5.9 KB

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