useTraces.tsx 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. import {useEffect} from 'react';
  2. import {normalizeDateTimeParams} from 'sentry/components/organizations/pageFilters/parse';
  3. import type {PageFilters} from 'sentry/types/core';
  4. import {trackAnalytics} from 'sentry/utils/analytics';
  5. import type {DiscoverDatasets} from 'sentry/utils/discover/types';
  6. import {useApiQuery} from 'sentry/utils/queryClient';
  7. import useOrganization from 'sentry/utils/useOrganization';
  8. import usePageFilters from 'sentry/utils/usePageFilters';
  9. import useProjects from 'sentry/utils/useProjects';
  10. export const BREAKDOWN_SLICES = 40;
  11. interface TraceBreakdownBase {
  12. duration: number; // Contains the accurate duration for display. Start and end may be quantized.
  13. end: number;
  14. opCategory: string | null;
  15. sdkName: string | null;
  16. sliceEnd: number;
  17. sliceStart: number;
  18. sliceWidth: number;
  19. start: number;
  20. }
  21. type TraceBreakdownProject = TraceBreakdownBase & {
  22. kind: 'project';
  23. project: string;
  24. };
  25. type TraceBreakdownMissing = TraceBreakdownBase & {
  26. kind: 'missing';
  27. project: null;
  28. };
  29. export interface TraceResult {
  30. breakdowns: TraceBreakdownResult[];
  31. duration: number;
  32. end: number;
  33. matchingSpans: number;
  34. name: string | null;
  35. numErrors: number;
  36. numOccurrences: number;
  37. numSpans: number;
  38. project: string | null;
  39. rootDuration: number | null;
  40. slices: number;
  41. start: number;
  42. trace: string;
  43. }
  44. export type TraceBreakdownResult = TraceBreakdownProject | TraceBreakdownMissing;
  45. interface TraceResults {
  46. data: TraceResult[];
  47. meta: any;
  48. }
  49. interface UseTracesOptions {
  50. cursor?: string;
  51. dataset?: DiscoverDatasets;
  52. datetime?: PageFilters['datetime'];
  53. enabled?: boolean;
  54. limit?: number;
  55. query?: string | string[];
  56. sort?: 'timestamp' | '-timestamp';
  57. }
  58. export function useTraces({
  59. cursor,
  60. dataset,
  61. datetime,
  62. enabled,
  63. limit,
  64. query,
  65. sort,
  66. }: UseTracesOptions) {
  67. const organization = useOrganization();
  68. const {projects} = useProjects();
  69. const {selection} = usePageFilters();
  70. const path = `/organizations/${organization.slug}/traces/`;
  71. const endpointOptions = {
  72. query: {
  73. project: selection.projects,
  74. environment: selection.environments,
  75. ...normalizeDateTimeParams(datetime ?? selection.datetime),
  76. dataset,
  77. query,
  78. sort, // only has an effect when `dataset` is `EAPSpans`
  79. per_page: limit,
  80. cursor,
  81. breakdownSlices: BREAKDOWN_SLICES,
  82. },
  83. };
  84. const serializedEndpointOptions = JSON.stringify(endpointOptions);
  85. let queries: string[] = [];
  86. if (Array.isArray(query)) {
  87. queries = query;
  88. } else if (query !== undefined) {
  89. queries = [query];
  90. }
  91. useEffect(() => {
  92. trackAnalytics('trace_explorer.search_request', {
  93. organization,
  94. queries,
  95. });
  96. // `queries` is already included as a dep in serializedEndpointOptions
  97. // eslint-disable-next-line react-hooks/exhaustive-deps
  98. }, [serializedEndpointOptions, organization]);
  99. const result = useApiQuery<TraceResults>([path, endpointOptions], {
  100. staleTime: 0,
  101. refetchOnWindowFocus: false,
  102. refetchOnMount: false,
  103. retry: false,
  104. enabled,
  105. });
  106. useEffect(() => {
  107. if (result.status === 'success') {
  108. const project_slugs = [...new Set(result.data.data.map(trace => trace.project))];
  109. const project_platforms = projects
  110. .filter(p => project_slugs.includes(p.slug))
  111. .map(p => p.platform ?? '');
  112. trackAnalytics('trace_explorer.search_success', {
  113. organization,
  114. queries,
  115. has_data: result.data.data.length > 0,
  116. num_traces: result.data.data.length,
  117. num_missing_trace_root: result.data.data.filter(trace => trace.name === null)
  118. .length,
  119. project_platforms,
  120. });
  121. } else if (result.status === 'error') {
  122. const response = result.error.responseJSON;
  123. const error =
  124. typeof response?.detail === 'string'
  125. ? response?.detail
  126. : response?.detail?.message;
  127. trackAnalytics('trace_explorer.search_failure', {
  128. organization,
  129. queries,
  130. error: error ?? '',
  131. });
  132. }
  133. // result.status is tied to result.data. No need to explicitly
  134. // include result.data as an additional dep.
  135. // eslint-disable-next-line react-hooks/exhaustive-deps
  136. }, [serializedEndpointOptions, result.status, organization]);
  137. return result;
  138. }