Skip to content

Commit 4d0d142

Browse files
authored
ref(timeseries): Use /events-timeseries/ hook in Database Insights (#99339)
First steps to using `/events-timeseries/` endpoint in production! - Accept `TimeSeries` in `InsightsTimeSeriesWidget`, so we can pass the hook's response data directly to charts - Remove unnecessary Knip config, since the hook is in actual use now - Fix incorrectly named fixture (just to clarify) - Add partial application span hook (just for convenience) - Use `useFetchEventsTimeSeries` in database module (the bulk of the change) The change is pretty simple overall, just swapping the hook one-for-one! Some of the parameter names are different, but that's about it. The main change is that the `useFetchSpanTimeSeries` returns an _array_ of `TimeSeries` objects, rathen than an object keyed by the `yAxis`. This means we can just pass the response array directly to the charts. CAVEAT: If the hook is fetching more data than is needed for the charts, the extra data needs to be filtered out! This is not the case for Database module, but it'll be the case for some other ones. Otherwise, it's basically that: 1. `search` is called `query 2. the aliasing is _always on_, so no need for a parameter
1 parent 7f2f082 commit 4d0d142

14 files changed

+145
-114
lines changed

knip.config.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,6 @@ const config: KnipConfig = {
5858
// TEMPORARY!
5959
'!static/app/components/core/disclosure/index.tsx',
6060
'!static/app/components/core/disclosure/disclosure.tsx',
61-
'!static/app/utils/timeSeries/useFetchEventsTimeSeries.tsx',
6261
],
6362
compilers: {
6463
mdx: async text => String(await compile(text)),

static/app/components/charts/chartWidgetLoader-unmocked-imports.spec.tsx

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ import fs from 'node:fs';
33
// eslint-disable-next-line import/no-nodejs-modules
44
import path from 'node:path';
55

6-
import {TimeSeriesFixture} from 'sentry-fixture/discoverSeries';
6+
import {DiscoverSeriesFixture} from 'sentry-fixture/discoverSeries';
7+
import {TimeSeriesFixture} from 'sentry-fixture/timeSeries';
78

89
import {render, screen, waitFor} from 'sentry-test/reactTestingLibrary';
910

@@ -13,11 +14,17 @@ import type {ChartId} from './chartWidgetLoader';
1314
import {ChartWidgetLoader} from './chartWidgetLoader';
1415

1516
function mockDiscoverSeries(seriesName: string) {
16-
return TimeSeriesFixture({
17+
return DiscoverSeriesFixture({
1718
seriesName,
1819
});
1920
}
2021

22+
function mockTimeSeries(yAxis: string) {
23+
return TimeSeriesFixture({
24+
yAxis,
25+
});
26+
}
27+
2128
// Mock this component so it doesn't yell at us for no plottables
2229
jest.mock(
2330
'sentry/views/dashboards/widgets/timeSeriesWidget/timeSeriesWidgetVisualization',
@@ -128,6 +135,36 @@ jest.mock('sentry/views/insights/common/queries/useDiscoverSeries', () => ({
128135
error: null,
129136
})),
130137
}));
138+
jest.mock('sentry/utils/timeSeries/useFetchEventsTimeSeries', () => ({
139+
useFetchSpanTimeSeries: jest.fn(() => ({
140+
data: {
141+
timeSeries: [
142+
mockTimeSeries('epm()'),
143+
mockTimeSeries('count(span.duration)'),
144+
mockTimeSeries('avg(span.duration)'),
145+
mockTimeSeries('p95(span.duration)'),
146+
mockTimeSeries('trace_status_rate(internal_error)'),
147+
mockTimeSeries('cache_miss_rate()'),
148+
mockTimeSeries('http_response_rate(3)'),
149+
mockTimeSeries('http_response_rate(4)'),
150+
mockTimeSeries('http_response_rate(5)'),
151+
mockTimeSeries('avg(span.self_time)'),
152+
mockTimeSeries('avg(http.response_content_length)'),
153+
mockTimeSeries('avg(http.response_transfer_size)'),
154+
mockTimeSeries('avg(http.decoded_response_content_length)'),
155+
mockTimeSeries('avg(messaging.message.receive.latency)'),
156+
mockTimeSeries('performance_score(measurements.score.lcp)'),
157+
mockTimeSeries('performance_score(measurements.score.fcp)'),
158+
mockTimeSeries('performance_score(measurements.score.cls)'),
159+
mockTimeSeries('performance_score(measurements.score.inp)'),
160+
mockTimeSeries('performance_score(measurements.score.ttfb)'),
161+
mockTimeSeries('count()'),
162+
],
163+
},
164+
isPending: false,
165+
error: null,
166+
})),
167+
}));
131168
jest.mock('sentry/views/insights/sessions/queries/useErroredSessions', () => ({
132169
__esModule: true,
133170
default: jest.fn(() => ({

static/app/utils/timeSeries/useFetchEventsTimeSeries.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import {
1414
getRetryDelay,
1515
shouldRetryHandler,
1616
} from 'sentry/views/insights/common/utils/retryHandlers';
17+
import type {SpanProperty} from 'sentry/views/insights/types';
1718

1819
import {getIntervalForTimeSeriesQuery} from './getIntervalForTimeSeriesQuery';
1920

@@ -32,6 +33,13 @@ interface UseFetchEventsTimeSeriesOptions<Field> {
3233
topEvents?: number;
3334
}
3435

36+
export function useFetchSpanTimeSeries<Fields extends SpanProperty>(
37+
options: UseFetchEventsTimeSeriesOptions<Fields>,
38+
referrer: string
39+
) {
40+
return useFetchEventsTimeSeries<Fields>(DiscoverDatasets.SPANS, options, referrer);
41+
}
42+
3543
/**
3644
* Fetch time series data from the `/events-timeseries/` endpoint. Returns an array of `TimeSeries` objects.
3745
*/

static/app/views/insights/common/components/insightsTimeSeriesWidget.tsx

Lines changed: 40 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@ import type {MutableSearch} from 'sentry/utils/tokenizeSearch';
1111
import usePageFilters from 'sentry/utils/usePageFilters';
1212
import {useReleaseStats} from 'sentry/utils/useReleaseStats';
1313
import {MISSING_DATA_MESSAGE} from 'sentry/views/dashboards/widgets/common/settings';
14-
import type {LegendSelection} from 'sentry/views/dashboards/widgets/common/types';
14+
import type {
15+
LegendSelection,
16+
TimeSeries,
17+
} from 'sentry/views/dashboards/widgets/common/types';
1518
import {Area} from 'sentry/views/dashboards/widgets/timeSeriesWidget/plottables/area';
1619
import {Bars} from 'sentry/views/dashboards/widgets/timeSeriesWidget/plottables/bars';
1720
import {Line} from 'sentry/views/dashboards/widgets/timeSeriesWidget/plottables/line';
@@ -48,7 +51,6 @@ export interface InsightsTimeSeriesWidgetProps
4851
LoadableChartWidgetProps {
4952
error: Error | null;
5053
isLoading: boolean;
51-
series: DiscoverSeries[];
5254
visualizationType: 'line' | 'area' | 'bar';
5355
aliases?: Record<string, string>;
5456
description?: React.ReactNode;
@@ -69,11 +71,18 @@ export interface InsightsTimeSeriesWidgetProps
6971
groupBy?: SpanFields[];
7072
yAxis?: string[];
7173
};
72-
7374
samples?: Samples;
75+
/**
76+
* During the transition from the `/events-stats/` endpoint to the `/events-timeseries/` endpoint we accept both `timeSeries` and `series` so different components can pass different data. Eventually `series` will go away.
77+
*/
78+
series?: DiscoverSeries[];
7479
showLegend?: TimeSeriesWidgetVisualizationProps['showLegend'];
7580
showReleaseAs?: 'line' | 'bubble' | 'none';
7681
stacked?: boolean;
82+
/**
83+
* During the transition from the `/events-stats/` endpoint to the `/events-timeseries/` endpoint we accept both `timeSeries` and `series` so different components can pass different data. Eventually `timeSeries` will take over.
84+
*/
85+
timeSeries?: TimeSeries[];
7786
}
7887

7988
export function InsightsTimeSeriesWidget(props: InsightsTimeSeriesWidgetProps) {
@@ -94,27 +103,40 @@ export function InsightsTimeSeriesWidget(props: InsightsTimeSeriesWidgetProps) {
94103
...props?.aliases,
95104
};
96105

106+
const PlottableDataConstructor =
107+
props.visualizationType === 'line'
108+
? Line
109+
: props.visualizationType === 'area'
110+
? Area
111+
: Bars;
112+
97113
const yAxes = new Set<string>();
98114
const plottables = [
99-
...(props.series.filter(Boolean) ?? []).map(serie => {
100-
const timeSeries = markDelayedData(
115+
...(props.series?.filter(Boolean) ?? []).map(serie => {
116+
const delayedTimeSeries = markDelayedData(
101117
convertSeriesToTimeseries(serie),
102118
INGESTION_DELAY
103119
);
104-
const PlottableDataConstructor =
105-
props.visualizationType === 'line'
106-
? Line
107-
: props.visualizationType === 'area'
108-
? Area
109-
: Bars;
110120

111121
// yAxis should not contain whitespace, some yAxes are like `epm() span.op:queue.publish`
112-
yAxes.add(timeSeries?.yAxis?.split(' ')[0] ?? '');
122+
yAxes.add(delayedTimeSeries?.yAxis?.split(' ')[0] ?? '');
123+
124+
return new PlottableDataConstructor(delayedTimeSeries, {
125+
color: serie.color ?? COMMON_COLORS(theme)[delayedTimeSeries.yAxis],
126+
stack: props.stacked && props.visualizationType === 'bar' ? 'all' : undefined,
127+
alias: aliases?.[delayedTimeSeries.yAxis],
128+
});
129+
}),
130+
...(props.timeSeries?.filter(Boolean) ?? []).map(timeSeries => {
131+
// TODO: After merge of ENG-5375 we don't need to run `markDelayedData` on output of `/events-timeseries/`
132+
const delayedTimeSeries = markDelayedData(timeSeries, INGESTION_DELAY);
133+
134+
yAxes.add(timeSeries.yAxis);
113135

114-
return new PlottableDataConstructor(timeSeries, {
115-
color: serie.color ?? COMMON_COLORS(theme)[timeSeries.yAxis],
136+
return new PlottableDataConstructor(delayedTimeSeries, {
137+
color: COMMON_COLORS(theme)[delayedTimeSeries.yAxis],
116138
stack: props.stacked && props.visualizationType === 'bar' ? 'all' : undefined,
117-
alias: aliases?.[timeSeries.yAxis],
139+
alias: aliases?.[delayedTimeSeries.yAxis],
118140
});
119141
}),
120142
...(props.extraPlottables ?? []),
@@ -158,7 +180,9 @@ export function InsightsTimeSeriesWidget(props: InsightsTimeSeriesWidgetProps) {
158180
);
159181
}
160182

161-
if (props.series.filter(Boolean).length === 0) {
183+
if (
184+
[...(props.series ?? []), ...(props.timeSeries ?? [])].filter(Boolean).length === 0
185+
) {
162186
return (
163187
<ChartContainer height={props.height}>
164188
<Widget

static/app/views/insights/common/components/widgets/databaseLandingDurationChartWidget.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import {useDatabaseLandingDurationQuery} from 'sentry/views/insights/common/comp
44
import type {LoadableChartWidgetProps} from 'sentry/views/insights/common/components/widgets/types';
55
import {getDurationChartTitle} from 'sentry/views/insights/common/views/spans/types';
66
import {Referrer} from 'sentry/views/insights/database/referrers';
7-
import {DEFAULT_DURATION_AGGREGATE} from 'sentry/views/insights/database/settings';
87

98
export default function DatabaseLandingDurationChartWidget(
109
props: LoadableChartWidgetProps
@@ -22,7 +21,7 @@ export default function DatabaseLandingDurationChartWidget(
2221
queryInfo={{search, referrer}}
2322
id="databaseLandingDurationChartWidget"
2423
title={getDurationChartTitle('db')}
25-
series={[data[`${DEFAULT_DURATION_AGGREGATE}(span.self_time)`]]}
24+
timeSeries={data?.timeSeries}
2625
isLoading={isPending}
2726
error={error}
2827
/>

static/app/views/insights/common/components/widgets/databaseLandingThroughputChartWidget.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export default function DatabaseLandingThroughputChartWidget(
2323
queryInfo={{search, referrer}}
2424
id="databaseLandingThroughputChartWidget"
2525
title={getThroughputChartTitle('db')}
26-
series={[data['epm()']]}
26+
timeSeries={data?.timeSeries}
2727
isLoading={isPending}
2828
error={error}
2929
/>

static/app/views/insights/common/components/widgets/databaseSummaryDurationChartWidget.tsx

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1+
import {useFetchSpanTimeSeries} from 'sentry/utils/timeSeries/useFetchEventsTimeSeries';
12
import {MutableSearch} from 'sentry/utils/tokenizeSearch';
23
import {useParams} from 'sentry/utils/useParams';
34
import {InsightsLineChartWidget} from 'sentry/views/insights/common/components/insightsLineChartWidget';
45
import type {LoadableChartWidgetProps} from 'sentry/views/insights/common/components/widgets/types';
5-
import {useSpanSeries} from 'sentry/views/insights/common/queries/useDiscoverSeries';
66
import {getDurationChartTitle} from 'sentry/views/insights/common/views/spans/types';
77
import {Referrer} from 'sentry/views/insights/database/referrers';
88
import {DEFAULT_DURATION_AGGREGATE} from 'sentry/views/insights/database/settings';
@@ -19,12 +19,11 @@ export default function DatabaseSummaryDurationChartWidget(
1919
const search = MutableSearch.fromQueryObject(filters);
2020
const referrer = Referrer.SUMMARY_DURATION_CHART;
2121

22-
const {isPending, data, error} = useSpanSeries(
22+
const {isPending, data, error} = useFetchSpanTimeSeries(
2323
{
24-
search,
24+
query: search,
2525
yAxis: [`${DEFAULT_DURATION_AGGREGATE}(${SpanFields.SPAN_SELF_TIME})`],
2626
enabled: Boolean(groupId),
27-
transformAliasToInputFormat: true,
2827
},
2928
referrer
3029
);
@@ -35,7 +34,7 @@ export default function DatabaseSummaryDurationChartWidget(
3534
queryInfo={{search, referrer}}
3635
id="databaseSummaryDurationChartWidget"
3736
title={getDurationChartTitle('db')}
38-
series={[data[`${DEFAULT_DURATION_AGGREGATE}(${SpanFields.SPAN_SELF_TIME})`]]}
37+
timeSeries={data?.timeSeries}
3938
isLoading={isPending}
4039
error={error}
4140
/>

static/app/views/insights/common/components/widgets/databaseSummaryThroughputChartWidget.tsx

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1+
import {useFetchSpanTimeSeries} from 'sentry/utils/timeSeries/useFetchEventsTimeSeries';
12
import {MutableSearch} from 'sentry/utils/tokenizeSearch';
23
import {useParams} from 'sentry/utils/useParams';
34
import {InsightsLineChartWidget} from 'sentry/views/insights/common/components/insightsLineChartWidget';
45
import type {LoadableChartWidgetProps} from 'sentry/views/insights/common/components/widgets/types';
5-
import {useSpanSeries} from 'sentry/views/insights/common/queries/useDiscoverSeries';
66
import {getThroughputChartTitle} from 'sentry/views/insights/common/views/spans/types';
77
import {Referrer} from 'sentry/views/insights/database/referrers';
88
import {FIELD_ALIASES} from 'sentry/views/insights/database/settings';
@@ -18,12 +18,11 @@ export default function DatabaseSummaryThroughputChartWidget(
1818
const search = MutableSearch.fromQueryObject(filters);
1919
const referrer = Referrer.SUMMARY_THROUGHPUT_CHART;
2020

21-
const {isPending, data, error} = useSpanSeries(
21+
const {isPending, data, error} = useFetchSpanTimeSeries(
2222
{
23-
search,
23+
query: search,
2424
yAxis: ['epm()'],
2525
enabled: Boolean(groupId),
26-
transformAliasToInputFormat: true,
2726
},
2827
referrer
2928
);
@@ -35,7 +34,7 @@ export default function DatabaseSummaryThroughputChartWidget(
3534
queryInfo={{search, referrer}}
3635
id="databaseSummaryThroughputChartWidget"
3736
title={getThroughputChartTitle('db')}
38-
series={[data['epm()']]}
37+
timeSeries={data?.timeSeries}
3938
isLoading={isPending}
4039
error={error}
4140
/>

static/app/views/insights/common/components/widgets/hooks/useDatabaseLandingDurationQuery.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1+
import {useFetchSpanTimeSeries} from 'sentry/utils/timeSeries/useFetchEventsTimeSeries';
12
import type {MutableSearch} from 'sentry/utils/tokenizeSearch';
2-
import {useSpanSeries} from 'sentry/views/insights/common/queries/useDiscoverSeries';
33
import {Referrer} from 'sentry/views/insights/database/referrers';
44
import {DEFAULT_DURATION_AGGREGATE} from 'sentry/views/insights/database/settings';
55
import {SpanFields} from 'sentry/views/insights/types';
@@ -10,11 +10,10 @@ type Props = {
1010
};
1111

1212
export function useDatabaseLandingDurationQuery({search, enabled}: Props) {
13-
return useSpanSeries(
13+
return useFetchSpanTimeSeries(
1414
{
15-
search,
15+
query: search,
1616
yAxis: [`${DEFAULT_DURATION_AGGREGATE}(${SpanFields.SPAN_SELF_TIME})`],
17-
transformAliasToInputFormat: true,
1817
enabled,
1918
},
2019
Referrer.LANDING_DURATION_CHART

static/app/views/insights/common/components/widgets/hooks/useDatabaseLandingThroughputQuery.tsx

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1+
import {useFetchSpanTimeSeries} from 'sentry/utils/timeSeries/useFetchEventsTimeSeries';
12
import type {MutableSearch} from 'sentry/utils/tokenizeSearch';
2-
import {useSpanSeries} from 'sentry/views/insights/common/queries/useDiscoverSeries';
33
import {Referrer} from 'sentry/views/insights/database/referrers';
44

55
type Props = {
@@ -8,11 +8,10 @@ type Props = {
88
};
99

1010
export function useDatabaseLandingThroughputQuery({search, enabled}: Props) {
11-
return useSpanSeries(
11+
return useFetchSpanTimeSeries(
1212
{
13-
search,
13+
query: search,
1414
yAxis: ['epm()'],
15-
transformAliasToInputFormat: true,
1615
enabled,
1716
},
1817
Referrer.LANDING_THROUGHPUT_CHART

0 commit comments

Comments
 (0)