api.tsx 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. import type {MetricType} from 'sentry/types/metrics';
  2. import type {FormattingSupportedMetricUnit} from 'sentry/utils/metrics/formatters';
  3. import {
  4. type ApiQueryKey,
  5. getApiQueryData,
  6. type QueryClient,
  7. setApiQueryData,
  8. useApiQuery,
  9. useMutation,
  10. useQueryClient,
  11. } from 'sentry/utils/queryClient';
  12. import type RequestError from 'sentry/utils/requestError/requestError';
  13. import useApi from 'sentry/utils/useApi';
  14. const getMetricsExtractionRulesEndpoint = (orgSlug: string, projectSlug: string) =>
  15. [`/projects/${orgSlug}/${projectSlug}/metrics/extraction-rules/`] as const;
  16. export interface MetricsExtractionRule {
  17. conditions: string[];
  18. spanAttribute: string;
  19. tags: string[];
  20. type: MetricType;
  21. unit: FormattingSupportedMetricUnit;
  22. }
  23. export function useMetricsExtractionRules(orgSlug: string, projectSlug: string) {
  24. return useApiQuery<MetricsExtractionRule[]>(
  25. getMetricsExtractionRulesEndpoint(orgSlug, projectSlug),
  26. {
  27. staleTime: 0,
  28. retry: false,
  29. }
  30. );
  31. }
  32. // Rules are identified by the combination of span_attribute, type and unit
  33. function getRuleIdentifier(rule: MetricsExtractionRule) {
  34. return rule.spanAttribute + rule.type + rule.unit;
  35. }
  36. function createOptimisticUpdate(
  37. queryClient: QueryClient,
  38. queryKey: ApiQueryKey,
  39. updater: (
  40. variables: {metricsExtractionRules: MetricsExtractionRule[]},
  41. old: MetricsExtractionRule[] | undefined
  42. ) => MetricsExtractionRule[] | undefined
  43. ) {
  44. return function (variables: {metricsExtractionRules: MetricsExtractionRule[]}) {
  45. queryClient.cancelQueries(queryKey);
  46. const previous = getApiQueryData<MetricsExtractionRule[]>(queryClient, queryKey);
  47. setApiQueryData<MetricsExtractionRule[] | undefined>(
  48. queryClient,
  49. queryKey,
  50. oldRules => {
  51. return updater(variables, oldRules);
  52. }
  53. );
  54. return {previous};
  55. };
  56. }
  57. function createRollback(queryClient: QueryClient, queryKey: ApiQueryKey) {
  58. return function (
  59. _error: RequestError,
  60. _variables: {metricsExtractionRules: MetricsExtractionRule[]},
  61. context?: {previous?: MetricsExtractionRule[]}
  62. ) {
  63. if (context?.previous) {
  64. setApiQueryData<MetricsExtractionRule[]>(queryClient, queryKey, context.previous);
  65. }
  66. };
  67. }
  68. export function useDeleteMetricsExtractionRules(orgSlug: string, projectSlug: string) {
  69. const api = useApi();
  70. const queryClient = useQueryClient();
  71. const queryKey = getMetricsExtractionRulesEndpoint(orgSlug, projectSlug);
  72. return useMutation<
  73. MetricsExtractionRule[],
  74. RequestError,
  75. {metricsExtractionRules: MetricsExtractionRule[]},
  76. {previous?: MetricsExtractionRule[]}
  77. >(
  78. data => {
  79. return api.requestPromise(queryKey[0], {
  80. method: 'DELETE',
  81. data,
  82. });
  83. },
  84. {
  85. onMutate: createOptimisticUpdate(queryClient, queryKey, (variables, old) => {
  86. const deletedRules = variables.metricsExtractionRules;
  87. const deletedKeys = new Set(deletedRules.map(getRuleIdentifier));
  88. return old?.filter(rule => !deletedKeys.has(getRuleIdentifier(rule)));
  89. }),
  90. onError: createRollback(queryClient, queryKey),
  91. onSettled: () => {
  92. queryClient.invalidateQueries(queryKey);
  93. },
  94. }
  95. );
  96. }
  97. export function useCreateMetricsExtractionRules(orgSlug: string, projectSlug: string) {
  98. const api = useApi();
  99. const queryClient = useQueryClient();
  100. const queryKey = getMetricsExtractionRulesEndpoint(orgSlug, projectSlug);
  101. return useMutation<
  102. MetricsExtractionRule[],
  103. RequestError,
  104. {metricsExtractionRules: MetricsExtractionRule[]},
  105. {previous?: MetricsExtractionRule[]}
  106. >(
  107. data => {
  108. return api.requestPromise(queryKey[0], {
  109. method: 'POST',
  110. data,
  111. });
  112. },
  113. {
  114. onMutate: createOptimisticUpdate(queryClient, queryKey, (variables, old) => {
  115. const newRules = variables.metricsExtractionRules;
  116. const existingKeys = new Set((old ?? []).map(getRuleIdentifier));
  117. const copy = old ? [...old] : [];
  118. newRules.forEach(rule => {
  119. if (!existingKeys.has(getRuleIdentifier(rule))) {
  120. copy.push(rule);
  121. }
  122. });
  123. return copy;
  124. }),
  125. onError: createRollback(queryClient, queryKey),
  126. onSettled: () => {
  127. queryClient.invalidateQueries(queryKey);
  128. },
  129. }
  130. );
  131. }
  132. export function useUpdateMetricsExtractionRules(orgSlug: string, projectSlug: string) {
  133. const api = useApi();
  134. const queryClient = useQueryClient();
  135. const queryKey = getMetricsExtractionRulesEndpoint(orgSlug, projectSlug);
  136. return useMutation<
  137. MetricsExtractionRule[],
  138. RequestError,
  139. {metricsExtractionRules: MetricsExtractionRule[]},
  140. {previous?: MetricsExtractionRule[]}
  141. >(
  142. data => {
  143. return api.requestPromise(queryKey[0], {
  144. method: 'PUT',
  145. data,
  146. });
  147. },
  148. {
  149. onMutate: createOptimisticUpdate(queryClient, queryKey, (variables, old) => {
  150. const updatedRules = variables.metricsExtractionRules;
  151. const updatedRulesMap = new Map(
  152. updatedRules.map(rule => [getRuleIdentifier(rule), rule])
  153. );
  154. return old?.map(rule => {
  155. const updatedRule = updatedRulesMap.get(getRuleIdentifier(rule));
  156. return updatedRule ?? rule;
  157. });
  158. }),
  159. onError: createRollback(queryClient, queryKey),
  160. onSettled: () => {
  161. queryClient.invalidateQueries(queryKey);
  162. },
  163. }
  164. );
  165. }