index.tsx 6.6 KB

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