Aggiunge sulla dashboard invii il KPI mail/ora dagli invii SMTP confermati.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -16,6 +16,7 @@ class MailingsController < ApplicationController
|
|||||||
@paused_mailings = @window_paused_mailings
|
@paused_mailings = @window_paused_mailings
|
||||||
@completed_today = @mailings.count { |mailing| mailing.sent? && mailing.completed_at&.to_date == Time.zone.today }
|
@completed_today = @mailings.count { |mailing| mailing.sent? && mailing.completed_at&.to_date == Time.zone.today }
|
||||||
@failed_open = @mailings.sum(&:failed_count)
|
@failed_open = @mailings.sum(&:failed_count)
|
||||||
|
@hourly_throughput = Mailings::HourlyThroughput.new(current_project)
|
||||||
end
|
end
|
||||||
|
|
||||||
def new
|
def new
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -26,11 +26,21 @@
|
|||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<div class="grid grid-cols-2 gap-3 xl:grid-cols-4">
|
<div class="grid grid-cols-2 gap-3 md:grid-cols-3 xl:grid-cols-5">
|
||||||
<div class="<%= card_class %> p-4">
|
<div class="<%= card_class %> p-4">
|
||||||
<div class="text-xs uppercase tracking-wide text-zinc-500">In corso</div>
|
<div class="text-xs uppercase tracking-wide text-zinc-500">In corso</div>
|
||||||
<div class="mt-1 text-2xl font-semibold tabular-nums"><%= @active_mailings.size %></div>
|
<div class="mt-1 text-2xl font-semibold tabular-nums"><%= @active_mailings.size %></div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="<%= card_class %> p-4">
|
||||||
|
<div class="text-xs uppercase tracking-wide text-zinc-500">Mail / ora</div>
|
||||||
|
<div class="mt-1 text-2xl font-semibold tabular-nums"><%= @hourly_throughput.sent %></div>
|
||||||
|
<div class="mt-1 text-xs text-zinc-500">
|
||||||
|
<%= @hourly_throughput.recent_sent %> ultimi 15 min
|
||||||
|
<% if @hourly_throughput.failed.positive? %>
|
||||||
|
· <%= @hourly_throughput.failed %> scodate in errore
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="<%= card_class %> p-4">
|
<div class="<%= card_class %> p-4">
|
||||||
<div class="text-xs uppercase tracking-wide text-zinc-500">In pausa (fascia)</div>
|
<div class="text-xs uppercase tracking-wide text-zinc-500">In pausa (fascia)</div>
|
||||||
<div class="mt-1 text-2xl font-semibold tabular-nums"><%= @paused_mailings.size %></div>
|
<div class="mt-1 text-2xl font-semibold tabular-nums"><%= @paused_mailings.size %></div>
|
||||||
|
|||||||
@@ -16,6 +16,26 @@ class MailingsControllerTest < ActionDispatch::IntegrationTest
|
|||||||
assert_match(/Invii email/, response.body)
|
assert_match(/Invii email/, response.body)
|
||||||
end
|
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
|
test "creates a draft and opens audience filters" do
|
||||||
login_as users(:admin)
|
login_as users(:admin)
|
||||||
follow_redirect! if response.redirect?
|
follow_redirect! if response.redirect?
|
||||||
|
|||||||
@@ -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
|
||||||
Reference in New Issue
Block a user