はじめに
僕に似て夏休みの宿題をサボる傾向にあるようなので、この形質を遺伝させるのはまずいと思い、バーンダウンチャートもどきを作りました。
アーキテクチャ
asanaのタスクをGASでsheetに書いてdataportalでグラフ化する流れです。
asanaのPortfolioという機能でサクッとバーンダウンチャートを作成できるっぽいんですが、有料なので自分で書くのです。
やったこと
大変ポイント(ストーリーポイント)をつける
最初に子供と話し合ってこんな感じでポイントをつけました。
進捗を定量化することが目的なので割とテキトウに付けています。
Asanaにタスクを作る
決めた単位だけブワーッと作ります。うちの場合は一旦スプレッドシートでcsvを作ってasanaにimportしました。
中身のdescriptionはこんな感じにします。
point: 1
は先ほど決めたポイントです。これは後でGASでparseしてゴニョゴニョします。
GASを書く
これをスケジュール実行(1hくらい?)で仕掛けておきます。
コード内容
// main.gas
const token = "asana token"
const groupId = "asana project group id"
const spreadSheetId = "集計用のスプレッドシートのid"
const start = new Date("2021-07-21") // 夏休み初日
const end = new Date("2021-08-31") // 夏休み最終日
const allPoint = 295 // 大変ポイントの総計
function exec() {
const asana = new Asana(token)
const spreadSheet = new SpreadSheet(spreadSheetId, "list")
const list = asana.getTasks(groupId)
const headers = Object.keys(list[0])
spreadSheet.apply(headers, list)
const list2 = _summarize(list, start, end, allPoint)
const spreadSheet2 = new SpreadSheet(spreadSheetId, "summary")
spreadSheet2.apply(Object.keys(list2[0]), list2)
}
function _summarize(list, start, end, allPoint) {
const range = __dateRange(start, end)
const expected = allPoint / range.length
const dates = range.map(date => {
const dateStr = date.getFullYear() + "/" + (date.getMonth() + 1) + "/" + date.getDate()
return {
dateStr: dateStr,
point: 0,
expected: expected,
}
})
return list.reduce((ac, val) => {
if (!val.completed) {
return ac
}
const date = new Date(val.completed_at)
const dateStr = date.getFullYear() + "/" + (date.getMonth() + 1) + "/" + date.getDate()
const di = ac.findIndex(v => v.dateStr === dateStr)
if (di < 0) {
ac.push({
dateStr: dateStr,
point: val.point,
expected: expected,
})
} else {
ac[di].point += val.point
}
return ac
}, dates)
}
function __dateRange(startDate, endDate, steps = 1) {
const dateArray = []
const currentDate = startDate
while (currentDate <= endDate) {
dateArray.push(new Date(currentDate.getTime()))
currentDate.setUTCDate(currentDate.getUTCDate() + steps)
}
return dateArray
}
//asana.gas
class Asana {
constructor(token) {
this.token = token
}
getTasks(groupId) {
const options = {
'method': 'get',
'contentType': 'application/json',
'headers': {
'Authorization': 'Bearer ' + this.token,
},
}
const params = [
"name", "modified_at", "completed", "completed_at", "html_notes"
]
const url = Utilities.formatString(
`https://app.asana.com/api/1.0/projects/%s/tasks?opt_fields=%s`, groupId, params.join(","))
const res = UrlFetchApp.fetch(url, options)
if (res.getResponseCode() !== 200) {
const errStr = Utilities.formatString("invalid status code!: %d, %s", res.getResponseCode, res.getContentText)
throw new Error(errStr)
}
const data = JSON.parse(res).data
const list = data.map(val => {
const html = val.html_notes
const point = parseInt(Parser.data(html).from("point:</strong> ").to("</body>").build())
const completed = val.completed === "TRUE" ? true : false
return {...val, point: point}
})
return list
}
}
//spreadsheet.gas
class SpreadSheet {
constructor(id, sheetName) {
this.spreadSheet = SpreadsheetApp.openById(id)
this.sheetNameData = sheetName
}
apply(headers, list) {
const sheet = this.spreadSheet.getSheetByName(this.sheetNameData)
const range = sheet.getRange(1, 1, 1, headers.length)
range.setValues([headers])
const list2 = list.map(obj => {
return Object.values(obj)
})
sheet.getRange(2, 1, list2.length, headers.length).setValues(list2)
}
}
Dataportalでグラフを作る
下記のような感じで集計されたカラムのうちdateStr
をディメンジョンにして、
point
,point
,expected
,expected
と2つずつカラムをフィールドに加えます。
さらに同じ種類のカラムのうち一方だけをスタイル
で累計
にチェックをするとバーンダウンチャートぽくなります。
このグラフだと目標累計
の線にポイント累計
が追いつくように進捗していけばいいことがわかります。
可視化できてよかったですね!
最後に
はっきり言って僕がAsana apiを触りたかったので作ったんですが、うちでは「これだけ頑張ればこのくらいさぼれるよ」と教えるために使っています。決して子供へプレッシャーを与える目的で使ってはいけないなあと思いました。
以上