Notion で(自分なりの)タスク・時間管理を行う方法(フォーム編①)
前回は、Notion で「プレ着手」を実践しやすくするため、
他のルーチンと「プレ着手リスト」を分けて管理し、各10分ずつのタスクを自動生成する方法について、説明させていただきました。
今回も、一見、かなりニッチな内容だと思われるかもしれませんが、
Google フォームから Notion の(タスクリスト)データベースを更新する方法を、紹介したいと思います。
確かに、本記事で解説する内容そのものは、主に、Notion カレンダー上でタスクを追加したり、動かしたりした際などに、
自動的には更新されない「開始予定時刻」や「見積値」などのプロパティの値を、追加・修正する手間を省くための手法なので、
タイムラインビューや Notion カレンダーを使って、あるいは、タスクごとに手動で「実行日」プロパティを更新して、
頻繁にプランを調整するような人でなければ、恩恵は少ないでしょう。
そもそも、「実行日」と「開始予定時刻」や「見積値」との間の齟齬を気にせず、例えば、上の記事の最後の方でも少し触れているように、
Notion カレンダーだけを見て過ごすなら、そこで「実行日」プロパティのみ更新できていれば、十分かもしれませんし、
逆に、Notion カレンダーを使わないなら、「開始予定時刻」と「見積値」さえ、ちゃんと設定できていれば、実用上、特に問題はないかもしれません。
ともかく、実行してしまえば、そのタスクのログ関連のプロパティは、同じように記録されることになるわけですからね。
とは言え、簡単に修正できるなら、それに越したことはないしょう。
しかし、今のところは、Notion の「ボタン」機能でやるには、やはり、ちょっと限界があります。
というわけで、いつものように、Google の Apps Script(GAS)を使おうということになるのですが、
少し違うのは、なんらかの処理を、定期的に自動実行させるのではなく、
任意のタイミングで、例えば、Notion カレンダーでプランを調整した直後に、データベースを更新したいという点です。
そこで、「Google フォーム」が出てくるわけですね。
この「フォーム」の「送信」ボタンを押した時などにも、GAS のスクリプトを実行することができるということは、知っていましたでしょうか?
しかも、Notion のページには「フォーム」を埋め込むことも可能なので、
ここまで言えば、これから何をやろうとしているのかは、大体、想像できたかもしれません。
それでは、そろそろ、始めていきましょう!
フォームを作成する
まず、Google にログインして、https://docs.google.com/forms/u/0/ にアクセスし、「空白のフォーム」を作成してください。
タイトルは、適当に「update」などとし、デフォルトで設置されている「ラジオボタン」を使った「無題の質問」は不要なので、削除したら、
右上のメニューから、「スクリプトエディタ」を選択して、いつもの Apps Script の画面を開きます。

まぁ、ここまでは、簡単ですね。
もちろん、何か質問を作成しておいて、例えば、タスクの新規作成を行うフォームなどを作ることも、可能なのですが、
それについては、次回のテーマに取っておくとして、今回は、フォームの実行だけができれば良いものとしましょう。
別に、質問の回答データは何もなくても、フォームを実行した際に、いつも同じ処理が行われるよう、設定しておくことはできます。
右上あたりにある「プレビュー」ボタンを押せば、ページを確認することもできますが、右上の「送信」ボタンからリンクをコピーし、

早速、Notion 上に埋め込んでみて、表示を確認してもらっても構いません。

ちなみに、見た目に関しても、ブラウザの機能でダークモードを強制するなど、検討の余地はありますが、ライトモードなら、以下のようになります。

タスクリストを更新する処理を書く
では、いよいよ、上で開いた Apps Script のページに、例えば、以下のようなスクリプトを記述してみてください。
function myFunction() {
const today = new Date();
const after = today.toLocaleDateString("ja-JP", { year: "numeric", month: "2-digit", day: "2-digit" });
const end = new Date(today.setHours(23, 59, 0, 0));
const before = end.toLocaleDateString("ja-JP", { year: "numeric", month: "2-digit", day: "2-digit" });
const filter = {
"filter": { "and": [
{ "property": "実行日", "date": { "on_or_after": after.replaceAll("/", "-") } },
{ "property": "実行日", "date": { "on_or_before": before.replaceAll("/", "-") } },
{ "property": "ステータス", "status": { "equals": "未着手" } },
{ "property": "先送り", "checkbox": { "equals": false } }
] }
};
const options = {
'method': 'post',
'headers': {
'Authorization': 'Bearer secret_XXXXX',
'Content-Type': 'application/json',
'Notion-Version': '2022-06-28'
},
'payload': JSON.stringify(filter)
};
const response = UrlFetchApp.fetch('https://api.notion.com/v1/databases/xxxxx/query', options);
const contents = JSON.parse(response.getContentText());
const results = contents.results;
const sections = getSections();
results.forEach((result) => {
const properties = result.properties;
const date = properties["実行日"].date;
if (date.end) {
const start = new Date(date.start);
const end = new Date(date.end);
const time = start.toLocaleTimeString("ja-JP", { hour: "2-digit", minute: "2-digit" });
const section = time2section(sections, time);
const minutes = (end.getTime() - start.getTime()) / 60000;
const data = {
"properties": {
"開始予定時刻": { "rich_text": [{ "type": "text", "text": { "content": time } }] },
"セクション": { "relation": [{ "id": section }] },
"見積値": { "type": "number", "number": minutes }
}
};
const patch = {
'method': 'patch',
'headers': {
'Authorization': 'Bearer secret_XXXXX',
'Content-Type': 'application/json',
'Notion-Version': '2022-06-28'
},
'payload': JSON.stringify(data)
};
UrlFetchApp.fetch('https://api.notion.com/v1/pages/' + result.id, patch);
}
});
}
function getSections() {
const data = { "sorts": [{ "property": "開始時刻", "direction": "descending" }] };
const options = {
'method': 'post',
'headers': {
'Authorization': 'Bearer secret_XXXXX',
'Content-Type': 'application/json',
'Notion-Version': '2022-06-28'
},
'payload': JSON.stringify(data)
};
const response = UrlFetchApp.fetch('https://api.notion.com/v1/databases/yyyyy/query', options);
const contents = JSON.parse(response.getContentText());
const object = {};
for (const result of contents.results) {
object[result.properties["開始時刻"].rich_text[0].plain_text] = result.id;
}
return object;
}
function time2section(sections, time) {
const keys = Object.keys(sections);
const start = keys.find(key => time >= key);
return sections[start];
}ちょっと長いですが、下の「getSections()」や「time2section()」といった関数は、「セクション」プロパティを扱うためのもので、
実は、以前「セクションリスト」を導入した際に書いたコードと、ほとんど変わりないため、細かい説明は省略させてもらいます。
まぁ、単に、「time2section()」を使って、「開始予定時刻」をもとに、「セクション」を自動判定するためのスクリプトですね。
いつものように、"xxxxx" と "secret_XXXXX" は、「タスクデータリストデータベースの ID」と「Notion API のシークレット」に、全て書き換え、
今回は、それらと同様、"yyyyy" も、変数にはしていないので、少し分かりにくいかもしれませんが、下の方の「UrlFetchApp.fetch()」の引数の、
URL 中に含まれる "yyyyy" には、「セクションリストデータベースの ID」を入れておいてください。
そして、上の「myFunction()」でやっていることを、簡単に説明すると、相変わらず、日付の扱いは少し難しいですが、
まず、最初に出てくる「UrlFetchApp.fetch()」では、フィルター設定により、「実行日」が「今日」の「未着手」のタスクだけを取得しています。
あと、「先送りチェックボックス」がオンになっているものは、一応、省いていますね。
ともあれ、こうして取得したタスクの中に、「開始予定時刻」や「見積値」などのプロパティが、ちゃんと設定されていなかったり、
Notion カレンダー上で動かした「実行日」の値との間に、齟齬が生じている場合があったりするかもしれないので、
下の「forEach()」の中にある「UrlFetchApp.fetch()」で、Notion API を実行し、一つずつ上書きしていこう、というわけです。
もちろん、上述した「セクション」プロパティについても、同じようなコードを書けば、追加・修正されていくことになります。
まぁ、どういう条件に当てはまるタスクを処理対象とするか、どのプロパティを更新するかは自由なので、特に「filter」や「data」変数の中身は、
是非、自分の運用に合わせて、考えてみてください。
トリガーを追加する
最後に、フォームの「送信」ボタンを押したら上のスクリプトが実行されるような、新しい「トリガー」を追加しましょう。
今までは、「時間主導型」のトリガーで、「午前0時〜1時」に実行するとか、
例えば、「12時間おき」に実行するといった感じで、指定することが多かったかと思いますが、
今回は、次のようにして、「フォーム送信時」に「myFunction()」が実行されるよう、設定しておく必要があります。

これで、Notion カレンダーなどから追加した、「実行日」プロパティしか設定されていないようなタスクについても、
上で設置したフォームの「送信」ボタンを押せば、「開始予定時刻」や「見積値」そして「セクション」まで、自動的に入力されるはずです。
また、ボタンを押し忘れた場合などに備え、念の為、「時間主導型」のトリガーも、別で追加しておくといいかもしれませんね。
まとめ
さて、今回、ご紹介した内容は、個人的に必要に迫られて作成した、ちょっと特殊なニーズに対応するためのものでしたが、
Google フォームを利用した、このような仕組み自体は、色々な用途に、応用できそうだと思いませんか?
例えば、「実行日」というか、「日付」プロパティを、開始終了時刻まで手動で入力するのは、割と面倒なので、今回の内容とは逆に、
タスクの「開始予定時刻」と「見積値」だけが入力されている状態から、「実行日」の方を更新するような、スクリプトを書くこともできます。
まぁ、それを言うなら、そもそも、新しいタスクを追加する方法としても、これは、結構、便利かもしれません。
というわけで、次回は、フォームに「開始予定時刻」や「見積値」などを入力して、タスクを生成する方法についても、考えてみるとしましょう。
ついでに、Apple の「ショートカット」アプリを使って、簡易的にタスクを追加する手法についても、紹介する予定です。
ではまた。
