GraphQLとREST APIで、GitHub ProjectsのDraftIssueとIssueのタイトルを一括で編集する

記事タイトルとURLをコピーする

サービス開発課のくればやしです。

GitHub Projects のカード(DraftIssue)とIssueのタイトルを一括で編集するスクリプトを作りましたので、調べたことを残しておきたいと思います。

背景&やりたかったこと

サービス開発課では開発プロジェクトのタスク管理にGitHubのProjectsを活用しています。開発プロジェクトは3ヶ月サイクルで、1サイクルの中で大体3機能前後を並行して進めます。

技術的な内容は開発する機能ごとに異なりますが、仕様決めやユーザーへの告知、必要なドキュメント作成作業等、開発以外の作業は同じ内容が多いです。そのため、Projectsのテンプレート機能を用いてタスクを管理しており、開発プロジェクトごとにテンプレートを展開してProjectsを作成しています。

さて、そうなると、カードやIssueのタイトルが同じものが多く作成されてしまうため、Issueを一覧で確認する際にどのプロジェクトの物なのか視認性が悪くなってしまうため、開発プロジェクトごとにコードのようなものをすべてのカードのタイトルの先頭にPrefixとして付与していました。ただ、このPrefixを付与する作業が手作業のため、毎回非常に手間がかかっていました。

Issueの管理には、ラベルやマイルストーンも活用しているものの、Pulseを用いた最新Issueの確認や、Issueを一覧画面でざっと見る際には、やはりタイトルだけでどの開発プロジェクトの物かパッと分かると便利です。

そこで、ProjectsにあるカードやIssueのタイトルを一括で編集して、その作業を効率的に行えるようにしようと考えました。やりたいことのイメージは以下図のとおりです。

実現方法

GitHubではAPIとして、GraphQLのAPIと、RESTのAPIが提供されています。

GraphQLの方が新しいようなので、基本的にはGraphQLで進めようとしましたが、どうやらProjectsにあるIssueのタイトルの変更はGraphQLのAPIでは出来ないようだったので、REST APIも併用することとしました(参考リンク)。

また、GitHub CLIでも出来たかもしれませんが、今回はGraphQLを試したかったのできちんと調べてはいません。 cli.github.com

実装内容

APIのやり取りの部分を中心に以下実装内容を記載します。言語はRuby、クライアントライブラリにはGraphQLにはgraphlient、RESTにはoctokitを使わせて頂きました

まず、プロジェクトのIDの取得を行います。プロジェクトの番号はProjects のURLの "https://github.com/orgs/[org_name]/projects/[number]" の number 部で確認できるため、それを利用してGraphQLでIDを取得します。今回作成したクエリは以下です。実行すると "PVT_xxxxxxxxxx" のような文字列を得られます。

# @param [Integer] project_number
# @return [String]
def fetch_project_id(project_number)
  response = @client.query <<~GRAPHQL
  query {
    organization(login: "serverworks") {
        projectV2(number: #{project_number}) {
          id
        }
    }
  }
  GRAPHQL
  response.to_h["data"]["organization"]["projectV2"]["id"]
end

次に、取得したプロジェクトIDを利用してカード(DraftIssue)とIssueのデータを取得します。以下のクエリを実行することにより、カードとIssueのIDや現在のタイトルを取得します。

def fetch_items(project_id)
  response = @client.query <<~GRAPHQL
  query {
      node(id: "#{project_id}") {
        ... on ProjectV2 {
          items {
            nodes {
              id,
              content {
                ... on DraftIssue {
                  title
                }
                ... on Issue {
                  number,
                  title,
                  repository {
                    name
                  }
                }
              }
            }
          }
        }
      }
  }
  GRAPHQL

  nodes = response.to_h["data"]["node"]["items"]["nodes"]
  # ... データをいい感じに格納するための実装
end

取得したデータを使って、タイトルを編集していきます。カード(DraftIssue)の場合は、GraphQLで編集できるため、updateProjectV2ItemFieldValueミューテックスで編集しました。

mutation {
  updateProjectV2ItemFieldValue(input: {
    itemId: "#{item.id}",
    projectId: "#{project_id}",
    fieldId: "#{field_id}"
    value: {
      text : "#{title_with_prefix}"
    }
  })
}
GRAPHQL

前述の通り、Issueの方のタイトルは、Projectsに登録されているとGraphQLでの編集に対応していなそうだったため、REST APIで実行しました。

client = Octokit::Client.new(:access_token => "[token]")
client.update_issue(
  "serverworks/#{repository}",
  item.number, # リポジトリ内のIssueの番号
  "#{title_with_prefix}"
)

おわりに

今回、GraphQLとREST APIで、GitHub ProjectsのDraftIssueとIssueのタイトルを一括で編集してみました。これで、1開発プロジェクトにつき、数十分&若干のストレスが削減出来たのではないかと思います。

ブラウザでProjectsを扱っているときは、DraftIssueはIssueにさくっとConvertできるため似たようなものと思っていましたが、APIを触ってみて、DraftIssueとIssueはデータの内容や紐づき方が全く異なるのだなと思いました。

www.serverworks.co.jp

www.serverworks.co.jp

紅林輝(くればやしあきら)(サービス開発部) 記事一覧

サービス開発部所属。2015年にサーバーワークスにJOIN。クラウドインテグレーション部を経て、現在はCloud Automatorの開発に従事。ドラクエ部。推しナンバーはⅤ、推しモンスターはクックルー。