From 390cb0e65722b4fbb44d49035f00a765d52e0578 Mon Sep 17 00:00:00 2001 From: Emiliano Frascaro Date: Mon, 7 Sep 2026 12:43:39 +0200 Subject: [PATCH] Aggiunge sulla dashboard invii il KPI mail/ora dagli invii SMTP confermati. Co-authored-by: Cursor --- app/controllers/mailings_controller.rb | 1 + app/services/mailings/hourly_throughput.rb | 35 ++++++++++ app/views/mailings/dashboard.html.erb | 12 +++- test/controllers/mailings_controller_test.rb | 20 ++++++ .../mailings/hourly_throughput_test.rb | 65 +++++++++++++++++++ 5 files changed, 132 insertions(+), 1 deletion(-) create mode 100644 app/services/mailings/hourly_throughput.rb create mode 100644 test/services/mailings/hourly_throughput_test.rb diff --git a/app/controllers/mailings_controller.rb b/app/controllers/mailings_controller.rb index 4dcbbed..62a9100 100644 --- a/app/controllers/mailings_controller.rb +++ b/app/controllers/mailings_controller.rb @@ -16,6 +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) end def new diff --git a/app/services/mailings/hourly_throughput.rb b/app/services/mailings/hourly_throughput.rb new file mode 100644 index 0000000..94040d9 --- /dev/null +++ b/app/services/mailings/hourly_throughput.rb @@ -0,0 +1,35 @@ +class Mailings::HourlyThroughput + HOUR = 1.hour + RECENT = 15.minutes + + def initialize(project, now: Time.current) + @project = project + @now = now + end + + def sent + @sent ||= sent_since(HOUR) + end + + def recent_sent + @recent_sent ||= sent_since(RECENT) + end + + def failed + @failed ||= recipients.failed.where(updated_at: (@now - HOUR)..@now).count + end + + def drained + sent + failed + end + + private + + def sent_since(window) + recipients.sent.where(sent_at: (@now - window)..@now).count + end + + def recipients + MailingRecipient.joins(:mailing).merge(Mailing.for_project(@project)) + end +end diff --git a/app/views/mailings/dashboard.html.erb b/app/views/mailings/dashboard.html.erb index 342065d..977465e 100644 --- a/app/views/mailings/dashboard.html.erb +++ b/app/views/mailings/dashboard.html.erb @@ -26,11 +26,21 @@ <% end %> -
+
In corso
<%= @active_mailings.size %>
+
+
Mail / ora
+
<%= @hourly_throughput.sent %>
+
+ <%= @hourly_throughput.recent_sent %> ultimi 15 min + <% if @hourly_throughput.failed.positive? %> + ยท <%= @hourly_throughput.failed %> scodate in errore + <% end %> +
+
In pausa (fascia)
<%= @paused_mailings.size %>
diff --git a/test/controllers/mailings_controller_test.rb b/test/controllers/mailings_controller_test.rb index 3f569c3..9b5bfcc 100644 --- a/test/controllers/mailings_controller_test.rb +++ b/test/controllers/mailings_controller_test.rb @@ -16,6 +16,26 @@ class MailingsControllerTest < ActionDispatch::IntegrationTest assert_match(/Invii email/, response.body) end + test "dashboard shows hourly send throughput" 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: 8.minutes.ago + ) + + get dashboard_mailings_path(project_code: @project.code) + assert_response :success + assert_select "div", text: "Mail / ora" + assert_match(/1 ultimi 15 min/, response.body) + end + test "creates a draft and opens audience filters" do login_as users(:admin) follow_redirect! if response.redirect? diff --git a/test/services/mailings/hourly_throughput_test.rb b/test/services/mailings/hourly_throughput_test.rb new file mode 100644 index 0000000..a086254 --- /dev/null +++ b/test/services/mailings/hourly_throughput_test.rb @@ -0,0 +1,65 @@ +require "test_helper" + +class Mailings::HourlyThroughputTest < ActiveSupport::TestCase + setup do + @project = projects(:matchlivetv) + @identity = create_mail_identity + @mailing = create_mailing(identity: @identity, audience: "all") + end + + test "counts confirmed sends in the last hour and last 15 minutes" do + add_recipient status: "sent", sent_at: 10.minutes.ago + add_recipient status: "sent", sent_at: 40.minutes.ago, mailing: extra_mailing + add_recipient status: "sent", sent_at: 2.hours.ago, mailing: extra_mailing("Vecchia") + add_recipient status: "queued", mailing: extra_mailing("In coda") + + stats = Mailings::HourlyThroughput.new(@project) + + assert_equal 2, stats.sent + assert_equal 1, stats.recent_sent + assert_equal 0, stats.failed + assert_equal 2, stats.drained + end + + test "counts failed recipients that left the queue in the last hour" do + recipient = add_recipient(status: "failed") + recipient.update_columns(updated_at: 5.minutes.ago) + + stale = add_recipient(status: "failed", mailing: extra_mailing) + stale.update_columns(updated_at: 2.hours.ago) + + stats = Mailings::HourlyThroughput.new(@project) + + assert_equal 0, stats.sent + assert_equal 1, stats.failed + assert_equal 1, stats.drained + end + + test "ignores sends from other projects" do + other = create_mailing(project: projects(:riskmeter), identity: @identity, audience: "all") + add_recipient status: "sent", sent_at: 5.minutes.ago, mailing: other + add_recipient status: "sent", sent_at: 5.minutes.ago + + stats = Mailings::HourlyThroughput.new(@project) + + assert_equal 1, stats.sent + assert_equal 1, stats.recent_sent + end + + private + + def extra_mailing(name = "Altra campagna") + create_mailing(identity: @identity, audience: "all", name: name) + end + + def add_recipient(status:, sent_at: nil, mailing: @mailing) + org = organizations(:acme) + mailing.mailing_recipients.create!( + organization: org, + contact: org.contacts.first, + email: org.email, + status: status, + sent_at: sent_at + ) + end +end