errors.tsx 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  1. import trimStart from 'lodash/trimStart';
  2. import {doEventsRequest} from 'sentry/actionCreators/events';
  3. import type {Client} from 'sentry/api';
  4. import type {
  5. EventsStats,
  6. MultiSeriesEventsStats,
  7. Organization,
  8. PageFilters,
  9. TagCollection,
  10. } from 'sentry/types';
  11. import type {Series} from 'sentry/types/echarts';
  12. import type {CustomMeasurementCollection} from 'sentry/utils/customMeasurements/customMeasurements';
  13. import type {EventsTableData, TableData} from 'sentry/utils/discover/discoverQuery';
  14. import type {MetaType} from 'sentry/utils/discover/eventView';
  15. import {getFieldRenderer} from 'sentry/utils/discover/fieldRenderers';
  16. import {
  17. AGGREGATIONS,
  18. ERROR_FIELDS,
  19. ERRORS_AGGREGATION_FUNCTIONS,
  20. isEquation,
  21. isEquationAlias,
  22. } from 'sentry/utils/discover/fields';
  23. import type {DiscoverQueryRequestParams} from 'sentry/utils/discover/genericDiscoverQuery';
  24. import {doDiscoverQuery} from 'sentry/utils/discover/genericDiscoverQuery';
  25. import {DiscoverDatasets, TOP_N} from 'sentry/utils/discover/types';
  26. import type {AggregationKey} from 'sentry/utils/fields';
  27. import type {MEPState} from 'sentry/utils/performance/contexts/metricsEnhancedSetting';
  28. import type {OnDemandControlContext} from 'sentry/utils/performance/contexts/onDemandControl';
  29. import type {FieldValueOption} from 'sentry/views/discover/table/queryField';
  30. import {generateFieldOptions} from 'sentry/views/discover/utils';
  31. import type {Widget, WidgetQuery} from '../types';
  32. import {DisplayType} from '../types';
  33. import {eventViewFromWidget, getNumEquations, getWidgetInterval} from '../utils';
  34. import {EventsSearchBar} from '../widgetBuilder/buildSteps/filterResultsStep/eventsSearchBar';
  35. import {type DatasetConfig, handleOrderByReset} from './base';
  36. import {
  37. filterAggregateParams,
  38. filterSeriesSortOptions,
  39. filterYAxisAggregateParams, // TODO: Does this need to be overridden?
  40. getTableSortOptions,
  41. getTimeseriesSortOptions,
  42. renderEventIdAsLinkable,
  43. renderTraceAsLinkable,
  44. transformEventsResponseToSeries,
  45. transformEventsResponseToTable,
  46. } from './errorsAndTransactions';
  47. const DEFAULT_WIDGET_QUERY: WidgetQuery = {
  48. name: '',
  49. fields: ['count()'],
  50. columns: [],
  51. fieldAliases: [],
  52. aggregates: ['count()'],
  53. conditions: '',
  54. orderby: '-count()',
  55. };
  56. export type SeriesWithOrdering = [order: number, series: Series];
  57. // TODO: Commented out functions will be given implementations
  58. // to be able to make events-stats requests
  59. export const ErrorsConfig: DatasetConfig<
  60. EventsStats | MultiSeriesEventsStats,
  61. TableData | EventsTableData
  62. > = {
  63. defaultWidgetQuery: DEFAULT_WIDGET_QUERY,
  64. enableEquations: true,
  65. getCustomFieldRenderer: getCustomEventsFieldRenderer,
  66. SearchBar: EventsSearchBar,
  67. filterSeriesSortOptions,
  68. filterYAxisAggregateParams,
  69. filterYAxisOptions,
  70. getTableFieldOptions: getEventsTableFieldOptions,
  71. getTimeseriesSortOptions: (organization, widgetQuery, tags) =>
  72. getTimeseriesSortOptions(organization, widgetQuery, tags, getEventsTableFieldOptions),
  73. getTableSortOptions,
  74. getGroupByFieldOptions: getEventsTableFieldOptions,
  75. handleOrderByReset,
  76. supportedDisplayTypes: [
  77. DisplayType.AREA,
  78. DisplayType.BAR,
  79. DisplayType.BIG_NUMBER,
  80. DisplayType.LINE,
  81. DisplayType.TABLE,
  82. DisplayType.TOP_N,
  83. ],
  84. getTableRequest: (
  85. api: Client,
  86. _widget: Widget,
  87. query: WidgetQuery,
  88. organization: Organization,
  89. pageFilters: PageFilters,
  90. _onDemandControlContext?: OnDemandControlContext,
  91. limit?: number,
  92. cursor?: string,
  93. referrer?: string,
  94. _mepSetting?: MEPState | null
  95. ) => {
  96. return getEventsRequest(
  97. api,
  98. query,
  99. organization,
  100. pageFilters,
  101. limit,
  102. cursor,
  103. referrer
  104. );
  105. },
  106. getSeriesRequest: getErrorsSeriesRequest,
  107. transformTable: transformEventsResponseToTable,
  108. transformSeries: transformEventsResponseToSeries,
  109. filterAggregateParams,
  110. };
  111. function getEventsTableFieldOptions(
  112. organization: Organization,
  113. tags?: TagCollection,
  114. _customMeasurements?: CustomMeasurementCollection
  115. ) {
  116. return generateFieldOptions({
  117. organization,
  118. tagKeys: Object.values(tags ?? {}).map(({key}) => key),
  119. aggregations: Object.keys(AGGREGATIONS)
  120. .filter(key => ERRORS_AGGREGATION_FUNCTIONS.includes(key as AggregationKey))
  121. .reduce((obj, key) => {
  122. obj[key] = AGGREGATIONS[key];
  123. return obj;
  124. }, {}),
  125. fieldKeys: ERROR_FIELDS,
  126. });
  127. }
  128. export function getCustomEventsFieldRenderer(field: string, meta: MetaType) {
  129. if (field === 'id') {
  130. return renderEventIdAsLinkable;
  131. }
  132. if (field === 'trace') {
  133. return renderTraceAsLinkable;
  134. }
  135. return getFieldRenderer(field, meta, false);
  136. }
  137. export function getEventsRequest(
  138. api: Client,
  139. query: WidgetQuery,
  140. organization: Organization,
  141. pageFilters: PageFilters,
  142. limit?: number,
  143. cursor?: string,
  144. referrer?: string
  145. ) {
  146. const url = `/organizations/${organization.slug}/events/`;
  147. const eventView = eventViewFromWidget('', query, pageFilters);
  148. const params: DiscoverQueryRequestParams = {
  149. per_page: limit,
  150. cursor,
  151. referrer,
  152. dataset: DiscoverDatasets.ERRORS,
  153. };
  154. if (query.orderby) {
  155. params.sort = typeof query.orderby === 'string' ? [query.orderby] : query.orderby;
  156. }
  157. return doDiscoverQuery<EventsTableData>(
  158. api,
  159. url,
  160. {
  161. ...eventView.generateQueryStringObject(),
  162. ...params,
  163. },
  164. // Tries events request up to 3 times on rate limit
  165. {
  166. retry: {
  167. statusCodes: [429],
  168. tries: 3,
  169. },
  170. }
  171. );
  172. }
  173. // The y-axis options are a strict set of available aggregates
  174. export function filterYAxisOptions(_displayType: DisplayType) {
  175. return (option: FieldValueOption) => {
  176. return ERRORS_AGGREGATION_FUNCTIONS.includes(
  177. option.value.meta.name as AggregationKey
  178. );
  179. };
  180. }
  181. function getErrorsSeriesRequest(
  182. api: Client,
  183. widget: Widget,
  184. queryIndex: number,
  185. organization: Organization,
  186. pageFilters: PageFilters,
  187. _onDemandControlContext?: OnDemandControlContext,
  188. referrer?: string,
  189. _mepSetting?: MEPState | null
  190. ) {
  191. const widgetQuery = widget.queries[queryIndex];
  192. const {displayType, limit} = widget;
  193. const {environments, projects} = pageFilters;
  194. const {start, end, period: statsPeriod} = pageFilters.datetime;
  195. const interval = getWidgetInterval(
  196. displayType,
  197. {start, end, period: statsPeriod},
  198. '1m'
  199. );
  200. let requestData;
  201. if (displayType === DisplayType.TOP_N) {
  202. requestData = {
  203. organization,
  204. interval,
  205. start,
  206. end,
  207. project: projects,
  208. environment: environments,
  209. period: statsPeriod,
  210. query: widgetQuery.conditions,
  211. yAxis: widgetQuery.aggregates[widgetQuery.aggregates.length - 1],
  212. includePrevious: false,
  213. referrer,
  214. partial: true,
  215. field: [...widgetQuery.columns, ...widgetQuery.aggregates],
  216. includeAllArgs: true,
  217. topEvents: TOP_N,
  218. dataset: DiscoverDatasets.ERRORS,
  219. };
  220. if (widgetQuery.orderby) {
  221. requestData.orderby = widgetQuery.orderby;
  222. }
  223. } else {
  224. requestData = {
  225. organization,
  226. interval,
  227. start,
  228. end,
  229. project: projects,
  230. environment: environments,
  231. period: statsPeriod,
  232. query: widgetQuery.conditions,
  233. yAxis: widgetQuery.aggregates,
  234. orderby: widgetQuery.orderby,
  235. includePrevious: false,
  236. referrer,
  237. partial: true,
  238. includeAllArgs: true,
  239. dataset: DiscoverDatasets.ERRORS,
  240. };
  241. if (widgetQuery.columns?.length !== 0) {
  242. requestData.topEvents = limit ?? TOP_N;
  243. requestData.field = [...widgetQuery.columns, ...widgetQuery.aggregates];
  244. // Compare field and orderby as aliases to ensure requestData has
  245. // the orderby selected
  246. // If the orderby is an equation alias, do not inject it
  247. const orderby = trimStart(widgetQuery.orderby, '-');
  248. if (
  249. widgetQuery.orderby &&
  250. !isEquationAlias(orderby) &&
  251. !requestData.field.includes(orderby)
  252. ) {
  253. requestData.field.push(orderby);
  254. }
  255. // The "Other" series is only included when there is one
  256. // y-axis and one widgetQuery
  257. requestData.excludeOther =
  258. widgetQuery.aggregates.length !== 1 || widget.queries.length !== 1;
  259. if (isEquation(trimStart(widgetQuery.orderby, '-'))) {
  260. const nextEquationIndex = getNumEquations(widgetQuery.aggregates);
  261. const isDescending = widgetQuery.orderby.startsWith('-');
  262. const prefix = isDescending ? '-' : '';
  263. // Construct the alias form of the equation and inject it into the request
  264. requestData.orderby = `${prefix}equation[${nextEquationIndex}]`;
  265. requestData.field = [
  266. ...widgetQuery.columns,
  267. ...widgetQuery.aggregates,
  268. trimStart(widgetQuery.orderby, '-'),
  269. ];
  270. }
  271. }
  272. }
  273. return doEventsRequest<true>(api, requestData);
  274. }