Update API to allow specifying channels by names

Fixes: #440
This commit is contained in:
Pēteris Caune 2020-10-14 15:37:04 +03:00
parent 20008a1d7e
commit 0e77064c44
No known key found for this signature in database
GPG Key ID: E28D7679E9A9EDE2
5 changed files with 140 additions and 36 deletions

View File

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

View File

@ -123,6 +123,28 @@ class UpdateCheckTestCase(BaseTestCase):
self.check.refresh_from_db() self.check.refresh_from_db()
self.assertEqual(self.check.channel_set.count(), 1) 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): def test_it_handles_comma_separated_channel_codes(self):
c1 = Channel.objects.create(project=self.project) c1 = Channel.objects.create(project=self.project)
c2 = 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.check.refresh_from_db()
self.assertEqual(self.check.channel_set.count(), 0) 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"}) r = self.post(self.check.code, {"api_key": "X" * 32, "channels": "foo"})
self.assertEqual(r.status_code, 400) 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.check.refresh_from_db()
self.assertEqual(self.check.channel_set.count(), 0) self.assertEqual(self.check.channel_set.count(), 0)

View File

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

View File

@ -347,10 +347,20 @@ and POST requests.</p>
<p>By default, this API call assigns no integrations to the newly created <p>By default, this API call assigns no integrations to the newly created
check.</p> check.</p>
<p>Set this field to a special value "*" to automatically assign all existing <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 <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> UUIDs. You can look up integration UUIDs using the
API call to look up the available integration identifiers.</p> <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> </dd>
<dt>unique</dt> <dt>unique</dt>
<dd> <dd>
@ -496,13 +506,23 @@ and POST requests.</p>
<dd> <dd>
<p>string, optional.</p> <p>string, optional.</p>
<p>Set this field to a special value "*" to automatically assign all existing <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> <p>Set this field to a special value "" (empty string) to automatically <em>unassign</em>
all notification channels.</p> all existing integrations. Example:</p>
<p>Set this field to a comma-separated list of channel identifiers to assign <p><pre>{"channels": ""}</pre></p>
specific notification channels.</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>Example:</p>
<p><pre>{"channels": "4ec5a071-2d08-4baa-898a-eb4eb3cd6941,746a083e-f542-4554-be1a-707ce16d3acc"}</pre></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> </dd>
</dl> </dl>
<h3>Response Codes</h3> <h3>Response Codes</h3>

View File

@ -360,11 +360,27 @@ channels
check. check.
Set this field to a special value "*" to automatically assign all existing 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 To assign specific integrations, use a comma-separated list of integration
identifiers. Use the [Get a List of Existing Integrations](#list-channels) UUIDs. You can look up integration UUIDs using the
API call to look up the available integration identifiers. [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 unique
: array of string values, optional, default value: []. : array of string values, optional, default value: [].
@ -540,17 +556,32 @@ channels
: string, optional. : string, optional.
Set this field to a special value "*" to automatically assign all existing 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* Set this field to a special value "" (empty string) to automatically *unassign*
all notification channels. all existing integrations. Example:
Set this field to a comma-separated list of channel identifiers to assign <pre>{"channels": ""}</pre>
specific notification channels.
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: Example:
<pre>{"channels": "4ec5a071-2d08-4baa-898a-eb4eb3cd6941,746a083e-f542-4554-be1a-707ce16d3acc"}</pre> <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>
### Response Codes ### Response Codes