先日Project(beta)でGraphQLのAPIに更新があった
The new GitHub Issues - June 23rd update | GitHub Changelog
以前はProjectNext
という名前でデータを取得できていた
すでにProjectNext
でデータを取っていたが新たにできたProjectV2
という型で取得するように変更したのでそのときのメモなどを残しておく
取得したデータを何に使っているかというと、そのままBigQueryに突っ込んでDataportalで次のようなダッシュボードを作って月の消化タスク量を計測している
本記事ではデータの取得(JSONの取得)までのメモを残している
その後のよしなにやる部分は別途書くかもしれない
ドキュメント
Projects(beta)
Using the API to manage projects (beta) - GitHub Docs
https://docs.github.com/en/issues/trying-out-the-new-projects-experience/using-the-api-to-manage-projectsdocs.github.com
基本操作はこのドキュメントを読みながら進めれば良さそう
GraphQLの型情報
https://docs.github.com/en/graphql/referencedocs.github.com
プロジェクトのデータ取得
number: 2
はprojectのURLから取得する
例だとhttps://github.com/users/${user}/projects/2/views/8
で2となる
login
は自分のユーザー名
- sample.graphql
query{ user(login: "swfz") { projectV2(number: 2) { title id } } }
$ gh api graphql -f query="$(cat sample.graphql)" { "data": { "user": { "projectV2": { "title": "@swfz's individual project", "id": "PVT_hogehoge" } } } }
プロジェクト情報を取得できた
カスタムフィールドのID取得
nodes{ id updatedAt fieldValues(first: 50) { nodes{ ... on ProjectV2ItemFieldTextValue { text field { ... on ProjectV2FieldCommon { name } } } ... on ProjectV2ItemFieldDateValue { date field { ... on ProjectV2FieldCommon { name } } } ... on ProjectV2ItemFieldSingleSelectValue { name field { ... on ProjectV2FieldCommon { name } } } } } }
上記クエリは一部ドキュメントから引用した
サンプルだと3つの型を例示してこの型のフィールドの場合はこのフィールド名を指定するという感じ
Textならtext
,Dateならdate
といったように型によって取れる値のフィールド名が変わる
型の種類がどれくらいあるのかみたら11個もあった
一個ずつ確認しに行こうかと思ったがProjectV2のフィールドで共通の型ProjectV2FieldCommon
というのを見つけたので出力してみる
- fieldtype.graphql
query{ node(id: "PVT_hogehoge") { ... on ProjectV2 { fields(first: 20) { nodes { ... on ProjectV2FieldCommon { id name dataType } } } } } }
$ gh api graphql -f query="$(cat fieldtype.graphql)" { "data": { "node": { "fields": { "nodes": [ { "id": "PVTF_aaaaaaaaaa", "name": "Title", "dataType": "TITLE" }, { "id": "PVTF_bbbbbbbbbb", "name": "Assignees", "dataType": "ASSIGNEES" }, { "id": "PVTSSF_cccccccccc", "name": "Status", "dataType": "SINGLE_SELECT" }, { "id": "PVTF_dddddddddd", "name": "Labels", "dataType": "LABELS" }, { "id": "PVTF_eeeeeeeeee", "name": "Repository", "dataType": "REPOSITORY" }, { "id": "PVTF_ffffffffff", "name": "Milestone", "dataType": "MILESTONE" }, { "id": "PVTF_gggggggggg", "name": "Linked pull requests", "dataType": "LINKED_PULL_REQUESTS" }, { "id": "PVTF_hhhhhhhhhh", "name": "Point", "dataType": "NUMBER" }, { "id": "PVTF_iiiiiiiiii", "name": "Month", "dataType": "DATE" }, { "id": "PVTF_jjjjjjjjjj", "name": "Reviewers", "dataType": "REVIEWERS" }, { "id": "PVTIF_kkkkkkkkkk", "name": "Iteration", "dataType": "ITERATION" }, { "id": "PVTF_llllllllll", "name": "Tracks", "dataType": "TRACKS" } ] } } } }
これでいったん、Projectで使っているフィールド、カスタムフィールドのID、名前、タイプが取得できる
このdataType
に対応するProjectV2ItemField{DataType}Value
を定義すればデータを取得できそう
Projectで扱っているIssue,PullRequestのデータ、カスタムフィールドのデータを取得するGraphQLクエリ
結局こんな感じのクエリになった
- items_v2.graphql
query($projectId: ID! $cursor: String){ node(id: $projectId) { ... on ProjectV2 { items(first: 50, after: $cursor) { pageInfo { hasNextPage endCursor } nodes{ id createdAt updatedAt isArchived type fieldValues(first: 20) { nodes{ ... on ProjectV2ItemFieldTextValue { text field { ... on ProjectV2FieldCommon { id dataType name } } } ... on ProjectV2ItemFieldDateValue { date field { ... on ProjectV2FieldCommon { id dataType name } } } ... on ProjectV2ItemFieldSingleSelectValue { name nameHTML optionId field { ... on ProjectV2FieldCommon { id dataType name } } } ... on ProjectV2ItemFieldIterationValue { iterationId startDate title titleHTML field { ... on ProjectV2FieldCommon { id dataType name } } } ... on ProjectV2ItemFieldLabelValue { labels(first: 5) { nodes { color description isDefault name url } } field { ... on ProjectV2FieldCommon { id dataType name } } } ... on ProjectV2ItemFieldMilestoneValue { milestone { id title } field { ... on ProjectV2FieldCommon { id dataType name } } } ... on ProjectV2ItemFieldNumberValue { number field { ... on ProjectV2FieldCommon { id dataType name } } } ... on ProjectV2ItemFieldPullRequestValue { pullRequests(first: 10) { nodes { title id number url closed closedAt createdAt merged mergedAt repository { name } assignees(first: 5) { nodes{ name login } } reviewRequests(first: 5) { nodes { requestedReviewer { ... on User { name } } } } } } field { ... on ProjectV2FieldCommon { id dataType name } } } ... on ProjectV2ItemFieldRepositoryValue { repository { name } field { ... on ProjectV2FieldCommon { id dataType name } } } ... on ProjectV2ItemFieldUserValue { users(first: 3) { nodes { id login name } } field { ... on ProjectV2FieldCommon { id dataType name } } } } } content{ ... on DraftIssue { title body createdAt } ...on Issue { title id number url closed closedAt createdAt repository { name } milestone { id title } assignees(first: 5) { nodes{ name login } } } ...on PullRequest { title id number url closed closedAt createdAt merged mergedAt repository { name } assignees(first: 5) { nodes{ name login } } reviewRequests(first: 5) { nodes { requestedReviewer { ... on User { name } } } } } } } } } } }
めちゃくちゃ長いw
ざっくりやっていることとしては
次のデータを取得している
- ProjectV2Itemの各種フィールドのデータ
- Point(ProjectV2ItemFieldNumberValue)
- Month(ProjectV2ItemFieldDateValue)
- Status(ProjectV2ItemFieldSingleSelectValue)
- Iteration(ProjectV2ItemFieldIterationValue)
- Milestone(ProjectV2ItemFieldMilestoneValue)
- Repository(ProjectV2ItemFieldRepositoryValue)
- Label(ProjectV2ItemFieldLabelValue)
- etc…
- ProjectV2Itemのcontentデータ
- PullRequest
- Issue
- DraftIssue
- ボード(Project)だけに存在するカード的なもの
カスタムフィールドの定義がProjectNextよりしっかりしたようでカスタムフィールドの型によって出力項目も変わる
IDが欲しいだけなら上記のクエリで書いている各フィールドの定義をProjectV2FieldCommon
にまとめてしまうだけでOKだが今回は値も欲しいのでこのようなクエリになった
値が欲しいなら結局は型によってそれぞれ記述するしかなさそう
ProjectV2Itemの件数が50件にしているのは色々なフィールドを増やしまくった結果制限に達してしまったため下げた(最大だと100だったはず)
variables
渡す予定の引数について
cursor
はページング用、レスポンスで返ってくるendCursor
の値を指定して次のクエリをcursor
に指定して投げる
projectId
は最初の段階でProjectV2のIDを取得しているので変数で指定する
実行(初回)
$ gh api graphql -f query="$(cat items_v2.graphql)" -f projectId=${GH_PROJECT_ID} { "data": { "node": { "items": { "pageInfo": { "hasNextPage": true, "endCursor": "NTA" }, "nodes": [ { "id": "PVTI_aaaaaaaaaaaaaaaaaaaa", "createdAt": "2021-12-10T17:18:16Z", "updatedAt": "2021-12-10T17:20:33Z", "isArchived": false, "type": "ISSUE", "fieldValues": { "nodes": [ { "users": { "nodes": [ { "id": "bbbbbbbbbb", "login": "swfz", "name": "swfz" } ] }, "field": { "id": "PVTF_cccccccccccccccccc", "dataType": "ASSIGNEES", "name": "Assignees" } }, { "repository": { "name": "memo" }, "field": { "id": "PVTF_dddddddddddddddddd", "dataType": "REPOSITORY", "name": "Repository" } }, { "labels": { "nodes": [ { "color": "128de5", "description": "", "isDefault": false, "name": "blog", "url": "https://github.com/swfz/sample/labels/blog" } ] }, "field": { "id": "PVTF_eeeeeeeeeeeeeeeeee", "dataType": "LABELS", "name": "Labels" } }, { "text": "jqでファイルに書き出したリストを用いて比較する", "field": { "id": "PVTF_ffffffffffffffffff", "dataType": "TITLE", "name": "Title" } }, { "name": "Done", "nameHTML": "Done", "optionId": "98236657", "field": { "id": "PVTSSF_gggggggggggggggggg", "dataType": "SINGLE_SELECT", "name": "Status" } }, { "number": 1, "field": { "id": "PVTF_hhhhhhhhhhhhhhhhhh", "dataType": "NUMBER", "name": "Point" } }, { "date": "2022-03-01", "field": { "id": "PVTF_iiiiiiiiiiiiiiiiii", "dataType": "DATE", "name": "Month" } }, { "iterationId": "61051d6c", "startDate": "2022-03-01", "title": "2022-03", "titleHTML": "2022-03", "field": { "id": "PVTIF_jjjjjjjjjjjjjjjjjj", "dataType": "ITERATION", "name": "Iteration" } } ] }, "content": { "title": "jqでファイルに書き出したリストを用いて比較する", "id": "kkkkkkkkkkkkkkkkkkkkkkkk", "number": 1466, "url": "https://github.com/swfz/sample/issues/1466", "closed": true, "closedAt": "2022-03-27T09:39:58Z", "createdAt": "2021-06-03T19:37:41Z", "repository": { "name": "memo" }, "milestone": null, "assignees": { "nodes": [ { "name": "swfz", "login": "swfz" } ] } } },
とりあえず最初の1件だけ抜き出した
こんな感じでデータが取れるのであとは用途によってよしなにすればよい
他余談
権限
GitHub Appsで払い出したTOKENを用いてクエリをたたこうとするとNotFoundとなる
自分のプライベートタスク管理なのでuser projectを作成して活用していた
で、user projectへアクセスできないなーと思っていたがどうやらユーザー配下のデータにアクセスするにはOAuthAppもしくはPersonalAccessTokenじゃないと無理そう?
Forumにも同様の質問が投稿されていたが返信はついてなかった…
また、ProjectV2ではread:project
もしくはwrite:project
の権限が必要になったよう
Insight
Insightも使えるようになってグラフ化も簡単に行えるようになった
なのでカスタムフィールドにStoryPointなどを設定しベロシティトラッキング用のチャートを出力するということがコードなしに実現できる
ただ、実際に試してみたがバーンダウン系のチャートはうまく実現できなそうだったので自前でやるしかなさそう
まとめ
- GitHubのGraphQL APIを用いてProject(beta)(ProjectV2)のデータを取得した
- ProjectV2Item
- ProjectV2ItemにひもづくPullRequestやIssueのデータも合わせて取得した
- ProjectV2になってカスタムフィールドの型がしっかりしたのでカスタムフィールドの値をそれぞれ定義する必要があった
- GitHubのCLIでGraphQLのAPIにクエリできるのが結構便利