feat: Add script for automated GitHub issue creation with environment variable validation and issue management
This commit is contained in:
@@ -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.
|
||||||
@@ -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()
|
||||||
Reference in New Issue
Block a user