From 44865b02b958673ae5cccbef077d07855fb39d24 Mon Sep 17 00:00:00 2001 From: Pascal Zimmermann Date: Wed, 5 Jan 2022 09:05:44 +0100 Subject: [PATCH 01/19] WIP --- .coveragerc | 26 ++++ .flake8 | 6 + .github/workflows/publish-to-pypi.yml | 48 ++++++++ .github/workflows/pull-request-checks.yml | 114 ++++++++++++++++++ README.md | 3 +- grafana_api/__init__.py => docs/.placeholder | 0 requirements.txt | 1 - setup.py | 27 +++++ src/grafana_api/__init__.py | 0 .../api.py => src/grafana_api/dashboard.py | 25 ++-- src/grafana_api/model.py | 9 ++ test/test_dashboard.py | 10 ++ test/test_model.py | 10 ++ 13 files changed, 260 insertions(+), 19 deletions(-) create mode 100644 .coveragerc create mode 100644 .flake8 create mode 100644 .github/workflows/publish-to-pypi.yml create mode 100644 .github/workflows/pull-request-checks.yml rename grafana_api/__init__.py => docs/.placeholder (100%) create mode 100644 setup.py create mode 100644 src/grafana_api/__init__.py rename grafana_api/api.py => src/grafana_api/dashboard.py (79%) create mode 100644 src/grafana_api/model.py create mode 100644 test/test_dashboard.py create mode 100644 test/test_model.py diff --git a/.coveragerc b/.coveragerc new file mode 100644 index 0000000..ec86585 --- /dev/null +++ b/.coveragerc @@ -0,0 +1,26 @@ +[run] +branch = True +omit = + */setup.py + */dashboard.json.sample + tests/* +source = . + +[report] +omit = + */setup.py + */dashboard.json.sample + tests/* +fail_under = 80 +show_missing = True +skip_covered = False +exclude_lines = + # Have to re-enable the standard pragma + pragma: no cover + + # Don't complain if tests don't hit defensive assertion code: + raise AssertionError + raise NotImplementedError + + # Don't complain if non-runnable code isn't run: + if __name__ == .__main__.: diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..e87e4f3 --- /dev/null +++ b/.flake8 @@ -0,0 +1,6 @@ +[flake8] +ignore = E203, E266, E501, W503 +max-line-length = 120 +max-complexity = 20 +select = B,C,D,E,F,W,T4,B902,B950 +exclude = venc,.git \ No newline at end of file diff --git a/.github/workflows/publish-to-pypi.yml b/.github/workflows/publish-to-pypi.yml new file mode 100644 index 0000000..aaba681 --- /dev/null +++ b/.github/workflows/publish-to-pypi.yml @@ -0,0 +1,48 @@ +name: Build and publish + +on: + release: + types: [ published ] + +jobs: + + build-and-publish: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ['3.x'] + + steps: + - name: Checkout the repository and the branch + uses: actions/checkout@v2 + + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + architecture: x64 + cache: 'pip' + + - name: Install the requirements + run: pip install -r requirements.txt + + - name: Install pypa/build + run: >- + python -m + pip install + build + --user + + - name: Build a binary wheel and a source tarball + run: >- + python -m + build + --sdist + --wheel + --outdir dist/ + + - name: Publish distribution package to PyPI + if: startsWith(github.ref, 'refs/tags') + uses: pypa/gh-action-pypi-publish@master + with: + password: ${{ secrets.PYPI_API_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/pull-request-checks.yml b/.github/workflows/pull-request-checks.yml new file mode 100644 index 0000000..e349ebe --- /dev/null +++ b/.github/workflows/pull-request-checks.yml @@ -0,0 +1,114 @@ +name: PR checks + +on: + pull_request: + branches: [ main ] + +jobs: + + test: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: [ '3.x' ] + + steps: + - uses: actions/checkout@v2 + + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + architecture: x64 + cache: 'pip' + + - name: Install the requirements + run: pip install -r requirements.txt + + - name: Execute the unittests + run: python3 -m unittest discover tests + + lint: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: [ '3.x' ] + + steps: + - uses: actions/checkout@v2 + + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + architecture: x64 + cache: 'pip' + + - name: Install the requirements + run: pip install -r requirements.txt + + - name: Execute the linting checks + uses: reviewdog/action-flake8@v3.2.0 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + flake8_args: --config=.flake8 + + coverage: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: [ '3.x' ] + + steps: + - uses: actions/checkout@v2 + with: + persist-credentials: false + fetch-depth: 0 + + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + architecture: x64 + cache: 'pip' + + - name: Install the requirements + run: pip install -r requirements.txt && pip install pytest pytest-cov coverage-badge + + - name: Generate the coverage report + run: export PYTHONPATH=$PWD && pytest --junitxml=pytest.xml --cov=. tests/ | tee pytest-coverage.txt + + - name: Execute the coverage checks + uses: MishaKav/pytest-coverage-comment@v1.1.16 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + pytest-coverage-path: ./pytest-coverage.txt + junitxml-path: ./pytest.xml + hide-badge: true + create-new-commit: true + + - name: "Check if coverage badge file existence" + id: check_files + uses: andstor/file-existence-action@v1 + with: + files: "docs/coverage.svg" + + - name: Generate coverage badge + if: steps.check_files.outputs.files_exists == 'false' + run: coverage-badge -o docs/coverage.svg -f + + - name: Commit files + if: steps.check_files.outputs.files_exists == 'false' + run: | + git config --local user.email "github-actions[bot]@users.noreply.github.com" + git config --local user.name "github-actions[bot]" + git add --force docs/coverage.svg + git commit -m "Add coverage badge" + + - name: Push changes + if: steps.check_files.outputs.files_exists == 'false' + uses: ad-m/github-push-action@master + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + branch: ${{ github.head_ref }} + force: true \ No newline at end of file diff --git a/README.md b/README.md index dbe3a41..878a76f 100644 --- a/README.md +++ b/README.md @@ -5,9 +5,11 @@ The repository includes an SDK for the Grafana API - Documentation - Docstrings - PYPI support +- Tests ## Currently, supported features +### Dashboard - Get a Dashboard by uid - Get folder id by dashboard path - Get all folder ids and folder names @@ -19,7 +21,6 @@ The repository includes an SDK for the Grafana API ### Programs & tools to install -- json-extensions - requests ## Contribution diff --git a/grafana_api/__init__.py b/docs/.placeholder similarity index 100% rename from grafana_api/__init__.py rename to docs/.placeholder diff --git a/requirements.txt b/requirements.txt index 076a11e..663bd1f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1 @@ -json-extensions requests \ No newline at end of file diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..ecdea65 --- /dev/null +++ b/setup.py @@ -0,0 +1,27 @@ +import setuptools + +with open("README.md", "r", encoding="utf-8") as fh: + long_description = fh.read() + +setuptools.setup( + name="grafana-api-sdk", + version="0.0.1", + author="Pascal Zimmermann", + author_email="info@theiotstudio.com", + description="A Grafana API SDK", + long_description=long_description, + long_description_content_type="text/markdown", + url="https://github.com/ZPascal/grafana_api_sdk", + project_urls={ + "Bug Tracker": "https://github.com/ZPascal/grafana_api_sdk/issues", + }, + classifiers=[ + "Programming Language :: Python :: 3", + "License :: OSI Approved", + "Operating System :: OS Independent", + ], + package_dir={"": "src"}, + packages=setuptools.find_packages(where="src"), + install_requires=["requests"], + python_requires=">=3.6", +) diff --git a/src/grafana_api/__init__.py b/src/grafana_api/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/grafana_api/api.py b/src/grafana_api/dashboard.py similarity index 79% rename from grafana_api/api.py rename to src/grafana_api/dashboard.py index b7dd7cc..8df56a2 100644 --- a/grafana_api/api.py +++ b/src/grafana_api/dashboard.py @@ -3,22 +3,13 @@ import logging import requests - -class GrafanaAPIModel: - - def __init__(self, host: str = None, token: str = None, message: str = None, dashboard_path: str = None, - dashboard_name: str = None): - self.host = host - self.token = token - self.message = message - self.dashboard_path = dashboard_path - self.dashboard_name = dashboard_name +from model import Model # https://grafana.com/docs/grafana/latest/http_api/dashboard/ -class GrafanaAPI: +class Dashboard: - def __init__(self, grafana_api_model: GrafanaAPIModel): + def __init__(self, grafana_api_model: Model): self.grafana_api_model = grafana_api_model self.logging = logging.Logger @@ -32,7 +23,7 @@ def create_or_update_dashboard(self, dashboard_json: any, overwrite: bool = Fals "overwrite": overwrite } - api_call = GrafanaAPI.__call_the_api(self, "/api/dashboards/db", "POST", json.dumps(dashboard_json_complete)) + api_call = Dashboard.__call_the_api(self, "/api/dashboards/db", "POST", json.dumps(dashboard_json_complete)) status: str = api_call["status"] @@ -47,7 +38,7 @@ def delete_dashboard_by_name_and_path(self): if dashboard_uids != list(): for dashboard_uid in dashboard_uids: - api_call = GrafanaAPI.__call_the_api(self, f"/api/dashboards/uid/{dashboard_uid}", "DELETE") + api_call = Dashboard.__call_the_api(self, f"/api/dashboards/uid/{dashboard_uid}", "DELETE") message: str = api_call["message"] @@ -63,7 +54,7 @@ def get_dashboard_uid_by_name_and_folder(self) -> list: folder_id: int = self.get_folder_id_by_dashboard_path() search_query: str = f"/api/search?folderIds={folder_id}&query={self.grafana_api_model.dashboard_name}" - dashboard_meta_list: list = GrafanaAPI.__call_the_api(self, search_query) + dashboard_meta_list: list = Dashboard.__call_the_api(self, search_query) dashboard_uids: list = list() for dashboard_meta in dashboard_meta_list: @@ -86,7 +77,7 @@ def get_folder_id_by_dashboard_path(self) -> int: return folder_id def get_all_folder_ids_and_names(self) -> list: - folders_raw: list = GrafanaAPI.__call_the_api(self, "/api/search/?folderIds=0") + folders_raw: list = Dashboard.__call_the_api(self, "/api/search/?folderIds=0") folders_raw_len: int = len(folders_raw) folders: list = list() @@ -105,7 +96,7 @@ def __call_the_api(self, api_call: str, method: str = "GET", dashboard_json_comp if dashboard_json_complete is not None: return requests.post(api_url, data=dashboard_json_complete, headers=headers).json() else: - logging.error("Please define the dashbboard_json_complete.") + logging.error("Please define the dashboard_json_complete.") sys.exit(1) elif method == "DELETE": return requests.delete(api_url, headers=headers).json() diff --git a/src/grafana_api/model.py b/src/grafana_api/model.py new file mode 100644 index 0000000..a43adb0 --- /dev/null +++ b/src/grafana_api/model.py @@ -0,0 +1,9 @@ +class Model: + + def __init__(self, host: str = None, token: str = None, message: str = None, dashboard_path: str = None, + dashboard_name: str = None): + self.host = host + self.token = token + self.message = message + self.dashboard_path = dashboard_path + self.dashboard_name = dashboard_name diff --git a/test/test_dashboard.py b/test/test_dashboard.py new file mode 100644 index 0000000..1af168f --- /dev/null +++ b/test/test_dashboard.py @@ -0,0 +1,10 @@ +import unittest + + +class DashboardTestCase(unittest.TestCase): + def test_something(self): + self.assertEqual(True, False) # add assertion here + + +if __name__ == '__main__': + unittest.main() diff --git a/test/test_model.py b/test/test_model.py new file mode 100644 index 0000000..dfef9d9 --- /dev/null +++ b/test/test_model.py @@ -0,0 +1,10 @@ +import unittest + + +class ModelTestCase(unittest.TestCase): + def test_something(self): + self.assertEqual(True, False) # add assertion here + + +if __name__ == '__main__': + unittest.main() From 653bfe60d3b49d18af12f9b3cad292410ae42811 Mon Sep 17 00:00:00 2001 From: Pascal Zimmermann Date: Wed, 5 Jan 2022 09:15:21 +0100 Subject: [PATCH 02/19] Update the API --- src/grafana_api/dashboard.py | 12 ++++++------ src/grafana_api/model.py | 12 +++++++++++- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/grafana_api/dashboard.py b/src/grafana_api/dashboard.py index 8df56a2..03a1d76 100644 --- a/src/grafana_api/dashboard.py +++ b/src/grafana_api/dashboard.py @@ -3,13 +3,13 @@ import logging import requests -from model import Model +from model import APIModel, APIEndpoints # https://grafana.com/docs/grafana/latest/http_api/dashboard/ class Dashboard: - def __init__(self, grafana_api_model: Model): + def __init__(self, grafana_api_model: APIModel): self.grafana_api_model = grafana_api_model self.logging = logging.Logger @@ -23,7 +23,7 @@ def create_or_update_dashboard(self, dashboard_json: any, overwrite: bool = Fals "overwrite": overwrite } - api_call = Dashboard.__call_the_api(self, "/api/dashboards/db", "POST", json.dumps(dashboard_json_complete)) + api_call = Dashboard.__call_the_api(self, f"{APIEndpoints.DASHBOARDS}/db", "POST", json.dumps(dashboard_json_complete)) status: str = api_call["status"] @@ -38,7 +38,7 @@ def delete_dashboard_by_name_and_path(self): if dashboard_uids != list(): for dashboard_uid in dashboard_uids: - api_call = Dashboard.__call_the_api(self, f"/api/dashboards/uid/{dashboard_uid}", "DELETE") + api_call = Dashboard.__call_the_api(self, f"{APIEndpoints.DASHBOARDS}/uid/{dashboard_uid}", "DELETE") message: str = api_call["message"] @@ -53,7 +53,7 @@ def delete_dashboard_by_name_and_path(self): def get_dashboard_uid_by_name_and_folder(self) -> list: folder_id: int = self.get_folder_id_by_dashboard_path() - search_query: str = f"/api/search?folderIds={folder_id}&query={self.grafana_api_model.dashboard_name}" + search_query: str = f"{APIEndpoints.SEARCH}?folderIds={folder_id}&query={self.grafana_api_model.dashboard_name}" dashboard_meta_list: list = Dashboard.__call_the_api(self, search_query) dashboard_uids: list = list() @@ -77,7 +77,7 @@ def get_folder_id_by_dashboard_path(self) -> int: return folder_id def get_all_folder_ids_and_names(self) -> list: - folders_raw: list = Dashboard.__call_the_api(self, "/api/search/?folderIds=0") + folders_raw: list = Dashboard.__call_the_api(self, f"{APIEndpoints.SEARCH}?folderIds=0") folders_raw_len: int = len(folders_raw) folders: list = list() diff --git a/src/grafana_api/model.py b/src/grafana_api/model.py index a43adb0..6d72e45 100644 --- a/src/grafana_api/model.py +++ b/src/grafana_api/model.py @@ -1,4 +1,14 @@ -class Model: +from enum import Enum + +class RequestsMethods(Enum): + GET = True + + +class APIEndpoints(Enum): + SEARCH: str = "/api/search" + DASHBOARDS: str = "/api/dashboards" + +class APIModel: def __init__(self, host: str = None, token: str = None, message: str = None, dashboard_path: str = None, dashboard_name: str = None): From 88873d81959b33cdde8330db81a9767e42520f85 Mon Sep 17 00:00:00 2001 From: Pascal Zimmermann Date: Fri, 7 Jan 2022 07:32:04 +0100 Subject: [PATCH 03/19] WIP --- README.md | 5 +++-- src/grafana_api/dashboard.py | 3 +++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 878a76f..2f4f384 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,11 @@ The repository includes an SDK for the Grafana API ## TODO -- Documentation +- Exception handling - Docstrings -- PYPI support - Tests +- Documentation +- PYPI support ## Currently, supported features diff --git a/src/grafana_api/dashboard.py b/src/grafana_api/dashboard.py index 03a1d76..3bfc828 100644 --- a/src/grafana_api/dashboard.py +++ b/src/grafana_api/dashboard.py @@ -34,6 +34,7 @@ def create_or_update_dashboard(self, dashboard_json: any, overwrite: bool = Fals logging.info("You successfully deployed the dashboard.") def delete_dashboard_by_name_and_path(self): + # TODO Check if a list is necessary dashboard_uids: list = self.get_dashboard_uid_by_name_and_folder() if dashboard_uids != list(): @@ -71,6 +72,7 @@ def get_folder_id_by_dashboard_path(self) -> int: folder_id = f["id"] if folder_id == 0: + # TODO Print the folder id logging.error("There's no folder_id available.") sys.exit(1) @@ -89,6 +91,7 @@ def get_all_folder_ids_and_names(self) -> list: def __call_the_api(self, api_call: str, method: str = "GET", dashboard_json_complete=None): api_url: str = f"{self.grafana_api_model.host}{api_call}" headers: dict = {"Authorization": f"Bearer {self.grafana_api_model.token}", "Content-Type": "application/json"} + #TODO Use a ENUM as method name description try: if method == "GET": return requests.get(api_url, headers=headers).json() From 61d808c3694b882de81499d12d3017ddee59bc9c Mon Sep 17 00:00:00 2001 From: Pascal Zimmermann Date: Mon, 10 Jan 2022 10:01:50 +0100 Subject: [PATCH 04/19] Add unittests and the integrationtests --- README.md | 2 +- src/grafana_api/dashboard.py | 102 +++++---- src/grafana_api/model.py | 25 +- test/integrationtest/dashboard.py | 37 +++ test/integrationtest/resources/dashboard.json | 0 test/test_dashboard.py | 10 - test/test_model.py | 10 - test/unittests/test_dashboard.py | 215 ++++++++++++++++++ test/unittests/test_model.py | 30 +++ 9 files changed, 363 insertions(+), 68 deletions(-) create mode 100644 test/integrationtest/dashboard.py create mode 100644 test/integrationtest/resources/dashboard.json delete mode 100644 test/test_dashboard.py delete mode 100644 test/test_model.py create mode 100644 test/unittests/test_dashboard.py create mode 100644 test/unittests/test_model.py diff --git a/README.md b/README.md index 2f4f384..3679063 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,9 @@ The repository includes an SDK for the Grafana API ## TODO -- Exception handling - Docstrings - Tests + - Integrationtest - Documentation - PYPI support diff --git a/src/grafana_api/dashboard.py b/src/grafana_api/dashboard.py index 3bfc828..d7260dc 100644 --- a/src/grafana_api/dashboard.py +++ b/src/grafana_api/dashboard.py @@ -1,67 +1,74 @@ -import sys import json import logging import requests -from model import APIModel, APIEndpoints +from grafana_api.model import APIModel, APIEndpoints, RequestsMethods # https://grafana.com/docs/grafana/latest/http_api/dashboard/ class Dashboard: - def __init__(self, grafana_api_model: APIModel): self.grafana_api_model = grafana_api_model self.logging = logging.Logger - def create_or_update_dashboard(self, dashboard_json: any, overwrite: bool = False): + def create_or_update_dashboard(self, dashboard_json: dict, overwrite: bool = False): folder_id: int = self.get_folder_id_by_dashboard_path() dashboard_json_complete: dict = { "dashboard": dashboard_json, "folderId": folder_id, "message": self.grafana_api_model.message, - "overwrite": overwrite + "overwrite": overwrite, } - api_call = Dashboard.__call_the_api(self, f"{APIEndpoints.DASHBOARDS}/db", "POST", json.dumps(dashboard_json_complete)) + api_call: dict = Dashboard.__call_the_api( + self, + f"{APIEndpoints.DASHBOARDS.value}/db", + RequestsMethods.POST, + json.dumps(dashboard_json_complete), + ) + + print(api_call) status: str = api_call["status"] if status != "success": logging.error(f"Check the error: {api_call}.") - sys.exit(1) + raise Exception else: logging.info("You successfully deployed the dashboard.") def delete_dashboard_by_name_and_path(self): - # TODO Check if a list is necessary - dashboard_uids: list = self.get_dashboard_uid_by_name_and_folder() + dashboard_uids: str = self.get_dashboard_uid_by_name_and_folder() - if dashboard_uids != list(): + if len(dashboard_uids) != 0: for dashboard_uid in dashboard_uids: - api_call = Dashboard.__call_the_api(self, f"{APIEndpoints.DASHBOARDS}/uid/{dashboard_uid}", "DELETE") + api_call: dict = Dashboard.__call_the_api( + self, + f"{APIEndpoints.DASHBOARDS.value}/uid/{dashboard_uid}", + RequestsMethods.DELETE, + ) message: str = api_call["message"] - if f"Dashboard {self.grafana_api_model.dashboard_name} deleted" != message: + if ( + f"Dashboard {self.grafana_api_model.dashboard_name} deleted" + != message + ): logging.error(f"Please, check the error: {api_call}.") - sys.exit(1) + raise Exception else: logging.info("You successfully destroyed the dashboard.") else: logging.info("Nothing to delete. There is no dashboard available.") - def get_dashboard_uid_by_name_and_folder(self) -> list: + def get_dashboard_uid_by_name_and_folder(self) -> str: folder_id: int = self.get_folder_id_by_dashboard_path() - search_query: str = f"{APIEndpoints.SEARCH}?folderIds={folder_id}&query={self.grafana_api_model.dashboard_name}" - dashboard_meta_list: list = Dashboard.__call_the_api(self, search_query) + search_query: str = f"{APIEndpoints.SEARCH.value}?folderIds={folder_id}&query={self.grafana_api_model.dashboard_name}" + dashboard_meta: list = Dashboard.__call_the_api(self, search_query) - dashboard_uids: list = list() - for dashboard_meta in dashboard_meta_list: - dashboard_uids.append(dashboard_meta["uid"]) - - return dashboard_uids + return dashboard_meta[0]["uid"] def get_folder_id_by_dashboard_path(self) -> int: folders: list = self.get_all_folder_ids_and_names() @@ -72,40 +79,57 @@ def get_folder_id_by_dashboard_path(self) -> int: folder_id = f["id"] if folder_id == 0: - # TODO Print the folder id - logging.error("There's no folder_id available.") - sys.exit(1) + logging.error( + f"There's no folder_id for the dashboard named {self.grafana_api_model.dashboard_path} available." + ) + raise Exception return folder_id def get_all_folder_ids_and_names(self) -> list: - folders_raw: list = Dashboard.__call_the_api(self, f"{APIEndpoints.SEARCH}?folderIds=0") + folders_raw: list = Dashboard.__call_the_api( + self, f"{APIEndpoints.SEARCH.value}?folderIds=0" + ) folders_raw_len: int = len(folders_raw) folders: list = list() + print(folders_raw) + for i in range(0, folders_raw_len): - folders.append({"title": folders_raw[i]["title"], "id": folders_raw[i]["id"]}) + folders.append( + {"title": folders_raw[i]["title"], "id": folders_raw[i]["id"]} + ) return folders - def __call_the_api(self, api_call: str, method: str = "GET", dashboard_json_complete=None): + def __call_the_api( + self, + api_call: str, + method: RequestsMethods = RequestsMethods.GET, + dashboard_json_complete: str = None, + ) -> any: api_url: str = f"{self.grafana_api_model.host}{api_call}" - headers: dict = {"Authorization": f"Bearer {self.grafana_api_model.token}", "Content-Type": "application/json"} - #TODO Use a ENUM as method name description + + print(api_url) + print(dashboard_json_complete) + + headers: dict = { + "Authorization": f"Bearer {self.grafana_api_model.token}", + "Content-Type": "application/json", + } try: - if method == "GET": + if method.value == RequestsMethods.GET.value: return requests.get(api_url, headers=headers).json() - elif method == "POST": + elif method.value == RequestsMethods.POST.value: if dashboard_json_complete is not None: - return requests.post(api_url, data=dashboard_json_complete, headers=headers).json() + return requests.post( + api_url, data=dashboard_json_complete, headers=headers + ).json() else: logging.error("Please define the dashboard_json_complete.") - sys.exit(1) - elif method == "DELETE": + raise Exception + elif method.value == RequestsMethods.DELETE.value: return requests.delete(api_url, headers=headers).json() - else: - logging.error("Please a define valid method.") - sys.exit(1) except Exception as e: - logging.error(f"Please, check the error: {e}.") - sys.exit(1) + logging.error(f"Please a define valid method and check the error: {e}.") + raise e diff --git a/src/grafana_api/model.py b/src/grafana_api/model.py index 6d72e45..c127bf4 100644 --- a/src/grafana_api/model.py +++ b/src/grafana_api/model.py @@ -1,17 +1,26 @@ from enum import Enum -class RequestsMethods(Enum): - GET = True - class APIEndpoints(Enum): - SEARCH: str = "/api/search" - DASHBOARDS: str = "/api/dashboards" + SEARCH = "/api/search" + DASHBOARDS = "/api/dashboards" -class APIModel: - def __init__(self, host: str = None, token: str = None, message: str = None, dashboard_path: str = None, - dashboard_name: str = None): +class RequestsMethods(Enum): + GET = "GET" + POST = "POST" + DELETE = "DELETE" + + +class APIModel: + def __init__( + self, + host: str = None, + token: str = None, + message: str = None, + dashboard_path: str = None, + dashboard_name: str = None, + ) -> object: self.host = host self.token = token self.message = message diff --git a/test/integrationtest/dashboard.py b/test/integrationtest/dashboard.py new file mode 100644 index 0000000..f3a1a5a --- /dev/null +++ b/test/integrationtest/dashboard.py @@ -0,0 +1,37 @@ +import os +import json +from unittest import TestCase, main + +from src.grafana_api.model import APIModel +from src.grafana_api.dashboard import Dashboard + + +class DashboardTest(TestCase): + model: APIModel = APIModel( + host=os.environ["GRAFANA_HOST"], + token=os.environ["GRAFANA_TOKEN"], + message="Create a new test dashboard", + dashboard_path=os.environ["GRAFANA_DASHBOARD_PATH"], + dashboard_name=os.environ["GRAFANA_DASHBOARD_NAME"], + ) + dashboard: Dashboard = Dashboard(model) + + def test_dashboard_creation(self): + with open(f"{os.getcwd()}{os.sep}resources{os.sep}dashboard.json") as file: + json_dashboard = json.load(file) + + self.dashboard.create_or_update_dashboard( + dashboard_json=json_dashboard, overwrite=True + ) + + self.assertEqual("tests", self.dashboard.get_dashboard_uid_by_name_and_folder()) + self.assertEqual(30, self.dashboard.get_folder_id_by_dashboard_path()) + + def test_dashboard_deletion(self): + self.dashboard.delete_dashboard_by_name_and_path() + + self.assertEqual(None, self.dashboard.get_dashboard_uid_by_name_and_folder()) + + +if __name__ == "__main__": + main() diff --git a/test/integrationtest/resources/dashboard.json b/test/integrationtest/resources/dashboard.json new file mode 100644 index 0000000..e69de29 diff --git a/test/test_dashboard.py b/test/test_dashboard.py deleted file mode 100644 index 1af168f..0000000 --- a/test/test_dashboard.py +++ /dev/null @@ -1,10 +0,0 @@ -import unittest - - -class DashboardTestCase(unittest.TestCase): - def test_something(self): - self.assertEqual(True, False) # add assertion here - - -if __name__ == '__main__': - unittest.main() diff --git a/test/test_model.py b/test/test_model.py deleted file mode 100644 index dfef9d9..0000000 --- a/test/test_model.py +++ /dev/null @@ -1,10 +0,0 @@ -import unittest - - -class ModelTestCase(unittest.TestCase): - def test_something(self): - self.assertEqual(True, False) # add assertion here - - -if __name__ == '__main__': - unittest.main() diff --git a/test/unittests/test_dashboard.py b/test/unittests/test_dashboard.py new file mode 100644 index 0000000..9b6dc59 --- /dev/null +++ b/test/unittests/test_dashboard.py @@ -0,0 +1,215 @@ +from unittest import TestCase, main +from unittest.mock import MagicMock, patch, Mock + +from requests.exceptions import MissingSchema + +from src.grafana_api.model import APIModel, RequestsMethods +from src.grafana_api.dashboard import Dashboard + + +class DashboardTestCase(TestCase): + @patch("src.grafana_api.dashboard.Dashboard._Dashboard__call_the_api") + @patch("src.grafana_api.dashboard.Dashboard.get_folder_id_by_dashboard_path") + def test_create_or_update_dashboard( + self, folder_id_by_dashboard_path_mock, call_the_api_mock + ): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + dashboard: Dashboard = Dashboard(grafana_api_model=model) + + folder_id_by_dashboard_path_mock.return_value = 1 + call_the_api_mock.return_value = dict({"status": "success"}) + self.assertEqual( + None, + dashboard.create_or_update_dashboard(dashboard_json=dict({"test": "test"})), + ) + + @patch("src.grafana_api.dashboard.Dashboard._Dashboard__call_the_api") + @patch("src.grafana_api.dashboard.Dashboard.get_folder_id_by_dashboard_path") + def test_create_or_update_dashboard_update_not_possible( + self, folder_id_by_dashboard_path_mock, call_the_api_mock + ): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + dashboard: Dashboard = Dashboard(grafana_api_model=model) + + folder_id_by_dashboard_path_mock.return_value = 1 + call_the_api_mock.return_value = dict({"status": "error"}) + with self.assertRaises(Exception): + dashboard.create_or_update_dashboard(dashboard_json=dict({"test": "test"})) + + @patch("src.grafana_api.dashboard.Dashboard._Dashboard__call_the_api") + @patch("src.grafana_api.dashboard.Dashboard.get_dashboard_uid_by_name_and_folder") + def test_delete_dashboard_by_name_and_path( + self, dashboard_uid_by_name_and_folder_mock, call_the_api_mock + ): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + dashboard: Dashboard = Dashboard(grafana_api_model=model) + + dashboard_uid_by_name_and_folder_mock.return_value = "test" + call_the_api_mock.return_value = dict({"message": "Dashboard None deleted"}) + self.assertEqual(None, dashboard.delete_dashboard_by_name_and_path()) + + @patch("src.grafana_api.dashboard.Dashboard._Dashboard__call_the_api") + @patch("src.grafana_api.dashboard.Dashboard.get_dashboard_uid_by_name_and_folder") + def test_delete_dashboard_by_name_and_path_deletion_list_empty( + self, dashboard_uid_by_name_and_folder_mock, call_the_api_mock + ): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + dashboard: Dashboard = Dashboard(grafana_api_model=model) + + dashboard_uid_by_name_and_folder_mock.return_value = "" + call_the_api_mock.return_value = dict({"message": "error"}) + self.assertEqual(None, dashboard.delete_dashboard_by_name_and_path()) + + @patch("src.grafana_api.dashboard.Dashboard._Dashboard__call_the_api") + @patch("src.grafana_api.dashboard.Dashboard.get_dashboard_uid_by_name_and_folder") + def test_delete_dashboard_by_name_and_path_deletion_not_possible( + self, dashboard_uid_by_name_and_folder_mock, call_the_api_mock + ): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + dashboard: Dashboard = Dashboard(grafana_api_model=model) + + dashboard_uid_by_name_and_folder_mock.return_value = "test" + call_the_api_mock.return_value = dict({"message": "error"}) + with self.assertRaises(Exception): + dashboard.delete_dashboard_by_name_and_path() + + @patch("src.grafana_api.dashboard.Dashboard._Dashboard__call_the_api") + @patch("src.grafana_api.dashboard.Dashboard.get_folder_id_by_dashboard_path") + def test_get_dashboard_uid_by_name_and_folder( + self, folder_id_by_dashboard_path_mock, call_the_api_mock + ): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + dashboard: Dashboard = Dashboard(grafana_api_model=model) + + folder_id_by_dashboard_path_mock.return_value = 1 + call_the_api_mock.return_value = [{"uid": 10}] + self.assertEqual(10, dashboard.get_dashboard_uid_by_name_and_folder()) + + @patch("src.grafana_api.dashboard.Dashboard.get_all_folder_ids_and_names") + def test_get_folder_id_by_dashboard_path(self, all_folder_ids_and_names_mock): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + dashboard: Dashboard = Dashboard(grafana_api_model=model) + + all_folder_ids_and_names_mock.return_value = [{"title": None, "id": 12}] + self.assertEqual(12, dashboard.get_folder_id_by_dashboard_path()) + + @patch("src.grafana_api.dashboard.Dashboard.get_all_folder_ids_and_names") + def test_get_folder_id_by_dashboard_path_no_title_match( + self, all_folder_ids_and_names_mock + ): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + dashboard: Dashboard = Dashboard(grafana_api_model=model) + + all_folder_ids_and_names_mock.return_value = [{"title": "test", "id": "xty13y"}] + with self.assertRaises(Exception): + dashboard.get_folder_id_by_dashboard_path() + + @patch("src.grafana_api.dashboard.Dashboard._Dashboard__call_the_api") + def test_get_all_folder_ids_and_names(self, call_the_api_mock): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + dashboard: Dashboard = Dashboard(grafana_api_model=model) + + call_the_api_mock.return_value = [{"title": "test", "id": 12, "test": "test"}] + self.assertEqual( + [{"title": "test", "id": 12}], dashboard.get_all_folder_ids_and_names() + ) + + def test_call_the_api_non_method(self): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + dashboard: Dashboard = Dashboard(grafana_api_model=model) + + with self.assertRaises(Exception): + dashboard._Dashboard__call_the_api(api_call=MagicMock(), method=None) + + @patch("requests.get") + def test_call_the_api_get_valid(self, get_mock): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + dashboard: Dashboard = Dashboard(grafana_api_model=model) + + mock: Mock = Mock() + mock.json = Mock(return_value={"status": "success"}) + + get_mock.return_value = mock + + self.assertEqual( + "success", + dashboard._Dashboard__call_the_api(api_call=MagicMock())["status"], + ) + + def test_call_the_api_get_not_valid(self): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + dashboard: Dashboard = Dashboard(grafana_api_model=model) + + with self.assertRaises(MissingSchema): + dashboard._Dashboard__call_the_api( + api_call=MagicMock(), method=RequestsMethods.GET + ) + + @patch("requests.post") + def test_call_the_api_post_valid(self, post_mock): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + dashboard: Dashboard = Dashboard(grafana_api_model=model) + + mock: Mock = Mock() + mock.json = Mock(return_value={"status": "success"}) + + post_mock.return_value = mock + + self.assertEqual( + "success", + dashboard._Dashboard__call_the_api( + api_call=MagicMock(), + method=RequestsMethods.POST, + dashboard_json_complete=MagicMock(), + )["status"], + ) + + def test_call_the_api_post_not_valid(self): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + dashboard: Dashboard = Dashboard(grafana_api_model=model) + + with self.assertRaises(MissingSchema): + dashboard._Dashboard__call_the_api( + api_call=MagicMock(), + method=RequestsMethods.POST, + dashboard_json_complete=MagicMock(), + ) + + def test_call_the_api_post_no_data(self): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + dashboard: Dashboard = Dashboard(grafana_api_model=model) + + with self.assertRaises(Exception): + dashboard._Dashboard__call_the_api( + api_call=MagicMock(), method=RequestsMethods.POST + ) + + @patch("requests.delete") + def test_call_the_api_delete_valid(self, delete_mock): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + dashboard: Dashboard = Dashboard(grafana_api_model=model) + + mock: Mock = Mock() + mock.json = Mock(return_value={"message": "Deletion successful"}) + + delete_mock.return_value = mock + + self.assertEqual( + "Deletion successful", + dashboard._Dashboard__call_the_api( + api_call=MagicMock(), method=RequestsMethods.DELETE + )["message"], + ) + + def test_call_the_api_delete_not_valid(self): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + dashboard: Dashboard = Dashboard(grafana_api_model=model) + + with self.assertRaises(Exception): + dashboard._Dashboard__call_the_api( + api_call=MagicMock(), method=RequestsMethods.DELETE + ) + + +if __name__ == "__main__": + main() diff --git a/test/unittests/test_model.py b/test/unittests/test_model.py new file mode 100644 index 0000000..765f972 --- /dev/null +++ b/test/unittests/test_model.py @@ -0,0 +1,30 @@ +from unittest import TestCase, main + +from src.grafana_api.model import APIModel, RequestsMethods, APIEndpoints + + +class APIEndpointsTestCase(TestCase): + def test_api_endpoints_init(self): + self.assertEqual("APIEndpoints.DASHBOARDS", str(APIEndpoints.DASHBOARDS)) + self.assertEqual("APIEndpoints.SEARCH", str(APIEndpoints.SEARCH)) + + +class RequestsMethodsTestCase(TestCase): + def test_requests_methods_init(self): + self.assertEqual("RequestsMethods.GET", str(RequestsMethods.GET)) + self.assertEqual("RequestsMethods.POST", str(RequestsMethods.POST)) + self.assertEqual("RequestsMethods.DELETE", str(RequestsMethods.DELETE)) + + +class APIModelTestCase(TestCase): + def test_api_model_init(self): + model = APIModel( + host="test", token="test", dashboard_name="test", dashboard_path="test" + ) + + self.assertEqual(None, model.message) + self.assertEqual("test", model.host) + + +if __name__ == "__main__": + main() From 2b45d1a3ee476adbef4b1b3f6e236d0ac11efd9e Mon Sep 17 00:00:00 2001 From: Pascal Zimmermann Date: Tue, 11 Jan 2022 23:54:39 +0100 Subject: [PATCH 05/19] Update the tests and the src --- .github/workflows/integrationtest.yml | 21 ++ .github/workflows/pull-request-checks.yml | 2 +- README.md | 4 - src/grafana_api/dashboard.py | 208 +++++++++++------- src/grafana_api/folder.py | 52 +++++ src/grafana_api/model.py | 23 +- src/grafana_api/search.py | 22 ++ src/grafana_api/utils.py | 61 +++++ test/integrationtest/resources/dashboard.json | 0 .../integrationtest/resources/dashboard.json | 30 +++ .../integrationtest/test_dashboard.py | 24 +- {test => tests}/unittests/test_dashboard.py | 190 ++++++++-------- tests/unittests/test_folder.py | 36 +++ {test => tests}/unittests/test_model.py | 0 tests/unittests/test_search.py | 37 ++++ tests/unittests/test_utils.py | 115 ++++++++++ 16 files changed, 646 insertions(+), 179 deletions(-) create mode 100644 .github/workflows/integrationtest.yml create mode 100644 src/grafana_api/folder.py create mode 100644 src/grafana_api/search.py create mode 100644 src/grafana_api/utils.py delete mode 100644 test/integrationtest/resources/dashboard.json create mode 100644 tests/integrationtest/resources/dashboard.json rename test/integrationtest/dashboard.py => tests/integrationtest/test_dashboard.py (51%) rename {test => tests}/unittests/test_dashboard.py (52%) create mode 100644 tests/unittests/test_folder.py rename {test => tests}/unittests/test_model.py (100%) create mode 100644 tests/unittests/test_search.py create mode 100644 tests/unittests/test_utils.py diff --git a/.github/workflows/integrationtest.yml b/.github/workflows/integrationtest.yml new file mode 100644 index 0000000..0b7035c --- /dev/null +++ b/.github/workflows/integrationtest.yml @@ -0,0 +1,21 @@ +name: Integrationtest + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + + test: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Install the requirements + run: pip install -r requirements.txt + + - name: Execute the unittests + run: python3 -m unittest discover tests/integrationtest \ No newline at end of file diff --git a/.github/workflows/pull-request-checks.yml b/.github/workflows/pull-request-checks.yml index e349ebe..11dcb92 100644 --- a/.github/workflows/pull-request-checks.yml +++ b/.github/workflows/pull-request-checks.yml @@ -26,7 +26,7 @@ jobs: run: pip install -r requirements.txt - name: Execute the unittests - run: python3 -m unittest discover tests + run: python3 -m unittest discover tests/unittests lint: runs-on: ubuntu-latest diff --git a/README.md b/README.md index 3679063..56c88f4 100644 --- a/README.md +++ b/README.md @@ -2,16 +2,12 @@ The repository includes an SDK for the Grafana API ## TODO -- Docstrings -- Tests - - Integrationtest - Documentation - PYPI support ## Currently, supported features ### Dashboard -- Get a Dashboard by uid - Get folder id by dashboard path - Get all folder ids and folder names - Specify the grafana dashboard folder diff --git a/src/grafana_api/dashboard.py b/src/grafana_api/dashboard.py index d7260dc..1e0f066 100644 --- a/src/grafana_api/dashboard.py +++ b/src/grafana_api/dashboard.py @@ -1,18 +1,34 @@ import json import logging -import requests -from grafana_api.model import APIModel, APIEndpoints, RequestsMethods +from .model import APIModel, APIEndpoints, RequestsMethods +from .folder import Folder +from .utils import Utils - -# https://grafana.com/docs/grafana/latest/http_api/dashboard/ +# https://grafana.com/docs/grafana/latest/http_api/dashboard_versions/ +# GET /api/dashboards/id/:dashboardId/versions 12.01 +# GET /api/dashboards/id/:dashboardId/versions/:id 12.01 +# POST /api/dashboards/id/:dashboardId/restore 12.01 +# POST /api/dashboards/calculate-diff 12.01 class Dashboard: + """The class includes all necessary methods to access the Grafana dashboard API endpoints + + Keyword arguments: + grafana_api_model -> Inject a Grafana API model object that includes all necessary values and information + """ + def __init__(self, grafana_api_model: APIModel): self.grafana_api_model = grafana_api_model - self.logging = logging.Logger def create_or_update_dashboard(self, dashboard_json: dict, overwrite: bool = False): - folder_id: int = self.get_folder_id_by_dashboard_path() + """The method includes a functionality to create the specified dashboard + + Keyword arguments: + dashboard_json -> Specify the inserted dashboard as dict + overwrite -> Should the already existing dashboard be overwritten + """ + + folder_id: int = Folder(self.grafana_api_model).get_folder_id_by_dashboard_path() dashboard_json_complete: dict = { "dashboard": dashboard_json, @@ -21,15 +37,12 @@ def create_or_update_dashboard(self, dashboard_json: dict, overwrite: bool = Fal "overwrite": overwrite, } - api_call: dict = Dashboard.__call_the_api( - self, + api_call: dict = Utils(self.grafana_api_model).call_the_api( f"{APIEndpoints.DASHBOARDS.value}/db", RequestsMethods.POST, json.dumps(dashboard_json_complete), ) - print(api_call) - status: str = api_call["status"] if status != "success": @@ -39,97 +52,130 @@ def create_or_update_dashboard(self, dashboard_json: dict, overwrite: bool = Fal logging.info("You successfully deployed the dashboard.") def delete_dashboard_by_name_and_path(self): - dashboard_uids: str = self.get_dashboard_uid_by_name_and_folder() + """The method includes a functionality to delete the specified dashboard inside the model""" + + dashboard_uid: str = self.get_dashboard_uid_by_name_and_folder() - if len(dashboard_uids) != 0: - for dashboard_uid in dashboard_uids: - api_call: dict = Dashboard.__call_the_api( - self, - f"{APIEndpoints.DASHBOARDS.value}/uid/{dashboard_uid}", - RequestsMethods.DELETE, - ) + if len(dashboard_uid) != 0: + api_call: dict = Utils(self.grafana_api_model).call_the_api( + f"{APIEndpoints.DASHBOARDS.value}/uid/{dashboard_uid}", + RequestsMethods.DELETE, + ) - message: str = api_call["message"] + message: str = api_call["message"] - if ( + if ( f"Dashboard {self.grafana_api_model.dashboard_name} deleted" != message - ): - logging.error(f"Please, check the error: {api_call}.") - raise Exception - else: - logging.info("You successfully destroyed the dashboard.") + ): + logging.error(f"Please, check the error: {api_call}.") + raise Exception + else: + logging.info("You successfully destroyed the dashboard.") else: logging.info("Nothing to delete. There is no dashboard available.") + raise ValueError - def get_dashboard_uid_by_name_and_folder(self) -> str: - folder_id: int = self.get_folder_id_by_dashboard_path() + def get_dashboard_by_uid(self, uid: str) -> dict: + """The method includes a functionality to get the dashboard from the specified uid - search_query: str = f"{APIEndpoints.SEARCH.value}?folderIds={folder_id}&query={self.grafana_api_model.dashboard_name}" - dashboard_meta: list = Dashboard.__call_the_api(self, search_query) + Keyword arguments: + uid -> Specify the uid of the dashboard + """ - return dashboard_meta[0]["uid"] + if len(uid) != 0: + api_call: dict = Utils(self.grafana_api_model).call_the_api( + f"{APIEndpoints.DASHBOARDS.value}/uid/{uid}" + ) + + if api_call.get("dashboard") is None: + logging.error(f"Please, check the error: {api_call}.") + raise Exception + else: + return api_call + else: + logging.info("There is no dashboard uid defined.") + raise ValueError - def get_folder_id_by_dashboard_path(self) -> int: - folders: list = self.get_all_folder_ids_and_names() - folder_id: int = 0 + def get_dashboard_home(self) -> dict: + """The method includes a functionality to get the home dashboard""" - for f in folders: - if self.grafana_api_model.dashboard_path == f["title"]: - folder_id = f["id"] + api_call: dict = Utils(self.grafana_api_model).call_the_api( + f"{APIEndpoints.DASHBOARDS.value}/home" + ) - if folder_id == 0: - logging.error( - f"There's no folder_id for the dashboard named {self.grafana_api_model.dashboard_path} available." - ) + if api_call.get("dashboard") is None: + logging.error(f"Please, check the error: {api_call}.") raise Exception + else: + return api_call - return folder_id + def get_dashboard_tags(self) -> list: + """The method includes a functionality to get the all tags of all dashboards""" - def get_all_folder_ids_and_names(self) -> list: - folders_raw: list = Dashboard.__call_the_api( - self, f"{APIEndpoints.SEARCH.value}?folderIds=0" + api_call: list = Utils(self.grafana_api_model).call_the_api( + f"{APIEndpoints.DASHBOARDS.value}/tags" ) - folders_raw_len: int = len(folders_raw) - folders: list = list() - print(folders_raw) + if api_call == list() or api_call[0].get("term") is None: + logging.error(f"Please, check the error: {api_call}.") + raise Exception + else: + return api_call - for i in range(0, folders_raw_len): - folders.append( - {"title": folders_raw[i]["title"], "id": folders_raw[i]["id"]} - ) + def get_dashboard_uid_by_name_and_folder(self) -> str: + """The method includes a functionality to extract the dashboard uid specified inside the model""" - return folders + folder_id: int = Folder(self.grafana_api_model).get_folder_id_by_dashboard_path() - def __call_the_api( - self, - api_call: str, - method: RequestsMethods = RequestsMethods.GET, - dashboard_json_complete: str = None, - ) -> any: - api_url: str = f"{self.grafana_api_model.host}{api_call}" + search_query: str = f"{APIEndpoints.SEARCH.value}?folderIds={folder_id}&query={self.grafana_api_model.dashboard_name}" + dashboard_meta: list = Utils(self.grafana_api_model).call_the_api(search_query) + + return dashboard_meta[0]["uid"] - print(api_url) - print(dashboard_json_complete) + def get_dashboard_permissions(self, uid: str): + """The method includes a functionality to extract the dashboard uid specified inside the model - headers: dict = { - "Authorization": f"Bearer {self.grafana_api_model.token}", - "Content-Type": "application/json", - } - try: - if method.value == RequestsMethods.GET.value: - return requests.get(api_url, headers=headers).json() - elif method.value == RequestsMethods.POST.value: - if dashboard_json_complete is not None: - return requests.post( - api_url, data=dashboard_json_complete, headers=headers - ).json() - else: - logging.error("Please define the dashboard_json_complete.") - raise Exception - elif method.value == RequestsMethods.DELETE.value: - return requests.delete(api_url, headers=headers).json() - except Exception as e: - logging.error(f"Please a define valid method and check the error: {e}.") - raise e + Keyword arguments: + uid -> Specify the uid of the dashboard + """ + + if len(uid) != 0: + api_call: list = Utils(self.grafana_api_model).call_the_api( + f"{APIEndpoints.DASHBOARDS.value}/id/{uid}/permissions" + ) + + if api_call == list() or api_call[0].get("role") is None: + logging.error(f"Please, check the error: {api_call}.") + raise Exception + else: + return api_call + else: + logging.info("There is no dashboard uid defined.") + raise ValueError + + def update_dashboard_permissions(self, uid: str, permission_json: dict): + """The method includes a functionality to extract the dashboard uid specified inside the model + + Keyword arguments: + uid -> Specify the uid of the dashboard + permission_json -> Specify the inserted permissions as dict + """ + + if len(uid) != 0 and len(permission_json) != 0: + api_call: dict = Utils(self.grafana_api_model).call_the_api( + f"{APIEndpoints.DASHBOARDS.value}/id/{uid}/permissions", + RequestsMethods.POST, + json.dumps(permission_json), + ) + + status: str = api_call["message"] + + if status != "Dashboard permissions updated": + logging.error(f"Please, check the error: {api_call}.") + raise Exception + else: + logging.info("You successfully modified the dashboard permissions.") + else: + logging.info("There is no dashboard uid or permission json defined.") + raise ValueError diff --git a/src/grafana_api/folder.py b/src/grafana_api/folder.py new file mode 100644 index 0000000..0138cc8 --- /dev/null +++ b/src/grafana_api/folder.py @@ -0,0 +1,52 @@ +import logging + +from .utils import Utils +from .model import APIModel, APIEndpoints + +# https://grafana.com/docs/grafana/latest/http_api/dashboard_versions/ +# GET /api/folders 13.01 +# GET /api/folders/:uid 13.01 +# POST /api/folders 13.01 +# PUT /api/folders/:uid 13.01 +# DELETE /api/folders/:uid 14.01 +# GET /api/folders/id/:id 14.01 + +# https://grafana.com/docs/grafana/latest/http_api/folder_permissions/ +# GET /api/folders/:uid/permissions 14.01 +# POST /api/folders/:uid/permissions 14.01 +class Folder: + + def __init__(self, grafana_api_model: APIModel): + self.grafana_api_model = grafana_api_model + + def get_folder_id_by_dashboard_path(self) -> int: + """The method includes a functionality to extract the folder id specified inside model dashboard path""" + + folders: list = self.get_all_folder_ids_and_names() + folder_id: int = 0 + + for f in folders: + if self.grafana_api_model.dashboard_path == f["title"]: + folder_id = f["id"] + + if folder_id == 0: + logging.error( + f"There's no folder_id for the dashboard named {self.grafana_api_model.dashboard_path} available." + ) + raise Exception + + return folder_id + + def get_all_folder_ids_and_names(self) -> list: + """The method extract all folder id and names inside the complete organisation""" + + folders_raw: list = Utils(self.grafana_api_model).call_the_api(f"{APIEndpoints.SEARCH.value}?folderIds=0") + folders_raw_len: int = len(folders_raw) + folders: list = list() + + for i in range(0, folders_raw_len): + folders.append( + {"title": folders_raw[i]["title"], "id": folders_raw[i]["id"]} + ) + + return folders diff --git a/src/grafana_api/model.py b/src/grafana_api/model.py index c127bf4..83fe7a7 100644 --- a/src/grafana_api/model.py +++ b/src/grafana_api/model.py @@ -1,18 +1,39 @@ from enum import Enum +"""""" +ERROR_MESSAGES: list = ["invalid API key"] + class APIEndpoints(Enum): + """The class includes all necessary methods to template the selected dashboard and return it as a dict + + Keyword arguments: + dashboard_model -> Inject a dashboard object that includes all necessary values and information + """ + SEARCH = "/api/search" DASHBOARDS = "/api/dashboards" class RequestsMethods(Enum): + """The class includes all necessary methods to template the selected dashboard and return it as a dict + + Keyword arguments: + dashboard_model -> Inject a dashboard object that includes all necessary values and information + """ + GET = "GET" POST = "POST" DELETE = "DELETE" class APIModel: + """The class includes all necessary methods to template the selected dashboard and return it as a dict + + Keyword arguments: + dashboard_model -> Inject a dashboard object that includes all necessary values and information + """ + def __init__( self, host: str = None, @@ -20,7 +41,7 @@ def __init__( message: str = None, dashboard_path: str = None, dashboard_name: str = None, - ) -> object: + ): self.host = host self.token = token self.message = message diff --git a/src/grafana_api/search.py b/src/grafana_api/search.py new file mode 100644 index 0000000..f93f188 --- /dev/null +++ b/src/grafana_api/search.py @@ -0,0 +1,22 @@ +from .utils import Utils +from .model import APIModel + + +class Search: + + def __init__(self, grafana_api_model: APIModel): + self.grafana_api_model = grafana_api_model + + def search(self, search_query: str) -> list: + """The method includes a functionality to execute a custom query + + Keyword arguments: + search_query -> Specify the inserted query as string + """ + + result: list = Utils(self.grafana_api_model).call_the_api(search_query) + if result == list(): + raise Exception + else: + return result + diff --git a/src/grafana_api/utils.py b/src/grafana_api/utils.py new file mode 100644 index 0000000..92d7534 --- /dev/null +++ b/src/grafana_api/utils.py @@ -0,0 +1,61 @@ +import logging +import requests + +from .model import RequestsMethods, ERROR_MESSAGES, APIModel + + +class Utils: + + def __init__(self, grafana_api_model: APIModel): + self.grafana_api_model = grafana_api_model + + def call_the_api( + self, + api_call: str, + method: RequestsMethods = RequestsMethods.GET, + json_complete: str = None, + ) -> any: + """The method execute a defined API call against the Grafana endpoints + + Keyword arguments: + api_call -> Specify the API call endpoint + method -> Specify the used method + dashboard_json_complete -> Specify the inserted dashboard JSON as string + """ + + api_url: str = f"{self.grafana_api_model.host}{api_call}" + + headers: dict = { + "Authorization": f"Bearer {self.grafana_api_model.token}", + "Content-Type": "application/json", + } + try: + if method.value == RequestsMethods.GET.value: + return Utils.__check_the_api_call_response(requests.get(api_url, headers=headers).json()) + elif method.value == RequestsMethods.POST.value: + if json_complete is not None: + return Utils.__check_the_api_call_response(requests.post( + api_url, data=json_complete, headers=headers + ).json()) + else: + logging.error("Please define the dashboard_json_complete.") + raise Exception + elif method.value == RequestsMethods.DELETE.value: + return Utils.__check_the_api_call_response(requests.delete(api_url, headers=headers).json()) + except Exception as e: + raise e + + @staticmethod + def __check_the_api_call_response(response: any = None) -> any: + """The method includes a functionality to check the output of API call method for errors + + Keyword arguments: + response -> Specify the inserted response + """ + + if type(response) == dict: + if "message" in response.keys() and response["message"] in ERROR_MESSAGES: + logging.error(response["message"]) + raise requests.exceptions.ConnectionError + + return response diff --git a/test/integrationtest/resources/dashboard.json b/test/integrationtest/resources/dashboard.json deleted file mode 100644 index e69de29..0000000 diff --git a/tests/integrationtest/resources/dashboard.json b/tests/integrationtest/resources/dashboard.json new file mode 100644 index 0000000..f20876e --- /dev/null +++ b/tests/integrationtest/resources/dashboard.json @@ -0,0 +1,30 @@ +{ + "id": null, + "uid": "tests", + "title": "Test", + "tags": [], + "style": "dark", + "timezone": "browser", + "editable": true, + "hideControls": false, + "graphTooltip": 1, + "panels": [], + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "time_options": [], + "refresh_intervals": [] + }, + "templating": { + "list": [] + }, + "annotations": { + "list": [] + }, + "refresh": "5s", + "schemaVersion": 17, + "version": 0, + "links": [] +} \ No newline at end of file diff --git a/test/integrationtest/dashboard.py b/tests/integrationtest/test_dashboard.py similarity index 51% rename from test/integrationtest/dashboard.py rename to tests/integrationtest/test_dashboard.py index f3a1a5a..efe74d9 100644 --- a/test/integrationtest/dashboard.py +++ b/tests/integrationtest/test_dashboard.py @@ -2,8 +2,11 @@ import json from unittest import TestCase, main +import requests.exceptions + from src.grafana_api.model import APIModel from src.grafana_api.dashboard import Dashboard +from src.grafana_api.folder import Folder class DashboardTest(TestCase): @@ -15,9 +18,10 @@ class DashboardTest(TestCase): dashboard_name=os.environ["GRAFANA_DASHBOARD_NAME"], ) dashboard: Dashboard = Dashboard(model) + folder: Folder = Folder(model) def test_dashboard_creation(self): - with open(f"{os.getcwd()}{os.sep}resources{os.sep}dashboard.json") as file: + with open(f"{os.getcwd()}tests{os.sep}integrationtest{os.sep}resources{os.sep}dashboard.json") as file: json_dashboard = json.load(file) self.dashboard.create_or_update_dashboard( @@ -25,12 +29,26 @@ def test_dashboard_creation(self): ) self.assertEqual("tests", self.dashboard.get_dashboard_uid_by_name_and_folder()) - self.assertEqual(30, self.dashboard.get_folder_id_by_dashboard_path()) + self.assertEqual(72, self.folder.get_folder_id_by_dashboard_path()) def test_dashboard_deletion(self): self.dashboard.delete_dashboard_by_name_and_path() - self.assertEqual(None, self.dashboard.get_dashboard_uid_by_name_and_folder()) + def test_wrong_token(self): + self.model.token = "test" + + with self.assertRaises(requests.exceptions.ConnectionError): + self.dashboard.delete_dashboard_by_name_and_path() + + def test_get_dashboard(self): + with open(f"{os.getcwd()}tests{os.sep}integrationtest{os.sep}resources{os.sep}dashboard.json") as file: + json_dashboard = json.load(file) + + self.dashboard.create_or_update_dashboard( + dashboard_json=json_dashboard, overwrite=True + ) + + self.assertEqual(json_dashboard, self.dashboard.get_dashboard_by_uid("tests")) if __name__ == "__main__": diff --git a/test/unittests/test_dashboard.py b/tests/unittests/test_dashboard.py similarity index 52% rename from test/unittests/test_dashboard.py rename to tests/unittests/test_dashboard.py index 9b6dc59..79ad813 100644 --- a/test/unittests/test_dashboard.py +++ b/tests/unittests/test_dashboard.py @@ -1,15 +1,13 @@ from unittest import TestCase, main -from unittest.mock import MagicMock, patch, Mock +from unittest.mock import MagicMock, patch -from requests.exceptions import MissingSchema - -from src.grafana_api.model import APIModel, RequestsMethods +from src.grafana_api.model import APIModel from src.grafana_api.dashboard import Dashboard class DashboardTestCase(TestCase): - @patch("src.grafana_api.dashboard.Dashboard._Dashboard__call_the_api") - @patch("src.grafana_api.dashboard.Dashboard.get_folder_id_by_dashboard_path") + @patch("src.grafana_api.utils.Utils.call_the_api") + @patch("src.grafana_api.folder.Folder.get_folder_id_by_dashboard_path") def test_create_or_update_dashboard( self, folder_id_by_dashboard_path_mock, call_the_api_mock ): @@ -23,8 +21,8 @@ def test_create_or_update_dashboard( dashboard.create_or_update_dashboard(dashboard_json=dict({"test": "test"})), ) - @patch("src.grafana_api.dashboard.Dashboard._Dashboard__call_the_api") - @patch("src.grafana_api.dashboard.Dashboard.get_folder_id_by_dashboard_path") + @patch("src.grafana_api.utils.Utils.call_the_api") + @patch("src.grafana_api.folder.Folder.get_folder_id_by_dashboard_path") def test_create_or_update_dashboard_update_not_possible( self, folder_id_by_dashboard_path_mock, call_the_api_mock ): @@ -36,7 +34,7 @@ def test_create_or_update_dashboard_update_not_possible( with self.assertRaises(Exception): dashboard.create_or_update_dashboard(dashboard_json=dict({"test": "test"})) - @patch("src.grafana_api.dashboard.Dashboard._Dashboard__call_the_api") + @patch("src.grafana_api.utils.Utils.call_the_api") @patch("src.grafana_api.dashboard.Dashboard.get_dashboard_uid_by_name_and_folder") def test_delete_dashboard_by_name_and_path( self, dashboard_uid_by_name_and_folder_mock, call_the_api_mock @@ -48,7 +46,7 @@ def test_delete_dashboard_by_name_and_path( call_the_api_mock.return_value = dict({"message": "Dashboard None deleted"}) self.assertEqual(None, dashboard.delete_dashboard_by_name_and_path()) - @patch("src.grafana_api.dashboard.Dashboard._Dashboard__call_the_api") + @patch("src.grafana_api.utils.Utils.call_the_api") @patch("src.grafana_api.dashboard.Dashboard.get_dashboard_uid_by_name_and_folder") def test_delete_dashboard_by_name_and_path_deletion_list_empty( self, dashboard_uid_by_name_and_folder_mock, call_the_api_mock @@ -58,9 +56,10 @@ def test_delete_dashboard_by_name_and_path_deletion_list_empty( dashboard_uid_by_name_and_folder_mock.return_value = "" call_the_api_mock.return_value = dict({"message": "error"}) - self.assertEqual(None, dashboard.delete_dashboard_by_name_and_path()) + with self.assertRaises(ValueError): + dashboard.delete_dashboard_by_name_and_path() - @patch("src.grafana_api.dashboard.Dashboard._Dashboard__call_the_api") + @patch("src.grafana_api.utils.Utils.call_the_api") @patch("src.grafana_api.dashboard.Dashboard.get_dashboard_uid_by_name_and_folder") def test_delete_dashboard_by_name_and_path_deletion_not_possible( self, dashboard_uid_by_name_and_folder_mock, call_the_api_mock @@ -73,8 +72,8 @@ def test_delete_dashboard_by_name_and_path_deletion_not_possible( with self.assertRaises(Exception): dashboard.delete_dashboard_by_name_and_path() - @patch("src.grafana_api.dashboard.Dashboard._Dashboard__call_the_api") - @patch("src.grafana_api.dashboard.Dashboard.get_folder_id_by_dashboard_path") + @patch("src.grafana_api.utils.Utils.call_the_api") + @patch("src.grafana_api.folder.Folder.get_folder_id_by_dashboard_path") def test_get_dashboard_uid_by_name_and_folder( self, folder_id_by_dashboard_path_mock, call_the_api_mock ): @@ -85,130 +84,143 @@ def test_get_dashboard_uid_by_name_and_folder( call_the_api_mock.return_value = [{"uid": 10}] self.assertEqual(10, dashboard.get_dashboard_uid_by_name_and_folder()) - @patch("src.grafana_api.dashboard.Dashboard.get_all_folder_ids_and_names") - def test_get_folder_id_by_dashboard_path(self, all_folder_ids_and_names_mock): + @patch("src.grafana_api.utils.Utils.call_the_api") + def test_get_dashboard_by_uid( + self, call_the_api_mock + ): model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") dashboard: Dashboard = Dashboard(grafana_api_model=model) - all_folder_ids_and_names_mock.return_value = [{"title": None, "id": 12}] - self.assertEqual(12, dashboard.get_folder_id_by_dashboard_path()) + call_the_api_mock.return_value = dict({"dashboard": "test"}) + self.assertEqual(dict({"dashboard": "test"}), dashboard.get_dashboard_by_uid(uid="test")) - @patch("src.grafana_api.dashboard.Dashboard.get_all_folder_ids_and_names") - def test_get_folder_id_by_dashboard_path_no_title_match( - self, all_folder_ids_and_names_mock + @patch("src.grafana_api.utils.Utils.call_the_api") + def test_get_dashboard_by_uid_no_dashboard( + self, call_the_api_mock ): model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") dashboard: Dashboard = Dashboard(grafana_api_model=model) - all_folder_ids_and_names_mock.return_value = [{"title": "test", "id": "xty13y"}] + call_the_api_mock.return_value = dict() with self.assertRaises(Exception): - dashboard.get_folder_id_by_dashboard_path() + dashboard.get_dashboard_by_uid(uid="test") - @patch("src.grafana_api.dashboard.Dashboard._Dashboard__call_the_api") - def test_get_all_folder_ids_and_names(self, call_the_api_mock): + @patch("src.grafana_api.utils.Utils.call_the_api") + def test_get_dashboard_by_uid_no_uid( + self, call_the_api_mock + ): model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") dashboard: Dashboard = Dashboard(grafana_api_model=model) - call_the_api_mock.return_value = [{"title": "test", "id": 12, "test": "test"}] - self.assertEqual( - [{"title": "test", "id": 12}], dashboard.get_all_folder_ids_and_names() - ) + call_the_api_mock.return_value = dict({"dashboard": "test"}) + with self.assertRaises(ValueError): + dashboard.get_dashboard_by_uid(uid="") - def test_call_the_api_non_method(self): + @patch("src.grafana_api.utils.Utils.call_the_api") + def test_get_dashboard_home( + self, call_the_api_mock + ): model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") dashboard: Dashboard = Dashboard(grafana_api_model=model) - with self.assertRaises(Exception): - dashboard._Dashboard__call_the_api(api_call=MagicMock(), method=None) + call_the_api_mock.return_value = dict({"dashboard": "test"}) + self.assertEqual(dict({"dashboard": "test"}), dashboard.get_dashboard_home()) - @patch("requests.get") - def test_call_the_api_get_valid(self, get_mock): + @patch("src.grafana_api.utils.Utils.call_the_api") + def test_get_dashboard_home_no_dashboard( + self, call_the_api_mock + ): model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") dashboard: Dashboard = Dashboard(grafana_api_model=model) - mock: Mock = Mock() - mock.json = Mock(return_value={"status": "success"}) - - get_mock.return_value = mock - - self.assertEqual( - "success", - dashboard._Dashboard__call_the_api(api_call=MagicMock())["status"], - ) + call_the_api_mock.return_value = dict() + with self.assertRaises(Exception): + dashboard.get_dashboard_home() - def test_call_the_api_get_not_valid(self): + @patch("src.grafana_api.utils.Utils.call_the_api") + def test_get_dashboard_tags( + self, call_the_api_mock + ): model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") dashboard: Dashboard = Dashboard(grafana_api_model=model) - with self.assertRaises(MissingSchema): - dashboard._Dashboard__call_the_api( - api_call=MagicMock(), method=RequestsMethods.GET - ) + call_the_api_mock.return_value = list([{"term": "test", "count": 4}]) + self.assertEqual(list([{"term": "test", "count": 4}]), dashboard.get_dashboard_tags()) - @patch("requests.post") - def test_call_the_api_post_valid(self, post_mock): + @patch("src.grafana_api.utils.Utils.call_the_api") + def test_get_dashboard_tags_no_tags( + self, call_the_api_mock + ): model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") dashboard: Dashboard = Dashboard(grafana_api_model=model) - mock: Mock = Mock() - mock.json = Mock(return_value={"status": "success"}) - - post_mock.return_value = mock - - self.assertEqual( - "success", - dashboard._Dashboard__call_the_api( - api_call=MagicMock(), - method=RequestsMethods.POST, - dashboard_json_complete=MagicMock(), - )["status"], - ) + call_the_api_mock.return_value = list() + with self.assertRaises(Exception): + dashboard.get_dashboard_tags() - def test_call_the_api_post_not_valid(self): + @patch("src.grafana_api.utils.Utils.call_the_api") + def test_get_dashboard_permissions( + self, call_the_api_mock + ): model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") dashboard: Dashboard = Dashboard(grafana_api_model=model) - with self.assertRaises(MissingSchema): - dashboard._Dashboard__call_the_api( - api_call=MagicMock(), - method=RequestsMethods.POST, - dashboard_json_complete=MagicMock(), - ) + call_the_api_mock.return_value = list([{"role": "test", "count": 4}]) + self.assertEqual(list([{"role": "test", "count": 4}]), dashboard.get_dashboard_permissions("test")) - def test_call_the_api_post_no_data(self): + @patch("src.grafana_api.utils.Utils.call_the_api") + def test_get_dashboard_permissions_empty_list( + self, call_the_api_mock + ): model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") dashboard: Dashboard = Dashboard(grafana_api_model=model) + call_the_api_mock.return_value = list() with self.assertRaises(Exception): - dashboard._Dashboard__call_the_api( - api_call=MagicMock(), method=RequestsMethods.POST - ) + dashboard.get_dashboard_permissions("test") - @patch("requests.delete") - def test_call_the_api_delete_valid(self, delete_mock): + @patch("src.grafana_api.utils.Utils.call_the_api") + def test_get_dashboard_permissions_no_uid( + self, call_the_api_mock + ): model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") dashboard: Dashboard = Dashboard(grafana_api_model=model) - mock: Mock = Mock() - mock.json = Mock(return_value={"message": "Deletion successful"}) + call_the_api_mock.return_value = list() + with self.assertRaises(ValueError): + dashboard.get_dashboard_permissions("") - delete_mock.return_value = mock + @patch("src.grafana_api.utils.Utils.call_the_api") + def test_update_dashboard_permissions( + self, call_the_api_mock + ): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + dashboard: Dashboard = Dashboard(grafana_api_model=model) - self.assertEqual( - "Deletion successful", - dashboard._Dashboard__call_the_api( - api_call=MagicMock(), method=RequestsMethods.DELETE - )["message"], - ) + call_the_api_mock.return_value = dict({"message": "Dashboard permissions updated"}) + self.assertEqual(None, dashboard.update_dashboard_permissions("test", {"test": "test"})) - def test_call_the_api_delete_not_valid(self): + @patch("src.grafana_api.utils.Utils.call_the_api") + def test_update_dashboard_permissions_error_response( + self, call_the_api_mock + ): model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") dashboard: Dashboard = Dashboard(grafana_api_model=model) + call_the_api_mock.return_value = dict({"message": "Error"}) with self.assertRaises(Exception): - dashboard._Dashboard__call_the_api( - api_call=MagicMock(), method=RequestsMethods.DELETE - ) + dashboard.update_dashboard_permissions("test", {"test": "test"}) + + @patch("src.grafana_api.utils.Utils.call_the_api") + def test_update_dashboard_permissions_no_uid( + self, call_the_api_mock + ): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + dashboard: Dashboard = Dashboard(grafana_api_model=model) + + call_the_api_mock.return_value = list() + with self.assertRaises(ValueError): + dashboard.update_dashboard_permissions("", MagicMock()) if __name__ == "__main__": diff --git a/tests/unittests/test_folder.py b/tests/unittests/test_folder.py new file mode 100644 index 0000000..63dc3f8 --- /dev/null +++ b/tests/unittests/test_folder.py @@ -0,0 +1,36 @@ +from unittest import TestCase +from unittest.mock import MagicMock, patch + +from src.grafana_api.model import APIModel +from src.grafana_api.folder import Folder + + +class FolderTestCase(TestCase): + @patch("src.grafana_api.folder.Folder.get_all_folder_ids_and_names") + def test_get_folder_id_by_dashboard_path(self, all_folder_ids_and_names_mock): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + folder: Folder = Folder(grafana_api_model=model) + + all_folder_ids_and_names_mock.return_value = [{"title": None, "id": 12}] + self.assertEqual(12, folder.get_folder_id_by_dashboard_path()) + + @patch("src.grafana_api.folder.Folder.get_all_folder_ids_and_names") + def test_get_folder_id_by_dashboard_path_no_title_match( + self, all_folder_ids_and_names_mock + ): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + folder: Folder = Folder(grafana_api_model=model) + + all_folder_ids_and_names_mock.return_value = [{"title": "test", "id": "xty13y"}] + with self.assertRaises(Exception): + folder.get_folder_id_by_dashboard_path() + + @patch("src.grafana_api.utils.Utils.call_the_api") + def test_get_all_folder_ids_and_names(self, call_the_api_mock): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + folder: Folder = Folder(grafana_api_model=model) + + call_the_api_mock.return_value = [{"title": "test", "id": 12, "test": "test"}] + self.assertEqual( + [{"title": "test", "id": 12}], folder.get_all_folder_ids_and_names() + ) \ No newline at end of file diff --git a/test/unittests/test_model.py b/tests/unittests/test_model.py similarity index 100% rename from test/unittests/test_model.py rename to tests/unittests/test_model.py diff --git a/tests/unittests/test_search.py b/tests/unittests/test_search.py new file mode 100644 index 0000000..bfb0da0 --- /dev/null +++ b/tests/unittests/test_search.py @@ -0,0 +1,37 @@ +from unittest import TestCase +from unittest.mock import MagicMock, patch + +from src.grafana_api.model import APIModel +from src.grafana_api.search import Search + + +class SearchTestCase(TestCase): + + @patch("src.grafana_api.utils.Utils.call_the_api") + def test_search(self, call_the_api_mock): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + search: Search = Search(grafana_api_model=model) + + call_the_api_mock.return_value = ["test"] + + self.assertEqual(["test"], search.search(search_query=MagicMock())) + + @patch("src.grafana_api.utils.Utils.call_the_api") + def test_search_unvalid_empty_list(self, call_the_api_mock): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + search: Search = Search(grafana_api_model=model) + + call_the_api_mock.return_value = list() + + with self.assertRaises(Exception): + search.search(search_query=MagicMock()) + + @patch("src.grafana_api.utils.Utils.call_the_api") + def test_search_unvalid_output(self, call_the_api_mock): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + search: Search = Search(grafana_api_model=model) + + call_the_api_mock.side_effect = Exception + + with self.assertRaises(Exception): + search.search(search_query=MagicMock()) diff --git a/tests/unittests/test_utils.py b/tests/unittests/test_utils.py new file mode 100644 index 0000000..50340ba --- /dev/null +++ b/tests/unittests/test_utils.py @@ -0,0 +1,115 @@ +import requests + +from unittest import TestCase +from unittest.mock import MagicMock, patch, Mock + +from requests.exceptions import MissingSchema + +from src.grafana_api.model import APIModel, RequestsMethods +from src.grafana_api.utils import Utils + + +class UtilsTestCase(TestCase): + + def test_call_the_api_non_method(self): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + utils: Utils = Utils(grafana_api_model=model) + + with self.assertRaises(Exception): + utils.call_the_api(api_call=MagicMock(), method=None) + + @patch("requests.get") + def test_call_the_api_get_valid(self, get_mock): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + utils: Utils = Utils(grafana_api_model=model) + + mock: Mock = Mock() + mock.json = Mock(return_value={"status": "success"}) + + get_mock.return_value = mock + + self.assertEqual( + "success", + utils.call_the_api(api_call=MagicMock())["status"], + ) + + def test_call_the_api_get_not_valid(self): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + utils: Utils = Utils(grafana_api_model=model) + + with self.assertRaises(MissingSchema): + utils.call_the_api( + api_call=MagicMock(), method=RequestsMethods.GET + ) + + @patch("requests.post") + def test_call_the_api_post_valid(self, post_mock): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + utils: Utils = Utils(grafana_api_model=model) + + mock: Mock = Mock() + mock.json = Mock(return_value={"status": "success"}) + + post_mock.return_value = mock + + self.assertEqual( + "success", + utils.call_the_api( + api_call=MagicMock(), + method=RequestsMethods.POST, + json_complete=MagicMock(), + )["status"], + ) + + def test_call_the_api_post_not_valid(self): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + utils: Utils = Utils(grafana_api_model=model) + + with self.assertRaises(MissingSchema): + utils.call_the_api( + api_call=MagicMock(), + method=RequestsMethods.POST, + json_complete=MagicMock(), + ) + + def test_call_the_api_post_no_data(self): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + utils: Utils = Utils(grafana_api_model=model) + + with self.assertRaises(Exception): + utils.call_the_api( + api_call=MagicMock(), method=RequestsMethods.POST + ) + + @patch("requests.delete") + def test_call_the_api_delete_valid(self, delete_mock): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + utils: Utils = Utils(grafana_api_model=model) + + mock: Mock = Mock() + mock.json = Mock(return_value={"message": "Deletion successful"}) + + delete_mock.return_value = mock + + self.assertEqual( + "Deletion successful", + utils.call_the_api( + api_call=MagicMock(), method=RequestsMethods.DELETE + )["message"], + ) + + def test_call_the_api_delete_not_valid(self): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + utils: Utils = Utils(grafana_api_model=model) + + with self.assertRaises(Exception): + utils.call_the_api( + api_call=MagicMock(), method=RequestsMethods.DELETE + ) + + def test_check_the_api_call_response(self): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + utils: Utils = Utils(grafana_api_model=model) + + with self.assertRaises(requests.exceptions.ConnectionError): + utils._Utils__check_the_api_call_response(response=dict({"message": "invalid API key"})) From 0f713846dcd55dd017059288bf4f477aceab7202 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 12 Jan 2022 06:55:55 +0000 Subject: [PATCH 06/19] Add coverage badge --- docs/coverage.svg | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 docs/coverage.svg diff --git a/docs/coverage.svg b/docs/coverage.svg new file mode 100644 index 0000000..a9be2c5 --- /dev/null +++ b/docs/coverage.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + coverage + coverage + 29% + 29% + + From 13238a9c28d12f47a576ad1cb6d0d6cf2c15d20b Mon Sep 17 00:00:00 2001 From: Pascal Zimmermann Date: Wed, 12 Jan 2022 09:19:28 +0100 Subject: [PATCH 07/19] Reformat the files --- .coveragerc | 4 +- src/grafana_api/dashboard.py | 14 +++-- src/grafana_api/folder.py | 6 +- src/grafana_api/search.py | 2 - src/grafana_api/utils.py | 25 +++++---- tests/integrationtest/test_dashboard.py | 8 ++- tests/unittests/test_dashboard.py | 73 ++++++++++--------------- tests/unittests/test_folder.py | 4 +- tests/unittests/test_search.py | 1 - tests/unittests/test_utils.py | 67 +++++++++++------------ 10 files changed, 97 insertions(+), 107 deletions(-) diff --git a/.coveragerc b/.coveragerc index ec86585..3e4ab6e 100644 --- a/.coveragerc +++ b/.coveragerc @@ -2,15 +2,15 @@ branch = True omit = */setup.py - */dashboard.json.sample tests/* + tests/integrationtest/* source = . [report] omit = */setup.py - */dashboard.json.sample tests/* + tests/integrationtest/* fail_under = 80 show_missing = True skip_covered = False diff --git a/src/grafana_api/dashboard.py b/src/grafana_api/dashboard.py index 1e0f066..8dc93c6 100644 --- a/src/grafana_api/dashboard.py +++ b/src/grafana_api/dashboard.py @@ -5,6 +5,7 @@ from .folder import Folder from .utils import Utils + # https://grafana.com/docs/grafana/latest/http_api/dashboard_versions/ # GET /api/dashboards/id/:dashboardId/versions 12.01 # GET /api/dashboards/id/:dashboardId/versions/:id 12.01 @@ -28,7 +29,9 @@ def create_or_update_dashboard(self, dashboard_json: dict, overwrite: bool = Fal overwrite -> Should the already existing dashboard be overwritten """ - folder_id: int = Folder(self.grafana_api_model).get_folder_id_by_dashboard_path() + folder_id: int = Folder( + self.grafana_api_model + ).get_folder_id_by_dashboard_path() dashboard_json_complete: dict = { "dashboard": dashboard_json, @@ -64,10 +67,7 @@ def delete_dashboard_by_name_and_path(self): message: str = api_call["message"] - if ( - f"Dashboard {self.grafana_api_model.dashboard_name} deleted" - != message - ): + if f"Dashboard {self.grafana_api_model.dashboard_name} deleted" != message: logging.error(f"Please, check the error: {api_call}.") raise Exception else: @@ -126,7 +126,9 @@ def get_dashboard_tags(self) -> list: def get_dashboard_uid_by_name_and_folder(self) -> str: """The method includes a functionality to extract the dashboard uid specified inside the model""" - folder_id: int = Folder(self.grafana_api_model).get_folder_id_by_dashboard_path() + folder_id: int = Folder( + self.grafana_api_model + ).get_folder_id_by_dashboard_path() search_query: str = f"{APIEndpoints.SEARCH.value}?folderIds={folder_id}&query={self.grafana_api_model.dashboard_name}" dashboard_meta: list = Utils(self.grafana_api_model).call_the_api(search_query) diff --git a/src/grafana_api/folder.py b/src/grafana_api/folder.py index 0138cc8..bedbb9b 100644 --- a/src/grafana_api/folder.py +++ b/src/grafana_api/folder.py @@ -11,11 +11,11 @@ # DELETE /api/folders/:uid 14.01 # GET /api/folders/id/:id 14.01 + # https://grafana.com/docs/grafana/latest/http_api/folder_permissions/ # GET /api/folders/:uid/permissions 14.01 # POST /api/folders/:uid/permissions 14.01 class Folder: - def __init__(self, grafana_api_model: APIModel): self.grafana_api_model = grafana_api_model @@ -40,7 +40,9 @@ def get_folder_id_by_dashboard_path(self) -> int: def get_all_folder_ids_and_names(self) -> list: """The method extract all folder id and names inside the complete organisation""" - folders_raw: list = Utils(self.grafana_api_model).call_the_api(f"{APIEndpoints.SEARCH.value}?folderIds=0") + folders_raw: list = Utils(self.grafana_api_model).call_the_api( + f"{APIEndpoints.SEARCH.value}?folderIds=0" + ) folders_raw_len: int = len(folders_raw) folders: list = list() diff --git a/src/grafana_api/search.py b/src/grafana_api/search.py index f93f188..069eceb 100644 --- a/src/grafana_api/search.py +++ b/src/grafana_api/search.py @@ -3,7 +3,6 @@ class Search: - def __init__(self, grafana_api_model: APIModel): self.grafana_api_model = grafana_api_model @@ -19,4 +18,3 @@ def search(self, search_query: str) -> list: raise Exception else: return result - diff --git a/src/grafana_api/utils.py b/src/grafana_api/utils.py index 92d7534..2a68ce9 100644 --- a/src/grafana_api/utils.py +++ b/src/grafana_api/utils.py @@ -5,15 +5,14 @@ class Utils: - def __init__(self, grafana_api_model: APIModel): self.grafana_api_model = grafana_api_model def call_the_api( - self, - api_call: str, - method: RequestsMethods = RequestsMethods.GET, - json_complete: str = None, + self, + api_call: str, + method: RequestsMethods = RequestsMethods.GET, + json_complete: str = None, ) -> any: """The method execute a defined API call against the Grafana endpoints @@ -31,17 +30,23 @@ def call_the_api( } try: if method.value == RequestsMethods.GET.value: - return Utils.__check_the_api_call_response(requests.get(api_url, headers=headers).json()) + return Utils.__check_the_api_call_response( + requests.get(api_url, headers=headers).json() + ) elif method.value == RequestsMethods.POST.value: if json_complete is not None: - return Utils.__check_the_api_call_response(requests.post( - api_url, data=json_complete, headers=headers - ).json()) + return Utils.__check_the_api_call_response( + requests.post( + api_url, data=json_complete, headers=headers + ).json() + ) else: logging.error("Please define the dashboard_json_complete.") raise Exception elif method.value == RequestsMethods.DELETE.value: - return Utils.__check_the_api_call_response(requests.delete(api_url, headers=headers).json()) + return Utils.__check_the_api_call_response( + requests.delete(api_url, headers=headers).json() + ) except Exception as e: raise e diff --git a/tests/integrationtest/test_dashboard.py b/tests/integrationtest/test_dashboard.py index efe74d9..aa2798e 100644 --- a/tests/integrationtest/test_dashboard.py +++ b/tests/integrationtest/test_dashboard.py @@ -21,7 +21,9 @@ class DashboardTest(TestCase): folder: Folder = Folder(model) def test_dashboard_creation(self): - with open(f"{os.getcwd()}tests{os.sep}integrationtest{os.sep}resources{os.sep}dashboard.json") as file: + with open( + f"{os.getcwd()}tests{os.sep}integrationtest{os.sep}resources{os.sep}dashboard.json" + ) as file: json_dashboard = json.load(file) self.dashboard.create_or_update_dashboard( @@ -41,7 +43,9 @@ def test_wrong_token(self): self.dashboard.delete_dashboard_by_name_and_path() def test_get_dashboard(self): - with open(f"{os.getcwd()}tests{os.sep}integrationtest{os.sep}resources{os.sep}dashboard.json") as file: + with open( + f"{os.getcwd()}tests{os.sep}integrationtest{os.sep}resources{os.sep}dashboard.json" + ) as file: json_dashboard = json.load(file) self.dashboard.create_or_update_dashboard( diff --git a/tests/unittests/test_dashboard.py b/tests/unittests/test_dashboard.py index 79ad813..3b18eba 100644 --- a/tests/unittests/test_dashboard.py +++ b/tests/unittests/test_dashboard.py @@ -85,19 +85,17 @@ def test_get_dashboard_uid_by_name_and_folder( self.assertEqual(10, dashboard.get_dashboard_uid_by_name_and_folder()) @patch("src.grafana_api.utils.Utils.call_the_api") - def test_get_dashboard_by_uid( - self, call_the_api_mock - ): + def test_get_dashboard_by_uid(self, call_the_api_mock): model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") dashboard: Dashboard = Dashboard(grafana_api_model=model) call_the_api_mock.return_value = dict({"dashboard": "test"}) - self.assertEqual(dict({"dashboard": "test"}), dashboard.get_dashboard_by_uid(uid="test")) + self.assertEqual( + dict({"dashboard": "test"}), dashboard.get_dashboard_by_uid(uid="test") + ) @patch("src.grafana_api.utils.Utils.call_the_api") - def test_get_dashboard_by_uid_no_dashboard( - self, call_the_api_mock - ): + def test_get_dashboard_by_uid_no_dashboard(self, call_the_api_mock): model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") dashboard: Dashboard = Dashboard(grafana_api_model=model) @@ -106,9 +104,7 @@ def test_get_dashboard_by_uid_no_dashboard( dashboard.get_dashboard_by_uid(uid="test") @patch("src.grafana_api.utils.Utils.call_the_api") - def test_get_dashboard_by_uid_no_uid( - self, call_the_api_mock - ): + def test_get_dashboard_by_uid_no_uid(self, call_the_api_mock): model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") dashboard: Dashboard = Dashboard(grafana_api_model=model) @@ -117,9 +113,7 @@ def test_get_dashboard_by_uid_no_uid( dashboard.get_dashboard_by_uid(uid="") @patch("src.grafana_api.utils.Utils.call_the_api") - def test_get_dashboard_home( - self, call_the_api_mock - ): + def test_get_dashboard_home(self, call_the_api_mock): model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") dashboard: Dashboard = Dashboard(grafana_api_model=model) @@ -127,9 +121,7 @@ def test_get_dashboard_home( self.assertEqual(dict({"dashboard": "test"}), dashboard.get_dashboard_home()) @patch("src.grafana_api.utils.Utils.call_the_api") - def test_get_dashboard_home_no_dashboard( - self, call_the_api_mock - ): + def test_get_dashboard_home_no_dashboard(self, call_the_api_mock): model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") dashboard: Dashboard = Dashboard(grafana_api_model=model) @@ -138,19 +130,17 @@ def test_get_dashboard_home_no_dashboard( dashboard.get_dashboard_home() @patch("src.grafana_api.utils.Utils.call_the_api") - def test_get_dashboard_tags( - self, call_the_api_mock - ): + def test_get_dashboard_tags(self, call_the_api_mock): model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") dashboard: Dashboard = Dashboard(grafana_api_model=model) call_the_api_mock.return_value = list([{"term": "test", "count": 4}]) - self.assertEqual(list([{"term": "test", "count": 4}]), dashboard.get_dashboard_tags()) + self.assertEqual( + list([{"term": "test", "count": 4}]), dashboard.get_dashboard_tags() + ) @patch("src.grafana_api.utils.Utils.call_the_api") - def test_get_dashboard_tags_no_tags( - self, call_the_api_mock - ): + def test_get_dashboard_tags_no_tags(self, call_the_api_mock): model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") dashboard: Dashboard = Dashboard(grafana_api_model=model) @@ -159,19 +149,18 @@ def test_get_dashboard_tags_no_tags( dashboard.get_dashboard_tags() @patch("src.grafana_api.utils.Utils.call_the_api") - def test_get_dashboard_permissions( - self, call_the_api_mock - ): + def test_get_dashboard_permissions(self, call_the_api_mock): model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") dashboard: Dashboard = Dashboard(grafana_api_model=model) call_the_api_mock.return_value = list([{"role": "test", "count": 4}]) - self.assertEqual(list([{"role": "test", "count": 4}]), dashboard.get_dashboard_permissions("test")) + self.assertEqual( + list([{"role": "test", "count": 4}]), + dashboard.get_dashboard_permissions("test"), + ) @patch("src.grafana_api.utils.Utils.call_the_api") - def test_get_dashboard_permissions_empty_list( - self, call_the_api_mock - ): + def test_get_dashboard_permissions_empty_list(self, call_the_api_mock): model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") dashboard: Dashboard = Dashboard(grafana_api_model=model) @@ -180,9 +169,7 @@ def test_get_dashboard_permissions_empty_list( dashboard.get_dashboard_permissions("test") @patch("src.grafana_api.utils.Utils.call_the_api") - def test_get_dashboard_permissions_no_uid( - self, call_the_api_mock - ): + def test_get_dashboard_permissions_no_uid(self, call_the_api_mock): model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") dashboard: Dashboard = Dashboard(grafana_api_model=model) @@ -191,19 +178,19 @@ def test_get_dashboard_permissions_no_uid( dashboard.get_dashboard_permissions("") @patch("src.grafana_api.utils.Utils.call_the_api") - def test_update_dashboard_permissions( - self, call_the_api_mock - ): + def test_update_dashboard_permissions(self, call_the_api_mock): model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") dashboard: Dashboard = Dashboard(grafana_api_model=model) - call_the_api_mock.return_value = dict({"message": "Dashboard permissions updated"}) - self.assertEqual(None, dashboard.update_dashboard_permissions("test", {"test": "test"})) + call_the_api_mock.return_value = dict( + {"message": "Dashboard permissions updated"} + ) + self.assertEqual( + None, dashboard.update_dashboard_permissions("test", {"test": "test"}) + ) @patch("src.grafana_api.utils.Utils.call_the_api") - def test_update_dashboard_permissions_error_response( - self, call_the_api_mock - ): + def test_update_dashboard_permissions_error_response(self, call_the_api_mock): model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") dashboard: Dashboard = Dashboard(grafana_api_model=model) @@ -212,9 +199,7 @@ def test_update_dashboard_permissions_error_response( dashboard.update_dashboard_permissions("test", {"test": "test"}) @patch("src.grafana_api.utils.Utils.call_the_api") - def test_update_dashboard_permissions_no_uid( - self, call_the_api_mock - ): + def test_update_dashboard_permissions_no_uid(self, call_the_api_mock): model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") dashboard: Dashboard = Dashboard(grafana_api_model=model) diff --git a/tests/unittests/test_folder.py b/tests/unittests/test_folder.py index 63dc3f8..03dd861 100644 --- a/tests/unittests/test_folder.py +++ b/tests/unittests/test_folder.py @@ -16,7 +16,7 @@ def test_get_folder_id_by_dashboard_path(self, all_folder_ids_and_names_mock): @patch("src.grafana_api.folder.Folder.get_all_folder_ids_and_names") def test_get_folder_id_by_dashboard_path_no_title_match( - self, all_folder_ids_and_names_mock + self, all_folder_ids_and_names_mock ): model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") folder: Folder = Folder(grafana_api_model=model) @@ -33,4 +33,4 @@ def test_get_all_folder_ids_and_names(self, call_the_api_mock): call_the_api_mock.return_value = [{"title": "test", "id": 12, "test": "test"}] self.assertEqual( [{"title": "test", "id": 12}], folder.get_all_folder_ids_and_names() - ) \ No newline at end of file + ) diff --git a/tests/unittests/test_search.py b/tests/unittests/test_search.py index bfb0da0..a15e4cf 100644 --- a/tests/unittests/test_search.py +++ b/tests/unittests/test_search.py @@ -6,7 +6,6 @@ class SearchTestCase(TestCase): - @patch("src.grafana_api.utils.Utils.call_the_api") def test_search(self, call_the_api_mock): model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") diff --git a/tests/unittests/test_utils.py b/tests/unittests/test_utils.py index 50340ba..440a0bc 100644 --- a/tests/unittests/test_utils.py +++ b/tests/unittests/test_utils.py @@ -10,48 +10,45 @@ class UtilsTestCase(TestCase): - def test_call_the_api_non_method(self): model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") utils: Utils = Utils(grafana_api_model=model) - + with self.assertRaises(Exception): utils.call_the_api(api_call=MagicMock(), method=None) - + @patch("requests.get") def test_call_the_api_get_valid(self, get_mock): model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") utils: Utils = Utils(grafana_api_model=model) - + mock: Mock = Mock() mock.json = Mock(return_value={"status": "success"}) - + get_mock.return_value = mock - + self.assertEqual( "success", utils.call_the_api(api_call=MagicMock())["status"], ) - + def test_call_the_api_get_not_valid(self): model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") utils: Utils = Utils(grafana_api_model=model) - + with self.assertRaises(MissingSchema): - utils.call_the_api( - api_call=MagicMock(), method=RequestsMethods.GET - ) - + utils.call_the_api(api_call=MagicMock(), method=RequestsMethods.GET) + @patch("requests.post") def test_call_the_api_post_valid(self, post_mock): model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") utils: Utils = Utils(grafana_api_model=model) - + mock: Mock = Mock() mock.json = Mock(return_value={"status": "success"}) - + post_mock.return_value = mock - + self.assertEqual( "success", utils.call_the_api( @@ -60,11 +57,11 @@ def test_call_the_api_post_valid(self, post_mock): json_complete=MagicMock(), )["status"], ) - + def test_call_the_api_post_not_valid(self): model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") utils: Utils = Utils(grafana_api_model=model) - + with self.assertRaises(MissingSchema): utils.call_the_api( api_call=MagicMock(), @@ -75,41 +72,39 @@ def test_call_the_api_post_not_valid(self): def test_call_the_api_post_no_data(self): model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") utils: Utils = Utils(grafana_api_model=model) - + with self.assertRaises(Exception): - utils.call_the_api( - api_call=MagicMock(), method=RequestsMethods.POST - ) - + utils.call_the_api(api_call=MagicMock(), method=RequestsMethods.POST) + @patch("requests.delete") def test_call_the_api_delete_valid(self, delete_mock): model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") utils: Utils = Utils(grafana_api_model=model) - + mock: Mock = Mock() mock.json = Mock(return_value={"message": "Deletion successful"}) - + delete_mock.return_value = mock - + self.assertEqual( "Deletion successful", - utils.call_the_api( - api_call=MagicMock(), method=RequestsMethods.DELETE - )["message"], + utils.call_the_api(api_call=MagicMock(), method=RequestsMethods.DELETE)[ + "message" + ], ) - + def test_call_the_api_delete_not_valid(self): model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") utils: Utils = Utils(grafana_api_model=model) - + with self.assertRaises(Exception): - utils.call_the_api( - api_call=MagicMock(), method=RequestsMethods.DELETE - ) - + utils.call_the_api(api_call=MagicMock(), method=RequestsMethods.DELETE) + def test_check_the_api_call_response(self): model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") utils: Utils = Utils(grafana_api_model=model) - + with self.assertRaises(requests.exceptions.ConnectionError): - utils._Utils__check_the_api_call_response(response=dict({"message": "invalid API key"})) + utils._Utils__check_the_api_call_response( + response=dict({"message": "invalid API key"}) + ) From 58055704f2f8d974197aa2ef2661114304b5ef47 Mon Sep 17 00:00:00 2001 From: Pascal Zimmermann Date: Fri, 14 Jan 2022 22:41:13 +0100 Subject: [PATCH 08/19] Add new functions and the corresponding tests --- README.md | 2 + src/grafana_api/dashboard.py | 153 +++++++++++++++++---- src/grafana_api/folder.py | 215 ++++++++++++++++++++++++++--- src/grafana_api/model.py | 2 + src/grafana_api/utils.py | 56 +++++++- tests/unittests/test_dashboard.py | 156 +++++++++++++++++---- tests/unittests/test_folder.py | 216 +++++++++++++++++++++++++++++- tests/unittests/test_utils.py | 79 +++++++++++ 8 files changed, 805 insertions(+), 74 deletions(-) diff --git a/README.md b/README.md index 56c88f4..bd92f92 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,8 @@ The repository includes an SDK for the Grafana API - Create/ Update a dashboard - Delete a dashboard +### Feature timeline + ## Installation & Requirements ### Programs & tools to install diff --git a/src/grafana_api/dashboard.py b/src/grafana_api/dashboard.py index 8dc93c6..4731bca 100644 --- a/src/grafana_api/dashboard.py +++ b/src/grafana_api/dashboard.py @@ -6,11 +6,6 @@ from .utils import Utils -# https://grafana.com/docs/grafana/latest/http_api/dashboard_versions/ -# GET /api/dashboards/id/:dashboardId/versions 12.01 -# GET /api/dashboards/id/:dashboardId/versions/:id 12.01 -# POST /api/dashboards/id/:dashboardId/restore 12.01 -# POST /api/dashboards/calculate-diff 12.01 class Dashboard: """The class includes all necessary methods to access the Grafana dashboard API endpoints @@ -46,9 +41,7 @@ def create_or_update_dashboard(self, dashboard_json: dict, overwrite: bool = Fal json.dumps(dashboard_json_complete), ) - status: str = api_call["status"] - - if status != "success": + if api_call.get("status") != "success": logging.error(f"Check the error: {api_call}.") raise Exception else: @@ -57,17 +50,15 @@ def create_or_update_dashboard(self, dashboard_json: dict, overwrite: bool = Fal def delete_dashboard_by_name_and_path(self): """The method includes a functionality to delete the specified dashboard inside the model""" - dashboard_uid: str = self.get_dashboard_uid_by_name_and_folder() + dashboard_uid: dict = self.get_dashboard_uid_and_id_by_name_and_folder() if len(dashboard_uid) != 0: api_call: dict = Utils(self.grafana_api_model).call_the_api( - f"{APIEndpoints.DASHBOARDS.value}/uid/{dashboard_uid}", + f"{APIEndpoints.DASHBOARDS.value}/uid/{dashboard_uid.get('uid')}", RequestsMethods.DELETE, ) - message: str = api_call["message"] - - if f"Dashboard {self.grafana_api_model.dashboard_name} deleted" != message: + if f"Dashboard {self.grafana_api_model.dashboard_name} deleted" != api_call.get("message"): logging.error(f"Please, check the error: {api_call}.") raise Exception else: @@ -123,7 +114,7 @@ def get_dashboard_tags(self) -> list: else: return api_call - def get_dashboard_uid_by_name_and_folder(self) -> str: + def get_dashboard_uid_and_id_by_name_and_folder(self) -> dict: """The method includes a functionality to extract the dashboard uid specified inside the model""" folder_id: int = Folder( @@ -133,18 +124,18 @@ def get_dashboard_uid_by_name_and_folder(self) -> str: search_query: str = f"{APIEndpoints.SEARCH.value}?folderIds={folder_id}&query={self.grafana_api_model.dashboard_name}" dashboard_meta: list = Utils(self.grafana_api_model).call_the_api(search_query) - return dashboard_meta[0]["uid"] + return dict({"uid": dashboard_meta[0]["uid"], "id": dashboard_meta[0]["id"]}) - def get_dashboard_permissions(self, uid: str): - """The method includes a functionality to extract the dashboard uid specified inside the model + def get_dashboard_permissions(self, id: int) -> list: + """The method includes a functionality to extract the dashboard permissions based on the specified id Keyword arguments: - uid -> Specify the uid of the dashboard + id -> Specify the id of the dashboard """ - if len(uid) != 0: + if id != 0: api_call: list = Utils(self.grafana_api_model).call_the_api( - f"{APIEndpoints.DASHBOARDS.value}/id/{uid}/permissions" + f"{APIEndpoints.DASHBOARDS.value}/id/{id}/permissions" ) if api_call == list() or api_call[0].get("role") is None: @@ -156,24 +147,23 @@ def get_dashboard_permissions(self, uid: str): logging.info("There is no dashboard uid defined.") raise ValueError - def update_dashboard_permissions(self, uid: str, permission_json: dict): - """The method includes a functionality to extract the dashboard uid specified inside the model + def update_dashboard_permissions(self, id: int, permission_json: dict): + """The method includes a functionality to update the dashboard permissions based on the specified id + and the permission json document Keyword arguments: - uid -> Specify the uid of the dashboard + id -> Specify the id of the dashboard permission_json -> Specify the inserted permissions as dict """ - if len(uid) != 0 and len(permission_json) != 0: + if id != 0 and len(permission_json) != 0: api_call: dict = Utils(self.grafana_api_model).call_the_api( - f"{APIEndpoints.DASHBOARDS.value}/id/{uid}/permissions", + f"{APIEndpoints.DASHBOARDS.value}/id/{id}/permissions", RequestsMethods.POST, json.dumps(permission_json), ) - status: str = api_call["message"] - - if status != "Dashboard permissions updated": + if api_call.get("message") != "Dashboard permissions updated": logging.error(f"Please, check the error: {api_call}.") raise Exception else: @@ -181,3 +171,110 @@ def update_dashboard_permissions(self, uid: str, permission_json: dict): else: logging.info("There is no dashboard uid or permission json defined.") raise ValueError + + def get_dashboard_versions(self, id: int) -> list: + """The method includes a functionality to extract the versions of a dashboard based on the specified id + + Keyword arguments: + id -> Specify the id of the dashboard + """ + + if id != 0: + api_call: list = Utils(self.grafana_api_model).call_the_api( + f"{APIEndpoints.DASHBOARDS.value}/id/{id}/versions", + ) + + if api_call == list() or api_call[0].get("id") is None: + logging.error(f"Please, check the error: {api_call}.") + raise Exception + else: + return api_call + else: + logging.info("There is no dashboard uid defined.") + raise ValueError + + def get_dashboard_version(self, id: int, version_id: int) -> dict: + """The method includes a functionality to extract a specified version of a dashboard based on the specified \ + dashboard id and a version_id of the dashboard + + Keyword arguments: + id -> Specify the id of the dashboard + version_id -> Specify the version_id of a dashboard + """ + + if id != 0 and version_id != 0: + api_call: dict = Utils(self.grafana_api_model).call_the_api( + f"{APIEndpoints.DASHBOARDS.value}/id/{id}/versions/{version_id}", + ) + + if api_call == dict() or api_call.get("id") is None: + logging.error(f"Please, check the error: {api_call}.") + raise Exception + else: + return api_call + else: + logging.info("There is no dashboard uid or version_id defined.") + raise ValueError + + def restore_dashboard_version(self, id: int, version: dict): + """The method includes a functionality to restore a specified version of a dashboard based on the specified \ + dashboard uid and a version as dict of the dashboard + + Keyword arguments: + uid -> Specify the id of the dashboard + version -> Specify the version_id of a dashboard + """ + + if id != 0 and version != dict(): + api_call: dict = Utils(self.grafana_api_model).call_the_api( + f"{APIEndpoints.DASHBOARDS.value}/id/{id}/restore", + RequestsMethods.POST, + json.dumps(version), + ) + + if api_call.get("status") != "success" or api_call.get("message") is not None: + logging.error(f"Check the error: {api_call}.") + raise Exception + else: + logging.info("You successfully restored the dashboard.") + else: + logging.info("There is no dashboard uid or version_id defined.") + raise ValueError + + def calculate_dashboard_diff(self, dashboard_id_and_version_base: dict, dashboard_id_and_version_new: dict, + diff_type: str = "json") -> str: + """The method includes a functionality to calculate the diff of specified versions of a dashboard based on the \ + specified dashboard uid and the selected version of the base dashboard and the new dashboard and the diff \ + type (basic or json) + + Keyword arguments: + dashboard_id_and_version_base -> Specify the version and id of the base dashboard + dashboard_id_and_version_new -> Specify the version and id of the new dashboard + diff_type -> Specify the diff type (basic or json the default is json) + """ + possible_diff_types: list = list(["basic", "json"]) + + if diff_type.lower() in possible_diff_types: + if dashboard_id_and_version_base != dict() and dashboard_id_and_version_new != 0: + diff_object: dict = dict() + diff_object.update(dashboard_id_and_version_base) + diff_object.update(dashboard_id_and_version_new) + diff_object.update({"diffType": diff_type.lower()}) + + api_call: any = Utils(self.grafana_api_model).call_the_api_non_json_output( + f"{APIEndpoints.DASHBOARDS.value}/calculate-diff", + RequestsMethods.POST, + json.dumps(diff_object), + ) + + if api_call.status_code != 200: + logging.error(f"Check the error: {api_call.text}.") + raise Exception + else: + return api_call.text + else: + logging.info("There is no dashboard_uid_and_version_base or dashboard_uid_and_version_new defined.") + raise ValueError + else: + logging.info(f"The diff_type: {diff_type.lower()} is not valid. Please specify a valid value.") + raise ValueError diff --git a/src/grafana_api/folder.py b/src/grafana_api/folder.py index bedbb9b..b44190a 100644 --- a/src/grafana_api/folder.py +++ b/src/grafana_api/folder.py @@ -1,24 +1,209 @@ import logging +import json from .utils import Utils -from .model import APIModel, APIEndpoints +from .model import APIModel, APIEndpoints, RequestsMethods -# https://grafana.com/docs/grafana/latest/http_api/dashboard_versions/ -# GET /api/folders 13.01 -# GET /api/folders/:uid 13.01 -# POST /api/folders 13.01 -# PUT /api/folders/:uid 13.01 -# DELETE /api/folders/:uid 14.01 -# GET /api/folders/id/:id 14.01 - -# https://grafana.com/docs/grafana/latest/http_api/folder_permissions/ -# GET /api/folders/:uid/permissions 14.01 -# POST /api/folders/:uid/permissions 14.01 class Folder: def __init__(self, grafana_api_model: APIModel): self.grafana_api_model = grafana_api_model + def get_folders(self) -> list: + """The method includes a functionality to extract all folders inside the organization""" + + api_call: list = Utils(self.grafana_api_model).call_the_api( + APIEndpoints.FOLDERS.value + ) + + if api_call == list() or api_call[0].get("id") is None: + logging.error(f"Please, check the error: {api_call}.") + raise Exception + else: + return api_call + + def get_folder_by_uid(self, uid: str) -> dict: + """The method includes a functionality to extract all folder information specified by the uid of the folder + + Keyword arguments: + uid -> Specify the uid of the folder + """ + + if len(uid) != 0: + api_call: dict = Utils(self.grafana_api_model).call_the_api( + f"{APIEndpoints.FOLDERS.value}/{uid}" + ) + + if api_call == dict() or api_call.get("id") is None: + logging.error(f"Please, check the error: {api_call}.") + raise Exception + else: + return api_call + else: + logging.info("There is no dashboard uid defined.") + raise ValueError + + def get_folder_by_id(self, id: int) -> dict: + """The method includes a functionality to extract all folder information specified by the id of the folder + + Keyword arguments: + id -> Specify the id of the folder + """ + + if id != 0: + api_call: dict = Utils(self.grafana_api_model).call_the_api( + f"{APIEndpoints.FOLDERS.value}/id/{id}", + ) + + if api_call == dict() or api_call.get("id") is None: + logging.error(f"Please, check the error: {api_call}.") + raise Exception + else: + return api_call + else: + logging.info("There is no folder id defined.") + raise ValueError + + def create_folder(self, title: str, uid: str = None) -> dict: + """The method includes a functionality to create a new folder inside the organization specified by the \ + defined title and the optional uid + + Keyword arguments: + title -> Specify the title of the folder + uid -> Specify the uid of the folder (the default value is None) + """ + + if len(title) != 0: + folder_information: dict = dict() + folder_information.update({"title": title}) + if uid is None or len(uid) != 0: + folder_information.update({"uid": uid}) + + api_call: dict = Utils(self.grafana_api_model).call_the_api( + APIEndpoints.FOLDERS.value, + RequestsMethods.POST, + json.dumps(folder_information), + ) + + if api_call == dict() or api_call.get("id") is None: + logging.error(f"Please, check the error: {api_call}.") + raise Exception + else: + return api_call + else: + logging.info("There is no folder uid or title defined.") + raise ValueError + + def update_folder(self, uid: str, title: str, version: int = 0, overwrite: bool = False) -> dict: + """The method includes a functionality to update a folder information inside the organization specified \ + by the uid, the title, the version of the folder or if folder information be overwritten + + Keyword arguments: + uid -> Specify the uid of the folder + title -> Specify the title of the folder + version -> Specify the version of the folder + overwrite -> Should the already existing folder information be overwritten + """ + + if overwrite is True: + version = None + + if len(title) != 0 and version != 0: + folder_information: dict = dict() + folder_information.update({"title": title}) + if len(uid) != 0: + folder_information.update({"uid": uid}) + folder_information.update({"overwrite": overwrite}) + + if version is not None: + folder_information.update({"version": version}) + + api_call: dict = Utils(self.grafana_api_model).call_the_api( + f"{APIEndpoints.FOLDERS.value}/{uid}", + RequestsMethods.PUT, + json.dumps(folder_information), + ) + + if api_call == dict() or api_call.get("id") is None: + logging.error(f"Please, check the error: {api_call}.") + raise Exception + else: + return api_call + else: + logging.info("There is no folder title or version defined.") + raise ValueError + + def delete_folder(self, uid: str): + """The method includes a functionality to delete a folder inside the organization specified by the \ + defined uid + + Keyword arguments: + uid -> Specify the uid of the folder + """ + + if len(uid) != 0: + api_call: dict = Utils(self.grafana_api_model).call_the_api( + f"{APIEndpoints.FOLDERS.value}/{uid}", + RequestsMethods.DELETE, + ) + + if "Folder deleted" != api_call.get("message"): + logging.error(f"Please, check the error: {api_call}.") + raise Exception + else: + logging.info("You successfully destroyed the folder.") + else: + logging.info("There is no folder uid defined.") + raise ValueError + + def get_folder_permissions(self, uid: str) -> list: + """The method includes a functionality to extract the folder permissions inside the organization specified by \ + the defined uid + + Keyword arguments: + uid -> Specify the uid of the folder + """ + + if len(uid) != 0: + api_call: list = Utils(self.grafana_api_model).call_the_api( + f"{APIEndpoints.FOLDERS.value}/{uid}/permissions", + RequestsMethods.GET, + ) + + if api_call == list() or api_call[0].get("id") is None: + logging.error(f"Please, check the error: {api_call}.") + raise Exception + else: + return api_call + else: + logging.info("There is no folder uid defined.") + raise ValueError + + def update_folder_permissions(self, uid: str, permission_json: dict): + """The method includes a functionality to update the folder permissions based on the specified uid \ + and the permission json document + + Keyword arguments: + uid -> Specify the uid of the folder + permission_json -> Specify the inserted permissions as dict + """ + + if len(uid) != 0 and len(permission_json) != 0: + api_call: dict = Utils(self.grafana_api_model).call_the_api( + f"{APIEndpoints.FOLDERS.value}/{uid}/permissions", + RequestsMethods.POST, + json.dumps(permission_json), + ) + + if api_call.get("message") != "Folder permissions updated": + logging.error(f"Please, check the error: {api_call}.") + raise Exception + else: + logging.info("You successfully modified the folder permissions.") + else: + logging.info("There is no folder uid or permission json defined.") + raise ValueError + def get_folder_id_by_dashboard_path(self) -> int: """The method includes a functionality to extract the folder id specified inside model dashboard path""" @@ -26,8 +211,8 @@ def get_folder_id_by_dashboard_path(self) -> int: folder_id: int = 0 for f in folders: - if self.grafana_api_model.dashboard_path == f["title"]: - folder_id = f["id"] + if self.grafana_api_model.dashboard_path == f.get("title"): + folder_id = f.get("id") if folder_id == 0: logging.error( @@ -48,7 +233,7 @@ def get_all_folder_ids_and_names(self) -> list: for i in range(0, folders_raw_len): folders.append( - {"title": folders_raw[i]["title"], "id": folders_raw[i]["id"]} + {"title": folders_raw[i].get("title"), "id": folders_raw[i].get("id")} ) return folders diff --git a/src/grafana_api/model.py b/src/grafana_api/model.py index 83fe7a7..3f2f714 100644 --- a/src/grafana_api/model.py +++ b/src/grafana_api/model.py @@ -13,6 +13,7 @@ class APIEndpoints(Enum): SEARCH = "/api/search" DASHBOARDS = "/api/dashboards" + FOLDERS = "/api/folders" class RequestsMethods(Enum): @@ -23,6 +24,7 @@ class RequestsMethods(Enum): """ GET = "GET" + PUT = "PUT" POST = "POST" DELETE = "DELETE" diff --git a/src/grafana_api/utils.py b/src/grafana_api/utils.py index 2a68ce9..4df412e 100644 --- a/src/grafana_api/utils.py +++ b/src/grafana_api/utils.py @@ -19,7 +19,7 @@ def call_the_api( Keyword arguments: api_call -> Specify the API call endpoint method -> Specify the used method - dashboard_json_complete -> Specify the inserted dashboard JSON as string + json_complete -> Specify the inserted JSON as string """ api_url: str = f"{self.grafana_api_model.host}{api_call}" @@ -33,6 +33,16 @@ def call_the_api( return Utils.__check_the_api_call_response( requests.get(api_url, headers=headers).json() ) + elif method.value == RequestsMethods.PUT.value: + if json_complete is not None: + return Utils.__check_the_api_call_response( + requests.post( + api_url, data=json_complete, headers=headers + ).json() + ) + else: + logging.error("Please define the json_complete.") + raise Exception elif method.value == RequestsMethods.POST.value: if json_complete is not None: return Utils.__check_the_api_call_response( @@ -41,7 +51,7 @@ def call_the_api( ).json() ) else: - logging.error("Please define the dashboard_json_complete.") + logging.error("Please define the json_complete.") raise Exception elif method.value == RequestsMethods.DELETE.value: return Utils.__check_the_api_call_response( @@ -50,6 +60,48 @@ def call_the_api( except Exception as e: raise e + def call_the_api_non_json_output( + self, + api_call: str, + method: RequestsMethods = RequestsMethods.GET, + json_complete: str = None, + ) -> any: + """The method execute a defined API call against the Grafana endpoints + + Keyword arguments: + api_call -> Specify the API call endpoint + method -> Specify the used method + json_complete -> Specify the inserted JSON as string + """ + + api_url: str = f"{self.grafana_api_model.host}{api_call}" + + headers: dict = { + "Authorization": f"Bearer {self.grafana_api_model.token}", + "Content-Type": "application/json", + } + try: + if method.value == RequestsMethods.GET.value: + return Utils.__check_the_api_call_response( + requests.get(api_url, headers=headers) + ) + elif method.value == RequestsMethods.POST.value: + if json_complete is not None: + return Utils.__check_the_api_call_response( + requests.post( + api_url, data=json_complete, headers=headers + ) + ) + else: + logging.error("Please define the json_complete.") + raise Exception + elif method.value == RequestsMethods.DELETE.value: + return Utils.__check_the_api_call_response( + requests.delete(api_url, headers=headers) + ) + except Exception as e: + raise e + @staticmethod def __check_the_api_call_response(response: any = None) -> any: """The method includes a functionality to check the output of API call method for errors diff --git a/tests/unittests/test_dashboard.py b/tests/unittests/test_dashboard.py index 3b18eba..301f010 100644 --- a/tests/unittests/test_dashboard.py +++ b/tests/unittests/test_dashboard.py @@ -35,54 +35,54 @@ def test_create_or_update_dashboard_update_not_possible( dashboard.create_or_update_dashboard(dashboard_json=dict({"test": "test"})) @patch("src.grafana_api.utils.Utils.call_the_api") - @patch("src.grafana_api.dashboard.Dashboard.get_dashboard_uid_by_name_and_folder") + @patch("src.grafana_api.dashboard.Dashboard.get_dashboard_uid_and_id_by_name_and_folder") def test_delete_dashboard_by_name_and_path( - self, dashboard_uid_by_name_and_folder_mock, call_the_api_mock + self, dashboard_uid_and_id_by_name_and_folder_mock, call_the_api_mock ): model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") dashboard: Dashboard = Dashboard(grafana_api_model=model) - dashboard_uid_by_name_and_folder_mock.return_value = "test" + dashboard_uid_and_id_by_name_and_folder_mock.return_value = dict({"uid": "test", "id": 10}) call_the_api_mock.return_value = dict({"message": "Dashboard None deleted"}) self.assertEqual(None, dashboard.delete_dashboard_by_name_and_path()) @patch("src.grafana_api.utils.Utils.call_the_api") - @patch("src.grafana_api.dashboard.Dashboard.get_dashboard_uid_by_name_and_folder") + @patch("src.grafana_api.dashboard.Dashboard.get_dashboard_uid_and_id_by_name_and_folder") def test_delete_dashboard_by_name_and_path_deletion_list_empty( - self, dashboard_uid_by_name_and_folder_mock, call_the_api_mock + self, dashboard_uid_and_id_by_name_and_folder_mock, call_the_api_mock ): model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") dashboard: Dashboard = Dashboard(grafana_api_model=model) - dashboard_uid_by_name_and_folder_mock.return_value = "" + dashboard_uid_and_id_by_name_and_folder_mock.return_value = dict() call_the_api_mock.return_value = dict({"message": "error"}) with self.assertRaises(ValueError): dashboard.delete_dashboard_by_name_and_path() @patch("src.grafana_api.utils.Utils.call_the_api") - @patch("src.grafana_api.dashboard.Dashboard.get_dashboard_uid_by_name_and_folder") + @patch("src.grafana_api.dashboard.Dashboard.get_dashboard_uid_and_id_by_name_and_folder") def test_delete_dashboard_by_name_and_path_deletion_not_possible( - self, dashboard_uid_by_name_and_folder_mock, call_the_api_mock + self, dashboard_uid_and_id_by_name_and_folder_mock, call_the_api_mock ): model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") dashboard: Dashboard = Dashboard(grafana_api_model=model) - dashboard_uid_by_name_and_folder_mock.return_value = "test" + dashboard_uid_and_id_by_name_and_folder_mock.return_value = dict({"uid": "test", "id": 10}) call_the_api_mock.return_value = dict({"message": "error"}) with self.assertRaises(Exception): dashboard.delete_dashboard_by_name_and_path() @patch("src.grafana_api.utils.Utils.call_the_api") @patch("src.grafana_api.folder.Folder.get_folder_id_by_dashboard_path") - def test_get_dashboard_uid_by_name_and_folder( + def test_get_dashboard_uid_and_id_by_name_and_folder( self, folder_id_by_dashboard_path_mock, call_the_api_mock ): model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") dashboard: Dashboard = Dashboard(grafana_api_model=model) folder_id_by_dashboard_path_mock.return_value = 1 - call_the_api_mock.return_value = [{"uid": 10}] - self.assertEqual(10, dashboard.get_dashboard_uid_by_name_and_folder()) + call_the_api_mock.return_value = [{"uid": "test", "id": 10}] + self.assertEqual(dict({"uid": "test", "id": 10}), dashboard.get_dashboard_uid_and_id_by_name_and_folder()) @patch("src.grafana_api.utils.Utils.call_the_api") def test_get_dashboard_by_uid(self, call_the_api_mock): @@ -103,12 +103,10 @@ def test_get_dashboard_by_uid_no_dashboard(self, call_the_api_mock): with self.assertRaises(Exception): dashboard.get_dashboard_by_uid(uid="test") - @patch("src.grafana_api.utils.Utils.call_the_api") - def test_get_dashboard_by_uid_no_uid(self, call_the_api_mock): + def test_get_dashboard_by_uid_no_uid(self): model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") dashboard: Dashboard = Dashboard(grafana_api_model=model) - call_the_api_mock.return_value = dict({"dashboard": "test"}) with self.assertRaises(ValueError): dashboard.get_dashboard_by_uid(uid="") @@ -156,7 +154,7 @@ def test_get_dashboard_permissions(self, call_the_api_mock): call_the_api_mock.return_value = list([{"role": "test", "count": 4}]) self.assertEqual( list([{"role": "test", "count": 4}]), - dashboard.get_dashboard_permissions("test"), + dashboard.get_dashboard_permissions(1), ) @patch("src.grafana_api.utils.Utils.call_the_api") @@ -166,16 +164,14 @@ def test_get_dashboard_permissions_empty_list(self, call_the_api_mock): call_the_api_mock.return_value = list() with self.assertRaises(Exception): - dashboard.get_dashboard_permissions("test") + dashboard.get_dashboard_permissions(1) - @patch("src.grafana_api.utils.Utils.call_the_api") - def test_get_dashboard_permissions_no_uid(self, call_the_api_mock): + def test_get_dashboard_permissions_no_id(self): model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") dashboard: Dashboard = Dashboard(grafana_api_model=model) - call_the_api_mock.return_value = list() with self.assertRaises(ValueError): - dashboard.get_dashboard_permissions("") + dashboard.get_dashboard_permissions(0) @patch("src.grafana_api.utils.Utils.call_the_api") def test_update_dashboard_permissions(self, call_the_api_mock): @@ -186,7 +182,7 @@ def test_update_dashboard_permissions(self, call_the_api_mock): {"message": "Dashboard permissions updated"} ) self.assertEqual( - None, dashboard.update_dashboard_permissions("test", {"test": "test"}) + None, dashboard.update_dashboard_permissions(1, {"test": "test"}) ) @patch("src.grafana_api.utils.Utils.call_the_api") @@ -196,16 +192,126 @@ def test_update_dashboard_permissions_error_response(self, call_the_api_mock): call_the_api_mock.return_value = dict({"message": "Error"}) with self.assertRaises(Exception): - dashboard.update_dashboard_permissions("test", {"test": "test"}) + dashboard.update_dashboard_permissions(1, {"test": "test"}) + + def test_update_dashboard_permissions_no_uid(self): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + dashboard: Dashboard = Dashboard(grafana_api_model=model) + + with self.assertRaises(ValueError): + dashboard.update_dashboard_permissions(0, MagicMock()) @patch("src.grafana_api.utils.Utils.call_the_api") - def test_update_dashboard_permissions_no_uid(self, call_the_api_mock): + def test_get_dashboard_versions(self, call_the_api_mock): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + dashboard: Dashboard = Dashboard(grafana_api_model=model) + + call_the_api_mock.return_value = list([{"id": "test"}]) + self.assertEqual( + list([{"id": "test"}]), dashboard.get_dashboard_versions(1) + ) + + @patch("src.grafana_api.utils.Utils.call_the_api") + def test_get_dashboard_versions_error_response(self, call_the_api_mock): model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") dashboard: Dashboard = Dashboard(grafana_api_model=model) call_the_api_mock.return_value = list() + with self.assertRaises(Exception): + dashboard.get_dashboard_versions(1) + + def test_get_dashboard_versions_no_uid(self): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + dashboard: Dashboard = Dashboard(grafana_api_model=model) + + with self.assertRaises(ValueError): + dashboard.get_dashboard_versions(0) + + @patch("src.grafana_api.utils.Utils.call_the_api") + def test_get_dashboard_version(self, call_the_api_mock): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + dashboard: Dashboard = Dashboard(grafana_api_model=model) + + call_the_api_mock.return_value = dict({"id": "test"}) + self.assertEqual( + dict({"id": "test"}), dashboard.get_dashboard_version(1, 10) + ) + + @patch("src.grafana_api.utils.Utils.call_the_api") + def test_get_dashboard_version_error_response(self, call_the_api_mock): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + dashboard: Dashboard = Dashboard(grafana_api_model=model) + + call_the_api_mock.return_value = dict() + with self.assertRaises(Exception): + dashboard.get_dashboard_version(1, MagicMock()) + + def test_get_dashboard_version_no_uid(self): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + dashboard: Dashboard = Dashboard(grafana_api_model=model) + + with self.assertRaises(ValueError): + dashboard.get_dashboard_version(0, MagicMock()) + + @patch("src.grafana_api.utils.Utils.call_the_api") + def test_restore_dashboard_version(self, call_the_api_mock): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + dashboard: Dashboard = Dashboard(grafana_api_model=model) + + call_the_api_mock.return_value = dict({"status": "success"}) + self.assertEqual( + None, dashboard.restore_dashboard_version(1, dict({"version": 1})) + ) + + @patch("src.grafana_api.utils.Utils.call_the_api") + def test_restore_dashboard_version_error_response(self, call_the_api_mock): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + dashboard: Dashboard = Dashboard(grafana_api_model=model) + + call_the_api_mock.return_value = dict({"status": "error"}) + with self.assertRaises(Exception): + dashboard.restore_dashboard_version(1, dict({"version": 1})) + + def test_restore_dashboard_version_no_uid(self): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + dashboard: Dashboard = Dashboard(grafana_api_model=model) + + with self.assertRaises(ValueError): + dashboard.restore_dashboard_version(0, MagicMock()) + + @patch("src.grafana_api.utils.Utils.call_the_api_non_json_output") + def test_calculate_dashboard_diff(self, call_the_api_non_json_output_mock): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + dashboard: Dashboard = Dashboard(grafana_api_model=model) + + call_the_api_non_json_output_mock.return_value.status_code = 200 + call_the_api_non_json_output_mock.return_value.text = "test" + self.assertEqual( + "test", dashboard.calculate_dashboard_diff(dict({"dashboardId": 1, "version": 1}), dict({"dashboardId": 2, "version": 1})) + ) + + @patch("src.grafana_api.utils.Utils.call_the_api_non_json_output") + def test_calculate_dashboard_diff_error_response(self, call_the_api_non_json_output_mock): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + dashboard: Dashboard = Dashboard(grafana_api_model=model) + + call_the_api_non_json_output_mock.status_code.return_value = 400 + with self.assertRaises(Exception): + dashboard.calculate_dashboard_diff(dict({"dashboardId": 1, "version": 1}), dict({"dashboardId": 2, "version": 1})) + + def test_calculate_dashboard_diff_no_dashboard_id_and_version_base(self): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + dashboard: Dashboard = Dashboard(grafana_api_model=model) + + with self.assertRaises(ValueError): + dashboard.calculate_dashboard_diff({}, MagicMock()) + + def test_calculate_dashboard_diff_no_valid_diff_type(self): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + dashboard: Dashboard = Dashboard(grafana_api_model=model) + with self.assertRaises(ValueError): - dashboard.update_dashboard_permissions("", MagicMock()) + dashboard.calculate_dashboard_diff({}, MagicMock(), "test") if __name__ == "__main__": diff --git a/tests/unittests/test_folder.py b/tests/unittests/test_folder.py index 03dd861..0adf929 100644 --- a/tests/unittests/test_folder.py +++ b/tests/unittests/test_folder.py @@ -6,12 +6,220 @@ class FolderTestCase(TestCase): + + @patch("src.grafana_api.utils.Utils.call_the_api") + def test_get_folders(self, call_the_api_mock): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + folder: Folder = Folder(grafana_api_model=model) + + call_the_api_mock.return_value = list([{"title": None, "id": 12}]) + self.assertEqual(list([{"title": None, "id": 12}]), folder.get_folders()) + + @patch("src.grafana_api.utils.Utils.call_the_api") + def test_get_folders_error_response(self, call_the_api_mock): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + folder: Folder = Folder(grafana_api_model=model) + + call_the_api_mock.return_value = list() + with self.assertRaises(Exception): + folder.get_folders() + + @patch("src.grafana_api.utils.Utils.call_the_api") + def test_get_folder_by_uid(self, call_the_api_mock): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + folder: Folder = Folder(grafana_api_model=model) + + call_the_api_mock.return_value = dict({"title": None, "id": 12}) + self.assertEqual(dict({"title": None, "id": 12}), folder.get_folder_by_uid("xty13y")) + + @patch("src.grafana_api.utils.Utils.call_the_api") + def test_get_folder_by_uid_no_uid(self, call_the_api_mock): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + folder: Folder = Folder(grafana_api_model=model) + + call_the_api_mock.return_value = dict() + with self.assertRaises(ValueError): + folder.get_folder_by_uid("") + + @patch("src.grafana_api.utils.Utils.call_the_api") + def test_get_folder_by_uid_error_response(self, call_the_api_mock): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + folder: Folder = Folder(grafana_api_model=model) + + call_the_api_mock.return_value = dict() + with self.assertRaises(Exception): + folder.get_folder_by_uid("xty13y") + + @patch("src.grafana_api.utils.Utils.call_the_api") + def test_get_folder_by_id(self, call_the_api_mock): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + folder: Folder = Folder(grafana_api_model=model) + + call_the_api_mock.return_value = dict({"title": None, "id": 12}) + self.assertEqual(dict({"title": None, "id": 12}), folder.get_folder_by_id(12)) + + @patch("src.grafana_api.utils.Utils.call_the_api") + def test_get_folder_by_id_no_id(self, call_the_api_mock): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + folder: Folder = Folder(grafana_api_model=model) + + call_the_api_mock.return_value = dict() + with self.assertRaises(ValueError): + folder.get_folder_by_id(0) + + @patch("src.grafana_api.utils.Utils.call_the_api") + def test_get_folder_by_id_error_response(self, call_the_api_mock): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + folder: Folder = Folder(grafana_api_model=model) + + call_the_api_mock.return_value = dict() + with self.assertRaises(Exception): + folder.get_folder_by_id(10) + + @patch("src.grafana_api.utils.Utils.call_the_api") + def test_create_folder(self, call_the_api_mock): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + folder: Folder = Folder(grafana_api_model=model) + + call_the_api_mock.return_value = dict({"title": None, "id": 12}) + self.assertEqual(dict({"title": None, "id": 12}), folder.create_folder("test", "test")) + + @patch("src.grafana_api.utils.Utils.call_the_api") + def test_create_folder_no_title(self, call_the_api_mock): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + folder: Folder = Folder(grafana_api_model=model) + + call_the_api_mock.return_value = dict() + with self.assertRaises(ValueError): + folder.create_folder(MagicMock()) + + @patch("src.grafana_api.utils.Utils.call_the_api") + def test_create_folder_error_response(self, call_the_api_mock): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + folder: Folder = Folder(grafana_api_model=model) + + call_the_api_mock.return_value = dict() + with self.assertRaises(Exception): + folder.create_folder("test") + + @patch("src.grafana_api.utils.Utils.call_the_api") + def test_update_folder(self, call_the_api_mock): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + folder: Folder = Folder(grafana_api_model=model) + + call_the_api_mock.return_value = dict({"title": None, "id": 12}) + self.assertEqual(dict({"title": None, "id": 12}), folder.update_folder("test", "test", 10)) + + @patch("src.grafana_api.utils.Utils.call_the_api") + def test_update_folder_overwrite_true(self, call_the_api_mock): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + folder: Folder = Folder(grafana_api_model=model) + + call_the_api_mock.return_value = dict({"title": None, "id": 12}) + self.assertEqual(dict({"title": None, "id": 12}), folder.update_folder("test", "test", overwrite=True)) + + @patch("src.grafana_api.utils.Utils.call_the_api") + def test_update_folder_no_title(self, call_the_api_mock): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + folder: Folder = Folder(grafana_api_model=model) + + call_the_api_mock.return_value = dict() + with self.assertRaises(ValueError): + folder.update_folder(MagicMock(), MagicMock()) + + @patch("src.grafana_api.utils.Utils.call_the_api") + def test_update_folder_error_response(self, call_the_api_mock): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + folder: Folder = Folder(grafana_api_model=model) + + call_the_api_mock.return_value = dict() + with self.assertRaises(Exception): + folder.update_folder("test", "test", 10) + + @patch("src.grafana_api.utils.Utils.call_the_api") + def test_delete_folder(self, call_the_api_mock): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + folder: Folder = Folder(grafana_api_model=model) + + call_the_api_mock.return_value = dict({"message": "Folder deleted"}) + self.assertEqual(None, folder.delete_folder("test")) + + @patch("src.grafana_api.utils.Utils.call_the_api") + def test_delete_folder_no_uid(self, call_the_api_mock): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + folder: Folder = Folder(grafana_api_model=model) + + call_the_api_mock.return_value = dict() + with self.assertRaises(ValueError): + folder.delete_folder("") + + @patch("src.grafana_api.utils.Utils.call_the_api") + def test_delete_folder_error_response(self, call_the_api_mock): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + folder: Folder = Folder(grafana_api_model=model) + + call_the_api_mock.return_value = dict({"message": "error"}) + with self.assertRaises(Exception): + folder.delete_folder("test") + + @patch("src.grafana_api.utils.Utils.call_the_api") + def test_get_folder_permissions(self, call_the_api_mock): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + folder: Folder = Folder(grafana_api_model=model) + + call_the_api_mock.return_value = list([{"id": "test"}]) + self.assertEqual(list([{"id": "test"}]), folder.get_folder_permissions("test")) + + @patch("src.grafana_api.utils.Utils.call_the_api") + def test_get_folder_permissions_no_uid(self, call_the_api_mock): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + folder: Folder = Folder(grafana_api_model=model) + + call_the_api_mock.return_value = list() + with self.assertRaises(ValueError): + folder.get_folder_permissions("") + + @patch("src.grafana_api.utils.Utils.call_the_api") + def test_get_folder_permissions_error_response(self, call_the_api_mock): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + folder: Folder = Folder(grafana_api_model=model) + + call_the_api_mock.return_value = list([{"test": "test"}]) + with self.assertRaises(Exception): + folder.get_folder_permissions("test") + + @patch("src.grafana_api.utils.Utils.call_the_api") + def test_update_folder_permissions(self, call_the_api_mock): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + folder: Folder = Folder(grafana_api_model=model) + + call_the_api_mock.return_value = dict({"message": "Folder permissions updated"}) + self.assertEqual(None, folder.update_folder_permissions("test", dict({"test": "test"}))) + + @patch("src.grafana_api.utils.Utils.call_the_api") + def test_update_folder_permissions_no_uid(self, call_the_api_mock): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + folder: Folder = Folder(grafana_api_model=model) + + call_the_api_mock.return_value = dict() + with self.assertRaises(ValueError): + folder.update_folder_permissions("", dict()) + + @patch("src.grafana_api.utils.Utils.call_the_api") + def test_update_folder_permissions_error_response(self, call_the_api_mock): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + folder: Folder = Folder(grafana_api_model=model) + + call_the_api_mock.return_value = dict({"message": "test"}) + with self.assertRaises(Exception): + folder.update_folder_permissions("test", dict({"test": "test"})) + @patch("src.grafana_api.folder.Folder.get_all_folder_ids_and_names") def test_get_folder_id_by_dashboard_path(self, all_folder_ids_and_names_mock): model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") folder: Folder = Folder(grafana_api_model=model) - all_folder_ids_and_names_mock.return_value = [{"title": None, "id": 12}] + all_folder_ids_and_names_mock.return_value = list([{"title": None, "id": 12}]) self.assertEqual(12, folder.get_folder_id_by_dashboard_path()) @patch("src.grafana_api.folder.Folder.get_all_folder_ids_and_names") @@ -21,7 +229,7 @@ def test_get_folder_id_by_dashboard_path_no_title_match( model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") folder: Folder = Folder(grafana_api_model=model) - all_folder_ids_and_names_mock.return_value = [{"title": "test", "id": "xty13y"}] + all_folder_ids_and_names_mock.return_value = list([{"title": "test", "id": "xty13y"}]) with self.assertRaises(Exception): folder.get_folder_id_by_dashboard_path() @@ -30,7 +238,7 @@ def test_get_all_folder_ids_and_names(self, call_the_api_mock): model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") folder: Folder = Folder(grafana_api_model=model) - call_the_api_mock.return_value = [{"title": "test", "id": 12, "test": "test"}] + call_the_api_mock.return_value = list([{"title": "test", "id": 12, "test": "test"}]) self.assertEqual( - [{"title": "test", "id": 12}], folder.get_all_folder_ids_and_names() + list([{"title": "test", "id": 12}]), folder.get_all_folder_ids_and_names() ) diff --git a/tests/unittests/test_utils.py b/tests/unittests/test_utils.py index 440a0bc..38a3212 100644 --- a/tests/unittests/test_utils.py +++ b/tests/unittests/test_utils.py @@ -100,6 +100,85 @@ def test_call_the_api_delete_not_valid(self): with self.assertRaises(Exception): utils.call_the_api(api_call=MagicMock(), method=RequestsMethods.DELETE) + def test_call_the_api_non_json_output_non_method(self): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + utils: Utils = Utils(grafana_api_model=model) + + with self.assertRaises(Exception): + utils. call_the_api_non_json_output(api_call=MagicMock(), method=None) + + @patch("requests.get") + def test_call_the_api_non_json_output_get_valid(self, get_mock): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + utils: Utils = Utils(grafana_api_model=model) + + get_mock.return_value.text = "success" + + self.assertEqual( + "success", + utils. call_the_api_non_json_output(api_call=MagicMock()).text, + ) + + def test_call_the_api_non_json_output_get_not_valid(self): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + utils: Utils = Utils(grafana_api_model=model) + + with self.assertRaises(MissingSchema): + utils. call_the_api_non_json_output(api_call=MagicMock(), method=RequestsMethods.GET) + + @patch("requests.post") + def test_call_the_api_non_json_output_post_valid(self, post_mock): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + utils: Utils = Utils(grafana_api_model=model) + + post_mock.return_value.text = "success" + + self.assertEqual( + "success", + utils. call_the_api_non_json_output( + api_call=MagicMock(), + method=RequestsMethods.POST, + json_complete=MagicMock(), + ).text, + ) + + def test_call_the_api_non_json_output_post_not_valid(self): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + utils: Utils = Utils(grafana_api_model=model) + + with self.assertRaises(MissingSchema): + utils. call_the_api_non_json_output( + api_call=MagicMock(), + method=RequestsMethods.POST, + json_complete=MagicMock(), + ) + + def test_call_the_api_non_json_output_post_no_data(self): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + utils: Utils = Utils(grafana_api_model=model) + + with self.assertRaises(Exception): + utils. call_the_api_non_json_output(api_call=MagicMock(), method=RequestsMethods.POST) + + @patch("requests.delete") + def test_call_the_api_non_json_output_delete_valid(self, delete_mock): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + utils: Utils = Utils(grafana_api_model=model) + + delete_mock.return_value.text = "Deletion successful" + + self.assertEqual( + "Deletion successful", + utils. call_the_api_non_json_output(api_call=MagicMock(), method=RequestsMethods.DELETE).text, + ) + + def test_call_the_api_non_json_output_delete_not_valid(self): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + utils: Utils = Utils(grafana_api_model=model) + + with self.assertRaises(Exception): + utils. call_the_api_non_json_output(api_call=MagicMock(), method=RequestsMethods.DELETE) + def test_check_the_api_call_response(self): model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") utils: Utils = Utils(grafana_api_model=model) From 44fb0f98d9580e4e47d96e8555b65f78bb168023 Mon Sep 17 00:00:00 2001 From: Pascal Zimmermann Date: Fri, 14 Jan 2022 23:25:16 +0100 Subject: [PATCH 09/19] WIP --- README.md | 5 +++++ tests/unittests/test_dashboard.py | 4 ---- tests/unittests/test_model.py | 4 ---- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index bd92f92..9126019 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,11 @@ The repository includes an SDK for the Grafana API ### Feature timeline +The following table describes the plan to implement the rest of the Grafana API functionality + +| API endpoint group | Implementation week | Maintainer | PR | +|:------------------:|:-------------------:|:----------:|:--:| +|||| ## Installation & Requirements ### Programs & tools to install diff --git a/tests/unittests/test_dashboard.py b/tests/unittests/test_dashboard.py index 301f010..10e3241 100644 --- a/tests/unittests/test_dashboard.py +++ b/tests/unittests/test_dashboard.py @@ -312,7 +312,3 @@ def test_calculate_dashboard_diff_no_valid_diff_type(self): with self.assertRaises(ValueError): dashboard.calculate_dashboard_diff({}, MagicMock(), "test") - - -if __name__ == "__main__": - main() diff --git a/tests/unittests/test_model.py b/tests/unittests/test_model.py index 765f972..b61f7ec 100644 --- a/tests/unittests/test_model.py +++ b/tests/unittests/test_model.py @@ -24,7 +24,3 @@ def test_api_model_init(self): self.assertEqual(None, model.message) self.assertEqual("test", model.host) - - -if __name__ == "__main__": - main() From 23597403eca617fcaddf05b7f647deba064ed931 Mon Sep 17 00:00:00 2001 From: Pascal Zimmermann Date: Fri, 14 Jan 2022 23:27:52 +0100 Subject: [PATCH 10/19] WIP --- src/grafana_api/dashboard.py | 35 +++++++++++++++++----- src/grafana_api/folder.py | 4 ++- src/grafana_api/utils.py | 4 +-- tests/unittests/test_dashboard.py | 50 +++++++++++++++++++++---------- tests/unittests/test_folder.py | 30 ++++++++++++++----- tests/unittests/test_model.py | 2 +- tests/unittests/test_utils.py | 24 ++++++++++----- 7 files changed, 104 insertions(+), 45 deletions(-) diff --git a/src/grafana_api/dashboard.py b/src/grafana_api/dashboard.py index 4731bca..4855e97 100644 --- a/src/grafana_api/dashboard.py +++ b/src/grafana_api/dashboard.py @@ -58,7 +58,10 @@ def delete_dashboard_by_name_and_path(self): RequestsMethods.DELETE, ) - if f"Dashboard {self.grafana_api_model.dashboard_name} deleted" != api_call.get("message"): + if ( + f"Dashboard {self.grafana_api_model.dashboard_name} deleted" + != api_call.get("message") + ): logging.error(f"Please, check the error: {api_call}.") raise Exception else: @@ -232,7 +235,10 @@ def restore_dashboard_version(self, id: int, version: dict): json.dumps(version), ) - if api_call.get("status") != "success" or api_call.get("message") is not None: + if ( + api_call.get("status") != "success" + or api_call.get("message") is not None + ): logging.error(f"Check the error: {api_call}.") raise Exception else: @@ -241,8 +247,12 @@ def restore_dashboard_version(self, id: int, version: dict): logging.info("There is no dashboard uid or version_id defined.") raise ValueError - def calculate_dashboard_diff(self, dashboard_id_and_version_base: dict, dashboard_id_and_version_new: dict, - diff_type: str = "json") -> str: + def calculate_dashboard_diff( + self, + dashboard_id_and_version_base: dict, + dashboard_id_and_version_new: dict, + diff_type: str = "json", + ) -> str: """The method includes a functionality to calculate the diff of specified versions of a dashboard based on the \ specified dashboard uid and the selected version of the base dashboard and the new dashboard and the diff \ type (basic or json) @@ -255,13 +265,18 @@ def calculate_dashboard_diff(self, dashboard_id_and_version_base: dict, dashboar possible_diff_types: list = list(["basic", "json"]) if diff_type.lower() in possible_diff_types: - if dashboard_id_and_version_base != dict() and dashboard_id_and_version_new != 0: + if ( + dashboard_id_and_version_base != dict() + and dashboard_id_and_version_new != 0 + ): diff_object: dict = dict() diff_object.update(dashboard_id_and_version_base) diff_object.update(dashboard_id_and_version_new) diff_object.update({"diffType": diff_type.lower()}) - api_call: any = Utils(self.grafana_api_model).call_the_api_non_json_output( + api_call: any = Utils( + self.grafana_api_model + ).call_the_api_non_json_output( f"{APIEndpoints.DASHBOARDS.value}/calculate-diff", RequestsMethods.POST, json.dumps(diff_object), @@ -273,8 +288,12 @@ def calculate_dashboard_diff(self, dashboard_id_and_version_base: dict, dashboar else: return api_call.text else: - logging.info("There is no dashboard_uid_and_version_base or dashboard_uid_and_version_new defined.") + logging.info( + "There is no dashboard_uid_and_version_base or dashboard_uid_and_version_new defined." + ) raise ValueError else: - logging.info(f"The diff_type: {diff_type.lower()} is not valid. Please specify a valid value.") + logging.info( + f"The diff_type: {diff_type.lower()} is not valid. Please specify a valid value." + ) raise ValueError diff --git a/src/grafana_api/folder.py b/src/grafana_api/folder.py index b44190a..e9731db 100644 --- a/src/grafana_api/folder.py +++ b/src/grafana_api/folder.py @@ -94,7 +94,9 @@ def create_folder(self, title: str, uid: str = None) -> dict: logging.info("There is no folder uid or title defined.") raise ValueError - def update_folder(self, uid: str, title: str, version: int = 0, overwrite: bool = False) -> dict: + def update_folder( + self, uid: str, title: str, version: int = 0, overwrite: bool = False + ) -> dict: """The method includes a functionality to update a folder information inside the organization specified \ by the uid, the title, the version of the folder or if folder information be overwritten diff --git a/src/grafana_api/utils.py b/src/grafana_api/utils.py index 4df412e..4b112cd 100644 --- a/src/grafana_api/utils.py +++ b/src/grafana_api/utils.py @@ -88,9 +88,7 @@ def call_the_api_non_json_output( elif method.value == RequestsMethods.POST.value: if json_complete is not None: return Utils.__check_the_api_call_response( - requests.post( - api_url, data=json_complete, headers=headers - ) + requests.post(api_url, data=json_complete, headers=headers) ) else: logging.error("Please define the json_complete.") diff --git a/tests/unittests/test_dashboard.py b/tests/unittests/test_dashboard.py index 10e3241..423781c 100644 --- a/tests/unittests/test_dashboard.py +++ b/tests/unittests/test_dashboard.py @@ -1,4 +1,4 @@ -from unittest import TestCase, main +from unittest import TestCase from unittest.mock import MagicMock, patch from src.grafana_api.model import APIModel @@ -35,19 +35,25 @@ def test_create_or_update_dashboard_update_not_possible( dashboard.create_or_update_dashboard(dashboard_json=dict({"test": "test"})) @patch("src.grafana_api.utils.Utils.call_the_api") - @patch("src.grafana_api.dashboard.Dashboard.get_dashboard_uid_and_id_by_name_and_folder") + @patch( + "src.grafana_api.dashboard.Dashboard.get_dashboard_uid_and_id_by_name_and_folder" + ) def test_delete_dashboard_by_name_and_path( self, dashboard_uid_and_id_by_name_and_folder_mock, call_the_api_mock ): model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") dashboard: Dashboard = Dashboard(grafana_api_model=model) - dashboard_uid_and_id_by_name_and_folder_mock.return_value = dict({"uid": "test", "id": 10}) + dashboard_uid_and_id_by_name_and_folder_mock.return_value = dict( + {"uid": "test", "id": 10} + ) call_the_api_mock.return_value = dict({"message": "Dashboard None deleted"}) self.assertEqual(None, dashboard.delete_dashboard_by_name_and_path()) @patch("src.grafana_api.utils.Utils.call_the_api") - @patch("src.grafana_api.dashboard.Dashboard.get_dashboard_uid_and_id_by_name_and_folder") + @patch( + "src.grafana_api.dashboard.Dashboard.get_dashboard_uid_and_id_by_name_and_folder" + ) def test_delete_dashboard_by_name_and_path_deletion_list_empty( self, dashboard_uid_and_id_by_name_and_folder_mock, call_the_api_mock ): @@ -60,14 +66,18 @@ def test_delete_dashboard_by_name_and_path_deletion_list_empty( dashboard.delete_dashboard_by_name_and_path() @patch("src.grafana_api.utils.Utils.call_the_api") - @patch("src.grafana_api.dashboard.Dashboard.get_dashboard_uid_and_id_by_name_and_folder") + @patch( + "src.grafana_api.dashboard.Dashboard.get_dashboard_uid_and_id_by_name_and_folder" + ) def test_delete_dashboard_by_name_and_path_deletion_not_possible( self, dashboard_uid_and_id_by_name_and_folder_mock, call_the_api_mock ): model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") dashboard: Dashboard = Dashboard(grafana_api_model=model) - dashboard_uid_and_id_by_name_and_folder_mock.return_value = dict({"uid": "test", "id": 10}) + dashboard_uid_and_id_by_name_and_folder_mock.return_value = dict( + {"uid": "test", "id": 10} + ) call_the_api_mock.return_value = dict({"message": "error"}) with self.assertRaises(Exception): dashboard.delete_dashboard_by_name_and_path() @@ -82,7 +92,10 @@ def test_get_dashboard_uid_and_id_by_name_and_folder( folder_id_by_dashboard_path_mock.return_value = 1 call_the_api_mock.return_value = [{"uid": "test", "id": 10}] - self.assertEqual(dict({"uid": "test", "id": 10}), dashboard.get_dashboard_uid_and_id_by_name_and_folder()) + self.assertEqual( + dict({"uid": "test", "id": 10}), + dashboard.get_dashboard_uid_and_id_by_name_and_folder(), + ) @patch("src.grafana_api.utils.Utils.call_the_api") def test_get_dashboard_by_uid(self, call_the_api_mock): @@ -207,9 +220,7 @@ def test_get_dashboard_versions(self, call_the_api_mock): dashboard: Dashboard = Dashboard(grafana_api_model=model) call_the_api_mock.return_value = list([{"id": "test"}]) - self.assertEqual( - list([{"id": "test"}]), dashboard.get_dashboard_versions(1) - ) + self.assertEqual(list([{"id": "test"}]), dashboard.get_dashboard_versions(1)) @patch("src.grafana_api.utils.Utils.call_the_api") def test_get_dashboard_versions_error_response(self, call_the_api_mock): @@ -233,9 +244,7 @@ def test_get_dashboard_version(self, call_the_api_mock): dashboard: Dashboard = Dashboard(grafana_api_model=model) call_the_api_mock.return_value = dict({"id": "test"}) - self.assertEqual( - dict({"id": "test"}), dashboard.get_dashboard_version(1, 10) - ) + self.assertEqual(dict({"id": "test"}), dashboard.get_dashboard_version(1, 10)) @patch("src.grafana_api.utils.Utils.call_the_api") def test_get_dashboard_version_error_response(self, call_the_api_mock): @@ -287,17 +296,26 @@ def test_calculate_dashboard_diff(self, call_the_api_non_json_output_mock): call_the_api_non_json_output_mock.return_value.status_code = 200 call_the_api_non_json_output_mock.return_value.text = "test" self.assertEqual( - "test", dashboard.calculate_dashboard_diff(dict({"dashboardId": 1, "version": 1}), dict({"dashboardId": 2, "version": 1})) + "test", + dashboard.calculate_dashboard_diff( + dict({"dashboardId": 1, "version": 1}), + dict({"dashboardId": 2, "version": 1}), + ), ) @patch("src.grafana_api.utils.Utils.call_the_api_non_json_output") - def test_calculate_dashboard_diff_error_response(self, call_the_api_non_json_output_mock): + def test_calculate_dashboard_diff_error_response( + self, call_the_api_non_json_output_mock + ): model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") dashboard: Dashboard = Dashboard(grafana_api_model=model) call_the_api_non_json_output_mock.status_code.return_value = 400 with self.assertRaises(Exception): - dashboard.calculate_dashboard_diff(dict({"dashboardId": 1, "version": 1}), dict({"dashboardId": 2, "version": 1})) + dashboard.calculate_dashboard_diff( + dict({"dashboardId": 1, "version": 1}), + dict({"dashboardId": 2, "version": 1}), + ) def test_calculate_dashboard_diff_no_dashboard_id_and_version_base(self): model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") diff --git a/tests/unittests/test_folder.py b/tests/unittests/test_folder.py index 0adf929..5423eb0 100644 --- a/tests/unittests/test_folder.py +++ b/tests/unittests/test_folder.py @@ -6,7 +6,6 @@ class FolderTestCase(TestCase): - @patch("src.grafana_api.utils.Utils.call_the_api") def test_get_folders(self, call_the_api_mock): model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") @@ -30,7 +29,9 @@ def test_get_folder_by_uid(self, call_the_api_mock): folder: Folder = Folder(grafana_api_model=model) call_the_api_mock.return_value = dict({"title": None, "id": 12}) - self.assertEqual(dict({"title": None, "id": 12}), folder.get_folder_by_uid("xty13y")) + self.assertEqual( + dict({"title": None, "id": 12}), folder.get_folder_by_uid("xty13y") + ) @patch("src.grafana_api.utils.Utils.call_the_api") def test_get_folder_by_uid_no_uid(self, call_the_api_mock): @@ -82,7 +83,9 @@ def test_create_folder(self, call_the_api_mock): folder: Folder = Folder(grafana_api_model=model) call_the_api_mock.return_value = dict({"title": None, "id": 12}) - self.assertEqual(dict({"title": None, "id": 12}), folder.create_folder("test", "test")) + self.assertEqual( + dict({"title": None, "id": 12}), folder.create_folder("test", "test") + ) @patch("src.grafana_api.utils.Utils.call_the_api") def test_create_folder_no_title(self, call_the_api_mock): @@ -108,7 +111,9 @@ def test_update_folder(self, call_the_api_mock): folder: Folder = Folder(grafana_api_model=model) call_the_api_mock.return_value = dict({"title": None, "id": 12}) - self.assertEqual(dict({"title": None, "id": 12}), folder.update_folder("test", "test", 10)) + self.assertEqual( + dict({"title": None, "id": 12}), folder.update_folder("test", "test", 10) + ) @patch("src.grafana_api.utils.Utils.call_the_api") def test_update_folder_overwrite_true(self, call_the_api_mock): @@ -116,7 +121,10 @@ def test_update_folder_overwrite_true(self, call_the_api_mock): folder: Folder = Folder(grafana_api_model=model) call_the_api_mock.return_value = dict({"title": None, "id": 12}) - self.assertEqual(dict({"title": None, "id": 12}), folder.update_folder("test", "test", overwrite=True)) + self.assertEqual( + dict({"title": None, "id": 12}), + folder.update_folder("test", "test", overwrite=True), + ) @patch("src.grafana_api.utils.Utils.call_the_api") def test_update_folder_no_title(self, call_the_api_mock): @@ -194,7 +202,9 @@ def test_update_folder_permissions(self, call_the_api_mock): folder: Folder = Folder(grafana_api_model=model) call_the_api_mock.return_value = dict({"message": "Folder permissions updated"}) - self.assertEqual(None, folder.update_folder_permissions("test", dict({"test": "test"}))) + self.assertEqual( + None, folder.update_folder_permissions("test", dict({"test": "test"})) + ) @patch("src.grafana_api.utils.Utils.call_the_api") def test_update_folder_permissions_no_uid(self, call_the_api_mock): @@ -229,7 +239,9 @@ def test_get_folder_id_by_dashboard_path_no_title_match( model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") folder: Folder = Folder(grafana_api_model=model) - all_folder_ids_and_names_mock.return_value = list([{"title": "test", "id": "xty13y"}]) + all_folder_ids_and_names_mock.return_value = list( + [{"title": "test", "id": "xty13y"}] + ) with self.assertRaises(Exception): folder.get_folder_id_by_dashboard_path() @@ -238,7 +250,9 @@ def test_get_all_folder_ids_and_names(self, call_the_api_mock): model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") folder: Folder = Folder(grafana_api_model=model) - call_the_api_mock.return_value = list([{"title": "test", "id": 12, "test": "test"}]) + call_the_api_mock.return_value = list( + [{"title": "test", "id": 12, "test": "test"}] + ) self.assertEqual( list([{"title": "test", "id": 12}]), folder.get_all_folder_ids_and_names() ) diff --git a/tests/unittests/test_model.py b/tests/unittests/test_model.py index b61f7ec..8e77185 100644 --- a/tests/unittests/test_model.py +++ b/tests/unittests/test_model.py @@ -1,4 +1,4 @@ -from unittest import TestCase, main +from unittest import TestCase from src.grafana_api.model import APIModel, RequestsMethods, APIEndpoints diff --git a/tests/unittests/test_utils.py b/tests/unittests/test_utils.py index 38a3212..1f4fd16 100644 --- a/tests/unittests/test_utils.py +++ b/tests/unittests/test_utils.py @@ -105,7 +105,7 @@ def test_call_the_api_non_json_output_non_method(self): utils: Utils = Utils(grafana_api_model=model) with self.assertRaises(Exception): - utils. call_the_api_non_json_output(api_call=MagicMock(), method=None) + utils.call_the_api_non_json_output(api_call=MagicMock(), method=None) @patch("requests.get") def test_call_the_api_non_json_output_get_valid(self, get_mock): @@ -116,7 +116,7 @@ def test_call_the_api_non_json_output_get_valid(self, get_mock): self.assertEqual( "success", - utils. call_the_api_non_json_output(api_call=MagicMock()).text, + utils.call_the_api_non_json_output(api_call=MagicMock()).text, ) def test_call_the_api_non_json_output_get_not_valid(self): @@ -124,7 +124,9 @@ def test_call_the_api_non_json_output_get_not_valid(self): utils: Utils = Utils(grafana_api_model=model) with self.assertRaises(MissingSchema): - utils. call_the_api_non_json_output(api_call=MagicMock(), method=RequestsMethods.GET) + utils.call_the_api_non_json_output( + api_call=MagicMock(), method=RequestsMethods.GET + ) @patch("requests.post") def test_call_the_api_non_json_output_post_valid(self, post_mock): @@ -135,7 +137,7 @@ def test_call_the_api_non_json_output_post_valid(self, post_mock): self.assertEqual( "success", - utils. call_the_api_non_json_output( + utils.call_the_api_non_json_output( api_call=MagicMock(), method=RequestsMethods.POST, json_complete=MagicMock(), @@ -147,7 +149,7 @@ def test_call_the_api_non_json_output_post_not_valid(self): utils: Utils = Utils(grafana_api_model=model) with self.assertRaises(MissingSchema): - utils. call_the_api_non_json_output( + utils.call_the_api_non_json_output( api_call=MagicMock(), method=RequestsMethods.POST, json_complete=MagicMock(), @@ -158,7 +160,9 @@ def test_call_the_api_non_json_output_post_no_data(self): utils: Utils = Utils(grafana_api_model=model) with self.assertRaises(Exception): - utils. call_the_api_non_json_output(api_call=MagicMock(), method=RequestsMethods.POST) + utils.call_the_api_non_json_output( + api_call=MagicMock(), method=RequestsMethods.POST + ) @patch("requests.delete") def test_call_the_api_non_json_output_delete_valid(self, delete_mock): @@ -169,7 +173,9 @@ def test_call_the_api_non_json_output_delete_valid(self, delete_mock): self.assertEqual( "Deletion successful", - utils. call_the_api_non_json_output(api_call=MagicMock(), method=RequestsMethods.DELETE).text, + utils.call_the_api_non_json_output( + api_call=MagicMock(), method=RequestsMethods.DELETE + ).text, ) def test_call_the_api_non_json_output_delete_not_valid(self): @@ -177,7 +183,9 @@ def test_call_the_api_non_json_output_delete_not_valid(self): utils: Utils = Utils(grafana_api_model=model) with self.assertRaises(Exception): - utils. call_the_api_non_json_output(api_call=MagicMock(), method=RequestsMethods.DELETE) + utils.call_the_api_non_json_output( + api_call=MagicMock(), method=RequestsMethods.DELETE + ) def test_check_the_api_call_response(self): model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") From 8042591cfdf85a8ab56c3831190cde341772f794 Mon Sep 17 00:00:00 2001 From: Pascal Zimmermann Date: Fri, 14 Jan 2022 23:38:06 +0100 Subject: [PATCH 11/19] Update the integrationtest --- .github/workflows/integrationtest.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/integrationtest.yml b/.github/workflows/integrationtest.yml index 0b7035c..c32b7cd 100644 --- a/.github/workflows/integrationtest.yml +++ b/.github/workflows/integrationtest.yml @@ -18,4 +18,9 @@ jobs: run: pip install -r requirements.txt - name: Execute the unittests - run: python3 -m unittest discover tests/integrationtest \ No newline at end of file + run: python3 -m unittest discover tests/integrationtest + env: + GRAFANA_HOST: ${{ secrets.GRAFANA_HOST }} + GRAFANA_TOKEN: ${{ secrets.GRAFANA_TOKEN }} + GRAFANA_DASHBOARD_PATH: ${{ secrets.GRAFANA_DASHBOARD_PATH }} + GRAFANA_DASHBOARD_NAME: ${{ secrets.GRAFANA_DASHBOARD_NAME }} \ No newline at end of file From c7cc522ce9c9c8691f500708ce0fdd39d197f83b Mon Sep 17 00:00:00 2001 From: Pascal Zimmermann Date: Fri, 14 Jan 2022 23:45:03 +0100 Subject: [PATCH 12/19] WIP --- tests/integrationtest/test_dashboard.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/integrationtest/test_dashboard.py b/tests/integrationtest/test_dashboard.py index aa2798e..ffddbc1 100644 --- a/tests/integrationtest/test_dashboard.py +++ b/tests/integrationtest/test_dashboard.py @@ -22,7 +22,7 @@ class DashboardTest(TestCase): def test_dashboard_creation(self): with open( - f"{os.getcwd()}tests{os.sep}integrationtest{os.sep}resources{os.sep}dashboard.json" + f"{os.getcwd()}{os.sep}tests{os.sep}integrationtest{os.sep}resources{os.sep}dashboard.json" ) as file: json_dashboard = json.load(file) @@ -44,7 +44,7 @@ def test_wrong_token(self): def test_get_dashboard(self): with open( - f"{os.getcwd()}tests{os.sep}integrationtest{os.sep}resources{os.sep}dashboard.json" + f"{os.getcwd()}{os.sep}tests{os.sep}integrationtest{os.sep}resources{os.sep}dashboard.json" ) as file: json_dashboard = json.load(file) From b53397c633062c34c4e882d79817de5900537246 Mon Sep 17 00:00:00 2001 From: Pascal Zimmermann Date: Sat, 15 Jan 2022 20:50:52 +0100 Subject: [PATCH 13/19] WIP --- src/grafana_api/dashboard.py | 121 +++++++++++------ src/grafana_api/folder.py | 34 +++-- src/grafana_api/model.py | 11 +- src/grafana_api/search.py | 6 + src/grafana_api/utils.py | 8 +- .../resources/dashboard_expected_result.json | 55 ++++++++ tests/integrationtest/test_dashboard.py | 47 ++++--- tests/unittests/test_dashboard.py | 128 ++++++++++++------ tests/unittests/test_folder.py | 71 +++++----- tests/unittests/test_model.py | 6 +- tests/unittests/test_search.py | 10 +- tests/unittests/test_utils.py | 60 +++++--- 12 files changed, 376 insertions(+), 181 deletions(-) create mode 100644 tests/integrationtest/resources/dashboard_expected_result.json diff --git a/src/grafana_api/dashboard.py b/src/grafana_api/dashboard.py index 4855e97..2890d15 100644 --- a/src/grafana_api/dashboard.py +++ b/src/grafana_api/dashboard.py @@ -16,58 +16,81 @@ class Dashboard: def __init__(self, grafana_api_model: APIModel): self.grafana_api_model = grafana_api_model - def create_or_update_dashboard(self, dashboard_json: dict, overwrite: bool = False): + def create_or_update_dashboard( + self, + dashboard_path: str, + dashboard_json: dict, + message: str, + overwrite: bool = False, + ): """The method includes a functionality to create the specified dashboard Keyword arguments: + dashboard_path -> Specify the dashboard path in which the dashboard is to be placed dashboard_json -> Specify the inserted dashboard as dict + message -> Specify the message that should be injected as commit message inside the dashboard overwrite -> Should the already existing dashboard be overwritten """ - folder_id: int = Folder( - self.grafana_api_model - ).get_folder_id_by_dashboard_path() + if len(dashboard_path) != 0 and dashboard_json != dict() and len(message) != 0: + folder_id: int = Folder( + self.grafana_api_model + ).get_folder_id_by_dashboard_path(dashboard_path) - dashboard_json_complete: dict = { - "dashboard": dashboard_json, - "folderId": folder_id, - "message": self.grafana_api_model.message, - "overwrite": overwrite, - } + dashboard_json_complete: dict = { + "dashboard": dashboard_json, + "folderId": folder_id, + "message": message, + "overwrite": overwrite, + } - api_call: dict = Utils(self.grafana_api_model).call_the_api( - f"{APIEndpoints.DASHBOARDS.value}/db", - RequestsMethods.POST, - json.dumps(dashboard_json_complete), - ) + api_call: dict = Utils(self.grafana_api_model).call_the_api( + f"{APIEndpoints.DASHBOARDS.value}/db", + RequestsMethods.POST, + json.dumps(dashboard_json_complete), + ) - if api_call.get("status") != "success": - logging.error(f"Check the error: {api_call}.") - raise Exception + if api_call.get("status") != "success": + logging.error(f"Check the error: {api_call}.") + raise Exception + else: + logging.info("You successfully deployed the dashboard.") else: - logging.info("You successfully deployed the dashboard.") + logging.info( + "There is no dashboard_path or dashboard_json or message defined." + ) + raise ValueError - def delete_dashboard_by_name_and_path(self): - """The method includes a functionality to delete the specified dashboard inside the model""" + def delete_dashboard_by_name_and_path( + self, dashboard_name: str, dashboard_path: str + ): + """The method includes a functionality to delete the specified dashboard inside the model - dashboard_uid: dict = self.get_dashboard_uid_and_id_by_name_and_folder() + dashboard_name -> Specify the dashboard name of the deleted dashboard + dashboard_path -> Specify the dashboard path of the deleted dashboard + """ - if len(dashboard_uid) != 0: - api_call: dict = Utils(self.grafana_api_model).call_the_api( - f"{APIEndpoints.DASHBOARDS.value}/uid/{dashboard_uid.get('uid')}", - RequestsMethods.DELETE, + if len(dashboard_name) != 0 and len(dashboard_path) != 0: + dashboard_uid: dict = self.get_dashboard_uid_and_id_by_name_and_folder( + dashboard_name, dashboard_path ) - if ( - f"Dashboard {self.grafana_api_model.dashboard_name} deleted" - != api_call.get("message") - ): - logging.error(f"Please, check the error: {api_call}.") - raise Exception + if len(dashboard_uid) != 0: + api_call: dict = Utils(self.grafana_api_model).call_the_api( + f"{APIEndpoints.DASHBOARDS.value}/uid/{dashboard_uid.get('uid')}", + RequestsMethods.DELETE, + ) + + if f"Dashboard {dashboard_name} deleted" != api_call.get("message"): + logging.error(f"Please, check the error: {api_call}.") + raise Exception + else: + logging.info("You successfully destroyed the dashboard.") else: - logging.info("You successfully destroyed the dashboard.") + logging.info("Nothing to delete. There is no dashboard available.") + raise ValueError else: - logging.info("Nothing to delete. There is no dashboard available.") + logging.info("There is no dashboard_name or dashboard_path defined.") raise ValueError def get_dashboard_by_uid(self, uid: str) -> dict: @@ -117,17 +140,31 @@ def get_dashboard_tags(self) -> list: else: return api_call - def get_dashboard_uid_and_id_by_name_and_folder(self) -> dict: - """The method includes a functionality to extract the dashboard uid specified inside the model""" + def get_dashboard_uid_and_id_by_name_and_folder( + self, dashboard_name: str, dashboard_path: str + ) -> dict: + """The method includes a functionality to extract the dashboard uid specified inside the model + + dashboard_name -> Specify the dashboard name of the dashboard + dashboard_path -> Specify the dashboard path of the dashboard + """ - folder_id: int = Folder( - self.grafana_api_model - ).get_folder_id_by_dashboard_path() + if len(dashboard_name) != 0 and len(dashboard_path) != 0: + folder_id: int = Folder( + self.grafana_api_model + ).get_folder_id_by_dashboard_path(dashboard_path) - search_query: str = f"{APIEndpoints.SEARCH.value}?folderIds={folder_id}&query={self.grafana_api_model.dashboard_name}" - dashboard_meta: list = Utils(self.grafana_api_model).call_the_api(search_query) + search_query: str = f"{APIEndpoints.SEARCH.value}?folderIds={folder_id}&query={dashboard_name}" + dashboard_meta: list = Utils(self.grafana_api_model).call_the_api( + search_query + ) - return dict({"uid": dashboard_meta[0]["uid"], "id": dashboard_meta[0]["id"]}) + return dict( + {"uid": dashboard_meta[0]["uid"], "id": dashboard_meta[0]["id"]} + ) + else: + logging.info("There is no dashboard_name or dashboard_path defined.") + raise ValueError def get_dashboard_permissions(self, id: int) -> list: """The method includes a functionality to extract the dashboard permissions based on the specified id diff --git a/src/grafana_api/folder.py b/src/grafana_api/folder.py index e9731db..1d5f864 100644 --- a/src/grafana_api/folder.py +++ b/src/grafana_api/folder.py @@ -6,6 +6,12 @@ class Folder: + """The class includes all necessary methods to access the Grafana folder API endpoints + + Keyword arguments: + grafana_api_model -> Inject a Grafana API model object that includes all necessary values and information + """ + def __init__(self, grafana_api_model: APIModel): self.grafana_api_model = grafana_api_model @@ -206,23 +212,27 @@ def update_folder_permissions(self, uid: str, permission_json: dict): logging.info("There is no folder uid or permission json defined.") raise ValueError - def get_folder_id_by_dashboard_path(self) -> int: + def get_folder_id_by_dashboard_path(self, dashboard_path: str) -> int: """The method includes a functionality to extract the folder id specified inside model dashboard path""" - folders: list = self.get_all_folder_ids_and_names() - folder_id: int = 0 + if len(dashboard_path) != 0: + folders: list = self.get_all_folder_ids_and_names() + folder_id: int = 0 - for f in folders: - if self.grafana_api_model.dashboard_path == f.get("title"): - folder_id = f.get("id") + for f in folders: + if dashboard_path == f.get("title"): + folder_id = f.get("id") - if folder_id == 0: - logging.error( - f"There's no folder_id for the dashboard named {self.grafana_api_model.dashboard_path} available." - ) - raise Exception + if folder_id == 0: + logging.error( + f"There's no folder_id for the dashboard named {dashboard_path} available." + ) + raise Exception - return folder_id + return folder_id + else: + logging.info("There is no dashboard_path defined.") + raise ValueError def get_all_folder_ids_and_names(self) -> list: """The method extract all folder id and names inside the complete organisation""" diff --git a/src/grafana_api/model.py b/src/grafana_api/model.py index 3f2f714..b6c9e35 100644 --- a/src/grafana_api/model.py +++ b/src/grafana_api/model.py @@ -30,22 +30,17 @@ class RequestsMethods(Enum): class APIModel: - """The class includes all necessary methods to template the selected dashboard and return it as a dict + """The class includes all necessary variables to establish a connection to the Grafana API endpoints Keyword arguments: - dashboard_model -> Inject a dashboard object that includes all necessary values and information + host -> Specify the host of the Grafana system + token -> Specify the access token of the Grafana system """ def __init__( self, host: str = None, token: str = None, - message: str = None, - dashboard_path: str = None, - dashboard_name: str = None, ): self.host = host self.token = token - self.message = message - self.dashboard_path = dashboard_path - self.dashboard_name = dashboard_name diff --git a/src/grafana_api/search.py b/src/grafana_api/search.py index 069eceb..b77711b 100644 --- a/src/grafana_api/search.py +++ b/src/grafana_api/search.py @@ -3,6 +3,12 @@ class Search: + """The class includes all necessary methods to access the Grafana search API endpoints + + Keyword arguments: + grafana_api_model -> Inject a Grafana API model object that includes all necessary values and information + """ + def __init__(self, grafana_api_model: APIModel): self.grafana_api_model = grafana_api_model diff --git a/src/grafana_api/utils.py b/src/grafana_api/utils.py index 4b112cd..e2b2dfa 100644 --- a/src/grafana_api/utils.py +++ b/src/grafana_api/utils.py @@ -5,6 +5,12 @@ class Utils: + """The class includes all necessary methods to make API calls to the Grafana API endpoints + + Keyword arguments: + grafana_api_model -> Inject a Grafana API model object that includes all necessary values and information + """ + def __init__(self, grafana_api_model: APIModel): self.grafana_api_model = grafana_api_model @@ -36,7 +42,7 @@ def call_the_api( elif method.value == RequestsMethods.PUT.value: if json_complete is not None: return Utils.__check_the_api_call_response( - requests.post( + requests.put( api_url, data=json_complete, headers=headers ).json() ) diff --git a/tests/integrationtest/resources/dashboard_expected_result.json b/tests/integrationtest/resources/dashboard_expected_result.json new file mode 100644 index 0000000..007becc --- /dev/null +++ b/tests/integrationtest/resources/dashboard_expected_result.json @@ -0,0 +1,55 @@ +{ + "dashboard": { + "annotations": { + "list": [] + }, + "editable": true, + "graphTooltip": 1, + "hideControls": false, + "id": 104, + "links": [], + "panels": [], + "refresh": "5s", + "schemaVersion": 17, + "style": "dark", + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-6h", + "to": "now" + }, + "timepicker": { + "refresh_intervals": [], + "time_options": [] + }, + "timezone": "browser", + "title": "Test 1", + "uid": "tests1", + "version": 1 + }, + "meta": { + "canAdmin": true, + "canEdit": true, + "canSave": true, + "canStar": true, + "created": "2022-01-15T20:46:44+01:00", + "createdBy": "Anonymous", + "expires": "0001-01-01T00:00:00Z", + "folderId": 72, + "folderTitle": "Github Integrationtest", + "folderUid": "6U_QdWJnz", + "folderUrl": "/dashboards/f/6U_QdWJnz/github-integrationtest", + "hasAcl": false, + "isFolder": false, + "provisioned": false, + "provisionedExternalId": "", + "slug": "test-1", + "type": "db", + "updated": "2022-01-15T20:46:44+01:00", + "updatedBy": "Anonymous", + "url": "/d/tests1/test-1", + "version": 1 + } +} \ No newline at end of file diff --git a/tests/integrationtest/test_dashboard.py b/tests/integrationtest/test_dashboard.py index ffddbc1..e59b991 100644 --- a/tests/integrationtest/test_dashboard.py +++ b/tests/integrationtest/test_dashboard.py @@ -13,46 +13,49 @@ class DashboardTest(TestCase): model: APIModel = APIModel( host=os.environ["GRAFANA_HOST"], token=os.environ["GRAFANA_TOKEN"], - message="Create a new test dashboard", - dashboard_path=os.environ["GRAFANA_DASHBOARD_PATH"], - dashboard_name=os.environ["GRAFANA_DASHBOARD_NAME"], ) dashboard: Dashboard = Dashboard(model) folder: Folder = Folder(model) - def test_dashboard_creation(self): + def test_a_dashboard_creation(self): with open( f"{os.getcwd()}{os.sep}tests{os.sep}integrationtest{os.sep}resources{os.sep}dashboard.json" ) as file: json_dashboard = json.load(file) self.dashboard.create_or_update_dashboard( - dashboard_json=json_dashboard, overwrite=True + message="Create a new test dashboard", + dashboard_json=json_dashboard, + dashboard_path=os.environ["GRAFANA_DASHBOARD_PATH"], + overwrite=True, ) - self.assertEqual("tests", self.dashboard.get_dashboard_uid_by_name_and_folder()) - self.assertEqual(72, self.folder.get_folder_id_by_dashboard_path()) - - def test_dashboard_deletion(self): - self.dashboard.delete_dashboard_by_name_and_path() - - def test_wrong_token(self): - self.model.token = "test" - - with self.assertRaises(requests.exceptions.ConnectionError): - self.dashboard.delete_dashboard_by_name_and_path() + self.assertEqual( + "tests", self.dashboard.get_dashboard_uid_and_id_by_name_and_folder( + dashboard_path=os.environ["GRAFANA_DASHBOARD_PATH"], + dashboard_name=os.environ["GRAFANA_DASHBOARD_NAME"])["uid"] + ) + self.assertEqual(72, self.folder.get_folder_id_by_dashboard_path( + dashboard_path=os.environ["GRAFANA_DASHBOARD_PATH"])) - def test_get_dashboard(self): + def test_b_get_dashboard(self): with open( - f"{os.getcwd()}{os.sep}tests{os.sep}integrationtest{os.sep}resources{os.sep}dashboard.json" + f"{os.getcwd()}{os.sep}tests{os.sep}integrationtest{os.sep}resources{os.sep}dashboard_expected_result.json" ) as file: json_dashboard = json.load(file) - self.dashboard.create_or_update_dashboard( - dashboard_json=json_dashboard, overwrite=True - ) + self.assertEqual(json_dashboard, self.dashboard.get_dashboard_by_uid("tests1")) - self.assertEqual(json_dashboard, self.dashboard.get_dashboard_by_uid("tests")) + def test_c_dashboard_deletion(self): + self.dashboard.delete_dashboard_by_name_and_path(dashboard_path=os.environ["GRAFANA_DASHBOARD_PATH"], + dashboard_name=os.environ["GRAFANA_DASHBOARD_NAME"]) + + def test_wrong_token(self): + self.model.token = "test" + + with self.assertRaises(requests.exceptions.ConnectionError): + self.dashboard.delete_dashboard_by_name_and_path(dashboard_path=os.environ["GRAFANA_DASHBOARD_PATH"], + dashboard_name=os.environ["GRAFANA_DASHBOARD_NAME"]) if __name__ == "__main__": diff --git a/tests/unittests/test_dashboard.py b/tests/unittests/test_dashboard.py index 423781c..a6731a3 100644 --- a/tests/unittests/test_dashboard.py +++ b/tests/unittests/test_dashboard.py @@ -11,28 +11,45 @@ class DashboardTestCase(TestCase): def test_create_or_update_dashboard( self, folder_id_by_dashboard_path_mock, call_the_api_mock ): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) dashboard: Dashboard = Dashboard(grafana_api_model=model) folder_id_by_dashboard_path_mock.return_value = 1 call_the_api_mock.return_value = dict({"status": "success"}) self.assertEqual( None, - dashboard.create_or_update_dashboard(dashboard_json=dict({"test": "test"})), + dashboard.create_or_update_dashboard( + dashboard_path="test", + dashboard_json=dict({"test": "test"}), + message="test", + ), ) + def test_create_or_update_dashboard_no_dashboard_path_defined(self): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) + dashboard: Dashboard = Dashboard(grafana_api_model=model) + + with self.assertRaises(ValueError): + dashboard.create_or_update_dashboard( + dashboard_path="", dashboard_json=dict({"test": "test"}), message="test" + ) + @patch("src.grafana_api.utils.Utils.call_the_api") @patch("src.grafana_api.folder.Folder.get_folder_id_by_dashboard_path") def test_create_or_update_dashboard_update_not_possible( self, folder_id_by_dashboard_path_mock, call_the_api_mock ): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) dashboard: Dashboard = Dashboard(grafana_api_model=model) folder_id_by_dashboard_path_mock.return_value = 1 call_the_api_mock.return_value = dict({"status": "error"}) with self.assertRaises(Exception): - dashboard.create_or_update_dashboard(dashboard_json=dict({"test": "test"})) + dashboard.create_or_update_dashboard( + dashboard_path="test", + dashboard_json=dict({"test": "test"}), + message="test", + ) @patch("src.grafana_api.utils.Utils.call_the_api") @patch( @@ -41,14 +58,30 @@ def test_create_or_update_dashboard_update_not_possible( def test_delete_dashboard_by_name_and_path( self, dashboard_uid_and_id_by_name_and_folder_mock, call_the_api_mock ): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) dashboard: Dashboard = Dashboard(grafana_api_model=model) dashboard_uid_and_id_by_name_and_folder_mock.return_value = dict( {"uid": "test", "id": 10} ) - call_the_api_mock.return_value = dict({"message": "Dashboard None deleted"}) - self.assertEqual(None, dashboard.delete_dashboard_by_name_and_path()) + call_the_api_mock.return_value = dict({"message": "Dashboard test deleted"}) + self.assertEqual( + None, + dashboard.delete_dashboard_by_name_and_path( + dashboard_name="test", dashboard_path="test" + ), + ) + + def test_delete_dashboard_by_name_and_path_no_dashboard_name( + self, + ): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) + dashboard: Dashboard = Dashboard(grafana_api_model=model) + + with self.assertRaises(ValueError): + dashboard.delete_dashboard_by_name_and_path( + dashboard_name="", dashboard_path="test" + ) @patch("src.grafana_api.utils.Utils.call_the_api") @patch( @@ -57,13 +90,15 @@ def test_delete_dashboard_by_name_and_path( def test_delete_dashboard_by_name_and_path_deletion_list_empty( self, dashboard_uid_and_id_by_name_and_folder_mock, call_the_api_mock ): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) dashboard: Dashboard = Dashboard(grafana_api_model=model) dashboard_uid_and_id_by_name_and_folder_mock.return_value = dict() call_the_api_mock.return_value = dict({"message": "error"}) with self.assertRaises(ValueError): - dashboard.delete_dashboard_by_name_and_path() + dashboard.delete_dashboard_by_name_and_path( + dashboard_name="test", dashboard_path="test" + ) @patch("src.grafana_api.utils.Utils.call_the_api") @patch( @@ -72,7 +107,7 @@ def test_delete_dashboard_by_name_and_path_deletion_list_empty( def test_delete_dashboard_by_name_and_path_deletion_not_possible( self, dashboard_uid_and_id_by_name_and_folder_mock, call_the_api_mock ): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) dashboard: Dashboard = Dashboard(grafana_api_model=model) dashboard_uid_and_id_by_name_and_folder_mock.return_value = dict( @@ -80,26 +115,41 @@ def test_delete_dashboard_by_name_and_path_deletion_not_possible( ) call_the_api_mock.return_value = dict({"message": "error"}) with self.assertRaises(Exception): - dashboard.delete_dashboard_by_name_and_path() + dashboard.delete_dashboard_by_name_and_path( + dashboard_name="test", dashboard_path="test" + ) @patch("src.grafana_api.utils.Utils.call_the_api") @patch("src.grafana_api.folder.Folder.get_folder_id_by_dashboard_path") def test_get_dashboard_uid_and_id_by_name_and_folder( self, folder_id_by_dashboard_path_mock, call_the_api_mock ): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) dashboard: Dashboard = Dashboard(grafana_api_model=model) folder_id_by_dashboard_path_mock.return_value = 1 call_the_api_mock.return_value = [{"uid": "test", "id": 10}] self.assertEqual( dict({"uid": "test", "id": 10}), - dashboard.get_dashboard_uid_and_id_by_name_and_folder(), + dashboard.get_dashboard_uid_and_id_by_name_and_folder( + dashboard_name="test", dashboard_path="test" + ), ) + def test_get_dashboard_uid_and_id_by_name_and_folder_bo_dashboard_name_defined( + self, + ): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) + dashboard: Dashboard = Dashboard(grafana_api_model=model) + + with self.assertRaises(ValueError): + dashboard.get_dashboard_uid_and_id_by_name_and_folder( + dashboard_name="", dashboard_path="test" + ) + @patch("src.grafana_api.utils.Utils.call_the_api") def test_get_dashboard_by_uid(self, call_the_api_mock): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) dashboard: Dashboard = Dashboard(grafana_api_model=model) call_the_api_mock.return_value = dict({"dashboard": "test"}) @@ -109,7 +159,7 @@ def test_get_dashboard_by_uid(self, call_the_api_mock): @patch("src.grafana_api.utils.Utils.call_the_api") def test_get_dashboard_by_uid_no_dashboard(self, call_the_api_mock): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) dashboard: Dashboard = Dashboard(grafana_api_model=model) call_the_api_mock.return_value = dict() @@ -117,7 +167,7 @@ def test_get_dashboard_by_uid_no_dashboard(self, call_the_api_mock): dashboard.get_dashboard_by_uid(uid="test") def test_get_dashboard_by_uid_no_uid(self): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) dashboard: Dashboard = Dashboard(grafana_api_model=model) with self.assertRaises(ValueError): @@ -125,7 +175,7 @@ def test_get_dashboard_by_uid_no_uid(self): @patch("src.grafana_api.utils.Utils.call_the_api") def test_get_dashboard_home(self, call_the_api_mock): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) dashboard: Dashboard = Dashboard(grafana_api_model=model) call_the_api_mock.return_value = dict({"dashboard": "test"}) @@ -133,7 +183,7 @@ def test_get_dashboard_home(self, call_the_api_mock): @patch("src.grafana_api.utils.Utils.call_the_api") def test_get_dashboard_home_no_dashboard(self, call_the_api_mock): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) dashboard: Dashboard = Dashboard(grafana_api_model=model) call_the_api_mock.return_value = dict() @@ -142,7 +192,7 @@ def test_get_dashboard_home_no_dashboard(self, call_the_api_mock): @patch("src.grafana_api.utils.Utils.call_the_api") def test_get_dashboard_tags(self, call_the_api_mock): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) dashboard: Dashboard = Dashboard(grafana_api_model=model) call_the_api_mock.return_value = list([{"term": "test", "count": 4}]) @@ -152,7 +202,7 @@ def test_get_dashboard_tags(self, call_the_api_mock): @patch("src.grafana_api.utils.Utils.call_the_api") def test_get_dashboard_tags_no_tags(self, call_the_api_mock): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) dashboard: Dashboard = Dashboard(grafana_api_model=model) call_the_api_mock.return_value = list() @@ -161,7 +211,7 @@ def test_get_dashboard_tags_no_tags(self, call_the_api_mock): @patch("src.grafana_api.utils.Utils.call_the_api") def test_get_dashboard_permissions(self, call_the_api_mock): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) dashboard: Dashboard = Dashboard(grafana_api_model=model) call_the_api_mock.return_value = list([{"role": "test", "count": 4}]) @@ -172,7 +222,7 @@ def test_get_dashboard_permissions(self, call_the_api_mock): @patch("src.grafana_api.utils.Utils.call_the_api") def test_get_dashboard_permissions_empty_list(self, call_the_api_mock): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) dashboard: Dashboard = Dashboard(grafana_api_model=model) call_the_api_mock.return_value = list() @@ -180,7 +230,7 @@ def test_get_dashboard_permissions_empty_list(self, call_the_api_mock): dashboard.get_dashboard_permissions(1) def test_get_dashboard_permissions_no_id(self): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) dashboard: Dashboard = Dashboard(grafana_api_model=model) with self.assertRaises(ValueError): @@ -188,7 +238,7 @@ def test_get_dashboard_permissions_no_id(self): @patch("src.grafana_api.utils.Utils.call_the_api") def test_update_dashboard_permissions(self, call_the_api_mock): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) dashboard: Dashboard = Dashboard(grafana_api_model=model) call_the_api_mock.return_value = dict( @@ -200,7 +250,7 @@ def test_update_dashboard_permissions(self, call_the_api_mock): @patch("src.grafana_api.utils.Utils.call_the_api") def test_update_dashboard_permissions_error_response(self, call_the_api_mock): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) dashboard: Dashboard = Dashboard(grafana_api_model=model) call_the_api_mock.return_value = dict({"message": "Error"}) @@ -208,7 +258,7 @@ def test_update_dashboard_permissions_error_response(self, call_the_api_mock): dashboard.update_dashboard_permissions(1, {"test": "test"}) def test_update_dashboard_permissions_no_uid(self): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) dashboard: Dashboard = Dashboard(grafana_api_model=model) with self.assertRaises(ValueError): @@ -216,7 +266,7 @@ def test_update_dashboard_permissions_no_uid(self): @patch("src.grafana_api.utils.Utils.call_the_api") def test_get_dashboard_versions(self, call_the_api_mock): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) dashboard: Dashboard = Dashboard(grafana_api_model=model) call_the_api_mock.return_value = list([{"id": "test"}]) @@ -224,7 +274,7 @@ def test_get_dashboard_versions(self, call_the_api_mock): @patch("src.grafana_api.utils.Utils.call_the_api") def test_get_dashboard_versions_error_response(self, call_the_api_mock): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) dashboard: Dashboard = Dashboard(grafana_api_model=model) call_the_api_mock.return_value = list() @@ -232,7 +282,7 @@ def test_get_dashboard_versions_error_response(self, call_the_api_mock): dashboard.get_dashboard_versions(1) def test_get_dashboard_versions_no_uid(self): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) dashboard: Dashboard = Dashboard(grafana_api_model=model) with self.assertRaises(ValueError): @@ -240,7 +290,7 @@ def test_get_dashboard_versions_no_uid(self): @patch("src.grafana_api.utils.Utils.call_the_api") def test_get_dashboard_version(self, call_the_api_mock): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) dashboard: Dashboard = Dashboard(grafana_api_model=model) call_the_api_mock.return_value = dict({"id": "test"}) @@ -248,7 +298,7 @@ def test_get_dashboard_version(self, call_the_api_mock): @patch("src.grafana_api.utils.Utils.call_the_api") def test_get_dashboard_version_error_response(self, call_the_api_mock): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) dashboard: Dashboard = Dashboard(grafana_api_model=model) call_the_api_mock.return_value = dict() @@ -256,7 +306,7 @@ def test_get_dashboard_version_error_response(self, call_the_api_mock): dashboard.get_dashboard_version(1, MagicMock()) def test_get_dashboard_version_no_uid(self): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) dashboard: Dashboard = Dashboard(grafana_api_model=model) with self.assertRaises(ValueError): @@ -264,7 +314,7 @@ def test_get_dashboard_version_no_uid(self): @patch("src.grafana_api.utils.Utils.call_the_api") def test_restore_dashboard_version(self, call_the_api_mock): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) dashboard: Dashboard = Dashboard(grafana_api_model=model) call_the_api_mock.return_value = dict({"status": "success"}) @@ -274,7 +324,7 @@ def test_restore_dashboard_version(self, call_the_api_mock): @patch("src.grafana_api.utils.Utils.call_the_api") def test_restore_dashboard_version_error_response(self, call_the_api_mock): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) dashboard: Dashboard = Dashboard(grafana_api_model=model) call_the_api_mock.return_value = dict({"status": "error"}) @@ -282,7 +332,7 @@ def test_restore_dashboard_version_error_response(self, call_the_api_mock): dashboard.restore_dashboard_version(1, dict({"version": 1})) def test_restore_dashboard_version_no_uid(self): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) dashboard: Dashboard = Dashboard(grafana_api_model=model) with self.assertRaises(ValueError): @@ -290,7 +340,7 @@ def test_restore_dashboard_version_no_uid(self): @patch("src.grafana_api.utils.Utils.call_the_api_non_json_output") def test_calculate_dashboard_diff(self, call_the_api_non_json_output_mock): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) dashboard: Dashboard = Dashboard(grafana_api_model=model) call_the_api_non_json_output_mock.return_value.status_code = 200 @@ -307,7 +357,7 @@ def test_calculate_dashboard_diff(self, call_the_api_non_json_output_mock): def test_calculate_dashboard_diff_error_response( self, call_the_api_non_json_output_mock ): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) dashboard: Dashboard = Dashboard(grafana_api_model=model) call_the_api_non_json_output_mock.status_code.return_value = 400 @@ -318,14 +368,14 @@ def test_calculate_dashboard_diff_error_response( ) def test_calculate_dashboard_diff_no_dashboard_id_and_version_base(self): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) dashboard: Dashboard = Dashboard(grafana_api_model=model) with self.assertRaises(ValueError): dashboard.calculate_dashboard_diff({}, MagicMock()) def test_calculate_dashboard_diff_no_valid_diff_type(self): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) dashboard: Dashboard = Dashboard(grafana_api_model=model) with self.assertRaises(ValueError): diff --git a/tests/unittests/test_folder.py b/tests/unittests/test_folder.py index 5423eb0..7a08d4f 100644 --- a/tests/unittests/test_folder.py +++ b/tests/unittests/test_folder.py @@ -8,7 +8,7 @@ class FolderTestCase(TestCase): @patch("src.grafana_api.utils.Utils.call_the_api") def test_get_folders(self, call_the_api_mock): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) folder: Folder = Folder(grafana_api_model=model) call_the_api_mock.return_value = list([{"title": None, "id": 12}]) @@ -16,7 +16,7 @@ def test_get_folders(self, call_the_api_mock): @patch("src.grafana_api.utils.Utils.call_the_api") def test_get_folders_error_response(self, call_the_api_mock): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) folder: Folder = Folder(grafana_api_model=model) call_the_api_mock.return_value = list() @@ -25,7 +25,7 @@ def test_get_folders_error_response(self, call_the_api_mock): @patch("src.grafana_api.utils.Utils.call_the_api") def test_get_folder_by_uid(self, call_the_api_mock): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) folder: Folder = Folder(grafana_api_model=model) call_the_api_mock.return_value = dict({"title": None, "id": 12}) @@ -35,7 +35,7 @@ def test_get_folder_by_uid(self, call_the_api_mock): @patch("src.grafana_api.utils.Utils.call_the_api") def test_get_folder_by_uid_no_uid(self, call_the_api_mock): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) folder: Folder = Folder(grafana_api_model=model) call_the_api_mock.return_value = dict() @@ -44,7 +44,7 @@ def test_get_folder_by_uid_no_uid(self, call_the_api_mock): @patch("src.grafana_api.utils.Utils.call_the_api") def test_get_folder_by_uid_error_response(self, call_the_api_mock): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) folder: Folder = Folder(grafana_api_model=model) call_the_api_mock.return_value = dict() @@ -53,7 +53,7 @@ def test_get_folder_by_uid_error_response(self, call_the_api_mock): @patch("src.grafana_api.utils.Utils.call_the_api") def test_get_folder_by_id(self, call_the_api_mock): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) folder: Folder = Folder(grafana_api_model=model) call_the_api_mock.return_value = dict({"title": None, "id": 12}) @@ -61,7 +61,7 @@ def test_get_folder_by_id(self, call_the_api_mock): @patch("src.grafana_api.utils.Utils.call_the_api") def test_get_folder_by_id_no_id(self, call_the_api_mock): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) folder: Folder = Folder(grafana_api_model=model) call_the_api_mock.return_value = dict() @@ -70,7 +70,7 @@ def test_get_folder_by_id_no_id(self, call_the_api_mock): @patch("src.grafana_api.utils.Utils.call_the_api") def test_get_folder_by_id_error_response(self, call_the_api_mock): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) folder: Folder = Folder(grafana_api_model=model) call_the_api_mock.return_value = dict() @@ -79,7 +79,7 @@ def test_get_folder_by_id_error_response(self, call_the_api_mock): @patch("src.grafana_api.utils.Utils.call_the_api") def test_create_folder(self, call_the_api_mock): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) folder: Folder = Folder(grafana_api_model=model) call_the_api_mock.return_value = dict({"title": None, "id": 12}) @@ -89,7 +89,7 @@ def test_create_folder(self, call_the_api_mock): @patch("src.grafana_api.utils.Utils.call_the_api") def test_create_folder_no_title(self, call_the_api_mock): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) folder: Folder = Folder(grafana_api_model=model) call_the_api_mock.return_value = dict() @@ -98,7 +98,7 @@ def test_create_folder_no_title(self, call_the_api_mock): @patch("src.grafana_api.utils.Utils.call_the_api") def test_create_folder_error_response(self, call_the_api_mock): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) folder: Folder = Folder(grafana_api_model=model) call_the_api_mock.return_value = dict() @@ -107,7 +107,7 @@ def test_create_folder_error_response(self, call_the_api_mock): @patch("src.grafana_api.utils.Utils.call_the_api") def test_update_folder(self, call_the_api_mock): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) folder: Folder = Folder(grafana_api_model=model) call_the_api_mock.return_value = dict({"title": None, "id": 12}) @@ -117,7 +117,7 @@ def test_update_folder(self, call_the_api_mock): @patch("src.grafana_api.utils.Utils.call_the_api") def test_update_folder_overwrite_true(self, call_the_api_mock): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) folder: Folder = Folder(grafana_api_model=model) call_the_api_mock.return_value = dict({"title": None, "id": 12}) @@ -128,7 +128,7 @@ def test_update_folder_overwrite_true(self, call_the_api_mock): @patch("src.grafana_api.utils.Utils.call_the_api") def test_update_folder_no_title(self, call_the_api_mock): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) folder: Folder = Folder(grafana_api_model=model) call_the_api_mock.return_value = dict() @@ -137,7 +137,7 @@ def test_update_folder_no_title(self, call_the_api_mock): @patch("src.grafana_api.utils.Utils.call_the_api") def test_update_folder_error_response(self, call_the_api_mock): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) folder: Folder = Folder(grafana_api_model=model) call_the_api_mock.return_value = dict() @@ -146,7 +146,7 @@ def test_update_folder_error_response(self, call_the_api_mock): @patch("src.grafana_api.utils.Utils.call_the_api") def test_delete_folder(self, call_the_api_mock): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) folder: Folder = Folder(grafana_api_model=model) call_the_api_mock.return_value = dict({"message": "Folder deleted"}) @@ -154,7 +154,7 @@ def test_delete_folder(self, call_the_api_mock): @patch("src.grafana_api.utils.Utils.call_the_api") def test_delete_folder_no_uid(self, call_the_api_mock): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) folder: Folder = Folder(grafana_api_model=model) call_the_api_mock.return_value = dict() @@ -163,7 +163,7 @@ def test_delete_folder_no_uid(self, call_the_api_mock): @patch("src.grafana_api.utils.Utils.call_the_api") def test_delete_folder_error_response(self, call_the_api_mock): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) folder: Folder = Folder(grafana_api_model=model) call_the_api_mock.return_value = dict({"message": "error"}) @@ -172,7 +172,7 @@ def test_delete_folder_error_response(self, call_the_api_mock): @patch("src.grafana_api.utils.Utils.call_the_api") def test_get_folder_permissions(self, call_the_api_mock): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) folder: Folder = Folder(grafana_api_model=model) call_the_api_mock.return_value = list([{"id": "test"}]) @@ -180,7 +180,7 @@ def test_get_folder_permissions(self, call_the_api_mock): @patch("src.grafana_api.utils.Utils.call_the_api") def test_get_folder_permissions_no_uid(self, call_the_api_mock): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) folder: Folder = Folder(grafana_api_model=model) call_the_api_mock.return_value = list() @@ -189,7 +189,7 @@ def test_get_folder_permissions_no_uid(self, call_the_api_mock): @patch("src.grafana_api.utils.Utils.call_the_api") def test_get_folder_permissions_error_response(self, call_the_api_mock): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) folder: Folder = Folder(grafana_api_model=model) call_the_api_mock.return_value = list([{"test": "test"}]) @@ -198,7 +198,7 @@ def test_get_folder_permissions_error_response(self, call_the_api_mock): @patch("src.grafana_api.utils.Utils.call_the_api") def test_update_folder_permissions(self, call_the_api_mock): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) folder: Folder = Folder(grafana_api_model=model) call_the_api_mock.return_value = dict({"message": "Folder permissions updated"}) @@ -208,7 +208,7 @@ def test_update_folder_permissions(self, call_the_api_mock): @patch("src.grafana_api.utils.Utils.call_the_api") def test_update_folder_permissions_no_uid(self, call_the_api_mock): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) folder: Folder = Folder(grafana_api_model=model) call_the_api_mock.return_value = dict() @@ -217,7 +217,7 @@ def test_update_folder_permissions_no_uid(self, call_the_api_mock): @patch("src.grafana_api.utils.Utils.call_the_api") def test_update_folder_permissions_error_response(self, call_the_api_mock): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) folder: Folder = Folder(grafana_api_model=model) call_the_api_mock.return_value = dict({"message": "test"}) @@ -226,28 +226,37 @@ def test_update_folder_permissions_error_response(self, call_the_api_mock): @patch("src.grafana_api.folder.Folder.get_all_folder_ids_and_names") def test_get_folder_id_by_dashboard_path(self, all_folder_ids_and_names_mock): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) folder: Folder = Folder(grafana_api_model=model) - all_folder_ids_and_names_mock.return_value = list([{"title": None, "id": 12}]) - self.assertEqual(12, folder.get_folder_id_by_dashboard_path()) + all_folder_ids_and_names_mock.return_value = list([{"title": "test", "id": 12}]) + self.assertEqual( + 12, folder.get_folder_id_by_dashboard_path(dashboard_path="test") + ) + + def test_get_folder_id_by_dashboard_path_no_dashboard_path_defined(self): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) + folder: Folder = Folder(grafana_api_model=model) + + with self.assertRaises(ValueError): + folder.get_folder_id_by_dashboard_path(dashboard_path="") @patch("src.grafana_api.folder.Folder.get_all_folder_ids_and_names") def test_get_folder_id_by_dashboard_path_no_title_match( self, all_folder_ids_and_names_mock ): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) folder: Folder = Folder(grafana_api_model=model) all_folder_ids_and_names_mock.return_value = list( - [{"title": "test", "id": "xty13y"}] + [{"title": None, "id": "xty13y"}] ) with self.assertRaises(Exception): - folder.get_folder_id_by_dashboard_path() + folder.get_folder_id_by_dashboard_path(dashboard_path="test") @patch("src.grafana_api.utils.Utils.call_the_api") def test_get_all_folder_ids_and_names(self, call_the_api_mock): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) folder: Folder = Folder(grafana_api_model=model) call_the_api_mock.return_value = list( diff --git a/tests/unittests/test_model.py b/tests/unittests/test_model.py index 8e77185..3807ab2 100644 --- a/tests/unittests/test_model.py +++ b/tests/unittests/test_model.py @@ -18,9 +18,7 @@ def test_requests_methods_init(self): class APIModelTestCase(TestCase): def test_api_model_init(self): - model = APIModel( - host="test", token="test", dashboard_name="test", dashboard_path="test" - ) + model = APIModel(host="test", token="test") - self.assertEqual(None, model.message) self.assertEqual("test", model.host) + self.assertEqual("test", model.token) diff --git a/tests/unittests/test_search.py b/tests/unittests/test_search.py index a15e4cf..b9bf11e 100644 --- a/tests/unittests/test_search.py +++ b/tests/unittests/test_search.py @@ -8,7 +8,7 @@ class SearchTestCase(TestCase): @patch("src.grafana_api.utils.Utils.call_the_api") def test_search(self, call_the_api_mock): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) search: Search = Search(grafana_api_model=model) call_the_api_mock.return_value = ["test"] @@ -16,8 +16,8 @@ def test_search(self, call_the_api_mock): self.assertEqual(["test"], search.search(search_query=MagicMock())) @patch("src.grafana_api.utils.Utils.call_the_api") - def test_search_unvalid_empty_list(self, call_the_api_mock): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + def test_search_invalid_empty_list(self, call_the_api_mock): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) search: Search = Search(grafana_api_model=model) call_the_api_mock.return_value = list() @@ -26,8 +26,8 @@ def test_search_unvalid_empty_list(self, call_the_api_mock): search.search(search_query=MagicMock()) @patch("src.grafana_api.utils.Utils.call_the_api") - def test_search_unvalid_output(self, call_the_api_mock): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + def test_search_invalid_output(self, call_the_api_mock): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) search: Search = Search(grafana_api_model=model) call_the_api_mock.side_effect = Exception diff --git a/tests/unittests/test_utils.py b/tests/unittests/test_utils.py index 1f4fd16..077a613 100644 --- a/tests/unittests/test_utils.py +++ b/tests/unittests/test_utils.py @@ -11,7 +11,7 @@ class UtilsTestCase(TestCase): def test_call_the_api_non_method(self): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) utils: Utils = Utils(grafana_api_model=model) with self.assertRaises(Exception): @@ -19,7 +19,7 @@ def test_call_the_api_non_method(self): @patch("requests.get") def test_call_the_api_get_valid(self, get_mock): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) utils: Utils = Utils(grafana_api_model=model) mock: Mock = Mock() @@ -33,15 +33,41 @@ def test_call_the_api_get_valid(self, get_mock): ) def test_call_the_api_get_not_valid(self): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) utils: Utils = Utils(grafana_api_model=model) with self.assertRaises(MissingSchema): utils.call_the_api(api_call=MagicMock(), method=RequestsMethods.GET) + @patch("requests.put") + def test_call_the_api_put_valid(self, put_mock): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) + utils: Utils = Utils(grafana_api_model=model) + + mock: Mock = Mock() + mock.json = Mock(return_value={"status": "success"}) + + put_mock.return_value = mock + + self.assertEqual( + "success", + utils.call_the_api( + api_call=MagicMock(), + method=RequestsMethods.PUT, + json_complete=MagicMock(), + )["status"], + ) + + def test_call_the_api_put_not_valid(self): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) + utils: Utils = Utils(grafana_api_model=model) + + with self.assertRaises(Exception): + utils.call_the_api(api_call=MagicMock(), method=RequestsMethods.PUT) + @patch("requests.post") def test_call_the_api_post_valid(self, post_mock): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) utils: Utils = Utils(grafana_api_model=model) mock: Mock = Mock() @@ -59,7 +85,7 @@ def test_call_the_api_post_valid(self, post_mock): ) def test_call_the_api_post_not_valid(self): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) utils: Utils = Utils(grafana_api_model=model) with self.assertRaises(MissingSchema): @@ -70,7 +96,7 @@ def test_call_the_api_post_not_valid(self): ) def test_call_the_api_post_no_data(self): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) utils: Utils = Utils(grafana_api_model=model) with self.assertRaises(Exception): @@ -78,7 +104,7 @@ def test_call_the_api_post_no_data(self): @patch("requests.delete") def test_call_the_api_delete_valid(self, delete_mock): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) utils: Utils = Utils(grafana_api_model=model) mock: Mock = Mock() @@ -94,14 +120,14 @@ def test_call_the_api_delete_valid(self, delete_mock): ) def test_call_the_api_delete_not_valid(self): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) utils: Utils = Utils(grafana_api_model=model) with self.assertRaises(Exception): utils.call_the_api(api_call=MagicMock(), method=RequestsMethods.DELETE) def test_call_the_api_non_json_output_non_method(self): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) utils: Utils = Utils(grafana_api_model=model) with self.assertRaises(Exception): @@ -109,7 +135,7 @@ def test_call_the_api_non_json_output_non_method(self): @patch("requests.get") def test_call_the_api_non_json_output_get_valid(self, get_mock): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) utils: Utils = Utils(grafana_api_model=model) get_mock.return_value.text = "success" @@ -120,7 +146,7 @@ def test_call_the_api_non_json_output_get_valid(self, get_mock): ) def test_call_the_api_non_json_output_get_not_valid(self): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) utils: Utils = Utils(grafana_api_model=model) with self.assertRaises(MissingSchema): @@ -130,7 +156,7 @@ def test_call_the_api_non_json_output_get_not_valid(self): @patch("requests.post") def test_call_the_api_non_json_output_post_valid(self, post_mock): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) utils: Utils = Utils(grafana_api_model=model) post_mock.return_value.text = "success" @@ -145,7 +171,7 @@ def test_call_the_api_non_json_output_post_valid(self, post_mock): ) def test_call_the_api_non_json_output_post_not_valid(self): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) utils: Utils = Utils(grafana_api_model=model) with self.assertRaises(MissingSchema): @@ -156,7 +182,7 @@ def test_call_the_api_non_json_output_post_not_valid(self): ) def test_call_the_api_non_json_output_post_no_data(self): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) utils: Utils = Utils(grafana_api_model=model) with self.assertRaises(Exception): @@ -166,7 +192,7 @@ def test_call_the_api_non_json_output_post_no_data(self): @patch("requests.delete") def test_call_the_api_non_json_output_delete_valid(self, delete_mock): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) utils: Utils = Utils(grafana_api_model=model) delete_mock.return_value.text = "Deletion successful" @@ -179,7 +205,7 @@ def test_call_the_api_non_json_output_delete_valid(self, delete_mock): ) def test_call_the_api_non_json_output_delete_not_valid(self): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) utils: Utils = Utils(grafana_api_model=model) with self.assertRaises(Exception): @@ -188,7 +214,7 @@ def test_call_the_api_non_json_output_delete_not_valid(self): ) def test_check_the_api_call_response(self): - model: APIModel = APIModel(host=MagicMock(), token=MagicMock(), message="Test") + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) utils: Utils = Utils(grafana_api_model=model) with self.assertRaises(requests.exceptions.ConnectionError): From 0ca173014043998433aa7342ff7f0768650320de Mon Sep 17 00:00:00 2001 From: Pascal Zimmermann Date: Sat, 15 Jan 2022 23:19:08 +0100 Subject: [PATCH 14/19] WIP --- .github/workflows/integrationtest.yml | 2 +- README.md | 61 +++++++++++++++++++++++---- 2 files changed, 53 insertions(+), 10 deletions(-) diff --git a/.github/workflows/integrationtest.yml b/.github/workflows/integrationtest.yml index c32b7cd..11ff708 100644 --- a/.github/workflows/integrationtest.yml +++ b/.github/workflows/integrationtest.yml @@ -17,7 +17,7 @@ jobs: - name: Install the requirements run: pip install -r requirements.txt - - name: Execute the unittests + - name: Execute the integrationtests run: python3 -m unittest discover tests/integrationtest env: GRAFANA_HOST: ${{ secrets.GRAFANA_HOST }} diff --git a/README.md b/README.md index 9126019..72ea3ad 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,6 @@ The repository includes an SDK for the Grafana API ## TODO - Documentation -- PYPI support ## Currently, supported features @@ -14,18 +13,62 @@ The repository includes an SDK for the Grafana API - Create/ Update a dashboard - Delete a dashboard -### Feature timeline +### Folder -The following table describes the plan to implement the rest of the Grafana API functionality +### Search -| API endpoint group | Implementation week | Maintainer | PR | -|:------------------:|:-------------------:|:----------:|:--:| -|||| -## Installation & Requirements -### Programs & tools to install +## Feature timeline -- requests +The following table describes the plan to implement the rest of the Grafana API functionality. Please, open an issue and vote them up, if you prefer a faster implementation of an API functionality. + +| API endpoint group | Implementation week | Maintainer | PR | State | +|:------------------:|:-------------------:|:----------:|:--:|:-----:| +| [Admin HTTP API](https://grafana.com/docs/grafana/latest/http_api/admin/) | | | | | +| [Alerting HTTP API](https://grafana.com/docs/grafana/latest/http_api/alerting/) | 4 | [ZPascal](https://github.com/ZPascal) | | Planned | +| [Alerting Notification Channels HTTP API](https://grafana.com/docs/grafana/latest/http_api/alerting_notification_channels/) | 4 | [ZPascal](https://github.com/ZPascal) | | Planned | +| [Annotations HTTP API](https://grafana.com/docs/grafana/latest/http_api/annotations/) | | | | | +| [Authentication HTTP API](https://grafana.com/docs/grafana/latest/http_api/auth/) | | | | | +| [Data source HTTP API](https://grafana.com/docs/grafana/latest/http_api/data_source/) | 5 | [ZPascal](https://github.com/ZPascal) | | Planned | +| [Datasource Permissions HTTP API](https://grafana.com/docs/grafana/latest/http_api/datasource_permissions/) | | | | | +| [External Group Sync HTTP API](https://grafana.com/docs/grafana/latest/http_api/external_group_sync/) | | | | | +| [Fine-grained access control HTTP API](https://grafana.com/docs/grafana/latest/http_api/access_control/) | | | | | +| [HTTP Preferences API](https://grafana.com/docs/grafana/latest/http_api/preferences/) | | | | | +| [HTTP Snapshot API](https://grafana.com/docs/grafana/latest/http_api/snapshot/) | | | | | +| [Library Element HTTP API](https://grafana.com/docs/grafana/latest/http_api/library_element/) | | | | | +| [Licensing HTTP API](https://grafana.com/docs/grafana/latest/http_api/licensing/) | | | | | +| [Organization HTTP API](https://grafana.com/docs/grafana/latest/http_api/org/) | | | | | +| [Other HTTP API](https://grafana.com/docs/grafana/latest/http_api/other/) | | | | | +| [Playlist HTTP API](https://grafana.com/docs/grafana/latest/http_api/playlist/) | | | | | +| [Reporting API](https://grafana.com/docs/grafana/latest/http_api/reporting/) | | | | | +| [Short URL HTTP API](https://grafana.com/docs/grafana/latest/http_api/short_url/) | | | | | +| [Team HTTP API](https://grafana.com/docs/grafana/latest/http_api/team/) | | | | | +| [User HTTP API](https://grafana.com/docs/grafana/latest/http_api/user/) | | | | | + +## Installation + +`pip install grafana-api-sdk` + +## Example + +```python +import json + +from grafana_api.model import APIModel +from grafana_api.dashboard import Dashboard + +model: APIModel = APIModel(host="test", token="test") + +dashboard: Dashboard = Dashboard(model) + +with open("/tmp/test/test.json") as file: + json_dashboard = json.load(file) + +dashboard.create_or_update_dashboard(message="Create a new test dashboard", dashboard_json=json_dashboard, dashboard_path="test") +``` + +## Templating +If you want to template your JSON document based on a predefined folder structure you can check out one of my other [project](https://github.com/ZPascal/grafana_dashboard_templater) and integrate the functionality inside your code. ## Contribution If you would like to contribute something, have an improvement request, or want to make a change inside the code, please open a pull request. From 1a72050482f123586485ae5a8009c0f6bb707e31 Mon Sep 17 00:00:00 2001 From: Pascal Zimmermann Date: Sun, 16 Jan 2022 00:00:36 +0100 Subject: [PATCH 15/19] Remove the coverage.svg and update the README.md --- README.md | 26 ++++++++++++++++++-------- docs/coverage.svg | 21 --------------------- 2 files changed, 18 insertions(+), 29 deletions(-) delete mode 100644 docs/coverage.svg diff --git a/README.md b/README.md index 72ea3ad..4b02816 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,32 @@ # grafana_api_sdk -The repository includes an SDK for the Grafana API - -## TODO -- Documentation +The repository includes an SDK for the Grafana API. It's possible to communicate with the Grafana API endpoints. Another feature of the SDK is the possibility to specify the used folder for the dashboard. ## Currently, supported features ### Dashboard -- Get folder id by dashboard path -- Get all folder ids and folder names -- Specify the grafana dashboard folder - Create/ Update a dashboard - Delete a dashboard +- Get permissions of a dashboard +- Update the permissions of a dashboard +- Get all dashboard versions +- Get dashboard version of a specific dashboard +- Restore a dashboard version of a specific dashboard +- Compare two dashboard versions and extract the diff between booth dashboards ### Folder +- Get folder id by dashboard path +- Get all folder ids and folder names +- Get all folders +- Get folder by uid +- Get folder by id +- Create a folder +- Update a folder +- Delete a folder +- Get permissions for a folder +- Update permissions for a folder ### Search - +- Execute a custom query against the Grafana search endpoint ## Feature timeline diff --git a/docs/coverage.svg b/docs/coverage.svg deleted file mode 100644 index a9be2c5..0000000 --- a/docs/coverage.svg +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - coverage - coverage - 29% - 29% - - From e276c213abb27e9527d78bb8c72c758a768cde9d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 15 Jan 2022 23:01:15 +0000 Subject: [PATCH 16/19] Add coverage badge --- docs/coverage.svg | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 docs/coverage.svg diff --git a/docs/coverage.svg b/docs/coverage.svg new file mode 100644 index 0000000..f4f15fa --- /dev/null +++ b/docs/coverage.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + coverage + coverage + 18% + 18% + + From bde5c1aab0acc0dc680454fd448d685f32f38901 Mon Sep 17 00:00:00 2001 From: Pascal Zimmermann Date: Sun, 16 Jan 2022 18:14:26 +0100 Subject: [PATCH 17/19] WIP --- .coveragerc | 4 --- .github/workflows/pull-request-checks.yml | 2 +- docs/coverage.svg | 21 --------------- src/grafana_api/folder.py | 10 +++++--- src/grafana_api/utils.py | 6 +++++ tests/unittests/test_folder.py | 31 +++++++++++++++++++---- tests/unittests/test_utils.py | 14 ++++++++++ 7 files changed, 53 insertions(+), 35 deletions(-) delete mode 100644 docs/coverage.svg diff --git a/.coveragerc b/.coveragerc index 3e4ab6e..954413c 100644 --- a/.coveragerc +++ b/.coveragerc @@ -2,15 +2,11 @@ branch = True omit = */setup.py - tests/* - tests/integrationtest/* source = . [report] omit = */setup.py - tests/* - tests/integrationtest/* fail_under = 80 show_missing = True skip_covered = False diff --git a/.github/workflows/pull-request-checks.yml b/.github/workflows/pull-request-checks.yml index 11dcb92..8fd1726 100644 --- a/.github/workflows/pull-request-checks.yml +++ b/.github/workflows/pull-request-checks.yml @@ -76,7 +76,7 @@ jobs: run: pip install -r requirements.txt && pip install pytest pytest-cov coverage-badge - name: Generate the coverage report - run: export PYTHONPATH=$PWD && pytest --junitxml=pytest.xml --cov=. tests/ | tee pytest-coverage.txt + run: export PYTHONPATH=$PWD && pytest --junitxml=pytest.xml --cov=. tests/unittests | tee pytest-coverage.txt - name: Execute the coverage checks uses: MishaKav/pytest-coverage-comment@v1.1.16 diff --git a/docs/coverage.svg b/docs/coverage.svg deleted file mode 100644 index f4f15fa..0000000 --- a/docs/coverage.svg +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - - - - - coverage - coverage - 18% - 18% - - diff --git a/src/grafana_api/folder.py b/src/grafana_api/folder.py index 1d5f864..904a264 100644 --- a/src/grafana_api/folder.py +++ b/src/grafana_api/folder.py @@ -82,7 +82,8 @@ def create_folder(self, title: str, uid: str = None) -> dict: if len(title) != 0: folder_information: dict = dict() folder_information.update({"title": title}) - if uid is None or len(uid) != 0: + + if uid is not None and len(uid) != 0: folder_information.update({"uid": uid}) api_call: dict = Utils(self.grafana_api_model).call_the_api( @@ -101,7 +102,7 @@ def create_folder(self, title: str, uid: str = None) -> dict: raise ValueError def update_folder( - self, uid: str, title: str, version: int = 0, overwrite: bool = False + self, title: str, uid: str = None, version: int = 0, overwrite: bool = False ) -> dict: """The method includes a functionality to update a folder information inside the organization specified \ by the uid, the title, the version of the folder or if folder information be overwritten @@ -119,9 +120,10 @@ def update_folder( if len(title) != 0 and version != 0: folder_information: dict = dict() folder_information.update({"title": title}) - if len(uid) != 0: + folder_information.update({"overwrite": overwrite}) + + if uid is not None and len(uid) != 0: folder_information.update({"uid": uid}) - folder_information.update({"overwrite": overwrite}) if version is not None: folder_information.update({"version": version}) diff --git a/src/grafana_api/utils.py b/src/grafana_api/utils.py index e2b2dfa..b63d42b 100644 --- a/src/grafana_api/utils.py +++ b/src/grafana_api/utils.py @@ -63,6 +63,9 @@ def call_the_api( return Utils.__check_the_api_call_response( requests.delete(api_url, headers=headers).json() ) + else: + logging.error("Please define a valid method.") + raise Exception except Exception as e: raise e @@ -103,6 +106,9 @@ def call_the_api_non_json_output( return Utils.__check_the_api_call_response( requests.delete(api_url, headers=headers) ) + else: + logging.error("Please define a valid method.") + raise Exception except Exception as e: raise e diff --git a/tests/unittests/test_folder.py b/tests/unittests/test_folder.py index 7a08d4f..9f5efa5 100644 --- a/tests/unittests/test_folder.py +++ b/tests/unittests/test_folder.py @@ -84,7 +84,17 @@ def test_create_folder(self, call_the_api_mock): call_the_api_mock.return_value = dict({"title": None, "id": 12}) self.assertEqual( - dict({"title": None, "id": 12}), folder.create_folder("test", "test") + dict({"title": None, "id": 12}), folder.create_folder("test") + ) + + @patch("src.grafana_api.utils.Utils.call_the_api") + def test_create_folder_specified_uid(self, call_the_api_mock): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) + folder: Folder = Folder(grafana_api_model=model) + + call_the_api_mock.return_value = dict({"title": None, "id": 12, "uid": "test"}) + self.assertEqual( + dict({"title": None, "id": 12, "uid": "test"}), folder.create_folder("test", "test") ) @patch("src.grafana_api.utils.Utils.call_the_api") @@ -110,9 +120,20 @@ def test_update_folder(self, call_the_api_mock): model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) folder: Folder = Folder(grafana_api_model=model) - call_the_api_mock.return_value = dict({"title": None, "id": 12}) + call_the_api_mock.return_value = dict({"title": "test1", "id": 12}) self.assertEqual( - dict({"title": None, "id": 12}), folder.update_folder("test", "test", 10) + dict({"title": "test1", "id": 12}), folder.update_folder("test", "test1", 10) + ) + + @patch("src.grafana_api.utils.Utils.call_the_api") + def test_update_folder_no_uid(self, call_the_api_mock): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) + folder: Folder = Folder(grafana_api_model=model) + + call_the_api_mock.return_value = dict({"title": "test", "id": 12}) + self.assertEqual( + dict({"title": "test", "id": 12}), + folder.update_folder("test", overwrite=True), ) @patch("src.grafana_api.utils.Utils.call_the_api") @@ -120,9 +141,9 @@ def test_update_folder_overwrite_true(self, call_the_api_mock): model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) folder: Folder = Folder(grafana_api_model=model) - call_the_api_mock.return_value = dict({"title": None, "id": 12}) + call_the_api_mock.return_value = dict({"title": "test", "id": 12}) self.assertEqual( - dict({"title": None, "id": 12}), + dict({"title": "test", "id": 12}), folder.update_folder("test", "test", overwrite=True), ) diff --git a/tests/unittests/test_utils.py b/tests/unittests/test_utils.py index 077a613..28a9b25 100644 --- a/tests/unittests/test_utils.py +++ b/tests/unittests/test_utils.py @@ -17,6 +17,13 @@ def test_call_the_api_non_method(self): with self.assertRaises(Exception): utils.call_the_api(api_call=MagicMock(), method=None) + def test_call_the_api_non_valid_method(self): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) + utils: Utils = Utils(grafana_api_model=model) + + with self.assertRaises(Exception): + utils.call_the_api(api_call=MagicMock(), method=MagicMock()) + @patch("requests.get") def test_call_the_api_get_valid(self, get_mock): model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) @@ -133,6 +140,13 @@ def test_call_the_api_non_json_output_non_method(self): with self.assertRaises(Exception): utils.call_the_api_non_json_output(api_call=MagicMock(), method=None) + def test_call_the_api_non_json_output_non__valid_method(self): + model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) + utils: Utils = Utils(grafana_api_model=model) + + with self.assertRaises(Exception): + utils.call_the_api_non_json_output(api_call=MagicMock(), method=MagicMock()) + @patch("requests.get") def test_call_the_api_non_json_output_get_valid(self, get_mock): model: APIModel = APIModel(host=MagicMock(), token=MagicMock()) From 9ccbc2fa5ae89c5d499fa68fad7094b0a252aadc Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 16 Jan 2022 17:15:28 +0000 Subject: [PATCH 18/19] Add coverage badge --- docs/coverage.svg | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 docs/coverage.svg diff --git a/docs/coverage.svg b/docs/coverage.svg new file mode 100644 index 0000000..e5db27c --- /dev/null +++ b/docs/coverage.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + coverage + coverage + 100% + 100% + + From bbca43ecee58bcec7724a4f81535a641652ac734 Mon Sep 17 00:00:00 2001 From: Pascal Zimmermann Date: Sun, 16 Jan 2022 18:42:53 +0100 Subject: [PATCH 19/19] WIP --- README.md | 2 +- setup.py | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4b02816..2c52760 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# grafana_api_sdk +# Grafana API SDK ![Coverage report](https://github.com/ZPascal/grafana_api_sdk/blob/main/docs/coverage.svg) The repository includes an SDK for the Grafana API. It's possible to communicate with the Grafana API endpoints. Another feature of the SDK is the possibility to specify the used folder for the dashboard. ## Currently, supported features diff --git a/setup.py b/setup.py index ecdea65..5f672a8 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,10 @@ import setuptools with open("README.md", "r", encoding="utf-8") as fh: - long_description = fh.read() + coverage_string: str = "![Coverage report](https://github.com/ZPascal/grafana_api_sdk/blob/main/docs/coverage.svg)" + long_description: str = fh.read() + +long_description = long_description.replace(coverage_string, "") setuptools.setup( name="grafana-api-sdk",