![見出し画像](https://assets.st-note.com/production/uploads/images/92652686/rectangle_large_type_2_6a2e3762abfd55dc920dd7579428e35d.png?width=1200)
GitHubで親Issueの情報を子Issueに引き継ぎたい話
この記事はmikan Advent Calendar 2022 13日目の記事です!
12日目は、@ag_ayakanの「教材一覧リニューアルに見るmikanのデザイン仕事」でした!
「教材一覧リニューアル」という具体的な施策を例に、デザイナーがmikanでどんな風に施策に関わっているのか?がわかる記事なので、興味ある方は見てみてください!
はじめに
こんにちは、株式会社mikanでiOSエンジニアをしている@uk_oasisです。
最近、mikanのiOSチームではスクラムを導入し始めました。スプリントのタスク管理には、GitHub Projectを利用しています。詳しくは、チームメンバーである@satoshin21が2日目の記事で、紹介してくれているので、読んでみてください!
エンジニアなので、GitHubで全て完結するのサイコーって思っていましたが、使い始めてみると、あれやこれや足りないものが出てきます。その一つに、「タスク分解をした時、子Issueの作成が面倒」という事があります。
タスクリストを作成して、チェックボックスからIssueを作るのは簡単なのですが、作成されたIssueは、もととなるIssueの情報を持っておらず、ぽちぽちと情報を追加してあげないといけないのが、地味に面倒です。
![](https://assets.st-note.com/img/1670203708520-WlPAUK2od7.png?width=1200)
この面倒さを感じるのが、見積もりのためにタスク分解をする時です。Issueの追加時にタスクの粒度が大きいので、サブタスクのリストを作成し、タスクリストからサブタスクのIssueを作成します。作成したサブタスクのIssueには、label, milestoneなど必要な情報がないので、もととなるIssueを見ながら追加していきます・・・ここがとてもつらい。
もととなるIssue(以下、親Issueと書きます)から、サブタスクのIssue(以下、子Issueと書きます)を作成時に、親Issueの情報が子Issueに引き継がれていると、つらみが減りそうです!
なので、このつらみをなくすために、自動化する方法を検証してみました。
環境
GitHub API v4を利用する
今回対象としているGitHub Project(以下ProjectV2と書きます)を操作する為のAPIエンドポイントが、GraphQLで提供されているAPI v4(以下特に指定がない場合、APIはv4を指します)しかないため、こちらを利用することにしました。
当初、子Issueから親Issueを取得する処理は、GitHub API v3で取得しようとしていたのですが、v4で取得する方が容易だったため、最終的に全ての処理でv4を利用するように変えました。同じIssueでも、v3よりもv4の方が多くの情報を取得できたので、欲しい情報が取れない!と思っている方は、一度v4のドキュメントを見てみることをお勧めします!
エクスプローラーを使って、動作を確認する
query、mutationを試すために、GitHubが用意しているエクスプローラーを利用して、動作の確認をしました。
ghコマンドを利用しても、同様に動作の確認ができるのですが、queryの妥当性の確認、型の確認が容易だったため、エクスプローラーを利用しました。どちらでも確認が可能なので、個人の好みで好きな方を利用してください。
必要な処理の確認
まず、「親Issueの情報を子Issueに引き継ぐ」を実現するために必要な処理を、調べてみました。この順番に処理ができれば、実現できそうです。
子Issueが作成されたタイミングで、Actionを実行する
子Issueの情報をもとに、親Issueの情報を取得する
親Issueの情報をもとに、子Issueの情報を更新する
また、引き継げる情報は色々あるので、引き継ぎたい情報として、以下を定義しました。
assignee
label
milestone
project
status
sprint
これらを踏まえて、それぞれ必要な処理を確認していきます。
0. 必要な情報を取得する
ProjectV2のフィールド情報を取得する
ProjectV2では、カスタムフィールドによって、フィールドが自由に定義できるようになっているため、どのフィールドのどの値を更新するか?を指定する必要がありました。動的に取得する方法を模索していたのですが、頻繁に更新する情報ではないので、事前に必要な情報を取得して、利用するようにしました。
まず、ProjectV2のIDを取得します。
query
query {
organization(login: "<team名>") {
projectsV2(first: 20) {
nodes {
id
title
}
}
}
}
結果
{
"data": {
"organization": {
"projectsV2": {
"nodes": [
...
{
"id": "PVT_aaaa", <- 次で使う
"title": "🍏 iOS Tasks"
},
...
]
}
}
}
}
次に、取得したProjectV2のIDを利用して、フィールドを取得するためのクエリを実行します。
query
query{
node(id: "<ProjectV2のID>") {
... on ProjectV2 {
fields(first: 20) {
nodes {
... on ProjectV2FieldCommon {
id
name
dataType
}
}
}
}
}
}
結果
{
"data": {
"node": {
"fields": {
"nodes": [
...
{
"id": "aaaa", <- 3-3で使う
"name": "Status",
"dataType": "SINGLE_SELECT" <- 2-2で使う
},
{
"id": "bbbb", <- 3-3で使う
"name": "Sprint",
"dataType": "ITERATION" <- 2-2で使う
},
...
]
}
}
}
}
各フィールドごとに型が定義されているのですが、ここでは更新するために必要な情報として、idとdataTypeがわかれば良いので、ProjectV2のフィールドで共通の型であるProjectV2FieldCommonを利用しました。
ここで取得した値は、以下のタイミングで利用します。
id
「3-3. 子Issueに親IssueのProject情報を追加」のmutationを作成するときに利用します
dataType
「2-2. 親IssueのIDを使って、Issue情報を取得する」のqueryを作成するときに利用します
1. 子Issueが作成されたタイミングで、Actionを実行する
Issueが作成されたタイミングで、作成されたIssueの情報を取得します。
on:
issues:
types:
- opened
今回は、子Issueが作成されたタイミングで実行したかったので opened を利用しました。他にも、いくつか実行できるタイミングがあるので、興味がある方は、公式ドキュメントをみてください。
作成されたIssueの情報は github.event.issue の中に入ってくるので、子Issueの情報はこれを利用します。
2. 子Issueの情報をもとに、親Issueの情報を取得する
github.event.issue で取得できる情報では、親Issueの情報が取得できませんでした。そのため、APIを利用して親Issueの情報を取得します。
2-1. 子IssueのIDを使って、親IssueのIDを取得する
query {
node(id: "<github.event.issue.node_id>") {
... on Issue {
trackedInIssues(first: 10) {
nodes {
id <- 2-2で使う
}
}
}
}
}
タスクリストからIssueが作られた時は、trackedInIssuesに親Issueが入ってくるので、そのIssueのIDを取得します
2-2. 親IssueのIDを使って、Issue情報を取得する
ProjectV2の情報を取得するために、0で取得したdataTypeを利用してqueryを作成しました。
statusがSINGLE_SELECTだったので、ProjectV2ItemFieldSingleSelectValueを利用、sprintがITERATIONだったのでProjectV2ItemFieldIterationValueを利用しています。
dataTypeごとの型については、こちらを参照しました。
query
query {
node(id: "<親IssueのID>") {
... on Issue {
assignees(first: 100) {
nodes {
id
}
}
labels(first: 100) {
nodes {
id
}
}
milestone {
id
}
projectItems(first: 5) {
nodes {
fieldValues(last: 10) {
nodes {
... on ProjectV2ItemFieldSingleSelectValue {
id
optionId
}
... on ProjectV2ItemFieldIterationValue {
id
iterationId
}
}
}
}
}
}
}
}
結果
{
"data": {
"node": {
"assignees": {
"nodes": [
{
"id": "aaaa" <- 3-1で使う
}
]
},
"labels": {
"nodes": [
{
"id": "LA_aaaa" <- 3-1で使う
}
]
},
"milestone": {
"id": "MI_aaaa" <- 3-1で使う
},
"projectItems": {
"nodes": [
{
"fieldValues": {
"nodes": [
{},
{},
{},
{},
{},
{},
{
"id": "PVTFSV_aaaa",
"optionId": "xxxx" <- 3-3で使う
},
{},
{
"id": "PVTFIV_bbbb",
"iterationId": "zzzz" <- 3-3で使う
}
]
}
}
]
}
}
}
}
3.親Issueの情報をもとに、子Issueの情報を更新する
あとは、取得した情報をもとに、子Issueの情報を更新してあげれば良いのですが、ProjectV2はIssueの更新とは別に追加、更新してあげないといけないため、以下3ステップを実行するようにしました。
注: 同じ呼び出しで項目を追加および更新することはできません。 addProjectV2ItemById を使用して項目を追加してから、updateProjectV2ItemFieldValue を使用して項目を更新する必要があります。
3-1. 親Issueの情報をもとに子Issueの情報を更新
ここでは、assignee、label、milestoneの情報を追加します。
mutation updateIssue($issueId: ID!, $assigneeIds: [ID]!, $labelIds: [ID]!, $milestoneId: ID!) {
updateIssue(
input: {
id: $issueId,
assigneeIds: $assigneeIds,
labelIds: $labelIds,
milestoneId: $milestoneId
}
) {
issue {
id
}
}
}
--- query variables ---
{
"issueId": "<github.event.issue.node_id>",
"assigneeIds": ["<2-2で取得したassigneeのid>"],
"labelIds": ["<2-2で取得したlabelのid>"],
"milestoneId": "<2-2で取得したmilestoneのid>"
}
3-2. 子IssueをProjectに追加
mutation ($projectId: ID!, $nodeId: ID!) {
addProjectV2ItemById(
input: {
projectId: $projectId,
contentId: $nodeId
}
) {
item {
id <- 3-3で使う
}
}
}
--- query variables ---
{
"projectId": "<0で取得したProjectV2のID>",
"nodeId": "<github.event.issue.node_id>"
}
実行時に取得できるID(itemId)を次のmutationで利用します。
3-3. 子Issueに親IssueのProject情報を追加
mutation($projectId: ID! $itemId: ID! $statusId: ID! $statusValue: String! $sprintId: ID! $sprintValue: String!) {
updateStatus: updateProjectV2ItemFieldValue(
input: {
projectId: $projectId
itemId: $itemId
fieldId: $statusId
value: {
singleSelectOptionId: $statusValue
}
}
) {
projectV2Item {
id
}
}
updateSprint: updateProjectV2ItemFieldValue(
input: {
projectId: $projectId
itemId: $itemId
fieldId: $sprintId
value: {
iterationId: $sprintValue
}
}
) {
projectV2Item {
id
}
}
}
--- query variables ---
{
"projectId": "<0で取得したProjectV2のID>",
"itemId": "<3-2の実行時に取得したID>",
"statusId": "<0で取得したstatusのフィールドのID>",
"statusValue": "<2-2で取得したstatusのID>",
"sprintId": "<0で取得したsprintのフィールドのID>",
"sprintValue": "<2-2で取得したsprintのID>"
}
ここでは、status、sprintを追加します。
それぞれ実行してみて、動作が確認できたので、残りはquery, mutationを順番に実行していくActionを作成するだけです。
Action内でquery, mutationを実行するために、以下のActionの利用を予定しています。
「利用を予定しています」と書いたのですが、執筆時点でAction作成ができていません・・・調査に時間がかかってしまい、実装まで完了できませんでした。今後、Actionを実装し、運用フローへのせていく予定です!
今回、色々触ってみたのですが、API(特にProjectV2)については情報が少なく、調査に時間がかかってしまいました。こうすればもっと改善できるよ!ここに詳しく書いてあるよ!など、ご存じの方がいれば、教えてください!
さいごに
この対応の調査内容をチームメンバーに共有したところ、OSSにしても面白いかも!と、実装に関しての意見をもらえたので、どのように実装していくか?を目下検討中です!
この記事を見て、面白そうなチームだなーと、mikanに興味を持ってもらえたら、@uk_oasisや他のmikanメンバーに気軽に話しかけてみてください!(特にデザイナーさんとか・・・)
明日は@mikan_yunmaの記事です!お楽しみに!