useSpansQuery.tsx 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  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. forceUseDiscover,
  21. enabled,
  22. }: {
  23. enabled?: boolean;
  24. eventView?: EventView;
  25. forceUseDiscover?: boolean;
  26. initialData?: any;
  27. queryString?: string;
  28. }): UseSpansQueryReturnType<T> {
  29. const {options} = useStarfishOptions();
  30. const {useDiscover} = options;
  31. const queryFunction = getQueryFunction({
  32. useDiscover: forceUseDiscover ?? useDiscover,
  33. isTimeseriesQuery: (eventView?.yAxis?.length ?? 0) > 0,
  34. });
  35. if (isDiscoverFunction(queryFunction) || isDiscoverTimeseriesFunction(queryFunction)) {
  36. if (eventView) {
  37. return queryFunction({eventView, initialData, enabled});
  38. }
  39. throw new Error(
  40. 'eventView argument must be defined when Starfish useDiscover is true'
  41. );
  42. }
  43. if (queryString) {
  44. return queryFunction({queryString, initialData, enabled});
  45. }
  46. throw new Error(
  47. 'queryString argument must be defined when Starfish useDiscover is false, ie when using scraped data via fetch API'
  48. );
  49. }
  50. function isDiscoverFunction(
  51. queryFunction: Function
  52. ): queryFunction is typeof useWrappedDiscoverQuery {
  53. return queryFunction === useWrappedDiscoverQuery;
  54. }
  55. function isDiscoverTimeseriesFunction(
  56. queryFunction: Function
  57. ): queryFunction is typeof useWrappedDiscoverTimeseriesQuery {
  58. return queryFunction === useWrappedDiscoverTimeseriesQuery;
  59. }
  60. export function useWrappedQuery({
  61. queryString,
  62. initialData,
  63. enabled,
  64. }: {
  65. queryString: string;
  66. enabled?: boolean;
  67. initialData?: any;
  68. }) {
  69. const {isLoading, data} = useQuery({
  70. queryKey: [queryString],
  71. queryFn: () => fetch(`${HOST}/?query=${queryString}`).then(res => res.json()),
  72. retry: false,
  73. initialData,
  74. enabled,
  75. refetchOnWindowFocus: false,
  76. });
  77. return {isLoading, data};
  78. }
  79. export function useWrappedDiscoverTimeseriesQuery({
  80. eventView,
  81. enabled,
  82. initialData,
  83. referrer,
  84. }: {
  85. eventView: EventView;
  86. enabled?: boolean;
  87. initialData?: any;
  88. referrer?: string;
  89. }) {
  90. const location = useLocation();
  91. const organization = useOrganization();
  92. const {isLoading, data} = useGenericDiscoverQuery<
  93. {
  94. data: any[];
  95. },
  96. DiscoverQueryProps
  97. >({
  98. route: 'events-stats',
  99. eventView,
  100. location,
  101. orgSlug: organization.slug,
  102. getRequestPayload: () => ({
  103. ...eventView.getEventsAPIPayload(location),
  104. yAxis: eventView.yAxis,
  105. topEvents: eventView.topEvents,
  106. excludeOther: 1,
  107. partial: 1,
  108. orderby: eventView.sorts?.[0] ? encodeSort(eventView.sorts?.[0]) : undefined,
  109. interval: eventView.interval,
  110. }),
  111. options: {
  112. enabled,
  113. refetchOnWindowFocus: false,
  114. },
  115. referrer,
  116. });
  117. return {
  118. isLoading,
  119. data:
  120. isLoading && initialData
  121. ? initialData
  122. : processDiscoverTimeseriesResult(data, eventView),
  123. };
  124. }
  125. export function useWrappedDiscoverQuery({
  126. eventView,
  127. initialData,
  128. referrer,
  129. }: {
  130. eventView: EventView;
  131. initialData?: any;
  132. referrer?: string;
  133. }) {
  134. const location = useLocation();
  135. const organization = useOrganization();
  136. const {isLoading, data} = useDiscoverQuery({
  137. eventView,
  138. orgSlug: organization.slug,
  139. location,
  140. referrer,
  141. });
  142. return {isLoading, data: isLoading && initialData ? initialData : data?.data};
  143. }
  144. function getQueryFunction({
  145. useDiscover,
  146. isTimeseriesQuery,
  147. }: {
  148. useDiscover: boolean;
  149. isTimeseriesQuery?: boolean;
  150. }) {
  151. if (useDiscover) {
  152. if (isTimeseriesQuery) {
  153. return useWrappedDiscoverTimeseriesQuery;
  154. }
  155. return useWrappedDiscoverQuery;
  156. }
  157. return useWrappedQuery;
  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. return processSingleDiscoverTimeseriesResult(
  172. result,
  173. singleYAxis ? firstYAxis : 'count'
  174. );
  175. }
  176. Object.keys(result).forEach(key => {
  177. if (result[key].data) {
  178. intervals = mergeIntervals(
  179. intervals,
  180. processSingleDiscoverTimeseriesResult(result[key], singleYAxis ? firstYAxis : key)
  181. );
  182. } else {
  183. Object.keys(result[key]).forEach(innerKey => {
  184. if (innerKey !== 'order') {
  185. intervals = mergeIntervals(
  186. intervals,
  187. processSingleDiscoverTimeseriesResult(result[key][innerKey], innerKey, key)
  188. );
  189. }
  190. });
  191. }
  192. });
  193. const processed = intervals.map(interval => ({
  194. ...interval,
  195. interval: moment(parseInt(interval.interval, 10) * 1000).format(DATE_FORMAT),
  196. }));
  197. return processed;
  198. }
  199. function processSingleDiscoverTimeseriesResult(result, key: string, group?: string) {
  200. const intervals = [] as Interval[];
  201. result.data.forEach(([timestamp, [{count: value}]]) => {
  202. const existingInterval = intervals.find(
  203. interval =>
  204. interval.interval === timestamp && (group ? interval.group === group : true)
  205. );
  206. if (existingInterval) {
  207. existingInterval[key] = value;
  208. return;
  209. }
  210. intervals.push({
  211. interval: timestamp,
  212. [key]: value,
  213. group,
  214. });
  215. });
  216. return intervals;
  217. }
  218. function mergeIntervals(first: Interval[], second: Interval[]) {
  219. const target: Interval[] = JSON.parse(JSON.stringify(first));
  220. second.forEach(({interval: timestamp, group, ...rest}) => {
  221. const existingInterval = target.find(
  222. interval =>
  223. interval.interval === timestamp && (group ? interval.group === group : true)
  224. );
  225. if (existingInterval) {
  226. Object.keys(rest).forEach(key => {
  227. existingInterval[key] = rest[key];
  228. });
  229. return;
  230. }
  231. target.push({interval: timestamp, group, ...rest});
  232. });
  233. return target;
  234. }