redash/query_runner/jql.py (152 lines of code) (raw):
import re
from collections import OrderedDict
from redash.query_runner import *
from redash.utils import json_dumps, json_loads
# 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 parse_issue(issue, field_mapping):
result = OrderedDict()
result["key"] = issue["key"]
for k, v in issue["fields"].items(): #
output_name = field_mapping.get_output_field_name(k)
member_names = field_mapping.get_dict_members(k)
if isinstance(v, dict):
if len(member_names) > 0:
# if field mapping with dict member mappings defined get value of each member
for member_name in member_names:
if member_name in v:
result[
field_mapping.get_dict_output_field_name(k, member_name)
] = v[member_name]
else:
# these special mapping rules are kept for backwards compatibility
if "key" in v:
result["{}_key".format(output_name)] = v["key"]
if "name" in v:
result["{}_name".format(output_name)] = v["name"]
if k in v:
result[output_name] = v[k]
if "watchCount" in v:
result[output_name] = v["watchCount"]
elif isinstance(v, list):
if len(member_names) > 0:
# if field mapping with dict member mappings defined get value of each member
for member_name in member_names:
listValues = []
for listItem in v:
if isinstance(listItem, dict):
if member_name in listItem:
listValues.append(listItem[member_name])
if len(listValues) > 0:
result[
field_mapping.get_dict_output_field_name(k, member_name)
] = ",".join(listValues)
else:
# otherwise support list values only for non-dict items
listValues = []
for listItem in v:
if not isinstance(listItem, dict):
listValues.append(listItem)
if len(listValues) > 0:
result[output_name] = ",".join(listValues)
else:
result[output_name] = v
return result
def parse_issues(data, field_mapping):
results = ResultSet()
for issue in data["issues"]:
results.add_row(parse_issue(issue, field_mapping))
return results
def parse_count(data):
results = ResultSet()
results.add_row({"count": data["total"]})
return results
class FieldMapping:
def __init__(cls, query_field_mapping):
cls.mapping = []
for k, v in query_field_mapping.items():
field_name = k
member_name = None
# check for member name contained in field name
member_parser = re.search("(\w+)\.(\w+)", k)
if member_parser:
field_name = member_parser.group(1)
member_name = member_parser.group(2)
cls.mapping.append(
{
"field_name": field_name,
"member_name": member_name,
"output_field_name": v,
}
)
def get_output_field_name(cls, field_name):
for item in cls.mapping:
if item["field_name"] == field_name and not item["member_name"]:
return item["output_field_name"]
return field_name
def get_dict_members(cls, field_name):
member_names = []
for item in cls.mapping:
if item["field_name"] == field_name and item["member_name"]:
member_names.append(item["member_name"])
return member_names
def get_dict_output_field_name(cls, field_name, member_name):
for item in cls.mapping:
if item["field_name"] == field_name and item["member_name"] == member_name:
return item["output_field_name"]
return None
class JiraJQL(BaseHTTPQueryRunner):
noop_query = '{"queryType": "count"}'
response_error = "JIRA returned unexpected status code"
requires_authentication = True
url_title = "JIRA URL"
username_title = "Username"
password_title = "API Token"
@classmethod
def name(cls):
return "JIRA (JQL)"
def __init__(self, configuration):
super(JiraJQL, self).__init__(configuration)
self.syntax = "json"
def run_query(self, query, user):
jql_url = "{}/rest/api/2/search".format(self.configuration["url"])
query = json_loads(query)
query_type = query.pop("queryType", "select")
field_mapping = FieldMapping(query.pop("fieldMapping", {}))
if query_type == "count":
query["maxResults"] = 1
query["fields"] = ""
else:
query["maxResults"] = query.get("maxResults", 1000)
response, error = self.get_response(jql_url, params=query)
if error is not None:
return None, error
data = response.json()
if query_type == "count":
results = parse_count(data)
else:
results = parse_issues(data, field_mapping)
index = data["startAt"] + data["maxResults"]
while data["total"] > index:
query["startAt"] = index
response, error = self.get_response(jql_url, params=query)
if error is not None:
return None, error
data = response.json()
index = data["startAt"] + data["maxResults"]
addl_results = parse_issues(data, field_mapping)
results.merge(addl_results)
return results.to_json(), None
register(JiraJQL)