import {useEffect} from 'react'; import * as Sentry from '@sentry/react'; import {defined} from 'sentry/utils'; import {trackAnalytics} from 'sentry/utils/analytics'; import {dedupeArray} from 'sentry/utils/dedupeArray'; import {DiscoverDatasets} from 'sentry/utils/discover/types'; import {MutableSearch} from 'sentry/utils/tokenizeSearch'; import useOrganization from 'sentry/utils/useOrganization'; import { useExploreDataset, useExploreFields, useExploreQuery, useExploreTitle, useExploreVisualizes, } from 'sentry/views/explore/contexts/pageParamsContext'; import type {Visualize} from 'sentry/views/explore/contexts/pageParamsContext/visualizes'; import type {AggregatesTableResult} from 'sentry/views/explore/hooks/useExploreAggregatesTable'; import type {SpansTableResult} from 'sentry/views/explore/hooks/useExploreSpansTable'; import type {TracesTableResult} from 'sentry/views/explore/hooks/useExploreTracesTable'; import type {ReadableExploreQueryParts} from 'sentry/views/explore/multiQueryMode/locationUtils'; import {combineConfidenceForSeries} from 'sentry/views/explore/utils'; import type {useSortedTimeSeries} from 'sentry/views/insights/common/queries/useSortedTimeSeries'; import {usePerformanceSubscriptionDetails} from 'sentry/views/performance/newTraceDetails/traceTypeWarnings/usePerformanceSubscriptionDetails'; const {info, fmt} = Sentry._experiment_log; export function useTrackAnalytics({ queryType, aggregatesTableResult, spansTableResult, tracesTableResult, timeseriesResult, dataset, title, query, fields, visualizes, page_source, }: { aggregatesTableResult: AggregatesTableResult; dataset: DiscoverDatasets; fields: string[]; page_source: 'explore' | 'compare'; query: string; queryType: 'aggregate' | 'samples' | 'traces'; spansTableResult: SpansTableResult; timeseriesResult: ReturnType; visualizes: Visualize[]; title?: string; tracesTableResult?: TracesTableResult; }) { const organization = useOrganization(); const { data: {hasExceededPerformanceUsageLimit}, isLoading: isLoadingSubscriptionDetails, } = usePerformanceSubscriptionDetails(); const tableError = queryType === 'aggregate' ? (aggregatesTableResult.result.error?.message ?? '') : queryType === 'traces' ? (tracesTableResult?.result.error?.message ?? '') : (spansTableResult.result.error?.message ?? ''); const chartError = timeseriesResult.error?.message ?? ''; const query_status = tableError || chartError ? 'error' : 'success'; useEffect(() => { if ( queryType !== 'aggregate' || aggregatesTableResult.result.isPending || timeseriesResult.isPending || isLoadingSubscriptionDetails ) { return; } const search = new MutableSearch(query); const columns = aggregatesTableResult.eventView.getColumns() as unknown as string[]; trackAnalytics('trace.explorer.metadata', { organization, dataset, result_mode: 'aggregates', columns, columns_count: columns.length, query_status, result_length: aggregatesTableResult.result.data?.length || 0, result_missing_root: 0, user_queries: search.formatString(), user_queries_count: search.tokens.length, visualizes: visualizes.map(visualize => ({ chartType: visualize.chartType, yAxes: visualize.yAxes, })), visualizes_count: visualizes.length, title: title || '', confidences: computeConfidence(visualizes, timeseriesResult.data), has_exceeded_performance_usage_limit: hasExceededPerformanceUsageLimit, page_source, }); info( fmt`trace.explorer.metadata: organization: ${organization.slug} dataset: ${dataset} query: [omitted] visualizes: ${visualizes.map(v => v.chartType).join(', ')} title: ${title || ''} queryType: ${queryType} result_length: ${String(aggregatesTableResult.result.data?.length || 0)} user_queries: ${search.formatString()} user_queries_count: ${String(String(search.tokens).length)} visualizes_count: ${String(String(visualizes).length)} has_exceeded_performance_usage_limit: ${String(hasExceededPerformanceUsageLimit)} page_source: ${page_source} `, {isAnalytics: true} ); }, [ organization, dataset, fields, query, visualizes, title, queryType, aggregatesTableResult.result.isPending, aggregatesTableResult.result.status, aggregatesTableResult.result.data?.length, aggregatesTableResult.eventView, timeseriesResult.isPending, timeseriesResult.data, hasExceededPerformanceUsageLimit, isLoadingSubscriptionDetails, query_status, page_source, ]); useEffect(() => { if ( queryType !== 'samples' || spansTableResult.result.isPending || timeseriesResult.isPending || isLoadingSubscriptionDetails ) { return; } const search = new MutableSearch(query); trackAnalytics('trace.explorer.metadata', { organization, dataset, result_mode: 'span samples', columns: fields, columns_count: fields.length, query_status, result_length: spansTableResult.result.data?.length || 0, result_missing_root: 0, user_queries: search.formatString(), user_queries_count: search.tokens.length, visualizes, visualizes_count: visualizes.length, title: title || '', confidences: computeConfidence(visualizes, timeseriesResult.data), has_exceeded_performance_usage_limit: hasExceededPerformanceUsageLimit, page_source, }); info(fmt`trace.explorer.metadata: organization: ${organization.slug} dataset: ${dataset} query: ${query} visualizes: ${visualizes.map(v => v.chartType).join(', ')} title: ${title || ''} queryType: ${queryType} result_length: ${String(spansTableResult.result.data?.length || 0)} user_queries: ${search.formatString()} user_queries_count: ${String(search.tokens.length)} visualizes_count: ${String(visualizes.length)} has_exceeded_performance_usage_limit: ${String(hasExceededPerformanceUsageLimit)} page_source: ${page_source} `); }, [ organization, dataset, fields, query, visualizes, title, queryType, spansTableResult.result.isPending, spansTableResult.result.status, spansTableResult.result.data?.length, timeseriesResult.isPending, timeseriesResult.data, hasExceededPerformanceUsageLimit, isLoadingSubscriptionDetails, query_status, page_source, ]); const tracesTableResultDefined = defined(tracesTableResult); useEffect(() => { if ( !tracesTableResultDefined || queryType !== 'traces' || tracesTableResult.result.isPending || timeseriesResult.isPending || isLoadingSubscriptionDetails ) { return; } const search = new MutableSearch(query); const columns = [ 'trace id', 'trace root', 'total spans', 'timeline', 'root duration', 'timestamp', ]; const resultMissingRoot = tracesTableResult?.result?.data?.data?.filter(trace => !defined(trace.name)) .length ?? 0; trackAnalytics('trace.explorer.metadata', { organization, dataset, result_mode: 'trace samples', columns, columns_count: columns.length, query_status, result_length: tracesTableResult.result.data?.data?.length || 0, result_missing_root: resultMissingRoot, user_queries: search.formatString(), user_queries_count: search.tokens.length, visualizes, visualizes_count: visualizes.length, title: title || '', confidences: computeConfidence(visualizes, timeseriesResult.data), has_exceeded_performance_usage_limit: hasExceededPerformanceUsageLimit, page_source, }); }, [ organization, dataset, fields, query, visualizes, title, queryType, tracesTableResult?.result.isPending, tracesTableResult?.result.status, tracesTableResult?.result.data?.data, timeseriesResult.isPending, timeseriesResult.data, hasExceededPerformanceUsageLimit, isLoadingSubscriptionDetails, query_status, page_source, tracesTableResultDefined, ]); } export function useAnalytics({ queryType, aggregatesTableResult, spansTableResult, tracesTableResult, timeseriesResult, }: { aggregatesTableResult: AggregatesTableResult; queryType: 'aggregate' | 'samples' | 'traces'; spansTableResult: SpansTableResult; timeseriesResult: ReturnType; tracesTableResult: TracesTableResult; }) { const dataset = useExploreDataset(); const title = useExploreTitle(); const query = useExploreQuery(); const fields = useExploreFields(); const visualizes = useExploreVisualizes(); return useTrackAnalytics({ queryType, aggregatesTableResult, spansTableResult, tracesTableResult, timeseriesResult, dataset, title, query, fields, visualizes, page_source: 'explore', }); } export function useCompareAnalytics({ query: queryParts, index, queryType, aggregatesTableResult, spansTableResult, timeseriesResult, }: { aggregatesTableResult: AggregatesTableResult; index: number; query: ReadableExploreQueryParts; queryType: 'aggregate' | 'samples' | 'traces'; spansTableResult: SpansTableResult; timeseriesResult: ReturnType; }) { const dataset = DiscoverDatasets.SPANS_EAP_RPC; const query = queryParts.query; const fields = queryParts.fields; const visualizes = queryParts.yAxes.map(yAxis => { return { chartType: queryParts.chartType, yAxes: [yAxis], label: String(index), } as Visualize; }); return useTrackAnalytics({ queryType, aggregatesTableResult, spansTableResult, timeseriesResult, dataset, query, fields, visualizes, page_source: 'compare', }); } function computeConfidence( visualizes: Visualize[], data: ReturnType['data'] ) { return visualizes.map(visualize => { const dedupedYAxes = dedupeArray(visualize.yAxes); const series = dedupedYAxes.flatMap(yAxis => data[yAxis]).filter(defined); return String(combineConfidenceForSeries(series)); }); }