123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294 |
- import {Fragment} from 'react';
- import {useTheme} from '@emotion/react';
- import type {Query} from 'history';
- import EventsRequest from 'sentry/components/charts/eventsRequest';
- import {HeaderTitleLegend} from 'sentry/components/charts/styles';
- import {getInterval, getSeriesSelection} from 'sentry/components/charts/utils';
- import {normalizeDateTimeParams} from 'sentry/components/organizations/pageFilters/parse';
- import QuestionTooltip from 'sentry/components/questionTooltip';
- import {t} from 'sentry/locale';
- import type {Series} from 'sentry/types/echarts';
- import type {
- EventsStats,
- EventsStatsData,
- OrganizationSummary,
- } from 'sentry/types/organization';
- import type {Project} from 'sentry/types/project';
- import {browserHistory} from 'sentry/utils/browserHistory';
- import {getUtcToLocalDateObject} from 'sentry/utils/dates';
- import type EventView from 'sentry/utils/discover/eventView';
- import {DURATION_UNITS, SIZE_UNITS} from 'sentry/utils/discover/fieldRenderers';
- import {getAggregateAlias} from 'sentry/utils/discover/fields';
- import {useMetricsCardinalityContext} from 'sentry/utils/performance/contexts/metricsCardinality';
- import TrendsDiscoverQuery from 'sentry/utils/performance/trends/trendsDiscoverQuery';
- import useApi from 'sentry/utils/useApi';
- import {useLocation} from 'sentry/utils/useLocation';
- import useRouter from 'sentry/utils/useRouter';
- import type {TrendFunctionField, TrendView} from 'sentry/views/performance/trends/types';
- import {TrendChangeType} from 'sentry/views/performance/trends/types';
- import {modifyTrendView, normalizeTrends} from 'sentry/views/performance/trends/utils';
- import generateTrendFunctionAsString from 'sentry/views/performance/trends/utils/generateTrendFunctionAsString';
- import type {ViewProps} from 'sentry/views/performance/types';
- import {getSelectedTransaction} from 'sentry/views/performance/utils/getSelectedTransaction';
- import Content from './content';
- type Props = ViewProps & {
- eventView: EventView;
- organization: OrganizationSummary;
- projects: Project[];
- queryExtra: Query;
- trendFunction: TrendFunctionField;
- trendParameter: string;
- withoutZerofill: boolean;
- withBreakpoint?: boolean;
- };
- function TrendChart({
- project,
- environment,
- organization,
- query,
- statsPeriod,
- trendFunction,
- trendParameter,
- queryExtra,
- withoutZerofill,
- withBreakpoint,
- eventView,
- start: propsStart,
- end: propsEnd,
- projects,
- }: Props) {
- const router = useRouter();
- const location = useLocation();
- const api = useApi();
- const theme = useTheme();
- const {isLoading: isCardinalityCheckLoading, outcome} = useMetricsCardinalityContext();
- const shouldGetBreakpoint =
- withBreakpoint && !isCardinalityCheckLoading && !outcome?.forceTransactionsOnly;
- function handleLegendSelectChanged(legendChange: {
- name: string;
- selected: Record<string, boolean>;
- type: string;
- }) {
- const {selected} = legendChange;
- const unselected = Object.keys(selected).filter(key => !selected[key]);
- const to = {
- ...location,
- query: {
- ...location.query,
- unselectedSeries: unselected,
- },
- };
- browserHistory.push(to);
- }
- const start = propsStart ? getUtcToLocalDateObject(propsStart) : null;
- const end = propsEnd ? getUtcToLocalDateObject(propsEnd) : null;
- const utc = normalizeDateTimeParams(location.query)?.utc === 'true';
- const period = statsPeriod;
- const legend = {
- right: 10,
- top: 0,
- selected: getSeriesSelection(location),
- };
- const datetimeSelection = {start, end, period};
- const contentCommonProps = {
- theme,
- router,
- start,
- end,
- utc,
- legend,
- queryExtra,
- period,
- projects: project,
- environments: environment,
- onLegendSelectChanged: handleLegendSelectChanged,
- };
- const requestCommonProps = {
- api,
- start,
- end,
- project,
- environment,
- query,
- period,
- interval: getInterval(datetimeSelection, 'high'),
- };
- const header = (
- <HeaderTitleLegend>
- {t('Trend')}
- <QuestionTooltip
- size="sm"
- position="top"
- title={t('Trends shows the smoothed value of an aggregate over time.')}
- />
- </HeaderTitleLegend>
- );
- const trendDisplay = generateTrendFunctionAsString(trendFunction, trendParameter);
- const trendView = eventView.clone() as TrendView;
- modifyTrendView(
- trendView,
- location,
- TrendChangeType.ANY,
- projects,
- shouldGetBreakpoint
- );
- function transformTimeseriesData(
- data: EventsStatsData,
- meta: EventsStats['meta'],
- seriesName: string
- ): Series[] {
- let scale = 1;
- if (seriesName) {
- const unit = meta?.units?.[getAggregateAlias(seriesName)];
- // Scale series values to milliseconds or bytes depending on units from meta
- scale = (unit && (DURATION_UNITS[unit] ?? SIZE_UNITS[unit])) ?? 1;
- }
- return [
- {
- seriesName,
- data: data.map(([timestamp, countsForTimestamp]) => ({
- name: timestamp * 1000,
- value: countsForTimestamp.reduce((acc, {count}) => acc + count, 0) * scale,
- })),
- },
- ];
- }
- return (
- <Fragment>
- {header}
- {shouldGetBreakpoint ? (
- // queries events-trends-statsv2 for breakpoint data (feature flag only)
- <TrendsDiscoverQuery
- eventView={trendView}
- orgSlug={organization.slug}
- location={location}
- limit={1}
- withBreakpoint
- >
- {({isLoading, trendsData}) => {
- const events = normalizeTrends(trendsData?.events?.data || []);
- // keep trend change type as regression until the backend can support passing the type
- const selectedTransaction = getSelectedTransaction(
- location,
- TrendChangeType.ANY,
- events
- );
- const statsData = trendsData?.stats || {};
- const transactionEvent = (
- statsData &&
- selectedTransaction?.project &&
- selectedTransaction?.transaction
- ? statsData[
- [selectedTransaction?.project, selectedTransaction?.transaction].join(
- ','
- )
- ]
- : undefined
- ) as EventsStats;
- const data = transactionEvent?.data ?? [];
- const meta = transactionEvent?.meta ?? ({} as EventsStats['meta']);
- const timeSeriesMetricsData = transformTimeseriesData(
- data,
- meta,
- trendDisplay
- );
- const metricsTimeFrame =
- transactionEvent?.start && transactionEvent.end
- ? {start: transactionEvent.start * 1000, end: transactionEvent.end * 1000}
- : undefined;
- return data.length !== 0 ? (
- <Content
- series={timeSeriesMetricsData}
- errored={!trendsData && !isLoading}
- loading={isLoading || isCardinalityCheckLoading}
- reloading={isLoading}
- timeFrame={metricsTimeFrame}
- withBreakpoint
- transaction={selectedTransaction}
- {...contentCommonProps}
- />
- ) : (
- // queries events-stats for trend data if metrics trend data not found
- <EventsRequest
- {...requestCommonProps}
- organization={organization}
- showLoading={false}
- includePrevious={false}
- yAxis={trendDisplay}
- currentSeriesNames={[trendDisplay]}
- partial
- withoutZerofill={withoutZerofill}
- referrer="api.performance.transaction-summary.trends-chart"
- >
- {({errored, loading, reloading, timeseriesData, timeframe}) => {
- return (
- <Content
- series={timeseriesData}
- errored={errored}
- loading={loading || isLoading}
- reloading={reloading}
- timeFrame={timeframe}
- withBreakpoint
- transaction={selectedTransaction}
- {...contentCommonProps}
- />
- );
- }}
- </EventsRequest>
- );
- }}
- </TrendsDiscoverQuery>
- ) : (
- <EventsRequest
- {...requestCommonProps}
- organization={organization}
- showLoading={false}
- includePrevious={false}
- yAxis={trendDisplay}
- currentSeriesNames={[trendDisplay]}
- partial
- withoutZerofill={withoutZerofill}
- referrer="api.performance.transaction-summary.trends-chart"
- >
- {({errored, loading, reloading, timeseriesData, timeframe: timeFrame}) => {
- return (
- <Content
- series={timeseriesData}
- errored={errored}
- loading={loading || isCardinalityCheckLoading}
- reloading={reloading}
- timeFrame={timeFrame}
- {...contentCommonProps}
- />
- );
- }}
- </EventsRequest>
- )}
- </Fragment>
- );
- }
- export default TrendChart;
|