|
14 | 14 | from django.core.cache import cache
|
15 | 15 | from django.core.exceptions import ValidationError
|
16 | 16 | from django.db import IntegrityError, OperationalError, connection, router, transaction
|
17 |
| -from django.db.models import Max |
| 17 | +from django.db.models import Max, Q |
18 | 18 | from django.db.models.signals import post_save
|
19 | 19 | from django.utils.encoding import force_str
|
20 | 20 | from urllib3.exceptions import MaxRetryError, TimeoutError
|
21 | 21 | from usageaccountant import UsageUnit
|
22 | 22 |
|
23 | 23 | from sentry import (
|
| 24 | + audit_log, |
24 | 25 | eventstore,
|
25 | 26 | eventstream,
|
26 | 27 | eventtypes,
|
|
37 | 38 | LOG_LEVELS_MAP,
|
38 | 39 | MAX_TAG_VALUE_LENGTH,
|
39 | 40 | PLACEHOLDER_EVENT_TITLES,
|
| 41 | + VALID_PLATFORMS, |
40 | 42 | DataCategory,
|
41 | 43 | InsightModules,
|
42 | 44 | )
|
|
127 | 129 | from sentry.types.group import GroupSubStatus, PriorityLevel
|
128 | 130 | from sentry.usage_accountant import record
|
129 | 131 | from sentry.utils import metrics
|
| 132 | +from sentry.utils.audit import create_system_audit_entry |
130 | 133 | from sentry.utils.cache import cache_key_for_event
|
131 | 134 | from sentry.utils.circuit_breaker import (
|
132 | 135 | ERROR_COUNT_CACHE_KEY,
|
|
137 | 140 | from sentry.utils.event import has_event_minified_stack_trace, has_stacktrace, is_handled
|
138 | 141 | from sentry.utils.eventuser import EventUser
|
139 | 142 | from sentry.utils.metrics import MutableTags
|
| 143 | +from sentry.utils.options import sample_modulo |
140 | 144 | from sentry.utils.outcomes import Outcome, track_outcome
|
141 | 145 | from sentry.utils.projectflags import set_project_flag_and_signal
|
142 | 146 | from sentry.utils.safe import get_path, safe_execute, setdefault_path, trim
|
@@ -464,6 +468,11 @@ def save(
|
464 | 468 | # After calling _pull_out_data we get some keys in the job like the platform
|
465 | 469 | _pull_out_data([job], projects)
|
466 | 470 |
|
| 471 | + # Sometimes projects get created without a platform (e.g. through the API), in which case we |
| 472 | + # attempt to set it based on the first event |
| 473 | + if sample_modulo("sentry:infer_project_platform", project.id): |
| 474 | + _set_project_platform_if_needed(project, job["event"]) |
| 475 | + |
467 | 476 | event_type = self._data.get("type")
|
468 | 477 | if event_type == "transaction":
|
469 | 478 | job["data"]["project"] = project.id
|
@@ -690,6 +699,30 @@ def _pull_out_data(jobs: Sequence[Job], projects: ProjectsMapping) -> None:
|
690 | 699 | job["groups"] = []
|
691 | 700 |
|
692 | 701 |
|
| 702 | +def _set_project_platform_if_needed(project: Project, event: Event) -> None: |
| 703 | + if project.platform: |
| 704 | + return |
| 705 | + |
| 706 | + if event.platform not in VALID_PLATFORMS or event.get_tag("sample_event") == "yes": |
| 707 | + return |
| 708 | + |
| 709 | + try: |
| 710 | + updated = Project.objects.filter( |
| 711 | + Q(id=project.id) & (Q(platform__isnull=True) | Q(platform="")) |
| 712 | + ).update(platform=event.platform) |
| 713 | + |
| 714 | + if updated: |
| 715 | + create_system_audit_entry( |
| 716 | + organization=project.organization, |
| 717 | + target_object=project.id, |
| 718 | + event=audit_log.get_event_id("PROJECT_EDIT"), |
| 719 | + data={**project.get_audit_log_data(), "platform": event.platform}, |
| 720 | + ) |
| 721 | + |
| 722 | + except Exception: |
| 723 | + logger.exception("Failed to infer and set project platform") |
| 724 | + |
| 725 | + |
693 | 726 | @sentry_sdk.tracing.trace
|
694 | 727 | def _get_or_create_release_many(jobs: Sequence[Job], projects: ProjectsMapping) -> None:
|
695 | 728 | for job in jobs:
|
|
0 commit comments