diff --git a/src/check_issues.py b/src/check_issues.py new file mode 100644 index 0000000..f7768bc --- /dev/null +++ b/src/check_issues.py @@ -0,0 +1,103 @@ +""" +Main script to check GitHub issues with caching optimization +""" +from response_agent import process_issue +from triggers import ( + should_run_detailed_checks, + has_blech_bot_tag, + has_generate_edit_command_trigger, + has_bot_response, + has_user_feedback, + has_develop_issue_trigger, + has_pull_request_trigger, + has_pr_creation_comment, + has_user_comment_on_pr +) +from git_utils import ( + get_github_client, + get_repository, + get_open_issues, + load_cache_from_file, + save_cache_to_file +) +from github import Issue +import os +import sys +from typing import Dict, List + +# Add the src directory to the path if needed +sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) + + +def check_all_triggers(issue: Issue) -> Dict[str, bool]: + """ + Run all trigger checks on an issue + + Args: + issue: The GitHub issue to check + + Returns: + Dictionary with trigger names as keys and boolean results as values + """ + return { + 'has_blech_bot_tag': has_blech_bot_tag(issue), + 'has_generate_edit_command_trigger': has_generate_edit_command_trigger(issue), + 'has_bot_response': has_bot_response(issue), + 'has_user_feedback': has_user_feedback(issue), + 'has_develop_issue_trigger': has_develop_issue_trigger(issue), + 'has_pull_request_trigger': has_pull_request_trigger(issue), + 'has_pr_creation_comment': has_pr_creation_comment(issue)[0], + 'has_user_comment_on_pr': has_user_comment_on_pr(issue) + } + + +def main(): + """Main function to check issues with caching optimization""" + # Load the cache from file + cache = load_cache_from_file() + + # Initialize GitHub client and get repository + client = get_github_client() + + # Get repositories from config file + repo_names = [] + with open('config/repos.txt', 'r') as f: + repo_names = [line.strip() for line in f if line.strip()] + + for repo_name in repo_names: + try: + repo = get_repository(client, repo_name) + issues = get_open_issues(repo) + + print(f"Checking {len(issues)} open issues in {repo_name}") + + for issue in issues: + # Check if we need to run detailed checks + if should_run_detailed_checks(issue, cache): + print( + f"Changes detected in issue #{issue.number}, running detailed checks") + + # Run all trigger checks + trigger_results = check_all_triggers(issue) + + # Process the issue based on trigger results + if any(trigger_results.values()): + print( + f"Processing issue #{issue.number} with active triggers: {[k for k, v in trigger_results.items() if v]}") + process_issue(issue, repo_name) + else: + print(f"No active triggers for issue #{issue.number}") + else: + print( + f"No changes detected in issue #{issue.number}, skipping detailed checks") + + except Exception as e: + print(f"Error processing repository {repo_name}: {str(e)}") + + # Save the updated cache to file + save_cache_to_file(cache) + print("Cache saved successfully") + + +if __name__ == "__main__": + main() diff --git a/src/git_utils.py b/src/git_utils.py index 2929f87..a4198b8 100644 --- a/src/git_utils.py +++ b/src/git_utils.py @@ -1,7 +1,9 @@ """ Utility functions for interacting with GitHub API """ -from typing import List, Dict, Optional, Tuple +from typing import List, Dict, Optional, Tuple, Any +import json +import pickle import os import subprocess import git @@ -335,9 +337,9 @@ def push_changes_with_authentication( if isinstance(out_thread, IssueComment): write_issue_response(out_thread, error_msg) elif isinstance(out_thread, PullRequest): - pr_comments = list(pull_request.get_issue_comments()) + pr_comments = list(out_thread.get_issue_comments()) if 'Failed to push changes' not in pr_comments[-1].body: - pull_request.create_issue_comment(error_msg) + out_thread.create_issue_comment(error_msg) else: raise ValueError( "Invalid output thread type, must be IssueComment or PullRequest") @@ -375,6 +377,92 @@ def has_linked_pr(issue: Issue) -> bool: return False +def get_issue_timeline_hash(issue: Issue) -> str: + """ + Generate a hash representing the current state of an issue's timeline + + Args: + issue: The GitHub issue to check + + Returns: + A string hash representing the current state of the issue's timeline + """ + # Get comments and their timestamps + comments = get_issue_comments(issue) + comment_data = [(c.id, c.updated_at.timestamp()) for c in comments] + + # Get issue details that might change + issue_data = { + 'updated_at': issue.updated_at.timestamp(), + 'state': issue.state, + 'labels': [label.name for label in issue.labels], + 'comments_count': issue.comments + } + + # Combine all data and create a hash + import hashlib + combined_data = str(comment_data) + str(issue_data) + return hashlib.md5(combined_data.encode()).hexdigest() + + +def cache_issue_timeline(issue: Issue, cache: Dict[int, str]) -> bool: + """ + Cache the timeline of an issue and compare with the current timeline. + + Args: + issue: The GitHub issue to check + cache: A dictionary to store cached timeline hashes + + Returns: + True if changes are detected, False otherwise + """ + issue_number = issue.number + current_hash = get_issue_timeline_hash(issue) + + if issue_number not in cache: + cache[issue_number] = current_hash + return True + + cached_hash = cache[issue_number] + if current_hash != cached_hash: + cache[issue_number] = current_hash + return True + + return False + + +def save_cache_to_file(cache: Dict[int, str], filename: str = 'issue_cache.pkl') -> None: + """ + Save the cache to a file. + + Args: + cache: The cache dictionary to save + filename: The name of the file to save the cache to + """ + with open(filename, 'wb') as f: + pickle.dump(cache, f) + + +def load_cache_from_file(filename: str = 'issue_cache.pkl') -> Dict[int, str]: + """ + Load the cache from a file. + + Args: + filename: The name of the file to load the cache from + + Returns: + The loaded cache dictionary, or an empty dictionary if the file doesn't exist + """ + import os + if os.path.exists(filename): + try: + with open(filename, 'rb') as f: + return pickle.load(f) + except (pickle.PickleError, EOFError): + return {} + return {} + + if __name__ == '__main__': client = get_github_client() repo = get_repository(client, 'katzlabbrandeis/blech_clust') diff --git a/src/triggers.py b/src/triggers.py index 7f3994e..7549aab 100644 --- a/src/triggers.py +++ b/src/triggers.py @@ -2,7 +2,12 @@ Functions to check specific conditions """ from github import Issue -from git_utils import get_issue_comments +from git_utils import ( + get_issue_comments, + cache_issue_timeline, + save_cache_to_file, + load_cache_from_file +) def has_blech_bot_tag(issue: Issue) -> bool: @@ -140,3 +145,21 @@ def has_user_comment_on_pr(issue: Issue) -> bool: if "generated by blech_bot" not in comment.body: return True return False + + +def should_run_detailed_checks(issue: Issue, cache: dict) -> bool: + """ + Determine if detailed trigger checks should be run based on timeline changes. + + This function checks if there have been any changes to the issue timeline + since the last check. If changes are detected, it updates the cache and + returns True to indicate that detailed checks should be run. + + Args: + issue: The GitHub issue to check + cache: A dictionary to store cached timeline hashes + + Returns: + True if detailed checks should be run, False otherwise + """ + return cache_issue_timeline(issue, cache)