GitHub projects betaに同期するGitHub Actions

最近GitHub projectsのbetaを使用してタスク管理をしている
どういうものかは公式のドキュメント参照

GitHub Docs
Projects について - GitHub Docs Projects は、GitHub での作業を計画および追跡するための、適応性のある柔軟なツールです。

動画がよくまとまっていて分かりやすかった

一応簡単に紹介すると、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を使う。
基本公式ドキュメントに記載の方法に従えばできそう。

GitHub Docs
Actions を使用した Projects の自動化 - GitHub Enterprise Cloud Docs GitHub Actions を使ってプロジェクトを自動化できます。

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でrepowrite: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なるものを作ってくれてる人がいて今回ありがたく使わせてもらったんだけど開発体験かなり良くなったので感謝。

はやくプログラムになりたい
GitHub Actions のワークフローをチェックする actionlint をつくった - はやくプログラムになりたい GitHub Actions のワークフローを静的にチェックする actionlint というコマンドラインツールを最近つくっていて,概ね欲しい機能が揃って実装も安定してきたので紹介しま...

マーケットプレイスへの公開

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

やり方は公式ドキュメントにあったまんま。

GitHub Docs
Publishing actions in GitHub Marketplace - GitHub Docs You can publish actions in GitHub Marketplace and share actions you've created with the GitHub community.

ルートに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/coregetinputで取れるし、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
Add projects beta - GitHub Marketplace add issue or PR to GitHub Projects(Beta)

まとめ

GitHub Actionsのymlファイル内でJS使えるんだっていう学びがあったのとやっぱこういう自動化関連のタスクは好きだなという気づき。

ふとマーケットプレイスに公開ってどうやるんだろって思ってやってみたけど、やってみたら簡単にできたしチャレンジしてよかった。

この記事が参考になったからコーヒーくらいおごってもいいぜという方は、以下からサポートいただけると次の記事書くモチベになりますのでよろしくお願いします

Buy Me A Coffee

参考

GitHub Docs
Publishing actions in GitHub Marketplace - GitHub Docs You can publish actions in GitHub Marketplace and share actions you've created with the GitHub community.
GitHub Docs
Creating a JavaScript action - GitHub Docs In this guide, you'll learn how to build a JavaScript action using the actions toolkit.
よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!
目次