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 ## 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


+ 46
- 1
hc/api/tests/test_update_check.py 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)


+ 25
- 18
hc/api/views.py 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
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(","): 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) 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") == "*":
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 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 <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>
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> </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>
<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>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> </dd>
</dl> </dl>
<h3>Response Codes</h3> <h3>Response Codes</h3>


+ 39
- 8
templates/docs/api.md 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)
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 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:
<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: 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 ### Response Codes


Loading…
Cancel
Save