diff --git a/CHANGELOG.md b/CHANGELOG.md index fd9e1bb2..bd9a34ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,9 @@ All notable changes to this project will be documented in this file. ## Unreleased +### Improvements +- Paused ping handling can be controlled via API (#376) + ### Bug Fixes - Removing Pager Team integration, project appears to be discontinued diff --git a/hc/api/models.py b/hc/api/models.py index 4f31ad8d..59a97f95 100644 --- a/hc/api/models.py +++ b/hc/api/models.py @@ -217,6 +217,7 @@ class Check(models.Model): "status": self.get_status(), "last_ping": isostring(self.last_ping), "next_ping": isostring(self.get_grace_start()), + "manual_resume": self.manual_resume, } if self.last_duration: diff --git a/hc/api/schemas.py b/hc/api/schemas.py index fd1a87da..b7117b9d 100644 --- a/hc/api/schemas.py +++ b/hc/api/schemas.py @@ -9,6 +9,7 @@ check = { "schedule": {"type": "string", "format": "cron", "maxLength": 100}, "tz": {"type": "string", "format": "timezone", "maxLength": 36}, "channels": {"type": "string"}, + "manual_resume": {"type": "boolean"}, "unique": { "type": "array", "items": {"enum": ["name", "tags", "timeout", "grace"]}, diff --git a/hc/api/tests/test_create_check.py b/hc/api/tests/test_create_check.py index bc04f68c..1e74bdd6 100644 --- a/hc/api/tests/test_create_check.py +++ b/hc/api/tests/test_create_check.py @@ -225,3 +225,15 @@ class CreateCheckTestCase(BaseTestCase): r = self.post({"api_key": "R" * 32, "name": "Foo"}) self.assertEqual(r.status_code, 401) + + def test_it_sets_manual_resume(self): + r = self.post({"api_key": "X" * 32, "manual_resume": True}) + + self.assertEqual(r.status_code, 201) + check = Check.objects.get() + self.assertTrue(check.manual_resume) + + def test_it_rejects_non_boolean_manual_resume(self): + r = self.post({"api_key": "X" * 32, "manual_resume": "surprise"}) + + self.assertEqual(r.status_code, 400) diff --git a/hc/api/tests/test_get_check.py b/hc/api/tests/test_get_check.py index f4ae91c8..1501c620 100644 --- a/hc/api/tests/test_get_check.py +++ b/hc/api/tests/test_get_check.py @@ -33,7 +33,7 @@ class GetCheckTestCase(BaseTestCase): self.assertEqual(r["Access-Control-Allow-Origin"], "*") doc = r.json() - self.assertEqual(len(doc), 13) + self.assertEqual(len(doc), 14) self.assertEqual(doc["timeout"], 3600) self.assertEqual(doc["grace"], 900) @@ -43,6 +43,7 @@ class GetCheckTestCase(BaseTestCase): self.assertEqual(doc["status"], "new") self.assertEqual(doc["channels"], str(self.c1.code)) self.assertEqual(doc["desc"], "This is description") + self.assertFalse(doc["manual_resume"]) def test_it_handles_invalid_uuid(self): r = self.get("not-an-uuid") diff --git a/hc/api/tests/test_update_check.py b/hc/api/tests/test_update_check.py index c18eb54f..5e3a2c17 100644 --- a/hc/api/tests/test_update_check.py +++ b/hc/api/tests/test_update_check.py @@ -225,3 +225,20 @@ class UpdateCheckTestCase(BaseTestCase): r = self.post(self.check.code, {"api_key": "R" * 32, "name": "Foo"}) self.assertEqual(r.status_code, 401) + + def test_it_sets_manual_resume_to_true(self): + r = self.post(self.check.code, {"api_key": "X" * 32, "manual_resume": True}) + self.assertEqual(r.status_code, 200) + + self.check.refresh_from_db() + self.assertTrue(self.check.manual_resume) + + def test_it_sets_manual_resume_to_false(self): + self.check.manual_resume = True + self.check.save() + + r = self.post(self.check.code, {"api_key": "X" * 32, "manual_resume": False}) + self.assertEqual(r.status_code, 200) + + self.check.refresh_from_db() + self.assertFalse(self.check.manual_resume) diff --git a/hc/api/views.py b/hc/api/views.py index 3cd684a1..300ae376 100644 --- a/hc/api/views.py +++ b/hc/api/views.py @@ -92,6 +92,9 @@ def _update(check, spec): if "desc" in spec: check.desc = spec["desc"] + if "manual_resume" in spec: + check.manual_resume = spec["manual_resume"] + if "timeout" in spec and "schedule" not in spec: check.kind = "simple" check.timeout = td(seconds=spec["timeout"]) diff --git a/hc/lib/jsonschema.py b/hc/lib/jsonschema.py index d8d1f0b0..8051975f 100644 --- a/hc/lib/jsonschema.py +++ b/hc/lib/jsonschema.py @@ -39,6 +39,10 @@ def validate(obj, schema, obj_name="value"): if "maximum" in schema and obj > schema["maximum"]: raise ValidationError("%s is too large" % obj_name) + elif schema.get("type") == "boolean": + if not isinstance(obj, bool): + raise ValidationError("%s is not a boolean" % obj_name) + elif schema.get("type") == "array": if not isinstance(obj, list): raise ValidationError("%s is not an array" % obj_name) diff --git a/hc/lib/tests/test_jsonschema.py b/hc/lib/tests/test_jsonschema.py index 302d6cb7..4049787b 100644 --- a/hc/lib/tests/test_jsonschema.py +++ b/hc/lib/tests/test_jsonschema.py @@ -82,3 +82,7 @@ class JsonSchemaTestCase(TestCase): def test_it_checks_timezone_format(self): with self.assertRaises(ValidationError): validate("X/Y", {"type": "string", "format": "timezone"}) + + def test_it_checks_boolean_type(self): + with self.assertRaises(ValidationError): + validate(123, {"type": "boolean"}) diff --git a/templates/docs/api.html b/templates/docs/api.html index 1cbd7552..faea2f74 100644 --- a/templates/docs/api.html +++ b/templates/docs/api.html @@ -117,6 +117,7 @@ specified value.
"status": "up", "last_ping": "2020-03-24T14:02:03+00:00", "next_ping": "2020-03-24T15:02:03+00:00", + "manual_resume": false, "ping_url": "PING_ENDPOINT31365bce-8da9-4729-8ff3-aaa71d56b712", "update_url": "SITE_ROOT/api/v1/checks/31365bce-8da9-4729-8ff3-aaa71d56b712", "pause_url": "SITE_ROOT/api/v1/checks/31365bce-8da9-4729-8ff3-aaa71d56b712/pause", @@ -132,6 +133,7 @@ specified value. "status": "down", "last_ping": "2020-03-23T10:19:32+00:00", "next_ping": null, + "manual_resume": false, "ping_url": "PING_ENDPOINT803f680d-e89b-492b-82ef-2be7b774a92d", "update_url": "SITE_ROOT/api/v1/checks/803f680d-e89b-492b-82ef-2be7b774a92d", "pause_url": "SITE_ROOT/api/v1/checks/803f680d-e89b-492b-82ef-2be7b774a92d/pause", @@ -158,6 +160,7 @@ is added. This identifier is stable across API calls. Example: "status": "up", "last_ping": "2020-03-24T14:02:03+00:00", "next_ping": "2020-03-24T15:02:03+00:00", + "manual_resume": false, "unique_key": "a6c7b0a8a66bed0df66abfdab3c77736861703ee", "timeout": 3600 }, @@ -170,6 +173,7 @@ is added. This identifier is stable across API calls. Example: "status": "down", "last_ping": "2020-03-23T10:19:32+00:00", "next_ping": null, + "manual_resume": false, "unique_key": "124f983e0e3dcaeba921cfcef46efd084576e783", "schedule": "15 5 * * *", "tz": "UTC" @@ -208,6 +212,7 @@ is added. This identifier is stable across API calls. Example: "status": "down", "last_ping": "2020-03-23T10:19:32+00:00", "next_ping": null, + "manual_resume": false, "ping_url": "PING_ENDPOINT803f680d-e89b-492b-82ef-2be7b774a92d", "update_url": "SITE_ROOT/api/v1/checks/803f680d-e89b-492b-82ef-2be7b774a92d", "pause_url": "SITE_ROOT/api/v1/checks/803f680d-e89b-492b-82ef-2be7b774a92d/pause", @@ -234,6 +239,7 @@ construct these URLs by itself. "status": "down", "last_ping": "2020-03-23T10:19:32+00:00", "next_ping": null, + "manual_resume": false, "unique_key": "124f983e0e3dcaeba921cfcef46efd084576e783", "schedule": "15 5 * * *", "tz": "UTC" @@ -299,6 +305,15 @@ ignored and "schedule" will be used.Example:
{"tz": "Europe/Riga"}+
boolean, optional, default value: false.
+Controls whether a paused ping resumes automatically when pinged (the default),
+or not. If set to false, a paused check will leave the paused state when it receives
+a ping. If set to true, a paused check will ignore pings and stay paused until it is
+either manually resumed from the web dashboard or the manual_resume
flag is
+changed.
string, optional
@@ -362,6 +377,7 @@ the limit is 20 checks per account.Example:
{"tz": "Europe/Riga"}+
boolean, optional, default value: false.
+Controls whether a paused ping resumes automatically when pinged (the default),
+or not. If set to false, a paused check will leave the paused state when it receives
+a ping. If set to true, a paused check will ignore pings and stay paused until it is
+either manually resumed from the web dashboard or the manual_resume
flag is
+changed.
string, optional.
@@ -476,6 +501,7 @@ field values.{"tz": "Europe/Riga"}+manual_resume +: boolean, optional, default value: false. + + Controls whether a paused ping resumes automatically when pinged (the default), + or not. If set to false, a paused check will leave the paused state when it receives + a ping. If set to true, a paused check will ignore pings and stay paused until it is + either manually resumed from the web dashboard or the `manual_resume` flag is + changed. + channels : string, optional @@ -381,6 +396,7 @@ curl SITE_ROOT/api/v1/checks/ \ "n_pings": 0, "name": "Backups", "next_ping": null, + "manual_resume": false, "pause_url": "SITE_ROOT/api/v1/checks/f618072a-7bde-4eee-af63-71a77c5723bc/pause", "ping_url": "PING_ENDPOINTf618072a-7bde-4eee-af63-71a77c5723bc", "status": "new", @@ -459,6 +475,15 @@ tz
{"tz": "Europe/Riga"}+manual_resume +: boolean, optional, default value: false. + + Controls whether a paused ping resumes automatically when pinged (the default), + or not. If set to false, a paused check will leave the paused state when it receives + a ping. If set to true, a paused check will ignore pings and stay paused until it is + either manually resumed from the web dashboard or the `manual_resume` flag is + changed. + channels : string, optional. @@ -521,6 +546,7 @@ curl SITE_ROOT/api/v1/checks/f618072a-7bde-4eee-af63-71a77c5723bc \ "n_pings": 0, "name": "Backups", "next_ping": null, + "manual_resume": false, "pause_url": "SITE_ROOT/api/v1/checks/f618072a-7bde-4eee-af63-71a77c5723bc/pause", "ping_url": "PING_ENDPOINTf618072a-7bde-4eee-af63-71a77c5723bc", "status": "new", @@ -575,6 +601,7 @@ header is sometimes required by some network proxies and web servers. "n_pings": 0, "name": "Backups", "next_ping": null, + "manual_resume": false, "pause_url": "SITE_ROOT/api/v1/checks/f618072a-7bde-4eee-af63-71a77c5723bc/pause", "ping_url": "PING_ENDPOINTf618072a-7bde-4eee-af63-71a77c5723bc", "status": "paused", @@ -625,6 +652,7 @@ curl SITE_ROOT/api/v1/checks/f618072a-7bde-4eee-af63-71a77c5723bc \ "n_pings": 0, "name": "Backups", "next_ping": null, + "manual_resume": false, "pause_url": "SITE_ROOT/api/v1/checks/f618072a-7bde-4eee-af63-71a77c5723bc/pause", "ping_url": "PING_ENDPOINTf618072a-7bde-4eee-af63-71a77c5723bc", "status": "new",