Browse Source

Update API to allow specifying channels by names

Fixes: #440
pull/456/head
Pēteris Caune 4 years ago
parent
commit
0e77064c44
No known key found for this signature in database GPG Key ID: E28D7679E9A9EDE2
5 changed files with 139 additions and 35 deletions
  1. +1
    -0
      CHANGELOG.md
  2. +46
    -1
      hc/api/tests/test_update_check.py
  3. +25
    -18
      hc/api/views.py
  4. +28
    -8
      templates/docs/api.html
  5. +39
    -8
      templates/docs/api.md

+ 1
- 0
CHANGELOG.md View File

@ -5,6 +5,7 @@ All notable changes to this project will be documented in this file.
## Improvements
- Add a tooltip to the 'confirmation link' label (#436)
- Update API to allow specifying channels by names (#440)
## v1.17.0 - 2020-10-14


+ 46
- 1
hc/api/tests/test_update_check.py View File

@ -123,6 +123,28 @@ class UpdateCheckTestCase(BaseTestCase):
self.check.refresh_from_db()
self.assertEqual(self.check.channel_set.count(), 1)
def test_it_sets_channel_by_name(self):
channel = Channel.objects.create(project=self.project, name="alerts")
r = self.post(self.check.code, {"api_key": "X" * 32, "channels": "alerts"})
self.assertEqual(r.status_code, 200)
self.check.refresh_from_db()
self.assertEqual(self.check.channel_set.count(), 1)
self.assertEqual(self.check.channel_set.first().code, channel.code)
def test_it_sets_channel_by_name_formatted_as_uuid(self):
name = "102eaa82-a274-4b15-a499-c1bb6bbcd7b6"
channel = Channel.objects.create(project=self.project, name=name)
r = self.post(self.check.code, {"api_key": "X" * 32, "channels": name})
self.assertEqual(r.status_code, 200)
self.check.refresh_from_db()
self.assertEqual(self.check.channel_set.count(), 1)
self.assertEqual(self.check.channel_set.first().code, channel.code)
def test_it_handles_comma_separated_channel_codes(self):
c1 = Channel.objects.create(project=self.project)
c2 = Channel.objects.create(project=self.project)
@ -187,10 +209,33 @@ class UpdateCheckTestCase(BaseTestCase):
self.check.refresh_from_db()
self.assertEqual(self.check.channel_set.count(), 0)
def test_it_rejects_non_uuid_channel_code(self):
def test_it_handles_channel_lookup_by_name_with_no_results(self):
r = self.post(self.check.code, {"api_key": "X" * 32, "channels": "foo"})
self.assertEqual(r.status_code, 400)
self.assertEqual(r.json()["error"], "invalid channel identifier: foo")
self.check.refresh_from_db()
self.assertEqual(self.check.channel_set.count(), 0)
def test_it_handles_channel_lookup_by_name_with_multiple_results(self):
Channel.objects.create(project=self.project, name="foo")
Channel.objects.create(project=self.project, name="foo")
r = self.post(self.check.code, {"api_key": "X" * 32, "channels": "foo"})
self.assertEqual(r.status_code, 400)
self.assertEqual(r.json()["error"], "non-unique channel identifier: foo")
self.check.refresh_from_db()
self.assertEqual(self.check.channel_set.count(), 0)
def test_it_rejects_multiple_empty_channel_names(self):
Channel.objects.create(project=self.project, name="")
r = self.post(self.check.code, {"api_key": "X" * 32, "channels": ","})
self.assertEqual(r.status_code, 400)
self.assertEqual(r.json()["error"], "empty channel identifier")
self.check.refresh_from_db()
self.assertEqual(self.check.channel_set.count(), 0)


+ 25
- 18
hc/api/views.py View File

@ -1,6 +1,5 @@
from datetime import timedelta as td
import time
import uuid
from django.conf import settings
from django.db import connection
@ -71,20 +70,32 @@ def _lookup(project, spec):
def _update(check, spec):
channels = set()
# First, validate the supplied channel codes
if "channels" in spec and spec["channels"] not in ("*", ""):
q = Channel.objects.filter(project=check.project)
# First, validate the supplied channel codes/names
if "channels" not in spec:
# If the channels key is not present, don't update check's channels
new_channels = None
elif spec["channels"] == "*":
# "*" means "all project's channels"
new_channels = Channel.objects.filter(project=check.project)
elif spec.get("channels") == "":
# "" means "empty list"
new_channels = []
else:
# expect a comma-separated list of channel codes or names
new_channels = set()
available = list(Channel.objects.filter(project=check.project))
for s in spec["channels"].split(","):
try:
code = uuid.UUID(s)
except ValueError:
raise BadChannelException("invalid channel identifier: %s" % s)
if s == "":
raise BadChannelException("empty channel identifier")
try:
channels.add(q.get(code=code))
except Channel.DoesNotExist:
matches = [c for c in available if str(c.code) == s or c.name == s]
if len(matches) == 0:
raise BadChannelException("invalid channel identifier: %s" % s)
elif len(matches) > 1:
raise BadChannelException("non-unique channel identifier: %s" % s)
new_channels.add(matches[0])
if "name" in spec:
check.name = spec["name"]
@ -119,12 +130,8 @@ def _update(check, spec):
# This needs to be done after saving the check, because of
# the M2M relation between checks and channels:
if spec.get("channels") == "*":
check.assign_all_channels()
elif spec.get("channels") == "":
check.channel_set.clear()
elif channels:
check.channel_set.set(channels)
if new_channels is not None:
check.channel_set.set(new_channels)
return check


+ 28
- 8
templates/docs/api.html View File

@ -347,10 +347,20 @@ and POST requests.</p>
<p>By default, this API call assigns no integrations to the newly created
check.</p>
<p>Set this field to a special value "*" to automatically assign all existing
integrations.</p>
integrations. Example:</p>
<p><pre>{"channels": "*"}</pre></p>
<p>To assign specific integrations, use a comma-separated list of integration
identifiers. Use the <a href="#list-channels">Get a List of Existing Integrations</a>
API call to look up the available integration identifiers.</p>
UUIDs. You can look up integration UUIDs using the
<a href="#list-channels">Get a List of Existing Integrations</a> API call.</p>
<p>Example:</p>
<p><pre>{"channels":
"4ec5a071-2d08-4baa-898a-eb4eb3cd6941,746a083e-f542-4554-be1a-707ce16d3acc"}</pre></p>
<p>Alternatively, if you have named your integrations in SITE_NAME dashboard,
you can specify integrations by their names. For this to work, your integrations
need non-empty and unique names, and they must not contain commas. The names
must match exactly, whitespace is significant.</p>
<p>Example:</p>
<p><pre>{"channels": "Email to Alice,SMS to Alice"}</pre></p>
</dd>
<dt>unique</dt>
<dd>
@ -496,13 +506,23 @@ and POST requests.</p>
<dd>
<p>string, optional.</p>
<p>Set this field to a special value "*" to automatically assign all existing
notification channels.</p>
integrations. Example:</p>
<p><pre>{"channels": "*"}</pre></p>
<p>Set this field to a special value "" (empty string) to automatically <em>unassign</em>
all notification channels.</p>
<p>Set this field to a comma-separated list of channel identifiers to assign
specific notification channels.</p>
all existing integrations. Example:</p>
<p><pre>{"channels": ""}</pre></p>
<p>To assign specific integrations, use a comma-separated list of integration
UUIDs. You can look up integration UUIDs using the
<a href="#list-channels">Get a List of Existing Integrations</a> API call.</p>
<p>Example:</p>
<p><pre>{"channels":
"4ec5a071-2d08-4baa-898a-eb4eb3cd6941,746a083e-f542-4554-be1a-707ce16d3acc"}</pre></p>
<p>Alternatively, if you have named your integrations in SITE_NAME dashboard,
you can specify integrations by their names. For this to work, your integrations
need non-empty and unique names, and they must not contain commas. The names
must match exactly, whitespace is significant.</p>
<p>Example:</p>
<p><pre>{"channels": "4ec5a071-2d08-4baa-898a-eb4eb3cd6941,746a083e-f542-4554-be1a-707ce16d3acc"}</pre></p>
<p><pre>{"channels": "Email to Alice,SMS to Alice"}</pre></p>
</dd>
</dl>
<h3>Response Codes</h3>


+ 39
- 8
templates/docs/api.md View File

@ -360,11 +360,27 @@ channels
check.
Set this field to a special value "*" to automatically assign all existing
integrations.
integrations. Example:
<pre>{"channels": "*"}</pre>
To assign specific integrations, use a comma-separated list of integration
identifiers. Use the [Get a List of Existing Integrations](#list-channels)
API call to look up the available integration identifiers.
UUIDs. You can look up integration UUIDs using the
[Get a List of Existing Integrations](#list-channels) API call.
Example:
<pre>{"channels":
"4ec5a071-2d08-4baa-898a-eb4eb3cd6941,746a083e-f542-4554-be1a-707ce16d3acc"}</pre>
Alternatively, if you have named your integrations in SITE_NAME dashboard,
you can specify integrations by their names. For this to work, your integrations
need non-empty and unique names, and they must not contain commas. The names
must match exactly, whitespace is significant.
Example:
<pre>{"channels": "Email to Alice,SMS to Alice"}</pre>
unique
: array of string values, optional, default value: [].
@ -540,17 +556,32 @@ channels
: string, optional.
Set this field to a special value "*" to automatically assign all existing
notification channels.
integrations. Example:
<pre>{"channels": "*"}</pre>
Set this field to a special value "" (empty string) to automatically *unassign*
all notification channels.
all existing integrations. Example:
<pre>{"channels": ""}</pre>
To assign specific integrations, use a comma-separated list of integration
UUIDs. You can look up integration UUIDs using the
[Get a List of Existing Integrations](#list-channels) API call.
Example:
<pre>{"channels":
"4ec5a071-2d08-4baa-898a-eb4eb3cd6941,746a083e-f542-4554-be1a-707ce16d3acc"}</pre>
Set this field to a comma-separated list of channel identifiers to assign
specific notification channels.
Alternatively, if you have named your integrations in SITE_NAME dashboard,
you can specify integrations by their names. For this to work, your integrations
need non-empty and unique names, and they must not contain commas. The names
must match exactly, whitespace is significant.
Example:
<pre>{"channels": "4ec5a071-2d08-4baa-898a-eb4eb3cd6941,746a083e-f542-4554-be1a-707ce16d3acc"}</pre>
<pre>{"channels": "Email to Alice,SMS to Alice"}</pre>
### Response Codes


Loading…
Cancel
Save