spansTab.tsx 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. import {useMemo} from 'react';
  2. import styled from '@emotion/styled';
  3. import * as Sentry from '@sentry/react';
  4. import Alert from 'sentry/components/alert';
  5. import * as Layout from 'sentry/components/layouts/thirds';
  6. import {DatePageFilter} from 'sentry/components/organizations/datePageFilter';
  7. import {EnvironmentPageFilter} from 'sentry/components/organizations/environmentPageFilter';
  8. import PageFilterBar from 'sentry/components/organizations/pageFilterBar';
  9. import {ProjectPageFilter} from 'sentry/components/organizations/projectPageFilter';
  10. import {
  11. EAPSpanSearchQueryBuilder,
  12. SpanSearchQueryBuilder,
  13. } from 'sentry/components/performance/spanSearchQueryBuilder';
  14. import {t} from 'sentry/locale';
  15. import {space} from 'sentry/styles/space';
  16. import {defined} from 'sentry/utils';
  17. import {dedupeArray} from 'sentry/utils/dedupeArray';
  18. import {DiscoverDatasets} from 'sentry/utils/discover/types';
  19. import {
  20. type AggregationKey,
  21. ALLOWED_EXPLORE_VISUALIZE_AGGREGATES,
  22. } from 'sentry/utils/fields';
  23. import useOrganization from 'sentry/utils/useOrganization';
  24. import usePageFilters from 'sentry/utils/usePageFilters';
  25. import {ExploreCharts} from 'sentry/views/explore/charts';
  26. import {
  27. PageParamsProvider,
  28. useExploreDataset,
  29. useExploreMode,
  30. useExploreQuery,
  31. useExploreVisualizes,
  32. useSetExploreQuery,
  33. } from 'sentry/views/explore/contexts/pageParamsContext';
  34. import {Mode} from 'sentry/views/explore/contexts/pageParamsContext/mode';
  35. import {
  36. SpanTagsProvider,
  37. useSpanTags,
  38. } from 'sentry/views/explore/contexts/spanTagsContext';
  39. import {useExploreAggregatesTable} from 'sentry/views/explore/hooks/useExploreAggregatesTable';
  40. import {useExploreSpansTable} from 'sentry/views/explore/hooks/useExploreSpansTable';
  41. import {useExploreTimeseries} from 'sentry/views/explore/hooks/useExploreTimeseries';
  42. import {useExploreTracesTable} from 'sentry/views/explore/hooks/useExploreTracesTable';
  43. import {Tab, useTab} from 'sentry/views/explore/hooks/useTab';
  44. import {ExploreTables} from 'sentry/views/explore/tables';
  45. import {ExploreToolbar} from 'sentry/views/explore/toolbar';
  46. import {
  47. combineConfidenceForSeries,
  48. type DefaultPeriod,
  49. type MaxPickableDays,
  50. } from 'sentry/views/explore/utils';
  51. export type SpanTabProps = {
  52. defaultPeriod: DefaultPeriod;
  53. maxPickableDays: MaxPickableDays;
  54. relativeOptions: Record<string, React.ReactNode>;
  55. };
  56. export function SpansTabContentImpl({
  57. defaultPeriod,
  58. maxPickableDays,
  59. relativeOptions,
  60. }: SpanTabProps) {
  61. const organization = useOrganization();
  62. const {selection} = usePageFilters();
  63. const dataset = useExploreDataset();
  64. const mode = useExploreMode();
  65. const visualizes = useExploreVisualizes();
  66. const [samplesTab, setSamplesTab] = useTab();
  67. const numberTags = useSpanTags('number');
  68. const stringTags = useSpanTags('string');
  69. const query = useExploreQuery();
  70. const setQuery = useSetExploreQuery();
  71. const toolbarExtras = organization?.features?.includes('visibility-explore-dataset')
  72. ? ['dataset toggle' as const]
  73. : [];
  74. const queryType: 'aggregate' | 'samples' | 'traces' =
  75. mode === Mode.AGGREGATE
  76. ? 'aggregate'
  77. : samplesTab === Tab.TRACE
  78. ? 'traces'
  79. : 'samples';
  80. const limit = 25;
  81. const aggregatesTableResult = useExploreAggregatesTable({
  82. query,
  83. limit,
  84. enabled: queryType === 'aggregate',
  85. });
  86. const spansTableResult = useExploreSpansTable({
  87. query,
  88. limit,
  89. enabled: queryType === 'samples',
  90. });
  91. const tracesTableResult = useExploreTracesTable({
  92. query,
  93. enabled: queryType === 'traces',
  94. });
  95. const {timeseriesResult, canUsePreviousResults} = useExploreTimeseries({query});
  96. const confidences = useMemo(
  97. () =>
  98. visualizes.map(visualize => {
  99. const dedupedYAxes = dedupeArray(visualize.yAxes);
  100. const series = dedupedYAxes
  101. .flatMap(yAxis => timeseriesResult.data[yAxis])
  102. .filter(defined);
  103. return combineConfidenceForSeries(series);
  104. }),
  105. [timeseriesResult.data, visualizes]
  106. );
  107. const tableError =
  108. queryType === 'aggregate'
  109. ? aggregatesTableResult.result.error?.message ?? ''
  110. : queryType === 'traces'
  111. ? tracesTableResult.result.error?.message ?? ''
  112. : spansTableResult.result.error?.message ?? '';
  113. const chartError = timeseriesResult.error?.message ?? '';
  114. return (
  115. <Body>
  116. <TopSection>
  117. <StyledPageFilterBar condensed>
  118. <ProjectPageFilter />
  119. <EnvironmentPageFilter />
  120. <DatePageFilter
  121. defaultPeriod={defaultPeriod}
  122. maxPickableDays={maxPickableDays}
  123. relativeOptions={({arbitraryOptions}) => ({
  124. ...arbitraryOptions,
  125. ...relativeOptions,
  126. })}
  127. />
  128. </StyledPageFilterBar>
  129. {dataset === DiscoverDatasets.SPANS_INDEXED ? (
  130. <SpanSearchQueryBuilder
  131. projects={selection.projects}
  132. initialQuery={query}
  133. onSearch={setQuery}
  134. searchSource="explore"
  135. />
  136. ) : (
  137. <EAPSpanSearchQueryBuilder
  138. projects={selection.projects}
  139. initialQuery={query}
  140. onSearch={setQuery}
  141. searchSource="explore"
  142. getFilterTokenWarning={
  143. mode === Mode.SAMPLES
  144. ? key => {
  145. if (
  146. ALLOWED_EXPLORE_VISUALIZE_AGGREGATES.includes(key as AggregationKey)
  147. ) {
  148. return t(
  149. "This key won't affect the results because samples mode does not support aggregate functions"
  150. );
  151. }
  152. return undefined;
  153. }
  154. : undefined
  155. }
  156. supportedAggregates={ALLOWED_EXPLORE_VISUALIZE_AGGREGATES}
  157. numberTags={numberTags}
  158. stringTags={stringTags}
  159. />
  160. )}
  161. </TopSection>
  162. <ExploreToolbar extras={toolbarExtras} />
  163. <MainSection fullWidth>
  164. {(tableError || chartError) && (
  165. <Alert type="error" showIcon>
  166. {tableError || chartError}
  167. </Alert>
  168. )}
  169. <ExploreCharts
  170. canUsePreviousResults={canUsePreviousResults}
  171. confidences={confidences}
  172. query={query}
  173. timeseriesResult={timeseriesResult}
  174. />
  175. <ExploreTables
  176. aggregatesTableResult={aggregatesTableResult}
  177. spansTableResult={spansTableResult}
  178. tracesTableResult={tracesTableResult}
  179. confidences={confidences}
  180. samplesTab={samplesTab}
  181. setSamplesTab={setSamplesTab}
  182. />
  183. </MainSection>
  184. </Body>
  185. );
  186. }
  187. function ExploreTagsProvider({children}: any) {
  188. const dataset = useExploreDataset();
  189. return (
  190. <SpanTagsProvider dataset={dataset} enabled>
  191. {children}
  192. </SpanTagsProvider>
  193. );
  194. }
  195. export function SpansTabContent(props: SpanTabProps) {
  196. Sentry.setTag('explore.visited', 'yes');
  197. return (
  198. <PageParamsProvider>
  199. <ExploreTagsProvider>
  200. <SpansTabContentImpl {...props} />
  201. </ExploreTagsProvider>
  202. </PageParamsProvider>
  203. );
  204. }
  205. const Body = styled(Layout.Body)`
  206. gap: ${space(2)};
  207. @media (min-width: ${p => p.theme.breakpoints.medium}) {
  208. grid-template-columns: 300px minmax(100px, auto);
  209. gap: ${space(2)};
  210. }
  211. @media (min-width: ${p => p.theme.breakpoints.xxlarge}) {
  212. grid-template-columns: 400px minmax(100px, auto);
  213. }
  214. `;
  215. const TopSection = styled('div')`
  216. display: grid;
  217. gap: ${space(2)};
  218. grid-column: 1/3;
  219. margin-bottom: ${space(2)};
  220. @media (min-width: ${p => p.theme.breakpoints.large}) {
  221. grid-template-columns: minmax(300px, auto) 1fr;
  222. margin-bottom: 0;
  223. }
  224. @media (min-width: ${p => p.theme.breakpoints.xxlarge}) {
  225. grid-template-columns: minmax(400px, auto) 1fr;
  226. }
  227. `;
  228. const MainSection = styled(Layout.Main)`
  229. grid-column: 2/3;
  230. `;
  231. const StyledPageFilterBar = styled(PageFilterBar)`
  232. width: auto;
  233. `;