import {useMemo} from 'react'; import type {Location} from 'history'; import * as qs from 'query-string'; import {normalizeDateTimeParams} from 'sentry/components/organizations/pageFilters/parse'; import {DEFAULT_STATS_PERIOD} from 'sentry/constants'; import type {PageFilters} from 'sentry/types/core'; import type {TraceMeta} from 'sentry/utils/performance/quickTrace/types'; import {type ApiQueryKey, useApiQueries} from 'sentry/utils/queryClient'; import {decodeScalar} from 'sentry/utils/queryString'; import useOrganization from 'sentry/utils/useOrganization'; import usePageFilters from 'sentry/utils/usePageFilters'; function getMetaQueryParams( query: Location['query'], filters: Partial = {} ): | { // demo has the format ${projectSlug}:${eventId} // used to query a demo transaction event from the backend. demo: string | undefined; statsPeriod: string; } | { demo: string | undefined; timestamp: string; } { const normalizedParams = normalizeDateTimeParams(query, { allowAbsolutePageDatetime: true, }); const statsPeriod = decodeScalar(normalizedParams.statsPeriod); const timestamp = decodeScalar(normalizedParams.timestamp); if (timestamp) { return {timestamp, demo: decodeScalar(normalizedParams.demo)}; } return { statsPeriod: (statsPeriod || filters?.datetime?.period) ?? DEFAULT_STATS_PERIOD, demo: decodeScalar(normalizedParams.demo), }; } function getTraceMetaQueryKey( traceSlug: string, orgSlug: string, queryParams: | { // demo has the format ${projectSlug}:${eventId} // used to query a demo transaction event from the backend. demo: string | undefined; statsPeriod: string; } | { demo: string | undefined; timestamp: string; } ): ApiQueryKey { return [ `/organizations/${orgSlug}/events-trace-meta/${traceSlug}/`, {query: queryParams}, ]; } export type TraceMetaQueryResults = { data: TraceMeta; isLoading: boolean; isRefetching: boolean; refetch: () => void; }; export function useTraceMeta(traceSlugs: string[]): { data: TraceMeta; isLoading: boolean; isRefetching: boolean; refetch: () => void; } { const filters = usePageFilters(); const organization = useOrganization(); const queryParams = useMemo(() => { const query = qs.parse(location.search); return getMetaQueryParams(query, filters.selection); // eslint-disable-next-line react-hooks/exhaustive-deps }, []); const mode = queryParams.demo ? 'demo' : undefined; const queryKeys = useMemo(() => { return traceSlugs.map(traceSlug => getTraceMetaQueryKey(traceSlug, organization.slug, queryParams) ); }, [traceSlugs, organization, queryParams]); const results = useApiQueries(queryKeys, { enabled: traceSlugs.length > 0 && !!organization.slug && mode !== 'demo', staleTime: Infinity, }); const mergedTraceMeta = useMemo(() => { const mergedResult: { data: TraceMeta; isLoading: boolean; isRefetching: boolean; refetch: () => void; } = { data: { errors: 0, performance_issues: 0, projects: 0, transactions: 0, }, isLoading: false, isRefetching: false, refetch: () => { results.forEach(result => result.refetch()); }, }; for (const metaResult of results) { mergedResult.isLoading ||= metaResult.isLoading; mergedResult.isRefetching ||= metaResult.isRefetching; mergedResult.data.errors += metaResult.data?.errors ?? 0; mergedResult.data.performance_issues += metaResult.data?.performance_issues ?? 0; // TODO: We want the count of unique projects, not the sum of all projects. When there are multiple traces, taking the best guess by using the max // project count accross all traces for now. We don't use the project count for anything yet, so this is fine for now. mergedResult.data.projects += Math.max( metaResult.data?.projects ?? 0, mergedResult.data.projects ); mergedResult.data.transactions += metaResult.data?.transactions ?? 0; } return mergedResult; }, [results]); // When projects don't have performance set up, we allow them to view a sample transaction. // The backend creates the sample transaction, however the trace is created async, so when the // page loads, we cannot guarantee that querying the trace will succeed as it may not have been stored yet. // When this happens, we assemble a fake trace response to only include the transaction that had already been // created and stored already so that the users can visualize in the context of a trace. // The trace meta query has to reflect this by returning a single transaction and project. if (mode === 'demo') { return { data: { errors: 0, performance_issues: 0, projects: 1, transactions: 1, }, isLoading: false, isRefetching: false, refetch: () => {}, }; } return mergedTraceMeta; }