Skip to content

Commit 69bf5b7

Browse files
authored
Dashboard usage model (#226)
* Added Dashboard usage model * Update * Update * Update
1 parent 3a141f9 commit 69bf5b7

File tree

7 files changed

+170
-5
lines changed

7 files changed

+170
-5
lines changed

databuilder/extractor/db_api_extractor.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ def _execute_query(self):
4646
Use cursor to execute the {sql}
4747
:return:
4848
"""
49+
LOGGER.info('Executing query: \n{}'.format(self.sql))
4950
self.cursor.execute(self.sql)
5051
return self.cursor.fetchall()
5152

databuilder/models/column_usage_model.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@
44
Neo4jCsvSerializable, RELATION_START_KEY, RELATION_END_KEY,
55
RELATION_START_LABEL, RELATION_END_LABEL, RELATION_TYPE, RELATION_REVERSE_TYPE
66
)
7+
from databuilder.models.usage.usage_constants import (
8+
READ_RELATION_TYPE, READ_REVERSE_RELATION_TYPE, READ_RELATION_COUNT_PROPERTY
9+
)
710
from databuilder.models.table_metadata import TableMetadata
811
from databuilder.models.user import User
9-
from databuilder.publisher.neo4j_csv_publisher import UNQUOTED_SUFFIX
1012

1113

1214
class ColumnUsageModel(Neo4jCsvSerializable):
@@ -18,11 +20,11 @@ class ColumnUsageModel(Neo4jCsvSerializable):
1820
TABLE_NODE_LABEL = TableMetadata.TABLE_NODE_LABEL
1921
TABLE_NODE_KEY_FORMAT = TableMetadata.TABLE_KEY_FORMAT
2022

21-
USER_TABLE_RELATION_TYPE = 'READ'
22-
TABLE_USER_RELATION_TYPE = 'READ_BY'
23+
USER_TABLE_RELATION_TYPE = READ_RELATION_TYPE
24+
TABLE_USER_RELATION_TYPE = READ_REVERSE_RELATION_TYPE
2325

2426
# Property key for relationship read, readby relationship
25-
READ_RELATION_COUNT = 'read_count{}'.format(UNQUOTED_SUFFIX)
27+
READ_RELATION_COUNT = READ_RELATION_COUNT_PROPERTY
2628

2729
def __init__(self,
2830
database, # type: str
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import logging
2+
3+
from typing import Optional, Dict, Any, Union, Iterator # noqa: F401
4+
5+
from databuilder.models.dashboard.dashboard_metadata import DashboardMetadata
6+
from databuilder.models.neo4j_csv_serde import (
7+
Neo4jCsvSerializable, RELATION_START_KEY, RELATION_END_KEY, RELATION_START_LABEL,
8+
RELATION_END_LABEL, RELATION_TYPE, RELATION_REVERSE_TYPE)
9+
from databuilder.models.usage.usage_constants import (
10+
READ_RELATION_TYPE, READ_REVERSE_RELATION_TYPE, READ_RELATION_COUNT_PROPERTY
11+
)
12+
from databuilder.models.user import User
13+
14+
LOGGER = logging.getLogger(__name__)
15+
16+
17+
class DashboardUsage(Neo4jCsvSerializable):
18+
"""
19+
A model that encapsulate Dashboard usage between Dashboard and User
20+
"""
21+
22+
def __init__(self,
23+
dashboard_group_id, # type: Optional[str]
24+
dashboard_id, # type: Optional[str]
25+
email, # type: str
26+
view_count, # type: int
27+
should_create_user_node=False, # type: Optional[bool]
28+
product='', # type: Optional[str]
29+
cluster='gold', # type: Optional[str]
30+
**kwargs
31+
):
32+
# type: () -> None
33+
"""
34+
35+
:param dashboard_group_id:
36+
:param dashboard_id:
37+
:param email:
38+
:param view_count:
39+
:param should_create_user_node: Enable this if it is fine to create/update User node with only with email
40+
address. Please be advised that other fields will be emptied. Current use case is to create anonymous user.
41+
For example, Mode dashboard does not provide which user viewed the dashboard and anonymous user can be used
42+
to show the usage.
43+
:param product:
44+
:param cluster:
45+
:param kwargs:
46+
"""
47+
self._dashboard_group_id = dashboard_group_id
48+
self._dashboard_id = dashboard_id
49+
self._email = email
50+
self._view_count = view_count
51+
self._product = product
52+
self._cluster = cluster
53+
self._user_model = User(email=email)
54+
self._should_create_user_node = bool(should_create_user_node)
55+
self._relation_iterator = self._create_relation_iterator()
56+
57+
def create_next_node(self):
58+
# type: () -> Union[Dict[str, Any], None]
59+
if self._should_create_user_node:
60+
return self._user_model.create_next_node()
61+
62+
def create_next_relation(self):
63+
# type: () -> Union[Dict[str, Any], None]
64+
try:
65+
return next(self._relation_iterator)
66+
except StopIteration:
67+
return None
68+
69+
def _create_relation_iterator(self):
70+
# type: () -> Iterator[[Dict[str, Any]]]
71+
72+
yield {
73+
RELATION_START_LABEL: DashboardMetadata.DASHBOARD_NODE_LABEL,
74+
RELATION_END_LABEL: User.USER_NODE_LABEL,
75+
RELATION_START_KEY: DashboardMetadata.DASHBOARD_KEY_FORMAT.format(
76+
product=self._product,
77+
cluster=self._cluster,
78+
dashboard_group=self._dashboard_group_id,
79+
dashboard_name=self._dashboard_id
80+
),
81+
RELATION_END_KEY: User.get_user_model_key(email=self._email),
82+
RELATION_TYPE: READ_REVERSE_RELATION_TYPE,
83+
RELATION_REVERSE_TYPE: READ_RELATION_TYPE,
84+
READ_RELATION_COUNT_PROPERTY: self._view_count
85+
}
86+
87+
def __repr__(self):
88+
return 'DashboardUsage({!r}, {!r}, {!r}, {!r}, {!r}, {!r}, {!r})'.format(
89+
self._dashboard_group_id,
90+
self._dashboard_id,
91+
self._email,
92+
self._view_count,
93+
self._should_create_user_node,
94+
self._product,
95+
self._cluster
96+
)

databuilder/models/usage/__init__.py

Whitespace-only changes.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from databuilder.publisher.neo4j_csv_publisher import UNQUOTED_SUFFIX
2+
3+
4+
READ_RELATION_TYPE = 'READ'
5+
READ_REVERSE_RELATION_TYPE = 'READ_BY'
6+
7+
READ_RELATION_COUNT_PROPERTY = 'read_count{}'.format(UNQUOTED_SUFFIX)

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from setuptools import setup, find_packages
33

44

5-
__version__ = '2.4.1'
5+
__version__ = '2.4.2'
66

77
requirements_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'requirements.txt')
88
with open(requirements_path) as requirements_file:
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import unittest
2+
3+
from databuilder.models.dashboard.dashboard_usage import DashboardUsage
4+
from databuilder.models.neo4j_csv_serde import RELATION_START_KEY, RELATION_START_LABEL, RELATION_END_KEY, \
5+
RELATION_END_LABEL, RELATION_TYPE, RELATION_REVERSE_TYPE
6+
7+
8+
class TestDashboardOwner(unittest.TestCase):
9+
10+
def test_dashboard_usage_user_nodes(self):
11+
# type: () -> None
12+
dashboard_usage = DashboardUsage(dashboard_group_id='dashboard_group_id', dashboard_id='dashboard_id',
13+
email='[email protected]', view_count=123, cluster='cluster_id',
14+
product='product_id', should_create_user_node=True)
15+
16+
actual = dashboard_usage.create_next_node()
17+
expected = {'is_active:UNQUOTED': True,
18+
'last_name': '',
19+
'full_name': '',
20+
'employee_type': '',
21+
'first_name': '',
22+
'updated_at': 0,
23+
'LABEL': 'User',
24+
'slack_id': '',
25+
26+
'github_username': '',
27+
'team_name': '',
28+
'email': '[email protected]',
29+
'role_name': ''}
30+
31+
self.assertDictEqual(expected, actual)
32+
self.assertIsNone(dashboard_usage.create_next_node())
33+
34+
def test_dashboard_usage_no_user_nodes(self):
35+
# type: () -> None
36+
dashboard_usage = DashboardUsage(dashboard_group_id='dashboard_group_id', dashboard_id='dashboard_id',
37+
email='[email protected]', view_count=123,
38+
should_create_user_node=False, cluster='cluster_id',
39+
product='product_id')
40+
41+
self.assertIsNone(dashboard_usage.create_next_node())
42+
43+
def test_dashboard_owner_relations(self):
44+
# type: () -> None
45+
dashboard_usage = DashboardUsage(dashboard_group_id='dashboard_group_id', dashboard_id='dashboard_id',
46+
email='[email protected]', view_count=123, cluster='cluster_id',
47+
product='product_id')
48+
49+
actual = dashboard_usage.create_next_relation()
50+
expected = {'read_count:UNQUOTED': 123,
51+
RELATION_END_KEY: '[email protected]',
52+
RELATION_START_LABEL: 'Dashboard',
53+
RELATION_END_LABEL: 'User',
54+
RELATION_START_KEY: 'product_id_dashboard://cluster_id.dashboard_group_id/dashboard_id',
55+
RELATION_TYPE: 'READ_BY',
56+
RELATION_REVERSE_TYPE: 'READ'}
57+
58+
self.assertDictEqual(expected, actual)
59+
self.assertIsNone(dashboard_usage.create_next_relation())

0 commit comments

Comments
 (0)