From a08163edee32856355df78474a91666a74ff7b26 Mon Sep 17 00:00:00 2001 From: Mudasir Mirza Date: Thu, 3 Mar 2022 00:45:56 +0400 Subject: [PATCH] Adding NewRelic Datasource with count and comparison NRQL support --- .../assets/images/db-logos/newrelicgql.png | Bin 0 -> 15345 bytes redash/query_runner/newrelic.py | 156 ++++++++++++++++++ redash/settings/__init__.py | 1 + 3 files changed, 157 insertions(+) create mode 100644 client/app/assets/images/db-logos/newrelicgql.png create mode 100644 redash/query_runner/newrelic.py diff --git a/client/app/assets/images/db-logos/newrelicgql.png b/client/app/assets/images/db-logos/newrelicgql.png new file mode 100644 index 0000000000000000000000000000000000000000..145df1ca081d885c432d6812283d83b9e57a15b6 GIT binary patch literal 15345 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 literal 0 HcmV?d00001 diff --git a/redash/query_runner/newrelic.py b/redash/query_runner/newrelic.py new file mode 100644 index 0000000000..b051722fa9 --- /dev/null +++ b/redash/query_runner/newrelic.py @@ -0,0 +1,156 @@ +import json +import logging +from collections import OrderedDict + + +from redash.query_runner import * +from redash.utils import json_dumps, json_loads + + +logger = logging.getLogger(__name__) + +# TODO: make this more general and move into __init__.py +class ResultSet(object): + def __init__(self): + self.columns = OrderedDict() + self.rows = [] + + def add_row(self, row): + for key in row.keys(): + self.add_column(key) + + self.rows.append(row) + + def add_column(self, column, column_type=TYPE_STRING): + if column not in self.columns: + self.columns[column] = { + "name": column, + "type": column_type, + "friendly_name": column, + } + + def to_json(self): + return json_dumps({"rows": self.rows, "columns": list(self.columns.values())}) + + def merge(self, set): + self.rows = self.rows + set.rows + + +def pct_change(current, previous): + diff = current - previous + change = 0 + try: + if diff > 0: + change = (diff / current) * 100 + elif diff < 0: + diff = previous - current + change = -((diff / current) * 100) + except ZeroDivisionError: + return float("inf") + return float("{:.2f}".format(change)) + + +def parse_comparision(data): + results = ResultSet() + try: + nested_data = data.get("data").get("actor").get("account").get("nrql").get("results") + except (KeyError, ValueError) as err: + logger.error("Error Raised: %s", err) + else: + data_dict = {} + for rows in nested_data: + try: + data_dict[rows["comparison"]] = rows["count"] + except (KeyError, IndexError) as err: + logger.error(f"Error adding data to dictionary, err: {err}") + if len(data_dict) >= 1: + data_dict['change'] = pct_change(data_dict.get('current'), data_dict.get('previous')) + results.add_row(data_dict) + return results + + +def parse_count(data): + results = ResultSet() + try: + nested_data = data.get("data").get("actor").get("account").get("nrql").get("results")[0] + except (KeyError, ValueError) as err: + logger.error("Error Raised: %s", err) + else: + key_name = list(nested_data.keys())[0] + data_count = list(nested_data.values())[0] + results.add_row({key_name: data_count}) + return results + + +class NewRelicGQL(BaseHTTPQueryRunner): + should_annotate_query = False + response_error = "NewRelic returned unexpected status code" + + @classmethod + def configuration_schema(cls): + return { + "type": "object", + "properties": { + "nr_account_id": {"type": "string", "title": "NewRelic Account ID"}, + "url": {"type": "string", "title": "API URL"}, + "token": {"type": "string", "title": "Security Token"}, + }, + "required": ["nr_account_id", "url", "token"], + "secret": ["token"], + "order": [ + "nr_account_id", + "url", + "token", + ] + } + + @classmethod + def name(cls): + return "NewRelic (GraphQL)" + + def test_connection(self): + nr_account_id = str("{}".format(self.configuration["nr_account_id"])) + qraphql_test_query = '{actor {account(id: ' + nr_account_id +') {nrql(query: "SELECT 1") {results}}}}' + testQuery = {"queryType": "count", "query": qraphql_test_query} + try: + response = self.run_query(query=json.dumps(testQuery), user="test") + except Exception as err: + logger.info(f"Raised Exception: {err}") + response = None + if response is None: + raise Exception("Failed describing objects.") + pass + + def run_query(self, query, user): + nr_url = "{}".format(self.configuration["url"]) + nr_token = "{}".format(self.configuration["token"]) + nr_account_id = "{}".format(self.configuration["nr_account_id"]) + headers = { + "Content-Type": "application/json", + "API-Key": "{}".format(nr_token), + } + + query = json_loads(query) + query_type = query.pop("queryType", "count") + nrql_query = query.pop("nrql", None) + if not nrql_query or not nr_account_id: + return None, None + + qraphql_query = '{actor {account(id: ' + nr_account_id +') {nrql(query: "' + nrql_query + '") {results}}}}' + payload = {"query":qraphql_query} + response, error = self.get_response(nr_url, http_method="post", data=json.dumps(payload), headers=headers) + + if error is not None: + return None, error + data = response.json() + + if query_type == "count": + results = parse_count(data) + + if query_type == "comparison": + results = parse_comparision(data) + + return results.to_json(), None + + +register(NewRelicGQL) diff --git a/redash/settings/__init__.py b/redash/settings/__init__.py index 4879ddb309..ef76e076ab 100644 --- a/redash/settings/__init__.py +++ b/redash/settings/__init__.py @@ -362,6 +362,7 @@ def email_server_is_configured(): "redash.query_runner.mssql_odbc", "redash.query_runner.memsql_ds", "redash.query_runner.mapd", + "redash.query_runner.newrelic", "redash.query_runner.jql", "redash.query_runner.google_analytics", "redash.query_runner.axibase_tsd",