From c374ec3ccb0921e4197715d39a48f63e039b85db Mon Sep 17 00:00:00 2001 From: Irene Liu Date: Tue, 18 Nov 2025 16:14:55 -0800 Subject: [PATCH 1/5] fix bug --- .../serializers/rest_framework/data_forwarder.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/sentry/integrations/api/serializers/rest_framework/data_forwarder.py b/src/sentry/integrations/api/serializers/rest_framework/data_forwarder.py index 0105b9cd9011fb..80aafe2471ca8f 100644 --- a/src/sentry/integrations/api/serializers/rest_framework/data_forwarder.py +++ b/src/sentry/integrations/api/serializers/rest_framework/data_forwarder.py @@ -210,12 +210,16 @@ def create(self, validated_data: Mapping[str, Any]) -> DataForwarder: # Enroll specified projects if project_ids: - for project_id in project_ids: - DataForwarderProject.objects.create( - data_forwarder=data_forwarder, - project_id=project_id, - is_enabled=False, - ) + DataForwarderProject.objects.bulk_create( + [ + DataForwarderProject( + data_forwarder=data_forwarder, + project_id=project_id, + is_enabled=True, + ) + for project_id in project_ids + ] + ) return data_forwarder def update(self, instance: DataForwarder, validated_data: Mapping[str, Any]) -> DataForwarder: From 367ef866b01fe6135f0a1a8d58a5329f22d5352a Mon Sep 17 00:00:00 2001 From: Irene Liu Date: Tue, 18 Nov 2025 16:48:06 -0800 Subject: [PATCH 2/5] allow blanks --- .../api/serializers/rest_framework/data_forwarder.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/sentry/integrations/api/serializers/rest_framework/data_forwarder.py b/src/sentry/integrations/api/serializers/rest_framework/data_forwarder.py index 80aafe2471ca8f..4a8312154795c4 100644 --- a/src/sentry/integrations/api/serializers/rest_framework/data_forwarder.py +++ b/src/sentry/integrations/api/serializers/rest_framework/data_forwarder.py @@ -50,12 +50,14 @@ class DataForwarderSerializer(Serializer): (DataForwarderProviderSlug.SPLUNK, "Splunk"), ] ) - config = serializers.DictField(child=serializers.CharField(allow_blank=False), default=dict) + config = serializers.DictField(child=serializers.CharField(allow_blank=True), default=dict) project_ids = serializers.ListField( child=serializers.IntegerField(), allow_empty=True, required=True ) def validate_config(self, config) -> SQSConfig | SegmentConfig | SplunkConfig: + # Filter out empty string values (cleared optional fields) + config = {k: v for k, v in config.items() if v != ""} provider = self.initial_data.get("provider") if provider == DataForwarderProviderSlug.SQS: From 5f75ecfc7b4e540e3360bad8b8caaf768938bcfc Mon Sep 17 00:00:00 2001 From: Irene Liu Date: Wed, 19 Nov 2025 12:19:14 -0800 Subject: [PATCH 3/5] test fix --- .../api/serializers/rest_framework/data_forwarder.py | 2 +- .../integrations/api/endpoints/test_data_forwarding.py | 9 ++++++--- .../serializers/rest_framework/test_data_forwarder.py | 8 ++++---- 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/sentry/integrations/api/serializers/rest_framework/data_forwarder.py b/src/sentry/integrations/api/serializers/rest_framework/data_forwarder.py index 4a8312154795c4..137ea9165900ba 100644 --- a/src/sentry/integrations/api/serializers/rest_framework/data_forwarder.py +++ b/src/sentry/integrations/api/serializers/rest_framework/data_forwarder.py @@ -52,7 +52,7 @@ class DataForwarderSerializer(Serializer): ) config = serializers.DictField(child=serializers.CharField(allow_blank=True), default=dict) project_ids = serializers.ListField( - child=serializers.IntegerField(), allow_empty=True, required=True + child=serializers.IntegerField(), allow_empty=True, required=False, default=list ) def validate_config(self, config) -> SQSConfig | SegmentConfig | SplunkConfig: diff --git a/tests/sentry/integrations/api/endpoints/test_data_forwarding.py b/tests/sentry/integrations/api/endpoints/test_data_forwarding.py index 3c07f9964f195c..29053128de97f6 100644 --- a/tests/sentry/integrations/api/endpoints/test_data_forwarding.py +++ b/tests/sentry/integrations/api/endpoints/test_data_forwarding.py @@ -346,14 +346,17 @@ def test_create_missing_config(self) -> None: response = self.get_error_response(self.organization.slug, status_code=400, **payload) assert "config" in str(response.data).lower() - def test_create_missing_project_ids(self) -> None: + def test_create_without_project_ids(self) -> None: payload = { "provider": DataForwarderProviderSlug.SEGMENT, "config": {"write_key": "test_key"}, } - response = self.get_error_response(self.organization.slug, status_code=400, **payload) - assert "project_ids" in str(response.data).lower() + response = self.get_success_response(self.organization.slug, status_code=201, **payload) + assert response.data["provider"] == DataForwarderProviderSlug.SEGMENT + + data_forwarder = DataForwarder.objects.get(id=response.data["id"]) + assert data_forwarder.dataforwarderproject_set.count() == 0 def test_create_sqs_fifo_queue_validation(self) -> None: payload = { diff --git a/tests/sentry/integrations/api/serializers/rest_framework/test_data_forwarder.py b/tests/sentry/integrations/api/serializers/rest_framework/test_data_forwarder.py index 5c23e479ea554f..9541599f421191 100644 --- a/tests/sentry/integrations/api/serializers/rest_framework/test_data_forwarder.py +++ b/tests/sentry/integrations/api/serializers/rest_framework/test_data_forwarder.py @@ -230,8 +230,8 @@ def test_sqs_config_validation_empty_credentials(self) -> None: ) assert not serializer.is_valid() assert "config" in serializer.errors - config_errors = serializer.errors["config"] - assert "access_key" in config_errors or "secret_key" in config_errors + config_errors_str = str(serializer.errors["config"]) + assert "access_key" in config_errors_str and "secret_key" in config_errors_str def test_sqs_config_validation_fifo_queue_without_message_group_id(self) -> None: config: dict[str, str] = { @@ -419,8 +419,8 @@ def test_splunk_config_validation_empty_strings(self) -> None: ) assert not serializer.is_valid() assert "config" in serializer.errors - config_errors = serializer.errors["config"] - assert "index" in config_errors or "source" in config_errors + config_errors_str = str(serializer.errors["config"]) + assert "index" in config_errors_str and "source" in config_errors_str def test_splunk_config_validation_invalid_token_format(self) -> None: config: dict[str, str] = { From 865082eb0b0595092dbfcf13328f28ad79f622f8 Mon Sep 17 00:00:00 2001 From: Irene Liu Date: Wed, 19 Nov 2025 12:36:02 -0800 Subject: [PATCH 4/5] typing --- tests/sentry/integrations/api/endpoints/test_data_forwarding.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/sentry/integrations/api/endpoints/test_data_forwarding.py b/tests/sentry/integrations/api/endpoints/test_data_forwarding.py index 29053128de97f6..1b9705a98089d6 100644 --- a/tests/sentry/integrations/api/endpoints/test_data_forwarding.py +++ b/tests/sentry/integrations/api/endpoints/test_data_forwarding.py @@ -356,7 +356,7 @@ def test_create_without_project_ids(self) -> None: assert response.data["provider"] == DataForwarderProviderSlug.SEGMENT data_forwarder = DataForwarder.objects.get(id=response.data["id"]) - assert data_forwarder.dataforwarderproject_set.count() == 0 + assert data_forwarder.project.count() == 0 def test_create_sqs_fifo_queue_validation(self) -> None: payload = { From 421f61f1fe1a1ac278326c30e8ec8d8e057c04da Mon Sep 17 00:00:00 2001 From: Irene Liu Date: Thu, 20 Nov 2025 10:04:11 -0800 Subject: [PATCH 5/5] fix tests --- .../api/endpoints/test_data_forwarding.py | 2 +- .../serializers/rest_framework/test_data_forwarder.py | 11 ----------- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/tests/sentry/integrations/api/endpoints/test_data_forwarding.py b/tests/sentry/integrations/api/endpoints/test_data_forwarding.py index 1b9705a98089d6..0616420ad2a5c0 100644 --- a/tests/sentry/integrations/api/endpoints/test_data_forwarding.py +++ b/tests/sentry/integrations/api/endpoints/test_data_forwarding.py @@ -356,7 +356,7 @@ def test_create_without_project_ids(self) -> None: assert response.data["provider"] == DataForwarderProviderSlug.SEGMENT data_forwarder = DataForwarder.objects.get(id=response.data["id"]) - assert data_forwarder.project.count() == 0 + assert data_forwarder.projects.count() == 0 def test_create_sqs_fifo_queue_validation(self) -> None: payload = { diff --git a/tests/sentry/integrations/api/serializers/rest_framework/test_data_forwarder.py b/tests/sentry/integrations/api/serializers/rest_framework/test_data_forwarder.py index 9541599f421191..64a5e037c4688a 100644 --- a/tests/sentry/integrations/api/serializers/rest_framework/test_data_forwarder.py +++ b/tests/sentry/integrations/api/serializers/rest_framework/test_data_forwarder.py @@ -69,17 +69,6 @@ def test_required_fields(self) -> None: assert not serializer.is_valid() assert "provider" in serializer.errors - # Missing project_ids - serializer = DataForwarderSerializer( - data={ - "organization_id": self.organization.id, - "provider": DataForwarderProviderSlug.SEGMENT, - "config": {"write_key": "test_key"}, - } - ) - assert not serializer.is_valid() - assert "project_ids" in serializer.errors - def test_provider_choice_validation(self) -> None: # Valid providers provider_configs = {