mirror of
https://github.com/hanzoai/sentinel.git
synced 2026-10-30 14:49:14 +00:00
214 lines
6.5 KiB
TypeScript
214 lines
6.5 KiB
TypeScript
import round from 'lodash/round';
|
|
|
|
import {t} from 'sentry/locale';
|
|
import type {Organization} from 'sentry/types';
|
|
import {SessionFieldWithOperation} from 'sentry/types';
|
|
import type {IssueAlertRule} from 'sentry/types/alerts';
|
|
import {defined} from 'sentry/utils';
|
|
import {getUtcDateString} from 'sentry/utils/dates';
|
|
import {axisLabelFormatter, tooltipFormatter} from 'sentry/utils/discover/charts';
|
|
import {aggregateOutputType} from 'sentry/utils/discover/fields';
|
|
import {formatMetricUsingFixedUnit} from 'sentry/utils/metrics/formatters';
|
|
import {parseField, parseMRI} from 'sentry/utils/metrics/mri';
|
|
import toArray from 'sentry/utils/toArray';
|
|
import type {MetricRule, SavedMetricRule} from 'sentry/views/alerts/rules/metric/types';
|
|
import {
|
|
Dataset,
|
|
Datasource,
|
|
EventTypes,
|
|
SessionsAggregate,
|
|
} from 'sentry/views/alerts/rules/metric/types';
|
|
import {isCustomMetricAlert} from 'sentry/views/alerts/rules/metric/utils/isCustomMetricAlert';
|
|
|
|
import type {Incident, IncidentStats} from '../types';
|
|
import {AlertRuleStatus} from '../types';
|
|
|
|
/**
|
|
* Gets start and end date query parameters from stats
|
|
*/
|
|
export function getStartEndFromStats(stats: IncidentStats) {
|
|
const start = getUtcDateString(stats.eventStats.data[0][0] * 1000);
|
|
const end = getUtcDateString(
|
|
stats.eventStats.data[stats.eventStats.data.length - 1][0] * 1000
|
|
);
|
|
|
|
return {start, end};
|
|
}
|
|
|
|
export function isIssueAlert(
|
|
data: IssueAlertRule | SavedMetricRule | MetricRule
|
|
): data is IssueAlertRule {
|
|
return !data.hasOwnProperty('triggers');
|
|
}
|
|
|
|
export const DATA_SOURCE_LABELS = {
|
|
[Dataset.ERRORS]: t('Errors'),
|
|
[Dataset.TRANSACTIONS]: t('Transactions'),
|
|
[Datasource.ERROR_DEFAULT]: 'event.type:error OR event.type:default',
|
|
[Datasource.ERROR]: 'event.type:error',
|
|
[Datasource.DEFAULT]: 'event.type:default',
|
|
[Datasource.TRANSACTION]: 'event.type:transaction',
|
|
};
|
|
|
|
// Maps a datasource to the relevant dataset and event_types for the backend to use
|
|
export const DATA_SOURCE_TO_SET_AND_EVENT_TYPES = {
|
|
[Datasource.ERROR_DEFAULT]: {
|
|
dataset: Dataset.ERRORS,
|
|
eventTypes: [EventTypes.ERROR, EventTypes.DEFAULT],
|
|
},
|
|
[Datasource.ERROR]: {
|
|
dataset: Dataset.ERRORS,
|
|
eventTypes: [EventTypes.ERROR],
|
|
},
|
|
[Datasource.DEFAULT]: {
|
|
dataset: Dataset.ERRORS,
|
|
eventTypes: [EventTypes.DEFAULT],
|
|
},
|
|
[Datasource.TRANSACTION]: {
|
|
dataset: Dataset.TRANSACTIONS,
|
|
eventTypes: [EventTypes.TRANSACTION],
|
|
},
|
|
};
|
|
|
|
// Converts the given dataset and event types array to a datasource for the datasource dropdown
|
|
export function convertDatasetEventTypesToSource(
|
|
dataset: Dataset,
|
|
eventTypes: EventTypes[]
|
|
) {
|
|
// transactions and generic_metrics only have one datasource option regardless of event type
|
|
if (dataset === Dataset.TRANSACTIONS || dataset === Dataset.GENERIC_METRICS) {
|
|
return Datasource.TRANSACTION;
|
|
}
|
|
// if no event type was provided use the default datasource
|
|
if (!eventTypes) {
|
|
return Datasource.ERROR;
|
|
}
|
|
|
|
if (eventTypes.includes(EventTypes.DEFAULT) && eventTypes.includes(EventTypes.ERROR)) {
|
|
return Datasource.ERROR_DEFAULT;
|
|
}
|
|
if (eventTypes.includes(EventTypes.DEFAULT)) {
|
|
return Datasource.DEFAULT;
|
|
}
|
|
return Datasource.ERROR;
|
|
}
|
|
|
|
/**
|
|
* Attempt to guess the data source of a discover query
|
|
*
|
|
* @returns An object containing the datasource and new query without the datasource.
|
|
* Returns null on no datasource.
|
|
*/
|
|
export function getQueryDatasource(
|
|
query: string
|
|
): {query: string; source: Datasource} | null {
|
|
let match = query.match(
|
|
/\(?\bevent\.type:(error|default|transaction)\)?\WOR\W\(?event\.type:(error|default|transaction)\)?/i
|
|
);
|
|
if (match) {
|
|
// should be [error, default] or [default, error]
|
|
const eventTypes = match.slice(1, 3).sort().join(',');
|
|
if (eventTypes !== 'default,error') {
|
|
return null;
|
|
}
|
|
|
|
return {source: Datasource.ERROR_DEFAULT, query: query.replace(match[0], '').trim()};
|
|
}
|
|
|
|
match = query.match(/(^|\s)event\.type:(error|default|transaction)/i);
|
|
if (match && Datasource[match[2].toUpperCase()]) {
|
|
return {
|
|
source: Datasource[match[2].toUpperCase()],
|
|
query: query.replace(match[0], '').trim(),
|
|
};
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
export function isSessionAggregate(aggregate: string) {
|
|
return Object.values(SessionsAggregate).includes(aggregate as SessionsAggregate);
|
|
}
|
|
|
|
export const SESSION_AGGREGATE_TO_FIELD = {
|
|
[SessionsAggregate.CRASH_FREE_SESSIONS]: SessionFieldWithOperation.SESSIONS,
|
|
[SessionsAggregate.CRASH_FREE_USERS]: SessionFieldWithOperation.USERS,
|
|
};
|
|
|
|
export function alertAxisFormatter(value: number, seriesName: string, aggregate: string) {
|
|
if (isSessionAggregate(aggregate)) {
|
|
return defined(value) ? `${round(value, 2)}%` : '\u2015';
|
|
}
|
|
|
|
if (isCustomMetricAlert(aggregate)) {
|
|
const {mri, op} = parseField(aggregate)!;
|
|
const {unit} = parseMRI(mri)!;
|
|
return formatMetricUsingFixedUnit(value, unit, op);
|
|
}
|
|
|
|
return axisLabelFormatter(value, aggregateOutputType(seriesName));
|
|
}
|
|
|
|
export function alertTooltipValueFormatter(
|
|
value: number,
|
|
seriesName: string,
|
|
aggregate: string
|
|
) {
|
|
if (isSessionAggregate(aggregate)) {
|
|
return defined(value) ? `${value}%` : '\u2015';
|
|
}
|
|
|
|
if (isCustomMetricAlert(aggregate)) {
|
|
const {mri, op} = parseField(aggregate)!;
|
|
const {unit} = parseMRI(mri)!;
|
|
return formatMetricUsingFixedUnit(value, unit, op);
|
|
}
|
|
|
|
return tooltipFormatter(value, aggregateOutputType(seriesName));
|
|
}
|
|
|
|
export const ALERT_CHART_MIN_MAX_BUFFER = 1.03;
|
|
|
|
export function shouldScaleAlertChart(aggregate: string) {
|
|
// We want crash free rate charts to be scaled because they are usually too
|
|
// close to 100% and therefore too fine to see the spikes on 0%-100% scale.
|
|
return isSessionAggregate(aggregate);
|
|
}
|
|
|
|
export function alertDetailsLink(organization: Organization, incident: Incident) {
|
|
return `/organizations/${organization.slug}/alerts/rules/details/${
|
|
incident.alertRule.status === AlertRuleStatus.SNAPSHOT &&
|
|
incident.alertRule.originalAlertRuleId
|
|
? incident.alertRule.originalAlertRuleId
|
|
: incident.alertRule.id
|
|
}/`;
|
|
}
|
|
|
|
/**
|
|
* Noramlizes a status string
|
|
*/
|
|
export function getQueryStatus(status: string | string[]): string {
|
|
if (Array.isArray(status) || status === '') {
|
|
return 'all';
|
|
}
|
|
|
|
return ['open', 'closed'].includes(status) ? status : 'all';
|
|
}
|
|
|
|
const ALERT_LIST_QUERY_DEFAULT_TEAMS = ['myteams', 'unassigned'];
|
|
|
|
/**
|
|
* Noramlize a team slug from the query
|
|
*/
|
|
export function getTeamParams(team?: string | string[]): string[] {
|
|
if (team === undefined) {
|
|
return ALERT_LIST_QUERY_DEFAULT_TEAMS;
|
|
}
|
|
|
|
if (team === '') {
|
|
return [];
|
|
}
|
|
|
|
return toArray(team);
|
|
}
|