base.tsx 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  1. import trimStart from 'lodash/trimStart';
  2. import type {Client, ResponseMeta} from 'sentry/api';
  3. import type {SearchBarProps} from 'sentry/components/events/searchBar';
  4. import type {PageFilters, SelectValue} from 'sentry/types/core';
  5. import type {Series} from 'sentry/types/echarts';
  6. import type {TagCollection} from 'sentry/types/group';
  7. import type {Organization} from 'sentry/types/organization';
  8. import type {CustomMeasurementCollection} from 'sentry/utils/customMeasurements/customMeasurements';
  9. import type {TableData} from 'sentry/utils/discover/discoverQuery';
  10. import type {MetaType} from 'sentry/utils/discover/eventView';
  11. import type {getFieldRenderer} from 'sentry/utils/discover/fieldRenderers';
  12. import type {AggregationOutputType, QueryFieldValue} from 'sentry/utils/discover/fields';
  13. import {isEquation} from 'sentry/utils/discover/fields';
  14. import type {DiscoverDatasets} from 'sentry/utils/discover/types';
  15. import type {MEPState} from 'sentry/utils/performance/contexts/metricsEnhancedSetting';
  16. import type {OnDemandControlContext} from 'sentry/utils/performance/contexts/onDemandControl';
  17. import type {FieldValueOption} from 'sentry/views/discover/table/queryField';
  18. import type {FieldValue} from 'sentry/views/discover/table/types';
  19. import type {DisplayType, Widget, WidgetQuery} from '../types';
  20. import {WidgetType} from '../types';
  21. import {getNumEquations} from '../utils';
  22. import {ErrorsConfig} from './errors';
  23. import {ErrorsAndTransactionsConfig} from './errorsAndTransactions';
  24. import {IssuesConfig} from './issues';
  25. import {ReleasesConfig} from './releases';
  26. import {SpansConfig} from './spans';
  27. import {TransactionsConfig} from './transactions';
  28. export type WidgetBuilderSearchBarProps = {
  29. getFilterWarning: SearchBarProps['getFilterWarning'];
  30. onClose: SearchBarProps['onClose'];
  31. onSearch: SearchBarProps['onSearch'];
  32. pageFilters: PageFilters;
  33. widgetQuery: WidgetQuery;
  34. dataset?: DiscoverDatasets;
  35. };
  36. export interface DatasetConfig<SeriesResponse, TableResponse> {
  37. /**
  38. * Dataset specific search bar for the 'Filter' step in the
  39. * widget builder.
  40. */
  41. SearchBar: (props: WidgetBuilderSearchBarProps) => JSX.Element;
  42. /**
  43. * Default field to add to the widget query when adding a new field.
  44. */
  45. defaultField: QueryFieldValue;
  46. /**
  47. * Default query to display when dataset is selected in the
  48. * Widget Builder.
  49. */
  50. defaultWidgetQuery: WidgetQuery;
  51. /**
  52. * Whether or not the current dataset supports adding equations.
  53. */
  54. enableEquations: boolean;
  55. /**
  56. * Field options to display in the Column selectors for
  57. * Table display type.
  58. */
  59. getTableFieldOptions: (
  60. organization: Organization,
  61. tags?: TagCollection,
  62. customMeasurements?: CustomMeasurementCollection,
  63. api?: Client
  64. ) => Record<string, SelectValue<FieldValue>>;
  65. /**
  66. * List of supported display types for dataset.
  67. */
  68. supportedDisplayTypes: DisplayType[];
  69. /**
  70. * Transforms table API results into format that is used by
  71. * table and big number components.
  72. */
  73. transformTable: (
  74. data: TableResponse,
  75. widgetQuery: WidgetQuery,
  76. organization: Organization,
  77. pageFilters: PageFilters
  78. ) => TableData;
  79. /**
  80. * Configure enabling/disabling sort/direction options with an
  81. * optional message for why it is disabled.
  82. */
  83. disableSortOptions?: (widgetQuery: WidgetQuery) => {
  84. disableSort: boolean;
  85. disableSortDirection: boolean;
  86. disableSortReason?: string;
  87. };
  88. /**
  89. * Filter the options available to the parameters list
  90. * of an aggregate function in QueryField component on the
  91. * Widget Builder.
  92. */
  93. filterAggregateParams?: (
  94. option: FieldValueOption,
  95. fieldValue?: QueryFieldValue
  96. ) => boolean;
  97. /**
  98. * Refine the options available in the sort options for timeseries
  99. * displays on the 'Sort by' step of the Widget Builder.
  100. */
  101. filterSeriesSortOptions?: (
  102. columns: Set<string>
  103. ) => (option: FieldValueOption) => boolean;
  104. /**
  105. * Filter the primary options available in a table widget
  106. * columns on the Widget Builder.
  107. */
  108. filterTableOptions?: (option: FieldValueOption) => boolean;
  109. /**
  110. * Filter the options available to the parameters list
  111. * of an aggregate function in QueryField component on the
  112. * Widget Builder.
  113. */
  114. filterYAxisAggregateParams?: (
  115. fieldValue: QueryFieldValue,
  116. displayType: DisplayType
  117. ) => (option: FieldValueOption) => boolean;
  118. filterYAxisOptions?: (
  119. displayType: DisplayType
  120. ) => (option: FieldValueOption) => boolean;
  121. /**
  122. * Used to select custom renderers for field types.
  123. */
  124. getCustomFieldRenderer?: (
  125. field: string,
  126. meta: MetaType,
  127. organization?: Organization
  128. ) => ReturnType<typeof getFieldRenderer> | null;
  129. /**
  130. * Generate field header used for mapping column
  131. * names to more desirable values in tables.
  132. */
  133. getFieldHeaderMap?: (widgetQuery?: WidgetQuery) => Record<string, string>;
  134. /**
  135. * Field options to display in the Group by selector.
  136. */
  137. getGroupByFieldOptions?: (
  138. organization: Organization,
  139. tags?: TagCollection,
  140. customMeasurements?: CustomMeasurementCollection,
  141. api?: Client,
  142. queries?: WidgetQuery[]
  143. ) => Record<string, SelectValue<FieldValue>>;
  144. /**
  145. * Generate the request promises for fetching
  146. * series data.
  147. */
  148. getSeriesRequest?: (
  149. api: Client,
  150. widget: Widget,
  151. queryIndex: number,
  152. organization: Organization,
  153. pageFilters: PageFilters,
  154. onDemandControlContext?: OnDemandControlContext,
  155. referrer?: string,
  156. mepSetting?: MEPState | null
  157. ) => Promise<[SeriesResponse, string | undefined, ResponseMeta | undefined]>;
  158. /**
  159. * Get the result type of the series. ie duration, size, percentage, etc
  160. */
  161. getSeriesResultType?: (
  162. data: SeriesResponse,
  163. widgetQuery: WidgetQuery
  164. ) => Record<string, AggregationOutputType>;
  165. /**
  166. * Generate the request promises for fetching
  167. * tabular data.
  168. */
  169. getTableRequest?: (
  170. api: Client,
  171. widget: Widget,
  172. query: WidgetQuery,
  173. organization: Organization,
  174. pageFilters: PageFilters,
  175. onDemandControlContext?: OnDemandControlContext,
  176. limit?: number,
  177. cursor?: string,
  178. referrer?: string,
  179. mepSetting?: MEPState | null
  180. ) => Promise<[TableResponse, string | undefined, ResponseMeta | undefined]>;
  181. /**
  182. * Generate the list of sort options for table
  183. * displays on the 'Sort by' step of the Widget Builder.
  184. */
  185. getTableSortOptions?: (
  186. organization: Organization,
  187. widgetQuery: WidgetQuery
  188. ) => SelectValue<string>[];
  189. /**
  190. * Generate the list of sort options for timeseries
  191. * displays on the 'Sort by' step of the Widget Builder.
  192. */
  193. getTimeseriesSortOptions?: (
  194. organization: Organization,
  195. widgetQuery: WidgetQuery,
  196. tags?: TagCollection
  197. ) => Record<string, SelectValue<FieldValue>>;
  198. /**
  199. * Apply dataset specific overrides to the logic that handles
  200. * column updates for tables in the Widget Builder.
  201. */
  202. handleColumnFieldChangeOverride?: (widgetQuery: WidgetQuery) => WidgetQuery;
  203. /**
  204. * Called on column or y-axis change in the Widget Builder
  205. * to reset the orderby of the widget query.
  206. */
  207. handleOrderByReset?: (widgetQuery: WidgetQuery, newFields: string[]) => WidgetQuery;
  208. /**
  209. * Transforms timeseries API results into series data that is
  210. * ingestable by echarts for timeseries visualizations.
  211. */
  212. transformSeries?: (
  213. data: SeriesResponse,
  214. widgetQuery: WidgetQuery,
  215. organization: Organization
  216. ) => Series[];
  217. }
  218. export function getDatasetConfig<T extends WidgetType | undefined>(
  219. widgetType: T
  220. ): T extends WidgetType.ISSUE
  221. ? typeof IssuesConfig
  222. : T extends WidgetType.RELEASE
  223. ? typeof ReleasesConfig
  224. : T extends WidgetType.ERRORS
  225. ? typeof ErrorsConfig
  226. : T extends WidgetType.TRANSACTIONS
  227. ? typeof TransactionsConfig
  228. : typeof ErrorsAndTransactionsConfig;
  229. export function getDatasetConfig(
  230. widgetType?: WidgetType
  231. ):
  232. | typeof IssuesConfig
  233. | typeof ReleasesConfig
  234. | typeof ErrorsAndTransactionsConfig
  235. | typeof ErrorsConfig
  236. | typeof TransactionsConfig {
  237. switch (widgetType) {
  238. case WidgetType.ISSUE:
  239. return IssuesConfig;
  240. case WidgetType.RELEASE:
  241. return ReleasesConfig;
  242. case WidgetType.ERRORS:
  243. return ErrorsConfig;
  244. case WidgetType.TRANSACTIONS:
  245. return TransactionsConfig;
  246. case WidgetType.SPANS:
  247. return SpansConfig;
  248. case WidgetType.DISCOVER:
  249. default:
  250. return ErrorsAndTransactionsConfig;
  251. }
  252. }
  253. /**
  254. * A generic orderby reset helper function that updates the query's
  255. * orderby based on new selected fields.
  256. */
  257. export function handleOrderByReset(
  258. widgetQuery: WidgetQuery,
  259. newFields: string[]
  260. ): WidgetQuery {
  261. const rawOrderby = trimStart(widgetQuery.orderby, '-');
  262. const isDescending = widgetQuery.orderby.startsWith('-');
  263. const orderbyPrefix = isDescending ? '-' : '';
  264. if (!newFields.includes(rawOrderby) && widgetQuery.orderby !== '') {
  265. const isFromAggregates = widgetQuery.aggregates.includes(rawOrderby);
  266. const isCustomEquation = isEquation(rawOrderby);
  267. const isUsedInGrouping = widgetQuery.columns.includes(rawOrderby);
  268. const keepCurrentOrderby = isFromAggregates || isCustomEquation || isUsedInGrouping;
  269. const firstAggregateAlias = isEquation(widgetQuery.aggregates[0] ?? '')
  270. ? `equation[${getNumEquations(widgetQuery.aggregates) - 1}]`
  271. : newFields[0];
  272. widgetQuery.orderby =
  273. (keepCurrentOrderby && widgetQuery.orderby) ||
  274. `${orderbyPrefix}${firstAggregateAlias}`;
  275. }
  276. return widgetQuery;
  277. }