defmodule PlausibleWeb.StatsController do use PlausibleWeb, :controller use Plausible.Repo alias Plausible.Stats defp referrer_link(site, {name, count}, query) do link = "/#{site.domain}/referrers/#{name}" <> PlausibleWeb.StatsView.query_params(query) {{:link, name, link}, count} end def stats(conn, %{"website" => website}) do site = Repo.get_by(Plausible.Site, domain: website) if site && current_user_can_access?(conn, site) do user = conn.assigns[:current_user] if user && Plausible.Billing.needs_to_upgrade?(conn.assigns[:current_user]) do redirect(conn, to: "/billing/upgrade") else if Plausible.Sites.has_pageviews?(site) do demo = site.domain == "plausible.io" offer_email_report = get_session(conn, site.domain <> "_offer_email_report") Plausible.Tracking.event(conn, "Site Analytics: Open", %{demo: demo}) {conn, params} = fetch_period(conn, site) query = Stats.Query.from(site.timezone, params) current_visitors = Stats.current_visitors(site) conn |> assign(:skip_plausible_tracking, !demo) |> put_session(site.domain <> "_offer_email_report", nil) |> render("stats.html", site: site, query: query, current_visitors: current_visitors, title: "Plausible ยท " <> site.domain, offer_email_report: offer_email_report ) else conn |> assign(:skip_plausible_tracking, true) |> render("waiting_first_pageview.html", site: site) end end else render_error(conn, 404) end end def browsers_preview(conn, %{"domain" => domain}) do site = Repo.get_by(Plausible.Site, domain: domain) {conn, params} = fetch_period(conn, site) query = Stats.Query.from(site.timezone, params) if site && current_user_can_access?(conn, site) do render(conn, "browsers_preview.html", browsers: Stats.browsers(site, query), site: site, query: query, layout: false ) else render_error(conn, 404) end end def operating_systems_preview(conn, %{"domain" => domain}) do site = Repo.get_by(Plausible.Site, domain: domain) {conn, params} = fetch_period(conn, site) query = Stats.Query.from(site.timezone, params) if site && current_user_can_access?(conn, site) do render(conn, "operating_systems_preview.html", operating_systems: Stats.operating_systems(site, query), site: site, query: query, layout: false ) else render_error(conn, 404) end end def screen_sizes_preview(conn, %{"domain" => domain}) do site = Repo.get_by(Plausible.Site, domain: domain) {conn, params} = fetch_period(conn, site) query = Stats.Query.from(site.timezone, params) if site && current_user_can_access?(conn, site) do render(conn, "screen_sizes_preview.html", top_screen_sizes: Stats.top_screen_sizes(site, query), site: site, query: query, layout: false ) else render_error(conn, 404) end end def referrers_preview(conn, %{"domain" => domain}) do site = Repo.get_by(Plausible.Site, domain: domain) {conn, params} = fetch_period(conn, site) query = Stats.Query.from(site.timezone, params) if site && current_user_can_access?(conn, site) do render(conn, "referrers_preview.html", top_referrers: Stats.top_referrers(site, query) |> Enum.map(&(referrer_link(site, &1, query))), site: site, query: query, layout: false ) else render_error(conn, 404) end end def pages_preview(conn, %{"domain" => domain}) do site = Repo.get_by(Plausible.Site, domain: domain) {conn, params} = fetch_period(conn, site) query = Stats.Query.from(site.timezone, params) if site && current_user_can_access?(conn, site) do render(conn, "pages_preview.html", top_pages: Stats.top_pages(site, query), site: site, query: query, layout: false ) else render_error(conn, 404) end end def countries_preview(conn, %{"domain" => domain}) do site = Repo.get_by(Plausible.Site, domain: domain) {conn, params} = fetch_period(conn, site) query = Stats.Query.from(site.timezone, params) if site && current_user_can_access?(conn, site) do render(conn, "countries_preview.html", top_countries: Stats.countries(site, query), site: site, query: query, layout: false ) else render_error(conn, 404) end end def main_graph(conn, %{"domain" => domain}) do site = Repo.get_by(Plausible.Site, domain: domain) {conn, params} = fetch_period(conn, site) query = Stats.Query.from(site.timezone, params) plot_task = Task.async(fn -> Stats.calculate_plot(site, query) end) {pageviews, visitors} = Stats.pageviews_and_visitors(site, query) {plot, labels, present_index} = Task.await(plot_task) json(conn, %{ plot: plot, labels: labels, present_index: present_index, pageviews: pageviews, unique_visitors: visitors, interval: query.step_type }) end def compare(conn, %{"domain" => domain}) do site = Repo.get_by(Plausible.Site, domain: domain) {conn, params} = fetch_period(conn, site) query = Stats.Query.from(site.timezone, params) {pageviews, ""} = Integer.parse(conn.params["pageviews"]) {unique_visitors, ""} = Integer.parse(conn.params["unique_visitors"]) if site && current_user_can_access?(conn, site) do {change_pageviews, change_visitors} = Stats.compare_pageviews_and_visitors(site, query, {pageviews, unique_visitors}) json(conn, %{ change_pageviews: change_pageviews, change_visitors: change_visitors }) end end def referrers(conn, %{"domain" => domain}) do site = Repo.get_by(Plausible.Site, domain: domain) if site && current_user_can_access?(conn, site) do {conn, params} = fetch_period(conn, site) query = Stats.Query.from(site.timezone, params) referrers = Stats.top_referrers(site, query, 100) render(conn, "referrers.html", layout: false, site: site, top_referrers: referrers, query: query) else render_error(conn, 404) end end def referrer_drilldown(conn, %{"domain" => domain, "referrer" => "Google"}) do site = Repo.get_by(Plausible.Site, domain: domain) |> Repo.preload(:google_auth) if site && current_user_can_access?(conn, site) do {conn, params} = fetch_period(conn, site) query = Stats.Query.from(site.timezone, params) total_visitors = Stats.visitors_from_referrer(site, query, "Google") search_terms = if site.google_auth && site.google_auth.property do Plausible.Google.Api.fetch_stats(site.google_auth, query) end render(conn, "google_referrer.html", layout: false, site: site, search_terms: search_terms, total_visitors: total_visitors, query: query ) else render_error(conn, 404) end end def referrer_drilldown(conn, %{"domain" => domain, "referrer" => referrer}) do site = Repo.get_by(Plausible.Site, domain: domain) if site && current_user_can_access?(conn, site) do {conn, params} = fetch_period(conn, site) query = Stats.Query.from(site.timezone, params) referrers = Stats.referrer_drilldown(site, query, referrer) total_visitors = Stats.visitors_from_referrer(site, query, referrer) render(conn, "referrer_drilldown.html", layout: false, site: site, referrers: referrers, referrer: referrer, total_visitors: total_visitors, query: query) else render_error(conn, 404) end end def pages(conn, %{"domain" => domain}) do site = Repo.get_by(Plausible.Site, domain: domain) if site && current_user_can_access?(conn, site) do {conn, params} = fetch_period(conn, site) query = Stats.Query.from(site.timezone, params) pages = Stats.top_pages(site, query, 100) render(conn, "pages.html", layout: false, site: site, top_pages: pages) else render_error(conn, 404) end end def countries(conn, %{"domain" => domain}) do site = Repo.get_by(Plausible.Site, domain: domain) if site && current_user_can_access?(conn, site) do {conn, params} = fetch_period(conn, site) query = Stats.Query.from(site.timezone, params) countries = Stats.countries(site, query, 100) render(conn, "countries.html", layout: false, site: site, countries: countries) else render_error(conn, 404) end end def operating_systems(conn, %{"domain" => domain}) do site = Repo.get_by(Plausible.Site, domain: domain) if site && current_user_can_access?(conn, site) do {conn, params} = fetch_period(conn, site) query = Stats.Query.from(site.timezone, params) operating_systems = Stats.operating_systems(site, query, 100) render(conn, "operating_systems.html", layout: false, site: site, operating_systems: operating_systems) else render_error(conn, 404) end end def browsers(conn, %{"domain" => domain}) do site = Repo.get_by(Plausible.Site, domain: domain) if site && current_user_can_access?(conn, site) do {conn, params} = fetch_period(conn, site) query = Stats.Query.from(site.timezone, params) browsers = Stats.browsers(site, query, 100) render(conn, "browsers.html", layout: false, site: site, browsers: browsers) else render_error(conn, 404) end end def current_visitors(conn, %{"domain" => domain}) do site = Repo.get_by(Plausible.Site, domain: domain) if site && current_user_can_access?(conn, site) do json(conn, Stats.current_visitors(site)) else render_error(conn, 404) end end defp current_user_can_access?(_conn, %Plausible.Site{public: true}) do true end defp current_user_can_access?(conn, site) do case conn.assigns[:current_user] do nil -> false user -> Plausible.Sites.is_owner?(user.id, site) end end defp fetch_period(conn, site) do case conn.params["period"] do p when p in ["day", "month", "7d", "3mo", "6mo"] -> saved_periods = get_session(conn, :saved_periods) || %{} {put_session(conn, :saved_periods, Map.merge(saved_periods, %{site.domain => p})), conn.params} _ -> saved_period = (get_session(conn, :saved_periods) || %{})[site.domain] if saved_period do {conn, %{"period" => saved_period}} else {conn, conn.params} end end end end