Coverage for chalicelib/core/assist.py: 32%

193 statements  

« prev     ^ index     » next       coverage.py v7.15.2, created at 2026-10-10 12:56 +0000

1import logging 

2from os import access, R_OK 

3from os.path import exists as path_exists, getsize 

4 

5import jwt 

6import requests 

7from decouple import config 

8from fastapi import HTTPException, status 

9 

10import schemas 

11from chalicelib.core import projects 

12from chalicelib.utils.TimeUTC import TimeUTC 

13from chalicelib.utils.log import sanitize 

14 

15logger = logging.getLogger(__name__) 

16 

17ASSIST_KEY = config("ASSIST_KEY") 

18ASSIST_URL = config("ASSIST_URL") % ASSIST_KEY 

19 

20 

21def get_live_sessions_ws_user_id(project_id, user_id): 

22 data = { 

23 "filter": {"userId": user_id} if user_id else {} 

24 } 

25 return __get_live_sessions_ws(project_id=project_id, data=data) 

26 

27 

28def get_live_sessions_ws_test_id(project_id, test_id): 

29 data = { 

30 "filter": { 

31 'uxtId': test_id, 

32 'operator': 'is' 

33 } 

34 } 

35 return __get_live_sessions_ws(project_id=project_id, data=data) 

36 

37 

38def get_live_sessions_ws(project_id, body: schemas.LiveSessionsSearchPayloadSchema): 

39 data = { 

40 "filter": {}, 

41 "pagination": {"limit": body.limit, "page": body.page}, 

42 "sort": {"key": body.sort, "order": body.order} 

43 } 

44 for f in body.filters: 

45 if f.name == schemas.LiveFilterType.METADATA: 45 ↛ 46line 45 didn't jump to line 46 because the condition on line 45 was never true

46 data["filter"][f.source] = {"values": f.value, "operator": f.operator} 

47 

48 else: 

49 data["filter"][f.name] = {"values": f.value, "operator": f.operator} 

50 return __get_live_sessions_ws(project_id=project_id, data=data) 

51 

52 

53def __get_live_sessions_ws(project_id, data): 

54 project_key = projects.get_project_key(project_id) 

55 try: 

56 results = requests.post(ASSIST_URL + config("assist") + f"/{project_key}", 

57 json=data, timeout=config("assistTimeout", cast=int, default=5)) 

58 if results.status_code != 200: 

59 logger.error(f"!! issue with the peer-server code:{results.status_code} for __get_live_sessions_ws") 

60 logger.error(sanitize(results.text)) 

61 return {"total": 0, "sessions": []} 

62 live_peers = results.json().get("data", []) 

63 except requests.exceptions.Timeout: 

64 logger.error("!! Timeout getting Assist response") 

65 live_peers = {"total": 0, "sessions": []} 

66 except Exception as e: 

67 logger.error("!! Issue getting Live-Assist response") 

68 logger.exception(e) 

69 logger.error("expected JSON, received:") 

70 try: 

71 logger.error(sanitize(results.text)) 

72 except: 

73 logger.error("couldn't get response") 

74 live_peers = {"total": 0, "sessions": []} 

75 _live_peers = live_peers 

76 if "sessions" in live_peers: 76 ↛ 78line 76 didn't jump to line 78 because the condition on line 76 was always true

77 _live_peers = live_peers["sessions"] 

78 for s in _live_peers: 78 ↛ 79line 78 didn't jump to line 79 because the loop on line 78 never started

79 s["live"] = True 

80 s["projectId"] = project_id 

81 if "projectID" in s: 

82 s.pop("projectID") 

83 return live_peers 

84 

85 

86def __get_agent_token(project_id, project_key, session_id): 

87 iat = TimeUTC.now() 

88 return jwt.encode( 

89 payload={ 

90 "projectKey": project_key, 

91 "projectId": project_id, 

92 "sessionId": session_id, 

93 "iat": iat // 1000, 

94 "exp": iat // 1000 + config("ASSIST_JWT_EXPIRATION", cast=int) + TimeUTC.get_utc_offset() // 1000, 

95 "iss": config("JWT_ISSUER"), 

96 "aud": f"openreplay:agent" 

97 }, 

98 key=config("ASSIST_JWT_SECRET"), 

99 algorithm=config("JWT_ALGORITHM") 

100 ) 

101 

102 

103def get_live_session_by_id(project_id, session_id): 

104 project_key = projects.get_project_key(project_id) 

105 try: 

106 results = requests.get(ASSIST_URL + config("assist") + f"/{project_key}/{session_id}", 

107 timeout=config("assistTimeout", cast=int, default=5)) 

108 if results.status_code != 200: 

109 logger.error(f"!! issue with the peer-server code:{results.status_code} for get_live_session_by_id") 

110 logger.error(sanitize(results.text)) 

111 return None 

112 results = results.json().get("data") 

113 if results is None: 

114 return None 

115 results["live"] = True 

116 results["agentToken"] = __get_agent_token(project_id=project_id, project_key=project_key, session_id=session_id) 

117 except requests.exceptions.Timeout: 

118 logger.error("!! Timeout getting Assist response") 

119 return None 

120 except Exception as e: 

121 logger.error("!! Issue getting Assist response") 

122 logger.exception(e) 

123 logger.error("expected JSON, received:") 

124 try: 

125 logger.error(sanitize(results.text)) 

126 except: 

127 logger.error("couldn't get response") 

128 return None 

129 return results 

130 

131 

132def is_live(project_id, session_id, project_key=None): 

133 if project_key is None: 

134 project_key = projects.get_project_key(project_id) 

135 try: 

136 results = requests.get(ASSIST_URL + config("assistList") + f"/{project_key}/{session_id}", 

137 timeout=config("assistTimeout", cast=int, default=5)) 

138 if results.status_code != 200: 

139 logger.error(f"!! issue with the peer-server code:{results.status_code} for is_live") 

140 logger.error(sanitize(results.text)) 

141 return False 

142 results = results.json().get("data") 

143 except requests.exceptions.Timeout: 

144 logger.error("!! Timeout getting Assist response") 

145 return False 

146 except Exception as e: 

147 logger.error("!! Issue getting Assist response") 

148 logger.exception(e) 

149 logger.error("expected JSON, received:") 

150 try: 

151 logger.error(sanitize(results.text)) 

152 except: 

153 logger.error("couldn't get response") 

154 return False 

155 return str(session_id) == results 

156 

157 

158def autocomplete(project_id, q: str, key: str = None): 

159 project_key = projects.get_project_key(project_id) 

160 params = {"q": q} 

161 if key: 

162 params["key"] = key 

163 try: 

164 results = requests.get( 

165 ASSIST_URL + config("assistList") + f"/{project_key}/autocomplete", 

166 params=params, timeout=config("assistTimeout", cast=int, default=5)) 

167 if results.status_code != 200: 

168 logger.error(f"!! issue with the peer-server code:{results.status_code} for autocomplete") 

169 logger.error(sanitize(results.text)) 

170 return {"errors": [f"Something went wrong wile calling assist:{results.text}"]} 

171 results = results.json().get("data", []) 

172 except requests.exceptions.Timeout: 

173 logger.error("!! Timeout getting Assist response") 

174 return {"errors": ["Assist request timeout"]} 

175 except Exception as e: 

176 logger.error("!! Issue getting Assist response") 

177 logger.exception(e) 

178 logger.error("expected JSON, received:") 

179 try: 

180 logger.error(sanitize(results.text)) 

181 except: 

182 logger.error("couldn't get response") 

183 return {"errors": ["Something went wrong wile calling assist"]} 

184 for r in results: 

185 r["type"] = __change_keys(r["type"]) 

186 return {"data": results} 

187 

188 

189def __get_efs_path(): 

190 efs_path = config("FS_DIR") 

191 if not path_exists(efs_path): 

192 raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=f"EFS not found in path: {efs_path}") 

193 

194 if not access(efs_path, R_OK): 

195 raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, 

196 detail=f"EFS found under: {efs_path}; but it is not readable, please check permissions") 

197 return efs_path 

198 

199 

200def __get_mob_path(project_id, session_id): 

201 params = {"projectId": project_id, "sessionId": session_id} 

202 return config("EFS_SESSION_MOB_PATTERN", default="%(sessionId)s") % params 

203 

204 

205def get_raw_mob_by_id(project_id, session_id): 

206 efs_path = __get_efs_path() 

207 path_to_file = efs_path + "/" + __get_mob_path(project_id=project_id, session_id=session_id) 

208 if path_exists(path_to_file): 

209 if not access(path_to_file, R_OK): 

210 raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, 

211 detail=f"Replay file found under: {efs_path};" + 

212 " but it is not readable, please check permissions") 

213 # getsize return size in bytes, UNPROCESSED_MAX_SIZE is in Kb 

214 if (getsize(path_to_file) / 1000) >= config("UNPROCESSED_MAX_SIZE", cast=int, default=200 * 1000): 

215 raise HTTPException(status_code=status.HTTP_413_REQUEST_ENTITY_TOO_LARGE, detail="Replay file too large") 

216 return path_to_file 

217 

218 return None 

219 

220 

221def __get_devtools_path(project_id, session_id): 

222 params = {"projectId": project_id, "sessionId": session_id} 

223 return config("EFS_DEVTOOLS_MOB_PATTERN", default="%(sessionId)s") % params 

224 

225 

226def get_raw_devtools_by_id(project_id, session_id): 

227 efs_path = __get_efs_path() 

228 path_to_file = efs_path + "/" + __get_devtools_path(project_id=project_id, session_id=session_id) 

229 if path_exists(path_to_file): 

230 if not access(path_to_file, R_OK): 

231 raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, 

232 detail=f"Devtools file found under: {efs_path};" 

233 " but it is not readable, please check permissions") 

234 

235 return path_to_file 

236 

237 return None 

238 

239 

240def session_exists(project_id, session_id): 

241 project_key = projects.get_project_key(project_id) 

242 try: 

243 results = requests.get(ASSIST_URL + config("assist") + f"/{project_key}/{session_id}", 

244 timeout=config("assistTimeout", cast=int, default=5)) 

245 if results.status_code != 200: 

246 logger.error(f"!! issue with the peer-server code:{results.status_code} for session_exists") 

247 logger.error(sanitize(results.text)) 

248 return None 

249 results = results.json().get("data") 

250 if results is None: 

251 return False 

252 return True 

253 except requests.exceptions.Timeout: 

254 logger.error("!! Timeout getting Assist response") 

255 return False 

256 except Exception as e: 

257 logger.error("!! Issue getting Assist response") 

258 logger.exception(e) 

259 logger.error("expected JSON, received:") 

260 try: 

261 logger.error(sanitize(results.text)) 

262 except: 

263 logger.error("couldn't get response") 

264 return False 

265 

266 

267def __change_keys(key): 

268 return { 

269 "PAGETITLE": schemas.LiveFilterType.PAGE_TITLE.value, 

270 "ACTIVE": "active", 

271 "LIVE": "live", 

272 "SESSIONID": schemas.LiveFilterType.SESSION_ID.value, 

273 "METADATA": schemas.LiveFilterType.METADATA.value, 

274 "USERID": schemas.LiveFilterType.USER_ID.value, 

275 "USERUUID": schemas.LiveFilterType.USER_UUID.value, 

276 "PROJECTKEY": "projectKey", 

277 "REVID": schemas.LiveFilterType.REV_ID.value, 

278 "TIMESTAMP": "timestamp", 

279 "TRACKERVERSION": schemas.LiveFilterType.TRACKER_VERSION.value, 

280 "ISSNIPPET": "isSnippet", 

281 "USEROS": schemas.LiveFilterType.USER_OS.value, 

282 "USERBROWSER": schemas.LiveFilterType.USER_BROWSER.value, 

283 "USERBROWSERVERSION": schemas.LiveFilterType.USER_BROWSER_VERSION.value, 

284 "USERDEVICE": schemas.LiveFilterType.USER_DEVICE.value, 

285 "USERDEVICETYPE": schemas.LiveFilterType.USER_DEVICE_TYPE.value, 

286 "USERCOUNTRY": schemas.LiveFilterType.USER_COUNTRY.value, 

287 "PROJECTID": "projectId" 

288 }.get(key.upper(), key)