【GitHub GraphQL API】GitHubの草を表示するNotion用のウィジェットを作ってみた

## はじめに

Notion上でGitHubの草（コントリビューション）を確認できると便利だと思い、ウィジェットを作成してみました。

### 使い方
URLのクエリパラメータに`username`を指定して、自分のGitHubアカウント名を入力します。
```txt
https://notion-contribution-widget.azurewebsites.net/?username=your_github_username
```
`background_color`や`color_shceme`などを指定することで自分好みにカスタマイズすることができます。
![img_bw.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3828517/81c6ab70-c74b-4194-81b1-f07904391843.png)


デモページです。

https://www.notion.so/DemoPage-1814e6035cdd8060b088d3d8c19ffc95?pvs=4

iPhoneのウィジェットを参考にして作ってみました。
![img_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3828517/0f1b84fe-f06d-d8b0-bd30-b4fc519dbf72.png)

## 実装

### 1. GitHubAPIを叩いてコントリビューションを取得する

プライベートリポジトリへのコントリビューション数も取得したいので、GitHubが提供するGraphQL APIを使用します。
1. GitHubのアクセストークンを取得する
    1. GitHubにログインし、右上の自分のアイコンをクリックして、Settingsを選択します。次に左側のタブから`Developer settings`を選択し`Personal Access Tokens (classic)`のページに移動します
    2. `Generate new token`をクリックし、必要なスコープを選択してトークンを生成します
    3. トークンをコピーしておきます
2. GraphQLクエリの作成
    以下のようなGraphQLクエリを作成して、特定のユーザー(`username`)の各日(`date`)のコントリビューションデータ(`contributionCount`)を取得します。
    
    ```
    query($username: String!) {
      user(login: $username) {
        contributionsCollection {
          contributionCalendar {
            weeks {
              contributionDays {
                date
                contributionCount
              }
            }
          }
        }
      }
    }
    ```
    
3. Pythonでの実装
    
    以下に、Pythonを使用してGitHub GraphQL APIを叩き、コントリビューションデータを取得するプログラムを示します。
    
    ```python
    TOKEN = os.getenv('TOKEN')
    GITHUB_GRAPHQL_URL = "https://api.github.com/graphql"
    
    def load_contributions(username):
        headers = {
            "Authorization": f"Bearer {TOKEN}",
            "Content-Type": "application/json"
        }
        
        # GraphQLクエリ
        query = """
        query($username: String!) {
            user(login: $username) {
                contributionsCollection {
                    contributionCalendar {
                        weeks {
                            contributionDays {
                                date
                                contributionCount
                            }
                        }
                    }
                }
            }
        }
        """
        
        # リクエスト
        payload = {
            "query": query,
            "variables": {"username": username}
        }
        
        response = requests.post(GITHUB_GRAPHQL_URL, json=payload, headers=headers)
        data = response.json()
        
        # 中略
        
        return contributions, max_count
    ```

### 2. Webサイトを作成する

コントリビューショングラフの色分けには以下の基準を使用します。

- レベル０：コントリビューションなし
- レベル１： $(0, \frac{最大値}{4}]$
- レベル２： $(\frac{最大値}{4}, \frac{最大値}{2}]$
- レベル３： $(\frac{最大値}{2}, \frac{3 *最大値}{4}]$
- レベル４： $(\frac{3*最大値}{4},最大値]$

また、描画する日数を計算しなければいけません。例えば今日が水曜日だとすると7×6+4で合計46日間のデータが必要です。
![img_graph.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3828517/e1b2779c-e4d0-4db7-b106-a7d93760c771.png)


`datetime`モジュールの`weekday`メソッドを使うと日付の曜日を数値で取得することができます。月曜日の場合は0を返すので工夫しました。以下にFlaskの処理例を示します。

```app.py
@app.route('/')
def index():

	# 中略
			
    today = datetime.now()
    dow = (6 if today.weekday() == 6 else 5 - today.weekday()) # 曜日
    size = 49 # サイズ（7の倍数）

    # カレンダー
    calendar_data = []
    for i in range(size - dow):
        day = today - timedelta(days=size - dow - i - 1)
        date_str = day.strftime("%Y-%m-%d")
        count = contributions.get(date_str, 0)
        calendar_data.append({"day": day.day, "color": get_color(count)})

    show_data = [[] for _ in range(len(calendar_data) // 7 + 1)]
    for i in range(len(calendar_data)):
        show_data[i // 7].append(calendar_data[i])

    return render_template('index.html', calendar=show_data, username=username)
```

続いてフロント側の処理も書いていきます。このとき以下のようにWebサイトの中央にウィジェットとして表示したいものが来るようにします。

![img_4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3828517/a61704d4-8dce-dafe-5296-4504242bfaab.png)

コントリビューションレベルに応じたカラーコードは以下の通りです。
| レベル | カラーコード |
| --- | --- |
| 0 | <font color="#2b2b2b">**#2b2b2b**</font> |
| 1 | <font color="#0e4429">**#0e4429**</font> |
| 2 | <font color="#006d32">**#006d32**</font> |
| 3 | <font color="#26a641">**#26a641**</font> |
| 4 | <font color="#39d353">**#39d353**</font> |

また、Notionの背景色とそろえておく必要があります。
| モード | カラーコード |
| --- | --- |
| ライト | <font color="#ffffff">**#ffffff**</font> |
| ダーク | <font color="#191919">**#191919**</font> |

### 3. AzureWebAppsでデプロイする

今回、Azureにデプロイする際にGitHub Actionsを使いたいのでGitHubにコードを上げます。アプリケーションのコードを以下のような構成で整理し、GitHubにアップロードします。このとき、`app.py`(または`application.py`)と`requirements.txt`はルートディレクトリに置かなければいけません。HTMLは`templates`フォルダに、CSSは`static`フォルダに置きます。

```
project/
├── app.py                # メインのアプリケーションコード
├── requirements.txt      # 必要なPythonライブラリを記載
├── static/               # CSSや画像など静的ファイル
│   ├── styles.css
├── templates/            # HTMLファイル
│   ├── index.html
└── .github/
    └── workflows/        # GitHub Actionsの設定ファイル
        └── deploy.yml
```

[Azureポータル](https://portal.azure.com/)にアクセスし、必要であればリソースグループを新規作成します。リソースグループは、アプリケーションや関連リソースをまとめて管理するための単位です。

![img_5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3828517/a9b9c6a9-e310-639e-ba9e-0c12a0a9e355.png)


App ServiseからWebアプリを新規作成します。ランタイムスタックにPythonを指定します。デプロイの項目は作成完了後に設定が可能になります。

![img_6.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3828517/2677a106-1c91-f861-609a-a45e7a52f67f.jpeg)


作成後、デプロイセンターに移動します。デプロイオプションでGitHubを選択し、自分のリポジトリとブランチを指定します。

![img_7.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3828517/d0b82940-9428-90fc-06d6-cb8ec495b1df.png)

保存をクリックするとデプロイが開始されGitHubリポジトリのActionsタブで確認することができます。

![img_8.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3828517/f3529d35-8363-7846-e50d-7495156f414e.png)

### 6. Notionに埋め込む

デプロイが完了し、Webサイトが問題なく表示されたらNotionに埋め込みます。

サイトのURLをNotionに張り付けて「埋め込み」を選択すれば完成です。

![img_embed.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3828517/fb90c559-1acf-438e-8321-76e9e28f9a8c.png)

## おわりに
今回、Notionのウィジェットを制作してみましたが、実際にはWebサイトを埋め込むだけで簡単に作れました。ここまで読んでいただきありがとうございました。

## 参考にしたサイト等
https://qiita.com/y_a_m_a/items/2fef42212f5ffeb11531#github%E3%81%AB%E3%82%B3%E3%83%BC%E3%83%89%E3%82%92%E3%81%82%E3%81%92%E3%82%8B

https://qiita.com/waiiioss/items/c6bbd4f3e5c71648f29f#4-html-css-js%E3%82%92github%E3%81%AB%E3%81%82%E3%81%92%E3%81%A6amplify%E3%81%A7%E3%83%87%E3%83%97%E3%83%AD%E3%82%A4%E3%81%99%E3%82%8B