123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276 |
- import {t} from 'sentry/locale';
- import type EventView from 'sentry/utils/discover/eventView';
- import type {AggregationKeyWithAlias, LooseFieldKey} from 'sentry/utils/discover/fields';
- import {SPAN_OP_BREAKDOWN_FIELDS} from 'sentry/utils/discover/fields';
- import {parsePeriodToHours} from 'sentry/utils/duration/parsePeriodToHours';
- import {AggregationKey} from 'sentry/utils/fields';
- import {WEB_VITAL_DETAILS} from 'sentry/utils/performance/vitals/constants';
- import type {Trigger, UnsavedMetricRule} from 'sentry/views/alerts/rules/metric/types';
- import {
- AlertRuleComparisonType,
- AlertRuleThresholdType,
- AlertRuleTriggerType,
- Dataset,
- Datasource,
- EventTypes,
- TimeWindow,
- } from 'sentry/views/alerts/rules/metric/types';
- import {
- DATA_SOURCE_TO_SET_AND_EVENT_TYPES,
- getQueryDatasource,
- isSessionAggregate,
- } from 'sentry/views/alerts/utils';
- import type {AlertType, WizardRuleTemplate} from 'sentry/views/alerts/wizard/options';
- export const DEFAULT_COUNT_TIME_WINDOW = 1; // 1min
- export const DEFAULT_CHANGE_TIME_WINDOW = 60; // 1h
- export const DEFAULT_CHANGE_COMP_DELTA = 10080; // 1w
- export const DEFAULT_AGGREGATE = 'count()';
- export const DEFAULT_TRANSACTION_AGGREGATE = 'p95(transaction.duration)';
- export const DATASET_EVENT_TYPE_FILTERS = {
- [Dataset.ERRORS]: 'event.type:error',
- [Dataset.TRANSACTIONS]: 'event.type:transaction',
- [Dataset.GENERIC_METRICS]: 'event.type:transaction',
- } as const;
- export const DATASOURCE_EVENT_TYPE_FILTERS = {
- [Datasource.ERROR_DEFAULT]: 'event.type:[error, default]',
- [Datasource.ERROR]: 'event.type:error',
- [Datasource.DEFAULT]: 'event.type:default',
- [Datasource.TRANSACTION]: 'event.type:transaction',
- } as const;
- export type OptionConfig = {
- aggregations: AggregationKeyWithAlias[];
- fields: LooseFieldKey[];
- measurementKeys?: string[];
- spanOperationBreakdownKeys?: string[];
- };
- /**
- * Allowed error aggregations for alerts
- */
- export const errorFieldConfig: OptionConfig = {
- aggregations: [AggregationKey.COUNT, AggregationKey.COUNT_UNIQUE],
- fields: ['user'],
- };
- const commonAggregations = [
- AggregationKey.AVG,
- AggregationKey.P50,
- AggregationKey.P75,
- AggregationKey.P90,
- AggregationKey.P95,
- AggregationKey.P99,
- AggregationKey.P100,
- ];
- const allAggregations = [
- ...commonAggregations,
- AggregationKey.FAILURE_RATE,
- AggregationKey.APDEX,
- AggregationKey.COUNT,
- ];
- export const DuplicateMetricFields: string[] = [
- 'dataset',
- 'eventTypes',
- 'aggregate',
- 'query',
- 'timeWindow',
- 'thresholdPeriod',
- 'projects',
- 'environment',
- 'resolveThreshold',
- 'thresholdType',
- 'owner',
- 'name',
- 'projectId',
- 'comparisonDelta',
- ];
- export const DuplicateTriggerFields: string[] = ['alertThreshold', 'label'];
- export const DuplicateActionFields: string[] = [
- 'type',
- 'targetType',
- 'targetIdentifier',
- 'inputChannelId',
- 'options',
- ];
- export const COMPARISON_DELTA_OPTIONS = [
- {value: 5, label: t('same time 5 minutes ago')}, // 5 minutes
- {value: 15, label: t('same time 15 minutes ago')}, // 15 minutes
- {value: 60, label: t('same time one hour ago')}, // one hour
- {value: 1440, label: t('same time one day ago')}, // one day
- {value: 10080, label: t('same time one week ago')}, // one week
- {value: 43200, label: t('same time one month ago')}, // 30 days
- ];
- export function getWizardAlertFieldConfig(
- alertType: AlertType,
- dataset: Dataset
- ): OptionConfig {
- if (alertType === 'custom_transactions' && dataset === Dataset.ERRORS) {
- return errorFieldConfig;
- }
- // If user selected apdex we must include that in the OptionConfig as it has a user specified column
- const aggregations =
- alertType === 'apdex' || alertType === 'custom_transactions'
- ? allAggregations
- : commonAggregations;
- const config: OptionConfig = {
- aggregations,
- fields: ['transaction.duration'],
- measurementKeys: Object.keys(WEB_VITAL_DETAILS),
- };
- if ([Dataset.TRANSACTIONS, Dataset.GENERIC_METRICS].includes(dataset)) {
- config.spanOperationBreakdownKeys = SPAN_OP_BREAKDOWN_FIELDS;
- }
- return config;
- }
- /**
- * Allowed transaction aggregations for alerts
- */
- export const transactionFieldConfig: OptionConfig = {
- aggregations: allAggregations,
- fields: ['transaction.duration'],
- spanOperationBreakdownKeys: SPAN_OP_BREAKDOWN_FIELDS,
- measurementKeys: Object.keys(WEB_VITAL_DETAILS),
- };
- export function createDefaultTrigger(label: AlertRuleTriggerType): Trigger {
- return {
- label,
- alertThreshold: '',
- actions: [],
- };
- }
- export function createDefaultRule(
- defaultRuleOptions: Partial<UnsavedMetricRule> = {}
- ): UnsavedMetricRule {
- return {
- dataset: Dataset.ERRORS,
- eventTypes: [EventTypes.ERROR],
- aggregate: DEFAULT_AGGREGATE,
- query: '',
- timeWindow: 60,
- thresholdPeriod: 1,
- triggers: [
- createDefaultTrigger(AlertRuleTriggerType.CRITICAL),
- createDefaultTrigger(AlertRuleTriggerType.WARNING),
- ],
- projects: [],
- environment: null,
- resolveThreshold: '',
- thresholdType: AlertRuleThresholdType.ABOVE,
- ...defaultRuleOptions,
- };
- }
- function getAlertTimeWindow(period: string | undefined): TimeWindow | undefined {
- if (!period) {
- return undefined;
- }
- const periodMinutes = parsePeriodToHours(period) * 60;
- if (periodMinutes < 0) {
- return undefined;
- }
- const timeWindows = Object.values(TimeWindow)
- .filter((value): value is TimeWindow => typeof value === 'number')
- .sort((a, b) => a - b);
- for (let index = 0; index < timeWindows.length; index++) {
- const timeWindow = timeWindows[index];
- if (periodMinutes <= timeWindow) {
- return timeWindow;
- }
- }
- return undefined;
- }
- /**
- * Create an unsaved alert from a discover EventView object
- */
- export function createRuleFromEventView(eventView: EventView): UnsavedMetricRule {
- const parsedQuery = getQueryDatasource(eventView.query);
- const datasetAndEventtypes = parsedQuery
- ? DATA_SOURCE_TO_SET_AND_EVENT_TYPES[parsedQuery.source]
- : DATA_SOURCE_TO_SET_AND_EVENT_TYPES.error;
- let aggregate = eventView.getYAxis();
- if (
- datasetAndEventtypes.dataset === 'transactions' &&
- /^p\d{2,3}\(\)/.test(eventView.getYAxis())
- ) {
- // p95() -> p95(transaction.duration)
- aggregate = eventView.getYAxis().slice(0, 3) + '(transaction.duration)';
- }
- const defaultRule = createDefaultRule();
- return {
- ...defaultRule,
- ...datasetAndEventtypes,
- query: parsedQuery?.query ?? eventView.query,
- aggregate,
- timeWindow: getAlertTimeWindow(eventView.interval) ?? defaultRule.timeWindow,
- environment: eventView.environment.length ? eventView.environment[0] : null,
- };
- }
- export function createRuleFromWizardTemplate(
- wizardTemplate: WizardRuleTemplate
- ): UnsavedMetricRule {
- const {eventTypes, aggregate, dataset, query} = wizardTemplate;
- const defaultRuleOptions: Partial<UnsavedMetricRule> = {};
- if (isSessionAggregate(aggregate)) {
- defaultRuleOptions.thresholdType = AlertRuleThresholdType.BELOW;
- defaultRuleOptions.timeWindow = TimeWindow.ONE_HOUR;
- }
- if (aggregate.includes('apdex')) {
- defaultRuleOptions.thresholdType = AlertRuleThresholdType.BELOW;
- }
- return {
- ...createDefaultRule(defaultRuleOptions),
- eventTypes: [eventTypes],
- aggregate,
- dataset,
- query: query ?? '',
- };
- }
- export function getThresholdUnits(
- aggregate: string,
- comparisonType: AlertRuleComparisonType
- ): string {
- // cls is a number not a measurement of time
- if (
- isSessionAggregate(aggregate) ||
- comparisonType === AlertRuleComparisonType.CHANGE
- ) {
- return '%';
- }
- if (aggregate.includes('measurements.cls')) {
- return '';
- }
- if (aggregate.includes('duration') || aggregate.includes('measurements')) {
- return 'ms';
- }
- return '';
- }
|