useMetricsQuery.tsx 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. import {useMemo} from 'react';
  2. import type {PageFilters} from 'sentry/types/core';
  3. import {parsePeriodToHours} from 'sentry/utils/dates';
  4. import {getDateTimeParams, getMetricsInterval} from 'sentry/utils/metrics';
  5. import {getUseCaseFromMRI, MRIToField} from 'sentry/utils/metrics/mri';
  6. import {useApiQuery} from 'sentry/utils/queryClient';
  7. import useOrganization from 'sentry/utils/useOrganization';
  8. import type {
  9. MetricsDataIntervalLadder,
  10. MetricsQueryApiResponse,
  11. MRI,
  12. } from '../../types/metrics';
  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. }: {
  72. autoOrder?: boolean;
  73. interval?: string;
  74. intervalLadder?: MetricsDataIntervalLadder;
  75. } = {}
  76. ) {
  77. // We want to use the largest interval from all queries so none fails
  78. // In the future the endpoint should handle this
  79. const interval =
  80. intervalParam ??
  81. queries
  82. .map(query =>
  83. !isMetricFormula(query) ? getQueryInterval(query, datetime, intervalLadder) : '1m'
  84. )
  85. .reduce(
  86. (acc, curr) => (parsePeriodToHours(curr) > parsePeriodToHours(acc) ? curr : acc),
  87. '1m'
  88. );
  89. const requestQueries: {mql: string; name: string}[] = [];
  90. const requestFormulas: {
  91. mql: string;
  92. limit?: number;
  93. name?: string;
  94. order?: 'asc' | 'desc';
  95. }[] = [];
  96. queries.forEach((query, index) => {
  97. if (isMetricFormula(query)) {
  98. requestFormulas.push({
  99. mql: query.formula,
  100. limit: query.limit,
  101. order: query.orderBy,
  102. });
  103. return;
  104. }
  105. const {
  106. mri,
  107. op,
  108. groupBy,
  109. limit,
  110. orderBy,
  111. query: queryParam,
  112. name: nameParam,
  113. isQueryOnly,
  114. } = query;
  115. const name = nameParam || `query_${index + 1}`;
  116. const hasGroupBy = groupBy && groupBy.length > 0;
  117. requestQueries.push({
  118. name,
  119. mql: createMqlQuery({
  120. field: MRIToField(mri, op),
  121. query: queryParam,
  122. groupBy,
  123. }),
  124. });
  125. if (!isQueryOnly) {
  126. requestFormulas.push({
  127. mql: `$${name}`,
  128. limit,
  129. order: hasGroupBy ? orderBy : undefined,
  130. });
  131. }
  132. });
  133. return {
  134. query: {
  135. ...getDateTimeParams(datetime),
  136. project: projects,
  137. environment: environments,
  138. interval,
  139. },
  140. body: {
  141. queries: requestQueries,
  142. formulas: requestFormulas,
  143. },
  144. };
  145. }
  146. export function useMetricsQuery(
  147. queries: MetricsQueryApiQueryParams[],
  148. {projects, environments, datetime}: PageFilters,
  149. overrides: {interval?: string; intervalLadder?: MetricsDataIntervalLadder} = {},
  150. enableRefetch = true
  151. ) {
  152. const organization = useOrganization();
  153. const {query: queryToSend, body} = useMemo(
  154. () =>
  155. getMetricsQueryApiRequestPayload(
  156. queries,
  157. {datetime, projects, environments},
  158. {...overrides}
  159. ),
  160. [queries, datetime, projects, environments, overrides]
  161. );
  162. return useApiQuery<MetricsQueryApiResponse>(
  163. [
  164. `/organizations/${organization.slug}/metrics/query/`,
  165. {query: queryToSend, data: body, method: 'POST'},
  166. ],
  167. {
  168. retry: 0,
  169. staleTime: 0,
  170. refetchOnReconnect: enableRefetch,
  171. refetchOnWindowFocus: enableRefetch,
  172. refetchInterval: false,
  173. }
  174. );
  175. }