useTraces.tsx 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  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. query?: string | string[];
  52. }
  53. export function useTraces({datetime, enabled, limit, query}: UseTracesOptions) {
  54. const organization = useOrganization();
  55. const {projects} = useProjects();
  56. const {selection} = usePageFilters();
  57. const path = `/organizations/${organization.slug}/traces/`;
  58. const endpointOptions = {
  59. query: {
  60. project: selection.projects,
  61. environment: selection.environments,
  62. ...normalizeDateTimeParams(datetime ?? selection.datetime),
  63. query,
  64. per_page: limit,
  65. breakdownSlices: BREAKDOWN_SLICES,
  66. },
  67. };
  68. const serializedEndpointOptions = JSON.stringify(endpointOptions);
  69. let queries: string[] = [];
  70. if (Array.isArray(query)) {
  71. queries = query;
  72. } else if (query !== undefined) {
  73. queries = [query];
  74. }
  75. useEffect(() => {
  76. trackAnalytics('trace_explorer.search_request', {
  77. organization,
  78. queries,
  79. });
  80. // `queries` is already included as a dep in serializedEndpointOptions
  81. // eslint-disable-next-line react-hooks/exhaustive-deps
  82. }, [serializedEndpointOptions, organization]);
  83. const result = useApiQuery<TraceResults>([path, endpointOptions], {
  84. staleTime: 0,
  85. refetchOnWindowFocus: false,
  86. retry: false,
  87. enabled,
  88. });
  89. useEffect(() => {
  90. if (result.status === 'success') {
  91. const project_slugs = [...new Set(result.data.data.map(trace => trace.project))];
  92. const project_platforms = projects
  93. .filter(p => project_slugs.includes(p.slug))
  94. .map(p => p.platform ?? '');
  95. trackAnalytics('trace_explorer.search_success', {
  96. organization,
  97. queries,
  98. has_data: result.data.data.length > 0,
  99. num_traces: result.data.data.length,
  100. num_missing_trace_root: result.data.data.filter(trace => trace.name === null)
  101. .length,
  102. project_platforms,
  103. });
  104. } else if (result.status === 'error') {
  105. const response = result.error.responseJSON;
  106. const error =
  107. typeof response?.detail === 'string'
  108. ? response?.detail
  109. : response?.detail?.message;
  110. trackAnalytics('trace_explorer.search_failure', {
  111. organization,
  112. queries,
  113. error: error ?? '',
  114. });
  115. }
  116. // result.status is tied to result.data. No need to explicitly
  117. // include result.data as an additional dep.
  118. // eslint-disable-next-line react-hooks/exhaustive-deps
  119. }, [serializedEndpointOptions, result.status, organization]);
  120. return result;
  121. }