Skip to content

✨ feat(aci): add support for sentry apps for issue alerts in NOA #85218

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Feb 14, 2025
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
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from sentry.models.rule import Rule, RuleSource
from sentry.notifications.models.notificationaction import ActionTarget
from sentry.rules.processing.processor import activate_downstream_actions
from sentry.sentry_apps.services.app import app_service
from sentry.types.rules import RuleFuture
from sentry.utils.registry import Registry
from sentry.utils.safe import safe_execute
Expand All @@ -25,6 +26,7 @@
EmailDataBlob,
EmailFieldMappingKeys,
OnCallDataBlob,
SentryAppDataBlob,
SlackDataBlob,
TicketFieldMappingKeys,
TicketingActionDataBlobHelper,
Expand All @@ -47,7 +49,9 @@ def get_integration_id(action: Action, mapping: ActionFieldMapping) -> dict[str,
raise ValueError(f"No integration id key found for action type: {action.type}")

@classmethod
def get_target_identifier(cls, action: Action, mapping: ActionFieldMapping) -> dict[str, Any]:
def get_target_identifier(
cls, action: Action, mapping: ActionFieldMapping, organization_id: int
) -> dict[str, Any]:
if mapping.get(ActionFieldMappingKeys.TARGET_IDENTIFIER_KEY.value):
if action.target_identifier is None:
raise ValueError(f"No target identifier found for action type: {action.type}")
Expand Down Expand Up @@ -75,6 +79,7 @@ def get_additional_fields(cls, action: Action, mapping: ActionFieldMapping) -> d
def build_rule_action_blob(
cls,
action: Action,
organization_id: int,
) -> dict[str, Any]:
"""Build the base action blob using the standard mapping"""
mapping = ACTION_FIELD_MAPPINGS.get(Action.Type(action.type))
Expand All @@ -83,8 +88,9 @@ def build_rule_action_blob(
blob: dict[str, Any] = {
"id": mapping["id"],
}

blob.update(cls.get_integration_id(action, mapping))
blob.update(cls.get_target_identifier(action, mapping))
blob.update(cls.get_target_identifier(action, mapping, organization_id))
blob.update(cls.get_target_display(action, mapping))
blob.update(cls.get_additional_fields(action, mapping))
return blob
Expand All @@ -108,12 +114,18 @@ def create_rule_instance_from_action(
if workflow and workflow.environment:
environment_id = workflow.environment.id

# TODO(iamrajjoshi): Remove the project null check once https://github.com/getsentry/sentry/pull/85240/files is merged
if detector.project is None:
raise ValueError(f"No project found for action type: {action.type}")

rule = Rule(
id=action.id,
project=detector.project,
environment_id=environment_id,
label=detector.name,
data={"actions": [cls.build_rule_action_blob(action)]},
data={
"actions": [cls.build_rule_action_blob(action, detector.project.organization.id)]
},
status=ObjectStatus.ACTIVE,
source=RuleSource.ISSUE,
)
Expand Down Expand Up @@ -250,7 +262,9 @@ def get_target_display(cls, action: Action, mapping: ActionFieldMapping) -> dict
return {}

@classmethod
def get_target_identifier(cls, action: Action, mapping: ActionFieldMapping) -> dict[str, Any]:
def get_target_identifier(
cls, action: Action, mapping: ActionFieldMapping, organization_id: int
) -> dict[str, Any]:
return {}

@classmethod
Expand Down Expand Up @@ -279,7 +293,9 @@ def get_target_display(cls, action: Action, mapping: ActionFieldMapping) -> dict
return {}

@classmethod
def get_target_identifier(cls, action: Action, mapping: ActionFieldMapping) -> dict[str, Any]:
def get_target_identifier(
cls, action: Action, mapping: ActionFieldMapping, organization_id: int
) -> dict[str, Any]:
# this would be when the target_type is IssueOwners
if action.target_identifier is None:
if action.target_type != ActionTarget.ISSUE_OWNERS.value:
Expand Down Expand Up @@ -321,7 +337,9 @@ def get_integration_id(cls, action: Action, mapping: ActionFieldMapping) -> dict
return {}

@classmethod
def get_target_identifier(cls, action: Action, mapping: ActionFieldMapping) -> dict[str, Any]:
def get_target_identifier(
cls, action: Action, mapping: ActionFieldMapping, organization_id: int
) -> dict[str, Any]:
return {}

@classmethod
Expand All @@ -338,3 +356,56 @@ def get_integration_id(cls, action: Action, mapping: ActionFieldMapping) -> dict
@classmethod
def get_target_display(cls, action: Action, mapping: ActionFieldMapping) -> dict[str, Any]:
return {}


@issue_alert_handler_registry.register(Action.Type.SENTRY_APP)
class SentryAppIssueAlertHandler(BaseIssueAlertHandler):
@classmethod
def get_integration_id(cls, action: Action, mapping: ActionFieldMapping) -> dict[str, Any]:
return {}

@classmethod
def get_target_display(cls, action: Action, mapping: ActionFieldMapping) -> dict[str, Any]:
return {}

@classmethod
def get_target_identifier(
cls, action: Action, mapping: ActionFieldMapping, organization_id: int
) -> dict[str, Any]:
if mapping.get(ActionFieldMappingKeys.TARGET_IDENTIFIER_KEY.value):
if action.target_identifier is None:
raise ValueError(f"No target identifier found for action type: {action.type}")

sentry_app_id = action.target_identifier

sentry_app_installations = app_service.get_many(
filter=dict(app_ids=[sentry_app_id], organization_id=organization_id)
)

if len(sentry_app_installations) != 1:
raise ValueError(
f"Expected 1 sentry app installation for action type: {action.type}, target_identifier: {sentry_app_id}, but got {len(sentry_app_installations)}"
)

sentry_app_installation = sentry_app_installations[0]

if sentry_app_installation is None:
raise ValueError(
f"Sentry app not found for action type: {action.type}, target_identifier: {sentry_app_id}"
)

return {
mapping[
ActionFieldMappingKeys.TARGET_IDENTIFIER_KEY.value
]: sentry_app_installation.uuid
}
raise ValueError(f"No target identifier key found for action type: {action.type}")

@classmethod
def get_additional_fields(cls, action: Action, mapping: ActionFieldMapping) -> dict[str, Any]:
# Need to check for the settings key, if it exists, then we need to return the settings
# It won't exist for legacy webhook actions, but will exist for sentry app actions
if action.data.get("settings"):
blob = SentryAppDataBlob(**action.data)
return {"settings": blob.settings}
return {}
Loading
Loading