111 lines
3.1 KiB
Elixir
111 lines
3.1 KiB
Elixir
defmodule Plausible.Stats.GoalSuggestions do
|
|
@moduledoc false
|
|
|
|
alias Plausible.{Repo, ClickhouseRepo}
|
|
alias Plausible.Stats.Query
|
|
import Plausible.Stats.Base
|
|
import Ecto.Query
|
|
|
|
# As excluded goal names are interpolated as separate
|
|
# parameters in the query, there's a risk of running
|
|
# against max parameters limit. Given the failure mode
|
|
# in this case is suggesting an event that is already
|
|
# added as a goal, which will be validated when creating,
|
|
# it's safe to trim exclusions list.
|
|
@max_excluded 1000
|
|
|
|
defmacrop visitors(e) do
|
|
quote do
|
|
selected_as(
|
|
fragment("toUInt64(round(uniq(?)*any(_sample_factor)))", unquote(e).user_id),
|
|
:visitors
|
|
)
|
|
end
|
|
end
|
|
|
|
def suggest_event_names(site, search_input, opts \\ []) do
|
|
matches = "%#{search_input}%"
|
|
|
|
site =
|
|
site
|
|
|> Repo.preload(:goals)
|
|
|
|
excluded =
|
|
opts
|
|
|> Keyword.get(:exclude, [])
|
|
|> Enum.take(@max_excluded)
|
|
|
|
limit = Keyword.get(opts, :limit, 25)
|
|
|
|
to_date = Date.utc_today()
|
|
from_date = Date.shift(to_date, month: -6)
|
|
|
|
query =
|
|
Plausible.Stats.Query.parse_and_build!(
|
|
site,
|
|
:internal,
|
|
%{
|
|
"site_id" => site.domain,
|
|
"date_range" => [Date.to_iso8601(from_date), Date.to_iso8601(to_date)],
|
|
"metrics" => ["pageviews"],
|
|
"include" => %{"imports" => true}
|
|
}
|
|
)
|
|
|
|
native_q =
|
|
from(e in base_event_query(query),
|
|
where: fragment("? ilike ?", e.name, ^matches),
|
|
where: e.name not in ["pageview", "engagement"],
|
|
where: fragment("trim(?)", e.name) != "",
|
|
where: e.name == fragment("trim(?)", e.name),
|
|
where: e.name not in ^excluded,
|
|
select: %{
|
|
name: e.name,
|
|
visitors: visitors(e)
|
|
},
|
|
order_by: selected_as(:visitors),
|
|
group_by: e.name
|
|
)
|
|
|> maybe_set_limit(limit)
|
|
|
|
date_range = Query.date_range(query)
|
|
|
|
imported_q =
|
|
from(i in "imported_custom_events",
|
|
where: i.site_id == ^site.id,
|
|
where: i.import_id in ^Plausible.Imported.complete_import_ids(site),
|
|
where: i.date >= ^date_range.first and i.date <= ^date_range.last,
|
|
where: i.visitors > 0,
|
|
where: fragment("? ilike ?", i.name, ^matches),
|
|
where: fragment("trim(?)", i.name) != "",
|
|
where: i.name == fragment("trim(?)", i.name),
|
|
where: i.name not in ^excluded,
|
|
select: %{
|
|
name: i.name,
|
|
visitors: selected_as(sum(i.visitors), :visitors)
|
|
},
|
|
order_by: selected_as(:visitors),
|
|
group_by: i.name
|
|
)
|
|
|> maybe_set_limit(limit)
|
|
|
|
from(e in Ecto.Query.subquery(native_q),
|
|
full_join: i in subquery(imported_q),
|
|
on: e.name == i.name,
|
|
select: selected_as(fragment("if(empty(?), ?, ?)", e.name, i.name, e.name), :name),
|
|
order_by: [desc: e.visitors + i.visitors]
|
|
)
|
|
|> maybe_set_limit(limit)
|
|
|> ClickhouseRepo.all()
|
|
|> Enum.reject(&(String.length(&1) > Plausible.Goal.max_event_name_length()))
|
|
end
|
|
|
|
defp maybe_set_limit(q, :unlimited) do
|
|
q
|
|
end
|
|
|
|
defp maybe_set_limit(q, limit) when is_integer(limit) and limit > 0 do
|
|
limit(q, ^limit)
|
|
end
|
|
end
|