index.tsx 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. import round from 'lodash/round';
  2. import {t} from 'sentry/locale';
  3. import {Organization, SessionFieldWithOperation} from 'sentry/types';
  4. import {IssueAlertRule} from 'sentry/types/alerts';
  5. import {defined} from 'sentry/utils';
  6. import {getUtcDateString} from 'sentry/utils/dates';
  7. import {axisLabelFormatter, tooltipFormatter} from 'sentry/utils/discover/charts';
  8. import {aggregateOutputType} from 'sentry/utils/discover/fields';
  9. import {
  10. Dataset,
  11. Datasource,
  12. EventTypes,
  13. MetricRule,
  14. SavedMetricRule,
  15. SessionsAggregate,
  16. } from 'sentry/views/alerts/rules/metric/types';
  17. import {AlertRuleStatus, Incident, IncidentStats} from '../types';
  18. /**
  19. * Gets start and end date query parameters from stats
  20. */
  21. export function getStartEndFromStats(stats: IncidentStats) {
  22. const start = getUtcDateString(stats.eventStats.data[0][0] * 1000);
  23. const end = getUtcDateString(
  24. stats.eventStats.data[stats.eventStats.data.length - 1][0] * 1000
  25. );
  26. return {start, end};
  27. }
  28. export function isIssueAlert(
  29. data: IssueAlertRule | SavedMetricRule | MetricRule
  30. ): data is IssueAlertRule {
  31. return !data.hasOwnProperty('triggers');
  32. }
  33. export const DATA_SOURCE_LABELS = {
  34. [Dataset.ERRORS]: t('Errors'),
  35. [Dataset.TRANSACTIONS]: t('Transactions'),
  36. [Datasource.ERROR_DEFAULT]: 'event.type:error OR event.type:default',
  37. [Datasource.ERROR]: 'event.type:error',
  38. [Datasource.DEFAULT]: 'event.type:default',
  39. [Datasource.TRANSACTION]: 'event.type:transaction',
  40. };
  41. // Maps a datasource to the relevant dataset and event_types for the backend to use
  42. export const DATA_SOURCE_TO_SET_AND_EVENT_TYPES = {
  43. [Datasource.ERROR_DEFAULT]: {
  44. dataset: Dataset.ERRORS,
  45. eventTypes: [EventTypes.ERROR, EventTypes.DEFAULT],
  46. },
  47. [Datasource.ERROR]: {
  48. dataset: Dataset.ERRORS,
  49. eventTypes: [EventTypes.ERROR],
  50. },
  51. [Datasource.DEFAULT]: {
  52. dataset: Dataset.ERRORS,
  53. eventTypes: [EventTypes.DEFAULT],
  54. },
  55. [Datasource.TRANSACTION]: {
  56. dataset: Dataset.TRANSACTIONS,
  57. eventTypes: [EventTypes.TRANSACTION],
  58. },
  59. };
  60. // Converts the given dataset and event types array to a datasource for the datasource dropdown
  61. export function convertDatasetEventTypesToSource(
  62. dataset: Dataset,
  63. eventTypes: EventTypes[]
  64. ) {
  65. // transactions and generic_metrics only have one datasource option regardless of event type
  66. if (dataset === Dataset.TRANSACTIONS || dataset === Dataset.GENERIC_METRICS) {
  67. return Datasource.TRANSACTION;
  68. }
  69. // if no event type was provided use the default datasource
  70. if (!eventTypes) {
  71. return Datasource.ERROR;
  72. }
  73. if (eventTypes.includes(EventTypes.DEFAULT) && eventTypes.includes(EventTypes.ERROR)) {
  74. return Datasource.ERROR_DEFAULT;
  75. }
  76. if (eventTypes.includes(EventTypes.DEFAULT)) {
  77. return Datasource.DEFAULT;
  78. }
  79. return Datasource.ERROR;
  80. }
  81. /**
  82. * Attempt to guess the data source of a discover query
  83. *
  84. * @returns An object containing the datasource and new query without the datasource.
  85. * Returns null on no datasource.
  86. */
  87. export function getQueryDatasource(
  88. query: string
  89. ): {query: string; source: Datasource} | null {
  90. let match = query.match(
  91. /\(?\bevent\.type:(error|default|transaction)\)?\WOR\W\(?event\.type:(error|default|transaction)\)?/i
  92. );
  93. if (match) {
  94. // should be [error, default] or [default, error]
  95. const eventTypes = match.slice(1, 3).sort().join(',');
  96. if (eventTypes !== 'default,error') {
  97. return null;
  98. }
  99. return {source: Datasource.ERROR_DEFAULT, query: query.replace(match[0], '').trim()};
  100. }
  101. match = query.match(/(^|\s)event\.type:(error|default|transaction)/i);
  102. if (match && Datasource[match[2].toUpperCase()]) {
  103. return {
  104. source: Datasource[match[2].toUpperCase()],
  105. query: query.replace(match[0], '').trim(),
  106. };
  107. }
  108. return null;
  109. }
  110. export function isSessionAggregate(aggregate: string) {
  111. return Object.values(SessionsAggregate).includes(aggregate as SessionsAggregate);
  112. }
  113. export const SESSION_AGGREGATE_TO_FIELD = {
  114. [SessionsAggregate.CRASH_FREE_SESSIONS]: SessionFieldWithOperation.SESSIONS,
  115. [SessionsAggregate.CRASH_FREE_USERS]: SessionFieldWithOperation.USERS,
  116. };
  117. export function alertAxisFormatter(value: number, seriesName: string, aggregate: string) {
  118. if (isSessionAggregate(aggregate)) {
  119. return defined(value) ? `${round(value, 2)}%` : '\u2015';
  120. }
  121. return axisLabelFormatter(value, aggregateOutputType(seriesName));
  122. }
  123. export function alertTooltipValueFormatter(
  124. value: number,
  125. seriesName: string,
  126. aggregate: string
  127. ) {
  128. if (isSessionAggregate(aggregate)) {
  129. return defined(value) ? `${value}%` : '\u2015';
  130. }
  131. return tooltipFormatter(value, aggregateOutputType(seriesName));
  132. }
  133. export const ALERT_CHART_MIN_MAX_BUFFER = 1.03;
  134. export function shouldScaleAlertChart(aggregate: string) {
  135. // We want crash free rate charts to be scaled because they are usually too
  136. // close to 100% and therefore too fine to see the spikes on 0%-100% scale.
  137. return isSessionAggregate(aggregate);
  138. }
  139. export function alertDetailsLink(organization: Organization, incident: Incident) {
  140. return `/organizations/${organization.slug}/alerts/rules/details/${
  141. incident.alertRule.status === AlertRuleStatus.SNAPSHOT &&
  142. incident.alertRule.originalAlertRuleId
  143. ? incident.alertRule.originalAlertRuleId
  144. : incident.alertRule.id
  145. }/`;
  146. }
  147. /**
  148. * Noramlizes a status string
  149. */
  150. export function getQueryStatus(status: string | string[]): string {
  151. if (Array.isArray(status) || status === '') {
  152. return 'all';
  153. }
  154. return ['open', 'closed'].includes(status) ? status : 'all';
  155. }
  156. const ALERT_LIST_QUERY_DEFAULT_TEAMS = ['myteams', 'unassigned'];
  157. /**
  158. * Noramlize a team slug from the query
  159. */
  160. export function getTeamParams(team?: string | string[]): string[] {
  161. if (team === undefined) {
  162. return ALERT_LIST_QUERY_DEFAULT_TEAMS;
  163. }
  164. if (team === '') {
  165. return [];
  166. }
  167. if (Array.isArray(team)) {
  168. return team;
  169. }
  170. return [team];
  171. }