Просмотр исходного кода

feat(inbound-filters): Add inbound filter for ChunkLoadError (#57277)

Riccardo Busetti 1 год назад
Родитель
Сommit
5f4e2a109e

+ 5 - 0
src/sentry/api/endpoints/project_details.py

@@ -811,6 +811,11 @@ class ProjectDetailsEndpoint(ProjectEndpoint):
                     "filters:react-hydration-errors",
                     bool(options["filters:react-hydration-errors"]),
                 )
+            if "filters:chunk-load-error" in options:
+                project.update_option(
+                    "filters:chunk-load-error",
+                    "1" if bool(options["filters:chunk-load-error"]) else "0",
+                )
             if "filters:blacklisted_ips" in options:
                 project.update_option(
                     "sentry:blacklisted_ips",

+ 1 - 0
src/sentry/api/serializers/models/project.py

@@ -216,6 +216,7 @@ def format_options(attrs: dict[str, Any]) -> dict[str, Any]:
         "sentry:reprocessing_active": bool(options.get("sentry:reprocessing_active", False)),
         "filters:blacklisted_ips": "\n".join(options.get("sentry:blacklisted_ips", [])),
         "filters:react-hydration-errors": bool(options.get("filters:react-hydration-errors", True)),
+        "filters:chunk-load-error": options.get("filters:chunk-load-error", "1") == "1",
         f"filters:{FilterTypes.RELEASES}": "\n".join(
             options.get(f"sentry:{FilterTypes.RELEASES}", [])
         ),

+ 1 - 0
src/sentry/apidocs/examples/project_examples.py

@@ -189,6 +189,7 @@ detailed_project = {
         "sentry:reprocessing_active": False,
         "filters:blacklisted_ips": "",
         "filters:react-hydration-errors": True,
+        "filters:chunk-load-error": True,
         "filters:releases": "",
         "filters:error_messages": "",
         "feedback:branding": True,

+ 1 - 0
src/sentry/models/options/project_option.py

@@ -64,6 +64,7 @@ OPTION_KEYS = frozenset(
         "mail:subject_prefix",
         "mail:subject_template",
         "filters:react-hydration-errors",
+        "filters:chunk-load-error",
     ]
 )
 

+ 3 - 0
src/sentry/projectoptions/defaults.py

@@ -79,6 +79,9 @@ register(key="filters:localhost", epoch_defaults={1: "0"})
 # Default react hydration errors filter
 register(key="filters:react-hydration-errors", epoch_defaults={1: "1"})
 
+# Default NextJS chunk load error filter
+register(key="filters:chunk-load-error", epoch_defaults={1: "1"})
+
 # Default breakdowns config
 register(
     key="sentry:breakdowns",

+ 7 - 0
src/sentry/relay/config/__init__.py

@@ -135,6 +135,8 @@ def get_filter_settings(project: Project) -> Mapping[str, Any]:
 
         error_messages += project.get_option(f"sentry:{FilterTypes.ERROR_MESSAGES}") or []
 
+    # TODO: refactor the system to allow management of error messages filtering via the inbound filters, since right
+    #  now the system maps an option to a single top-level filter like "ignoreTransactions".
     enable_react = project.get_option("filters:react-hydration-errors")
     if enable_react:
         # 418 - Hydration failed because the initial UI does not match what was rendered on the server.
@@ -146,6 +148,11 @@ def get_filter_settings(project: Project) -> Mapping[str, Any]:
             "*https://reactjs.org/docs/error-decoder.html?invariant={418,419,422,423,425}*"
         ]
 
+    if project.get_option("filters:chunk-load-error") == "1":
+        # ChunkLoadError: Loading chunk 3662 failed.\n(error:
+        # https://xxx.com/_next/static/chunks/29107295-0151559bd23117ba.js)
+        error_messages += ["ChunkLoadError: Loading chunk * failed.\n(error: *)"]
+
     if error_messages:
         filter_settings["errorMessages"] = {"patterns": error_messages}
 

+ 13 - 0
tests/sentry/api/endpoints/test_project_details.py

@@ -600,6 +600,7 @@ class ProjectUpdateTest(APITestCase):
             "sentry:verify_ssl": False,
             "feedback:branding": False,
             "filters:react-hydration-errors": True,
+            "filters:chunk-load-error": True,
         }
         with self.feature("projects:custom-inbound-filters"), outbox_runner():
             self.get_success_response(self.org_slug, self.proj_slug, options=options)
@@ -716,6 +717,7 @@ class ProjectUpdateTest(APITestCase):
                 event=audit_log.get_event_id("PROJECT_EDIT"),
             ).exists()
         assert project.get_option("filters:react-hydration-errors", "1")
+        assert project.get_option("filters:chunk-load-error", "1")
 
     def test_bookmarks(self):
         self.get_success_response(self.org_slug, self.proj_slug, isBookmarked="false")
@@ -855,6 +857,17 @@ class ProjectUpdateTest(APITestCase):
         assert self.project.get_option("filters:react-hydration-errors") == value
         assert resp.data["options"]["filters:react-hydration-errors"] == value
 
+    def test_chunk_load_error(self):
+        options = {"filters:chunk-load-error": False}
+        resp = self.get_success_response(self.org_slug, self.proj_slug, options=options)
+        assert self.project.get_option("filters:chunk-load-error") == "0"
+        assert resp.data["options"]["filters:chunk-load-error"] is False
+
+        options = {"filters:chunk-load-error": True}
+        resp = self.get_success_response(self.org_slug, self.proj_slug, options=options)
+        assert self.project.get_option("filters:chunk-load-error") == "1"
+        assert resp.data["options"]["filters:chunk-load-error"] is True
+
     def test_relay_pii_config(self):
         value = '{"applications": {"freeform": []}}'
         resp = self.get_success_response(self.org_slug, self.proj_slug, relayPiiConfig=value)

+ 4 - 1
tests/sentry/relay/snapshots/test_config/test_get_project_config/full_config/MONOLITH.pysnap

@@ -1,5 +1,5 @@
 ---
-created: '2023-09-15T07:41:54.167449Z'
+created: '2023-10-02T11:41:08.185986Z'
 creator: sentry
 source: tests/sentry/relay/test_config.py
 ---
@@ -83,6 +83,9 @@ config:
     errorMessages:
       patterns:
       - '*https://reactjs.org/docs/error-decoder.html?invariant={418,419,422,423,425}*'
+      - 'ChunkLoadError: Loading chunk * failed.
+
+        (error: *)'
     ignoreTransactions:
       isEnabled: true
       patterns:

+ 4 - 1
tests/sentry/relay/snapshots/test_config/test_get_project_config/full_config/REGION.pysnap

@@ -1,5 +1,5 @@
 ---
-created: '2023-09-15T07:41:54.637425Z'
+created: '2023-10-02T11:41:08.393725Z'
 creator: sentry
 source: tests/sentry/relay/test_config.py
 ---
@@ -83,6 +83,9 @@ config:
     errorMessages:
       patterns:
       - '*https://reactjs.org/docs/error-decoder.html?invariant={418,419,422,423,425}*'
+      - 'ChunkLoadError: Loading chunk * failed.
+
+        (error: *)'
     ignoreTransactions:
       isEnabled: true
       patterns:

+ 18 - 0
tests/sentry/relay/test_config.py

@@ -179,6 +179,7 @@ def test_project_config_uses_filter_features(
     default_project.update_option("sentry:error_messages", error_messages)
     default_project.update_option("sentry:releases", releases)
     default_project.update_option("filters:react-hydration-errors", False)
+    default_project.update_option("filters:chunk-load-error", "0")
 
     if has_blacklisted_ips:
         default_project.update_option("sentry:blacklisted_ips", blacklisted_ips)
@@ -205,6 +206,23 @@ def test_project_config_uses_filter_features(
         assert cfg_client_ips is None
 
 
+@django_db_all
+@region_silo_test(stable=True)
+def test_project_config_with_chunk_load_error_filter(default_project):
+    # The react-hydration-errors option is set as string in the defaults but then its changed as a boolean in the
+    # options endpoint, which is something that should be changed.
+    default_project.update_option("filters:react-hydration-errors", False)
+    default_project.update_option("filters:chunk-load-error", "1")
+
+    project_cfg = get_project_config(default_project, full_config=True)
+
+    cfg = project_cfg.to_dict()
+    _validate_project_config(cfg["config"])
+    cfg_error_messages = get_path(cfg, "config", "filterSettings", "errorMessages")
+
+    assert len(cfg_error_messages) == 1
+
+
 @django_db_all
 @region_silo_test(stable=True)
 @mock.patch("sentry.relay.config.EXPOSABLE_FEATURES", ["organizations:profiling"])