useTraces.tsx 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  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. slices: number;
  40. start: number;
  41. trace: string;
  42. }
  43. export type TraceBreakdownResult = TraceBreakdownProject | TraceBreakdownMissing;
  44. interface TraceResults {
  45. data: TraceResult[];
  46. meta: any;
  47. }
  48. interface UseTracesOptions {
  49. cursor?: string;
  50. dataset?: DiscoverDatasets;
  51. datetime?: PageFilters['datetime'];
  52. enabled?: boolean;
  53. limit?: number;
  54. query?: string | string[];
  55. sort?: 'timestamp' | '-timestamp';
  56. }
  57. export function useTraces({
  58. cursor,
  59. dataset,
  60. datetime,
  61. enabled,
  62. limit,
  63. query,
  64. sort,
  65. }: UseTracesOptions) {
  66. const organization = useOrganization();
  67. const {projects} = useProjects();
  68. const {selection} = usePageFilters();
  69. const path = `/organizations/${organization.slug}/traces/`;
  70. const endpointOptions = {
  71. query: {
  72. project: selection.projects,
  73. environment: selection.environments,
  74. ...normalizeDateTimeParams(datetime ?? selection.datetime),
  75. dataset,
  76. query,
  77. sort, // only has an effect when `dataset` is `EAPSpans`
  78. per_page: limit,
  79. cursor,
  80. breakdownSlices: BREAKDOWN_SLICES,
  81. },
  82. };
  83. const serializedEndpointOptions = JSON.stringify(endpointOptions);
  84. let queries: string[] = [];
  85. if (Array.isArray(query)) {
  86. queries = query;
  87. } else if (query !== undefined) {
  88. queries = [query];
  89. }
  90. useEffect(() => {
  91. trackAnalytics('trace_explorer.search_request', {
  92. organization,
  93. queries,
  94. });
  95. // `queries` is already included as a dep in serializedEndpointOptions
  96. // eslint-disable-next-line react-hooks/exhaustive-deps
  97. }, [serializedEndpointOptions, organization]);
  98. const result = useApiQuery<TraceResults>([path, endpointOptions], {
  99. staleTime: 0,
  100. refetchOnWindowFocus: false,
  101. refetchOnMount: false,
  102. retry: false,
  103. enabled,
  104. });
  105. useEffect(() => {
  106. if (result.status === 'success') {
  107. const project_slugs = [...new Set(result.data.data.map(trace => trace.project))];
  108. const project_platforms = projects
  109. .filter(p => project_slugs.includes(p.slug))
  110. .map(p => p.platform ?? '');
  111. trackAnalytics('trace_explorer.search_success', {
  112. organization,
  113. queries,
  114. has_data: result.data.data.length > 0,
  115. num_traces: result.data.data.length,
  116. num_missing_trace_root: result.data.data.filter(trace => trace.name === null)
  117. .length,
  118. project_platforms,
  119. });
  120. } else if (result.status === 'error') {
  121. const response = result.error.responseJSON;
  122. const error =
  123. typeof response?.detail === 'string'
  124. ? response?.detail
  125. : response?.detail?.message;
  126. trackAnalytics('trace_explorer.search_failure', {
  127. organization,
  128. queries,
  129. error: error ?? '',
  130. });
  131. }
  132. // result.status is tied to result.data. No need to explicitly
  133. // include result.data as an additional dep.
  134. // eslint-disable-next-line react-hooks/exhaustive-deps
  135. }, [serializedEndpointOptions, result.status, organization]);
  136. return result;
  137. }