Mac版VBAからGitHub GraphQLを呼び出す

# 概要
社内の自チームにて「みんなのソースレビュー完了時間が遅いかも？！」という感覚的な問題があがったので、
`Mac版VBA × AppleScript × GitHub GraphQL`を用いてGitHubからPR情報を取得し、自チームのレビュー生産性を数値化してみました。

`Mac版VBA`は情報が少ない上にActiveXが使えないので`Python`とかで作った方が効率的かなと思いましたが、
社内のプレゼンでもバエるようにExcelでグラフとかサマリーとか色々カスタマイズしたくなったのでこんな組み合わせになりました。

しかし、肝心のコードについては情報があまりなく試行錯誤したのでメモします。

# 構成
![image_6483441.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/406684/78f3302c-ee2c-14cc-45d6-72bd54798083.jpeg)
- Mac版VBA：Excel
    - Excelシートの入力値を取得する
    - AppleScriptを呼び出してPR情報（JSON）を取得する
    - PR情報をパースして集計し、Excelシートに出力する
- AppleScript：通信
    - VBAから受け取った検索パラメータをGitHub GraphQLにPOSTリクエストする
    - レスポンス結果のPR情報（JSON）をVBAに返却する
- GitHub GraphQL：外部API
    - POSTリクエストの検索パラメータに従ってPR情報（JSON）がレスポンスされる

# 呼出方法
## ①Mac版VBAからAppleScript

```vb:VBA 
' GitHubから指定アカウントIDに関連するPRのJSONデータを取得する
Function FetchGitHubJson(gitHubToken As String, gitHubId As String, createdFrom As String, createdTo As String, endCursor As String) As String
    Dim params As String: params = gitHubToken & "," & gitHubId & "," & createdFrom & "," & createdTo & "," & endCursor
    
    FetchGitHubJson = AppleScriptTask("curl_github_graphql.scpt", "postGithubGraphqlJson", params)
End Function
```
- `AppleScriptTask`で下記ローカルディレクトリに用意した`curl_github_graphql.scpt`ファイルの`postGithubGraphqlJson`関数を呼び出す
    - `~/Library/Application Scripts/com.microsoft.Excel/curl_github_graphql.scpt`
- `AppleScriptTask`は固定引数のため、複数の検索パラメータをカンマ区切りの文字列にして第三引数`params`に渡している

### Personal access tokenの設定
- 引数`gitHubToken`に以下の設定を行った`Personal access token`を渡している
![image_6483442.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/406684/186d0241-387e-c58e-caab-4c6131606a25.jpeg)


## ②AppleScriptからGitHub GraphQLへPOSTリクエスト

```applescript:curl_github_graphql.scpt
use scripting additions
use framework "Foundation"

on postGithubGraphqlJson(params)
	set paramList to params
	set OriginalDelimiters to AppleScript's text item delimiters
	set AppleScript's text item delimiters to {","}
	set paramList to text items of paramList
	set AppleScript's text item delimiters to OriginalDelimiters
	
	set github_token to item 1 of paramList
	set github_id to item 2 of paramList
	set created_from to item 3 of paramList
	set created_to to item 4 of paramList
	set end_cursor to item 5 of paramList
	
	set query to replaceString(getQL(github_id, created_from, created_to, end_cursor), "\\n", "")
	
	do shell script "curl -H 'Authorization: bearer '" & github_token & " -X POST -d '{ \"query\":" & query & "}' https://api.github.com/graphql"
	
	return result
end postGithubGraphqlJson

on getQL(github_id, created_from, created_to, end_cursor)
	set with_cursor to ""
	if end_cursor is not "" then
		set with_cursor to ", after:  \\\"" & end_cursor & "\\\""
	end if
	
	set query to " \"
{
  search(type: ISSUE, first: 100, query: \\\"is:pr is:closed involves:" & github_id & " created:" & created_from & ".." & created_to & " NOT 集約 NOT Revert in:title comments:>=1 sort:created-asc\\\"" & with_cursor & ") {
    edges {
      node {
        ... on PullRequest {
          author {
            login
          }
          title
          url
          changedFiles
          additions
          deletions
          createdAt
          mergedAt
          closedAt
          comments(first: 50) {
            edges {
              node {
                author {
                  login
                }
                createdAt
              }
            }
          }
          reviews(first: 50) {
            edges {
              node {
                author {
                  login
                }
                createdAt
              }
            }
          }
        }
      }
    }
    pageInfo {
       hasNextPage
       endCursor
    }
  }
}
\" "
	
	return query
end getQL

on replaceString(originalStr as text, pattern as text, newString as text)
	set regularExpression to current application's NSRegularExpression's regularExpressionWithPattern:pattern options:0 |error|:(missing value)
	
	return (regularExpression's stringByReplacingMatchesInString:originalStr options:0 range:{location:0, |length|:count originalStr} withTemplate:newString) as text
end replaceString
```

- VBAから直接呼ばれる関数は`postGithubGraphqlJson`
    - カンマ区切りの文字列で送信されてきたパラメータをリストに変換し、1要素ずつ取り出している
    - `curl`を実行してGitHub GraphQLにPOSTリクエストし、レスポンス結果を返却している

# GitHub GraphQLについて
## クエリを確認する方法
前述の`curl_github_graphql.scpt`のようにクエリを利用し、GitHubで管理されているリポジトリ情報やユーザー情報などを取得することができます。 
しかし、クエリの種類は多岐にわたるので、お手軽に確認したい場合はGitHubのWebアプリ「[GraphQL API Explorer](https://docs.github.com/en/graphql/overview/explorer)」を利用することをオススメします。
ただ、これ自体がGitHubのOAuthアプリになっており、Privateリポジトリに対してクエリを実行したい場合はOrganizationの承認が必要です。

## 注意点
- クエリをGitHub GraphQLへリクエストする際、改行コードがあればブランクに変換しないといけない
- <font color="Red">GitHub GraphQLの仕様により、リクエスト1回につきレスポンスデータは100件まで</font>
    - 対応として、VBA側で以下の処理を行っている
        - 101件以上のデータが存在する場合（`pageInfo.hasNextPage`がtrue）、
           100件目のカーソル（`pageInfo.endCursor`）を引数にして`FetchGitHubJson`関数を再呼出

# 実行結果
で、最終的にVBAや数式でPR情報をごにょごにょしてサマリーした結果
![image_6483443.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/406684/870f0cca-a531-afef-f542-1fae5ee0bc99.jpeg)