transactions.tsx 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. import {doEventsRequest} from 'sentry/actionCreators/events';
  2. import type {Client} from 'sentry/api';
  3. import type {PageFilters} from 'sentry/types/core';
  4. import type {Series} from 'sentry/types/echarts';
  5. import type {TagCollection} from 'sentry/types/group';
  6. import type {
  7. EventsStats,
  8. GroupedMultiSeriesEventsStats,
  9. MultiSeriesEventsStats,
  10. Organization,
  11. } from 'sentry/types/organization';
  12. import {defined} from 'sentry/utils';
  13. import type {CustomMeasurementCollection} from 'sentry/utils/customMeasurements/customMeasurements';
  14. import type {EventsTableData, TableData} from 'sentry/utils/discover/discoverQuery';
  15. import {
  16. type QueryFieldValue,
  17. SPAN_OP_BREAKDOWN_FIELDS,
  18. TRANSACTION_FIELDS,
  19. } from 'sentry/utils/discover/fields';
  20. import type {
  21. DiscoverQueryExtras,
  22. DiscoverQueryRequestParams,
  23. } from 'sentry/utils/discover/genericDiscoverQuery';
  24. import {doDiscoverQuery} from 'sentry/utils/discover/genericDiscoverQuery';
  25. import {DiscoverDatasets} from 'sentry/utils/discover/types';
  26. import {getMeasurements} from 'sentry/utils/measurements/measurements';
  27. import {MEPState} from 'sentry/utils/performance/contexts/metricsEnhancedSetting';
  28. import {
  29. type OnDemandControlContext,
  30. shouldUseOnDemandMetrics,
  31. } from 'sentry/utils/performance/contexts/onDemandControl';
  32. import {getSeriesRequestData} from 'sentry/views/dashboards/datasetConfig/utils/getSeriesRequestData';
  33. import {FieldValueKind} from 'sentry/views/discover/table/types';
  34. import {generateFieldOptions} from 'sentry/views/discover/utils';
  35. import type {Widget, WidgetQuery} from '../types';
  36. import {DisplayType} from '../types';
  37. import {eventViewFromWidget} from '../utils';
  38. import {transformEventsResponseToSeries} from '../utils/transformEventsResponseToSeries';
  39. import {EventsSearchBar} from '../widgetBuilder/buildSteps/filterResultsStep/eventsSearchBar';
  40. import {type DatasetConfig, handleOrderByReset} from './base';
  41. import {
  42. doOnDemandMetricsRequest,
  43. filterAggregateParams,
  44. filterSeriesSortOptions,
  45. filterYAxisAggregateParams,
  46. filterYAxisOptions,
  47. getCustomEventsFieldRenderer,
  48. getTableSortOptions,
  49. getTimeseriesSortOptions,
  50. transformEventsResponseToTable,
  51. } from './errorsAndTransactions';
  52. const DEFAULT_WIDGET_QUERY: WidgetQuery = {
  53. name: '',
  54. fields: ['count()'],
  55. columns: [],
  56. fieldAliases: [],
  57. aggregates: ['count()'],
  58. conditions: '',
  59. orderby: '-count()',
  60. };
  61. const DEFAULT_FIELD: QueryFieldValue = {
  62. function: ['count', '', undefined, undefined],
  63. kind: FieldValueKind.FUNCTION,
  64. };
  65. export type SeriesWithOrdering = [order: number, series: Series];
  66. export const TransactionsConfig: DatasetConfig<
  67. EventsStats | MultiSeriesEventsStats | GroupedMultiSeriesEventsStats,
  68. TableData | EventsTableData
  69. > = {
  70. defaultField: DEFAULT_FIELD,
  71. defaultWidgetQuery: DEFAULT_WIDGET_QUERY,
  72. enableEquations: true,
  73. getCustomFieldRenderer: getCustomEventsFieldRenderer,
  74. SearchBar: EventsSearchBar,
  75. filterSeriesSortOptions,
  76. filterYAxisAggregateParams,
  77. filterYAxisOptions,
  78. getTableFieldOptions: getEventsTableFieldOptions,
  79. getTimeseriesSortOptions,
  80. getTableSortOptions,
  81. getGroupByFieldOptions: getEventsTableFieldOptions,
  82. handleOrderByReset,
  83. supportedDisplayTypes: [
  84. DisplayType.AREA,
  85. DisplayType.BAR,
  86. DisplayType.BIG_NUMBER,
  87. DisplayType.LINE,
  88. DisplayType.TABLE,
  89. DisplayType.TOP_N,
  90. ],
  91. getTableRequest: (
  92. api: Client,
  93. widget: Widget,
  94. query: WidgetQuery,
  95. organization: Organization,
  96. pageFilters: PageFilters,
  97. onDemandControlContext?: OnDemandControlContext,
  98. limit?: number,
  99. cursor?: string,
  100. referrer?: string,
  101. mepSetting?: MEPState | null
  102. ) => {
  103. const useOnDemandMetrics = shouldUseOnDemandMetrics(
  104. organization,
  105. widget,
  106. onDemandControlContext
  107. );
  108. const queryExtras = {
  109. useOnDemandMetrics,
  110. onDemandType: 'dynamic_query',
  111. };
  112. return getEventsRequest(
  113. api,
  114. query,
  115. organization,
  116. pageFilters,
  117. limit,
  118. cursor,
  119. referrer,
  120. mepSetting,
  121. queryExtras
  122. );
  123. },
  124. getSeriesRequest: getEventsSeriesRequest,
  125. transformSeries: transformEventsResponseToSeries,
  126. transformTable: transformEventsResponseToTable,
  127. filterAggregateParams,
  128. };
  129. function getEventsTableFieldOptions(
  130. organization: Organization,
  131. tags?: TagCollection,
  132. customMeasurements?: CustomMeasurementCollection
  133. ) {
  134. const measurements = getMeasurements();
  135. return generateFieldOptions({
  136. organization,
  137. tagKeys: Object.values(tags ?? {}).map(({key}) => key),
  138. measurementKeys: Object.values(measurements).map(({key}) => key),
  139. spanOperationBreakdownKeys: SPAN_OP_BREAKDOWN_FIELDS,
  140. customMeasurements: Object.values(customMeasurements ?? {}).map(
  141. ({key, functions}) => ({
  142. key,
  143. functions,
  144. })
  145. ),
  146. fieldKeys: TRANSACTION_FIELDS,
  147. });
  148. }
  149. function getEventsRequest(
  150. api: Client,
  151. query: WidgetQuery,
  152. organization: Organization,
  153. pageFilters: PageFilters,
  154. limit?: number,
  155. cursor?: string,
  156. referrer?: string,
  157. mepSetting?: MEPState | null,
  158. queryExtras?: DiscoverQueryExtras
  159. ) {
  160. const isMEPEnabled = defined(mepSetting) && mepSetting !== MEPState.TRANSACTIONS_ONLY;
  161. const url = `/organizations/${organization.slug}/events/`;
  162. // To generate the target url for TRACE ID links we always include a timestamp,
  163. // to speed up the trace endpoint. Adding timestamp for the non-aggregate case and
  164. // max(timestamp) for the aggregate case as fields, to accomodate this.
  165. if (
  166. query.aggregates.length &&
  167. query.columns.includes('trace') &&
  168. !query.aggregates.includes('max(timestamp)') &&
  169. !query.columns.includes('timestamp')
  170. ) {
  171. query.aggregates.push('max(timestamp)');
  172. } else if (query.columns.includes('trace') && !query.columns.includes('timestamp')) {
  173. query.columns.push('timestamp');
  174. }
  175. const eventView = eventViewFromWidget('', query, pageFilters);
  176. const params: DiscoverQueryRequestParams = {
  177. per_page: limit,
  178. cursor,
  179. referrer,
  180. dataset: isMEPEnabled
  181. ? DiscoverDatasets.METRICS_ENHANCED
  182. : DiscoverDatasets.TRANSACTIONS,
  183. ...queryExtras,
  184. };
  185. if (query.orderby) {
  186. params.sort = typeof query.orderby === 'string' ? [query.orderby] : query.orderby;
  187. }
  188. return doDiscoverQuery<EventsTableData>(
  189. api,
  190. url,
  191. {
  192. ...eventView.generateQueryStringObject(),
  193. ...params,
  194. },
  195. // Tries events request up to 3 times on rate limit
  196. {
  197. retry: {
  198. statusCodes: [429],
  199. tries: 3,
  200. },
  201. }
  202. );
  203. }
  204. function getEventsSeriesRequest(
  205. api: Client,
  206. widget: Widget,
  207. queryIndex: number,
  208. organization: Organization,
  209. pageFilters: PageFilters,
  210. onDemandControlContext?: OnDemandControlContext,
  211. referrer?: string,
  212. mepSetting?: MEPState | null
  213. ) {
  214. const isMEPEnabled = defined(mepSetting) && mepSetting !== MEPState.TRANSACTIONS_ONLY;
  215. const requestData = getSeriesRequestData(
  216. widget,
  217. queryIndex,
  218. organization,
  219. pageFilters,
  220. isMEPEnabled ? DiscoverDatasets.METRICS_ENHANCED : DiscoverDatasets.TRANSACTIONS,
  221. referrer
  222. );
  223. if (shouldUseOnDemandMetrics(organization, widget, onDemandControlContext)) {
  224. requestData.queryExtras = {
  225. ...requestData.queryExtras,
  226. ...{dataset: DiscoverDatasets.METRICS_ENHANCED},
  227. };
  228. return doOnDemandMetricsRequest(api, requestData, widget.widgetType);
  229. }
  230. return doEventsRequest<true>(api, requestData);
  231. }