-
-
Notifications
You must be signed in to change notification settings - Fork 4.4k
feat(issues): infer project platform if not set #95402
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 8 commits
36b63c7
88faf1b
6fb23e5
afc8907
c559e38
2d2ec4d
c7c5a2a
05c536d
597b3c4
9bfc4ba
29f6f79
1a95c89
1e25c0a
b0e8ec8
26c7fe3
7bde68e
054c0b2
89d40a7
553895b
6f7ee89
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,6 +21,7 @@ | |
from usageaccountant import UsageUnit | ||
|
||
from sentry import ( | ||
audit_log, | ||
eventstore, | ||
eventstream, | ||
eventtypes, | ||
|
@@ -80,6 +81,7 @@ | |
from sentry.issues.producer import PayloadType, produce_occurrence_to_kafka | ||
from sentry.killswitches import killswitch_matches_context | ||
from sentry.lang.native.utils import STORE_CRASH_REPORTS_ALL, convert_crashreport_count | ||
from sentry.locks import locks | ||
from sentry.models.activity import Activity | ||
from sentry.models.environment import Environment | ||
from sentry.models.event import EventDict | ||
|
@@ -127,6 +129,7 @@ | |
from sentry.types.group import GroupSubStatus, PriorityLevel | ||
from sentry.usage_accountant import record | ||
from sentry.utils import metrics | ||
from sentry.utils.audit import create_system_audit_entry | ||
from sentry.utils.cache import cache_key_for_event | ||
from sentry.utils.circuit_breaker import ( | ||
ERROR_COUNT_CACHE_KEY, | ||
|
@@ -464,6 +467,10 @@ def save( | |
# After calling _pull_out_data we get some keys in the job like the platform | ||
_pull_out_data([job], projects) | ||
|
||
# Sometimes projects get created without a platform (e.g. through the API), in which case we | ||
# attempt to set it based on the first event | ||
_set_project_platform_if_needed(project, job["event"]) | ||
|
||
event_type = self._data.get("type") | ||
if event_type == "transaction": | ||
job["data"]["project"] = project.id | ||
|
@@ -690,6 +697,57 @@ def _pull_out_data(jobs: Sequence[Job], projects: ProjectsMapping) -> None: | |
job["groups"] = [] | ||
|
||
|
||
def _set_project_platform_if_needed(project: Project, event: Event) -> None: | ||
# Only infer the platform if it's useful - if the event platform is "other" or null, there's | ||
# no useful information for us to set the project platform | ||
if not event.platform or event.platform == "other": | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. here, we exclude events without a platform - we won't overwrite |
||
return | ||
|
||
# Use a lock to prevent race conditions when multiple events are processed | ||
# concurrently for a project with no initial platform | ||
lock_key = f"project-platform-lock:{project.id}" | ||
cvxluo marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
try: | ||
with locks.get(lock_key, duration=60, name="project-platform-lock").acquire(): | ||
cvxluo marked this conversation as resolved.
Show resolved
Hide resolved
|
||
project.refresh_from_db(fields=["platform"]) | ||
|
||
if not project.platform: | ||
with transaction.atomic(router.db_for_write(Project)): | ||
project.update(platform=event.platform) | ||
cvxluo marked this conversation as resolved.
Show resolved
Hide resolved
|
||
project.update_option("sentry:project_platform_inferred", event.platform) | ||
|
||
create_system_audit_entry( | ||
organization=project.organization, | ||
target_object=project.id, | ||
event=audit_log.get_event_id("PROJECT_EDIT"), | ||
data={**project.get_audit_log_data(), "platform": event.platform}, | ||
) | ||
return | ||
|
||
if project.platform != event.platform: | ||
cvxluo marked this conversation as resolved.
Show resolved
Hide resolved
|
||
inferred_platform = project.get_option("sentry:project_platform_inferred") | ||
|
||
# If current platform matches what we inferred, we can safely continue inferring it | ||
cvxluo marked this conversation as resolved.
Show resolved
Hide resolved
|
||
# since it hasn't been manually changed | ||
if inferred_platform and project.platform == inferred_platform: | ||
with transaction.atomic(router.db_for_write(Project)): | ||
project.update(platform="other") | ||
project.update_option("sentry:project_platform_inferred", "other") | ||
create_system_audit_entry( | ||
organization=project.organization, | ||
target_object=project.id, | ||
event=audit_log.get_event_id("PROJECT_EDIT"), | ||
data={**project.get_audit_log_data(), "platform": "other"}, | ||
) | ||
|
||
# Otherwise, we need to mark that we should stop inferring platform (unless it becomes null again) | ||
else: | ||
project.update_option("sentry:project_platform_inferred", None) | ||
|
||
except Exception: | ||
return | ||
cvxluo marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
|
||
@sentry_sdk.tracing.trace | ||
def _get_or_create_release_many(jobs: Sequence[Job], projects: ProjectsMapping) -> None: | ||
for job in jobs: | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
from sentry.testutils.cases import TestCase | ||
from sentry.testutils.helpers.eventprocessing import save_new_event | ||
|
||
|
||
class ProjectPlatformInferTest(TestCase): | ||
def test_platform_inferred_on_event(self): | ||
project = self.create_project() | ||
|
||
save_new_event({"message": "test", "platform": "javascript"}, project) | ||
|
||
project.refresh_from_db() | ||
assert project.platform == "javascript" | ||
assert project.get_option("sentry:project_platform_inferred") == "javascript" | ||
|
||
def test_platform_inferred_other_when_mismatch(self): | ||
project = self.create_project() | ||
|
||
save_new_event({"message": "test", "platform": "javascript"}, project) | ||
save_new_event({"message": "test", "platform": "python"}, project) | ||
|
||
project.refresh_from_db() | ||
assert project.platform == "other" | ||
assert project.get_option("sentry:project_platform_inferred") == "other" | ||
|
||
def test_platform_does_not_override_existing_platform(self): | ||
project = self.create_project(platform="python") | ||
|
||
save_new_event({"message": "test", "platform": "javascript"}, project) | ||
|
||
project.refresh_from_db() | ||
assert project.platform == "python" | ||
assert project.get_option("sentry:project_platform_inferred") is None | ||
|
||
def test_platform_stops_inferring_when_manually_set(self): | ||
project = self.create_project() | ||
|
||
save_new_event({"message": "test", "platform": "javascript"}, project) | ||
project.refresh_from_db() | ||
|
||
assert project.platform == "javascript" | ||
assert project.get_option("sentry:project_platform_inferred") == "javascript" | ||
|
||
project.update(platform="python") | ||
project.refresh_from_db() | ||
|
||
assert project.platform == "python" | ||
assert project.get_option("sentry:project_platform_inferred") == "javascript" | ||
|
||
save_new_event({"message": "test", "platform": "native"}, project) | ||
|
||
project.refresh_from_db() | ||
assert project.platform == "python" | ||
assert project.get_option("sentry:project_platform_inferred") is None |
Uh oh!
There was an error while loading. Please reload this page.