Browse Source

Add "Get a list of checks's logged pings" API call (#371)

pull/379/head
Pēteris Caune 4 years ago
parent
commit
a07325e40f
No known key found for this signature in database GPG Key ID: E28D7679E9A9EDE2
7 changed files with 267 additions and 8 deletions
  1. +2
    -0
      CHANGELOG.md
  2. +11
    -0
      hc/api/models.py
  3. +58
    -0
      hc/api/tests/test_get_pings.py
  4. +1
    -0
      hc/api/urls.py
  5. +35
    -1
      hc/api/views.py
  6. +76
    -3
      templates/docs/api.html
  7. +84
    -4
      templates/docs/api.md

+ 2
- 0
CHANGELOG.md View File

@ -5,6 +5,8 @@ All notable changes to this project will be documented in this file.
### Improvements
- Paused ping handling can be controlled via API (#376)
- Add "Get a list of checks's logged pings" API call (#371)
### Bug Fixes
- Removing Pager Team integration, project appears to be discontinued


+ 11
- 0
hc/api/models.py View File

@ -342,6 +342,17 @@ class Ping(models.Model):
ua = models.CharField(max_length=200, blank=True)
body = models.TextField(blank=True, null=True)
def to_dict(self):
return {
"type": self.kind or "success",
"date": self.created.isoformat(),
"n": self.n,
"scheme": self.scheme,
"remote_addr": self.remote_addr,
"method": self.method,
"ua": self.ua,
}
class Channel(models.Model):
name = models.CharField(max_length=100, blank=True)


+ 58
- 0
hc/api/tests/test_get_pings.py View File

@ -0,0 +1,58 @@
from datetime import timedelta as td
from hc.api.models import Check
from hc.test import BaseTestCase
class GetPingsTestCase(BaseTestCase):
def setUp(self):
super(GetPingsTestCase, self).setUp()
self.a1 = Check(project=self.project, name="Alice 1")
self.a1.timeout = td(seconds=3600)
self.a1.grace = td(seconds=900)
self.a1.n_pings = 0
self.a1.status = "new"
self.a1.tags = "a1-tag a1-additional-tag"
self.a1.desc = "This is description"
self.a1.save()
self.url = "/api/v1/checks/%s/pings/" % self.a1.code
def get(self, api_key="X" * 32):
return self.client.get(self.url, HTTP_X_API_KEY=api_key)
def test_it_works(self):
self.a1.ping(
remote_addr="1.2.3.4",
scheme="https",
method="get",
ua="foo-agent",
body="",
action=None,
)
r = self.get()
self.assertEqual(r.status_code, 200)
self.assertEqual(r["Access-Control-Allow-Origin"], "*")
doc = r.json()
self.assertEqual(len(doc["pings"]), 1)
ping = doc["pings"][0]
self.assertEqual(ping["n"], 1)
self.assertEqual(ping["remote_addr"], "1.2.3.4")
self.assertEqual(ping["scheme"], "https")
self.assertEqual(ping["method"], "get")
self.assertEqual(ping["ua"], "foo-agent")
def test_readonly_key_is_not_allowed(self):
self.project.api_key_readonly = "R" * 32
self.project.save()
r = self.get(api_key=self.project.api_key_readonly)
self.assertEqual(r.status_code, 401)
def test_it_rejects_post(self):
r = self.client.post(self.url, HTTP_X_API_KEY="X" * 32)
self.assertEqual(r.status_code, 405)

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

@ -25,6 +25,7 @@ urlpatterns = [
path("api/v1/checks/<uuid:code>", views.single, name="hc-api-single"),
path("api/v1/checks/<uuid:code>/pause", views.pause, name="hc-api-pause"),
path("api/v1/notifications/<uuid:code>/bounce", views.bounce, name="hc-api-bounce"),
path("api/v1/checks/<uuid:code>/pings/", views.pings, name="hc-api-pings"),
path("api/v1/channels/", views.channels),
path(
"badge/<slug:badge_key>/<slug:signature>/<quoted:tag>.<slug:fmt>",


+ 35
- 1
hc/api/views.py View File

@ -16,9 +16,10 @@ from django.views.decorators.cache import never_cache
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_POST
from hc.accounts.models import Profile
from hc.api import schemas
from hc.api.decorators import authorize, authorize_read, cors, validate_json
from hc.api.models import Flip, Channel, Check, Notification, Ping
from hc.api.models import MAX_DELTA, Flip, Channel, Check, Notification, Ping
from hc.lib.badges import check_signature, get_badge_svg
@ -246,6 +247,39 @@ def pause(request, code):
return JsonResponse(check.to_dict())
@cors("GET")
@validate_json()
@authorize
def pings(request, code):
check = get_object_or_404(Check, code=code)
if check.project_id != request.project.id:
return HttpResponseForbidden()
# Look up ping log limit from account's profile.
# There might be more pings in the database (depends on how pruning is handled)
# but we will not return more than the limit allows.
profile = Profile.objects.get(user__project=request.project)
limit = profile.ping_log_limit
# Query in descending order so we're sure to get the most recent
# pings, regardless of the limit restriction
pings = Ping.objects.filter(owner=check).order_by("-id")[:limit]
# Ascending order is more convenient for calculating duration, so use reverse()
prev, dicts = None, []
for ping in reversed(pings):
d = ping.to_dict()
if ping.kind != "start" and prev and prev.kind == "start":
delta = ping.created - prev.created
if delta < MAX_DELTA:
d["duration"] = delta.total_seconds()
dicts.insert(0, d)
prev = ping
return JsonResponse({"pings": dicts})
@never_cache
@cors("GET")
def badge(request, badge_key, signature, tag, fmt="svg"):


+ 76
- 3
templates/docs/api.html View File

@ -35,6 +35,10 @@ checks in user's account.</p>
<td><code>DELETE SITE_ROOT/api/v1/checks/&lt;uuid&gt;</code></td>
</tr>
<tr>
<td><a href="#list-pings">Get a list of checks's logged pings</a></td>
<td><code>GET SITE_ROOT/api/v1/checks/&lt;uuid&gt;/pings/</code></td>
</tr>
<tr>
<td><a href="#list-channels">Get a list of existing integrations</a></td>
<td><code>GET SITE_ROOT/api/v1/channels/</code></td>
</tr>
@ -56,11 +60,11 @@ an API key. You can create read-write and read-only API keys in the
<tbody>
<tr>
<td>Regular API key</td>
<td>Have full access to all documented API endpoints.</td>
<td>Has full access to all documented API endpoints.</td>
</tr>
<tr>
<td>Read-only API key</td>
<td>Only work with the <a href="#list-checks">Get a list of existing checks</a> endpoint. Some fields are omitted from the API responses.</td>
<td>Only works with the <a href="#list-checks">Get a list of existing checks</a> and <a href="#get-check">Get a single check</a> endpoints. Some fields are omitted from the API responses.</td>
</tr>
</tbody>
</table>
@ -599,9 +603,78 @@ check that was just deleted.</p>
</code></pre></div>
<h2 class="rule" id="list-pings">Get a list of checks's logged pings</h2>
<p><code>GET SITE_ROOT/api/v1/checks/&lt;uuid&gt;/pings/</code></p>
<p>Returns a list of pings this check has received.</p>
<p>This endpoint returns pings in reverse order (most recent first), and the total
number of returned pings depends on account's billing plan: 100 for free accounts,
1000 for paid accounts.</p>
<h3>Response Codes</h3>
<dl>
<dt>200 OK</dt>
<dd>The request succeeded.</dd>
<dt>401 Unauthorized</dt>
<dd>The API key is either missing or invalid.</dd>
<dt>403 Forbidden</dt>
<dd>Access denied, wrong API key.</dd>
<dt>404 Not Found</dt>
<dd>The specified check does not exist.</dd>
</dl>
<h3>Example Request</h3>
<div class="highlight"><pre><span></span><code>curl SITE_ROOT/api/v1/checks/f618072a-7bde-4eee-af63-71a77c5723bc/pings/ <span class="se">\</span>
--header <span class="s2">&quot;X-Api-Key: your-api-key&quot;</span>
</code></pre></div>
<h3>Example Response</h3>
<div class="highlight"><pre><span></span><code><span class="p">{</span>
<span class="nt">&quot;pings&quot;</span><span class="p">:</span> <span class="p">[</span>
<span class="p">{</span>
<span class="nt">&quot;type&quot;</span><span class="p">:</span> <span class="s2">&quot;success&quot;</span><span class="p">,</span>
<span class="nt">&quot;date&quot;</span><span class="p">:</span> <span class="s2">&quot;2020-06-09T14:51:06.113073+00:00&quot;</span><span class="p">,</span>
<span class="nt">&quot;n&quot;</span><span class="p">:</span> <span class="mi">4</span><span class="p">,</span>
<span class="nt">&quot;scheme&quot;</span><span class="p">:</span> <span class="s2">&quot;http&quot;</span><span class="p">,</span>
<span class="nt">&quot;remote_addr&quot;</span><span class="p">:</span> <span class="s2">&quot;192.0.2.0&quot;</span><span class="p">,</span>
<span class="nt">&quot;method&quot;</span><span class="p">:</span> <span class="s2">&quot;GET&quot;</span><span class="p">,</span>
<span class="nt">&quot;ua&quot;</span><span class="p">:</span> <span class="s2">&quot;curl/7.68.0&quot;</span><span class="p">,</span>
<span class="nt">&quot;duration&quot;</span><span class="p">:</span> <span class="mf">2.896736</span>
<span class="p">},</span>
<span class="p">{</span>
<span class="nt">&quot;type&quot;</span><span class="p">:</span> <span class="s2">&quot;start&quot;</span><span class="p">,</span>
<span class="nt">&quot;date&quot;</span><span class="p">:</span> <span class="s2">&quot;2020-06-09T14:51:03.216337+00:00&quot;</span><span class="p">,</span>
<span class="nt">&quot;n&quot;</span><span class="p">:</span> <span class="mi">3</span><span class="p">,</span>
<span class="nt">&quot;scheme&quot;</span><span class="p">:</span> <span class="s2">&quot;http&quot;</span><span class="p">,</span>
<span class="nt">&quot;remote_addr&quot;</span><span class="p">:</span> <span class="s2">&quot;192.0.2.0&quot;</span><span class="p">,</span>
<span class="nt">&quot;method&quot;</span><span class="p">:</span> <span class="s2">&quot;GET&quot;</span><span class="p">,</span>
<span class="nt">&quot;ua&quot;</span><span class="p">:</span> <span class="s2">&quot;curl/7.68.0&quot;</span>
<span class="p">},</span>
<span class="p">{</span>
<span class="nt">&quot;type&quot;</span><span class="p">:</span> <span class="s2">&quot;success&quot;</span><span class="p">,</span>
<span class="nt">&quot;date&quot;</span><span class="p">:</span> <span class="s2">&quot;2020-06-09T14:50:59.633577+00:00&quot;</span><span class="p">,</span>
<span class="nt">&quot;n&quot;</span><span class="p">:</span> <span class="mi">2</span><span class="p">,</span>
<span class="nt">&quot;scheme&quot;</span><span class="p">:</span> <span class="s2">&quot;http&quot;</span><span class="p">,</span>
<span class="nt">&quot;remote_addr&quot;</span><span class="p">:</span> <span class="s2">&quot;192.0.2.0&quot;</span><span class="p">,</span>
<span class="nt">&quot;method&quot;</span><span class="p">:</span> <span class="s2">&quot;GET&quot;</span><span class="p">,</span>
<span class="nt">&quot;ua&quot;</span><span class="p">:</span> <span class="s2">&quot;curl/7.68.0&quot;</span><span class="p">,</span>
<span class="nt">&quot;duration&quot;</span><span class="p">:</span> <span class="mf">2.997976</span>
<span class="p">},</span>
<span class="p">{</span>
<span class="nt">&quot;type&quot;</span><span class="p">:</span> <span class="s2">&quot;start&quot;</span><span class="p">,</span>
<span class="nt">&quot;date&quot;</span><span class="p">:</span> <span class="s2">&quot;2020-06-09T14:50:56.635601+00:00&quot;</span><span class="p">,</span>
<span class="nt">&quot;n&quot;</span><span class="p">:</span> <span class="mi">1</span><span class="p">,</span>
<span class="nt">&quot;scheme&quot;</span><span class="p">:</span> <span class="s2">&quot;http&quot;</span><span class="p">,</span>
<span class="nt">&quot;remote_addr&quot;</span><span class="p">:</span> <span class="s2">&quot;192.0.2.0&quot;</span><span class="p">,</span>
<span class="nt">&quot;method&quot;</span><span class="p">:</span> <span class="s2">&quot;GET&quot;</span><span class="p">,</span>
<span class="nt">&quot;ua&quot;</span><span class="p">:</span> <span class="s2">&quot;curl/7.68.0&quot;</span>
<span class="p">}</span>
<span class="p">]</span>
<span class="p">}</span>
</code></pre></div>
<h2 class="rule" id="list-channels">Get a List of Existing Integrations</h2>
<p><code>GET SITE_ROOT/api/v1/channels/</code></p>
<p>Returns a list of integrations belonging to the user.</p>
<p>Returns a list of integrations belonging to the project.</p>
<h3>Response Codes</h3>
<dl>
<dt>200 OK</dt>


+ 84
- 4
templates/docs/api.md View File

@ -8,11 +8,12 @@ checks in user's account.
Endpoint Name | Endpoint Address
------------------------------------------------------|-------
[Get a list of existing checks](#list-checks) | `GET SITE_ROOT/api/v1/checks/`
[Get a single check](#get-check) | `GET SITE_ROOT/api/v1/checks/<uuid>`
[Get a single check](#get-check) | `GET SITE_ROOT/api/v1/checks/<uuid>`
[Create a new check](#create-check) | `POST SITE_ROOT/api/v1/checks/`
[Update an existing check](#update-check) | `POST SITE_ROOT/api/v1/checks/<uuid>`
[Pause monitoring of a check](#pause-check) | `POST SITE_ROOT/api/v1/checks/<uuid>/pause`
[Delete check](#delete-check) | `DELETE SITE_ROOT/api/v1/checks/<uuid>`
[Get a list of checks's logged pings](#list-pings) | `GET SITE_ROOT/api/v1/checks/<uuid>/pings/`
[Get a list of existing integrations](#list-channels) | `GET SITE_ROOT/api/v1/channels/`
## Authentication
@ -25,8 +26,8 @@ an API key. You can create read-write and read-only API keys in the
Key Type | Description
-------------------|------------
Regular API key | Have full access to all documented API endpoints.
Read-only API key | Only work with the [Get a list of existing checks](#list-checks) endpoint. Some fields are omitted from the API responses.
Regular API key | Has full access to all documented API endpoints.
Read-only API key | Only works with the [Get a list of existing checks](#list-checks) and [Get a single check](#get-check) endpoints. Some fields are omitted from the API responses.
The client can authenticate itself by sending an appropriate HTTP
request header. The header's name should be `X-Api-Key` and
@ -662,11 +663,90 @@ curl SITE_ROOT/api/v1/checks/f618072a-7bde-4eee-af63-71a77c5723bc \
}
```
## Get a list of checks's logged pings {: #list-pings .rule }
`GET SITE_ROOT/api/v1/checks/<uuid>/pings/`
Returns a list of pings this check has received.
This endpoint returns pings in reverse order (most recent first), and the total
number of returned pings depends on account's billing plan: 100 for free accounts,
1000 for paid accounts.
### Response Codes
200 OK
: The request succeeded.
401 Unauthorized
: The API key is either missing or invalid.
403 Forbidden
: Access denied, wrong API key.
404 Not Found
: The specified check does not exist.
### Example Request
```bash
curl SITE_ROOT/api/v1/checks/f618072a-7bde-4eee-af63-71a77c5723bc/pings/ \
--header "X-Api-Key: your-api-key"
```
### Example Response
```json
{
"pings": [
{
"type": "success",
"date": "2020-06-09T14:51:06.113073+00:00",
"n": 4,
"scheme": "http",
"remote_addr": "192.0.2.0",
"method": "GET",
"ua": "curl/7.68.0",
"duration": 2.896736
},
{
"type": "start",
"date": "2020-06-09T14:51:03.216337+00:00",
"n": 3,
"scheme": "http",
"remote_addr": "192.0.2.0",
"method": "GET",
"ua": "curl/7.68.0"
},
{
"type": "success",
"date": "2020-06-09T14:50:59.633577+00:00",
"n": 2,
"scheme": "http",
"remote_addr": "192.0.2.0",
"method": "GET",
"ua": "curl/7.68.0",
"duration": 2.997976
},
{
"type": "start",
"date": "2020-06-09T14:50:56.635601+00:00",
"n": 1,
"scheme": "http",
"remote_addr": "192.0.2.0",
"method": "GET",
"ua": "curl/7.68.0"
}
]
}
```
## Get a List of Existing Integrations {: #list-channels .rule }
`GET SITE_ROOT/api/v1/channels/`
Returns a list of integrations belonging to the user.
Returns a list of integrations belonging to the project.
### Response Codes


Loading…
Cancel
Save