エンジニアのチーム目標をGithub APIとGASとSpreadsheetで運用する


この記事は、[ただの集団 Advent Calendar 2018](https://adventar.org/calendars/4054)の17日目の記事です。
昨日は、takatorixさん(タカトリックス :bird: )の[社内の検索技術勉強会ふりかえり](https://takatorix.hatenablog.com/entry/2019/12/16/092321)でした。

## きっかけ
我々のエンジニアチームでは、チーム全体の定量的な目標を決めています。
そこで、日次で数値の確認が必須だということになり、目標進捗可視化プロジェクトをしました。:blush:
エンジニア個人の評価が難しいという話はよく聞きますが、エンジニアチームとしての評価は数値化出来て、追って(改善して)ゆくのはとても大事なことだと思ったので、今回の題材に選びました :relaxed: 

## はじめに
- この記事に含まれる事
    - エンジニアチームの定量的な目標を運用していく方法

- この記事に含まれない事
    - エンジニアチームの目標の決め方
    - 運用した結果(最近始めたため。効果測定は今後の記事で書くかもしれません。)

## 前提
### チーム構成
- エンジニアチーム: 4チーム(内、1チームはインフラ担当)
    - 1チーム: 4-7名

### 開発の進め方
- スクラム(※ほぼ全員がCSD研修受験済み)

### 用意するもの
- github
    - 我々のサービスはマイクロサービスのため、リポジトリは大量です。
- Google Spreadsheet
- Google App Script(GAS)

## チーム目標(一部)
1. デプロイ頻度
    - 1チームあたり、何回リリースしているか
    - 集計方法: 直近の4スプリントの平均
    - ただし、チーム数が変動しても割合に影響ないような集計方法
    - 集計対象
        - mergeされている、masterへのPullRequest
1. 変更失敗率
    - 「Bugfix リリース / 全リリース」の割合
    - いわゆる、Hotfixの割合
    - 集計方法: 直近の4スプリントの平均
    - 集計対象
        - mergeされていてbugラベルが付与されている、masterへのPullRequest

# 実際にやった事
大きく分けると
1. データ抽出
2. 集計
3. ダッシュボード作成
4. 自動化

の4つです。詳しく説明していきます。

## 1. データ抽出
こちらはGASから、githubのREST APIを叩く形で実現しました。
#### 事前準備
- MomentをGASで使えるように設定
    - 日付の計算には、jsのライブラリのMomentを利用しています。(GASでMomentを使うやり方は各所で紹介されているので今回は省略します。:Bow:)
- ログ出力用のspread sheetのシートを用意
    - シート名は `rowData`
    - <img width="860" alt="スクリーンショット 2019-12-16 21.08.16.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/289114/9874d7bf-bdcd-0d3b-cdaa-40df98eb0753.png">


- ダッシュボード用のspread sheetのシートを用意
    - シート名は `Dashboard`

こちらほぼ実際のGASのコード。
必要な部分は、NOTEで説明を入れています。

```javascript
function extractRowData() {
  const prList = []

  var githubAccessToken = PropertiesService.getScriptProperties().getProperty('GITHUB_ACCESS_TOKEN'); //NOTE: GASの環境変数に設定しておく
  
  const options = {"headers" : {"Authorization": "token " + githubAccessToken }} 
  const repoNames = Array(
    "リポジトリ1",
    "リポジトリ2",
    "リポジトリ3"
  )
  var lineNumber = 2 // NOTE: 生データとしてspreadSheetに吐きだす為の行番号。1行目はヘッダーなので、2行目から始める。

  for(var repo = 0; repo < repoNames.length; repo++) {
    var repoName = repoNames[repo]
    var prResponse = UrlFetchApp.fetch('https://api.github.com/repos/オーガニゼーション名/' + repoName + '/pulls?state=closed&base=master', options) // NOTE: closeされている、かつmasterに対するPullRequestを取得
    var prJsonData = JSON.parse(prResponse.getContentText())
    var prs = prJsonData.map(function(d){
      return {
        prNum:d.number,
        prMergedAt: Moment.moment(d.merged_at),
        state: d.state,
        labels: d.labels
      }
    }) // NOTE: 集計に必要な情報だけ抽出する。
        
    for (var i = 0; i < prs.length; i++) {
      var pullRequest = prs[i]
      var commitResponse = UrlFetchApp.fetch('https://api.github.com/repos/オーガニゼーション名/' + repoName + '/pulls/' + pullRequest.prNum + '/commits', options)
      var commitJsonData = JSON.parse(commitResponse.getContentText())  
      var author = commitJsonData.map(function(d){
        return d.commit.committer.name
      })

      const countStart = Moment.moment("2019-08-01")
      if(pullRequest.prMergedAt.year() !== 70  && pullRequest.prMergedAt.isAfter(countStart)) { // NOTE: closeされたprはmergeAtが1970年になるから除外する。もっと良い除外方法が絶対にある。
        // NOTE: ここから下は、spread sheetに書き出す部分
        // NOTE: 👇 merge日
        writeSpreadSheet('A', lineNumber, pullRequest.prMergedAt.format("YYYY-MM-DD"), "rowData")
        // NOTE: 👇 mergeした人
        writeSpreadSheet('B', lineNumber, author, "rowData")
        // NOTE: 👇 リポジトリ名
        writeSpreadSheet('C', lineNumber, repoName, "rowData")
        // NOTE: 👇 PullRequestURL(REST APIのresponseにPRのURLが含まれてないので、仕方なく文字列演算)
        writeSpreadSheet('D', lineNumber, "https://github.com/オーガニゼーション名/" + repoName + "/pull/" + pullRequest.prNum, "rowData")
        // NOTE: 👇 Option[ラベル]
        writeSpreadSheet('E', lineNumber, pullRequest.labels, "rowData")       
        lineNumber++
      }
    }
  }
  // NOTE: :point_down: ダッシュボードの作成用に集計日を出力
  writeSpreadSheet('A', 1, Moment.moment().format("YYYY-MM-DD"), "Dashboard")
}

//NOTE: スプレットシートに書き出す為の関数
function writeSpreadSheet(column, line, prInfo, sheetName) {
  var sheet = SpreadsheetApp.getActive().getSheetByName(sheetName);
  sheet.getRange(column + line).setValue(prInfo);
}
```


## 2. 集計
ここからは、エクセル芸人です。
spreadSheetのrowDataというシートには、データ抽出段階で抽出されたデータがあります。
このデータを元に、バンバン集計していきます。
#### 事前準備
- ログ出力用のspread sheetのシートを用意
    - シート名は `集計`
    - <img width="631" alt="スクリーンショット 2019-12-17 9.13.21.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/289114/df4729eb-54cb-7798-79fd-964e98f028e9.png">
    - A列： 日付
        - ドラッグ・アンド・ドロップ :v:
### デプロイ頻度
- B列: デプロイ数
    - 2019/9/1(A10: A列は日付)の分を集計する例
    - <img width="558" alt="countIf.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/289114/62894a81-54e6-8093-e6fc-7e0d48bf7bcf.png">

- C列: チーム数
    - 固定
- D列: デプロイ頻度
    - <img width="413" alt="d1.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/289114/f2002156-7ee3-c2e5-0ae3-0c8e5cdc6385.png">

### 変更失敗率
- F列: ラベルが付いているPRの数
    - 2019/9/1(A10: A列は日付)の分を集計する例
    - <img width="558" alt="bug.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/289114/3d55d9eb-20c2-3541-f95b-c057f0f98b98.png">


- G列: 変更失敗率
    - 単純な、bugラベル付きPR数 / リリース数

## 3. ダッシュボード作成
#### 事前準備
- ログ出力用のspread sheetのシートを用意
    - シート名は `Dashboad`
- グラフと、その日の速報値を置けばOK :ok_woman: 

## 4. 自動化
データ抽出用の関数を自動で実行するように設定します。
GASを利用しているので、簡単にtriggerが設定できます :tada:
<img width="500" alt="スクリーンショット 2019-12-15 18.18.59.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/289114/dea53238-c92e-5f98-cc82-45307dc15398.png">

今回は、1日1回実行するように設定しました。

## まとめ
指標を置くのはとても大事。
効果検証して、改善していくのもっと大事。
この指標をもって、改善していくのが楽しみ :relaxed: