mirror of
https://github.com/plausible/analytics.git
synced 2024-12-22 17:11:36 +03:00
Merge pull request #13 from plausible-insights/monthly-reports
Monthly reports
This commit is contained in:
commit
e34242ee51
@ -13,26 +13,56 @@ defmodule Mix.Tasks.SendEmailReports do
|
||||
of the site. This job runs every hour to be able to send it with hourly precision.
|
||||
"""
|
||||
def execute(args \\ []) do
|
||||
send_weekly_emails()
|
||||
send_monthly_emails()
|
||||
end
|
||||
|
||||
defp send_weekly_emails() do
|
||||
sites = Repo.all(
|
||||
from s in Plausible.Site,
|
||||
join: es in Plausible.Site.EmailSettings, on: es.site_id == s.id,
|
||||
left_join: se in "sent_email_reports", on: se.site_id == s.id and se.year == fragment("EXTRACT(year from (now() at time zone ?))", s.timezone) and se.week == fragment("EXTRACT(week from (now() at time zone ?))", s.timezone),
|
||||
join: wr in Plausible.Site.WeeklyReport, on: wr.site_id == s.id,
|
||||
left_join: se in "sent_weekly_reports", on: se.site_id == s.id and se.year == fragment("EXTRACT(year from (now() at time zone ?))", s.timezone) and se.week == fragment("EXTRACT(week from (now() at time zone ?))", s.timezone),
|
||||
where: is_nil(se), # We haven't sent a report for this site on this week
|
||||
where: fragment("EXTRACT(dow from (now() at time zone ?))", s.timezone) == 1, # It's monday in the local timezone
|
||||
where: fragment("EXTRACT(hour from (now() at time zone ?))", s.timezone) >= 9, # It's after 9am
|
||||
select: s,
|
||||
preload: [email_settings: es]
|
||||
preload: [weekly_report: wr]
|
||||
)
|
||||
|
||||
for site <- sites do
|
||||
email = site.email_settings.email
|
||||
IO.puts("Sending email report for #{site.domain} to #{email}")
|
||||
send_report(email, site)
|
||||
email = site.weekly_report.email
|
||||
query = Plausible.Stats.Query.from(site.timezone, %{"period" => "7d"})
|
||||
|
||||
IO.puts("Sending weekly report for #{site.domain} to #{email}")
|
||||
|
||||
send_report(email, site, query)
|
||||
weekly_report_sent(site)
|
||||
end
|
||||
end
|
||||
|
||||
defp send_report(email, site) do
|
||||
query = Plausible.Stats.Query.from(site.timezone, %{"period" => "7d"})
|
||||
defp send_monthly_emails() do
|
||||
sites = Repo.all(
|
||||
from s in Plausible.Site,
|
||||
join: mr in Plausible.Site.MonthlyReport, on: mr.site_id == s.id,
|
||||
left_join: se in "sent_monthly_reports", on: se.site_id == s.id and se.year == fragment("EXTRACT(year from (now() at time zone ?))", s.timezone) and se.month == fragment("EXTRACT(month from (now() at time zone ?))", s.timezone),
|
||||
where: is_nil(se), # We haven't sent a report for this site this month
|
||||
where: fragment("EXTRACT(day from (now() at time zone ?))", s.timezone) == 1, # It's the 1st of the month in the local timezone
|
||||
where: fragment("EXTRACT(hour from (now() at time zone ?))", s.timezone) >= 9, # It's after 9am
|
||||
preload: [monthly_report: mr]
|
||||
)
|
||||
|
||||
for site <- sites do
|
||||
email = site.monthly_report.email
|
||||
last_month = Timex.now(site.timezone) |> Timex.shift(months: -1) |> Timex.beginning_of_month |> Timex.format!("{ISOdate}")
|
||||
query = Plausible.Stats.Query.from(site.timezone, %{"period" => "month", "date" => last_month})
|
||||
|
||||
IO.puts("Sending monthly report for #{site.domain} to #{email}")
|
||||
|
||||
send_report(email, site, query)
|
||||
monthly_report_sent(site)
|
||||
end
|
||||
end
|
||||
|
||||
defp send_report(email, site, query) do
|
||||
{pageviews, unique_visitors} = Plausible.Stats.pageviews_and_visitors(site, query)
|
||||
{change_pageviews, change_visitors} = Plausible.Stats.compare_pageviews_and_visitors(site, query, {pageviews, unique_visitors})
|
||||
referrers = Plausible.Stats.top_referrers(site, query)
|
||||
@ -51,18 +81,27 @@ defmodule Mix.Tasks.SendEmailReports do
|
||||
pages: pages,
|
||||
query: query
|
||||
) |> Plausible.Mailer.deliver_now()
|
||||
|
||||
email_report_sent(site)
|
||||
end
|
||||
|
||||
defp email_report_sent(site) do
|
||||
defp weekly_report_sent(site) do
|
||||
{year, week} = Timex.now(site.timezone) |> DateTime.to_date |> Timex.iso_week
|
||||
|
||||
Repo.insert_all("sent_email_reports", [%{
|
||||
Repo.insert_all("sent_weekly_reports", [%{
|
||||
site_id: site.id,
|
||||
year: year,
|
||||
week: week,
|
||||
timestamp: Timex.now()
|
||||
}])
|
||||
end
|
||||
|
||||
defp monthly_report_sent(site) do
|
||||
date = Timex.now(site.timezone) |> DateTime.to_date
|
||||
|
||||
Repo.insert_all("sent_monthly_reports", [%{
|
||||
site_id: site.id,
|
||||
year: date.year,
|
||||
month: date.month,
|
||||
timestamp: Timex.now()
|
||||
}])
|
||||
end
|
||||
end
|
||||
|
20
lib/plausible/site/monthly_report.ex
Normal file
20
lib/plausible/site/monthly_report.ex
Normal file
@ -0,0 +1,20 @@
|
||||
defmodule Plausible.Site.MonthlyReport do
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
@mail_regex ~r/^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}$/
|
||||
|
||||
schema "monthly_reports" do
|
||||
field :email, :string
|
||||
belongs_to :site, Plausible.Site
|
||||
|
||||
timestamps()
|
||||
end
|
||||
|
||||
def changeset(settings, attrs \\ %{}) do
|
||||
settings
|
||||
|> cast(attrs, [:site_id, :email])
|
||||
|> validate_required([:site_id, :email])
|
||||
|> validate_format(:email, @mail_regex)
|
||||
|> unique_constraint(:site)
|
||||
end
|
||||
end
|
@ -11,7 +11,8 @@ defmodule Plausible.Site do
|
||||
|
||||
many_to_many :members, User, join_through: Plausible.Site.Membership
|
||||
has_one :google_auth, GoogleAuth
|
||||
has_one :email_settings, Plausible.Site.EmailSettings
|
||||
has_one :weekly_report, Plausible.Site.WeeklyReport
|
||||
has_one :monthly_report, Plausible.Site.MonthlyReport
|
||||
|
||||
timestamps()
|
||||
end
|
||||
|
@ -1,9 +1,9 @@
|
||||
defmodule Plausible.Site.EmailSettings do
|
||||
defmodule Plausible.Site.WeeklyReport do
|
||||
use Ecto.Schema
|
||||
import Ecto.Changeset
|
||||
@mail_regex ~r/^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,4}$/
|
||||
|
||||
schema "email_settings" do
|
||||
schema "weekly_reports" do
|
||||
field :email, :string
|
||||
belongs_to :site, Plausible.Site
|
||||
|
@ -42,33 +42,22 @@ defmodule PlausibleWeb.SiteController do
|
||||
!google_site["error"]
|
||||
end
|
||||
|
||||
report = Repo.get_by(Plausible.Site.EmailSettings, site_id: site.id)
|
||||
report_changeset = report && Plausible.Site.EmailSettings.changeset(report, %{})
|
||||
|
||||
changeset = Plausible.Site.changeset(site, %{})
|
||||
weekly_report = Repo.get_by(Plausible.Site.WeeklyReport, site_id: site.id)
|
||||
weekly_report_changeset = weekly_report && Plausible.Site.WeeklyReport.changeset(weekly_report, %{})
|
||||
monthly_report = Repo.get_by(Plausible.Site.MonthlyReport, site_id: site.id)
|
||||
monthly_report_changeset = monthly_report && Plausible.Site.WeeklyReport.changeset(monthly_report, %{})
|
||||
|
||||
conn
|
||||
|> assign(:skip_plausible_tracking, true)
|
||||
|> render("settings.html",
|
||||
site: site,
|
||||
report_changeset: report_changeset,
|
||||
weekly_report_changeset: weekly_report_changeset,
|
||||
monthly_report_changeset: monthly_report_changeset,
|
||||
google_search_console_verified: google_search_console_verified,
|
||||
changeset: changeset
|
||||
changeset: Plausible.Site.changeset(site, %{})
|
||||
)
|
||||
end
|
||||
|
||||
|
||||
def update_email_settings(conn, %{"website" => website, "email_settings" => email_settings}) do
|
||||
site = Sites.get_for_user!(conn.assigns[:current_user].id, website)
|
||||
Repo.get_by(Plausible.Site.EmailSettings, site_id: site.id)
|
||||
|> Plausible.Site.EmailSettings.changeset(email_settings)
|
||||
|> Repo.update!
|
||||
|
||||
conn
|
||||
|> put_flash(:success, "Email address saved succesfully")
|
||||
|> redirect(to: "/#{site.domain}/settings")
|
||||
end
|
||||
|
||||
def update_settings(conn, %{"website" => website, "site" => site_params}) do
|
||||
site = Sites.get_for_user!(conn.assigns[:current_user].id, website)
|
||||
changeset = site |> Plausible.Site.changeset(site_params)
|
||||
@ -122,10 +111,10 @@ defmodule PlausibleWeb.SiteController do
|
||||
|> redirect(to: "/" <> site.domain <> "/settings")
|
||||
end
|
||||
|
||||
def enable_email_report(conn, %{"website" => website}) do
|
||||
def enable_weekly_report(conn, %{"website" => website}) do
|
||||
site = Sites.get_for_user!(conn.assigns[:current_user].id, website)
|
||||
|
||||
Plausible.Site.EmailSettings.changeset(%Plausible.Site.EmailSettings{}, %{
|
||||
Plausible.Site.WeeklyReport.changeset(%Plausible.Site.WeeklyReport{}, %{
|
||||
site_id: site.id,
|
||||
email: conn.assigns[:current_user].email
|
||||
})
|
||||
@ -136,15 +125,60 @@ defmodule PlausibleWeb.SiteController do
|
||||
|> redirect(to: "/" <> site.domain <> "/settings")
|
||||
end
|
||||
|
||||
def disable_email_report(conn, %{"website" => website}) do
|
||||
def disable_weekly_report(conn, %{"website" => website}) do
|
||||
site = Sites.get_for_user!(conn.assigns[:current_user].id, website)
|
||||
Repo.delete_all(from es in Plausible.Site.EmailSettings, where: es.site_id == ^site.id)
|
||||
Repo.delete_all(from wr in Plausible.Site.WeeklyReport, where: wr.site_id == ^site.id)
|
||||
|
||||
conn
|
||||
|> put_flash(:success, "Success! You will not receive weekly email reports going forward")
|
||||
|> redirect(to: "/" <> site.domain <> "/settings")
|
||||
end
|
||||
|
||||
def update_weekly_settings(conn, %{"website" => website, "weekly_report" => weekly_report}) do
|
||||
site = Sites.get_for_user!(conn.assigns[:current_user].id, website)
|
||||
Repo.get_by(Plausible.Site.WeeklyReport, site_id: site.id)
|
||||
|> Plausible.Site.WeeklyReport.changeset(weekly_report)
|
||||
|> Repo.update!
|
||||
|
||||
conn
|
||||
|> put_flash(:success, "Email address saved succesfully")
|
||||
|> redirect(to: "/#{site.domain}/settings")
|
||||
end
|
||||
|
||||
def enable_monthly_report(conn, %{"website" => website}) do
|
||||
site = Sites.get_for_user!(conn.assigns[:current_user].id, website)
|
||||
|
||||
Plausible.Site.MonthlyReport.changeset(%Plausible.Site.MonthlyReport{}, %{
|
||||
site_id: site.id,
|
||||
email: conn.assigns[:current_user].email
|
||||
})
|
||||
|> Repo.insert!
|
||||
|
||||
conn
|
||||
|> put_flash(:success, "Success! You will receive an email report every month going forward")
|
||||
|> redirect(to: "/" <> site.domain <> "/settings")
|
||||
end
|
||||
|
||||
def disable_monthly_report(conn, %{"website" => website}) do
|
||||
site = Sites.get_for_user!(conn.assigns[:current_user].id, website)
|
||||
Repo.delete_all(from mr in Plausible.Site.MonthlyReport, where: mr.site_id == ^site.id)
|
||||
|
||||
conn
|
||||
|> put_flash(:success, "Success! You will not receive monthly email reports going forward")
|
||||
|> redirect(to: "/" <> site.domain <> "/settings")
|
||||
end
|
||||
|
||||
def update_monthly_settings(conn, %{"website" => website, "monthly_report" => monthly_report}) do
|
||||
site = Sites.get_for_user!(conn.assigns[:current_user].id, website)
|
||||
Repo.get_by(Plausible.Site.MonthlyReport, site_id: site.id)
|
||||
|> Plausible.Site.WeeklyReport.changeset(monthly_report)
|
||||
|> Repo.update!
|
||||
|
||||
conn
|
||||
|> put_flash(:success, "Email address saved succesfully")
|
||||
|> redirect(to: "/#{site.domain}/settings")
|
||||
end
|
||||
|
||||
defp insert_site(user_id, params) do
|
||||
site_changeset = Plausible.Site.changeset(%Plausible.Site{}, params)
|
||||
|
||||
|
@ -85,9 +85,12 @@ defmodule PlausibleWeb.Router do
|
||||
post "/sites", SiteController, :create_site
|
||||
post "/sites/:website/make-public", SiteController, :make_public
|
||||
post "/sites/:website/make-private", SiteController, :make_private
|
||||
post "/sites/:website/email-report/enable", SiteController, :enable_email_report
|
||||
post "/sites/:website/email-report/disable", SiteController, :disable_email_report
|
||||
put "/sites/:website/email-report", SiteController, :update_email_settings
|
||||
post "/sites/:website/weekly-report/enable", SiteController, :enable_weekly_report
|
||||
post "/sites/:website/weekly-report/disable", SiteController, :disable_weekly_report
|
||||
put "/sites/:website/weekly-report", SiteController, :update_weekly_settings
|
||||
post "/sites/:website/monthly-report/enable", SiteController, :enable_monthly_report
|
||||
post "/sites/:website/monthly-report/disable", SiteController, :disable_monthly_report
|
||||
put "/sites/:website/monthly-report", SiteController, :update_monthly_settings
|
||||
get "/:website/snippet", SiteController, :add_snippet
|
||||
get "/:website/settings", SiteController, :settings
|
||||
put "/:website/settings", SiteController, :update_settings
|
||||
|
@ -102,28 +102,48 @@
|
||||
<div class="my-4 border-b border-grey-light"></div>
|
||||
|
||||
<div class="my-8 flex items-center">
|
||||
<%= if @report_changeset do %>
|
||||
<%= button(to: "/sites/#{@site.domain}/email-report/disable", method: :post, class: "border rounded-full border-grey flex items-center cursor-pointer w-8 bg-green justify-end") do %>
|
||||
<%= if @weekly_report_changeset do %>
|
||||
<%= button(to: "/sites/#{@site.domain}/weekly-report/disable", method: :post, class: "border rounded-full border-grey flex items-center cursor-pointer w-8 bg-green justify-end") do %>
|
||||
<span class="rounded-full border w-4 h-4 border-grey shadow-inner bg-white shadow"></span>
|
||||
<% end %>
|
||||
<span class="ml-2">Receive a weekly email report every Monday</span>
|
||||
<% else %>
|
||||
<%= button(to: "/sites/#{@site.domain}/email-report/enable", method: :post, class: "border rounded-full border-grey flex items-center cursor-pointer w-8 justify-start") do %>
|
||||
<%= button(to: "/sites/#{@site.domain}/weekly-report/enable", method: :post, class: "border rounded-full border-grey flex items-center cursor-pointer w-8 justify-start") do %>
|
||||
<span class="rounded-full border w-4 h-4 border-grey shadow-inner bg-white shadow"></span>
|
||||
<% end %>
|
||||
<span class="ml-2">Receive a weekly email report every Monday</span>
|
||||
<% end %>
|
||||
<span class="ml-2">Receive a weekly email report every Monday</span>
|
||||
</div>
|
||||
<%= if @report_changeset do %>
|
||||
<div class="my-4 border-b border-grey-light"></div>
|
||||
<%= if @weekly_report_changeset do %>
|
||||
<div class="text-sm text-grey-darker mt-6">
|
||||
<%= form_for @report_changeset, "/sites/#{@site.domain}/email-report", [class: "max-w-xs"], fn f -> %>
|
||||
<div class="my-4">
|
||||
<%= label f, :email, "Email address", class: "block text-grey-darker text-sm font-bold mb-2" %>
|
||||
<%= email_input f, :email, class: "transition bg-grey-lighter appearance-none border border-transparent rounded w-full p-2 text-grey-darker leading-normal appearance-none focus:outline-none focus:border-grey-light" %>
|
||||
<%= error_tag f, :email %>
|
||||
<%= form_for @weekly_report_changeset, "/sites/#{@site.domain}/weekly-report", [class: "max-w-xs"], fn f -> %>
|
||||
<%= label f, :email, "Email address", class: "block text-grey-darker text-sm font-bold mb-2" %>
|
||||
<div class="flex">
|
||||
<%= email_input f, :email, class: "transition bg-grey-lighter appearance-none border border-transparent rounded w-full p-2 text-grey-darker leading-normal appearance-none focus:outline-none focus:border-grey-light", style: "flex-grow: 2" %>
|
||||
<%= submit "Update", class: "button rounded-l-none" %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
<div class="my-4 border-b border-grey-light"></div>
|
||||
<div class="my-8 flex items-center">
|
||||
<%= if @monthly_report_changeset do %>
|
||||
<%= button(to: "/sites/#{@site.domain}/monthly-report/disable", method: :post, class: "border rounded-full border-grey flex items-center cursor-pointer w-8 bg-green justify-end") do %>
|
||||
<span class="rounded-full border w-4 h-4 border-grey shadow-inner bg-white shadow"></span>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<%= button(to: "/sites/#{@site.domain}/monthly-report/enable", method: :post, class: "border rounded-full border-grey flex items-center cursor-pointer w-8 justify-start") do %>
|
||||
<span class="rounded-full border w-4 h-4 border-grey shadow-inner bg-white shadow"></span>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<span class="ml-2">Receive a monthly email report on 1st of the month</span>
|
||||
</div>
|
||||
<%= if @monthly_report_changeset do %>
|
||||
<div class="text-sm text-grey-darker mt-6">
|
||||
<%= form_for @monthly_report_changeset, "/sites/#{@site.domain}/monthly-report", [class: "max-w-xs"], fn f -> %>
|
||||
<div class="flex">
|
||||
<%= email_input f, :email, class: "transition bg-grey-lighter appearance-none border border-transparent rounded w-full p-2 text-grey-darker leading-normal appearance-none focus:outline-none focus:border-grey-light", style: "flex-grow: 2" %>
|
||||
<%= submit "Update", class: "button rounded-l-none" %>
|
||||
</div>
|
||||
<%= submit "Set email address", class: "button mt-2" %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
79
priv/repo/migrations/20190911102027_add_monthly_reports.exs
Normal file
79
priv/repo/migrations/20190911102027_add_monthly_reports.exs
Normal file
@ -0,0 +1,79 @@
|
||||
defmodule Plausible.Repo.Migrations.AddMonthlyReports do
|
||||
use Ecto.Migration
|
||||
use Plausible.Repo
|
||||
|
||||
def up do
|
||||
drop constraint(:email_settings, "email_settings_site_id_fkey")
|
||||
drop constraint(:email_settings, "email_settings_pkey")
|
||||
execute "DROP INDEX email_settings_site_id_index"
|
||||
|
||||
rename table(:email_settings), to: table(:weekly_reports)
|
||||
|
||||
alter table(:weekly_reports) do
|
||||
modify :id, :bigint, primary_key: true
|
||||
modify :site_id, references(:sites, on_delete: :delete_all), null: false
|
||||
end
|
||||
|
||||
execute "ALTER SEQUENCE email_settings_id_seq RENAME TO weekly_reports_id_seq;"
|
||||
create unique_index(:weekly_reports, :site_id)
|
||||
|
||||
drop constraint(:sent_email_reports, "sent_email_reports_site_id_fkey")
|
||||
drop constraint(:sent_email_reports, "sent_email_reports_pkey")
|
||||
|
||||
rename table(:sent_email_reports), to: table(:sent_weekly_reports)
|
||||
|
||||
alter table(:sent_weekly_reports) do
|
||||
modify :id, :bigint, primary_key: true
|
||||
modify :site_id, references(:sites, on_delete: :delete_all), null: false
|
||||
end
|
||||
|
||||
execute "ALTER SEQUENCE sent_email_reports_id_seq RENAME TO sent_weekly_reports_id_seq;"
|
||||
|
||||
create table(:monthly_reports) do
|
||||
add :site_id, references(:sites, on_delete: :delete_all), null: false
|
||||
add :email, :citext, null: false
|
||||
|
||||
timestamps()
|
||||
end
|
||||
|
||||
create table(:sent_monthly_reports) do
|
||||
add :site_id, references(:sites, on_delete: :delete_all), null: false
|
||||
add :year, :integer, null: false
|
||||
add :month, :integer, null: false
|
||||
|
||||
add :timestamp, :naive_datetime
|
||||
end
|
||||
end
|
||||
|
||||
def down do
|
||||
drop constraint(:weekly_reports, "weekly_reports_site_id_fkey")
|
||||
drop constraint(:weekly_reports, "weekly_reports_pkey")
|
||||
execute "DROP INDEX weekly_reports_site_id_index"
|
||||
|
||||
rename table(:weekly_reports), to: table(:email_settings)
|
||||
|
||||
alter table(:email_settings) do
|
||||
modify :id, :bigint, primary_key: true
|
||||
modify :site_id, references(:sites, on_delete: :delete_all), null: false
|
||||
end
|
||||
|
||||
execute "ALTER SEQUENCE weekly_reports_id_seq RENAME TO email_settings_id_seq;"
|
||||
create unique_index(:email_settings, :site_id)
|
||||
|
||||
|
||||
drop constraint(:sent_weekly_reports, "sent_weekly_reports_site_id_fkey")
|
||||
drop constraint(:sent_weekly_reports, "sent_weekly_reports_pkey")
|
||||
|
||||
rename table(:sent_weekly_reports), to: table(:sent_email_reports)
|
||||
|
||||
alter table(:sent_email_reports) do
|
||||
modify :id, :bigint, primary_key: true
|
||||
modify :site_id, references(:sites, on_delete: :delete_all), null: false
|
||||
end
|
||||
|
||||
execute "ALTER SEQUENCE sent_weekly_reports_id_seq RENAME TO sent_email_reports_id_seq;"
|
||||
|
||||
drop table(:monthly_reports)
|
||||
drop table(:sent_monthly_reports)
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue
Block a user