123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184 |
- import {useContext} from 'react';
- import omit from 'lodash/omit';
- import {Client} from 'sentry/api';
- import {isMultiSeriesStats} from 'sentry/components/charts/utils';
- import {
- EventsStats,
- MultiSeriesEventsStats,
- Organization,
- PageFilters,
- } from 'sentry/types';
- import {Series} from 'sentry/types/echarts';
- import {EventsTableData, TableData} from 'sentry/utils/discover/discoverQuery';
- import {DURATION_UNITS, SIZE_UNITS} from 'sentry/utils/discover/fieldRenderers';
- import {getAggregateAlias} from 'sentry/utils/discover/fields';
- import {ErrorsAndTransactionsConfig} from '../datasetConfig/errorsAndTransactions';
- import {DashboardFilters, Widget} from '../types';
- import {DashboardsMEPContext} from './dashboardsMEPContext';
- import GenericWidgetQueries, {
- GenericWidgetQueriesChildrenProps,
- OnDataFetchedProps,
- } from './genericWidgetQueries';
- type SeriesResult = EventsStats | MultiSeriesEventsStats;
- type TableResult = TableData | EventsTableData;
- type SeriesWithOrdering = [order: number, series: Series];
- export function transformSeries(
- stats: EventsStats,
- seriesName: string,
- field: string
- ): Series {
- const unit = stats.meta?.units?.[getAggregateAlias(field)];
- // Scale series values to milliseconds or bytes depending on units from meta
- const scale = (unit && (DURATION_UNITS[unit] ?? SIZE_UNITS[unit])) ?? 1;
- return {
- seriesName,
- data:
- stats?.data?.map(([timestamp, counts]) => {
- return {
- name: timestamp * 1000,
- value: counts.reduce((acc, {count}) => acc + count, 0) * scale,
- };
- }) ?? [],
- };
- }
- /**
- * Multiseries data with a grouping needs to be "flattened" because the aggregate data
- * are stored under the group names. These names need to be combined with the aggregate
- * names to show a series.
- *
- * e.g. count() and count_unique() grouped by environment
- * {
- * "local": {
- * "count()": {...},
- * "count_unique()": {...}
- * },
- * "prod": {
- * "count()": {...},
- * "count_unique()": {...}
- * }
- * }
- */
- export function flattenMultiSeriesDataWithGrouping(
- result: SeriesResult,
- queryAlias: string
- ): SeriesWithOrdering[] {
- const seriesWithOrdering: SeriesWithOrdering[] = [];
- const groupNames = Object.keys(result);
- groupNames.forEach(groupName => {
- // Each group contains an order key which we should ignore
- const aggregateNames = Object.keys(omit(result[groupName], 'order'));
- aggregateNames.forEach(aggregate => {
- const seriesName = `${groupName} : ${aggregate}`;
- const prefixedName = queryAlias ? `${queryAlias} > ${seriesName}` : seriesName;
- const seriesData: EventsStats = result[groupName][aggregate];
- seriesWithOrdering.push([
- result[groupName].order || 0,
- transformSeries(seriesData, prefixedName, seriesName),
- ]);
- });
- });
- return seriesWithOrdering;
- }
- export function getIsMetricsDataFromSeriesResponse(
- result: SeriesResult
- ): boolean | undefined {
- const multiIsMetricsData = Object.values(result)
- .map(({isMetricsData}) => isMetricsData)
- // One non-metrics series will cause all of them to be marked as such
- .reduce((acc, value) => (acc === false ? false : value), undefined);
- return isMultiSeriesStats(result) ? multiIsMetricsData : result.isMetricsData;
- }
- type Props = {
- api: Client;
- children: (props: GenericWidgetQueriesChildrenProps) => JSX.Element;
- organization: Organization;
- selection: PageFilters;
- widget: Widget;
- cursor?: string;
- dashboardFilters?: DashboardFilters;
- limit?: number;
- onDataFetched?: (results: OnDataFetchedProps) => void;
- };
- function WidgetQueries({
- api,
- children,
- organization,
- selection,
- widget,
- dashboardFilters,
- cursor,
- limit,
- onDataFetched,
- }: Props) {
- const config = ErrorsAndTransactionsConfig;
- const context = useContext(DashboardsMEPContext);
- let setIsMetricsData: undefined | ((value?: boolean) => void);
- if (context) {
- setIsMetricsData = context.setIsMetricsData;
- }
- const isSeriesMetricsDataResults: boolean[] = [];
- const afterFetchSeriesData = (rawResults: SeriesResult) => {
- if (rawResults.data) {
- rawResults = rawResults as EventsStats;
- if (rawResults.isMetricsData !== undefined) {
- isSeriesMetricsDataResults.push(rawResults.isMetricsData);
- }
- } else {
- Object.keys(rawResults).forEach(key => {
- const rawResult: EventsStats = rawResults[key];
- if (rawResult.isMetricsData !== undefined) {
- isSeriesMetricsDataResults.push(rawResult.isMetricsData);
- }
- });
- }
- // If one of the queries is sampled, then mark the whole thing as sampled
- setIsMetricsData?.(!isSeriesMetricsDataResults.includes(false));
- };
- const isTableMetricsDataResults: boolean[] = [];
- const afterFetchTableData = (rawResults: TableResult) => {
- if (rawResults.meta?.isMetricsData !== undefined) {
- isTableMetricsDataResults.push(rawResults.meta.isMetricsData);
- }
- // If one of the queries is sampled, then mark the whole thing as sampled
- setIsMetricsData?.(!isTableMetricsDataResults.includes(false));
- };
- return (
- <GenericWidgetQueries<SeriesResult, TableResult>
- config={config}
- api={api}
- organization={organization}
- selection={selection}
- widget={widget}
- cursor={cursor}
- limit={limit}
- dashboardFilters={dashboardFilters}
- onDataFetched={onDataFetched}
- afterFetchSeriesData={afterFetchSeriesData}
- afterFetchTableData={afterFetchTableData}
- >
- {children}
- </GenericWidgetQueries>
- );
- }
- export default WidgetQueries;
|