diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b065ad60820..0edbc5169800 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,9 @@ All notable changes to this project will be documented in this file. ## Unreleased +### Fixed +- Fixed weekly/monthly e-mail report [rendering issues](https://github.com/plausible/analytics/issues/284) + ## v2.0.0 - 2023-07-12 ### Added diff --git a/lib/plausible_web/email.ex b/lib/plausible_web/email.ex index f222500eca8b..fe5c5504e1e5 100644 --- a/lib/plausible_web/email.ex +++ b/lib/plausible_web/email.ex @@ -104,11 +104,13 @@ defmodule PlausibleWeb.Email do end def weekly_report(email, site, assigns) do + assigns = Keyword.put(assigns, :site, site) + base_email(%{layout: nil}) |> to(email) |> tag("weekly-report") |> subject("#{assigns[:name]} report for #{site.domain}") - |> render("weekly_report.html", Keyword.put(assigns, :site, site)) + |> html_body(PlausibleWeb.MJML.WeeklyReport.render(assigns)) end def spike_notification(email, site, current_visitors, sources, dashboard_link) do diff --git a/lib/plausible_web/mjml/templates/weekly_report.mjml.eex b/lib/plausible_web/mjml/templates/weekly_report.mjml.eex new file mode 100644 index 000000000000..9fbd84b96df8 --- /dev/null +++ b/lib/plausible_web/mjml/templates/weekly_report.mjml.eex @@ -0,0 +1,181 @@ + + + + + + + + + + + + + + + + + + + Plausible Analytics + + + <%= @site.domain %> + + + <%= @name %> Report (<%= Timex.format!(@query.date_range.last, "{D} {Mshort} {YYYY}") %>) + + + + + + + + + + + + + UNIQUE VISITORS + + <%= PlausibleWeb.StatsView.large_number_format(@unique_visitors) %> + + <%= cond do %> + <% @change_visitors == nil -> %> + N/A + <% @change_visitors >= 0 -> %> + + +<%= @change_visitors %>% + + <% @change_visitors < 0 -> %> + + <%= @change_visitors %>% + + <% end %> + + + + PAGEVIEWS + + <%= PlausibleWeb.StatsView.large_number_format(@pageviews) %> + + <%= cond do %> + <% is_nil(@change_pageviews) -> %> + N/A + <% @change_pageviews >= 0 -> %> + + +<%= @change_pageviews %>% + + <% @change_pageviews < 0 -> %> + + <%= @change_pageviews %>% + + <% end %> + + + + + BOUNCE RATE + + <%= @bounce_rate %> + + + <%= cond do %> + <% @change_bounce_rate == nil -> %> + N/A + <% @change_bounce_rate <= 0 -> %> + + <%= @change_bounce_rate %>% + + <% @change_bounce_rate > 0 -> %> + + +<%= @change_bounce_rate %>% + + <% end %> + + + + + + + + + + + + + + Referrer + + <%= for source <- @sources do %> + + <%= source[:source] %> + + <% end %> + + + Visitors + + <%= for source <- @sources do %> + + <%= PlausibleWeb.StatsView.large_number_format(source[:visitors]) %> + + <% end %> + + + + + + + + + + + + + + + Page + + + <%= for page <- @pages do %> + + <%= page[:page] %> + + <% end %> + + + + Visitors + + + <%= for page <- @pages do %> + + <%= PlausibleWeb.StatsView.large_number_format(page[:visitors]) %> + + <% end %> + + + + + <%= if @login_link do %> + + + Login to view your dashboard + + + <% end %> + + + + + + + + + + Don't want to receive these e-mails? Click here to unsusbscribe. + + + + + diff --git a/lib/plausible_web/mjml/weekly_report.ex b/lib/plausible_web/mjml/weekly_report.ex new file mode 100644 index 000000000000..e1e675d05ab8 --- /dev/null +++ b/lib/plausible_web/mjml/weekly_report.ex @@ -0,0 +1,7 @@ +defmodule PlausibleWeb.MJML.WeeklyReport do + @moduledoc """ + MJML rendered for the weekly report e-mail + """ + + use MjmlEEx, mjml_template: "templates/weekly_report.mjml.eex" +end diff --git a/lib/plausible_web/templates/email/weekly_report.html.eex b/lib/plausible_web/templates/email/weekly_report.html.eex deleted file mode 100644 index 0b508b942192..000000000000 --- a/lib/plausible_web/templates/email/weekly_report.html.eex +++ /dev/null @@ -1,697 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/workers/send_email_report.ex b/lib/workers/send_email_report.ex index 5076a6c46921..9826bac74efe 100644 --- a/lib/workers/send_email_report.ex +++ b/lib/workers/send_email_report.ex @@ -31,26 +31,30 @@ defmodule Plausible.Workers.SendEmailReport do def perform(%Oban.Job{args: %{"interval" => "monthly", "site_id" => site_id}}) do site = Repo.get(Plausible.Site, site_id) |> Repo.preload(:monthly_report) - last_month = - Timex.now(site.timezone) - |> Timex.shift(months: -1) - |> Timex.beginning_of_month() - - query = - Query.from(site, %{ - "period" => "month", - "date" => Timex.format!(last_month, "{ISOdate}") - }) - - for email <- site.monthly_report.recipients do - unsubscribe_link = - PlausibleWeb.Endpoint.url() <> - "/sites/#{URI.encode_www_form(site.domain)}/monthly-report/unsubscribe?email=#{email}" - - send_report(email, site, Timex.format!(last_month, "{Mfull}"), unsubscribe_link, query) - end + if site do + last_month = + Timex.now(site.timezone) + |> Timex.shift(months: -1) + |> Timex.beginning_of_month() + + query = + Query.from(site, %{ + "period" => "month", + "date" => Timex.format!(last_month, "{ISOdate}") + }) + + for email <- site.monthly_report.recipients do + unsubscribe_link = + PlausibleWeb.Endpoint.url() <> + "/sites/#{URI.encode_www_form(site.domain)}/monthly-report/unsubscribe?email=#{email}" + + send_report(email, site, Timex.format!(last_month, "{Mfull}"), unsubscribe_link, query) + end - :ok + :ok + else + :discard + end end defp send_report(email, site, name, unsubscribe_link, query) do diff --git a/mix.exs b/mix.exs index 96d40c348977..e08bc6b30a37 100644 --- a/mix.exs +++ b/mix.exs @@ -118,7 +118,9 @@ defmodule Plausible.MixProject do {:timex, "~> 3.7"}, {:ua_inspector, "~> 3.0"}, {:ex_doc, "~> 0.28", only: :dev, runtime: false}, - {:ex_money, "~> 5.12"} + {:ex_money, "~> 5.12"}, + {:mjml_eex, "~> 0.9.0"}, + {:mjml, "~> 1.5.0"} ] end diff --git a/mix.lock b/mix.lock index d4e9aba8bef7..72ca7adc1005 100644 --- a/mix.lock +++ b/mix.lock @@ -78,6 +78,8 @@ "mime": {:hex, :mime, "1.6.0", "dabde576a497cef4bbdd60aceee8160e02a6c89250d6c0b29e56c0dfb00db3d2", [:mix], [], "hexpm", "31a1a8613f8321143dde1dafc36006a17d28d02bdfecb9e95a880fa7aabd19a7"}, "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, "mint": {:hex, :mint, "1.5.1", "8db5239e56738552d85af398798c80648db0e90f343c8469f6c6d8898944fb6f", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "4a63e1e76a7c3956abd2c72f370a0d0aecddc3976dea5c27eccbecfa5e7d5b1e"}, + "mjml": {:hex, :mjml, "1.5.0", "20a4ed2490a60c6928d45a69b64fb45ce8d8bdac686ef689315b0adda69c6406", [:mix], [{:rustler, ">= 0.0.0", [hex: :rustler, repo: "hexpm", optional: true]}, {:rustler_precompiled, "~> 0.6.0", [hex: :rustler_precompiled, repo: "hexpm", optional: false]}], "hexpm", "44dc36c0fccf52eeb8e0afcb26a863ba41a5f9adcb71bb32e084619a13bb4cdf"}, + "mjml_eex": {:hex, :mjml_eex, "0.9.1", "102b6b6e57bfd6db01e0feef801b573fcddb1ee34effb884695da8407544a5be", [:mix], [{:erlexec, "~> 2.0", [hex: :erlexec, repo: "hexpm", optional: true]}, {:mjml, "~> 1.5.0", [hex: :mjml, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.2", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "310f9364d4f1126170835db6fb8dad87e393b28860b0e710d870812fb0bd7892"}, "mox": {:hex, :mox, "1.0.2", "dc2057289ac478b35760ba74165b4b3f402f68803dd5aecd3bfd19c183815d64", [:mix], [], "hexpm", "f9864921b3aaf763c8741b5b8e6f908f44566f1e427b2630e89e9a73b981fef2"}, "nanoid": {:hex, :nanoid, "2.0.5", "1d2948d8967ef2d948a58c3fef02385040bd9823fc6394bd604b8d98e5516b22", [:mix], [], "hexpm", "956e8876321104da72aa48770539ff26b36b744cd26753ec8e7a8a37e53d5f58"}, "nimble_csv": {:hex, :nimble_csv, "1.2.0", "4e26385d260c61eba9d4412c71cea34421f296d5353f914afe3f2e71cce97722", [:mix], [], "hexpm", "d0628117fcc2148178b034044c55359b26966c6eaa8e2ce15777be3bbc91b12a"}, @@ -119,6 +121,7 @@ "recon": {:hex, :recon, "2.5.2", "cba53fa8db83ad968c9a652e09c3ed7ddcc4da434f27c3eaa9ca47ffb2b1ff03", [:mix, :rebar3], [], "hexpm", "2c7523c8dee91dff41f6b3d63cba2bd49eb6d2fe5bf1eec0df7f87eb5e230e1c"}, "ref_inspector": {:hex, :ref_inspector, "1.3.1", "bb0489a4c4299dcd633f2b7a60c41a01f5590789d0b28225a60be484e1fbe777", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}, {:yamerl, "~> 0.7", [hex: :yamerl, repo: "hexpm", optional: false]}], "hexpm", "3172eb1b08e5c69966f796e3fe0e691257546fa143a5eb0ecc18a6e39b233854"}, "referrer_blocklist": {:git, "https://github.com/plausible/referrer-blocklist.git", "d6f52c225cccb4f04b80e3a5d588868ec234139d", []}, + "rustler_precompiled": {:hex, :rustler_precompiled, "0.6.2", "d2218ba08a43fa331957f30481d00b666664d7e3861431b02bd3f4f30eec8e5b", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:rustler, "~> 0.23", [hex: :rustler, repo: "hexpm", optional: true]}], "hexpm", "b9048eaed8d7d14a53f758c91865cc616608a438d2595f621f6a4b32a5511709"}, "sentry": {:hex, :sentry, "8.0.6", "c8de1bf0523bc120ec37d596c55260901029ecb0994e7075b0973328779ceef7", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.6", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, "~> 2.3", [hex: :plug_cowboy, repo: "hexpm", optional: true]}], "hexpm", "051a2d0472162f3137787c7c9d6e6e4ef239de9329c8c45b1f1bf1e9379e1883"}, "siphash": {:hex, :siphash, "3.2.0", "ec03fd4066259218c85e2a4b8eec4bb9663bc02b127ea8a0836db376ba73f2ed", [:make, :mix], [], "hexpm", "ba3810701c6e95637a745e186e8a4899087c3b079ba88fb8f33df054c3b0b7c3"}, "sleeplocks": {:hex, :sleeplocks, "1.1.1", "3d462a0639a6ef36cc75d6038b7393ae537ab394641beb59830a1b8271faeed3", [:rebar3], [], "hexpm", "84ee37aeff4d0d92b290fff986d6a95ac5eedf9b383fadfd1d88e9b84a1c02e1"}, diff --git a/test/workers/send_email_report_test.exs b/test/workers/send_email_report_test.exs index 97c2b2968f1f..311f4286b1df 100644 --- a/test/workers/send_email_report_test.exs +++ b/test/workers/send_email_report_test.exs @@ -65,8 +65,9 @@ defmodule Plausible.Workers.SendEmailReportTest do }) # Should find 2 visiors - assert html_body =~ - ~s(2) + + page_count = html_body |> Floki.find(".page-count") |> Floki.text() |> String.trim() + assert page_count == "2" end test "includes the correct stats" do @@ -93,22 +94,28 @@ defmodule Plausible.Workers.SendEmailReportTest do {:ok, document} = Floki.parse_document(html_body) - visitors = Floki.find(document, "#visitors") |> Floki.text() + visitors = + Floki.find(document, ".visitors") + |> List.first() + |> Floki.text() + |> String.trim() + assert visitors == "2" - pageviews = Floki.find(document, "#pageviews") |> Floki.text() + pageviews = Floki.find(document, ".pageviews") |> Floki.text() |> String.trim() assert pageviews == "3" - referrer = Floki.find(document, ".referrer") |> List.first() - referrer_name = referrer |> Floki.find("#referrer-name") |> Floki.text() - referrer_count = referrer |> Floki.find("#referrer-count") |> Floki.text() + referrer_name = + document |> Floki.find(".referrer-name") |> List.first() |> Floki.text() |> String.trim() + + referrer_count = + document |> Floki.find(".referrer-count") |> List.first() |> Floki.text() |> String.trim() assert referrer_name == "Google" assert referrer_count == "1" - page = Floki.find(document, ".page") |> List.first() - page_name = page |> Floki.find("#page-name") |> Floki.text() - page_count = page |> Floki.find("#page-count") |> Floki.text() + page_name = document |> Floki.find(".page-name") |> Floki.text() |> String.trim() + page_count = document |> Floki.find(".page-count") |> Floki.text() |> String.trim() assert page_name == "/" assert page_count == "2"