チームのこれまでのConfluenceへのアウトプット数を確認してみた話

こんにちは、インフラの天津です。

今日は当社で使用しているドキュメントツールのConfluence Cloudへのアウトプット数を確認して可視化してみた話をしようと思います。

なぜやったか

エンジニアとしてアウトプットが大事なのは言うまでもないことです。

自分が所属しているチーム(前回記事参照)でもアウトプットを推奨していますし、がんがんドキュメントを起こして更新しています。

ただ、実態として「どれくらいの量なのか」という疑問は残っていました。

そこで実態を計測して可視化し、あわよくばチームの指標の1つとして活用したいと思いやってみました。

先に結論

先に結論なのですが、いい感じに可視化ができました!

そして自分たちのチームがどれくらいのアウトプットをしているのかが把握できました。

f:id:AdwaysEngineerBlog:20200207135926p:plain

今回のゴール

始めたときには下記を考えていました。

  • Confluenceのアウトプット数を取得する。
  • いい感じに可視化する。
  • チームの状況を知る。

何をどうしたか

では、何をどうしたかを説明していきます。

1. そもそもConfluenceに機能があるか?

Confluenceにそもそも機能があれば使えばよいだけなので確認しましたが、Premium版にしか無いようです。(弊社はstandard版を使用)

Plans and pricing - Confluence

なので自作の道へ進みます。

2. 作ってみる

まずは大まかな設計です。

f:id:AdwaysEngineerBlog:20200207125059p:plain

データ取得はConfluence Cloud REST APIを使用します。

この中のcontents APIでスペース毎のコンテンツの作成者が取得できることがわかったので取得してみました。

Get contents API

今回はbashスクリプトで実装します。ページネーションの実装も必要ですね。

#!/bin/bash

set -eu

function get_data_with_pagenation() {
  user=$1
  token=$2
  url=$3

  limit=100
  start_at=0

  # 初回apiアクセス
  api_response=$(curl -s -X GET -u "${user}:${token}" "${url}start=${start_at}&limit=${limit}")

  start_at=$(echo "${api_response}" | jq -r '.start')
  size=$(echo "${api_response}" | jq -r '.size')

  echo "${api_response}" | jq '.results[]'

  # page分繰り返す
  while [[ ${size} == "${limit}" ]]
  do
    start_at=$((${start_at} + ${limit}))
    api_response=$( curl -s -X GET -u "${user}:${token}" "${url}start=${start_at}&limit=${limit}" | jq .)
    start_at=$(echo "${api_response}" | jq -r '.start')
    size=$(echo "${api_response}" | jq -r '.size')

    echo "${api_response}" | jq '.results[]'
  done

}

function get_contents_by_space() {
  space=$1
  get_data_with_pagenation "${ACCOUNT}" "${API_TOKEN}" "https://${CONFLU_HOST}/wiki/rest/api/content?spaceKey=${space}&"
}

# main

get_contents_by_space SPACE
$ ACCOUNT=xxx API_TOKEN=xxxx ./get_contents.sh >contents.json

このような感じです。

実行したところ結果のjsonがいい感じに取得できましたが、作成のみではアウトプットとは言えないのでは?という疑問がでてきました。

3. アウトプットとは何かを考えた

アウトプットとは?と考えましたが、今回はシンプルに早めにゴールを達成したかったので下記2つをアウトプットと捉えることにしました。

  • 作成
  • 更新

4. 更新データの取得

Confluenceにはバージョン管理機能があり、ページを更新(publish)した際に版数と更新ユーザが記録されるのでそちらも取得することにします。

更新データ取得のためConfluence Cloud APIを再確認したところversion APIでコンテンツ毎の更新者が取得できることがわかったので取得してみます。

スクリプトはこんな感じになりました。

#!/bin/bash

set -eu

function get_data_with_pagenation() {
  user=$1
  token=$2
  url=$3

  limit=100
  start_at=0

  # 初回apiアクセス
  api_response=$(curl -s -X GET -u "${user}:${token}" "${url}start=${start_at}&limit=${limit}")

  start_at=$(echo "${api_response}" | jq -r '.start')
  size=$(echo "${api_response}" | jq -r '.size')

  echo "${api_response}" | jq '.results[]'

  # page分繰り返す
  while [[ ${size} == "${limit}" ]]
  do
    start_at=$((${start_at} + ${limit}))
    api_response=$( curl -s -X GET -u "${user}:${token}" "${url}start=${start_at}&limit=${limit}" | jq .)
    start_at=$(echo "${api_response}" | jq -r '.start')
    size=$(echo "${api_response}" | jq -r '.size')

    echo "${api_response}" | jq '.results[]'
  done

}

function get_contents_by_space() {
  space=$1
  get_data_with_pagenation "${ACCOUNT}" "${API_TOKEN}" "https://${CONFLU_HOST}/wiki/rest/api/content?spaceKey=${space}&"
}

function get_contents_version() {
  contents="$1"
  echo "$contents" | jq -r '.id' | while read id
  do
    get_data_with_pagenation "${ACCOUNT}" "${API_TOKEN}" "https://adways.atlassian.net/wiki/rest/api/content/${id}/version?"
  done
}

# main

DATA=$(get_contents_by_space SPACE | tee contents.json)
get_contents_version "${DATA}"
$ ACCOUNT=xxx API_TOKEN=xxxx get_versions.sh >versions.json

実行したところページごとの全バージョンの更新者が取得できました。

データが一旦揃ったので可視化してみます。

5. 可視化

データファイルの生成

まず、jsonデータを切り出して、tsvファイルを作成しspreadsheetにアップロードします

$ jq -r '[.when, .by.email, .number] | @tsv' versions.json | sort | head -3
2016-09-15T07:56:45.154Z        amatsu.satoshi@adways.net       1
2016-09-15T07:58:09.687Z        amatsu.satoshi@adways.net       1
2016-09-15T08:00:11.110Z        amatsu.satoshi@adways.net       2

うまく取れていそうなのでファイルに吐き出しgoogle driveにアップロードしましょう。

$ printf "DATE\tUSER\tVERSION\n" > data.csv
$ jq -r '[.when, .by.email, .number] | @tsv' versions.json | sort > data.tsv

ちなみにversion APIのresponseデータは下記のようなフォーマットです。

versions APIのresponse jsonデータ(長いので折りたたんでいます)

{
  "results": [
    {
      "by": {
        "type": "known",
        "username": "<string>",
        "userKey": "<string>",
        "accountId": "<string>",
        "accountType": "atlassian",
        "email": "<string>",
        "publicName": "<string>",
        "profilePicture": {
          "path": "<string>",
          "width": 2154,
          "height": 2154,
          "isDefault": true
        },
        "displayName": "<string>",
        "operations": [
          {
            "operation": "administer",
            "targetType": "page"
          }
        ],
        "details": {},
        "personalSpace": {
          "id": 2154,
          "key": "<string>",
          "name": "<string>",
          "type": "<string>",
          "status": "<string>",
          "_expandable": {},
          "_links": {}
        },
        "_expandable": {
          "operations": "<string>",
          "details": "<string>",
          "personalSpace": "<string>"
        },
        "_links": {}
      },
      "when": "<string>",
      "friendlyWhen": "<string>",
      "message": "<string>",
      "number": 2154,
      "minorEdit": true,
      "content": {
        "id": "<string>",
        "type": "<string>",
        "status": "<string>",
        "title": "<string>",
        "space": {
          "id": 2154,
          "key": "<string>",
          "name": "<string>",
          "type": "<string>",
          "status": "<string>",
          "_expandable": {},
          "_links": {}
        },
        "history": {
          "latest": true,
          "createdBy": {
            "type": "known",
            "accountId": "<string>",
            "accountType": "atlassian",
            "email": "<string>",
            "publicName": "<string>",
            "profilePicture": {
              "path": "<string>",
              "width": 2154,
              "height": 2154,
              "isDefault": true
            },
            "displayName": "<string>",
            "_expandable": {},
            "_links": {}
          },
          "createdDate": "<string>"
        },
        "ancestors": [],
        "operations": [
          {
            "operation": "administer",
            "targetType": "page"
          }
        ],
        "children": {
          "_expandable": {},
          "_links": {}
        },
        "childTypes": {
          "_expandable": {}
        },
        "descendants": {
          "_expandable": {},
          "_links": {}
        },
        "container": {},
        "body": {
          "view": {
            "value": "<string>",
            "representation": "view",
            "_expandable": {}
          },
          "export_view": {
            "value": "<string>",
            "representation": "view",
            "_expandable": {}
          },
          "styled_view": {
            "value": "<string>",
            "representation": "view",
            "_expandable": {}
          },
          "storage": {
            "value": "<string>",
            "representation": "view",
            "_expandable": {}
          },
          "editor2": {
            "value": "<string>",
            "representation": "view",
            "_expandable": {}
          },
          "anonymous_export_view": {
            "value": "<string>",
            "representation": "view",
            "_expandable": {}
          },
          "_expandable": {
            "editor": "<string>",
            "view": "<string>",
            "export_view": "<string>",
            "styled_view": "<string>",
            "storage": "<string>",
            "editor2": "<string>",
            "anonymous_export_view": "<string>"
          }
        },
        "restrictions": {
          "read": {
            "operation": "administer",
            "_expandable": {},
            "_links": {}
          },
          "update": {
            "operation": "administer",
            "_expandable": {},
            "_links": {}
          },
          "_links": {}
        },
        "_expandable": {
          "childTypes": "<string>",
          "container": "<string>",
          "metadata": "<string>",
          "operations": "<string>",
          "children": "<string>",
          "restrictions": "<string>",
          "history": "<string>",
          "ancestors": "<string>",
          "body": "<string>",
          "version": "<string>",
          "descendants": "<string>",
          "space": "<string>"
        },
        "_links": {}
      },
      "collaborators": {
        "users": [
          {
            "type": "known",
            "accountId": "<string>",
            "accountType": "atlassian",
            "email": "<string>",
            "publicName": "<string>",
            "profilePicture": {
              "path": "<string>",
              "width": 2154,
              "height": 2154,
              "isDefault": true
            },
            "displayName": "<string>",
            "_expandable": {},
            "_links": {}
          }
        ],
        "userKeys": [
          "<string>"
        ],
        "_links": {}
      },
      "_expandable": {
        "content": "<string>",
        "collaborators": "<string>"
      },
      "_links": {}
    }
  ],
  "start": 2154,
  "limit": 2154,
  "size": 2154,
  "_links": {}
}

データポータルにてアップロードしたspreadsheetをデータソースにして可視化してみます。

(このあたりは説明を省略しますが、Gsuiteをお使いであれば情報も多いので簡単にできると思います。)

可視化にあたっては時系列での状況と、個人別の状況、チームの状況が一覧できるようにしてみました。

出来上がったのはこのようなダッシュボードです。

f:id:AdwaysEngineerBlog:20200207125117g:plain

いい感じになりました。A君の更新数が半端ないです。さすがリーダーです。(自分は更新数では2位でした。)

個人の更新回数や、ページ作成数も時系列で確認できます。

チームでの各数値もサクッと作れました。

可視化でわかったこと

今回可視化してみてチームの2019-04-01〜2020-01-31までの状況がわかりました。

  • 作成数: 平均 25件/週、合計1,000件弱
  • 更新数: 平均100件/週、合計4,000件弱

もちろん作成や更新の数が全てとは言えないのですが、現在のチーム状況と照らし合わせて1つの指標として活用できそうです。

(たとえば更新が25件/週以上かどうかウォッチするなど。)

また、チームメンバーがいずれも多くのアウトプットを残していることを改めて実感できました。

他にわかったこと

ConfluenceのAPIはページネーションもあり、今の作りでは少し時間がかかるので高速化も考えないと実用に耐えない状態です。

今後やりたいこと

1. 質の測定

これはやってみたいですが、ドキュメントの質の測定方法は数字で表すには様々な意見がありそうでして、ハードルが高いなと感じています。

なにかいい方法をご存じの方がいらっしゃいましたらぜひ教えていただきたいです。

2. Confluence以外のアウトプットの計測

実際のエンジニアの仕事としてはコードも重要ですよね。なので、リポジトリのコミット数、コメント数なども含める必要があると思っています。

3. 自動での実行と蓄積

現在は手動での実行になっているので定期的に実行し蓄積していきたいです。

具体的には何らかのジョブスケジューラを使用し、BigQueryあたりに蓄積できればいいかなと思っています。

4. 全スペースのデータ収集

今回はチームのメインのスペースのデータのみ収集しましたが、他の部署の状況なども知ることで自分たちの状況をより把握できる気がしています。

先に書いた高速化と合わせて行えればなと思います。

5. データの拡張

今回時間がなく手がつけられませんでしたが、下記のようなコンテンツのメタデータやコメントなどを含めることで更に意味のあるデータになるかな?と感じています。

  • ページの容量
  • 更新時の容量
  • コメント数
  • Like数

今回は以上になります。

Confluenceをお使いの方はぜひ参考にしていただければ幸いです。

また、今後のブログでこの後についてもご紹介できればと思います。

最後までお読みいただきありがとうございました。