github_api.py 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. """
  2. GitHub API module.
  3. This module handles interactions with the GitHub API for posting comments to PRs.
  4. """
  5. import os
  6. import requests
  7. from .util import log, file_exists
  8. def generate_comment(base_ext_cov, pr_ext_cov, ext_decreased, ext_diff,
  9. base_web_cov, pr_web_cov, web_decreased, web_diff):
  10. """
  11. Generate a PR comment with coverage comparison.
  12. Args:
  13. base_ext_cov: Base branch extension coverage
  14. pr_ext_cov: PR branch extension coverage
  15. ext_decreased: Whether extension coverage decreased
  16. ext_diff: Extension coverage difference
  17. base_web_cov: Base branch webview coverage
  18. pr_web_cov: PR branch webview coverage
  19. web_decreased: Whether webview coverage decreased
  20. web_diff: Webview coverage difference
  21. Returns:
  22. Comment text
  23. """
  24. from datetime import datetime
  25. # Convert string inputs to appropriate types
  26. try:
  27. base_ext_cov = float(base_ext_cov)
  28. pr_ext_cov = float(pr_ext_cov)
  29. # Handle ext_decreased as either string or boolean
  30. if isinstance(ext_decreased, str):
  31. ext_decreased = ext_decreased.lower() == 'true'
  32. else:
  33. ext_decreased = bool(ext_decreased)
  34. ext_diff = float(ext_diff)
  35. base_web_cov = float(base_web_cov)
  36. pr_web_cov = float(pr_web_cov)
  37. # Handle web_decreased as either string or boolean
  38. if isinstance(web_decreased, str):
  39. web_decreased = web_decreased.lower() == 'true'
  40. else:
  41. web_decreased = bool(web_decreased)
  42. web_diff = float(web_diff)
  43. except ValueError as e:
  44. log(f"Error converting input values: {e}")
  45. return ""
  46. # Add a unique identifier to find this comment later
  47. comment = '<!-- COVERAGE_REPORT -->\n'
  48. comment += '## Coverage Report\n\n'
  49. # Extension coverage
  50. comment += '### Extension Coverage\n\n'
  51. comment += f'Base branch: {base_ext_cov:.0f}%\n\n'
  52. comment += f'PR branch: {pr_ext_cov:.0f}%\n\n'
  53. if ext_decreased:
  54. comment += f'⚠️ **Warning: Coverage decreased by {ext_diff:.2f}%**\n\n'
  55. comment += 'Consider adding tests to cover your changes.\n\n'
  56. else:
  57. comment += '✅ Coverage increased or remained the same\n\n'
  58. # Webview coverage
  59. comment += '### Webview Coverage\n\n'
  60. comment += f'Base branch: {base_web_cov:.0f}%\n\n'
  61. comment += f'PR branch: {pr_web_cov:.0f}%\n\n'
  62. if web_decreased:
  63. comment += f'⚠️ **Warning: Coverage decreased by {web_diff:.2f}%**\n\n'
  64. comment += 'Consider adding tests to cover your changes.\n\n'
  65. else:
  66. comment += '✅ Coverage increased or remained the same\n\n'
  67. # Overall assessment
  68. comment += '### Overall Assessment\n\n'
  69. if ext_decreased or web_decreased:
  70. comment += '⚠️ **Test coverage has decreased in this PR**\n\n'
  71. comment += 'Please consider adding tests to maintain or improve coverage.\n\n'
  72. else:
  73. comment += '✅ **Test coverage has been maintained or improved**\n\n'
  74. # Add timestamp
  75. comment += f'\n\n<sub>Last updated: {datetime.now().isoformat()}</sub>'
  76. return comment
  77. def post_comment(comment_path, pr_number, repo, token=None):
  78. """
  79. Post a comment to a GitHub PR.
  80. Args:
  81. comment_path: Path to the file containing the comment text
  82. pr_number: PR number
  83. repo: Repository in the format "owner/repo"
  84. token: GitHub token
  85. """
  86. if not file_exists(comment_path):
  87. log(f"Error: Comment file {comment_path} does not exist")
  88. return
  89. with open(comment_path, 'r') as f:
  90. comment_body = f.read()
  91. if not token:
  92. token = os.environ.get('GITHUB_TOKEN')
  93. if not token:
  94. log("Error: GitHub token not provided")
  95. return
  96. # Find existing comment
  97. headers = {
  98. 'Authorization': f'token {token}',
  99. 'Accept': 'application/vnd.github.v3+json'
  100. }
  101. # Get all comments
  102. comments_url = f'https://api.github.com/repos/{repo}/issues/{pr_number}/comments'
  103. log(f"Getting comments from: {comments_url}")
  104. response = requests.get(comments_url, headers=headers)
  105. if response.status_code != 200:
  106. log(f"Error getting comments: {response.status_code} - {response.text}")
  107. return
  108. comments = response.json()
  109. log(f"Found {len(comments)} existing comments")
  110. # Find comment with our identifier
  111. comment_id = None
  112. for comment in comments:
  113. if '<!-- COVERAGE_REPORT -->' in comment['body']:
  114. comment_id = comment['id']
  115. log(f"Found existing coverage report comment with ID: {comment_id}")
  116. break
  117. if comment_id:
  118. # Update existing comment
  119. update_url = f'https://api.github.com/repos/{repo}/issues/comments/{comment_id}'
  120. log(f"Updating existing comment at: {update_url}")
  121. response = requests.patch(update_url, headers=headers, json={'body': comment_body})
  122. if response.status_code == 200:
  123. log(f"Successfully updated existing comment: {comment_id}")
  124. else:
  125. log(f"Error updating comment: {response.status_code} - {response.text}")
  126. else:
  127. # Create new comment
  128. log(f"Creating new comment at: {comments_url}")
  129. response = requests.post(comments_url, headers=headers, json={'body': comment_body})
  130. if response.status_code == 201:
  131. log("Successfully created new comment")
  132. else:
  133. log(f"Error creating comment: {response.status_code} - {response.text}")
  134. def set_github_output(name, value):
  135. """
  136. Set GitHub Actions output variable.
  137. Args:
  138. name: Output variable name
  139. value: Output variable value
  140. """
  141. # Write to the GitHub output file if available
  142. if 'GITHUB_OUTPUT' in os.environ:
  143. with open(os.environ['GITHUB_OUTPUT'], 'a') as f:
  144. f.write(f"{name}={value}\n")
  145. else:
  146. # Fallback to the deprecated method for backward compatibility
  147. log(f"::set-output name={name}::{value}")
  148. # Also print for human readability
  149. log(f"{name}: {value}")