virtualMetricsContext.tsx 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. import {createContext, useCallback, useContext, useMemo} from 'react';
  2. import type {
  3. MetricAggregation,
  4. MetricMeta,
  5. MetricsExtractionCondition,
  6. MetricsExtractionRule,
  7. MRI,
  8. } from 'sentry/types/metrics';
  9. import {aggregationToMetricType} from 'sentry/utils/metrics/extractionRules';
  10. import {DEFAULT_MRI, parseMRI} from 'sentry/utils/metrics/mri';
  11. import type {MetricTag} from 'sentry/utils/metrics/types';
  12. import {useApiQuery} from 'sentry/utils/queryClient';
  13. import useOrganization from 'sentry/utils/useOrganization';
  14. import usePageFilters from 'sentry/utils/usePageFilters';
  15. const Context = createContext<{
  16. getCondition: (mri: MRI, conditionId: number) => MetricsExtractionCondition | null;
  17. getConditions: (mri: MRI) => MetricsExtractionCondition[];
  18. getExtractionRule: (mri: MRI) => MetricsExtractionRule | null;
  19. getTags: (mri: MRI) => MetricTag[];
  20. getVirtualMRI: (mri: MRI) => MRI | null;
  21. getVirtualMRIQuery: (
  22. mri: MRI,
  23. aggregation: MetricAggregation
  24. ) => {
  25. aggregation: MetricAggregation;
  26. conditionId: number;
  27. mri: MRI;
  28. } | null;
  29. getVirtualMeta: (mri: MRI) => MetricMeta;
  30. isLoading: boolean;
  31. resolveVirtualMRI: (
  32. mri: MRI,
  33. conditionId: number,
  34. aggregation: MetricAggregation
  35. ) => {aggregation: MetricAggregation; mri: MRI};
  36. virtualMeta: MetricMeta[];
  37. }>({
  38. getVirtualMRI: () => null,
  39. getVirtualMeta: () => {
  40. throw new Error('Not implemented');
  41. },
  42. getConditions: () => [],
  43. getCondition: () => null,
  44. getExtractionRule: () => null,
  45. getTags: () => [],
  46. getVirtualMRIQuery: () => null,
  47. resolveVirtualMRI: (mri, _, aggregation) => ({mri, aggregation}),
  48. virtualMeta: [],
  49. isLoading: false,
  50. });
  51. export function useVirtualMetricsContext() {
  52. return useContext(Context);
  53. }
  54. interface Props {
  55. children: React.ReactNode;
  56. }
  57. export function createVirtualMRI(rule: MetricsExtractionRule): MRI {
  58. return `v:custom/${rule.spanAttribute}|${rule.projectId}@${rule.unit}`;
  59. }
  60. export function createMRIToVirtualMap(rules: MetricsExtractionRule[]): Map<MRI, MRI> {
  61. const mriMap = new Map<MRI, MRI>();
  62. for (const rule of rules) {
  63. for (const condition of rule.conditions) {
  64. for (const mri of condition.mris) {
  65. mriMap.set(mri, createVirtualMRI(rule));
  66. }
  67. }
  68. }
  69. return mriMap;
  70. }
  71. const getMetricsExtractionRulesApiKey = (orgSlug: string, projects: number[]) =>
  72. [
  73. `/organizations/${orgSlug}/metrics/extraction-rules/`,
  74. {
  75. query: {
  76. project: projects,
  77. },
  78. },
  79. ] as const;
  80. const EMPTY_ARRAY: never[] = [];
  81. export function VirtualMetricsContextProvider({children}: Props) {
  82. const organization = useOrganization();
  83. const {selection, isReady} = usePageFilters();
  84. const {isLoading, data = EMPTY_ARRAY} = useApiQuery<MetricsExtractionRule[]>(
  85. getMetricsExtractionRulesApiKey(organization.slug, selection.projects),
  86. {staleTime: 0, enabled: isReady}
  87. );
  88. const mriToVirtualMap = useMemo(() => createMRIToVirtualMap(data), [data]);
  89. const virtualMRIToRuleMap = useMemo(
  90. () =>
  91. new Map<MRI, MetricsExtractionRule>(
  92. data.map(rule => [createVirtualMRI(rule), rule])
  93. ),
  94. [data]
  95. );
  96. const getVirtualMRI = useCallback(
  97. (mri: MRI): MRI | null => {
  98. const virtualMRI = mriToVirtualMap.get(mri);
  99. if (!virtualMRI) {
  100. return null;
  101. }
  102. return virtualMRI;
  103. },
  104. [mriToVirtualMap]
  105. );
  106. const getVirtualMeta = useCallback(
  107. (mri: MRI): MetricMeta => {
  108. const rule = virtualMRIToRuleMap.get(mri);
  109. if (!rule) {
  110. throw new Error('Rule not found');
  111. }
  112. return {
  113. type: 'v',
  114. unit: rule.unit,
  115. blockingStatus: [],
  116. mri: mri,
  117. operations: rule.aggregates,
  118. projectIds: [rule.projectId],
  119. };
  120. },
  121. [virtualMRIToRuleMap]
  122. );
  123. const getConditions = useCallback(
  124. (mri: MRI): MetricsExtractionCondition[] => {
  125. const rule = virtualMRIToRuleMap.get(mri);
  126. return rule?.conditions || [];
  127. },
  128. [virtualMRIToRuleMap]
  129. );
  130. const getCondition = useCallback(
  131. (mri: MRI, conditionId: number) => {
  132. const rule = virtualMRIToRuleMap.get(mri);
  133. return rule?.conditions.find(c => c.id === conditionId) || null;
  134. },
  135. [virtualMRIToRuleMap]
  136. );
  137. const getTags = useCallback(
  138. (mri: MRI): MetricTag[] => {
  139. const rule = virtualMRIToRuleMap.get(mri);
  140. return rule?.tags.map(tag => ({key: tag})) || [];
  141. },
  142. [virtualMRIToRuleMap]
  143. );
  144. const resolveVirtualMRI = useCallback(
  145. (
  146. mri: MRI,
  147. conditionId: number,
  148. aggregation: MetricAggregation
  149. ): {
  150. aggregation: MetricAggregation;
  151. mri: MRI;
  152. } => {
  153. const rule = virtualMRIToRuleMap.get(mri);
  154. if (!rule) {
  155. return {mri: DEFAULT_MRI, aggregation: 'sum'};
  156. }
  157. const condition = rule.conditions.find(c => c.id === conditionId);
  158. if (!condition) {
  159. return {mri: DEFAULT_MRI, aggregation: 'sum'};
  160. }
  161. const metricType = aggregationToMetricType[aggregation];
  162. let resolvedMRI = condition.mris.find(m => m.startsWith(metricType));
  163. if (!resolvedMRI) {
  164. resolvedMRI = mri;
  165. }
  166. return {mri: resolvedMRI, aggregation: metricType === 'c' ? 'sum' : aggregation};
  167. },
  168. [virtualMRIToRuleMap]
  169. );
  170. const getVirtualMRIQuery = useCallback(
  171. (
  172. mri: MRI,
  173. aggregation: MetricAggregation
  174. ): {
  175. aggregation: MetricAggregation;
  176. conditionId: number;
  177. mri: MRI;
  178. } | null => {
  179. const virtualMRI = getVirtualMRI(mri);
  180. if (!virtualMRI) {
  181. return null;
  182. }
  183. const rule = virtualMRIToRuleMap.get(virtualMRI);
  184. if (!rule) {
  185. return null;
  186. }
  187. const condition = rule.conditions.find(c => c.mris.includes(mri));
  188. if (!condition) {
  189. return null;
  190. }
  191. return {
  192. mri: virtualMRI,
  193. conditionId: condition.id,
  194. aggregation: parseMRI(mri).type === 'c' ? 'count' : aggregation,
  195. };
  196. },
  197. [getVirtualMRI, virtualMRIToRuleMap]
  198. );
  199. const getExtractionRule = useCallback(
  200. (mri: MRI) => {
  201. return virtualMRIToRuleMap.get(mri) ?? null;
  202. },
  203. [virtualMRIToRuleMap]
  204. );
  205. const virtualMeta = useMemo(
  206. () => Array.from(virtualMRIToRuleMap.keys()).map(getVirtualMeta),
  207. [getVirtualMeta, virtualMRIToRuleMap]
  208. );
  209. const contextValue = useMemo(
  210. () => ({
  211. getVirtualMRI,
  212. getVirtualMeta,
  213. getConditions,
  214. getCondition,
  215. getExtractionRule,
  216. getTags,
  217. getVirtualMRIQuery,
  218. resolveVirtualMRI,
  219. virtualMeta,
  220. isLoading,
  221. }),
  222. [
  223. getVirtualMRI,
  224. getVirtualMeta,
  225. getConditions,
  226. getCondition,
  227. getExtractionRule,
  228. getTags,
  229. getVirtualMRIQuery,
  230. resolveVirtualMRI,
  231. virtualMeta,
  232. isLoading,
  233. ]
  234. );
  235. return <Context.Provider value={contextValue}>{children}</Context.Provider>;
  236. }