useTraces.tsx 4.0 KB

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