Browse Source

Trello integration WIP

pull/193/head
Pēteris Caune 6 years ago
parent
commit
e4d0103544
No known key found for this signature in database GPG Key ID: E28D7679E9A9EDE2
18 changed files with 248 additions and 8 deletions
  1. +1
    -0
      CHANGELOG.md
  2. +25
    -1
      hc/api/models.py
  3. +17
    -0
      hc/api/transports.py
  4. +2
    -0
      hc/front/urls.py
  5. +53
    -0
      hc/front/views.py
  6. +3
    -0
      hc/settings.py
  7. +10
    -6
      static/css/icomoon.css
  8. BIN
      static/fonts/icomoon.eot
  9. +1
    -0
      static/fonts/icomoon.svg
  10. BIN
      static/fonts/icomoon.ttf
  11. BIN
      static/fonts/icomoon.woff
  12. BIN
      static/img/integrations/trello.png
  13. +32
    -0
      static/js/add_trello.js
  14. +17
    -1
      templates/front/channels.html
  15. +6
    -0
      templates/front/welcome.html
  16. +39
    -0
      templates/integrations/add_trello.html
  17. +37
    -0
      templates/integrations/trello_settings.html
  18. +5
    -0
      templates/integrations/trello_title.html

+ 1
- 0
CHANGELOG.md View File

@ -9,6 +9,7 @@ All notable changes to this project will be documented in this file.
- Improved layout and styling in "Login" page.
- Separate "sign Up" and "Log In" forms.
- "My Checks" page: support filtering checks by query string parameters.
- Added Trello integration
### Bug Fixes
- Timezones were missing in the "Change Schedule" dialog, fixed.


+ 25
- 1
hc/api/models.py View File

@ -40,7 +40,8 @@ CHANNEL_KINDS = (("email", "Email"),
("discord", "Discord"),
("telegram", "Telegram"),
("sms", "SMS"),
("zendesk", "Zendesk"))
("zendesk", "Zendesk"),
("trello", "Trello"))
PO_PRIORITIES = {
-2: "lowest",
@ -300,6 +301,8 @@ class Channel(models.Model):
return transports.Sms(self)
elif self.kind == "zendesk":
return transports.Zendesk(self)
elif self.kind == "trello":
return transports.Trello(self)
else:
raise NotImplementedError("Unknown channel kind: %s" % self.kind)
@ -502,6 +505,27 @@ class Channel(models.Model):
doc = json.loads(self.value)
return doc["label"]
@property
def trello_token(self):
assert self.kind == "trello"
if self.value.startswith("{"):
doc = json.loads(self.value)
return doc["token"]
@property
def trello_board_list(self):
assert self.kind == "trello"
if self.value.startswith("{"):
doc = json.loads(self.value)
return doc["board_name"], doc["list_name"]
@property
def trello_list_id(self):
assert self.kind == "trello"
if self.value.startswith("{"):
doc = json.loads(self.value)
return doc["list_id"]
class Notification(models.Model):
class Meta:


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

@ -424,3 +424,20 @@ class Zendesk(HttpTransport):
return self.notify_down(check)
if check.status == "up":
return self.notify_up(check)
class Trello(HttpTransport):
URL = 'https://api.trello.com/1/cards'
def is_noop(self, check):
return check.status != "down"
def notify(self, check):
params = {
"idList": self.channel.trello_list_id,
"name": tmpl("trello_title.html", check=check),
"key": settings.TRELLO_APP_KEY,
"token": self.channel.trello_token
}
return self.post(self.URL, params=params)

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

@ -35,6 +35,8 @@ channel_urls = [
path('add_telegram/', views.add_telegram, name="hc-add-telegram"),
path('add_sms/', views.add_sms, name="hc-add-sms"),
path('add_zendesk/', views.add_zendesk, name="hc-add-zendesk"),
path('add_trello/', views.add_trello, name="hc-add-trello"),
path('add_trello/settings/', views.trello_settings, name="hc-trello-settings"),
path('<uuid:code>/checks/', views.channel_checks, name="hc-channel-checks"),
path('<uuid:code>/remove/', views.remove_channel, name="hc-remove-channel"),
path('<uuid:code>/verify/<slug:token>/', views.verify_email,


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

@ -156,6 +156,7 @@ def index(request):
"enable_telegram": settings.TELEGRAM_TOKEN is not None,
"enable_sms": settings.TWILIO_AUTH is not None,
"enable_pd": settings.PD_VENDOR_KEY is not None,
"enable_trello": settings.TRELLO_APP_KEY is not None,
"registration_open": settings.REGISTRATION_OPEN
}
@ -461,6 +462,7 @@ def channels(request):
"enable_sms": settings.TWILIO_AUTH is not None,
"enable_pd": settings.PD_VENDOR_KEY is not None,
"enable_zendesk": settings.ZENDESK_CLIENT_ID is not None,
"enable_trello": settings.TRELLO_APP_KEY is not None,
"use_payments": settings.USE_PAYMENTS
}
@ -1058,3 +1060,54 @@ def add_zendesk(request):
ctx = {"page": "channels"}
return render(request, "integrations/add_zendesk.html", ctx)
@login_required
def add_trello(request):
if settings.TRELLO_APP_KEY is None:
raise Http404("trello integration is not available")
if request.method == "POST":
channel = Channel(user=request.team.user, kind="trello")
channel.value = request.POST["settings"]
channel.save()
channel.assign_all_checks()
return redirect("hc-channels")
authorize_url = "https://trello.com/1/authorize?" + urlencode({
"expiration": "never",
"name": settings.SITE_NAME,
"scope": "read,write",
"response_type": "token",
"key": settings.TRELLO_APP_KEY,
"return_url": settings.SITE_ROOT + reverse("hc-add-trello")
})
ctx = {
"page": "channels",
"authorize_url": authorize_url
}
return render(request, "integrations/add_trello.html", ctx)
@login_required
@require_POST
def trello_settings(request):
token = request.POST.get("token")
url = "https://api.trello.com/1/members/me/boards?" + urlencode({
"key": settings.TRELLO_APP_KEY,
"token": token,
"fields": "id,name",
"lists": "open",
"list_fields": "id,name"
})
r = requests.get(url)
ctx = {
"token": token,
"data": r.json()
}
return render(request, "integrations/trello_settings.html", ctx)

+ 3
- 0
hc/settings.py View File

@ -169,6 +169,9 @@ PD_VENDOR_KEY = None
ZENDESK_CLIENT_ID = None
ZENDESK_CLIENT_SECRET = None
# Trello
TRELLO_APP_KEY = None
if os.path.exists(os.path.join(BASE_DIR, "hc/local_settings.py")):
from .local_settings import *
else:


+ 10
- 6
static/css/icomoon.css View File

@ -1,10 +1,10 @@
@font-face {
font-family: 'icomoon';
src: url('../fonts/icomoon.eot?7cu72n');
src: url('../fonts/icomoon.eot?7cu72n#iefix') format('embedded-opentype'),
url('../fonts/icomoon.ttf?7cu72n') format('truetype'),
url('../fonts/icomoon.woff?7cu72n') format('woff'),
url('../fonts/icomoon.svg?7cu72n#icomoon') format('svg');
src: url('../fonts/icomoon.eot?b4dy0b');
src: url('../fonts/icomoon.eot?b4dy0b#iefix') format('embedded-opentype'),
url('../fonts/icomoon.ttf?b4dy0b') format('truetype'),
url('../fonts/icomoon.woff?b4dy0b') format('woff'),
url('../fonts/icomoon.svg?b4dy0b#icomoon') format('svg');
font-weight: normal;
font-style: normal;
}
@ -76,6 +76,10 @@
content: "\e906";
color: #2ca5e0;
}
.icon-trello:before {
content: "\e911";
color: #0079bf;
}
.icon-zendesk:before {
content: "\e907";
}
@ -120,4 +124,4 @@
}
.icon-delete:before {
content: "\e900";
}
}

BIN
static/fonts/icomoon.eot View File


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

@ -35,4 +35,5 @@
<glyph unicode="&#xe90e;" glyph-name="po" d="M495.997 953.405c-141.739-4.445-268.129-66.64-357.064-163.715l-0.342-0.378c-81.965-89.463-132.19-209.178-132.19-340.623 0-147.43 63.183-280.103 163.948-372.418l0.377-0.341c89.463-81.965 209.178-132.191 340.625-132.191 147.429 0 280.102 63.182 372.416 163.947l0.341 0.377c81.965 89.463 132.19 209.178 132.19 340.623 0 147.43-63.183 280.103-163.948 372.418l-0.377 0.341c-89.463 81.967-209.18 132.193-340.627 132.193-5.398 0-10.776-0.085-16.133-0.253l0.783 0.019zM614.591 758.362c32.229 0 59.316-4.581 81.259-13.744 21.955-9.173 39.041-21.263 51.266-36.266s19.864-32.505 22.914-52.508c3.062-20.004 2.091-40.841-2.91-62.512-6.112-26.115-17.644-52.375-34.598-78.774-16.942-26.388-38.19-50.283-63.751-71.681-25.561-21.387-54.876-38.747-87.944-52.083-33.055-13.336-68.199-20.003-105.429-20.003h-4.166l-105.854-237.545h-120.023l235.88 530.096 126.688 16.671-123.355-278.385c21.671 1.667 42.922 9.028 63.754 22.080 20.842 13.064 40.020 29.456 57.528 49.176 17.498 19.732 32.636 41.678 45.415 65.844 12.78 24.176 21.948 47.935 27.505 71.272 3.334 14.447 4.864 28.339 4.591 41.674-0.284 13.336-3.757 25.005-10.425 35.007s-16.812 18.061-30.432 24.173c-13.608 6.112-32.081 9.166-55.419 9.166-27.228 0-54.039-4.444-80.439-13.336-26.388-8.891-50.84-21.81-73.349-38.764-22.499-16.942-42.081-38.057-58.751-63.346-16.67-25.277-28.616-54.030-35.841-86.259-2.778-10.558-4.444-19.028-5-25.412-0.556-6.396-0.693-11.675-0.409-15.837 0.272-4.173 0.823-7.368 1.651-9.591 0.839-2.223 1.533-4.445 2.090-6.668-28.339 0-49.039 5.696-62.103 17.093-13.052 11.385-16.243 31.249-9.575 59.588 6.668 29.449 21.81 57.508 45.432 84.179 23.61 26.672 51.665 50.151 84.166 70.439 32.511 20.276 68.075 36.385 106.688 48.326 38.625 11.953 76.27 17.93 112.944 17.93z" />
<glyph unicode="&#xe90f;" glyph-name="victorops" d="M512 960c-281.6 0-512-230.4-512-512s230.4-512 512-512c281.6 0 512 230.4 512 512s-230.4 512-512 512zM517.117 878.078l38.406-107.516 43.516 12.797-33.281-87.039 7.68-58.883 107.523 107.523-28.164-107.523 7.68-15.359-58.875-92.156 5.117-28.164 204.805 92.164-74.242-99.844 28.156-56.32-156.156-74.234-2.562-25.602 120.32 35.836-58.883-71.68-5.117-23.039-74.242-30.719-7.68-20.477 33.281 12.797-87.039-97.281-7.68-79.359v-74.242h-15.359v71.68l-5.117 81.922-87.047 97.281 33.281-12.797-7.68 20.477-74.234 30.719-5.125 23.039-58.875 71.68 120.32-35.836-2.563 25.602-156.164 74.234 35.844 56.32-74.242 99.844 204.805-92.164 5.117 28.164-58.883 92.156 7.68 15.359-28.156 107.523 107.516-107.523 7.68 58.883-33.273 87.039 43.516-12.797 38.398 107.516z" />
<glyph unicode="&#xe910;" glyph-name="opsgenie" d="M512 960c-282.768 0-512-229.232-512-512s229.232-512 512-512c282.768 0 512 229.232 512 512s-229.232 512-512 512zM495.383 948.391l30.367-37.445c0 0 7.253-8.951 10.758-22.234 3.255-12.334 2.809-29.959-7.141-47.32l13.531 2.844c32.791 6.889 66.948-2.705 93.945-21.547s47.633-47.728 50.227-81.617l-29.672-9.445c-26.13 43.575-68.24 62.829-117.078 82.070l-14.922 5.883c0.834-2.752 1.891-5.781 1.891-5.781l30.148-54.023-17.68 10.984c2.603-8.385 2.762-17.111 2.563-26.313-0.275-12.669-2.528-25.181-7.719-36.484 34.914-8.778 59.267-22.599 74.531-43.477 8.387-11.47 13.717-24.424 17.539-38.672 12.752 22.768 22.966 48.621 30.305 77.68l31.508-4.43c-2.381-73.83-26.175-120.553-73.156-155.898l-23.937-18.008-1.656 29.906c-1.456 26.262-4.843 50.034-14.461 70.43s-25.144 38.148-54.391 53.508l6.281 16.719c-6.554-6.579-15.462-10.447-24.266-10.32-11.75 0.168-22.661 6.567-29.648 16.508-8.886 12.638-11.428 27.924-10.898 42.922 0.529 15.001 4.106 30.041 13.969 41.945h0.008c5.104 6.156 12.225 10.734 19.867 12.617-3.339 2.91-7.009 6.168-10.5 10.906-4.247 5.764-8.275 13.225-9.469 22.461s1.062 19.955 7.258 29.336c15.019 22.741 13.852 38.125 13.852 38.125l-1.953 48.172zM457.164 842.617l11.641-28.938c-35.17-24.457-61.217-53.654-67.016-99.367-4.035-31.823-0.717-63.517 8.141-95.078 0.862-3.070 1.781-6.193 2.664-9.273 5.501 27.020 19.848 54.466 43.797 81.469l26.547-17.211c-17.941-39.65-24.277-67.814-20.008-93.922s19.673-52.577 50.789-87.469l-20.141-24.391c-12.069 7.204-22.848 14.899-32.539 22.969l-16.906-38.57c-8.287-18.91-21.003-34.435-36.875-46.672-24.642-18.997-52.489-28.874-79.508-36.773-26.367-7.711-52.851-15.207-79.492-22.25-15.792-4.175-26.281-10.884-33.375-19.727s-11.269-20.414-11.898-36.344c-1.008-25.485 0.648-51.913 1.156-80.688l1.055-59.766-30.766 51.25c-18.686 31.12-37.679 64.942-39.688 106.539-1.651 34.274 15.93 62.961 45.219 79.016v-0.008c23.426 12.844 48.363 17.632 72.398 20.273 39.239 4.306 76.612 12.084 110.328 29.242 18.845 9.591 37.246 22.81 58.344 35.922l36.078 22.414c-24.805 21.848-41.242 46.521-46.148 74.164-0.826 4.652-1.268 9.34-1.453 14.047l-19.055-9.125c-29.818 42.373-51.69 90.897-47.047 147.508 2.319 28.257 18.219 55.301 39.102 77.258s47.038 39.123 74.656 43.5zM586.336 490.391l19.969-17.898c46.577-41.765 103.259-57.55 166.203-65.32 21.030-2.594 42.295-7.902 62.086-16.016 18.984-7.782 34.064-19.927 43.234-35.852s12.119-34.924 9.859-55.062c-4.221-37.635-21.318-69.699-42.055-98.531l-27.195 16.711c1.625 3.133 4.464 22.376 4.195 35.969-0.38 19.329-1.923 40.502-5.617 58.406-2.954 14.317-11.918 23.384-27.922 28.484-26.183 8.341-53.183 15.682-80.242 24.406-21.345 6.882-43.337 13.9-64.867 23.234-24.373 10.566-44.874 27.966-58.914 51.312h-0.008v0.008c-1.988 3.31-3.731 6.805-5.18 10.453l-0.016 0.031-0.008 0.023c-2.683 6.832-0.709 10.745-0.234 12.266s0.532 1.7 0.437 1.305l6.273 26.070zM354.375 324.844c23.935 0.095 47.809-3.628 71.352-11.805l-5.258-25.375c34.753 11.803 70.997 19.494 106.859 19.992 58.332 0.81 124.732-31.934 169.859-60.227 0.371-0.233 0.858-0.471 1.227-0.703l-15.828-27.773c-34.072 17.473-58.857 23.082-92.32 29.477-38.314 7.318-77.564 7.713-115.953 1.039-42.665-7.42-103.194-24.248-133.297-33.008l-12.492 29.203c24.064 13.944 50.669 26.201 78.148 36.547-51.852 2.32-96.396-5.678-132.656-46.914l-37.344-42.469 9.555 55.742c7.106 41.435 40.632 70.255 84.203 74.93 7.984 0.858 15.967 1.312 23.945 1.344zM537.414 187.75c50.711-36.067 106.904-64.631 170.977-78.609l47.477-10.359-44.344-19.875c-10.521-4.716-20.742-10.556-33.070-14.367s-27.352-4.76-42.648 1.070c-47.215 17.993-86.476 49.949-114.727 91.742-34.219-47.839-81.731-84.28-141.695-98.625l-5.172-1.242-4.891 2.109c-13.876 5.986-27.016 11.651-44.078 19.008l-46.18 19.906 49.195 10.438c65.628 13.918 124.592 41.606 176.227 77.844l15.602-15.227 17.328 16.188z" />
<glyph unicode="&#xe911;" glyph-name="trello" d="M896 960h-768c-70.699 0-128-57.301-128-128v-768c0-70.656 57.301-128 128-128h768c70.656 0 128 57.344 128 128v768c0 70.699-57.344 128-128 128zM445.44 184.32c0-33.92-27.52-61.44-61.44-61.44h-189.44c-33.92 0-61.44 27.563-61.44 61.44v581.12c0 33.92 27.52 61.44 61.44 61.44h189.44c33.92 0 61.44-27.52 61.44-61.44v-581.12zM890.88 440.32c0-33.877-27.52-61.44-61.44-61.44h-189.44c-33.92 0-61.44 27.563-61.44 61.44v325.12c0 33.92 27.563 61.44 61.44 61.44h189.44c33.92 0 61.44-27.52 61.44-61.44v-325.12z" />
</font></defs></svg>

BIN
static/fonts/icomoon.ttf View File


BIN
static/fonts/icomoon.woff View File


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

Before After
Width: 200  |  Height: 200  |  Size: 1.4 KiB

+ 32
- 0
static/js/add_trello.js View File

@ -0,0 +1,32 @@
$(function() {
function updateSettings() {
var opt = $('#list-selector').find(":selected");
$("#settings").val(JSON.stringify({
"token": $("#settings").data("token"),
"list_id": opt.data("listId"),
"board_name": opt.data("boardName"),
"list_name": opt.data("listName")
}));
}
var tokenMatch = window.location.hash.match(/token=(\w+)/);
if (tokenMatch) {
$(".jumbotron").hide();
$("integration-settings").text("Loading...");
token = tokenMatch[1];
var csrf = $('input[name=csrfmiddlewaretoken]').val();
$.ajax({
url: "/integrations/add_trello/settings/",
type: "post",
headers: {"X-CSRFToken": csrf},
data: {token: token},
success: function(data) {
$("#integration-settings" ).html(data);
updateSettings();
}
});
}
$("#integration-settings").on("change", "#list-selector", updateSettings);
});

+ 17
- 1
templates/front/channels.html View File

@ -108,6 +108,11 @@
{% else %}
{{ ch.sms_number }}
{% endif %}
{% elif ch.kind == "trello" %}
<span class="preposition">board</span>
{{ ch.trello_board_list|first }},
<span class="preposition">list</span>
{{ ch.trello_board_list|last }}
{% else %}
{{ ch.value }}
{% endif %}
@ -288,7 +293,7 @@
{% if enable_zendesk %}
<li>
<img src="{% static 'img/integrations/zendesk.png' %}"
class="icon" alt="Discord icon" />
class="icon" alt="Zendesk icon" />
<h2>Zendesk Support</h2>
<p>Create a Zendesk support ticket when a check goes down.</p>
@ -296,6 +301,17 @@
<a href="{% url 'hc-add-zendesk' %}" class="btn btn-primary">Add Integration</a>
</li>
{% endif %}
{% if enable_trello %}
<li>
<img src="{% static 'img/integrations/trello.png' %}"
class="icon" alt="Trello icon" />
<h2>Trello</h2>
<p>Create a Trello card when a check goes down.</p>
<a href="{% url 'hc-add-trello' %}" class="btn btn-primary">Add Integration</a>
</li>
{% endif %}
<li class="link-to-github">
<img src="{% static 'img/integrations/missing.png' %}"
class="icon" alt="Suggest New Integration" />


+ 6
- 0
templates/front/welcome.html View File

@ -368,6 +368,12 @@
<img src="{% static 'img/integrations/pagertree.png' %}" class="icon" alt="PagerTree icon" />
<h3>PagerTree<br><small>Incident Management</small></h3>
</div>
{% if enable_trello %}
<div class="integration">
<img src="{% static 'img/integrations/trello.png' %}" class="icon" alt="Trello icon" />
<h3>Trello<br><small>Project Management</small></h3>
</div>
{% endif %}
</div>
</div>


+ 39
- 0
templates/integrations/add_trello.html View File

@ -0,0 +1,39 @@
{% extends "base.html" %}
{% load compress humanize static hc_extras %}
{% block title %}Add Trello - {% site_name %}{% endblock %}
{% block content %}
<div class="row">
<div class="col-sm-12">
<h1>Trello</h1>
<div class="jumbotron">
<p>
With this integration, {% site_name %} will create a
<a href="https://trell.ocom/">Trello</a> card each
time a check goes down.
</p>
<div class="text-center">
{% csrf_token %}
<a href="{{ authorize_url }}" class="btn btn-lg btn-default">
<img class="ai-icon" src="{% static 'img/integrations/discord.png' %}" alt="Discord" />
Connect Trello
</a>
</div>
</div>
<div id="integration-settings"></div>
</div>
</div>
{% endblock %}
{% block scripts %}
{% compress js %}
<script src="{% static 'js/jquery-2.1.4.min.js' %}"></script>
<script src="{% static 'js/bootstrap.min.js' %}"></script>
<script src="{% static 'js/add_trello.js' %}"></script>
{% endcompress %}
{% endblock %}

+ 37
- 0
templates/integrations/trello_settings.html View File

@ -0,0 +1,37 @@
<p class="text-success">Authentication successful!</p>
<p>Please select the Trello list to post notifications to:</p>
<form method="post" class="form-horizontal">
{% csrf_token %}
<input type="hidden" id="settings" name="settings" data-token="{{ token }}"/>
<div class="form-group {{ form.value.css_classes }}">
<label for="list-selector" class="col-sm-3 control-label">Post Notifications to</label>
<div class="col-sm-3">
<select id="list-selector" name="board_list_id" class="form-control">
{% for board in data %}
<optgroup label="{{ board.name }}">
{% for list in board.lists %}
<option
data-list-id="{{ list.id }}"
data-board-name="{{ board.name }}"
data-list-name="{{ list.name}}"
value="{{ board.id }},{{ list.id}}">{{ list.name }}</option>
{% endfor %}
</optgroup>
{% endfor %}
</select>
{% if form.board_list_id.errors %}
<div class="help-block">
{{ form.board_list_id.errors|join:"" }}
</div>
{% endif %}
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-3 col-sm-9">
<button type="submit" class="btn btn-primary">Save Integration</button>
</div>
</div>
</form>

+ 5
- 0
templates/integrations/trello_title.html View File

@ -0,0 +1,5 @@
{% if check.status == "down" %}
{{ check.name_then_code }} is DOWN
{% else %}
{{ check.name_then_code }} is now UP
{% endif %}

Loading…
Cancel
Save