10
4

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 5 years have passed since last update.

Togglの記録をServerless + Pixelaで草化

Last updated at Posted at 2018-11-10

Togglの記録をServerless + Pixelaで草化

  • 作業時間などの時間管理ツールとしてTogglがある
    • いつ,どの作業をしたかを記録
    • 各作業をプロジェクトやタグで分類可能
    • Toggl Reportsで可視化も提供されており,特定の作業をどれくらい継続しているか,どのくらい時間をかけているかを見れる
    • でもとりあえず草化したい!
  • ToggleはAPIを提供しているので比較的用意にデータ抽出可能

作ったもの

  • 1日1回,前日に特定プロジェクトにかけた時間をTogglから抽出し,Pixelaに記録

toggl2pixela.png

結果

  • 自分の勉強時間を草化できた🌱🌱🌱

スクリーンショット 2018-11-08 11.24.36.png

環境

  • MacOS Mojave
  • Go 1.11.1
  • Serverless Framework 1.32.0

つまづきメモ

  • しょぼい内容だが備忘録として

Lambdaにて時間を扱う場合の注意

  • CloudWatch Eventsをcron式で時間指定する場合,UTCで指定すること
    • e.g. JSTで毎日午前1時に実行したい→UTCで午後4時(-9時間)を指定する cron( 0 16 * * ? * )
  • Lambda関数で日時を取得する場合(e.g. Goでのtime.Now()),標準ではUTCで取得する
  • 日本時間を使いたい場合はLambda関数の環境変数でタイムゾーンを指定すること
    • e.g. 変数TZ, 値Asia/Tokyo

Toggl APIの使い方

  • TogglのAPIを利用したい場合,リクエストにAPIトークンを含める
  • 今回は特定期間の記録を全取得し,特定プロジェクトの記録のみ加算していき合計時間を取得
  • 特定期間の記録を取得するAPIは以下
    • GET https://www.toggl.com/api/v8/time_entries?start_date=XXX&end_date=XXX
    • 日時はISO 8601形式
  • 今回はGoのwrapperであるdougEfresh/gtogglを利用
    • READMEの記載内容だとうまく行かず
import "github.com/dougEfresh/gtoggl"
import "github.com/dougEfresh/gtoggl-api/gtproject"

func main() {
  // HTTP client作成
  thc, err := gthttp.NewClient("your-api-token")
  ...
  // Togglの記録(time entry)取得用クライアント作成
  tec := gttimeentry.NewClient(thc)
  // 特定期間の記録を取得
	entries, eerr := tec.GetRange(start_date, end_date)
}

開発詳細

Serverless framework + Goで開始

  • $GOHOME/src配下で作業
$ serverless create -t aws-go-dep -p <project-name>
  • 東京リージョンにデプロイしたいのでserverless.ymlregionを追記
serverless.yml
provider:
  name: aws
  runtime: go1.x
  region: ap-northeast-1
  • 以下でひとまずデプロイテスト可能
$ cd <project-name>
$ make
$ sls deploy

新規関数を作成

  • 関数を新規作成
    • 自動生成された関数は不要なので削除
    • toggl2pixelaフォルダを作成し,main.goを作成
    • Makefilebuild:に以下を追記
Makefile
	env GOOS=linux go build -ldflags="-s -w" -o bin/toggl2pixela toggl2pixela/main.go

serverless.ymlの修正

  • serverless.ymlの主な修正・追記点は以下
    • 新規作成した関数定義の追記 (+自動生成された関数定義の削除)
    • events下にschecule: ***を書くことで定期実行を定義 (下記では毎日午前1時に実行,上述の通りcron式の時間はUTC指定なので注意)
    • Lambda関数でJSTで日時取得したいので,環境変数TZ, 値Asia/Tokyoを指定
    • Lambda関数の環境変数(environment)にTogglのAPIキー/対象プロジェクトID,Pixelaのユーザ/トークン/グラフ情報を与える
serverless.yml
service: toggl2pixela

frameworkVersion: ">=1.28.0 <2.0.0"

provider:
  name: aws
  runtime: go1.x
  region: ap-northeast-1

package:
 exclude:
   - ./**
 include:
   - ./bin/**

functions:
  toggl2pixela:
    handler: bin/toggl2pixela
    events:
      - schedule: cron(0 16 * * ? *)
    # you need to fill the followings with your own
    environment:
      TZ: Asia/Tokyo
      TOGGL_API_TOKEN: <your-api-token>
      TOGGL_PROJECT_ID: <target-project-id> 
      PIXELA_USER: <user-id>
      PIXELA_TOKEN: <your-token>
      PIXELA_GRAPH: <your-graph-id-1>
    timeout: 10

関数本体を作成

  • 素直に実装しただけなので,特記事項なし…
    • データ元のToggl,データ投入先のPixelaの情報は環境変数(TOGGL_API_TOKEN, TOGGL_PROJECT_ID, PIXELA_USER, PIXELA_TOKEN, PIXELA_GRAPH)から取得
    • GoでのToggl操作にはdougEfresh/gtogglを利用
      • 利用方法は上述
    • GoでのPixela操作にはgainings/pixela-go-clientを利用
toggl2pixela/main.go
package main

import (
	"context"
	"errors"
	"fmt"
	"os"
	"strconv"
	"time"

	"github.com/aws/aws-lambda-go/lambda"
	"github.com/dougEfresh/gtoggl-api/gthttp"
	"github.com/dougEfresh/gtoggl-api/gttimentry"
	pixela "github.com/gainings/pixela-go-client"
)

// Handler is our lambda handler invoked by the `lambda.Start` function call
func Handler(ctx context.Context) error {
	// extract env var
	apiToken := os.Getenv("TOGGL_API_TOKEN")
	pjID, _ := strconv.ParseUint(os.Getenv("TOGGL_PROJECT_ID"), 10, 64)
	user := os.Getenv("PIXELA_USER")
	token := os.Getenv("PIXELA_TOKEN")
	graph := os.Getenv("PIXELA_GRAPH")

	// extract data from toggl
	date, quantity := getDateAndTimeFromToggl(apiToken, pjID)
	if date == "-1" || quantity == "-1" {
		return errors.New("Error in accessing toggl")
	}
	fmt.Printf("date: %s, quantity: %s\n", date, quantity)

	// record pixel
	perr := recordPixel(user, token, graph, date, quantity)
	if perr != nil {
		return errors.New("Error in accessing pixela")
	}

	return nil
}

func getDateAndTimeFromToggl(apiToken string, pjID uint64) (string, string) {
	// create toggl client
	thc, err := gthttp.NewClient(apiToken)
	if err != nil {
		fmt.Println(err)
		return "-1", "-1"
	}

	// set time range to be analyzed
	y := time.Now().AddDate(0, 0, -1)
	s := time.Date(y.Year(), y.Month(), y.Day(), 0, 0, 0, 0, time.Local)
	e := time.Date(y.Year(), y.Month(), y.Day(), 23, 59, 59, 0, time.Local)
	date := y.Format("20060102")

	// get time entries
	total := int64(0)
	tec := gttimeentry.NewClient(thc)
	entries, eerr := tec.GetRange(s, e)
	if eerr != nil {
		fmt.Println(eerr)
		return "-1", "-1"
	}

	// sum durations with project pjID
	for _, e := range entries {
		if e.Pid == pjID {
			total += e.Duration
		}
	}
	totalMin := float64(total) / 60
	quantity := strconv.FormatFloat(totalMin, 'f', 4, 64)

	return date, quantity
}

func recordPixel(user, token, graph, date, quantity string) error {
	c := pixela.NewClient(user, token)

	// try to record
	err := c.RegisterPixel(graph, date, quantity)
	if err == nil {
		fmt.Println("recorded")
		return err
	}

	// if fail, try to update
	err = c.UpdatePixelQuantity(graph, date, quantity)
	if err == nil {
		fmt.Println("updated")
	}

	return err
}

func main() {
	lambda.Start(Handler)
}
10
4
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
10
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?