目標
背景
今年6月に株式会社RevCommに入社いたしました。
弊社RevCommは、フルリモートフルフレックス勤務体制というとても働きやすい環境となっています。
ただ、今自分がどんな作業を抱えているか、とか、どんな作業をしているか、というのが職場内の雰囲気でなんとなく伝わる感じではないので、自分から作業内容を明確にしていこうと思いました。
弊社では、GithubやGoogleカレンダーなど、基本的にはChrome上で業務内容を管理しています。
どうせならどのくらい作業に時間がかかっているかも実績として残していきたいと思ったので、Toggl+Chromeプラグインで、各タスクの入力を簡単に行なっていくことにしました。
そして、Togglの計測状況を随時Slackに流していくことで、自動的に「今、自分がどのような業務を行なっているか」を周知できるようにしてみようと思いました。
作業概要
- ChromeにGoogleアカウントでログインする
- TogglにGoogleアカウントでログインする
- ChromeにTogglプラグインを入れる
- Togglプラグインの設定を行う
- Toggl API トークンを取得する
- Slack Webhook URL を取得する
- Toggl API と Slack Webhook URL を通じて、取得した現在のTogglタスクをSlackに投稿する
- 7.を実行する shellスクリプトを作成する
- launchd で 8. を定期的に自動実行する
手順詳細
1. ChromeにGoogleアカウントでログインする
Chrome を入手して、Googleアカウントでログインする
2. TogglにGoogleアカウントでログインする
「Toggl」 の右上ページから、「Log in」 > 「Toogl track」(真ん中) > 「Login via Google」 でログインする
3. ChromeにTogglプラグインを入れる
Chromeウェブストアから「Toggl Track: Productivity & Time Tracker」を導入する
4. Togglプラグインの設定を行う
拡張機能から先ほど導入したTogglプラグインの設定画面を開く
「Integrations」でタスクがあるサイトにチェックを入れていく
Chromeを再起動すると、連携したサイトのタスク粒度ページに「Start Timer」のボタンが追加されている。
(例は、Githubのissueページ)
上記ボタンをクリックすると、Togglでタスクの計測が開始される
プロジェクト、タグも登録しておくと、後でレポートの時に集計しやすくなる
5. Toggl API トークンを取得する
「Toggl Trackのプロフィールページ」の下部にある「API token」の欄で、「--Click to reveal--」をクリックすると、トークン値が表示されるので、コピーする
6. Slack Webhook URL を取得する
Slackの公式手順に従って、Slack Webhook URL を取得する
次の画面でWebhook URLが表示されるので、コピーして保存。
※投稿するチャネルごとにURLの取得が必要。意図しないチャネルへのURLを使わないよう注意!
7. Toggl API と Slack Webhook URL を通じて、取得した現在のTogglタスクをSlackに投稿する
投稿にはPythonを使います。
7.1. 必要ライブラリ
pip install requests
7.2. 自動投稿スクリプト
スクリプトの仕様
- 現在Togglでタスクが計測されていたら、そのタスクをSlackに投稿する
- Slackに投稿したタスクは前回タスクとして
history.txt
に保存する - 次回スクリプトを起動した際
-
history.txt
と同じタスクの場合 … Slackに投稿しない -
history.txt
と違うタスクの場合 … Slackに投稿して、history.txt
を上書きする - タスクが計測されていない場合 … 日付が変わっていなければ、
history.txt
のタスクの終了をSlackに投稿する
-
# -*- coding: utf-8 -*-
import requests
import json
import datetime
import os
# Slack hooks
SLACK_HOOKS_URL = '<6で取得したSlack Webhook URL>'
TOGGL_API_TOKEN = '<5で取得したToggle API token>'
file_name = './history.txt'
def get_toggl():
headers = {'content-type': 'application/json'}
auth = requests.auth.HTTPBasicAuth(TOGGL_API_TOKEN, 'api_token')
current = requests.get('https://track.toggl.com/api/v8/time_entries/current', auth=auth, headers=headers)
if current.status_code != 200:
print("togglの情報取得に失敗しました")
return None, [], None
current_json = current.json()
if not current_json['data']:
return None, [], None
print(f'{current_json["data"]}')
description = current_json['data']['description']
# この時点でプロジェクト名をとってきておく
pname = None
if 'pid' in current_json['data']:
pid = current_json['data']['pid']
pname = get_project_name(pid)
print(f'pid: {pid}, pname: {pname}, description: {description}')
return pname, current_json['data']['tags'], description
def get_project_name(pid):
headers = {'content-type': 'application/json'}
auth = requests.auth.HTTPBasicAuth(TOGGL_API_TOKEN, 'api_token')
current = requests.get(f'https://track.toggl.com/api/v8/projects/{pid}', auth=auth, headers=headers)
if current.status_code != 200:
print("togglの情報取得に失敗しました")
return None
current_json = current.json()
if not current_json['data']:
return None
return current_json['data']['name']
def check_old_toggl(now_task):
prev_task = None
with open(file_name, mode='r', encoding='utf-8') as f:
history = f.read()
prev_task = history.strip()
write_history(now_task)
return (prev_task == now_task), prev_task
def write_history(str_history):
with open(file_name, mode='w', encoding='utf-8') as fw:
fw.write(str_history)
def write_slack(title, description):
text = f"""【{title}】 `{description}` """
payload = {'username': 'Toggl',
'text': text,
'icon_emoji': ':clock10:'}
requests.post(SLACK_HOOKS_URL, data=json.dumps(payload))
if __name__ == '__main__':
now = datetime.datetime.now()
now_str = now.strftime('%Y/%m/%d %H:%M:%S')
# 現在のタスクの取得
pname, tags, description = get_toggl()
now_task = f'{description} ({pname}:{", ".join(tags)})'
is_same, prev_task = check_old_toggl(now_task)
if not pname and not description:
print(f'[{now_str}] not pname+description')
stat = os.stat(file_name)
mdt = datetime.datetime.fromtimestamp(os.stat(file_name).st_mtime)
if now.day != mdt.day:
# 日付が変わっていたら、問答無用で終了
write_history('')
# ログクリア
os.remove('./out.log')
exit(1)
elif prev_task and not description and 'None' not in prev_task:
# slackへの書き込み(次が始まってない時だけ)
print(f'[{now_str}] write end')
write_slack("作業終了", prev_task)
# 終了して次が始まってない場合
write_history('')
exit(1)
if not pname:
# プロジェクト名が入ってない場合、スルー
exit(1)
if is_same:
print(f'[{now_str}] same')
# 前回取得したものと同じ場合は終了
exit(1)
if description:
print(f'[{now_str}] write start')
# slackへの書き込み
write_slack("作業開始", now_task)
8. 7.を実行する shellスクリプトを作成する
8.1. shellスクリプト
私はAnacondaを導入したので、conda activate
も内部で行なっています。
# >>> conda initialize >>>
# !! Contents within this block are managed by 'conda init' !!
__conda_setup="$('/Users/***/opt/anaconda3/bin/conda' 'shell.zsh' 'hook' 2> /dev/null)"
if [ $? -eq 0 ]; then
eval "$__conda_setup"
else
if [ -f "/Users/***/opt/anaconda3/etc/profile.d/conda.sh" ]; then
. "/Users/***/opt/anaconda3/etc/profile.d/conda.sh"
else
export PATH="/Users/***/opt/anaconda3/bin:$PATH"
fi
fi
unset __conda_setup
# <<< conda initialize <<<
cd <toggl2slack.pyへのフルパス> && conda activate bot && python toggl2slack.py
8.2. shellの権限変更
シェルに実行権限を付与する
chmod 744 toggl2slack.sh
9. launchd で 8. を定期的に自動実行する
9.1. 設定ファイル
~/Library/LaunchAgents
ディレクトリ以下に toggl2slack.plist
を配置する
- 5分おきに 8. で作成した
toggl2slack.sh
を実行する
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>toggl2slack</string>
<key>ProgramArguments</key>
<array>
<string>sh</string>
<string>/Users/***/work/bot/toggl2slack/toggl2slack.sh</string>
</array>
<key>StartInterval</key>
<integer>300</integer>
<key>StandardOutPath</key>
<string>/Users/***/work/bot/toggl2slack/out.log</string>
<key>StandardErrorPath</key>
<string>/Users/※**/work/bot/toggl2slack/error.log</string>
</dict>
</plist>
Slackへの投稿イメージ
Togglで計測を開始すれば、自動的にSlackに作業状況が通知されます。
次回予告
せっかくTogglで時間を計測できるようにしたのですから、レポートも一括で取ってこれるようにしちゃいましょう!