diff --git a/CHANGELOG.md b/CHANGELOG.md index 85be1a2d..7e82fbf9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ All notable changes to this project will be documented in this file. - Add the WEBHOOKS_ENABLED setting (#471) - Add the SLACK_ENABLED setting (#471) - Add the MATTERMOST_ENABLED setting (#471) +- Add the MSTEAMS_ENABLED setting (#471) ## Bug Fixes - Fix unwanted HTML escaping in SMS and WhatsApp notifications diff --git a/docker/.env b/docker/.env index 1d0310d7..1aa7514f 100644 --- a/docker/.env +++ b/docker/.env @@ -26,6 +26,7 @@ MATRIX_ACCESS_TOKEN= MATRIX_HOMESERVER= MATRIX_USER_ID= MATTERMOST_ENABLED=True +MSTEAMS_ENABLED=True PD_VENDOR_KEY= PING_BODY_LIMIT=10000 PING_EMAIL_DOMAIN=localhost diff --git a/hc/api/tests/test_notify.py b/hc/api/tests/test_notify.py index 0eb73268..54d7be2d 100644 --- a/hc/api/tests/test_notify.py +++ b/hc/api/tests/test_notify.py @@ -283,45 +283,6 @@ class NotifyTestCase(BaseTestCase): with self.assertRaises(NotImplementedError): self.channel.notify(self.check) - @patch("hc.api.transports.requests.request") - def test_msteams(self, mock_post): - self._setup_data("msteams", "http://example.com/webhook") - mock_post.return_value.status_code = 200 - - self.check.name = "_underscores_ & more" - - self.channel.notify(self.check) - assert Notification.objects.count() == 1 - - args, kwargs = mock_post.call_args - payload = kwargs["json"] - self.assertEqual(payload["@type"], "MessageCard") - - # summary and title should be the same, except - # title should have any special HTML characters escaped - self.assertEqual(payload["summary"], "“_underscores_ & more” is DOWN.") - self.assertEqual(payload["title"], "“_underscores_ & more” is DOWN.") - - @patch("hc.api.transports.requests.request") - def test_msteams_escapes_html_and_markdown_in_desc(self, mock_post): - self._setup_data("msteams", "http://example.com/webhook") - mock_post.return_value.status_code = 200 - - self.check.desc = """ - TEST _underscore_ `backticks` underline \\backslash\\ "quoted" - """ - - self.channel.notify(self.check) - - args, kwargs = mock_post.call_args - text = kwargs["json"]["sections"][0]["text"] - - self.assertIn(r"\_underscore\_", text) - self.assertIn(r"\`backticks\`", text) - self.assertIn("<u>underline</u>", text) - self.assertIn(r"\\backslash\\ ", text) - self.assertIn(""quoted"", text) - @patch("hc.api.transports.os.system") @override_settings(SHELL_ENABLED=True) def test_shell(self, mock_system): diff --git a/hc/api/tests/test_notify_msteams.py b/hc/api/tests/test_notify_msteams.py new file mode 100644 index 00000000..7acdff0b --- /dev/null +++ b/hc/api/tests/test_notify_msteams.py @@ -0,0 +1,71 @@ +# coding: utf-8 + +from datetime import timedelta as td +from unittest.mock import patch + +from django.utils.timezone import now +from hc.api.models import Channel, Check, Notification +from hc.test import BaseTestCase +from django.test.utils import override_settings + + +class NotifyTestCase(BaseTestCase): + def _setup_data(self, value, status="down", email_verified=True): + self.check = Check(project=self.project) + self.check.status = status + self.check.last_ping = now() - td(minutes=61) + self.check.save() + + self.channel = Channel(project=self.project) + self.channel.kind = "msteams" + self.channel.value = value + self.channel.email_verified = email_verified + self.channel.save() + self.channel.checks.add(self.check) + + @patch("hc.api.transports.requests.request") + def test_msteams(self, mock_post): + self._setup_data("http://example.com/webhook") + mock_post.return_value.status_code = 200 + + self.check.name = "_underscores_ & more" + + self.channel.notify(self.check) + assert Notification.objects.count() == 1 + + args, kwargs = mock_post.call_args + payload = kwargs["json"] + self.assertEqual(payload["@type"], "MessageCard") + + # summary and title should be the same, except + # title should have any special HTML characters escaped + self.assertEqual(payload["summary"], "“_underscores_ & more” is DOWN.") + self.assertEqual(payload["title"], "“_underscores_ & more” is DOWN.") + + @patch("hc.api.transports.requests.request") + def test_msteams_escapes_html_and_markdown_in_desc(self, mock_post): + self._setup_data("http://example.com/webhook") + mock_post.return_value.status_code = 200 + + self.check.desc = """ + TEST _underscore_ `backticks` underline \\backslash\\ "quoted" + """ + + self.channel.notify(self.check) + + args, kwargs = mock_post.call_args + text = kwargs["json"]["sections"][0]["text"] + + self.assertIn(r"\_underscore\_", text) + self.assertIn(r"\`backticks\`", text) + self.assertIn("<u>underline</u>", text) + self.assertIn(r"\\backslash\\ ", text) + self.assertIn(""quoted"", text) + + @override_settings(MSTEAMS_ENABLED=False) + def test_it_requires_msteams_enabled(self): + self._setup_data("http://example.com/webhook") + self.channel.notify(self.check) + + n = Notification.objects.get() + self.assertEqual(n.error, "MS Teams notifications are not enabled.") diff --git a/hc/api/transports.py b/hc/api/transports.py index e93b968d..d7d642b3 100644 --- a/hc/api/transports.py +++ b/hc/api/transports.py @@ -610,6 +610,9 @@ class MsTeams(HttpTransport): return s def notify(self, check): + if not settings.MSTEAMS_ENABLED: + return "MS Teams notifications are not enabled." + text = tmpl("msteams_message.json", check=check) payload = json.loads(text) diff --git a/hc/front/tests/test_add_msteams.py b/hc/front/tests/test_add_msteams.py index 7fb9894d..5bbbbf77 100644 --- a/hc/front/tests/test_add_msteams.py +++ b/hc/front/tests/test_add_msteams.py @@ -1,3 +1,4 @@ +from django.test.utils import override_settings from hc.api.models import Channel from hc.test import BaseTestCase @@ -31,3 +32,9 @@ class AddMsTeamsTestCase(BaseTestCase): self.client.login(username="bob@example.org", password="password") r = self.client.get(self.url) self.assertEqual(r.status_code, 403) + + @override_settings(MSTEAMS_ENABLED=False) + def test_it_handles_disabled_integration(self): + self.client.login(username="alice@example.org", password="password") + r = self.client.get(self.url) + self.assertEqual(r.status_code, 404) diff --git a/hc/front/views.py b/hc/front/views.py index 7a301654..f60baaaa 100644 --- a/hc/front/views.py +++ b/hc/front/views.py @@ -296,6 +296,7 @@ def index(request): "enable_linenotify": settings.LINENOTIFY_CLIENT_ID is not None, "enable_matrix": settings.MATRIX_ACCESS_TOKEN is not None, "enable_mattermost": settings.MATTERMOST_ENABLED is True, + "enable_msteams": settings.MSTEAMS_ENABLED is True, "enable_pdc": settings.PD_VENDOR_KEY is not None, "enable_pushbullet": settings.PUSHBULLET_CLIENT_ID is not None, "enable_pushover": settings.PUSHOVER_API_TOKEN is not None, @@ -765,6 +766,7 @@ def channels(request, code): "enable_linenotify": settings.LINENOTIFY_CLIENT_ID is not None, "enable_matrix": settings.MATRIX_ACCESS_TOKEN is not None, "enable_mattermost": settings.MATTERMOST_ENABLED is True, + "enable_msteams": settings.MSTEAMS_ENABLED is True, "enable_pdc": settings.PD_VENDOR_KEY is not None, "enable_pushbullet": settings.PUSHBULLET_CLIENT_ID is not None, "enable_pushover": settings.PUSHOVER_API_TOKEN is not None, @@ -1783,6 +1785,7 @@ def trello_settings(request): return render(request, "integrations/trello_settings.html", ctx) +@require_setting("MSTEAMS_ENABLED") @login_required def add_msteams(request, code): project = _get_rw_project_for_user(request, code) diff --git a/hc/settings.py b/hc/settings.py index 65e00961..fd656ce8 100644 --- a/hc/settings.py +++ b/hc/settings.py @@ -202,6 +202,9 @@ MATRIX_ACCESS_TOKEN = os.getenv("MATRIX_ACCESS_TOKEN") # Mattermost MATTERMOST_ENABLED = envbool("MATTERMOST_ENABLED", "True") +# MS Teams +MSTEAMS_ENABLED = envbool("MSTEAMS_ENABLED", "True") + # PagerDuty PD_VENDOR_KEY = os.getenv("PD_VENDOR_KEY") diff --git a/templates/docs/self_hosted_configuration.html b/templates/docs/self_hosted_configuration.html index 3ac83246..d8e68a06 100644 --- a/templates/docs/self_hosted_configuration.html +++ b/templates/docs/self_hosted_configuration.html @@ -143,6 +143,9 @@ integration.

MATTERMOST_ENABLED

Default: True

A boolean that turns on/off the Mattermost integration. Enabled by default.

+

MSTEAMS_ENABLED

+

Default: True

+

A boolean that turns on/off the MS Teams integration. Enabled by default.

PD_VENDOR_KEY

Default: None

PagerDuty vendor key, diff --git a/templates/docs/self_hosted_configuration.md b/templates/docs/self_hosted_configuration.md index cc66d165..57460fb5 100644 --- a/templates/docs/self_hosted_configuration.md +++ b/templates/docs/self_hosted_configuration.md @@ -236,6 +236,12 @@ Default: `True` A boolean that turns on/off the Mattermost integration. Enabled by default. +## `MSTEAMS_ENABLED` {: #MSTEAMS_ENABLED } + +Default: `True` + +A boolean that turns on/off the MS Teams integration. Enabled by default. + ## `PD_VENDOR_KEY` {: #PD_VENDOR_KEY } Default: `None` diff --git a/templates/front/channels.html b/templates/front/channels.html index fa32c652..19ed3429 100644 --- a/templates/front/channels.html +++ b/templates/front/channels.html @@ -276,6 +276,7 @@ {% endif %} + {% if enable_msteams %}

  • Microsoft Teams @@ -284,6 +285,7 @@

    Chat and collaboration platform for Microsoft Office 365 customers.

    Add Integration
  • + {% endif %}
  • {% endif %} + {% if enable_msteams %}
    @@ -481,6 +482,7 @@
    + {% endif %}