2023-07-11 21:03:34 +03:00
|
|
|
import json
|
2023-09-27 07:22:52 -06:00
|
|
|
from dataclasses import asdict
|
2023-07-11 21:03:34 +03:00
|
|
|
|
2023-08-02 20:07:47 +02:00
|
|
|
from django.core.exceptions import ObjectDoesNotExist
|
2023-03-23 09:52:59 +08:00
|
|
|
from django_filters import rest_framework as filters
|
2024-09-09 09:17:23 -03:00
|
|
|
from drf_spectacular.utils import extend_schema, inline_serializer
|
|
|
|
|
from rest_framework import serializers, status
|
2023-03-23 09:52:59 +08:00
|
|
|
from rest_framework.decorators import action
|
2023-03-10 14:00:06 -03:00
|
|
|
from rest_framework.exceptions import NotFound
|
2023-03-23 09:52:59 +08:00
|
|
|
from rest_framework.filters import SearchFilter
|
2023-03-10 14:00:06 -03:00
|
|
|
from rest_framework.permissions import IsAuthenticated
|
2023-03-23 09:52:59 +08:00
|
|
|
from rest_framework.response import Response
|
2023-03-10 14:00:06 -03:00
|
|
|
from rest_framework.viewsets import ModelViewSet
|
|
|
|
|
|
2024-09-09 09:17:23 -03:00
|
|
|
from apps.alerts.models import AlertGroup, AlertReceiveChannel
|
Webhook labels (#3383)
This PR add labels for webhooks.
1. Make webhook "labelable" with ability to filter by labels.
2. Add labels to the webhook payload. It contain new field webhook with
it's name, id and labels. Field integration and alert_group has a
corresponding label field as well. See example of a new payload below:
```
{
"event": {
"type": "escalation"
},
"user": null,
"alert_group": {
"id": "IRFN6ZD31N31B",
"integration_id": "CTWM7U4A2QG97",
"route_id": "RUE7U7Z46SKGY",
"alerts_count": 1,
"state": "firing",
"created_at": "2023-11-22T08:54:55.178243Z",
"resolved_at": null,
"acknowledged_at": null,
"title": "Incident",
"permalinks": {
"slack": null,
"telegram": null,
"web": "http://grafana:3000/a/grafana-oncall-app/alert-groups/IRFN6ZD31N31B"
},
"labels": {
"severity": "critical"
}
},
"alert_group_id": "IRFN6ZD31N31B",
"alert_payload": {
"message": "This alert was sent by user for demonstration purposes"
},
"integration": {
"id": "CTWM7U4A2QG97",
"type": "webhook",
"name": "hi - Webhook",
"team": null,
"labels": {
"hello": "world",
"severity": "critical"
}
},
"notified_users": [],
"users_to_be_notified": [],
"webhook": {
"id": "WHAXK4BTC7TAEQ",
"name": "test",
"labels": {
"hello": "kesha"
}
}
}
```
I feel that there is an opportunity to make code cleaner - remove all
label logic from serializers, views and utils to models or dedicated
LabelerService and introduce Labelable interface with something like
label_verbal, update_labels methods. However, I don't want to tie
webhook labels with a refactoring.
---------
Co-authored-by: Dominik <dominik.broj@grafana.com>
2023-11-22 19:17:41 +08:00
|
|
|
from apps.api.label_filtering import parse_label_query
|
2023-03-10 14:00:06 -03:00
|
|
|
from apps.api.permissions import RBACPermission
|
2023-07-11 21:03:34 +03:00
|
|
|
from apps.api.serializers.webhook import WebhookResponseSerializer, WebhookSerializer
|
Webhook labels (#3383)
This PR add labels for webhooks.
1. Make webhook "labelable" with ability to filter by labels.
2. Add labels to the webhook payload. It contain new field webhook with
it's name, id and labels. Field integration and alert_group has a
corresponding label field as well. See example of a new payload below:
```
{
"event": {
"type": "escalation"
},
"user": null,
"alert_group": {
"id": "IRFN6ZD31N31B",
"integration_id": "CTWM7U4A2QG97",
"route_id": "RUE7U7Z46SKGY",
"alerts_count": 1,
"state": "firing",
"created_at": "2023-11-22T08:54:55.178243Z",
"resolved_at": null,
"acknowledged_at": null,
"title": "Incident",
"permalinks": {
"slack": null,
"telegram": null,
"web": "http://grafana:3000/a/grafana-oncall-app/alert-groups/IRFN6ZD31N31B"
},
"labels": {
"severity": "critical"
}
},
"alert_group_id": "IRFN6ZD31N31B",
"alert_payload": {
"message": "This alert was sent by user for demonstration purposes"
},
"integration": {
"id": "CTWM7U4A2QG97",
"type": "webhook",
"name": "hi - Webhook",
"team": null,
"labels": {
"hello": "world",
"severity": "critical"
}
},
"notified_users": [],
"users_to_be_notified": [],
"webhook": {
"id": "WHAXK4BTC7TAEQ",
"name": "test",
"labels": {
"hello": "kesha"
}
}
}
```
I feel that there is an opportunity to make code cleaner - remove all
label logic from serializers, views and utils to models or dedicated
LabelerService and introduce Labelable interface with something like
label_verbal, update_labels methods. However, I don't want to tie
webhook labels with a refactoring.
---------
Co-authored-by: Dominik <dominik.broj@grafana.com>
2023-11-22 19:17:41 +08:00
|
|
|
from apps.api.views.labels import schedule_update_label_cache
|
2023-03-10 14:00:06 -03:00
|
|
|
from apps.auth_token.auth import PluginAuthentication
|
Webhook labels (#3383)
This PR add labels for webhooks.
1. Make webhook "labelable" with ability to filter by labels.
2. Add labels to the webhook payload. It contain new field webhook with
it's name, id and labels. Field integration and alert_group has a
corresponding label field as well. See example of a new payload below:
```
{
"event": {
"type": "escalation"
},
"user": null,
"alert_group": {
"id": "IRFN6ZD31N31B",
"integration_id": "CTWM7U4A2QG97",
"route_id": "RUE7U7Z46SKGY",
"alerts_count": 1,
"state": "firing",
"created_at": "2023-11-22T08:54:55.178243Z",
"resolved_at": null,
"acknowledged_at": null,
"title": "Incident",
"permalinks": {
"slack": null,
"telegram": null,
"web": "http://grafana:3000/a/grafana-oncall-app/alert-groups/IRFN6ZD31N31B"
},
"labels": {
"severity": "critical"
}
},
"alert_group_id": "IRFN6ZD31N31B",
"alert_payload": {
"message": "This alert was sent by user for demonstration purposes"
},
"integration": {
"id": "CTWM7U4A2QG97",
"type": "webhook",
"name": "hi - Webhook",
"team": null,
"labels": {
"hello": "world",
"severity": "critical"
}
},
"notified_users": [],
"users_to_be_notified": [],
"webhook": {
"id": "WHAXK4BTC7TAEQ",
"name": "test",
"labels": {
"hello": "kesha"
}
}
}
```
I feel that there is an opportunity to make code cleaner - remove all
label logic from serializers, views and utils to models or dedicated
LabelerService and introduce Labelable interface with something like
label_verbal, update_labels methods. However, I don't want to tie
webhook labels with a refactoring.
---------
Co-authored-by: Dominik <dominik.broj@grafana.com>
2023-11-22 19:17:41 +08:00
|
|
|
from apps.labels.utils import is_labels_feature_enabled
|
2025-02-18 14:53:07 -03:00
|
|
|
from apps.webhooks.models import PersonalNotificationWebhook, Webhook, WebhookResponse
|
2023-09-27 07:22:52 -06:00
|
|
|
from apps.webhooks.presets.preset_options import WebhookPresetOptions
|
2024-09-09 09:17:23 -03:00
|
|
|
from apps.webhooks.tasks import execute_webhook
|
2023-07-13 13:53:06 -06:00
|
|
|
from apps.webhooks.utils import apply_jinja_template_for_json
|
2023-07-11 21:03:34 +03:00
|
|
|
from common.api_helpers.exceptions import BadRequest
|
2024-09-09 09:17:23 -03:00
|
|
|
from common.api_helpers.filters import (
|
|
|
|
|
ByTeamModelFieldFilterMixin,
|
|
|
|
|
ModelFieldFilterMixin,
|
|
|
|
|
MultipleChoiceCharFilter,
|
|
|
|
|
TeamModelMultipleChoiceFilter,
|
|
|
|
|
)
|
2023-03-10 14:00:06 -03:00
|
|
|
from common.api_helpers.mixins import PublicPrimaryKeyMixin, TeamFilteringMixin
|
2023-08-22 14:05:52 -06:00
|
|
|
from common.insight_log import EntityEvent, write_resource_insight_log
|
2023-07-11 21:03:34 +03:00
|
|
|
from common.jinja_templater.apply_jinja_template import JinjaTemplateError, JinjaTemplateWarning
|
|
|
|
|
|
2023-07-11 14:01:38 -06:00
|
|
|
NEW_WEBHOOK_PK = "new"
|
|
|
|
|
|
2023-07-11 21:03:34 +03:00
|
|
|
RECENT_RESPONSE_LIMIT = 20
|
|
|
|
|
|
|
|
|
|
WEBHOOK_URL = "url"
|
|
|
|
|
WEBHOOK_HEADERS = "headers"
|
|
|
|
|
WEBHOOK_TRIGGER_TEMPLATE = "trigger_template"
|
|
|
|
|
WEBHOOK_TRIGGER_DATA = "data"
|
|
|
|
|
|
|
|
|
|
WEBHOOK_TEMPLATE_NAMES = [WEBHOOK_URL, WEBHOOK_HEADERS, WEBHOOK_TRIGGER_TEMPLATE, WEBHOOK_TRIGGER_DATA]
|
2023-03-10 14:00:06 -03:00
|
|
|
|
|
|
|
|
|
2024-09-09 09:17:23 -03:00
|
|
|
def get_integration_queryset(request):
|
|
|
|
|
if request is None:
|
|
|
|
|
return AlertReceiveChannel.objects.none()
|
|
|
|
|
|
|
|
|
|
return AlertReceiveChannel.objects_with_maintenance.filter(organization=request.user.organization)
|
|
|
|
|
|
|
|
|
|
|
2023-03-23 09:52:59 +08:00
|
|
|
class WebhooksFilter(ByTeamModelFieldFilterMixin, ModelFieldFilterMixin, filters.FilterSet):
|
|
|
|
|
team = TeamModelMultipleChoiceFilter()
|
2024-09-09 09:17:23 -03:00
|
|
|
trigger_type = filters.MultipleChoiceFilter(choices=Webhook.TRIGGER_TYPES)
|
|
|
|
|
integration = MultipleChoiceCharFilter(
|
|
|
|
|
field_name="filtered_integrations",
|
|
|
|
|
queryset=get_integration_queryset,
|
|
|
|
|
to_field_name="public_primary_key",
|
|
|
|
|
method="filter_integration",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def filter_integration(self, queryset, name, value):
|
|
|
|
|
if not value:
|
|
|
|
|
return queryset
|
|
|
|
|
lookup_kwargs = {f"{name}__in": value}
|
|
|
|
|
# include webhooks without filtered_integrations set (ie. apply to all integrations)
|
|
|
|
|
queryset = queryset.filter(**lookup_kwargs) | queryset.filter(filtered_integrations__isnull=True)
|
|
|
|
|
return queryset
|
2023-03-23 09:52:59 +08:00
|
|
|
|
|
|
|
|
|
2024-01-30 13:07:19 -05:00
|
|
|
class WebhooksView(TeamFilteringMixin, PublicPrimaryKeyMixin[Webhook], ModelViewSet):
|
2023-03-10 14:00:06 -03:00
|
|
|
authentication_classes = (PluginAuthentication,)
|
|
|
|
|
permission_classes = (IsAuthenticated, RBACPermission)
|
|
|
|
|
|
|
|
|
|
rbac_permissions = {
|
|
|
|
|
"metadata": [RBACPermission.Permissions.OUTGOING_WEBHOOKS_READ],
|
2023-03-23 09:52:59 +08:00
|
|
|
"filters": [RBACPermission.Permissions.OUTGOING_WEBHOOKS_READ],
|
2023-03-10 14:00:06 -03:00
|
|
|
"list": [RBACPermission.Permissions.OUTGOING_WEBHOOKS_READ],
|
|
|
|
|
"retrieve": [RBACPermission.Permissions.OUTGOING_WEBHOOKS_READ],
|
|
|
|
|
"create": [RBACPermission.Permissions.OUTGOING_WEBHOOKS_WRITE],
|
|
|
|
|
"update": [RBACPermission.Permissions.OUTGOING_WEBHOOKS_WRITE],
|
|
|
|
|
"partial_update": [RBACPermission.Permissions.OUTGOING_WEBHOOKS_WRITE],
|
|
|
|
|
"destroy": [RBACPermission.Permissions.OUTGOING_WEBHOOKS_WRITE],
|
2023-07-11 21:03:34 +03:00
|
|
|
"responses": [RBACPermission.Permissions.OUTGOING_WEBHOOKS_READ],
|
|
|
|
|
"preview_template": [RBACPermission.Permissions.OUTGOING_WEBHOOKS_WRITE],
|
2023-09-27 07:22:52 -06:00
|
|
|
"preset_options": [RBACPermission.Permissions.OUTGOING_WEBHOOKS_READ],
|
2024-09-09 09:17:23 -03:00
|
|
|
"trigger_manual": [RBACPermission.Permissions.OUTGOING_WEBHOOKS_READ],
|
2025-02-18 14:53:07 -03:00
|
|
|
"current_personal_notification": [RBACPermission.Permissions.USER_SETTINGS_READ],
|
|
|
|
|
"set_personal_notification": [RBACPermission.Permissions.USER_SETTINGS_WRITE],
|
2023-03-10 14:00:06 -03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
model = Webhook
|
|
|
|
|
serializer_class = WebhookSerializer
|
|
|
|
|
|
2023-03-23 09:52:59 +08:00
|
|
|
filter_backends = [SearchFilter, filters.DjangoFilterBackend]
|
|
|
|
|
search_fields = ["public_primary_key", "name"]
|
|
|
|
|
filterset_class = WebhooksFilter
|
|
|
|
|
|
2023-08-22 14:05:52 -06:00
|
|
|
def perform_create(self, serializer):
|
|
|
|
|
serializer.save()
|
|
|
|
|
write_resource_insight_log(instance=serializer.instance, author=self.request.user, event=EntityEvent.CREATED)
|
|
|
|
|
|
|
|
|
|
def perform_update(self, serializer):
|
|
|
|
|
prev_state = serializer.instance.insight_logs_serialized
|
|
|
|
|
serializer.save()
|
|
|
|
|
new_state = serializer.instance.insight_logs_serialized
|
|
|
|
|
write_resource_insight_log(
|
|
|
|
|
instance=serializer.instance,
|
|
|
|
|
author=self.request.user,
|
|
|
|
|
event=EntityEvent.UPDATED,
|
|
|
|
|
prev_state=prev_state,
|
|
|
|
|
new_state=new_state,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def perform_destroy(self, instance):
|
|
|
|
|
write_resource_insight_log(
|
|
|
|
|
instance=instance,
|
|
|
|
|
author=self.request.user,
|
|
|
|
|
event=EntityEvent.DELETED,
|
|
|
|
|
)
|
|
|
|
|
instance.delete()
|
|
|
|
|
|
2023-03-22 00:57:20 +08:00
|
|
|
def get_queryset(self, ignore_filtering_by_available_teams=False):
|
2024-04-08 11:13:17 -03:00
|
|
|
queryset = Webhook.objects.filter(organization=self.request.auth.organization)
|
|
|
|
|
if self.action == "list":
|
|
|
|
|
# exclude connected integration webhooks when listing entries
|
|
|
|
|
queryset = queryset.filter(is_from_connected_integration=False)
|
|
|
|
|
|
2023-03-22 00:57:20 +08:00
|
|
|
if not ignore_filtering_by_available_teams:
|
2023-03-22 15:43:32 +08:00
|
|
|
queryset = queryset.filter(*self.available_teams_lookup_args).distinct()
|
Webhook labels (#3383)
This PR add labels for webhooks.
1. Make webhook "labelable" with ability to filter by labels.
2. Add labels to the webhook payload. It contain new field webhook with
it's name, id and labels. Field integration and alert_group has a
corresponding label field as well. See example of a new payload below:
```
{
"event": {
"type": "escalation"
},
"user": null,
"alert_group": {
"id": "IRFN6ZD31N31B",
"integration_id": "CTWM7U4A2QG97",
"route_id": "RUE7U7Z46SKGY",
"alerts_count": 1,
"state": "firing",
"created_at": "2023-11-22T08:54:55.178243Z",
"resolved_at": null,
"acknowledged_at": null,
"title": "Incident",
"permalinks": {
"slack": null,
"telegram": null,
"web": "http://grafana:3000/a/grafana-oncall-app/alert-groups/IRFN6ZD31N31B"
},
"labels": {
"severity": "critical"
}
},
"alert_group_id": "IRFN6ZD31N31B",
"alert_payload": {
"message": "This alert was sent by user for demonstration purposes"
},
"integration": {
"id": "CTWM7U4A2QG97",
"type": "webhook",
"name": "hi - Webhook",
"team": null,
"labels": {
"hello": "world",
"severity": "critical"
}
},
"notified_users": [],
"users_to_be_notified": [],
"webhook": {
"id": "WHAXK4BTC7TAEQ",
"name": "test",
"labels": {
"hello": "kesha"
}
}
}
```
I feel that there is an opportunity to make code cleaner - remove all
label logic from serializers, views and utils to models or dedicated
LabelerService and introduce Labelable interface with something like
label_verbal, update_labels methods. However, I don't want to tie
webhook labels with a refactoring.
---------
Co-authored-by: Dominik <dominik.broj@grafana.com>
2023-11-22 19:17:41 +08:00
|
|
|
|
|
|
|
|
# filter by labels
|
|
|
|
|
label_query = self.request.query_params.getlist("label", [])
|
|
|
|
|
kv_pairs = parse_label_query(label_query)
|
|
|
|
|
for key, value in kv_pairs:
|
|
|
|
|
queryset = queryset.filter(
|
|
|
|
|
labels__key_id=key,
|
|
|
|
|
labels__value_id=value,
|
|
|
|
|
)
|
|
|
|
|
# distinct to remove duplicates after webhooks X labels join
|
|
|
|
|
queryset = queryset.distinct()
|
|
|
|
|
# schedule update of labels cache
|
|
|
|
|
ids = [d.id for d in queryset]
|
|
|
|
|
schedule_update_label_cache(self.model.__name__, self.request.auth.organization, ids)
|
|
|
|
|
|
2023-03-10 14:00:06 -03:00
|
|
|
return queryset
|
|
|
|
|
|
|
|
|
|
def get_object(self):
|
|
|
|
|
# get the object from the whole organization if there is a flag `get_from_organization=true`
|
|
|
|
|
# otherwise get the object from the current team
|
|
|
|
|
get_from_organization = self.request.query_params.get("from_organization", "false") == "true"
|
|
|
|
|
if get_from_organization:
|
|
|
|
|
return self.get_object_from_organization()
|
|
|
|
|
return super().get_object()
|
|
|
|
|
|
|
|
|
|
def get_object_from_organization(self):
|
|
|
|
|
# use this method to get the object from the whole organization instead of the current team
|
|
|
|
|
pk = self.kwargs["pk"]
|
|
|
|
|
organization = self.request.auth.organization
|
|
|
|
|
try:
|
2023-05-23 17:23:06 +01:00
|
|
|
obj = organization.webhooks.filter(*self.available_teams_lookup_args).distinct().get(public_primary_key=pk)
|
2023-03-10 14:00:06 -03:00
|
|
|
except ObjectDoesNotExist:
|
|
|
|
|
raise NotFound
|
|
|
|
|
|
|
|
|
|
# May raise a permission denied
|
|
|
|
|
self.check_object_permissions(self.request, obj)
|
|
|
|
|
|
|
|
|
|
return obj
|
2023-03-14 08:31:47 -06:00
|
|
|
|
2024-09-09 09:17:23 -03:00
|
|
|
@extend_schema(
|
|
|
|
|
responses=inline_serializer(
|
|
|
|
|
name="WebhookFilters",
|
|
|
|
|
fields={
|
|
|
|
|
"name": serializers.CharField(),
|
|
|
|
|
"display_name": serializers.CharField(required=False),
|
|
|
|
|
"type": serializers.CharField(),
|
|
|
|
|
"href": serializers.CharField(),
|
|
|
|
|
"global": serializers.BooleanField(required=False),
|
|
|
|
|
},
|
|
|
|
|
many=True,
|
|
|
|
|
)
|
|
|
|
|
)
|
2023-03-23 09:52:59 +08:00
|
|
|
@action(methods=["get"], detail=False)
|
|
|
|
|
def filters(self, request):
|
|
|
|
|
api_root = "/api/internal/v1/"
|
|
|
|
|
|
|
|
|
|
filter_options = [
|
2024-07-22 11:30:28 +01:00
|
|
|
{"name": "search", "type": "search"},
|
2023-03-23 09:52:59 +08:00
|
|
|
{
|
|
|
|
|
"name": "team",
|
|
|
|
|
"type": "team_select",
|
|
|
|
|
"href": api_root + "teams/",
|
|
|
|
|
"global": True,
|
|
|
|
|
},
|
2024-09-09 09:17:23 -03:00
|
|
|
{
|
|
|
|
|
"name": "trigger_type",
|
|
|
|
|
"type": "options",
|
|
|
|
|
"options": [{"display_name": label, "value": value} for value, label in Webhook.TRIGGER_TYPES],
|
|
|
|
|
},
|
|
|
|
|
{"name": "integration", "type": "options", "href": api_root + "alert_receive_channels/?filters=true"},
|
2023-03-23 09:52:59 +08:00
|
|
|
]
|
|
|
|
|
|
Webhook labels (#3383)
This PR add labels for webhooks.
1. Make webhook "labelable" with ability to filter by labels.
2. Add labels to the webhook payload. It contain new field webhook with
it's name, id and labels. Field integration and alert_group has a
corresponding label field as well. See example of a new payload below:
```
{
"event": {
"type": "escalation"
},
"user": null,
"alert_group": {
"id": "IRFN6ZD31N31B",
"integration_id": "CTWM7U4A2QG97",
"route_id": "RUE7U7Z46SKGY",
"alerts_count": 1,
"state": "firing",
"created_at": "2023-11-22T08:54:55.178243Z",
"resolved_at": null,
"acknowledged_at": null,
"title": "Incident",
"permalinks": {
"slack": null,
"telegram": null,
"web": "http://grafana:3000/a/grafana-oncall-app/alert-groups/IRFN6ZD31N31B"
},
"labels": {
"severity": "critical"
}
},
"alert_group_id": "IRFN6ZD31N31B",
"alert_payload": {
"message": "This alert was sent by user for demonstration purposes"
},
"integration": {
"id": "CTWM7U4A2QG97",
"type": "webhook",
"name": "hi - Webhook",
"team": null,
"labels": {
"hello": "world",
"severity": "critical"
}
},
"notified_users": [],
"users_to_be_notified": [],
"webhook": {
"id": "WHAXK4BTC7TAEQ",
"name": "test",
"labels": {
"hello": "kesha"
}
}
}
```
I feel that there is an opportunity to make code cleaner - remove all
label logic from serializers, views and utils to models or dedicated
LabelerService and introduce Labelable interface with something like
label_verbal, update_labels methods. However, I don't want to tie
webhook labels with a refactoring.
---------
Co-authored-by: Dominik <dominik.broj@grafana.com>
2023-11-22 19:17:41 +08:00
|
|
|
if is_labels_feature_enabled(self.request.auth.organization):
|
|
|
|
|
filter_options.append(
|
|
|
|
|
{
|
|
|
|
|
"name": "label",
|
|
|
|
|
"display_name": "Label",
|
|
|
|
|
"type": "labels",
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
|
2023-03-23 09:52:59 +08:00
|
|
|
return Response(filter_options)
|
2023-07-11 21:03:34 +03:00
|
|
|
|
2024-09-09 09:17:23 -03:00
|
|
|
@extend_schema(responses=WebhookResponseSerializer(many=True))
|
2023-07-11 21:03:34 +03:00
|
|
|
@action(methods=["get"], detail=True)
|
|
|
|
|
def responses(self, request, pk):
|
2024-09-09 09:17:23 -03:00
|
|
|
"""Return recent responses data for the webhook."""
|
2023-07-11 14:01:38 -06:00
|
|
|
if pk == NEW_WEBHOOK_PK:
|
|
|
|
|
return Response([], status=status.HTTP_200_OK)
|
|
|
|
|
|
2023-07-11 21:03:34 +03:00
|
|
|
webhook = self.get_object()
|
|
|
|
|
queryset = WebhookResponse.objects.filter(webhook_id=webhook.id, trigger_type=webhook.trigger_type).order_by(
|
|
|
|
|
"-timestamp"
|
|
|
|
|
)[:RECENT_RESPONSE_LIMIT]
|
|
|
|
|
response_serializer = WebhookResponseSerializer(queryset, many=True)
|
|
|
|
|
return Response(response_serializer.data)
|
|
|
|
|
|
2024-09-09 09:17:23 -03:00
|
|
|
@extend_schema(
|
|
|
|
|
request=inline_serializer(
|
|
|
|
|
name="WebhookPreviewTemplateRequest",
|
|
|
|
|
fields={
|
|
|
|
|
"template_body": serializers.CharField(required=False, allow_null=True),
|
|
|
|
|
"template_name": serializers.CharField(required=False, allow_null=True),
|
|
|
|
|
"payload": serializers.DictField(required=False, allow_null=True),
|
|
|
|
|
},
|
|
|
|
|
),
|
|
|
|
|
responses=inline_serializer(
|
|
|
|
|
name="WebhookPreviewTemplateResponse",
|
|
|
|
|
fields={
|
|
|
|
|
"preview": serializers.CharField(allow_null=True),
|
|
|
|
|
},
|
|
|
|
|
),
|
|
|
|
|
)
|
2023-07-11 21:03:34 +03:00
|
|
|
@action(methods=["post"], detail=True)
|
|
|
|
|
def preview_template(self, request, pk):
|
2024-09-09 09:17:23 -03:00
|
|
|
"""Return webhook template preview."""
|
2023-07-11 14:01:38 -06:00
|
|
|
if pk != NEW_WEBHOOK_PK:
|
|
|
|
|
self.get_object() # Check webhook exists
|
|
|
|
|
|
2023-07-11 21:03:34 +03:00
|
|
|
template_body = request.data.get("template_body", None)
|
|
|
|
|
template_name = request.data.get("template_name", None)
|
|
|
|
|
payload = request.data.get("payload", None)
|
|
|
|
|
|
|
|
|
|
if not payload:
|
|
|
|
|
response = {"preview": template_body}
|
|
|
|
|
return Response(response, status=status.HTTP_200_OK)
|
|
|
|
|
|
|
|
|
|
if isinstance(payload, str):
|
|
|
|
|
try:
|
|
|
|
|
payload = json.loads(payload)
|
|
|
|
|
except json.JSONDecodeError:
|
|
|
|
|
raise BadRequest(detail={"payload": "Could not parse json"})
|
|
|
|
|
|
|
|
|
|
if template_body is None or template_name is None:
|
|
|
|
|
response = {"preview": None}
|
|
|
|
|
return Response(response, status=status.HTTP_200_OK)
|
|
|
|
|
|
|
|
|
|
if template_name not in WEBHOOK_TEMPLATE_NAMES:
|
|
|
|
|
raise BadRequest(detail={"template_name": "Unknown template name"})
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
result = apply_jinja_template_for_json(template_body, payload)
|
|
|
|
|
except (JinjaTemplateError, JinjaTemplateWarning) as e:
|
|
|
|
|
return Response({"preview": e.fallback_message}, status.HTTP_200_OK)
|
|
|
|
|
|
|
|
|
|
response = {"preview": result}
|
|
|
|
|
return Response(response, status=status.HTTP_200_OK)
|
2023-09-27 07:22:52 -06:00
|
|
|
|
2024-09-09 09:17:23 -03:00
|
|
|
@extend_schema(
|
|
|
|
|
responses={
|
|
|
|
|
status.HTTP_200_OK: inline_serializer(
|
|
|
|
|
name="WebhookPresetOptions",
|
|
|
|
|
fields={
|
|
|
|
|
"id": serializers.CharField(),
|
|
|
|
|
"name": serializers.CharField(),
|
|
|
|
|
"logo": serializers.CharField(),
|
|
|
|
|
"description": serializers.CharField(),
|
|
|
|
|
"controlled_fields": serializers.ListField(child=serializers.CharField()),
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
},
|
|
|
|
|
)
|
2023-09-27 07:22:52 -06:00
|
|
|
@action(methods=["get"], detail=False)
|
|
|
|
|
def preset_options(self, request):
|
2024-09-09 09:17:23 -03:00
|
|
|
"""Return available webhook preset options."""
|
2023-09-27 07:22:52 -06:00
|
|
|
result = [asdict(preset) for preset in WebhookPresetOptions.WEBHOOK_PRESET_CHOICES]
|
|
|
|
|
return Response(result)
|
2024-09-09 09:17:23 -03:00
|
|
|
|
|
|
|
|
@extend_schema(
|
|
|
|
|
request=inline_serializer(
|
|
|
|
|
name="WebhookTriggerManual",
|
|
|
|
|
fields={
|
|
|
|
|
"alert_group": serializers.CharField(),
|
|
|
|
|
},
|
|
|
|
|
),
|
|
|
|
|
responses={status.HTTP_200_OK: None},
|
|
|
|
|
)
|
|
|
|
|
@action(methods=["post"], detail=True)
|
|
|
|
|
def trigger_manual(self, request, pk):
|
|
|
|
|
"""Trigger specified webhook in the context of the given alert group."""
|
|
|
|
|
user = self.request.user
|
|
|
|
|
organization = self.request.auth.organization
|
|
|
|
|
webhook = self.get_object()
|
|
|
|
|
if webhook.trigger_type != Webhook.TRIGGER_MANUAL:
|
|
|
|
|
raise BadRequest(detail={"trigger_type": "This webhook is not manually triggerable."})
|
|
|
|
|
|
|
|
|
|
alert_group_ppk = request.data.get("alert_group")
|
|
|
|
|
if not alert_group_ppk:
|
|
|
|
|
raise BadRequest(detail={"alert_group": "This field is required."})
|
|
|
|
|
|
|
|
|
|
alert_groups = AlertGroup.objects.filter(
|
|
|
|
|
channel__organization=organization,
|
|
|
|
|
public_primary_key=alert_group_ppk,
|
|
|
|
|
)
|
|
|
|
|
# check for filtered integrations
|
|
|
|
|
if webhook.filtered_integrations.exists():
|
|
|
|
|
alert_groups = alert_groups.filter(channel_id__in=webhook.filtered_integrations.all())
|
|
|
|
|
try:
|
|
|
|
|
alert_group = alert_groups.get()
|
|
|
|
|
except ObjectDoesNotExist:
|
|
|
|
|
raise NotFound
|
|
|
|
|
|
|
|
|
|
execute_webhook.apply_async(
|
|
|
|
|
(webhook.pk, alert_group.pk, user.pk, None), kwargs={"trigger_type": Webhook.TRIGGER_MANUAL}
|
|
|
|
|
)
|
|
|
|
|
return Response(status=status.HTTP_200_OK)
|
2025-02-18 14:53:07 -03:00
|
|
|
|
|
|
|
|
@extend_schema(
|
|
|
|
|
responses={
|
|
|
|
|
status.HTTP_200_OK: inline_serializer(
|
|
|
|
|
name="PersonalNotificationWebhook",
|
|
|
|
|
fields={
|
|
|
|
|
"webhook": serializers.CharField(),
|
|
|
|
|
"context": serializers.DictField(required=False, allow_null=True),
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
@action(methods=["get"], detail=False)
|
|
|
|
|
def current_personal_notification(self, request):
|
|
|
|
|
user = self.request.user
|
|
|
|
|
notification_channel = {
|
|
|
|
|
"webhook": None,
|
|
|
|
|
"context": None,
|
|
|
|
|
}
|
|
|
|
|
try:
|
|
|
|
|
personal_webhook = PersonalNotificationWebhook.objects.get(user=user)
|
|
|
|
|
except PersonalNotificationWebhook.DoesNotExist:
|
|
|
|
|
personal_webhook = None
|
|
|
|
|
|
|
|
|
|
if personal_webhook is not None:
|
|
|
|
|
notification_channel["webhook"] = personal_webhook.webhook.public_primary_key
|
|
|
|
|
notification_channel["context"] = personal_webhook.context_data
|
|
|
|
|
|
|
|
|
|
return Response(notification_channel)
|
|
|
|
|
|
|
|
|
|
@extend_schema(
|
|
|
|
|
request=inline_serializer(
|
|
|
|
|
name="PersonalNotificationWebhookRequest",
|
|
|
|
|
fields={
|
|
|
|
|
"webhook": serializers.CharField(),
|
|
|
|
|
"context": serializers.DictField(required=False, allow_null=True),
|
|
|
|
|
},
|
|
|
|
|
),
|
|
|
|
|
responses={status.HTTP_200_OK: None},
|
|
|
|
|
)
|
|
|
|
|
@action(methods=["post"], detail=False)
|
|
|
|
|
def set_personal_notification(self, request):
|
|
|
|
|
"""Set up a webhook as personal notification channel for the user."""
|
|
|
|
|
user = self.request.user
|
|
|
|
|
|
|
|
|
|
webhook_id = request.data.get("webhook")
|
|
|
|
|
if not webhook_id:
|
|
|
|
|
raise BadRequest(detail={"webhook": "This field is required."})
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
webhook = Webhook.objects.get(
|
|
|
|
|
organization=user.organization,
|
|
|
|
|
public_primary_key=webhook_id,
|
|
|
|
|
trigger_type=Webhook.TRIGGER_PERSONAL_NOTIFICATION,
|
|
|
|
|
)
|
|
|
|
|
except Webhook.DoesNotExist:
|
|
|
|
|
raise BadRequest(detail={"webhook": "Webhook not found."})
|
|
|
|
|
|
|
|
|
|
context = request.data.get("context", None)
|
|
|
|
|
if context is not None:
|
|
|
|
|
if not isinstance(context, dict):
|
|
|
|
|
raise BadRequest(detail={"context": "Invalid context."})
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
context = json.dumps(context)
|
|
|
|
|
except TypeError:
|
|
|
|
|
raise BadRequest(detail={"context": "Invalid context."})
|
|
|
|
|
|
|
|
|
|
# set or update personal webhook for user
|
|
|
|
|
PersonalNotificationWebhook.objects.update_or_create(
|
|
|
|
|
user=user,
|
|
|
|
|
defaults={
|
|
|
|
|
"webhook": webhook,
|
|
|
|
|
"additional_context_data": context,
|
|
|
|
|
},
|
|
|
|
|
)
|
|
|
|
|
return Response(status=status.HTTP_200_OK)
|