Skip to content

Commit 6b20631

Browse files
authored
feat(mep): Introduce the count_web_vitals functions to metrics (#32507)
- These are the equivalent metrics function versions of what was added in discover
1 parent 4270d4a commit 6b20631

File tree

4 files changed

+184
-2
lines changed

4 files changed

+184
-2
lines changed

src/sentry/search/events/builder.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1468,7 +1468,7 @@ def column(self, name: str) -> Column:
14681468
try:
14691469
return super().column(name)
14701470
except InvalidSearchQuery:
1471-
raise IncompatibleMetricsQuery("Column was not found in metrics indexer")
1471+
raise IncompatibleMetricsQuery(f"Column {name} was not found in metrics indexer")
14721472

14731473
def aliased_column(self, name: str) -> SelectType:
14741474
try:

src/sentry/search/events/datasets/metrics.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,16 @@ def function_converter(self) -> Mapping[str, fields.MetricsFunction]:
126126
),
127127
default_result_type="integer",
128128
),
129+
fields.MetricsFunction(
130+
"count_web_vitals",
131+
required_args=[
132+
fields.FunctionArg("column"),
133+
fields.SnQLStringArg("quality", allowed_strings=["good", "meh", "poor"]),
134+
],
135+
calculated_args=[resolve_metric_id],
136+
snql_distribution=self._resolve_web_vital_function,
137+
default_result_type="integer",
138+
),
129139
fields.MetricsFunction(
130140
"epm",
131141
snql_distribution=lambda args, alias: Function(
@@ -223,6 +233,7 @@ def _event_type_converter(self, search_filter: SearchFilter) -> Optional[WhereTy
223233

224234
raise IncompatibleMetricsQuery("Can only filter event.type:transaction")
225235

236+
# Query Functions
226237
def _resolve_failure_count(
227238
self,
228239
_: Mapping[str, Union[str, Column, SelectType, int, float]],
@@ -276,3 +287,47 @@ def _resolve_percentile(
276287
],
277288
alias,
278289
)
290+
291+
def _resolve_web_vital_function(
292+
self,
293+
args: Mapping[str, Union[str, Column, SelectType, int, float]],
294+
alias: str,
295+
) -> SelectType:
296+
column = args["column"]
297+
metric_id = args["metric_id"]
298+
quality = args["quality"].lower()
299+
300+
if column not in [
301+
"measurements.lcp",
302+
"measurements.fcp",
303+
"measurements.fp",
304+
"measurements.fid",
305+
"measurements.cls",
306+
]:
307+
raise InvalidSearchQuery("count_web_vitals only supports measurements")
308+
309+
measurement_rating = self.builder.resolve_column("measurement_rating")
310+
311+
quality_id = indexer.resolve(quality)
312+
if quality_id is None:
313+
return Function(
314+
# This matches the type from doing `select toTypeName(count()) ...` from clickhouse
315+
"toUInt64",
316+
[0],
317+
alias,
318+
)
319+
320+
return Function(
321+
"countIf",
322+
[
323+
Column("value"),
324+
Function(
325+
"and",
326+
[
327+
Function("equals", [measurement_rating, quality_id]),
328+
Function("equals", [Column("metric_id"), metric_id]),
329+
],
330+
),
331+
],
332+
alias,
333+
)

src/sentry/testutils/cases.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1101,6 +1101,7 @@ class MetricsEnhancedPerformanceTestCase(SessionMetricsTestCase, TestCase):
11011101
ENTITY_MAP = {
11021102
"transaction.duration": "metrics_distributions",
11031103
"measurements.lcp": "metrics_distributions",
1104+
"measurements.fp": "metrics_distributions",
11041105
"measurements.fcp": "metrics_distributions",
11051106
"measurements.fid": "metrics_distributions",
11061107
"measurements.cls": "metrics_distributions",

tests/snuba/api/endpoints/test_organization_events_v2.py

Lines changed: 127 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4951,7 +4951,15 @@ def test_count_web_vitals_invalid_vital(self):
49514951

49524952

49534953
class OrganizationEventsMetricsEnhancedPerformanceEndpointTest(MetricsEnhancedPerformanceTestCase):
4954-
METRIC_STRINGS = ["foo_transaction", "bar_transaction", "staging"]
4954+
# Poor intentionally omitted for test_measurement_rating_that_does_not_exist
4955+
METRIC_STRINGS = [
4956+
"foo_transaction",
4957+
"bar_transaction",
4958+
"staging",
4959+
"measurement_rating",
4960+
"good",
4961+
"meh",
4962+
]
49554963

49564964
def setUp(self):
49574965
super().setUp()
@@ -5171,3 +5179,121 @@ def test_performance_homepage_query(self):
51715179
assert data["p75_measurements_fid"] == 3.0
51725180
assert data["p75_measurements_cls"] == 4.0
51735181
assert response.data["meta"]["isMetricsData"]
5182+
5183+
def test_measurement_rating(self):
5184+
self.store_metric(
5185+
50,
5186+
metric="measurements.lcp",
5187+
tags={"measurement_rating": "good", "transaction": "foo_transaction"},
5188+
timestamp=self.min_ago,
5189+
)
5190+
self.store_metric(
5191+
15,
5192+
metric="measurements.fp",
5193+
tags={"measurement_rating": "good", "transaction": "foo_transaction"},
5194+
timestamp=self.min_ago,
5195+
)
5196+
self.store_metric(
5197+
1500,
5198+
metric="measurements.fcp",
5199+
tags={"measurement_rating": "meh", "transaction": "foo_transaction"},
5200+
timestamp=self.min_ago,
5201+
)
5202+
self.store_metric(
5203+
125,
5204+
metric="measurements.fid",
5205+
tags={"measurement_rating": "meh", "transaction": "foo_transaction"},
5206+
timestamp=self.min_ago,
5207+
)
5208+
self.store_metric(
5209+
0.15,
5210+
metric="measurements.cls",
5211+
tags={"measurement_rating": "good", "transaction": "foo_transaction"},
5212+
timestamp=self.min_ago,
5213+
)
5214+
5215+
response = self.do_request(
5216+
{
5217+
"field": [
5218+
"transaction",
5219+
"count_web_vitals(measurements.lcp, good)",
5220+
"count_web_vitals(measurements.fp, good)",
5221+
"count_web_vitals(measurements.fcp, meh)",
5222+
"count_web_vitals(measurements.fid, meh)",
5223+
"count_web_vitals(measurements.cls, good)",
5224+
],
5225+
"query": "event.type:transaction",
5226+
"metricsEnhanced": "1",
5227+
"per_page": 50,
5228+
}
5229+
)
5230+
assert response.status_code == 200, response.content
5231+
assert len(response.data["data"]) == 1
5232+
assert response.data["meta"]["isMetricsData"]
5233+
assert response.data["data"][0]["count_web_vitals_measurements_lcp_good"] == 1
5234+
assert response.data["data"][0]["count_web_vitals_measurements_fp_good"] == 1
5235+
assert response.data["data"][0]["count_web_vitals_measurements_fcp_meh"] == 1
5236+
assert response.data["data"][0]["count_web_vitals_measurements_fid_meh"] == 1
5237+
assert response.data["data"][0]["count_web_vitals_measurements_cls_good"] == 1
5238+
5239+
def test_measurement_rating_that_does_not_exist(self):
5240+
self.store_metric(
5241+
1,
5242+
metric="measurements.lcp",
5243+
tags={"measurement_rating": "good", "transaction": "foo_transaction"},
5244+
timestamp=self.min_ago,
5245+
)
5246+
5247+
response = self.do_request(
5248+
{
5249+
"field": ["transaction", "count_web_vitals(measurements.lcp, poor)"],
5250+
"query": "event.type:transaction",
5251+
"metricsEnhanced": "1",
5252+
"per_page": 50,
5253+
}
5254+
)
5255+
assert response.status_code == 200, response.content
5256+
assert len(response.data["data"]) == 1
5257+
assert response.data["meta"]["isMetricsData"]
5258+
assert response.data["data"][0]["count_web_vitals_measurements_lcp_poor"] == 0
5259+
5260+
def test_count_web_vitals_invalid_vital(self):
5261+
query = {
5262+
"field": [
5263+
"count_web_vitals(measurements.foo, poor)",
5264+
],
5265+
"project": [self.project.id],
5266+
"metricsEnhanced": "1",
5267+
}
5268+
response = self.do_request(query)
5269+
assert response.status_code == 400, response.content
5270+
5271+
query = {
5272+
"field": [
5273+
"count_web_vitals(tags[lcp], poor)",
5274+
],
5275+
"project": [self.project.id],
5276+
"metricsEnhanced": "1",
5277+
}
5278+
response = self.do_request(query)
5279+
assert response.status_code == 400, response.content
5280+
5281+
query = {
5282+
"field": [
5283+
"count_web_vitals(transaction.duration, poor)",
5284+
],
5285+
"project": [self.project.id],
5286+
"metricsEnhanced": "1",
5287+
}
5288+
response = self.do_request(query)
5289+
assert response.status_code == 400, response.content
5290+
5291+
query = {
5292+
"field": [
5293+
"count_web_vitals(measurements.lcp, bad)",
5294+
],
5295+
"project": [self.project.id],
5296+
"metricsEnhanced": "1",
5297+
}
5298+
response = self.do_request(query)
5299+
assert response.status_code == 400, response.content

0 commit comments

Comments
 (0)