見出し画像

GitHubで親Issueの情報を子Issueに引き継ぎたい話

この記事はmikan Advent Calendar 2022 13日目の記事です!

12日目は、@ag_ayakanの「教材一覧リニューアルに見るmikanのデザイン仕事」でした!
「教材一覧リニューアル」という具体的な施策を例に、デザイナーがmikanでどんな風に施策に関わっているのか?がわかる記事なので、興味ある方は見てみてください!

はじめに

こんにちは、株式会社mikanでiOSエンジニアをしている@uk_oasisです。

最近、mikanのiOSチームではスクラムを導入し始めました。スプリントのタスク管理には、GitHub Projectを利用しています。詳しくは、チームメンバーである@satoshin212日目の記事で、紹介してくれているので、読んでみてください!

エンジニアなので、GitHubで全て完結するのサイコーって思っていましたが、使い始めてみると、あれやこれや足りないものが出てきます。その一つに、「タスク分解をした時、子Issueの作成が面倒」という事があります。

タスクリストを作成して、チェックボックスからIssueを作るのは簡単なのですが、作成されたIssueは、もととなるIssueの情報を持っておらず、ぽちぽちと情報を追加してあげないといけないのが、地味に面倒です。

タスクリストからIssueを作るのは簡単!

この面倒さを感じるのが、見積もりのためにタスク分解をする時です。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に引き継ぐ」を実現するために必要な処理を、調べてみました。この順番に処理ができれば、実現できそうです。

  1. 子Issueが作成されたタイミングで、Actionを実行する

  2. 子Issueの情報をもとに、親Issueの情報を取得する

  3. 親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 を使用して項目を更新する必要があります。

https://docs.github.com/ja/issues/planning-and-tracking-with-projects/automating-your-project/using-the-api-to-manage-projects#updating-projects

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の記事です!お楽しみに!


この記事が気に入ったらサポートをしてみませんか?