Skip to content
Open
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
20 changes: 19 additions & 1 deletion src/sentry/api/serializers/models/rule.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
from sentry.api.serializers import Serializer, register
from sentry.constants import ObjectStatus
from sentry.db.models.manager.base_query_set import BaseQuerySet
from sentry.integrations.services.integration.model import RpcIntegration
from sentry.integrations.services.integration.service import integration_service
from sentry.models.environment import Environment
from sentry.models.project import Project
from sentry.models.rule import NeglectedRule, Rule, RuleActivity, RuleActivityType
Expand Down Expand Up @@ -591,6 +593,22 @@ def get_attrs(self, item_list: Sequence[Workflow], user, **kwargs):
)
actions_by_dcg = self._fetch_actions_by_dcg(all_dcg_ids)

# Batch-fetch integrations for all actions to avoid per-action RPC calls in render_label
all_integration_ids: set[int] = set()
for action_list in actions_by_dcg.values():
for action in action_list:
if action.integration_id is not None:
all_integration_ids.add(action.integration_id)

integration_cache: dict[int, RpcIntegration] = {}
if all_integration_ids and item_list:
integrations = integration_service.get_integrations(
integration_ids=list(all_integration_ids),
organization_id=item_list[0].organization_id,
status=ObjectStatus.ACTIVE,
)
integration_cache = {i.id: i for i in integrations}

last_triggered_lookup: dict[int, datetime] = {}
if "lastTriggered" in self.expand:
last_triggered_lookup = self._fetch_workflow_last_triggered(item_list)
Expand Down Expand Up @@ -667,7 +685,7 @@ def get_attrs(self, item_list: Sequence[Workflow], user, **kwargs):
continue

action_data["name"] = action_to_handler[action].render_label(
workflow.organization_id, action_data
workflow.organization_id, action_data, integration_cache=integration_cache
)
installation_uuid = action_data.get("sentryAppInstallationUuid")
install_context = None
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from sentry.constants import ObjectStatus
from sentry.integrations.services.integration import integration_service
from sentry.integrations.services.integration.model import RpcIntegration
from sentry.notifications.notification_action.registry import issue_alert_handler_registry
from sentry.notifications.notification_action.types import BaseIssueAlertHandler
from sentry.workflow_engine.models import Action
Expand All @@ -20,12 +21,20 @@ def get_target_display(cls, action: Action, mapping: ActionFieldMapping) -> dict
return {}

@classmethod
def render_label(cls, organization_id: int, blob: dict[str, Any]) -> str:
integration = integration_service.get_integration(
integration_id=blob["server"],
organization_id=organization_id,
status=ObjectStatus.ACTIVE,
)
def render_label(
cls,
organization_id: int,
blob: dict[str, Any],
integration_cache: dict[int, RpcIntegration] | None = None,
) -> str:
integration_id = blob["server"]
integration = cls._get_cached_integration(integration_id, integration_cache)
if integration is None:
integration = integration_service.get_integration(
integration_id=integration_id,
organization_id=organization_id,
status=ObjectStatus.ACTIVE,
)
if not integration:
return ""

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,9 @@ def get_additional_fields(cls, action: Action, mapping: ActionFieldMapping) -> d
return final_blob

@classmethod
def render_label(cls, organization_id: int, blob: dict[str, Any]) -> str:
def render_label(
cls, organization_id: int, blob: dict[str, Any], integration_cache: Any = None
) -> str:
target_type = blob["targetType"]
fallthrough_type = blob.get("fallthrough_type", FallthroughChoiceType.ACTIVE_MEMBERS.value)
label = f"Send a notification to {target_type} and if none can be found then send a notification to {fallthrough_type}"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from sentry.constants import ObjectStatus
from sentry.integrations.services.integration import integration_service
from sentry.integrations.services.integration.model import RpcIntegration
from sentry.notifications.notification_action.registry import issue_alert_handler_registry
from sentry.notifications.notification_action.types import BaseIssueAlertHandler
from sentry.workflow_engine.models import Action
Expand All @@ -10,12 +11,20 @@
@issue_alert_handler_registry.register(Action.Type.MSTEAMS)
class MSTeamsIssueAlertHandler(BaseIssueAlertHandler):
@classmethod
def render_label(cls, organization_id: int, blob: dict[str, Any]) -> str:
integration = integration_service.get_integration(
integration_id=blob["team"],
organization_id=organization_id,
status=ObjectStatus.ACTIVE,
)
def render_label(
cls,
organization_id: int,
blob: dict[str, Any],
integration_cache: dict[int, RpcIntegration] | None = None,
) -> str:
integration_id = blob["team"]
integration = cls._get_cached_integration(integration_id, integration_cache)
if integration is None:
integration = integration_service.get_integration(
integration_id=integration_id,
organization_id=organization_id,
status=ObjectStatus.ACTIVE,
)
if not integration:
return ""

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ def get_additional_fields(cls, action: Action, mapping: ActionFieldMapping) -> d
return {"priority": blob.priority}

@classmethod
def render_label(cls, organization_id: int, blob: dict[str, Any]) -> str:
def render_label(
cls, organization_id: int, blob: dict[str, Any], integration_cache: Any = None
) -> str:
result = integration_service.organization_context(
organization_id=organization_id,
integration_id=blob["account"],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ def get_additional_fields(cls, action: Action, mapping: ActionFieldMapping) -> d
return {"severity": blob.priority}

@classmethod
def render_label(cls, organization_id: int, blob: dict[str, Any]) -> str:
def render_label(
cls, organization_id: int, blob: dict[str, Any], integration_cache: Any = None
) -> str:
result = integration_service.organization_context(
organization_id=organization_id,
integration_id=blob["account"],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,5 +23,7 @@ def get_target_display(cls, action: Action, mapping: ActionFieldMapping) -> dict
return {}

@classmethod
def render_label(cls, organization_id: int, blob: dict[str, Any]) -> str:
def render_label(
cls, organization_id: int, blob: dict[str, Any], integration_cache: Any = None
) -> str:
return "Send a notification (for all legacy integrations)"
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,9 @@ def get_additional_fields(cls, action: Action, mapping: ActionFieldMapping) -> d
return {}

@classmethod
def render_label(cls, organization_id: int, blob: dict[str, Any]) -> str:
def render_label(
cls, organization_id: int, blob: dict[str, Any], integration_cache: Any = None
) -> str:
"""
blob: {
'id': 'sentry.rules.actions.notify_event_sentry_app.NotifyEventSentryAppAction',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from sentry.constants import ObjectStatus
from sentry.integrations.services.integration import integration_service
from sentry.integrations.services.integration.model import RpcIntegration
from sentry.notifications.notification_action.registry import issue_alert_handler_registry
from sentry.notifications.notification_action.types import BaseIssueAlertHandler
from sentry.workflow_engine.models import Action
Expand All @@ -19,12 +20,20 @@ def get_additional_fields(cls, action: Action, mapping: ActionFieldMapping) -> d
}

@classmethod
def render_label(cls, organization_id: int, blob: dict[str, Any]) -> str:
integration = integration_service.get_integration(
integration_id=blob["workspace"],
organization_id=organization_id,
status=ObjectStatus.ACTIVE,
)
def render_label(
cls,
organization_id: int,
blob: dict[str, Any],
integration_cache: dict[int, RpcIntegration] | None = None,
) -> str:
integration_id = blob["workspace"]
integration = cls._get_cached_integration(integration_id, integration_cache)
if integration is None:
integration = integration_service.get_integration(
integration_id=integration_id,
organization_id=organization_id,
status=ObjectStatus.ACTIVE,
)
if not integration:
return ""

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,7 @@ def get_target_display(cls, action: Action, mapping: ActionFieldMapping) -> dict
return {}

@classmethod
def render_label(cls, organization_id: int, blob: dict[str, Any]) -> str:
def render_label(
cls, organization_id: int, blob: dict[str, Any], integration_cache: Any = None
) -> str:
return "Send a notification via webhooks"
40 changes: 33 additions & 7 deletions src/sentry/notifications/notification_action/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,8 +156,26 @@ def get_action_mapping(cls, action: Action) -> ActionFieldMapping:
raise ValueError(f"No mapping found for action type: {action.type}")
return mapping

@staticmethod
def _get_cached_integration(
integration_id: Any,
integration_cache: dict[int, RpcIntegration] | None,
) -> RpcIntegration | None:
"""Look up an integration from the pre-fetched cache, safely coercing the ID to int."""
if integration_cache is None or integration_id is None:
return None
try:
return integration_cache.get(int(integration_id))
except (ValueError, TypeError):
return None

@classmethod
def render_label(cls, organization_id: int, blob: dict[str, Any]) -> str:
def render_label(
cls,
organization_id: int,
blob: dict[str, Any],
integration_cache: dict[int, RpcIntegration] | None = None,
) -> str:
return "Send a notification"

@classmethod
Expand Down Expand Up @@ -345,12 +363,20 @@ class TicketingIssueAlertHandler(BaseIssueAlertHandler):
label_template = "Create a ticket in {integration}"

@classmethod
def render_label(cls, organization_id: int, blob: dict[str, Any]) -> str:
integration = integration_service.get_integration(
integration_id=blob.get("integration"),
organization_id=organization_id,
status=ObjectStatus.ACTIVE,
)
def render_label(
cls,
organization_id: int,
blob: dict[str, Any],
integration_cache: dict[int, RpcIntegration] | None = None,
) -> str:
integration_id = blob.get("integration")
integration = cls._get_cached_integration(integration_id, integration_cache)
if integration is None:
integration = integration_service.get_integration(
integration_id=integration_id,
organization_id=organization_id,
status=ObjectStatus.ACTIVE,
)
integration_name = integration.name if integration else "[removed]"
return cls.label_template.format(integration=integration_name)

Expand Down
Loading