def test_invalid_team(self): trigger = AlertRuleTriggerAction( target_type=AlertRuleTriggerAction.TargetType.TEAM.value, target_identifier="10000000") assert trigger.target is None
def test_user(self): trigger = AlertRuleTriggerAction( target_type=AlertRuleTriggerAction.TargetType.USER.value, target_identifier=six.text_type(self.user.id), ) assert trigger.target == self.user
def test_team(self): trigger = AlertRuleTriggerAction( target_type=AlertRuleTriggerAction.TargetType.TEAM.value, target_identifier=six.text_type(self.team.id), ) assert trigger.target == self.team
AlertRule, AlertRuleThresholdType, AlertRuleTrigger, AlertRuleTriggerAction, ) from sentry.models.organizationmember import OrganizationMember from sentry.models.team import Team from sentry.models.user import User from sentry.snuba.models import QueryAggregations from sentry.snuba.subscriptions import aggregation_function_translations from sentry.utils.compat import zip string_to_action_type = { registration.slug: registration.type for registration in AlertRuleTriggerAction.get_registered_types() } action_target_type_to_string = { AlertRuleTriggerAction.TargetType.USER: "******", AlertRuleTriggerAction.TargetType.TEAM: "team", AlertRuleTriggerAction.TargetType.SPECIFIC: "specific", } string_to_action_target_type = {v: k for (k, v) in action_target_type_to_string.items()} CRITICAL_TRIGGER_LABEL = "critical" WARNING_TRIGGER_LABEL = "warning" class AlertRuleTriggerActionSerializer(CamelSnakeModelSerializer): """ Serializer for creating/updating a trigger action. Required context:
def test_remove_unchanged_fields(self): type = AlertRuleTriggerAction.Type.EMAIL target_type = AlertRuleTriggerAction.TargetType.USER identifier = six.text_type(self.user.id) action = create_alert_rule_trigger_action(self.trigger, type, target_type, identifier) self._run_changed_fields_test( action, { "type": AlertRuleTriggerAction.get_registered_type(type).slug, "target_type": action_target_type_to_string[target_type], "target_identifier": identifier, }, {}, ) integration = Integration.objects.create(external_id="1", provider="slack", metadata={}) self._run_changed_fields_test( action, { "type": AlertRuleTriggerAction.get_registered_type( AlertRuleTriggerAction.Type.SLACK).slug, "targetIdentifier": identifier, "targetType": action_target_type_to_string[ AlertRuleTriggerAction.TargetType.SPECIFIC], "integration": integration.id, }, { "type": AlertRuleTriggerAction.Type.SLACK, "integration": integration, "target_identifier": identifier, "target_type": AlertRuleTriggerAction.TargetType.SPECIFIC, }, ) new_team = self.create_team(self.organization) self._run_changed_fields_test( action, { "type": AlertRuleTriggerAction.get_registered_type(type).slug, "target_type": action_target_type_to_string[ AlertRuleTriggerAction.TargetType.TEAM], "target_identifier": six.text_type(new_team.id), }, { "type": type, "target_type": AlertRuleTriggerAction.TargetType.TEAM, "target_identifier": six.text_type(new_team.id), }, )
def test_no_handler(self): trigger = AlertRuleTriggerAction(type=AlertRuleTriggerAction.Type.EMAIL.value) assert trigger.fire(Mock(), Mock(), Mock(), 123) is None
def test_unhandled(self): trigger = AlertRuleTriggerAction(type=AlertRuleTriggerAction.Type.EMAIL.value) trigger.build_handler(Mock(), Mock(), Mock()) self.metrics.incr.assert_called_once_with("alert_rule_trigger.unhandled_type.0")
def test_specific(self): email = "*****@*****.**" trigger = AlertRuleTriggerAction( target_type=AlertRuleTriggerAction.TargetType.SPECIFIC.value, target_identifier=email ) assert trigger.target == email
def test_create_and_update_sentry_app_action_success(self): responses.add( method=responses.POST, url="https://example.com/sentry/alert-rule", status=200, json={}, ) serializer = AlertRuleTriggerActionSerializer( context=self.context, data={ "type": AlertRuleTriggerAction.get_registered_type( AlertRuleTriggerAction.Type.SENTRY_APP).slug, "target_type": action_target_type_to_string[ AlertRuleTriggerAction.TargetType.SENTRY_APP], "target_identifier": "1", "sentry_app": self.sentry_app.id, "sentry_app_config": { "channel": "#general" }, "sentry_app_installation_uuid": self.sentry_app_installation.uuid, }, ) assert serializer.is_valid() # Create action serializer.save() # # Make sure the action was created. alert_rule_trigger_actions = list( AlertRuleTriggerAction.objects.filter(sentry_app=self.sentry_app)) assert len(alert_rule_trigger_actions) == 1 # Update action serializer = AlertRuleTriggerActionSerializer( context=self.context, data={ "type": AlertRuleTriggerAction.get_registered_type( AlertRuleTriggerAction.Type.SENTRY_APP).slug, "target_type": action_target_type_to_string[ AlertRuleTriggerAction.TargetType.SENTRY_APP], "target_identifier": "1", "sentry_app": self.sentry_app.id, "sentry_app_config": { "channel": "#announcements" }, "sentry_app_installation_uuid": self.sentry_app_installation.uuid, }, instance=alert_rule_trigger_actions[0], ) assert serializer.is_valid() # Update action serializer.save() alert_rule_trigger_action = AlertRuleTriggerAction.objects.get( sentry_app=self.sentry_app) # Make sure the changes got applied assert alert_rule_trigger_action.sentry_app_config == { "channel": "#announcements" }
def validate(self, attrs): if ("type" in attrs) != ("target_type" in attrs) != ("target_identifier" in attrs): raise serializers.ValidationError( "type, targetType and targetIdentifier must be passed together" ) type = attrs.get("type") target_type = attrs.get("target_type") access = self.context["access"] identifier = attrs.get("target_identifier") if type is not None: type_info = AlertRuleTriggerAction.get_registered_type(type) if target_type not in type_info.supported_target_types: allowed_target_types = ",".join( ACTION_TARGET_TYPE_TO_STRING[type_name] for type_name in type_info.supported_target_types) raise serializers.ValidationError({ "target_type": "Invalid target type for %s. Valid types are [%s]" % (type_info.slug, allowed_target_types) }) if attrs.get("type") == AlertRuleTriggerAction.Type.EMAIL: if target_type == AlertRuleTriggerAction.TargetType.TEAM: try: team = Team.objects.get(id=identifier) except Team.DoesNotExist: raise serializers.ValidationError("Team does not exist") if not access.has_team(team): raise serializers.ValidationError("Team does not exist") elif target_type == AlertRuleTriggerAction.TargetType.USER: try: user = User.objects.get(id=identifier) except User.DoesNotExist: raise serializers.ValidationError("User does not exist") if not OrganizationMember.objects.filter( organization=self.context["organization"], user=user).exists(): raise serializers.ValidationError( "User does not belong to this organization") elif attrs.get("type") == AlertRuleTriggerAction.Type.SLACK: if not attrs.get("integration"): raise serializers.ValidationError( {"integration": "Integration must be provided for slack"}) elif attrs.get("type") == AlertRuleTriggerAction.Type.SENTRY_APP: if not attrs.get("sentry_app"): raise serializers.ValidationError({ "sentry_app": "SentryApp must be provided for sentry_app" }) if attrs.get("sentry_app_config"): if attrs.get("sentry_app_installation_uuid") is None: raise serializers.ValidationError({ "sentry_app": "Missing parameter: sentry_app_installation_uuid" }) try: install = SentryAppInstallation.objects.get( uuid=attrs.get("sentry_app_installation_uuid")) except SentryAppInstallation.DoesNotExist: raise serializers.ValidationError( {"sentry_app": "The installation does not exist."}) # Check response from creator and bubble up errors from providers as a ValidationError result = alert_rule_actions.AlertRuleActionCreator.run( install=install, fields=attrs.get("sentry_app_config"), ) if not result["success"]: raise serializers.ValidationError( {"sentry_app": result["message"]}) del attrs["sentry_app_installation_uuid"] attrs["use_async_lookup"] = self.context.get("use_async_lookup") attrs["input_channel_id"] = self.context.get("input_channel_id") should_validate_channel_id = self.context.get("validate_channel_id", True) # validate_channel_id is assumed to be true unless explicitly passed as false if attrs["input_channel_id"] and should_validate_channel_id: validate_channel_id(identifier, attrs["integration"].id, attrs["input_channel_id"]) return attrs
class OrganizationAlertRuleAvailableActionIndexEndpointTest(APITestCase): endpoint = "sentry-api-0-organization-alert-rule-available-actions" email = AlertRuleTriggerAction.get_registered_type( AlertRuleTriggerAction.Type.EMAIL) slack = AlertRuleTriggerAction.get_registered_type( AlertRuleTriggerAction.Type.SLACK) sentry_app = AlertRuleTriggerAction.get_registered_type( AlertRuleTriggerAction.Type.SENTRY_APP) pagerduty = AlertRuleTriggerAction.get_registered_type( AlertRuleTriggerAction.Type.PAGERDUTY) def setUp(self): super().setUp() self.login_as(self.user) def install_new_sentry_app(self, name, **kwargs): kwargs.update(name=name, organization=self.organization, is_alertable=True, verify_install=False) sentry_app = self.create_sentry_app(**kwargs) installation = self.create_sentry_app_installation( slug=sentry_app.slug, organization=self.organization, user=self.user) return installation def test_build_action_response_email(self): data = build_action_response(self.email) assert data["type"] == "email" assert sorted(data["allowedTargetTypes"]) == ["team", "user"] def test_build_action_response_slack(self): data = build_action_response(self.slack) assert data["type"] == "slack" assert data["allowedTargetTypes"] == ["specific"] def test_build_action_response_pagerduty(self): service_name = SERVICES[0]["service_name"] integration = Integration.objects.create( provider="pagerduty", name="Example PagerDuty", external_id="example-pagerduty", metadata={"services": SERVICES}, ) integration.add_organization(self.organization, self.user) service = PagerDutyService.objects.create( service_name=service_name, integration_key=SERVICES[0]["integration_key"], organization_integration=integration.organizationintegration_set. first(), ) data = build_action_response(self.pagerduty, integration=integration, organization=self.organization) assert data["type"] == "pagerduty" assert data["allowedTargetTypes"] == ["specific"] assert data["options"] == [{ "value": service.id, "label": service_name }] def test_build_action_response_sentry_app(self): installation = self.install_new_sentry_app("foo") data = build_action_response(self.sentry_app, sentry_app_installation=installation) assert data["type"] == "sentry_app" assert data["allowedTargetTypes"] == ["sentry_app"] assert data["status"] == SentryAppStatus.UNPUBLISHED_STR def test_no_integrations(self): with self.feature("organizations:incidents"): response = self.get_success_response(self.organization.slug) assert response.data == [build_action_response(self.email)] def test_simple(self): integration = Integration.objects.create(external_id="1", provider="slack") integration.add_organization(self.organization) with self.feature("organizations:incidents"): response = self.get_success_response(self.organization.slug) assert len(response.data) == 2 assert build_action_response(self.email) in response.data assert (build_action_response(self.slack, integration=integration, organization=self.organization) in response.data) def test_duplicate_integrations(self): integration = Integration.objects.create(external_id="1", provider="slack", name="slack 1") integration.add_organization(self.organization) other_integration = Integration.objects.create(external_id="2", provider="slack", name="slack 2") other_integration.add_organization(self.organization) with self.feature("organizations:incidents"): response = self.get_success_response(self.organization.slug) assert len(response.data) == 3 assert build_action_response(self.email) in response.data assert (build_action_response(self.slack, integration=integration, organization=self.organization) in response.data) assert (build_action_response(self.slack, integration=other_integration, organization=self.organization) in response.data) def test_no_feature(self): self.create_team(organization=self.organization, members=[self.user]) self.get_error_response(self.organization.slug, status_code=404) def test_sentry_apps(self): installation = self.install_new_sentry_app("foo") with self.feature("organizations:incidents"): response = self.get_success_response(self.organization.slug) assert len(response.data) == 2 assert build_action_response(self.email) in response.data assert (build_action_response(self.sentry_app, sentry_app_installation=installation) in response.data) def test_published_sentry_apps(self): # Should show up in available actions. installation = self.install_new_sentry_app("published", published=True) with self.feature("organizations:incidents"): response = self.get_success_response(self.organization.slug) assert len(response.data) == 2 assert (build_action_response(self.sentry_app, sentry_app_installation=installation) in response.data) def test_no_ticket_actions(self): integration = Integration.objects.create(external_id="1", provider="jira") integration.add_organization(self.organization) with self.feature([ "organizations:incidents", "organizations:integrations-ticket-rules" ]): response = self.get_success_response(self.organization.slug) # There should be no ticket actions for Metric Alerts. assert len(response.data) == 1 assert build_action_response(self.email) in response.data def test_integration_disabled(self): integration = Integration.objects.create(external_id="1", provider="slack", status=ObjectStatus.DISABLED) integration.add_organization(self.organization) with self.feature("organizations:incidents"): response = self.get_success_response(self.organization.slug) assert len(response.data) == 1 assert build_action_response(self.email) in response.data def test_org_integration_disabled(self): integration = Integration.objects.create(external_id="1", provider="slack") org_integration = integration.add_organization(self.organization) org_integration.update(status=ObjectStatus.DISABLED) with self.feature("organizations:incidents"): response = self.get_success_response(self.organization.slug) assert len(response.data) == 1 assert build_action_response(self.email) in response.data
def validate(self, attrs): if ("type" in attrs) != ("target_type" in attrs) != ("target_identifier" in attrs): raise serializers.ValidationError( "type, targetType and targetIdentifier must be passed together" ) type = attrs.get("type") target_type = attrs.get("target_type") access = self.context["access"] identifier = attrs.get("target_identifier") if type is not None: type_info = AlertRuleTriggerAction.get_registered_type(type) if target_type not in type_info.supported_target_types: allowed_target_types = ",".join([ action_target_type_to_string[type_name] for type_name in type_info.supported_target_types ]) raise serializers.ValidationError({ "target_type": "Invalid target type for %s. Valid types are [%s]" % (type_info.slug, allowed_target_types) }) if attrs.get("type") == AlertRuleTriggerAction.Type.EMAIL: if target_type == AlertRuleTriggerAction.TargetType.TEAM: try: team = Team.objects.get(id=identifier) except Team.DoesNotExist: raise serializers.ValidationError("Team does not exist") if not access.has_team(team): raise serializers.ValidationError("Team does not exist") elif target_type == AlertRuleTriggerAction.TargetType.USER: try: user = User.objects.get(id=identifier) except User.DoesNotExist: raise serializers.ValidationError("User does not exist") if not OrganizationMember.objects.filter( organization=self.context["organization"], user=user).exists(): raise serializers.ValidationError( "User does not belong to this organization") elif attrs.get("type") == AlertRuleTriggerAction.Type.SLACK: if not attrs.get("integration"): raise serializers.ValidationError( {"integration": "Integration must be provided for slack"}) elif attrs.get("type") == AlertRuleTriggerAction.Type.SENTRY_APP: if not attrs.get("sentry_app"): raise serializers.ValidationError({ "sentry_app": "SentryApp must be provided for sentry_app" }) attrs["use_async_lookup"] = self.context.get("use_async_lookup") attrs["input_channel_id"] = self.context.get("input_channel_id") should_validate_channel_id = self.context.get("validate_channel_id", True) # validate_channel_id is assumed to be true unless explicitly passed as false if attrs["input_channel_id"] and should_validate_channel_id: validate_channel_id(identifier, attrs["integration"].id, attrs["input_channel_id"]) return attrs