GitHub Projects (Beta) に Issue を自動登録・削除する Actions を作ってみた
はじめに
みなさん、GitHub Projects (Beta) 使ってますよね?
え?まだ使ってない?そりゃぁ勿体ない!
今すぐ GitHub Issues の Sign up for the beta ボタンをクリックしてください!
豊かなプロジェクト管理ライフが貴方を待っていますよ!!!
はい、というワケで深夜の謎テンションで記事を書き始めてみました。
Projects (Beta) 未使用の方向けに粗く概要を説明すると、GitHub Projects (Beta) は次のような特徴をもった GitHub のプロダクトです。
- Board(いわゆるカンバン)や Table(Excel/Spread Sheet 的な表形式)で Issues, Pull Requests を管理できる
- User/Orgs の配下に Project を作成し、複数のリポジトリの Issues, Pull Requests を管理できる
- これまでも Projects 機能はあったが、それとは別モノとして扱われる
- GitHub Issues 機能群の1つとして現在 Public Beta テスト中
- Beta にサインアップすると Waiting リストに登録され、少し(数日〜数週間)待つとアクセス権が付与されます。
こちらの記事が凄く分かりやすく纏まっているので、詳細はそちらをご覧いただくとして、本稿はそんな GitHub Issues の新機能の一つである Projects (Beta) を便利に使えるようなツールを作ったよ、というお話しです。
TL; DR
次のような Workflow を .github/workflow/manage-project-on-issues.yml
に置いておくと、「Project
ラベルの付け外しに呼応して Projects (Beta) に登録・削除が行われる」というワークフローが仕込まれます。
name: Issue の Label 操作時に Projects (Beta) に登録・削除
on:
issues:
types:
- labeled
- unlabeled
env:
PROJECT_OWNER: <PROJECT_OWNER> # Project の所有者名(users, orgs のどちらでも OK)
PROJECT_NUMBER: <PROJECT_NUMBER> # Project 番号(確認方法は本題参照)
TARGET_LABEL: 'Project'
GITHUB_TOKEN: ${{ secrets.<SECRET_KEY> }} # `repo`, `read:org`, `write:org` 権限を付与した Personal Access Token を保存した Secrets のキー
jobs:
manage_project:
name: 「Project」ラベルの付け外しに対応して、Issue を Projects (Beta) に出し入れする
runs-on: ubuntu-latest
steps:
- name: Add Issue to Project
if: ${{ github.event.action == 'labeled' && contains(github.event.issue.labels.*.name, env.TARGET_LABEL) }}
id: add-issue-to-project
uses: monry/actions-add-issue-to-project@v1
with:
github-token: ${{ env.GITHUB_TOKEN }}
project-owner: ${{ env.PROJECT_OWNER }}
project-number: ${{ env.PROJECT_NUMBER }}
issue-id: ${{ github.event.issue.node_id }}
- name: Delete Issue from Project
if: ${{ github.event.action == 'unlabeled' && !contains(github.event.issue.labels.*.name, env.TARGET_LABEL) }}
id: delete-issue-from-project
uses: monry/actions-delete-issue-from-project@v1
with:
github-token: ${{ env.GITHUB_TOKEN }}
project-owner: ${{ env.PROJECT_OWNER }}
project-number: ${{ env.PROJECT_NUMBER }}
issue-id: ${{ github.event.issue.node_id }}
本題
GitHub Projects (Beta) には Workflow という自動化機能が公式に備わっているのですが、2021/10/23 時点ではカスタマイズ性が低く「Issue が Close されたら Card の Status を Done に変更する(Done カラムに移動する)」「Projects (Beta) に追加された時点で自動的に Status を Backlog に変更する(Backlog カラムに移動する)」といった最低限の設定しか行えません。
まぁ詳細なカスタマイズについては、設定出来そうな UI が Coming soon ってなっているので、そのうち便利になると思われます。
で、まぁそれは別に良いんですが、「Projects に Card を登録する」という処理については、自動化できなさそうだったので「できないなら自分で作ろう!」と思い立ちました。
具体的には任意のリポジトリに仕込む Workflows から利用できる汎用的な Actions を作るという方針を決め、次の4つの Actions を作りました。
それぞれの使い方などの詳細を以下に纏めます。
なお、GitHub Actions の Workflow から呼び出されるコトが前提の Action となっているため、「Actions?Workflow?なにそれ美味しいの?🤔」という方は GitHub 公式ドキュメントを読み、概要を理解しておくことを強くお薦めいたします。
Get Project Id
Projects (Beta) の Node Id を取得します。
Node Id というのは、GitHub のあらゆる要素を一意に特定するための文字列で、GraphQL を用いた API アクセスを行う際のパラメータとして利用されます。
この Action を単体で利用してもあまり意味はなく、Job の後続 Step から Project の ID を参照する用途で利用されることを想定しております。
実際、後述の Get Project Item Id や Add Issue to Project などで Project の Node Id を取得するために利用しています。
使用例
次のような Workflow を .github/workflows/get-project-id-sample.yml
として保存すると、Issue 作成時にワークフローが発火しログに PN_
から始まる Project の Node Id が表示されます。
name: Example
on:
issues:
types:
- opened
env:
PROJECT_OWNER: monry
PROJECT_NUMBER: 1
jobs:
example:
name: Example job
runs-on: ubuntu-latest
steps:
- uses: monry/actions-get-project-id@v1
id: get-project-id
with:
github-token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
project-owner: ${{ env.PROJECT_OWNER }}
project-number: ${{ env.PROJECT_NUMBER }}
- name: Output result
run: |
echo '${{ steps.get-project-id.outputs.project-id }}'
このサンプルは on: issues: types: [ 'opened' ]
となっているので、Action に渡すパラメータのうち project-owner
は project-owner: ${{ github.event.issue.repository.owner.login }}
などとしても良いかもしれません。
Inputs
github-token
権限として repo
, read:org
を付与した Personal Access Token を設定します。
リポジトリや Organization の Secrets として設定しておいて、それを渡すと良いでしょう。
Secrets の名前は任意です。
本稿ではリポジトリか Organization の Secrets として PERSONAL_ACCESS_TOKEN
という名前で登録されている想定になっています。
実際の運用では、利用範囲を絞って不要になった際に破棄しやすくすると良いかもしれません。(私は PAT_
プレフィックスを付けて管理しています。)
project-owner
Project を所有している User / Organization の名前を設定します。
project-number
Projects (Beta) には Project Number という概念があり、URL の /users/monry/projects/:number
や /orgs/kidsstar/projects/:number
の :number
に該当する値がソレになります。
プロジェクト一覧にも表示されています。
Outputs
project-id
project-owner
と project-number
から絞り込まれた Projects (Beta) の ID を返します。
以降の Step で参照する際には、本 Action を呼び出す際に id
を設定したうえで、 ${{ steps.<id>.outputs.project-id }}
とすることで値を取得できます。
steps:
- uses: monry/actions-get-project-id@v1
id: get-project-id # この ID が steps. のキーになる
with:
github-token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
project-owner: ${{ env.PROJECT_OWNER }}
project-number: ${{ env.PROJECT_NUMBER }}
- name: Output result
run: |
echo '${{ steps.get-project-id.outputs.project-id }}'
Get Project Item Id
Projects (Beta) に登録された Item の Node Id を取得します。
ココで言う Item は、Board(カンバン)で言う所のカードや、Table(表)で言う所の1行分の要素のコトを指します。
この Action も Get Project Id 同様単体で利用してもあまり意味はなく、Job の後続 Step から Project Item の ID を参照する用途で利用されることを想定しております。
実際、後述の Remove Issue from Project で利用しています。
使用例
次のような Workflow を .github/workflows/get-project-item-id-sample.yml
として保存すると、Issue に Label を設定した際にワークフローが発火し、当該 Issue が Project に登録されている場合はログに PNI_
から始まる Project Item の Node Id が表示されます。(未登録の場合は空文字が表示されます。)
name: Example
on:
issues:
types:
- labeled
env:
PROJECT_OWNER: monry
PROJECT_NUMBER: 1
jobs:
example:
name: Example job
runs-on: ubuntu-latest
steps:
- uses: monry/actions-get-project-item-id@v1
id: get-project-item-id
with:
github-token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
project-owner: ${{ env.PROJECT_OWNER }}
project-number: ${{ env.PROJECT_NUMBER }}
issue-id: ${{ github.event.issue.node_id }}
- name: Output result
run: |
echo '${{ steps.get-project-item-id.outputs.project-item-id }}'
Inputs
github-token
権限として repo
, read:org
を付与した Personal Access Token を設定します。
リポジトリや Organization の Secrets として設定しておいて、それを渡すと良いでしょう。(名前は何でもOK)
project-id
Item を検索する Project の Node Id が分かっている場合に設定します。
project-name
, project-number
の組み合わせを設定する場合、内部で Project Id を問い合わせることになるので、前述の Get Project Id を用いて取得した値を定数的に設定しておくと、Rate Limit の節約ができます。
project-owner
Project を所有している User / Organization の名前を設定します。
project-id
を設定しなかった場合に必須になります。
project-number
Project Number を設定します。
project-id
を設定しなかった場合に必須になります。
確認方法は Get Project Id の記述を参照してください。
issue-id
検索する Issue の Node Id を設定します。
Workflow が on: issues
により発火する場合、github.event.issue.node_id
に Node Id が格納されているので、そのまま渡すと良いでしょう。
issue-repository
検索する Issue のリポジトリ名を設定します。
ココで言うリポジトリ名は、monry/awesome-repos
といった Owner 名を含む値を期待しています。
issue-id
を設定しなかった場合に必須になります。
issue-number
検索する Issue の番号を設定します。
issue-id
を設定しなかった場合に必須になります。
Outputs
project-item-id
渡された Project / Issue 関連のパラメータから絞り込まれた Project Item の ID を返します。
以降の Step で参照する際には、本 Action を呼び出す際に id
を設定したうえで、 ${{ steps.<id>.outputs.project-item-id }}
とすることで値を取得できます。
steps:
- uses: monry/actions-get-project-item-id@v1
id: get-project-item-id # この ID が steps. のキーになる
with:
github-token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
project-owner: ${{ env.PROJECT_OWNER }}
project-number: ${{ env.PROJECT_NUMBER }}
issue-id: ${{ github.event.issue.node_id }}
- name: Output result
run: |
echo '${{ steps.get-project-item-id.outputs.project-item-id }}'
Add Issue to Project
本稿の本題です。
Issue を Project に登録します。
Project に登録済みの Issue が指定されてもエラーにはならず、Outputs の added-project-item-id
には登録済みの Project Item Id が出力されます。
使用例
次のような Workflow を .github/workflows/add-issue-to-project-sample.yml
として保存すると、Issue に FooBar
という Label が設定された際にワークフローが発火し、当該 Issue が Project に登録されて、登録された Project Item Id がログに出力されます。
name: Example
on:
issues:
types:
- labeled
env:
PROJECT_OWNER: monry
PROJECT_NUMBER: 1
TARGET_LABEL: 'FooBar'
jobs:
example:
name: Example job
runs-on: ubuntu-latest
steps:
- uses: monry/add-issue-to-project@v1
if: ${{ contains(github.event.issue.labels.*.name, env.TARGET_LABEL) }} # ココを工夫するコトで、柔軟な設定ができる
id: add-issue-to-project
with:
github-token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
project-owner: ${{ env.PROJECT_OWNER }}
project-number: ${{ env.PROJECT_NUMBER }}
issue-id: ${{ github.event.issue.node_id }}
- name: Output result
run: |
echo '${{ steps.add-issue-to-project.outputs.added-project-item-id }}'
Inputs
github-token
権限として repo
, read:org
, write:org
を付与した Personal Access Token を設定します。
リポジトリや Organization の Secrets として設定しておいて、それを渡すと良いでしょう。(名前は何でもOK)
Get 系の Action とは異なり write:org
も必要になるので注意。
project-id
Item を検索する Project の Node Id が分かっている場合に設定します。
project-name
, project-number
の組み合わせを設定する場合、内部で Project Id を問い合わせることになるので、前述の Get Project Id を用いて取得した値を定数的に設定しておくと、Rate Limit の節約ができます。
project-owner
Project を所有している User / Organization の名前を設定します。
project-id
を設定しなかった場合に必須になります。
project-number
Project Number を設定します。
project-id
を設定しなかった場合に必須になります。
確認方法は Get Project Id の記述を参照してください。
issue-id
検索する Issue の Node Id を設定します。
Workflow が on: issues
により発火する場合、github.event.issue.node_id
に Node Id が格納されているので、そのまま渡すと良いでしょう。
issue-repository
検索する Issue のリポジトリ名を設定します。
ココで言うリポジトリ名は、monry/awesome-repos
といった Owner 名を含む値を期待しています。
issue-id
を設定しなかった場合に必須になります。
issue-number
検索する Issue の番号を設定します。
issue-id
を設定しなかった場合に必須になります。
Outputs
added-project-item-id
登録された Project Item Id を返します。
以降の Step で参照する際には、本 Action を呼び出す際に id
を設定したうえで、 ${{ steps.<id>.outputs.added-project-item-id }}
とすることで値を取得できます。
steps:
- uses: monry/add-issue-to-project@v1
if: ${{ contains(github.event.issue.labels.*.name, env.TARGET_LABEL) }}
id: add-issue-to-project # この ID が steps. のキーになる
with:
github-token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
project-owner: ${{ env.PROJECT_OWNER }}
project-number: ${{ env.PROJECT_NUMBER }}
issue-id: ${{ github.event.issue.node_id }}
- name: Output result
run: |
echo '${{ steps.add-issue-to-project.outputs.added-project-item-id }}'
Delete Issue from Project
こちらも本題に近いモノがあります。
Issue を Project から削除します。
Project に未登録 Issue が指定されてもエラーにはならず、Outputs の deleted-project-item-id
には空文字が出力されます。
使用例
次のような Workflow を .github/workflows/delete-issue-to-project-sample.yml
として保存すると、Issue から FooBar
という Label が削除された際にワークフローが発火し、当該 Issue が Project から削除されて、削除された Project Item Id がログに出力されます。
name: Example
on:
issues:
types:
- unlabeled
env:
PROJECT_OWNER: monry
PROJECT_NUMBER: 1
TARGET_LABEL: 'FooBar'
jobs:
example:
name: Example job
runs-on: ubuntu-latest
steps:
- uses: monry/delete-issue-to-project@v1
if: ${{ !contains(github.event.issue.labels.*.name, env.TARGET_LABEL) }} # ココを工夫するコトで、柔軟な設定ができる
id: delete-issue-to-project
with:
github-token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
project-owner: ${{ env.PROJECT_OWNER }}
project-number: ${{ env.PROJECT_NUMBER }}
issue-id: ${{ github.event.issue.node_id }}
- name: Output result
run: |
echo '${{ steps.delete-issue-to-project.outputs.deleted-project-item-id }}'
Inputs
github-token
権限として repo
, read:org
, write:org
を付与した Personal Access Token を設定します。
リポジトリや Organization の Secrets として設定しておいて、それを渡すと良いでしょう。(名前は何でもOK)
Get 系の Action とは異なり write:org
も必要になるので注意。
project-id
Item を検索する Project の Node Id が分かっている場合に設定します。
project-name
, project-number
の組み合わせを設定する場合、内部で Project Id を問い合わせることになるので、前述の Get Project Id を用いて取得した値を定数的に設定しておくと、Rate Limit の節約ができます。
project-owner
Project を所有している User / Organization の名前を設定します。
project-id
を設定しなかった場合に必須になります。
project-number
Project Number を設定します。
project-id
を設定しなかった場合に必須になります。
確認方法は Get Project Id の記述を参照してください。
project-item-id
削除対象の Project Item Id を設定します。
通常の Workflow でこの値を知る方法は無いため、利用頻度は低いかもしれません。
もし何らかの方法で削除対象の Project Item Id が分かっている場合には設定することで Rate Limit の節約ができます。
issue-id
検索する Issue の Node Id を設定します。
Workflow が on: issues
により発火する場合、github.event.issue.node_id
に Node Id が格納されているので、そのまま渡すと良いでしょう。
issue-repository
検索する Issue のリポジトリ名を設定します。
ココで言うリポジトリ名は、monry/awesome-repos
といった Owner 名を含む値を期待しています。
project-item-id
, issue-id
を設定しなかった場合に必須になります。
issue-number
検索する Issue の番号を設定します。
project-item-id
, issue-id
を設定しなかった場合に必須になります。
Outputs
deleted-project-item-id
削除された Project Item Id を返します。
対象の Project Item が見付からないなどの理由で削除が行われなかった場合は空文字が返されます。
以降の Step で参照する際には、本 Action を呼び出す際に id
を設定したうえで、 ${{ steps.<id>.outputs.deleted-project-item-id }}
とすることで値を取得できます。
steps:
- uses: monry/delete-issue-to-project@v1
if: ${{ !contains(github.event.issue.labels.*.name, env.TARGET_LABEL) }}
id: delete-issue-to-project # この ID が steps. のキーになる
with:
github-token: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
project-owner: ${{ env.PROJECT_OWNER }}
project-number: ${{ env.PROJECT_NUMBER }}
issue-id: ${{ github.event.issue.node_id }}
- name: Output result
run: |
echo '${{ steps.delete-issue-to-project.outputs.deleted-project-item-id }}'
おわりに
本業の方で Projects (Beta) を試用してみるコトになり、使っているうちに「こういうのあったら便利だなぁ」とか思ったので作ってみたんですが、「半日あればできるだろー」とか思って作り始めてみたら思いの外苦戦してしまい、トータルで12時間くらいは掛かってしまった気がします。
(なんなら、この記事も結構な大作になってしまった…😅)
GitHub Actions や GraphQL の勉強を兼ねて作っていたんですが、かなり理解が深まったのでトライしてみて良かったです。
「動きがオカシイ」とか「こういう機能が欲しい」とかあれば Issue 立ててもらったり Pull Request 送ってもらえたりすると嬉しいです。
使用した感想とかもらえると泣いて喜びます😂
Discussion
プロジェクトへの追加に関してのみ、 actions が提供し始めたようです。