mirror of
https://github.com/plausible/analytics.git
synced 2024-12-26 11:02:52 +03:00
3380685d40
* Fix custom property total conversions value not displayed The custom property conversion metrics are not consistent with the other metrics resulting in the total conversions not being displayed in the dashboard. This fixes that. * Export custom props of current goal when filtering dashboard for goal This makes the CSV export also output a `prop_breakdown.csv` file which, for the currently filtered goal, contains the conversion data for each of its configured properties. * Add test for goal-filtered CSV export
185 lines
6.4 KiB
Elixir
185 lines
6.4 KiB
Elixir
defmodule PlausibleWeb.StatsController do
|
|
use PlausibleWeb, :controller
|
|
use Plausible.Repo
|
|
alias PlausibleWeb.Api
|
|
alias Plausible.Stats.{Query, Filters}
|
|
|
|
plug PlausibleWeb.AuthorizeSiteAccess when action in [:stats, :csv_export]
|
|
|
|
def stats(%{assigns: %{site: site}} = conn, _params) do
|
|
has_stats = Plausible.Sites.has_stats?(site)
|
|
|
|
cond do
|
|
!site.locked && has_stats ->
|
|
demo = site.domain == PlausibleWeb.Endpoint.host()
|
|
offer_email_report = get_session(conn, site.domain <> "_offer_email_report")
|
|
|
|
conn
|
|
|> assign(:skip_plausible_tracking, !demo)
|
|
|> remove_email_report_banner(site)
|
|
|> put_resp_header("x-robots-tag", "noindex")
|
|
|> render("stats.html",
|
|
site: site,
|
|
has_goals: Plausible.Sites.has_goals?(site),
|
|
title: "Plausible · " <> site.domain,
|
|
offer_email_report: offer_email_report,
|
|
demo: demo
|
|
)
|
|
|
|
!site.locked && !has_stats ->
|
|
conn
|
|
|> assign(:skip_plausible_tracking, true)
|
|
|> render("waiting_first_pageview.html", site: site)
|
|
|
|
site.locked ->
|
|
owner = Plausible.Sites.owner_for(site)
|
|
|
|
conn
|
|
|> assign(:skip_plausible_tracking, true)
|
|
|> render("site_locked.html", owner: owner, site: site)
|
|
end
|
|
end
|
|
|
|
def csv_export(conn, params) do
|
|
site = conn.assigns[:site]
|
|
query = Query.from(site.timezone, params) |> Filters.add_prefix()
|
|
|
|
metrics = ["visitors", "pageviews", "bounce_rate", "visit_duration"]
|
|
graph = Plausible.Stats.timeseries(site, query, metrics)
|
|
headers = ["date" | metrics]
|
|
|
|
visitors =
|
|
Enum.map(graph, fn row -> Enum.map(headers, &row[&1]) end)
|
|
|> (fn data -> [headers | data] end).()
|
|
|> CSV.encode()
|
|
|> Enum.join()
|
|
|
|
filename =
|
|
"Plausible export #{params["domain"]} #{Timex.format!(query.date_range.first, "{ISOdate} ")} to #{Timex.format!(query.date_range.last, "{ISOdate} ")}.zip"
|
|
|
|
params = Map.merge(params, %{"limit" => "300", "csv" => "True", "detailed" => "True"})
|
|
|
|
csvs = [
|
|
{'sources.csv', fn -> Api.StatsController.sources(conn, params) end},
|
|
{'utm_mediums.csv', fn -> Api.StatsController.utm_mediums(conn, params) end},
|
|
{'utm_sources.csv', fn -> Api.StatsController.utm_sources(conn, params) end},
|
|
{'utm_campaigns.csv', fn -> Api.StatsController.utm_campaigns(conn, params) end},
|
|
{'pages.csv', fn -> Api.StatsController.pages(conn, params) end},
|
|
{'entry_pages.csv', fn -> Api.StatsController.entry_pages(conn, params) end},
|
|
{'exit_pages.csv', fn -> Api.StatsController.exit_pages(conn, params) end},
|
|
{'countries.csv', fn -> Api.StatsController.countries(conn, params) end},
|
|
{'browsers.csv', fn -> Api.StatsController.browsers(conn, params) end},
|
|
{'operating_systems.csv', fn -> Api.StatsController.operating_systems(conn, params) end},
|
|
{'devices.csv', fn -> Api.StatsController.screen_sizes(conn, params) end},
|
|
{'conversions.csv', fn -> Api.StatsController.conversions(conn, params) end},
|
|
{'prop_breakdown.csv', fn -> Api.StatsController.all_props_breakdown(conn, params) end}
|
|
]
|
|
|
|
csvs =
|
|
csvs
|
|
|> Enum.map(fn {file, task} -> {file, Task.async(task)} end)
|
|
|> Enum.map(fn {file, task} -> {file, Task.await(task)} end)
|
|
|
|
csvs = [{'visitors.csv', visitors} | csvs]
|
|
|
|
{:ok, {_, zip_content}} = :zip.create(filename, csvs, [:memory])
|
|
|
|
conn
|
|
|> put_resp_content_type("application/zip")
|
|
|> put_resp_header("content-disposition", "attachment; filename=\"#{filename}\"")
|
|
|> delete_resp_cookie("exporting")
|
|
|> send_resp(200, zip_content)
|
|
end
|
|
|
|
def shared_link(conn, %{"slug" => domain, "auth" => auth}) do
|
|
shared_link =
|
|
Repo.get_by(Plausible.Site.SharedLink, slug: auth)
|
|
|> Repo.preload(:site)
|
|
|
|
if shared_link && shared_link.site.domain == domain do
|
|
if shared_link.password_hash do
|
|
with conn <- Plug.Conn.fetch_cookies(conn),
|
|
{:ok, token} <- Map.fetch(conn.req_cookies, "shared-link-token"),
|
|
{:ok, _} <- Plausible.Auth.Token.verify_shared_link(token) do
|
|
render_shared_link(conn, shared_link)
|
|
else
|
|
_e ->
|
|
conn
|
|
|> assign(:skip_plausible_tracking, true)
|
|
|> render("shared_link_password.html",
|
|
link: shared_link,
|
|
layout: {PlausibleWeb.LayoutView, "focus.html"}
|
|
)
|
|
end
|
|
else
|
|
render_shared_link(conn, shared_link)
|
|
end
|
|
end
|
|
end
|
|
|
|
def shared_link(conn, %{"slug" => slug}) do
|
|
shared_link =
|
|
Repo.get_by(Plausible.Site.SharedLink, slug: slug)
|
|
|> Repo.preload(:site)
|
|
|
|
if shared_link do
|
|
redirect(conn, to: "/share/#{URI.encode_www_form(shared_link.site.domain)}?auth=#{slug}")
|
|
else
|
|
render_error(conn, 404)
|
|
end
|
|
end
|
|
|
|
def authenticate_shared_link(conn, %{"slug" => slug, "password" => password}) do
|
|
shared_link =
|
|
Repo.get_by(Plausible.Site.SharedLink, slug: slug)
|
|
|> Repo.preload(:site)
|
|
|
|
if shared_link do
|
|
if Plausible.Auth.Password.match?(password, shared_link.password_hash) do
|
|
token = Plausible.Auth.Token.sign_shared_link(slug)
|
|
|
|
conn
|
|
|> put_resp_cookie("shared-link-token", token)
|
|
|> redirect(to: "/share/#{URI.encode_www_form(shared_link.site.domain)}?auth=#{slug}")
|
|
else
|
|
conn
|
|
|> assign(:skip_plausible_tracking, true)
|
|
|> render("shared_link_password.html",
|
|
link: shared_link,
|
|
error: "Incorrect password. Please try again.",
|
|
layout: {PlausibleWeb.LayoutView, "focus.html"}
|
|
)
|
|
end
|
|
else
|
|
render_error(conn, 404)
|
|
end
|
|
end
|
|
|
|
defp render_shared_link(conn, shared_link) do
|
|
conn
|
|
|> assign(:skip_plausible_tracking, true)
|
|
|> put_resp_header("x-robots-tag", "noindex")
|
|
|> delete_resp_header("x-frame-options")
|
|
|> render("stats.html",
|
|
site: shared_link.site,
|
|
has_goals: Plausible.Sites.has_goals?(shared_link.site),
|
|
title: "Plausible · " <> shared_link.site.domain,
|
|
offer_email_report: false,
|
|
demo: false,
|
|
skip_plausible_tracking: true,
|
|
shared_link_auth: shared_link.slug,
|
|
embedded: conn.params["embed"] == "true",
|
|
background: conn.params["background"],
|
|
theme: conn.params["theme"]
|
|
)
|
|
end
|
|
|
|
defp remove_email_report_banner(conn, site) do
|
|
if conn.assigns[:current_user] do
|
|
delete_session(conn, site.domain <> "_offer_email_report")
|
|
else
|
|
conn
|
|
end
|
|
end
|
|
end
|