From b761fa7e6afdc33e8c8c3fef5e3bf57db2b2faa1 Mon Sep 17 00:00:00 2001 From: Ildar Iskhakov Date: Tue, 11 Apr 2023 10:56:33 +0800 Subject: [PATCH 01/13] Add json_dumps and regex_search filters to jinja2 --- engine/common/jinja_templater/filters.py | 14 ++++++++++++++ .../common/jinja_templater/jinja_template_env.py | 12 +++++++++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/engine/common/jinja_templater/filters.py b/engine/common/jinja_templater/filters.py index 1264aa680a..88b4797de6 100644 --- a/engine/common/jinja_templater/filters.py +++ b/engine/common/jinja_templater/filters.py @@ -37,3 +37,17 @@ def regex_match(pattern, value): return bool(re.match(value, pattern)) except (ValueError, AttributeError, TypeError): return None + + +def regex_search(pattern, value): + try: + return bool(re.search(value, pattern)) + except (ValueError, AttributeError, TypeError): + return None + + +def json_dumps(value): + try: + return json.dumps(value) + except (ValueError, AttributeError, TypeError): + return None diff --git a/engine/common/jinja_templater/jinja_template_env.py b/engine/common/jinja_templater/jinja_template_env.py index 32ceda6be0..f4d2d65a3e 100644 --- a/engine/common/jinja_templater/jinja_template_env.py +++ b/engine/common/jinja_templater/jinja_template_env.py @@ -3,7 +3,15 @@ from jinja2.exceptions import SecurityError from jinja2.sandbox import SandboxedEnvironment -from .filters import datetimeformat, iso8601_to_time, regex_match, regex_replace, to_pretty_json +from .filters import ( + datetimeformat, + iso8601_to_time, + json_dumps, + regex_match, + regex_replace, + regex_search, + to_pretty_json, +) def raise_security_exception(name): @@ -19,3 +27,5 @@ def raise_security_exception(name): jinja_template_env.globals["range"] = lambda *args: raise_security_exception("range") jinja_template_env.filters["regex_replace"] = regex_replace jinja_template_env.filters["regex_match"] = regex_match +jinja_template_env.filters["regex_search"] = regex_search +jinja_template_env.filters["json_dumps"] = json_dumps From c27b8dd18dceaa4f4220bd94c0b4ea96f5a8231b Mon Sep 17 00:00:00 2001 From: Ildar Iskhakov Date: Tue, 11 Apr 2023 10:57:23 +0800 Subject: [PATCH 02/13] Add new field routing_template, deprecate filtering_term and filtering_term_type --- .../0011_channelfilter_routing_template.py | 26 +++++++++++++++++++ engine/apps/alerts/models/channel_filter.py | 5 ++++ 2 files changed, 31 insertions(+) create mode 100644 engine/apps/alerts/migrations/0011_channelfilter_routing_template.py diff --git a/engine/apps/alerts/migrations/0011_channelfilter_routing_template.py b/engine/apps/alerts/migrations/0011_channelfilter_routing_template.py new file mode 100644 index 0000000000..e307ea6793 --- /dev/null +++ b/engine/apps/alerts/migrations/0011_channelfilter_routing_template.py @@ -0,0 +1,26 @@ +# Generated by Django 3.2.18 on 2023-04-11 02:48 + +from django.db import migrations, models + +def combine_names(apps, schema_editor): + ChannelFilter = apps.get_model("alerts", "ChannelFilter") + for route in ChannelFilter.objects.all(): + if route.filtering_term_type == ChannelFilter.FILTERING_TERM_TYPE_REGEX: + route.routing_template = f'{{ payload | json_dumps | regex_search("{route.filtering_term}") }}' + elif route.filtering_term_type == ChannelFilter.FILTERING_TERM_TYPE_JINJA2: + route.routing_template = route.filtering_term + route.save() + +class Migration(migrations.Migration): + + dependencies = [ + ('alerts', '0010_channelfilter_filtering_term_type'), + ] + + operations = [ + migrations.AddField( + model_name='channelfilter', + name='routing_template', + field=models.CharField(default=None, max_length=1024, null=True), + ), + ] diff --git a/engine/apps/alerts/models/channel_filter.py b/engine/apps/alerts/models/channel_filter.py index 7ea55abe51..426ef3a246 100644 --- a/engine/apps/alerts/models/channel_filter.py +++ b/engine/apps/alerts/models/channel_filter.py @@ -69,6 +69,8 @@ class ChannelFilter(OrderedModel): notification_backends = models.JSONField(null=True, default=None) created_at = models.DateTimeField(auto_now_add=True) + + # deprecated in favor of routing_template, as regex will be part of jinja2 template filtering_term = models.CharField(max_length=1024, null=True, default=None) FILTERING_TERM_TYPE_REGEX = 0 @@ -77,8 +79,11 @@ class ChannelFilter(OrderedModel): (FILTERING_TERM_TYPE_REGEX, "regex"), (FILTERING_TERM_TYPE_JINJA2, "jinja2"), ] + # deprecated in favor of routing_template, as regex will be part of jinja2 template filtering_term_type = models.IntegerField(choices=FILTERING_TERM_TYPE_CHOICES, default=FILTERING_TERM_TYPE_REGEX) + routing_template = models.CharField(max_length=1024, null=True, default=None) + is_default = models.BooleanField(default=False) class Meta: From 625a0233845e98be143425cdce72173aadf50588 Mon Sep 17 00:00:00 2001 From: Ildar Iskhakov Date: Tue, 11 Apr 2023 11:12:04 +0800 Subject: [PATCH 03/13] Revert "Add new field routing_template, deprecate filtering_term and filtering_term_type" This reverts commit c27b8dd18dceaa4f4220bd94c0b4ea96f5a8231b. --- .../0011_channelfilter_routing_template.py | 26 ------------------- engine/apps/alerts/models/channel_filter.py | 5 ---- 2 files changed, 31 deletions(-) delete mode 100644 engine/apps/alerts/migrations/0011_channelfilter_routing_template.py diff --git a/engine/apps/alerts/migrations/0011_channelfilter_routing_template.py b/engine/apps/alerts/migrations/0011_channelfilter_routing_template.py deleted file mode 100644 index e307ea6793..0000000000 --- a/engine/apps/alerts/migrations/0011_channelfilter_routing_template.py +++ /dev/null @@ -1,26 +0,0 @@ -# Generated by Django 3.2.18 on 2023-04-11 02:48 - -from django.db import migrations, models - -def combine_names(apps, schema_editor): - ChannelFilter = apps.get_model("alerts", "ChannelFilter") - for route in ChannelFilter.objects.all(): - if route.filtering_term_type == ChannelFilter.FILTERING_TERM_TYPE_REGEX: - route.routing_template = f'{{ payload | json_dumps | regex_search("{route.filtering_term}") }}' - elif route.filtering_term_type == ChannelFilter.FILTERING_TERM_TYPE_JINJA2: - route.routing_template = route.filtering_term - route.save() - -class Migration(migrations.Migration): - - dependencies = [ - ('alerts', '0010_channelfilter_filtering_term_type'), - ] - - operations = [ - migrations.AddField( - model_name='channelfilter', - name='routing_template', - field=models.CharField(default=None, max_length=1024, null=True), - ), - ] diff --git a/engine/apps/alerts/models/channel_filter.py b/engine/apps/alerts/models/channel_filter.py index 426ef3a246..7ea55abe51 100644 --- a/engine/apps/alerts/models/channel_filter.py +++ b/engine/apps/alerts/models/channel_filter.py @@ -69,8 +69,6 @@ class ChannelFilter(OrderedModel): notification_backends = models.JSONField(null=True, default=None) created_at = models.DateTimeField(auto_now_add=True) - - # deprecated in favor of routing_template, as regex will be part of jinja2 template filtering_term = models.CharField(max_length=1024, null=True, default=None) FILTERING_TERM_TYPE_REGEX = 0 @@ -79,11 +77,8 @@ class ChannelFilter(OrderedModel): (FILTERING_TERM_TYPE_REGEX, "regex"), (FILTERING_TERM_TYPE_JINJA2, "jinja2"), ] - # deprecated in favor of routing_template, as regex will be part of jinja2 template filtering_term_type = models.IntegerField(choices=FILTERING_TERM_TYPE_CHOICES, default=FILTERING_TERM_TYPE_REGEX) - routing_template = models.CharField(max_length=1024, null=True, default=None) - is_default = models.BooleanField(default=False) class Meta: From f9473f2309b47b9278dde2c8f12840106c76ff30 Mon Sep 17 00:00:00 2001 From: Ildar Iskhakov Date: Tue, 11 Apr 2023 13:37:45 +0800 Subject: [PATCH 04/13] Add filtering_term_as_jinja2 route field, add convert_from_regex_to_jinja2 action to route on private api --- engine/apps/api/serializers/channel_filter.py | 12 ++++++++++++ engine/apps/api/views/channel_filter.py | 13 +++++++++++++ 2 files changed, 25 insertions(+) diff --git a/engine/apps/api/serializers/channel_filter.py b/engine/apps/api/serializers/channel_filter.py index 2347a78bce..d95181946f 100644 --- a/engine/apps/api/serializers/channel_filter.py +++ b/engine/apps/api/serializers/channel_filter.py @@ -26,6 +26,7 @@ class ChannelFilterSerializer(OrderedModelSerializerMixin, EagerLoadingMixin, se queryset=TelegramToOrganizationConnector.objects, filter_field="organization", allow_null=True, required=False ) order = serializers.IntegerField(required=False) + filtering_term_as_jinja2 = serializers.SerializerMethodField() SELECT_RELATED = ["escalation_chain", "alert_receive_channel"] @@ -45,6 +46,7 @@ class Meta: "notify_in_slack", "notify_in_telegram", "notification_backends", + "filtering_term_as_jinja2", ] read_only_fields = ["created_at", "is_default"] extra_kwargs = {"filtering_term": {"required": True, "allow_null": False}} @@ -107,6 +109,16 @@ def validate_notification_backends(self, notification_backends): notification_backends = updated return notification_backends + def get_filtering_term_as_jinja2(self, obj): + """ + Returns the filtering term as a jinja2 template, we need it during migration from regex to jinja2""" + if obj.filtering_term_type == ChannelFilter.FILTERING_TERM_TYPE_JINJA2: + return obj.filtering_term + elif obj.filtering_term_type == ChannelFilter.FILTERING_TERM_TYPE_REGEX: + # Four curly braces will result in two curly braces in the final string + return f'{{{{ payload | json_dumps | regex_search("{obj.filtering_term}") }}}}' + return obj.filtering_term + class ChannelFilterCreateSerializer(ChannelFilterSerializer): alert_receive_channel = OrganizationFilteredPrimaryKeyRelatedField(queryset=AlertReceiveChannel.objects) diff --git a/engine/apps/api/views/channel_filter.py b/engine/apps/api/views/channel_filter.py index adfe534fce..0f14c20200 100644 --- a/engine/apps/api/views/channel_filter.py +++ b/engine/apps/api/views/channel_filter.py @@ -41,6 +41,7 @@ class ChannelFilterView( "destroy": [RBACPermission.Permissions.INTEGRATIONS_WRITE], "move_to_position": [RBACPermission.Permissions.INTEGRATIONS_WRITE], "send_demo_alert": [RBACPermission.Permissions.INTEGRATIONS_TEST], + "convert_from_regex_to_jinja2": [RBACPermission.Permissions.INTEGRATIONS_WRITE], } model = ChannelFilter @@ -143,3 +144,15 @@ def send_demo_alert(self, request, pk): except UnableToSendDemoAlert as e: raise BadRequest(detail=str(e)) return Response(status=status.HTTP_200_OK) + + @action(detail=True, methods=["post"]) + def convert_from_regex_to_jinja2(self, request, pk): + # instance = ChannelFilter.objects.get(public_primary_key=pk) + instance = self.get_queryset().get(public_primary_key=pk) + if not instance.filtering_term_type == ChannelFilter.FILTERING_TERM_TYPE_REGEX: + raise BadRequest(detail="Only regex filtering term type is supported") + data = self.serializer_class(instance).data + instance.filtering_term = data["filtering_term_as_jinja2"] + instance.filtering_term_type = ChannelFilter.FILTERING_TERM_TYPE_JINJA2 + instance.save() + return Response(status=status.HTTP_200_OK, data=self.serializer_class(instance).data) From 8952294ec555d6402f07511185c9bab987be81e6 Mon Sep 17 00:00:00 2001 From: Ildar Iskhakov Date: Tue, 11 Apr 2023 13:39:14 +0800 Subject: [PATCH 05/13] Update channel_filter.py --- engine/apps/api/serializers/channel_filter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/apps/api/serializers/channel_filter.py b/engine/apps/api/serializers/channel_filter.py index d95181946f..2780a7472a 100644 --- a/engine/apps/api/serializers/channel_filter.py +++ b/engine/apps/api/serializers/channel_filter.py @@ -111,7 +111,7 @@ def validate_notification_backends(self, notification_backends): def get_filtering_term_as_jinja2(self, obj): """ - Returns the filtering term as a jinja2 template, we need it during migration from regex to jinja2""" + Returns the regex filtering term as a jinja2, we need to display it during migration from regex to jinja2""" if obj.filtering_term_type == ChannelFilter.FILTERING_TERM_TYPE_JINJA2: return obj.filtering_term elif obj.filtering_term_type == ChannelFilter.FILTERING_TERM_TYPE_REGEX: From e11d244b55052b76bd54edd34f7ea1a5e2efc84e Mon Sep 17 00:00:00 2001 From: Ildar Iskhakov Date: Tue, 11 Apr 2023 13:39:39 +0800 Subject: [PATCH 06/13] Update channel_filter.py --- engine/apps/api/views/channel_filter.py | 1 - 1 file changed, 1 deletion(-) diff --git a/engine/apps/api/views/channel_filter.py b/engine/apps/api/views/channel_filter.py index 0f14c20200..e3a7bd8224 100644 --- a/engine/apps/api/views/channel_filter.py +++ b/engine/apps/api/views/channel_filter.py @@ -147,7 +147,6 @@ def send_demo_alert(self, request, pk): @action(detail=True, methods=["post"]) def convert_from_regex_to_jinja2(self, request, pk): - # instance = ChannelFilter.objects.get(public_primary_key=pk) instance = self.get_queryset().get(public_primary_key=pk) if not instance.filtering_term_type == ChannelFilter.FILTERING_TERM_TYPE_REGEX: raise BadRequest(detail="Only regex filtering term type is supported") From c7ada8ed6df3cf21218004ea58df43321b135247 Mon Sep 17 00:00:00 2001 From: Ildar Iskhakov Date: Fri, 14 Apr 2023 10:44:09 +0800 Subject: [PATCH 07/13] Add tests --- engine/apps/api/tests/test_channel_filter.py | 46 ++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/engine/apps/api/tests/test_channel_filter.py b/engine/apps/api/tests/test_channel_filter.py index fe02e97b88..e20328ec12 100644 --- a/engine/apps/api/tests/test_channel_filter.py +++ b/engine/apps/api/tests/test_channel_filter.py @@ -502,3 +502,49 @@ def test_channel_filter_update_invalid_notification_backends( assert response.status_code == status.HTTP_400_BAD_REQUEST assert response.json() == {"notification_backends": ["Invalid messaging backend"]} assert channel_filter.notification_backends is None + + +@pytest.mark.django_db +@pytest.mark.parametrize( + "role,expected_status", + [ + (LegacyAccessControlRole.ADMIN, status.HTTP_200_OK), + (LegacyAccessControlRole.EDITOR, status.HTTP_403_FORBIDDEN), + (LegacyAccessControlRole.VIEWER, status.HTTP_403_FORBIDDEN), + ], +) +def test_channel_filter_convert_from_regex_to_jinja2( + make_organization_and_user_with_plugin_token, + make_alert_receive_channel, + make_channel_filter, + make_user_auth_headers, + role, + expected_status, +): + organization, user, token = make_organization_and_user_with_plugin_token(role) + alert_receive_channel = make_alert_receive_channel(organization) + + make_channel_filter(alert_receive_channel, is_default=True) + regex_channel_filter = make_channel_filter(alert_receive_channel, filtering_term=".*", is_default=False) + assert regex_channel_filter.filtering_term_type == regex_channel_filter.FILTERING_TERM_TYPE_REGEX + + final_filtering_term = '{{ payload | json_dumps | regex_search(".*") }}' + + client = APIClient() + url = reverse("api-internal:channel_filter-detail", kwargs={"pk": regex_channel_filter.public_primary_key}) + + response = client.get(url, format="json", **make_user_auth_headers(user, token)) + + assert response.status_code == status.HTTP_200_OK + assert response.json()["filtering_term_as_jinja2"] == final_filtering_term + + url = reverse( + "api-internal:channel_filter-convert-from-regex-to-jinja2", + kwargs={"pk": regex_channel_filter.public_primary_key}, + ) + response = client.post(url, **make_user_auth_headers(user, token)) + assert response.status_code == expected_status + if expected_status == status.HTTP_200_OK: + regex_channel_filter.refresh_from_db() + assert regex_channel_filter.filtering_term_type == regex_channel_filter.FILTERING_TERM_TYPE_JINJA2 + assert regex_channel_filter.filtering_term == final_filtering_term From b2b66782ca23d8009267417f4cd1b323c3d0e140 Mon Sep 17 00:00:00 2001 From: Ildar Iskhakov Date: Fri, 14 Apr 2023 10:46:11 +0800 Subject: [PATCH 08/13] Fix typo --- engine/apps/api/serializers/channel_filter.py | 1 - 1 file changed, 1 deletion(-) diff --git a/engine/apps/api/serializers/channel_filter.py b/engine/apps/api/serializers/channel_filter.py index 2780a7472a..d5b3ce6a10 100644 --- a/engine/apps/api/serializers/channel_filter.py +++ b/engine/apps/api/serializers/channel_filter.py @@ -117,7 +117,6 @@ def get_filtering_term_as_jinja2(self, obj): elif obj.filtering_term_type == ChannelFilter.FILTERING_TERM_TYPE_REGEX: # Four curly braces will result in two curly braces in the final string return f'{{{{ payload | json_dumps | regex_search("{obj.filtering_term}") }}}}' - return obj.filtering_term class ChannelFilterCreateSerializer(ChannelFilterSerializer): From b883c8f69970fb1db3914853c2ed6bca336d426d Mon Sep 17 00:00:00 2001 From: Ildar Iskhakov Date: Fri, 14 Apr 2023 10:47:27 +0800 Subject: [PATCH 09/13] Use more sophisticated regex in tests --- engine/apps/api/tests/test_channel_filter.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/engine/apps/api/tests/test_channel_filter.py b/engine/apps/api/tests/test_channel_filter.py index e20328ec12..71a444fadb 100644 --- a/engine/apps/api/tests/test_channel_filter.py +++ b/engine/apps/api/tests/test_channel_filter.py @@ -525,10 +525,14 @@ def test_channel_filter_convert_from_regex_to_jinja2( alert_receive_channel = make_alert_receive_channel(organization) make_channel_filter(alert_receive_channel, is_default=True) - regex_channel_filter = make_channel_filter(alert_receive_channel, filtering_term=".*", is_default=False) + regex_channel_filter = make_channel_filter( + alert_receive_channel, + filtering_term='".*": "This alert was sent by user for the demonstration purposes"', + is_default=False, + ) assert regex_channel_filter.filtering_term_type == regex_channel_filter.FILTERING_TERM_TYPE_REGEX - final_filtering_term = '{{ payload | json_dumps | regex_search(".*") }}' + final_filtering_term = '{{ payload | json_dumps | regex_search("".*": "This alert was sent by user for the demonstration purposes"") }}' client = APIClient() url = reverse("api-internal:channel_filter-detail", kwargs={"pk": regex_channel_filter.public_primary_key}) From d5319391a9ef87946ad6e03920406c4022ec0007 Mon Sep 17 00:00:00 2001 From: Ildar Iskhakov Date: Fri, 14 Apr 2023 10:48:59 +0800 Subject: [PATCH 10/13] Add comments to the tests --- engine/apps/api/tests/test_channel_filter.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/engine/apps/api/tests/test_channel_filter.py b/engine/apps/api/tests/test_channel_filter.py index 71a444fadb..7454bd73a0 100644 --- a/engine/apps/api/tests/test_channel_filter.py +++ b/engine/apps/api/tests/test_channel_filter.py @@ -530,6 +530,7 @@ def test_channel_filter_convert_from_regex_to_jinja2( filtering_term='".*": "This alert was sent by user for the demonstration purposes"', is_default=False, ) + # Check if the filtering term is a regex assert regex_channel_filter.filtering_term_type == regex_channel_filter.FILTERING_TERM_TYPE_REGEX final_filtering_term = '{{ payload | json_dumps | regex_search("".*": "This alert was sent by user for the demonstration purposes"") }}' @@ -540,6 +541,7 @@ def test_channel_filter_convert_from_regex_to_jinja2( response = client.get(url, format="json", **make_user_auth_headers(user, token)) assert response.status_code == status.HTTP_200_OK + # Check if preview of the filtering term migration is correct assert response.json()["filtering_term_as_jinja2"] == final_filtering_term url = reverse( @@ -547,8 +549,10 @@ def test_channel_filter_convert_from_regex_to_jinja2( kwargs={"pk": regex_channel_filter.public_primary_key}, ) response = client.post(url, **make_user_auth_headers(user, token)) + # Only admins can convert from regex to jinja2 assert response.status_code == expected_status if expected_status == status.HTTP_200_OK: regex_channel_filter.refresh_from_db() + # Check if the filtering term is a jinja2, and if it is correct assert regex_channel_filter.filtering_term_type == regex_channel_filter.FILTERING_TERM_TYPE_JINJA2 assert regex_channel_filter.filtering_term == final_filtering_term From fc033650c4d8b1b49c76edcbcb33463e67ce590b Mon Sep 17 00:00:00 2001 From: Ildar Iskhakov Date: Fri, 14 Apr 2023 10:50:14 +0800 Subject: [PATCH 11/13] More comments --- engine/apps/api/serializers/channel_filter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/engine/apps/api/serializers/channel_filter.py b/engine/apps/api/serializers/channel_filter.py index d5b3ce6a10..7fdfc8de01 100644 --- a/engine/apps/api/serializers/channel_filter.py +++ b/engine/apps/api/serializers/channel_filter.py @@ -111,7 +111,7 @@ def validate_notification_backends(self, notification_backends): def get_filtering_term_as_jinja2(self, obj): """ - Returns the regex filtering term as a jinja2, we need to display it during migration from regex to jinja2""" + Returns the regex filtering term as a jinja2, for the preview before migration from regex to jinja2""" if obj.filtering_term_type == ChannelFilter.FILTERING_TERM_TYPE_JINJA2: return obj.filtering_term elif obj.filtering_term_type == ChannelFilter.FILTERING_TERM_TYPE_REGEX: From 7ee64dc0902aa20f6525a1536a31076ef3ea7acb Mon Sep 17 00:00:00 2001 From: Ildar Iskhakov Date: Fri, 14 Apr 2023 11:59:28 +0800 Subject: [PATCH 12/13] Add more test and clean up --- engine/apps/api/serializers/channel_filter.py | 3 ++- engine/apps/api/tests/test_channel_filter.py | 20 +++++++++++++----- engine/apps/api/views/channel_filter.py | 8 ++++--- .../common/tests/test_apply_jinja_template.py | 21 +++++++++++++++++++ 4 files changed, 43 insertions(+), 9 deletions(-) diff --git a/engine/apps/api/serializers/channel_filter.py b/engine/apps/api/serializers/channel_filter.py index 7fdfc8de01..8987374fd1 100644 --- a/engine/apps/api/serializers/channel_filter.py +++ b/engine/apps/api/serializers/channel_filter.py @@ -116,7 +116,8 @@ def get_filtering_term_as_jinja2(self, obj): return obj.filtering_term elif obj.filtering_term_type == ChannelFilter.FILTERING_TERM_TYPE_REGEX: # Four curly braces will result in two curly braces in the final string - return f'{{{{ payload | json_dumps | regex_search("{obj.filtering_term}") }}}}' + # rf"..." is a raw f string, to keep original filtering_term + return rf'{{{{ payload | json_dumps | regex_search("{obj.filtering_term}") }}}}' class ChannelFilterCreateSerializer(ChannelFilterSerializer): diff --git a/engine/apps/api/tests/test_channel_filter.py b/engine/apps/api/tests/test_channel_filter.py index 7454bd73a0..d22b70404f 100644 --- a/engine/apps/api/tests/test_channel_filter.py +++ b/engine/apps/api/tests/test_channel_filter.py @@ -525,15 +525,21 @@ def test_channel_filter_convert_from_regex_to_jinja2( alert_receive_channel = make_alert_receive_channel(organization) make_channel_filter(alert_receive_channel, is_default=True) + + # r"..." used to keep this string as raw string + regex_filtering_term = r"\".*\": \"This alert was sent by user for the demonstration purposes\"" + final_filtering_term = r'{{ payload | json_dumps | regex_search("\".*\": \"This alert was sent by user for the demonstration purposes\"") }}' + payload = {"description": "This alert was sent by user for the demonstration purposes"} + regex_channel_filter = make_channel_filter( alert_receive_channel, - filtering_term='".*": "This alert was sent by user for the demonstration purposes"', + filtering_term=regex_filtering_term, is_default=False, ) # Check if the filtering term is a regex assert regex_channel_filter.filtering_term_type == regex_channel_filter.FILTERING_TERM_TYPE_REGEX - - final_filtering_term = '{{ payload | json_dumps | regex_search("".*": "This alert was sent by user for the demonstration purposes"") }}' + # Check if the alert is matched to the channel filter (route) regex + assert bool(regex_channel_filter.is_satisfying(payload)) is True client = APIClient() url = reverse("api-internal:channel_filter-detail", kwargs={"pk": regex_channel_filter.public_primary_key}) @@ -553,6 +559,10 @@ def test_channel_filter_convert_from_regex_to_jinja2( assert response.status_code == expected_status if expected_status == status.HTTP_200_OK: regex_channel_filter.refresh_from_db() + # Regex is now converted to jinja2 + jinja2_channel_filter = regex_channel_filter # Check if the filtering term is a jinja2, and if it is correct - assert regex_channel_filter.filtering_term_type == regex_channel_filter.FILTERING_TERM_TYPE_JINJA2 - assert regex_channel_filter.filtering_term == final_filtering_term + assert jinja2_channel_filter.filtering_term_type == jinja2_channel_filter.FILTERING_TERM_TYPE_JINJA2 + assert jinja2_channel_filter.filtering_term == final_filtering_term + # Check if the same alert is matched to the channel filter (route) new jinja2 + assert bool(jinja2_channel_filter.is_satisfying(payload)) is True diff --git a/engine/apps/api/views/channel_filter.py b/engine/apps/api/views/channel_filter.py index e3a7bd8224..f9f87bd0f1 100644 --- a/engine/apps/api/views/channel_filter.py +++ b/engine/apps/api/views/channel_filter.py @@ -150,8 +150,10 @@ def convert_from_regex_to_jinja2(self, request, pk): instance = self.get_queryset().get(public_primary_key=pk) if not instance.filtering_term_type == ChannelFilter.FILTERING_TERM_TYPE_REGEX: raise BadRequest(detail="Only regex filtering term type is supported") - data = self.serializer_class(instance).data - instance.filtering_term = data["filtering_term_as_jinja2"] + + serializer_class = self.serializer_class + + instance.filtering_term = serializer_class(instance).get_filtering_term_as_jinja2(instance) instance.filtering_term_type = ChannelFilter.FILTERING_TERM_TYPE_JINJA2 instance.save() - return Response(status=status.HTTP_200_OK, data=self.serializer_class(instance).data) + return Response(status=status.HTTP_200_OK, data=serializer_class(instance).data) diff --git a/engine/common/tests/test_apply_jinja_template.py b/engine/common/tests/test_apply_jinja_template.py index 632f50261f..aab3f488d6 100644 --- a/engine/common/tests/test_apply_jinja_template.py +++ b/engine/common/tests/test_apply_jinja_template.py @@ -14,6 +14,14 @@ def test_apply_jinja_template(): assert payload == result +def test_apply_jinja_template_json_dumps(): + payload = {"name": "test"} + + result = apply_jinja_template("{{ payload | json_dumps }}", payload) + expected = json.dumps(payload) + assert result == expected + + def test_apply_jinja_template_regex_match(): payload = {"name": "test"} @@ -26,6 +34,19 @@ def test_apply_jinja_template_regex_match(): apply_jinja_template("{{ payload.name | regex_match('*') }}", payload) +def test_apply_jinja_template_regex_search(): + payload = {"name": "test"} + + assert apply_jinja_template("{{ payload.name | regex_search('.*') }}", payload) == "True" + assert apply_jinja_template("{{ payload.name | regex_search('tes') }}", payload) == "True" + assert apply_jinja_template("{{ payload.name | regex_search('est') }}", payload) == "True" + assert apply_jinja_template("{{ payload.name | regex_search('test1') }}", payload) == "False" + + # Check that exception is raised when regex is invalid + with pytest.raises(JinjaTemplateError): + apply_jinja_template("{{ payload.name | regex_search('*') }}", payload) + + def test_apply_jinja_template_bad_syntax_error(): with pytest.raises(JinjaTemplateError): apply_jinja_template("{{%", payload={}) From eec7f2388f360f5c93643c36a8889607e6402ba7 Mon Sep 17 00:00:00 2001 From: Ildar Iskhakov Date: Tue, 18 Apr 2023 10:48:52 +0800 Subject: [PATCH 13/13] Update CHANGELOG.md --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a992aa947..f4ab2a784a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +### Added + +- Added preview and migration API endpoints for route migration from regex into jinja2 ([1715](https://github.com/grafana/oncall/pull/1715)) + ## v1.2.10 (2023-04-13) ### Fixed