base.tsx 8.7 KB

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