Skip to content

Commit 32518b3

Browse files
committed
♻️ Convert highlight context to schema validator
1 parent cc29c9b commit 32518b3

File tree

4 files changed

+82
-17
lines changed

4 files changed

+82
-17
lines changed

src/sentry/api/endpoints/project_details.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
from sentry.grouping.enhancer.exceptions import InvalidEnhancerConfig
3535
from sentry.grouping.fingerprinting import FingerprintingRules, InvalidFingerprintingConfig
3636
from sentry.ingest.inbound_filters import FilterTypes
37+
from sentry.issues.highlights import HighlightContextField
3738
from sentry.lang.native.sources import (
3839
InvalidSourcesError,
3940
parse_backfill_sources,
@@ -181,7 +182,7 @@ class ProjectAdminSerializer(ProjectMemberSerializer):
181182
dataScrubberDefaults = serializers.BooleanField(required=False)
182183
sensitiveFields = ListField(child=serializers.CharField(), required=False)
183184
safeFields = ListField(child=serializers.CharField(), required=False)
184-
highlightContext = ListField(child=serializers.CharField(), required=False)
185+
highlightContext = HighlightContextField(required=False)
185186
highlightTags = ListField(child=serializers.CharField(), required=False)
186187
storeCrashReports = serializers.IntegerField(
187188
min_value=-1, max_value=STORE_CRASH_REPORTS_MAX, required=False, allow_null=True

src/sentry/api/serializers/models/project.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -953,7 +953,7 @@ def serialize(
953953
),
954954
"highlightContext": attrs["options"].get(
955955
"sentry:highlight_context",
956-
attrs["highlight_preset"].get("context", []),
956+
attrs["highlight_preset"].get("context", {}),
957957
),
958958
"groupingConfig": self.get_value_with_default(attrs, "sentry:grouping_config"),
959959
"groupingEnhancements": self.get_value_with_default(

src/sentry/issues/highlights.py

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,63 @@
1+
from collections.abc import Mapping
12
from typing import TypedDict
23

4+
from jsonschema import Draft7Validator
5+
from jsonschema.exceptions import ValidationError as SchemaValidationError
6+
from rest_framework import serializers
7+
from rest_framework.serializers import ValidationError
8+
39
from sentry.models.project import Project
10+
from sentry.utils.json import JSONData
411
from sentry.utils.platform_categories import BACKEND, FRONTEND, MOBILE
512

13+
HIGHLIGHT_CONTEXT_SCHEMA: JSONData = {
14+
"type": "object",
15+
"patternProperties": {"^.*$": {"type": "array", "items": {"type": "string"}}},
16+
"additionalProperties": False,
17+
}
18+
19+
20+
class HighlightContextField(serializers.Field):
21+
def to_internal_value(self, data):
22+
if data is None:
23+
return
24+
25+
if data == "" or data == {} or data == []:
26+
return {}
27+
28+
v = Draft7Validator(HIGHLIGHT_CONTEXT_SCHEMA)
29+
try:
30+
v.validate(data)
31+
except SchemaValidationError as e:
32+
raise ValidationError(e.message)
33+
34+
return data
35+
636

737
class HighlightPreset(TypedDict):
838
tags: list[str]
9-
context: list[str]
39+
context: Mapping[str, list[str]]
1040

1141

1242
SENTRY_TAGS = ["handled", "level", "release", "environment"]
1343

1444
BACKEND_HIGHLIGHTS: HighlightPreset = {
1545
"tags": SENTRY_TAGS + ["url", "transaction", "status_code"],
16-
"context": ["trace", "runtime"],
46+
"context": {"trace": ["trace_id"], "runtime": ["name", "version"]},
1747
}
1848
FRONTEND_HIGHLIGHTS: HighlightPreset = {
1949
"tags": SENTRY_TAGS + ["url", "transaction", "browser", "replayId", "user"],
20-
"context": ["browser", "state"],
50+
"context": {"browser": ["name"], "user": ["email"]},
2151
}
2252
MOBILE_HIGHLIGHTS: HighlightPreset = {
2353
"tags": SENTRY_TAGS + ["mobile", "main_thread"],
24-
"context": ["profile", "app", "device"],
54+
"context": {"profile": ["profile_id"], "app": ["name"], "device": ["family"]},
2555
}
2656

27-
FALLBACK_HIGLIGHTS: HighlightPreset = {"tags": SENTRY_TAGS, "context": ["user", "trace"]}
57+
FALLBACK_HIGLIGHTS: HighlightPreset = {
58+
"tags": SENTRY_TAGS,
59+
"context": {"user": ["email"], "trace": ["trace_id"]},
60+
}
2861

2962

3063
def get_highlight_preset_for_project(project: Project) -> HighlightPreset:

tests/sentry/api/endpoints/test_project_details.py

Lines changed: 41 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -838,17 +838,14 @@ def test_safe_fields(self):
838838
'Invalid syntax near "er ror" (line 1),\nDeep wildcard used more than once (line 2)',
839839
]
840840

841-
def test_highlights(self):
841+
def test_highlight_tags(self):
842842
# Default with or without flag, ignore update attempt
843-
highlight_context = ["red", "robin"]
844843
highlight_tags = ["bears", "beets", "battlestar_galactica"]
845844
resp = self.get_success_response(
846845
self.org_slug,
847846
self.proj_slug,
848-
highlightContext=highlight_context,
849847
highlightTags=highlight_tags,
850848
)
851-
assert self.project.get_option("sentry:highlight_context") is None
852849
assert self.project.get_option("sentry:highlight_tags") is None
853850

854851
preset = get_highlight_preset_for_project(self.project)
@@ -860,26 +857,60 @@ def test_highlights(self):
860857
resp = self.get_success_response(
861858
self.org_slug,
862859
self.proj_slug,
863-
highlightContext=highlight_context,
864860
highlightTags=highlight_tags,
865861
)
866-
assert self.project.get_option("sentry:highlight_context") == highlight_context
867862
assert self.project.get_option("sentry:highlight_tags") == highlight_tags
868-
assert resp.data["highlightContext"] == highlight_context
869863
assert resp.data["highlightTags"] == highlight_tags
870864

871865
# Set to empty
872866
resp = self.get_success_response(
873867
self.org_slug,
874868
self.proj_slug,
875-
highlightContext=[],
876869
highlightTags=[],
877870
)
878-
assert self.project.get_option("sentry:highlight_context") == []
879871
assert self.project.get_option("sentry:highlight_tags") == []
880-
assert resp.data["highlightContext"] == []
881872
assert resp.data["highlightTags"] == []
882873

874+
def test_highlight_context(self):
875+
# Default with or without flag, ignore update attempt
876+
highlight_context = {"bird-words": ["red", "robin"]}
877+
resp = self.get_success_response(
878+
self.org_slug,
879+
self.proj_slug,
880+
highlightContext=highlight_context,
881+
)
882+
assert self.project.get_option("sentry:highlight_context") is None
883+
884+
preset = get_highlight_preset_for_project(self.project)
885+
assert resp.data["highlightContext"] == preset["context"]
886+
887+
with self.feature("organizations:event-tags-tree-ui"):
888+
# Set to custom
889+
resp = self.get_success_response(
890+
self.org_slug,
891+
self.proj_slug,
892+
highlightContext=highlight_context,
893+
)
894+
assert self.project.get_option("sentry:highlight_context") == highlight_context
895+
assert resp.data["highlightContext"] == highlight_context
896+
897+
# Set to empty
898+
resp = self.get_success_response(
899+
self.org_slug,
900+
self.proj_slug,
901+
highlightContext={},
902+
)
903+
assert self.project.get_option("sentry:highlight_context") == {}
904+
assert resp.data["highlightContext"] == {}
905+
906+
# Checking schema validation
907+
resp = self.get_error_response(
908+
self.org_slug,
909+
self.proj_slug,
910+
highlightContext={"bird-words": ["invalid", 123, "integer"]},
911+
)
912+
assert "not of type 'string'" in resp.data["highlightContext"][0]
913+
883914
def test_store_crash_reports(self):
884915
resp = self.get_success_response(self.org_slug, self.proj_slug, storeCrashReports=10)
885916
assert self.project.get_option("sentry:store_crash_reports") == 10

0 commit comments

Comments
 (0)