Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
49979b7
Add constants and default options
alexsohn1126 Dec 18, 2025
a1f26a6
add initial files
alexsohn1126 Dec 19, 2025
a9a6009
Merge branch 'master' into alexsohn/feat/add-console-sdk-invite-const…
alexsohn1126 Dec 19, 2025
017849b
Merge branch 'alexsohn/feat/add-console-sdk-invite-constants-and-opti…
alexsohn1126 Dec 19, 2025
70bb748
Update constants.py
alexsohn1126 Jan 2, 2026
a68ac5f
changed defaults and added defaults for testing
alexsohn1126 Jan 2, 2026
6b4a35a
Merge branch 'master' into alexsohn/feat/add-console-sdk-invite-const…
alexsohn1126 Jan 2, 2026
2416b4b
Merge branch 'alexsohn/feat/add-console-sdk-invite-constants-and-opti…
alexsohn1126 Jan 2, 2026
19e7649
remove console-sdk-app name option
alexsohn1126 Jan 2, 2026
852912d
Merge branch 'alexsohn/feat/add-console-sdk-invite-constants-and-opti…
alexsohn1126 Jan 2, 2026
f2cd3d6
change ordering and flags for defaults
alexsohn1126 Jan 2, 2026
9b67109
Update src/sentry/constants.py
alexsohn1126 Jan 5, 2026
9f01568
delete object deletion code in outbox processing
alexsohn1126 Jan 5, 2026
d30be33
send identity update outbox to all regions
alexsohn1126 Jan 5, 2026
e6ce7c3
add api update for consolesdkinvitequota
alexsohn1126 Jan 5, 2026
a946acf
change quota limit to use default constant
alexsohn1126 Jan 5, 2026
338f24a
Merge branch 'alexsohn/feat/add-console-sdk-invite-constants-and-opti…
alexsohn1126 Jan 5, 2026
48897a5
Merge branch 'master' into alexsohn/feat/add-outbox-generation-for-Id…
alexsohn1126 Jan 8, 2026
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
2 changes: 2 additions & 0 deletions src/sentry/hybridcloud/outbox/category.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ class OutboxCategory(IntEnum):
SERVICE_HOOK_UPDATE = 40
SENTRY_APP_DELETE = 41
SENTRY_APP_INSTALLATION_DELETE = 42
IDENTITY_UPDATE = 43

@classmethod
def as_choices(cls) -> Sequence[tuple[int, int]]:
Expand Down Expand Up @@ -282,6 +283,7 @@ class OutboxScope(IntEnum):
OutboxCategory.UNUSED_TWO,
OutboxCategory.UNUSUED_THREE,
OutboxCategory.AUTH_IDENTITY_UPDATE,
OutboxCategory.IDENTITY_UPDATE,
},
)
# Webhook scope is no longer in use
Expand Down
6 changes: 6 additions & 0 deletions src/sentry/receivers/outbox/control.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
from sentry.sentry_apps.models.sentry_app_installation import SentryAppInstallation
from sentry.sentry_apps.services.hook.service import hook_service
from sentry.sentry_apps.tasks.sentry_apps import clear_region_cache
from sentry.users.models.identity import Identity
from sentry.workflow_engine.service.action.service import action_service

logger = logging.getLogger(__name__)
Expand All @@ -42,6 +43,11 @@ def process_integration_updates(object_identifier: int, region_name: str, **kwds
integration # Currently we do not sync any other integration changes, but if we did, you can use this variable.


@receiver(process_control_outbox, sender=OutboxCategory.IDENTITY_UPDATE)
def process_identity_updates(object_identifier: int, region_name: str, **kwds: Any):
maybe_process_tombstone(Identity, object_identifier, region_name=region_name)


@receiver(process_control_outbox, sender=OutboxCategory.SENTRY_APP_UPDATE)
def process_sentry_app_updates(object_identifier: int, region_name: str, **kwds: Any):
if (
Expand Down
19 changes: 18 additions & 1 deletion src/sentry/users/models/identity.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

from django.conf import settings
from django.contrib.postgres.fields.array import ArrayField
from django.db import IntegrityError, models
from django.db import IntegrityError, models, router, transaction
from django.db.models import Q, QuerySet
from django.utils import timezone

Expand All @@ -19,7 +19,10 @@
control_silo_model,
)
from sentry.db.models.manager.base import BaseManager
from sentry.hybridcloud.models.outbox import ControlOutbox, outbox_context
from sentry.hybridcloud.outbox.category import OutboxCategory, OutboxScope
from sentry.integrations.types import ExternalProviders, IntegrationProviderSlug
from sentry.types.region import find_all_region_names
from sentry.users.services.user import RpcUser

if TYPE_CHECKING:
Expand Down Expand Up @@ -212,6 +215,20 @@ class Meta:
db_table = "sentry_identity"
unique_together = (("idp", "external_id"), ("idp", "user"))

def delete(self, *args: Any, **kwargs: Any) -> tuple[int, dict[str, int]]:
with outbox_context(transaction.atomic(router.db_for_write(Identity))):
# Fan out to all regions to ensure HybridCloudForeignKey cascade works even without org memberships
region_names = find_all_region_names()
for region_name in region_names:
ControlOutbox(
shard_scope=OutboxScope.USER_SCOPE,
shard_identifier=self.user_id,
object_identifier=self.id,
category=OutboxCategory.IDENTITY_UPDATE,
region_name=region_name,
).save()
return super().delete(*args, **kwargs)
Comment thread
alexsohn1126 marked this conversation as resolved.
Resolved

def get_provider(self) -> Provider:
from sentry.identity import get

Expand Down
9 changes: 9 additions & 0 deletions tests/sentry/receivers/outbox/test_control.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@
from sentry.models.apiapplication import ApiApplication
from sentry.receivers.outbox.control import (
process_api_application_updates,
process_identity_updates,
process_integration_updates,
process_sentry_app_updates,
)
from sentry.testutils.cases import TestCase
from sentry.testutils.silo import control_silo_test
from sentry.types.region import Region, RegionCategory
from sentry.users.models.identity import Identity

_TEST_REGION = Region("eu", 1, "http://eu.testserver", RegionCategory.MULTI_TENANT)

Expand All @@ -27,6 +29,13 @@ def test_process_integration_updates(self, mock_maybe_process: MagicMock) -> Non
Integration, self.identifier, region_name=_TEST_REGION.name
)

@patch("sentry.receivers.outbox.control.maybe_process_tombstone")
def test_process_identity_updates(self, mock_maybe_process: MagicMock) -> None:
process_identity_updates(object_identifier=self.identifier, region_name=_TEST_REGION.name)
mock_maybe_process.assert_called_with(
Identity, self.identifier, region_name=_TEST_REGION.name
)

@patch("sentry.receivers.outbox.control.maybe_process_tombstone")
def test_process_api_application_updates(self, mock_maybe_process: MagicMock) -> None:
process_api_application_updates(
Expand Down
Loading