Skip to content

Commit ece1db2

Browse files
iamrajjoshiclaude
andcommitted
ref(gitlab): Wrap status sync delete+create in transaction.atomic()
Ensure the IntegrationExternalProject delete-then-recreate sequence in update_organization_config is atomic. Without this, a partial failure during the create loop would leave the org with no external project records and status sync silently broken. Co-Authored-By: Claude <noreply@anthropic.com>
1 parent c0b439e commit ece1db2

File tree

1 file changed

+27
-22
lines changed

1 file changed

+27
-22
lines changed

src/sentry/integrations/gitlab/integration.py

Lines changed: 27 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from urllib.parse import urlparse
77

88
from django import forms
9+
from django.db import transaction
910
from django.http.request import HttpRequest
1011
from django.http.response import HttpResponseBase
1112
from django.urls import reverse
@@ -355,30 +356,34 @@ def update_organization_config(self, data: MutableMapping[str, Any]) -> None:
355356

356357
data["sync_status_forward"] = bool(project_mappings)
357358

358-
IntegrationExternalProject.objects.filter(
359-
organization_integration_id=self.org_integration.id
360-
).delete()
361-
362-
for project_path, statuses in project_mappings.items():
363-
# Validate status values
364-
valid_statuses = {GitLabIssueStatus.OPENED.value, GitLabIssueStatus.CLOSED.value}
365-
if statuses["on_resolve"] not in valid_statuses:
366-
raise IntegrationError(
367-
f"Invalid resolve status: {statuses['on_resolve']}. Must be 'opened' or 'closed'."
368-
)
369-
if statuses["on_unresolve"] not in valid_statuses:
370-
raise IntegrationError(
371-
f"Invalid unresolve status: {statuses['on_unresolve']}. Must be 'opened' or 'closed'."
359+
with transaction.atomic():
360+
IntegrationExternalProject.objects.filter(
361+
organization_integration_id=self.org_integration.id
362+
).delete()
363+
364+
for project_path, statuses in project_mappings.items():
365+
# Validate status values
366+
valid_statuses = {
367+
GitLabIssueStatus.OPENED.value,
368+
GitLabIssueStatus.CLOSED.value,
369+
}
370+
if statuses["on_resolve"] not in valid_statuses:
371+
raise IntegrationError(
372+
f"Invalid resolve status: {statuses['on_resolve']}. Must be 'opened' or 'closed'."
373+
)
374+
if statuses["on_unresolve"] not in valid_statuses:
375+
raise IntegrationError(
376+
f"Invalid unresolve status: {statuses['on_unresolve']}. Must be 'opened' or 'closed'."
377+
)
378+
379+
IntegrationExternalProject.objects.create(
380+
organization_integration_id=self.org_integration.id,
381+
external_id=project_path,
382+
name=project_path,
383+
resolved_status=statuses["on_resolve"],
384+
unresolved_status=statuses["on_unresolve"],
372385
)
373386

374-
IntegrationExternalProject.objects.create(
375-
organization_integration_id=self.org_integration.id,
376-
external_id=project_path,
377-
name=project_path,
378-
resolved_status=statuses["on_resolve"],
379-
unresolved_status=statuses["on_unresolve"],
380-
)
381-
382387
# Check webhook version BEFORE updating config to determine if migration is needed
383388
current_webhook_version = config.get(GITLAB_WEBHOOK_VERSION_KEY, 0)
384389

0 commit comments

Comments
 (0)