123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183 |
- import {useMemo} from 'react';
- import {useQuery} from '@tanstack/react-query';
- import * as qs from 'query-string';
- import type {Client} from 'sentry/api';
- import {normalizeDateTimeParams} from 'sentry/components/organizations/pageFilters/parse';
- import {DEFAULT_STATS_PERIOD} from 'sentry/constants';
- import type {PageFilters} from 'sentry/types/core';
- import type {Organization} from 'sentry/types/organization';
- import type {TraceMeta} from 'sentry/utils/performance/quickTrace/types';
- import type {QueryStatus} from 'sentry/utils/queryClient';
- import {decodeScalar} from 'sentry/utils/queryString';
- import useApi from 'sentry/utils/useApi';
- import useOrganization from 'sentry/utils/useOrganization';
- import usePageFilters from 'sentry/utils/usePageFilters';
- import type {ReplayTrace} from 'sentry/views/replays/detail/trace/useReplayTraces';
- type TraceMetaQueryParams =
- | {
- // demo has the format ${projectSlug}:${eventId}
- // used to query a demo transaction event from the backend.
- statsPeriod: string;
- }
- | {
- timestamp: number;
- };
- function getMetaQueryParams(
- row: ReplayTrace,
- normalizedParams: any,
- filters: Partial<PageFilters> = {}
- ): TraceMetaQueryParams {
- const statsPeriod = decodeScalar(normalizedParams.statsPeriod);
- if (row.timestamp) {
- return {timestamp: row.timestamp};
- }
- return {
- statsPeriod: (statsPeriod || filters?.datetime?.period) ?? DEFAULT_STATS_PERIOD,
- };
- }
- async function fetchSingleTraceMetaNew(
- api: Client,
- organization: Organization,
- replayTrace: ReplayTrace,
- queryParams: any
- ) {
- const data = await api.requestPromise(
- `/organizations/${organization.slug}/events-trace-meta/${replayTrace.traceSlug}/`,
- {
- method: 'GET',
- data: queryParams,
- }
- );
- return data;
- }
- async function fetchTraceMetaInBatches(
- api: Client,
- organization: Organization,
- replayTraces: ReplayTrace[],
- normalizedParams: any,
- filters: Partial<PageFilters> = {}
- ) {
- const clonedTraceIds = [...replayTraces];
- const meta: TraceMeta = {
- errors: 0,
- performance_issues: 0,
- projects: 0,
- transactions: 0,
- transactiontoSpanChildrenCount: {},
- };
- const apiErrors: Error[] = [];
- while (clonedTraceIds.length > 0) {
- const batch = clonedTraceIds.splice(0, 3);
- const results = await Promise.allSettled(
- batch.map(replayTrace => {
- const queryParams = getMetaQueryParams(replayTrace, normalizedParams, filters);
- return fetchSingleTraceMetaNew(api, organization, replayTrace, queryParams);
- })
- );
- results.reduce((acc, result) => {
- if (result.status === 'fulfilled') {
- acc.errors += result.value.errors;
- acc.performance_issues += result.value.performance_issues;
- acc.projects = Math.max(acc.projects, result.value.projects);
- acc.transactions += result.value.transactions;
- // Turn the transaction_child_count_map array into a map of transaction id to child count
- // for more efficient lookups.
- result.value.transaction_child_count_map.forEach(
- ({'transaction.id': id, count}) => {
- acc.transactiontoSpanChildrenCount[id] = count;
- }
- );
- } else {
- apiErrors.push(new Error(result?.reason));
- }
- return acc;
- }, meta);
- }
- return {meta, apiErrors};
- }
- export type TraceMetaQueryResults = {
- data: TraceMeta | undefined;
- errors: Error[];
- status: QueryStatus;
- };
- export function useTraceMeta(replayTraces: ReplayTrace[]): TraceMetaQueryResults {
- const api = useApi();
- const filters = usePageFilters();
- const organization = useOrganization();
- const normalizedParams = useMemo(() => {
- const query = qs.parse(location.search);
- return normalizeDateTimeParams(query, {
- allowAbsolutePageDatetime: true,
- });
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, []);
- // demo has the format ${projectSlug}:${eventId}
- // used to query a demo transaction event from the backend.
- const mode = decodeScalar(normalizedParams.demo) ? 'demo' : undefined;
- const query = useQuery<
- {
- apiErrors: Error[];
- meta: TraceMeta;
- },
- Error
- >({
- queryKey: ['traceData', replayTraces],
- queryFn: () =>
- fetchTraceMetaInBatches(
- api,
- organization,
- replayTraces,
- normalizedParams,
- filters.selection
- ),
- enabled: replayTraces.length > 0,
- });
- const results = useMemo(() => {
- return {
- data: query.data?.meta,
- errors: query.data?.apiErrors ?? [],
- status:
- query.data?.apiErrors?.length === replayTraces.length ? 'error' : query.status,
- };
- }, [query, replayTraces.length]);
- // 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.
- const demoResults = useMemo(() => {
- return {
- data: {
- errors: 0,
- performance_issues: 0,
- projects: 1,
- transactions: 1,
- transactiontoSpanChildrenCount: {},
- },
- errors: [],
- status: 'success' as QueryStatus,
- };
- }, []);
- return mode === 'demo' ? demoResults : results;
- }
|