メモ帳

読むな

Toggl の作業内容を Redmine に登録する

f:id:IlIIlIIIlIIlI:20220220215827p:plain

弊社では作業管理ツールに Redmine を使用しています。
チケットと呼ばれる各タスクに作業時間を入力して、↓みたいに退社時に Slackbot で1日の作業内容がわかるようになっています。
f:id:IlIIlIIIlIIlI:20220220184335p:plain
(ちなみに上記のSlackbotは弊社代表の自作です。)

ただ、これが死ぬほどめんどくさいんです。
作業開始時の時刻を覚えておいて作業が完了したら終了時の時刻から逆引きして時間を出すっていう、算数の引き算でつまずいた人間からすると地獄です。冗談抜きに引き算だけで30分くらい取られます。
ということでめんどくさいことはプログラムにやらせよう!

事前準備

Toggl アカウントの作成

toggl.com

これがなきゃ始まりません。
アカウント作成後の profile 画面に API Token が発行されているのでコピります。

Toggl 拡張機能のインストールと設定

chrome.google.com

なくても成立はするんですが、Redmine 上でタイマーを打ちたいのでインストールします。
インストール後に、拡張機能の設定で Integrations メニューの中で Redmine のURLを有効にします。
(自社サーバーに Redmine を立ててる場合は Custom URLs for integrationsドメインを登録すればOK)

f:id:IlIIlIIIlIIlI:20220219234021p:plain

最終的にRedmine のチケットを見た時に Toggl のボタンが表示されてればOK

f:id:IlIIlIIIlIIlI:20220219215728p:plain

プログラムの作成

最低限必要な処理以外は省略してるので「リクエスト周りのエラーハンドリングどうすんの?」とか言う輩は帰ってください。
アーキテクチャ警察も帰ってください。

API Document は以下を参照しました

① 当日の作業記録を取得する

<?php
$togglUri = 'https://api.track.toggl.com/api/v8/time_entries';
$options = ['auth' => [$_ENV['TOGGL_API_TOKEN'], 'api_token']];
$dateTime = new DateTimeImmutable();
$query = http_build_query([
    'start_date' => $dateTime->setTime(00, 00, 00)->format('c'),
    'end_date' => $dateTime->setTime(23, 59, 59)->format('c'),
]);
$entryRequest = (new \GuzzleHttp\Client())->request('GET', "{$togglUri}?{$query}", $options);

$res = $entryRequest->getBody()->getContents();
$entries = json_decode($res, true);

② Toggl の作業内容から Redmine に登録する作業時間データを用意する

<?php
foreach ($entries as $entry) {
    // 作業時間のコメント
    $description = $entry['description'] ?? '';

    // 作業時間の時間
    // 開始・終了の差分
    $start = new DateTimeImmutable($entry['start']);
    $stop = new DateTimeImmutable($entry['stop']);
    $diff = $stop->diff($start);
    $min = round(($diff->i / 60), 3);
    $diffHours = $diff->h + $min;

    // Redmine でタイマーを打ったときのフォーマットに沿って` #1234 `の文字列をチケットIDとして検索
    preg_match('/\ #([0-9]+)\ /', $description, $results);
    // description にチケットIDがあればサブパターンマッチの文字列を使用する
    // プロジェクトに紐付かない場合は任意のデフォルトのチケットIDを指定
    $issueId = $results[1] ?? $_ENV['REDMINE_DEFAULT_ISSUE_ID'];

    // 活動の選択肢
    $activities = ['作業内容1' => 1, '作業内容2' => 2];
    // タグの登録があれば1番目のタグを活動とする
    // なければデフォルトの活動IDを指定
    $activityId = $activities[$entry['tags'][0] ?? 1];

    // xmlの生成
    $xmlValues = [
        'issue_id' => $issueId,
        'spent_on' => $dateTime->format('Y-m-d'),
        'hours' => $diffHours,
        'activity_id' => $activityId,
        'user_id' => $_ENV['REDMINE_USER_ID'],
        'comments' => $description,
    ];
    $xml = new SimpleXMLElement('<time_entry></time_entry>');
    foreach ($xmlValues as $key => $value) {
        $xml->addChild($key, $value);
    }
}

Toggl の作業レコードごとに以下のようなXMLが生成されます。

<time_entry>
    <issue_id>1234</issue_id>
    <spent_on>2022-02-19</spent_on>
    <hours>1.0</hours>
    <activity_id>1</activity_id>
    <comments>タスク #1234 テキストテキストテキストテキスト</comments>
    <user_id>1</user_id>
</time_entry>

Redmine に作業時間を登録する

<?php
$redmineUri = "{$_ENV['REDMINE_URL']}time_entries.xml?key={$_ENV['REDMINE_API_KEY']}";
$request = (new \GuzzleHttp\Client())->request('POST', $redmineUri, [
    'headers' => ['Content-Type' => 'text/xml',],
    'body' => $xml->asXML()
]);

APIキーが発行されてない Redmine を使ってる場合は知りません!管理者に頼もう!

作業の登録

ここからがやりたかったことです

  1. 作業の前後で Redmine のチケット画面に表示されてる Toggl のタイマーをクリック
    f:id:IlIIlIIIlIIlI:20220220184536p:plain
    タグを付けると活動がわかるのでタグだけ任意でつけておく
  2. 退勤前にプログラムを実行する
  3. Redmine に作業内容が登録されるのを確認する

最終的に↓みたいにして使ってます!

コード全文は以下に置いてあります。

https://github.com/m1y474/toggl-redmine

最後に

Toggl 過激派なので Toggl 布教の記事も書きたい…(Toggl Trackという名称を使ってない時点で過激派ではない)

ちなみにこのプログラムを作るまでは、下記サイトをiframeに埋め込んだものを拡張機能にして手動で計算してました!お世話になりました!2桁以上の引き算は苦手です!!

www.jalps.net