Browse Source

Add Microsoft Teams integration. Fixes #135

pull/307/head
Pēteris Caune 5 years ago
parent
commit
dc84b7be01
No known key found for this signature in database GPG Key ID: E28D7679E9A9EDE2
20 changed files with 266 additions and 5 deletions
  1. +1
    -0
      CHANGELOG.md
  2. +3
    -0
      hc/api/models.py
  3. +12
    -0
      hc/api/tests/test_notify.py
  4. +7
    -0
      hc/api/transports.py
  5. +21
    -0
      hc/front/tests/test_add_msteams.py
  6. +1
    -0
      hc/front/urls.py
  7. +18
    -0
      hc/front/views.py
  8. +9
    -5
      static/css/icomoon.css
  9. BIN
      static/fonts/icomoon.eot
  10. +1
    -0
      static/fonts/icomoon.svg
  11. BIN
      static/fonts/icomoon.ttf
  12. BIN
      static/fonts/icomoon.woff
  13. BIN
      static/img/integrations/msteams.png
  14. BIN
      static/img/integrations/setup_msteams_1.png
  15. BIN
      static/img/integrations/setup_msteams_2.png
  16. BIN
      static/img/integrations/setup_msteams_3.png
  17. BIN
      static/img/integrations/setup_msteams_4.png
  18. +12
    -0
      templates/front/channels.html
  19. +126
    -0
      templates/integrations/add_msteams.html
  20. +55
    -0
      templates/integrations/msteams_message.json

+ 1
- 0
CHANGELOG.md View File

@ -5,6 +5,7 @@ All notable changes to this project will be documented in this file.
### Improvements
- In monthly reports, no downtime stats for the current month (month has just started)
- Add Microsoft Teams integration (#135)
### Bug Fixes
- On mobile, "My Checks" page, always show the gear (Details) button (#286)


+ 3
- 0
hc/api/models.py View File

@ -45,6 +45,7 @@ CHANNEL_KINDS = (
("whatsapp", "WhatsApp"),
("apprise", "Apprise"),
("mattermost", "Mattermost"),
("msteams", "Microsoft Teams"),
)
PO_PRIORITIES = {-2: "lowest", -1: "low", 0: "normal", 1: "high", 2: "emergency"}
@ -410,6 +411,8 @@ class Channel(models.Model):
return transports.WhatsApp(self)
elif self.kind == "apprise":
return transports.Apprise(self)
elif self.kind == "msteams":
return transports.MsTeams(self)
else:
raise NotImplementedError("Unknown channel kind: %s" % self.kind)


+ 12
- 0
hc/api/tests/test_notify.py View File

@ -699,3 +699,15 @@ class NotifyTestCase(BaseTestCase):
with self.assertRaises(NotImplementedError):
self.channel.notify(self.check)
@patch("hc.api.transports.requests.request")
def test_mesteams(self, mock_post):
self._setup_data("msteams", "http://example.com/webhook")
mock_post.return_value.status_code = 200
self.channel.notify(self.check)
assert Notification.objects.count() == 1
args, kwargs = mock_post.call_args
payload = kwargs["json"]
self.assertEqual(payload["@type"], "MessageCard")

+ 7
- 0
hc/api/transports.py View File

@ -498,3 +498,10 @@ class Apprise(HttpTransport):
if not a.notify(body=body, title=title, notify_type=notify_type)
else None
)
class MsTeams(HttpTransport):
def notify(self, check):
text = tmpl("msteams_message.json", check=check)
payload = json.loads(text)
return self.post(self.channel.value, json=payload)

+ 21
- 0
hc/front/tests/test_add_msteams.py View File

@ -0,0 +1,21 @@
from hc.api.models import Channel
from hc.test import BaseTestCase
class AddMsTeamsTestCase(BaseTestCase):
def test_instructions_work(self):
self.client.login(username="[email protected]", password="password")
r = self.client.get("/integrations/add_msteams/")
self.assertContains(r, "Integration Settings", status_code=200)
def test_it_works(self):
form = {"value": "https://example.com/foo"}
self.client.login(username="[email protected]", password="password")
r = self.client.post("/integrations/add_msteams/", form)
self.assertRedirects(r, "/integrations/")
c = Channel.objects.get()
self.assertEqual(c.kind, "msteams")
self.assertEqual(c.value, "https://example.com/foo")
self.assertEqual(c.project, self.project)

+ 1
- 0
hc/front/urls.py View File

@ -46,6 +46,7 @@ channel_urls = [
path("add_trello/settings/", views.trello_settings, name="hc-trello-settings"),
path("add_matrix/", views.add_matrix, name="hc-add-matrix"),
path("add_apprise/", views.add_apprise, name="hc-add-apprise"),
path("add_msteams/", views.add_msteams, name="hc-add-msteams"),
path("<uuid:code>/checks/", views.channel_checks, name="hc-channel-checks"),
path("<uuid:code>/name/", views.update_channel_name, name="hc-channel-name"),
path("<uuid:code>/test/", views.send_test_notification, name="hc-channel-test"),


+ 18
- 0
hc/front/views.py View File

@ -1415,3 +1415,21 @@ def trello_settings(request):
r = requests.get(url)
ctx = {"token": token, "data": r.json()}
return render(request, "integrations/trello_settings.html", ctx)
@login_required
def add_msteams(request):
if request.method == "POST":
form = AddUrlForm(request.POST)
if form.is_valid():
channel = Channel(project=request.project, kind="msteams")
channel.value = form.cleaned_data["value"]
channel.save()
channel.assign_all_checks()
return redirect("hc-channels")
else:
form = AddUrlForm()
ctx = {"page": "channels", "project": request.project, "form": form}
return render(request, "integrations/add_msteams.html", ctx)

+ 9
- 5
static/css/icomoon.css View File

@ -1,10 +1,10 @@
@font-face {
font-family: 'icomoon';
src: url('../fonts/icomoon.eot?q2glnk');
src: url('../fonts/icomoon.eot?q2glnk#iefix') format('embedded-opentype'),
url('../fonts/icomoon.ttf?q2glnk') format('truetype'),
url('../fonts/icomoon.woff?q2glnk') format('woff'),
url('../fonts/icomoon.svg?q2glnk#icomoon') format('svg');
src: url('../fonts/icomoon.eot?tg9zp8');
src: url('../fonts/icomoon.eot?tg9zp8#iefix') format('embedded-opentype'),
url('../fonts/icomoon.ttf?tg9zp8') format('truetype'),
url('../fonts/icomoon.woff?tg9zp8') format('woff'),
url('../fonts/icomoon.svg?tg9zp8#icomoon') format('svg');
font-weight: normal;
font-style: normal;
}
@ -24,6 +24,10 @@
-moz-osx-font-smoothing: grayscale;
}
.icon-msteams:before {
content: "\e916";
color: #4e56be;
}
.icon-opsgenie:before {
content: "\e910";
color: #2684ff;


BIN
static/fonts/icomoon.eot View File


+ 1
- 0
static/fonts/icomoon.svg View File

@ -40,4 +40,5 @@
<glyph unicode="&#xe913;" glyph-name="delete" d="M810 768.667v-86h-596v86h148l44 42h212l44-42h148zM256 128.667v512h512v-512c0-46-40-86-86-86h-340c-46 0-86 40-86 86z" />
<glyph unicode="&#xe914;" glyph-name="pagerteam" horiz-adv-x="981" d="M484.289 621.868c-67.542-1.322-134.652-26.387-187.221-70.263-56.96-47.541-94.861-114.277-106.117-186.879-3.371-21.755-3.183-14.333-3.367-130.672-0.144-87.121-0.309-106.544-0.894-107.030-31.25-1.325-94.191-0.875-94.191-0.875s-0.436-123.022-0.285-189.922l799.764-0.228-0.323 189.561-94.818 0.856-0.228 104.062c-0.235 112.101-0.168 109.401-3.138 130.159-1.569 10.965-4.997 27.461-7.798 37.528-26.226 94.196-95.127 169.766-186.194 204.207-32.295 12.214-64.792 18.42-101.666 19.439-4.502 0.124-9.021 0.145-13.524 0.057zM504.47 546.85c51.456 0 106.155-17.857 149.103-56.434s72.159-98.866 70.871-174.553c-1.391-48.523-73.925-47.248-73.61 1.293 0.97 57.004-18.643 93.453-46.506 118.48s-65.885 37.604-99.859 37.604c-49.962-0.897-49.962 74.507 0 73.61zM38.407 694.94c-0.476 0.022-1.035 0.035-1.596 0.035-20.33 0-36.811-16.481-36.811-36.811 0-13.706 7.49-25.662 18.6-31.998l0.181-0.095 156.359-91.3c5.323-3.162 11.735-5.030 18.583-5.030 20.347 0 36.841 16.494 36.841 36.841 0 13.498-7.259 25.301-18.087 31.717l-0.171 0.094-156.431 91.3c-4.992 3.055-10.98 4.971-17.393 5.245l-0.076 0.003zM232.796 876.532c-19.71-0.785-35.391-16.953-35.391-36.784 0-6.961 1.932-13.471 5.29-19.023l-0.092 0.165 87.202-150.968c6.296-11.786 18.515-19.67 32.577-19.67 20.33 0 36.811 16.481 36.811 36.811 0 7.295-2.122 14.094-5.782 19.814l0.088-0.148-87.13 150.968c-6.42 11.337-18.401 18.863-32.139 18.863-0.504 0-1.006-0.010-1.505-0.030l0.072 0.002zM492.029 959.996c-20.081-0.324-36.236-16.679-36.236-36.807 0-0.177 0.001-0.354 0.004-0.531v0.027-187.2c-0.002-0.155-0.004-0.338-0.004-0.521 0-20.33 16.481-36.811 36.811-36.811s36.811 16.481 36.811 36.811c0 0.183-0.001 0.366-0.004 0.548v-0.028 187.2c0.002 0.15 0.003 0.327 0.003 0.504 0 20.33-16.481 36.811-36.811 36.811-0.202 0-0.404-0.002-0.605-0.005h0.030zM945.507 690.842c-0.29 0.008-0.632 0.013-0.974 0.013-7.054 0-13.644-1.984-19.243-5.424l0.16 0.091-156.431-91.3c-11.571-6.357-19.282-18.463-19.282-32.37 0-20.33 16.481-36.811 36.811-36.811 7.253 0 14.016 2.098 19.716 5.719l-0.15-0.089 156.431 91.3c11.271 6.437 18.746 18.382 18.746 32.073 0 19.969-15.9 36.224-35.731 36.795l-0.053 0.001zM747.38 872.362c-0.089 0.001-0.193 0.001-0.298 0.001-13.728 0-25.7-7.514-32.029-18.654l-0.095-0.182-87.13-150.895c-3.572-5.572-5.694-12.371-5.694-19.666 0-20.33 16.481-36.811 36.811-36.811 14.061 0 26.281 7.884 32.48 19.472l0.096 0.198 87.202 150.895c3.256 5.381 5.182 11.882 5.182 18.832 0 20.23-16.319 36.648-36.51 36.81h-0.015z" />
<glyph unicode="&#xe915;" glyph-name="apprise" horiz-adv-x="1103" d="M419.207-63.539c-36.821 10.251-62.633 68.381-78.184 96.84s-44.871 79.948-44.871 79.948l144.118 77.477c0 0 60.549-101.414 89.638-152.536 19.503-48.548-37.228-71.026-70.145-90.61-12.11-6.879-26.274-13.39-40.556-11.119zM139.125 137.497c-83.563 6.246-150.932 89.762-137.383 173.161 0.044 28.578 33.377 106.495 61.177 57.277 41.786-74.223 86.086-147.054 127.101-221.634-9.907-13.558-36.039-7.416-50.895-8.805zM256.767 178.268c-51.040 82.94-97.903 168.519-147.818 252.248 31.046 22.803 61.092 39.433 87.762 60.464 113.646 71.464 237.133 203.369 288.762 347.602 13.484 45.244 66.37 79.001 93.522 38.262 100.485-174.847 203.317-348.42 302.511-523.936 17.51-66.627-63.993-53.787-103.86-44.62-133.333 17.402-276.261 7.503-394.63-61.032-41.186-22.873-80.753-48.963-122.811-70.028l-3.438 1.038zM1008.674 488.667c-59.824 20.665 2.515 73.201 14.237 107.157 44.133 94.328 5.38 215.539-83.422 269.141-47.146 29.856-104.57 37.992-159.139 29.894-49.006 8.783-26.794 61.723 19.937 63.521 135.186 15.694 273.035-84.419 296.526-219.010 18.169-86.287-5.187-184.47-69.789-246.399-5.822-2.236-11.938-5.013-18.349-4.303zM874.499 536.119c-56.018 26.015 12.996 72.844 8.156 111.868 9.085 66.073-58.288 124.609-122.441 110.005-37.378 8.906-34.985 58.261 13.385 63.11 100.043 8.227 190.553-92.3 170.885-191.055-6.546-34.584-27.598-94.615-69.985-93.926z" />
<glyph unicode="&#xe916;" glyph-name="msteams" horiz-adv-x="1082" d="M46.124 716.056c-25.473 0-46.124-20.65-46.124-46.124v-461.34c0-25.473 20.65-46.124 46.124-46.124h461.34c25.473 0 46.124 20.65 46.124 46.124v461.34c0 25.473-20.65 46.124-46.124 46.124zM155.407 589.183h242.773v-48.716h-92.223v-251.128h-58.755v251.128h-91.795zM875.863 565.077c22.27-4.283 38.848-24.124 38.305-47.545v-290.357c1.163-49.999-10.679-97.28-32.435-138.607 7.763-1.047 15.685-1.59 23.734-1.589h0.831c97.044 0 175.713 78.669 175.713 175.713v254.575c0 26.405-21.405 47.809-47.809 47.809zM1056.849 728.637c0-62.537-50.697-113.234-113.234-113.234s-113.234 50.697-113.234 113.234c0 62.537 50.697 113.234 113.234 113.234s113.234-50.697 113.234-113.234zM591.332 960c-90.332 0-163.56-73.229-163.56-163.56 0 0 0 0 0-0.001v0c0.070-13.428 1.748-26.431 4.85-38.871l-0.238 1.126h125.481c25.392-0.096 45.952-20.656 46.049-46.048v-79.234c84.737 6.734 150.952 77.144 150.978 163.024v0.002c0 0 0 0 0 0 0 90.332-73.228 163.56-163.56 163.56 0 0 0 0 0 0v0zM433.549 753.505c0.349-1.523 0.451-1.91 0.554-2.296l-0.259 1.142c-0.103 0.383-0.195 0.77-0.295 1.154zM445.484 722.433c0.5-1.032 0.572-1.168 0.643-1.303l-0.429 0.89c-0.071 0.138-0.144 0.275-0.214 0.413zM453.43 708.54c0.573-0.936 0.756-1.218 0.939-1.5l-0.386 0.634c-0.186 0.288-0.368 0.578-0.553 0.867zM462.734 695.437c0.587-0.757 0.913-1.165 1.241-1.573l-0.246 0.316c-0.334 0.416-0.664 0.836-0.995 1.256zM473.304 683.28c0.541-0.563 1.035-1.070 1.532-1.573l-0.017 0.017c-0.508 0.515-1.014 1.034-1.515 1.556zM484.912 672.322c0.495-0.431 1.21-1.036 1.93-1.635l0.266-0.215c-0.738 0.61-1.47 1.227-2.197 1.85zM497.604 662.494c0.406-0.305 1.331-0.945 2.263-1.576l0.577-0.368c-0.955 0.638-1.9 1.287-2.841 1.944zM511.254 653.922c0.322-0.22 1.457-0.849 2.601-1.465l0.87-0.428c-1.165 0.617-2.322 1.249-3.471 1.893zM525.667 646.743c0.317-0.191 1.683-0.778 3.058-1.347l1.085-0.398c-1.39 0.564-2.772 1.144-4.144 1.745zM540.6 641.060c0.485-0.213 2.136-0.735 3.798-1.232l1.158-0.297c-1.663 0.484-3.314 0.994-4.955 1.528zM557.738 636.421c-0.543 0.086 0.017-0.041 0.579-0.165l1.085-0.201c-0.556 0.117-1.11 0.242-1.664 0.366zM603.455 633.357c-0.696-0.041-1.385-0.082-2.071-0.121 1.133 0.057 1.851 0.1 2.568 0.148l-0.498-0.027zM602.36 565.077v-406.887c-0.125-18.659-11.432-35.421-28.686-42.525-5.493-2.324-11.397-3.522-17.362-3.523h-233.326c41.443-101.046 139.606-173.299 255.765-176.142 156.566 3.832 280.436 133.761 276.794 290.331v290.357c0.604 26.091-20.034 47.743-46.125 48.389z" />
</font></defs></svg>

BIN
static/fonts/icomoon.ttf View File


BIN
static/fonts/icomoon.woff View File


BIN
static/img/integrations/msteams.png View File

Before After
Width: 128  |  Height: 128  |  Size: 4.1 KiB

BIN
static/img/integrations/setup_msteams_1.png View File

Before After
Width: 657  |  Height: 505  |  Size: 62 KiB

BIN
static/img/integrations/setup_msteams_2.png View File

Before After
Width: 949  |  Height: 798  |  Size: 40 KiB

BIN
static/img/integrations/setup_msteams_3.png View File

Before After
Width: 847  |  Height: 986  |  Size: 92 KiB

BIN
static/img/integrations/setup_msteams_4.png View File

Before After
Width: 839  |  Height: 983  |  Size: 101 KiB

+ 12
- 0
templates/front/channels.html View File

@ -102,6 +102,8 @@
{% endif %}
{% elif ch.kind == "mattermost" %}
Mattermost
{% elif ch.kind == "msteams" %}
Microsoft Teams
{% else %}
{{ ch.kind }}
{% endif %}
@ -251,6 +253,16 @@
<a href="{% url 'hc-add-mattermost' %}" class="btn btn-primary">Add Integration</a>
</li>
<li>
<img src="{% static 'img/integrations/msteams.png' %}"
class="icon" alt="Microsoft Teams" />
<h2>Microsoft Teams</h2>
<p>Chat and collaboration platform for Microsoft Office 365 customers.</p>
<a href="{% url 'hc-add-msteams' %}" class="btn btn-primary">Add Integration</a>
</li>
<li>
<img src="{% static 'img/integrations/opsgenie.png' %}"
class="icon" alt="OpsGenie icon" />


+ 126
- 0
templates/integrations/add_msteams.html View File

@ -0,0 +1,126 @@
{% extends "base.html" %}
{% load humanize static hc_extras %}
{% block title %}Add Microsoft Teams - {% site_name %}{% endblock %}
{% block content %}
<div class="row">
<div class="col-sm-12">
<h1>Microsoft Teams</h1>
<p>If your team uses <a href="https://products.office.com/en-us/microsoft-teams/group-chat-software">Microsoft Teams</a>,
you can set up {% site_name %} to post status updates directly to an appropriate
Microsoft Teams channel.</p>
<h2>Setup Guide</h2>
<div class="row ai-step">
<div class="col-sm-6">
<span class="step-no">1</span>
<p>
Log into your Microsoft Teams account, click the <strong>Apps</strong> tab.
</p>
<p>
Search for the <strong>Incoming Webhook</strong> connector, and add it.
</p>
</div>
<div class="col-sm-6">
<div class="marker-wrap">
<span style="left: 6%; top: 83%;" class="marker"></span>
<img
class="ai-guide-screenshot"
alt="Add the Incoming Webhook connector"
src="{% static 'img/integrations/setup_msteams_1.png' %}" />
</div>
</div>
</div>
<div class="row ai-step">
<div class="col-sm-6">
<span class="step-no">2</span>
<p>
Select the channel where you want {% site_name %} to post
notifications.
</p>
</div>
<div class="col-sm-6">
<img
class="ai-guide-screenshot"
alt="Select the channel"
src="{% static 'img/integrations/setup_msteams_2.png' %}">
</div>
</div>
<div class="row ai-step">
<div class="col-sm-6">
<span class="step-no">3</span>
<p>
Give the connector a descriptive name.
</p>
<p>
Optionally, upload an icon
(feel free to use <a href="{% static 'img/[email protected]' %}">this one</a>).
</p>
<p>
Click on <strong>Create</strong>.
</p>
</div>
<div class="col-sm-6">
<img
class="ai-guide-screenshot"
alt="Create the connector"
src="{% static 'img/integrations/setup_msteams_3.png' %}">
</div>
</div>
<div class="row ai-step">
<div class="col-sm-6">
<span class="step-no">4</span>
<p>
Copy the displayed webhook URL and paste it
in the form below. Save the integration, and you are done!
</p>
</div>
<div class="col-sm-6">
<div class="marker-wrap">
<span style="left: 42%; top: 70%;" class="marker"></span>
<img
class="ai-guide-screenshot"
alt="Copy the Webhook URL"
src="{% static 'img/integrations/setup_msteams_4.png' %}">
</div>
</div>
</div>
<h2>Integration Settings</h2>
<form method="post" class="form-horizontal" action="{% url 'hc-add-msteams' %}">
{% csrf_token %}
<div class="form-group {{ form.value.css_classes }}">
<label for="post-url" class="col-sm-2 control-label">Webhook URL</label>
<div class="col-sm-10">
<input
id="post-url"
type="text"
class="form-control"
name="value"
placeholder="https://"
value="{{ form.value.value|default:"" }}">
{% if form.value.errors %}
<div class="help-block">
{{ form.value.errors|join:"" }}
</div>
{% endif %}
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<button type="submit" class="btn btn-primary">Save Integration</button>
</div>
</div>
</form>
</div>
</div>
{% endblock %}

+ 55
- 0
templates/integrations/msteams_message.json View File

@ -0,0 +1,55 @@
{% load hc_extras humanize %}
{
"@type": "MessageCard",
"@context": "https://schema.org/extensions",
"themeColor": "{% if check.status == "up" %}5cb85c{% endif %}{% if check.status == "down" %}d9534f{% endif %}",
"text": "“{{ check.name_then_code|escapejs }}” is {{ check.status|upper }}.",
"sections": [
{
"facts": [
{% if check.tags_list %}
{
"name": "Tags:",
"value": "{% for tag in check.tags_list %}`{{ tag|escapejs }}` {% endfor %}"
},
{% endif %}
{% if check.kind == "simple" %}
{
"name": "Period:",
"value": "{{ check.timeout|hc_duration }}"
},
{% elif check.kind == "cron" %}
{
"name": "Schedule:",
"value": "{{ check.schedule|escapejs }}"
},
{% endif %}
{
"name": "Last Ping:",
{% if check.last_ping %}
"value": "{{ check.last_ping|naturaltime }}"
{% else %}
"value": "Never"
{% endif %}
},
{
"name": "Total Pings:",
"value": "{{ check.n_pings }}"
}
],
"text": "{{ check.desc|escapejs }}"
}
],
"potentialAction": [
{
"@type": "OpenUri",
"name": "View in {% site_name %}",
"targets": [
{
"os": "default",
"uri": "{{ check.details_url }}"
}
]
}
]
}

Loading…
Cancel
Save