GitHub CLIでIssueのCloseされるまでの日数を取得する(ちょっとShell有り)
はじめに
最近ようやくGitHub CLIを使い始めました。
マニュアルを呼んでいると、CLI経由でGraphQLが投げられるとわかったので、GraphQLの勉强がてら、GitHubのIssueのリストを取得して、Open→Closeまでのリードタイムを測るようなスクリプトを組んでみました。
なお、動作環境はMacOS 10.15.6(19G2021) となっています。
Issueを取得するスクリプト
はじめに、Issueのデータと、OpenからCloseするまでの日数(リードタイム)を取得するスクリプトを記載します。
#!/bin/bash
# $@をダブルクォーテーションで囲うのが必要。それぞれの引数を""で囲って展開するため。
# 囲わないと、日付の部分にシングルクォーテーションが入ってしまい、挙動がおかしくなる
gh api graphql -F queryString="$@" --paginate -f query='
query($endCursor: String, $queryString: String!) {
search(query: $queryString, type: ISSUE, first: 100, after: $endCursor) {
edges {
node {
... on Issue {
number
title
url
createdAt
closedAt
comments(first: 100) {
totalCount
}
state
labels(first: 100) {
nodes {
name
}
}
assignees(first: 100) {
nodes {
name
}
}
}
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
' |
jq -r '.data[].edges[].node | [.createdAt, .closedAt, .number, .url, .comments.totalCount, .state, .title, .assignees.nodes[].name, .labels.nodes[].name] | @tsv' |
# titleにはスペースが入っていることがあるので、一番最後にしてreadで読む
while IFS=' ' read createdAt closedAt number url commentsCount state title others; do
# null回避
[ $closedAt = "null" ] && closedAt=$(date -u +%Y-%m-%dT%H:%M:%SZ)
closedAt=$(echo -n $closedAt | tr -d "'")
createdAt=$(echo -n $createdAt | tr -d "'")
leadDays=$(( $(( $(date -u -jf %FT%TZ ${closedAt} +%s) - $(date -u -jf %FT%TZ ${createdAt} +%s) )) / (60 * 60 * 24) ))
createdAtJST=$(date -jf %FT%TZ ${createdAt} +%FT%T%z)
closedAtJST=$(date -jf %FT%TZ ${closedAt} +%FT%T%z)
# Issue番号 ステータス 作成日時 Close日時 リードタイム コメント数 URL タイトル、Author
echo $number $state $createdAtJST $closedAtJST $leadDays $commentsCount $title $url $others
done
使用例
./ggq_issue.sh "repo:ModelingKai/Reservation2 is:issue is:closed "
21 CLOSED 2020-12-29T15:23:59+0900 2021-01-03T11:08:11+0900 4 0 予約Idファクトリを導入したADR作成する https://github.com/ModelingKai/Reservation2/issues/21 Jun Nakajima
17 CLOSED 2020-12-06T15:51:28+0900 2020-12-29T12:45:46+0900 22 0 ADRの導入の検討をする https://github.com/ModelingKai/Reservation2/issues/17
15 CLOSED 2020-11-15T13:04:24+0900 2020-11-22T11:45:57+0900 6 0 Azure App Service に ASP.NET Core アプリケーションをデプロイする GitHub Actions を追加 https://github.com/ModelingKai/Reservation2/issues/15
13 CLOSED 2020-11-15T09:09:26+0900 2020-11-22T11:35:38+0900 7 0 ASP.NET MVCをherokuにデプロイする https://github.com/ModelingKai/Reservation2/issues/13
6 CLOSED 2020-11-01T13:26:44+0900 2020-11-08T11:54:26+0900 6 1 予約ステータスの実装をする https://github.com/ModelingKai/Reservation2/issues/6
5 CLOSED 2020-11-01T13:24:00+0900 2020-11-08T11:54:12+0900 6 0 予約の状態遷移をRDRAに戻って考える https://github.com/ModelingKai/Reservation2/issues/5
順を追ってスクリプトの中身を説明していきます。
スクリプトの説明
GraphQLの実行
gh api graphql -F queryString="$@" --paginate -f query='
gh api graphql
でCLI経由でgraphqlを投げることができます。
query=
の後に、実際投げるクエリ。
-F
の後はパラメータを指定ができます。
--paginate
を設定すると、 要素の最後までを取得することができます。
In '--paginate' mode, all pages of results will sequentially be requested until there are no more pages of results. For GraphQL requests, this requires that the original query accepts an '$endCursor: String' variable and that it fetches the 'pageInfo{ hasNextPage, endCursor }' set of fields from a collection.
GraphQLの中身
query($endCursor: String, $queryString: String!) {
search(query: $queryString, type: ISSUE, first: 100, after: $endCursor) {
$endCursor: String
は --paginate
オプションを付けた場合は必要です。
search(query: $queryString
にスクリプトでもらった引数を渡します。
参考:searchコネクション
edges {
node {
... on Issue {
number
title
url
createdAt
closedAt
comments(first: 100) {
totalCount
}
state
labels(first: 100) {
nodes {
name
}
}
assignees(first: 100) {
nodes {
name
}
}
}
}
}
searchから取得できるのが、SearchResultItemである為、... on Issue
でIssueとして扱っています。
参考:SearchResultItem
以下のpageInfo
は、ghコマンドに--paginate
オプションを付けている場合は必要です。
pageInfo {
hasNextPage
endCursor
}
GraphQLで取得した結果例
gh api graphql
で取得するデータは以下の例のようにJSON形式となります。
{
"data": {
"search": {
"edges": [
{
"node": {
"number": 21,
"title": "予約Idファクトリを導入したADR作成する",
"url": "https://github.com/ModelingKai/Reservation2/issues/21",
"createdAt": "2020-12-29T15:23:59Z",
"closedAt": "2021-01-03T11:08:11Z",
"comments": {
"totalCount": 0
},
"state": "CLOSED",
"labels": {
"nodes": []
},
"assignees": {
"nodes": [
{
"name": "Jun Nakajima"
}
]
}
}
},
{
"node": {
...
この状態だと集計などに扱いにくい為、1Issue、1レコードに纏めたいと思います。
JSONのパース
JSONのパースに jqコマンドを使用します。
Issueのタイトルにスペースやカンマが入っていることを考慮し、@tsv
でtsv形式に変換しています。
jq -r '.data[].edges[].node | [.createdAt, .closedAt, .number, .url, .comments.totalCount, .state, .title, .assignees.nodes[].name, .labels.nodes[].name] | @tsv' |
jqコマンドでパース後のデータ(tsv)
2020-12-29T15:23:59Z 2021-01-03T11:08:11Z 21 https://github.com/ModelingKai/Reservation2/issues/21 0 CLOSED 予約Idファクトリを導入したADR作成する Jun Nakajima
2020-12-06T15:51:28Z 2020-12-29T12:45:46Z 17 https://github.com/ModelingKai/Reservation2/issues/17 0 CLOSED ADRの導入の検討をする
2020-11-15T13:04:24Z 2020-11-22T11:45:57Z 15 https://github.com/ModelingKai/Reservation2/issues/15 0 CLOSED Azure App Service に ASP.NET Core アプリケーションをデプロイする GitHub Actions を追加
2020-11-15T09:09:26Z 2020-11-22T11:35:38Z 13 https://github.com/ModelingKai/Reservation2/issues/13 0 CLOSED ASP.NET MVCをherokuにデプロイする
2020-11-01T13:26:44Z 2020-11-08T11:54:26Z 6 https://github.com/ModelingKai/Reservation2/issues/6 1 CLOSED 予約ステータスの実装をする
2020-11-01T13:24:00Z 2020-11-08T11:54:12Z 5 https://github.com/ModelingKai/Reservation2/issues/5 0 CLOSED 予約の状態遷移をRDRAに戻って考える
Closeまでの時間を計算する
while read
でデータを読み込みます。
このとき、タブ区切りで読み込みをしたいので、while IFS=' ' read
で指定しています。
(IFS='\t' を指定しましたが、うまくいきませんでした)
createdAtとclosedAtは、UTCでISO8601形式(yyyy-MM-ddTHH:mm:ss)となっているので、UNIX時間にした後、差分を計算したあとに、日に直しています。
while IFS=' ' read createdAt closedAt number url commentsCount state title others; do
# null回避
[ $closedAt = "null" ] && closedAt=$(date -u +%Y-%m-%dT%H:%M:%SZ)
closedAt=$(echo -n $closedAt | tr -d "'")
createdAt=$(echo -n $createdAt | tr -d "'")
leadDays=$(( $(( $(date -u -jf %FT%TZ ${closedAt} +%s) - $(date -u -jf %FT%TZ ${createdAt} +%s) )) / (60 * 60 * 24) ))
createdAtJST=$(date -jf %FT%TZ ${createdAt} +%FT%T%z)
closedAtJST=$(date -jf %FT%TZ ${closedAt} +%FT%T%z)
echo $number $state $createdAtJST $closedAtJST $leadDays $commentsCount $title $url $others
done
おわりに
単純ではありますが、Issueのリストを取得して整形するところまでをやってみました。
スプレッドシートなどに貼ってグラフ化とかするのであれば、おそらくjqコマンドでパースした状態をそのまま貼れば良さそうなので、わざわざwhile readで頑張らなくても良さそうです。
今回欲しいデータもShellで整形しなくても良いように、GraphQL側でやり方ってなんかあるのかな。
あと、Label検索やAuthor検索する時、OR条件でやりたいけど、そういうやり方は無いんですかね…?
Discussion