mri.tsx 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126
  1. import {t} from 'sentry/locale';
  2. import type {MetricType, MRI, ParsedMRI, UseCase} from 'sentry/types/metrics';
  3. import {parseFunction} from 'sentry/utils/discover/fields';
  4. import {DEFAULT_AGGREGATES} from 'sentry/utils/metrics/constants';
  5. export const DEFAULT_MRI: MRI = 'c:custom/sentry_metric@none';
  6. // This is a workaround as the alert builder requires a valid aggregate to be set
  7. export const DEFAULT_METRIC_ALERT_FIELD = `sum(${DEFAULT_MRI})`;
  8. export function isMRI(mri?: unknown): mri is MRI {
  9. return !!parseMRI(mri);
  10. }
  11. export function parseMRI(mri?: unknown): ParsedMRI | null {
  12. if (!mri) {
  13. return null;
  14. }
  15. try {
  16. return _parseMRI(mri as MRI);
  17. } catch (e) {
  18. return null;
  19. }
  20. }
  21. function _parseMRI(mri: MRI): ParsedMRI {
  22. const mriArray = mri.split(new RegExp(/[:/@]/));
  23. if (mriArray.length !== 4) {
  24. throw new Error('Invalid MRI');
  25. }
  26. const [metricType, useCase, name, unit] = mriArray;
  27. return {
  28. type: metricType as MetricType,
  29. name: parseName(name, useCase as UseCase),
  30. unit,
  31. useCase: useCase as UseCase,
  32. };
  33. }
  34. function parseName(name: string, useCase: UseCase): string {
  35. if (useCase === 'custom') {
  36. return name;
  37. }
  38. if (useCase === 'transactions') {
  39. if (name === 'duration') {
  40. return 'transaction.duration';
  41. }
  42. return name;
  43. }
  44. if (useCase === 'spans') {
  45. if (['duration', 'self_time', 'exclusive_time'].includes(name)) {
  46. return `span.${name}`;
  47. }
  48. return name;
  49. }
  50. return `${useCase}.${name}`;
  51. }
  52. export function toMRI({type, useCase, name, unit}: ParsedMRI): MRI {
  53. return `${type}:${useCase}/${name}@${unit}`;
  54. }
  55. export function formatMRI(mri: MRI): string {
  56. return parseMRI(mri)?.name ?? mri;
  57. }
  58. export function getUseCaseFromMRI(mri?: string): UseCase | undefined {
  59. const parsed = parseMRI(mri);
  60. return parsed?.useCase;
  61. }
  62. export function MRIToField(mri: MRI, op: string): string {
  63. return `${op}(${mri})`;
  64. }
  65. export function parseField(field: string): {mri: MRI; op: string} | null {
  66. const parsedFunction = parseFunction(field);
  67. if (!parsedFunction) {
  68. return null;
  69. }
  70. return {
  71. mri: parsedFunction.arguments[0] as MRI,
  72. op: parsedFunction.name,
  73. };
  74. }
  75. export function isMRIField(field: string): boolean {
  76. return !!parseMRI(parseField(field)?.mri);
  77. }
  78. // convenience function to get the MRI from a field, returns defaut MRI if it fails
  79. export function getMRI(field: string): MRI {
  80. const parsed = parseField(field);
  81. return parsed?.mri ?? DEFAULT_MRI;
  82. }
  83. export function formatMRIField(aggregate: string) {
  84. if (aggregate === DEFAULT_METRIC_ALERT_FIELD) {
  85. return t('Select a metric to get started');
  86. }
  87. const parsed = parseField(aggregate);
  88. // The field does not contain an MRI -> return the aggregate as is
  89. if (!parsed || !parsed.mri) {
  90. return aggregate;
  91. }
  92. return `${parsed.op}(${formatMRI(parsed.mri)})`;
  93. }
  94. export function defaultAggregateForMRI(mri: MRI) {
  95. const parsedMRI = parseMRI(mri);
  96. const fallbackAggregate = 'sum';
  97. if (!parsedMRI) {
  98. return fallbackAggregate;
  99. }
  100. return DEFAULT_AGGREGATES[parsedMRI.type] || fallbackAggregate;
  101. }