最近GitHub projectsのbetaを使用してタスク管理をしている
どういうものかは公式のドキュメント参照
動画がよくまとまっていて分かりやすかった
一応簡単に紹介すると、issueやPRをこのprojects betaの中に突っ込んでおくと、それらをテーブル管理できるようになる
設定したmilestoneごとにgroup byで分けて表示したりとか進行statusとかも管理できる
betaじゃないprojectsだとカンバン方式で表示できるがそれも可能
Notion使ってる人だったらデータベースと同じような感じって言えばなんとなく伝わるかも

使い心地は非常によいのだが、1つ問題があってせっかくPR発行してもprojectsとして設定しないとテーブル内に表示されない

これだと設定し忘れたらprojectsに反映されてなくて気づかない可能性もある
なのでissue作ったりPR発行したら自動的にprojects betaに同期したいよねってことで、それを実現するためのGitHub Actionsを作ったという話
ちなみにworkflowsなる機能が既にあって、projects betaに追加した時に発火するworkflowsとかは設定できるっぽい。
ただこれもcoming soonとなっていて、まだまだできることは少ない。

そのうち今回実装した内容もいらなくなる気はしてるが、それまでの繋ぎということでやってみた。
ついでにマーケットプレイスへの公開も興味あったのでやってみた。
GitHub Actionsの実装
projects(beta)の作り方とかは省略する。
画面上でぽちぽちクリックしてけば簡単に作れる。
改めてやりたいことを確認すると、issueやPRを作成した時にそれを特定のprojects(beta)に設定する。
ここではとりあえずissueのみに絞って記載していくが、PRでもやることは変わらない。
大まかな実現方法としては、GitHubのAPIを使う。
基本公式ドキュメントに記載の方法に従えばできそう。

addProjectNextItem
なるメソッドを使えばよさそうなのだが、そのためには対象となるprojects(beta)のユニークなnode idが必要。
なので手順としては大まかに以下の2つに分かれる。
1. 対象projects(beta)のnode idを取得する
2. 作成したissueを上記のprojectsに追加する
対象projects(beta)のnode idを取得
GitHub Actionsの中でactions/github-script
を使えばJSを使ってGraphQLのクエリ投げたりできる。
これを使って以下のようなクエリを発行する。
個々に設定が必要な情報はorganization名(or アカウント名)とprojects(beta)の番号のみ。
name: 'add projects beta'
on:
issues:
types: [opened]
jobs:
sync:
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v5
id: project-id
with:
result-encoding: string
github-token: ${{ secrets.ADD_PROJECT_DATA }}
script: |
const query = `
query($login: String!, $projectNumber: Int!) {
organization(login: $login) {
projectNext(number: $projectNumber) {
id
}
}
}
`
const result = await github.graphql(query, {
login: 'foo',
projectNumber: 1
})
return result.organization.projectNext.id
注意点としては、Actionsがprojectsにアクセスするためにそれ用のアクセストークンが必要。
今回はPersonal Access Tokenでrepo
とwrite:org
を付与したアクセストークンを作成して、それをADD_PROJECT_DATA
という名前でsecretsに保管してこれを使用している。
(この時点ではread:org
で十分だが、次のstepでwrite権限が必要なのでwrite:org
にしている)
今回はorganizationで使用するものだったので17行目でorganizationとしているが、個人プロジェクトの場合はここをuserにすればよい。
その場合は28行目のorganizationもuserにする。
25行目のloginは個人プロジェクトの場合はアカウント名、organizationの場合はorganization名を記載。
26行目のprojectNumberはそのprojects(beta)の番号を記載する。この番号はURLにも記載してある。以下の例だと1。https://github.com/orgs/foo/projects/1
作成したissueを上記のprojectsに追加する
いよいよprojects(beta)に追加する処理を書いていく。と言っても必要な処理としては上記と同じ様にしてGitHubのGraphQLのmutationを発行するのみ。
必要な情報は上記で取得したprojectsのnode idと対象issueのnode idのみ。
name: 'add projects beta'
on:
issues:
types: [opened]
jobs:
sync:
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v5
id: project-id
with:
...
- uses: actions/github-script@v5
with:
result-encoding: string
github-token: ${{ secrets.ADD_PROJECT_DATA }}
script: |
const mutation = `
mutation($projectId: ID!, $itemId: ID!) {
addProjectNextItem(input: {projectId: $projectId, contentId: $itemId}) {
projectNextItem {
id
}
}
}
`
await github.graphql(mutation, {
projectId: "${{ steps.project-id.outputs.result }}",
itemId: "${{ github.event.issue.node_id }}"
})
このmutationにも権限が必要なので同じsecrets.ADD_PROJECT_DATA
を設定している。
projectIdは先ほど取得したものを使用するので、steps.project-id.outputs.result
をそのまま付与している。
issueのnode idはgithub.event.issue.node_id
で取得できるのでそれをそのまま付与している。
これでissueを発行した時に対象のprojects(beta)にissueが登録される。
issueとPRでprojects(beta)を分ける
特定のprojects(beta)にはissueの登録ができる様になったので、PR発行時にも同様の操作をする。
実際にはprojects(beta)をissue用のものとPR用のものに分けて運用しているので、それぞれのprojectsに同期する必要がある。
これまで作成したymlファイルをちょっと修正すればよい。
name: 'add projects beta'
on:
issues:
types: [opened]
pull_request:
types: [opened]
jobs:
sync:
runs-on: ubuntu-latest
steps:
- uses: actions/github-script@v5
id: project-id
with:
result-encoding: string
github-token: ${{ secrets.ADD_PROJECT_DATA }}
script: |
const query = `
query($login: String!, $projectNumber: Int!) {
organization(login: $login) {
projectNext(number: $projectNumber) {
id
}
}
}
`
const result = await github.graphql(query, {
login: 'foo',
projectNumber: "${{ github.event_name }}" === "issues" ? 1 : 2
})
return result.organization.projectNext.id
- uses: actions/github-script@v5
with:
result-encoding: string
github-token: ${{ secrets.ADD_PROJECT_DATA }}
script: |
const mutation = `
mutation($projectId: ID!, $itemId: ID!) {
addProjectNextItem(input: {projectId: $projectId, contentId: $itemId}) {
projectNextItem {
id
}
}
}
`
await github.graphql(mutation, {
projectId: "${{ steps.project-id.outputs.result }}",
itemId: "${{ github.event_name }}" === "issues" ? "${{ github.event.issue.node_id }}" : "${{ github.event.pull_request.node_id }}"
})
トリガーとしてPRがopenした時を追加する。
projectsのnode idを取得する際にgithub.event_name
がissueかどうかによって対象のprojects(beta)のprojectNumberを切り替えている。
追加する処理の時にもitemIdにissueの時はissueのnode id、PRの時はPRのnode idを渡す様に修正してある。
ちなみにGitHub Actionsのyml書く時、動作確認するために毎回pushして動作確認するのだるいなと思ってたんだけど、静的解析してくれるactionlintなるものを作ってくれてる人がいて今回ありがたく使わせてもらったんだけど開発体験かなり良くなったので感謝。

マーケットプレイスへの公開
せっかくなのでこのprojects betaに追加してくれる一連の処理をマーケットプレイスで公開してみようと思った。
どうやってやるのか知りたかったというのが主な理由なんだけど、やってみたら結構簡単だった。
やり方は公式ドキュメントにあったまんま。

ルートにaction.yml
を作成するとポップアップが出てくるので処理とREADMEを書けばマーケットプレイスに公開できる。
action.ymlに処理する内容を記述する。
記述の仕方はdockerを使う方法、JSを使う方法、シェルスクリプトで書く(composite)方法の3種類がある。
簡単な処理をする程度のことならcompositeがサクッとできてよさそう。
実際これまでに作成した内容をそのままcompositeとして書けばそれで終わりな気はするが、それもなんかつまらないなと思ったので、今回はTSで記述してJSにコンパイルする方法にしてみた。
name: "Add projects beta"
author: "foo"
description: "add issue or PR to GitHub Projects(Beta)"
branding:
icon: "database"
color: "orange"
inputs:
github-token:
required: true
description: "Personal access token that contains `repo` and `write:org` is required."
project-owner:
required: true
description: "User or Organization name of projects beta"
project-number:
required: true
description: "The number of the target projects beta"
content-id:
required: true
description: "Node id of issue or PR"
outputs:
added-item-id:
description: "Project item id added to the project"
runs:
using: "node16"
main: "src/index.js"
brandingはマーケットプレイスに公開した時のアイコンと色を指定する。
inputsには使う時にwithで指定するパラメータを記載する。
outputsはそのアクションによる返り値。
usingにnode16
としており、JSで処理を記述することを指定している。
dockerの場合はdocker
とする。シェルスクリプトで書く場合はcomposite
にする。
あとは処理する内容をTSファイルとして記述して、main
で指定したパスのJSファイルにトランスパイルする。
ちなみにinputs
で与えられたパラメータは@actions/core
のgetinput
で取れるし、outputsの設定はsetOutput
でできる。
import { getInput, setOutput } from "@actions/core";
// inputsの取得
const projectOwner = getInput("project-owner");
// outputsの設定
setOutput("added-item-id", foo)
詳細な実装は省略。
使ったライブラリの型が結構緩めでTS使ったけどあんまり型安全にならなかった。今回はサクッと書いて終わりだったのでそのまま流した。
実装を書いて、GitHub Actionsを手動でdispatchするテスト書いてREADME書いてpushする。
ルートにaction.yml
があるとポップアップが出てくるのでそれに従ってぽちぽち進めていくとtagを打った上でリリースされる。
実際にやってみたけどめちゃくちゃ簡単だった。
マーケットプレイスに公開した後、別リポジトリのGitHub Actionsで設定してみたけどちゃんと使えた。
公開したのはこちら。
まとめ
GitHub Actionsのymlファイル内でJS使えるんだっていう学びがあったのとやっぱこういう自動化関連のタスクは好きだなという気づき。
ふとマーケットプレイスに公開ってどうやるんだろって思ってやってみたけど、やってみたら簡単にできたしチャレンジしてよかった。
この記事が参考になったからコーヒーくらいおごってもいいぜという方は、以下からサポートいただけると次の記事書くモチベになりますのでよろしくお願いします

参考


コメント