base.tsx 9.2 KB

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