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

1from typing import Optional 

2from urllib.parse import urlparse 

3 

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 

11 

12 

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 

31 

32 

33def __merge_cells(rows, start, count, replacement): 

34 rows[start] = replacement 

35 rows = rows[:start + 1] + rows[start + count:] 

36 return rows 

37 

38 

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 

43 

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 

58 

59 

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)) 

64 

65 

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") 

77 

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") 

123 

124 

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) 

150 

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 

155 

156 

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) 

179 

180 

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 

201 

202 

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)