123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245 |
- import omit from 'lodash/omit';
- import {doMetricsRequest} from 'sentry/actionCreators/metrics';
- import {Client, ResponseMeta} from 'sentry/api';
- import {t} from 'sentry/locale';
- import {
- MetricsApiResponse,
- Organization,
- PageFilters,
- SessionApiResponse,
- SessionField,
- } from 'sentry/types';
- import {Series} from 'sentry/types/echarts';
- import {TableData} from 'sentry/utils/discover/discoverQuery';
- import {getFieldRenderer} from 'sentry/utils/discover/fieldRenderers';
- import {OnDemandControlContext} from 'sentry/utils/performance/contexts/onDemandControl';
- import {ReleaseSearchBar} from 'sentry/views/dashboards/widgetBuilder/buildSteps/filterResultsStep/releaseSearchBar';
- import {DisplayType, Widget, WidgetQuery} from '../types';
- import {getWidgetInterval} from '../utils';
- import {resolveDerivedStatusFields} from '../widgetCard/metricWidgetQueries';
- import {getSeriesName} from '../widgetCard/transformSessionsResponseToSeries';
- import {
- changeObjectValuesToTypes,
- getDerivedMetrics,
- mapDerivedMetricsToFields,
- } from '../widgetCard/transformSessionsResponseToTable';
- import {DatasetConfig, handleOrderByReset} from './base';
- const DEFAULT_WIDGET_QUERY: WidgetQuery = {
- name: '',
- fields: [`avg(duration)`],
- columns: [],
- fieldAliases: [],
- aggregates: [`avg(duration)`],
- conditions: '',
- orderby: `-avg(duration)`,
- };
- export const MetricsConfig: DatasetConfig<MetricsApiResponse, MetricsApiResponse> = {
- defaultWidgetQuery: DEFAULT_WIDGET_QUERY,
- enableEquations: false,
- getTableRequest: (
- api: Client,
- _: Widget,
- query: WidgetQuery,
- organization: Organization,
- pageFilters: PageFilters,
- __?: OnDemandControlContext,
- limit?: number,
- cursor?: string
- ) =>
- getMetricRequest(
- 0,
- 1,
- api,
- query,
- organization,
- pageFilters,
- undefined,
- limit,
- cursor
- ),
- getSeriesRequest: getMetricSeriesRequest,
- getCustomFieldRenderer: (field, meta) => getFieldRenderer(field, meta, false),
- // TODO(ddm): check if we need a MetricSearchBar
- SearchBar: ReleaseSearchBar,
- handleColumnFieldChangeOverride,
- handleOrderByReset: handleMetricTableOrderByReset,
- supportedDisplayTypes: [
- DisplayType.AREA,
- DisplayType.BAR,
- DisplayType.BIG_NUMBER,
- DisplayType.LINE,
- DisplayType.TABLE,
- DisplayType.TOP_N,
- ],
- transformSeries: transformSessionsResponseToSeries,
- transformTable: transformSessionsResponseToTable,
- getTableFieldOptions: () => ({}),
- getTableSortOptions: undefined,
- };
- function getMetricSeriesRequest(
- api: Client,
- widget: Widget,
- queryIndex: number,
- organization: Organization,
- pageFilters: PageFilters
- ) {
- const query = widget.queries[queryIndex];
- const {displayType, limit} = widget;
- const {datetime} = pageFilters;
- const {start, end, period} = datetime;
- const includeTotals = query.columns.length > 0 ? 1 : 0;
- const interval = getWidgetInterval(
- displayType,
- {start, end, period},
- '5m'
- // requesting low fidelity for release sort because metrics api can't return 100 rows of high fidelity series data
- );
- return getMetricRequest(
- 1,
- includeTotals,
- api,
- query,
- organization,
- pageFilters,
- interval,
- limit
- );
- }
- function handleMetricTableOrderByReset(widgetQuery: WidgetQuery, newFields: string[]) {
- const disableSortBy = widgetQuery.columns.includes('session.status');
- if (disableSortBy) {
- widgetQuery.orderby = '';
- }
- return handleOrderByReset(widgetQuery, newFields);
- }
- function handleColumnFieldChangeOverride(widgetQuery: WidgetQuery): WidgetQuery {
- if (widgetQuery.aggregates.length === 0) {
- // Release Health widgets require an aggregate in tables
- const defaultReleaseHealthAggregate = `crash_free_rate(${SessionField.SESSION})`;
- widgetQuery.aggregates = [defaultReleaseHealthAggregate];
- widgetQuery.fields = widgetQuery.fields
- ? [...widgetQuery.fields, defaultReleaseHealthAggregate]
- : [defaultReleaseHealthAggregate];
- }
- return widgetQuery;
- }
- export function transformSessionsResponseToTable(
- data: SessionApiResponse | MetricsApiResponse,
- widgetQuery: WidgetQuery
- ): TableData {
- const {derivedStatusFields, injectedFields} = resolveDerivedStatusFields(
- widgetQuery.aggregates
- );
- const rows = data.groups.map((group, index) => ({
- id: String(index),
- ...mapDerivedMetricsToFields(group.by),
- // if `sum(session)` or `count_unique(user)` are not
- // requested as a part of the payload for
- // derived status metrics through the Sessions API,
- // they are injected into the payload and need to be
- // stripped.
- ...omit(mapDerivedMetricsToFields(group.totals), injectedFields),
- // if session.status is a groupby, some post processing
- // is needed to calculate the status derived metrics
- // from grouped results of `sum(session)` or `count_unique(user)`
- ...getDerivedMetrics(group.by, group.totals, derivedStatusFields),
- }));
- const singleRow = rows[0];
- const meta = {
- ...changeObjectValuesToTypes(omit(singleRow, 'id')),
- };
- return {meta, data: rows};
- }
- export function transformSessionsResponseToSeries(
- data: SessionApiResponse | MetricsApiResponse,
- widgetQuery: WidgetQuery
- ) {
- if (data === null) {
- return [];
- }
- const queryAlias = widgetQuery.name;
- const results: Series[] = [];
- if (!data.groups.length) {
- return [
- {
- seriesName: `(${t('no results')})`,
- data: data.intervals.map(interval => ({
- name: interval,
- value: 0,
- })),
- },
- ];
- }
- data.groups.forEach(group => {
- Object.keys(group.series).forEach(field => {
- results.push({
- seriesName: getSeriesName(field, group, queryAlias),
- data: data.intervals.map((interval, index) => ({
- name: interval,
- value: group.series[field][index] ?? 0,
- })),
- });
- });
- });
- return results;
- }
- function getMetricRequest(
- includeSeries: number,
- includeTotals: number,
- api: Client,
- query: WidgetQuery,
- organization: Organization,
- pageFilters: PageFilters,
- interval?: string,
- limit?: number,
- cursor?: string
- ) {
- const {environments, projects, datetime} = pageFilters;
- const {start, end, period} = datetime;
- const columns = query.columns;
- const requestData = {
- field: query.aggregates,
- orgSlug: organization.slug,
- end,
- environment: environments,
- groupBy: columns,
- limit: columns.length === 0 ? 1 : limit,
- orderBy: '',
- interval,
- project: projects,
- query: query.conditions,
- start,
- statsPeriod: period,
- includeAllArgs: true,
- cursor,
- includeSeries,
- includeTotals,
- };
- // TODO(ddm): get rid of this cast
- return doMetricsRequest(api, requestData) as Promise<
- [MetricsApiResponse, string | undefined, ResponseMeta | undefined]
- >;
- }
|