useSpansQuery.tsx 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256
  1. import {useQuery} from '@tanstack/react-query';
  2. import moment from 'moment';
  3. import {useDiscoverQuery} from 'sentry/utils/discover/discoverQuery';
  4. import EventView, {encodeSort} from 'sentry/utils/discover/eventView';
  5. import {
  6. DiscoverQueryProps,
  7. useGenericDiscoverQuery,
  8. } from 'sentry/utils/discover/genericDiscoverQuery';
  9. import {useLocation} from 'sentry/utils/useLocation';
  10. import useOrganization from 'sentry/utils/useOrganization';
  11. import {HOST} from 'sentry/views/starfish/utils/constants';
  12. import {useStarfishOptions} from 'sentry/views/starfish/utils/useStarfishOptions';
  13. const DATE_FORMAT = 'YYYY-MM-DDTHH:mm:ss';
  14. // Setting return type since I'd rather not know if its discover query or not
  15. export type UseSpansQueryReturnType<T> = {data: T; isLoading: boolean};
  16. export function useSpansQuery<T = any[]>({
  17. eventView,
  18. queryString,
  19. initialData,
  20. limit,
  21. forceUseDiscover,
  22. enabled,
  23. }: {
  24. enabled?: boolean;
  25. eventView?: EventView;
  26. forceUseDiscover?: boolean;
  27. initialData?: any;
  28. limit?: number;
  29. queryString?: string;
  30. }): UseSpansQueryReturnType<T> {
  31. const {options} = useStarfishOptions();
  32. const {useDiscover} = options;
  33. const queryFunction = getQueryFunction({
  34. useDiscover: forceUseDiscover ?? useDiscover,
  35. isTimeseriesQuery: (eventView?.yAxis?.length ?? 0) > 0,
  36. });
  37. if (isDiscoverFunction(queryFunction) || isDiscoverTimeseriesFunction(queryFunction)) {
  38. if (eventView) {
  39. return queryFunction({eventView, initialData, limit, enabled});
  40. }
  41. throw new Error(
  42. 'eventView argument must be defined when Starfish useDiscover is true'
  43. );
  44. }
  45. if (queryString) {
  46. return queryFunction({queryString, initialData, enabled});
  47. }
  48. throw new Error(
  49. 'queryString argument must be defined when Starfish useDiscover is false, ie when using scraped data via fetch API'
  50. );
  51. }
  52. function isDiscoverFunction(
  53. queryFunction: Function
  54. ): queryFunction is typeof useWrappedDiscoverQuery {
  55. return queryFunction === useWrappedDiscoverQuery;
  56. }
  57. function isDiscoverTimeseriesFunction(
  58. queryFunction: Function
  59. ): queryFunction is typeof useWrappedDiscoverTimeseriesQuery {
  60. return queryFunction === useWrappedDiscoverTimeseriesQuery;
  61. }
  62. export function useWrappedQuery({
  63. queryString,
  64. initialData,
  65. enabled,
  66. }: {
  67. queryString: string;
  68. enabled?: boolean;
  69. initialData?: any;
  70. }) {
  71. const {isLoading, data} = useQuery({
  72. queryKey: [queryString],
  73. queryFn: () => fetch(`${HOST}/?query=${queryString}`).then(res => res.json()),
  74. retry: false,
  75. initialData,
  76. enabled,
  77. refetchOnWindowFocus: false,
  78. });
  79. return {isLoading, data};
  80. }
  81. export function useWrappedDiscoverTimeseriesQuery({
  82. eventView,
  83. enabled,
  84. initialData,
  85. referrer,
  86. }: {
  87. eventView: EventView;
  88. enabled?: boolean;
  89. initialData?: any;
  90. referrer?: string;
  91. }) {
  92. const location = useLocation();
  93. const organization = useOrganization();
  94. const {isLoading, data} = useGenericDiscoverQuery<
  95. {
  96. data: any[];
  97. },
  98. DiscoverQueryProps
  99. >({
  100. route: 'events-stats',
  101. eventView,
  102. location,
  103. orgSlug: organization.slug,
  104. getRequestPayload: () => ({
  105. ...eventView.getEventsAPIPayload(location),
  106. yAxis: eventView.yAxis,
  107. topEvents: eventView.topEvents,
  108. excludeOther: 0,
  109. partial: 1,
  110. orderby: eventView.sorts?.[0] ? encodeSort(eventView.sorts?.[0]) : undefined,
  111. interval: eventView.interval,
  112. }),
  113. options: {
  114. enabled,
  115. refetchOnWindowFocus: false,
  116. },
  117. referrer,
  118. });
  119. return {
  120. isLoading,
  121. data:
  122. isLoading && initialData
  123. ? initialData
  124. : processDiscoverTimeseriesResult(data, eventView),
  125. };
  126. }
  127. export function useWrappedDiscoverQuery({
  128. eventView,
  129. initialData,
  130. referrer,
  131. limit,
  132. }: {
  133. eventView: EventView;
  134. initialData?: any;
  135. limit?: number;
  136. referrer?: string;
  137. }) {
  138. const location = useLocation();
  139. const organization = useOrganization();
  140. const {isLoading, data} = useDiscoverQuery({
  141. eventView,
  142. orgSlug: organization.slug,
  143. location,
  144. referrer,
  145. limit,
  146. });
  147. return {isLoading, data: isLoading && initialData ? initialData : data?.data};
  148. }
  149. function getQueryFunction({
  150. useDiscover,
  151. isTimeseriesQuery,
  152. }: {
  153. useDiscover: boolean;
  154. isTimeseriesQuery?: boolean;
  155. }) {
  156. if (useDiscover) {
  157. if (isTimeseriesQuery) {
  158. return useWrappedDiscoverTimeseriesQuery;
  159. }
  160. return useWrappedDiscoverQuery;
  161. }
  162. return useWrappedQuery;
  163. }
  164. type Interval = {[key: string]: any; interval: string; group?: string};
  165. function processDiscoverTimeseriesResult(result, eventView: EventView) {
  166. if (!eventView.yAxis) {
  167. return [];
  168. }
  169. let intervals = [] as Interval[];
  170. const singleYAxis =
  171. eventView.yAxis &&
  172. (typeof eventView.yAxis === 'string' || eventView.yAxis.length === 1);
  173. const firstYAxis =
  174. typeof eventView.yAxis === 'string' ? eventView.yAxis : eventView.yAxis[0];
  175. if (result.data) {
  176. return processSingleDiscoverTimeseriesResult(
  177. result,
  178. singleYAxis ? firstYAxis : 'count'
  179. );
  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. }