Unverified Commit a8440b13 authored by Jin Hyuk Chang's avatar Jin Hyuk Chang Committed by GitHub

feat: Add Query text to Dashboard Query model (#273)

* Add Query text to Dashboard Query model

* Update

* Update

* Update
parent d33016e1
...@@ -10,6 +10,8 @@ from databuilder.rest_api.mode_analytics.mode_paginated_rest_api_query import Mo ...@@ -10,6 +10,8 @@ from databuilder.rest_api.mode_analytics.mode_paginated_rest_api_query import Mo
from databuilder.rest_api.rest_api_query import RestApiQuery from databuilder.rest_api.rest_api_query import RestApiQuery
from databuilder.transformer.base_transformer import ChainedTransformer from databuilder.transformer.base_transformer import ChainedTransformer
from databuilder.transformer.dict_to_model import DictToModel, MODEL_CLASS from databuilder.transformer.dict_to_model import DictToModel, MODEL_CLASS
from databuilder.transformer.regex_str_replace_transformer import RegexStrReplaceTransformer, \
REGEX_REPLACE_TUPLE_LIST, ATTRIBUTE_NAME
from databuilder.transformer.template_variable_substitution_transformer import \ from databuilder.transformer.template_variable_substitution_transformer import \
TemplateVariableSubstitutionTransformer, TEMPLATE, FIELD_NAME TemplateVariableSubstitutionTransformer, TEMPLATE, FIELD_NAME
...@@ -44,6 +46,14 @@ class ModeDashboardQueriesExtractor(Extractor): ...@@ -44,6 +46,14 @@ class ModeDashboardQueriesExtractor(Extractor):
transformers.append(variable_substitution_transformer) transformers.append(variable_substitution_transformer)
# Escape backslash as it breaks Cypher statement.
replace_transformer = RegexStrReplaceTransformer()
replace_transformer.init(
conf=Scoped.get_scoped_conf(self._conf, replace_transformer.get_scope()).with_fallback(
ConfigFactory.from_dict(
{REGEX_REPLACE_TUPLE_LIST: [('\\', '\\\\')], ATTRIBUTE_NAME: 'query_text'})))
transformers.append(replace_transformer)
dict_to_model_transformer = DictToModel() dict_to_model_transformer = DictToModel()
dict_to_model_transformer.init( dict_to_model_transformer.init(
conf=Scoped.get_scoped_conf(self._conf, dict_to_model_transformer.get_scope()).with_fallback( conf=Scoped.get_scoped_conf(self._conf, dict_to_model_transformer.get_scope()).with_fallback(
...@@ -86,8 +96,8 @@ class ModeDashboardQueriesExtractor(Extractor): ...@@ -86,8 +96,8 @@ class ModeDashboardQueriesExtractor(Extractor):
json_path=json_path, field_names=field_names, skip_no_result=True) json_path=json_path, field_names=field_names, skip_no_result=True)
queries_url_template = 'https://app.mode.com/api/{organization}/reports/{dashboard_id}/queries' queries_url_template = 'https://app.mode.com/api/{organization}/reports/{dashboard_id}/queries'
json_path = '_embedded.queries[*].[token,name]' json_path = '_embedded.queries[*].[token,name,raw_query]'
field_names = ['query_id', 'query_name'] field_names = ['query_id', 'query_name', 'query_text']
query_names_query = RestApiQuery(query_to_join=reports_query, url=queries_url_template, params=params, query_names_query = RestApiQuery(query_to_join=reports_query, url=queries_url_template, params=params,
json_path=json_path, field_names=field_names, skip_no_result=True) json_path=json_path, field_names=field_names, skip_no_result=True)
......
...@@ -26,6 +26,7 @@ class DashboardQuery(Neo4jCsvSerializable): ...@@ -26,6 +26,7 @@ class DashboardQuery(Neo4jCsvSerializable):
query_name, # type: str query_name, # type: str
query_id=None, # type: Optional[str] query_id=None, # type: Optional[str]
url='', # type: Optional[str] url='', # type: Optional[str]
query_text=None, # type: Optional[str]
product='', # type: Optional[str] product='', # type: Optional[str]
cluster='gold', # type: str cluster='gold', # type: str
**kwargs **kwargs
...@@ -35,6 +36,7 @@ class DashboardQuery(Neo4jCsvSerializable): ...@@ -35,6 +36,7 @@ class DashboardQuery(Neo4jCsvSerializable):
self._query_name = query_name self._query_name = query_name
self._query_id = query_id if query_id else query_name self._query_id = query_id if query_id else query_name
self._url = url self._url = url
self._query_text = query_text
self._product = product self._product = product
self._cluster = cluster self._cluster = cluster
self._node_iterator = self._create_node_iterator() self._node_iterator = self._create_node_iterator()
...@@ -59,6 +61,9 @@ class DashboardQuery(Neo4jCsvSerializable): ...@@ -59,6 +61,9 @@ class DashboardQuery(Neo4jCsvSerializable):
if self._url: if self._url:
node['url'] = self._url node['url'] = self._url
if self._query_text:
node['query_text'] = self._query_text
yield node yield node
def create_next_relation(self): def create_next_relation(self):
...@@ -94,12 +99,13 @@ class DashboardQuery(Neo4jCsvSerializable): ...@@ -94,12 +99,13 @@ class DashboardQuery(Neo4jCsvSerializable):
) )
def __repr__(self): def __repr__(self):
return 'DashboardQuery({!r}, {!r}, {!r}, {!r}, {!r}, {!r}, {!r})'.format( return 'DashboardQuery({!r}, {!r}, {!r}, {!r}, {!r}, {!r}, {!r}, {!r})'.format(
self._dashboard_group_id, self._dashboard_group_id,
self._dashboard_id, self._dashboard_id,
self._query_name, self._query_name,
self._query_id, self._query_id,
self._url, self._url,
self._query_text,
self._product, self._product,
self._cluster self._cluster
) )
...@@ -28,7 +28,11 @@ class RegexStrReplaceTransformer(Transformer): ...@@ -28,7 +28,11 @@ class RegexStrReplaceTransformer(Transformer):
def transform(self, record): def transform(self, record):
# type: (Any) -> Any # type: (Any) -> Any
val = getattr(record, self._attribute_name)
if isinstance(record, dict):
val = record.get(self._attribute_name)
else:
val = getattr(record, self._attribute_name)
if val is None or not isinstance(val, six.string_types): if val is None or not isinstance(val, six.string_types):
return record return record
...@@ -40,7 +44,11 @@ class RegexStrReplaceTransformer(Transformer): ...@@ -40,7 +44,11 @@ class RegexStrReplaceTransformer(Transformer):
for regex_replace_tuple in self._regex_replace_tuples: for regex_replace_tuple in self._regex_replace_tuples:
val = val.replace(regex_replace_tuple[0], regex_replace_tuple[1]) val = val.replace(regex_replace_tuple[0], regex_replace_tuple[1])
setattr(record, self._attribute_name, val) if isinstance(record, dict):
record[self._attribute_name] = val
else:
setattr(record, self._attribute_name, val)
return record return record
def get_scope(self): def get_scope(self):
......
...@@ -2,7 +2,7 @@ import os ...@@ -2,7 +2,7 @@ import os
from setuptools import setup, find_packages from setuptools import setup, find_packages
__version__ = '2.5.17' __version__ = '2.5.18'
requirements_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'requirements.txt') requirements_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'requirements.txt')
with open(requirements_path) as requirements_file: with open(requirements_path) as requirements_file:
......
...@@ -15,10 +15,12 @@ class TestDashboardQuery(unittest.TestCase): ...@@ -15,10 +15,12 @@ class TestDashboardQuery(unittest.TestCase):
dashboard_id='d_id', dashboard_id='d_id',
query_id='q_id', query_id='q_id',
query_name='q_name', query_name='q_name',
url='http://foo.bar/query/baz') url='http://foo.bar/query/baz',
query_text='SELECT * FROM foo.bar')
actual = dashboard_query.create_next_node() actual = dashboard_query.create_next_node()
expected = {'url': 'http://foo.bar/query/baz', 'name': 'q_name', 'id': 'q_id', expected = {'url': 'http://foo.bar/query/baz', 'name': 'q_name', 'id': 'q_id',
'query_text': 'SELECT * FROM foo.bar',
NODE_KEY: '_dashboard://gold.dg_id/d_id/query/q_id', NODE_KEY: '_dashboard://gold.dg_id/d_id/query/q_id',
NODE_LABEL: DashboardQuery.DASHBOARD_QUERY_LABEL} NODE_LABEL: DashboardQuery.DASHBOARD_QUERY_LABEL}
......
...@@ -2,7 +2,8 @@ import unittest ...@@ -2,7 +2,8 @@ import unittest
from pyhocon import ConfigFactory from pyhocon import ConfigFactory
from databuilder.transformer.regex_str_replace_transformer import RegexStrReplaceTransformer from databuilder.transformer.regex_str_replace_transformer import RegexStrReplaceTransformer, \
REGEX_REPLACE_TUPLE_LIST, ATTRIBUTE_NAME
class TestRegexReplacement(unittest.TestCase): class TestRegexReplacement(unittest.TestCase):
...@@ -37,8 +38,8 @@ class TestRegexReplacement(unittest.TestCase): ...@@ -37,8 +38,8 @@ class TestRegexReplacement(unittest.TestCase):
def _default_test_transformer(self): def _default_test_transformer(self):
# type: () -> RegexStrReplaceTransformer # type: () -> RegexStrReplaceTransformer
config = ConfigFactory.from_dict({ config = ConfigFactory.from_dict({
'regex_replace_tuple_list': [('a', 'b'), ('c', 'a')], REGEX_REPLACE_TUPLE_LIST: [('a', 'b'), ('c', 'a')],
'attribute_name': 'val' ATTRIBUTE_NAME: 'val'
}) })
transformer = RegexStrReplaceTransformer() transformer = RegexStrReplaceTransformer()
...@@ -46,6 +47,22 @@ class TestRegexReplacement(unittest.TestCase): ...@@ -46,6 +47,22 @@ class TestRegexReplacement(unittest.TestCase):
return transformer return transformer
def test_dict_replace(self):
# type: () -> None
config = ConfigFactory.from_dict({
REGEX_REPLACE_TUPLE_LIST: [('\\', '\\\\')],
ATTRIBUTE_NAME: 'val'
})
transformer = RegexStrReplaceTransformer()
transformer.init(config)
d = {'val': '\\'}
actual = transformer.transform(d)
self.assertEqual({'val': '\\\\'}, actual)
class Foo(object): class Foo(object):
def __init__(self, val): def __init__(self, val):
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment