Skip to content

Commit 54f7071

Browse files
ukutahtjerodsanto
andcommitted
Add basic spike notifications
Co-authored-by: Jerod Santo <[email protected]>
1 parent 838e954 commit 54f7071

File tree

12 files changed

+154
-6
lines changed

12 files changed

+154
-6
lines changed

config/config.exs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,9 @@ extra_cron = [
118118
# Daily at midday
119119
{"0 12 * * *", Plausible.Workers.SendCheckStatsEmails},
120120
# Every 10 minutes
121-
{"*/10 * * * *", Plausible.Workers.ProvisionSslCertificates}
121+
{"*/10 * * * *", Plausible.Workers.ProvisionSslCertificates},
122+
# Every 15 minutes
123+
{"*/15 * * * *", Plausible.Workers.SpikeNotifier}
122124
]
123125

124126
base_queues = [rotate_salts: 1]
@@ -130,7 +132,8 @@ extra_queues = [
130132
site_setup_emails: 1,
131133
trial_notification_emails: 1,
132134
schedule_email_reports: 1,
133-
send_email_reports: 1
135+
send_email_reports: 1,
136+
spike_notifications: 1
134137
]
135138

136139
config :plausible, Oban,

config/releases.exs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,9 @@ extra_cron = [
157157
# Daily at midday
158158
{"0 12 * * *", Plausible.Workers.SendCheckStatsEmails},
159159
# Every 10 minutes
160-
{"*/10 * * * *", Plausible.Workers.ProvisionSslCertificates}
160+
{"*/10 * * * *", Plausible.Workers.ProvisionSslCertificates},
161+
# Every 15 minutes
162+
{"*/15 * * * *", Plausible.Workers.SpikeNotifier}
161163
]
162164

163165
base_queues = [rotate_salts: 1]
@@ -169,7 +171,8 @@ extra_queues = [
169171
site_setup_emails: 1,
170172
trial_notification_emails: 1,
171173
schedule_email_reports: 1,
172-
send_email_reports: 1
174+
send_email_reports: 1,
175+
spike_notifications: 1
173176
]
174177

175178
config :plausible, Oban,

lib/plausible/site/schema.ex

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ defmodule Plausible.Site do
1414
has_one :weekly_report, Plausible.Site.WeeklyReport
1515
has_one :monthly_report, Plausible.Site.MonthlyReport
1616
has_one :custom_domain, Plausible.Site.CustomDomain
17+
has_one :spike_notification, Plausible.Site.SpikeNotification
1718

1819
timestamps()
1920
end
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
defmodule Plausible.Site.SpikeNotification do
2+
use Ecto.Schema
3+
import Ecto.Changeset
4+
5+
schema "spike_notifications" do
6+
field :recipients, {:array, :string}
7+
field :threshold, :integer
8+
field :last_sent, :naive_datetime
9+
belongs_to :site, Plausible.Site
10+
11+
timestamps()
12+
end
13+
14+
def changeset(settings, attrs \\ %{}) do
15+
settings
16+
|> cast(attrs, [:site_id, :recipients])
17+
|> validate_required([:site_id, :recipients])
18+
|> unique_constraint(:site)
19+
end
20+
21+
def add_recipient(report, recipient) do
22+
report
23+
|> change(recipients: report.recipients ++ [recipient])
24+
end
25+
26+
def remove_recipient(report, recipient) do
27+
report
28+
|> change(recipients: List.delete(report.recipients, recipient))
29+
end
30+
end

lib/plausible_web/email.ex

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ defmodule PlausibleWeb.Email do
1313
|> subject("Activate your Plausible free trial")
1414
|> render("activation_email.html", name: user.name, link: link)
1515
end
16-
1716
def welcome_email(user) do
1817
base_email()
1918
|> to(user)
@@ -94,6 +93,14 @@ defmodule PlausibleWeb.Email do
9493
|> render("weekly_report.html", Keyword.put(assigns, :site, site))
9594
end
9695

96+
def spike_notification(email, site, current_visitors) do
97+
base_email()
98+
|> to(email)
99+
|> tag("spike-notification")
100+
|> subject("Traffic spike on #{site.domain}")
101+
|> render("spike_notification.html", %{current_visitors: current_visitors})
102+
end
103+
97104
def cancellation_email(user) do
98105
base_email()
99106
|> to(user.email)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Yours site has <%= @current_visitors %> visitors. Nice!

lib/workers/spike_notifier.ex

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
defmodule Plausible.Workers.SpikeNotifier do
2+
use Plausible.Repo
3+
alias Plausible.Stats.Query
4+
use Oban.Worker, queue: :spike_notifications
5+
@at_most_every "12 hours"
6+
7+
@impl Oban.Worker
8+
def perform(_args, _job, clickhouse \\ Plausible.Stats.Clickhouse) do
9+
notifications = Repo.all(
10+
from sn in Plausible.Site.SpikeNotification,
11+
where: is_nil(sn.last_sent),
12+
or_where: sn.last_sent < fragment("now() - INTERVAL ?", @at_most_every)
13+
)
14+
15+
for notification <- notifications do
16+
notification = Repo.preload(notification, :site)
17+
query = Query.from(notification.site.timezone, %{"period" => "realtime"})
18+
current_visitors = clickhouse.current_visitors(notification.site, query)
19+
notify(notification, current_visitors)
20+
end
21+
end
22+
23+
def notify(notification, current_visitors) do
24+
if current_visitors >= notification.threshold do
25+
for recipient <- notification.recipients do
26+
send_notification(recipient, notification.site, current_visitors)
27+
end
28+
end
29+
end
30+
31+
defp send_notification(recipient, site, current_visitors) do
32+
template = PlausibleWeb.Email.spike_notification(recipient, site, current_visitors)
33+
try do
34+
Plausible.Mailer.send_email(template)
35+
rescue
36+
_ -> nil
37+
end
38+
end
39+
end

mix.exs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,8 @@ defmodule Plausible.MixProject do
9090
{:sshex, "2.2.1"},
9191
{:geolix, "~> 1.0"},
9292
{:clickhouse_ecto, git: "https://github.com/plausible/clickhouse_ecto.git"},
93-
{:geolix_adapter_mmdb2, "~> 0.5.0"}
93+
{:geolix_adapter_mmdb2, "~> 0.5.0"},
94+
{:mix_test_watch, "~> 1.0", only: :dev}
9495
]
9596
end
9697

mix.lock

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
4545
"mime": {:hex, :mime, "1.3.1", "30ce04ab3175b6ad0bdce0035cba77bba68b813d523d1aac73d9781b4d193cf8", [:mix], [], "hexpm", "6cbe761d6a0ca5a31a0931bf4c63204bceb64538e664a8ecf784a9a6f3b875f1"},
4646
"mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"},
47+
"mix_test_watch": {:hex, :mix_test_watch, "1.0.2", "34900184cbbbc6b6ed616ed3a8ea9b791f9fd2088419352a6d3200525637f785", [:mix], [{:file_system, "~> 0.2.1 or ~> 0.3", [hex: :file_system, repo: "hexpm", optional: false]}], "hexpm", "47ac558d8b06f684773972c6d04fcc15590abdb97aeb7666da19fcbfdc441a07"},
4748
"mmdb2_decoder": {:hex, :mmdb2_decoder, "3.0.0", "54828676a36e75e9a25bc9a0bb0598d4c7fcc767bf0b40674850b22e05b7b6cc", [:mix], [], "hexpm", "359dc9242915538d1dceb9f6d96c72201dca76ce62e49d22e2ed1e86f20bea8e"},
4849
"nanoid": {:hex, :nanoid, "2.0.2", "f3f7b4bf103ab6667f22beb00b6315825ee3f30100dd2c93d534e5c02164e857", [:mix], [], "hexpm", "3095cb1fac7bbc78843a8ccd99f1af375d0da1d3ebaa8552e846b73438c0c44f"},
4950
"oauther": {:hex, :oauther, "1.1.1", "7d8b16167bb587ecbcddd3f8792beb9ec3e7b65c1f8ebd86b8dd25318d535752", [:mix], [], "hexpm", "9374f4302045321874cccdc57eb975893643bd69c3b22bf1312dab5f06e5788e"},
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
defmodule Plausible.Repo.Migrations.AddSpikeNotifications do
2+
use Ecto.Migration
3+
4+
def change do
5+
create table(:spike_notifications) do
6+
add :site_id, references(:sites), null: false
7+
add :threshold, :integer, null: false
8+
add :last_sent, :naive_datetime
9+
add :recipients, {:array, :citext}, null: false, default: []
10+
11+
timestamps()
12+
end
13+
end
14+
end

0 commit comments

Comments
 (0)