77import  xml .etree .ElementTree  as  ET 
88from  pathlib  import  Path 
99from  datetime  import  datetime , timezone 
10- import  subprocess 
11- import  tempfile 
12- import  json 
1310import  os 
1411
1512# ============================================================================= 
@@ -25,24 +22,6 @@ def parse_repository(repository):
2522        print (f"❌ Invalid repository format: { repository }  . Expected: 'owner/name'" )
2623        return  None , None 
2724
28- def  make_github_request (url , headers , method = 'GET' , data = None ):
29-     """Make a GitHub API request with consistent error handling""" 
30-     try :
31-         if  method .upper () ==  'GET' :
32-             response  =  requests .get (url , headers = headers )
33-         elif  method .upper () ==  'POST' :
34-             response  =  requests .post (url , headers = headers , json = data )
35-         elif  method .upper () ==  'PATCH' :
36-             response  =  requests .patch (url , headers = headers , json = data )
37-         else :
38-             print (f"❌ Unsupported HTTP method: { method }  " )
39-             return  None 
40-         
41-         return  response 
42-     except  requests .RequestException  as  e :
43-         print (f"❌ Request failed: { e }  " )
44-         return  None 
45- 
4625# ============================================================================= 
4726# GitHub API Functions 
4827# ============================================================================= 
@@ -176,15 +155,36 @@ def find_line_number_of_change(original_content, old_value):
176155# ============================================================================= 
177156
178157def  process_image_list (image_list ):
179-     """Process a list of images - can be URLs or local file paths """ 
158+     """Process a list of images - converts local paths to GitLab artifact URLs when in CI. """ 
180159    if  not  image_list :
181160        return  []
182161
183162    processed_images  =  []
163+     ci_project_url  =  os .environ .get ('CI_PROJECT_URL' )
164+     ci_job_id  =  os .environ .get ('CI_JOB_ID' )
165+     ci_project_dir  =  os .environ .get ('CI_PROJECT_DIR' , os .getcwd ())
166+     
184167    for  img  in  image_list :
185-         # Accept both URLs and local file paths 
186-         # Local paths will be handled by gh CLI when creating the comment 
187-         processed_images .append (img )
168+         # If it's already a URL, accept as-is 
169+         if  isinstance (img , str ) and  (img .startswith ('http://' ) or  img .startswith ('https://' )):
170+             processed_images .append (img )
171+             continue 
172+ 
173+         # Handle local file paths - convert to GitLab artifact URLs 
174+         if  isinstance (img , str ) and  os .path .exists (img ):
175+             if  ci_project_url  and  ci_job_id :
176+                 try :
177+                     rel_path  =  os .path .relpath (os .path .abspath (img ), start = os .path .abspath (ci_project_dir ))
178+                     # Use raw endpoint so images render inline 
179+                     artifact_url  =  f"{ ci_project_url }  /-/jobs/{ ci_job_id }  /artifacts/raw/{ rel_path }  " 
180+                     processed_images .append (artifact_url )
181+                     print (f"Using artifact URL for image: { artifact_url }  " )
182+                 except  Exception  as  e :
183+                     print (f"⚠️ Could not compute artifact URL for { img }  : { e }  . Skipping." )
184+             else :
185+                 print (f"⚠️ CI environment variables not available; skipping local image: { img }  " )
186+         else :
187+             print (f"⚠️ Invalid image entry (not a URL or existing file): { img }  " )
188188
189189    return  processed_images 
190190
@@ -210,11 +210,8 @@ def create_pr_suggestion(repo_owner, repo_name, pr_number, calibration_file, xml
210210    lines  =  content .split ('\n ' ) if  content  else  []
211211    current_line  =  lines [line_number  -  1 ].strip () if  line_number  <=  len (lines ) else  "Line not found" 
212212
213-     # Process image lists - should be URLs from gh CLI upload 
214-     print ("\n Processing before images..." )
213+     # Process image lists 
215214    processed_before_images  =  process_image_list (before_images )
216-     
217-     print ("\n Processing after images..." )
218215    processed_after_images  =  process_image_list (after_images )
219216
220217    comment_body  =  f"""{ bot_comment_base } { ' (Updated)'  if  existing_comment_id  else  '' }  
@@ -238,110 +235,44 @@ def create_pr_suggestion(repo_owner, repo_name, pr_number, calibration_file, xml
238235Please update the calibration URL in `{ xml_file }  ` at line { line_number }  .""" 
239236
240237    # Add before images section if provided 
241-     if  processed_before_images   and   len ( processed_before_images )  >   0 :
238+     if  processed_before_images :
242239        comment_body  +=  "\n \n ---\n \n ### 📊 Before Calibration Update\n \n " 
243240        for  i , img_url  in  enumerate (processed_before_images , 1 ):
244241            comment_body  +=  f"\n \n " 
245242
246243    # Add after images section if provided 
247-     if  processed_after_images   and   len ( processed_after_images )  >   0 :
244+     if  processed_after_images :
248245        comment_body  +=  "\n \n ---\n \n ### 📈 After Calibration Update\n \n " 
249246        for  i , img_url  in  enumerate (processed_after_images , 1 ):
250247            comment_body  +=  f"\n \n " 
251248
252249    if  existing_comment_id :
253-         # Update existing comment using gh CLI  
250+         # Update existing comment 
254251        print (f"Updating existing comment { existing_comment_id }  ..." )
255-         
256-         # Write comment body to temp file 
257-         with  tempfile .NamedTemporaryFile (mode = 'w' , suffix = '.md' , delete = False ) as  f :
258-             f .write (comment_body )
259-             temp_file  =  f .name 
260-         
261-         try :
262-             # gh api to update comment 
263-             result  =  subprocess .run (
264-                 ['gh' , 'api' , 
265-                  f'/repos/{ repo_owner }  /{ repo_name }  /issues/comments/{ existing_comment_id }  ' ,
266-                  '-X' , 'PATCH' ,
267-                  '-F' , f'body=@{ temp_file }  ' ],
268-                 capture_output = True ,
269-                 text = True ,
270-                 check = True 
271-             )
252+         update_url  =  f"https://api.github.com/repos/{ repo_owner }  /{ repo_name }  /issues/comments/{ existing_comment_id }  " 
253+         update_data  =  {'body' : comment_body }
254+         response  =  requests .patch (update_url , headers = headers , json = update_data )
255+         if  response .status_code  ==  200 :
272256            print ("✅ Existing PR comment updated successfully" )
273-             return  json .loads (result .stdout )
274-         except  subprocess .CalledProcessError  as  e :
275-             print (f"❌ Failed to update existing comment: { e }  " )
276-             print (f"   Error: { e .stderr }  " )
257+             return  response .json ()
258+         else :
259+             print (f"❌ Failed to update existing comment: { response .status_code } \n { response .text }  " )
277260            return  None 
278-         finally :
279-             os .unlink (temp_file )
280261    else :
281-         # Create new regular PR comment using gh CLI  
262+         # Create new regular PR comment 
282263        print ("Creating new PR comment..." )
283-         
284-         # Write comment body to temp file 
285-         with  tempfile .NamedTemporaryFile (mode = 'w' , suffix = '.md' , delete = False ) as  f :
286-             f .write (comment_body )
287-             temp_file  =  f .name 
288-         
289-         try :
290-             # gh pr comment will automatically upload local image files 
291-             result  =  subprocess .run (
292-                 ['gh' , 'pr' , 'comment' , str (pr_number ),
293-                  '--repo' , f'{ repo_owner }  /{ repo_name }  ' ,
294-                  '--body-file' , temp_file ],
295-                 capture_output = True ,
296-                 text = True ,
297-                 check = True 
298-             )
264+         comment_url  =  f"https://api.github.com/repos/{ repo_owner }  /{ repo_name }  /issues/{ pr_number }  /comments" 
265+         comment_data  =  {'body' : comment_body }
266+         response  =  requests .post (comment_url , headers = headers , json = comment_data )
267+         if  response .status_code  ==  201 :
299268            print ("✅ New PR comment created successfully" )
300-             # gh pr comment returns URL, parse to get comment data 
301-             return  {'html_url' : result .stdout .strip ()}
302-         except  subprocess .CalledProcessError  as  e :
303-             print (f"❌ Failed to create PR comment: { e }  " )
304-             print (f"   Error: { e .stderr }  " )
269+             return  response .json ()
270+         else :
271+             print (f"❌ Failed to create PR comment: { response .status_code } \n { response .text }  " )
305272            return  None 
306-         finally :
307-             os .unlink (temp_file )
308- 
309- def  find_existing_bot_comment (repo_owner , repo_name , pr_number , bot_comment_base , xml_file , line_number , github_token ):
310-     """Find existing bot comment on the specific line""" 
311-     print (f"Checking for existing bot comments on line { line_number }  ..." )
312-     
313-     headers  =  {
314-         'Accept' : 'application/vnd.github+json' ,
315-         'Authorization' : f'token { github_token }  ' 
316-     }
317-         
318-     # Get all review comments for the PR 
319-     comments_url  =  f"https://api.github.com/repos/{ repo_owner }  /{ repo_name }  /pulls/{ pr_number }  /comments" 
320-     response  =  requests .get (comments_url , headers = headers )
321-     
322-     if  response .status_code  !=  200 :
323-         print (f"❌ Failed to get PR comments: { response .status_code }  " )
324-         return  None 
325-     
326-     comments  =  response .json ()
327-     
328-     # Look for existing bot comment on the same line and file 
329-     for  comment  in  comments :
330-         # Check if it's from the bot (contains the bot identifier) 
331-         # and on the same file and line 
332-         if  (comment .get ('path' ) ==  xml_file  and  
333-             comment .get ('line' ) ==  line_number  and 
334-             bot_comment_base  in  comment .get ('body' , '' )):
335-             print (f"✅ Found existing bot comment: { comment ['id' ]}  " )
336-             return  comment ['id' ]
337-     
338-     print ("No existing bot comment found" )
339-     return  None 
340273
341274def  find_existing_bot_comment_general (repo_owner , repo_name , pr_number , bot_comment_base , github_token ):
342275    """Find existing bot comment (general PR comment, not line-specific)""" 
343-     print (f"Checking for existing bot comments in PR #{ pr_number }  ..." )
344-     
345276    headers  =  {
346277        'Accept' : 'application/vnd.github+json' ,
347278        'Authorization' : f'token { github_token }  ' 
@@ -355,11 +286,8 @@ def find_existing_bot_comment_general(repo_owner, repo_name, pr_number, bot_comm
355286        print (f"❌ Failed to get PR comments: { response .status_code }  " )
356287        return  None 
357288
358-     comments  =  response .json ()
359-     
360289    # Look for existing bot comment 
361-     for  comment  in  comments :
362-         # Check if it's from the bot (contains the bot identifier) 
290+     for  comment  in  response .json ():
363291        if  bot_comment_base  in  comment .get ('body' , '' ):
364292            print (f"✅ Found existing bot comment: { comment ['id' ]}  " )
365293            return  comment ['id' ]
0 commit comments