Browse Source

feat(native): Add option to limit crash reports per group (#15798)

Adds an organization and project option to limit crash reports per issue group.
Jan Michael Auer 5 years ago
parent
commit
a57b833d91

+ 4 - 3
src/sentry/api/endpoints/organization_details.py

@@ -16,6 +16,7 @@ from sentry.api.serializers import serialize
 from sentry.api.serializers.models import organization as org_serializers
 from sentry.api.serializers.rest_framework import ListField
 from sentry.constants import LEGACY_RATE_LIMIT_OPTIONS, RESERVED_ORGANIZATION_SLUGS
+from sentry.lang.native.utils import STORE_CRASH_REPORTS_DEFAULT, convert_crashreport_count
 from sentry.models import (
     AuditLogEntryEvent,
     Authenticator,
@@ -66,8 +67,8 @@ ORG_OPTIONS = (
     (
         "storeCrashReports",
         "sentry:store_crash_reports",
-        bool,
-        org_serializers.STORE_CRASH_REPORTS_DEFAULT,
+        convert_crashreport_count,
+        STORE_CRASH_REPORTS_DEFAULT,
     ),
     (
         "attachmentsRole",
@@ -130,7 +131,7 @@ class OrganizationSerializer(serializers.Serializer):
     dataScrubberDefaults = serializers.BooleanField(required=False)
     sensitiveFields = ListField(child=serializers.CharField(), required=False)
     safeFields = ListField(child=serializers.CharField(), required=False)
-    storeCrashReports = serializers.BooleanField(required=False)
+    storeCrashReports = serializers.IntegerField(min_value=-1, max_value=20, required=False)
     attachmentsRole = serializers.CharField(required=True)
     scrubIPAddresses = serializers.BooleanField(required=False)
     scrapeJavaScript = serializers.BooleanField(required=False)

+ 4 - 2
src/sentry/api/endpoints/project_details.py

@@ -24,6 +24,7 @@ from sentry.api.serializers.rest_framework.list import ListField
 from sentry.api.serializers.rest_framework.origin import OriginField
 from sentry.constants import RESERVED_PROJECT_SLUGS
 from sentry.lang.native.symbolicator import parse_sources, InvalidSourcesError
+from sentry.lang.native.utils import convert_crashreport_count
 from sentry.models import (
     AuditLogEntryEvent,
     Group,
@@ -109,7 +110,7 @@ class ProjectAdminSerializer(ProjectMemberSerializer):
     dataScrubberDefaults = serializers.BooleanField(required=False)
     sensitiveFields = ListField(child=serializers.CharField(), required=False)
     safeFields = ListField(child=serializers.CharField(), required=False)
-    storeCrashReports = serializers.BooleanField(required=False)
+    storeCrashReports = serializers.IntegerField(min_value=-1, max_value=20, required=False)
     relayPiiConfig = serializers.CharField(required=False, allow_blank=True, allow_null=True)
     builtinSymbolSources = ListField(child=serializers.CharField(), required=False)
     symbolSources = serializers.CharField(required=False, allow_blank=True, allow_null=True)
@@ -532,7 +533,8 @@ class ProjectDetailsEndpoint(ProjectEndpoint):
                 )
             if "sentry:store_crash_reports" in options:
                 project.update_option(
-                    "sentry:store_crash_reports", bool(options["sentry:store_crash_reports"])
+                    "sentry:store_crash_reports",
+                    convert_crashreport_count(options["sentry:store_crash_reports"]),
                 )
             if "sentry:relay_pii_config" in options:
                 project.update_option(

+ 3 - 3
src/sentry/api/serializers/models/organization.py

@@ -8,6 +8,7 @@ from sentry import roles
 from sentry.app import quotas
 from sentry.api.serializers import Serializer, register, serialize
 from sentry.constants import LEGACY_RATE_LIMIT_OPTIONS
+from sentry.lang.native.utils import convert_crashreport_count
 from sentry.models import (
     ApiKey,
     Organization,
@@ -29,7 +30,6 @@ REQUIRE_SCRUB_DATA_DEFAULT = False
 REQUIRE_SCRUB_DEFAULTS_DEFAULT = False
 SENSITIVE_FIELDS_DEFAULT = None
 SAFE_FIELDS_DEFAULT = None
-STORE_CRASH_REPORTS_DEFAULT = False
 ATTACHMENTS_ROLE_DEFAULT = settings.SENTRY_DEFAULT_ROLE
 REQUIRE_SCRUB_IP_ADDRESS_DEFAULT = False
 SCRAPE_JAVASCRIPT_DEFAULT = True
@@ -174,8 +174,8 @@ class DetailedOrganizationSerializer(OrganizationSerializer):
                 )
                 or [],
                 "safeFields": obj.get_option("sentry:safe_fields", SAFE_FIELDS_DEFAULT) or [],
-                "storeCrashReports": bool(
-                    obj.get_option("sentry:store_crash_reports", STORE_CRASH_REPORTS_DEFAULT)
+                "storeCrashReports": convert_crashreport_count(
+                    obj.get_option("sentry:store_crash_reports")
                 ),
                 "attachmentsRole": six.text_type(
                     obj.get_option("sentry:attachments_role", ATTACHMENTS_ROLE_DEFAULT)

+ 3 - 2
src/sentry/api/serializers/models/project.py

@@ -17,6 +17,7 @@ from sentry.app import env
 from sentry.auth.superuser import is_active_superuser
 from sentry.constants import StatsPeriod
 from sentry.digests import backend as digests
+from sentry.lang.native.utils import convert_crashreport_count
 from sentry.models import (
     EnvironmentProject,
     Project,
@@ -551,8 +552,8 @@ class DetailedProjectSerializer(ProjectWithTeamSerializer):
                 "dataScrubber": bool(attrs["options"].get("sentry:scrub_data", True)),
                 "dataScrubberDefaults": bool(attrs["options"].get("sentry:scrub_defaults", True)),
                 "safeFields": attrs["options"].get("sentry:safe_fields", []),
-                "storeCrashReports": bool(
-                    attrs["options"].get("sentry:store_crash_reports", False)
+                "storeCrashReports": convert_crashreport_count(
+                    attrs["options"].get("sentry:store_crash_reports")
                 ),
                 "sensitiveFields": attrs["options"].get("sentry:sensitive_fields", []),
                 "subjectTemplate": attrs["options"].get("mail:subject_template")

+ 1 - 4
src/sentry/coreapi.py

@@ -22,6 +22,7 @@ from sentry.models import ProjectKey
 from sentry.tasks.store import preprocess_event, preprocess_event_from_reprocessing
 from sentry.utils import json
 from sentry.utils.auth import parse_auth_header
+from sentry.utils.cache import cache_key_for_event
 from sentry.utils.http import origin_from_request
 from sentry.utils.strings import decompress
 from sentry.utils.sdk import configure_scope
@@ -253,10 +254,6 @@ class SecurityAuthHelper(AbstractAuthHelper):
         return auth
 
 
-def cache_key_for_event(data):
-    return u"e:{1}:{0}".format(data["project"], data["event_id"])
-
-
 def decompress_deflate(encoded_data):
     try:
         return zlib.decompress(encoded_data).decode("utf-8")

+ 1 - 0
src/sentry/deletions/defaults/group.py

@@ -85,6 +85,7 @@ class GroupDeletionTask(ModelDeletionTask):
             models.GroupEmailThread,
             models.GroupSubscription,
             models.UserReport,
+            models.EventAttachment,
             IncidentGroup,
             # Event is last as its the most time consuming
             models.Event,

+ 1 - 1
src/sentry/ingest/ingest_consumer.py

@@ -8,12 +8,12 @@ from sentry.utils.batching_kafka_consumer import AbstractBatchWorker
 from django.conf import settings
 from django.core.cache import cache
 
-from sentry.coreapi import cache_key_for_event
 from sentry.cache import default_cache
 from sentry.models import Project
 from sentry.signals import event_accepted
 from sentry.tasks.store import preprocess_event
 from sentry.utils import json
+from sentry.utils.cache import cache_key_for_event
 from sentry.utils.kafka import create_batching_kafka_consumer
 
 logger = logging.getLogger(__name__)

+ 30 - 1
src/sentry/lang/native/utils.py

@@ -5,8 +5,8 @@ import six
 import logging
 
 from sentry.attachments import attachment_cache
-from sentry.coreapi import cache_key_for_event
 from sentry.stacktraces.processing import find_stacktraces_in_data
+from sentry.utils.cache import cache_key_for_event
 from sentry.utils.safe import get_path
 
 logger = logging.getLogger(__name__)
@@ -29,6 +29,11 @@ NATIVE_IMAGE_TYPES = (
     "pe",  # Windows
 )
 
+# Default disables storing crash reports.
+STORE_CRASH_REPORTS_DEFAULT = 0
+# Do not limit crash report attachments per group.
+STORE_CRASH_REPORTS_ALL = -1
+
 
 def is_native_platform(platform):
     return platform in NATIVE_PLATFORMS
@@ -108,3 +113,27 @@ def get_event_attachment(data, attachment_type):
     cache_key = cache_key_for_event(data)
     attachments = attachment_cache.get(cache_key) or []
     return next((a for a in attachments if a.type == attachment_type), None)
+
+
+def get_crashreport_key(group_id):
+    return u"cr:%s" % (group_id,)
+
+
+def convert_crashreport_count(value):
+    """
+    Shim to read both legacy and new `sentry:store_crash_reports` project and
+    organization options.
+
+    The legacy format stored `True` for an unlimited number of crash reports,
+    and `False` for no crash reports.
+
+    The new format stores `-1` for unbounded storage, `0` for no crash reports,
+    and a positive number for a bounded number per group.
+
+    Defaults to `0` (no storage).
+    """
+    if value is True:
+        return STORE_CRASH_REPORTS_ALL
+    if value is None:
+        return STORE_CRASH_REPORTS_DEFAULT
+    return int(value)

+ 7 - 1
src/sentry/static/sentry/app/data/forms/organizationGeneralSettings.jsx

@@ -1,6 +1,10 @@
 import {extractMultilineFields} from 'app/utils';
 import {t} from 'app/locale';
 import slugify from 'app/utils/slugify';
+import {
+  STORE_CRASH_REPORTS_VALUES,
+  formatStoreCrashReports,
+} from 'app/utils/crashReports';
 
 // Export route to make these forms searchable by label/help
 export const route = '/settings/:orgId/';
@@ -194,12 +198,14 @@ const formGroups = [
       },
       {
         name: 'storeCrashReports',
-        type: 'boolean',
+        type: 'range',
         label: t('Store Native Crash Reports'),
         help: t(
           'Store native crash reports such as Minidumps for improved processing and download in issue details'
         ),
         visible: ({features}) => features.has('event-attachments'),
+        formatLabel: formatStoreCrashReports,
+        allowedValues: STORE_CRASH_REPORTS_VALUES,
       },
       {
         name: 'attachmentsRole',

+ 8 - 2
src/sentry/static/sentry/app/data/forms/projectGeneralSettings.jsx

@@ -9,6 +9,10 @@ import getDynamicText from 'app/utils/getDynamicText';
 import marked from 'app/utils/marked';
 import platforms from 'app/data/platforms';
 import slugify from 'app/utils/slugify';
+import {
+  STORE_CRASH_REPORTS_VALUES,
+  formatStoreCrashReports,
+} from 'app/utils/crashReports';
 import space from 'app/styles/space';
 import {GroupingConfigItem} from 'app/components/events/groupingInfo';
 
@@ -330,12 +334,14 @@ export const fields = {
   },
   storeCrashReports: {
     name: 'storeCrashReports',
-    type: 'boolean',
+    type: 'range',
     label: t('Store Native Crash Reports'),
     help: t(
-      'Store native crash reports such as Minidumps for improved processing and download in issue details'
+      'Store native crash reports such as Minidumps for improved processing and download in issue details.  Overrides organization settings when enabled.'
     ),
     visible: ({features}) => features.has('event-attachments'),
+    formatLabel: formatStoreCrashReports,
+    allowedValues: STORE_CRASH_REPORTS_VALUES,
   },
   relayPiiConfig: {
     name: 'relayPiiConfig',

Some files were not shown because too many files changed in this diff