diff --git a/app/controllers/mailings_controller.rb b/app/controllers/mailings_controller.rb index 62a9100..4a06b8a 100644 --- a/app/controllers/mailings_controller.rb +++ b/app/controllers/mailings_controller.rb @@ -16,7 +16,7 @@ class MailingsController < ApplicationController @paused_mailings = @window_paused_mailings @completed_today = @mailings.count { |mailing| mailing.sent? && mailing.completed_at&.to_date == Time.zone.today } @failed_open = @mailings.sum(&:failed_count) - @hourly_throughput = Mailings::HourlyThroughput.new(current_project) + @hourly_throughput = Mailings::HourlyThroughput.new(current_project, range: params[:history]) end def new diff --git a/app/services/mailings/hourly_throughput.rb b/app/services/mailings/hourly_throughput.rb index f9aa9ca..f7e57a6 100644 --- a/app/services/mailings/hourly_throughput.rb +++ b/app/services/mailings/hourly_throughput.rb @@ -1,11 +1,28 @@ class Mailings::HourlyThroughput HOUR = 1.hour RECENT = 15.minutes - HISTORY_HOURS = 12 + DEFAULT_RANGE = "12h" + RANGES = { + "12h" => { grain: :hour, periods: 12, short: "12h", label: "ultime 12 ore" }, + "24h" => { grain: :hour, periods: 24, short: "24h", label: "ultime 24 ore" }, + "7d" => { grain: :day, periods: 7, short: "7g", label: "ultima settimana" } + }.freeze - def initialize(project, now: Time.current) + def self.normalize_range(value) + key = value.to_s + RANGES.key?(key) ? key : DEFAULT_RANGE + end + + def initialize(project, now: Time.current, range: DEFAULT_RANGE) @project = project @now = now + @range_key = self.class.normalize_range(range) + end + + attr_reader :range_key + + def range + RANGES.fetch(@range_key) end def sent @@ -24,33 +41,54 @@ class Mailings::HourlyThroughput sent + failed end - def hourly_history - @hourly_history ||= begin - from = (@now - (HISTORY_HOURS - 1).hours).beginning_of_hour - keyed = recipients.sent - .where(sent_at: from..@now) - .pluck(:sent_at) - .each_with_object(Hash.new(0)) do |sent_at, counts| - counts[sent_at.in_time_zone.beginning_of_hour] += 1 - end - - HISTORY_HOURS.times.map do |index| - at = from + index.hours - { at: at, count: keyed[at] || 0 } - end - end + def history + @history ||= buckets_for_range end def history_total - hourly_history.sum { |bucket| bucket[:count] } + history.sum { |bucket| bucket[:count] } end def history_max - [hourly_history.map { |bucket| bucket[:count] }.max, 1].max + [history.map { |bucket| bucket[:count] }.max, 1].max end private + def buckets_for_range + from, step = history_origin + keyed = recipients.sent + .where(sent_at: from..@now) + .pluck(:sent_at) + .each_with_object(Hash.new(0)) do |sent_at, counts| + counts[bucket_at(sent_at)] += 1 + end + + range[:periods].times.map do |index| + at = from + (step * index) + { at: at, count: keyed[at] || 0, label: bucket_label(at) } + end + end + + def history_origin + if range[:grain] == :day + from = (@now.to_date - (range[:periods] - 1)).in_time_zone.beginning_of_day + [from, 1.day] + else + from = (@now - (range[:periods] - 1).hours).beginning_of_hour + [from, 1.hour] + end + end + + def bucket_at(sent_at) + time = sent_at.in_time_zone + range[:grain] == :day ? time.beginning_of_day : time.beginning_of_hour + end + + def bucket_label(at) + range[:grain] == :day ? at.strftime("%d/%m") : at.strftime("%H:%M") + end + def sent_since(window) recipients.sent.where(sent_at: (@now - window)..@now).count end diff --git a/app/views/mailings/dashboard.html.erb b/app/views/mailings/dashboard.html.erb index 9438549..090bd1f 100644 --- a/app/views/mailings/dashboard.html.erb +++ b/app/views/mailings/dashboard.html.erb @@ -42,16 +42,27 @@
-
Storico invio
- -
<%= @hourly_throughput.history_total %> inviate · ultime 12 ore
+
<%= @hourly_throughput.history_total %> inviate · <%= @hourly_throughput.range[:label] %>
In pausa (fascia)
diff --git a/test/controllers/mailings_controller_test.rb b/test/controllers/mailings_controller_test.rb index a46e75f..5a7d355 100644 --- a/test/controllers/mailings_controller_test.rb +++ b/test/controllers/mailings_controller_test.rb @@ -36,6 +36,30 @@ class MailingsControllerTest < ActionDispatch::IntegrationTest assert_match(/1 ultimi 15 min/, response.body) assert_select "div", text: "Storico invio" assert_match(/1 inviate · ultime 12 ore/, response.body) + assert_select "span", text: "12h" + assert_select "a", text: "24h" + assert_select "a", text: "7g" + end + + test "dashboard history range can switch to the last week" do + login_as users(:admin) + follow_redirect! if response.redirect? + + mailing = create_mailing(identity: @identity, audience: "to_send") + org = organizations(:acme) + mailing.mailing_recipients.create!( + organization: org, + contact: org.contacts.first, + email: org.email, + status: "sent", + sent_at: 2.days.ago + ) + + get dashboard_mailings_path(project_code: @project.code, history: "7d") + assert_response :success + assert_match(/1 inviate · ultima settimana/, response.body) + assert_select "span", text: "7g" + assert_select "a[href=?]", dashboard_mailings_path(project_code: @project.code, history: "24h") end test "creates a draft and opens audience filters" do diff --git a/test/services/mailings/hourly_throughput_test.rb b/test/services/mailings/hourly_throughput_test.rb index 1d6312b..f441850 100644 --- a/test/services/mailings/hourly_throughput_test.rb +++ b/test/services/mailings/hourly_throughput_test.rb @@ -55,7 +55,7 @@ class Mailings::HourlyThroughputTest < ActiveSupport::TestCase add_recipient status: "sent", sent_at: now - 13.hours, mailing: extra_mailing("Fuori finestra") stats = Mailings::HourlyThroughput.new(@project, now: now) - history = stats.hourly_history + history = stats.history assert_equal 12, history.size assert_equal now.beginning_of_hour, history.last[:at] @@ -66,6 +66,45 @@ class Mailings::HourlyThroughputTest < ActiveSupport::TestCase end end + test "includes the previous day when the range is 24 hours" do + now = Time.zone.parse("2026-09-07 12:40") + travel_to now do + add_recipient status: "sent", sent_at: now - 20.hours + + twelve = Mailings::HourlyThroughput.new(@project, now: now, range: "12h") + day = Mailings::HourlyThroughput.new(@project, now: now, range: "24h") + + assert_equal 0, twelve.history_total + assert_equal 24, day.history.size + assert_equal 1, day.history_total + end + end + + test "groups the last week by day" do + now = Time.zone.parse("2026-09-07 12:40") + travel_to now do + add_recipient status: "sent", sent_at: now - 3.days + add_recipient status: "sent", sent_at: now - 8.days, mailing: extra_mailing("Vecchia") + + stats = Mailings::HourlyThroughput.new(@project, now: now, range: "7d") + history = stats.history + + assert_equal 7, history.size + assert_equal now.beginning_of_day, history.last[:at] + assert_equal 1, history[-4][:count] + assert_equal 0, history.last[:count] + assert_equal 1, stats.history_total + assert_equal "7d", stats.range_key + end + end + + test "falls back to 12 hours for an unknown range" do + stats = Mailings::HourlyThroughput.new(@project, range: "nope") + + assert_equal "12h", stats.range_key + assert_equal 12, stats.history.size + end + private def extra_mailing(name = "Altra campagna")