GitHub Issuesを自動でREADMEに反映させる仕組みの作り方

## はじめに

GitHubのIssuesでタスク管理をしている場合、現在オープンになっているIssueを一覧化して可視化したいことがあります。この記事では、GitHub Actionsを使用して自動的にIssueの一覧をREADMEに反映させる方法を紹介します。

## 実装の概要

この実装では以下の機能を実現しています。

1. Issueの変化を自動取得
2. ラベルごとに分類して構造化
3. 自動的にREADMEを更新
4. 手動での更新も可能

自動更新された`README.md`は以下のように表示されます。
![スクリーンショット 2025-03-02 4.19.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3876904/234235dd-8273-4bfc-95b2-50975483a17d.png)


## 実装方法

### 1. Pythonスクリプト (update_issues.py)

GitHubのAPIを使用してIssueを取得し、README.mdを生成するスクリプトです。

```python
import os
import requests
from collections import defaultdict
from datetime import datetime
from github import Github

def get_issues(repo_name, token):
    """
    GitHubのオープンなIssueを取得する
    
    Args:
        repo_name (str): リポジトリ名 (例: "username/repository")
        token (str): GitHub Personal Access Token
    
    Returns:
        list: Issue情報のリスト
    """
    g = Github(token)
    repo = g.get_repo(repo_name)
    
    issues = []
    # stateをopenに指定して、オープンなissueのみを取得
    for issue in repo.get_issues(state='open'):
        issue_data = {
            'number': issue.number,
            'title': issue.title,
            'state': issue.state,
            'created_at': issue.created_at,
            'labels': [label.name for label in issue.labels],
            'url': issue.html_url
        }
        issues.append(issue_data)
    
    return issues

def organize_by_label(issues):
    """
    Issueをラベルごとに整理する
    
    Args:
        issues (list): Issue情報のリスト
    
    Returns:
        dict: ラベルごとにIssueを整理した辞書
    """
    label_issues = defaultdict(list)
    
    # ラベルなしのIssueも考慮
    for issue in issues:
        if not issue['labels']:
            label_issues['no_label'].append(issue)
        else:
            for label in issue['labels']:
                label_issues[label].append(issue)
    
    return dict(label_issues)

def create_readme_content(organized_issues):
    """
    整理されたIssue情報からREADMEの内容を生成する
    
    Args:
        organized_issues (dict): ラベルごとに整理されたIssue情報
    
    Returns:
        str: README用のマークダウン文字列
    """
    now = datetime.now()
    
    content = ["# Issue Summary\n"]
    content.append(f"*Last updated: {now.strftime('%Y-%m-%d %H:%M:%S')}*\n")
    content.append(f"*Update ID: {now.timestamp()}*\n")  # 必ず差分が出るように一意のIDを追加
    
    total_issues = sum(len(issues) for issues in organized_issues.values())
    content.append(f"**Total Open Issues: {total_issues}**\n")
    
    # ラベルごとにIssueを表示
    for label, issues in sorted(organized_issues.items()):
        content.append(f"## {label}\n")
        content.append(f"*Number of issues: {len(issues)}*\n")
        
        # Issue一覧
        for issue in sorted(issues, key=lambda x: x['created_at'], reverse=True):
            created_date = issue['created_at'].strftime('%Y-%m-%d')
            content.append(f"- 🟢 [{issue['title']}]({issue['url']}) (#{issue['number']}) - created: {created_date}")
        
        content.append("\n")
    
    # 実行ログ
    content.append("## Update Log\n")
    content.append(f"- Script executed at: {now.strftime('%Y-%m-%d %H:%M:%S.%f')} UTC\n")
    
    return "\n".join(content)

def main():
    # 環境変数から認証情報を取得
    token = os.environ.get("GITHUB_TOKEN")
    repo_name = os.environ.get("GITHUB_REPOSITORY")
    
    if not token or not repo_name:
        raise ValueError("Required environment variables are not set")
    
    # Issueを取得して整理
    issues = get_issues(repo_name, token)
    organized_issues = organize_by_label(issues)
    
    # README用の内容を生成
    readme_content = create_readme_content(organized_issues)
    
    # README.mdに書き出し
    with open("README.md", "w", encoding="utf-8") as f:
        f.write(readme_content)

if __name__ == "__main__":
    main()
```

### 2. GitHub Actions Workflow (.github/workflows/update-issues.yml)

Issueの作成削除時とラベル付与削除時と手動実行を可能にするワークフローの設定です。

```yaml
name: Update Issue Summary

on:
  issues:
    types: 
      - opened                # Issue作成時
      - closed               # Issue close時
      - labeled              # ラベル付与時
      - unlabeled            # ラベル削除時
  workflow_dispatch:      # 手動実行も可能

jobs:
  update-readme:
    runs-on: ubuntu-latest
    
    steps:
    - uses: actions/checkout@v2
      with:
        fetch-depth: 1
    
    - name: Set up Python
      uses: actions/setup-python@v2
      with:
        python-version: '3.x'
    
    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip
        pip install PyGithub requests
    
    - name: Generate new README
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
      run: python update_issues.py
    
    - name: Commit and Push changes
      run: |
        git config --local user.email "action@github.com"
        git config --local user.name "GitHub Action"
        git add README.md
        git diff --quiet && git diff --staged --quiet || (git commit -m "Update Issue summary [skip ci]" && git push)
```

## セットアップ手順

1. リポジトリのセットアップ
   - 新しいリポジトリを作成
   - `update_issues.py`をリポジトリのルートに配置
   - `.github/workflows/update-issues.yml`を作成

2. 環境設定
   - 特別な設定は必要ありません
   - GitHub ActionsのデフォルトのGITHUB_TOKENとGITHUB_REPOSITORYが自動的に利用されます

## コードの解説

### 主要な機能

1. Issue取得（get_issues関数）
   ```python
   def get_issues(repo_name, token):
       # state='open'を指定してオープンなIssueのみを取得
       for issue in repo.get_issues(state='open'):
           # ...
   ```
   - オープン状態のIssueのみを取得
   - タイトル、作成日、ラベルなどの情報を収集

2. ラベルによる整理（organize_by_label関数）
   ```python
   def organize_by_label(issues):
       label_issues = defaultdict(list)
       # ラベルなしのIssueも'no_label'として管理
   ```
   - 各Issueをラベルごとに分類
   - ラベルのないIssueも適切に管理

### GitHub Actionsの特徴的な部分

```yaml
on:
  issues:
    types: 
      - opened                # Issue作成時
      - closed               # Issue close時
      - labeled              # ラベル付与時
      - unlabeled            # ラベル削除時
  workflow_dispatch:         # 手動実行も可能
```
- `workflow_dispatch`: 手動実行を可能にする設定

## 動作確認方法

1. 手動実行による確認
   - GitHubリポジトリの「Actions」タブに移動
   - 「Update Issue Summary」ワークフローを選択
   - 「Run workflow」ボタンをクリック

2. 実行結果の確認
   - READMEが更新されているか確認
   - 実行ログでエラーが発生していないか確認


## カスタマイズのポイント
1. README形式のカスタマイズ
   - `create_readme_content`関数を編集することで、出力形式を変更可能

## まとめ

この実装により、以下のことが実現できます。
- Issueの自動的な可視化
- ラベルを使った効率的なタスク管理

実装は比較的シンプルですが、タスク管理の効率化に役立つ機能となっています。