diff --git a/src/sentry/api/endpoints/project_details.py b/src/sentry/api/endpoints/project_details.py index eeaf7baf282e6d..362f3ebbd8d747 100644 --- a/src/sentry/api/endpoints/project_details.py +++ b/src/sentry/api/endpoints/project_details.py @@ -695,6 +695,11 @@ def put(self, request: Request, project) -> Response: "sentry:reprocessing_active", bool(options["sentry:reprocessing_active"]), ) + if "filters:react-hydration-errors" in options: + project.update_option( + "filters:react-hydration-errors", + bool(options["filters:react-hydration-errors"]), + ) if "filters:blacklisted_ips" in options: project.update_option( "sentry:blacklisted_ips", diff --git a/src/sentry/api/serializers/models/project.py b/src/sentry/api/serializers/models/project.py index be2f4b2e188575..fbaf131cef3351 100644 --- a/src/sentry/api/serializers/models/project.py +++ b/src/sentry/api/serializers/models/project.py @@ -176,6 +176,7 @@ def format_options(attrs: defaultdict(dict)): "sentry:performance_issue_creation_rate" ), "filters:blacklisted_ips": "\n".join(options.get("sentry:blacklisted_ips", [])), + "filters:react-hydration-errors": bool(options.get("filters:react-hydration-errors", True)), f"filters:{FilterTypes.RELEASES}": "\n".join( options.get(f"sentry:{FilterTypes.RELEASES}", []) ), diff --git a/src/sentry/relay/config/__init__.py b/src/sentry/relay/config/__init__.py index e31ac182eaceab..4afb13c94953bb 100644 --- a/src/sentry/relay/config/__init__.py +++ b/src/sentry/relay/config/__init__.py @@ -109,14 +109,31 @@ def get_filter_settings(project: Project) -> Mapping[str, Any]: settings = _load_filter_settings(flt, project) filter_settings[filter_id] = settings + error_messages = [] if features.has("projects:custom-inbound-filters", project): invalid_releases = project.get_option(f"sentry:{FilterTypes.RELEASES}") if invalid_releases: filter_settings["releases"] = {"releases": invalid_releases} - error_messages = project.get_option(f"sentry:{FilterTypes.ERROR_MESSAGES}") - if error_messages: - filter_settings["errorMessages"] = {"patterns": error_messages} + error_messages += project.get_option(f"sentry:{FilterTypes.ERROR_MESSAGES}") or [] + + enable_react = project.get_option("filters:react-hydration-errors") + if enable_react: + error_messages += [ + # Hydration failed because the initial UI does not match what was rendered on the server. + "https://reactjs.org/docs/error-decoder.html?invariant=418", + # The server could not finish this Suspense boundary, likely due to an error during server rendering. Switched to client rendering. + "https://reactjs.org/docs/error-decoder.html?invariant=419", + # There was an error while hydrating this Suspense boundary. Switched to client rendering. + "https://reactjs.org/docs/error-decoder.html?invariant=422", + # There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will switch to client rendering. + "https://reactjs.org/docs/error-decoder.html?invariant=423", + # Text content does not match server-rendered HTML. + "https://reactjs.org/docs/error-decoder.html?invariant=425", + ] + + if error_messages: + filter_settings["errorMessages"] = {"patterns": error_messages} blacklisted_ips = project.get_option("sentry:blacklisted_ips") if blacklisted_ips: diff --git a/static/app/data/forms/inboundFilters.tsx b/static/app/data/forms/inboundFilters.tsx index b035bc029777f2..5b157b6543f849 100644 --- a/static/app/data/forms/inboundFilters.tsx +++ b/static/app/data/forms/inboundFilters.tsx @@ -12,7 +12,7 @@ const globHelpText = tct('Allows [link:glob pattern matching].', { link: , }); -const getOptionsData = (data: object) => ({options: data}); +export const getOptionsData = (data: object) => ({options: data}); const formGroups: JsonFormObject[] = [ { diff --git a/static/app/views/settings/project/projectFilters/projectFiltersSettings.tsx b/static/app/views/settings/project/projectFilters/projectFiltersSettings.tsx index 68a9c420cbd576..00b3fe1df0143f 100644 --- a/static/app/views/settings/project/projectFilters/projectFiltersSettings.tsx +++ b/static/app/views/settings/project/projectFilters/projectFiltersSettings.tsx @@ -23,7 +23,10 @@ import { PanelItem, } from 'sentry/components/panels'; import Switch from 'sentry/components/switchButton'; -import filterGroups, {customFilterFields} from 'sentry/data/forms/inboundFilters'; +import filterGroups, { + customFilterFields, + getOptionsData, +} from 'sentry/data/forms/inboundFilters'; import {t} from 'sentry/locale'; import HookStore from 'sentry/stores/hookStore'; import ProjectsStore from 'sentry/stores/projectsStore'; @@ -359,6 +362,31 @@ class ProjectFiltersSettings extends AsyncComponent { ); })} + + + + +