widgetQueries.tsx 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. import type {Client} from 'sentry/api';
  2. import {isMultiSeriesStats} from 'sentry/components/charts/utils';
  3. import type {PageFilters} from 'sentry/types/core';
  4. import type {Series} from 'sentry/types/echarts';
  5. import type {
  6. EventsStats,
  7. GroupedMultiSeriesEventsStats,
  8. MultiSeriesEventsStats,
  9. Organization,
  10. } from 'sentry/types/organization';
  11. import type {EventsTableData, TableData} from 'sentry/utils/discover/discoverQuery';
  12. import {DURATION_UNITS, SIZE_UNITS} from 'sentry/utils/discover/fieldRenderers';
  13. import {getAggregateAlias} from 'sentry/utils/discover/fields';
  14. import type {MetricsResultsMetaMapKey} from 'sentry/utils/performance/contexts/metricsEnhancedPerformanceDataContext';
  15. import {useMetricsResultsMeta} from 'sentry/utils/performance/contexts/metricsEnhancedPerformanceDataContext';
  16. import {useMEPSettingContext} from 'sentry/utils/performance/contexts/metricsEnhancedSetting';
  17. import {OnDemandControlConsumer} from 'sentry/utils/performance/contexts/onDemandControl';
  18. import {getDatasetConfig} from 'sentry/views/dashboards/datasetConfig/base';
  19. import {type DashboardFilters, type Widget, WidgetType} from '../types';
  20. import {useDashboardsMEPContext} from './dashboardsMEPContext';
  21. import type {
  22. GenericWidgetQueriesChildrenProps,
  23. OnDataFetchedProps,
  24. } from './genericWidgetQueries';
  25. import GenericWidgetQueries from './genericWidgetQueries';
  26. type SeriesResult = EventsStats | MultiSeriesEventsStats | GroupedMultiSeriesEventsStats;
  27. type TableResult = TableData | EventsTableData;
  28. export function transformSeries(
  29. stats: EventsStats,
  30. seriesName: string,
  31. field: string
  32. ): Series {
  33. const unit = stats.meta?.units?.[getAggregateAlias(field)];
  34. // Scale series values to milliseconds or bytes depending on units from meta
  35. // @ts-expect-error TS(7053): Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
  36. const scale = (unit && (DURATION_UNITS[unit] ?? SIZE_UNITS[unit])) ?? 1;
  37. return {
  38. seriesName,
  39. data:
  40. stats?.data?.map(([timestamp, counts]) => {
  41. return {
  42. name: timestamp * 1000,
  43. value: counts.reduce((acc, {count}) => acc + count, 0) * scale,
  44. };
  45. }) ?? [],
  46. };
  47. }
  48. export function getIsMetricsDataFromSeriesResponse(
  49. result: SeriesResult
  50. ): boolean | undefined {
  51. const multiIsMetricsData = Object.values(result)
  52. .map(({isMetricsData}) => isMetricsData)
  53. // One non-metrics series will cause all of them to be marked as such
  54. .reduce((acc, value) => (acc === false ? false : value), undefined);
  55. return isMultiSeriesStats(result) ? multiIsMetricsData : result.isMetricsData;
  56. }
  57. type Props = {
  58. api: Client;
  59. children: (props: GenericWidgetQueriesChildrenProps) => JSX.Element;
  60. organization: Organization;
  61. selection: PageFilters;
  62. widget: Widget;
  63. cursor?: string;
  64. dashboardFilters?: DashboardFilters;
  65. limit?: number;
  66. onDataFetched?: (results: OnDataFetchedProps) => void;
  67. onWidgetSplitDecision?: (splitDecision: WidgetType) => void;
  68. };
  69. function WidgetQueries({
  70. api,
  71. children,
  72. organization,
  73. selection,
  74. widget,
  75. dashboardFilters,
  76. cursor,
  77. limit,
  78. onDataFetched,
  79. onWidgetSplitDecision,
  80. }: Props) {
  81. // Discover and Errors datasets are the only datasets processed in this component
  82. const config = getDatasetConfig(
  83. widget.widgetType as WidgetType.DISCOVER | WidgetType.ERRORS | WidgetType.TRANSACTIONS
  84. );
  85. const context = useDashboardsMEPContext();
  86. const metricsMeta = useMetricsResultsMeta();
  87. const mepSettingContext = useMEPSettingContext();
  88. let setIsMetricsData: undefined | ((value?: boolean) => void);
  89. let setIsMetricsExtractedData:
  90. | undefined
  91. | ((mapKey: MetricsResultsMetaMapKey, value?: boolean) => void);
  92. if (context) {
  93. setIsMetricsData = context.setIsMetricsData;
  94. }
  95. if (metricsMeta) {
  96. setIsMetricsExtractedData = metricsMeta.setIsMetricsExtractedData;
  97. }
  98. const isSeriesMetricsDataResults: boolean[] = [];
  99. const isSeriesMetricsExtractedDataResults: Array<boolean | undefined> = [];
  100. const afterFetchSeriesData = (rawResults: SeriesResult) => {
  101. if (rawResults.data) {
  102. rawResults = rawResults as EventsStats;
  103. if (rawResults.isMetricsData !== undefined) {
  104. isSeriesMetricsDataResults.push(rawResults.isMetricsData);
  105. }
  106. if (rawResults.isMetricsExtractedData !== undefined) {
  107. isSeriesMetricsExtractedDataResults.push(rawResults.isMetricsExtractedData);
  108. }
  109. isSeriesMetricsExtractedDataResults.push(
  110. rawResults.isMetricsExtractedData || rawResults.meta?.isMetricsExtractedData
  111. );
  112. } else {
  113. Object.keys(rawResults).forEach(key => {
  114. // @ts-expect-error TS(7053): Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
  115. const rawResult: EventsStats = rawResults[key];
  116. if (rawResult.isMetricsData !== undefined) {
  117. isSeriesMetricsDataResults.push(rawResult.isMetricsData);
  118. }
  119. if (
  120. (rawResult.isMetricsExtractedData || rawResult.meta?.isMetricsExtractedData) !==
  121. undefined
  122. ) {
  123. isSeriesMetricsExtractedDataResults.push(
  124. rawResult.isMetricsExtractedData || rawResult.meta?.isMetricsExtractedData
  125. );
  126. }
  127. });
  128. }
  129. // If one of the queries is sampled, then mark the whole thing as sampled
  130. setIsMetricsData?.(!isSeriesMetricsDataResults.includes(false));
  131. setIsMetricsExtractedData?.(
  132. widget,
  133. isSeriesMetricsExtractedDataResults.every(Boolean) &&
  134. isSeriesMetricsExtractedDataResults.some(Boolean)
  135. );
  136. const resultValues = Object.values(rawResults);
  137. if (organization.features.includes('performance-discover-dataset-selector')) {
  138. let splitDecision: WidgetType | undefined = undefined;
  139. if (rawResults.meta) {
  140. splitDecision = (rawResults.meta as EventsStats['meta'])?.discoverSplitDecision;
  141. } else if (Object.values(rawResults).length > 0) {
  142. // Multi-series queries will have a meta key on each series
  143. // We can just read the decision from one.
  144. splitDecision = resultValues[0]?.meta?.discoverSplitDecision;
  145. }
  146. if (splitDecision) {
  147. // Update the dashboard state with the split decision
  148. onWidgetSplitDecision?.(splitDecision);
  149. }
  150. }
  151. };
  152. const isTableMetricsDataResults: boolean[] = [];
  153. const isTableMetricsExtractedDataResults: boolean[] = [];
  154. const afterFetchTableData = (rawResults: TableResult) => {
  155. if (rawResults.meta?.isMetricsData !== undefined) {
  156. isTableMetricsDataResults.push(rawResults.meta.isMetricsData);
  157. }
  158. if (rawResults.meta?.isMetricsExtractedData !== undefined) {
  159. isTableMetricsExtractedDataResults.push(rawResults.meta.isMetricsExtractedData);
  160. }
  161. // If one of the queries is sampled, then mark the whole thing as sampled
  162. setIsMetricsData?.(!isTableMetricsDataResults.includes(false));
  163. setIsMetricsExtractedData?.(
  164. widget,
  165. isTableMetricsExtractedDataResults.every(Boolean) &&
  166. isTableMetricsExtractedDataResults.some(Boolean)
  167. );
  168. if (
  169. organization.features.includes('performance-discover-dataset-selector') &&
  170. [WidgetType.ERRORS, WidgetType.TRANSACTIONS].includes(
  171. rawResults?.meta?.discoverSplitDecision
  172. )
  173. ) {
  174. // Update the dashboard state with the split decision
  175. onWidgetSplitDecision?.(rawResults?.meta?.discoverSplitDecision);
  176. }
  177. };
  178. return (
  179. <OnDemandControlConsumer>
  180. {OnDemandControlContext => (
  181. <GenericWidgetQueries<SeriesResult, TableResult>
  182. config={config}
  183. api={api}
  184. organization={organization}
  185. selection={selection}
  186. widget={widget}
  187. cursor={cursor}
  188. limit={limit}
  189. dashboardFilters={dashboardFilters}
  190. onDataFetched={onDataFetched}
  191. afterFetchSeriesData={afterFetchSeriesData}
  192. afterFetchTableData={afterFetchTableData}
  193. mepSetting={mepSettingContext.metricSettingState}
  194. onDemandControlContext={OnDemandControlContext}
  195. {...OnDemandControlContext}
  196. >
  197. {children}
  198. </GenericWidgetQueries>
  199. )}
  200. </OnDemandControlConsumer>
  201. );
  202. }
  203. export default WidgetQueries;