Packages
phoenix_kit
1.7.80
1.7.208
1.7.207
1.7.206
1.7.205
1.7.204
1.7.203
1.7.202
1.7.201
1.7.200
1.7.199
1.7.198
1.7.197
1.7.196
1.7.194
1.7.193
1.7.192
1.7.191
1.7.190
1.7.189
1.7.187
1.7.186
1.7.185
1.7.184
1.7.183
1.7.182
1.7.181
1.7.180
1.7.179
1.7.178
1.7.177
1.7.176
1.7.175
1.7.174
1.7.173
1.7.172
1.7.171
1.7.170
1.7.169
1.7.168
1.7.167
1.7.166
1.7.165
1.7.164
1.7.162
1.7.161
1.7.160
1.7.159
1.7.157
1.7.156
1.7.155
1.7.154
1.7.153
1.7.152
1.7.151
1.7.150
1.7.149
1.7.146
1.7.145
1.7.144
1.7.143
1.7.138
1.7.133
1.7.132
1.7.131
1.7.130
1.7.128
1.7.126
1.7.125
1.7.121
1.7.120
1.7.119
1.7.118
1.7.117
1.7.116
1.7.115
1.7.114
1.7.113
1.7.112
1.7.111
1.7.110
1.7.109
1.7.108
1.7.107
1.7.106
1.7.105
1.7.104
1.7.103
1.7.102
1.7.101
1.7.100
1.7.99
1.7.98
1.7.97
1.7.96
1.7.95
1.7.94
1.7.93
1.7.92
1.7.91
1.7.90
1.7.89
1.7.88
1.7.87
1.7.86
1.7.85
1.7.84
1.7.83
1.7.82
1.7.81
1.7.80
1.7.79
1.7.78
1.7.77
1.7.76
1.7.75
1.7.74
1.7.71
1.7.70
1.7.69
1.7.66
1.7.65
1.7.64
1.7.63
1.7.62
1.7.61
1.7.59
1.7.58
1.7.57
1.7.56
1.7.55
1.7.54
1.7.53
1.7.52
1.7.51
1.7.49
1.7.44
1.7.43
1.7.42
1.7.41
1.7.39
1.7.38
1.7.37
1.7.36
1.7.34
1.7.33
1.7.31
1.7.30
1.7.29
1.7.28
1.7.27
1.7.26
1.7.25
1.7.24
1.7.23
1.7.22
1.7.21
1.7.20
1.7.19
1.7.18
1.7.17
1.7.16
1.7.15
1.7.14
1.7.13
1.7.12
1.7.11
1.7.10
1.7.9
1.7.8
1.7.7
1.7.6
1.7.5
1.7.4
1.7.3
1.7.2
1.7.1
1.7.0
1.6.20
1.6.19
1.6.18
1.6.17
1.6.16
1.6.15
1.6.14
1.6.13
1.6.12
1.6.11
1.6.10
1.6.9
1.6.8
1.6.7
1.6.6
1.6.5
1.6.4
1.6.3
1.5.2
1.5.1
1.5.0
1.4.9
1.4.8
1.4.7
1.4.6
1.4.5
1.4.4
1.4.3
1.4.2
1.4.1
1.4.0
1.3.2
1.3.1
1.3.0
1.2.10
1.2.9
1.2.8
1.2.7
1.2.5
1.2.4
1.2.2
1.2.1
1.2.0
1.1.0
1.0.0
A foundation for building Elixir Phoenix apps — SaaS, social networks, ERP systems, marketplaces, and more
Current section
Files
Jump to
Current section
Files
lib/modules/emails/web/metrics.ex
defmodule PhoenixKit.Modules.Emails.Web.Metrics do
@moduledoc """
LiveView for email metrics and analytics dashboard.
Provides comprehensive analytics visualization for email campaigns including:
- **Key Performance Indicators**: Send, delivery, bounce, complaint rates
- **Trend Analysis**: Time-series charts for performance tracking
- **Geographic Distribution**: Map showing engagement by location
- **Provider Performance**: Comparison of different email providers
- **Campaign Analytics**: Performance breakdown by campaign and template
## Features
- **Interactive Charts**: Built with Chart.js for responsive visualizations
- **Date Range Filtering**: Custom date ranges for detailed analysis
- **Export Functionality**: Download charts and data as PNG/CSV
- **Responsive Design**: Mobile-friendly dashboard layout
- **Performance Metrics**: Delivery rates, open rates, click-through rates
- **Bounce Analysis**: Hard vs soft bounce categorization
- **Complaint Tracking**: Spam complaint monitoring and alerts
## Route
This LiveView is mounted at `{prefix}/admin/emails/dashboard` and requires
appropriate admin permissions.
Note: `{prefix}` is your configured PhoenixKit URL prefix (default: `/phoenix_kit`).
## Usage
# In your Phoenix router
live "/email-metrics", PhoenixKitWeb.Live.Modules.Emails.EmailMetricsLive, :index
## Permissions
Access is restricted to users with admin or owner roles in PhoenixKit.
"""
use PhoenixKitWeb, :live_view
alias PhoenixKit.Modules.Emails
alias PhoenixKit.Settings
alias PhoenixKit.Utils.Date, as: UtilsDate
alias PhoenixKit.Utils.Number, as: UtilsNumber
alias PhoenixKit.Utils.Routes
## --- Lifecycle Callbacks ---
@impl true
def mount(_params, _session, socket) do
# Check if email is enabled
if Emails.enabled?() do
# Get project title from settings
project_title = Settings.get_project_title()
socket =
socket
|> assign(:project_title, project_title)
|> assign(:loading, true)
|> assign(:period, :last_7_days)
|> assign(:custom_range, false)
|> assign(:start_date, nil)
|> assign(:end_date, nil)
|> assign(:metrics, %{})
|> assign(:charts_data, %{
delivery_trend: %{labels: [], datasets: []},
engagement: %{labels: [], datasets: []}
})
|> assign(:last_updated, UtilsDate.utc_now())
|> load_metrics_data()
{:ok, socket}
else
{:ok,
socket
|> put_flash(:error, "Email is not enabled")
|> push_navigate(to: Routes.path("/admin"))}
end
end
## --- Event Handlers ---
@impl true
def handle_event("refresh", _params, socket) do
{:noreply,
socket
|> assign(:loading, true)
|> load_metrics_data()}
end
@impl true
def handle_event("change_period", %{"period" => period}, socket) do
period_atom = String.to_atom(period)
{:noreply,
socket
|> assign(:period, period_atom)
|> assign(:custom_range, false)
|> assign(:loading, true)
|> load_metrics_data()}
end
@impl true
def handle_event("toggle_custom_range", _params, socket) do
{:noreply,
socket
|> assign(:custom_range, !socket.assigns.custom_range)}
end
@impl true
def handle_event(
"apply_custom_range",
%{"start_date" => start_date, "end_date" => end_date},
socket
) do
case {Date.from_iso8601(start_date), Date.from_iso8601(end_date)} do
{{:ok, start_date}, {:ok, end_date}} ->
{:noreply,
socket
|> assign(:period, :custom)
|> assign(:start_date, start_date)
|> assign(:end_date, end_date)
|> assign(:custom_range, false)
|> assign(:loading, true)
|> load_metrics_data()}
_ ->
{:noreply,
socket
|> put_flash(:error, "Invalid date range")}
end
end
@impl true
def handle_event("export_metrics", %{"format" => format}, socket) do
case format do
"csv" ->
csv_content = export_metrics_csv(socket.assigns.metrics)
filename = "email_metrics_#{Date.utc_today()}.csv"
{:noreply,
socket
|> push_event("download", %{
filename: filename,
content: csv_content,
mime_type: "text/csv"
})}
"json" ->
json_content = Jason.encode!(socket.assigns.metrics, pretty: true)
filename = "email_metrics_#{Date.utc_today()}.json"
{:noreply,
socket
|> push_event("download", %{
filename: filename,
content: json_content,
mime_type: "application/json"
})}
_ ->
{:noreply,
socket
|> put_flash(:error, "Unsupported export format")}
end
end
defp load_metrics_data(socket) do
period = determine_period(socket.assigns)
metrics =
Emails.get_system_stats(period)
|> Map.merge(load_additional_metrics(period))
charts_data = prepare_charts_data(metrics, period)
socket =
socket
|> assign(:metrics, metrics)
|> assign(:charts_data, charts_data)
|> assign(:loading, false)
# Push chart data to JavaScript if the socket is connected
if connected?(socket) do
socket |> push_event("email-charts-update", %{charts: charts_data})
else
socket
end
end
defp determine_period(assigns) do
if assigns.period == :custom and assigns.start_date and assigns.end_date do
{:date_range, assigns.start_date, assigns.end_date}
else
assigns.period
end
end
defp load_additional_metrics(period) do
%{
by_provider: Emails.get_provider_performance(period),
today_count: get_today_count()
}
end
defp get_today_count do
today_start = UtilsDate.utc_now() |> DateTime.to_date() |> DateTime.new!(~T[00:00:00])
now = UtilsDate.utc_now()
case Emails.get_system_stats(
{:date_range, DateTime.to_date(today_start), DateTime.to_date(now)}
) do
%{total_sent: count} -> count
_ -> 0
end
end
defp prepare_charts_data(metrics, period) do
# Get daily delivery trends for the chart
daily_trends = Emails.get_daily_delivery_trends(period)
charts_data = %{
delivery_trend: %{
labels: Map.get(daily_trends, :labels, []),
datasets: [
%{
label: "Delivered",
data: Map.get(daily_trends, :delivered, []),
borderColor: "rgb(34, 197, 94)",
backgroundColor: "rgba(34, 197, 94, 0.1)",
tension: 0.1,
fill: true
},
%{
label: "Bounced",
data: Map.get(daily_trends, :bounced, []),
borderColor: "rgb(239, 68, 68)",
backgroundColor: "rgba(239, 68, 68, 0.1)",
tension: 0.1,
fill: true
}
]
},
engagement: %{
labels: ["Opens", "Clicks", "Bounces", "Complaints"],
datasets: [
%{
data: [
metrics.opened || 0,
metrics.clicked || 0,
metrics.bounced || 0,
metrics.complained || 0
],
backgroundColor: [
"rgb(59, 130, 246)",
"rgb(34, 197, 94)",
"rgb(251, 191, 36)",
"rgb(239, 68, 68)"
]
}
]
}
}
charts_data
end
defp export_metrics_csv(metrics) do
headers = "Metric,Value\n"
rows = [
"Total Sent,#{metrics.total_sent || 0}",
"Delivered,#{metrics.delivered || 0}",
"Bounced,#{metrics.bounced || 0}",
"Delivery Rate,#{metrics.delivery_rate || 0}%",
"Bounce Rate,#{metrics.bounce_rate || 0}%",
"Open Rate,#{metrics.open_rate || 0}%",
"Click Rate,#{metrics.click_rate || 0}%"
]
headers <> Enum.join(rows, "\n")
end
end