悩む男性
Togglでの記録を、Slackに自動で飛ばせたら便利そうだな〜
上司に稼働報告をするとき、Togglの記録をSlackに自動で転送できたら便利そうですよね。
また、スプレッドシートにも転記されたら、時間の集計もできて、うれしいと思います。
このページでは、TogglからSlackとスプレッドシートに転送するGASを紹介します。
コピペして使ってみてくださいね。
目次
仕様
仕様
- 前日のTogglの記録をSlackとスプレッドシートに転送する
- 転送するのは、指定したプロジェクトのみ
- Slackの転送先のスレッドを指定できる
- 転送先スプレッドシートのテンプレあり
コード
スクリプトファイルごとに、書いています。
GASのスクリプトエディター上で、スクリプトファイルを6つ作って、それぞれにコピペして使ってください。
global
/**
* スプレッドシートの設定
*/
const SPRREADSHEET_URL = '***スプレッドシートのURL***';
const SHEET_NAME = '***シートの名前***';
/**
* Toggl APIの設定
*/
// TogglのAPIキーをスクリプトプロパティから取得する
const TOGGL_API_KEY = PropertiesService.getScriptProperties().getProperty('toggl_api_key');
// 抽出するプロジェクト名を指定する
const PROJECT_NAME = '***抽出するプロジェクト名***';
// Toggl APIのURL
const TOGGL_API_URL = 'https://api.track.toggl.com/api/v8/time_entries';
/**
* Slackの設定
*/
// 稼働時間を報告するチャンネルのチャンネルID
const CHANNEL_ID_FOR_REPORT = '***稼働時間を報告するチャンネルのチャンネルID***';
// 稼働時間を報告するチャンネルのWebhook URL
const REPORT_CHANNEL_WEBHOOK_URL = '***Webhook URL***';
// 稼働時間を報告するスレッドの親投稿のURL
const THREAD_URL = '***スレッドのURL***';
// 稼働時間を報告するスレッドのタイムスタンプ
const THREAD_TS = getThreadTimestampFromSlackURL(THREAD_URL);
/**
* 前日の日付を取得する
*/
const YESTERDAY_DATE = getYesterdayDate();
function getYesterdayDate() {
const today = new Date();
const yesterday = new Date(today);
yesterday.setDate(today.getDate() - 1);
return yesterday.toISOString().split('T')[0];
}
/**
* スレッドのタイムスタンプを取得する
*/
function getThreadTimestampFromSlackURL(url) {
const match = url.match(/p(\d{10})(\d{6})$/);
if (!match) {
throw new Error('Invalid Slack URL format.');
}
console.log(`${match[1]}.${match[2]}`);
return `${match[1]}.${match[2]}`;
}
01_main
/**
* メイン
*/
function main() {
// 指定したプロジェクトの記録をTogglから取得する
const entries = getTogglEntriesForProject();
// スプレッドシートに記録を入力する
inputEntriesOnSpreadSheet(entries);
// Slackに転送するメッセージを作る
const formattedMessage = formatEntriesForSlack(entries);
// Slackに投稿する
postToSlack(formattedMessage);
}
02_getTogglEntriesForProject
/**
* タイムエントリーの中から、指定してプロジェクトのみを抽出する
*/
// 指定したプロジェクト名 かつ 前日のタイムエントリーを抽出する
function getTogglEntriesForProject() {
// プロジェクトIDを取得する
const projectId = getProjectIdForName();
// 抽出条件文をつくる
const startDate = `${YESTERDAY_DATE}T00:00:00Z`;
const endDate = `${YESTERDAY_DATE}T23:59:59Z`;
const filteredURL = `${TOGGL_API_URL}?start_date=${startDate}&end_date=${endDate}`;
// 前日のタイムエントリーを取得する
const entries = fetchFromTogglAPI(filteredURL);
console.log(entries);
Logger.log(entries);
// 指定したプロジェクトIDのエントリーのみを抽出する
return entries.filter(entry => entry.pid === projectId);
}
// プロジェクト名からプロジェクトIDを取得する
function getProjectIdForName() {
const workspaceId = getUserWorkspaceId();
const PROJECTS_API_URL = `https://api.track.toggl.com/api/v8/workspaces/${workspaceId}/projects`;
const projects = fetchFromTogglAPI(PROJECTS_API_URL);
const project = projects.find(p => p.name === PROJECT_NAME);
if (project) {
return project.id;
} else {
throw new Error(`Project with name ${PROJECT_NAME} not found.`);
}
}
// Toggl APIからユーザーのワークスペースを取得する
function getUserWorkspaceId() {
const WORKSPACES_API_URL = 'https://api.track.toggl.com/api/v8/workspaces';
const workspaces = fetchFromTogglAPI(WORKSPACES_API_URL);
if (workspaces && workspaces.length > 0) {
// 最初のワークスペースのIDを返す
return workspaces[0].id;
} else {
throw new Error('No workspaces found for the user.');
}
}
// Toggl APIへのリクエストを行う共通関数
function fetchFromTogglAPI(url) {
const headers = {
'Authorization': 'Basic ' + Utilities.base64Encode(TOGGL_API_KEY + ':api_token'),
'Content-Type': 'application/json'
};
const options = {
'method': 'GET',
'headers': headers
};
const response = UrlFetchApp.fetch(url, options);
return JSON.parse(response.getContentText());
}
03_inputEntriesOnSpreadSheet
/**
* Togglのエントリーをスプレッドシートに出力する
* 出力する項目
* ・開始日:entry.start
* ・開始時刻:entry.start
* ・終了日:entry.stop
* ・終了時刻:entry.stop
* ・経過時間:entry.duration
* ・内容:entry.description
*/
function inputEntriesOnSpreadSheet(entries) {
// スプレッドシートを取得する
let spreadSheet = SpreadsheetApp.openByUrl(SPRREADSHEET_URL);
// シートを取得する
let logSheet = spreadSheet.getSheetByName(SHEET_NAME);
// データのある最終行の行番号を取得する
let lastRow = getLastRow(logSheet);
// Togglのエントリーズから、エントリーを取り出して、各項目に分けていく
let data = entries.map(entry => {
// UTC時間の開始日と開始時刻を日本時間になおす
let [jpnStartDate, jpnStartTime] = formatDateTimeToJPN(entry.start);
// UTC時間の終了日と終了時刻を日本時間になおす
let [jpnStopDate, jpnStopTime] = formatDateTimeToJPN(entry.stop);
// 経過時間(秒)を時分秒になおす
const formattedDuration = formatDuration(entry.duration);
// スプレッドシートの列の順番でdataに格納する
// 開始日、開始時刻、終了日、終了時刻、内容、空欄(スプシ上の分類単語が入る)、経過時間
return [jpnStartDate, jpnStartTime, jpnStopDate, jpnStopTime, entry.description, '', formattedDuration];
});
// シートにdataを出力する
logSheet.getRange(lastRow + 1, 1, data.length, 7).setValues(data);
}
// UTC時間を日本時間になおす
function formatDateTimeToJPN(utcDateOfString) {
// string形の日時を時間型になおす
let utcDateOfTime = new Date(utcDateOfString);
// UTC時間を日本時間になおす(9時間を足す)
utcDateOfTime.setHours(utcDateOfTime.getHours() + 9);
// 日付と時刻を配列で返す
return [
utcDateOfTime.toISOString().split('T')[0], utcDateOfTime.toISOString().slice(11, 19)
];
}
// データの入ってる最終行の行番号を取得する
function getLastRow(logSheet) {
// シートの最終行を取得する
let maxRow = logSheet.getMaxRows();
// シートの最終行から、上向きにサーチして、データが入っている行を探す。
let lastRow = logSheet.getRange(maxRow, 1).getNextDataCell(SpreadsheetApp.Direction.UP).getRowIndex();
// その行番号を返す
return lastRow;
}
// 秒数をhh:mm:ssに変換する
function formatDuration(durationInSeconds) {
// 秒→時間
const hours = Math.floor(durationInSeconds / 3600);
// 秒→分
const minutes = Math.floor((durationInSeconds % 3600) / 60);
// 秒
const seconds = durationInSeconds % 60;
// hh:mm:ssの形式に変換する
return `${hours}:${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;
}
04_formatEntriesForSlack
function formatEntriesForSlack(entries) {
return entries.map(entry => {
return `・ ${entry.description}:${secondsToHMS(entry.duration)}`;
}).join('\n');
}
// 秒数を hh時間mm分ss秒 の形式に変換する関数
function secondsToHMS(seconds) {
const hours = Math.floor(seconds / 3600);
seconds %= 3600;
const minutes = Math.floor(seconds / 60);
seconds %= 60;
return `${hours}時間${minutes}分${seconds}秒`;
}
05_postToSlack
function postToSlack(message) {
const payload = {
'channel': CHANNEL_ID_FOR_REPORT,
'username': 'Toggl_昨日の稼働報告',
'icon_emoji' : ':alphabet-yellow-t:',
'text': formatDateToMMDD(YESTERDAY_DATE) + '\n' + message
};
payload.thread_ts = THREAD_TS; // thread_tsをpayloadに追加
const options = {
'method': 'POST',
'contentType': 'application/json',
'payload': JSON.stringify(payload)
};
UrlFetchApp.fetch(REPORT_CHANNEL_WEBHOOK_URL, options);
}
// ISO形式の日付を MM/dd の形式に変換する関数
function formatDateToMMDD(isoDate) {
const date = new Date(isoDate);
const month = date.getMonth() + 1; // 月は0から始まるため、+1が必要
const day = date.getDate();
return `${month}/${day}`;
}
さいごに
私がテレワーク中に、稼働報告をするために作りました。
もし、自分じゃ実装できないという場合は、以下のページからお気軽にご連絡ください。
あなたの状況に合わせて、GASを実装します。
コメント