Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
147 changes: 147 additions & 0 deletions src/sentry/apidocs/examples/integration_examples.py
Original file line number Diff line number Diff line change
Expand Up @@ -754,3 +754,150 @@ class IntegrationExamples:
response_only=True,
)
]

LIST_DATA_FORWARDERS = [
OpenApiExample(
"List all data forwarders for an organization",
value=[
[
{
"id": "1",
"organizationId": "1",
"isEnabled": True,
"enrollNewProjects": True,
"enrolledProjects": [],
"provider": "sqs",
"config": {
"region": "us-east-1",
"queue_url": "https://sqs.us-east-1.amazonaws.com/01234567890/sentry-errors.fifo",
"s3_bucket": "sentry-errors-bucket",
"access_key": "AKIAIOSFODNN7EXAMPLE",
"secret_key": "wJalrXUtnFEMI1K7MDENGSbPxRfiCYEXAMPLEKEY",
"message_group_id": "sentry-errors",
},
"projectConfigs": [],
"dateAdded": "2025-11-01T00:00:00.000000Z",
"dateUpdated": "2025-11-01T00:00:00.000000Z",
},
{
"id": "2",
"organizationId": "1",
"isEnabled": True,
"enrollNewProjects": False,
"enrolledProjects": [
{"id": "1", "slug": "proj-1", "platform": "javascript-react"},
{"id": "2", "slug": "proj-2", "platform": "python-flask"},
],
"provider": "segment",
"config": {"write_key": "itA5bLOPNxccvZ9ON1NYg9EXAMPLEKEY"},
"projectConfigs": [
{
"id": "1",
"isEnabled": True,
"dataForwarderId": "2",
"project": {
"id": "1",
"slug": "proj-1",
"platform": "javascript-react",
},
"overrides": {},
"effectiveConfig": {
"write_key": "itA5bLOPNxccvZ9ON1NYg9EXAMPLEKEY"
},
"dateAdded": "2025-11-01T00:00:00.000000Z",
"dateUpdated": "2025-11-01T00:00:00.000000Z",
},
{
"id": "2",
"isEnabled": True,
"dataForwarderId": "2",
"project": {
"id": "2",
"slug": "proj-2",
"platform": "python-flask",
},
"overrides": {},
"effectiveConfig": {
"write_key": "itA5bLOPNxccvZ9ON1NYg9EXAMPLEKEY"
},
"dateAdded": "2025-11-01T00:00:00.000000Z",
"dateUpdated": "2025-11-01T00:00:00.000000Z",
},
],
"dateAdded": "2025-11-01T00:00:00.000000Z",
"dateUpdated": "2025-11-01T00:00:00.000000Z",
},
{
"id": "3",
"organizationId": "1",
"isEnabled": True,
"enrollNewProjects": True,
"enrolledProjects": [
{"id": "1", "slug": "proj-1", "platform": "javascript-react"},
],
"provider": "splunk",
"config": {
"index": "main",
"token": "ab13cdef-45aa-1bcd-a123-bcEXAMPLEKEY",
"source": "sentry",
"instance_url": "https://prd-a-abcde.splunkcloud.com:8088",
},
"projectConfigs": [
{
"id": "3",
"isEnabled": True,
"dataForwarderId": "3",
"project": {
"id": "1",
"slug": "proj-1",
"platform": "javascript-react",
},
"overrides": {
"source": "sentry-custom",
},
"effectiveConfig": {
"index": "main",
"token": "ab13cdef-45aa-1bcd-a123-bcEXAMPLEKEY",
"source": "sentry-custom",
"instance_url": "https://prd-a-abcde.splunkcloud.com:8088",
},
"dateAdded": "2025-11-01T00:00:00.000000Z",
"dateUpdated": "2025-11-01T00:00:00.000000Z",
}
],
"dateAdded": "2025-11-01T00:00:00.000000Z",
"dateUpdated": "2025-11-01T00:00:00.000000Z",
},
]
],
status_codes=["200"],
response_only=True,
)
]

SINGLE_DATA_FORWARDER = [
OpenApiExample(
"A data forwarder for an organization",
value={
"id": "1",
"organizationId": "1",
"isEnabled": True,
"enrollNewProjects": True,
"enrolledProjects": [],
"provider": "sqs",
"config": {
"region": "us-east-1",
"queue_url": "https://sqs.us-east-1.amazonaws.com/01234567890/sentry-errors.fifo",
"s3_bucket": "sentry-errors-bucket",
"access_key": "AKIAIOSFODNN7EXAMPLE",
"secret_key": "wJalrXUtnFEMI1K7MDENGSbPxRfiCYEXAMPLEKEY",
"message_group_id": "sentry-errors",
},
"projectConfigs": [],
"dateAdded": "2025-11-01T00:00:00.000000Z",
"dateUpdated": "2025-11-01T00:00:00.000000Z",
},
status_codes=["200"],
response_only=True,
)
]
10 changes: 10 additions & 0 deletions src/sentry/apidocs/parameters.py
Original file line number Diff line number Diff line change
Expand Up @@ -544,6 +544,16 @@ class MetricAlertParams:
)


class DataForwarderParams:
DATA_FORWARDER_ID = OpenApiParameter(
name="data_forwarder_id",
location="path",
required=True,
type=int,
description="The ID of the data forwarder you'd like to query.",
)


class SentryAppParams:
SENTRY_APP_ID_OR_SLUG = OpenApiParameter(
name="sentry_app_id_or_slug",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
from sentry.api.exceptions import ResourceDoesNotExist
from sentry.api.serializers import serialize
from sentry.apidocs.constants import RESPONSE_BAD_REQUEST, RESPONSE_FORBIDDEN, RESPONSE_NO_CONTENT
from sentry.apidocs.parameters import GlobalParams
from sentry.apidocs.examples.integration_examples import IntegrationExamples
from sentry.apidocs.parameters import DataForwarderParams, GlobalParams
from sentry.integrations.api.serializers.models.data_forwarder import (
DataForwarderSerializer as DataForwarderModelSerializer,
)
Expand Down Expand Up @@ -290,13 +291,14 @@ def _update_single_project_configuration(
@method_decorator(never_cache)
@extend_schema(
operation_id="Update a Data Forwarding Configuration for an Organization",
parameters=[GlobalParams.ORG_ID_OR_SLUG],
parameters=[GlobalParams.ORG_ID_OR_SLUG, DataForwarderParams.DATA_FORWARDER_ID],
request=DataForwarderSerializer,
responses={
200: DataForwarderModelSerializer,
400: RESPONSE_BAD_REQUEST,
403: RESPONSE_FORBIDDEN,
},
examples=IntegrationExamples.SINGLE_DATA_FORWARDER,
)
def put(
self, request: Request, organization: Organization, data_forwarder: DataForwarder
Expand Down Expand Up @@ -332,7 +334,7 @@ def put(

@extend_schema(
operation_id="Delete a Data Forwarding Configuration for an Organization",
parameters=[GlobalParams.ORG_ID_OR_SLUG],
parameters=[GlobalParams.ORG_ID_OR_SLUG, DataForwarderParams.DATA_FORWARDER_ID],
responses={
204: RESPONSE_NO_CONTENT,
403: RESPONSE_FORBIDDEN,
Expand Down
11 changes: 8 additions & 3 deletions src/sentry/integrations/api/endpoints/data_forwarding_index.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@
from sentry.api.bases.organization import OrganizationEndpoint, OrganizationPermission
from sentry.api.paginator import OffsetPaginator
from sentry.api.serializers import serialize
from sentry.apidocs.constants import RESPONSE_BAD_REQUEST, RESPONSE_CONFLICT, RESPONSE_FORBIDDEN
from sentry.apidocs.constants import RESPONSE_BAD_REQUEST, RESPONSE_FORBIDDEN
from sentry.apidocs.examples.integration_examples import IntegrationExamples
from sentry.apidocs.parameters import GlobalParams
from sentry.apidocs.utils import inline_sentry_response_serializer
from sentry.integrations.api.serializers.models.data_forwarder import (
DataForwarderSerializer as DataForwarderModelSerializer,
)
Expand Down Expand Up @@ -52,8 +54,11 @@ def convert_args(self, request: Request, *args, **kwargs):
operation_id="Retrieve Data Forwarding Configurations for an Organization",
parameters=[GlobalParams.ORG_ID_OR_SLUG],
responses={
200: DataForwarderModelSerializer,
200: inline_sentry_response_serializer(
"ListDataForwarderResponse", list[DataForwarderSerializer]
)
},
examples=IntegrationExamples.LIST_DATA_FORWARDERS,
)
@set_referrer_policy("strict-origin-when-cross-origin")
@method_decorator(never_cache)
Expand All @@ -75,8 +80,8 @@ def get(self, request: Request, organization) -> Response:
201: DataForwarderModelSerializer,
400: RESPONSE_BAD_REQUEST,
403: RESPONSE_FORBIDDEN,
409: RESPONSE_CONFLICT,
},
examples=IntegrationExamples.SINGLE_DATA_FORWARDER,
)
@set_referrer_policy("strict-origin-when-cross-origin")
@method_decorator(never_cache)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,19 +40,35 @@ class SplunkConfig(TypedDict, total=False):


class DataForwarderSerializer(Serializer):
organization_id = serializers.IntegerField()
is_enabled = serializers.BooleanField(default=True)
enroll_new_projects = serializers.BooleanField(default=False)
organization_id = serializers.IntegerField(
help_text="The ID of the organization related to the data forwarder."
)
is_enabled = serializers.BooleanField(
default=True, help_text="Whether the data forwarder is enabled."
)
enroll_new_projects = serializers.BooleanField(
default=False,
help_text="Whether to enroll new projects automatically, after they're created.",
)
provider = serializers.ChoiceField(
choices=[
(DataForwarderProviderSlug.SEGMENT, "Segment"),
(DataForwarderProviderSlug.SQS, "Amazon SQS"),
(DataForwarderProviderSlug.SPLUNK, "Splunk"),
]
],
help_text='The provider of the data forwarder. One of "segment", "sqs", or "splunk".',
)
config = serializers.DictField(
child=serializers.CharField(allow_blank=True),
default=dict,
help_text="The configuration for the data forwarder.",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there a place people can look for what to put in this field?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good call, I can try just throwing all the data in here but its a bit cumbersome. For now I think it's okay to leave it vague, I included all of the fields with realistic data for examples so hopefully if someone really does wanna do this via API it works alright

)
config = serializers.DictField(child=serializers.CharField(allow_blank=True), default=dict)
project_ids = serializers.ListField(
child=serializers.IntegerField(), allow_empty=True, required=False, default=list
child=serializers.IntegerField(),
allow_empty=True,
required=False,
default=list,
help_text="The IDs of the projects to attach the data forwarder to.",
)

def validate_config(self, config) -> SQSConfig | SegmentConfig | SplunkConfig:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import JsonForm from 'sentry/components/forms/jsonForm';
import FormModel from 'sentry/components/forms/model';
import Panel from 'sentry/components/panels/panel';
import PanelHeader from 'sentry/components/panels/panelHeader';
import {IconRefresh} from 'sentry/icons';
import {IconInfo} from 'sentry/icons/iconInfo';
import {t} from 'sentry/locale';
import type {AvatarProject} from 'sentry/types/project';
Expand Down Expand Up @@ -75,7 +76,20 @@ export function ProjectOverrideForm({
</Flex>
)}
renderFooter={() => (
<Flex justify="end" padding="lg xl">
<Flex justify="between" padding="lg xl">
<Button
size="sm"
icon={<IconRefresh color="danger" transform="scale(-1, 1)" />}
onClick={() => {
updateDataForwarder({
project_id: `${project.id}`,
overrides: {},
is_enabled: projectConfig?.isEnabled ?? false,
});
}}
>
{t('Clear Override')}
</Button>
<Button priority="primary" size="sm" type="submit">
{t('Save Override')}
</Button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ const SQS_GLOBAL_CONFIGURATION_FORM: JsonFormObject = {
label: 'Secret Key',
type: 'text',
required: true,
help: 'Only visible once when the access key is created..',
help: 'Only visible once when the access key is created.',
placeholder: 'e.g. wJalrXUtnFEMI1K7MDENGSbPxRfiCYEXAMPLEKEY',
},
{
Expand Down Expand Up @@ -241,7 +241,7 @@ const SPLUNK_GLOBAL_CONFIGURATION_FORM: JsonFormObject = {
type: 'text',
required: true,
help: 'The token generated for your HTTP Event Collector.',
placeholder: 'e.g. 1234567890abcdef1234567890abcdef',
placeholder: 'e.g. ab13cdef-45aa-1bcd-a123-bcEXAMPLEKEY',
},
{
name: 'index',
Expand Down
Loading