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)