content.tsx 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. import {useCallback, useState} from 'react';
  2. import styled from '@emotion/styled';
  3. import * as Sentry from '@sentry/react';
  4. import type {Location} from 'history';
  5. import Feature from 'sentry/components/acl/feature';
  6. import {Alert} from 'sentry/components/alert';
  7. import {Button} from 'sentry/components/button';
  8. import ButtonBar from 'sentry/components/buttonBar';
  9. import FeedbackWidgetButton from 'sentry/components/feedback/widget/feedbackWidgetButton';
  10. import * as Layout from 'sentry/components/layouts/thirds';
  11. import {DatePageFilter} from 'sentry/components/organizations/datePageFilter';
  12. import {EnvironmentPageFilter} from 'sentry/components/organizations/environmentPageFilter';
  13. import PageFilterBar from 'sentry/components/organizations/pageFilterBar';
  14. import PageFiltersContainer from 'sentry/components/organizations/pageFilters/container';
  15. import {ProjectPageFilter} from 'sentry/components/organizations/projectPageFilter';
  16. import {PageHeadingQuestionTooltip} from 'sentry/components/pageHeadingQuestionTooltip';
  17. import {
  18. EAPSpanSearchQueryBuilder,
  19. SpanSearchQueryBuilder,
  20. } from 'sentry/components/performance/spanSearchQueryBuilder';
  21. import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle';
  22. import {t} from 'sentry/locale';
  23. import {space} from 'sentry/styles/space';
  24. import type {Confidence} from 'sentry/types/organization';
  25. import {DiscoverDatasets} from 'sentry/utils/discover/types';
  26. import {
  27. type AggregationKey,
  28. ALLOWED_EXPLORE_VISUALIZE_AGGREGATES,
  29. } from 'sentry/utils/fields';
  30. import {useLocation} from 'sentry/utils/useLocation';
  31. import {useNavigate} from 'sentry/utils/useNavigate';
  32. import useOrganization from 'sentry/utils/useOrganization';
  33. import usePageFilters from 'sentry/utils/usePageFilters';
  34. import {ExploreCharts} from 'sentry/views/explore/charts';
  35. import {
  36. PageParamsProvider,
  37. useExploreDataset,
  38. useExploreMode,
  39. useExploreQuery,
  40. useSetExploreQuery,
  41. } from 'sentry/views/explore/contexts/pageParamsContext';
  42. import {Mode} from 'sentry/views/explore/contexts/pageParamsContext/mode';
  43. import {
  44. SpanTagsProvider,
  45. useSpanTags,
  46. } from 'sentry/views/explore/contexts/spanTagsContext';
  47. import {ExploreTables} from 'sentry/views/explore/tables';
  48. import {ExploreToolbar} from 'sentry/views/explore/toolbar';
  49. interface ExploreContentProps {
  50. location: Location;
  51. }
  52. function ExploreContentImpl({}: ExploreContentProps) {
  53. const location = useLocation();
  54. const navigate = useNavigate();
  55. const organization = useOrganization();
  56. const {selection} = usePageFilters();
  57. const dataset = useExploreDataset();
  58. const mode = useExploreMode();
  59. const numberTags = useSpanTags('number');
  60. const stringTags = useSpanTags('string');
  61. const query = useExploreQuery();
  62. const setQuery = useSetExploreQuery();
  63. const toolbarExtras = organization.features.includes('visibility-explore-dataset')
  64. ? ['dataset toggle' as const]
  65. : [];
  66. const switchToOldTraceExplorer = useCallback(() => {
  67. navigate({
  68. ...location,
  69. query: {
  70. ...location.query,
  71. view: 'trace',
  72. },
  73. });
  74. }, [location, navigate]);
  75. const [confidence, setConfidence] = useState<Confidence>(null);
  76. const [chartError, setChartError] = useState<string>('');
  77. const [tableError, setTableError] = useState<string>('');
  78. const maxPickableDays = 7;
  79. return (
  80. <SentryDocumentTitle title={t('Traces')} orgSlug={organization.slug}>
  81. <PageFiltersContainer maxPickableDays={maxPickableDays}>
  82. <Layout.Page>
  83. <Layout.Header>
  84. <Layout.HeaderContent>
  85. <Layout.Title>
  86. {t('Traces')}
  87. <PageHeadingQuestionTooltip
  88. docsUrl="https://github.com/getsentry/sentry/discussions/81239"
  89. title={t(
  90. 'Find problematic spans/traces or compute real-time metrics via aggregation.'
  91. )}
  92. linkLabel={t('Read the Discussion')}
  93. />
  94. </Layout.Title>
  95. </Layout.HeaderContent>
  96. <Layout.HeaderActions>
  97. <ButtonBar gap={1}>
  98. <Feature organization={organization} features="visibility-explore-admin">
  99. <Button onClick={switchToOldTraceExplorer} size="sm">
  100. {t('Switch to Old Trace Explore')}
  101. </Button>
  102. </Feature>
  103. <FeedbackWidgetButton />
  104. </ButtonBar>
  105. </Layout.HeaderActions>
  106. </Layout.Header>
  107. <Body>
  108. <TopSection>
  109. <StyledPageFilterBar condensed>
  110. <ProjectPageFilter />
  111. <EnvironmentPageFilter />
  112. <DatePageFilter
  113. defaultPeriod="7d"
  114. maxPickableDays={maxPickableDays}
  115. relativeOptions={({arbitraryOptions}) => ({
  116. ...arbitraryOptions,
  117. '1h': t('Last 1 hour'),
  118. '24h': t('Last 24 hours'),
  119. '7d': t('Last 7 days'),
  120. })}
  121. />
  122. </StyledPageFilterBar>
  123. {dataset === DiscoverDatasets.SPANS_INDEXED ? (
  124. <SpanSearchQueryBuilder
  125. projects={selection.projects}
  126. initialQuery={query}
  127. onSearch={setQuery}
  128. searchSource="explore"
  129. />
  130. ) : (
  131. <EAPSpanSearchQueryBuilder
  132. projects={selection.projects}
  133. initialQuery={query}
  134. onSearch={setQuery}
  135. searchSource="explore"
  136. getFilterTokenWarning={
  137. mode === Mode.SAMPLES
  138. ? key => {
  139. if (
  140. ALLOWED_EXPLORE_VISUALIZE_AGGREGATES.includes(
  141. key as AggregationKey
  142. )
  143. ) {
  144. return t(
  145. "This key won't affect the results because samples mode does not support aggregate functions"
  146. );
  147. }
  148. return undefined;
  149. }
  150. : undefined
  151. }
  152. supportedAggregates={ALLOWED_EXPLORE_VISUALIZE_AGGREGATES}
  153. numberTags={numberTags}
  154. stringTags={stringTags}
  155. />
  156. )}
  157. </TopSection>
  158. <ExploreToolbar extras={toolbarExtras} />
  159. <MainSection fullWidth>
  160. {(tableError || chartError) && (
  161. <Alert type="error" showIcon>
  162. {tableError || chartError}
  163. </Alert>
  164. )}
  165. <ExploreCharts
  166. query={query}
  167. setConfidence={setConfidence}
  168. setError={setChartError}
  169. />
  170. <ExploreTables confidence={confidence} setError={setTableError} />
  171. </MainSection>
  172. </Body>
  173. </Layout.Page>
  174. </PageFiltersContainer>
  175. </SentryDocumentTitle>
  176. );
  177. }
  178. function ExploreTagsProvider({children}) {
  179. const dataset = useExploreDataset();
  180. return (
  181. <SpanTagsProvider dataset={dataset} enabled>
  182. {children}
  183. </SpanTagsProvider>
  184. );
  185. }
  186. export function ExploreContent(props: ExploreContentProps) {
  187. Sentry.setTag('explore.visited', 'yes');
  188. return (
  189. <PageParamsProvider>
  190. <ExploreTagsProvider>
  191. <ExploreContentImpl {...props} />
  192. </ExploreTagsProvider>
  193. </PageParamsProvider>
  194. );
  195. }
  196. const Body = styled(Layout.Body)`
  197. gap: ${space(2)};
  198. @media (min-width: ${p => p.theme.breakpoints.medium}) {
  199. grid-template-columns: 350px minmax(100px, auto);
  200. gap: ${space(2)};
  201. }
  202. @media (min-width: ${p => p.theme.breakpoints.xxlarge}) {
  203. grid-template-columns: 400px minmax(100px, auto);
  204. }
  205. `;
  206. const TopSection = styled('div')`
  207. display: grid;
  208. gap: ${space(2)};
  209. grid-column: 1/3;
  210. margin-bottom: ${space(2)};
  211. @media (min-width: ${p => p.theme.breakpoints.large}) {
  212. grid-template-columns: minmax(350px, auto) 1fr;
  213. margin-bottom: 0;
  214. }
  215. @media (min-width: ${p => p.theme.breakpoints.xxlarge}) {
  216. grid-template-columns: minmax(400px, auto) 1fr;
  217. }
  218. `;
  219. const MainSection = styled(Layout.Main)`
  220. grid-column: 2/3;
  221. `;
  222. const StyledPageFilterBar = styled(PageFilterBar)`
  223. width: auto;
  224. `;