Skip to content

Commit 550b158

Browse files
leeandherryan953
authored andcommitted
chore(data-forwarding): Allow clearing empty overrides, examples for endpoints, typo (#104479)
This PR adds a clear overrides button. Therefore projects can have an empty value where there was once a value if they choose (i.e. global has "some-value", override has "", which is different than no override) <img width="835" height="368" alt="image" src="https://github.com/user-attachments/assets/c51df12f-c855-4cc5-8854-0f56a2b81ace" /> Also edits a typo on the SQS form, and adds modifies some examples with realistic payloads.
1 parent 8d08dc5 commit 550b158

File tree

7 files changed

+209
-15
lines changed

7 files changed

+209
-15
lines changed

src/sentry/apidocs/examples/integration_examples.py

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -754,3 +754,150 @@ class IntegrationExamples:
754754
response_only=True,
755755
)
756756
]
757+
758+
LIST_DATA_FORWARDERS = [
759+
OpenApiExample(
760+
"List all data forwarders for an organization",
761+
value=[
762+
[
763+
{
764+
"id": "1",
765+
"organizationId": "1",
766+
"isEnabled": True,
767+
"enrollNewProjects": True,
768+
"enrolledProjects": [],
769+
"provider": "sqs",
770+
"config": {
771+
"region": "us-east-1",
772+
"queue_url": "https://sqs.us-east-1.amazonaws.com/01234567890/sentry-errors.fifo",
773+
"s3_bucket": "sentry-errors-bucket",
774+
"access_key": "AKIAIOSFODNN7EXAMPLE",
775+
"secret_key": "wJalrXUtnFEMI1K7MDENGSbPxRfiCYEXAMPLEKEY",
776+
"message_group_id": "sentry-errors",
777+
},
778+
"projectConfigs": [],
779+
"dateAdded": "2025-11-01T00:00:00.000000Z",
780+
"dateUpdated": "2025-11-01T00:00:00.000000Z",
781+
},
782+
{
783+
"id": "2",
784+
"organizationId": "1",
785+
"isEnabled": True,
786+
"enrollNewProjects": False,
787+
"enrolledProjects": [
788+
{"id": "1", "slug": "proj-1", "platform": "javascript-react"},
789+
{"id": "2", "slug": "proj-2", "platform": "python-flask"},
790+
],
791+
"provider": "segment",
792+
"config": {"write_key": "itA5bLOPNxccvZ9ON1NYg9EXAMPLEKEY"},
793+
"projectConfigs": [
794+
{
795+
"id": "1",
796+
"isEnabled": True,
797+
"dataForwarderId": "2",
798+
"project": {
799+
"id": "1",
800+
"slug": "proj-1",
801+
"platform": "javascript-react",
802+
},
803+
"overrides": {},
804+
"effectiveConfig": {
805+
"write_key": "itA5bLOPNxccvZ9ON1NYg9EXAMPLEKEY"
806+
},
807+
"dateAdded": "2025-11-01T00:00:00.000000Z",
808+
"dateUpdated": "2025-11-01T00:00:00.000000Z",
809+
},
810+
{
811+
"id": "2",
812+
"isEnabled": True,
813+
"dataForwarderId": "2",
814+
"project": {
815+
"id": "2",
816+
"slug": "proj-2",
817+
"platform": "python-flask",
818+
},
819+
"overrides": {},
820+
"effectiveConfig": {
821+
"write_key": "itA5bLOPNxccvZ9ON1NYg9EXAMPLEKEY"
822+
},
823+
"dateAdded": "2025-11-01T00:00:00.000000Z",
824+
"dateUpdated": "2025-11-01T00:00:00.000000Z",
825+
},
826+
],
827+
"dateAdded": "2025-11-01T00:00:00.000000Z",
828+
"dateUpdated": "2025-11-01T00:00:00.000000Z",
829+
},
830+
{
831+
"id": "3",
832+
"organizationId": "1",
833+
"isEnabled": True,
834+
"enrollNewProjects": True,
835+
"enrolledProjects": [
836+
{"id": "1", "slug": "proj-1", "platform": "javascript-react"},
837+
],
838+
"provider": "splunk",
839+
"config": {
840+
"index": "main",
841+
"token": "ab13cdef-45aa-1bcd-a123-bcEXAMPLEKEY",
842+
"source": "sentry",
843+
"instance_url": "https://prd-a-abcde.splunkcloud.com:8088",
844+
},
845+
"projectConfigs": [
846+
{
847+
"id": "3",
848+
"isEnabled": True,
849+
"dataForwarderId": "3",
850+
"project": {
851+
"id": "1",
852+
"slug": "proj-1",
853+
"platform": "javascript-react",
854+
},
855+
"overrides": {
856+
"source": "sentry-custom",
857+
},
858+
"effectiveConfig": {
859+
"index": "main",
860+
"token": "ab13cdef-45aa-1bcd-a123-bcEXAMPLEKEY",
861+
"source": "sentry-custom",
862+
"instance_url": "https://prd-a-abcde.splunkcloud.com:8088",
863+
},
864+
"dateAdded": "2025-11-01T00:00:00.000000Z",
865+
"dateUpdated": "2025-11-01T00:00:00.000000Z",
866+
}
867+
],
868+
"dateAdded": "2025-11-01T00:00:00.000000Z",
869+
"dateUpdated": "2025-11-01T00:00:00.000000Z",
870+
},
871+
]
872+
],
873+
status_codes=["200"],
874+
response_only=True,
875+
)
876+
]
877+
878+
SINGLE_DATA_FORWARDER = [
879+
OpenApiExample(
880+
"A data forwarder for an organization",
881+
value={
882+
"id": "1",
883+
"organizationId": "1",
884+
"isEnabled": True,
885+
"enrollNewProjects": True,
886+
"enrolledProjects": [],
887+
"provider": "sqs",
888+
"config": {
889+
"region": "us-east-1",
890+
"queue_url": "https://sqs.us-east-1.amazonaws.com/01234567890/sentry-errors.fifo",
891+
"s3_bucket": "sentry-errors-bucket",
892+
"access_key": "AKIAIOSFODNN7EXAMPLE",
893+
"secret_key": "wJalrXUtnFEMI1K7MDENGSbPxRfiCYEXAMPLEKEY",
894+
"message_group_id": "sentry-errors",
895+
},
896+
"projectConfigs": [],
897+
"dateAdded": "2025-11-01T00:00:00.000000Z",
898+
"dateUpdated": "2025-11-01T00:00:00.000000Z",
899+
},
900+
status_codes=["200"],
901+
response_only=True,
902+
)
903+
]

src/sentry/apidocs/parameters.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -544,6 +544,16 @@ class MetricAlertParams:
544544
)
545545

546546

547+
class DataForwarderParams:
548+
DATA_FORWARDER_ID = OpenApiParameter(
549+
name="data_forwarder_id",
550+
location="path",
551+
required=True,
552+
type=int,
553+
description="The ID of the data forwarder you'd like to query.",
554+
)
555+
556+
547557
class SentryAppParams:
548558
SENTRY_APP_ID_OR_SLUG = OpenApiParameter(
549559
name="sentry_app_id_or_slug",

src/sentry/integrations/api/endpoints/data_forwarding_details.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818
from sentry.api.exceptions import ResourceDoesNotExist
1919
from sentry.api.serializers import serialize
2020
from sentry.apidocs.constants import RESPONSE_BAD_REQUEST, RESPONSE_FORBIDDEN, RESPONSE_NO_CONTENT
21-
from sentry.apidocs.parameters import GlobalParams
21+
from sentry.apidocs.examples.integration_examples import IntegrationExamples
22+
from sentry.apidocs.parameters import DataForwarderParams, GlobalParams
2223
from sentry.integrations.api.serializers.models.data_forwarder import (
2324
DataForwarderSerializer as DataForwarderModelSerializer,
2425
)
@@ -290,13 +291,14 @@ def _update_single_project_configuration(
290291
@method_decorator(never_cache)
291292
@extend_schema(
292293
operation_id="Update a Data Forwarding Configuration for an Organization",
293-
parameters=[GlobalParams.ORG_ID_OR_SLUG],
294+
parameters=[GlobalParams.ORG_ID_OR_SLUG, DataForwarderParams.DATA_FORWARDER_ID],
294295
request=DataForwarderSerializer,
295296
responses={
296297
200: DataForwarderModelSerializer,
297298
400: RESPONSE_BAD_REQUEST,
298299
403: RESPONSE_FORBIDDEN,
299300
},
301+
examples=IntegrationExamples.SINGLE_DATA_FORWARDER,
300302
)
301303
def put(
302304
self, request: Request, organization: Organization, data_forwarder: DataForwarder
@@ -332,7 +334,7 @@ def put(
332334

333335
@extend_schema(
334336
operation_id="Delete a Data Forwarding Configuration for an Organization",
335-
parameters=[GlobalParams.ORG_ID_OR_SLUG],
337+
parameters=[GlobalParams.ORG_ID_OR_SLUG, DataForwarderParams.DATA_FORWARDER_ID],
336338
responses={
337339
204: RESPONSE_NO_CONTENT,
338340
403: RESPONSE_FORBIDDEN,

src/sentry/integrations/api/endpoints/data_forwarding_index.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@
1313
from sentry.api.bases.organization import OrganizationEndpoint, OrganizationPermission
1414
from sentry.api.paginator import OffsetPaginator
1515
from sentry.api.serializers import serialize
16-
from sentry.apidocs.constants import RESPONSE_BAD_REQUEST, RESPONSE_CONFLICT, RESPONSE_FORBIDDEN
16+
from sentry.apidocs.constants import RESPONSE_BAD_REQUEST, RESPONSE_FORBIDDEN
17+
from sentry.apidocs.examples.integration_examples import IntegrationExamples
1718
from sentry.apidocs.parameters import GlobalParams
19+
from sentry.apidocs.utils import inline_sentry_response_serializer
1820
from sentry.integrations.api.serializers.models.data_forwarder import (
1921
DataForwarderSerializer as DataForwarderModelSerializer,
2022
)
@@ -52,8 +54,11 @@ def convert_args(self, request: Request, *args, **kwargs):
5254
operation_id="Retrieve Data Forwarding Configurations for an Organization",
5355
parameters=[GlobalParams.ORG_ID_OR_SLUG],
5456
responses={
55-
200: DataForwarderModelSerializer,
57+
200: inline_sentry_response_serializer(
58+
"ListDataForwarderResponse", list[DataForwarderSerializer]
59+
)
5660
},
61+
examples=IntegrationExamples.LIST_DATA_FORWARDERS,
5762
)
5863
@set_referrer_policy("strict-origin-when-cross-origin")
5964
@method_decorator(never_cache)
@@ -75,8 +80,8 @@ def get(self, request: Request, organization) -> Response:
7580
201: DataForwarderModelSerializer,
7681
400: RESPONSE_BAD_REQUEST,
7782
403: RESPONSE_FORBIDDEN,
78-
409: RESPONSE_CONFLICT,
7983
},
84+
examples=IntegrationExamples.SINGLE_DATA_FORWARDER,
8085
)
8186
@set_referrer_policy("strict-origin-when-cross-origin")
8287
@method_decorator(never_cache)

src/sentry/integrations/api/serializers/rest_framework/data_forwarder.py

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,19 +40,35 @@ class SplunkConfig(TypedDict, total=False):
4040

4141

4242
class DataForwarderSerializer(Serializer):
43-
organization_id = serializers.IntegerField()
44-
is_enabled = serializers.BooleanField(default=True)
45-
enroll_new_projects = serializers.BooleanField(default=False)
43+
organization_id = serializers.IntegerField(
44+
help_text="The ID of the organization related to the data forwarder."
45+
)
46+
is_enabled = serializers.BooleanField(
47+
default=True, help_text="Whether the data forwarder is enabled."
48+
)
49+
enroll_new_projects = serializers.BooleanField(
50+
default=False,
51+
help_text="Whether to enroll new projects automatically, after they're created.",
52+
)
4653
provider = serializers.ChoiceField(
4754
choices=[
4855
(DataForwarderProviderSlug.SEGMENT, "Segment"),
4956
(DataForwarderProviderSlug.SQS, "Amazon SQS"),
5057
(DataForwarderProviderSlug.SPLUNK, "Splunk"),
51-
]
58+
],
59+
help_text='The provider of the data forwarder. One of "segment", "sqs", or "splunk".',
60+
)
61+
config = serializers.DictField(
62+
child=serializers.CharField(allow_blank=True),
63+
default=dict,
64+
help_text="The configuration for the data forwarder.",
5265
)
53-
config = serializers.DictField(child=serializers.CharField(allow_blank=True), default=dict)
5466
project_ids = serializers.ListField(
55-
child=serializers.IntegerField(), allow_empty=True, required=False, default=list
67+
child=serializers.IntegerField(),
68+
allow_empty=True,
69+
required=False,
70+
default=list,
71+
help_text="The IDs of the projects to attach the data forwarder to.",
5672
)
5773

5874
def validate_config(self, config) -> SQSConfig | SegmentConfig | SplunkConfig:

static/app/views/settings/organizationDataForwarding/components/projectOverrideForm.tsx

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import JsonForm from 'sentry/components/forms/jsonForm';
1010
import FormModel from 'sentry/components/forms/model';
1111
import Panel from 'sentry/components/panels/panel';
1212
import PanelHeader from 'sentry/components/panels/panelHeader';
13+
import {IconRefresh} from 'sentry/icons';
1314
import {IconInfo} from 'sentry/icons/iconInfo';
1415
import {t} from 'sentry/locale';
1516
import type {AvatarProject} from 'sentry/types/project';
@@ -75,7 +76,20 @@ export function ProjectOverrideForm({
7576
</Flex>
7677
)}
7778
renderFooter={() => (
78-
<Flex justify="end" padding="lg xl">
79+
<Flex justify="between" padding="lg xl">
80+
<Button
81+
size="sm"
82+
icon={<IconRefresh color="danger" transform="scale(-1, 1)" />}
83+
onClick={() => {
84+
updateDataForwarder({
85+
project_id: `${project.id}`,
86+
overrides: {},
87+
is_enabled: projectConfig?.isEnabled ?? false,
88+
});
89+
}}
90+
>
91+
{t('Clear Override')}
92+
</Button>
7993
<Button priority="primary" size="sm" type="submit">
8094
{t('Save Override')}
8195
</Button>

static/app/views/settings/organizationDataForwarding/util/forms.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ const SQS_GLOBAL_CONFIGURATION_FORM: JsonFormObject = {
188188
label: 'Secret Key',
189189
type: 'text',
190190
required: true,
191-
help: 'Only visible once when the access key is created..',
191+
help: 'Only visible once when the access key is created.',
192192
placeholder: 'e.g. wJalrXUtnFEMI1K7MDENGSbPxRfiCYEXAMPLEKEY',
193193
},
194194
{
@@ -241,7 +241,7 @@ const SPLUNK_GLOBAL_CONFIGURATION_FORM: JsonFormObject = {
241241
type: 'text',
242242
required: true,
243243
help: 'The token generated for your HTTP Event Collector.',
244-
placeholder: 'e.g. 1234567890abcdef1234567890abcdef',
244+
placeholder: 'e.g. ab13cdef-45aa-1bcd-a123-bcEXAMPLEKEY',
245245
},
246246
{
247247
name: 'index',

0 commit comments

Comments
 (0)