From a06fca2c4e539b9d4fdcae74e24fe02d49434f87 Mon Sep 17 00:00:00 2001 From: Colin <161344340+colin-sentry@users.noreply.github.com> Date: Wed, 22 Jan 2025 16:20:01 -0500 Subject: [PATCH] chore(ourlogs): Separate explore page from logs page (#83860) We want to also add logs to the explore page under a separate tab, so move the existing explore body into a separate file --- static/app/views/explore/content.tsx | 299 +------------------- static/app/views/explore/spans/spansTab.tsx | 256 +++++++++++++++++ static/app/views/explore/utils.tsx | 46 ++- 3 files changed, 316 insertions(+), 285 deletions(-) create mode 100644 static/app/views/explore/spans/spansTab.tsx diff --git a/static/app/views/explore/content.tsx b/static/app/views/explore/content.tsx index c067f6d85c6e41..6ae7cb77cb5ad1 100644 --- a/static/app/views/explore/content.tsx +++ b/static/app/views/explore/content.tsx @@ -1,82 +1,29 @@ -import {useCallback, useMemo} from 'react'; -import styled from '@emotion/styled'; -import * as Sentry from '@sentry/react'; +import {useCallback} from 'react'; import Feature from 'sentry/components/acl/feature'; -import {Alert} from 'sentry/components/alert'; import FeatureBadge from 'sentry/components/badge/featureBadge'; import {Button} from 'sentry/components/button'; import ButtonBar from 'sentry/components/buttonBar'; import FeedbackWidgetButton from 'sentry/components/feedback/widget/feedbackWidgetButton'; import * as Layout from 'sentry/components/layouts/thirds'; -import {DatePageFilter} from 'sentry/components/organizations/datePageFilter'; -import {EnvironmentPageFilter} from 'sentry/components/organizations/environmentPageFilter'; -import PageFilterBar from 'sentry/components/organizations/pageFilterBar'; import PageFiltersContainer from 'sentry/components/organizations/pageFilters/container'; -import {ProjectPageFilter} from 'sentry/components/organizations/projectPageFilter'; import {PageHeadingQuestionTooltip} from 'sentry/components/pageHeadingQuestionTooltip'; -import { - EAPSpanSearchQueryBuilder, - SpanSearchQueryBuilder, -} from 'sentry/components/performance/spanSearchQueryBuilder'; import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle'; import {t} from 'sentry/locale'; -import {space} from 'sentry/styles/space'; -import type {Organization} from 'sentry/types/organization'; -import {defined} from 'sentry/utils'; -import {dedupeArray} from 'sentry/utils/dedupeArray'; -import {DiscoverDatasets} from 'sentry/utils/discover/types'; -import { - type AggregationKey, - ALLOWED_EXPLORE_VISUALIZE_AGGREGATES, -} from 'sentry/utils/fields'; import {useLocation} from 'sentry/utils/useLocation'; import {useNavigate} from 'sentry/utils/useNavigate'; import useOrganization from 'sentry/utils/useOrganization'; -import usePageFilters from 'sentry/utils/usePageFilters'; -import {ExploreCharts} from 'sentry/views/explore/charts'; -import { - PageParamsProvider, - useExploreDataset, - useExploreMode, - useExploreQuery, - useExploreVisualizes, - useSetExploreQuery, -} from 'sentry/views/explore/contexts/pageParamsContext'; -import {Mode} from 'sentry/views/explore/contexts/pageParamsContext/mode'; -import { - SpanTagsProvider, - useSpanTags, -} from 'sentry/views/explore/contexts/spanTagsContext'; -import {useExploreAggregatesTable} from 'sentry/views/explore/hooks/useExploreAggregatesTable'; -import {useExploreSpansTable} from 'sentry/views/explore/hooks/useExploreSpansTable'; -import {useExploreTimeseries} from 'sentry/views/explore/hooks/useExploreTimeseries'; -import {useExploreTracesTable} from 'sentry/views/explore/hooks/useExploreTracesTable'; -import {Tab, useTab} from 'sentry/views/explore/hooks/useTab'; -import {ExploreTables} from 'sentry/views/explore/tables'; -import {ExploreToolbar} from 'sentry/views/explore/toolbar'; -import {combineConfidenceForSeries} from 'sentry/views/explore/utils'; +import {SpansTabContent} from 'sentry/views/explore/spans/spansTab'; +import {limitMaxPickableDays} from 'sentry/views/explore/utils'; -function ExploreContentImpl() { - const location = useLocation(); - const navigate = useNavigate(); +export function ExploreContent() { const organization = useOrganization(); - const {selection} = usePageFilters(); - const dataset = useExploreDataset(); - const mode = useExploreMode(); - const visualizes = useExploreVisualizes(); - const [samplesTab, setSamplesTab] = useTab(); - - const numberTags = useSpanTags('number'); - const stringTags = useSpanTags('string'); - - const query = useExploreQuery(); - const setQuery = useSetExploreQuery(); - - const toolbarExtras = organization.features.includes('visibility-explore-dataset') - ? ['dataset toggle' as const] - : []; + const {defaultPeriod, maxPickableDays, relativeOptions} = limitMaxPickableDays( + organization! + ); + const location = useLocation(); + const navigate = useNavigate(); const switchToOldTraceExplorer = useCallback(() => { navigate({ ...location, @@ -87,55 +34,8 @@ function ExploreContentImpl() { }); }, [location, navigate]); - const queryType: 'aggregate' | 'samples' | 'traces' = - mode === Mode.AGGREGATE - ? 'aggregate' - : samplesTab === Tab.TRACE - ? 'traces' - : 'samples'; - - const limit = 25; - - const aggregatesTableResult = useExploreAggregatesTable({ - query, - limit, - enabled: queryType === 'aggregate', - }); - const spansTableResult = useExploreSpansTable({ - query, - limit, - enabled: queryType === 'samples', - }); - const tracesTableResult = useExploreTracesTable({ - query, - enabled: queryType === 'traces', - }); - const {timeseriesResult, canUsePreviousResults} = useExploreTimeseries({query}); - const confidences = useMemo( - () => - visualizes.map(visualize => { - const dedupedYAxes = dedupeArray(visualize.yAxes); - const series = dedupedYAxes - .flatMap(yAxis => timeseriesResult.data[yAxis]) - .filter(defined); - return combineConfidenceForSeries(series); - }), - [timeseriesResult.data, visualizes] - ); - - const tableError = - queryType === 'aggregate' - ? aggregatesTableResult.result.error?.message ?? '' - : queryType === 'traces' - ? tracesTableResult.result.error?.message ?? '' - : spansTableResult.result.error?.message ?? ''; - const chartError = timeseriesResult.error?.message ?? ''; - - const {defaultPeriod, maxPickableDays, relativeOptions} = - limitMaxPickableDays(organization); - return ( - + @@ -168,182 +68,13 @@ function ExploreContentImpl() { - - - - - - ({ - ...arbitraryOptions, - ...relativeOptions, - })} - /> - - {dataset === DiscoverDatasets.SPANS_INDEXED ? ( - - ) : ( - { - if ( - ALLOWED_EXPLORE_VISUALIZE_AGGREGATES.includes( - key as AggregationKey - ) - ) { - return t( - "This key won't affect the results because samples mode does not support aggregate functions" - ); - } - return undefined; - } - : undefined - } - supportedAggregates={ALLOWED_EXPLORE_VISUALIZE_AGGREGATES} - numberTags={numberTags} - stringTags={stringTags} - /> - )} - - - - {(tableError || chartError) && ( - - {tableError || chartError} - - )} - - - - + ); } - -function ExploreTagsProvider({children}: any) { - const dataset = useExploreDataset(); - - return ( - - {children} - - ); -} - -export function ExploreContent() { - Sentry.setTag('explore.visited', 'yes'); - - return ( - - - - - - ); -} - -type MaxPickableDays = 7 | 14 | 30; -type DefaultPeriod = '7d' | '14d' | '30d'; - -function limitMaxPickableDays(organization: Organization): { - defaultPeriod: DefaultPeriod; - maxPickableDays: MaxPickableDays; - relativeOptions: Record; -} { - const defaultPeriods: Record = { - 7: '7d', - 14: '14d', - 30: '30d', - }; - - const relativeOptions: [DefaultPeriod, React.ReactNode][] = [ - ['7d', t('Last 7 days')], - ['14d', t('Last 14 days')], - ['30d', t('Last 30 days')], - ]; - - const maxPickableDays: MaxPickableDays = organization.features.includes( - 'visibility-explore-range-high' - ) - ? 30 - : organization.features.includes('visibility-explore-range-medium') - ? 14 - : 7; - const defaultPeriod: DefaultPeriod = defaultPeriods[maxPickableDays]; - - const index = relativeOptions.findIndex(([period, _]) => period === defaultPeriod) + 1; - const enabledOptions = relativeOptions.slice(0, index); - - return { - defaultPeriod, - maxPickableDays, - relativeOptions: { - '1h': t('Last hour'), - '24h': t('Last 24 hours'), - ...Object.fromEntries(enabledOptions), - }, - }; -} - -const Body = styled(Layout.Body)` - gap: ${space(2)}; - - @media (min-width: ${p => p.theme.breakpoints.medium}) { - grid-template-columns: 300px minmax(100px, auto); - gap: ${space(2)}; - } - - @media (min-width: ${p => p.theme.breakpoints.xxlarge}) { - grid-template-columns: 400px minmax(100px, auto); - } -`; - -const TopSection = styled('div')` - display: grid; - gap: ${space(2)}; - grid-column: 1/3; - margin-bottom: ${space(2)}; - - @media (min-width: ${p => p.theme.breakpoints.large}) { - grid-template-columns: minmax(300px, auto) 1fr; - margin-bottom: 0; - } - - @media (min-width: ${p => p.theme.breakpoints.xxlarge}) { - grid-template-columns: minmax(400px, auto) 1fr; - } -`; - -const MainSection = styled(Layout.Main)` - grid-column: 2/3; -`; - -const StyledPageFilterBar = styled(PageFilterBar)` - width: auto; -`; diff --git a/static/app/views/explore/spans/spansTab.tsx b/static/app/views/explore/spans/spansTab.tsx new file mode 100644 index 00000000000000..27f3211b97ae29 --- /dev/null +++ b/static/app/views/explore/spans/spansTab.tsx @@ -0,0 +1,256 @@ +import {useMemo} from 'react'; +import styled from '@emotion/styled'; +import * as Sentry from '@sentry/react'; + +import Alert from 'sentry/components/alert'; +import * as Layout from 'sentry/components/layouts/thirds'; +import {DatePageFilter} from 'sentry/components/organizations/datePageFilter'; +import {EnvironmentPageFilter} from 'sentry/components/organizations/environmentPageFilter'; +import PageFilterBar from 'sentry/components/organizations/pageFilterBar'; +import {ProjectPageFilter} from 'sentry/components/organizations/projectPageFilter'; +import { + EAPSpanSearchQueryBuilder, + SpanSearchQueryBuilder, +} from 'sentry/components/performance/spanSearchQueryBuilder'; +import {t} from 'sentry/locale'; +import {space} from 'sentry/styles/space'; +import {defined} from 'sentry/utils'; +import {dedupeArray} from 'sentry/utils/dedupeArray'; +import {DiscoverDatasets} from 'sentry/utils/discover/types'; +import { + type AggregationKey, + ALLOWED_EXPLORE_VISUALIZE_AGGREGATES, +} from 'sentry/utils/fields'; +import useOrganization from 'sentry/utils/useOrganization'; +import usePageFilters from 'sentry/utils/usePageFilters'; +import {ExploreCharts} from 'sentry/views/explore/charts'; +import { + PageParamsProvider, + useExploreDataset, + useExploreMode, + useExploreQuery, + useExploreVisualizes, + useSetExploreQuery, +} from 'sentry/views/explore/contexts/pageParamsContext'; +import {Mode} from 'sentry/views/explore/contexts/pageParamsContext/mode'; +import { + SpanTagsProvider, + useSpanTags, +} from 'sentry/views/explore/contexts/spanTagsContext'; +import {useExploreAggregatesTable} from 'sentry/views/explore/hooks/useExploreAggregatesTable'; +import {useExploreSpansTable} from 'sentry/views/explore/hooks/useExploreSpansTable'; +import {useExploreTimeseries} from 'sentry/views/explore/hooks/useExploreTimeseries'; +import {useExploreTracesTable} from 'sentry/views/explore/hooks/useExploreTracesTable'; +import {Tab, useTab} from 'sentry/views/explore/hooks/useTab'; +import {ExploreTables} from 'sentry/views/explore/tables'; +import {ExploreToolbar} from 'sentry/views/explore/toolbar'; +import { + combineConfidenceForSeries, + type DefaultPeriod, + type MaxPickableDays, +} from 'sentry/views/explore/utils'; + +export type SpanTabProps = { + defaultPeriod: DefaultPeriod; + maxPickableDays: MaxPickableDays; + relativeOptions: Record; +}; + +export function SpansTabContentImpl({ + defaultPeriod, + maxPickableDays, + relativeOptions, +}: SpanTabProps) { + const organization = useOrganization(); + const {selection} = usePageFilters(); + const dataset = useExploreDataset(); + const mode = useExploreMode(); + const visualizes = useExploreVisualizes(); + const [samplesTab, setSamplesTab] = useTab(); + + const numberTags = useSpanTags('number'); + const stringTags = useSpanTags('string'); + + const query = useExploreQuery(); + const setQuery = useSetExploreQuery(); + + const toolbarExtras = organization?.features?.includes('visibility-explore-dataset') + ? ['dataset toggle' as const] + : []; + + const queryType: 'aggregate' | 'samples' | 'traces' = + mode === Mode.AGGREGATE + ? 'aggregate' + : samplesTab === Tab.TRACE + ? 'traces' + : 'samples'; + + const limit = 25; + + const aggregatesTableResult = useExploreAggregatesTable({ + query, + limit, + enabled: queryType === 'aggregate', + }); + const spansTableResult = useExploreSpansTable({ + query, + limit, + enabled: queryType === 'samples', + }); + const tracesTableResult = useExploreTracesTable({ + query, + enabled: queryType === 'traces', + }); + const {timeseriesResult, canUsePreviousResults} = useExploreTimeseries({query}); + const confidences = useMemo( + () => + visualizes.map(visualize => { + const dedupedYAxes = dedupeArray(visualize.yAxes); + const series = dedupedYAxes + .flatMap(yAxis => timeseriesResult.data[yAxis]) + .filter(defined); + return combineConfidenceForSeries(series); + }), + [timeseriesResult.data, visualizes] + ); + + const tableError = + queryType === 'aggregate' + ? aggregatesTableResult.result.error?.message ?? '' + : queryType === 'traces' + ? tracesTableResult.result.error?.message ?? '' + : spansTableResult.result.error?.message ?? ''; + const chartError = timeseriesResult.error?.message ?? ''; + + return ( + + + + + + ({ + ...arbitraryOptions, + ...relativeOptions, + })} + /> + + {dataset === DiscoverDatasets.SPANS_INDEXED ? ( + + ) : ( + { + if ( + ALLOWED_EXPLORE_VISUALIZE_AGGREGATES.includes(key as AggregationKey) + ) { + return t( + "This key won't affect the results because samples mode does not support aggregate functions" + ); + } + return undefined; + } + : undefined + } + supportedAggregates={ALLOWED_EXPLORE_VISUALIZE_AGGREGATES} + numberTags={numberTags} + stringTags={stringTags} + /> + )} + + + + {(tableError || chartError) && ( + + {tableError || chartError} + + )} + + + + + ); +} + +function ExploreTagsProvider({children}: any) { + const dataset = useExploreDataset(); + + return ( + + {children} + + ); +} + +export function SpansTabContent(props: SpanTabProps) { + Sentry.setTag('explore.visited', 'yes'); + + return ( + + + + + + ); +} + +const Body = styled(Layout.Body)` + gap: ${space(2)}; + + @media (min-width: ${p => p.theme.breakpoints.medium}) { + grid-template-columns: 300px minmax(100px, auto); + gap: ${space(2)}; + } + + @media (min-width: ${p => p.theme.breakpoints.xxlarge}) { + grid-template-columns: 400px minmax(100px, auto); + } +`; + +const TopSection = styled('div')` + display: grid; + gap: ${space(2)}; + grid-column: 1/3; + margin-bottom: ${space(2)}; + + @media (min-width: ${p => p.theme.breakpoints.large}) { + grid-template-columns: minmax(300px, auto) 1fr; + margin-bottom: 0; + } + + @media (min-width: ${p => p.theme.breakpoints.xxlarge}) { + grid-template-columns: minmax(400px, auto) 1fr; + } +`; + +const MainSection = styled(Layout.Main)` + grid-column: 2/3; +`; + +const StyledPageFilterBar = styled(PageFilterBar)` + width: auto; +`; diff --git a/static/app/views/explore/utils.tsx b/static/app/views/explore/utils.tsx index 10edd1a813c4c6..5092bfbb3b3657 100644 --- a/static/app/views/explore/utils.tsx +++ b/static/app/views/explore/utils.tsx @@ -1,9 +1,10 @@ import type {Location} from 'history'; import * as qs from 'query-string'; +import {t} from 'sentry/locale'; import type {PageFilters} from 'sentry/types/core'; import type {Series} from 'sentry/types/echarts'; -import type {Confidence} from 'sentry/types/organization'; +import type {Confidence, Organization} from 'sentry/types/organization'; import type {Project} from 'sentry/types/project'; import {defined} from 'sentry/utils'; import {DiscoverDatasets} from 'sentry/utils/discover/types'; @@ -116,3 +117,46 @@ export function viewSamplesTarget( query: search.formatString(), }); } + +export type MaxPickableDays = 7 | 14 | 30; +export type DefaultPeriod = '7d' | '14d' | '30d'; + +export function limitMaxPickableDays(organization: Organization): { + defaultPeriod: DefaultPeriod; + maxPickableDays: MaxPickableDays; + relativeOptions: Record; +} { + const defaultPeriods: Record = { + 7: '7d', + 14: '14d', + 30: '30d', + }; + + const relativeOptions: [DefaultPeriod, React.ReactNode][] = [ + ['7d', t('Last 7 days')], + ['14d', t('Last 14 days')], + ['30d', t('Last 30 days')], + ]; + + const maxPickableDays: MaxPickableDays = organization.features.includes( + 'visibility-explore-range-high' + ) + ? 30 + : organization.features.includes('visibility-explore-range-medium') + ? 14 + : 7; + const defaultPeriod: DefaultPeriod = defaultPeriods[maxPickableDays]; + + const index = relativeOptions.findIndex(([period, _]) => period === defaultPeriod) + 1; + const enabledOptions = relativeOptions.slice(0, index); + + return { + defaultPeriod, + maxPickableDays, + relativeOptions: { + '1h': t('Last hour'), + '24h': t('Last 24 hours'), + ...Object.fromEntries(enabledOptions), + }, + }; +}