カレンダー

タイムトラッカーアプリClockifyのタイムエントリ(時間記録) をAPIでGoogleカレンダーにコピーする その4 – タイムエントリからカレンダーの予定を作成する

freeeに関係ない話題ですが、Google Apps Script(以下GAS)でできる効率化の例として番外編としてfreee API以外のGASでの活用に関しても記事にしています。

今回は、「ある1日にどんな作業にどれだけ時間を使ったのか記録するタイムトラッカーアプリであるClockifyのタイムエントリ(時間記録)をGoogleカレンダーにコピーしてライフログにする」に挑戦したいと思います。

前回の記事では、前日1日のTime entry(時間記録)すべてを取得しました。

配列で取得されたTime entry オブジェクト

前回のスクリプトでは、前日1日に発生したすべてのタイムエントリを配列でログ出力しました。今回はこの配列の各要素に格納されているTime entryオブジェクトから、Googleカレンダーへのコピーに必要な要素のみを抽出していきます。

公式ドキュメントでレスポンスのサンプルを確認します。

このレスポンスのプロパティから、Googleカレンダーのイベント作成に必要なものを選択します。

  • description:エントリの概要
  • projectId:プロジェクト ID
  • timeIntervalのstart:開始日時
  • timeIntervalのend:終了日時

今回は上記のプロパティを選択します。ただ、できればprojectIdはIDでなくprojectの名称で取得できればよりスムーズです。

hydratedをtrueにしてみる

projectIdをIDからprojectの名前に変換する処理をスキップするためにクエリパラメータにあるhydratedをtrueにしてみます。これは公式ドキュメントによると

このクエリパラメータを利用した場合、タイムエントリのプロジェクト、タスク、タグは、IDでなくすべての詳細なデータが、オブジェクトとして戻り値となり返されます。この場合、projectId, taskId, tagIdsは、リクエストのレスポンスでproject, task, tagsそれぞれの詳細な情報を格納したオブジェクトに変更されることに注意が必要です。

https://clockify.me/developers-api#operation–v1-workspaces–workspaceId–user–userId–time-entries-get

とあり、前回の記事のlogYesterdayTimeEntries()関数に少し手を加えて、hydratedをtrueにしてみると…

function logYesterdayTimeEntriesHydrated() {
  const apiKey = PropertiesService.getScriptProperties().getProperty('CLOCKIFY_API_KEY'); // API Keyはプロパティストアに格納
  const workspaceId = PropertiesService.getScriptProperties().getProperty('CLOCKIFY_WORKSPACE_ID'); // Workspace IDはプロパティストアに格納
  const userId = PropertiesService.getScriptProperties().getProperty('CLOCKIFY_USER_ID'); // User IDはプロパティストアに格納

  const now = new Date(); // スクリプト実行時の日時のDateオブジェクト
  const today = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0); // 今日の日付 00:00:00を取得
  const yesterday = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1, 0); // 昨日の日付 00:00:00を取得
  const startTime = Utilities.formatDate(yesterday, 'JST', "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); // ISO-8601形式の日時にフォーマット タイムゾーンはJST(日本標準時)
  const endTime = Utilities.formatDate(today, 'JST', "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); // ISO-8601形式の日時にフォーマット タイムゾーンはJST(日本標準時)

  const baseUrl = 'https://api.clockify.me/api/v1'
  const dataUrl = `/workspaces/${workspaceId}/user/${userId}/time-entries`;
  const paramsUrl = `?start=${startTime}&end=${endTime}&page-size=5000&hydrated=true`;
  const url = baseUrl + dataUrl + paramsUrl; // 共通URL + 個別URL + クエリパラメータ

  /* Class UrlFetchApp のリファレンスからfetch(url, params)メソッドを確認しパラメーターの書き方を読み替える */
  const params = {
    contentType: 'application/json',
    headers: { 'X-Api-Key': apiKey },
    muteHttpExceptions: true // レスポンスが失敗した場合でもエラーを出さずにHTTPResponseを返すオプション
  };

  const response = UrlFetchApp.fetch(url, params); // HTTPResponse
  const json = response.getContentText(); // HTTPResponseの内容の文字列 = JSON文字列
  const aryObj = JSON.parse(json); // JSON文字列をJSONオブジェクトにparse(解析)
  console.log(aryObj.length);
  console.log(aryObj);
}

プロジェクト名も確認できるようになりました。

個々のTime entryオブジェクトにprojectというプロパティがあり、その値のオブジェクトにnameプロパティがあり、この値からプロジェクト名が取得できます。

取得された各Time entryオブジェクトからGoogleカレンダーコピーに必要な値を抽出する

hydratedをtrueにしたことで、Time entryオブジェクトから必要な情報はすべて取得できるようになりました。今回は以下の項目を抜粋します。

  • description => CalendarEventのTitleの一部(後半)に利用
  • project.name => CalendarEventのTitleの一部(前半)に利用
  • timeIntervalのstart => CalendarEventのStartTime(開始時間)に利用
  • timeIntervalのend => CalendarEventのEndTime(開始時間)に利用

Time entryオブジェクトを複数格納した配列の各要素毎にGoogleカレンダーのイベントを作成する処理を実装します。

配列の各要素それぞれに対してある処理を行う場合は、forEach() メソッドを使用します。

const aryCalendarEvents = aryObj.forEach(entry => {
  /* Googleカレンダーのイベントを作成する処理 */
});

Clockify APIから必要な値をGoogleカレンダーイベント作成用に定数に代入しておきます。

const description = entry.description;
const projectName = entry.project.name;
const startTime = new Date(entry.timeInterval.start);
const endTime = new Date(entry.timeInterval.end);

作成するGoogleカレンダーのイベント(予定)のタイトルをClockifyのエントリのプロジェクト名と概要を組み合わせて作成します。

const title = projectName + ':' + description; // プロジェクト名:概要

Googleカレンダーのイベント作成時にClockifyのプロジェクトごとにイベントの色分けも行います。プロジェクト名に応じて指定色に変換できるようにEnum風オブジェクトを準備します。

  const objProjectColor = {
    'CORE WORK': 'BLUE',
    'HOUSE WORK': 'GREEN',
    'INPUT': 'PALE_BLUE',
    'INTROSPECTION': 'GRAY',
    'OUTPUT': 'YELLOW',
    'PHYSICAL': 'CYAN',
    'PRACTISE': 'MAUVE',
    'RESTORATION': 'GRAY',
    'SOCIAL': 'PALE_GREEN',
    'WORK': 'RED'
  };

指定できるイベントカラーは、CalendarApp.EventColor に列挙されていて、ここに示されているプロパティの文字列かインデックス(整数)をClass CalendarEventのsetColor(color)メソッドの引数に指定することでイベントの色を指定できます。

以下は、PALE_GREENを指定する場合の例です。

eventオブジェクト.setColor(CalendarApp.EventColor.PALE_GREEN);

これで、forEach() メソッドを使用してTime entryオブジェクトを格納した配列から、必要な情報のみを抽出しつつ、作成するイベントの色を指定できます。

  /* Time entryオブジェクトを格納した配列から必要な情報のみを抽出してGoogleカレンダーのイベントを作成 */
  aryObj.forEach(entry => {
    const description = entry.description;
    const projectName = entry.project.name;
    const startTime = new Date(entry.timeInterval.start);
    const endTime = new Date(entry.timeInterval.end);
    const title = projectName + ':' + description; // プロジェクト名:概要 のフォーマット
    const eventColor = objProjectColor[projectName]; // Googleカレンダーイベントの色を指定
    // ここに指定したGoogleカレンダーにイベントを新たに作成し、そのイベントの色を変更する処理を書く
  });
}

脱線:ワークスペースのすべてのプロジェクト名を配列で返すスクリプト

ちょっと脱線しますが、projectを手入力で列挙しても良いのですが、折角なのでワークスペースのすべてのプロジェクト名を配列で返すスクリプトを書いてみます。

いつものようにエンドポイントを確認して…

リクエストを送信します。

function logAryProjectName() {
  const apiKey = PropertiesService.getScriptProperties().getProperty('CLOCKIFY_API_KEY'); // API Keyはプロパティストアに格納
  const workspaceId = PropertiesService.getScriptProperties().getProperty('CLOCKIFY_WORKSPACE_ID'); // Workspace IDはプロパティストアに格納

  const baseUrl = 'https://api.clockify.me/api/v1'
  const dataUrl = `/workspaces/${workspaceId}/projects`;
  const url = baseUrl + dataUrl; // 共通URL + 個別URL

  /* Class UrlFetchApp のリファレンスからfetch(url, params)メソッドを確認しパラメーターの書き方を読み替える */
  const params = {
    contentType: 'application/json',
    headers: { 'X-Api-Key': apiKey },
    muteHttpExceptions: true // レスポンスが失敗した場合でもエラーを出さずにHTTPResponseを返すオプション
  };

  const response = UrlFetchApp.fetch(url, params); // HTTPResponse
  const json = response.getContentText(); // HTTPResponseの内容の文字列 = JSON文字列
  const aryObj = JSON.parse(json); // JSON文字列をJSONオブジェクトにparse(解析)
  const aryProjectName = aryObj.map(project => project.name); // project.nameを抽出して新しく配列を生成
  console.log(aryProjectName);
}

無事出力されました。

指定したGoogleカレンダーに新しくイベント(予定)を作成する

まず、新しくイベント(予定)を追加したいGoogleカレンダーを指定する必要があります。これには、各カレンダーに付与されたIDをまず確認します。

Googleカレンダーを開くと画面の左側に自分が閲覧・管理できるカレンダー一覧(マイカレンダー)がありますので、そこからイベントを追加したいカレンダーの設定と共有をクリックします。

設定と共有の画面の下部にカレンダーの結合という欄があり、そこにカレンダーIDが記載されています。

Class CalendarAppgetCalendarById(id)メソッドの引数にこのカレンダーIDを渡してCalendarEventオブジェクトを取得します。今回もカレンダーIDは確認後にプロパティストアに格納しています。

const idCalendar = PropertiesService.getScriptProperties().getProperty('CALENDAR_ID');
const calendarLifeLog = CalendarApp.getCalendarById(idCalendar);

このCalendarEventオブジェクトを取得する処理は、forEach() ループ内で都度呼び出しても良いのですが、重複する処理となり冗長なため、ループ外で事前に定数に代入しておきます。

最後にforEach() ループ内でcreateEvent(title, startTime, endTime)メソッドを用いて新しいイベントを作成し、setColor(color)メソッドでイベントの色を変更します。

calendarLifeLog.createEvent(title, startTime, endTime).setColor(CalendarApp.EventColor[eventColor]);

ということで、ようやく前日のClockifyのタイムエントリ(時間記録)をGoogleカレンダーにコピーしてライフログにするスクリプトが完成しました。

中の人が夜型のため日付をまたいで作業することも多いため、Clockifyのタイムエントリの取得の開始・終了時間をAM5:00区切りに設定しました。この時間であれば寝ているはずです…

function clockifyEntry2CalendarEvent() {
  const apiKey = PropertiesService.getScriptProperties().getProperty('CLOCKIFY_API_KEY'); // API Keyはプロパティストアに格納
  const workspaceId = PropertiesService.getScriptProperties().getProperty('CLOCKIFY_WORKSPACE_ID'); // Workspace IDはプロパティストアに格納
  const userId = PropertiesService.getScriptProperties().getProperty('CLOCKIFY_USER_ID'); // User IDはプロパティストアに格納

  const now = new Date(); // スクリプト実行時の日時のDateオブジェクト
  const today = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 5); // 今日の日付 05:00:00を取得
  const yesterday = new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1, 5); // 昨日の日付 05:00:00を取得
  const startTime = Utilities.formatDate(yesterday, 'JST', "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); // ISO-8601形式の日時にフォーマット タイムゾーンはJST(日本標準時)
  const endTime = Utilities.formatDate(today, 'JST', "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); // ISO-8601形式の日時にフォーマット タイムゾーンはJST(日本標準時)

  const baseUrl = 'https://api.clockify.me/api/v1'
  const dataUrl = `/workspaces/${workspaceId}/user/${userId}/time-entries`;
  const paramsUrl = `?start=${startTime}&end=${endTime}&page-size=5000&hydrated=true`;
  const url = baseUrl + dataUrl + paramsUrl; // 共通URL + 個別URL + クエリパラメータ

  /* Class UrlFetchApp のリファレンスからfetch(url, params)メソッドを確認しパラメーターの書き方を読み替える */
  const params = {
    contentType: 'application/json',
    headers: { 'X-Api-Key': apiKey },
    muteHttpExceptions: true // レスポンスが失敗した場合でもエラーを出さずにHTTPResponseを返すオプション
  };

  const response = UrlFetchApp.fetch(url, params); // HTTPResponse
  const json = response.getContentText(); // HTTPResponseの内容の文字列 = JSON文字列
  const aryObj = JSON.parse(json); // JSON文字列をJSONオブジェクトにparse(解析)

  /* プロジェクト名に応じてイベント色を変えるためのEnum風オブジェクト */
  const objProjectColor = {
    'CORE WORK': 'BLUE',
    'HOUSE WORK': 'GREEN',
    'INPUT': 'PALE_BLUE',
    'INTROSPECTION': 'GRAY',
    'OUTPUT': 'YELLOW',
    'PHYSICAL': 'CYAN',
    'PRACTISE': 'MAUVE',
    'RESTORATION': 'GRAY',
    'SOCIAL': 'PALE_GREEN',
    'WORK': 'RED'
  };

  const idCalendar = PropertiesService.getScriptProperties().getProperty('CALENDAR_ID'); // イベントを作成するGoogleカレンダーのIDはプロパティストアに格納
  const calendarLifeLog = CalendarApp.getCalendarById(idCalendar); // Calendarオブジェクトを取得

  /* Time entryオブジェクトを格納した配列から必要な情報のみを抽出してGoogleカレンダーのイベントを作成 */
  aryObj.forEach(entry => {
    const description = entry.description;
    const projectName = entry.project.name;
    const startTime = new Date(entry.timeInterval.start);
    const endTime = new Date(entry.timeInterval.end);
    const title = projectName + ':' + description; // プロジェクト名:概要 のフォーマット
    const eventColor = objProjectColor[projectName]; // Googleカレンダーイベントの色を指定
    calendarLifeLog.createEvent(title, startTime, endTime).setColor(CalendarApp.EventColor[eventColor]); // イベントを作成し色を変更
  });
}

あとは午前5時から6時の間にこのスクリプトが定期実行されるようにトリガーを設定すれば、翌朝には前日のタイムエントリがすべてカレンダーに転記されています。

おわりに

ということで「ある1日にどんな作業にどれだけ時間を使ったのか記録するタイムトラッカーアプリであるClockifyのタイムエントリ(時間記録)をGoogleカレンダーにコピーしてライフログにする」スクリプトが完成しました。

Clockifyにも過去のタイムエントリをカレンダーで色分けして表示する機能はありますが、Googleカレンダーに転記することで予定の詳細に振り返りコメントなどを追記できるので、ライフログには最適です。

またイベントを色分けすることで、どんな事にどれくらいの時間を費やしたかなどビジュアルで把握することができます(これもClockifyのダッシュボードでも確認可)。

シリーズ目次

  1. タイムトラッカーアプリClockifyのタイムエントリ(時間記録) をAPIでGoogleカレンダーにコピーする その1 – APIの認証を通してサンプルリクエストを送る
  2. タイムトラッカーアプリClockifyのタイムエントリ(時間記録) をAPIでGoogleカレンダーにコピーする その2 – UserとWorkspaceのIDを取得する
  3. タイムトラッカーアプリClockifyのタイムエントリ(時間記録) をAPIでGoogleカレンダーにコピーする その3 – クエリパラメータを指定して前日のエントリのみを取得する
  4. タイムトラッカーアプリClockifyのタイムエントリ(時間記録) をAPIでGoogleカレンダーにコピーする その4 – タイムエントリからカレンダーの予定を作成する

Google Apps Scriptを勉強したい方へ

この記事を見て、GASを勉強したいなと思われた方はぜひノンプログラマーのためのスキルアップ研究会(通称 ノンプロ研)にご参加ください。私も未経験からこの学習コミュニティに参加し、講座を受講したことでGASが書けるようになりました。

学習コミュニティ「ノンプログラマーのためのスキルアップ研究会」

挫折しがちなプログラミングの学習も、コミュニティの力で継続できます。ノンプロ研でお待ちしております!

タグ: , ,
Share on:
Previous Post
mike-kilcoyne-G1y7tcQxG34-unsplash
GAS活用法

Gmailのスター付きメールからTodoistのタスクを作成&Slackに通知する その1 – isStarred()メソッドを使って、未処理のスター付きメールのみを絞り込む

Next Post
stephen-kraakmo-uAzUg6_tMCo-unsplash
GAS活用法

タイムトラッカーアプリClockifyのタイムエントリ(時間記録) をAPIでGoogleカレンダーにコピーする その3 – クエリパラメータを指定して前日のエントリのみを取得する