useSpansQuery.tsx 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  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 {TrackResponse} from 'sentry/views/starfish/utils/trackResponse';
  15. export const DATE_FORMAT = 'YYYY-MM-DDTHH:mm:ssZ';
  16. export function useSpansQuery<T = any[]>({
  17. eventView,
  18. initialData,
  19. limit,
  20. enabled,
  21. referrer = 'use-spans-query',
  22. cursor,
  23. }: {
  24. cursor?: string;
  25. enabled?: boolean;
  26. eventView?: EventView;
  27. initialData?: any;
  28. limit?: number;
  29. referrer?: string;
  30. }) {
  31. const isTimeseriesQuery = (eventView?.yAxis?.length ?? 0) > 0;
  32. const queryFunction = isTimeseriesQuery
  33. ? useWrappedDiscoverTimeseriesQuery
  34. : useWrappedDiscoverQuery;
  35. if (eventView) {
  36. const response = queryFunction<T>({
  37. eventView,
  38. initialData,
  39. limit,
  40. enabled,
  41. referrer,
  42. cursor,
  43. });
  44. TrackResponse(eventView, response);
  45. return response;
  46. }
  47. throw new Error('eventView argument must be defined when Starfish useDiscover is true');
  48. }
  49. export function useWrappedDiscoverTimeseriesQuery<T>({
  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 result = 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. const data: T =
  92. result.isLoading && initialData
  93. ? initialData
  94. : processDiscoverTimeseriesResult(result.data, eventView);
  95. return {
  96. ...result,
  97. data,
  98. meta: result.data?.meta,
  99. };
  100. }
  101. export function useWrappedDiscoverQuery<T>({
  102. eventView,
  103. initialData,
  104. enabled,
  105. referrer,
  106. limit,
  107. cursor,
  108. }: {
  109. eventView: EventView;
  110. cursor?: string;
  111. enabled?: boolean;
  112. initialData?: any;
  113. limit?: number;
  114. referrer?: string;
  115. }) {
  116. const location = useLocation();
  117. const organization = useOrganization();
  118. const result = useDiscoverQuery({
  119. eventView,
  120. orgSlug: organization.slug,
  121. location,
  122. referrer,
  123. cursor,
  124. limit,
  125. options: {
  126. enabled,
  127. refetchOnWindowFocus: false,
  128. },
  129. });
  130. const meta = result.data?.meta as EventsMetaType | undefined;
  131. if (meta) {
  132. // TODO: Remove this hack when the backend returns `"rate"` as the data
  133. // type for `sps()` and other rate fields!
  134. meta.fields['sps()'] = 'rate';
  135. meta.units['sps()'] = '1/second';
  136. }
  137. const data: T = result.isLoading && initialData ? initialData : result.data?.data;
  138. return {
  139. ...result,
  140. data,
  141. meta, // TODO: useDiscoverQuery incorrectly states that it returns MetaType, but it does not!
  142. };
  143. }
  144. type Interval = {[key: string]: any; interval: string; group?: string};
  145. function processDiscoverTimeseriesResult(result, eventView: EventView) {
  146. if (!eventView.yAxis) {
  147. return [];
  148. }
  149. let intervals = [] as Interval[];
  150. const singleYAxis =
  151. eventView.yAxis &&
  152. (typeof eventView.yAxis === 'string' || eventView.yAxis.length === 1);
  153. const firstYAxis =
  154. typeof eventView.yAxis === 'string' ? eventView.yAxis : eventView.yAxis[0];
  155. if (result.data) {
  156. const timeSeriesResult: Interval[] = processSingleDiscoverTimeseriesResult(
  157. result,
  158. singleYAxis ? firstYAxis : 'count'
  159. ).map(data => ({
  160. interval: moment(parseInt(data.interval, 10) * 1000).format(DATE_FORMAT),
  161. [firstYAxis]: data[firstYAxis],
  162. group: data.group,
  163. }));
  164. return timeSeriesResult;
  165. }
  166. Object.keys(result).forEach(key => {
  167. if (result[key].data) {
  168. intervals = mergeIntervals(
  169. intervals,
  170. processSingleDiscoverTimeseriesResult(result[key], singleYAxis ? firstYAxis : key)
  171. );
  172. } else {
  173. Object.keys(result[key]).forEach(innerKey => {
  174. if (innerKey !== 'order') {
  175. intervals = mergeIntervals(
  176. intervals,
  177. processSingleDiscoverTimeseriesResult(result[key][innerKey], innerKey, key)
  178. );
  179. }
  180. });
  181. }
  182. });
  183. const processed = intervals.map(interval => ({
  184. ...interval,
  185. interval: moment(parseInt(interval.interval, 10) * 1000).format(DATE_FORMAT),
  186. }));
  187. return processed;
  188. }
  189. function processSingleDiscoverTimeseriesResult(result, key: string, group?: string) {
  190. const intervals = [] as Interval[];
  191. result.data.forEach(([timestamp, [{count: value}]]) => {
  192. const existingInterval = intervals.find(
  193. interval =>
  194. interval.interval === timestamp && (group ? interval.group === group : true)
  195. );
  196. if (existingInterval) {
  197. existingInterval[key] = value;
  198. return;
  199. }
  200. intervals.push({
  201. interval: timestamp,
  202. [key]: value,
  203. group,
  204. });
  205. });
  206. return intervals;
  207. }
  208. function mergeIntervals(first: Interval[], second: Interval[]) {
  209. const target: Interval[] = JSON.parse(JSON.stringify(first));
  210. second.forEach(({interval: timestamp, group, ...rest}) => {
  211. const existingInterval = target.find(
  212. interval =>
  213. interval.interval === timestamp && (group ? interval.group === group : true)
  214. );
  215. if (existingInterval) {
  216. Object.keys(rest).forEach(key => {
  217. existingInterval[key] = rest[key];
  218. });
  219. return;
  220. }
  221. target.push({interval: timestamp, group, ...rest});
  222. });
  223. return target;
  224. }