Просмотр исходного кода

ref(ddm): streamline MRI naming and parsing (#60543)

Ogi 1 год назад
Родитель
Сommit
662dd2c36e

+ 20 - 8
static/app/types/metrics.tsx

@@ -1,7 +1,5 @@
 import {DateString} from 'sentry/types/core';
 
-export type MetricsType = 'set' | 'counter' | 'distribution' | 'numeric';
-
 export type MetricsOperation =
   | 'sum'
   | 'count_unique'
@@ -13,6 +11,19 @@ export type MetricsOperation =
   | 'p95'
   | 'p99';
 
+export type MetricType = 'c' | 'd' | 'g' | 'e' | 's';
+
+export type UseCase = 'custom' | 'transactions' | 'sessions';
+
+export type MRI = `${MetricType}:${UseCase}${string}@${string}`;
+
+export type ParsedMRI = {
+  name: string;
+  type: MetricType;
+  unit: string;
+  useCase: UseCase;
+};
+
 export type MetricsApiRequestMetric = {
   field: string;
   query: string;
@@ -37,7 +48,7 @@ export type MetricsApiResponse = {
   end: string;
   groups: MetricsGroup[];
   intervals: string[];
-  meta: MetricsMeta[];
+  meta: MetricMeta[];
   query: string;
   start: string;
 };
@@ -59,12 +70,13 @@ export type MetricsTagValue = {
   value: string;
 };
 
-export type MetricsMeta = {
-  mri: string;
-  name: string;
+export type MetricMeta = {
+  mri: MRI;
+  // name is returned by the API but should not be used, use parseMRI(mri).name instead
+  // name: string;
   operations: MetricsOperation[];
-  type: MetricsType; // TODO(ddm): I think this is wrong, api returns "c" instead of "counter"
+  type: MetricType;
   unit: string;
 };
 
-export type MetricsMetaCollection = Record<string, MetricsMeta>;
+export type MetricsMetaCollection = Record<string, MetricMeta>;

+ 3 - 3
static/app/utils/discover/fields.tsx

@@ -1,7 +1,7 @@
 import isEqual from 'lodash/isEqual';
 
 import {RELEASE_ADOPTION_STAGES} from 'sentry/constants';
-import {MetricsType, Organization, SelectValue} from 'sentry/types';
+import {MetricType, Organization, SelectValue} from 'sentry/types';
 import {assert} from 'sentry/types/utils';
 import {
   SESSIONS_FIELDS,
@@ -53,7 +53,7 @@ type ValidateColumnValueFunction = (data: {
 
 export type ValidateColumnTypes =
   | ColumnType[]
-  | MetricsType[]
+  | MetricType[]
   | ValidateColumnValueFunction;
 
 export type AggregateParameter =
@@ -1230,7 +1230,7 @@ export function fieldAlignment(
 /**
  * Match on types that are legal to show on a timeseries chart.
  */
-export function isLegalYAxisType(match: ColumnType | MetricsType) {
+export function isLegalYAxisType(match: ColumnType | MetricType) {
   return ['number', 'integer', 'duration', 'percentage'].includes(match);
 }
 

+ 3 - 120
static/app/utils/metrics/index.spec.tsx

@@ -1,78 +1,11 @@
 import {PageFilters} from 'sentry/types';
 import {
-  fieldToMri,
   formatMetricsUsingUnitAndOp,
   getDateTimeParams,
   getMetricsApiRequestQuery,
   getMetricsInterval,
-  getUseCaseFromMRI,
-  parseMRI,
 } from 'sentry/utils/metrics';
 
-describe('parseMRI', () => {
-  it('should handle falsy values', () => {
-    expect(parseMRI('')).toEqual(null);
-    expect(parseMRI(undefined)).toEqual(null);
-  });
-
-  it('should parse MRI with name, unit, and mri (custom use case)', () => {
-    const mri = 'd:custom/sentry.events.symbolicator.query_task@second';
-    const expectedResult = {
-      name: 'sentry.events.symbolicator.query_task',
-      unit: 'second',
-      mri: 'd:custom/sentry.events.symbolicator.query_task@second',
-      useCase: 'custom',
-    };
-    expect(parseMRI(mri)).toEqual(expectedResult);
-  });
-
-  it('should parse MRI with name, unit, and cleanMRI (transactions use case)', () => {
-    const mri = 'g:transactions/gauge@milisecond';
-    const expectedResult = {
-      name: 'gauge',
-      unit: 'milisecond',
-      mri: 'g:transactions/gauge@milisecond',
-      useCase: 'transactions',
-    };
-    expect(parseMRI(mri)).toEqual(expectedResult);
-  });
-
-  it('should parse MRI with name, unit, and cleanMRI (sessions use case)', () => {
-    const mri = 'd:sessions/sentry.events.symbolicator.query_task@week';
-    const expectedResult = {
-      name: 'sentry.events.symbolicator.query_task',
-      unit: 'week',
-      mri: 'd:sessions/sentry.events.symbolicator.query_task@week',
-      useCase: 'sessions',
-    };
-    expect(parseMRI(mri)).toEqual(expectedResult);
-  });
-
-  it('should extract MRI from nested operations', () => {
-    const mri = 'd:custom/foobar@none';
-
-    const expectedResult = {
-      name: 'foobar',
-      unit: 'none',
-      mri: 'd:custom/foobar@none',
-      useCase: 'custom',
-    };
-    expect(parseMRI(`sum(avg(${mri}))`)).toEqual(expectedResult);
-  });
-
-  it('should extract MRI from nested operations (set)', () => {
-    const mri = 's:custom/foobar@none';
-
-    const expectedResult = {
-      name: 'foobar',
-      unit: 'none',
-      mri: 's:custom/foobar@none',
-      useCase: 'custom',
-    };
-    expect(parseMRI(`count_unique(${mri})`)).toEqual(expectedResult);
-  });
-});
-
 describe('formatMetricsUsingUnitAndOp', () => {
   it('should format the value according to the unit', () => {
     // Test cases for different units
@@ -114,7 +47,7 @@ describe('getMetricsApiRequestQuery', () => {
       project: [1],
       environment: ['production'],
       field: 'sessions',
-      useCase: 'sessions',
+      useCase: 'custom',
       interval: '12h',
       groupBy: ['project'],
       allowPrivate: true,
@@ -139,7 +72,7 @@ describe('getMetricsApiRequestQuery', () => {
       project: [1],
       environment: ['production'],
       field: 'sessions',
-      useCase: 'sessions',
+      useCase: 'custom',
       interval: '30m',
       groupBy: ['project'],
       allowPrivate: true,
@@ -165,7 +98,7 @@ describe('getMetricsApiRequestQuery', () => {
       project: [1],
       environment: ['production'],
       field: 'sessions',
-      useCase: 'sessions',
+      useCase: 'custom',
       interval: '5m',
       groupBy: ['environment'],
       allowPrivate: true,
@@ -222,53 +155,3 @@ describe('getDateTimeParams', () => {
     });
   });
 });
-
-describe('getUseCaseFromMRI', () => {
-  it('should return "custom" for mri containing "custom/"', () => {
-    const mri = 'd:custom/sentry.events.symbolicator.query_task@second';
-
-    const result = getUseCaseFromMRI(mri);
-
-    expect(result).toBe('custom');
-  });
-
-  it('should return "transactions" for mri containing "transactions/"', () => {
-    const mri = 'd:transactions/duration@second';
-
-    const result = getUseCaseFromMRI(mri);
-
-    expect(result).toBe('transactions');
-  });
-
-  it('should return "sessions" for other cases', () => {
-    const mri = 'e:test/project@timestamp';
-
-    const result = getUseCaseFromMRI(mri);
-
-    expect(result).toBe('sessions');
-  });
-});
-
-describe('fieldToMri', () => {
-  it('should return the correct mri and op from field', () => {
-    const field = 'op(c:test/project)';
-
-    const result = fieldToMri(field);
-
-    expect(result).toEqual({
-      mri: 'c:test/project',
-      op: 'op',
-    });
-  });
-
-  it('should return undefined mri and op for invalid field', () => {
-    const field = 'invalid-field';
-
-    const result = fieldToMri(field);
-
-    expect(result).toEqual({
-      mri: undefined,
-      op: undefined,
-    });
-  });
-});

+ 17 - 88
static/app/utils/metrics/index.tsx

@@ -9,14 +9,17 @@ import {
 } from 'sentry/components/charts/utils';
 import {t} from 'sentry/locale';
 import {
+  MetricMeta,
   MetricsApiRequestMetric,
   MetricsApiRequestQuery,
   MetricsGroup,
-  MetricsMeta,
+  MetricType,
+  MRI,
+  UseCase,
 } from 'sentry/types/metrics';
 import {defined, formatBytesBase2, formatBytesBase10} from 'sentry/utils';
-import {parseFunction} from 'sentry/utils/discover/fields';
 import {formatPercentage, getDuration} from 'sentry/utils/formatters';
+import {formatMRI, getUseCaseFromMRI, parseField} from 'sentry/utils/metrics/mri';
 
 import {DateString, PageFilters} from '../../types/core';
 
@@ -29,8 +32,6 @@ export enum MetricDisplayType {
 
 export const defaultMetricDisplayType = MetricDisplayType.LINE;
 
-export type UseCase = 'sessions' | 'transactions' | 'custom';
-
 export type MetricTag = {
   key: string;
 };
@@ -63,7 +64,7 @@ export interface DdmQueryParams {
 export type MetricsQuery = {
   datetime: PageFilters['datetime'];
   environments: PageFilters['environments'];
-  mri: string;
+  mri: MRI;
   projects: PageFilters['projects'];
   groupBy?: string[];
   op?: string;
@@ -116,7 +117,8 @@ export function getMetricsApiRequestQuery(
   {projects, environments, datetime}: PageFilters,
   overrides: Partial<MetricsApiRequestQuery>
 ): MetricsApiRequestQuery {
-  const useCase = getUseCaseFromMRI(fieldToMri(field).mri);
+  const {mri: mri} = parseField(field) ?? {};
+  const useCase = getUseCaseFromMRI(mri) ?? 'custom';
   const interval = getMetricsInterval(datetime, useCase);
 
   const queryToSend = {
@@ -159,7 +161,7 @@ export function getDateTimeParams({start, end, period}: PageFilters['datetime'])
     : {start: moment(start).toISOString(), end: moment(end).toISOString()};
 }
 
-const metricTypeToReadable = {
+const metricTypeToReadable: Record<MetricType, string> = {
   c: t('counter'),
   g: t('gauge'),
   d: t('distribution'),
@@ -168,41 +170,8 @@ const metricTypeToReadable = {
 };
 
 // Converts from "c" to "counter"
-export function getReadableMetricType(type) {
-  return metricTypeToReadable[type] ?? t('unknown');
-}
-
-const noUnit = 'none';
-
-export function parseMRI(mri?: string) {
-  if (!mri) {
-    return null;
-  }
-
-  const cleanMRI = mri.match(/[cdegs]:[\w/.@]+/)?.[0] ?? mri;
-
-  const name = cleanMRI.match(/^[a-z]:\w+\/(.+)(?:@\w+)$/)?.[1] ?? mri;
-  const unit = cleanMRI.split('@').pop() ?? noUnit;
-
-  const useCase = getUseCaseFromMRI(cleanMRI);
-
-  return {
-    name,
-    unit,
-
-    mri: cleanMRI,
-    useCase,
-  };
-}
-
-export function getUseCaseFromMRI(mri?: string): UseCase {
-  if (mri?.includes('custom/')) {
-    return 'custom';
-  }
-  if (mri?.includes('transactions/')) {
-    return 'transactions';
-  }
-  return 'sessions';
+export function getReadableMetricType(type?: string) {
+  return metricTypeToReadable[type as MetricType] ?? t('unknown');
 }
 
 export function formatMetricUsingUnit(value: number | null, unit: string) {
@@ -302,18 +271,14 @@ export function clearQuery(router: InjectedRouter) {
 export function getSeriesName(
   group: MetricsGroup,
   isOnlyGroup = false,
-  groupBy: MetricsQuery['groupBy'],
-  alias?: string
+  groupBy: MetricsQuery['groupBy']
 ) {
-  if (alias) {
-    return alias;
-  }
-
   if (isOnlyGroup && !groupBy?.length) {
-    const mri = Object.keys(group.series)?.[0];
-    const parsed = parseMRI(mri);
+    const field = Object.keys(group.series)?.[0];
+    const {mri} = parseField(field) ?? {mri: field};
+    const name = formatMRI(mri as MRI);
 
-    return parsed?.name ?? '(none)';
+    return name ?? '(none)';
   }
 
   return Object.entries(group.by)
@@ -321,23 +286,7 @@ export function getSeriesName(
     .join(', ');
 }
 
-export function mriToField(mri: string, op: string): string {
-  return `${op}(${mri})`;
-}
-
-export function fieldToMri(field: string): {mri?: string; op?: string} {
-  const parsedFunction = parseFunction(field);
-  if (!parsedFunction) {
-    // We only allow aggregate functions for custom metric alerts
-    return {};
-  }
-  return {
-    mri: parsedFunction.arguments[0],
-    op: parsedFunction.name,
-  };
-}
-
-export function groupByOp(metrics: MetricsMeta[]): Record<string, MetricsMeta[]> {
+export function groupByOp(metrics: MetricMeta[]): Record<string, MetricMeta[]> {
   const uniqueOperations = [
     ...new Set(metrics.flatMap(field => field.operations).filter(isAllowedOp)),
   ].sort();
@@ -349,23 +298,3 @@ export function groupByOp(metrics: MetricsMeta[]): Record<string, MetricsMeta[]>
 
   return groupedByOp;
 }
-// This is a workaround as the alert builder requires a valid aggregate to be set
-export const DEFAULT_METRIC_ALERT_AGGREGATE = 'sum(c:custom/iolnqzyenoqugwm@none)';
-
-export const formatMriAggregate = (aggregate: string) => {
-  if (aggregate === DEFAULT_METRIC_ALERT_AGGREGATE) {
-    return t('Select a metric to get started');
-  }
-
-  const {mri, op} = fieldToMri(aggregate);
-  const parsed = parseMRI(mri);
-
-  // The field does not contain an MRI -> return the aggregate as is
-  if (!parsed) {
-    return aggregate;
-  }
-
-  const {name} = parsed;
-
-  return `${op}(${name})`;
-};

+ 184 - 0
static/app/utils/metrics/mri.spec.tsx

@@ -0,0 +1,184 @@
+import {MetricType, MRI} from 'sentry/types';
+import {ParsedMRI, UseCase} from 'sentry/types/metrics';
+import {getUseCaseFromMRI, parseField, parseMRI, toMRI} from 'sentry/utils/metrics/mri';
+
+describe('parseMRI', () => {
+  it('should handle falsy values', () => {
+    expect(parseMRI('')).toEqual(null);
+    expect(parseMRI()).toEqual(null);
+    expect(parseMRI(null)).toEqual(null);
+    expect(parseMRI(undefined)).toEqual(null);
+  });
+
+  it.each(['c', 'd', 'e', 'g', 's'])(
+    'should correctly parse a valid MRI string - metric type %s',
+    metricType => {
+      const mri: MRI = `${metricType as MetricType}:custom/xyz@test`;
+      const parsedMRI = {
+        type: metricType,
+        name: 'xyz',
+        unit: 'test',
+        useCase: 'custom',
+      };
+      expect(parseMRI(mri)).toEqual(parsedMRI);
+    }
+  );
+
+  it.each(['sessions', 'transactions', 'custom'])(
+    'should correctly parse a valid MRI string - use case %s',
+    useCase => {
+      const mri: MRI = `c:${useCase as UseCase}/xyz@test`;
+      const parsedMRI = {
+        type: 'c',
+        name: 'xyz',
+        unit: 'test',
+        useCase,
+      };
+      expect(parseMRI(mri)).toEqual(parsedMRI);
+    }
+  );
+
+  it.each(['foo', 'foo_bar', 'foo_9-bar', '12-!foo][]312bar'])(
+    'should correctly parse a valid MRI string - name %s',
+    name => {
+      const mri: MRI = `c:custom/${name}@test`;
+      const parsedMRI = {
+        type: 'c',
+        name,
+        unit: 'test',
+        useCase: 'custom',
+      };
+      expect(parseMRI(mri)).toEqual(parsedMRI);
+    }
+  );
+
+  it.each(['ms', 'none', 'KiB'])(
+    'should correctly parse a valid MRI string - name %s',
+    unit => {
+      const mri: MRI = `c:custom/foo@${unit}`;
+      const parsedMRI = {
+        type: 'c',
+        name: 'foo',
+        unit,
+        useCase: 'custom',
+      };
+      expect(parseMRI(mri)).toEqual(parsedMRI);
+    }
+  );
+});
+
+describe('getUseCaseFromMRI', () => {
+  it('should return "custom" for mri containing "custom/"', () => {
+    const mri = 'd:custom/sentry.events.symbolicator.query_task@second';
+
+    const result = getUseCaseFromMRI(mri);
+
+    expect(result).toBe('custom');
+  });
+
+  it('should return "transactions" for mri containing "transactions/"', () => {
+    const mri = 'd:transactions/duration@second';
+
+    const result = getUseCaseFromMRI(mri);
+
+    expect(result).toBe('transactions');
+  });
+
+  it('should return undefined for invalid mris', () => {
+    const mri = 'foobar';
+
+    const result = getUseCaseFromMRI(mri);
+
+    expect(result).toBeUndefined();
+  });
+});
+
+describe('parseField', () => {
+  it('should return the correct mri and op from field', () => {
+    const field = 'op(c:test/project)';
+
+    const result = parseField(field);
+
+    expect(result).toEqual({
+      mri: 'c:test/project',
+      op: 'op',
+    });
+  });
+
+  it('should do nothing for already formatted field', () => {
+    const field = 'sum(my-metric)';
+
+    const result = parseField(field);
+
+    expect(result?.mri).toBe('my-metric');
+    expect(result?.op).toBe('sum');
+  });
+
+  it('should return null mri invalid field', () => {
+    const field = 'invalid-field';
+
+    const result = parseField(field);
+
+    expect(result).toBeNull();
+  });
+});
+
+describe('toMRI', () => {
+  it.each(['c', 'd', 'e', 'g', 's'])(
+    'should correctly parse a valid MRI string - metric type %s',
+    metricType => {
+      const mri = `${metricType as MetricType}:custom/xyz@test`;
+
+      const parsedMRI: ParsedMRI = {
+        type: metricType as MetricType,
+        name: 'xyz',
+        unit: 'test',
+        useCase: 'custom',
+      };
+
+      expect(toMRI(parsedMRI)).toEqual(mri);
+    }
+  );
+
+  it.each(['sessions', 'transactions', 'custom'])(
+    'should correctly parse a valid MRI string - use case %s',
+    useCase => {
+      const mri: MRI = `c:${useCase as UseCase}/xyz@test`;
+      const parsedMRI: ParsedMRI = {
+        type: 'c',
+        name: 'xyz',
+        unit: 'test',
+        useCase: useCase as UseCase,
+      };
+      expect(toMRI(parsedMRI)).toEqual(mri);
+    }
+  );
+
+  it.each(['foo', 'foo_bar', 'foo_9-bar', '12-!foo][]312bar'])(
+    'should correctly parse a valid MRI string - name %s',
+    name => {
+      const mri: MRI = `c:custom/${name}@test`;
+      const parsedMRI: ParsedMRI = {
+        type: 'c',
+        name,
+        unit: 'test',
+        useCase: 'custom',
+      };
+      expect(toMRI(parsedMRI)).toEqual(mri);
+    }
+  );
+
+  it.each(['ms', 'none', 'KiB'])(
+    'should correctly parse a valid MRI string - name %s',
+    unit => {
+      const mri: MRI = `c:custom/foo@${unit}`;
+      const parsedMRI: ParsedMRI = {
+        type: 'c',
+        name: 'foo',
+        unit,
+        useCase: 'custom',
+      };
+      expect(toMRI(parsedMRI)).toEqual(mri);
+    }
+  );
+});

+ 92 - 0
static/app/utils/metrics/mri.tsx

@@ -0,0 +1,92 @@
+import * as Sentry from '@sentry/react';
+
+import {t} from 'sentry/locale';
+import {MetricType, MRI, ParsedMRI, UseCase} from 'sentry/types/metrics';
+import {parseFunction} from 'sentry/utils/discover/fields';
+
+export const DEFAULT_MRI: MRI = 'c:custom/sentry_metric@none';
+// This is a workaround as the alert builder requires a valid aggregate to be set
+export const DEFAULT_METRIC_ALERT_FIELD = `sum(${DEFAULT_MRI})`;
+
+export function isMRI(mri?: unknown): mri is MRI {
+  return !!parseMRI(mri);
+}
+
+export function parseMRI(mri?: unknown): ParsedMRI | null {
+  if (!mri) {
+    return null;
+  }
+  try {
+    return _parseMRI(mri as MRI);
+  } catch (e) {
+    return null;
+  }
+}
+
+function _parseMRI(mri: MRI): ParsedMRI {
+  const mriArray = mri.split(new RegExp(/[:/@]/));
+
+  if (mriArray.length !== 4) {
+    Sentry.captureMessage(`Invalid MRI: ${mri}`);
+    throw new Error('Invalid MRI');
+  }
+
+  const [metricType, useCase, name, unit] = mriArray;
+
+  return {
+    type: metricType as MetricType,
+    name,
+    unit,
+    useCase: useCase as UseCase,
+  };
+}
+
+export function toMRI({type, useCase, name, unit}: ParsedMRI): MRI {
+  return `${type}:${useCase}/${name}@${unit}`;
+}
+
+export function formatMRI(mri: MRI): string {
+  return parseMRI(mri)?.name ?? mri;
+}
+
+export function getUseCaseFromMRI(mri?: string): UseCase | undefined {
+  const parsed = parseMRI(mri);
+
+  return parsed?.useCase;
+}
+
+export function MRIToField(mri: MRI, op: string): string {
+  return `${op}(${mri})`;
+}
+
+export function parseField(field: string): {mri: MRI; op: string} | null {
+  const parsedFunction = parseFunction(field);
+  if (!parsedFunction) {
+    return null;
+  }
+  return {
+    mri: parsedFunction.arguments[0] as MRI,
+    op: parsedFunction.name,
+  };
+}
+
+// convenience function to get the MRI from a field, returns defaut MRI if it fails
+export function getMRI(field: string): MRI {
+  const parsed = parseField(field);
+  return parsed?.mri ?? DEFAULT_MRI;
+}
+
+export function formatMRIField(aggregate: string) {
+  if (aggregate === DEFAULT_METRIC_ALERT_FIELD) {
+    return t('Select a metric to get started');
+  }
+
+  const parsed = parseField(aggregate);
+
+  // The field does not contain an MRI -> return the aggregate as is
+  if (!parsed || !parsed.mri) {
+    return aggregate;
+  }
+
+  return `${parsed.op}(${formatMRI(parsed.mri)})`;
+}

+ 1 - 9
static/app/utils/metrics/useMetricsMeta.tsx

@@ -1,18 +1,10 @@
 import {useMemo} from 'react';
 
 import {PageFilters} from 'sentry/types';
-import {UseCase} from 'sentry/utils/metrics';
 import {ApiQueryKey, useApiQuery} from 'sentry/utils/queryClient';
 import useOrganization from 'sentry/utils/useOrganization';
 
-// TODO(ddm): reuse from types/metrics.tsx
-type MetricMeta = {
-  mri: string;
-  name: string;
-  operations: string[];
-  type: string;
-  unit: string;
-};
+import {MetricMeta, UseCase} from '../../types/metrics';
 
 interface Options {
   useCases?: UseCase[];

+ 4 - 3
static/app/utils/metrics/useMetricsTagValues.tsx

@@ -1,10 +1,11 @@
-import {PageFilters} from 'sentry/types';
-import {getUseCaseFromMRI, MetricTag} from 'sentry/utils/metrics';
+import {MRI, PageFilters} from 'sentry/types';
+import {MetricTag} from 'sentry/utils/metrics';
+import {getUseCaseFromMRI} from 'sentry/utils/metrics/mri';
 import {useApiQuery} from 'sentry/utils/queryClient';
 import useOrganization from 'sentry/utils/useOrganization';
 
 export function useMetricsTagValues(
-  mri: string,
+  mri: MRI,
   tag: string,
   projects: PageFilters['projects']
 ) {

+ 5 - 7
static/app/utils/metrics/useMetricsTags.tsx

@@ -1,14 +1,12 @@
-import {PageFilters} from 'sentry/types';
-import {getUseCaseFromMRI, MetricTag} from 'sentry/utils/metrics';
+import {MRI, PageFilters} from 'sentry/types';
+import {MetricTag} from 'sentry/utils/metrics';
+import {getUseCaseFromMRI} from 'sentry/utils/metrics/mri';
 import {useApiQuery} from 'sentry/utils/queryClient';
 import useOrganization from 'sentry/utils/useOrganization';
 
-export function useMetricsTags(
-  mri: string | undefined,
-  projects: PageFilters['projects']
-) {
+export function useMetricsTags(mri: MRI | undefined, projects: PageFilters['projects']) {
   const {slug} = useOrganization();
-  const useCase = getUseCaseFromMRI(mri || '');
+  const useCase = getUseCaseFromMRI(mri);
   return useApiQuery<MetricTag[]>(
     [
       `/organizations/${slug}/metrics/tags/`,

+ 2 - 2
static/app/views/alerts/rules/metric/details/metricChart.tsx

@@ -45,8 +45,8 @@ import {ReactEchartsRef, Series} from 'sentry/types/echarts';
 import {getUtcDateString} from 'sentry/utils/dates';
 import {getDuration} from 'sentry/utils/formatters';
 import getDynamicText from 'sentry/utils/getDynamicText';
-import {formatMriAggregate} from 'sentry/utils/metrics';
 import {getForceMetricsLayerQueryExtras} from 'sentry/utils/metrics/features';
+import {formatMRIField} from 'sentry/utils/metrics/mri';
 import {shouldShowOnDemandMetricAlertUI} from 'sentry/utils/onDemandMetrics/features';
 import {MINUTES_THRESHOLD_TO_DISPLAY_SECONDS} from 'sentry/utils/sessions';
 import theme from 'sentry/utils/theme';
@@ -369,7 +369,7 @@ class MetricChart extends PureComponent<Props, State> {
           </ChartHeader>
           <ChartFilters>
             <StyledCircleIndicator size={8} />
-            <Filters>{formatMriAggregate(rule.aggregate)}</Filters>
+            <Filters>{formatMRIField(rule.aggregate)}</Filters>
             <Truncate value={queryFilter ?? ''} maxLength={truncateWidth} />
           </ChartFilters>
           {getDynamicText({

Некоторые файлы не были показаны из-за большого количества измененных файлов