Coverage for chalicelib/core/events/events_ch.py: 0%
98 statements
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-10 12:56 +0000
« prev ^ index » next coverage.py v7.15.2, created at 2026-10-10 12:56 +0000
1from typing import Optional
2from urllib.parse import urlparse
4import schemas
5from chalicelib.core.issues import issues
6from chalicelib.utils import ch_client, pg_client
7from chalicelib.utils import helper
8from chalicelib.utils.TimeUTC import TimeUTC
9from chalicelib.utils.exp_ch_helper import explode_dproperties, add_timestamp
10import json
13def get_customs_by_session_id(session_id, project_id):
14 with ch_client.ClickHouseClient() as cur:
15 query = cur.format(query=""" \
16 SELECT `$properties`,
17 properties,
18 created_at,
19 'CUSTOM' AS type,
20 `$event_name` AS name
21 FROM product_analytics.events
22 WHERE session_id = %(session_id)s
23 AND NOT `$auto_captured`
24 ORDER BY created_at;""",
25 parameters={"project_id": project_id, "session_id": session_id})
26 rows = cur.execute(query)
27 rows = helper.list_to_camel_case(rows, ignore_keys=["properties"])
28 rows = explode_dproperties(rows)
29 rows = add_timestamp(rows)
30 return rows
33def __merge_cells(rows, start, count, replacement):
34 rows[start] = replacement
35 rows = rows[:start + 1] + rows[start + count:]
36 return rows
39def __get_grouped_clickrage(rows, session_id, project_id):
40 click_rage_issues = issues.get_by_session_id(session_id=session_id, issue_type="click_rage", project_id=project_id)
41 if len(click_rage_issues) == 0:
42 return rows
44 for c in click_rage_issues:
45 merge_count = c.get("payload")
46 if merge_count is not None:
47 merge_count = merge_count.get("Count", 3)
48 else:
49 merge_count = 3
50 for i in range(len(rows)):
51 if rows[i]["created_at"] == c["createdAt"]:
52 rows = __merge_cells(rows=rows,
53 start=i,
54 count=merge_count,
55 replacement={**rows[i], "type": "CLICKRAGE", "count": merge_count})
56 break
57 return rows
60def _numeric_property_available(props: dict, key: str) -> bool:
61 return key in props \
62 and (isinstance(props[key], str) and props[key].isnumeric() \
63 or isinstance(props[key], int))
66def extract_required_values(rows):
67 for row in rows:
68 # props = row.pop("$properties")
69 props = json.loads(row.pop("p_properties"))
70 row["label"] = props.get("label")
71 # To remove extra attributes
72 if row["type"] != "INPUT":
73 row.pop("duration")
74 if row["type"] != "LOCATION":
75 row.pop("url")
76 row.pop("referrer")
78 # To extract/transform required attributes
79 if row["type"] == "CLICK":
80 row["hesitation"] = int(props["hesitation_time"]) \
81 if _numeric_property_available(props, "hesitation_time") else props.get("hesitation_time")
82 row["selector"] = props.get("selector")
83 elif row["type"] == "INPUT":
84 row["value"] = props.get("value")
85 row["hesitation"] = int(props["hesitation_time"]) \
86 if _numeric_property_available(props, "hesitation_time") else props.get("hesitation_time")
87 row["duration"] *= 1000
88 elif row["type"] == "LOCATION":
89 parsed_url = urlparse(row["url"])
90 row["host"] = parsed_url.hostname
91 row["pageLoad"] = None # TODO: find how to compute this value
92 row["fcpTime"] = int(props["first_contentful_paint_time"]) \
93 if _numeric_property_available(props, "first_contentful_paint_time") \
94 else props.get("first_contentful_paint_time")
95 row["loadTime"] = int(props["load_event_end"]) - int(props["load_event_start"]) \
96 if _numeric_property_available(props, "load_event_end") \
97 else None
98 row["domContentLoadedTime"] = int(props["dom_content_loaded_event_end"]) \
99 - int(props.get("dom_content_loaded_event_start", 0)) \
100 if "dom_content_loaded_event_end" in props and \
101 (isinstance(props["dom_content_loaded_event_end"], str) \
102 and props["dom_content_loaded_event_end"].isnumeric() \
103 or isinstance(props["dom_content_loaded_event_end"], int)) \
104 else None
105 row["domBuildingTime"] = int(props["dom_building_time"]) \
106 if _numeric_property_available(props, "dom_building_time") \
107 else props.get("dom_building_time")
108 row["speedIndex"] = int(props["speed_index"]) \
109 if _numeric_property_available(props, "speed_index") \
110 else props.get("speed_index")
111 row["visuallyComplete"] = int(props["visually_complete"]) \
112 if _numeric_property_available(props, "visually_complete") \
113 else props.get("visually_complete")
114 row["timeToInteractive"] = int(props["time_to_interactive"]) \
115 if _numeric_property_available(props, "time_to_interactive") \
116 else props.get("time_to_interactive")
117 row["firstContentfulPaintTime"] = int(props["first_contentful_paint_time"]) \
118 if _numeric_property_available(props, "first_contentful_paint_time") \
119 else props.get("first_contentful_paint_time")
120 row["firstPaintTime"] = props["first_paint"] \
121 if _numeric_property_available(props, "first_paint") \
122 else props.get("first_paint")
125def get_by_session_id(session_id, project_id, group_clickrage=False, event_type: Optional[schemas.EventType] = None):
126 with ch_client.ClickHouseClient() as cur:
127 select_events = ('CLICK', 'INPUT', 'LOCATION', 'TAP')
128 if event_type is not None:
129 select_events = (event_type,)
130 query = cur.format(query=""" \
131 SELECT created_at,
132 -- This is used because of an issue in clickhouse-python driver
133 toString(`$properties`) AS p_properties,
134 `$event_name` AS type,
135 `$duration_s` AS duration,
136 `$current_url` AS url,
137 `$referrer` AS referrer
138 FROM product_analytics.events
139 WHERE session_id = %(session_id)s
140 AND `$event_name` IN %(select_events)s
141 AND `$auto_captured`
142 ORDER BY created_at;""",
143 parameters={"project_id": project_id, "session_id": session_id,
144 "select_events": select_events})
145 rows = cur.execute(query)
146 # rows = explode_dproperties(rows)
147 extract_required_values(rows)
148 if group_clickrage and 'CLICK' in select_events:
149 rows = __get_grouped_clickrage(rows=rows, session_id=session_id, project_id=project_id)
151 rows = helper.list_to_camel_case(rows)
152 rows = sorted(rows, key=lambda k: k["createdAt"])
153 rows = add_timestamp(rows)
154 return rows
157def get_errors_by_session_id(session_id, project_id):
158 with ch_client.ClickHouseClient() as cur:
159 rows = cur.execute(""" \
160 SELECT error_id,
161 project_id,
162 `$time` AS time,
163 `$properties`.source AS source,
164 'ERROR' AS name,
165 `$properties`.message AS message,
166 `$properties`.payload AS payload,
167 stacktrace,
168 stacktrace_parsed_at
169 FROM product_analytics.events
170 LEFT JOIN experimental.parsed_errors USING (error_id)
171 WHERE "$event_name" = 'ERROR'
172 AND session_id = %(session_id)s
173 AND project_id = %(project_id)s
174 ORDER BY created_at;""",
175 {"session_id": session_id, "project_id": project_id})
176 for e in rows:
177 e["stacktrace_parsed_at"] = TimeUTC.datetime_to_timestamp(e["stacktrace_parsed_at"])
178 return helper.list_to_camel_case(rows)
181def get_incidents_by_session_id(session_id, project_id):
182 with ch_client.ClickHouseClient() as cur:
183 query = cur.format(query=""" \
184 SELECT created_at,
185 `$properties`.end_time AS end_time,
186 `$properties`.label AS label,
187 `$properties`.start_time AS start_time,
188 `$event_name` AS type
189 FROM product_analytics.events
190 WHERE session_id = %(session_id)s
191 AND `$event_name` = 'ISSUE'
192 AND `$auto_captured`
193 AND issue_type = 'incident'
194 ORDER BY created_at;""",
195 parameters={"project_id": project_id, "session_id": session_id})
196 rows = cur.execute(query)
197 # rows = explode_dproperties(rows)
198 rows = helper.list_to_camel_case(rows)
199 rows = sorted(rows, key=lambda k: k["createdAt"])
200 return rows
203def get_mobile_crashes_by_session_id(session_id):
204 with ch_client.ClickHouseClient() as cur:
205 query = """SELECT `$properties`,
206 properties,
207 created_at,
208 'CRASH' AS type,
209 `$event_name` AS name
210 FROM product_analytics.events
211 WHERE session_id = %(session_id)s
212 AND NOT `$auto_captured`
213 AND `$event_name` = 'CRASH'
214 ORDER BY created_at;"""
215 rows = cur.execute(query, {"session_id": session_id})
216 return helper.list_to_camel_case(rows)