useTraceItemDetails.tsx 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. import {useHover} from '@react-aria/interactions';
  2. import {captureException} from '@sentry/react';
  3. import type {DiscoverDatasets} from 'sentry/utils/discover/types';
  4. import {
  5. type ApiQueryKey,
  6. fetchDataQuery,
  7. useApiQuery,
  8. useQueryClient,
  9. } from 'sentry/utils/queryClient';
  10. import useOrganization from 'sentry/utils/useOrganization';
  11. import usePageFilters from 'sentry/utils/usePageFilters';
  12. import useProjectFromId from 'sentry/utils/useProjectFromId';
  13. import {
  14. getRetryDelay,
  15. shouldRetryHandler,
  16. } from 'sentry/views/insights/common/utils/retryHandlers';
  17. const DEFAULT_HOVER_TIMEOUT = 200;
  18. /**
  19. * ProjectTraceItemDetailsEndpoint currently only supports ourlogs dataset
  20. * TODO: Add SPANS_EAP once the backend supports it.
  21. */
  22. type EAPDataset = DiscoverDatasets.OURLOGS;
  23. export interface UseTraceItemDetailsProps {
  24. /**
  25. * Trace items are only supported by EAP.
  26. */
  27. dataset: EAPDataset;
  28. /**
  29. * Every trace item belongs to a project.
  30. */
  31. projectId: string;
  32. /**
  33. * Sets referrer parameter in the API Payload. Set of allowed referrers are defined
  34. * as ALLOWED_EVENTS_REFERRERS on the backend.
  35. */
  36. referrer: string;
  37. /**
  38. * The trace item ID representing an EAP trace item.
  39. */
  40. traceItemId: string;
  41. /**
  42. * Alias for `enabled` in react-query.
  43. */
  44. enabled?: boolean;
  45. }
  46. export type TraceItemAttributes = Record<string, TraceItemResponseAttribute>;
  47. interface TraceItemDetailsResponse {
  48. attributes: TraceItemAttributes;
  49. itemId: string;
  50. timestamp: string;
  51. }
  52. type TraceItemDetailsUrlParams = {
  53. organizationSlug: string;
  54. projectSlug: string;
  55. traceItemId: string;
  56. };
  57. type TraceItemDetailsQueryParams = {
  58. dataset: EAPDataset;
  59. referrer: string;
  60. };
  61. export type TraceItemResponseAttribute =
  62. | {type: 'str'; value: string}
  63. | {type: 'int'; value: number}
  64. | {type: 'float'; value: number}
  65. | {type: 'bool'; value: boolean};
  66. /**
  67. * Query hook fetching trace item details in EAP.
  68. */
  69. export function useTraceItemDetails(props: UseTraceItemDetailsProps) {
  70. const organization = useOrganization();
  71. const project = useProjectFromId({project_id: props.projectId});
  72. const {isReady: pageFiltersReady} = usePageFilters();
  73. const enabled = pageFiltersReady && (props.enabled ?? true) && !!project;
  74. if (!project) {
  75. captureException(
  76. new Error(`Project "${props.projectId}" not found in useTraceItemDetails`)
  77. );
  78. }
  79. const queryParams: TraceItemDetailsQueryParams = {
  80. referrer: props.referrer,
  81. dataset: props.dataset,
  82. };
  83. const result = useApiQuery<TraceItemDetailsResponse>(
  84. traceItemDetailsQueryKey({
  85. urlParams: {
  86. organizationSlug: organization.slug,
  87. projectSlug: project?.slug ?? '',
  88. traceItemId: props.traceItemId,
  89. },
  90. queryParams,
  91. }),
  92. {
  93. enabled: enabled && pageFiltersReady,
  94. retry: shouldRetryHandler,
  95. retryDelay: getRetryDelay,
  96. staleTime: Infinity,
  97. }
  98. );
  99. return result;
  100. }
  101. function traceItemDetailsQueryKey({
  102. urlParams,
  103. queryParams,
  104. }: {
  105. queryParams: TraceItemDetailsQueryParams;
  106. urlParams: TraceItemDetailsUrlParams;
  107. }): ApiQueryKey {
  108. const query: Record<string, string | string[]> = {
  109. dataset: queryParams.dataset,
  110. referrer: queryParams.referrer,
  111. };
  112. return [
  113. `/projects/${urlParams.organizationSlug}/${urlParams.projectSlug}/trace-items/${urlParams.traceItemId}/`,
  114. {query},
  115. ];
  116. }
  117. export function usePrefetchTraceItemDetailsOnHover({
  118. traceItemId,
  119. projectId,
  120. dataset,
  121. referrer,
  122. hoverPrefetchDisabled,
  123. sharedHoverTimeoutRef,
  124. }: UseTraceItemDetailsProps & {
  125. /**
  126. * A ref to a shared timeout so multiple hover events can be handled
  127. * without creating multiple timeouts and firing multiple prefetches.
  128. */
  129. sharedHoverTimeoutRef: React.MutableRefObject<NodeJS.Timeout | null>;
  130. /**
  131. * Whether the hover prefetch should be disabled.
  132. */
  133. hoverPrefetchDisabled?: boolean;
  134. }) {
  135. const organization = useOrganization();
  136. const project = useProjectFromId({project_id: projectId});
  137. const queryClient = useQueryClient();
  138. const {hoverProps} = useHover({
  139. onHoverStart: () => {
  140. if (sharedHoverTimeoutRef.current) {
  141. clearTimeout(sharedHoverTimeoutRef.current);
  142. }
  143. sharedHoverTimeoutRef.current = setTimeout(() => {
  144. queryClient.prefetchQuery({
  145. queryKey: traceItemDetailsQueryKey({
  146. urlParams: {
  147. organizationSlug: organization.slug,
  148. projectSlug: project?.slug ?? '',
  149. traceItemId,
  150. },
  151. queryParams: {
  152. dataset,
  153. referrer,
  154. },
  155. }),
  156. queryFn: fetchDataQuery,
  157. staleTime: 30_000,
  158. });
  159. }, DEFAULT_HOVER_TIMEOUT);
  160. },
  161. onHoverEnd: () => {
  162. if (sharedHoverTimeoutRef.current) {
  163. clearTimeout(sharedHoverTimeoutRef.current);
  164. }
  165. },
  166. isDisabled: hoverPrefetchDisabled,
  167. });
  168. return hoverProps;
  169. }