Co-authored-by: Eve832 <eve.meelan@grafana.com>
Co-authored-by: Francisco Montes de Oca <nevermind89x@gmail.com>
Co-authored-by: Ildar Iskhakov <ildar.iskhakov@grafana.com>
Co-authored-by: Innokentii Konstantinov <innokenty.konstantinov@grafana.com>
Co-authored-by: Julia <ferril.darkdiver@gmail.com>
Co-authored-by: maskin25 <kengurek@gmail.com>
Co-authored-by: Matias Bordese <mbordese@gmail.com>
Co-authored-by: Matvey Kukuy <motakuk@gmail.com>
Co-authored-by: Michael Derynck <michael.derynck@grafana.com>
Co-authored-by: Richard Hartmann <richih@richih.org>
Co-authored-by: Robby Milo <robbymilo@fastmail.com>
Co-authored-by: Timur Olzhabayev <timur.olzhabayev@grafana.com>
Co-authored-by: Vadim Stepanov <vadimkerr@gmail.com>
Co-authored-by: Yulia Shanyrova <yulia.shanyrova@grafana.com>
191 lines
8.5 KiB
Python
191 lines
8.5 KiB
Python
from django.core.exceptions import ObjectDoesNotExist
|
|
from jinja2 import TemplateSyntaxError
|
|
from rest_framework import fields, serializers
|
|
|
|
from apps.alerts.grafana_alerting_sync_manager.grafana_alerting_sync import GrafanaAlertingSyncManager
|
|
from apps.alerts.models import AlertReceiveChannel
|
|
from apps.public_api.constants import DEMO_INTEGRATION_LINK_TOKEN
|
|
from apps.public_api.helpers import is_demo_token_request
|
|
from common.api_helpers.custom_fields import TeamPrimaryKeyRelatedField
|
|
from common.api_helpers.exceptions import BadRequest
|
|
from common.api_helpers.mixins import EagerLoadingMixin
|
|
from common.jinja_templater import jinja_template_env
|
|
from common.utils import timed_lru_cache
|
|
|
|
from .integtration_heartbeat import IntegrationHeartBeatSerializer
|
|
from .maintenance import MaintainableObjectSerializerMixin
|
|
from .routes import DefaultChannelFilterSerializer
|
|
|
|
|
|
class IntegrationTypeField(fields.CharField):
|
|
def to_representation(self, value):
|
|
return AlertReceiveChannel.INTEGRATIONS_TO_REVERSE_URL_MAP[value]
|
|
|
|
def to_internal_value(self, data):
|
|
try:
|
|
integration_type = [
|
|
key for key, value in AlertReceiveChannel.INTEGRATIONS_TO_REVERSE_URL_MAP.items() if value == data
|
|
][0]
|
|
except IndexError:
|
|
raise BadRequest(detail="Invalid integration type")
|
|
return integration_type
|
|
|
|
|
|
class IntegrationSerializer(EagerLoadingMixin, serializers.ModelSerializer, MaintainableObjectSerializerMixin):
|
|
id = serializers.CharField(read_only=True, source="public_primary_key")
|
|
name = serializers.CharField(required=False, source="verbal_name")
|
|
team_id = TeamPrimaryKeyRelatedField(required=False, allow_null=True, source="team")
|
|
link = serializers.ReadOnlyField(source="integration_url")
|
|
type = IntegrationTypeField(source="integration")
|
|
templates = serializers.DictField(required=False)
|
|
default_route = serializers.DictField(required=False)
|
|
heartbeat = serializers.SerializerMethodField()
|
|
|
|
PREFETCH_RELATED = ["channel_filters"]
|
|
SELECT_RELATED = ["organization", "integration_heartbeat"]
|
|
|
|
class Meta:
|
|
model = AlertReceiveChannel
|
|
fields = MaintainableObjectSerializerMixin.Meta.fields + [
|
|
"id",
|
|
"name",
|
|
"team_id",
|
|
"link",
|
|
"type",
|
|
"default_route",
|
|
"templates",
|
|
"heartbeat",
|
|
]
|
|
|
|
def to_representation(self, instance):
|
|
result = super().to_representation(instance)
|
|
default_route = self._get_default_route_iterative(instance)
|
|
serializer = DefaultChannelFilterSerializer(default_route, context=self.context)
|
|
result["default_route"] = serializer.data
|
|
if is_demo_token_request(self.context["request"]):
|
|
# Replace integration token to not receive alerts on demo integration
|
|
link = result["link"]
|
|
real_token = instance.token
|
|
link = link.replace(real_token, DEMO_INTEGRATION_LINK_TOKEN)
|
|
result["link"] = link
|
|
|
|
return result
|
|
|
|
def create(self, validated_data):
|
|
validated_data = self._correct_validated_data(validated_data)
|
|
validated_data.pop("default_route", None)
|
|
organization = self.context["request"].auth.organization
|
|
integration = validated_data.get("integration")
|
|
if integration == AlertReceiveChannel.INTEGRATION_GRAFANA_ALERTING:
|
|
connection_error = GrafanaAlertingSyncManager.check_for_connection_errors(organization)
|
|
if connection_error:
|
|
raise serializers.ValidationError(connection_error)
|
|
instance = AlertReceiveChannel.create(
|
|
**validated_data,
|
|
author=self.context["request"].user,
|
|
organization=organization,
|
|
)
|
|
return instance
|
|
|
|
def validate(self, attrs):
|
|
organization = self.context["request"].auth.organization
|
|
verbal_name = attrs.get("verbal_name", None)
|
|
if verbal_name is None:
|
|
return attrs
|
|
try:
|
|
obj = AlertReceiveChannel.objects.get(organization=organization, verbal_name=verbal_name)
|
|
except AlertReceiveChannel.DoesNotExist:
|
|
return attrs
|
|
if self.instance and obj.id == self.instance.id:
|
|
return attrs
|
|
else:
|
|
raise BadRequest(detail="Integration with this name already exists")
|
|
|
|
def _correct_validated_data(self, validated_data):
|
|
templates = validated_data.pop("templates", {})
|
|
for template_name, templates_for_notification_channel in templates.items():
|
|
if type(templates_for_notification_channel) is dict:
|
|
for attr, template in templates_for_notification_channel.items():
|
|
try:
|
|
validated_data[AlertReceiveChannel.PUBLIC_TEMPLATES_FIELDS[template_name][attr]] = template
|
|
except KeyError:
|
|
raise BadRequest(detail="Invalid template data")
|
|
elif type(templates_for_notification_channel) is str:
|
|
try:
|
|
validated_data[
|
|
AlertReceiveChannel.PUBLIC_TEMPLATES_FIELDS[template_name]
|
|
] = templates_for_notification_channel
|
|
except KeyError:
|
|
raise BadRequest(detail="Invalid template data")
|
|
elif templates_for_notification_channel is None:
|
|
try:
|
|
template_to_set_to_default = AlertReceiveChannel.PUBLIC_TEMPLATES_FIELDS[template_name]
|
|
if type(template_to_set_to_default) is str:
|
|
validated_data[AlertReceiveChannel.PUBLIC_TEMPLATES_FIELDS[template_name]] = None
|
|
elif type(template_to_set_to_default) is dict:
|
|
for key in template_to_set_to_default.keys():
|
|
validated_data[AlertReceiveChannel.PUBLIC_TEMPLATES_FIELDS[template_name][key]] = None
|
|
except KeyError:
|
|
raise BadRequest(detail="Invalid template data")
|
|
|
|
return validated_data
|
|
|
|
def validate_templates(self, templates):
|
|
if not isinstance(templates, dict):
|
|
raise BadRequest(detail="Invalid template data")
|
|
|
|
for notification_channel in ["slack", "web", "sms", "phone_call", "email", "telegram"]:
|
|
template_data = templates.get(notification_channel, {})
|
|
if template_data is None:
|
|
continue
|
|
if not isinstance(template_data, dict):
|
|
raise BadRequest(detail=f"Invalid {notification_channel} template data")
|
|
for attr, attr_template in template_data.items():
|
|
if attr_template is None:
|
|
continue
|
|
try:
|
|
jinja_template_env.from_string(attr_template)
|
|
except TemplateSyntaxError:
|
|
raise BadRequest(detail=f"invalid {notification_channel} {attr} template")
|
|
|
|
for common_template in ["resolve_signal", "grouping_key"]:
|
|
template_data = templates.get(common_template, "")
|
|
if template_data is None:
|
|
continue
|
|
if not isinstance(template_data, str):
|
|
raise BadRequest(detail=f"Invalid {common_template} template data")
|
|
try:
|
|
jinja_template_env.from_string(template_data)
|
|
except TemplateSyntaxError:
|
|
raise BadRequest(detail=f"Invalid {common_template} template data")
|
|
return templates
|
|
|
|
def get_heartbeat(self, obj):
|
|
try:
|
|
heartbeat = obj.integration_heartbeat
|
|
except ObjectDoesNotExist:
|
|
return None
|
|
return IntegrationHeartBeatSerializer(heartbeat).data
|
|
|
|
@timed_lru_cache(timeout=5)
|
|
def _get_default_route_iterative(self, obj):
|
|
"""
|
|
Gets default route iterative to not hit db on each integration instance.
|
|
"""
|
|
for filter in obj.channel_filters.all():
|
|
if filter.is_default:
|
|
return filter
|
|
|
|
|
|
class IntegrationUpdateSerializer(IntegrationSerializer):
|
|
type = IntegrationTypeField(source="integration", read_only=True)
|
|
team_id = TeamPrimaryKeyRelatedField(source="team", read_only=True)
|
|
|
|
def update(self, instance, validated_data):
|
|
validated_data = self._correct_validated_data(validated_data)
|
|
default_route_data = validated_data.pop("default_route", {})
|
|
default_route = instance.default_channel_filter
|
|
serializer = DefaultChannelFilterSerializer(default_route, default_route_data, context=self.context)
|
|
serializer.is_valid(raise_exception=True)
|
|
serializer.save()
|
|
return super().update(instance, validated_data)
|