spans.tsx 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. import type {Client} from 'sentry/api';
  2. import type {PageFilters} from 'sentry/types/core';
  3. import type {TagCollection} from 'sentry/types/group';
  4. import type {
  5. EventsStats,
  6. MultiSeriesEventsStats,
  7. Organization,
  8. } from 'sentry/types/organization';
  9. import toArray from 'sentry/utils/array/toArray';
  10. import type {CustomMeasurementCollection} from 'sentry/utils/customMeasurements/customMeasurements';
  11. import type {EventsTableData, TableData} from 'sentry/utils/discover/discoverQuery';
  12. import {
  13. type DiscoverQueryExtras,
  14. type DiscoverQueryRequestParams,
  15. doDiscoverQuery,
  16. } from 'sentry/utils/discover/genericDiscoverQuery';
  17. import {DiscoverDatasets} from 'sentry/utils/discover/types';
  18. import {ALLOWED_EXPLORE_VISUALIZE_AGGREGATES} from 'sentry/utils/fields';
  19. import type {MEPState} from 'sentry/utils/performance/contexts/metricsEnhancedSetting';
  20. import type {OnDemandControlContext} from 'sentry/utils/performance/contexts/onDemandControl';
  21. import {
  22. type DatasetConfig,
  23. handleOrderByReset,
  24. } from 'sentry/views/dashboards/datasetConfig/base';
  25. import {
  26. getCustomEventsFieldRenderer,
  27. getTableSortOptions,
  28. transformEventsResponseToSeries,
  29. transformEventsResponseToTable,
  30. } from 'sentry/views/dashboards/datasetConfig/errorsAndTransactions';
  31. import {DisplayType, type Widget, type WidgetQuery} from 'sentry/views/dashboards/types';
  32. import {eventViewFromWidget} from 'sentry/views/dashboards/utils';
  33. import SpansSearchBar from 'sentry/views/dashboards/widgetBuilder/buildSteps/filterResultsStep/spansSearchBar';
  34. import type {FieldValueOption} from 'sentry/views/discover/table/queryField';
  35. import {FieldValueKind} from 'sentry/views/discover/table/types';
  36. import {generateFieldOptions} from 'sentry/views/discover/utils';
  37. const DEFAULT_WIDGET_QUERY: WidgetQuery = {
  38. name: '',
  39. fields: ['span.op', 'avg(span.duration)'],
  40. columns: ['span.op'],
  41. fieldAliases: [],
  42. aggregates: ['avg(span.duration)'],
  43. conditions: '',
  44. orderby: '-avg(span.duration)',
  45. };
  46. const EAP_AGGREGATIONS = ALLOWED_EXPLORE_VISUALIZE_AGGREGATES.reduce((acc, aggregate) => {
  47. acc[aggregate] = {
  48. isSortable: true,
  49. outputType: null,
  50. parameters: [
  51. {
  52. kind: 'column',
  53. columnTypes: ['number', 'string'], // Need to keep the string type for unknown values before tags are resolved
  54. defaultValue: 'span.duration',
  55. required: true,
  56. },
  57. ],
  58. };
  59. return acc;
  60. }, {});
  61. export const SpansConfig: DatasetConfig<
  62. EventsStats | MultiSeriesEventsStats,
  63. TableData | EventsTableData
  64. > = {
  65. defaultWidgetQuery: DEFAULT_WIDGET_QUERY,
  66. enableEquations: false,
  67. getCustomFieldRenderer: getCustomEventsFieldRenderer,
  68. SearchBar: SpansSearchBar,
  69. filterSeriesSortOptions: () => () => true,
  70. filterYAxisAggregateParams: () => () => true,
  71. filterYAxisOptions: () => () => true,
  72. getTableFieldOptions: getEventsTableFieldOptions,
  73. // getTimeseriesSortOptions: (organization, widgetQuery, tags) =>
  74. // getTimeseriesSortOptions(organization, widgetQuery, tags, getEventsTableFieldOptions),
  75. getTableSortOptions: getTableSortOptions,
  76. getGroupByFieldOptions: getEventsTableFieldOptions,
  77. handleOrderByReset,
  78. supportedDisplayTypes: [
  79. // DisplayType.AREA,
  80. // DisplayType.BAR,
  81. // DisplayType.BIG_NUMBER,
  82. // DisplayType.LINE,
  83. DisplayType.TABLE,
  84. // DisplayType.TOP_N,
  85. ],
  86. getTableRequest: (
  87. api: Client,
  88. _widget: Widget,
  89. query: WidgetQuery,
  90. organization: Organization,
  91. pageFilters: PageFilters,
  92. _onDemandControlContext?: OnDemandControlContext,
  93. limit?: number,
  94. cursor?: string,
  95. referrer?: string,
  96. _mepSetting?: MEPState | null
  97. ) => {
  98. return getEventsRequest(
  99. api,
  100. query,
  101. organization,
  102. pageFilters,
  103. limit,
  104. cursor,
  105. referrer
  106. );
  107. },
  108. // getSeriesRequest: getErrorsSeriesRequest,
  109. transformTable: transformEventsResponseToTable,
  110. transformSeries: transformEventsResponseToSeries,
  111. filterTableOptions,
  112. filterAggregateParams,
  113. };
  114. function getEventsTableFieldOptions(
  115. organization: Organization,
  116. tags?: TagCollection,
  117. _customMeasurements?: CustomMeasurementCollection
  118. ) {
  119. const baseFieldOptions = generateFieldOptions({
  120. organization,
  121. tagKeys: [],
  122. fieldKeys: [],
  123. aggregations: EAP_AGGREGATIONS,
  124. });
  125. const spanTags = Object.values(tags ?? {}).reduce(
  126. (acc, tag) => ({
  127. ...acc,
  128. [`${tag.kind}:${tag.key}`]: {
  129. label: tag.name,
  130. value: {
  131. kind: FieldValueKind.TAG,
  132. // We have numeric and string tags which have the same
  133. // display name, but one is used for aggregates and the other
  134. // is used for grouping.
  135. meta: {name: tag.key, dataType: tag.kind === 'tag' ? 'string' : 'number'},
  136. },
  137. },
  138. }),
  139. {}
  140. );
  141. return {...baseFieldOptions, ...spanTags};
  142. }
  143. function filterTableOptions(option: FieldValueOption) {
  144. // Filter out numeric tags from primary options, they only show up in
  145. // the parameter fields for aggregate functions
  146. if ('dataType' in option.value.meta) {
  147. return option.value.meta.dataType !== 'number';
  148. }
  149. return true;
  150. }
  151. function filterAggregateParams(option: FieldValueOption) {
  152. // Allow for unknown values to be used for aggregate functions
  153. // This supports showing the tag value even if it's not in the current
  154. // set of tags.
  155. if ('unknown' in option.value.meta && option.value.meta.unknown) {
  156. return true;
  157. }
  158. if ('dataType' in option.value.meta) {
  159. return option.value.meta.dataType === 'number';
  160. }
  161. return true;
  162. }
  163. function getEventsRequest(
  164. api: Client,
  165. query: WidgetQuery,
  166. organization: Organization,
  167. pageFilters: PageFilters,
  168. limit?: number,
  169. cursor?: string,
  170. referrer?: string,
  171. _mepSetting?: MEPState | null,
  172. queryExtras?: DiscoverQueryExtras
  173. ) {
  174. const url = `/organizations/${organization.slug}/events/`;
  175. const eventView = eventViewFromWidget('', query, pageFilters);
  176. const params: DiscoverQueryRequestParams = {
  177. per_page: limit,
  178. cursor,
  179. referrer,
  180. dataset: DiscoverDatasets.SPANS_EAP,
  181. ...queryExtras,
  182. };
  183. if (query.orderby) {
  184. params.sort = toArray(query.orderby);
  185. }
  186. return doDiscoverQuery<EventsTableData>(
  187. api,
  188. url,
  189. {
  190. ...eventView.generateQueryStringObject(),
  191. ...params,
  192. },
  193. // Tries events request up to 3 times on rate limit
  194. {
  195. retry: {
  196. statusCodes: [429],
  197. tries: 3,
  198. },
  199. }
  200. );
  201. }