useSpansQuery.tsx 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. import moment from 'moment';
  2. import {TableData, useDiscoverQuery} from 'sentry/utils/discover/discoverQuery';
  3. import EventView, {
  4. encodeSort,
  5. EventsMetaType,
  6. MetaType,
  7. } from 'sentry/utils/discover/eventView';
  8. import {
  9. DiscoverQueryProps,
  10. useGenericDiscoverQuery,
  11. } from 'sentry/utils/discover/genericDiscoverQuery';
  12. import {useLocation} from 'sentry/utils/useLocation';
  13. import useOrganization from 'sentry/utils/useOrganization';
  14. import usePageFilters from 'sentry/utils/usePageFilters';
  15. import {
  16. getRetryDelay,
  17. shouldRetryHandler,
  18. } from 'sentry/views/starfish/utils/retryHandlers';
  19. import {TrackResponse} from 'sentry/views/starfish/utils/trackResponse';
  20. export const DATE_FORMAT = 'YYYY-MM-DDTHH:mm:ssZ';
  21. export function useSpansQuery<T = any[]>({
  22. eventView,
  23. initialData,
  24. limit,
  25. enabled,
  26. referrer = 'use-spans-query',
  27. cursor,
  28. }: {
  29. cursor?: string;
  30. enabled?: boolean;
  31. eventView?: EventView;
  32. initialData?: T;
  33. limit?: number;
  34. referrer?: string;
  35. }) {
  36. const isTimeseriesQuery = (eventView?.yAxis?.length ?? 0) > 0;
  37. const queryFunction = isTimeseriesQuery
  38. ? useWrappedDiscoverTimeseriesQuery
  39. : useWrappedDiscoverQuery;
  40. const {isReady: pageFiltersReady} = usePageFilters();
  41. if (eventView) {
  42. const newEventView = eventView.clone();
  43. const response = queryFunction<T>({
  44. eventView: newEventView,
  45. initialData,
  46. limit,
  47. // We always want to wait until the pageFilters are ready to prevent clobbering requests
  48. enabled: (enabled || enabled === undefined) && pageFiltersReady,
  49. referrer,
  50. cursor,
  51. });
  52. TrackResponse(eventView, response);
  53. return response;
  54. }
  55. throw new Error('eventView argument must be defined when Starfish useDiscover is true');
  56. }
  57. function useWrappedDiscoverTimeseriesQuery<T>({
  58. eventView,
  59. enabled,
  60. initialData,
  61. referrer,
  62. cursor,
  63. }: {
  64. eventView: EventView;
  65. cursor?: string;
  66. enabled?: boolean;
  67. initialData?: any;
  68. referrer?: string;
  69. }) {
  70. const location = useLocation();
  71. const organization = useOrganization();
  72. const {isReady: pageFiltersReady} = usePageFilters();
  73. const result = useGenericDiscoverQuery<
  74. {
  75. data: any[];
  76. meta: MetaType;
  77. },
  78. DiscoverQueryProps
  79. >({
  80. route: 'events-stats',
  81. eventView,
  82. location,
  83. orgSlug: organization.slug,
  84. getRequestPayload: () => ({
  85. ...eventView.getEventsAPIPayload(location),
  86. yAxis: eventView.yAxis,
  87. topEvents: eventView.topEvents,
  88. excludeOther: 0,
  89. partial: 1,
  90. orderby: eventView.sorts?.[0] ? encodeSort(eventView.sorts?.[0]) : undefined,
  91. interval: eventView.interval,
  92. cursor,
  93. }),
  94. options: {
  95. enabled: enabled && pageFiltersReady,
  96. refetchOnWindowFocus: false,
  97. retry: shouldRetryHandler,
  98. retryDelay: getRetryDelay,
  99. staleTime: Infinity,
  100. },
  101. referrer,
  102. });
  103. const isFetchingOrLoading = result.isLoading || result.isFetching;
  104. const defaultData = initialData ?? undefined;
  105. const data: T = isFetchingOrLoading
  106. ? defaultData
  107. : processDiscoverTimeseriesResult(result.data, eventView);
  108. const pageLinks = result.response?.getResponseHeader('Link') ?? undefined;
  109. return {
  110. ...result,
  111. pageLinks,
  112. data,
  113. meta: result.data?.meta,
  114. };
  115. }
  116. export function useWrappedDiscoverQuery<T>({
  117. eventView,
  118. initialData,
  119. enabled,
  120. referrer,
  121. limit,
  122. cursor,
  123. }: {
  124. eventView: EventView;
  125. cursor?: string;
  126. enabled?: boolean;
  127. initialData?: T;
  128. limit?: number;
  129. referrer?: string;
  130. }) {
  131. const location = useLocation();
  132. const organization = useOrganization();
  133. const {isReady: pageFiltersReady} = usePageFilters();
  134. const result = useDiscoverQuery({
  135. eventView,
  136. orgSlug: organization.slug,
  137. location,
  138. referrer,
  139. cursor,
  140. limit,
  141. options: {
  142. enabled: enabled && pageFiltersReady,
  143. refetchOnWindowFocus: false,
  144. retry: shouldRetryHandler,
  145. retryDelay: getRetryDelay,
  146. staleTime: Infinity,
  147. },
  148. });
  149. // TODO: useDiscoverQuery incorrectly states that it returns MetaType, but it
  150. // does not!
  151. const meta = result.data?.meta as EventsMetaType | undefined;
  152. const data =
  153. result.isLoading && initialData ? initialData : (result.data?.data as T | undefined);
  154. return {
  155. ...result,
  156. data,
  157. meta,
  158. };
  159. }
  160. type Interval = {interval: string; group?: string};
  161. function processDiscoverTimeseriesResult(
  162. result: TableData | undefined,
  163. eventView: EventView
  164. ) {
  165. if (!result) {
  166. return undefined;
  167. }
  168. if (!eventView.yAxis) {
  169. return [];
  170. }
  171. let intervals = [] as Interval[];
  172. const singleYAxis =
  173. eventView.yAxis &&
  174. (typeof eventView.yAxis === 'string' || eventView.yAxis.length === 1);
  175. const firstYAxis =
  176. typeof eventView.yAxis === 'string' ? eventView.yAxis : eventView.yAxis[0];
  177. if (result.data) {
  178. const timeSeriesResult: Interval[] = processSingleDiscoverTimeseriesResult(
  179. result,
  180. singleYAxis ? firstYAxis : 'count'
  181. ).map(data => ({
  182. interval: moment(parseInt(data.interval, 10) * 1000).format(DATE_FORMAT),
  183. [firstYAxis]: data[firstYAxis],
  184. group: data.group,
  185. }));
  186. return timeSeriesResult;
  187. }
  188. Object.keys(result).forEach(key => {
  189. if (result[key].data) {
  190. intervals = mergeIntervals(
  191. intervals,
  192. processSingleDiscoverTimeseriesResult(result[key], singleYAxis ? firstYAxis : key)
  193. );
  194. } else {
  195. Object.keys(result[key]).forEach(innerKey => {
  196. if (innerKey !== 'order') {
  197. intervals = mergeIntervals(
  198. intervals,
  199. processSingleDiscoverTimeseriesResult(result[key][innerKey], innerKey, key)
  200. );
  201. }
  202. });
  203. }
  204. });
  205. const processed = intervals.map(interval => ({
  206. ...interval,
  207. interval: moment(parseInt(interval.interval, 10) * 1000).format(DATE_FORMAT),
  208. }));
  209. return processed;
  210. }
  211. function processSingleDiscoverTimeseriesResult(result, key: string, group?: string) {
  212. const intervals = [] as Interval[];
  213. result.data.forEach(([timestamp, [{count: value}]]) => {
  214. const existingInterval = intervals.find(
  215. interval =>
  216. interval.interval === timestamp && (group ? interval.group === group : true)
  217. );
  218. if (existingInterval) {
  219. existingInterval[key] = value;
  220. return;
  221. }
  222. intervals.push({
  223. interval: timestamp,
  224. [key]: value,
  225. group,
  226. });
  227. });
  228. return intervals;
  229. }
  230. function mergeIntervals(first: Interval[], second: Interval[]) {
  231. const target: Interval[] = JSON.parse(JSON.stringify(first));
  232. second.forEach(({interval: timestamp, group, ...rest}) => {
  233. const existingInterval = target.find(
  234. interval =>
  235. interval.interval === timestamp && (group ? interval.group === group : true)
  236. );
  237. if (existingInterval) {
  238. Object.keys(rest).forEach(key => {
  239. existingInterval[key] = rest[key];
  240. });
  241. return;
  242. }
  243. target.push({interval: timestamp, group, ...rest});
  244. });
  245. return target;
  246. }