8
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Last updated at Posted at 2019-12-17

この記事は、ただの集団 Advent Calendar 2018の17日目の記事です。
昨日は、takatorixさん(タカトリックス :bird: )の社内の検索技術勉強会ふりかえりでした。

きっかけ

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

はじめに

  • この記事に含まれる事

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

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

前提

チーム構成

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

開発の進め方

  • スクラム(※ほぼ全員がCSD研修受験済み)

用意するもの

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

チーム目標(一部)

  1. デプロイ頻度
    • 1チームあたり、何回リリースしているか
    • 集計方法: 直近の4スプリントの平均
    • ただし、チーム数が変動しても割合に影響ないような集計方法
    • 集計対象
      • mergeされている、masterへのPullRequest
  2. 変更失敗率
    • 「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
    • スクリーンショット 2019-12-16 21.08.16.png
  • ダッシュボード用のspread sheetのシートを用意

    • シート名は Dashboard

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

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のシートを用意
    • シート名は 集計
    • スクリーンショット 2019-12-17 9.13.21.png
    • A列: 日付
      • ドラッグ・アンド・ドロップ :v:

デプロイ頻度

  • B列: デプロイ数

    • 2019/9/1(A10: A列は日付)の分を集計する例
    • countIf.png
  • C列: チーム数

    • 固定
  • D列: デプロイ頻度

    • d1.png

変更失敗率

  • F列: ラベルが付いているPRの数

    • 2019/9/1(A10: A列は日付)の分を集計する例
    • bug.png
  • G列: 変更失敗率

    • 単純な、bugラベル付きPR数 / リリース数

3. ダッシュボード作成

事前準備

  • ログ出力用のspread sheetのシートを用意
    • シート名は Dashboad
  • グラフと、その日の速報値を置けばOK :ok_woman:

4. 自動化

データ抽出用の関数を自動で実行するように設定します。
GASを利用しているので、簡単にtriggerが設定できます :tada:
スクリーンショット 2019-12-15 18.18.59.png

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

まとめ

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

8
2
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
8
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?