useSpansQuery.tsx 5.8 KB

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