やり忘れのBacklog課題を定期的にSlackに通知してみる

#きっかけ
いつも見るのはSlack。メールもBacklogもあまり見ない。
そしてBacklogで振られている課題を忘れてしまう。。
よし、毎朝Slackに対応していないBacklog課題を連絡するようにしよう。
Google Apps ScriptからBacklogのAPIで該当の課題一覧を引っ張ってきて、それをSlackに投げればいいかな。

# BacklogのAPIで課題情報を取得
Backlogの課題一覧取得方法は[公式の課題一覧の取得](https://developer.nulab.com/ja/docs/backlog/api/2/get-issue-list/#%E8%AA%B2%E9%A1%8C%E4%B8%80%E8%A6%A7%E3%81%AE%E5%8F%96%E5%BE%97)ページに載っているので参考にGASで取得してみます。
#### BacklogのAPIキー
おっと、その前にBacklogのAPIキーを取得していきます。
Backlogページの右上にある自分のアイコンをクリックしたメニューの中の **個人設定** をクリックし、左のメニューの **API** から取得しておきます。
APIキーを取得したらGASから早速APIを叩いて課題一覧を取得してみましょう。


```js
function myFunction() {
  const baseUrl = 'https://xxxxx.backlog.jp';  // 利用しているBacklogのドメイン
  const apiKey = 'xxxxxxxxxxxxxxxxxxxxxxx';  // Backlogから取得したAPIキー
  const endpoint = '/api/v2/issues';  //　公式の課題一覧の取得に記載されているURL

  const url = baseUrl + endpoint + '?apiKey=' + apiKey;  // 問い合わせるURLにする
  const response = UrlFetchApp.fetch(url);  // fetchメソッドでHTTPリクエストを送る
  const json = JSON.parse(response.getContentText());  // テキストデータを取り出す

  Logger.log(json);  // ログに出力
}
```
色々ログに出力されました。
これだと各課題のログの量が多すぎて見にくいので一旦各課題の課題のIDだけ表示させることにします。

```js
function myFunction() {
  const baseUrl = 'https://xxxxx.backlog.jp';  // 利用しているBacklogのドメイン
  const apiKey = 'xxxxxxxxxxxxxxxxxxxxxxx';  // Backlogから取得したAPIキー
  const endpoint = '/api/v2/issues';  //　公式の課題一覧の取得に記載されているURL

  const url = baseUrl + endpoint + '?apiKey=' + apiKey;  // 問い合わせるURLにする
  const response = UrlFetchApp.fetch(url);  // fetchメソッドでHTTPリクエストを送る
  const json = JSON.parse(response.getContentText());  // テキストデータを取り出す
  
  for (let i in json) {  // 課題の数だけループを回して
    Logger.log(json[i]['issueKey']);  // ログに課題のIDを出力
  }
}
```

余計なログがなくなり見やすくはなりましたが、Backlog全体を対象にして取得しているので余計な課題が混ざってしまっています。
そのため以下の条件でフィルターをかけ必要な課題だけ取得します。
* 指定の担当者
* 指定の種別
* 指定のステータス

# 担当者のユーザーIDを探す
担当者を絞り込むには担当者のユーザーIDが必要なようですが、どうもGASで担当者のユーザーIDを出力するのがうまくいかず...
仕方ないので [BacklogでAPIを使ってユーザーのID（数値）を調べる方法](https://hacknote.jp/archives/55613/) を参考にしてPythonからBacklogのAPIを叩いてユーザーIDを取得します。
この方法では課題を指定してその課題の担当者のユーザーIDを取得するので、課題を指定するために課題IDを取得します。
と言ってもこれは簡単で、課題を開いた時にURLの最後（xxxx-xx）になります。
具体的には課題URLの末尾の太字の部分になります。（後ろのxxは数字）
xxxxx.backlog.jp/view/**xxxx-xx**
課題IDが分かったらPythonで担当者のユーザーIDを取得します。

```python
import requests

baseUrl = 'https://xxxxx.backlog.jp';  # 利用しているBacklogのドメイン
apiKey = 'xxxxxxxxxxxxxxxxxxxxxxx';  # Backlogから取得したAPIキー
ticket_id = 'xxxx-xx'  # 課題のID

url = base_url + ticket_id  # 問い合わせるURLにする
json = requests.get(url, {'apiKey': apiKey}).json() # テキストデータを取り出す

print(json['assignee']['id'])  # 取得結果の担当者のユーザーIDを出力
```

# 担当者で絞り込む
担当者のユーザーIDが分かったのでGASで担当者で絞り込みます。
下記の通り、問い合わせるURLのパラメータに `&assigneeId[]=担当者のユーザーID` を追加します。

```js
function myFunction() {
  const baseUrl = 'https://xxxxx.backlog.jp';
  const apiKey = 'xxxxxxxxxxxxxxxxxxxxxxx';
  const endpoint = '/api/v2/issues';

  const url = baseUrl + endpoint + '?apiKey=' + apiKey + '&assigneeId[]=' + xxxxxxx;  // 問い合わせるURLにする
  
// （以下略。上と同じ。）
}
```

#課題の種別で絞り込む
次は種別（タスク、バグ、要望..）で絞り込みます。
種別は `issueTypeId` で絞り込みます。
issueTypeIdの確認も簡単で、Backlogの左メニューの **プロジェクト設定** の **種別** タブの確認したい種別をクリックします。
クリックして表示された種別の編集ページのURLの **issueType.id** がそれになります。
issueTypeIdが分かったのでGASで絞り込みます。
これは問い合わせるURLのパラメータに `issueTypeId[]=issueType.id` を追加します。

```js
function myFunction() {
  const baseUrl = 'https://xxxxx.backlog.jp';
  const apiKey = 'xxxxxxxxxxxxxxxxxxxxxxx';
  const endpoint = '/api/v2/issues';

  const url = baseUrl + endpoint + '?apiKey=' + apiKey + '&assigneeId[]=' + xxxxxxx + '&issueTypeId[]=' + xxxxxxx;  // 問い合わせるURLにする
  
// （以下略。上と同じ。）
}
```

#課題のステータスで絞り込む
最後は課題のステータス（未対応、処理中、処理済み、完了）で絞り込みます。
ステータスは `statusId` で絞り込みます。
statusIdはAPIを使って調べられるのですが、多分以下で固定だと思います。
 * 未対応: 1
 * 処理中: 2
 * 処理済み: 3
 * 完了: 4
これは問い合わせるURLのパラメータに `statusId[]=x` を追加します。

```js
function myFunction() {
  const baseUrl = 'https://xxxxx.backlog.jp';
  const apiKey = 'xxxxxxxxxxxxxxxxxxxxxxx';
  const endpoint = '/api/v2/issues';

  const url = baseUrl + endpoint + '?apiKey=' + apiKey + '&assigneeId[]=' + xxxxxxx + '&issueTypeId[]=' + xxxxxxx + '&statusId[]=' + 1;  // 問い合わせるURLにする
  
// （以下略。上と同じ。）
}
```

#Slackに送信する
BacklogのAPIで必要な情報を取得できるようになったので、Slackに送信できるようにします。

#### SlackのIncoming Webhooksの設定
まずSlack送信に必要なWebhooksの設定を行います。
以前はSlackのカスタムインテグレーションでIncoming Webhookを作っていましたが、今はSlackアプリを作ってその中の機能としてIncoming Webhookを設定するらしいのでその方法で行います。
（参考：[Slack カスタムインテグレーションが非推奨へ](https://qiita.com/Akira07/items/890fbbc6c384fcf875d0) や [slackのIncoming webhookが新しくなっていたのでまとめてみた](https://qiita.com/kshibata101/items/0e13c420080a993c5d16)）
具体的には
1. https://api.slack.com/apps にアクセス
2. **Create New App** ボタンをクリック
3. 作成するSlackアプリの情報を入力して **Create App** ボタンをクリック
　- **App Name** : 作成するSlackアプリ名
　- **Development Slack Workspace** : 作成するSlackアプリの設置先スペース（プルダウンメニューから選択）
4. 左のメニューから **Incoming Webhooks** をクリック
5. Activate Incoming Webhooks をONにする
6. **Add New Webhook to Workspace** ボタンをクリック
7. Slackを送信したいチャンネルかDMを選択し、**許可する** ボタンをクリック
8. 該当のSlackチャンネルかDMに以下のメッセージが表示される
> added an integration to this channel: *設定したアプリ名*

Incoming Webhooksの設定が終わったらWebhookのURLをコピーしておきます。
このURLをGASで設定してSlack送信先にします。
なお、Incoming WebhooksでSlack通知される時のSlackアプリの表示名や画像は、左のメニューの **Basic Information** の **Display Information** で設定可能です。
Slackアプリを削除する時は**Basic Information** の **Delete App** から。

####GASコード
取得したWebhook URLを **postUrl** に貼り、以下のように書きます。

```js
function sendSlack() {
  const postUrl = '取得したWebhook URL';
  
  const json = {
    'text': 'Slackに送りたいメッセージ'
  };
  const payload = JSON.stringify(json);
  
  const options = {
    'method': 'post',
    'contentType': 'application/json',
    'payload': payload
  };
  
  UrlFetchApp.fetch(postUrl, options);
}
```
お約束なので説明は省略します。

#スプレッドシートから設定変更
スプレッドシートのGASを使っていて、せっかくなのでスプレッドシート自体も使ってみました。
スプレッドシートで以下のように設定できるようにしました。
<img width="249" alt="スクリーンショット 2020-04-26 22.00.14.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/361666/eedea8f8-e432-b4e8-c5d9-d12b8031569d.png">
スプレッドシート上でチェックを入れたもので絞り込むことにします。

```js
// スプレッドシートから設定を取得
function getConfig() {
  const sheet = SpreadsheetApp.getActiveSheet();
  const range = sheet.getRange(1, 1, 5, 2);
  const values = range.getValues();
  
  // ステータス設定を取得
  let statusId = '';
  for (let i in [...Array(4).keys()]) {
    let row = Number(i) + 1;
    if (true == values[row][0]) {
      statusId += '&statusId[]=' + row;
    }
  }
  return statusId;
}
```

#完成
全部合わせるとこんな感じです。

```js
const baseUrl = 'https://xxxxx.backlog.jp';
const apiKey = 'xxxxxxxxxxxxxxxxxxxxxxx';

function myFunction() {
  const statusId = getConfig();
  issues(statusId)
}

// 課題一覧の取得
function issues(statusId) {
  // Backlogから課題一覧を取得
  const endpoint = '/api/v2/issues';
  const url = baseUrl + endpoint + '?apiKey=' + apiKey + statusId + '&issueTypeId[]=' + xxxxxx + '&assigneeId[]=' + xxxxxx;
  const resp = UrlFetchApp.fetch(url);
  const json = JSON.parse(resp.getContentText());
  
  // Backlogから取得した課題一覧から必要な情報（課題ID、課題の件名、課題のURL）を取り出す
  let message = '';
  for (let i in json) {
    const issueKey = json[i]['issueKey'];  // 課題のID
    const summary = json[i]['summary'];  // 課題の件名
    message += issueKey + ' ' + summary + '\n' + baseUrl + '/view/' + issueKey;
    // 課題が複数の場合、ループ毎に改行を入れる
    if (json[i] != json[json.length - 1]) {
      message += '\n';
    }
  }
  // Slackで送信したい内容（message）を渡す
  sendSlack(message);
}

// Slackに送信
function sendSlack(message) {
  // テスト用Webhook URL
  const postUrl = '取得したWebhook URL';
  const json = {
    'text': message
  };
  const payload = JSON.stringify(json);
  const options = {
    'method': 'post',
    'contentType': 'application/json',
    'payload': payload
  };
  UrlFetchApp.fetch(postUrl, options);
}

// スプレッドシートから設定を取得
function getConfig() {
  const sheet = SpreadsheetApp.getActiveSheet();
  const range = sheet.getRange(1, 1, 5, 2);
  const values = range.getValues();
  
  // ステータス設定を取得
  let statusId = '';
  for (let i in [...Array(4).keys()]) {
    let row = Number(i) + 1;
    if (true == values[row][0]) {
      statusId += '&statusId[]=' + row;
    }
  }
  return statusId;
}
```
これをトリガーで毎朝9時起動とかにしておくと、毎朝Slackに以下のような通知が来るようになります。
![スクリーンショット 2020-04-26 22.28.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/361666/79d2cd11-a731-fb62-ca28-d15b70b14c2f.png)

#We're hiring!
AIチャットボットを開発しています。
ご興味ある方は [Wantedlyページ](https://www.wantedly.com/projects/266837) からお気軽にご連絡ください！

#参考記事
[課題一覧の取得（公式）](https://developer.nulab.com/ja/docs/backlog/api/2/get-issue-list/#%E8%AA%B2%E9%A1%8C%E4%B8%80%E8%A6%A7%E3%81%AE%E5%8F%96%E5%BE%97)
[課題情報の取得(公式)](https://developer.nulab.com/ja/docs/backlog/api/2/get-issue/#%E8%AA%B2%E9%A1%8C%E6%83%85%E5%A0%B1%E3%81%AE%E5%8F%96%E5%BE%97)
[Slack カスタムインテグレーションが非推奨へ](https://qiita.com/Akira07/items/890fbbc6c384fcf875d0)
[slackのIncoming webhookが新しくなっていたのでまとめてみた](https://qiita.com/kshibata101/items/0e13c420080a993c5d16)
[BacklogでAPIを使ってユーザーのID(数値)を調べる方法](https://hacknote.jp/archives/55613/)
[Pythonを使ってBacklog APIで課題を追加する](https://hacknote.jp/archives/33043/)