useSpansQuery.tsx 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259
  1. import moment from 'moment';
  2. import {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?: any;
  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 response = queryFunction<T>({
  43. eventView,
  44. initialData,
  45. limit,
  46. // We always want to wait until the pageFilters are ready to prevent clobbering requests
  47. enabled: (enabled || enabled === undefined) && pageFiltersReady,
  48. referrer,
  49. cursor,
  50. });
  51. TrackResponse(eventView, response);
  52. return response;
  53. }
  54. throw new Error('eventView argument must be defined when Starfish useDiscover is true');
  55. }
  56. function useWrappedDiscoverTimeseriesQuery<T>({
  57. eventView,
  58. enabled,
  59. initialData,
  60. referrer,
  61. cursor,
  62. }: {
  63. eventView: EventView;
  64. cursor?: string;
  65. enabled?: boolean;
  66. initialData?: any;
  67. referrer?: string;
  68. }) {
  69. const location = useLocation();
  70. const organization = useOrganization();
  71. const {isReady: pageFiltersReady} = usePageFilters();
  72. const result = useGenericDiscoverQuery<
  73. {
  74. data: any[];
  75. meta: MetaType;
  76. },
  77. DiscoverQueryProps
  78. >({
  79. route: 'events-stats',
  80. eventView,
  81. location,
  82. orgSlug: organization.slug,
  83. getRequestPayload: () => ({
  84. ...eventView.getEventsAPIPayload(location),
  85. yAxis: eventView.yAxis,
  86. topEvents: eventView.topEvents,
  87. excludeOther: 0,
  88. partial: 1,
  89. orderby: eventView.sorts?.[0] ? encodeSort(eventView.sorts?.[0]) : undefined,
  90. interval: eventView.interval,
  91. cursor,
  92. }),
  93. options: {
  94. enabled: enabled && pageFiltersReady,
  95. refetchOnWindowFocus: false,
  96. retry: shouldRetryHandler,
  97. retryDelay: getRetryDelay,
  98. staleTime: Infinity,
  99. },
  100. referrer,
  101. });
  102. const data: T =
  103. result.isLoading && initialData
  104. ? initialData
  105. : processDiscoverTimeseriesResult(result.data, eventView);
  106. return {
  107. ...result,
  108. data,
  109. meta: result.data?.meta,
  110. };
  111. }
  112. export function useWrappedDiscoverQuery<T>({
  113. eventView,
  114. initialData,
  115. enabled,
  116. referrer,
  117. limit,
  118. cursor,
  119. }: {
  120. eventView: EventView;
  121. cursor?: string;
  122. enabled?: boolean;
  123. initialData?: any;
  124. limit?: number;
  125. referrer?: string;
  126. }) {
  127. const location = useLocation();
  128. const organization = useOrganization();
  129. const {isReady: pageFiltersReady} = usePageFilters();
  130. const result = useDiscoverQuery({
  131. eventView,
  132. orgSlug: organization.slug,
  133. location,
  134. referrer,
  135. cursor,
  136. limit,
  137. options: {
  138. enabled: enabled && pageFiltersReady,
  139. refetchOnWindowFocus: false,
  140. retry: shouldRetryHandler,
  141. retryDelay: getRetryDelay,
  142. staleTime: Infinity,
  143. },
  144. });
  145. const meta = result.data?.meta as EventsMetaType | undefined;
  146. if (meta) {
  147. // TODO: Remove this hack when the backend returns `"rate"` as the data
  148. // type for `sps()` and other rate fields!
  149. meta.fields['sps()'] = 'rate';
  150. meta.units['sps()'] = '1/second';
  151. }
  152. const data: T = result.isLoading && initialData ? initialData : result.data?.data;
  153. return {
  154. ...result,
  155. data,
  156. meta, // TODO: useDiscoverQuery incorrectly states that it returns MetaType, but it does not!
  157. };
  158. }
  159. type Interval = {[key: string]: any; interval: string; group?: string};
  160. function processDiscoverTimeseriesResult(result, eventView: EventView) {
  161. if (!eventView.yAxis) {
  162. return [];
  163. }
  164. let intervals = [] as Interval[];
  165. const singleYAxis =
  166. eventView.yAxis &&
  167. (typeof eventView.yAxis === 'string' || eventView.yAxis.length === 1);
  168. const firstYAxis =
  169. typeof eventView.yAxis === 'string' ? eventView.yAxis : eventView.yAxis[0];
  170. if (result.data) {
  171. const timeSeriesResult: Interval[] = processSingleDiscoverTimeseriesResult(
  172. result,
  173. singleYAxis ? firstYAxis : 'count'
  174. ).map(data => ({
  175. interval: moment(parseInt(data.interval, 10) * 1000).format(DATE_FORMAT),
  176. [firstYAxis]: data[firstYAxis],
  177. group: data.group,
  178. }));
  179. return timeSeriesResult;
  180. }
  181. Object.keys(result).forEach(key => {
  182. if (result[key].data) {
  183. intervals = mergeIntervals(
  184. intervals,
  185. processSingleDiscoverTimeseriesResult(result[key], singleYAxis ? firstYAxis : key)
  186. );
  187. } else {
  188. Object.keys(result[key]).forEach(innerKey => {
  189. if (innerKey !== 'order') {
  190. intervals = mergeIntervals(
  191. intervals,
  192. processSingleDiscoverTimeseriesResult(result[key][innerKey], innerKey, key)
  193. );
  194. }
  195. });
  196. }
  197. });
  198. const processed = intervals.map(interval => ({
  199. ...interval,
  200. interval: moment(parseInt(interval.interval, 10) * 1000).format(DATE_FORMAT),
  201. }));
  202. return processed;
  203. }
  204. function processSingleDiscoverTimeseriesResult(result, key: string, group?: string) {
  205. const intervals = [] as Interval[];
  206. result.data.forEach(([timestamp, [{count: value}]]) => {
  207. const existingInterval = intervals.find(
  208. interval =>
  209. interval.interval === timestamp && (group ? interval.group === group : true)
  210. );
  211. if (existingInterval) {
  212. existingInterval[key] = value;
  213. return;
  214. }
  215. intervals.push({
  216. interval: timestamp,
  217. [key]: value,
  218. group,
  219. });
  220. });
  221. return intervals;
  222. }
  223. function mergeIntervals(first: Interval[], second: Interval[]) {
  224. const target: Interval[] = JSON.parse(JSON.stringify(first));
  225. second.forEach(({interval: timestamp, group, ...rest}) => {
  226. const existingInterval = target.find(
  227. interval =>
  228. interval.interval === timestamp && (group ? interval.group === group : true)
  229. );
  230. if (existingInterval) {
  231. Object.keys(rest).forEach(key => {
  232. existingInterval[key] = rest[key];
  233. });
  234. return;
  235. }
  236. target.push({interval: timestamp, group, ...rest});
  237. });
  238. return target;
  239. }