Skip to content

Commit 3dfca4c

Browse files
committed
feat(errors): Add error upsampling aggregation functions with feature flag
Allow projects with error upsampling to use new sample_count(), sample_eps() and sample_epm() function columns in Discover, returning the non extrapolated versions of these functions.
1 parent 7b261d2 commit 3dfca4c

File tree

8 files changed

+140
-23
lines changed

8 files changed

+140
-23
lines changed

src/sentry/api/bases/organization_events.py

Lines changed: 18 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -435,26 +435,24 @@ def handle_error_upsampling(self, project_ids: Sequence[int], results: dict[str,
435435
data = results.get("data", [])
436436
fields_meta = results.get("meta", {}).get("fields", {})
437437

438-
for result in data:
439-
if "count" in result:
440-
result["count()"] = result["count"]
441-
del result["count"]
442-
if "eps" in result:
443-
result["eps()"] = result["eps"]
444-
del result["eps"]
445-
if "epm" in result:
446-
result["epm()"] = result["epm"]
447-
del result["epm"]
448-
449-
if "count" in fields_meta:
450-
fields_meta["count()"] = fields_meta["count"]
451-
del fields_meta["count"]
452-
if "eps" in fields_meta:
453-
fields_meta["eps()"] = fields_meta["eps"]
454-
del fields_meta["eps"]
455-
if "epm" in fields_meta:
456-
fields_meta["epm()"] = fields_meta["epm"]
457-
del fields_meta["epm"]
438+
upsampling_affected_functions = [
439+
"count",
440+
"eps",
441+
"epm",
442+
"sample_count",
443+
"sample_eps",
444+
"sample_epm",
445+
]
446+
for function in upsampling_affected_functions:
447+
for result in data:
448+
if function in result:
449+
result[f"{function}()"] = result[function]
450+
del result[function]
451+
452+
for function in upsampling_affected_functions:
453+
if function in fields_meta:
454+
fields_meta[f"{function}()"] = fields_meta[function]
455+
del fields_meta[function]
458456

459457
def handle_issues(
460458
self, results: Sequence[Any], project_ids: Sequence[int], organization: Organization

src/sentry/api/helpers/error_upsampling.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,16 @@ def transform_query_columns_for_error_upsampling(query_columns: Sequence[str]) -
5555

5656
if column_lower == "count()":
5757
transformed_columns.append("upsampled_count() as count")
58-
5958
elif column_lower == "eps()":
6059
transformed_columns.append("upsampled_eps() as eps")
61-
6260
elif column_lower == "epm()":
6361
transformed_columns.append("upsampled_epm() as epm")
62+
elif column_lower == "sample_count()":
63+
transformed_columns.append("count() as sample_count")
64+
elif column_lower == "sample_eps()":
65+
transformed_columns.append("eps() as sample_eps")
66+
elif column_lower == "sample_epm()":
67+
transformed_columns.append("epm() as sample_epm")
6468
else:
6569
transformed_columns.append(column)
6670

src/sentry/features/temporary.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -558,6 +558,8 @@ def register_temporary_features(manager: FeatureManager):
558558
# Adds additional filters and a new section to issue alert rules.
559559
manager.add("projects:alert-filters", ProjectFeature, FeatureHandlerStrategy.INTERNAL, default=True)
560560
manager.add("projects:discard-transaction", ProjectFeature, FeatureHandlerStrategy.INTERNAL, api_expose=False)
561+
# Enable error upsampling
562+
manager.add("projects:error-upsampling", ProjectFeature, FeatureHandlerStrategy.FLAGPOLE, default=False, api_expose=True)
561563
# Enable calculating a severity score for events which create a new group
562564
manager.add("projects:first-event-severity-calculation", ProjectFeature, FeatureHandlerStrategy.INTERNAL, api_expose=False)
563565
# Enable similarity embeddings API call

static/app/utils/discover/fields.tsx

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,24 @@ export const AGGREGATIONS = {
357357
isSortable: true,
358358
multiPlotType: 'area',
359359
},
360+
[AggregationKey.UPSAMPLED_COUNT]: {
361+
...getDocsAndOutputType(AggregationKey.UPSAMPLED_COUNT),
362+
parameters: [],
363+
isSortable: true,
364+
multiPlotType: 'area',
365+
},
366+
[AggregationKey.UPSAMPLED_EPS]: {
367+
...getDocsAndOutputType(AggregationKey.UPSAMPLED_EPS),
368+
parameters: [],
369+
isSortable: true,
370+
multiPlotType: 'area',
371+
},
372+
[AggregationKey.UPSAMPLED_EPM]: {
373+
...getDocsAndOutputType(AggregationKey.UPSAMPLED_EPM),
374+
parameters: [],
375+
isSortable: true,
376+
multiPlotType: 'area',
377+
},
360378
[AggregationKey.FAILURE_COUNT]: {
361379
...getDocsAndOutputType(AggregationKey.FAILURE_COUNT),
362380
parameters: [],
@@ -817,6 +835,38 @@ export const ERRORS_AGGREGATION_FUNCTIONS = [
817835
AggregationKey.COUNT_UNIQUE,
818836
AggregationKey.EPS,
819837
AggregationKey.EPM,
838+
];
839+
840+
export const ERROR_UPSAMPLING_AGGREGATION_FUNCTIONS = [
841+
AggregationKey.UPSAMPLED_COUNT,
842+
AggregationKey.UPSAMPLED_EPS,
843+
AggregationKey.UPSAMPLED_EPM,
844+
];
845+
846+
export const TRANSACTIONS_AGGREGATION_FUNCTIONS = [
847+
AggregationKey.COUNT,
848+
AggregationKey.COUNT_IF,
849+
AggregationKey.COUNT_UNIQUE,
850+
AggregationKey.COUNT_MISERABLE,
851+
AggregationKey.COUNT_WEB_VITALS,
852+
AggregationKey.EPS,
853+
AggregationKey.EPM,
854+
AggregationKey.FAILURE_COUNT,
855+
AggregationKey.MIN,
856+
AggregationKey.MAX,
857+
AggregationKey.SUM,
858+
AggregationKey.ANY,
859+
AggregationKey.P50,
860+
AggregationKey.P75,
861+
AggregationKey.P90,
862+
AggregationKey.P95,
863+
AggregationKey.P99,
864+
AggregationKey.P100,
865+
AggregationKey.PERCENTILE,
866+
AggregationKey.AVG,
867+
AggregationKey.APDEX,
868+
AggregationKey.USER_MISERY,
869+
AggregationKey.FAILURE_RATE,
820870
AggregationKey.LAST_SEEN,
821871
];
822872

static/app/utils/fields/index.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,9 @@ export enum AggregationKey {
227227
COUNT_WEB_VITALS = 'count_web_vitals',
228228
EPS = 'eps',
229229
EPM = 'epm',
230+
UPSAMPLED_COUNT = 'upsampled_count',
231+
UPSAMPLED_EPS = 'upsampled_eps',
232+
UPSAMPLED_EPM = 'upsampled_epm',
230233
FAILURE_COUNT = 'failure_count',
231234
MIN = 'min',
232235
MAX = 'max',
@@ -507,6 +510,24 @@ export const AGGREGATION_FIELDS: Record<AggregationKey, FieldDefinition> = {
507510
valueType: FieldValueType.NUMBER,
508511
parameters: [],
509512
},
513+
[AggregationKey.UPSAMPLED_COUNT]: {
514+
desc: t('Upsampled count'),
515+
kind: FieldKind.FUNCTION,
516+
valueType: FieldValueType.INTEGER,
517+
parameters: [],
518+
},
519+
[AggregationKey.UPSAMPLED_EPS]: {
520+
desc: t('Upsampled EPS'),
521+
kind: FieldKind.FUNCTION,
522+
valueType: FieldValueType.NUMBER,
523+
parameters: [],
524+
},
525+
[AggregationKey.UPSAMPLED_EPM]: {
526+
desc: t('Upsampled EPM'),
527+
kind: FieldKind.FUNCTION,
528+
valueType: FieldValueType.NUMBER,
529+
parameters: [],
530+
},
510531
[AggregationKey.FAILURE_RATE]: {
511532
desc: t('Failed event percentage based on transaction.status'),
512533
kind: FieldKind.FUNCTION,

static/app/views/dashboards/datasetConfig/transactions.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,19 @@ import {defined} from 'sentry/utils';
1212
import type {CustomMeasurementCollection} from 'sentry/utils/customMeasurements/customMeasurements';
1313
import type {EventsTableData, TableData} from 'sentry/utils/discover/discoverQuery';
1414
import {
15+
getAggregations,
1516
type QueryFieldValue,
1617
SPAN_OP_BREAKDOWN_FIELDS,
1718
TRANSACTION_FIELDS,
19+
TRANSACTIONS_AGGREGATION_FUNCTIONS,
1820
} from 'sentry/utils/discover/fields';
1921
import type {
2022
DiscoverQueryExtras,
2123
DiscoverQueryRequestParams,
2224
} from 'sentry/utils/discover/genericDiscoverQuery';
2325
import {doDiscoverQuery} from 'sentry/utils/discover/genericDiscoverQuery';
2426
import {DiscoverDatasets} from 'sentry/utils/discover/types';
27+
import {AggregationKey} from 'sentry/utils/fields';
2528
import {getMeasurements} from 'sentry/utils/measurements/measurements';
2629
import {MEPState} from 'sentry/utils/performance/contexts/metricsEnhancedSetting';
2730
import {
@@ -135,6 +138,7 @@ function getEventsTableFieldOptions(
135138
customMeasurements?: CustomMeasurementCollection
136139
) {
137140
const measurements = getMeasurements();
141+
const aggregates = getAggregations(DiscoverDatasets.TRANSACTIONS);
138142

139143
return generateFieldOptions({
140144
organization,
@@ -147,6 +151,13 @@ function getEventsTableFieldOptions(
147151
functions,
148152
})
149153
),
154+
aggregations: Object.keys(aggregates)
155+
.filter(key => TRANSACTIONS_AGGREGATION_FUNCTIONS.includes(key as AggregationKey))
156+
.reduce((obj, key) => {
157+
// @ts-expect-error TS(7053): Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
158+
obj[key] = aggregates[key];
159+
return obj;
160+
}, {}),
150161
fieldKeys: TRANSACTION_FIELDS,
151162
});
152163
}

static/app/views/discover/table/columnEditModal.tsx

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,17 @@ import {DISCOVER2_DOCS_URL} from 'sentry/constants';
1111
import {t, tct} from 'sentry/locale';
1212
import {space} from 'sentry/styles/space';
1313
import type {Organization} from 'sentry/types/organization';
14+
import type {Project} from 'sentry/types/project';
1415
import {trackAnalytics} from 'sentry/utils/analytics';
1516
import type {CustomMeasurementCollection} from 'sentry/utils/customMeasurements/customMeasurements';
1617
import {
1718
type Column,
1819
ERROR_FIELDS,
20+
ERROR_UPSAMPLING_AGGREGATION_FUNCTIONS,
1921
ERRORS_AGGREGATION_FUNCTIONS,
2022
getAggregations,
2123
TRANSACTION_FIELDS,
24+
TRANSACTIONS_AGGREGATION_FUNCTIONS,
2225
} from 'sentry/utils/discover/fields';
2326
import {DiscoverDatasets} from 'sentry/utils/discover/types';
2427
import {AggregationKey, FieldKey} from 'sentry/utils/fields';
@@ -35,6 +38,7 @@ type Props = {
3538
organization: Organization;
3639
customMeasurements?: CustomMeasurementCollection;
3740
dataset?: DiscoverDatasets;
41+
selectedProjects?: Project[];
3842
spanOperationBreakdownKeys?: string[];
3943
} & ModalRenderProps;
4044

@@ -52,6 +56,7 @@ function ColumnEditModal(props: Props) {
5256
closeModal,
5357
customMeasurements,
5458
dataset,
59+
selectedProjects,
5560
} = props;
5661

5762
// Only run once for each organization.id.
@@ -73,19 +78,32 @@ function ColumnEditModal(props: Props) {
7378

7479
if (dataset === DiscoverDatasets.ERRORS) {
7580
const aggregations = getAggregations(DiscoverDatasets.ERRORS);
81+
82+
// Check if any selected projects have error upsampling enabled
83+
const hasErrorUpsampling =
84+
selectedProjects?.some((project: Project) =>
85+
project.features.includes('error-upsampling')
86+
) ?? false;
87+
88+
// Include upsampling functions if any project has the feature enabled
89+
const allowedAggregations = hasErrorUpsampling
90+
? [...ERRORS_AGGREGATION_FUNCTIONS, ...ERROR_UPSAMPLING_AGGREGATION_FUNCTIONS]
91+
: ERRORS_AGGREGATION_FUNCTIONS;
92+
7693
fieldOptions = generateFieldOptions({
7794
organization,
7895
tagKeys,
7996
fieldKeys: ERROR_FIELDS,
8097
aggregations: Object.keys(aggregations)
81-
.filter(key => ERRORS_AGGREGATION_FUNCTIONS.includes(key as AggregationKey))
98+
.filter(key => allowedAggregations.includes(key as AggregationKey))
8299
.reduce((obj, key) => {
83100
// @ts-expect-error TS(7053): Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
84101
obj[key] = aggregations[key];
85102
return obj;
86103
}, {}),
87104
});
88105
} else if (dataset === DiscoverDatasets.TRANSACTIONS) {
106+
const aggregations = getAggregations(DiscoverDatasets.TRANSACTIONS);
89107
fieldOptions = generateFieldOptions({
90108
organization,
91109
tagKeys,
@@ -97,6 +115,13 @@ function ColumnEditModal(props: Props) {
97115
functions,
98116
})
99117
),
118+
aggregations: Object.keys(aggregations)
119+
.filter(key => TRANSACTIONS_AGGREGATION_FUNCTIONS.includes(key as AggregationKey))
120+
.reduce((obj, key) => {
121+
// @ts-expect-error TS(7053): Element implicitly has an 'any' type because expre... Remove this comment to see the full error message
122+
obj[key] = aggregations[key];
123+
return obj;
124+
}, {}),
100125
fieldKeys: TRANSACTION_FIELDS,
101126
});
102127
} else {

static/app/views/discover/table/tableView.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import Truncate from 'sentry/components/truncate';
1616
import {IconStack} from 'sentry/icons';
1717
import {t} from 'sentry/locale';
1818
import type {Organization} from 'sentry/types/organization';
19+
import type {Project} from 'sentry/types/project';
1920
import {trackAnalytics} from 'sentry/utils/analytics';
2021
import {browserHistory} from 'sentry/utils/browserHistory';
2122
import type {CustomMeasurementCollection} from 'sentry/utils/customMeasurements/customMeasurements';
@@ -530,6 +531,10 @@ function TableView(props: TableViewProps) {
530531
dataset,
531532
} = props;
532533

534+
const selectedProjects = eventView
535+
.getFullSelectedProjects(projects)
536+
.filter((project): project is Project => project !== undefined);
537+
533538
openModal(
534539
modalProps => (
535540
<ColumnEditModal
@@ -541,6 +546,7 @@ function TableView(props: TableViewProps) {
541546
onApply={handleUpdateColumns}
542547
customMeasurements={customMeasurements}
543548
dataset={dataset}
549+
selectedProjects={selectedProjects}
544550
/>
545551
),
546552
{modalCss: modalCss(theme), closeEvents: 'escape-key'}

0 commit comments

Comments
 (0)