From e09422a07da12665bcc07a926e3d9331d5924869 Mon Sep 17 00:00:00 2001 From: Vadim Stepanov Date: Mon, 27 Nov 2023 17:28:34 +0000 Subject: [PATCH] Revert "Alert group payload labels" (#3433) Reverts grafana/oncall#3385 --- ...nnel_alert_group_labels_custom_and_more.py | 23 -- engine/apps/alerts/models/alert.py | 4 +- engine/apps/alerts/models/alert_group.py | 2 - .../alerts/models/alert_receive_channel.py | 32 +- .../api/serializers/alert_receive_channel.py | 198 +---------- engine/apps/api/serializers/channel_filter.py | 2 +- .../api/tests/test_alert_receive_channel.py | 106 +----- .../test_alert_receive_channel_template.py | 31 -- engine/apps/labels/alert_group_labels.py | 158 --------- engine/apps/labels/models.py | 12 +- engine/apps/labels/tests/test_alert_group.py | 59 +--- engine/apps/labels/utils.py | 38 +- engine/apps/public_api/serializers/routes.py | 2 +- engine/common/api_helpers/mixins.py | 20 +- engine/common/api_helpers/utils.py | 11 - engine/conftest.py | 30 +- grafana-plugin/package.json | 2 +- grafana-plugin/playwright.config.ts | 2 +- .../LabelsTooltipBadge/LabelsTooltipBadge.tsx | 2 +- .../components/MonacoEditor/MonacoEditor.tsx | 1 - .../IntegrationLabelsForm.tsx | 333 +++--------------- .../IntegrationTemplate.tsx | 13 +- .../src/containers/Labels/Labels.tsx | 2 +- .../alert_receive_channel.types.ts | 6 +- grafana-plugin/yarn.lock | 8 +- 25 files changed, 162 insertions(+), 935 deletions(-) delete mode 100644 engine/apps/alerts/migrations/0040_alertreceivechannel_alert_group_labels_custom_and_more.py delete mode 100644 engine/apps/labels/alert_group_labels.py diff --git a/engine/apps/alerts/migrations/0040_alertreceivechannel_alert_group_labels_custom_and_more.py b/engine/apps/alerts/migrations/0040_alertreceivechannel_alert_group_labels_custom_and_more.py deleted file mode 100644 index d08dbe4a..00000000 --- a/engine/apps/alerts/migrations/0040_alertreceivechannel_alert_group_labels_custom_and_more.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 4.2.7 on 2023-11-22 12:14 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('alerts', '0039_remove_alertreceivechannel_unique_integration_name'), - ] - - operations = [ - migrations.AddField( - model_name='alertreceivechannel', - name='alert_group_labels_custom', - field=models.JSONField(default=list, null=True), - ), - migrations.AddField( - model_name='alertreceivechannel', - name='alert_group_labels_template', - field=models.TextField(default=None, null=True), - ), - ] diff --git a/engine/apps/alerts/models/alert.py b/engine/apps/alerts/models/alert.py index 203f208a..79f458db 100644 --- a/engine/apps/alerts/models/alert.py +++ b/engine/apps/alerts/models/alert.py @@ -12,7 +12,7 @@ from django.db.models import JSONField from apps.alerts import tasks from apps.alerts.constants import TASK_DELAY_SECONDS from apps.alerts.incident_appearance.templaters import TemplateLoader -from apps.labels.alert_group_labels import assign_labels +from apps.labels.utils import assign_labels from common.jinja_templater import apply_jinja_template from common.jinja_templater.apply_jinja_template import JinjaTemplateError, JinjaTemplateWarning from common.public_primary_keys import generate_public_primary_key, increase_public_primary_key_length @@ -108,7 +108,7 @@ class Alert(models.Model): ) if group_created: - assign_labels(group, alert_receive_channel, raw_request_data) + assign_labels(group, alert_receive_channel) group.log_records.create(type=AlertGroupLogRecord.TYPE_REGISTERED) group.log_records.create(type=AlertGroupLogRecord.TYPE_ROUTE_ASSIGNED) diff --git a/engine/apps/alerts/models/alert_group.py b/engine/apps/alerts/models/alert_group.py index cbdb587a..6f6aec12 100644 --- a/engine/apps/alerts/models/alert_group.py +++ b/engine/apps/alerts/models/alert_group.py @@ -43,7 +43,6 @@ if typing.TYPE_CHECKING: ResolutionNoteSlackMessage, ) from apps.base.models import UserNotificationPolicyLogRecord - from apps.labels.models import AlertGroupAssociatedLabel from apps.slack.models import SlackMessage logger = logging.getLogger(__name__) @@ -195,7 +194,6 @@ class AlertGroup(AlertGroupSlackRenderingMixin, EscalationSnapshotMixin, models. slack_log_message: typing.Optional["SlackMessage"] slack_messages: "RelatedManager['SlackMessage']" users: "RelatedManager['User']" - labels: "RelatedManager['AlertGroupAssociatedLabel']" objects: models.Manager["AlertGroup"] = AlertGroupQuerySet.as_manager() diff --git a/engine/apps/alerts/models/alert_receive_channel.py b/engine/apps/alerts/models/alert_receive_channel.py index 2688b4c8..fb465193 100644 --- a/engine/apps/alerts/models/alert_receive_channel.py +++ b/engine/apps/alerts/models/alert_receive_channel.py @@ -42,7 +42,6 @@ if typing.TYPE_CHECKING: from django.db.models.manager import RelatedManager from apps.alerts.models import AlertGroup, ChannelFilter - from apps.labels.models import AlertReceiveChannelAssociatedLabel from apps.user_management.models import Organization, Team logger = logging.getLogger(__name__) @@ -88,6 +87,10 @@ def number_to_smiles_translator(number): return "".join(reversed(smileset)) +class IntegrationAlertGroupLabels(typing.TypedDict): + inheritable: typing.Dict[str, bool] + + class AlertReceiveChannelQueryset(models.QuerySet): def delete(self): self.update(deleted_at=timezone.now()) @@ -120,7 +123,6 @@ class AlertReceiveChannel(IntegrationOptionsMixin, MaintainableObject): channel_filters: "RelatedManager['ChannelFilter']" organization: "Organization" team: typing.Optional["Team"] - labels: "RelatedManager['AlertReceiveChannelAssociatedLabel']" objects = AlertReceiveChannelManager() objects_with_maintenance = AlertReceiveChannelManagerWithMaintenance() @@ -204,17 +206,6 @@ class AlertReceiveChannel(IntegrationOptionsMixin, MaintainableObject): rate_limited_in_slack_at = models.DateTimeField(null=True, default=None) rate_limit_message_task_id = models.CharField(max_length=100, null=True, default=None) - AlertGroupCustomLabels = list[tuple[str, str | None, str | None]] - alert_group_labels_custom: AlertGroupCustomLabels = models.JSONField(null=True, default=list) - """ - Stores "custom labels" for alert group labels. Custom labels can be either "plain" or "templated". - For plain labels, the format is: [, , None] - For templated labels, the format is: [, None, ] - """ - - alert_group_labels_template: str | None = models.TextField(null=True, default=None) - """Stores a Jinja2 template for "advanced label templating" for alert group labels.""" - def __str__(self): short_name_with_emojis = emojize(self.short_name, language="alias") return f"{self.pk}: {short_name_with_emojis}" @@ -644,6 +635,21 @@ class AlertReceiveChannel(IntegrationOptionsMixin, MaintainableObject): result["team"] = "General" return result + @property + def alert_group_labels(self) -> IntegrationAlertGroupLabels: + """ + Alert group labels configuration for the integration used by AlertReceiveChannelSerializer. + See AlertReceiveChannelAssociatedLabel.inheritable for more details. + """ + return {"inheritable": {label.key_id: label.inheritable for label in self.labels.all()}} + + @alert_group_labels.setter + def alert_group_labels(self, value: IntegrationAlertGroupLabels) -> None: + """Setter for alert_group_labels used by AlertReceiveChannelSerializer""" + inheritable_key_ids = [key_id for key_id, inheritable in value["inheritable"].items() if inheritable] + self.labels.filter(key_id__in=inheritable_key_ids).update(inheritable=True) + self.labels.filter(~Q(key_id__in=inheritable_key_ids)).update(inheritable=False) + @receiver(post_save, sender=AlertReceiveChannel) def listen_for_alertreceivechannel_model_save( diff --git a/engine/apps/api/serializers/alert_receive_channel.py b/engine/apps/api/serializers/alert_receive_channel.py index 45390996..ad9ddaf5 100644 --- a/engine/apps/api/serializers/alert_receive_channel.py +++ b/engine/apps/api/serializers/alert_receive_channel.py @@ -4,7 +4,6 @@ from collections import OrderedDict from django.conf import settings from django.core.exceptions import ObjectDoesNotExist from django.core.exceptions import ValidationError as DjangoValidationError -from django.db.models import Q from jinja2 import TemplateSyntaxError from rest_framework import serializers from rest_framework.exceptions import ValidationError @@ -15,188 +14,30 @@ from apps.alerts.models import AlertReceiveChannel from apps.alerts.models.channel_filter import ChannelFilter from apps.base.messaging import get_messaging_backends from apps.integrations.legacy_prefix import has_legacy_prefix -from apps.labels.models import LabelKeyCache, LabelValueCache -from apps.user_management.models import Organization from common.api_helpers.custom_fields import TeamPrimaryKeyRelatedField from common.api_helpers.exceptions import BadRequest from common.api_helpers.mixins import APPEARANCE_TEMPLATE_NAMES, EagerLoadingMixin from common.api_helpers.utils import CurrentTeamDefault -from common.jinja_templater import jinja_template_env +from common.jinja_templater import apply_jinja_template, jinja_template_env +from common.jinja_templater.apply_jinja_template import JinjaTemplateWarning from .integration_heartbeat import IntegrationHeartBeatSerializer from .labels import LabelsSerializerMixin -class AlertGroupCustomLabelKey(typing.TypedDict): - id: str - name: str - - -class AlertGroupCustomLabelValue(typing.TypedDict): - id: str | None # None for templated labels, label value ID for plain labels - name: str # Jinja template for templated labels, label value name for plain labels - - -class AlertGroupCustomLabel(typing.TypedDict): - key: AlertGroupCustomLabelKey - value: AlertGroupCustomLabelValue - - -AlertGroupCustomLabels = list[AlertGroupCustomLabel] - - -class IntegrationAlertGroupLabels(typing.TypedDict): - inheritable: dict[str, bool] - custom: AlertGroupCustomLabels - template: str | None - - -class CustomLabelSerializer(serializers.Serializer): - """This serializer is consistent with apps.api.serializers.labels.LabelSerializer, but allows null for value ID.""" - - class KeySerializer(serializers.Serializer): - id = serializers.CharField() - name = serializers.CharField() - - class ValueSerializer(serializers.Serializer): - # ID is null for templated labels. For such labels, the "name" value is a Jinja2 template. - id = serializers.CharField(allow_null=True) - name = serializers.CharField() - - key = KeySerializer() - value = ValueSerializer() +def valid_jinja_template_for_serializer_method_field(template): + for _, val in template.items(): + try: + apply_jinja_template(val, payload={}) + except JinjaTemplateWarning: + # Suppress warnings, template may be valid with payload + pass class IntegrationAlertGroupLabelsSerializer(serializers.Serializer): """Alert group labels configuration for the integration. See AlertReceiveChannel.alert_group_labels for details.""" inheritable = serializers.DictField(child=serializers.BooleanField()) - custom = CustomLabelSerializer(many=True) - template = serializers.CharField(allow_null=True) - - @staticmethod - def pop_alert_group_labels(validated_data: dict) -> IntegrationAlertGroupLabels | None: - """Get alert group labels from validated data.""" - - # the "alert_group_labels" field is optional, so either all 3 fields are present or none - if "inheritable" not in validated_data: - return None - - return { - "inheritable": validated_data.pop("inheritable"), - "custom": validated_data.pop("custom"), - "template": validated_data.pop("template"), - } - - @classmethod - def update( - cls, instance: AlertReceiveChannel, alert_group_labels: IntegrationAlertGroupLabels | None - ) -> AlertReceiveChannel: - if alert_group_labels is None: - return instance - - # update inheritable labels - inheritable_key_ids = [ - key_id for key_id, inheritable in alert_group_labels["inheritable"].items() if inheritable - ] - instance.labels.filter(key_id__in=inheritable_key_ids).update(inheritable=True) - instance.labels.filter(~Q(key_id__in=inheritable_key_ids)).update(inheritable=False) - - # update DB cache for custom labels - cls._create_custom_labels(instance.organization, alert_group_labels["custom"]) - # update custom labels - instance.alert_group_labels_custom = cls._custom_labels_to_internal_value(alert_group_labels["custom"]) - - # update template - instance.alert_group_labels_template = alert_group_labels["template"] - - instance.save(update_fields=["alert_group_labels_custom", "alert_group_labels_template"]) - return instance - - @staticmethod - def _create_custom_labels(organization: Organization, labels: AlertGroupCustomLabels) -> None: - """Create LabelKeyCache and LabelValueCache objects for custom labels.""" - - label_keys = [ - LabelKeyCache(id=label["key"]["id"], name=label["key"]["name"], organization=organization) - for label in labels - ] - - label_values = [ - LabelValueCache(id=label["value"]["id"], name=label["value"]["name"], key_id=label["key"]["id"]) - for label in labels - if label["value"]["id"] # don't create LabelValueCache objects for templated labels - ] - - LabelKeyCache.objects.bulk_create(label_keys, ignore_conflicts=True, batch_size=5000) - LabelValueCache.objects.bulk_create(label_values, ignore_conflicts=True, batch_size=5000) - - @classmethod - def to_representation(cls, instance: AlertReceiveChannel) -> IntegrationAlertGroupLabels: - """ - The API representation of alert group labels is very different from the underlying model. - - "inheritable" is based on AlertReceiveChannelAssociatedLabel.inheritable, a property of another model. - "custom" is based on AlertReceiveChannel.alert_group_labels_custom, a JSONField with a different schema. - "template" is based on AlertReceiveChannel.alert_group_labels_template, this one is straightforward. - """ - - return { - "inheritable": {label.key_id: label.inheritable for label in instance.labels.all()}, - "custom": cls._custom_labels_to_representation(instance.alert_group_labels_custom), - "template": instance.alert_group_labels_template, - } - - @staticmethod - def _custom_labels_to_internal_value( - custom_labels: AlertGroupCustomLabels, - ) -> AlertReceiveChannel.AlertGroupCustomLabels: - """Convert custom labels from API representation to the schema used by the JSONField on the model.""" - - return [ - [label["key"]["id"], label["value"]["id"], None if label["value"]["id"] else label["value"]["name"]] - for label in custom_labels - ] - - @staticmethod - def _custom_labels_to_representation( - custom_labels: AlertReceiveChannel.AlertGroupCustomLabels, - ) -> AlertGroupCustomLabels: - """ - Inverse of the _custom_labels_to_internal_value method above. - Fetches label names from DB cache, so the API response schema is consistent with other label endpoints. - """ - - from apps.labels.models import LabelKeyCache, LabelValueCache - - # get up-to-date label key names - label_key_names = { - k.id: k.name - for k in LabelKeyCache.objects.filter(id__in=[label[0] for label in custom_labels]).only("id", "name") - } - - # get up-to-date label value names - label_value_names = { - v.id: v.name - for v in LabelValueCache.objects.filter(id__in=[label[1] for label in custom_labels if label[1]]).only( - "id", "name" - ) - } - - return [ - { - "key": { - "id": key_id, - "name": label_key_names[key_id], - }, - "value": { - "id": value_id if value_id else None, - "name": label_value_names[value_id] if value_id else typing.cast(str, template), - }, - } - for key_id, value_id, template in custom_labels - if key_id in label_key_names and (value_id in label_value_names or not value_id) - ] class AlertReceiveChannelSerializer( @@ -223,7 +64,7 @@ class AlertReceiveChannelSerializer( connected_escalations_chains_count = serializers.SerializerMethodField() inbound_email = serializers.CharField(required=False) is_legacy = serializers.SerializerMethodField() - alert_group_labels = IntegrationAlertGroupLabelsSerializer(source="*", required=False) + alert_group_labels = IntegrationAlertGroupLabelsSerializer(required=False) # integration heartbeat is in PREFETCH_RELATED not by mistake. # With using of select_related ORM builds strange join @@ -297,10 +138,8 @@ class AlertReceiveChannelSerializer( if _integration.slug == integration: is_able_to_autoresolve = _integration.is_able_to_autoresolve - # pop associated labels and alert group labels, so they are not passed to AlertReceiveChannel.create labels = validated_data.pop("labels", None) - alert_group_labels = IntegrationAlertGroupLabelsSerializer.pop_alert_group_labels(validated_data) - + alert_group_labels = validated_data.pop("alert_group_labels", None) try: instance = AlertReceiveChannel.create( **validated_data, @@ -311,22 +150,17 @@ class AlertReceiveChannelSerializer( except AlertReceiveChannel.DuplicateDirectPagingError: raise BadRequest(detail=AlertReceiveChannel.DuplicateDirectPagingError.DETAIL) - # Create label associations first, then update alert group labels + # Create label associations first, then update inheritable labels self.update_labels_association_if_needed(labels, instance, organization) - instance = IntegrationAlertGroupLabelsSerializer.update(instance, alert_group_labels) + if alert_group_labels: + instance.alert_group_labels = alert_group_labels return instance def update(self, instance, validated_data): - # update associated labels labels = validated_data.pop("labels", None) - self.update_labels_association_if_needed(labels, instance, self.context["request"].auth.organization) - - # update alert group labels - instance = IntegrationAlertGroupLabelsSerializer.update( - instance, IntegrationAlertGroupLabelsSerializer.pop_alert_group_labels(validated_data) - ) - + organization = self.context["request"].auth.organization + self.update_labels_association_if_needed(labels, instance, organization) try: return super().update(instance, validated_data) except AlertReceiveChannel.DuplicateDirectPagingError: diff --git a/engine/apps/api/serializers/channel_filter.py b/engine/apps/api/serializers/channel_filter.py index 7815a584..b9239d6d 100644 --- a/engine/apps/api/serializers/channel_filter.py +++ b/engine/apps/api/serializers/channel_filter.py @@ -3,12 +3,12 @@ import typing from rest_framework import serializers from apps.alerts.models import AlertReceiveChannel, ChannelFilter, EscalationChain +from apps.api.serializers.alert_receive_channel import valid_jinja_template_for_serializer_method_field from apps.base.messaging import get_messaging_backend_from_id from apps.telegram.models import TelegramToOrganizationConnector from common.api_helpers.custom_fields import OrganizationFilteredPrimaryKeyRelatedField from common.api_helpers.exceptions import BadRequest from common.api_helpers.mixins import EagerLoadingMixin -from common.api_helpers.utils import valid_jinja_template_for_serializer_method_field from common.jinja_templater.apply_jinja_template import JinjaTemplateError from common.utils import is_regex_valid diff --git a/engine/apps/api/tests/test_alert_receive_channel.py b/engine/apps/api/tests/test_alert_receive_channel.py index 4855a87c..989171db 100644 --- a/engine/apps/api/tests/test_alert_receive_channel.py +++ b/engine/apps/api/tests/test_alert_receive_channel.py @@ -9,7 +9,6 @@ from rest_framework.test import APIClient from apps.alerts.models import AlertReceiveChannel, EscalationPolicy from apps.api.permissions import LegacyAccessControlRole -from apps.labels.models import LabelKeyCache, LabelValueCache @pytest.fixture() @@ -1384,49 +1383,23 @@ def test_update_alert_receive_channel_labels_duplicate_key( def test_alert_group_labels_get( make_organization_and_user_with_plugin_token, make_alert_receive_channel, - make_label_key_and_value, make_integration_label_association, make_user_auth_headers, ): organization, user, token = make_organization_and_user_with_plugin_token() alert_receive_channel = make_alert_receive_channel(organization) - label_key, label_value = make_label_key_and_value(organization) - label_key_1, _ = make_label_key_and_value(organization) client = APIClient() url = reverse("api-internal:alert_receive_channel-detail", kwargs={"pk": alert_receive_channel.public_primary_key}) response = client.get(url, **make_user_auth_headers(user, token)) assert response.status_code == status.HTTP_200_OK - assert response.json()["alert_group_labels"] == {"inheritable": {}, "custom": [], "template": None} + assert response.json()["alert_group_labels"] == {"inheritable": {}} label = make_integration_label_association(organization, alert_receive_channel) - - template = "{{ payload.labels | tojson }}" - alert_receive_channel.alert_group_labels_template = template - - alert_receive_channel.alert_group_labels_custom = [ - (label_key.id, label_value.id, None), - (label_key_1.id, None, "{{ payload.foo }}"), - ] - alert_receive_channel.save(update_fields=["alert_group_labels_custom", "alert_group_labels_template"]) - response = client.get(url, **make_user_auth_headers(user, token)) assert response.status_code == status.HTTP_200_OK - assert response.json()["alert_group_labels"] == { - "inheritable": {label.key_id: True}, - "custom": [ - { - "key": {"id": label_key.id, "name": label_key.name}, - "value": {"id": label_value.id, "name": label_value.name}, - }, - { - "key": {"id": label_key_1.id, "name": label_key_1.name}, - "value": {"id": None, "name": "{{ payload.foo }}"}, - }, - ], - "template": template, - } + assert response.json()["alert_group_labels"] == {"inheritable": {label.key_id: True}} @pytest.mark.django_db @@ -1440,75 +1413,14 @@ def test_alert_group_labels_put( alert_receive_channel = make_alert_receive_channel(organization) label_1 = make_integration_label_association(organization, alert_receive_channel) label_2 = make_integration_label_association(organization, alert_receive_channel, inheritable=False) - label_3 = make_integration_label_association(organization, alert_receive_channel, inheritable=False) - - custom = [ - # plain label - { - "key": {"id": label_2.key.id, "name": label_2.key.name}, - "value": {"id": label_2.value.id, "name": label_2.value.name}, - }, - # plain label not present in DB cache - { - "key": {"id": "hello", "name": "world"}, - "value": {"id": "foo", "name": "bar"}, - }, - # templated label - { - "key": {"id": label_3.key.id, "name": label_3.key.name}, - "value": {"id": None, "name": "{{ payload.foo }}"}, - }, - ] - template = "{{ payload.labels | tojson }}" # advanced template client = APIClient() url = reverse("api-internal:alert_receive_channel-detail", kwargs={"pk": alert_receive_channel.public_primary_key}) - data = { - "alert_group_labels": { - "inheritable": {label_1.key_id: False, label_2.key_id: True, label_3.key_id: False}, - "custom": custom, - "template": template, - } - } + data = {"alert_group_labels": {"inheritable": {label_1.key_id: False, label_2.key_id: True}}} response = client.put(url, data, format="json", **make_user_auth_headers(user, token)) assert response.status_code == status.HTTP_200_OK - assert response.json()["alert_group_labels"] == { - "inheritable": {label_1.key_id: False, label_2.key_id: True, label_3.key_id: False}, - "custom": custom, - "template": template, - } - - alert_receive_channel.refresh_from_db() - assert alert_receive_channel.alert_group_labels_custom == [ - [label_2.key_id, label_2.value_id, None], - ["hello", "foo", None], - [label_3.key_id, None, "{{ payload.foo }}"], - ] - assert alert_receive_channel.alert_group_labels_template == template - - # check label keys & values are created - key = LabelKeyCache.objects.filter(id="hello", name="world", organization=organization).first() - assert key is not None - assert LabelValueCache.objects.filter(key=key, id="foo", name="bar").exists() - - -@pytest.mark.django_db -def test_alert_group_labels_put_none( - make_organization_and_user_with_plugin_token, - make_alert_receive_channel, - make_user_auth_headers, -): - organization, user, token = make_organization_and_user_with_plugin_token() - alert_receive_channel = make_alert_receive_channel(organization) - - client = APIClient() - url = reverse("api-internal:alert_receive_channel-detail", kwargs={"pk": alert_receive_channel.public_primary_key}) - response = client.put(url, {"verbal_name": "123"}, format="json", **make_user_auth_headers(user, token)) - - assert response.status_code == status.HTTP_200_OK - assert response.json()["verbal_name"] == "123" - assert response.json()["alert_group_labels"] == {"inheritable": {}, "custom": [], "template": None} + assert response.json()["alert_group_labels"] == {"inheritable": {label_1.key_id: False, label_2.key_id: True}} @pytest.mark.django_db @@ -1516,11 +1428,7 @@ def test_alert_group_labels_post(alert_receive_channel_internal_api_setup, make_ user, token, _ = alert_receive_channel_internal_api_setup labels = [{"key": {"id": "test", "name": "test"}, "value": {"id": "123", "name": "123"}}] - alert_group_labels = { - "inheritable": {"test": False}, - "custom": [{"key": {"id": "test", "name": "test"}, "value": {"id": "123", "name": "123"}}], - "template": "{{ payload.labels | tojson }}", - } + alert_group_labels = {"inheritable": {"test": False}} data = { "integration": AlertReceiveChannel.INTEGRATION_GRAFANA, "team": None, @@ -1535,7 +1443,3 @@ def test_alert_group_labels_post(alert_receive_channel_internal_api_setup, make_ assert response.status_code == status.HTTP_201_CREATED assert response.json()["labels"] == labels assert response.json()["alert_group_labels"] == alert_group_labels - - alert_receive_channel = AlertReceiveChannel.objects.get(public_primary_key=response.json()["id"]) - assert alert_receive_channel.alert_group_labels_custom == [["test", "123", None]] - assert alert_receive_channel.alert_group_labels_template == "{{ payload.labels | tojson }}" diff --git a/engine/apps/api/tests/test_alert_receive_channel_template.py b/engine/apps/api/tests/test_alert_receive_channel_template.py index f494d776..111696cd 100644 --- a/engine/apps/api/tests/test_alert_receive_channel_template.py +++ b/engine/apps/api/tests/test_alert_receive_channel_template.py @@ -337,37 +337,6 @@ def test_preview_alert_receive_channel_backend_templater( assert response.json() == {"preview": "title: alert!"} -@pytest.mark.django_db -def test_preview_alert_group_labels( - make_organization_and_user_with_plugin_token, - make_user_auth_headers, - make_alert_receive_channel, - make_channel_filter, - make_alert_group, - make_alert, -): - organization, user, token = make_organization_and_user_with_plugin_token() - alert_receive_channel = make_alert_receive_channel(organization) - default_channel_filter = make_channel_filter(alert_receive_channel, is_default=True) - alert_group = make_alert_group(alert_receive_channel, channel_filter=default_channel_filter) - make_alert(alert_group=alert_group, raw_request_data={"labels": {"1": "2"}}) - - client = APIClient() - url = reverse( - "api-internal:alert_receive_channel-preview-template", - kwargs={"pk": alert_receive_channel.public_primary_key}, - ) - - data = { - "template_body": "{{ payload.labels | tojson }}", - "template_name": "alert_group_labels", - } - response = client.post(url, format="json", data=data, **make_user_auth_headers(user, token)) - - assert response.status_code == status.HTTP_200_OK - assert response.json() == {"preview": '{"1": "2"}'} - - @pytest.mark.django_db def test_update_alert_receive_channel_templates( make_organization_and_user_with_plugin_token, diff --git a/engine/apps/labels/alert_group_labels.py b/engine/apps/labels/alert_group_labels.py deleted file mode 100644 index df735ca1..00000000 --- a/engine/apps/labels/alert_group_labels.py +++ /dev/null @@ -1,158 +0,0 @@ -import json -import logging -import typing - -from apps.labels.utils import is_labels_feature_enabled -from common.jinja_templater import apply_jinja_template -from common.jinja_templater.apply_jinja_template import JinjaTemplateError, JinjaTemplateWarning - -if typing.TYPE_CHECKING: - from apps.alerts.models import AlertGroup, AlertReceiveChannel - - -logger = logging.getLogger(__name__) - - -# What can be used as a label key/value coming out from the template -LABEL_VALUE_TYPES = (str, int, float, bool) - - -def assign_labels( - alert_group: "AlertGroup", alert_receive_channel: "AlertReceiveChannel", raw_request_data: typing.Any -) -> None: - from apps.labels.models import AlertGroupAssociatedLabel - - if not is_labels_feature_enabled(alert_receive_channel.organization): - return - - # inherit labels from the integration - labels = { - label.key.name: label.value.name - for label in alert_receive_channel.labels.filter(inheritable=True).select_related("key", "value") - } - - # apply custom labels - labels.update(_custom_labels(alert_receive_channel, raw_request_data)) - - # apply template labels - labels.update(_template_labels(alert_receive_channel, raw_request_data)) - - # create associated labels - alert_group_labels = [ - AlertGroupAssociatedLabel( - alert_group=alert_group, - organization=alert_receive_channel.organization, - key_name=key, - value_name=value, - ) - for key, value in labels.items() - ] - # sort associated labels by key and value - alert_group_labels.sort(key=lambda label: (label.key_name, label.value_name)) - # bulk create associated labels - AlertGroupAssociatedLabel.objects.bulk_create(alert_group_labels) - - -def _custom_labels(alert_receive_channel: "AlertReceiveChannel", raw_request_data: typing.Any) -> dict[str, str]: - from apps.labels.models import MAX_VALUE_NAME_LENGTH, LabelKeyCache, LabelValueCache - - # fetch up-to-date label key names - label_key_names = { - k.id: k.name - for k in LabelKeyCache.objects.filter( - id__in=[label[0] for label in alert_receive_channel.alert_group_labels_custom] - ).only("id", "name") - } - - # fetch up-to-date label value names - label_value_names = { - v.id: v.name - for v in LabelValueCache.objects.filter( - id__in=[label[1] for label in alert_receive_channel.alert_group_labels_custom if label[1]] - ).only("id", "name") - } - - rendered_labels = {} - for label in alert_receive_channel.alert_group_labels_custom: - key_id, value_id, template = label - - if key_id in label_key_names: - key = label_key_names[key_id] - else: - logger.warning("Label key cache not found. %s", key_id) - continue - - if value_id: - if value_id in label_value_names: - rendered_labels[key] = label_value_names[value_id] - else: - logger.warning("Label value cache not found. %s", value_id) - continue - else: - try: - rendered_labels[key] = apply_jinja_template(template, raw_request_data) - except (JinjaTemplateError, JinjaTemplateWarning) as e: - logger.warning("Failed to apply template. %s", e.fallback_message) - continue - - labels = {} - for key in rendered_labels: - value = rendered_labels[key] - - # check value length - if len(value) > MAX_VALUE_NAME_LENGTH: - logger.warning("Template result value is too long. %s", value) - continue - - labels[key] = value - - return labels - - -def _template_labels(alert_receive_channel: "AlertReceiveChannel", raw_request_data: typing.Any) -> dict[str, str]: - from apps.labels.models import MAX_KEY_NAME_LENGTH, MAX_VALUE_NAME_LENGTH - - if not alert_receive_channel.alert_group_labels_template: - return {} - - try: - rendered = apply_jinja_template(alert_receive_channel.alert_group_labels_template, raw_request_data) - except (JinjaTemplateError, JinjaTemplateWarning) as e: - logger.warning("Failed to apply template. %s", e.fallback_message) - return {} - - try: - rendered_labels = json.loads(rendered) - except (TypeError, json.JSONDecodeError): - logger.warning("Failed to parse template result. %s", rendered) - return {} - - if not isinstance(rendered_labels, dict): - logger.warning("Template result is not a dict. %s", rendered_labels) - return {} - - labels = {} - for key in rendered_labels: - value = rendered_labels[key] - - # check value type - if not isinstance(value, LABEL_VALUE_TYPES): - logger.warning("Template result value has invalid type. %s", value) - continue - - # convert value to string - value = str(value) - - # check key length - if len(key) > MAX_KEY_NAME_LENGTH: - logger.warning("Template result key is too long. %s", key) - continue - - # check value length - if len(value) > MAX_VALUE_NAME_LENGTH: - logger.warning("Template result value is too long. %s", value) - continue - - labels[key] = value - - return labels diff --git a/engine/apps/labels/models.py b/engine/apps/labels/models.py index 14cd446c..28947e75 100644 --- a/engine/apps/labels/models.py +++ b/engine/apps/labels/models.py @@ -10,13 +10,9 @@ if typing.TYPE_CHECKING: from apps.user_management.models import Organization -MAX_KEY_NAME_LENGTH = 200 -MAX_VALUE_NAME_LENGTH = 200 - - class LabelKeyCache(models.Model): id = models.CharField(primary_key=True, editable=False, max_length=36) - name = models.CharField(max_length=MAX_KEY_NAME_LENGTH) + name = models.CharField(max_length=200) organization = models.ForeignKey("user_management.Organization", on_delete=models.CASCADE) last_synced = models.DateTimeField(auto_now=True) @@ -27,7 +23,7 @@ class LabelKeyCache(models.Model): class LabelValueCache(models.Model): id = models.CharField(primary_key=True, editable=False, max_length=36) - name = models.CharField(max_length=MAX_VALUE_NAME_LENGTH) + name = models.CharField(max_length=200) key = models.ForeignKey("labels.LabelKeyCache", on_delete=models.CASCADE, related_name="values") last_synced = models.DateTimeField(auto_now=True) @@ -133,8 +129,8 @@ class AlertGroupAssociatedLabel(models.Model): "user_management.Organization", on_delete=models.CASCADE, related_name="alert_group_labels" ) - key_name = models.CharField(max_length=MAX_KEY_NAME_LENGTH) - value_name = models.CharField(max_length=MAX_VALUE_NAME_LENGTH) + key_name = models.CharField(max_length=200) + value_name = models.CharField(max_length=200) class Meta: constraints = [ diff --git a/engine/apps/labels/tests/test_alert_group.py b/engine/apps/labels/tests/test_alert_group.py index 32144377..a5ac35ca 100644 --- a/engine/apps/labels/tests/test_alert_group.py +++ b/engine/apps/labels/tests/test_alert_group.py @@ -3,13 +3,9 @@ from unittest import mock import pytest from apps.alerts.models import Alert -from apps.labels.models import MAX_KEY_NAME_LENGTH, MAX_VALUE_NAME_LENGTH - -TOO_LONG_KEY_NAME = "k" * (MAX_KEY_NAME_LENGTH + 1) -TOO_LONG_VALUE_NAME = "v" * (MAX_VALUE_NAME_LENGTH + 1) -@mock.patch("apps.labels.alert_group_labels.is_labels_feature_enabled", return_value=False) +@mock.patch("apps.labels.utils.is_labels_feature_enabled", return_value=False) @pytest.mark.django_db def test_assign_labels_feature_flag_disabled( _, make_organization, make_alert_receive_channel, make_integration_label_association @@ -32,59 +28,22 @@ def test_assign_labels_feature_flag_disabled( @pytest.mark.django_db -def test_assign_labels( - make_organization, - make_alert_receive_channel, - make_label_key_and_value, - make_label_key, - make_integration_label_association, -): +def test_assign_labels(make_organization, make_alert_receive_channel, make_integration_label_association): organization = make_organization() + alert_receive_channel = make_alert_receive_channel(organization) + label = make_integration_label_association(organization, alert_receive_channel) + make_integration_label_association(organization, alert_receive_channel, inheritable=False) - # create label repo labels - label_key, label_value = make_label_key_and_value(organization, key_name="a", value_name="b") - label_key_1 = make_label_key(organization=organization, key_name="c") - label_key_2 = make_label_key(organization=organization) - label_key_3 = make_label_key(organization=organization) - - # create alert receive channel with all 3 types of labels - alert_receive_channel = make_alert_receive_channel( - organization, - alert_group_labels_custom=[ - [label_key.id, label_value.id, None], # plain label - ["nonexistent", label_value.id, None], # plain label with nonexistent key ID - [label_key_2.id, "nonexistent", None], # plain label with nonexistent value ID - [label_key_1.id, None, "{{ payload.c }}"], # templated label - [label_key_3.id, None, TOO_LONG_VALUE_NAME], # templated label too long - ], - alert_group_labels_template="{{ payload.advanced_template | tojson }}", - ) - make_integration_label_association(organization, alert_receive_channel, key_name="e", value_name="f") - - # create alert group alert = Alert.create( title="the title", message="the message", alert_receive_channel=alert_receive_channel, - raw_request_data={ - "c": "d", - "advanced_template": { - "g": 123, - "too_long": TOO_LONG_VALUE_NAME, - TOO_LONG_KEY_NAME: "too_long", - "invalid_type": {"test": "test"}, - }, - "extra": "hi", - }, + raw_request_data={}, integration_unique_data={}, image_url=None, link_to_upstream_details=None, ) - # check alert group labels are assigned correctly, in the lexicographical order - assert [(label.key_name, label.value_name) for label in alert.group.labels.all()] == [ - ("a", "b"), - ("c", "d"), - ("e", "f"), - ("g", "123"), - ] + assert alert.group.labels.count() == 1 + assert alert.group.labels.first().key_name == label.key.name + assert alert.group.labels.first().value_name == label.value.name diff --git a/engine/apps/labels/utils.py b/engine/apps/labels/utils.py index bacf86c0..46b0583a 100644 --- a/engine/apps/labels/utils.py +++ b/engine/apps/labels/utils.py @@ -1,16 +1,13 @@ -import logging import typing from django.apps import apps # noqa: I251 from django.conf import settings if typing.TYPE_CHECKING: - from apps.alerts.models import AlertGroup + from apps.alerts.models import AlertGroup, AlertReceiveChannel from apps.labels.models import AssociatedLabel from apps.user_management.models import Organization -logger = logging.getLogger(__name__) - LABEL_OUTDATED_TIMEOUT_MINUTES = 30 ASSOCIATED_MODEL_NAME = "AssociatedLabel" @@ -57,10 +54,35 @@ def is_labels_feature_enabled(organization: "Organization") -> bool: ) -def get_label_verbal(obj: typing.Any) -> dict[str, str]: - return {label.key.name: label.value.name for label in obj.labels.all().select_related("key", "value")} +def assign_labels(alert_group: "AlertGroup", alert_receive_channel: "AlertReceiveChannel") -> None: + from apps.labels.models import AlertGroupAssociatedLabel + + if not is_labels_feature_enabled(alert_receive_channel.organization): + return + + # inherit labels from the integration + alert_group_labels = [ + AlertGroupAssociatedLabel( + alert_group=alert_group, + organization=alert_receive_channel.organization, + key_name=label.key.name, + value_name=label.value.name, + ) + for label in alert_receive_channel.labels.filter(inheritable=True).select_related("key", "value") + ] + AlertGroupAssociatedLabel.objects.bulk_create(alert_group_labels) -def get_alert_group_label_verbal(alert_group: "AlertGroup") -> dict[str, str]: - """This is different from get_label_verbal because alert group labels store key/value names, not IDs""" +def get_label_verbal(labelable) -> typing.Dict[str, str]: + """ + label_verbal returns dict of labels' key and values names for the given object + """ + return {label.key.name: label.value.name for label in labelable.labels.all().select_related("key", "value")} + + +def get_alert_group_label_verbal(alert_group: "AlertGroup") -> typing.Dict[str, str]: + """ + get_alert_group_label_verbal returns dict of labels' key and values names for the given alert group. + It's different from get_label_verbal, because AlertGroupAssociated labels store key/value_name, not key/value_id + """ return {label.key_name: label.value_name for label in alert_group.labels.all()} diff --git a/engine/apps/public_api/serializers/routes.py b/engine/apps/public_api/serializers/routes.py index bbe9ff55..1907c9e9 100644 --- a/engine/apps/public_api/serializers/routes.py +++ b/engine/apps/public_api/serializers/routes.py @@ -1,10 +1,10 @@ from rest_framework import fields, serializers from apps.alerts.models import AlertReceiveChannel, ChannelFilter, EscalationChain +from apps.api.serializers.alert_receive_channel import valid_jinja_template_for_serializer_method_field from apps.base.messaging import get_messaging_backend_from_id, get_messaging_backends from common.api_helpers.custom_fields import OrganizationFilteredPrimaryKeyRelatedField from common.api_helpers.exceptions import BadRequest -from common.api_helpers.utils import valid_jinja_template_for_serializer_method_field from common.jinja_templater.apply_jinja_template import JinjaTemplateError from common.ordered_model.serializer import OrderedModelSerializer from common.utils import is_regex_valid diff --git a/engine/common/api_helpers/mixins.py b/engine/common/api_helpers/mixins.py index 5f15f137..4204095f 100644 --- a/engine/common/api_helpers/mixins.py +++ b/engine/common/api_helpers/mixins.py @@ -248,7 +248,6 @@ ACKNOWLEDGE_CONDITION = "acknowledge_condition" GROUPING_ID = "grouping_id" SOURCE_LINK = "source_link" ROUTE = "route" -ALERT_GROUP_LABELS = "alert_group_labels" NOTIFICATION_CHANNEL_TO_TEMPLATER_MAP = { SLACK: AlertSlackTemplater, @@ -265,15 +264,9 @@ for backend_id, backend in get_messaging_backends(): NOTIFICATION_CHANNEL_TO_TEMPLATER_MAP[backend.slug] = backend.get_templater_class() APPEARANCE_TEMPLATE_NAMES = [TITLE, MESSAGE, IMAGE_URL] -BEHAVIOUR_TEMPLATE_NAMES = [ - RESOLVE_CONDITION, - ACKNOWLEDGE_CONDITION, - GROUPING_ID, - SOURCE_LINK, - ROUTE, - ALERT_GROUP_LABELS, -] -ALL_TEMPLATE_NAMES = APPEARANCE_TEMPLATE_NAMES + BEHAVIOUR_TEMPLATE_NAMES +BEHAVIOUR_TEMPLATE_NAMES = [RESOLVE_CONDITION, ACKNOWLEDGE_CONDITION, GROUPING_ID, SOURCE_LINK] +ROUTE_TEMPLATE_NAMES = [ROUTE] +ALL_TEMPLATE_NAMES = APPEARANCE_TEMPLATE_NAMES + BEHAVIOUR_TEMPLATE_NAMES + ROUTE_TEMPLATE_NAMES class PreviewTemplateException(Exception): @@ -333,6 +326,11 @@ class PreviewTemplateMixin: templated_attr = apply_jinja_template(template_body, payload=alert_to_template.raw_request_data) except (JinjaTemplateError, JinjaTemplateWarning) as e: return Response({"preview": e.fallback_message}, status.HTTP_200_OK) + elif attr_name in ROUTE_TEMPLATE_NAMES: + try: + templated_attr = apply_jinja_template(template_body, payload=alert_to_template.raw_request_data) + except (JinjaTemplateError, JinjaTemplateWarning) as e: + return Response({"preview": e.fallback_message}, status.HTTP_200_OK) else: templated_attr = None response = {"preview": templated_attr} @@ -348,6 +346,8 @@ class PreviewTemplateMixin: destination = None if template_param.startswith(tuple(BEHAVIOUR_TEMPLATE_NAMES)): attr_name = template_param + if template_param.startswith(tuple(ROUTE_TEMPLATE_NAMES)): + attr_name = template_param elif template_param.startswith(tuple(NOTIFICATION_CHANNEL_OPTIONS)): for notification_channel in NOTIFICATION_CHANNEL_OPTIONS: if template_param.startswith(notification_channel): diff --git a/engine/common/api_helpers/utils.py b/engine/common/api_helpers/utils.py index 0ec76200..1233c4b0 100644 --- a/engine/common/api_helpers/utils.py +++ b/engine/common/api_helpers/utils.py @@ -14,8 +14,6 @@ from rest_framework.request import Request from apps.schedules.ical_utils import fetch_ical_file from common.api_helpers.exceptions import BadRequest -from common.jinja_templater import apply_jinja_template -from common.jinja_templater.apply_jinja_template import JinjaTemplateWarning from common.timezones import raise_exception_if_not_valid_timezone @@ -167,12 +165,3 @@ def check_phone_number_is_valid(phone_number): def serialize_datetime_as_utc_timestamp(dt: datetime.datetime) -> str: return dt.strftime("%Y-%m-%dT%H:%M:%S.%fZ") - - -def valid_jinja_template_for_serializer_method_field(template): - for _, val in template.items(): - try: - apply_jinja_template(val, payload={}) - except JinjaTemplateWarning: - # Suppress warnings, template may be valid with payload - pass diff --git a/engine/conftest.py b/engine/conftest.py index 1698befe..4f30e34a 100644 --- a/engine/conftest.py +++ b/engine/conftest.py @@ -954,13 +954,7 @@ def webhook_preset_api_setup(): @pytest.fixture def make_label_key(): - def _make_label_key(organization, key_id=None, key_name=None, **kwargs): - if key_id is not None: - kwargs["id"] = key_id - - if key_name is not None: - kwargs["name"] = key_name - + def _make_label_key(organization, **kwargs): return LabelKeyFactory(organization=organization, **kwargs) return _make_label_key @@ -968,13 +962,7 @@ def make_label_key(): @pytest.fixture def make_label_value(): - def _make_label_value(key, value_id=None, value_name=None, **kwargs): - if value_id is not None: - kwargs["id"] = value_id - - if value_name is not None: - kwargs["name"] = value_name - + def _make_label_value(key, **kwargs): return LabelValueFactory(key=key, **kwargs) return _make_label_value @@ -982,9 +970,9 @@ def make_label_value(): @pytest.fixture def make_label_key_and_value(make_label_key, make_label_value): - def _make_label_key_and_value(organization, key_id=None, key_name=None, value_id=None, value_name=None): - key = make_label_key(organization=organization, key_id=key_id, key_name=key_name) - value = make_label_value(key=key, value_id=value_id, value_name=value_name) + def _make_label_key_and_value(organization): + key = make_label_key(organization=organization) + value = make_label_value(key=key) return key, value return _make_label_key_and_value @@ -992,12 +980,8 @@ def make_label_key_and_value(make_label_key, make_label_value): @pytest.fixture def make_integration_label_association(make_label_key_and_value): - def _make_integration_label_association( - organization, alert_receive_channel, key_id=None, key_name=None, value_id=None, value_name=None, **kwargs - ): - key, value = make_label_key_and_value( - organization, key_id=key_id, key_name=key_name, value_id=value_id, value_name=value_name - ) + def _make_integration_label_association(organization, alert_receive_channel, **kwargs): + key, value = make_label_key_and_value(organization) return AlertReceiveChannelAssociatedLabelFactory( alert_receive_channel=alert_receive_channel, organization=organization, key=key, value=value, **kwargs ) diff --git a/grafana-plugin/package.json b/grafana-plugin/package.json index 0178bc57..3b7bda98 100644 --- a/grafana-plugin/package.json +++ b/grafana-plugin/package.json @@ -115,7 +115,7 @@ "@grafana/data": "^9.2.4", "@grafana/faro-web-sdk": "^1.0.0-beta4", "@grafana/faro-web-tracing": "^1.0.0-beta4", - "@grafana/labels": "1.3.4", + "@grafana/labels": "~1.2.1", "@grafana/runtime": "9.3.0-beta1", "@grafana/ui": "^9.4.7", "@opentelemetry/api": "^1.3.0", diff --git a/grafana-plugin/playwright.config.ts b/grafana-plugin/playwright.config.ts index 835639e5..689cd6cc 100644 --- a/grafana-plugin/playwright.config.ts +++ b/grafana-plugin/playwright.config.ts @@ -1,4 +1,4 @@ -import { PlaywrightTestProject, defineConfig, devices } from '@playwright/test'; +import { PlaywrightTestConfig, PlaywrightTestProject, defineConfig, devices } from '@playwright/test'; import path from 'path'; /** diff --git a/grafana-plugin/src/components/LabelsTooltipBadge/LabelsTooltipBadge.tsx b/grafana-plugin/src/components/LabelsTooltipBadge/LabelsTooltipBadge.tsx index a2b9262d..5ec9d084 100644 --- a/grafana-plugin/src/components/LabelsTooltipBadge/LabelsTooltipBadge.tsx +++ b/grafana-plugin/src/components/LabelsTooltipBadge/LabelsTooltipBadge.tsx @@ -22,7 +22,7 @@ const LabelsTooltipBadge: FC = ({ labels, onClick }) => {labels.map((label) => ( - + - - - - - - {customLabelIndexToShowTemplateEditor !== undefined && ( - setCustomLabelIndexToShowTemplateEditor(undefined)} - onUpdateTemplates={({ alert_group_labels }) => { - const newCustom = [...alertGroupLabels.custom]; - newCustom[customLabelIndexToShowTemplateEditor].value.name = alert_group_labels; - - setAlertGroupLabels({ - ...alertGroupLabels, - custom: newCustom, - }); - - setCustomLabelIndexToShowTemplateEditor(undefined); - }} - /> - )} - {showTemplateEditor && ( - setShowTemplateEditor(false)} - onUpdateTemplates={({ alert_group_labels }) => { - setAlertGroupLabels({ - ...alertGroupLabels, - template: alert_group_labels, - }); - - setShowTemplateEditor(undefined); - }} - /> - )} - + )} + +
+ + + + +
+ + ); }); -interface CustomLabelsProps { - alertGroupLabels: AlertReceiveChannel['alert_group_labels']; - onChange: (value: AlertReceiveChannel['alert_group_labels']) => void; - onShowTemplateEditor: (index: number) => void; -} - -const CustomLabels = (props: CustomLabelsProps) => { - const { alertGroupLabels, onChange, onShowTemplateEditor } = props; - - const { labelsStore } = useStore(); - - const handlePlainLabelAdd = () => { - onChange({ - ...alertGroupLabels, - custom: [ - ...alertGroupLabels.custom, - { - key: { id: undefined, name: undefined }, - value: { id: undefined, name: undefined }, - }, - ], - }); - }; - const handleTemplatedLabelAdd = () => { - onChange({ - ...alertGroupLabels, - custom: [ - ...alertGroupLabels.custom, - { - key: { id: undefined, name: undefined }, - value: { id: null, name: undefined }, // id = null means it's a templated value - }, - ], - }); - }; - - const cachedOnLoadKeys = useCallback(() => { - let result = undefined; - return async (search?: string) => { - if (!result) { - try { - result = await labelsStore.loadKeys(); - } catch (error) { - openErrorNotification('There was an error processing your request. Please try again'); - } - } - - return result.filter((k) => k.name.toLowerCase().includes(search.toLowerCase())); - }; - }, []); - - const cachedOnLoadValuesForKey = useCallback(() => { - let result = undefined; - return async (key: string, search?: string) => { - if (!result) { - try { - const { values } = await labelsStore.loadValuesForKey(key, search); - result = values; - } catch (error) { - openErrorNotification('There was an error processing your request. Please try again'); - } - } - - return result.filter((k) => k.name.toLowerCase().includes(search.toLowerCase())); - }; - }, []); - - return ( - - - - - { - if (res?.response?.status === 409) { - openErrorNotification(`Duplicate values are not allowed`); - } else { - openErrorNotification('An error has occurred. Please try again'); - } - }} - renderValue={(option, index, renderValueDefault) => { - if (option.value.id === null) { - return ( - { - onShowTemplateEditor(index); - }} - /> - } - onChange={(e: ChangeEvent) => { - const newCustom = [...alertGroupLabels.custom]; - newCustom[index].value.name = e.currentTarget.value; - - onChange({ ...alertGroupLabels, custom: newCustom }); - }} - /> - ); - } else { - return renderValueDefault(option, index); - } - }} - onDataUpdate={(value) => { - onChange({ - ...alertGroupLabels, - custom: value, - }); - }} - /> - - - - - } - > - - - - ); -}; - export default IntegrationLabelsForm; diff --git a/grafana-plugin/src/containers/IntegrationTemplate/IntegrationTemplate.tsx b/grafana-plugin/src/containers/IntegrationTemplate/IntegrationTemplate.tsx index 98e98b71..7376ee96 100644 --- a/grafana-plugin/src/containers/IntegrationTemplate/IntegrationTemplate.tsx +++ b/grafana-plugin/src/containers/IntegrationTemplate/IntegrationTemplate.tsx @@ -5,7 +5,6 @@ import cn from 'classnames/bind'; import { debounce } from 'lodash-es'; import { observer } from 'mobx-react'; -import { templateForEdit } from 'components/AlertTemplates/AlertTemplatesForm.config'; import { TemplateForEdit } from 'components/AlertTemplates/CommonAlertTemplatesForm.config'; import CheatSheet from 'components/CheatSheet/CheatSheet'; import { @@ -39,7 +38,7 @@ interface IntegrationTemplateProps { templates: AlertTemplatesDTO[]; onHide: () => void; onUpdateTemplates: (values: any) => void; - onUpdateRoute?: (values: any, channelFilterId?: ChannelFilter['id']) => void; + onUpdateRoute: (values: any, channelFilterId?: ChannelFilter['id']) => void; } const IntegrationTemplate = observer((props: IntegrationTemplateProps) => { @@ -54,13 +53,11 @@ const IntegrationTemplate = observer((props: IntegrationTemplateProps) => { const [isRecentAlertGroupExisting, setIsRecentAlertGroupExisting] = useState(false); useEffect(() => { - if (templateForEdit[template.name]) { - const locationParams: any = { template: template.name }; - if (template.isRoute) { - locationParams.routeId = channelFilterId; - } - LocationHelper.update(locationParams, 'partial'); + const locationParams: any = { template: template.name }; + if (template.isRoute) { + locationParams.routeId = channelFilterId; } + LocationHelper.update(locationParams, 'partial'); }, []); useEffect(() => { diff --git a/grafana-plugin/src/containers/Labels/Labels.tsx b/grafana-plugin/src/containers/Labels/Labels.tsx index 60fc1324..21c4d46a 100644 --- a/grafana-plugin/src/containers/Labels/Labels.tsx +++ b/grafana-plugin/src/containers/Labels/Labels.tsx @@ -1,6 +1,6 @@ import React, { forwardRef, useCallback, useImperativeHandle, useState } from 'react'; -import { ServiceLabels, ServiceLabelsProps } from '@grafana/labels'; +import ServiceLabels, { ServiceLabelsProps } from '@grafana/labels'; import { Field } from '@grafana/ui'; import cn from 'classnames/bind'; import { isEmpty } from 'lodash-es'; diff --git a/grafana-plugin/src/models/alert_receive_channel/alert_receive_channel.types.ts b/grafana-plugin/src/models/alert_receive_channel/alert_receive_channel.types.ts index fd3ea806..ec366bf2 100644 --- a/grafana-plugin/src/models/alert_receive_channel/alert_receive_channel.types.ts +++ b/grafana-plugin/src/models/alert_receive_channel/alert_receive_channel.types.ts @@ -49,11 +49,7 @@ export interface AlertReceiveChannel { allow_delete: boolean; deleted?: boolean; labels: LabelKeyValue[]; - alert_group_labels: { - inheritable: Record; - custom: LabelKeyValue[]; - template: string; - }; + alert_group_labels: { inheritable: Record }; } export interface AlertReceiveChannelChoice { diff --git a/grafana-plugin/yarn.lock b/grafana-plugin/yarn.lock index 3f55fb20..2b225b09 100644 --- a/grafana-plugin/yarn.lock +++ b/grafana-plugin/yarn.lock @@ -1966,10 +1966,10 @@ "@opentelemetry/sdk-trace-web" "^1.8.0" "@opentelemetry/semantic-conventions" "^1.8.0" -"@grafana/labels@1.3.4": - version "1.3.4" - resolved "https://registry.yarnpkg.com/@grafana/labels/-/labels-1.3.4.tgz#8d9cdd215a80a1da1045d402c037be85d7efd6f5" - integrity sha512-YYCuLGvtrMz7KkbMc6qoNJQr6drDLo6mMI27LcqsTDMHCNO3uJWpzC1Q2Y9MIwctIuTFYhbgfLvIunEegCx6PQ== +"@grafana/labels@~1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@grafana/labels/-/labels-1.2.1.tgz#4113d584bf5cd826d011f957cb69c90bd0416ea8" + integrity sha512-Nlqqvjwh0MjWsqnfpYbKdYwByeKSmEpiit5mKd6Mnnbc5Hxb8ORIruMr40lTxxWLEnDfhENcAs6pvlBuIMG7tQ== dependencies: "@emotion/css" "^11.11.2" "@grafana/ui" "^10.0.0"