useMetricsQuery.tsx 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. import {useMemo} from 'react';
  2. import type {PageFilters} from 'sentry/types';
  3. import {getDateTimeParams, getDDMInterval} from 'sentry/utils/metrics';
  4. import {getUseCaseFromMRI, MRIToField, parseField} 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. export function createMqlQuery({
  13. field,
  14. query,
  15. groupBy = [],
  16. }: {field: string; groupBy?: string[]; query?: string}) {
  17. let mql = field;
  18. if (query) {
  19. mql = `${mql}{${query}}`;
  20. }
  21. if (groupBy.length) {
  22. mql = `${mql} by (${groupBy.join(',')})`;
  23. }
  24. return mql;
  25. }
  26. interface MetricsQueryApiRequestQuery {
  27. field: string;
  28. groupBy?: string[];
  29. limit?: number;
  30. name?: string;
  31. orderBy?: 'asc' | 'desc';
  32. query?: string;
  33. }
  34. export function getMetricsQueryApiRequestPayload(
  35. queries: MetricsQueryApiRequestQuery[],
  36. {projects, environments, datetime}: PageFilters,
  37. {
  38. intervalLadder,
  39. interval: intervalParam,
  40. }: {interval?: string; intervalLadder?: MetricsDataIntervalLadder} = {}
  41. ) {
  42. // We take the first queries useCase to determine the interval
  43. // If no useCase is found we default to custom
  44. // The backend will error if the interval is not valid for any of the useCases
  45. const {mri: mri} = parseField(queries[0]?.field) ?? {};
  46. const useCase = getUseCaseFromMRI(mri) ?? 'custom';
  47. const interval = intervalParam ?? getDDMInterval(datetime, useCase, intervalLadder);
  48. const requestQueries: {mql: string; name: string}[] = [];
  49. const requestFormulas: {mql: string; limit?: number; order?: 'asc' | 'desc'}[] = [];
  50. queries.forEach((query, index) => {
  51. const {field, groupBy, limit, orderBy, query: queryParam, name: nameParam} = query;
  52. const name = nameParam || `query_${index + 1}`;
  53. const hasGoupBy = groupBy && groupBy.length > 0;
  54. requestQueries.push({name, mql: createMqlQuery({field, query: queryParam, groupBy})});
  55. requestFormulas.push({
  56. mql: `$${name}`,
  57. limit,
  58. order: hasGoupBy ? orderBy ?? 'desc' : undefined,
  59. });
  60. });
  61. return {
  62. query: {
  63. ...getDateTimeParams(datetime),
  64. project: projects,
  65. environment: environments,
  66. interval,
  67. },
  68. body: {
  69. queries: requestQueries,
  70. formulas: requestFormulas,
  71. },
  72. };
  73. }
  74. export function useMetricsQuery(
  75. queries: (Omit<MetricsQueryApiRequestQuery, 'field'> & {mri: MRI; op?: string})[],
  76. {projects, environments, datetime}: PageFilters,
  77. overrides: {interval?: string; intervalLadder?: MetricsDataIntervalLadder} = {}
  78. ) {
  79. const organization = useOrganization();
  80. const queryIsComplete = queries.every(({op}) => op);
  81. const {query: queryToSend, body} = useMemo(
  82. () =>
  83. getMetricsQueryApiRequestPayload(
  84. queries.map(query => ({...query, field: MRIToField(query.mri, query.op!)})),
  85. {datetime, projects, environments},
  86. {...overrides}
  87. ),
  88. [queries, datetime, projects, environments, overrides]
  89. );
  90. return useApiQuery<MetricsQueryApiResponse>(
  91. [
  92. `/organizations/${organization.slug}/metrics/query/`,
  93. {query: queryToSend, data: body, method: 'POST'},
  94. ],
  95. {
  96. retry: 0,
  97. staleTime: 0,
  98. refetchOnReconnect: true,
  99. refetchOnWindowFocus: true,
  100. refetchInterval: false,
  101. enabled: queryIsComplete,
  102. }
  103. );
  104. }