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