Skip to content

Commit

Permalink
Implement traffic drop notifications (#4300)
Browse files Browse the repository at this point in the history
* Expose current visitors 12h aggregate

* Remove unused site association

* Distinct drop/spike notification factories

* Rename modules accordingly + implement drop handling

* Rename periodic oban service

* Implement drop email

* Rest of the owl

* Update changelog

* Update moduledoc

* Update moduledoc

* Min threshold to 1

* Threshold 1

* Remove merge artifact

* Put panel behind a feature flag

* Format
  • Loading branch information
aerosol authored and RobertJoonas committed Jul 12, 2024
1 parent ab811ed commit 8c183fc
Show file tree
Hide file tree
Showing 20 changed files with 739 additions and 406 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ All notable changes to this project will be documented in this file.
- Icons for browsers plausible/analytics#4239
- Automatic custom property selection in the dashboard Properties report
- Add `does_not_contain` filter support to dashboard
- Traffic drop notifications plausible/analytics#4300

### Removed
- Deprecate `ECTO_IPV6` and `ECTO_CH_IPV6` env vars in CE plausible/analytics#4245
Expand Down
2 changes: 1 addition & 1 deletion config/runtime.exs
Original file line number Diff line number Diff line change
Expand Up @@ -528,7 +528,7 @@ base_cron = [
# Daily at midday
{"0 12 * * *", Plausible.Workers.SendCheckStatsEmails},
# Every 15 minutes
{"*/15 * * * *", Plausible.Workers.SpikeNotifier},
{"*/15 * * * *", Plausible.Workers.TrafficChangeNotifier},
# Every day at 1am
{"0 1 * * *", Plausible.Workers.CleanInvitations},
# Every 2 hours
Expand Down
1 change: 0 additions & 1 deletion lib/plausible/site.ex
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ defmodule Plausible.Site do
has_one :google_auth, GoogleAuth
has_one :weekly_report, Plausible.Site.WeeklyReport
has_one :monthly_report, Plausible.Site.MonthlyReport
has_one :spike_notification, Plausible.Site.SpikeNotification
has_one :ownership, Plausible.Site.Membership, where: [role: :owner]
has_one :owner, through: [:ownership, :user]

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
defmodule Plausible.Site.SpikeNotification do
defmodule Plausible.Site.TrafficChangeNotification do
@moduledoc """
Configuration schema for site-specific traffic change notifications.
"""
use Ecto.Schema
import Ecto.Changeset

# legacy table name since traffic drop notifications were introduced
schema "spike_notifications" do
field :recipients, {:array, :string}
field :threshold, :integer
Expand All @@ -14,8 +18,9 @@ defmodule Plausible.Site.SpikeNotification do

def changeset(schema, attrs) do
schema
|> cast(attrs, [:site_id, :recipients, :threshold])
|> validate_required([:site_id, :recipients, :threshold])
|> cast(attrs, [:site_id, :recipients, :threshold, :type])
|> validate_required([:site_id, :recipients, :threshold, :type])
|> validate_number(:threshold, greater_than_or_equal_to: 1)
|> unique_constraint([:site_id, :type])
end

Expand Down
4 changes: 2 additions & 2 deletions lib/plausible/stats.ex
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,9 @@ defmodule Plausible.Stats do
Timeseries.timeseries(site, query, metrics)
end

def current_visitors(site) do
def current_visitors(site, shift \\ [minutes: -5]) do
include_sentry_replay_info()
CurrentVisitors.current_visitors(site)
CurrentVisitors.current_visitors(site, shift)
end

on_ee do
Expand Down
4 changes: 4 additions & 0 deletions lib/plausible/stats/clickhouse.ex
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,10 @@ defmodule Plausible.Stats.Clickhouse do
Plausible.Stats.current_visitors(site)
end

def current_visitors_12h(site) do
Plausible.Stats.current_visitors(site, hours: -12)
end

def has_pageviews?(site) do
ClickhouseRepo.exists?(
from(e in "events_v2",
Expand Down
4 changes: 2 additions & 2 deletions lib/plausible/stats/current_visitors.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ defmodule Plausible.Stats.CurrentVisitors do
use Plausible.ClickhouseRepo
use Plausible.Stats.SQL.Fragments

def current_visitors(site) do
def current_visitors(site, shift \\ [minutes: -5]) do
first_datetime =
NaiveDateTime.utc_now()
|> Timex.shift(minutes: -5)
|> Timex.shift(shift)
|> NaiveDateTime.truncate(:second)

ClickhouseRepo.one(
Expand Down
62 changes: 41 additions & 21 deletions lib/plausible_web/controllers/site_controller.ex
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,10 @@ defmodule PlausibleWeb.SiteController do
site: site,
weekly_report: Repo.get_by(Plausible.Site.WeeklyReport, site_id: site.id),
monthly_report: Repo.get_by(Plausible.Site.MonthlyReport, site_id: site.id),
spike_notification: Repo.get_by(Plausible.Site.SpikeNotification, site_id: site.id),
spike_notification:
Repo.get_by(Plausible.Site.TrafficChangeNotification, site_id: site.id, type: :spike),
drop_notification:
Repo.get_by(Plausible.Site.TrafficChangeNotification, site_id: site.id, type: :drop),
dogfood_page_path: "/:dashboard/settings/email-reports",
layout: {PlausibleWeb.LayoutView, "site_settings.html"}
)
Expand Down Expand Up @@ -472,68 +475,85 @@ defmodule PlausibleWeb.SiteController do
|> redirect(external: Routes.site_path(conn, :settings_email_reports, site.domain))
end

def enable_spike_notification(conn, _params) do
def enable_traffic_change_notification(conn, %{"type" => type}) do
site = conn.assigns[:site]

res =
Plausible.Site.SpikeNotification.changeset(%Plausible.Site.SpikeNotification{}, %{
site_id: site.id,
threshold: 10,
recipients: [conn.assigns[:current_user].email]
})
Plausible.Site.TrafficChangeNotification.changeset(
%Plausible.Site.TrafficChangeNotification{},
%{
site_id: site.id,
type: type,
threshold: if(type == "spike", do: 10, else: 1),
recipients: [conn.assigns[:current_user].email]
}
)
|> Repo.insert()

case res do
{:ok, _} ->
conn
|> put_flash(:success, "You will a notification with traffic spikes going forward")
|> put_flash(:success, "Traffic #{type} notifications enabled")
|> redirect(external: Routes.site_path(conn, :settings_email_reports, site.domain))

{:error, _} ->
conn
|> put_flash(:error, "Unable to create a spike notification")
|> put_flash(:error, "Unable to create a #{type} notification")
|> redirect(external: Routes.site_path(conn, :settings_email_reports, site.domain))
end
end

def disable_spike_notification(conn, _params) do
def disable_traffic_change_notification(conn, %{"type" => type}) do
site = conn.assigns[:site]
Repo.delete_all(from(mr in Plausible.Site.SpikeNotification, where: mr.site_id == ^site.id))

Repo.delete_all(
from(mr in Plausible.Site.TrafficChangeNotification,
where: mr.site_id == ^site.id and mr.type == ^type
)
)

conn
|> put_flash(:success, "Spike notification disabled")
|> put_flash(:success, "Traffic #{type} notifications disabled")
|> redirect(external: Routes.site_path(conn, :settings_email_reports, site.domain))
end

def update_spike_notification(conn, %{"spike_notification" => params}) do
def update_traffic_change_notification(conn, %{
"traffic_change_notification" => params,
"type" => type
}) do
site = conn.assigns[:site]
notification = Repo.get_by(Plausible.Site.SpikeNotification, site_id: site.id)

Plausible.Site.SpikeNotification.changeset(notification, params)
notification =
Repo.get_by(Plausible.Site.TrafficChangeNotification, site_id: site.id, type: type)

Plausible.Site.TrafficChangeNotification.changeset(notification, params)
|> Repo.update!()

conn
|> put_flash(:success, "Notification settings updated")
|> redirect(external: Routes.site_path(conn, :settings_email_reports, site.domain))
end

def add_spike_notification_recipient(conn, %{"recipient" => recipient}) do
def add_traffic_change_notification_recipient(conn, %{"recipient" => recipient, "type" => type}) do
site = conn.assigns[:site]

Repo.get_by(Plausible.Site.SpikeNotification, site_id: site.id)
|> Plausible.Site.SpikeNotification.add_recipient(recipient)
Repo.get_by(Plausible.Site.TrafficChangeNotification, site_id: site.id, type: type)
|> Plausible.Site.TrafficChangeNotification.add_recipient(recipient)
|> Repo.update!()

conn
|> put_flash(:success, "Added #{recipient} as a recipient for the traffic spike notification")
|> redirect(external: Routes.site_path(conn, :settings_email_reports, site.domain))
end

def remove_spike_notification_recipient(conn, %{"recipient" => recipient}) do
def remove_traffic_change_notification_recipient(conn, %{
"recipient" => recipient,
"type" => type
}) do
site = conn.assigns[:site]

Repo.get_by(Plausible.Site.SpikeNotification, site_id: site.id)
|> Plausible.Site.SpikeNotification.remove_recipient(recipient)
Repo.get_by(Plausible.Site.TrafficChangeNotification, site_id: site.id, type: type)
|> Plausible.Site.TrafficChangeNotification.remove_recipient(recipient)
|> Repo.update!()

conn
Expand Down
13 changes: 13 additions & 0 deletions lib/plausible_web/email.ex
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,19 @@ defmodule PlausibleWeb.Email do
})
end

def drop_notification(email, site, current_visitors, dashboard_link, settings_link) do
base_email()
|> to(email)
|> tag("drop-notification")
|> subject("Traffic Drop on #{site.domain}")
|> render("drop_notification.html", %{
site: site,
current_visitors: current_visitors,
dashboard_link: dashboard_link,
settings_link: settings_link
})
end

def over_limit_email(user, usage, suggested_plan) do
priority_email()
|> to(user)
Expand Down
22 changes: 15 additions & 7 deletions lib/plausible_web/router.ex
Original file line number Diff line number Diff line change
Expand Up @@ -331,17 +331,25 @@ defmodule PlausibleWeb.Router do
SiteController,
:remove_monthly_report_recipient

post "/sites/:website/spike-notification/enable", SiteController, :enable_spike_notification
post "/sites/:website/spike-notification/disable", SiteController, :disable_spike_notification
put "/sites/:website/spike-notification", SiteController, :update_spike_notification
post "/sites/:website/traffic-change-notification/:type/enable",
SiteController,
:enable_traffic_change_notification

post "/sites/:website/traffic-change-notification/:type/disable",
SiteController,
:disable_traffic_change_notification

put "/sites/:website/traffic-change-notification/:type",
SiteController,
:update_traffic_change_notification

post "/sites/:website/spike-notification/recipients",
post "/sites/:website/traffic-change-notification/:type/recipients",
SiteController,
:add_spike_notification_recipient
:add_traffic_change_notification_recipient

delete "/sites/:website/spike-notification/recipients/:recipient",
delete "/sites/:website/traffic-change-notification/:type/recipients/:recipient",
SiteController,
:remove_spike_notification_recipient
:remove_traffic_change_notification_recipient

get "/sites/:website/shared-links/new", SiteController, :new_shared_link
post "/sites/:website/shared-links", SiteController, :create_shared_link
Expand Down
9 changes: 9 additions & 0 deletions lib/plausible_web/templates/email/drop_notification.html.eex
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
We've recorded <%= @current_visitors %> visitors on <%= link(@site.domain, to: "https://" <> @site.domain) %> in the last 12 hours.

<%= if @dashboard_link do %>
<br /><br />
View dashboard: <%= link(@dashboard_link, to: @dashboard_link) %>
<br /><br />
Something looks off? Please use our integration testing tool to verify that Plausible has been integrated correctly:
<%= link(@settings_link, to: @settings_link) %>
<% end %>
Loading

0 comments on commit 8c183fc

Please sign in to comment.