Skip to content

Commit 0c90f23

Browse files
authored
Merge branch 'master' into scttcper/fix-team-project-features-dupe
2 parents b29f15d + cf6b081 commit 0c90f23

File tree

39 files changed

+702
-735
lines changed

39 files changed

+702
-735
lines changed

src/sentry/api/serializers/models/rule.py

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -551,12 +551,10 @@ def _fetch_sentry_app_installations_by_uuid(
551551
sentry_app_installations_by_uuid: Mapping[str, RpcSentryAppComponentContext] = {}
552552
if self.prepare_component_fields:
553553
sentry_app_uuids = [
554-
sentry_app_uuid
555-
for sentry_app_uuid in (
556-
action_to_action_data[action].get("sentryAppInstallationUuid")
557-
for action in actions_with_handlers
558-
)
559-
if sentry_app_uuid is not None
554+
action_to_action_data[action]["sentryAppInstallationUuid"]
555+
for action in actions_with_handlers
556+
if action_to_action_data.get(action)
557+
and action_to_action_data[action].get("sentryAppInstallationUuid") is not None
560558
]
561559
install_contexts = app_service.get_component_contexts(
562560
filter={"uuids": sentry_app_uuids, "organization_id": workflow.organization_id},
@@ -640,12 +638,16 @@ def get_attrs(self, item_list: Sequence[Workflow], user, **kwargs):
640638
except Exception:
641639
continue # just keep iterating through the actions in case we have valid ones in there
642640
actions_with_handlers = list(action_to_handler.keys())
643-
action_to_action_data = {
644-
action: action_to_handler[action].build_rule_action_blob(
645-
action, workflow.organization_id
646-
)
647-
for action in actions_with_handlers # skip over actions w/o handlers
648-
}
641+
642+
action_to_action_data = {}
643+
for action in actions_with_handlers:
644+
try:
645+
action_to_action_data[action] = action_to_handler[
646+
action
647+
].build_rule_action_blob(action, workflow.organization_id)
648+
except ValueError:
649+
# if we have a missing sentry app installation but the action is still connected to the sentry app, we skip so we can return the rest of the rule
650+
continue
649651

650652
sentry_app_installations_by_uuid = self._fetch_sentry_app_installations_by_uuid(
651653
workflow, action_to_action_data, actions_with_handlers
@@ -660,7 +662,10 @@ def get_attrs(self, item_list: Sequence[Workflow], user, **kwargs):
660662
}
661663
)
662664
for action in actions_with_handlers:
663-
action_data = action_to_action_data[action]
665+
action_data = action_to_action_data.get(action)
666+
if not action_data:
667+
continue
668+
664669
action_data["name"] = action_to_handler[action].render_label(
665670
workflow.organization_id, action_data
666671
)

src/sentry/audit_log/events.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,3 +384,38 @@ def render(self, audit_log_entry: AuditLogEntry) -> str:
384384
return "updated repository settings for {repository_count} repositories".format(
385385
repository_count=data.get("repository_count", 0),
386386
)
387+
388+
389+
def _render_repo_event(action: str, audit_log_entry: AuditLogEntry) -> str:
390+
data = audit_log_entry.data
391+
actor = audit_log_entry.actor_label or "unknown"
392+
repo_name = data.get("repo_name", "unknown")
393+
source = data.get("source", "")
394+
msg = f"{actor} {action} repository {repo_name}"
395+
if source:
396+
msg += f" (via {source})"
397+
return msg
398+
399+
400+
class RepoAddedAuditLogEvent(AuditLogEvent):
401+
def __init__(self) -> None:
402+
super().__init__(event_id=1161, name="REPO_ADDED", api_name="repo.added")
403+
404+
def render(self, audit_log_entry: AuditLogEntry) -> str:
405+
return _render_repo_event("added", audit_log_entry)
406+
407+
408+
class RepoDisabledAuditLogEvent(AuditLogEvent):
409+
def __init__(self) -> None:
410+
super().__init__(event_id=1162, name="REPO_DISABLED", api_name="repo.disabled")
411+
412+
def render(self, audit_log_entry: AuditLogEntry) -> str:
413+
return _render_repo_event("disabled", audit_log_entry)
414+
415+
416+
class RepoEnabledAuditLogEvent(AuditLogEvent):
417+
def __init__(self) -> None:
418+
super().__init__(event_id=1163, name="REPO_ENABLED", api_name="repo.enabled")
419+
420+
def render(self, audit_log_entry: AuditLogEntry) -> str:
421+
return _render_repo_event("enabled", audit_log_entry)

src/sentry/audit_log/register.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -695,3 +695,6 @@
695695
template="updated autofix automation settings for {project_count} projects",
696696
)
697697
)
698+
default_manager.add(events.RepoAddedAuditLogEvent())
699+
default_manager.add(events.RepoDisabledAuditLogEvent())
700+
default_manager.add(events.RepoEnabledAuditLogEvent())

src/sentry/integrations/bitbucket/webhook.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
from sentry.integrations.source_code_management.webhook import SCMWebhook
2525
from sentry.integrations.types import IntegrationProviderSlug
2626
from sentry.integrations.utils.metrics import IntegrationWebhookEvent, IntegrationWebhookEventType
27+
from sentry.integrations.utils.webhook_viewer_context import webhook_viewer_context
2728
from sentry.models.commit import Commit
2829
from sentry.models.commitauthor import CommitAuthor
2930
from sentry.models.organization import Organization
@@ -258,11 +259,14 @@ def post(self, request: HttpRequest, organization_id: int) -> HttpResponse:
258259

259260
event_handler = handler()
260261

261-
with IntegrationWebhookEvent(
262-
interaction_type=event_handler.event_type,
263-
domain=IntegrationDomain.SOURCE_CODE_MANAGEMENT,
264-
provider_key=event_handler.provider,
265-
).capture():
262+
with (
263+
webhook_viewer_context(organization.id),
264+
IntegrationWebhookEvent(
265+
interaction_type=event_handler.event_type,
266+
domain=IntegrationDomain.SOURCE_CODE_MANAGEMENT,
267+
provider_key=event_handler.provider,
268+
).capture(),
269+
):
266270
event_handler(event, repo=repo, organization=organization)
267271

268272
return HttpResponse(status=204)

src/sentry/integrations/bitbucket_server/webhook.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from sentry.integrations.source_code_management.webhook import SCMWebhook
2222
from sentry.integrations.types import IntegrationProviderSlug
2323
from sentry.integrations.utils.metrics import IntegrationWebhookEvent, IntegrationWebhookEventType
24+
from sentry.integrations.utils.webhook_viewer_context import webhook_viewer_context
2425
from sentry.models.commit import Commit
2526
from sentry.models.commitauthor import CommitAuthor
2627
from sentry.models.organization import Organization
@@ -210,6 +211,7 @@ def post(self, request: HttpRequest, organization_id, integration_id) -> HttpRes
210211

211212
event_handler = handler()
212213

213-
event_handler(event, organization=organization, integration_id=integration_id)
214+
with webhook_viewer_context(organization.id):
215+
event_handler(event, organization=organization, integration_id=integration_id)
214216

215217
return HttpResponse(status=204)

src/sentry/integrations/cursor/webhooks/handler.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from sentry.api.base import Endpoint, cell_silo_endpoint
2222
from sentry.integrations.cursor.integration import CursorAgentIntegration
2323
from sentry.integrations.services.integration import integration_service
24+
from sentry.integrations.utils.webhook_viewer_context import webhook_viewer_context
2425
from sentry.seer.autofix.utils import (
2526
CodingAgentResult,
2627
CodingAgentStatus,
@@ -58,7 +59,8 @@ def post(self, request: Request, organization_id: int) -> Response:
5859
logger.warning("cursor_webhook.invalid_signature")
5960
raise PermissionDenied("Invalid signature")
6061

61-
self._process_webhook(payload)
62+
with webhook_viewer_context(organization_id):
63+
self._process_webhook(payload)
6264
logger.info("cursor_webhook.success", extra={"event_type": event_type})
6365
return self.respond(status=204)
6466

src/sentry/integrations/github/tasks/link_all_repos.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
)
1414
from sentry.organizations.services.organization import organization_service
1515
from sentry.plugins.providers.integration_repository import (
16-
RepoExistsError,
1716
RepositoryInputConfig,
1817
get_integration_repository_provider,
1918
)
@@ -40,7 +39,7 @@ def get_repo_config(repo: Mapping[str, Any], integration_id: int) -> RepositoryI
4039
processing_deadline_duration=60,
4140
silo_mode=SiloMode.CONTROL,
4241
)
43-
@retry(exclude=(RepoExistsError, KeyError))
42+
@retry(exclude=(KeyError,))
4443
def link_all_repos(
4544
integration_key: str,
4645
integration_id: int,
@@ -88,14 +87,15 @@ def link_all_repos(
8887
missing_repos.append(repo)
8988
continue
9089

91-
try:
90+
_created_repos, _reactivated_repos, existing_repos = (
9291
integration_repo_provider.create_repositories(
9392
configs=repo_configs, organization=rpc_org
9493
)
95-
except RepoExistsError as e:
94+
)
95+
if existing_repos:
9696
lifecycle.record_halt(
9797
str(LinkAllReposHaltReason.REPOSITORY_NOT_CREATED),
98-
{"missing_repos": e.repos, "integration_id": integration_id},
98+
{"missing_repos": existing_repos, "integration_id": integration_id},
9999
)
100100

101101
if missing_repos:

src/sentry/integrations/github/tasks/sync_repos.py

Lines changed: 42 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,9 @@
2121
SCMIntegrationInteractionEvent,
2222
SCMIntegrationInteractionType,
2323
)
24+
from sentry.integrations.source_code_management.repo_audit import log_repo_change
2425
from sentry.organizations.services.organization import organization_service
25-
from sentry.plugins.providers.integration_repository import (
26-
RepoExistsError,
27-
get_integration_repository_provider,
28-
)
26+
from sentry.plugins.providers.integration_repository import get_integration_repository_provider
2927
from sentry.shared_integrations.exceptions import ApiError
3028
from sentry.silo.base import SiloMode
3129
from sentry.tasks.base import instrumented_task, retry
@@ -171,6 +169,8 @@ def sync_repos_for_org(organization_integration_id: int) -> None:
171169
if dry_run:
172170
return
173171

172+
repo_by_external_id = {r.external_id: r for r in active_repos + disabled_repos}
173+
174174
if new_ids:
175175
integration_repo_provider = get_integration_repository_provider(integration)
176176
repo_configs = [
@@ -179,12 +179,27 @@ def sync_repos_for_org(organization_integration_id: int) -> None:
179179
if str(repo["id"]) in new_ids
180180
]
181181
if repo_configs:
182-
try:
183-
integration_repo_provider.create_repositories(
184-
configs=repo_configs, organization=rpc_org
182+
created_repos, reactivated_repos, _ = integration_repo_provider.create_repositories(
183+
configs=repo_configs, organization=rpc_org
184+
)
185+
186+
for repo in created_repos:
187+
log_repo_change(
188+
event_name="REPO_ADDED",
189+
organization_id=organization_id,
190+
repo=repo,
191+
source="repository sync",
192+
provider=integration.provider,
193+
)
194+
195+
for repo in reactivated_repos:
196+
log_repo_change(
197+
event_name="REPO_ENABLED",
198+
organization_id=organization_id,
199+
repo=repo,
200+
source="repository sync",
201+
provider=integration.provider,
185202
)
186-
except RepoExistsError:
187-
pass
188203

189204
if removed_ids:
190205
repository_service.disable_repositories_by_external_ids(
@@ -194,13 +209,31 @@ def sync_repos_for_org(organization_integration_id: int) -> None:
194209
external_ids=list(removed_ids),
195210
)
196211

212+
for eid in removed_ids:
213+
removed_repo = repo_by_external_id.get(eid)
214+
if removed_repo:
215+
log_repo_change(
216+
event_name="REPO_DISABLED",
217+
organization_id=organization_id,
218+
repo=removed_repo,
219+
source="automatic SCM syncing",
220+
provider=integration.provider,
221+
)
222+
197223
if restored_ids:
198224
for repo in disabled_repos:
199225
if repo.external_id in restored_ids:
200226
repo.status = ObjectStatus.ACTIVE
201227
repository_service.update_repository(
202228
organization_id=organization_id, update=repo
203229
)
230+
log_repo_change(
231+
event_name="REPO_ENABLED",
232+
organization_id=organization_id,
233+
repo=repo,
234+
source="automatic SCM syncing",
235+
provider=integration.provider,
236+
)
204237

205238

206239
@instrumented_task(

src/sentry/integrations/github/tasks/sync_repos_on_install_change.py

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@
1313
SCMIntegrationInteractionEvent,
1414
SCMIntegrationInteractionType,
1515
)
16+
from sentry.integrations.source_code_management.repo_audit import log_repo_change
1617
from sentry.organizations.services.organization import organization_service
1718
from sentry.organizations.services.organization.model import RpcOrganization
1819
from sentry.plugins.providers.integration_repository import (
19-
RepoExistsError,
2020
RepositoryInputConfig,
2121
get_integration_repository_provider,
2222
)
@@ -36,7 +36,7 @@
3636
processing_deadline_duration=120,
3737
silo_mode=SiloMode.CONTROL,
3838
)
39-
@retry(exclude=(RepoExistsError, KeyError))
39+
@retry(exclude=(KeyError,))
4040
def sync_repos_on_install_change(
4141
integration_id: int,
4242
action: str,
@@ -119,18 +119,59 @@ def _sync_repos_for_org(
119119
continue
120120

121121
if repo_configs:
122-
try:
122+
created_repos, reactivated_repos, _missing_repos = (
123123
integration_repo_provider.create_repositories(
124124
configs=repo_configs, organization=rpc_org
125125
)
126-
except RepoExistsError:
127-
pass
126+
)
127+
128+
for created_repo in created_repos:
129+
log_repo_change(
130+
event_name="REPO_ADDED",
131+
organization_id=rpc_org.id,
132+
repo=created_repo,
133+
source="GitHub webhook",
134+
provider=integration.provider,
135+
)
136+
137+
for reactivated_repo in reactivated_repos:
138+
log_repo_change(
139+
event_name="REPO_ENABLED",
140+
organization_id=rpc_org.id,
141+
repo=reactivated_repo,
142+
source="GitHub webhook",
143+
provider=integration.provider,
144+
)
128145

129146
if repos_removed:
147+
# Look up repos before disabling to get their IDs and names
130148
external_ids = [str(repo["id"]) for repo in repos_removed]
149+
existing_repos = repository_service.get_repositories(
150+
organization_id=rpc_org.id,
151+
integration_id=integration.id,
152+
providers=[provider],
153+
)
154+
repo_by_eid = {
155+
r.external_id: r
156+
for r in existing_repos
157+
if r.external_id and r.status == ObjectStatus.ACTIVE
158+
}
159+
131160
repository_service.disable_repositories_by_external_ids(
132161
organization_id=rpc_org.id,
133162
integration_id=integration.id,
134163
provider=provider,
135164
external_ids=external_ids,
136165
)
166+
167+
for repo in repos_removed:
168+
eid = str(repo["id"])
169+
sentry_repo = repo_by_eid.get(eid)
170+
if sentry_repo:
171+
log_repo_change(
172+
event_name="REPO_DISABLED",
173+
organization_id=rpc_org.id,
174+
repo=sentry_repo,
175+
source="GitHub webhook",
176+
provider=integration.provider,
177+
)

0 commit comments

Comments
 (0)