widgetQueries.tsx 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. import {useContext} from 'react';
  2. import omit from 'lodash/omit';
  3. import {Client} from 'sentry/api';
  4. import {isMultiSeriesStats} from 'sentry/components/charts/utils';
  5. import {
  6. EventsStats,
  7. MultiSeriesEventsStats,
  8. Organization,
  9. PageFilters,
  10. } from 'sentry/types';
  11. import {Series} from 'sentry/types/echarts';
  12. import {EventsTableData, TableData} from 'sentry/utils/discover/discoverQuery';
  13. import {DURATION_UNITS, SIZE_UNITS} from 'sentry/utils/discover/fieldRenderers';
  14. import {getAggregateAlias} from 'sentry/utils/discover/fields';
  15. import {
  16. MetricsResultsMetaMapKey,
  17. useMetricsResultsMeta,
  18. } from 'sentry/utils/performance/contexts/metricsEnhancedPerformanceDataContext';
  19. import {useMEPSettingContext} from 'sentry/utils/performance/contexts/metricsEnhancedSetting';
  20. import {OnDemandControlConsumer} from 'sentry/utils/performance/contexts/onDemandControl';
  21. import {ErrorsAndTransactionsConfig} from '../datasetConfig/errorsAndTransactions';
  22. import {DashboardFilters, Widget} from '../types';
  23. import {DashboardsMEPContext} from './dashboardsMEPContext';
  24. import GenericWidgetQueries, {
  25. GenericWidgetQueriesChildrenProps,
  26. OnDataFetchedProps,
  27. } from './genericWidgetQueries';
  28. type SeriesResult = EventsStats | MultiSeriesEventsStats;
  29. type TableResult = TableData | EventsTableData;
  30. type SeriesWithOrdering = [order: number, series: Series];
  31. export function transformSeries(
  32. stats: EventsStats,
  33. seriesName: string,
  34. field: string
  35. ): Series {
  36. const unit = stats.meta?.units?.[getAggregateAlias(field)];
  37. // Scale series values to milliseconds or bytes depending on units from meta
  38. const scale = (unit && (DURATION_UNITS[unit] ?? SIZE_UNITS[unit])) ?? 1;
  39. return {
  40. seriesName,
  41. data:
  42. stats?.data?.map(([timestamp, counts]) => {
  43. return {
  44. name: timestamp * 1000,
  45. value: counts.reduce((acc, {count}) => acc + count, 0) * scale,
  46. };
  47. }) ?? [],
  48. };
  49. }
  50. /**
  51. * Multiseries data with a grouping needs to be "flattened" because the aggregate data
  52. * are stored under the group names. These names need to be combined with the aggregate
  53. * names to show a series.
  54. *
  55. * e.g. count() and count_unique() grouped by environment
  56. * {
  57. * "local": {
  58. * "count()": {...},
  59. * "count_unique()": {...}
  60. * },
  61. * "prod": {
  62. * "count()": {...},
  63. * "count_unique()": {...}
  64. * }
  65. * }
  66. */
  67. export function flattenMultiSeriesDataWithGrouping(
  68. result: SeriesResult,
  69. queryAlias: string
  70. ): SeriesWithOrdering[] {
  71. const seriesWithOrdering: SeriesWithOrdering[] = [];
  72. const groupNames = Object.keys(result);
  73. groupNames.forEach(groupName => {
  74. // Each group contains an order key which we should ignore
  75. const aggregateNames = Object.keys(omit(result[groupName], 'order'));
  76. aggregateNames.forEach(aggregate => {
  77. const seriesName = `${groupName} : ${aggregate}`;
  78. const prefixedName = queryAlias ? `${queryAlias} > ${seriesName}` : seriesName;
  79. const seriesData: EventsStats = result[groupName][aggregate];
  80. seriesWithOrdering.push([
  81. result[groupName].order || 0,
  82. transformSeries(seriesData, prefixedName, seriesName),
  83. ]);
  84. });
  85. });
  86. return seriesWithOrdering;
  87. }
  88. export function getIsMetricsDataFromSeriesResponse(
  89. result: SeriesResult
  90. ): boolean | undefined {
  91. const multiIsMetricsData = Object.values(result)
  92. .map(({isMetricsData}) => isMetricsData)
  93. // One non-metrics series will cause all of them to be marked as such
  94. .reduce((acc, value) => (acc === false ? false : value), undefined);
  95. return isMultiSeriesStats(result) ? multiIsMetricsData : result.isMetricsData;
  96. }
  97. type Props = {
  98. api: Client;
  99. children: (props: GenericWidgetQueriesChildrenProps) => JSX.Element;
  100. organization: Organization;
  101. selection: PageFilters;
  102. widget: Widget;
  103. cursor?: string;
  104. dashboardFilters?: DashboardFilters;
  105. limit?: number;
  106. onDataFetched?: (results: OnDataFetchedProps) => void;
  107. };
  108. function WidgetQueries({
  109. api,
  110. children,
  111. organization,
  112. selection,
  113. widget,
  114. dashboardFilters,
  115. cursor,
  116. limit,
  117. onDataFetched,
  118. }: Props) {
  119. const config = ErrorsAndTransactionsConfig;
  120. const context = useContext(DashboardsMEPContext);
  121. const metricsMeta = useMetricsResultsMeta();
  122. const mepSettingContext = useMEPSettingContext();
  123. let setIsMetricsData: undefined | ((value?: boolean) => void);
  124. let setIsMetricsExtractedData:
  125. | undefined
  126. | ((mapKey: MetricsResultsMetaMapKey, value?: boolean) => void);
  127. if (context) {
  128. setIsMetricsData = context.setIsMetricsData;
  129. }
  130. if (metricsMeta) {
  131. setIsMetricsExtractedData = metricsMeta.setIsMetricsExtractedData;
  132. }
  133. const isSeriesMetricsDataResults: boolean[] = [];
  134. const isSeriesMetricsExtractedDataResults: (boolean | undefined)[] = [];
  135. const afterFetchSeriesData = (rawResults: SeriesResult) => {
  136. if (rawResults.data) {
  137. rawResults = rawResults as EventsStats;
  138. if (rawResults.isMetricsData !== undefined) {
  139. isSeriesMetricsDataResults.push(rawResults.isMetricsData);
  140. }
  141. if (rawResults.isMetricsExtractedData !== undefined) {
  142. isSeriesMetricsExtractedDataResults.push(rawResults.isMetricsExtractedData);
  143. }
  144. isSeriesMetricsExtractedDataResults.push(
  145. rawResults.isMetricsExtractedData || rawResults.meta?.isMetricsExtractedData
  146. );
  147. } else {
  148. Object.keys(rawResults).forEach(key => {
  149. const rawResult: EventsStats = rawResults[key];
  150. if (rawResult.isMetricsData !== undefined) {
  151. isSeriesMetricsDataResults.push(rawResult.isMetricsData);
  152. }
  153. if (
  154. (rawResult.isMetricsExtractedData || rawResult.meta?.isMetricsExtractedData) !==
  155. undefined
  156. ) {
  157. isSeriesMetricsExtractedDataResults.push(
  158. rawResult.isMetricsExtractedData || rawResult.meta?.isMetricsExtractedData
  159. );
  160. }
  161. });
  162. }
  163. // If one of the queries is sampled, then mark the whole thing as sampled
  164. setIsMetricsData?.(!isSeriesMetricsDataResults.includes(false));
  165. setIsMetricsExtractedData?.(
  166. widget,
  167. isSeriesMetricsExtractedDataResults.every(Boolean) &&
  168. isSeriesMetricsExtractedDataResults.some(Boolean)
  169. );
  170. };
  171. const isTableMetricsDataResults: boolean[] = [];
  172. const isTableMetricsExtractedDataResults: boolean[] = [];
  173. const afterFetchTableData = (rawResults: TableResult) => {
  174. if (rawResults.meta?.isMetricsData !== undefined) {
  175. isTableMetricsDataResults.push(rawResults.meta.isMetricsData);
  176. }
  177. if (rawResults.meta?.isMetricsExtractedData !== undefined) {
  178. isTableMetricsExtractedDataResults.push(rawResults.meta.isMetricsExtractedData);
  179. }
  180. // If one of the queries is sampled, then mark the whole thing as sampled
  181. setIsMetricsData?.(!isTableMetricsDataResults.includes(false));
  182. setIsMetricsExtractedData?.(
  183. widget,
  184. isTableMetricsExtractedDataResults.every(Boolean) &&
  185. isTableMetricsExtractedDataResults.some(Boolean)
  186. );
  187. };
  188. return (
  189. <OnDemandControlConsumer>
  190. {OnDemandControlContext => (
  191. <GenericWidgetQueries<SeriesResult, TableResult>
  192. config={config}
  193. api={api}
  194. organization={organization}
  195. selection={selection}
  196. widget={widget}
  197. cursor={cursor}
  198. limit={limit}
  199. dashboardFilters={dashboardFilters}
  200. onDataFetched={onDataFetched}
  201. afterFetchSeriesData={afterFetchSeriesData}
  202. afterFetchTableData={afterFetchTableData}
  203. mepSetting={mepSettingContext.metricSettingState}
  204. onDemandControlContext={OnDemandControlContext}
  205. {...OnDemandControlContext}
  206. >
  207. {children}
  208. </GenericWidgetQueries>
  209. )}
  210. </OnDemandControlConsumer>
  211. );
  212. }
  213. export default WidgetQueries;