api.tsx 5.6 KB

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