From 9815a1a3e0afb23b5176c851278fcded2e897905 Mon Sep 17 00:00:00 2001 From: Christian Krakau-Louis Date: Wed, 16 Apr 2025 15:39:21 +0200 Subject: [PATCH] feat: Add script for automated GitHub issue creation with environment variable validation and issue management --- scripts/README_github_issues.md | 67 +++++++++ scripts/create_github_issues.py | 249 ++++++++++++++++++++++++++++++++ 2 files changed, 316 insertions(+) create mode 100644 scripts/README_github_issues.md create mode 100644 scripts/create_github_issues.py diff --git a/scripts/README_github_issues.md b/scripts/README_github_issues.md new file mode 100644 index 0000000..b1129f0 --- /dev/null +++ b/scripts/README_github_issues.md @@ -0,0 +1,67 @@ +# GitHub Issue Creation + +This directory contains scripts for creating GitHub issues for the LeagueLedger project. + +## Create GitHub Issues Script + +The `create_github_issues.py` script allows you to automatically create structured GitHub issues for your project features. + +### Prerequisites + +1. A GitHub account +2. A personal access token with "repo" scope (Create one at https://github.com/settings/tokens) +3. Python 3.6+ installed with the requests library + +### Setup + +1. Update the script with your GitHub information: + ```python + REPO_OWNER = "YOUR_GITHUB_USERNAME" # Replace with your GitHub username + REPO_NAME = "LeagueLedger" # Replace with your repository name + ``` + +2. Set your GitHub token as an environment variable: + + **Windows:** + ```cmd + set GITHUB_TOKEN=your_token_here + ``` + + **Mac/Linux:** + ```bash + export GITHUB_TOKEN=your_token_here + ``` + +### Usage + +Run the script from the command line: + +```bash +python scripts/create_github_issues.py +``` + +This will create issues for the profile management features: +- Add profile picture update functionality +- Implement username change capability +- Create account deletion process +- Add profile privacy settings +- Implement social media integration for profiles + +### Customizing Issues + +You can customize the issues by editing the `PROFILE_ISSUES` list in the script. Each issue consists of: + +- `title`: The issue title +- `body`: Markdown-formatted issue description +- `labels`: List of labels to apply to the issue + +### Best Practices + +- Always use clear titles and detailed descriptions +- Include requirements and acceptance criteria in each issue +- Use labels to categorize issues for better organization +- Mention related areas of code that might be affected + +## Adding More Issue Sets + +To add more feature sets beyond profile management, create new issue lists following the same format and add them to the script. \ No newline at end of file diff --git a/scripts/create_github_issues.py b/scripts/create_github_issues.py new file mode 100644 index 0000000..f8b466a --- /dev/null +++ b/scripts/create_github_issues.py @@ -0,0 +1,249 @@ +#!/usr/bin/env python3 +""" +Script to create GitHub issues for LeagueLedger project. +""" +import requests +import os +import json +import sys +import re +from typing import Dict, List, Any +from dotenv import load_dotenv +from pathlib import Path + +# Load environment variables from .env file +env_path = Path(__file__).parent / '.env' +load_dotenv(dotenv_path=env_path) + +# GitHub repository information +GITHUB_API_URL = "https://api.github.com" +REPO_OWNER = os.getenv("REPO_OWNER") +REPO_NAME = os.getenv("REPO_NAME") + +# GitHub authentication - use a personal access token +GITHUB_TOKEN = os.getenv("GITHUB_TOKEN") + +def validate_env(): + """Validate that all required environment variables are set.""" + missing_vars = [] + if not GITHUB_TOKEN: + missing_vars.append("GITHUB_TOKEN") + if not REPO_OWNER: + missing_vars.append("REPO_OWNER") + if not REPO_NAME: + missing_vars.append("REPO_NAME") + + if missing_vars: + print("Error: The following environment variables are missing in your .env file:") + for var in missing_vars: + print(f"- {var}") + print("\nPlease update your .env file in the scripts directory.") + return False + return True + +def get_all_issues(): + """Get all open issues from the repository.""" + url = f"{GITHUB_API_URL}/repos/{REPO_OWNER}/{REPO_NAME}/issues" + params = { + "state": "open", + "per_page": 100 + } + + headers = { + "Authorization": f"token {GITHUB_TOKEN}", + "Accept": "application/vnd.github.v3+json" + } + + response = requests.get(url, params=params, headers=headers) + + if response.status_code == 200: + return response.json() + else: + print(f"Failed to fetch issues: {response.status_code}") + print(response.text) + return [] + +def close_issue(issue_number): + """Close an issue by its number.""" + url = f"{GITHUB_API_URL}/repos/{REPO_OWNER}/{REPO_NAME}/issues/{issue_number}" + + headers = { + "Authorization": f"token {GITHUB_TOKEN}", + "Accept": "application/vnd.github.v3+json" + } + + data = { + "state": "closed" + } + + response = requests.patch(url, json=data, headers=headers) + + if response.status_code == 200: + print(f"Successfully closed issue #{issue_number}") + return True + else: + print(f"Failed to close issue #{issue_number}: {response.status_code}") + print(response.text) + return False + +def delete_all_issues(): + """Close all open issues in the repository.""" + issues = get_all_issues() + + if not issues: + print("No open issues found.") + return + + print(f"Found {len(issues)} open issues. Closing all...") + + for issue in issues: + close_issue(issue["number"]) + + print("Finished closing issues.") + +def create_github_issue(issue_data: Dict[str, Any]) -> Dict[str, Any]: + """Create a GitHub issue using the GitHub API.""" + url = f"{GITHUB_API_URL}/repos/{REPO_OWNER}/{REPO_NAME}/issues" + + headers = { + "Authorization": f"token {GITHUB_TOKEN}", + "Accept": "application/vnd.github.v3+json" + } + + response = requests.post(url, json=issue_data, headers=headers) + + if response.status_code == 201: + print(f"Successfully created issue: {issue_data['title']}") + return response.json() + else: + print(f"Failed to create issue: {response.status_code}") + print(response.text) + return {} + +def parse_todo_file(file_path): + """Parse the TODO.md file and extract the issues.""" + try: + with open(file_path, 'r', encoding='utf-8') as f: + content = f.read() + except Exception as e: + print(f"Error reading TODO.md file: {e}") + return [] + + # Split the content by sections (## headings) + sections = re.split(r'^## ', content, flags=re.MULTILINE) + + # The first section is the title and intro, skip it + sections = sections[1:] + + all_issues = [] + + for section in sections: + # Get the section title from the first line + title_line = section.split('\n', 1)[0].strip() + section_content = section.split('\n', 1)[1].strip() if len(section.split('\n', 1)) > 1 else "" + + # Create a main issue for the section + section_issue = { + "title": title_line, + "body": f"## {title_line}\n\nThis issue tracks the progress of all {title_line.lower()} tasks.\n\n### Tasks\n{section_content}", + "labels": ["epic", title_line.lower().replace(' & ', '-').replace(' ', '-')] + } + all_issues.append(section_issue) + + # Parse individual tasks in the section + tasks = parse_section_tasks(section_content, title_line) + all_issues.extend(tasks) + + return all_issues + +def parse_section_tasks(section_content, section_title): + """Parse individual tasks from a section.""" + issues = [] + + # Extract tasks at the top level (directly under the section) + tasks = re.findall(r'^- \[ \] (.+)$', section_content, re.MULTILINE) + for task in tasks: + # Skip tasks that are groups (they have subtasks and usually end with a colon) + if task.strip().endswith(':') or task.strip().startswith('**'): + continue + + issue = { + "title": task.strip(), + "body": f"## Description\nImplement the following task from {section_title}:\n\n{task.strip()}\n\n## Related\nPart of the {section_title} epic.", + "labels": ["task", section_title.lower().replace(' & ', '-').replace(' ', '-')] + } + issues.append(issue) + + # Extract grouped tasks (subtasks under headings like "User Management") + groups = re.findall(r'- \[ \] \*\*(.*?)\*\*\s*\n((?: - \[ \].*\n)*)', section_content, re.MULTILINE) + + for group_name, group_tasks_text in groups: + # Create a group issue + group_issue = { + "title": f"{group_name}", + "body": f"## Description\nImplement {group_name} features for {section_title}.\n\n## Tasks\n{group_tasks_text}\n\n## Related\nPart of the {section_title} epic.", + "labels": ["group", section_title.lower().replace(' & ', '-').replace(' ', '-'), group_name.lower().replace(' ', '-')] + } + issues.append(group_issue) + + # Add individual subtasks + subtasks = re.findall(r' - \[ \] (.*)', group_tasks_text) + for subtask in subtasks: + subtask_issue = { + "title": f"{group_name}: {subtask.strip()}", + "body": f"## Description\nImplement the following {group_name} feature:\n\n{subtask.strip()}\n\n## Related\nPart of the {group_name} group in the {section_title} epic.", + "labels": ["subtask", section_title.lower().replace(' & ', '-').replace(' ', '-'), group_name.lower().replace(' ', '-')] + } + issues.append(subtask_issue) + + return issues + +def main(): + """Main function to create all the issues.""" + print("LeagueLedger GitHub Issue Creator") + print("=================================") + + # Validate environment variables + if not validate_env(): + sys.exit(1) + + print(f"Repository: {REPO_OWNER}/{REPO_NAME}") + + # Delete existing issues + delete_option = input("Do you want to close all existing open issues? (y/n): ").lower() + if delete_option == 'y': + delete_all_issues() + + # Parse TODO.md and create issues + todo_path = Path(__file__).parent.parent / 'TODO.md' + if not todo_path.exists(): + print(f"Error: TODO.md file not found at {todo_path}") + sys.exit(1) + + print(f"Parsing TODO.md at {todo_path}...") + issues = parse_todo_file(todo_path) + + print(f"Found {len(issues)} issues to create.") + create_option = input(f"Do you want to create {len(issues)} GitHub issues? (y/n): ").lower() + + if create_option != 'y': + print("Operation cancelled.") + sys.exit(0) + + # Create issues + created_issues = [] + for issue in issues: + result = create_github_issue(issue) + if result: + created_issues.append(result) + + # Show results + if created_issues: + print("\nSuccessfully created issues:") + for issue in created_issues: + print(f"- #{issue['number']} {issue['title']}: {issue['html_url']}") + + print("\nCompleted creating GitHub issues.") + +if __name__ == "__main__": + main() \ No newline at end of file