useTraces.tsx 4.2 KB

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