oncall-engine/engine/apps/api/serializers/webhook.py

238 lines
9.1 KiB
Python
Raw Permalink Normal View History

from collections import defaultdict
from rest_framework import serializers
from rest_framework.validators import UniqueTogetherValidator
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.serializers.labels import LabelsSerializerMixin
from apps.webhooks.models import Webhook, WebhookResponse
Add webhook presets (#2996) # What this PR does Add a system similar to how we select integrations when creating webhooks so that the user has a description of what webhookds do and does not have to write complex templates for common webhook use cases. Presets allow us to create the contents of the webhooks in code and define which fields are controlled by the preset. Some specifics: - Newly created webhooks must choose between Simple, Advanced or another predefined system - Simple is always an escalation step and will post the entire payload to the given URL - Advanced is the same as no preset which is our current view where all fields are available - There are no changes for all existing webhooks with empty preset fields - Once a webhook is created with a preset the preset cannot be changed - Fields in the webhook that are populated by code will give a validation error if they are modified - In the public API webhooks with presets are returned for viewing but cannot be created or modified. This restriction is in place because the Web UI provides the context for which fields to use with a preset. The public API is for interacting with webhooks where all fields are defined. To define a preset create a file with metadata and an override function. The metadata drives validation and what to display in the UI. There are two functions one is connected to the pre_save hook of the Webhook model for persistent changes, the other replaces parameters at execution time for ephemeral changes. See the simple and advanced presets as an example. The file must be listed in settings in `INSTALLED_WEBHOOK_PRESETS` to be enabled at runtime.. ## Which issue(s) this PR fixes ## Checklist - [x] Unit, integration, and e2e (if applicable) tests updated - [x] Documentation added (or `pr:no public docs` PR label added if not required) - [x] `CHANGELOG.md` updated (or `pr:no changelog` PR label added if not required) --------- Co-authored-by: Joey Orlando <joey.orlando@grafana.com>
2023-09-27 07:22:52 -06:00
from apps.webhooks.models.webhook import PUBLIC_WEBHOOK_HTTP_METHODS, WEBHOOK_FIELD_PLACEHOLDER
from apps.webhooks.presets.preset_options import WebhookPresetOptions
from common.api_helpers.custom_fields import IntegrationFilteredByOrganizationField, TeamPrimaryKeyRelatedField
from common.api_helpers.utils import CurrentOrganizationDefault, CurrentUserDefault
from common.jinja_templater import apply_jinja_template
from common.jinja_templater.apply_jinja_template import JinjaTemplateError, JinjaTemplateWarning
class WebhookResponseSerializer(serializers.ModelSerializer):
class Meta:
model = WebhookResponse
fields = [
"timestamp",
"url",
"request_trigger",
"request_headers",
"request_data",
"status_code",
"content",
"event_data",
]
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
class WebhookSerializer(LabelsSerializerMixin, serializers.ModelSerializer):
id = serializers.CharField(read_only=True, source="public_primary_key")
organization = serializers.HiddenField(default=CurrentOrganizationDefault())
team = TeamPrimaryKeyRelatedField(allow_null=True, required=False)
user = serializers.HiddenField(default=CurrentUserDefault())
forward_all = serializers.BooleanField(allow_null=True, required=False)
last_response_log = serializers.SerializerMethodField()
Add webhook presets (#2996) # What this PR does Add a system similar to how we select integrations when creating webhooks so that the user has a description of what webhookds do and does not have to write complex templates for common webhook use cases. Presets allow us to create the contents of the webhooks in code and define which fields are controlled by the preset. Some specifics: - Newly created webhooks must choose between Simple, Advanced or another predefined system - Simple is always an escalation step and will post the entire payload to the given URL - Advanced is the same as no preset which is our current view where all fields are available - There are no changes for all existing webhooks with empty preset fields - Once a webhook is created with a preset the preset cannot be changed - Fields in the webhook that are populated by code will give a validation error if they are modified - In the public API webhooks with presets are returned for viewing but cannot be created or modified. This restriction is in place because the Web UI provides the context for which fields to use with a preset. The public API is for interacting with webhooks where all fields are defined. To define a preset create a file with metadata and an override function. The metadata drives validation and what to display in the UI. There are two functions one is connected to the pre_save hook of the Webhook model for persistent changes, the other replaces parameters at execution time for ephemeral changes. See the simple and advanced presets as an example. The file must be listed in settings in `INSTALLED_WEBHOOK_PRESETS` to be enabled at runtime.. ## Which issue(s) this PR fixes ## Checklist - [x] Unit, integration, and e2e (if applicable) tests updated - [x] Documentation added (or `pr:no public docs` PR label added if not required) - [x] `CHANGELOG.md` updated (or `pr:no changelog` PR label added if not required) --------- Co-authored-by: Joey Orlando <joey.orlando@grafana.com>
2023-09-27 07:22:52 -06:00
trigger_type = serializers.CharField(allow_null=True)
trigger_type_name = serializers.SerializerMethodField()
integration_filter = IntegrationFilteredByOrganizationField(
source="filtered_integrations", many=True, required=False
)
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
PREFETCH_RELATED = ["labels", "labels__key", "labels__value"]
class Meta:
model = Webhook
fields = [
"id",
"name",
"is_webhook_enabled",
"is_legacy",
"team",
"user",
"username",
"password",
"authorization_header",
"organization",
"trigger_template",
"headers",
"url",
"data",
"forward_all",
"http_method",
"trigger_type",
"trigger_type_name",
"last_response_log",
"integration_filter",
Add webhook presets (#2996) # What this PR does Add a system similar to how we select integrations when creating webhooks so that the user has a description of what webhookds do and does not have to write complex templates for common webhook use cases. Presets allow us to create the contents of the webhooks in code and define which fields are controlled by the preset. Some specifics: - Newly created webhooks must choose between Simple, Advanced or another predefined system - Simple is always an escalation step and will post the entire payload to the given URL - Advanced is the same as no preset which is our current view where all fields are available - There are no changes for all existing webhooks with empty preset fields - Once a webhook is created with a preset the preset cannot be changed - Fields in the webhook that are populated by code will give a validation error if they are modified - In the public API webhooks with presets are returned for viewing but cannot be created or modified. This restriction is in place because the Web UI provides the context for which fields to use with a preset. The public API is for interacting with webhooks where all fields are defined. To define a preset create a file with metadata and an override function. The metadata drives validation and what to display in the UI. There are two functions one is connected to the pre_save hook of the Webhook model for persistent changes, the other replaces parameters at execution time for ephemeral changes. See the simple and advanced presets as an example. The file must be listed in settings in `INSTALLED_WEBHOOK_PRESETS` to be enabled at runtime.. ## Which issue(s) this PR fixes ## Checklist - [x] Unit, integration, and e2e (if applicable) tests updated - [x] Documentation added (or `pr:no public docs` PR label added if not required) - [x] `CHANGELOG.md` updated (or `pr:no changelog` PR label added if not required) --------- Co-authored-by: Joey Orlando <joey.orlando@grafana.com>
2023-09-27 07:22:52 -06:00
"preset",
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
"labels",
]
validators = [UniqueTogetherValidator(queryset=Webhook.objects.all(), fields=["name", "organization"])]
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
def create(self, validated_data):
organization = self.context["request"].auth.organization
labels = validated_data.pop("labels", None)
instance = super().create(validated_data)
self.update_labels_association_if_needed(labels, instance, organization)
return instance
def update(self, instance, validated_data):
labels = validated_data.pop("labels", None)
organization = self.context["request"].auth.organization
self.update_labels_association_if_needed(labels, instance, organization)
return super().update(instance, validated_data)
def to_representation(self, instance):
result = super().to_representation(instance)
if instance.password:
result["password"] = WEBHOOK_FIELD_PLACEHOLDER
if instance.authorization_header:
result["authorization_header"] = WEBHOOK_FIELD_PLACEHOLDER
return result
def to_internal_value(self, data):
webhook = self.instance
Add webhook presets (#2996) # What this PR does Add a system similar to how we select integrations when creating webhooks so that the user has a description of what webhookds do and does not have to write complex templates for common webhook use cases. Presets allow us to create the contents of the webhooks in code and define which fields are controlled by the preset. Some specifics: - Newly created webhooks must choose between Simple, Advanced or another predefined system - Simple is always an escalation step and will post the entire payload to the given URL - Advanced is the same as no preset which is our current view where all fields are available - There are no changes for all existing webhooks with empty preset fields - Once a webhook is created with a preset the preset cannot be changed - Fields in the webhook that are populated by code will give a validation error if they are modified - In the public API webhooks with presets are returned for viewing but cannot be created or modified. This restriction is in place because the Web UI provides the context for which fields to use with a preset. The public API is for interacting with webhooks where all fields are defined. To define a preset create a file with metadata and an override function. The metadata drives validation and what to display in the UI. There are two functions one is connected to the pre_save hook of the Webhook model for persistent changes, the other replaces parameters at execution time for ephemeral changes. See the simple and advanced presets as an example. The file must be listed in settings in `INSTALLED_WEBHOOK_PRESETS` to be enabled at runtime.. ## Which issue(s) this PR fixes ## Checklist - [x] Unit, integration, and e2e (if applicable) tests updated - [x] Documentation added (or `pr:no public docs` PR label added if not required) - [x] `CHANGELOG.md` updated (or `pr:no changelog` PR label added if not required) --------- Co-authored-by: Joey Orlando <joey.orlando@grafana.com>
2023-09-27 07:22:52 -06:00
# Some fields are conditionally required, add none values for missing required fields
if webhook and webhook.preset and "preset" not in data:
data["preset"] = webhook.preset
for key in ["url", "http_method", "trigger_type"]:
if key not in data:
if self.instance:
data[key] = getattr(self.instance, key)
else:
data[key] = None
# If webhook is being copied instance won't exist to copy values from
if not webhook and "id" in data:
webhook = Webhook.objects.get(
public_primary_key=data["id"], organization=self.context["request"].auth.organization
)
if data.get("password") == WEBHOOK_FIELD_PLACEHOLDER:
data["password"] = webhook.password
if data.get("authorization_header") == WEBHOOK_FIELD_PLACEHOLDER:
data["authorization_header"] = webhook.authorization_header
if not data.get("integration_filter"):
data["integration_filter"] = []
return super().to_internal_value(data)
def _validate_template_field(self, template):
try:
apply_jinja_template(template, alert_payload=defaultdict(str), alert_group_id="alert_group_1")
except JinjaTemplateError as e:
raise serializers.ValidationError(e.fallback_message)
except JinjaTemplateWarning:
# Suppress render exceptions since we do not have a representative payload to test with
pass
return template
def validate_trigger_template(self, trigger_template):
if not trigger_template:
return None
return self._validate_template_field(trigger_template)
def validate_headers(self, headers):
if not headers:
return None
return self._validate_template_field(headers)
def validate_url(self, url):
Add webhook presets (#2996) # What this PR does Add a system similar to how we select integrations when creating webhooks so that the user has a description of what webhookds do and does not have to write complex templates for common webhook use cases. Presets allow us to create the contents of the webhooks in code and define which fields are controlled by the preset. Some specifics: - Newly created webhooks must choose between Simple, Advanced or another predefined system - Simple is always an escalation step and will post the entire payload to the given URL - Advanced is the same as no preset which is our current view where all fields are available - There are no changes for all existing webhooks with empty preset fields - Once a webhook is created with a preset the preset cannot be changed - Fields in the webhook that are populated by code will give a validation error if they are modified - In the public API webhooks with presets are returned for viewing but cannot be created or modified. This restriction is in place because the Web UI provides the context for which fields to use with a preset. The public API is for interacting with webhooks where all fields are defined. To define a preset create a file with metadata and an override function. The metadata drives validation and what to display in the UI. There are two functions one is connected to the pre_save hook of the Webhook model for persistent changes, the other replaces parameters at execution time for ephemeral changes. See the simple and advanced presets as an example. The file must be listed in settings in `INSTALLED_WEBHOOK_PRESETS` to be enabled at runtime.. ## Which issue(s) this PR fixes ## Checklist - [x] Unit, integration, and e2e (if applicable) tests updated - [x] Documentation added (or `pr:no public docs` PR label added if not required) - [x] `CHANGELOG.md` updated (or `pr:no changelog` PR label added if not required) --------- Co-authored-by: Joey Orlando <joey.orlando@grafana.com>
2023-09-27 07:22:52 -06:00
if self.is_field_controlled("url"):
return url
if not url:
Add webhook presets (#2996) # What this PR does Add a system similar to how we select integrations when creating webhooks so that the user has a description of what webhookds do and does not have to write complex templates for common webhook use cases. Presets allow us to create the contents of the webhooks in code and define which fields are controlled by the preset. Some specifics: - Newly created webhooks must choose between Simple, Advanced or another predefined system - Simple is always an escalation step and will post the entire payload to the given URL - Advanced is the same as no preset which is our current view where all fields are available - There are no changes for all existing webhooks with empty preset fields - Once a webhook is created with a preset the preset cannot be changed - Fields in the webhook that are populated by code will give a validation error if they are modified - In the public API webhooks with presets are returned for viewing but cannot be created or modified. This restriction is in place because the Web UI provides the context for which fields to use with a preset. The public API is for interacting with webhooks where all fields are defined. To define a preset create a file with metadata and an override function. The metadata drives validation and what to display in the UI. There are two functions one is connected to the pre_save hook of the Webhook model for persistent changes, the other replaces parameters at execution time for ephemeral changes. See the simple and advanced presets as an example. The file must be listed in settings in `INSTALLED_WEBHOOK_PRESETS` to be enabled at runtime.. ## Which issue(s) this PR fixes ## Checklist - [x] Unit, integration, and e2e (if applicable) tests updated - [x] Documentation added (or `pr:no public docs` PR label added if not required) - [x] `CHANGELOG.md` updated (or `pr:no changelog` PR label added if not required) --------- Co-authored-by: Joey Orlando <joey.orlando@grafana.com>
2023-09-27 07:22:52 -06:00
raise serializers.ValidationError(detail="This field is required.")
return self._validate_template_field(url)
Add webhook presets (#2996) # What this PR does Add a system similar to how we select integrations when creating webhooks so that the user has a description of what webhookds do and does not have to write complex templates for common webhook use cases. Presets allow us to create the contents of the webhooks in code and define which fields are controlled by the preset. Some specifics: - Newly created webhooks must choose between Simple, Advanced or another predefined system - Simple is always an escalation step and will post the entire payload to the given URL - Advanced is the same as no preset which is our current view where all fields are available - There are no changes for all existing webhooks with empty preset fields - Once a webhook is created with a preset the preset cannot be changed - Fields in the webhook that are populated by code will give a validation error if they are modified - In the public API webhooks with presets are returned for viewing but cannot be created or modified. This restriction is in place because the Web UI provides the context for which fields to use with a preset. The public API is for interacting with webhooks where all fields are defined. To define a preset create a file with metadata and an override function. The metadata drives validation and what to display in the UI. There are two functions one is connected to the pre_save hook of the Webhook model for persistent changes, the other replaces parameters at execution time for ephemeral changes. See the simple and advanced presets as an example. The file must be listed in settings in `INSTALLED_WEBHOOK_PRESETS` to be enabled at runtime.. ## Which issue(s) this PR fixes ## Checklist - [x] Unit, integration, and e2e (if applicable) tests updated - [x] Documentation added (or `pr:no public docs` PR label added if not required) - [x] `CHANGELOG.md` updated (or `pr:no changelog` PR label added if not required) --------- Co-authored-by: Joey Orlando <joey.orlando@grafana.com>
2023-09-27 07:22:52 -06:00
def validate_http_method(self, http_method):
if self.is_field_controlled("http_method"):
return http_method
if http_method not in PUBLIC_WEBHOOK_HTTP_METHODS:
raise serializers.ValidationError(detail=f"This field must be one of {PUBLIC_WEBHOOK_HTTP_METHODS}.")
return http_method
def validate_trigger_type(self, trigger_type):
if self.is_field_controlled("trigger_type"):
return trigger_type
if not trigger_type or int(trigger_type) not in Webhook.ALL_TRIGGER_TYPES:
raise serializers.ValidationError(detail="This field is required.")
return trigger_type
def validate_data(self, data):
if not data:
return None
return self._validate_template_field(data)
def validate_forward_all(self, data):
if data is None:
return False
return data
Add webhook presets (#2996) # What this PR does Add a system similar to how we select integrations when creating webhooks so that the user has a description of what webhookds do and does not have to write complex templates for common webhook use cases. Presets allow us to create the contents of the webhooks in code and define which fields are controlled by the preset. Some specifics: - Newly created webhooks must choose between Simple, Advanced or another predefined system - Simple is always an escalation step and will post the entire payload to the given URL - Advanced is the same as no preset which is our current view where all fields are available - There are no changes for all existing webhooks with empty preset fields - Once a webhook is created with a preset the preset cannot be changed - Fields in the webhook that are populated by code will give a validation error if they are modified - In the public API webhooks with presets are returned for viewing but cannot be created or modified. This restriction is in place because the Web UI provides the context for which fields to use with a preset. The public API is for interacting with webhooks where all fields are defined. To define a preset create a file with metadata and an override function. The metadata drives validation and what to display in the UI. There are two functions one is connected to the pre_save hook of the Webhook model for persistent changes, the other replaces parameters at execution time for ephemeral changes. See the simple and advanced presets as an example. The file must be listed in settings in `INSTALLED_WEBHOOK_PRESETS` to be enabled at runtime.. ## Which issue(s) this PR fixes ## Checklist - [x] Unit, integration, and e2e (if applicable) tests updated - [x] Documentation added (or `pr:no public docs` PR label added if not required) - [x] `CHANGELOG.md` updated (or `pr:no changelog` PR label added if not required) --------- Co-authored-by: Joey Orlando <joey.orlando@grafana.com>
2023-09-27 07:22:52 -06:00
def validate_preset(self, preset):
if self.instance and self.instance.preset != preset:
raise serializers.ValidationError(detail="This field once set cannot be modified.")
if preset:
if preset not in WebhookPresetOptions.WEBHOOK_PRESETS:
raise serializers.ValidationError(detail=f"{preset} is not a valid preset id.")
preset_metadata = WebhookPresetOptions.WEBHOOK_PRESETS[preset].metadata
for controlled_field in preset_metadata.controlled_fields:
if controlled_field in self.initial_data:
if self.instance:
if bool(self.initial_data[controlled_field]) and self.initial_data[controlled_field] != getattr(
self.instance, controlled_field
):
Add webhook presets (#2996) # What this PR does Add a system similar to how we select integrations when creating webhooks so that the user has a description of what webhookds do and does not have to write complex templates for common webhook use cases. Presets allow us to create the contents of the webhooks in code and define which fields are controlled by the preset. Some specifics: - Newly created webhooks must choose between Simple, Advanced or another predefined system - Simple is always an escalation step and will post the entire payload to the given URL - Advanced is the same as no preset which is our current view where all fields are available - There are no changes for all existing webhooks with empty preset fields - Once a webhook is created with a preset the preset cannot be changed - Fields in the webhook that are populated by code will give a validation error if they are modified - In the public API webhooks with presets are returned for viewing but cannot be created or modified. This restriction is in place because the Web UI provides the context for which fields to use with a preset. The public API is for interacting with webhooks where all fields are defined. To define a preset create a file with metadata and an override function. The metadata drives validation and what to display in the UI. There are two functions one is connected to the pre_save hook of the Webhook model for persistent changes, the other replaces parameters at execution time for ephemeral changes. See the simple and advanced presets as an example. The file must be listed in settings in `INSTALLED_WEBHOOK_PRESETS` to be enabled at runtime.. ## Which issue(s) this PR fixes ## Checklist - [x] Unit, integration, and e2e (if applicable) tests updated - [x] Documentation added (or `pr:no public docs` PR label added if not required) - [x] `CHANGELOG.md` updated (or `pr:no changelog` PR label added if not required) --------- Co-authored-by: Joey Orlando <joey.orlando@grafana.com>
2023-09-27 07:22:52 -06:00
raise serializers.ValidationError(
detail=f"{controlled_field} is controlled by preset, cannot update"
)
elif bool(self.initial_data[controlled_field]):
Add webhook presets (#2996) # What this PR does Add a system similar to how we select integrations when creating webhooks so that the user has a description of what webhookds do and does not have to write complex templates for common webhook use cases. Presets allow us to create the contents of the webhooks in code and define which fields are controlled by the preset. Some specifics: - Newly created webhooks must choose between Simple, Advanced or another predefined system - Simple is always an escalation step and will post the entire payload to the given URL - Advanced is the same as no preset which is our current view where all fields are available - There are no changes for all existing webhooks with empty preset fields - Once a webhook is created with a preset the preset cannot be changed - Fields in the webhook that are populated by code will give a validation error if they are modified - In the public API webhooks with presets are returned for viewing but cannot be created or modified. This restriction is in place because the Web UI provides the context for which fields to use with a preset. The public API is for interacting with webhooks where all fields are defined. To define a preset create a file with metadata and an override function. The metadata drives validation and what to display in the UI. There are two functions one is connected to the pre_save hook of the Webhook model for persistent changes, the other replaces parameters at execution time for ephemeral changes. See the simple and advanced presets as an example. The file must be listed in settings in `INSTALLED_WEBHOOK_PRESETS` to be enabled at runtime.. ## Which issue(s) this PR fixes ## Checklist - [x] Unit, integration, and e2e (if applicable) tests updated - [x] Documentation added (or `pr:no public docs` PR label added if not required) - [x] `CHANGELOG.md` updated (or `pr:no changelog` PR label added if not required) --------- Co-authored-by: Joey Orlando <joey.orlando@grafana.com>
2023-09-27 07:22:52 -06:00
raise serializers.ValidationError(
detail=f"{controlled_field} is controlled by preset, cannot create"
)
return preset
def get_last_response_log(self, obj):
return WebhookResponseSerializer(obj.responses.last()).data
def get_trigger_type_name(self, obj):
trigger_type_name = ""
if obj.trigger_type is not None:
trigger_type_name = Webhook.TRIGGER_TYPES[int(obj.trigger_type)][1]
return trigger_type_name
Add webhook presets (#2996) # What this PR does Add a system similar to how we select integrations when creating webhooks so that the user has a description of what webhookds do and does not have to write complex templates for common webhook use cases. Presets allow us to create the contents of the webhooks in code and define which fields are controlled by the preset. Some specifics: - Newly created webhooks must choose between Simple, Advanced or another predefined system - Simple is always an escalation step and will post the entire payload to the given URL - Advanced is the same as no preset which is our current view where all fields are available - There are no changes for all existing webhooks with empty preset fields - Once a webhook is created with a preset the preset cannot be changed - Fields in the webhook that are populated by code will give a validation error if they are modified - In the public API webhooks with presets are returned for viewing but cannot be created or modified. This restriction is in place because the Web UI provides the context for which fields to use with a preset. The public API is for interacting with webhooks where all fields are defined. To define a preset create a file with metadata and an override function. The metadata drives validation and what to display in the UI. There are two functions one is connected to the pre_save hook of the Webhook model for persistent changes, the other replaces parameters at execution time for ephemeral changes. See the simple and advanced presets as an example. The file must be listed in settings in `INSTALLED_WEBHOOK_PRESETS` to be enabled at runtime.. ## Which issue(s) this PR fixes ## Checklist - [x] Unit, integration, and e2e (if applicable) tests updated - [x] Documentation added (or `pr:no public docs` PR label added if not required) - [x] `CHANGELOG.md` updated (or `pr:no changelog` PR label added if not required) --------- Co-authored-by: Joey Orlando <joey.orlando@grafana.com>
2023-09-27 07:22:52 -06:00
def is_field_controlled(self, field_name):
if self.instance:
if not self.instance.preset:
return False
elif "preset" not in self.initial_data:
return False
preset_id = self.instance.preset if self.instance else self.initial_data["preset"]
if preset_id:
if preset_id not in WebhookPresetOptions.WEBHOOK_PRESETS:
raise serializers.ValidationError(detail=f"unknown preset {preset_id} referenced")
preset = WebhookPresetOptions.WEBHOOK_PRESETS[preset_id]
if field_name not in preset.metadata.controlled_fields:
return False
return True
class WebhookFastSerializer(serializers.ModelSerializer):
id = serializers.CharField(read_only=True, source="public_primary_key")
class Meta:
model = Webhook
fields = ["id", "name"]