Redmineのチケット一覧（JSON）をGoogleスプレッドシートと連携したい

Redmineを見てくれない。
でもGoogleスプレッドシート見てくれる（かも。。。）
そんなときは、Redmineのチケット一覧をJSON形式で取得してきたものをGoogleスプレッドシートに連携させてしまえば万事解決なのでは？
ということです。
Google先生はベストプラクティスを返してくれないので、だったら作るしかないってことで作ってみました。

---

# 前提
- プロジェクト管理ツールは[Redmine](http://redmine.jp/)を使っている
- Googleスプレッドシートを利用している（エクセルベースの管理シートは利用できる）

# 課題
プロジェクト管理ツールは[Redmine](http://redmine.jp/)を使っているが、ステークホルダーによってはRedmineを見てくれないので、プロジェクトの進行状況やタスクの共有ができないという問題が発生している。
また、Redmineの使い方を覚えるつもりはそんなにないということも、この問題に拍車をかけている。
では、どうするか？

# 解決策
Redmineのチケット一覧をJSON形式で取得し、それをGoogleスプレッドシートに連携することで、この問題に対処したい。

# 目指すべき形
## 1.「JSONデータを取得する」を押下する
![01.png](https://qiita-image-store.s3.amazonaws.com/0/70138/43b63f97-33da-8eef-97f1-bc938a1f421e.png)
## 2.データが抽出される
![02.png](https://qiita-image-store.s3.amazonaws.com/0/70138/a061d7fb-38b5-ccb9-75a1-7bab0fa15784.png)

以上！

# GoogleAppsScript（GAS）のコード
```javascript
// スプレッドシートを開いた時に、メニューを追加する
function onOpen(){
  var ui = SpreadsheetApp.getUi();
  var menu = ui.createMenu('JSONデータ取得');
  menu.addItem('JSONデータを取得する', 'getJSON');
  menu.addToUi();
}

function getJSON(){
  //sheetを設定する
  var spreadsheet = SpreadsheetApp.getActiveSpreadsheet(); //1. 現在のスプレッドシートを取得
  var sheet = spreadsheet.getActiveSheet(); //2. 現在のシートを取得

  // JSONのURL
  var json_url = 'https://redmine.hogehoge.jp/issues.json';
  var api_key = 'xxxxxxxxxxxxxxxxxxxxxxxxxx';
  var limit = 100;
  // Basic認証
  var user = 'user';
  var pass = 'pass';
  var options = {
    "headers" : {"Authorization" : " Basic " + Utilities.base64Encode(user + ":" + pass)}
  };

  // JSONのtotal_countを取得する
  var total = json_url + '?key=' + api_key;
  var totalCount = UrlFetchApp.fetch(total,options).getContentText();
  var jsonDataTotalCount = JSON.parse(totalCount);
  var total_count = jsonDataTotalCount.total_count;
  if(total_count < 100){
    var pageCount = 1;
  }else{
    var pageCount = Math.ceil(total_count / limit);
  }

  var i = 2; // どの行から開始するのか？
  for(var page = 1; page <= pageCount; page++){
    var json_tmp_url = json_url + '?key=' + api_key + '&limit=' + limit + '&page=' + page;
    // JSONの設定
    var json = UrlFetchApp.fetch(json_tmp_url,options).getContentText();
    var jsonData = JSON.parse(json);
    var issues = jsonData.issues;

    // JSONを書き込む
    for (var idx in issues) {
      sheet.getRange(i, 1).setValue(issues[idx]['id']);
      sheet.getRange(i, 2).setValue(issues[idx]['project']['name']);
      if(issues[idx]['parent'] != undefined){
        sheet.getRange(i, 3).setValue(issues[idx]['parent']['id']);
      }else{
        sheet.getRange(i, 3).setValue('');
      }
      sheet.getRange(i, 4).setValue(issues[idx]['tracker']['name']);
      sheet.getRange(i, 5).setValue(issues[idx]['status']['name']);
      sheet.getRange(i, 6).setValue(issues[idx]['priority']['name']);
      sheet.getRange(i, 7).setValue(issues[idx]['subject']);
      if(issues[idx]['assigned_to'] != undefined){
        sheet.getRange(i, 8).setValue(issues[idx]['assigned_to']['name']);
      }else{
        sheet.getRange(i, 8).setValue('');
      }
      if(issues[idx]['start_date'] != undefined){
        sheet.getRange(i, 9).setValue(issues[idx]['start_date']);
      }else{
        sheet.getRange(i, 9).setValue('');
      }
      if(issues[idx]['due_date'] != undefined){
        sheet.getRange(i, 10).setValue(issues[idx]['due_date']);
      }else{
        sheet.getRange(i, 10).setValue('');
      }
      if(issues[idx]['estimated_hours'] != undefined){
        sheet.getRange(i, 11).setValue(issues[idx]['estimated_hours']);
      }else{
        sheet.getRange(i, 11).setValue('');
      }
      sheet.getRange(i, 12).setValue(issues[idx]['done_ratio']);
      i++;
    }
  }

}
```
## コードの説明
### JSONのURLとAPIキーを設定する
```javascript
  // JSONのURL
  var json_url = 'https://redmine.hogehoge.jp/issues.json';
  var api_key = 'xxxxxxxxxxxxxxxxxxxxxxxxxx';
  var limit = 100;
```
- json_url : jsonのURLを設定
- api_key : Redmineの個人設定から調べたAPIキーを設定する
- limit : Redmineのチケット表示上限は100なので、limitは100を設定する

![03.png](https://qiita-image-store.s3.amazonaws.com/0/70138/3ba4140c-4474-6311-3e41-defada31c76b.png)

※Redmineの[REST API](http://www.redmine.org/projects/redmine/wiki/Rest_Issues)については、本家のページを参照すると良い

### ベーシック認証のIDとPWを設定する
```javascript
  // Basic認証
  var user = 'user';
  var pass = 'pass';
  var options = {
    "headers" : {"Authorization" : " Basic " + Utilities.base64Encode(user + ":" + pass)}
  };
```
Redmineにベーシック認証がこれでいける。
ダイジェスト認証がかかっている場合は、どうやるかは不明。。。

### JSONデータのページネーションの対策をする
```javascript
  // JSONのtotal_countを取得する
  var total = json_url + '?key=' + api_key;
  var totalCount = UrlFetchApp.fetch(total,options).getContentText();
  var jsonDataTotalCount = JSON.parse(totalCount);
  var total_count = jsonDataTotalCount.total_count;
  if(total_count < 100){
    var pageCount = 1;
  }else{
    var pageCount = Math.ceil(total_count / limit);
  }
```
Redmineには、JSONデータを最大100個ずつしか読み込めないので、ページネーションを利用してデータを取得する必要があるので、その対応をしている。

あとは、データをスプレッドシートに書き込むだけ。

# 最後に
きっと、同じような問題にぶち当たって悩んでいる方もいることだろう。
そんなときは、こちらを試してもらいたい。