Skip to content

Commit 735880e

Browse files
authored
feat(explore): Hide Confidence footer with 100% sample rate (#86206)
The extrapolation/confidence footer doesn't make sense if sample rate is 100%
1 parent acde4a2 commit 735880e

File tree

8 files changed

+105
-22
lines changed

8 files changed

+105
-22
lines changed

static/app/views/alerts/rules/metric/ruleForm.tsx

+6-1
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ import ThresholdTypeForm from 'sentry/views/alerts/rules/metric/thresholdTypeFor
5252
import Triggers from 'sentry/views/alerts/rules/metric/triggers';
5353
import TriggersChart, {ErrorChart} from 'sentry/views/alerts/rules/metric/triggers/chart';
5454
import {
55+
determineIsSampled,
5556
determineMultiSeriesConfidence,
5657
determineSeriesConfidence,
5758
} from 'sentry/views/alerts/rules/metric/utils/determineSeriesConfidence';
@@ -148,6 +149,7 @@ type State = {
148149
comparisonDelta?: number;
149150
confidence?: Confidence;
150151
isExtrapolatedChartData?: boolean;
152+
isSampled?: boolean | null;
151153
seasonality?: AlertRuleSeasonality;
152154
} & DeprecatedAsyncComponent['state'];
153155

@@ -989,7 +991,8 @@ class RuleFormContainer extends DeprecatedAsyncComponent<Props, State> {
989991
const confidence = isEventsStats(data)
990992
? determineSeriesConfidence(data)
991993
: determineMultiSeriesConfidence(data);
992-
this.setState({confidence});
994+
const isSampled = determineIsSampled(data);
995+
this.setState({confidence, isSampled});
993996
}
994997

995998
handleHistoricalTimeSeriesDataFetched(
@@ -1124,6 +1127,7 @@ class RuleFormContainer extends DeprecatedAsyncComponent<Props, State> {
11241127
chartError,
11251128
chartErrorMessage,
11261129
confidence,
1130+
isSampled,
11271131
} = this.state;
11281132

11291133
if (chartError) {
@@ -1171,6 +1175,7 @@ class RuleFormContainer extends DeprecatedAsyncComponent<Props, State> {
11711175
onHistoricalDataLoaded: this.handleHistoricalTimeSeriesDataFetched,
11721176
includeConfidence: alertType === 'eap_metrics',
11731177
confidence,
1178+
isSampled,
11741179
};
11751180

11761181
let formattedQuery = `event.type:${eventTypes?.join(',')}`;

static/app/views/alerts/rules/metric/triggers/chart/index.tsx

+4-1
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ import {getComparisonMarkLines} from 'sentry/views/alerts/utils/getComparisonMar
5858
import {AlertWizardAlertNames} from 'sentry/views/alerts/wizard/options';
5959
import {getAlertTypeFromAggregateDataset} from 'sentry/views/alerts/wizard/utils';
6060
import {ConfidenceFooter} from 'sentry/views/explore/charts/confidenceFooter';
61+
import {showConfidence} from 'sentry/views/explore/utils';
6162

6263
import type {MetricRule, Trigger} from '../../types';
6364
import {
@@ -95,6 +96,7 @@ type Props = {
9596
includeConfidence?: boolean;
9697
includeHistorical?: boolean;
9798
isOnDemandMetricAlert?: boolean;
99+
isSampled?: boolean | null;
98100
onConfidenceDataLoaded?: (data: EventsStats | MultiSeriesEventsStats | null) => void;
99101
onDataLoaded?: (data: EventsStats | MultiSeriesEventsStats | null) => void;
100102
onHistoricalDataLoaded?: (data: EventsStats | MultiSeriesEventsStats | null) => void;
@@ -490,7 +492,8 @@ class TriggersChart extends PureComponent<Props, State> {
490492
<ChartControls>
491493
{showTotalCount ? (
492494
<InlineContainer data-test-id="alert-total-events">
493-
{dataset === Dataset.EVENTS_ANALYTICS_PLATFORM ? (
495+
{dataset === Dataset.EVENTS_ANALYTICS_PLATFORM &&
496+
showConfidence(this.props.isSampled) ? (
494497
<ConfidenceFooter
495498
sampleCount={extrapolationSampleCount ?? undefined}
496499
confidence={confidence}

static/app/views/alerts/rules/metric/utils/determineSeriesConfidence.tsx

+42
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ import type {
44
MultiSeriesEventsStats,
55
} from 'sentry/types/organization';
66
import {defined} from 'sentry/utils';
7+
import {
8+
isEventsStats,
9+
isMultiSeriesEventsStats,
10+
} from 'sentry/views/dashboards/utils/isEventsStats';
711

812
// Timeseries with more than this ratio of low confidence intervals will be considered low confidence
913
const LOW_CONFIDENCE_THRESHOLD = 0.25;
@@ -116,3 +120,41 @@ export function combineConfidence(a: Confidence, b: Confidence): Confidence {
116120

117121
return 'high';
118122
}
123+
124+
export function determineIsSampled(
125+
data: MultiSeriesEventsStats | EventsStats
126+
): boolean | null {
127+
let hasSampledInterval = false;
128+
let hasUnsampledInterval = false;
129+
130+
function resolve(sampleRate: number | null) {
131+
if (!defined(sampleRate)) {
132+
return;
133+
}
134+
if (sampleRate === 1) {
135+
hasUnsampledInterval = true;
136+
} else {
137+
hasSampledInterval = true;
138+
}
139+
}
140+
141+
if (isEventsStats(data)) {
142+
for (const sampleRate of data.meta?.accuracy?.samplingRate || []) {
143+
if (defined(sampleRate)) {
144+
resolve(sampleRate.value);
145+
}
146+
}
147+
}
148+
149+
if (isMultiSeriesEventsStats(data)) {
150+
for (const stats of Object.values(data)) {
151+
for (const sampleRate of stats.meta?.accuracy?.samplingRate || []) {
152+
if (defined(sampleRate)) {
153+
resolve(sampleRate.value);
154+
}
155+
}
156+
}
157+
}
158+
159+
return hasSampledInterval ? true : hasUnsampledInterval ? false : null;
160+
}

static/app/views/alerts/rules/metric/utils/determineSeriesSampleCount.tsx

+19-6
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import {defined} from 'sentry/utils';
22
import type {TimeSeries} from 'sentry/views/dashboards/widgets/common/types';
33

4-
export function determineSeriesSampleCount(
4+
export function determineSeriesSampleCountAndIsSampled(
55
data: TimeSeries[],
66
topNMode: boolean
7-
): number {
7+
): {isSampled: boolean | null; sampleCount: number} {
88
if (data.length <= 0) {
9-
return 0;
9+
return {sampleCount: 0, isSampled: null};
1010
}
1111

1212
if (topNMode) {
@@ -25,15 +25,28 @@ export function determineSeriesSampleCount(
2525
// to justify the potential low accuracy warning.
2626
Math.max;
2727

28+
let hasSampledInterval = false;
29+
let hasUnsampledInterval = false;
30+
2831
const series: number[] = data[0]?.sampleCount?.map(item => item.value) ?? [];
2932

30-
for (let i = 1; i < data.length; i++) {
33+
for (let i = 0; i < data.length; i++) {
3134
if (defined(data[i]?.sampleCount)) {
3235
for (let j = 0; j < data[i]!.sampleCount!.length; j++) {
33-
series[j] = merge(series[j]!, data[i]!.sampleCount![j]!.value);
36+
if (i > 0) {
37+
series[j] = merge(series[j]!, data[i]!.sampleCount![j]!.value);
38+
}
39+
const sampleRate = data[i]?.samplingRate?.[j]?.value;
40+
if (sampleRate === 1) {
41+
hasUnsampledInterval = true;
42+
} else if (defined(sampleRate) && sampleRate < 1) {
43+
hasSampledInterval = true;
44+
}
3445
}
3546
}
3647
}
3748

38-
return series.reduce((sum, count) => sum + count, 0);
49+
const isSampled = hasSampledInterval ? true : hasUnsampledInterval ? false : null;
50+
51+
return {sampleCount: series.reduce((sum, count) => sum + count, 0), isSampled};
3952
}

static/app/views/dashboards/widgetCard/spansWidgetQueries.tsx

+8-3
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {dedupeArray} from 'sentry/utils/dedupeArray';
1414
import type {EventsTableData, TableData} from 'sentry/utils/discover/discoverQuery';
1515
import getDynamicText from 'sentry/utils/getDynamicText';
1616
import {determineSeriesConfidence} from 'sentry/views/alerts/rules/metric/utils/determineSeriesConfidence';
17-
import {determineSeriesSampleCount} from 'sentry/views/alerts/rules/metric/utils/determineSeriesSampleCount';
17+
import {determineSeriesSampleCountAndIsSampled} from 'sentry/views/alerts/rules/metric/utils/determineSeriesSampleCount';
1818
import {SpansConfig} from 'sentry/views/dashboards/datasetConfig/spans';
1919
import {getTopEvents} from 'sentry/views/dashboards/widgetBuilder/utils/getTopEvents';
2020
import {combineConfidenceForSeries} from 'sentry/views/explore/utils';
@@ -70,7 +70,7 @@ function SpansWidgetQueries({
7070

7171
if (isEventsStats(result)) {
7272
seriesConfidence = determineSeriesConfidence(result);
73-
seriesSampleCount = determineSeriesSampleCount(
73+
const {sampleCount: calculatedSampleCount} = determineSeriesSampleCountAndIsSampled(
7474
[
7575
convertEventsStatsToTimeSeriesData(
7676
widget.queries[0]?.aggregates[0] ?? '',
@@ -79,11 +79,16 @@ function SpansWidgetQueries({
7979
],
8080
!!topEvents
8181
);
82+
seriesSampleCount = calculatedSampleCount;
8283
} else {
8384
const dedupedYAxes = dedupeArray(widget.queries[0]?.aggregates ?? []);
8485
const seriesMap = transformToSeriesMap(result, dedupedYAxes);
8586
const series = dedupedYAxes.flatMap(yAxis => seriesMap[yAxis]).filter(defined);
86-
seriesSampleCount = determineSeriesSampleCount(series, !!topEvents);
87+
const {sampleCount: calculatedSampleCount} = determineSeriesSampleCountAndIsSampled(
88+
series,
89+
!!topEvents
90+
);
91+
seriesSampleCount = calculatedSampleCount;
8792
seriesConfidence = combineConfidenceForSeries(series);
8893
}
8994

static/app/views/explore/charts/index.tsx

+9-3
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import {DiscoverDatasets} from 'sentry/utils/discover/types';
1515
import {MutableSearch} from 'sentry/utils/tokenizeSearch';
1616
import usePageFilters from 'sentry/utils/usePageFilters';
1717
import usePrevious from 'sentry/utils/usePrevious';
18-
import {determineSeriesSampleCount} from 'sentry/views/alerts/rules/metric/utils/determineSeriesSampleCount';
18+
import {determineSeriesSampleCountAndIsSampled} from 'sentry/views/alerts/rules/metric/utils/determineSeriesSampleCount';
1919
import {WidgetSyncContextProvider} from 'sentry/views/dashboards/contexts/widgetSyncContext';
2020
import {TimeSeriesWidgetVisualization} from 'sentry/views/dashboards/widgets/timeSeriesWidget/timeSeriesWidgetVisualization';
2121
import {Widget} from 'sentry/views/dashboards/widgets/widget/widget';
@@ -28,6 +28,7 @@ import {
2828
} from 'sentry/views/explore/contexts/pageParamsContext';
2929
import {useChartInterval} from 'sentry/views/explore/hooks/useChartInterval';
3030
import {useTopEvents} from 'sentry/views/explore/hooks/useTopEvents';
31+
import {showConfidence} from 'sentry/views/explore/utils';
3132
import {
3233
ChartType,
3334
useSynchronizeCharts,
@@ -135,7 +136,10 @@ export function ExploreCharts({
135136

136137
const {data, error, loading} = getSeries(dedupedYAxes, formattedYAxes);
137138

138-
const sampleCount = determineSeriesSampleCount(data, isTopN);
139+
const {sampleCount, isSampled} = determineSeriesSampleCountAndIsSampled(
140+
data,
141+
isTopN
142+
);
139143

140144
return {
141145
chartIcon: <IconGraph type={chartIcon} />,
@@ -148,6 +152,7 @@ export function ExploreCharts({
148152
loading,
149153
confidence: confidences[index],
150154
sampleCount,
155+
isSampled,
151156
};
152157
});
153158
}, [confidences, getSeries, visualizes, isTopN]);
@@ -284,7 +289,8 @@ export function ExploreCharts({
284289
/>
285290
}
286291
Footer={
287-
dataset === DiscoverDatasets.SPANS_EAP_RPC && (
292+
dataset === DiscoverDatasets.SPANS_EAP_RPC &&
293+
showConfidence(chartInfo.isSampled) && (
288294
<ConfidenceFooter
289295
sampleCount={chartInfo.sampleCount}
290296
confidence={chartInfo.confidence}

static/app/views/explore/multiQueryMode/queryVisualizations/chart.tsx

+10-8
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import usePageFilters from 'sentry/utils/usePageFilters';
1717
import usePrevious from 'sentry/utils/usePrevious';
1818
import useProjects from 'sentry/utils/useProjects';
1919
import {Dataset} from 'sentry/views/alerts/rules/metric/types';
20-
import {determineSeriesSampleCount} from 'sentry/views/alerts/rules/metric/utils/determineSeriesSampleCount';
20+
import {determineSeriesSampleCountAndIsSampled} from 'sentry/views/alerts/rules/metric/utils/determineSeriesSampleCount';
2121
import {TimeSeriesWidgetVisualization} from 'sentry/views/dashboards/widgets/timeSeriesWidget/timeSeriesWidgetVisualization';
2222
import {Widget} from 'sentry/views/dashboards/widgets/widget/widget';
2323
import {EXPLORE_CHART_TYPE_OPTIONS} from 'sentry/views/explore/charts';
@@ -31,7 +31,7 @@ import {
3131
useUpdateQueryAtIndex,
3232
} from 'sentry/views/explore/multiQueryMode/locationUtils';
3333
import {INGESTION_DELAY} from 'sentry/views/explore/settings';
34-
import {combineConfidenceForSeries} from 'sentry/views/explore/utils';
34+
import {combineConfidenceForSeries, showConfidence} from 'sentry/views/explore/utils';
3535
import {ChartType} from 'sentry/views/insights/common/components/chart';
3636
import type {useSortedTimeSeries} from 'sentry/views/insights/common/queries/useSortedTimeSeries';
3737
import {getAlertsUrl} from 'sentry/views/insights/common/utils/getAlertsUrl';
@@ -123,7 +123,7 @@ export function MultiQueryModeChart({
123123
]);
124124

125125
const {data, error, loading} = getSeries();
126-
const sampleCount = determineSeriesSampleCount(data, isTopN);
126+
const {sampleCount, isSampled} = determineSeriesSampleCountAndIsSampled(data, isTopN);
127127

128128
const visualizationType =
129129
queryParts.chartType === ChartType.LINE
@@ -301,11 +301,13 @@ export function MultiQueryModeChart({
301301
/>
302302
}
303303
Footer={
304-
<ConfidenceFooter
305-
sampleCount={sampleCount}
306-
confidence={confidence}
307-
topEvents={isTopN ? numSeries : undefined}
308-
/>
304+
showConfidence(isSampled) && (
305+
<ConfidenceFooter
306+
sampleCount={sampleCount}
307+
confidence={confidence}
308+
topEvents={isTopN ? numSeries : undefined}
309+
/>
310+
)
309311
}
310312
/>
311313
);

static/app/views/explore/utils.tsx

+7
Original file line numberDiff line numberDiff line change
@@ -167,3 +167,10 @@ export function limitMaxPickableDays(organization: Organization): {
167167
},
168168
};
169169
}
170+
171+
export function showConfidence(isSampled: boolean | null | undefined) {
172+
if (defined(isSampled) && isSampled === false) {
173+
return false;
174+
}
175+
return true;
176+
}

0 commit comments

Comments
 (0)