mirror of
https://github.com/plausible/analytics.git
synced 2024-12-23 17:44:43 +03:00
Allow setting sample_threshold in dashboard API queries (#2958)
* refactor Query.from/2 * allow specifying sample_threshold number in query
This commit is contained in:
parent
6be67bc6c2
commit
dafb60164a
@ -514,7 +514,7 @@ defmodule Plausible.Stats.Base do
|
|||||||
|
|
||||||
defp add_sample_hint(db_q, query) do
|
defp add_sample_hint(db_q, query) do
|
||||||
case query.sample_threshold do
|
case query.sample_threshold do
|
||||||
"infinite" ->
|
:infinite ->
|
||||||
db_q
|
db_q
|
||||||
|
|
||||||
threshold ->
|
threshold ->
|
||||||
|
@ -38,6 +38,34 @@ defmodule Plausible.Stats.Interval do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Returns the suggested interval for the given `Date.Range` struct.
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
iex> Plausible.Stats.Interval.default_for_date_range(Date.range(~D[2022-01-01], ~D[2023-01-01]))
|
||||||
|
"month"
|
||||||
|
|
||||||
|
iex> Plausible.Stats.Interval.default_for_date_range(Date.range(~D[2022-01-01], ~D[2022-01-15]))
|
||||||
|
"date"
|
||||||
|
|
||||||
|
iex> Plausible.Stats.Interval.default_for_date_range(Date.range(~D[2022-01-01], ~D[2022-01-01]))
|
||||||
|
"hour"
|
||||||
|
"""
|
||||||
|
|
||||||
|
def default_for_date_range(%Date.Range{first: first, last: last}) do
|
||||||
|
cond do
|
||||||
|
Timex.diff(last, first, :months) > 0 ->
|
||||||
|
"month"
|
||||||
|
|
||||||
|
Timex.diff(last, first, :days) > 0 ->
|
||||||
|
"date"
|
||||||
|
|
||||||
|
true ->
|
||||||
|
"hour"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
@valid_by_period %{
|
@valid_by_period %{
|
||||||
"realtime" => ["minute"],
|
"realtime" => ["minute"],
|
||||||
"day" => ["minute", "hour"],
|
"day" => ["minute", "hour"],
|
||||||
|
@ -13,60 +13,53 @@ defmodule Plausible.Stats.Query do
|
|||||||
|
|
||||||
@type t :: %__MODULE__{}
|
@type t :: %__MODULE__{}
|
||||||
|
|
||||||
def from(site, %{"period" => "realtime"} = params) do
|
def from(site, params) do
|
||||||
|
query_by_period(site, params)
|
||||||
|
|> maybe_put_interval(params)
|
||||||
|
|> put_parsed_filters(params)
|
||||||
|
|> put_imported_opts(site, params)
|
||||||
|
|> put_sample_threshold(params)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp query_by_period(site, %{"period" => "realtime"}) do
|
||||||
date = today(site.timezone)
|
date = today(site.timezone)
|
||||||
|
|
||||||
%__MODULE__{
|
%__MODULE__{
|
||||||
period: "realtime",
|
period: "realtime",
|
||||||
interval: params["interval"] || Interval.default_for_period(params["period"]),
|
date_range: Date.range(date, date)
|
||||||
date_range: Date.range(date, date),
|
|
||||||
filters: FilterParser.parse_filters(params["filters"]),
|
|
||||||
sample_threshold: Map.get(params, "sample_threshold", @default_sample_threshold)
|
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
def from(site, %{"period" => "day"} = params) do
|
defp query_by_period(site, %{"period" => "day"} = params) do
|
||||||
date = parse_single_date(site.timezone, params)
|
date = parse_single_date(site.timezone, params)
|
||||||
|
|
||||||
%__MODULE__{
|
%__MODULE__{
|
||||||
period: "day",
|
period: "day",
|
||||||
date_range: Date.range(date, date),
|
date_range: Date.range(date, date)
|
||||||
interval: params["interval"] || Interval.default_for_period(params["period"]),
|
|
||||||
filters: FilterParser.parse_filters(params["filters"]),
|
|
||||||
sample_threshold: Map.get(params, "sample_threshold", @default_sample_threshold)
|
|
||||||
}
|
}
|
||||||
|> put_imported_opts(site, params)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def from(site, %{"period" => "7d"} = params) do
|
defp query_by_period(site, %{"period" => "7d"} = params) do
|
||||||
end_date = parse_single_date(site.timezone, params)
|
end_date = parse_single_date(site.timezone, params)
|
||||||
start_date = end_date |> Timex.shift(days: -6)
|
start_date = end_date |> Timex.shift(days: -6)
|
||||||
|
|
||||||
%__MODULE__{
|
%__MODULE__{
|
||||||
period: "7d",
|
period: "7d",
|
||||||
date_range: Date.range(start_date, end_date),
|
date_range: Date.range(start_date, end_date)
|
||||||
interval: params["interval"] || Interval.default_for_period(params["period"]),
|
|
||||||
filters: FilterParser.parse_filters(params["filters"]),
|
|
||||||
sample_threshold: Map.get(params, "sample_threshold", @default_sample_threshold)
|
|
||||||
}
|
}
|
||||||
|> put_imported_opts(site, params)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def from(site, %{"period" => "30d"} = params) do
|
defp query_by_period(site, %{"period" => "30d"} = params) do
|
||||||
end_date = parse_single_date(site.timezone, params)
|
end_date = parse_single_date(site.timezone, params)
|
||||||
start_date = end_date |> Timex.shift(days: -30)
|
start_date = end_date |> Timex.shift(days: -30)
|
||||||
|
|
||||||
%__MODULE__{
|
%__MODULE__{
|
||||||
period: "30d",
|
period: "30d",
|
||||||
date_range: Date.range(start_date, end_date),
|
date_range: Date.range(start_date, end_date)
|
||||||
interval: params["interval"] || Interval.default_for_period(params["period"]),
|
|
||||||
filters: FilterParser.parse_filters(params["filters"]),
|
|
||||||
sample_threshold: Map.get(params, "sample_threshold", @default_sample_threshold)
|
|
||||||
}
|
}
|
||||||
|> put_imported_opts(site, params)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def from(site, %{"period" => "month"} = params) do
|
defp query_by_period(site, %{"period" => "month"} = params) do
|
||||||
date = parse_single_date(site.timezone, params)
|
date = parse_single_date(site.timezone, params)
|
||||||
|
|
||||||
start_date = Timex.beginning_of_month(date)
|
start_date = Timex.beginning_of_month(date)
|
||||||
@ -74,15 +67,11 @@ defmodule Plausible.Stats.Query do
|
|||||||
|
|
||||||
%__MODULE__{
|
%__MODULE__{
|
||||||
period: "month",
|
period: "month",
|
||||||
date_range: Date.range(start_date, end_date),
|
date_range: Date.range(start_date, end_date)
|
||||||
interval: params["interval"] || Interval.default_for_period(params["period"]),
|
|
||||||
filters: FilterParser.parse_filters(params["filters"]),
|
|
||||||
sample_threshold: Map.get(params, "sample_threshold", @default_sample_threshold)
|
|
||||||
}
|
}
|
||||||
|> put_imported_opts(site, params)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def from(site, %{"period" => "6mo"} = params) do
|
defp query_by_period(site, %{"period" => "6mo"} = params) do
|
||||||
end_date =
|
end_date =
|
||||||
parse_single_date(site.timezone, params)
|
parse_single_date(site.timezone, params)
|
||||||
|> Timex.end_of_month()
|
|> Timex.end_of_month()
|
||||||
@ -93,15 +82,11 @@ defmodule Plausible.Stats.Query do
|
|||||||
|
|
||||||
%__MODULE__{
|
%__MODULE__{
|
||||||
period: "6mo",
|
period: "6mo",
|
||||||
date_range: Date.range(start_date, end_date),
|
date_range: Date.range(start_date, end_date)
|
||||||
interval: params["interval"] || Interval.default_for_period(params["period"]),
|
|
||||||
filters: FilterParser.parse_filters(params["filters"]),
|
|
||||||
sample_threshold: Map.get(params, "sample_threshold", @default_sample_threshold)
|
|
||||||
}
|
}
|
||||||
|> put_imported_opts(site, params)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def from(site, %{"period" => "12mo"} = params) do
|
defp query_by_period(site, %{"period" => "12mo"} = params) do
|
||||||
end_date =
|
end_date =
|
||||||
parse_single_date(site.timezone, params)
|
parse_single_date(site.timezone, params)
|
||||||
|> Timex.end_of_month()
|
|> Timex.end_of_month()
|
||||||
@ -112,15 +97,11 @@ defmodule Plausible.Stats.Query do
|
|||||||
|
|
||||||
%__MODULE__{
|
%__MODULE__{
|
||||||
period: "12mo",
|
period: "12mo",
|
||||||
date_range: Date.range(start_date, end_date),
|
date_range: Date.range(start_date, end_date)
|
||||||
interval: params["interval"] || Interval.default_for_period(params["period"]),
|
|
||||||
filters: FilterParser.parse_filters(params["filters"]),
|
|
||||||
sample_threshold: Map.get(params, "sample_threshold", @default_sample_threshold)
|
|
||||||
}
|
}
|
||||||
|> put_imported_opts(site, params)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def from(site, %{"period" => "year"} = params) do
|
defp query_by_period(site, %{"period" => "year"} = params) do
|
||||||
end_date =
|
end_date =
|
||||||
parse_single_date(site.timezone, params)
|
parse_single_date(site.timezone, params)
|
||||||
|> Timex.end_of_year()
|
|> Timex.end_of_year()
|
||||||
@ -129,76 +110,67 @@ defmodule Plausible.Stats.Query do
|
|||||||
|
|
||||||
%__MODULE__{
|
%__MODULE__{
|
||||||
period: "year",
|
period: "year",
|
||||||
date_range: Date.range(start_date, end_date),
|
date_range: Date.range(start_date, end_date)
|
||||||
interval: params["interval"] || Interval.default_for_period(params["period"]),
|
|
||||||
filters: FilterParser.parse_filters(params["filters"]),
|
|
||||||
sample_threshold: Map.get(params, "sample_threshold", @default_sample_threshold)
|
|
||||||
}
|
}
|
||||||
|> put_imported_opts(site, params)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def from(site, %{"period" => "all"} = params) do
|
defp query_by_period(site, %{"period" => "all"}) do
|
||||||
now = today(site.timezone)
|
now = today(site.timezone)
|
||||||
start_date = Plausible.Site.local_start_date(site) || now
|
start_date = Plausible.Site.local_start_date(site) || now
|
||||||
|
|
||||||
cond do
|
date_range = Date.range(start_date, now)
|
||||||
Timex.diff(now, start_date, :months) > 0 ->
|
|
||||||
from(
|
|
||||||
site,
|
|
||||||
Map.merge(params, %{
|
|
||||||
"period" => "custom",
|
|
||||||
"from" => Date.to_iso8601(start_date),
|
|
||||||
"to" => Date.to_iso8601(now),
|
|
||||||
"interval" => params["interval"] || "month"
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|> Map.put(:period, "all")
|
|
||||||
|
|
||||||
Timex.diff(now, start_date, :days) > 0 ->
|
%__MODULE__{
|
||||||
from(
|
period: "all",
|
||||||
site,
|
date_range: date_range,
|
||||||
Map.merge(params, %{
|
interval: Interval.default_for_date_range(date_range)
|
||||||
"period" => "custom",
|
}
|
||||||
"from" => Date.to_iso8601(start_date),
|
|
||||||
"to" => Date.to_iso8601(now),
|
|
||||||
"interval" => params["interval"] || "date"
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|> Map.put(:period, "all")
|
|
||||||
|
|
||||||
true ->
|
|
||||||
from(site, Map.merge(params, %{"period" => "day", "date" => "today"}))
|
|
||||||
|> Map.put(:period, "all")
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def from(site, %{"period" => "custom", "from" => from, "to" => to} = params) do
|
defp query_by_period(site, %{"period" => "custom", "from" => from, "to" => to} = params) do
|
||||||
new_params =
|
new_params =
|
||||||
params
|
params
|
||||||
|> Map.delete("from")
|
|> Map.drop(["from", "to"])
|
||||||
|> Map.delete("to")
|
|
||||||
|> Map.put("date", Enum.join([from, to], ","))
|
|> Map.put("date", Enum.join([from, to], ","))
|
||||||
|
|
||||||
from(site, new_params)
|
query_by_period(site, new_params)
|
||||||
end
|
end
|
||||||
|
|
||||||
def from(site, %{"period" => "custom", "date" => date} = params) do
|
defp query_by_period(_site, %{"period" => "custom", "date" => date}) do
|
||||||
[from, to] = String.split(date, ",")
|
[from, to] = String.split(date, ",")
|
||||||
from_date = Date.from_iso8601!(String.trim(from))
|
from_date = Date.from_iso8601!(String.trim(from))
|
||||||
to_date = Date.from_iso8601!(String.trim(to))
|
to_date = Date.from_iso8601!(String.trim(to))
|
||||||
|
|
||||||
%__MODULE__{
|
%__MODULE__{
|
||||||
period: "custom",
|
period: "custom",
|
||||||
date_range: Date.range(from_date, to_date),
|
date_range: Date.range(from_date, to_date)
|
||||||
interval: params["interval"] || Interval.default_for_period(params["period"]),
|
|
||||||
filters: FilterParser.parse_filters(params["filters"]),
|
|
||||||
sample_threshold: Map.get(params, "sample_threshold", @default_sample_threshold)
|
|
||||||
}
|
}
|
||||||
|> put_imported_opts(site, params)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def from(tz, params) do
|
defp query_by_period(site, params) do
|
||||||
__MODULE__.from(tz, Map.merge(params, %{"period" => "30d"}))
|
query_by_period(site, Map.merge(params, %{"period" => "30d"}))
|
||||||
|
end
|
||||||
|
|
||||||
|
defp maybe_put_interval(%{interval: nil} = query, params) do
|
||||||
|
interval = Map.get(params, "interval", Interval.default_for_period(query.period))
|
||||||
|
Map.put(query, :interval, interval)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp maybe_put_interval(query, _), do: query
|
||||||
|
|
||||||
|
defp put_parsed_filters(query, params) do
|
||||||
|
Map.put(query, :filters, FilterParser.parse_filters(params["filters"]))
|
||||||
|
end
|
||||||
|
|
||||||
|
defp put_sample_threshold(query, params) do
|
||||||
|
sample_threshold =
|
||||||
|
case params["sample_threshold"] do
|
||||||
|
nil -> @default_sample_threshold
|
||||||
|
"infinite" -> :infinite
|
||||||
|
value -> String.to_integer(value)
|
||||||
|
end
|
||||||
|
|
||||||
|
Map.put(query, :sample_threshold, sample_threshold)
|
||||||
end
|
end
|
||||||
|
|
||||||
def put_filter(query, key, val) do
|
def put_filter(query, key, val) do
|
||||||
@ -244,7 +216,7 @@ defmodule Plausible.Stats.Query do
|
|||||||
case params["date"] do
|
case params["date"] do
|
||||||
"today" -> Timex.now(tz) |> Timex.to_date()
|
"today" -> Timex.now(tz) |> Timex.to_date()
|
||||||
date when is_binary(date) -> Date.from_iso8601!(date)
|
date when is_binary(date) -> Date.from_iso8601!(date)
|
||||||
_ -> Timex.now(tz) |> Timex.to_date()
|
_ -> today(tz)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -142,6 +142,16 @@ defmodule Plausible.Stats.QueryTest do
|
|||||||
assert q.interval == "date"
|
assert q.interval == "date"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "adds sample_threshold :infinite to query struct" do
|
||||||
|
q = Query.from(@site, %{"period" => "30d", "sample_threshold" => "infinite"})
|
||||||
|
assert q.sample_threshold == :infinite
|
||||||
|
end
|
||||||
|
|
||||||
|
test "casts sample_threshold to integer in query struct" do
|
||||||
|
q = Query.from(@site, %{"period" => "30d", "sample_threshold" => "30000000"})
|
||||||
|
assert q.sample_threshold == 30_000_000
|
||||||
|
end
|
||||||
|
|
||||||
describe "filters" do
|
describe "filters" do
|
||||||
test "parses goal filter" do
|
test "parses goal filter" do
|
||||||
filters = Jason.encode!(%{"goal" => "Signup"})
|
filters = Jason.encode!(%{"goal" => "Signup"})
|
||||||
|
Loading…
Reference in New Issue
Block a user