redash/query_runner/yandex_metrica.py (145 lines of code) (raw):
import logging
import yaml
from urllib.parse import parse_qs, urlparse
import requests
from redash.query_runner import *
from redash.utils import json_dumps
logger = logging.getLogger(__name__)
COLUMN_TYPES = {
"date": (
"firstVisitDate",
"firstVisitStartOfYear",
"firstVisitStartOfQuarter",
"firstVisitStartOfMonth",
"firstVisitStartOfWeek",
),
"datetime": (
"firstVisitStartOfHour",
"firstVisitStartOfDekaminute",
"firstVisitStartOfMinute",
"firstVisitDateTime",
"firstVisitHour",
"firstVisitHourMinute",
),
"int": (
"pageViewsInterval",
"pageViews",
"firstVisitYear",
"firstVisitMonth",
"firstVisitDayOfMonth",
"firstVisitDayOfWeek",
"firstVisitMinute",
"firstVisitDekaminute",
),
}
for type_, elements in COLUMN_TYPES.items():
for el in elements:
if "first" in el:
el = el.replace("first", "last")
COLUMN_TYPES[type_] += (el,)
def parse_ym_response(response):
columns = []
dimensions_len = len(response["query"]["dimensions"])
for h in response["query"]["dimensions"] + response["query"]["metrics"]:
friendly_name = h.split(":")[-1]
if friendly_name in COLUMN_TYPES["date"]:
data_type = TYPE_DATE
elif friendly_name in COLUMN_TYPES["datetime"]:
data_type = TYPE_DATETIME
else:
data_type = TYPE_STRING
columns.append({"name": h, "friendly_name": friendly_name, "type": data_type})
rows = []
for num, row in enumerate(response["data"]):
res = {}
for i, d in enumerate(row["dimensions"]):
res[columns[i]["name"]] = d["name"]
for i, d in enumerate(row["metrics"]):
res[columns[dimensions_len + i]["name"]] = d
if num == 0 and isinstance(d, float):
columns[dimensions_len + i]["type"] = TYPE_FLOAT
rows.append(res)
return {"columns": columns, "rows": rows}
class YandexMetrica(BaseSQLQueryRunner):
should_annotate_query = False
@classmethod
def type(cls):
# This is written with a "k" for backward-compatibility. See #2874.
return "yandex_metrika"
@classmethod
def name(cls):
return "Yandex Metrica"
@classmethod
def configuration_schema(cls):
return {
"type": "object",
"properties": {"token": {"type": "string", "title": "OAuth Token"}},
"secret": ["token"],
"required": ["token"],
}
def __init__(self, configuration):
super(YandexMetrica, self).__init__(configuration)
self.syntax = "yaml"
self.url = "https://api-metrica.yandex.com"
self.list_path = "counters"
def _get_tables(self, schema):
counters = self._send_query("management/v1/{0}".format(self.list_path))
for row in counters[self.list_path]:
owner = row.get("owner_login")
counter = "{0} | {1}".format(
row.get("name", "Unknown"), row.get("id", "Unknown")
)
if owner not in schema:
schema[owner] = {"name": owner, "columns": []}
schema[owner]["columns"].append(counter)
return list(schema.values())
def test_connection(self):
self._send_query("management/v1/{0}".format(self.list_path))
def _send_query(self, path="stat/v1/data", **kwargs):
token = kwargs.pop("oauth_token", self.configuration["token"])
r = requests.get(
"{0}/{1}".format(self.url, path),
headers={"Authorization": "OAuth {}".format(token)},
params=kwargs,
)
if r.status_code != 200:
raise Exception(r.text)
return r.json()
def run_query(self, query, user):
logger.debug("Metrica is about to execute query: %s", query)
data = None
query = query.strip()
if query == "":
error = "Query is empty"
return data, error
try:
params = yaml.safe_load(query)
except ValueError as e:
logging.exception(e)
error = str(e)
return data, error
if isinstance(params, dict):
if "url" in params:
params = parse_qs(urlparse(params["url"]).query, keep_blank_values=True)
else:
error = "The query format must be JSON or YAML"
return data, error
try:
data = json_dumps(parse_ym_response(self._send_query(**params)))
error = None
except Exception as e:
logging.exception(e)
error = str(e)
return data, error
class YandexAppMetrica(YandexMetrica):
@classmethod
def type(cls):
# This is written with a "k" for backward-compatibility. See #2874.
return "yandex_appmetrika"
@classmethod
def name(cls):
return "Yandex AppMetrica"
def __init__(self, configuration):
super(YandexAppMetrica, self).__init__(configuration)
self.url = "https://api.appmetrica.yandex.com"
self.list_path = "applications"
register(YandexMetrica)
register(YandexAppMetrica)