Add scheduled DemoFamilyRefreshJob to rebuild demo data daily (#1217)

* Add scheduled demo family refresh job

Rebuild demo data daily at 5am UTC by anonymizing and enqueueing deletion of the existing demo family while immediately generating new sample data. Add super-admin email notifications with 24-hour session and signup metrics, plus tests for the new job and mailer.

* Delete demo monitoring key before family refresh

Ensure DemoFamilyRefreshJob removes ApiKey::DEMO_MONITORING_KEY from the old demo family before enqueueing async family destruction and generating replacement sample data. Adds a regression assertion that the key is gone before generator execution.
This commit is contained in:
Juan José Mata
2026-03-17 19:41:26 +01:00
committed by GitHub
parent 26aa260fb1
commit 53f478a77b
6 changed files with 195 additions and 0 deletions

View File

@@ -0,0 +1,80 @@
class DemoFamilyRefreshJob < ApplicationJob
queue_as :scheduled
def perform
period_end = Time.current
period_start = period_end - 24.hours
demo_email = Rails.application.config_for(:demo).fetch("email")
demo_user = User.find_by(email: demo_email)
old_family = demo_user&.family
old_family_session_count = sessions_count_for(old_family, period_start:, period_end:)
newly_created_families_count = Family.where(created_at: period_start...period_end).count
if old_family
delete_old_family_monitoring_key!(old_family)
anonymize_family_emails!(old_family)
DestroyJob.perform_later(old_family)
end
Demo::Generator.new.generate_default_data!(skip_clear: true, email: demo_email)
notify_super_admins!(
old_family:,
old_family_session_count:,
newly_created_families_count:,
period_start:,
period_end:
)
end
private
def sessions_count_for(family, period_start:, period_end:)
return 0 unless family
Session
.joins(:user)
.where(users: { family_id: family.id })
.where(created_at: period_start...period_end)
.distinct
.count(:id)
end
def delete_old_family_monitoring_key!(family)
ApiKey
.where(user_id: family.users.select(:id), display_key: ApiKey::DEMO_MONITORING_KEY)
.delete_all
end
def anonymize_family_emails!(family)
family.users.find_each do |user|
user.update_columns(
email: deleted_email_for(user),
unconfirmed_email: nil,
updated_at: Time.current
)
end
end
def deleted_email_for(user)
local_part, domain = user.email.split("@", 2)
"#{local_part}+deleting-#{user.id}-#{SecureRandom.hex(4)}@#{domain}"
end
def notify_super_admins!(old_family:, old_family_session_count:, newly_created_families_count:, period_start:, period_end:)
User.super_admin.find_each do |super_admin|
DemoFamilyRefreshMailer.with(
super_admin:,
old_family_id: old_family&.id,
old_family_name: old_family&.name,
old_family_session_count:,
newly_created_families_count:,
period_start:,
period_end:
).completed.deliver_later
end
end
end

View File

@@ -0,0 +1,16 @@
class DemoFamilyRefreshMailer < ApplicationMailer
def completed
@super_admin = params.fetch(:super_admin)
@old_family_id = params[:old_family_id]
@old_family_name = params[:old_family_name]
@old_family_session_count = params.fetch(:old_family_session_count)
@newly_created_families_count = params.fetch(:newly_created_families_count)
@period_start = params.fetch(:period_start)
@period_end = params.fetch(:period_end)
mail(
to: @super_admin.email,
subject: "Demo family refresh completed"
)
end
end

View File

@@ -0,0 +1,6 @@
Demo family refresh has completed.
Period (UTC): <%= @period_start.iso8601 %> to <%= @period_end.iso8601 %>
Old demo family: <%= @old_family_name || "not found" %><% if @old_family_id %> (ID: <%= @old_family_id %>)<% end %>
Unique login sessions for old demo family in period: <%= @old_family_session_count %>
New family accounts created in period: <%= @newly_created_families_count %>

View File

@@ -36,3 +36,9 @@ clean_inactive_families:
class: "InactiveFamilyCleanerJob"
queue: "scheduled"
description: "Archives and destroys families that expired their trial without subscribing (managed mode only)"
refresh_demo_family:
cron: "0 5 * * *" # daily at 5:00 AM UTC
class: "DemoFamilyRefreshJob"
queue: "scheduled"
description: "Refreshes demo family data and emails super admins with daily usage summary"

View File

@@ -0,0 +1,64 @@
require "test_helper"
class DemoFamilyRefreshJobTest < ActiveJob::TestCase
setup do
@demo_email = "demo-user@example.com"
Rails.application.stubs(:config_for).with(:demo).returns({ "email" => @demo_email })
@demo_family = Family.create!(name: "Demo Family")
@demo_user = @demo_family.users.create!(
first_name: "Demo",
last_name: "Admin",
email: @demo_email,
password: "password123",
role: :admin,
onboarded_at: Time.current,
ai_enabled: true,
show_sidebar: true,
show_ai_sidebar: true,
ui_layout: :dashboard
)
@super_admin = families(:dylan_family).users.create!(
first_name: "Super",
last_name: "Admin",
email: "super-admin@example.com",
password: "password123",
role: :super_admin,
onboarded_at: Time.current,
ai_enabled: true,
show_sidebar: true,
show_ai_sidebar: true,
ui_layout: :dashboard
)
end
test "anonymizes old demo user email, enqueues deletion, regenerates data, and notifies super admins" do
travel_to Time.utc(2026, 1, 1, 5, 0, 0) do
Session.create!(user: @demo_user)
Family.create!(name: "New Family Today", created_at: 6.hours.ago)
Family.create!(name: "Old Family", created_at: 2.days.ago)
@demo_user.api_keys.create!(
name: "monitoring",
key: ApiKey::DEMO_MONITORING_KEY,
scopes: [ "read" ],
source: "monitoring"
)
generator = mock
generator.expects(:generate_default_data!).with(skip_clear: true, email: @demo_email) do
assert_nil ApiKey.find_by(display_key: ApiKey::DEMO_MONITORING_KEY)
end
Demo::Generator.expects(:new).returns(generator)
assert_enqueued_with(job: DestroyJob, args: [ @demo_family ]) do
assert_enqueued_jobs 2, only: ActionMailer::MailDeliveryJob do
DemoFamilyRefreshJob.perform_now
end
end
assert_not_equal @demo_email, @demo_user.reload.email
assert_match(/\+deleting-/, @demo_user.email)
end
end
end

View File

@@ -0,0 +1,23 @@
require "test_helper"
class DemoFamilyRefreshMailerTest < ActionMailer::TestCase
test "completed email includes summary metrics" do
period_start = Time.utc(2026, 1, 1, 5, 0, 0)
period_end = period_start + 24.hours
email = DemoFamilyRefreshMailer.with(
super_admin: users(:sure_support_staff),
old_family_id: families(:empty).id,
old_family_name: families(:empty).name,
old_family_session_count: 12,
newly_created_families_count: 4,
period_start:,
period_end:
).completed
assert_equal [ "support@sure.am" ], email.to
assert_equal "Demo family refresh completed", email.subject
assert_includes email.body.to_s, "Unique login sessions for old demo family in period: 12"
assert_includes email.body.to_s, "New family accounts created in period: 4"
end
end