base.tsx 8.5 KB

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