From 6174e2ec5ce2b31a7457f09021fff3bb00c6b2b0 Mon Sep 17 00:00:00 2001 From: Authapon Kongkaew Date: Wed, 24 Jul 2019 02:54:29 +0700 Subject: [PATCH] Add Snapshot API & Improve Unit Test (#34) * add snapshot api * add snapshot api & Improve unit test * Update readme * Improve unit test --- README.md | 2 +- grafana_api/api/__init__.py | 2 + grafana_api/api/snapshots.py | 92 ++++++++++++++ grafana_api/grafana_face.py | 4 +- test/api/test_folder.py | 35 ++++++ test/api/test_organization.py | 231 ++++++++++++++++++++++++++++++++++ test/api/test_search.py | 12 ++ test/api/test_snapshot.py | 153 ++++++++++++++++++++++ 8 files changed, 529 insertions(+), 2 deletions(-) create mode 100644 grafana_api/api/snapshots.py create mode 100644 test/api/test_organization.py create mode 100644 test/api/test_snapshot.py diff --git a/README.md b/README.md index 5acb6c8..8e186a8 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ Work on API implementation still in progress. | Organisation | + | | Other | + | | Preferences | + | -| Snapshot | - | +| Snapshot | + | | Teams | + | | User | + | diff --git a/grafana_api/api/__init__.py b/grafana_api/api/__init__.py index 5015b85..ff4c70c 100644 --- a/grafana_api/api/__init__.py +++ b/grafana_api/api/__init__.py @@ -8,3 +8,5 @@ from .user import User, Users from .team import Teams from .annotations import Annotations +from .snapshots import Snapshots + diff --git a/grafana_api/api/snapshots.py b/grafana_api/api/snapshots.py new file mode 100644 index 0000000..3e553a6 --- /dev/null +++ b/grafana_api/api/snapshots.py @@ -0,0 +1,92 @@ +from .base import Base + + +class Snapshots(Base): + def __init__(self, api): + super(Snapshots, self).__init__(api) + self.api = api + + def create_new_snapshot( + self, + dashboard=None, + name=None, + expires=None, + external=None, + key=None, + delete_key=None, + ): + """ + + :param dashboard: Required. The complete dashboard model. + :param name: Optional. snapshot name + :param expires: Optional. When the snapshot should expire in seconds. 3600 is 1 hour, 86400 is 1 day. Default is never to expire. + :param external: Optional. Save the snapshot on an external server rather than locally. Default is false. + :param key: Optional. Define the unique key. Required if external is true. + :param deleteKey: Optional. Unique key used to delete the snapshot. It is different from the key so that only the creator can delete the snapshot. Required if external is true. + :return: + """ + + path = "/snapshots" + post_json = { + "dashboard": dashboard + } + if name: + post_json["name"] = name + if expires: + post_json["expires"] = expires + if external: + post_json["external"] = external + if key: + post_json["key"] = key + if delete_key: + post_json["deleteKey"] = delete_key + + r = self.api.POST(path, json=post_json) + return r + + def get_dashboard_snapshots(self): + """ + + :return: + """ + path = "/dashboard/snapshots" + r = self.api.GET(path) + return r + + def get_snapshot_by_key(self, key): + """ + + :param key: + :return: + """ + path = "/snapshots/%s" % key + r = self.api.GET(path) + return r + + def delete_snapshot_by_key( + self, + snapshot_id=None + ): + """ + + :param snapshot_id: + :return: + """ + path = "/snapshots/{}".format(snapshot_id) + r = self.api.DELETE(path) + + return r + + def delete_snapshot_by_delete_key( + self, + snapshot_delete_key=None + ): + """ + + :param snapshot_delete_key: + :return: + """ + path = "/snapshots-delete/{}".format(snapshot_delete_key) + r = self.api.DELETE(path) + + return r diff --git a/grafana_api/grafana_face.py b/grafana_api/grafana_face.py index 0abb587..dcc8667 100644 --- a/grafana_api/grafana_face.py +++ b/grafana_api/grafana_face.py @@ -10,7 +10,8 @@ User, Users, Teams, - Annotations + Snapshots, + Annotations, ) @@ -43,3 +44,4 @@ def __init__( self.users = Users(self.api) self.teams = Teams(self.api) self.annotations = Annotations(self.api) + self.snapshots = Snapshots(self.api) diff --git a/test/api/test_folder.py b/test/api/test_folder.py index 7e85dae..66b5f44 100644 --- a/test/api/test_folder.py +++ b/test/api/test_folder.py @@ -3,6 +3,7 @@ import requests_mock from grafana_api.grafana_face import GrafanaFace +from grafana_api.grafana_api import GrafanaBadInputError class FolderTestCase(unittest.TestCase): @@ -83,6 +84,17 @@ def test_create_folder(self, m): folder = self.cli.folder.create_folder(title="Departmenet ABC", uid="nErXDvCkzz") self.assertEqual(folder["uid"], "nErXDvCkzz") + @requests_mock.Mocker() + def test_create_folder_empty_uid(self, m): + m.post( + "http://localhost/api/folders", + json={ + "message": "Folder title cannot be empty" + }, status_code=400 + ) + folder = self.cli.folder.create_folder(title="Departmenet ABC") + self.assertRaises(GrafanaBadInputError) + @requests_mock.Mocker() def test_update_folder(self, m): m.put( @@ -106,6 +118,29 @@ def test_update_folder(self, m): folder = self.cli.folder.update_folder(title="Departmenet DEF", uid="nErXDvCkzz", version=1, overwrite=True) self.assertEqual(folder["title"], "Departmenet DEF") + @requests_mock.Mocker() + def test_update_folder_some_param(self, m): + m.put( + "http://localhost/api/folders/nErXDvCkzz", + json={ + "id": 1, + "uid": "nErXDvCkzz", + "title": "Departmenet DEF", + "url": "/dashboards/f/nErXDvCkzz/department-def", + "hasAcl": "false", + "canSave": "false", + "canEdit": "false", + "canAdmin": "false", + "createdBy": "admin", + "created": "2018-01-31T17:43:12+01:00", + "updatedBy": "admin", + "updated": "2018-01-31T17:43:12+01:00", + "version": 1 + } + ) + folder = self.cli.folder.update_folder(title="Departmenet DEF", uid="nErXDvCkzz") + self.assertEqual(folder["title"], "Departmenet DEF") + @requests_mock.Mocker() def test_get_folder_by_id(self, m): m.get( diff --git a/test/api/test_organization.py b/test/api/test_organization.py new file mode 100644 index 0000000..05a3a61 --- /dev/null +++ b/test/api/test_organization.py @@ -0,0 +1,231 @@ +import unittest + +import requests_mock + +from grafana_api.grafana_face import GrafanaFace + + +class OrganizationTestCase(unittest.TestCase): + def setUp(self): + self.cli = GrafanaFace( + ("admin", "admin"), host="localhost", url_path_prefix="", protocol="http" + ) + + @requests_mock.Mocker() + def test_delete_snapshot_by_key(self, m): + m.delete('http://localhost/api/orgs/1/users/2', json={"message": "User removed from organization"}) + annotation = self.cli.organizations.organization_user_delete(organization_id=1, user_id=2) + self.assertEqual(annotation['message'], "User removed from organization") + + @requests_mock.Mocker() + def test_organization_preference_get(self, m): + m.get( + "http://localhost/api/org/preferences", + json={"theme": "", "homeDashboardId": 0, "timezone": ""} + ) + + result = self.cli.organizations.organization_preference_get() + self.assertEqual(result["homeDashboardId"], 0) + + @requests_mock.Mocker() + def test_organization_preference_update(self, m): + m.put( + "http://localhost/api/org/preferences", + json={"message": "Preferences updated"} + ) + preference = self.cli.organizations.organization_preference_update(theme="", home_dashboard_id=0, + timezone="utc") + self.assertEqual(preference["message"], "Preferences updated") + + @requests_mock.Mocker() + def test_organization_user_update(self, m): + m.patch( + "http://localhost/api/orgs/1/users/2", + json={"message": "Organization user updated"} + ) + preference = self.cli.organizations.organization_user_update(organization_id=1, user_id=2, user_role="Admin") + self.assertEqual(preference["message"], "Organization user updated") + + @requests_mock.Mocker() + def test_organization_user_add(self, m): + m.post( + "http://localhost/api/orgs/1/users", + json={"message": "User added to organization"} + ) + preference = self.cli.organizations.organization_user_add(organization_id=1, user={ + "loginOrEmail": "user", + "role": "Viewer" + }) + self.assertEqual(preference["message"], "User added to organization") + + @requests_mock.Mocker() + def test_organization_user_list(self, m): + m.get( + "http://localhost/api/orgs/1/users", + json=[ + { + "orgId": 1, + "userId": 1, + "email": "admin@mygraf.com", + "login": "admin", + "role": "Admin" + } + ] + ) + users = self.cli.organizations.organization_user_list(organization_id=1) + self.assertEqual(len(users), 1) + + @requests_mock.Mocker() + def test_list_organization(self, m): + m.get( + "http://localhost/api/orgs", + json=[ + { + "orgId": 1, + "userId": 1, + "email": "admin@mygraf.com", + "login": "admin", + "role": "Admin" + } + ] + ) + users = self.cli.organizations.list_organization() + self.assertEqual(len(users), 1) + + @requests_mock.Mocker() + def test_get_current_organization(self, m): + m.get( + "http://localhost/api/org", + json={ + "id": 1, + "name": "Main Org." + } + ) + orgs = self.cli.organization.get_current_organization() + self.assertEqual(orgs['name'], "Main Org.") + + @requests_mock.Mocker() + def test_update_current_organization(self, m): + m.put( + "http://localhost/api/org", + json={"message": "Organization updated"} + ) + org = self.cli.organization.update_current_organization(organization={ + "name": "Main Org." + }) + self.assertEqual(org['message'], "Organization updated") + + @requests_mock.Mocker() + def test_update_organization(self, m): + m.put( + "http://localhost/api/orgs/1", + json={"message": "Organization updated"} + ) + preference = self.cli.organizations.update_organization(organization_id=1, organization={ + "name": "Main Org 2." + }) + self.assertEqual(preference["message"], "Organization updated") + + @requests_mock.Mocker() + def test_delete_organization(self, m): + m.delete( + "http://localhost/api/orgs/1", + json={"message": "Organization deleted"} + ) + preference = self.cli.organizations.delete_organization(organization_id=1) + self.assertEqual(preference["message"], "Organization deleted") + + @requests_mock.Mocker() + def test_create_organization(self, m): + m.post( + "http://localhost/api/orgs", + json={ + "orgId": "1", + "message": "Organization created" + } + ) + preference = self.cli.organization.create_organization(organization={ + "name": "New Org." + }) + self.assertEqual(preference["message"], "Organization created") + + @requests_mock.Mocker() + def test_delete_user_current_organization(self, m): + m.delete( + "http://localhost/api/org/users/1", + json={"message": "User removed from organization"} + ) + preference = self.cli.organization.delete_user_current_organization(user_id=1) + self.assertEqual(preference["message"], "User removed from organization") + + @requests_mock.Mocker() + def test_add_user_current_organization(self, m): + m.post( + "http://localhost/api/org/users", + json={"message": "User added to organization"} + ) + preference = self.cli.organization.add_user_current_organization({ + "role": "Admin", + "loginOrEmail": "admin" + }) + self.assertEqual(preference["message"], "User added to organization") + + @requests_mock.Mocker() + def test_update_user_current_organization(self, m): + m.patch( + "http://localhost/api/org/users/1", + json={"message": "Organization user updated"} + ) + preference = self.cli.organization.update_user_current_organization(user_id=1, user={ + "role": "Viewer", + }) + self.assertEqual(preference["message"], "Organization user updated") + + @requests_mock.Mocker() + def test_get_current_organization_users(self, m): + m.get( + "http://localhost/api/org/users", + json=[ + { + "orgId": 1, + "userId": 1, + "email": "admin@mygraf.com", + "login": "admin", + "role": "Admin" + } + ] + ) + org = self.cli.organization.get_current_organization_users() + self.assertEqual(len(org), 1) + + @requests_mock.Mocker() + def test_find_organization(self, m): + m.get( + "http://localhost/api/orgs/name/Main", + json= + { + "id": 1, + "name": "Main Org.", + "address": { + "address1": "", + "address2": "", + "city": "", + "zipCode": "", + "state": "", + "country": "" + } + } + ) + org = self.cli.organization.find_organization(org_name="Main") + self.assertEqual(org['id'], 1) + + @requests_mock.Mocker() + def test_switch_organization(self, m): + m.post( + "http://localhost/api/user/using/2", + json={"message":"Active organization changed"} + ) + preference = self.cli.organizations.switch_organization(organization_id=2) + self.assertEqual(preference["message"], "Active organization changed") + + diff --git a/test/api/test_search.py b/test/api/test_search.py index bb7df23..134696b 100644 --- a/test/api/test_search.py +++ b/test/api/test_search.py @@ -36,3 +36,15 @@ def test_search_dashboards(self, m): type_="dash-folder", dashboard_ids=163, limit=10) self.assertEqual(result[0]["id"], 163) self.assertEqual(len(result), 1) + + @requests_mock.Mocker() + def test_search_dashboards_with_out_filter(self, m): + m.get( + "http://localhost/api/search", + json={ + "message": "Not found" + }, status_code=400 + ) + + result = self.cli.search.search_dashboards() + self.assertRaises(GrafanaBadInputError) diff --git a/test/api/test_snapshot.py b/test/api/test_snapshot.py new file mode 100644 index 0000000..e743826 --- /dev/null +++ b/test/api/test_snapshot.py @@ -0,0 +1,153 @@ +import unittest + +import requests_mock + +from grafana_api.grafana_face import GrafanaFace + + +class SnapshotTestCase(unittest.TestCase): + def setUp(self): + self.cli = GrafanaFace( + ("admin", "admin"), host="localhost", url_path_prefix="", protocol="http" + ) + + @requests_mock.Mocker() + def test_create_new_snapshot(self, m): + m.post( + "http://localhost/api/snapshots", + json={ + "deleteKey": "XXXXXXX", + "deleteUrl": "myurl/api/snapshots.py-delete/XXXXXXX", + "key": "YYYYYYY", + "url": "myurl/dashboard/snapshot/YYYYYYY" + }, + ) + snapshot = self.cli.snapshots.create_new_snapshot(dashboard={ + "editable": "false", + "hideControls": "true", + "nav": [ + { + "enable": "false", + "type": "timepicker" + } + ], + "rows": [ + { + + } + ], + "style": "dark", + "tags": [], + "templating": { + "list": [ + ] + }, + "time": { + }, + "timezone": "browser", + "title": "Home", + "version": 5 + }, name="Test", key="YYYYYYY", delete_key="XXXXXXX", external=True, expires=3600) + self.assertEqual(snapshot["key"], "YYYYYYY") + + @requests_mock.Mocker() + def test_create_new_snapshot_without_optional(self, m): + m.post( + "http://localhost/api/snapshots", + json={ + "deleteKey": "XXXXXXX", + "deleteUrl": "myurl/api/snapshots.py-delete/XXXXXXX", + "key": "YYYYYYY", + "url": "myurl/dashboard/snapshot/YYYYYYY" + }, + ) + snapshot = self.cli.snapshots.create_new_snapshot(dashboard={ + "editable": "false", + "hideControls": "true", + "nav": [ + { + "enable": "false", + "type": "timepicker" + } + ], + "rows": [ + { + + } + ], + "style": "dark", + "tags": [], + "templating": { + "list": [ + ] + }, + "time": { + }, + "timezone": "browser", + "title": "Home", + "version": 5 + }) + self.assertEqual(snapshot["key"], "YYYYYYY") + + @requests_mock.Mocker() + def test_get_dashboard_snapshots(self, m): + m.get( + "http://localhost/api/dashboard/snapshots", + json=[ + { + "id": 8, + "name": "Home", + "key": "YYYYYYY", + "orgId": 1, + "userId": 1, + "external": False, + "externalUrl": "", + "expires": "2200-13-32T25:23:23+02:00", + "created": "2200-13-32T28:24:23+02:00", + "updated": "2200-13-32T28:24:23+02:00" + } + ] + ) + dashboards = self.cli.snapshots.get_dashboard_snapshots() + self.assertEqual(len(dashboards), 1) + + @requests_mock.Mocker() + def test_get_snapshot_by_key(self, m): + m.get( + "http://localhost/api/snapshots/YYYYYYY", + json=[ + { + "id": 8, + "name": "Home", + "key": "YYYYYYY", + "orgId": 1, + "userId": 1, + "external": False, + "externalUrl": "", + "expires": "2200-13-32T25:23:23+02:00", + "created": "2200-13-32T28:24:23+02:00", + "updated": "2200-13-32T28:24:23+02:00" + } + ] + ) + dashboards = self.cli.snapshots.get_snapshot_by_key(key="YYYYYYY") + self.assertEqual(len(dashboards), 1) + + @requests_mock.Mocker() + def test_delete_snapshot_by_key(self, m): + m.delete('http://localhost/api/snapshots/YYYYYYY', json={"message": "Snapshot deleted. It might take an hour " + "before it's cleared from any CDN " + "caches."}) + annotation = self.cli.snapshots.delete_snapshot_by_key(snapshot_id="YYYYYYY") + self.assertEqual(annotation['message'], "Snapshot deleted. It might take an hour before it's cleared from any " + "CDN caches.") + + @requests_mock.Mocker() + def test_delete_snapshot_by_delete_key(self, m): + m.delete('http://localhost/api/snapshots-delete/XXXXXXX', json={"message": "Snapshot deleted. It might take an hour " + "before it's cleared from any CDN " + "caches."}) + annotation = self.cli.snapshots.delete_snapshot_by_delete_key(snapshot_delete_key="XXXXXXX") + self.assertEqual(annotation['message'], "Snapshot deleted. It might take an hour before it's cleared from any " + "CDN caches.") +