mirror of
https://github.com/plausible/analytics.git
synced 2024-12-23 09:33:19 +03:00
Mix format
This commit is contained in:
parent
f0cbf33d7c
commit
0a7684f3bc
@ -1,7 +1,8 @@
|
||||
use Mix.Config
|
||||
|
||||
base_url = System.get_env("BASE_URL", "http://localhost:8000")
|
||||
|> URI.parse
|
||||
base_url =
|
||||
System.get_env("BASE_URL", "http://localhost:8000")
|
||||
|> URI.parse()
|
||||
|
||||
config :plausible,
|
||||
admin_user: System.get_env("ADMIN_USER_NAME", "admin"),
|
||||
@ -81,10 +82,11 @@ config :plausible, :paddle,
|
||||
|
||||
config :plausible, Plausible.ClickhouseRepo,
|
||||
loggers: [Ecto.LogEntry],
|
||||
url: System.get_env(
|
||||
"CLICKHOUSE_DATABASE_URL",
|
||||
"http://127.0.0.1:8123/plausible_dev"
|
||||
)
|
||||
url:
|
||||
System.get_env(
|
||||
"CLICKHOUSE_DATABASE_URL",
|
||||
"http://127.0.0.1:8123/plausible_dev"
|
||||
)
|
||||
|
||||
config :plausible,
|
||||
Plausible.Repo,
|
||||
|
@ -5,8 +5,10 @@ import Config
|
||||
# params are made optional to facilitate smooth release
|
||||
|
||||
port = System.get_env("PORT") || 8000
|
||||
base_url = System.get_env("BASE_URL", "http://localhost:8000")
|
||||
|> URI.parse
|
||||
|
||||
base_url =
|
||||
System.get_env("BASE_URL", "http://localhost:8000")
|
||||
|> URI.parse()
|
||||
|
||||
secret_key_base = System.get_env("SECRET_KEY_BASE")
|
||||
|
||||
@ -23,7 +25,10 @@ env = System.get_env("ENVIRONMENT", "prod")
|
||||
mailer_adapter = System.get_env("MAILER_ADAPTER", "Bamboo.SMTPAdapter")
|
||||
mailer_email = System.get_env("MAILER_EMAIL", "hello@plausible.local")
|
||||
app_version = System.get_env("APP_VERSION", "0.0.1")
|
||||
ch_db_url = System.get_env("CLICKHOUSE_DATABASE_URL", "http://plausible_events_db:8123/plausible_events_db")
|
||||
|
||||
ch_db_url =
|
||||
System.get_env("CLICKHOUSE_DATABASE_URL", "http://plausible_events_db:8123/plausible_events_db")
|
||||
|
||||
### Mandatory params End
|
||||
|
||||
sentry_dsn = System.get_env("SENTRY_DSN")
|
||||
@ -75,9 +80,9 @@ config :plausible, PlausibleWeb.Endpoint,
|
||||
code_reloader: false
|
||||
|
||||
config :plausible,
|
||||
Plausible.Repo,
|
||||
url: db_url,
|
||||
adapter: Ecto.Adapters.Postgres
|
||||
Plausible.Repo,
|
||||
url: db_url,
|
||||
adapter: Ecto.Adapters.Postgres
|
||||
|
||||
config :sentry,
|
||||
dsn: sentry_dsn,
|
||||
@ -194,10 +199,11 @@ if geolite2_country_db do
|
||||
]
|
||||
end
|
||||
|
||||
logger_backends = case logflare_api_key do
|
||||
api_key when is_binary(api_key) -> [LogflareLogger.HttpBackend]
|
||||
_ -> [:console]
|
||||
end
|
||||
logger_backends =
|
||||
case logflare_api_key do
|
||||
api_key when is_binary(api_key) -> [LogflareLogger.HttpBackend]
|
||||
_ -> [:console]
|
||||
end
|
||||
|
||||
config :logger,
|
||||
level: log_level,
|
||||
|
@ -25,10 +25,11 @@ config :plausible,
|
||||
config :plausible, Plausible.ClickhouseRepo,
|
||||
loggers: [Ecto.LogEntry],
|
||||
pool_size: String.to_integer(System.get_env("CLICKHOUSE_DATABASE_POOLSIZE", "5")),
|
||||
url: System.get_env(
|
||||
"CLICKHOUSE_DATABASE_URL",
|
||||
"http://127.0.0.1:8123/plausible_test"
|
||||
)
|
||||
url:
|
||||
System.get_env(
|
||||
"CLICKHOUSE_DATABASE_URL",
|
||||
"http://127.0.0.1:8123/plausible_test"
|
||||
)
|
||||
|
||||
config :plausible, Plausible.Mailer, adapter: Bamboo.TestAdapter
|
||||
|
||||
|
@ -29,8 +29,9 @@ defmodule Plausible.Billing do
|
||||
end
|
||||
|
||||
def subscription_cancelled(params) do
|
||||
subscription = Repo.get_by(Subscription, paddle_subscription_id: params["subscription_id"])
|
||||
|> Repo.preload(:user)
|
||||
subscription =
|
||||
Repo.get_by(Subscription, paddle_subscription_id: params["subscription_id"])
|
||||
|> Repo.preload(:user)
|
||||
|
||||
if subscription do
|
||||
changeset =
|
||||
@ -42,8 +43,11 @@ defmodule Plausible.Billing do
|
||||
{:ok, updated} ->
|
||||
PlausibleWeb.Email.cancellation_email(subscription.user)
|
||||
|> Plausible.Mailer.send_email()
|
||||
|
||||
{:ok, updated}
|
||||
err -> err
|
||||
|
||||
err ->
|
||||
err
|
||||
end
|
||||
else
|
||||
{:ok, nil}
|
||||
|
@ -33,11 +33,12 @@ defmodule Plausible.Google.Api do
|
||||
Authorization: "Bearer #{auth.access_token}"
|
||||
)
|
||||
|
||||
domains = Jason.decode!(res.body)
|
||||
|> Map.get("siteEntry", [])
|
||||
|> Enum.filter(fn site -> site["permissionLevel"] in @verified_permission_levels end)
|
||||
|> Enum.map(fn site -> site["siteUrl"] end)
|
||||
|> Enum.map(fn url -> String.trim_trailing(url, "/") end)
|
||||
domains =
|
||||
Jason.decode!(res.body)
|
||||
|> Map.get("siteEntry", [])
|
||||
|> Enum.filter(fn site -> site["permissionLevel"] in @verified_permission_levels end)
|
||||
|> Enum.map(fn site -> site["siteUrl"] end)
|
||||
|> Enum.map(fn url -> String.trim_trailing(url, "/") end)
|
||||
|
||||
{:ok, domains}
|
||||
else
|
||||
@ -48,7 +49,7 @@ defmodule Plausible.Google.Api do
|
||||
defp property_base_url(property) do
|
||||
case property do
|
||||
"sc-domain:" <> domain -> "https://" <> domain
|
||||
url -> url
|
||||
url -> url
|
||||
end
|
||||
end
|
||||
|
||||
@ -63,12 +64,20 @@ defmodule Plausible.Google.Api do
|
||||
defp do_fetch_stats(auth, query, limit) do
|
||||
property = URI.encode_www_form(auth.property)
|
||||
base_url = property_base_url(auth.property)
|
||||
filter_groups = if query.filters["page"] do
|
||||
[%{filters: [%{
|
||||
dimension: "page",
|
||||
expression: "https://#{base_url}#{query.filters["page"]}"
|
||||
}]}]
|
||||
end
|
||||
|
||||
filter_groups =
|
||||
if query.filters["page"] do
|
||||
[
|
||||
%{
|
||||
filters: [
|
||||
%{
|
||||
dimension: "page",
|
||||
expression: "https://#{base_url}#{query.filters["page"]}"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
res =
|
||||
HTTPoison.post!(
|
||||
|
@ -40,7 +40,8 @@ defmodule Plausible.Stats.Clickhouse do
|
||||
fragment("uniq(?)", e.user_id)},
|
||||
group_by: fragment("month"),
|
||||
order_by: fragment("month")
|
||||
) |> Enum.into(%{})
|
||||
)
|
||||
|> Enum.into(%{})
|
||||
|
||||
present_index =
|
||||
Enum.find_index(steps, fn step ->
|
||||
@ -64,7 +65,8 @@ defmodule Plausible.Stats.Clickhouse do
|
||||
fragment("uniq(?)", e.user_id)},
|
||||
group_by: fragment("day"),
|
||||
order_by: fragment("day")
|
||||
) |> Enum.into(%{})
|
||||
)
|
||||
|> Enum.into(%{})
|
||||
|
||||
present_index =
|
||||
Enum.find_index(steps, fn step -> step == Timex.now(site.timezone) |> Timex.to_date() end)
|
||||
@ -87,7 +89,8 @@ defmodule Plausible.Stats.Clickhouse do
|
||||
fragment("uniq(?)", e.user_id)},
|
||||
group_by: fragment("hour"),
|
||||
order_by: fragment("hour")
|
||||
) |> Enum.into(%{})
|
||||
)
|
||||
|> Enum.into(%{})
|
||||
|
||||
now = Timex.now(site.timezone)
|
||||
is_today = Timex.to_date(now) == query.date_range.first
|
||||
@ -117,7 +120,8 @@ defmodule Plausible.Stats.Clickhouse do
|
||||
},
|
||||
group_by: fragment("relativeMinute"),
|
||||
order_by: fragment("relativeMinute")
|
||||
) |> Enum.into(%{})
|
||||
)
|
||||
|> Enum.into(%{})
|
||||
|
||||
labels = Enum.into(-30..-1, [])
|
||||
plot = Enum.map(labels, fn label -> groups[label] || 0 end)
|
||||
@ -143,28 +147,28 @@ defmodule Plausible.Stats.Clickhouse do
|
||||
|
||||
ClickhouseRepo.one(
|
||||
from e in base_session_query(site, query),
|
||||
select: fragment("sum(sign * pageviews)")
|
||||
select: fragment("sum(sign * pageviews)")
|
||||
)
|
||||
end
|
||||
|
||||
def total_events(site, query) do
|
||||
ClickhouseRepo.one(
|
||||
from e in base_query_w_sessions(site, query),
|
||||
select: fragment("count(*) as events")
|
||||
select: fragment("count(*) as events")
|
||||
)
|
||||
end
|
||||
|
||||
def pageviews_and_visitors(site, query) do
|
||||
ClickhouseRepo.one(
|
||||
from e in base_query_w_sessions(site, query),
|
||||
select: {fragment("count(*)"), fragment("uniq(user_id)")}
|
||||
select: {fragment("count(*)"), fragment("uniq(user_id)")}
|
||||
)
|
||||
end
|
||||
|
||||
def unique_visitors(site, query) do
|
||||
ClickhouseRepo.one(
|
||||
from e in base_query_w_sessions(site, query),
|
||||
select: fragment("uniq(user_id)")
|
||||
select: fragment("uniq(user_id)")
|
||||
)
|
||||
end
|
||||
|
||||
@ -173,17 +177,18 @@ defmodule Plausible.Stats.Clickhouse do
|
||||
|
||||
ClickhouseRepo.all(
|
||||
from s in base_query_w_sessions(site, query),
|
||||
where: s.referrer_source != "",
|
||||
group_by: s.referrer_source,
|
||||
order_by: [desc: fragment("count")],
|
||||
limit: ^limit,
|
||||
offset: ^offset,
|
||||
select: %{
|
||||
name: s.referrer_source,
|
||||
url: fragment("any(?)", s.referrer),
|
||||
count: fragment("uniq(?) as count", s.user_id)
|
||||
}
|
||||
) |> Enum.map(fn ref ->
|
||||
where: s.referrer_source != "",
|
||||
group_by: s.referrer_source,
|
||||
order_by: [desc: fragment("count")],
|
||||
limit: ^limit,
|
||||
offset: ^offset,
|
||||
select: %{
|
||||
name: s.referrer_source,
|
||||
url: fragment("any(?)", s.referrer),
|
||||
count: fragment("uniq(?) as count", s.user_id)
|
||||
}
|
||||
)
|
||||
|> Enum.map(fn ref ->
|
||||
Map.update(ref, :url, nil, fn url -> url && URI.parse("http://" <> url).host end)
|
||||
end)
|
||||
end
|
||||
@ -197,30 +202,40 @@ defmodule Plausible.Stats.Clickhouse do
|
||||
order_by: [desc: fragment("count"), asc: fragment("min(start)")],
|
||||
limit: ^limit,
|
||||
offset: ^offset
|
||||
) |> filter_converted_sessions(site, query)
|
||||
)
|
||||
|> filter_converted_sessions(site, query)
|
||||
|
||||
referrers = if show_noref do
|
||||
referrers
|
||||
else
|
||||
from(s in referrers, where: s.referrer_source != "")
|
||||
end
|
||||
referrers =
|
||||
if show_noref do
|
||||
referrers
|
||||
else
|
||||
from(s in referrers, where: s.referrer_source != "")
|
||||
end
|
||||
|
||||
referrers = if query.filters["page"] do
|
||||
page = query.filters["page"]
|
||||
from(s in referrers, where: s.entry_page == ^page)
|
||||
else
|
||||
referrers
|
||||
end
|
||||
referrers =
|
||||
if query.filters["page"] do
|
||||
page = query.filters["page"]
|
||||
from(s in referrers, where: s.entry_page == ^page)
|
||||
else
|
||||
referrers
|
||||
end
|
||||
|
||||
referrers =
|
||||
if "bounce_rate" in include do
|
||||
from(
|
||||
s in referrers,
|
||||
select: %{
|
||||
name: fragment("if(empty(?), ?, ?) as name", s.referrer_source, @no_ref, s.referrer_source),
|
||||
name:
|
||||
fragment(
|
||||
"if(empty(?), ?, ?) as name",
|
||||
s.referrer_source,
|
||||
@no_ref,
|
||||
s.referrer_source
|
||||
),
|
||||
url: fragment("any(?)", s.referrer),
|
||||
count: fragment("uniq(user_id) as count"),
|
||||
bounce_rate: fragment("round(sum(is_bounce * sign) / sum(sign) * 100) as bounce_rate"),
|
||||
bounce_rate:
|
||||
fragment("round(sum(is_bounce * sign) / sum(sign) * 100) as bounce_rate"),
|
||||
visit_duration: fragment("round(avg(duration * sign)) as visit_duration")
|
||||
}
|
||||
)
|
||||
@ -228,53 +243,64 @@ defmodule Plausible.Stats.Clickhouse do
|
||||
from(
|
||||
s in referrers,
|
||||
select: %{
|
||||
name: fragment("if(empty(?), ?, ?) as name", s.referrer_source, @no_ref, s.referrer_source),
|
||||
name:
|
||||
fragment(
|
||||
"if(empty(?), ?, ?) as name",
|
||||
s.referrer_source,
|
||||
@no_ref,
|
||||
s.referrer_source
|
||||
),
|
||||
url: fragment("any(?)", s.referrer),
|
||||
count: fragment("uniq(user_id) as count")
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
ClickhouseRepo.all(referrers)
|
||||
|> Enum.map(fn ref ->
|
||||
Map.update(ref, :url, nil, fn url -> url && URI.parse("http://" <> url).host end)
|
||||
end)
|
||||
ClickhouseRepo.all(referrers)
|
||||
|> Enum.map(fn ref ->
|
||||
Map.update(ref, :url, nil, fn url -> url && URI.parse("http://" <> url).host end)
|
||||
end)
|
||||
end
|
||||
|
||||
defp filter_converted_sessions(db_query, site, %Query{filters: %{"goal" => goal}} = query) when is_binary(goal) do
|
||||
defp filter_converted_sessions(db_query, site, %Query{filters: %{"goal" => goal}} = query)
|
||||
when is_binary(goal) do
|
||||
converted_sessions =
|
||||
from(e in base_query(site, query),
|
||||
select: %{session_id: e.session_id})
|
||||
select: %{session_id: e.session_id}
|
||||
)
|
||||
|
||||
from(s in db_query,
|
||||
join: cs in subquery(converted_sessions),
|
||||
on: s.session_id == cs.session_id
|
||||
)
|
||||
end
|
||||
|
||||
defp filter_converted_sessions(db_query, _site, _query), do: db_query
|
||||
|
||||
def utm_mediums(site, query, limit \\ 9, page \\ 1, show_noref \\ false) do
|
||||
offset = (page - 1) * limit
|
||||
|
||||
q = from(
|
||||
s in base_session_query(site, query),
|
||||
group_by: s.utm_medium,
|
||||
order_by: [desc: fragment("count"), asc: fragment("min(start)")],
|
||||
limit: ^limit,
|
||||
offset: ^offset,
|
||||
select: %{
|
||||
name: fragment("if(empty(?), ?, ?) as name", s.utm_medium, @no_ref, s.utm_medium),
|
||||
count: fragment("uniq(user_id) as count"),
|
||||
bounce_rate: fragment("round(sum(is_bounce * sign) / sum(sign) * 100)"),
|
||||
visit_duration: fragment("round(avg(duration * sign))")
|
||||
}
|
||||
)
|
||||
q =
|
||||
from(
|
||||
s in base_session_query(site, query),
|
||||
group_by: s.utm_medium,
|
||||
order_by: [desc: fragment("count"), asc: fragment("min(start)")],
|
||||
limit: ^limit,
|
||||
offset: ^offset,
|
||||
select: %{
|
||||
name: fragment("if(empty(?), ?, ?) as name", s.utm_medium, @no_ref, s.utm_medium),
|
||||
count: fragment("uniq(user_id) as count"),
|
||||
bounce_rate: fragment("round(sum(is_bounce * sign) / sum(sign) * 100)"),
|
||||
visit_duration: fragment("round(avg(duration * sign))")
|
||||
}
|
||||
)
|
||||
|
||||
q = if show_noref do
|
||||
q
|
||||
else
|
||||
from(s in q, where: s.utm_medium != "")
|
||||
end
|
||||
q =
|
||||
if show_noref do
|
||||
q
|
||||
else
|
||||
from(s in q, where: s.utm_medium != "")
|
||||
end
|
||||
|
||||
q
|
||||
|> filter_converted_sessions(site, query)
|
||||
@ -284,25 +310,27 @@ defmodule Plausible.Stats.Clickhouse do
|
||||
def utm_campaigns(site, query, limit \\ 9, page \\ 1, show_noref \\ false) do
|
||||
offset = (page - 1) * limit
|
||||
|
||||
q = from(
|
||||
s in base_session_query(site, query),
|
||||
group_by: s.utm_campaign,
|
||||
order_by: [desc: fragment("count"), asc: fragment("min(start)")],
|
||||
limit: ^limit,
|
||||
offset: ^offset,
|
||||
select: %{
|
||||
name: fragment("if(empty(?), ?, ?) as name", s.utm_campaign, @no_ref, s.utm_campaign),
|
||||
count: fragment("uniq(user_id) as count"),
|
||||
bounce_rate: fragment("round(sum(is_bounce * sign) / sum(sign) * 100)"),
|
||||
visit_duration: fragment("round(avg(duration * sign))")
|
||||
}
|
||||
)
|
||||
q =
|
||||
from(
|
||||
s in base_session_query(site, query),
|
||||
group_by: s.utm_campaign,
|
||||
order_by: [desc: fragment("count"), asc: fragment("min(start)")],
|
||||
limit: ^limit,
|
||||
offset: ^offset,
|
||||
select: %{
|
||||
name: fragment("if(empty(?), ?, ?) as name", s.utm_campaign, @no_ref, s.utm_campaign),
|
||||
count: fragment("uniq(user_id) as count"),
|
||||
bounce_rate: fragment("round(sum(is_bounce * sign) / sum(sign) * 100)"),
|
||||
visit_duration: fragment("round(avg(duration * sign))")
|
||||
}
|
||||
)
|
||||
|
||||
q = if show_noref do
|
||||
q
|
||||
else
|
||||
from(s in q, where: s.utm_campaign != "")
|
||||
end
|
||||
q =
|
||||
if show_noref do
|
||||
q
|
||||
else
|
||||
from(s in q, where: s.utm_campaign != "")
|
||||
end
|
||||
|
||||
q
|
||||
|> filter_converted_sessions(site, query)
|
||||
@ -312,25 +340,27 @@ defmodule Plausible.Stats.Clickhouse do
|
||||
def utm_sources(site, query, limit \\ 9, page \\ 1, show_noref \\ false) do
|
||||
offset = (page - 1) * limit
|
||||
|
||||
q = from(
|
||||
s in base_session_query(site, query),
|
||||
group_by: s.utm_source,
|
||||
order_by: [desc: fragment("count"), asc: fragment("min(start)")],
|
||||
limit: ^limit,
|
||||
offset: ^offset,
|
||||
select: %{
|
||||
name: fragment("if(empty(?), ?, ?) as name", s.utm_source, @no_ref, s.utm_source),
|
||||
count: fragment("uniq(user_id) as count"),
|
||||
bounce_rate: fragment("round(sum(is_bounce * sign) / sum(sign) * 100)"),
|
||||
visit_duration: fragment("round(avg(duration * sign))")
|
||||
}
|
||||
)
|
||||
q =
|
||||
from(
|
||||
s in base_session_query(site, query),
|
||||
group_by: s.utm_source,
|
||||
order_by: [desc: fragment("count"), asc: fragment("min(start)")],
|
||||
limit: ^limit,
|
||||
offset: ^offset,
|
||||
select: %{
|
||||
name: fragment("if(empty(?), ?, ?) as name", s.utm_source, @no_ref, s.utm_source),
|
||||
count: fragment("uniq(user_id) as count"),
|
||||
bounce_rate: fragment("round(sum(is_bounce * sign) / sum(sign) * 100)"),
|
||||
visit_duration: fragment("round(avg(duration * sign))")
|
||||
}
|
||||
)
|
||||
|
||||
q = if show_noref do
|
||||
q
|
||||
else
|
||||
from(s in q, where: s.utm_source != "")
|
||||
end
|
||||
q =
|
||||
if show_noref do
|
||||
q
|
||||
else
|
||||
from(s in q, where: s.utm_source != "")
|
||||
end
|
||||
|
||||
q
|
||||
|> filter_converted_sessions(site, query)
|
||||
@ -346,10 +376,10 @@ defmodule Plausible.Stats.Clickhouse do
|
||||
|
||||
ClickhouseRepo.one(
|
||||
from s in Plausible.ClickhouseSession,
|
||||
join: cs in subquery(converted_sessions),
|
||||
on: s.session_id == cs.session_id,
|
||||
where: s.referrer_source == ^referrer,
|
||||
select: fragment("uniq(user_id) as visitors")
|
||||
join: cs in subquery(converted_sessions),
|
||||
on: s.session_id == cs.session_id,
|
||||
where: s.referrer_source == ^referrer,
|
||||
select: fragment("uniq(user_id) as visitors")
|
||||
)
|
||||
end
|
||||
|
||||
@ -363,7 +393,8 @@ defmodule Plausible.Stats.Clickhouse do
|
||||
where: s.referrer_source == ^referrer,
|
||||
order_by: [desc: fragment("count")],
|
||||
limit: ^limit
|
||||
) |> filter_converted_sessions(site, query)
|
||||
)
|
||||
|> filter_converted_sessions(site, query)
|
||||
|
||||
q =
|
||||
if "bounce_rate" in include do
|
||||
@ -372,9 +403,11 @@ defmodule Plausible.Stats.Clickhouse do
|
||||
select: %{
|
||||
name: fragment("if(empty(?), ?, ?) as name", s.referrer, @no_ref, s.referrer),
|
||||
count: fragment("uniq(user_id) as count"),
|
||||
bounce_rate: fragment("round(sum(is_bounce * sign) / sum(sign) * 100) as bounce_rate"),
|
||||
bounce_rate:
|
||||
fragment("round(sum(is_bounce * sign) / sum(sign) * 100) as bounce_rate"),
|
||||
visit_duration: fragment("round(avg(duration * sign)) as visit_duration")
|
||||
})
|
||||
}
|
||||
)
|
||||
else
|
||||
from(s in q,
|
||||
select: %{
|
||||
@ -412,35 +445,37 @@ defmodule Plausible.Stats.Clickhouse do
|
||||
def referrer_drilldown_for_goal(site, query, referrer) do
|
||||
Plausible.ClickhouseRepo.all(
|
||||
from s in base_query_w_sessions(site, query),
|
||||
where: s.referrer_source == ^referrer,
|
||||
group_by: s.referrer,
|
||||
order_by: [desc: fragment("count")],
|
||||
limit: 100,
|
||||
select: %{
|
||||
name: s.referrer,
|
||||
count: fragment("uniq(user_id) as count")
|
||||
}
|
||||
where: s.referrer_source == ^referrer,
|
||||
group_by: s.referrer,
|
||||
order_by: [desc: fragment("count")],
|
||||
limit: 100,
|
||||
select: %{
|
||||
name: s.referrer,
|
||||
count: fragment("uniq(user_id) as count")
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
def entry_pages(site, query, limit, include) do
|
||||
q = from(
|
||||
s in base_session_query(site, query),
|
||||
group_by: s.entry_page,
|
||||
order_by: [desc: fragment("count")],
|
||||
limit: ^limit,
|
||||
select: %{
|
||||
name: s.entry_page,
|
||||
count: fragment("uniq(?) as count", s.user_id)
|
||||
}
|
||||
)
|
||||
q =
|
||||
from(
|
||||
s in base_session_query(site, query),
|
||||
group_by: s.entry_page,
|
||||
order_by: [desc: fragment("count")],
|
||||
limit: ^limit,
|
||||
select: %{
|
||||
name: s.entry_page,
|
||||
count: fragment("uniq(?) as count", s.user_id)
|
||||
}
|
||||
)
|
||||
|
||||
q = if query.filters["page"] do
|
||||
page = query.filters["page"]
|
||||
from(s in q, where: s.entry_page == ^page)
|
||||
else
|
||||
q
|
||||
end
|
||||
q =
|
||||
if query.filters["page"] do
|
||||
page = query.filters["page"]
|
||||
from(s in q, where: s.entry_page == ^page)
|
||||
else
|
||||
q
|
||||
end
|
||||
|
||||
pages = ClickhouseRepo.all(q)
|
||||
|
||||
@ -455,13 +490,13 @@ defmodule Plausible.Stats.Clickhouse do
|
||||
def top_pages(site, %Query{period: "realtime"} = query, limit, _include) do
|
||||
ClickhouseRepo.all(
|
||||
from s in base_session_query(site, query),
|
||||
group_by: s.exit_page,
|
||||
order_by: [desc: fragment("count")],
|
||||
limit: ^limit,
|
||||
select: %{
|
||||
name: fragment("? as name", s.exit_page),
|
||||
count: fragment("uniq(?) as count", s.user_id)
|
||||
}
|
||||
group_by: s.exit_page,
|
||||
order_by: [desc: fragment("count")],
|
||||
limit: ^limit,
|
||||
select: %{
|
||||
name: fragment("? as name", s.exit_page),
|
||||
count: fragment("uniq(?) as count", s.user_id)
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
@ -492,14 +527,14 @@ defmodule Plausible.Stats.Clickhouse do
|
||||
defp bounce_rates_by_page_url(site, query) do
|
||||
ClickhouseRepo.all(
|
||||
from s in base_session_query(site, query),
|
||||
group_by: s.entry_page,
|
||||
order_by: [desc: fragment("total")],
|
||||
limit: 100,
|
||||
select: %{
|
||||
entry_page: s.entry_page,
|
||||
total: fragment("count(*) as total"),
|
||||
bounce_rate: fragment("round(sum(is_bounce * sign) / sum(sign) * 100) as bounce_rate")
|
||||
}
|
||||
group_by: s.entry_page,
|
||||
order_by: [desc: fragment("total")],
|
||||
limit: 100,
|
||||
select: %{
|
||||
entry_page: s.entry_page,
|
||||
total: fragment("count(*) as total"),
|
||||
bounce_rate: fragment("round(sum(is_bounce * sign) / sum(sign) * 100) as bounce_rate")
|
||||
}
|
||||
)
|
||||
|> Enum.map(fn row -> {row[:entry_page], row[:bounce_rate]} end)
|
||||
|> Enum.into(%{})
|
||||
@ -516,26 +551,27 @@ defmodule Plausible.Stats.Clickhouse do
|
||||
def top_screen_sizes(site, query) do
|
||||
ClickhouseRepo.all(
|
||||
from e in base_query_w_sessions(site, query),
|
||||
group_by: e.screen_size,
|
||||
where: e.screen_size != "",
|
||||
order_by: [desc: fragment("count")],
|
||||
select: %{
|
||||
name: e.screen_size,
|
||||
count: fragment("uniq(user_id) as count")
|
||||
}
|
||||
) |> add_percentages
|
||||
group_by: e.screen_size,
|
||||
where: e.screen_size != "",
|
||||
order_by: [desc: fragment("count")],
|
||||
select: %{
|
||||
name: e.screen_size,
|
||||
count: fragment("uniq(user_id) as count")
|
||||
}
|
||||
)
|
||||
|> add_percentages
|
||||
end
|
||||
|
||||
def countries(site, query) do
|
||||
ClickhouseRepo.all(
|
||||
from e in base_query_w_sessions(site, query),
|
||||
group_by: e.country_code,
|
||||
where: e.country_code != "\0\0",
|
||||
order_by: [desc: fragment("count")],
|
||||
select: %{
|
||||
name: e.country_code,
|
||||
count: fragment("uniq(user_id) as count")
|
||||
}
|
||||
group_by: e.country_code,
|
||||
where: e.country_code != "\0\0",
|
||||
order_by: [desc: fragment("count")],
|
||||
select: %{
|
||||
name: e.country_code,
|
||||
count: fragment("uniq(user_id) as count")
|
||||
}
|
||||
)
|
||||
|> Enum.map(fn stat ->
|
||||
two_letter_code = stat[:name]
|
||||
@ -543,19 +579,20 @@ defmodule Plausible.Stats.Clickhouse do
|
||||
stat
|
||||
|> Map.put(:name, Plausible.Stats.CountryName.to_alpha3(two_letter_code))
|
||||
|> Map.put(:full_country_name, Plausible.Stats.CountryName.from_iso3166(two_letter_code))
|
||||
end) |> add_percentages
|
||||
end)
|
||||
|> add_percentages
|
||||
end
|
||||
|
||||
def browsers(site, query, limit \\ 5) do
|
||||
ClickhouseRepo.all(
|
||||
from e in base_query_w_sessions(site, query),
|
||||
group_by: e.browser,
|
||||
where: e.browser != "",
|
||||
order_by: [desc: fragment("count")],
|
||||
select: %{
|
||||
name: e.browser,
|
||||
count: fragment("uniq(user_id) as count")
|
||||
}
|
||||
group_by: e.browser,
|
||||
where: e.browser != "",
|
||||
order_by: [desc: fragment("count")],
|
||||
select: %{
|
||||
name: e.browser,
|
||||
count: fragment("uniq(user_id) as count")
|
||||
}
|
||||
)
|
||||
|> add_percentages
|
||||
|> Enum.take(limit)
|
||||
@ -564,23 +601,23 @@ defmodule Plausible.Stats.Clickhouse do
|
||||
def operating_systems(site, query, limit \\ 5) do
|
||||
ClickhouseRepo.all(
|
||||
from e in base_query_w_sessions(site, query),
|
||||
group_by: e.operating_system,
|
||||
where: e.operating_system != "",
|
||||
order_by: [desc: fragment("count")],
|
||||
select: %{
|
||||
name: e.operating_system,
|
||||
count: fragment("uniq(user_id) as count")
|
||||
}
|
||||
group_by: e.operating_system,
|
||||
where: e.operating_system != "",
|
||||
order_by: [desc: fragment("count")],
|
||||
select: %{
|
||||
name: e.operating_system,
|
||||
count: fragment("uniq(user_id) as count")
|
||||
}
|
||||
)
|
||||
|> add_percentages
|
||||
|> Enum.take(limit)
|
||||
end
|
||||
|
||||
def current_visitors(site, query) do
|
||||
Plausible.ClickhouseRepo.one(
|
||||
from s in base_query(site, query),
|
||||
select: fragment("uniq(user_id)")
|
||||
)
|
||||
Plausible.ClickhouseRepo.one(
|
||||
from s in base_query(site, query),
|
||||
select: fragment("uniq(user_id)")
|
||||
)
|
||||
end
|
||||
|
||||
def has_pageviews?([]), do: false
|
||||
@ -588,8 +625,8 @@ defmodule Plausible.Stats.Clickhouse do
|
||||
def has_pageviews?(domains) when is_list(domains) do
|
||||
ClickhouseRepo.exists?(
|
||||
from e in "events",
|
||||
select: e.timestamp,
|
||||
where: fragment("? IN tuple(?)", e.domain, ^domains)
|
||||
select: e.timestamp,
|
||||
where: fragment("? IN tuple(?)", e.domain, ^domains)
|
||||
)
|
||||
end
|
||||
|
||||
@ -606,9 +643,10 @@ defmodule Plausible.Stats.Clickhouse do
|
||||
else
|
||||
ClickhouseRepo.all(
|
||||
from [e, meta: meta] in base_query_w_sessions_bare(site, query),
|
||||
select: {e.name, meta.key},
|
||||
distinct: true
|
||||
) |> Enum.reduce(%{}, fn {goal_name, meta_key}, acc ->
|
||||
select: {e.name, meta.key},
|
||||
distinct: true
|
||||
)
|
||||
|> Enum.reduce(%{}, fn {goal_name, meta_key}, acc ->
|
||||
Map.update(acc, goal_name, [meta_key], fn list -> [meta_key | list] end)
|
||||
end)
|
||||
end
|
||||
@ -617,67 +655,71 @@ defmodule Plausible.Stats.Clickhouse do
|
||||
def all_props(site, query) do
|
||||
ClickhouseRepo.all(
|
||||
from e in base_query_w_sessions_bare(site, query),
|
||||
inner_lateral_join: meta in fragment("meta as m"),
|
||||
select: {e.name, meta.key},
|
||||
distinct: true
|
||||
) |> Enum.reduce(%{}, fn {goal_name, meta_key}, acc ->
|
||||
inner_lateral_join: meta in fragment("meta as m"),
|
||||
select: {e.name, meta.key},
|
||||
distinct: true
|
||||
)
|
||||
|> Enum.reduce(%{}, fn {goal_name, meta_key}, acc ->
|
||||
Map.update(acc, goal_name, [meta_key], fn list -> [meta_key | list] end)
|
||||
end)
|
||||
end
|
||||
|
||||
def property_breakdown(site, %Query{filters: %{"props" => meta}} = query, key) when is_map(meta) do
|
||||
def property_breakdown(site, %Query{filters: %{"props" => meta}} = query, key)
|
||||
when is_map(meta) do
|
||||
[{_key, val}] = meta |> Enum.into([])
|
||||
|
||||
if val == "(none)" do
|
||||
ClickhouseRepo.all(
|
||||
from e in base_query_w_sessions(site, query),
|
||||
where: fragment("not has(meta.key, ?)", ^key),
|
||||
order_by: [desc: fragment("count")],
|
||||
select: %{
|
||||
name: "(none)",
|
||||
count: fragment("uniq(user_id) as count"),
|
||||
total_count: fragment("count(*) as total_count")
|
||||
}
|
||||
)
|
||||
ClickhouseRepo.all(
|
||||
from e in base_query_w_sessions(site, query),
|
||||
where: fragment("not has(meta.key, ?)", ^key),
|
||||
order_by: [desc: fragment("count")],
|
||||
select: %{
|
||||
name: "(none)",
|
||||
count: fragment("uniq(user_id) as count"),
|
||||
total_count: fragment("count(*) as total_count")
|
||||
}
|
||||
)
|
||||
else
|
||||
ClickhouseRepo.all(
|
||||
from [e, meta: meta] in base_query_w_sessions(site, query),
|
||||
group_by: meta.value,
|
||||
order_by: [desc: fragment("count")],
|
||||
select: %{
|
||||
name: meta.value,
|
||||
count: fragment("uniq(user_id) as count"),
|
||||
total_count: fragment("count(*) as total_count")
|
||||
}
|
||||
)
|
||||
ClickhouseRepo.all(
|
||||
from [e, meta: meta] in base_query_w_sessions(site, query),
|
||||
group_by: meta.value,
|
||||
order_by: [desc: fragment("count")],
|
||||
select: %{
|
||||
name: meta.value,
|
||||
count: fragment("uniq(user_id) as count"),
|
||||
total_count: fragment("count(*) as total_count")
|
||||
}
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def property_breakdown(site, query, key) do
|
||||
none = ClickhouseRepo.all(
|
||||
from e in base_query_w_sessions(site, query),
|
||||
where: fragment("not has(meta.key, ?)", ^key),
|
||||
select: %{
|
||||
name: "(none)",
|
||||
count: fragment("uniq(?) as count", e.user_id),
|
||||
total_count: fragment("count(*) as total_count")
|
||||
}
|
||||
)
|
||||
none =
|
||||
ClickhouseRepo.all(
|
||||
from e in base_query_w_sessions(site, query),
|
||||
where: fragment("not has(meta.key, ?)", ^key),
|
||||
select: %{
|
||||
name: "(none)",
|
||||
count: fragment("uniq(?) as count", e.user_id),
|
||||
total_count: fragment("count(*) as total_count")
|
||||
}
|
||||
)
|
||||
|
||||
values = ClickhouseRepo.all(
|
||||
from e in base_query_w_sessions(site, query),
|
||||
inner_lateral_join: meta in fragment("meta as m"),
|
||||
where: meta.key == ^key,
|
||||
group_by: meta.value,
|
||||
order_by: [desc: fragment("count")],
|
||||
select: %{
|
||||
name: meta.value,
|
||||
count: fragment("uniq(user_id) as count"),
|
||||
total_count: fragment("count(*) as total_count")
|
||||
}
|
||||
)
|
||||
values =
|
||||
ClickhouseRepo.all(
|
||||
from e in base_query_w_sessions(site, query),
|
||||
inner_lateral_join: meta in fragment("meta as m"),
|
||||
where: meta.key == ^key,
|
||||
group_by: meta.value,
|
||||
order_by: [desc: fragment("count")],
|
||||
select: %{
|
||||
name: meta.value,
|
||||
count: fragment("uniq(user_id) as count"),
|
||||
total_count: fragment("count(*) as total_count")
|
||||
}
|
||||
)
|
||||
|
||||
values ++ none
|
||||
(values ++ none)
|
||||
|> Enum.sort(fn row1, row2 -> row1[:count] >= row2[:count] end)
|
||||
|> Enum.filter(fn row -> row[:count] > 0 end)
|
||||
end
|
||||
@ -685,13 +727,13 @@ defmodule Plausible.Stats.Clickhouse do
|
||||
def goal_conversions(site, %Query{filters: %{"goal" => goal}} = query) when is_binary(goal) do
|
||||
ClickhouseRepo.all(
|
||||
from e in base_query_w_sessions(site, query),
|
||||
group_by: e.name,
|
||||
order_by: [desc: fragment("count")],
|
||||
select: %{
|
||||
name: ^goal,
|
||||
count: fragment("uniq(user_id) as count"),
|
||||
total_count: fragment("count(*) as total_count")
|
||||
}
|
||||
group_by: e.name,
|
||||
order_by: [desc: fragment("count")],
|
||||
select: %{
|
||||
name: ^goal,
|
||||
count: fragment("uniq(user_id) as count"),
|
||||
total_count: fragment("count(*) as total_count")
|
||||
}
|
||||
)
|
||||
end
|
||||
|
||||
@ -710,16 +752,17 @@ defmodule Plausible.Stats.Clickhouse do
|
||||
|> Enum.filter(& &1)
|
||||
|
||||
if Enum.count(events) > 0 do
|
||||
q = from(
|
||||
e in base_query_w_sessions_bare(site, query),
|
||||
where: fragment("? IN tuple(?)", e.name, ^events),
|
||||
group_by: e.name,
|
||||
select: %{
|
||||
name: e.name,
|
||||
count: fragment("uniq(user_id) as count"),
|
||||
total_count: fragment("count(*) as total_count")
|
||||
}
|
||||
)
|
||||
q =
|
||||
from(
|
||||
e in base_query_w_sessions_bare(site, query),
|
||||
where: fragment("? IN tuple(?)", e.name, ^events),
|
||||
group_by: e.name,
|
||||
select: %{
|
||||
name: e.name,
|
||||
count: fragment("uniq(user_id) as count"),
|
||||
total_count: fragment("count(*) as total_count")
|
||||
}
|
||||
)
|
||||
|
||||
ClickhouseRepo.all(q)
|
||||
else
|
||||
@ -733,16 +776,17 @@ defmodule Plausible.Stats.Clickhouse do
|
||||
|> Enum.filter(& &1)
|
||||
|
||||
if Enum.count(pages) > 0 do
|
||||
q = from(
|
||||
e in base_query_w_sessions(site, query),
|
||||
where: fragment("? IN tuple(?)", e.pathname, ^pages),
|
||||
group_by: e.pathname,
|
||||
select: %{
|
||||
name: fragment("concat('Visit ', ?) as name", e.pathname),
|
||||
count: fragment("uniq(user_id) as count"),
|
||||
total_count: fragment("count(*) as total_count")
|
||||
}
|
||||
)
|
||||
q =
|
||||
from(
|
||||
e in base_query_w_sessions(site, query),
|
||||
where: fragment("? IN tuple(?)", e.pathname, ^pages),
|
||||
group_by: e.pathname,
|
||||
select: %{
|
||||
name: fragment("concat('Visit ', ?) as name", e.pathname),
|
||||
count: fragment("uniq(user_id) as count"),
|
||||
total_count: fragment("count(*) as total_count")
|
||||
}
|
||||
)
|
||||
|
||||
ClickhouseRepo.all(q)
|
||||
else
|
||||
@ -757,11 +801,12 @@ defmodule Plausible.Stats.Clickhouse do
|
||||
defp base_query_w_sessions_bare(site, query) do
|
||||
{first_datetime, last_datetime} = utc_boundaries(query, site.timezone)
|
||||
|
||||
sessions_q = from(s in "sessions",
|
||||
where: s.domain == ^site.domain,
|
||||
where: s.timestamp >= ^first_datetime and s.start < ^last_datetime,
|
||||
select: %{session_id: s.session_id}
|
||||
)
|
||||
sessions_q =
|
||||
from(s in "sessions",
|
||||
where: s.domain == ^site.domain,
|
||||
where: s.timestamp >= ^first_datetime and s.start < ^last_datetime,
|
||||
select: %{session_id: s.session_id}
|
||||
)
|
||||
|
||||
sessions_q =
|
||||
if query.filters["source"] do
|
||||
@ -828,12 +873,13 @@ defmodule Plausible.Stats.Clickhouse do
|
||||
sessions_q
|
||||
end
|
||||
|
||||
sessions_q = if query.filters["referrer"] do
|
||||
ref = query.filters["referrer"]
|
||||
from(s in sessions_q, where: s.referrer == ^ref)
|
||||
else
|
||||
sessions_q
|
||||
end
|
||||
sessions_q =
|
||||
if query.filters["referrer"] do
|
||||
ref = query.filters["referrer"]
|
||||
from(s in sessions_q, where: s.referrer == ^ref)
|
||||
else
|
||||
sessions_q
|
||||
end
|
||||
|
||||
q =
|
||||
from(e in "events",
|
||||
@ -841,22 +887,26 @@ defmodule Plausible.Stats.Clickhouse do
|
||||
where: e.timestamp >= ^first_datetime and e.timestamp < ^last_datetime
|
||||
)
|
||||
|
||||
q = if query.filters["source"] || query.filters['referrer'] || query.filters["utm_medium"] || query.filters["utm_source"] || query.filters["utm_campaign"] || query.filters["screen"] || query.filters["browser"] || query.filters["os"] || query.filters["country"] do
|
||||
from(
|
||||
e in q,
|
||||
join: sq in subquery(sessions_q),
|
||||
on: e.session_id == sq.session_id
|
||||
)
|
||||
else
|
||||
q
|
||||
end
|
||||
q =
|
||||
if query.filters["source"] || query.filters['referrer'] || query.filters["utm_medium"] ||
|
||||
query.filters["utm_source"] || query.filters["utm_campaign"] || query.filters["screen"] ||
|
||||
query.filters["browser"] || query.filters["os"] || query.filters["country"] do
|
||||
from(
|
||||
e in q,
|
||||
join: sq in subquery(sessions_q),
|
||||
on: e.session_id == sq.session_id
|
||||
)
|
||||
else
|
||||
q
|
||||
end
|
||||
|
||||
q = if query.filters["page"] do
|
||||
page = query.filters["page"]
|
||||
from(e in q, where: e.pathname == ^page)
|
||||
else
|
||||
q
|
||||
end
|
||||
q =
|
||||
if query.filters["page"] do
|
||||
page = query.filters["page"]
|
||||
from(e in q, where: e.pathname == ^page)
|
||||
else
|
||||
q
|
||||
end
|
||||
|
||||
if query.filters["props"] do
|
||||
[{key, val}] = query.filters["props"] |> Enum.into([])
|
||||
@ -871,7 +921,7 @@ defmodule Plausible.Stats.Clickhouse do
|
||||
e in q,
|
||||
inner_lateral_join: meta in fragment("meta as m"),
|
||||
as: :meta,
|
||||
where: meta.key == ^key and meta.value == ^val,
|
||||
where: meta.key == ^key and meta.value == ^val
|
||||
)
|
||||
end
|
||||
else
|
||||
@ -884,11 +934,12 @@ defmodule Plausible.Stats.Clickhouse do
|
||||
|
||||
{goal_event, path} = event_name_for_goal(query)
|
||||
|
||||
q = if goal_event do
|
||||
from(e in q, where: e.name == ^goal_event)
|
||||
else
|
||||
from(e in q, where: e.name == "pageview")
|
||||
end
|
||||
q =
|
||||
if goal_event do
|
||||
from(e in q, where: e.name == ^goal_event)
|
||||
else
|
||||
from(e in q, where: e.name == "pageview")
|
||||
end
|
||||
|
||||
if path do
|
||||
from(e in q, where: e.pathname == ^path)
|
||||
@ -897,7 +948,6 @@ defmodule Plausible.Stats.Clickhouse do
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
defp base_session_query(site, query) do
|
||||
{first_datetime, last_datetime} = utc_boundaries(query, site.timezone)
|
||||
|
||||
@ -1070,25 +1120,25 @@ defmodule Plausible.Stats.Clickhouse do
|
||||
q
|
||||
end
|
||||
|
||||
q = if query.filters["props"] do
|
||||
[{key, val}] = query.filters["props"] |> Enum.into([])
|
||||
q =
|
||||
if query.filters["props"] do
|
||||
[{key, val}] = query.filters["props"] |> Enum.into([])
|
||||
|
||||
if val == "(none)" do
|
||||
from(
|
||||
e in q,
|
||||
where: fragment("not has(meta.key, ?)", ^key)
|
||||
)
|
||||
if val == "(none)" do
|
||||
from(
|
||||
e in q,
|
||||
where: fragment("not has(meta.key, ?)", ^key)
|
||||
)
|
||||
else
|
||||
from(
|
||||
e in q,
|
||||
inner_lateral_join: meta in fragment("meta as m"),
|
||||
where: meta.key == ^key and meta.value == ^val
|
||||
)
|
||||
end
|
||||
else
|
||||
from(
|
||||
e in q,
|
||||
inner_lateral_join: meta in fragment("meta as m"),
|
||||
where: meta.key == ^key and meta.value == ^val,
|
||||
)
|
||||
q
|
||||
end
|
||||
else
|
||||
q
|
||||
end
|
||||
|
||||
|
||||
q =
|
||||
if path do
|
||||
|
@ -494,8 +494,8 @@ defmodule Plausible.Stats.CountryName do
|
||||
}
|
||||
|
||||
@alpha2_codes @alpha3_codes
|
||||
|> Enum.map(fn {k, v} -> {v, k} end)
|
||||
|> Enum.into(%{})
|
||||
|> Enum.map(fn {k, v} -> {v, k} end)
|
||||
|> Enum.into(%{})
|
||||
|
||||
def to_alpha3(code) do
|
||||
Map.get(@alpha3_codes, code, code)
|
||||
|
@ -9,6 +9,7 @@ defmodule Plausible.Release do
|
||||
|
||||
def init_admin do
|
||||
prepare()
|
||||
|
||||
{admin_email, admin_user, admin_pwd} =
|
||||
validate_admin(
|
||||
{Application.get_env(:plausible, :admin_email),
|
||||
@ -55,6 +56,7 @@ defmodule Plausible.Release do
|
||||
for repo <- repos() do
|
||||
:ok = ensure_repo_created(repo)
|
||||
end
|
||||
|
||||
IO.puts("Creation of Db successful!")
|
||||
end
|
||||
|
||||
|
@ -12,7 +12,9 @@ defmodule PlausibleWeb.Captcha do
|
||||
|
||||
def verify(token) do
|
||||
if enabled?() do
|
||||
res = HTTPoison.post!(@verify_endpoint, {:form, [{"response", token}, {"secret", secret()}]})
|
||||
res =
|
||||
HTTPoison.post!(@verify_endpoint, {:form, [{"response", token}, {"secret", secret()}]})
|
||||
|
||||
json = Jason.decode!(res.body)
|
||||
json["success"]
|
||||
else
|
||||
|
@ -72,6 +72,7 @@ defmodule PlausibleWeb.Api.ExternalController do
|
||||
{:ok, nil}
|
||||
else
|
||||
query = if uri && uri.query, do: URI.decode_query(uri.query), else: %{}
|
||||
|
||||
ua =
|
||||
if user_agent do
|
||||
UAInspector.Parser.parse(user_agent)
|
||||
@ -94,8 +95,8 @@ defmodule PlausibleWeb.Api.ExternalController do
|
||||
utm_source: query["utm_source"] || "",
|
||||
utm_campaign: query["utm_campaign"] || "",
|
||||
country_code: country_code || "",
|
||||
operating_system: ua && os_name(ua) || "",
|
||||
browser: ua && browser_name(ua) || "",
|
||||
operating_system: (ua && os_name(ua)) || "",
|
||||
browser: (ua && browser_name(ua)) || "",
|
||||
screen_size: calculate_screen_size(params["screen_width"]) || "",
|
||||
"meta.key": Map.keys(params["meta"]),
|
||||
"meta.value": Map.values(params["meta"])
|
||||
@ -118,6 +119,7 @@ defmodule PlausibleWeb.Api.ExternalController do
|
||||
|
||||
defp parse_meta(params) do
|
||||
raw_meta = params["m"] || params["meta"] || params["p"] || params["props"]
|
||||
|
||||
if raw_meta do
|
||||
Jason.decode!(raw_meta)
|
||||
else
|
||||
@ -126,8 +128,10 @@ defmodule PlausibleWeb.Api.ExternalController do
|
||||
end
|
||||
|
||||
defp get_pathname(nil, _), do: "/"
|
||||
|
||||
defp get_pathname(uri, hash_mode) do
|
||||
pathname = uri.path || "/"
|
||||
|
||||
if hash_mode && uri.fragment do
|
||||
pathname <> "#" <> uri.fragment
|
||||
else
|
||||
|
@ -13,6 +13,6 @@ defmodule PlausibleWeb.Api.InternalController do
|
||||
|
||||
def sites(conn, _) do
|
||||
user = Repo.preload(conn.assigns[:current_user], :sites)
|
||||
json(conn, Enum.map(user.sites, &(&1.domain)))
|
||||
json(conn, Enum.map(user.sites, & &1.domain))
|
||||
end
|
||||
end
|
||||
|
@ -79,16 +79,18 @@ defmodule PlausibleWeb.Api.StatsController do
|
||||
bounce_rate = Stats.bounce_rate(site, query)
|
||||
prev_bounce_rate = Stats.bounce_rate(site, prev_query)
|
||||
change_bounce_rate = if prev_bounce_rate > 0, do: bounce_rate - prev_bounce_rate
|
||||
visit_duration = if !query.filters["page"] do
|
||||
duration = Stats.visit_duration(site, query)
|
||||
prev_duration = Stats.visit_duration(site, prev_query)
|
||||
|
||||
%{
|
||||
name: "Visit duration",
|
||||
count: duration,
|
||||
change: percent_change(prev_duration, duration)
|
||||
}
|
||||
end
|
||||
visit_duration =
|
||||
if !query.filters["page"] do
|
||||
duration = Stats.visit_duration(site, query)
|
||||
prev_duration = Stats.visit_duration(site, prev_query)
|
||||
|
||||
%{
|
||||
name: "Visit duration",
|
||||
count: duration,
|
||||
change: percent_change(prev_duration, duration)
|
||||
}
|
||||
end
|
||||
|
||||
[
|
||||
%{
|
||||
@ -103,7 +105,8 @@ defmodule PlausibleWeb.Api.StatsController do
|
||||
},
|
||||
%{name: "Bounce rate", percentage: bounce_rate, change: change_bounce_rate},
|
||||
visit_duration
|
||||
] |> Enum.filter(&(&1))
|
||||
]
|
||||
|> Enum.filter(& &1)
|
||||
end
|
||||
|
||||
defp percent_change(old_count, new_count) do
|
||||
@ -253,7 +256,7 @@ defmodule PlausibleWeb.Api.StatsController do
|
||||
defp calculate_cr(unique_visitors, converted_visitors) do
|
||||
if unique_visitors > 0,
|
||||
do: Float.round(converted_visitors / unique_visitors * 100, 1),
|
||||
else: 0.0
|
||||
else: 0.0
|
||||
end
|
||||
|
||||
def conversions(conn, params) do
|
||||
@ -262,12 +265,14 @@ defmodule PlausibleWeb.Api.StatsController do
|
||||
total_filter = Map.merge(query.filters, %{"goal" => nil, "props" => nil})
|
||||
unique_visitors = Stats.unique_visitors(site, %{query | filters: total_filter})
|
||||
prop_names = Stats.all_props(site, query)
|
||||
conversions = Stats.goal_conversions(site, query)
|
||||
|> Enum.map(fn goal ->
|
||||
goal
|
||||
|> Map.put(:prop_names, prop_names[goal[:name]])
|
||||
|> Map.put(:conversion_rate, calculate_cr(unique_visitors, goal[:count]))
|
||||
end)
|
||||
|
||||
conversions =
|
||||
Stats.goal_conversions(site, query)
|
||||
|> Enum.map(fn goal ->
|
||||
goal
|
||||
|> Map.put(:prop_names, prop_names[goal[:name]])
|
||||
|> Map.put(:conversion_rate, calculate_cr(unique_visitors, goal[:count]))
|
||||
end)
|
||||
|
||||
json(conn, conversions)
|
||||
end
|
||||
@ -277,10 +282,12 @@ defmodule PlausibleWeb.Api.StatsController do
|
||||
query = Query.from(site.timezone, params)
|
||||
total_filter = Map.merge(query.filters, %{"goal" => nil, "props" => nil})
|
||||
unique_visitors = Stats.unique_visitors(site, %{query | filters: total_filter})
|
||||
props = Stats.property_breakdown(site, query, params["prop_name"])
|
||||
|> Enum.map(fn prop ->
|
||||
Map.put(prop, :conversion_rate, calculate_cr(unique_visitors, prop[:count]))
|
||||
end)
|
||||
|
||||
props =
|
||||
Stats.property_breakdown(site, query, params["prop_name"])
|
||||
|> Enum.map(fn prop ->
|
||||
Map.put(prop, :conversion_rate, calculate_cr(unique_visitors, prop[:count]))
|
||||
end)
|
||||
|
||||
json(conn, props)
|
||||
end
|
||||
|
@ -66,9 +66,11 @@ defmodule PlausibleWeb.AuthController do
|
||||
|> Plausible.Mailer.send_email()
|
||||
|
||||
user_activated_account(conn, user)
|
||||
|
||||
{:error, %Ecto.Changeset{errors: [email: {"has already been taken", _}]}} ->
|
||||
user = Auth.find_user_by(email: email)
|
||||
user_activated_account(conn, user)
|
||||
|
||||
{:error, changeset} ->
|
||||
send_resp(conn, 400, inspect(changeset.errors))
|
||||
end
|
||||
|
@ -7,6 +7,7 @@ defmodule PlausibleWeb.Firewall do
|
||||
|
||||
def call(conn, _opts) do
|
||||
blocklist = Keyword.fetch!(Application.get_env(:plausible, __MODULE__), :blocklist)
|
||||
|
||||
if PlausibleWeb.RemoteIp.get(conn) in blocklist do
|
||||
send_resp(conn, 404, "Not found") |> halt
|
||||
else
|
||||
|
@ -1,5 +1,6 @@
|
||||
defmodule PlausibleWeb.Tracker do
|
||||
import Plug.Conn
|
||||
|
||||
@templates [
|
||||
"plausible.js",
|
||||
"plausible.hash.js",
|
||||
@ -16,14 +17,20 @@ defmodule PlausibleWeb.Tracker do
|
||||
@max_age 3600
|
||||
|
||||
def init(_) do
|
||||
templates = Enum.reduce(@templates, %{}, fn template_filename, rendered_templates ->
|
||||
rendered = EEx.eval_file("priv/tracker/js/" <> template_filename, base_url: PlausibleWeb.Endpoint.url())
|
||||
aliases = Map.get(@aliases, template_filename, [])
|
||||
[template_filename | aliases]
|
||||
|> Enum.map(fn filename -> {"/js/" <> filename, rendered} end)
|
||||
|> Enum.into(%{})
|
||||
|> Map.merge(rendered_templates)
|
||||
end)
|
||||
templates =
|
||||
Enum.reduce(@templates, %{}, fn template_filename, rendered_templates ->
|
||||
rendered =
|
||||
EEx.eval_file("priv/tracker/js/" <> template_filename,
|
||||
base_url: PlausibleWeb.Endpoint.url()
|
||||
)
|
||||
|
||||
aliases = Map.get(@aliases, template_filename, [])
|
||||
|
||||
[template_filename | aliases]
|
||||
|> Enum.map(fn filename -> {"/js/" <> filename, rendered} end)
|
||||
|> Enum.into(%{})
|
||||
|> Map.merge(rendered_templates)
|
||||
end)
|
||||
|
||||
[templates: templates]
|
||||
end
|
||||
|
2
mix.exs
2
mix.exs
@ -91,7 +91,7 @@ defmodule Plausible.MixProject do
|
||||
{:geolix, "~> 1.0"},
|
||||
{:clickhouse_ecto, git: "https://github.com/plausible/clickhouse_ecto.git"},
|
||||
{:geolix_adapter_mmdb2, "~> 0.5.0"},
|
||||
{:logflare_logger_backend, "~> 0.7.6"},
|
||||
{:logflare_logger_backend, "~> 0.7.6"}
|
||||
]
|
||||
end
|
||||
|
||||
|
@ -213,7 +213,9 @@ defmodule Plausible.BillingTest do
|
||||
"status" => "deleted"
|
||||
})
|
||||
|
||||
assert_email_delivered_with(subject: "Your Plausible Analytics subscription has been canceled")
|
||||
assert_email_delivered_with(
|
||||
subject: "Your Plausible Analytics subscription has been canceled"
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -10,10 +10,12 @@ defmodule PlausibleWeb.AdminAuthControllerTest do
|
||||
|
||||
test "logs admin user in automatically when authentication is disabled", %{conn: conn} do
|
||||
set_config(disable_authentication: true)
|
||||
admin_user = insert(:user,
|
||||
email: Application.get_env(:plausible, :admin_email),
|
||||
password: Application.get_env(:plausible, :admin_pwd)
|
||||
)
|
||||
|
||||
admin_user =
|
||||
insert(:user,
|
||||
email: Application.get_env(:plausible, :admin_email),
|
||||
password: Application.get_env(:plausible, :admin_pwd)
|
||||
)
|
||||
|
||||
# goto landing page
|
||||
conn = get(conn, "/")
|
||||
|
@ -7,8 +7,8 @@ defmodule PlausibleWeb.Api.ExternalControllerTest do
|
||||
|
||||
ClickhouseRepo.one(
|
||||
from e in Plausible.ClickhouseEvent,
|
||||
where: e.domain == ^domain,
|
||||
order_by: [desc: e.timestamp]
|
||||
where: e.domain == ^domain,
|
||||
order_by: [desc: e.timestamp]
|
||||
)
|
||||
end
|
||||
|
||||
@ -259,7 +259,8 @@ defmodule PlausibleWeb.Api.ExternalControllerTest do
|
||||
test "utm tags are stored", %{conn: conn} do
|
||||
params = %{
|
||||
name: "pageview",
|
||||
url: "http://www.example.com/?utm_medium=ads&utm_source=instagram&utm_campaign=video_story",
|
||||
url:
|
||||
"http://www.example.com/?utm_medium=ads&utm_source=instagram&utm_campaign=video_story",
|
||||
domain: "external-controller-test-utm-tags.com"
|
||||
}
|
||||
|
||||
@ -414,7 +415,8 @@ defmodule PlausibleWeb.Api.ExternalControllerTest do
|
||||
test "URL is decoded", %{conn: conn} do
|
||||
params = %{
|
||||
name: "pageview",
|
||||
url: "http://www.example.com/opportunity/category/%D8%AC%D9%88%D8%A7%D8%A6%D8%B2-%D9%88%D9%85%D8%B3%D8%A7%D8%A8%D9%82%D8%A7%D8%AA",
|
||||
url:
|
||||
"http://www.example.com/opportunity/category/%D8%AC%D9%88%D8%A7%D8%A6%D8%B2-%D9%88%D9%85%D8%B3%D8%A7%D8%A8%D9%82%D8%A7%D8%AA",
|
||||
domain: "external-controller-test-21.com"
|
||||
}
|
||||
|
||||
|
@ -12,8 +12,20 @@ defmodule PlausibleWeb.Api.StatsController.ConversionsTest do
|
||||
conn = get(conn, "/api/stats/#{site.domain}/conversions?period=day&date=2019-01-01")
|
||||
|
||||
assert json_response(conn, 200) == [
|
||||
%{"name" => "Signup", "count" => 3, "total_count" => 3, "prop_names" => ["variant"], "conversion_rate" => 50.0},
|
||||
%{"name" => "Visit /register", "count" => 2, "total_count" => 2, "prop_names" => nil, "conversion_rate" => 33.3}
|
||||
%{
|
||||
"name" => "Signup",
|
||||
"count" => 3,
|
||||
"total_count" => 3,
|
||||
"prop_names" => ["variant"],
|
||||
"conversion_rate" => 50.0
|
||||
},
|
||||
%{
|
||||
"name" => "Visit /register",
|
||||
"count" => 2,
|
||||
"total_count" => 2,
|
||||
"prop_names" => nil,
|
||||
"conversion_rate" => 33.3
|
||||
}
|
||||
]
|
||||
end
|
||||
end
|
||||
@ -34,8 +46,14 @@ defmodule PlausibleWeb.Api.StatsController.ConversionsTest do
|
||||
)
|
||||
|
||||
assert json_response(conn, 200) == [
|
||||
%{"name" => "Signup", "count" => 3, "total_count" => 3, "prop_names" => ["variant"], "conversion_rate" => 50.0}
|
||||
]
|
||||
%{
|
||||
"name" => "Signup",
|
||||
"count" => 3,
|
||||
"total_count" => 3,
|
||||
"prop_names" => ["variant"],
|
||||
"conversion_rate" => 50.0
|
||||
}
|
||||
]
|
||||
end
|
||||
end
|
||||
|
||||
@ -50,13 +68,15 @@ defmodule PlausibleWeb.Api.StatsController.ConversionsTest do
|
||||
conn =
|
||||
get(
|
||||
conn,
|
||||
"/api/stats/#{site.domain}/property/#{prop_key}?period=day&date=2019-01-01&filters=#{filters}"
|
||||
"/api/stats/#{site.domain}/property/#{prop_key}?period=day&date=2019-01-01&filters=#{
|
||||
filters
|
||||
}"
|
||||
)
|
||||
|
||||
assert json_response(conn, 200) == [
|
||||
%{"count" => 2, "name" => "B", "total_count" => 2, "conversion_rate" => 33.3},
|
||||
%{"count" => 1, "name" => "A", "total_count" => 1, "conversion_rate" => 16.7}
|
||||
]
|
||||
%{"count" => 2, "name" => "B", "total_count" => 2, "conversion_rate" => 33.3},
|
||||
%{"count" => 1, "name" => "A", "total_count" => 1, "conversion_rate" => 16.7}
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -24,30 +24,31 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do
|
||||
)
|
||||
|
||||
assert json_response(conn, 200) == [
|
||||
%{
|
||||
"bounce_rate" => 33.0,
|
||||
"count" => 2,
|
||||
"pageviews" => 2,
|
||||
"name" => "/"},
|
||||
%{
|
||||
"bounce_rate" => nil,
|
||||
"count" => 2,
|
||||
"pageviews" => 2,
|
||||
"name" => "/register"
|
||||
},
|
||||
%{
|
||||
"bounce_rate" => nil,
|
||||
"count" => 1,
|
||||
"pageviews" => 1,
|
||||
"name" => "/contact"
|
||||
},
|
||||
%{
|
||||
"bounce_rate" => nil,
|
||||
"count" => 1,
|
||||
"pageviews" => 1,
|
||||
"name" => "/irrelevant"
|
||||
}
|
||||
]
|
||||
%{
|
||||
"bounce_rate" => 33.0,
|
||||
"count" => 2,
|
||||
"pageviews" => 2,
|
||||
"name" => "/"
|
||||
},
|
||||
%{
|
||||
"bounce_rate" => nil,
|
||||
"count" => 2,
|
||||
"pageviews" => 2,
|
||||
"name" => "/register"
|
||||
},
|
||||
%{
|
||||
"bounce_rate" => nil,
|
||||
"count" => 1,
|
||||
"pageviews" => 1,
|
||||
"name" => "/contact"
|
||||
},
|
||||
%{
|
||||
"bounce_rate" => nil,
|
||||
"count" => 1,
|
||||
"pageviews" => 1,
|
||||
"name" => "/irrelevant"
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
test "returns top pages in realtime report", %{conn: conn, site: site} do
|
||||
@ -67,7 +68,7 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do
|
||||
conn = get(conn, "/api/stats/#{site.domain}/entry-pages?period=day&date=2019-01-01")
|
||||
|
||||
assert json_response(conn, 200) == [
|
||||
%{"count" => 3, "name" => "/"},
|
||||
%{"count" => 3, "name" => "/"}
|
||||
]
|
||||
end
|
||||
|
||||
@ -79,12 +80,12 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do
|
||||
)
|
||||
|
||||
assert json_response(conn, 200) == [
|
||||
%{
|
||||
"bounce_rate" => 33.0,
|
||||
"count" => 3,
|
||||
"name" => "/"
|
||||
}
|
||||
]
|
||||
%{
|
||||
"bounce_rate" => 33.0,
|
||||
"count" => 3,
|
||||
"name" => "/"
|
||||
}
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -49,7 +49,8 @@ defmodule PlausibleWeb.Api.StatsController.SourcesTest do
|
||||
end
|
||||
|
||||
test "can paginate the results", %{conn: conn, site: site} do
|
||||
conn = get(conn, "/api/stats/#{site.domain}/sources?period=day&date=2019-01-01&limit=1&page=2")
|
||||
conn =
|
||||
get(conn, "/api/stats/#{site.domain}/sources?period=day&date=2019-01-01&limit=1&page=2")
|
||||
|
||||
assert json_response(conn, 200) == [
|
||||
%{"name" => "Bing", "count" => 1, "url" => ""}
|
||||
@ -64,8 +65,18 @@ defmodule PlausibleWeb.Api.StatsController.SourcesTest do
|
||||
conn = get(conn, "/api/stats/#{site.domain}/utm_mediums?period=day&date=2019-01-01")
|
||||
|
||||
assert json_response(conn, 200) == [
|
||||
%{"name" => "listing", "count" => 2, "bounce_rate" => 50.0, "visit_duration" => 50.0},
|
||||
%{"name" => "search", "count" => 1, "bounce_rate" => 0.0, "visit_duration" => 100.0}
|
||||
%{
|
||||
"name" => "listing",
|
||||
"count" => 2,
|
||||
"bounce_rate" => 50.0,
|
||||
"visit_duration" => 50.0
|
||||
},
|
||||
%{
|
||||
"name" => "search",
|
||||
"count" => 1,
|
||||
"bounce_rate" => 0.0,
|
||||
"visit_duration" => 100.0
|
||||
}
|
||||
]
|
||||
end
|
||||
end
|
||||
@ -107,7 +118,14 @@ defmodule PlausibleWeb.Api.StatsController.SourcesTest do
|
||||
|
||||
test "returns top referrers for a particular source", %{conn: conn, site: site} do
|
||||
filters = Jason.encode!(%{source: "10words"})
|
||||
conn = get(conn, "/api/stats/#{site.domain}/referrers/10words?period=day&date=2019-01-01&filters=#{filters}")
|
||||
|
||||
conn =
|
||||
get(
|
||||
conn,
|
||||
"/api/stats/#{site.domain}/referrers/10words?period=day&date=2019-01-01&filters=#{
|
||||
filters
|
||||
}"
|
||||
)
|
||||
|
||||
assert json_response(conn, 200) == %{
|
||||
"total_visitors" => 3,
|
||||
@ -119,10 +137,13 @@ defmodule PlausibleWeb.Api.StatsController.SourcesTest do
|
||||
|
||||
test "calculates bounce rate and visit duration for referrer urls", %{conn: conn, site: site} do
|
||||
filters = Jason.encode!(%{source: "10words"})
|
||||
|
||||
conn =
|
||||
get(
|
||||
conn,
|
||||
"/api/stats/#{site.domain}/referrers/10words?period=day&date=2019-01-01&filters=#{filters}&include=bounce_rate,visit_duration"
|
||||
"/api/stats/#{site.domain}/referrers/10words?period=day&date=2019-01-01&filters=#{
|
||||
filters
|
||||
}&include=bounce_rate,visit_duration"
|
||||
)
|
||||
|
||||
assert json_response(conn, 200) == %{
|
||||
|
@ -8,7 +8,7 @@ defmodule PlausibleWeb.FirewallTest do
|
||||
@opts [blocklist: [@blocked_ip]]
|
||||
|
||||
setup do
|
||||
Application.put_env(:plausible, PlausibleWeb.Firewall, [blocklist: [@blocked_ip]])
|
||||
Application.put_env(:plausible, PlausibleWeb.Firewall, blocklist: [@blocked_ip])
|
||||
:ok
|
||||
end
|
||||
|
||||
|
@ -12,25 +12,28 @@ defmodule Plausible.TestUtils do
|
||||
end
|
||||
|
||||
def create_pageviews(pageviews) do
|
||||
pageviews = Enum.map(pageviews, fn pageview ->
|
||||
Factory.build(:pageview, pageview) |> Map.from_struct() |> Map.delete(:__meta__)
|
||||
end)
|
||||
pageviews =
|
||||
Enum.map(pageviews, fn pageview ->
|
||||
Factory.build(:pageview, pageview) |> Map.from_struct() |> Map.delete(:__meta__)
|
||||
end)
|
||||
|
||||
Plausible.ClickhouseRepo.insert_all("events", pageviews)
|
||||
end
|
||||
|
||||
def create_events(events) do
|
||||
events = Enum.map(events, fn event ->
|
||||
Factory.build(:event, event) |> Map.from_struct() |> Map.delete(:__meta__)
|
||||
end)
|
||||
events =
|
||||
Enum.map(events, fn event ->
|
||||
Factory.build(:event, event) |> Map.from_struct() |> Map.delete(:__meta__)
|
||||
end)
|
||||
|
||||
Plausible.ClickhouseRepo.insert_all("events", events)
|
||||
end
|
||||
|
||||
def create_sessions(sessions) do
|
||||
sessions = Enum.map(sessions, fn session ->
|
||||
Factory.build(:ch_session, session) |> Map.from_struct() |> Map.delete(:__meta__)
|
||||
end)
|
||||
sessions =
|
||||
Enum.map(sessions, fn session ->
|
||||
Factory.build(:ch_session, session) |> Map.from_struct() |> Map.delete(:__meta__)
|
||||
end)
|
||||
|
||||
Plausible.ClickhouseRepo.insert_all("sessions", sessions)
|
||||
end
|
||||
|
Loading…
Reference in New Issue
Block a user