Skip to content

Commit fa4faa2

Browse files
committed
Merge branch 'add-telemetry' into 'enterprise'
feat(telemetry): Sending event when user logs in See merge request dkinternal/testgen/dataops-testgen!180
2 parents acc469f + 68675c5 commit fa4faa2

3 files changed

Lines changed: 97 additions & 0 deletions

File tree

testgen/common/mixpanel_service.py

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import json
2+
import logging
3+
import ssl
4+
import uuid
5+
from base64 import b64encode
6+
from functools import cached_property, wraps
7+
from hashlib import blake2b
8+
from urllib.parse import urlencode
9+
from urllib.request import Request, urlopen
10+
11+
from testgen import settings
12+
from testgen.ui.session import session
13+
from testgen.utils.singleton import Singleton
14+
15+
LOG = logging.getLogger("testgen")
16+
17+
18+
def safe_method(method):
19+
@wraps(method)
20+
def wrapped(*args, **kwargs):
21+
if settings.ANALYTICS_ENABLED:
22+
try:
23+
method(*args, **kwargs)
24+
except Exception:
25+
LOG.exception("Error processing analytics data")
26+
27+
return wrapped
28+
29+
30+
class MixpanelService(Singleton):
31+
32+
@cached_property
33+
def instance_id(self):
34+
return settings.INSTANCE_ID or blake2b(uuid.getnode().to_bytes(8), digest_size=8).hexdigest()
35+
36+
@cached_property
37+
def distinct_id(self):
38+
return self._hash_value(session.username)
39+
40+
def _hash_value(self, value: bytes | str, digest_size: int = 8) -> str:
41+
if isinstance(value, str):
42+
value = value.encode()
43+
return blake2b(value, salt=self.instance_id.encode(), digest_size=digest_size).hexdigest()
44+
45+
@safe_method
46+
def send_event(self, event_name, **properties):
47+
properties.setdefault("instance_id", self.instance_id)
48+
properties.setdefault("version", settings.VERSION)
49+
properties.setdefault("distinct_id", self.distinct_id)
50+
51+
track_payload = {
52+
"event": event_name,
53+
"properties": {
54+
"token": settings.MIXPANEL_TOKEN,
55+
**properties,
56+
}
57+
}
58+
self.send_mp_request("track?ip=1", track_payload)
59+
60+
def get_ssl_context(self):
61+
ssl_context = ssl.create_default_context()
62+
ssl_context.check_hostname = False
63+
ssl_context.verify_mode = ssl.CERT_NONE
64+
return ssl_context
65+
66+
def send_mp_request(self, endpoint, payload):
67+
try:
68+
post_data = urlencode(
69+
{"data": b64encode(json.dumps(payload).encode()).decode()}
70+
).encode()
71+
72+
req = Request(f"{settings.MIXPANEL_URL}/{endpoint}", data=post_data, method="POST") # noqa: S310
73+
req.add_header("Content-Type", "application/x-www-form-urlencoded")
74+
75+
urlopen(req, context=self.get_ssl_context(), timeout=settings.MIXPANEL_TIMEOUT) # noqa: S310
76+
except Exception:
77+
LOG.exception("Failed to send analytics data")

testgen/settings.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -466,3 +466,21 @@
466466
File paths for SSL certificate and private key to support HTTPS.
467467
Both files must be provided.
468468
"""
469+
470+
471+
MIXPANEL_URL: str = "https://api.mixpanel.com"
472+
MIXPANEL_TIMEOUT: int = 3
473+
MIXPANEL_TOKEN: str = "973680ddf8c2b512e6f6d1f2959149eb"
474+
"""
475+
Mixpanel configuration
476+
"""
477+
478+
INSTANCE_ID: str | None = os.getenv("TG_INSTANCE_ID", None)
479+
"""
480+
Random ID that uniquely identifies the instance.
481+
"""
482+
483+
ANALYTICS_ENABLED: bool = os.getenv("TG_ANALYTICS", "yes").lower() in ("true", "yes")
484+
"""
485+
Disables sending usage data when set to any value except "true" and "yes". Defaults to "yes"
486+
"""

testgen/ui/views/login.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import streamlit as st
55
import streamlit_authenticator as stauth
66

7+
from testgen.common.mixpanel_service import MixpanelService
78
from testgen.ui.components import widgets as testgen
89
from testgen.ui.navigation.page import Page
910
from testgen.ui.services import javascript_service, user_session_service
@@ -60,3 +61,4 @@ def render(self, **_kwargs) -> None:
6061
self.router.navigate(next_route)
6162
else:
6263
session.logging_in = True
64+
MixpanelService().send_event("login")

0 commit comments

Comments
 (0)