Browse Source

"Edit" function for webhook integrations (#176)

pull/358/head
Pēteris Caune 5 years ago
parent
commit
609f78c5ed
No known key found for this signature in database GPG Key ID: E28D7679E9A9EDE2
12 changed files with 192 additions and 18 deletions
  1. +1
    -0
      CHANGELOG.md
  2. +4
    -2
      hc/front/forms.py
  3. +16
    -0
      hc/front/tests/test_add_webhook.py
  4. +84
    -0
      hc/front/tests/test_edit_webhook.py
  5. +1
    -4
      hc/front/tests/test_update_channel.py
  6. +1
    -0
      hc/front/urls.py
  7. +41
    -3
      hc/front/views.py
  8. +4
    -0
      static/css/channels.css
  9. +4
    -0
      static/css/webhook_form.css
  10. +1
    -1
      templates/base.html
  11. +3
    -0
      templates/front/channels.html
  12. +32
    -8
      templates/integrations/webhook_form.html

+ 1
- 0
CHANGELOG.md View File

@ -6,6 +6,7 @@ All notable changes to this project will be documented in this file.
### Improvements
- Rate limiting for Telegram notifications (10 notifications per chat per minute)
- Use Slack V2 OAuth flow
- "Edit" function for webhook integrations (#176)
### Bug Fixes
- "Get a single check" API call now supports read-only API keys (#346)


+ 4
- 2
hc/front/forms.py View File

@ -136,8 +136,9 @@ class AddUrlForm(forms.Form):
METHODS = ("GET", "POST", "PUT")
class AddWebhookForm(forms.Form):
class WebhookForm(forms.Form):
error_css_class = "has-error"
name = forms.CharField(max_length=100, required=False)
method_down = forms.ChoiceField(initial="GET", choices=zip(METHODS, METHODS))
body_down = forms.CharField(max_length=1000, required=False)
@ -160,7 +161,8 @@ class AddWebhookForm(forms.Form):
url_up = self.cleaned_data.get("url_up")
if not url_down and not url_up:
self.add_error("url_down", "Enter a valid URL.")
if not self.has_error("url_down"):
self.add_error("url_down", "Enter a valid URL.")
def get_value(self):
return json.dumps(dict(self.cleaned_data), sort_keys=True)


+ 16
- 0
hc/front/tests/test_add_webhook.py View File

@ -12,6 +12,22 @@ class AddWebhookTestCase(BaseTestCase):
r = self.client.get(self.url)
self.assertContains(r, "Executes an HTTP request")
def test_it_saves_name(self):
form = {
"name": "Call foo.com",
"method_down": "GET",
"url_down": "http://foo.com",
"method_up": "GET",
"url_up": "",
}
self.client.login(username="[email protected]", password="password")
r = self.client.post(self.url, form)
self.assertRedirects(r, self.channels_url)
c = Channel.objects.get()
self.assertEqual(c.name, "Call foo.com")
def test_it_adds_two_webhook_urls_and_redirects(self):
form = {
"method_down": "GET",


+ 84
- 0
hc/front/tests/test_edit_webhook.py View File

@ -0,0 +1,84 @@
import json
from hc.api.models import Channel
from hc.test import BaseTestCase
class EditWebhookTestCase(BaseTestCase):
def setUp(self):
super(EditWebhookTestCase, self).setUp()
definition = {
"method_down": "GET",
"url_down": "http://example.org/down",
"body_down": "$NAME is down",
"headers_down": {"User-Agent": "My-Custom-UA"},
"method_up": "GET",
"url_up": "http://example.org/up",
"body_up": "$NAME is up",
"headers_up": {},
}
self.channel = Channel(project=self.project, kind="webhook")
self.channel.name = "Call example.org"
self.channel.value = json.dumps(definition)
self.channel.save()
self.url = "/integrations/%s/edit_webhook/" % self.channel.code
def test_it_shows_form(self):
self.client.login(username="[email protected]", password="password")
r = self.client.get(self.url)
self.assertContains(r, "Webhook Settings")
self.assertContains(r, "Call example.org")
# down
self.assertContains(r, "http://example.org/down")
self.assertContains(r, "My-Custom-UA")
self.assertContains(r, "$NAME is down")
# up
self.assertContains(r, "http://example.org/up")
self.assertContains(r, "$NAME is up")
def test_it_saves_form_and_redirects(self):
form = {
"name": "Call foo.com / bar.com",
"method_down": "POST",
"url_down": "http://foo.com",
"headers_down": "X-Foo: 1\nX-Bar: 2",
"body_down": "going down",
"method_up": "POST",
"url_up": "https://bar.com",
"headers_up": "Content-Type: text/plain",
"body_up": "going up",
}
self.client.login(username="[email protected]", password="password")
r = self.client.post(self.url, form)
self.assertRedirects(r, self.channels_url)
self.channel.refresh_from_db()
self.assertEqual(self.channel.name, "Call foo.com / bar.com")
down_spec = self.channel.down_webhook_spec
self.assertEqual(down_spec["method"], "POST")
self.assertEqual(down_spec["url"], "http://foo.com")
self.assertEqual(down_spec["body"], "going down")
self.assertEqual(down_spec["headers"], {"X-Foo": "1", "X-Bar": "2"})
up_spec = self.channel.up_webhook_spec
self.assertEqual(up_spec["method"], "POST")
self.assertEqual(up_spec["url"], "https://bar.com")
self.assertEqual(up_spec["body"], "going up")
self.assertEqual(up_spec["headers"], {"Content-Type": "text/plain"})
def test_it_requires_kind_webhook(self):
self.channel.kind = "email"
self.channel.value = "[email protected]"
self.channel.save()
self.client.login(username="[email protected]", password="password")
r = self.client.get(self.url)
self.assertEqual(r.status_code, 400)

+ 1
- 4
hc/front/tests/test_update_channel.py View File

@ -7,10 +7,7 @@ class UpdateChannelTestCase(BaseTestCase):
def setUp(self):
super(UpdateChannelTestCase, self).setUp()
self.check = Check.objects.create(project=self.project)
self.channel = Channel(project=self.project, kind="email")
self.channel.email = "[email protected]"
self.channel.save()
self.channel = Channel.objects.create(project=self.project, kind="email")
def test_it_works(self):
payload = {"channel": self.channel.code, "check-%s" % self.check.code: True}


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

@ -39,6 +39,7 @@ channel_urls = [
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>/name/", views.update_channel_name, name="hc-channel-name"),
path("<uuid:code>/edit_webhook/", views.edit_webhook, name="hc-edit-webhook"),
path("<uuid:code>/test/", views.send_test_notification, name="hc-channel-test"),
path("<uuid:code>/remove/", views.remove_channel, name="hc-remove-channel"),
path(


+ 41
- 3
hc/front/views.py View File

@ -843,16 +843,18 @@ def add_webhook(request, code):
project = _get_project_for_user(request, code)
if request.method == "POST":
form = forms.AddWebhookForm(request.POST)
form = forms.WebhookForm(request.POST)
if form.is_valid():
channel = Channel(project=project, kind="webhook")
channel.name = form.cleaned_data["name"]
channel.value = form.get_value()
channel.save()
channel.assign_all_checks()
return redirect("hc-p-channels", project.code)
else:
form = forms.AddWebhookForm()
form = forms.WebhookForm()
ctx = {
"page": "channels",
@ -860,7 +862,43 @@ def add_webhook(request, code):
"form": form,
"now": timezone.now().replace(microsecond=0).isoformat(),
}
return render(request, "integrations/add_webhook.html", ctx)
return render(request, "integrations/webhook_form.html", ctx)
@login_required
def edit_webhook(request, code):
channel = _get_channel_for_user(request, code)
if channel.kind != "webhook":
return HttpResponseBadRequest()
if request.method == "POST":
form = forms.WebhookForm(request.POST)
if form.is_valid():
channel.name = form.cleaned_data["name"]
channel.value = form.get_value()
channel.save()
return redirect("hc-p-channels", channel.project.code)
else:
def flatten(d):
return "\n".join("%s: %s" % pair for pair in d.items())
doc = json.loads(channel.value)
doc["headers_down"] = flatten(doc["headers_down"])
doc["headers_up"] = flatten(doc["headers_up"])
doc["name"] = channel.name
form = forms.WebhookForm(doc)
ctx = {
"page": "channels",
"project": channel.project,
"channel": channel,
"form": form,
"now": timezone.now().replace(microsecond=0).isoformat(),
}
return render(request, "integrations/webhook_form.html", ctx)
@require_setting("SHELL_ENABLED")


+ 4
- 0
static/css/channels.css View File

@ -105,6 +105,10 @@ table.channels-table > tbody > tr > th {
color: #000;
}
.channel-row .actions {
text-align: right;
}
.channel-row .actions form {
display: inline;
}


static/css/add_webhook.css → static/css/webhook_form.css View File


+ 1
- 1
templates/base.html View File

@ -22,7 +22,7 @@
<link rel="stylesheet" href="{% static 'css/add_project_modal.css' %}" type="text/css">
<link rel="stylesheet" href="{% static 'css/add_pushover.css' %}" type="text/css">
<link rel="stylesheet" href="{% static 'css/add_webhook.css' %}" type="text/css">
<link rel="stylesheet" href="{% static 'css/webhook_form.css' %}" type="text/css">
<link rel="stylesheet" href="{% static 'css/base.css' %}" type="text/css">
<link rel="stylesheet" href="{% static 'css/billing.css' %}" type="text/css">
<link rel="stylesheet" href="{% static 'css/channel_checks.css' %}" type="text/css">


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

@ -131,6 +131,9 @@
{% endif %}
</td>
<td class="actions">
{% if ch.kind == "webhook" %}
<a class="btn btn-sm btn-default" href="{% url 'hc-edit-webhook' ch.code %}">Edit</a>
{% endif %}
<form action="{% url 'hc-channel-test' ch.code %}" method="post">
{% csrf_token %}
<button


templates/integrations/add_webhook.html → templates/integrations/webhook_form.html View File


Loading…
Cancel
Save