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 ### Improvements
- Paused ping handling can be controlled via API (#376) - Paused ping handling can be controlled via API (#376)
- Add "Get a list of checks's logged pings" API call (#371)
### Bug Fixes ### Bug Fixes
- Removing Pager Team integration, project appears to be discontinued - 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) ua = models.CharField(max_length=200, blank=True)
body = models.TextField(blank=True, null=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): class Channel(models.Model):
name = models.CharField(max_length=100, blank=True) 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>", views.single, name="hc-api-single"),
path("api/v1/checks/<uuid:code>/pause", views.pause, name="hc-api-pause"), 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/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("api/v1/channels/", views.channels),
path( path(
"badge/<slug:badge_key>/<slug:signature>/<quoted:tag>.<slug:fmt>", "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.csrf import csrf_exempt
from django.views.decorators.http import require_POST from django.views.decorators.http import require_POST
from hc.accounts.models import Profile
from hc.api import schemas from hc.api import schemas
from hc.api.decorators import authorize, authorize_read, cors, validate_json 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 from hc.lib.badges import check_signature, get_badge_svg
@ -246,6 +247,39 @@ def pause(request, code):
return JsonResponse(check.to_dict()) 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 @never_cache
@cors("GET") @cors("GET")
def badge(request, badge_key, signature, tag, fmt="svg"): 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> <td><code>DELETE SITE_ROOT/api/v1/checks/&lt;uuid&gt;</code></td>
</tr> </tr>
<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><a href="#list-channels">Get a list of existing integrations</a></td>
<td><code>GET SITE_ROOT/api/v1/channels/</code></td> <td><code>GET SITE_ROOT/api/v1/channels/</code></td>
</tr> </tr>
@ -56,11 +60,11 @@ an API key. You can create read-write and read-only API keys in the
<tbody> <tbody>
<tr> <tr>
<td>Regular API key</td> <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>
<tr> <tr>
<td>Read-only API key</td> <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> </tr>
</tbody> </tbody>
</table> </table>
@ -599,9 +603,78 @@ check that was just deleted.</p>
</code></pre></div> </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> <h2 class="rule" id="list-channels">Get a List of Existing Integrations</h2>
<p><code>GET SITE_ROOT/api/v1/channels/</code></p> <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> <h3>Response Codes</h3>
<dl> <dl>
<dt>200 OK</dt> <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 Endpoint Name | Endpoint Address
------------------------------------------------------|------- ------------------------------------------------------|-------
[Get a list of existing checks](#list-checks) | `GET SITE_ROOT/api/v1/checks/` [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/` [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>` [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` [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>` [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/` [Get a list of existing integrations](#list-channels) | `GET SITE_ROOT/api/v1/channels/`
## Authentication ## Authentication
@ -25,8 +26,8 @@ an API key. You can create read-write and read-only API keys in the
Key Type | Description 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 The client can authenticate itself by sending an appropriate HTTP
request header. The header's name should be `X-Api-Key` and 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 a List of Existing Integrations {: #list-channels .rule }
`GET SITE_ROOT/api/v1/channels/` `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 ### Response Codes


Loading…
Cancel
Save