LoginSignup
2
5

More than 5 years have passed since last update.

(コードを書かずに)Backlogに課題が追加されたときに、課題の一部をリセットする仕掛けを作った

Last updated at Posted at 2019-03-26

困っていること

Backlogで過去の課題を複製する機能は便利なのですが、過去のチケットのデータを引き継いて欲しくないケースもあります。(複製時は消せと何度も言っているのに!)作成者が消すのを忘れてしまうことがあります。
こういうのは人間に任せるからミスをするので、人間に任せない仕組みにしてしまいたいと思います

今回使ったもの

  • Backlog WebHook(新規登録)
  • Microsoft Flow (HTTP受信とHTTP発信)
  • Backlog API(課題情報の更新)

今回は簡単に実現するために、Microsoft Flowを使いました。

やってみよう

1. BacklogAPIのKeyを発行

Backlog右上の人アイコンから、個人設定、APIからBacklogAPIのキーを発行し、APIをブラウザで叩いて自分の情報が取れるか確認しておきます。

https://kanaxx.backlog.com/api/v2/users/myself?apiKey=YOUR KEY

2. BacklogのWebHook設定

Backlogのダッシュボードからプロジェクト設定を開き、WebHookを設定をします。
image.png

まだMicrosoft Flow側の受信の設定をしていないので、WebHook URLは一時的にhttp://localhostを設定しています。
image.png

今回は課題の新規作成のみWebHookが飛ぶように設定をしました。
画面の一番下にある「テスト実行」を押してサンプルのデータを出力してみます(実際にはlocalhostで受信はできません。)

テスト実行を押したあとに、WebHookの一覧から送信履歴から送信データを確認します。
image.png

「全て表示」を押すと、WebHookで送信されるデータの形式が確認できます。
image.png

データはこんな感じです。(ユーザ情報は置き換え済みです)

webhook-sample.json
{
  "created": "2019-03-26T11:52:03Z",
  "project": {
    "archived": false,
    "projectKey": "TEST",
    "name": "TestProject",
    "chartEnabled": false,
    "id": 100,
    "subtaskingEnabled": false
  },
  "id": 10,
  "type": 1,
  "content": {
    "summary": "test issue",
    "key_id": 100,
    "customFields": [

    ],
    "dueDate": "",
    "description": "test description",
    "priority": {
      "name": "",
      "id": null
    },
    "resolution": {
      "name": "",
      "id": null
    },
    "actualHours": null,
    "issueType": {
      "color": "null",
      "name": "Bug",
      "displayOrder": null,
      "id": 400,
      "projectId": null
    },
    "milestone": [
      {
        "archived": "false",
        "releaseDueDate": "null",
        "name": "prototype release",
        "displayOrder": null,
        "description": "",
        "id": null,
        "projectId": null,
        "startDate": "null"
      },
      {
        "archived": "false",
        "releaseDueDate": "null",
        "name": "alpha release",
        "displayOrder": null,
        "description": "",
        "id": null,
        "projectId": null,
        "startDate": "null"
      },
      {
        "archived": "false",
        "releaseDueDate": "null",
        "name": "beta release",
        "displayOrder": null,
        "description": "",
        "id": null,
        "projectId": null,
        "startDate": "null"
      },
      {
        "archived": "false",
        "releaseDueDate": "null",
        "name": "product release",
        "displayOrder": null,
        "description": "",
        "id": null,
        "projectId": null,
        "startDate": "null"
      }
    ],
    "versions": [
      {
        "archived": "false",
        "releaseDueDate": "null",
        "name": "Version0.1",
        "displayOrder": null,
        "description": "",
        "id": null,
        "projectId": null,
        "startDate": "null"
      },
      {
        "archived": "false",
        "releaseDueDate": "null",
        "name": "Version0.2",
        "displayOrder": null,
        "description": "",
        "id": null,
        "projectId": null,
        "startDate": "null"
      },
      {
        "archived": "false",
        "releaseDueDate": "null",
        "name": "Version1.0",
        "displayOrder": null,
        "description": "",
        "id": null,
        "projectId": null,
        "startDate": "null"
      }
    ],
    "parentIssueId": null,
    "estimatedHours": null,
    "id": 100,
    "assignee": null,
    "category": [
      {
        "name": "Category1",
        "displayOrder": null,
        "id": null
      },
      {
        "name": "Category2",
        "displayOrder": null,
        "id": null
      }
    ],
    "startDate": "",
    "status": {
      "name": "In Progress",
      "id": 2
    }
  },
  "notifications": [

  ],
  "createdUser": {
    "nulabAccount": {
      "nulabId": "xxx",
      "name": "xxx",
      "uniqueId": "xxx"
    },
    "name": "kanaxx",
    "mailAddress": null,
    "id": 0,
    "roleType": 1,
    "lang": "ja",
    "userId": null
  }
}

WebHookデータの中にissueKeyがありません。これが今後のドハマりポイントになります。

3.Microsoft Flowの受信の詳細設定

Microsoft Flowでjsonデータを受信するときは、BacklogのWebHookデータのそのものか、json schemaが必要です。今回は上で作ったサンプルデータをもとにデータのペイロードをして形を作ります。

Flowを「一から作成」を選択
image.png

トリガーにHTTP要求の受信時を設定し、サンプルのペイロードを利用してスキーマを生成するをクリック。これは実データをMicrosoft Flowに解析してもらい、データの項目を確定させる方法です。
image.png

上で作ったBacklogのWebHookサンプルデータを投入し、OKを押すとjson schemaが作成される。
image.png

メソッドをPOSTに設定しておく。受信用のURLはまだ作成できません。次の工程までお待ちください

4.Microsoft FlowにHTTP(発信)を追加

アクションにHTTPを追加して、Microsoft FlowからBacklogのAPIを実行する仕掛けを作ります。

初期状態
image.png

入力が簡単なところは以下の3つ。
方法はPatch
ヘッダ Content-Type = application/x-www-form-urlencoded
クエリー apiKey = YOUR API KEY

さくっといれてしまいましょう。

URLの設定

URLはちょっと複雑です。
FlowのURLには、Backlogの課題更新のAPIのURLを設定します。このAPIは、URLの最後に課題のIDを設定する必要があります。つまりチケットごとにURLが変わるわけです。このため、課題IDは変数からIDを持ってきます。

URLにカーソルを置くと、右側の変数サポートで何も出てきません。「もっと見る」を押すと出てきます。
image.png

idが11個も並ぶ絶望的なインターフェイス:innocent:
image.png

課題のIDは9番目のIDなので、9番目のIDがURLの最後尾に付くように設定します。
image.png

必ずidの9番目が課題IDなのか自信がないので、コードのプレビュー機能を使って確認できます。
image.png

image.png

{triggerBody()?['content']?['id']}", になっていれば正解です。ならない場合には総当たりで11回試してください。たぶん、下から試すのが早いです。

本文

本文はBacklog APIの課題更新のパラメータを設定します。name=valueを&で繋ぐ形式です。

versionId=[]&milestoneId[]=&dueDate=

今回はバージョン、マイルストーン、締切日を空にするように設定しています。カスタムフィールドも対応可能ですが、フリープランだと使えないのでできる範囲でやっています。パラメータの詳細はBacklog APIのドキュメントを見てください。

URLの発行

フローを保存すると、HTTP要求受信用のURLが発行されます。
image.png

次で使うのでコピーしておきます。

5. Backlog WebHookのURLを修正

手順2で、Backlog側のWebHookのURLを一時的にhttp://localhostで作ってあります。WebHookの設定画面を開き、Microsoft Flowで発行されたURLに修正します。

これで準備完了。

いざ実行

元になるチケットを準備
image.png

バージョン、マイルストン、締切日を設定済み

チケットの複製を実行
image.png

うっかり、マイルストーンとバージョンと期限日を消すのを忘れてしまい、新規登録を完了させる

確認

WebHookの発信確認

image.png

増えてるので何か送っているっぽい。一番新しい送信履歴を見てみる

image.png

今追加したチケットの情報が送られているのを確認。(issueKeyはやっぱり無い)

チケットの確認

新規登録直後の状態
image.png

WebHook -> Microsoft Flow -> Backlog APIが実行されたあと。右上の期限日も消えてる
image.png

コメントにも変更履歴が残ります。
image.png

Flowのプランにもよると思いますが、自分の環境だと新規登録の1秒後には消えていました。
登録日:2019/03/26 23:05:22
更新日:2019/03/26 23:05:23
image.png

まとめ

Microsoft Flowは便利です。プログラム作らずにできちゃいます。たぶんZapierでもできると思います。
今回はやらなかったですが、Flowの分岐機能を使えば、特定のチケット種別のときだけ初期化するとか、子チケットのときだけ初期化するとか、期限日を強制的に3日後に更新してしまうとか、応用はいろいろできると思います。

FlowからのHTTPコネクタがFlow for Office365のライセンスでは使えないかもしれません。自分のところは使えているので、使えない場合にはライセンスを確認してください。
https://blog.iotaker.jp/?p=294

おまけ

実は手順3時点で、工夫したjson schemaを入れると、もうちょっと簡単に課題IDを設定できるようになります。参考までに自分が作ったものを置いておきます。細かいところは正確じゃないかもしれません。Backlog WebHookの仕様が変わってもこのQiitaは更新しないと思うので、変わっていたらごめんなさい。(json schemaはAPI提供側から提供されるのがよいですよね。)

変更点1:
ペイロードしたjson schemaドキュメントの"type"="object"の内側に、"title"="project"や"title"="content"と設定を追加する。

変更点2:
11個あるIDに名前が付くようになるので、backlog_issue content idを選べばOK
image.png

{
    "$schema": "http://json-schema.org/draft-04/schema#",
    "type": "object",
    "title": "backlog_issue",
    "properties": {
      "created": {
        "type": "string"
      },
      "project": {
        "type": "object",
        "title": "project",
        "properties": {
          "archived": {
            "type": "boolean"
          },
          "projectKey": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "chartEnabled": {
            "type": "boolean"
          },
          "id": {
            "type": "integer"
          },
          "subtaskingEnabled": {
            "type": "boolean"
          }
        },
        "required": [
          "archived",
          "projectKey",
          "name",
          "chartEnabled",
          "id",
          "subtaskingEnabled"
        ]
      },
      "id": {
        "type": "integer"
      },
      "type": {
        "type": "integer"
      },
      "content": {
        "type": "object",
        "title": "content",
        "properties": {
          "summary": {
            "type": "string"
          },
          "key_id": {
            "type": "integer"
          },
          "customFields": {
            "type": "array",
            "items": {}
          },
          "dueDate": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "priority": {
            "type": "object",
            "title": "priority",
            "properties": {
              "name": {
                "type": "string"
              },
              "id": {
                "type": "integer"
              }
            },
            "required": [
              "name",
              "id"
            ]
          },
          "resolution": {
            "type": "object",
            "title": "resolution",
            "properties": {
              "name": {
                "type": "string"
              },
              "id": {
                "type": "null"
              }
            },
            "required": [
              "name",
              "id"
            ]
          },
          "actualHours": {
            "type": "null"
          },
          "issueType": {
            "type": "object",
            "title": "issue_type",
            "properties": {
              "color": {
                "type": "string"
              },
              "name": {
                "type": "string"
              },
              "displayOrder": {
                "type": "integer"
              },
              "id": {
                "type": "integer"
              },
              "projectId": {
                "type": "integer"
              }
            },
            "required": [
              "color",
              "name",
              "displayOrder",
              "id",
              "projectId"
            ]
          },
          "milestone": {
            "type": "array",
            "items": {}
          },
          "versions": {
            "type": "array",
            "items": {}
          },
          "parentIssueId": {
            "type": "integer"
          },
          "estimatedHours": {
            "type": "null"
          },
          "id": {
            "type": "integer"
          },
          "assignee": {
            "type": "null"
          },
          "category": {
            "type": "array",
            "items": {}
          },
          "startDate": {
            "type": "string"
          },
          "status": {
            "type": "object",
            "title": "status",
            "properties": {
              "name": {
                "type": "string"
              },
              "id": {
                "type": "integer"
              }
            },
            "required": [
              "name",
              "id"
            ]
          }
        },
        "required": [
          "summary",
          "key_id",
          "customFields",
          "dueDate",
          "description",
          "priority",
          "resolution",
          "actualHours",
          "issueType",
          "milestone",
          "versions",
          "parentIssueId",
          "estimatedHours",
          "id",
          "assignee",
          "category",
          "startDate",
          "status"
        ]
      },
      "notifications": {
        "type": "array",
        "items": {}
      },
      "createdUser": {
        "type": "object",
        "title": "created_user",
        "properties": {
          "nulabAccount": {
            "type": "null"
          },
          "name": {
            "type": "string"
          },
          "mailAddress": {
            "type": "null"
          },
          "id": {
            "type": "integer"
          },
          "roleType": {
            "type": "integer"
          },
          "userId": {
            "type": "null"
          }
        },
        "required": [
          "nulabAccount",
          "name",
          "mailAddress",
          "id",
          "roleType",
          "userId"
        ]
      }
    },
    "required": [
      "created",
      "project",
      "id",
      "type",
      "content",
      "notifications",
      "createdUser"
    ]
  }
2
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
5