GitLabのプロジェクトをまたいだissue一覧を効率的に表示する方法

# はじめに
GitLabのバージョンによっては、プロジェクトごとのタスクは確認できますが、複数プロジェクトにまたがるタスク一覧を一元的に表示する機能がありません
その場合、複数のプロジェクトを横断したissueを管理することが面倒な場合があります

この記事では、GitLab APIを活用して、プロジェクトをまたいだ自分のissueを一覧表示する簡単なツールを作ったので、それを共有します

# 目的
* 複数プロジェクトを横断したissue管理を簡単にする
* 自分がアサインされたissueのみを対象にする
* JSON形式でデータを整形
* 見やすいように出力する

# 必要な準備
* GitLabのPersonal Access Tokenを取得
  * 必要なスコープ（read_api）を指定してトークンを生成
* APIドキュメントの確認
GitLabの公式APIドキュメントはこちらです。
  * 主に使用するエンドポイントは以下です：
    * [/projects](https://gitlab-docs.creationline.com/ee/api/projects.html#list-all-projects)：ユーザーがアクセスできるプロジェクト一覧を取得
    * [/issues](https://gitlab-docs.creationline.com/ee/api/issues.html#list-project-issues)：各プロジェクトのissueを取得

## 注意
[issueの一覧取得](https://gitlab-docs.creationline.com/ee/api/issues.html#list-issues)は以下でできますが、これは自分で作成したissueしか取得できません。
つまり、ほかのチームメンバーが作成して自分にアサインしたissueは取得できません
```
GET /issues
```


そのため、
1. 自分がアクセスできるプロジェクトの一覧を取得
1. そのプロジェクトのissueをの中から自分がアサインされているものを取得
の二段構成でissueを取得する必要があります


# 実装

```python
#!/usr/bin/env python3.12
# -*- coding: utf-8 -*-

import requests

color_dic = {"black":"\033[30m", "red":"\033[31m", "green":"\033[32m", "yellow":"\033[33m", "blue":"\033[34m", "end":"\033[0m"}

TOKEN = "発行したトークン"
BASE_URL = "https://ホスト名/api/v4"
# ヘッダーの設定
HEADERS = {
    "Private-Token": TOKEN
}
QUERY={
    "assignee_id":1 # 自分のUSER ID
    ,"page":1
}

def print_color(text, color="red"):
    print(color_dic[color] + text + color_dic["end"])

def get_project():
    """ユーザーがアクセス可能なプロジェクトを取得"""
    page=0
    result=[]

    while True:
        page=page+1
        url = f"{BASE_URL}/projects?simple=true&page={page}&per_page=20"
        response = requests.get(url, headers=HEADERS)
        try:
            response.raise_for_status()
            if len(response.json()) == 0:
                break
            result.extend(response.json())
        except requests.exceptions.RequestException as e:
            print(f"エラーが発生しました: {e}")
    return result


def get_issue(project_id):
    """指定したプロジェクトのタスクを取得"""

    url = f"{BASE_URL}/projects/{project_id}/issues"
    page=0
    result=[]

    while True:
        page=page+1
        QUERY['page']=page 
        response = requests.get(url, headers=HEADERS, params=QUERY)
        try:
            response.raise_for_status()
            if len(response.json()) == 0:
                break
            result.extend(response.json())
        except requests.exceptions.RequestException as e:
            print(f"エラーが発生しました: {e}")
    return result


def main():
    projects = get_project()
    all_issues=[]
    for project in projects:
       # プロジェクトに対してのループ
        project_id = project['id']
        data={}
        data['project_name']=project['name']
        data['issues']=[]

        results=get_issue(project_id)
        if len(results) == 0:
            continue
        
        print(project['name'])

        for result in results:
            issue={}
            issue['state']=result['state']
            issue['title']=result['title']
            issue['url']=result['web_url']
            data['issues'].append(issue)
            if result['state'] == "closed":
                print("- [*] "+result['title']+"("+result['web_url']+")")
            else :
                print_color("- [ ] "+result['title']+"("+result['web_url']+")","green")

        all_issues.append(data)
    print(json.dumps(all_issues, indent=2,ensure_ascii=False))


if __name__ == '__main__':
    main()

```

## ページネーション
GitLab APIのGETリクエストではデフォルトで一度に20件の結果を返却します(最大100)。
そのため、プロジェクト一覧取得、issue取得ではそれぞれページネーションを行う必要があります

* ページ
  * page=1
* 返却を行う件数
  * per_page=20

## 出力

[*]はcloseしたissue
[ ]は終わってないissue

```
プロジェクト1
- [*] 編集画面レイアウト修正(https://!(^^)!/-/issues/208)
- [*] CodeMirrorを使う(https://!(^^)!/-/issues/207)
- [*] Vue3移行(https://!(^^)!/-/issues/203)
プロジェクト2
- [ ] hoge(https://!(^^)!/-/issues/1)
プロジェクト3
- [ ] 調査(https://!(^^)!/-/issues/8)
プロジェクト4
- [*] Practice010(https://g!(^^)!/-/issues/1)
```

*  print(json.dumps(all_issues, indent=2,ensure_ascii=False))

```json
[
  {
    "project_name": "プロジェクト1",
    "issues": [
      {
        "state": "closed",
        "title": "編集画面レイアウト修正",
        "url": "https://!(^^)!/-/issues/208"
      },
      {
        "state": "closed",
        "title": "CodeMirrorを使う",
        "url": "https://!(^^)!/-/issues/207"
      },
      {
        "state": "closed",
        "title": "Vue3移行",
        "url": "https://!(^^)!/-/issues/203"
      }
    ]
  },
  {
    "project_name": "プロジェクト2",
    "issues": [
      {
        "state": "opened",
        "title": "hoge",
        "url": "https://!(^^)!/-/issues/1"
      }
    ]
  },
  {
    "project_name": "プロジェクト3",
    "issues": [
      {
        "state": "opened",
        "title": "調査",
        "url": "https://!(^^)!/-/issues/8"
      }
    ]
  },
  {
    "project_name": "プロジェクト4",
    "issues": [
      {
        "state": "closed",
        "title": "Practice010",
        "url": "https://!(^^)!/-/issues/1"
      }
    ]
  }
]
```

# 最後に
これで、複数プロジェクトにまたがるタスクを一目で確認できるようになります。
API化したり、コマンド化してカスタマイズすることも可能です