useTrace.tsx 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. import {useEffect, useMemo, useState} from 'react';
  2. import type {Location} from 'history';
  3. import * as qs from 'query-string';
  4. import type {Client} from 'sentry/api';
  5. import {normalizeDateTimeParams} from 'sentry/components/organizations/pageFilters/parse';
  6. import {ALL_ACCESS_PROJECTS} from 'sentry/constants/pageFilters';
  7. import type {PageFilters} from 'sentry/types';
  8. import type {
  9. TraceFullDetailed,
  10. TraceSplitResults,
  11. } from 'sentry/utils/performance/quickTrace/types';
  12. import {decodeScalar} from 'sentry/utils/queryString';
  13. import useApi from 'sentry/utils/useApi';
  14. import {useLocation} from 'sentry/utils/useLocation';
  15. import useOrganization from 'sentry/utils/useOrganization';
  16. import usePageFilters from 'sentry/utils/usePageFilters';
  17. import {useParams} from 'sentry/utils/useParams';
  18. export function fetchTrace(
  19. api: Client,
  20. params: {
  21. orgSlug: string;
  22. query: string;
  23. traceId: string;
  24. }
  25. ): Promise<TraceSplitResults<TraceFullDetailed>> {
  26. return api.requestPromise(
  27. `/organizations/${params.orgSlug}/events-trace/${params.traceId}/?${params.query}`
  28. );
  29. }
  30. const DEFAULT_TIMESTAMP_LIMIT = 10_000;
  31. const DEFAULT_LIMIT = 1_000;
  32. export function getTraceQueryParams(
  33. query: Location['query'],
  34. filters: Partial<PageFilters> = {},
  35. options: {limit?: number} = {}
  36. ) {
  37. const normalizedParams = normalizeDateTimeParams(query, {
  38. allowAbsolutePageDatetime: true,
  39. });
  40. let statsPeriod = decodeScalar(normalizedParams.statsPeriod);
  41. const project = decodeScalar(normalizedParams.project, ALL_ACCESS_PROJECTS + '');
  42. const timestamp = decodeScalar(normalizedParams.timestamp);
  43. let decodedLimit: string | number | undefined =
  44. options.limit ?? decodeScalar(normalizedParams.limit);
  45. if (typeof decodedLimit === 'string') {
  46. decodedLimit = parseInt(decodedLimit, 10);
  47. }
  48. if (timestamp) {
  49. decodedLimit = decodedLimit ?? DEFAULT_TIMESTAMP_LIMIT;
  50. } else {
  51. decodedLimit = decodedLimit ?? DEFAULT_LIMIT;
  52. }
  53. const limit = decodedLimit;
  54. if (statsPeriod === undefined && !timestamp && filters.datetime?.period) {
  55. statsPeriod = filters.datetime.period;
  56. }
  57. return {limit, statsPeriod, project, timestamp, useSpans: 1};
  58. }
  59. type UseTraceParams = {
  60. limit?: number;
  61. };
  62. type RequestState<T> = {
  63. data: T | null;
  64. status: 'resolved' | 'pending' | 'error' | 'initial';
  65. error?: Error | null;
  66. };
  67. const DEFAULT_OPTIONS = {};
  68. export function useTrace(
  69. options: Partial<UseTraceParams> = DEFAULT_OPTIONS
  70. ): RequestState<TraceSplitResults<TraceFullDetailed> | null> {
  71. const api = useApi();
  72. const filters = usePageFilters();
  73. const location = useLocation();
  74. const organization = useOrganization();
  75. const params = useParams<{traceSlug?: string}>();
  76. const [trace, setTrace] = useState<
  77. RequestState<TraceSplitResults<TraceFullDetailed> | null>
  78. >({
  79. status: 'initial',
  80. data: null,
  81. });
  82. const queryParams = useMemo(() => {
  83. return getTraceQueryParams(location.query, filters.selection, options);
  84. // eslint-disable-next-line react-hooks/exhaustive-deps
  85. }, [options]);
  86. useEffect(() => {
  87. if (!params.traceSlug) {
  88. return undefined;
  89. }
  90. let unmounted = false;
  91. setTrace({
  92. status: 'pending',
  93. data: null,
  94. });
  95. fetchTrace(api, {
  96. traceId: params.traceSlug,
  97. orgSlug: organization.slug,
  98. query: qs.stringify(queryParams),
  99. })
  100. .then(resp => {
  101. if (unmounted) return;
  102. setTrace({
  103. status: 'resolved',
  104. data: resp,
  105. });
  106. })
  107. .catch(e => {
  108. if (unmounted) return;
  109. setTrace({
  110. status: 'error',
  111. data: null,
  112. error: e,
  113. });
  114. });
  115. return () => {
  116. unmounted = true;
  117. };
  118. }, [api, organization.slug, params.traceSlug, queryParams]);
  119. return trace;
  120. }