useMetricsQuery.tsx 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. import {useMemo} from 'react';
  2. import type {PageFilters} from 'sentry/types/core';
  3. import {getDateTimeParams, getMetricsInterval} from 'sentry/utils/metrics';
  4. import {getUseCaseFromMRI, MRIToField} from 'sentry/utils/metrics/mri';
  5. import {useApiQuery} from 'sentry/utils/queryClient';
  6. import useOrganization from 'sentry/utils/useOrganization';
  7. import type {
  8. MetricsDataIntervalLadder,
  9. MetricsQueryApiResponse,
  10. MRI,
  11. } from '../../types/metrics';
  12. import {parsePeriodToHours} from '../duration/parsePeriodToHours';
  13. export function createMqlQuery({
  14. field,
  15. query,
  16. groupBy = [],
  17. }: {
  18. field: string;
  19. groupBy?: string[];
  20. query?: string;
  21. }) {
  22. let mql = field;
  23. if (query) {
  24. mql = `${mql}{${query}}`;
  25. }
  26. if (groupBy.length) {
  27. mql = `${mql} by (${groupBy.join(',')})`;
  28. }
  29. return mql;
  30. }
  31. export interface MetricsQueryApiRequestQuery {
  32. mri: MRI;
  33. name: string;
  34. op: string;
  35. alias?: string;
  36. groupBy?: string[];
  37. isQueryOnly?: boolean;
  38. limit?: number;
  39. orderBy?: 'asc' | 'desc';
  40. query?: string;
  41. }
  42. export interface MetricsQueryApiRequestFormula {
  43. formula: string;
  44. name: string;
  45. alias?: string;
  46. limit?: number;
  47. orderBy?: 'asc' | 'desc';
  48. }
  49. export type MetricsQueryApiQueryParams =
  50. | MetricsQueryApiRequestQuery
  51. | MetricsQueryApiRequestFormula;
  52. const getQueryInterval = (
  53. query: MetricsQueryApiRequestQuery,
  54. datetime: PageFilters['datetime'],
  55. intervalLadder?: MetricsDataIntervalLadder
  56. ) => {
  57. const useCase = getUseCaseFromMRI(query.mri) ?? 'custom';
  58. return getMetricsInterval(datetime, useCase, intervalLadder);
  59. };
  60. export function isMetricFormula(
  61. queryEntry: MetricsQueryApiQueryParams
  62. ): queryEntry is MetricsQueryApiRequestFormula {
  63. return 'formula' in queryEntry;
  64. }
  65. export function getMetricsQueryApiRequestPayload(
  66. queries: (MetricsQueryApiRequestQuery | MetricsQueryApiRequestFormula)[],
  67. {projects, environments, datetime}: PageFilters,
  68. {
  69. intervalLadder,
  70. interval: intervalParam,
  71. includeSeries = true,
  72. }: {
  73. includeSeries?: boolean;
  74. interval?: string;
  75. intervalLadder?: MetricsDataIntervalLadder;
  76. } = {}
  77. ) {
  78. // We want to use the largest interval from all queries so none fails
  79. // In the future the endpoint should handle this
  80. const interval =
  81. intervalParam ??
  82. queries
  83. .map(query =>
  84. !isMetricFormula(query) ? getQueryInterval(query, datetime, intervalLadder) : '1m'
  85. )
  86. .reduce(
  87. (acc, curr) => (parsePeriodToHours(curr) > parsePeriodToHours(acc) ? curr : acc),
  88. '1m'
  89. );
  90. const requestQueries: {mql: string; name: string}[] = [];
  91. const requestFormulas: {
  92. mql: string;
  93. limit?: number;
  94. name?: string;
  95. order?: 'asc' | 'desc';
  96. }[] = [];
  97. queries.forEach((query, index) => {
  98. if (isMetricFormula(query)) {
  99. requestFormulas.push({
  100. mql: query.formula,
  101. limit: query.limit,
  102. order: query.orderBy,
  103. });
  104. return;
  105. }
  106. const {
  107. mri,
  108. op,
  109. groupBy,
  110. limit,
  111. orderBy,
  112. query: queryParam,
  113. name: nameParam,
  114. isQueryOnly,
  115. } = query;
  116. const name = nameParam || `query_${index + 1}`;
  117. const hasGroupBy = groupBy && groupBy.length > 0;
  118. requestQueries.push({
  119. name,
  120. mql: createMqlQuery({
  121. field: MRIToField(mri, op),
  122. query: queryParam,
  123. groupBy,
  124. }),
  125. });
  126. if (!isQueryOnly) {
  127. requestFormulas.push({
  128. mql: `$${name}`,
  129. limit,
  130. order: hasGroupBy ? orderBy : undefined,
  131. });
  132. }
  133. });
  134. return {
  135. query: {
  136. ...getDateTimeParams(datetime),
  137. project: projects,
  138. environment: environments,
  139. interval,
  140. includeSeries,
  141. },
  142. body: {
  143. queries: requestQueries,
  144. formulas: requestFormulas,
  145. },
  146. };
  147. }
  148. export function useMetricsQuery(
  149. queries: MetricsQueryApiQueryParams[],
  150. {projects, environments, datetime}: PageFilters,
  151. overrides: {
  152. includeSeries?: boolean;
  153. interval?: string;
  154. intervalLadder?: MetricsDataIntervalLadder;
  155. } = {},
  156. enableRefetch = true
  157. ) {
  158. const organization = useOrganization();
  159. const {query: queryToSend, body} = useMemo(
  160. () =>
  161. getMetricsQueryApiRequestPayload(
  162. queries,
  163. {datetime, projects, environments},
  164. {...overrides}
  165. ),
  166. [queries, datetime, projects, environments, overrides]
  167. );
  168. return useApiQuery<MetricsQueryApiResponse>(
  169. [
  170. `/organizations/${organization.slug}/metrics/query/`,
  171. {query: queryToSend, data: body, method: 'POST'},
  172. ],
  173. {
  174. retry: 0,
  175. staleTime: 0,
  176. refetchOnReconnect: enableRefetch,
  177. refetchOnWindowFocus: enableRefetch,
  178. refetchInterval: false,
  179. }
  180. );
  181. }