Sprint 更新時のカレンダータスク自動割り当てを Notion のみで実現: hkob の雑記録 (41)

はじめに

hkob の雑記録の第41回目は、Sprint 更新時にカレンダータスクを自動割り当てしていたスクリプトをデータベースオートメーションで実現する方法を解説します。少し複雑なので、作りながら執筆していきます。

従来のタスク割り当てスクリプト

現在、毎週月曜日 7:13 に calendar_to_sprint という Ruby スクリプトを定時実行しています。これは、カレンダーに登録された Notion タスクのうち、今週分のものを Current に、来週分のものを Next に割り当てるスクリプトです。このスクリプトについては、こちらに掲載されています。

hkob.hatenablog.com

スクリプトはこちらになります。これと同じことをデータベースオートメーションで実現しようと思います。

#! /usr/bin/env ruby

require "date"
require "notion_ruby_mapping"
include NotionRubyMapping
NotionRubyMapping.configure { |c| c.token = ENV["NOTION_API_KEY"] }

SPRINT_DATABASE_ID = "ここにスプリントデータベースのIDを入れます"
sprint_db = Database.find SPRINT_DATABASE_ID
sp = sprint_db.properties

today = Date.today
cwday = today.cwday
monday = today + (8 - cwday)
sunday = monday + 6

next_sprint = sprint_db.query_database(sp["Sprint status"].filter_equals("Next")).first
nsdp = next_sprint.properties["Dates"]

next_sprint_week_key = if nsdp.start_date.nil?
                         nsdp.start_date = monday
                         nsdp.end_date = sunday
                         next_sprint.save
                         next_sprint.properties["week_id"].formula["string"]
                       else
                         next_sprint.properties["week_id"].formula["string"]
                       end
next_sprint_title = next_sprint.title
if next_sprint_week_key != next_sprint_title
  next_sprint.properties["Sprint name"].text_objects.rich_text_objects = next_sprint_week_key
  next_sprint.save
end

TASK_DATABASE_ID = "ここにタスクDBのIDを入れます"
task_db = Database.find TASK_DATABASE_ID
tp = task_db.properties
query = tp["Start date"].filter_on_or_before(sunday)
                        .and(tp["Sprint"].filter_is_empty)
                        .ascending(tp["Start date"])
checked_sprint = {next_sprint_week_key => next_sprint}
unsettled_tasks = task_db.query_database(query)
unsettled_tasks.each do |task|
  week_id = task.properties["week_id"].formula["string"]
  sprint = checked_sprint[week_id]
  unless sprint
    sprint = sprint_db.query_database(sp["Sprint name"].filter_equals(week_id)).first
    checked_sprint[week_id] = sprint
  end
  task.properties["Sprint"].relation = [sprint.id]
  print "set #{task.title} to #{sprint.title}\n"
  task.save
end

時刻トリガによる実装

昨日のタスクリストメール送信と同様に時刻トリガで作り直すにあたり、ネックになるのはトリガページが存在しなくなることです。このため、機能と同じ時刻トリガでページを作成することにします。昨日は Mail trigger というデータベースにしていましたが、それを流用するのでデータベース名を triggers に変更しました。

triggers データベース

1. 二つのプロパティを作成

スプリントが割り当てられていない Current になるべきタスクと、Next になるべきタスクを取得する必要があります。このため、Task データベースに二つのプロパティを準備しました。

  • WillChangeCurrentSprintTasks

      lets(
          currentSprint, prop("map").first().prop("Sprints").filter(current.prop("Sprint status") == "Current").first(),
          currentSprintWeekID, currentSprint.prop("week_id"),
          prop("Sprint").empty() && prop("week_id")== currentSprintWeekID
      )
    
  • WillChangeNextSprintTask

      lets(
          nextSprint, prop("map").first().prop("Sprints").filter(current.prop("Sprint status") == "Next").first(),
          nextSprintWeekID, nextSprint.prop("week_id"),
          prop("Sprint").empty() && prop("week_id")== nextSprintWeekID
      )
    

2. 時刻トリガの設定

前回は毎週月曜日 7:13 にトリガーを設定していました。トリガーは「〜ごと」で週を選びます。

トリガの設定

時刻はこれまでのものに合わせ 7:15 に駆動するようにしました。

月曜7:15に設定

3. トリガページの作成

まずは参照のきっかけとなる踏み台ページを作成します。これは昨日のメール送信と同じです。ここで重要なのは全ての参照のきっかけとなる map へのリンクを作っておくことです。

トリガページの作成

4. トリガーページから Current sprint, Next sprint を取得

この後、Current sprint と Next sprint は大量にアクセスするので、変数に定義しておきます。最初に sprints を定義した後で、さらに sprints から Current および Next の Sprint を取得します。前の変数の値を使うためには、一度変数定義を終了しないといけないため、定義部分を二つに分けています。

変数の設定

5. Next sprint のタイトル設定

Sprint が更新される前の状態の Sprints は以下のようになっています。Sprint 更新時には現在 Future となっている「スプリント 101」が Next となり、新しく「スプリント 102」が Future として作成されます。

現在の Sprints

まず、この Next スプリントの名前を変更しましょう。スクリプトではこの部分になります。

next_sprint_title = next_sprint.title
if next_sprint_week_key != next_sprint_title
  next_sprint.properties["Sprint name"].text_objects.rich_text_objects = next_sprint_week_key
  next_sprint.save
end

この処理は「ページを編集」で実施します。まず、Sprint データベースのうち、Sprint status が「Next」のものを抽出します。

Next sprint の抽出

このページの Sprint name を week_id の値で書き換えます。先ほど変数で定義した Next が使えます。

Next sprint の名前の設定

6. Current sprint の登録

スクリプトでは以下の部分になります。Notion API では複数個の設定は同時にできないので、繰り返しでスプリントを一つずつ設定しています。

TASK_DATABASE_ID = "ここにタスクDBのIDを入れます"
task_db = Database.find TASK_DATABASE_ID
tp = task_db.properties
query = tp["Start date"].filter_on_or_before(sunday)
                        .and(tp["Sprint"].filter_is_empty)
                        .ascending(tp["Start date"])
checked_sprint = {next_sprint_week_key => next_sprint}
unsettled_tasks = task_db.query_database(query)
unsettled_tasks.each do |task|
  week_id = task.properties["week_id"].formula["string"]
  sprint = checked_sprint[week_id]
  unless sprint
    sprint = sprint_db.query_database(sp["Sprint name"].filter_equals(week_id)).first
    checked_sprint[week_id] = sprint
  end
  task.properties["Sprint"].relation = [sprint.id]
  print "set #{task.title} to #{sprint.title}\n"
  task.save
end

一方で、データベースオートメーションではタスクごとの処理はできないので、Current sprint の設定と Next sprint の設定を別処理にします。まずは Current sprint の設定を行います。1. で準備した WillChangeCurrentSprintTasks にチェックが入った Tasks をフィルタします。

Current sprint にすべきタスクの抽出

選択されたタスクに対して Sprint を Current に設定します。

Current sprint の設定

7. Next sprint の登録

同様に 1. で準備した WillChangeNextSprintTasks にチェックが入った Tasks をフィルタします。

Next sprint にすべきタスクの抽出

選択されたタスクに対して Sprint を Next に設定します。

Next sprint の設定

おわりに

昨日に引き続き、crontab で実施していた作業がデータベースオートメーションで書き換えられました。あとは明日の 7:15 に無事に動作することを期待して、crontab の設定を外しておこうと思います。

hkob.notion.site