Skip to content

Commit 28bd8e7

Browse files
feat(insights): update total opportunity score function to handle missing vitals (#82923)
Updates total opportunity score function to upscale if there are any missing webvitals score. Consolidates the total weight logic in performance and opportunity scores into a single `_resolve_total_weights_function` function.
1 parent be63dc3 commit 28bd8e7

File tree

2 files changed

+166
-68
lines changed

2 files changed

+166
-68
lines changed

Diff for: src/sentry/search/events/datasets/metrics.py

+78-68
Original file line numberDiff line numberDiff line change
@@ -1754,29 +1754,36 @@ def _resolve_total_web_vital_opportunity_score_with_fixed_weights_function(
17541754
)
17551755
for vital in vitals
17561756
}
1757+
# TODO: Divide by the total weights to factor out any missing web vitals
17571758
return Function(
1758-
"plus",
1759+
"divide",
17591760
[
1760-
adjusted_opportunity_scores["lcp"],
17611761
Function(
17621762
"plus",
17631763
[
1764-
adjusted_opportunity_scores["fcp"],
1764+
adjusted_opportunity_scores["lcp"],
17651765
Function(
17661766
"plus",
17671767
[
1768-
adjusted_opportunity_scores["cls"],
1768+
adjusted_opportunity_scores["fcp"],
17691769
Function(
17701770
"plus",
17711771
[
1772-
adjusted_opportunity_scores["ttfb"],
1773-
adjusted_opportunity_scores["inp"],
1772+
adjusted_opportunity_scores["cls"],
1773+
Function(
1774+
"plus",
1775+
[
1776+
adjusted_opportunity_scores["ttfb"],
1777+
adjusted_opportunity_scores["inp"],
1778+
],
1779+
),
17741780
],
17751781
),
17761782
],
17771783
),
17781784
],
17791785
),
1786+
self._resolve_total_weights_function(),
17801787
],
17811788
alias,
17821789
)
@@ -1837,6 +1844,68 @@ def _resolve_count_scores_function(
18371844
alias,
18381845
)
18391846

1847+
def _resolve_total_weights_function(self) -> SelectType:
1848+
vitals = ["lcp", "fcp", "cls", "ttfb", "inp"]
1849+
weights = {
1850+
vital: Function(
1851+
"if",
1852+
[
1853+
Function(
1854+
"isZeroOrNull",
1855+
[
1856+
Function(
1857+
"countIf",
1858+
[
1859+
Column("value"),
1860+
Function(
1861+
"equals",
1862+
[
1863+
Column("metric_id"),
1864+
self.resolve_metric(f"measurements.score.{vital}"),
1865+
],
1866+
),
1867+
],
1868+
),
1869+
],
1870+
),
1871+
0,
1872+
constants.WEB_VITALS_PERFORMANCE_SCORE_WEIGHTS[vital],
1873+
],
1874+
)
1875+
for vital in vitals
1876+
}
1877+
1878+
if features.has(
1879+
"organizations:performance-vitals-handle-missing-webvitals",
1880+
self.builder.params.organization,
1881+
):
1882+
return Function(
1883+
"plus",
1884+
[
1885+
Function(
1886+
"plus",
1887+
[
1888+
Function(
1889+
"plus",
1890+
[
1891+
Function(
1892+
"plus",
1893+
[
1894+
weights["lcp"],
1895+
weights["fcp"],
1896+
],
1897+
),
1898+
weights["cls"],
1899+
],
1900+
),
1901+
weights["ttfb"],
1902+
],
1903+
),
1904+
weights["inp"],
1905+
],
1906+
)
1907+
return 1
1908+
18401909
def _resolve_total_performance_score_function(
18411910
self,
18421911
_: Mapping[str, str | Column | SelectType | int | float],
@@ -1868,39 +1937,11 @@ def _resolve_total_performance_score_function(
18681937
for vital in vitals
18691938
}
18701939

1871-
weights = {
1872-
vital: Function(
1873-
"if",
1874-
[
1875-
Function(
1876-
"isZeroOrNull",
1877-
[
1878-
Function(
1879-
"countIf",
1880-
[
1881-
Column("value"),
1882-
Function(
1883-
"equals",
1884-
[
1885-
Column("metric_id"),
1886-
self.resolve_metric(f"measurements.score.{vital}"),
1887-
],
1888-
),
1889-
],
1890-
),
1891-
],
1892-
),
1893-
0,
1894-
constants.WEB_VITALS_PERFORMANCE_SCORE_WEIGHTS[vital],
1895-
],
1896-
)
1897-
for vital in vitals
1898-
}
1899-
1900-
# TODO: Is there a way to sum more than 2 values at once?
1940+
# TODO: Divide by the total weights to factor out any missing web vitals
19011941
return Function(
19021942
"divide",
19031943
[
1944+
# TODO: Is there a way to sum more than 2 values at once?
19041945
Function(
19051946
"plus",
19061947
[
@@ -1926,38 +1967,7 @@ def _resolve_total_performance_score_function(
19261967
scores["inp"],
19271968
],
19281969
),
1929-
(
1930-
Function(
1931-
"plus",
1932-
[
1933-
Function(
1934-
"plus",
1935-
[
1936-
Function(
1937-
"plus",
1938-
[
1939-
Function(
1940-
"plus",
1941-
[
1942-
weights["lcp"],
1943-
weights["fcp"],
1944-
],
1945-
),
1946-
weights["cls"],
1947-
],
1948-
),
1949-
weights["ttfb"],
1950-
],
1951-
),
1952-
weights["inp"],
1953-
],
1954-
)
1955-
if features.has(
1956-
"organizations:performance-vitals-handle-missing-webvitals",
1957-
self.builder.params.organization,
1958-
)
1959-
else 1
1960-
),
1970+
self._resolve_total_weights_function(),
19611971
],
19621972
alias,
19631973
)

Diff for: tests/snuba/api/endpoints/test_organization_events_mep.py

+88
Original file line numberDiff line numberDiff line change
@@ -3021,6 +3021,90 @@ def test_opportunity_score_with_fixed_weights(self):
30213021
assert data[1]["total_opportunity_score()"] == 0.36
30223022
assert meta["isMetricsData"]
30233023

3024+
def test_opportunity_score_with_fixed_weights_and_missing_vitals(self):
3025+
self.store_transaction_metric(
3026+
0.5,
3027+
metric="measurements.score.inp",
3028+
tags={"transaction": "foo_transaction"},
3029+
timestamp=self.min_ago,
3030+
)
3031+
self.store_transaction_metric(
3032+
1.0,
3033+
metric="measurements.score.weight.inp",
3034+
tags={"transaction": "foo_transaction"},
3035+
timestamp=self.min_ago,
3036+
)
3037+
self.store_transaction_metric(
3038+
0.2,
3039+
metric="measurements.score.inp",
3040+
tags={"transaction": "foo_transaction"},
3041+
timestamp=self.min_ago,
3042+
)
3043+
self.store_transaction_metric(
3044+
1.0,
3045+
metric="measurements.score.weight.inp",
3046+
tags={"transaction": "foo_transaction"},
3047+
timestamp=self.min_ago,
3048+
)
3049+
self.store_transaction_metric(
3050+
0.2,
3051+
metric="measurements.score.inp",
3052+
tags={"transaction": "foo_transaction"},
3053+
timestamp=self.min_ago,
3054+
)
3055+
self.store_transaction_metric(
3056+
0.5,
3057+
metric="measurements.score.weight.inp",
3058+
tags={"transaction": "foo_transaction"},
3059+
timestamp=self.min_ago,
3060+
)
3061+
self.store_transaction_metric(
3062+
0.1,
3063+
metric="measurements.score.lcp",
3064+
tags={"transaction": "foo_transaction"},
3065+
timestamp=self.min_ago,
3066+
)
3067+
self.store_transaction_metric(
3068+
0.3,
3069+
metric="measurements.score.weight.lcp",
3070+
tags={"transaction": "foo_transaction"},
3071+
timestamp=self.min_ago,
3072+
)
3073+
self.store_transaction_metric(
3074+
0.2,
3075+
metric="measurements.score.inp",
3076+
tags={"transaction": "bar_transaction"},
3077+
timestamp=self.min_ago,
3078+
)
3079+
self.store_transaction_metric(
3080+
0.5,
3081+
metric="measurements.score.weight.inp",
3082+
tags={"transaction": "bar_transaction"},
3083+
timestamp=self.min_ago,
3084+
)
3085+
3086+
with self.feature({"organizations:performance-vitals-handle-missing-webvitals": True}):
3087+
response = self.do_request(
3088+
{
3089+
"field": [
3090+
"transaction",
3091+
"total_opportunity_score()",
3092+
],
3093+
"query": "event.type:transaction",
3094+
"orderby": "transaction",
3095+
"dataset": "metrics",
3096+
"per_page": 50,
3097+
}
3098+
)
3099+
assert response.status_code == 200, response.content
3100+
assert len(response.data["data"]) == 2
3101+
data = response.data["data"]
3102+
meta = response.data["meta"]
3103+
3104+
assert data[0]["total_opportunity_score()"] == 0.09999999999999999
3105+
assert data[1]["total_opportunity_score()"] == 0.6
3106+
assert meta["isMetricsData"]
3107+
30243108
def test_total_performance_score(self):
30253109
self.store_transaction_metric(
30263110
0.03,
@@ -4090,6 +4174,10 @@ def test_opportunity_score(self):
40904174
def test_opportunity_score_with_fixed_weights(self):
40914175
super().test_opportunity_score_with_fixed_weights()
40924176

4177+
@pytest.mark.xfail(reason="Not implemented")
4178+
def test_opportunity_score_with_fixed_weights_and_missing_vitals(self):
4179+
super().test_opportunity_score_with_fixed_weights_and_missing_vitals()
4180+
40934181
@pytest.mark.xfail(reason="Not implemented")
40944182
def test_count_scores(self):
40954183
super().test_count_scores()

0 commit comments

Comments
 (0)