diff --git a/app/models/period_highlights.rb b/app/models/period_highlights.rb index 391b5b75c..9e62403d0 100644 --- a/app/models/period_highlights.rb +++ b/app/models/period_highlights.rb @@ -3,6 +3,8 @@ # so that we reuse the same summary for users with different time zones or different accesses as long as the activity # is the same. This is important to keep AI costs down. class PeriodHighlights < ApplicationRecord + has_many :weekly_highlights, class_name: "User::WeeklyHighlights", dependent: :destroy + class << self def create_or_find_for(collections, starts_at:, duration: 1.week) self.for(collections, starts_at:, duration:) || create_for(collections, starts_at:, duration:) diff --git a/app/models/user/highlights.rb b/app/models/user/highlights.rb index 43a6745f8..86a015c04 100644 --- a/app/models/user/highlights.rb +++ b/app/models/user/highlights.rb @@ -1,6 +1,10 @@ module User::Highlights extend ActiveSupport::Concern + included do + has_many :weekly_highlights, class_name: "User::WeeklyHighlights", dependent: :destroy + end + class_methods do def generate_all_weekly_highlights_later User::Highlights::GenerateAllJob.perform_later @@ -15,20 +19,31 @@ module User::Highlights def generate_weekly_highlights(date = Time.current) in_time_zone do - date = date - 1.day if date.sunday? - PeriodHighlights.create_or_find_for collections, starts_at: highlights_starts_at(date), duration: 1.week + weekly_highlights_for(date) || create_weekly_highlights_for(date) end end def weekly_highlights_for(date) in_time_zone do - PeriodHighlights.for(collections, starts_at: highlights_starts_at(date), duration: 1.week) + weekly_highlights.find_by(starts_at: highlights_starts_at(date))&.period_highlights end end private + def create_weekly_highlights_for(date) + date = date - 1.day if date.sunday? + + # Outside of transaction as generating highlights can be a slow operation + PeriodHighlights.create_or_find_for(collections, starts_at: highlights_starts_at(date), duration: 1.week).tap do |period_highlights| + if period_highlights + puts "Generating highlights for #{date}: #{period_highlights.inspect}" + weekly_highlights.create! period_highlights: period_highlights, starts_at: highlights_starts_at(date) + end + end + end + def highlights_starts_at(date = Time.current) date = date.in_time_zone(timezone) - date.beginning_of_week(:sunday) + date.beginning_of_week(:sunday).to_date end end diff --git a/app/models/user/weekly_highlights.rb b/app/models/user/weekly_highlights.rb new file mode 100644 index 000000000..502992886 --- /dev/null +++ b/app/models/user/weekly_highlights.rb @@ -0,0 +1,6 @@ +# This acts as a join table between users and period_highlights so that we can reuse the +# same highlights for different users. +class User::WeeklyHighlights < ApplicationRecord + belongs_to :user + belongs_to :period_highlights +end diff --git a/app/views/events/_weekly_highlights.html.erb b/app/views/events/_weekly_highlights.html.erb index 023bca756..e0bf21538 100644 --- a/app/views/events/_weekly_highlights.html.erb +++ b/app/views/events/_weekly_highlights.html.erb @@ -1,3 +1,4 @@ +

REQUESTING WEEKLY HIGHLIGHTS FOR <%= day_timeline.week_starts_at %>: <%= day_timeline.week_starts_at - 1.week%>

<% if day_timeline.has_weekly_highlights? %>

diff --git a/db/migrate/20251006091442_create_user_highlights.rb b/db/migrate/20251006091442_create_user_highlights.rb new file mode 100644 index 000000000..417b4c0df --- /dev/null +++ b/db/migrate/20251006091442_create_user_highlights.rb @@ -0,0 +1,13 @@ +class CreateUserHighlights < ActiveRecord::Migration[8.1] + def change + create_table :user_weekly_highlights do |t| + t.references :user, null: false, foreign_key: true + t.references :period_highlights, null: false, foreign_key: true + t.date :starts_at, null: false + + t.timestamps + + t.index %i[ user_id starts_at ], unique: true + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 0e8674c4a..136bbc782 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema[8.1].define(version: 2025_10_03_205559) do +ActiveRecord::Schema[8.1].define(version: 2025_10_06_091442) do create_table "accesses", force: :cascade do |t| t.datetime "accessed_at" t.integer "collection_id", null: false @@ -447,6 +447,17 @@ ActiveRecord::Schema[8.1].define(version: 2025_10_03_205559) do t.index ["user_id"], name: "index_user_settings_on_user_id" end + create_table "user_weekly_highlights", force: :cascade do |t| + t.datetime "created_at", null: false + t.integer "period_highlights_id", null: false + t.date "starts_at", null: false + t.datetime "updated_at", null: false + t.integer "user_id", null: false + t.index ["period_highlights_id"], name: "index_user_weekly_highlights_on_period_highlights_id" + t.index ["user_id", "starts_at"], name: "index_user_weekly_highlights_on_user_id_and_starts_at", unique: true + t.index ["user_id"], name: "index_user_weekly_highlights_on_user_id" + end + create_table "users", force: :cascade do |t| t.boolean "active", default: true, null: false t.datetime "created_at", null: false @@ -548,6 +559,8 @@ ActiveRecord::Schema[8.1].define(version: 2025_10_03_205559) do add_foreign_key "taggings", "cards" add_foreign_key "taggings", "tags" add_foreign_key "user_settings", "users" + add_foreign_key "user_weekly_highlights", "period_highlights", column: "period_highlights_id" + add_foreign_key "user_weekly_highlights", "users" add_foreign_key "watches", "cards" add_foreign_key "watches", "users" add_foreign_key "webhook_delinquency_trackers", "webhooks" diff --git a/db/schema_cache.yml b/db/schema_cache.yml index 90b7ccacb..eb63f2cf9 100644 --- a/db/schema_cache.yml +++ b/db/schema_cache.yml @@ -464,12 +464,12 @@ columns: - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column auto_increment: name: due_on - cast_type: !ruby/object:ActiveRecord::Type::Date + cast_type: &37 !ruby/object:ActiveRecord::Type::Date precision: scale: limit: timezone: - sql_type_metadata: !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata + sql_type_metadata: &38 !ruby/object:ActiveRecord::ConnectionAdapters::SqlTypeMetadata sql_type: date type: :date limit: @@ -1268,8 +1268,33 @@ columns: comment: - *9 - *18 + user_weekly_highlights: + - *5 + - *6 + - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: period_highlights_id + cast_type: *3 + sql_type_metadata: *4 + 'null': false + default: + default_function: + collation: + comment: + - !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + auto_increment: + name: starts_at + cast_type: *37 + sql_type_metadata: *38 + 'null': false + default: + default_function: + collation: + comment: + - *9 + - *18 users: - - &38 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + - &40 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column auto_increment: name: active cast_type: *32 @@ -1353,7 +1378,7 @@ columns: comment: - *6 - *9 - - &37 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column + - &39 !ruby/object:ActiveRecord::ConnectionAdapters::SQLite3::Column auto_increment: name: webhook_id cast_type: *3 @@ -1407,9 +1432,9 @@ columns: collation: comment: - *9 - - *37 + - *39 webhooks: - - *38 + - *40 - *24 - *5 - *6 @@ -1533,6 +1558,7 @@ primary_keys: taggings: id tags: id user_settings: id + user_weekly_highlights: id users: id watches: id webhook_delinquency_trackers: id @@ -1589,6 +1615,7 @@ data_sources: taggings: true tags: true user_settings: true + user_weekly_highlights: true users: true watches: true webhook_delinquency_trackers: true @@ -3071,6 +3098,56 @@ indexes: nulls_not_distinct: comment: valid: true + user_weekly_highlights: + - !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition + table: user_weekly_highlights + name: index_user_weekly_highlights_on_period_highlights_id + unique: false + columns: + - period_highlights_id + lengths: {} + orders: {} + opclasses: {} + where: + type: + using: + include: + nulls_not_distinct: + comment: + valid: true + - !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition + table: user_weekly_highlights + name: index_user_weekly_highlights_on_user_id + unique: false + columns: + - user_id + lengths: {} + orders: {} + opclasses: {} + where: + type: + using: + include: + nulls_not_distinct: + comment: + valid: true + - !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition + table: user_weekly_highlights + name: index_user_weekly_highlights_on_user_id_and_starts_at + unique: true + columns: + - user_id + - starts_at + lengths: {} + orders: {} + opclasses: {} + where: + type: + using: + include: + nulls_not_distinct: + comment: + valid: true users: - !ruby/object:ActiveRecord::ConnectionAdapters::IndexDefinition table: users @@ -3222,4 +3299,4 @@ indexes: comment: valid: true workflows: [] -version: 20251003205559 +version: 20251006091442 diff --git a/script/migrations/backfill_weekly_highlights.rb b/script/migrations/backfill_weekly_highlights.rb index 3653fe9f8..c73499276 100644 --- a/script/migrations/backfill_weekly_highlights.rb +++ b/script/migrations/backfill_weekly_highlights.rb @@ -2,11 +2,12 @@ require_relative "../config/environment" -WEEKS_TO_BACKFILL = 10 +WEEKS_TO_BACKFILL = 3 ActiveRecord::Base.logger = Logger.new(File::NULL) ApplicationRecord.with_each_tenant do |tenant| + PeriodHighlights.destroy_all WEEKS_TO_BACKFILL.times do |index| User.active.find_each do |user| user.generate_weekly_highlights(Time.current - index.weeks)