{/* Header Container */}
-
- Top Sources
-
+
+
+ Top Sources
+
+
+
{renderTabs()}
{/* Main Contents */}
diff --git a/lib/plausible/imported.ex b/lib/plausible/imported.ex
index b8e00f19d890..35fa3489229f 100644
--- a/lib/plausible/imported.ex
+++ b/lib/plausible/imported.ex
@@ -54,6 +54,11 @@ defmodule Plausible.Imported do
@max_complete_imports
end
+ @spec imported_custom_props() :: [String.t()]
+ def imported_custom_props do
+ Plausible.Props.internal_keys()
+ end
+
@spec goals_with_url() :: [String.t()]
def goals_with_url() do
@goals_with_url
diff --git a/lib/plausible/stats/base.ex b/lib/plausible/stats/base.ex
index 07d14a7871b1..108e29f7f974 100644
--- a/lib/plausible/stats/base.ex
+++ b/lib/plausible/stats/base.ex
@@ -308,8 +308,10 @@ defmodule Plausible.Stats.Base do
def add_percentage_metric(q, site, query, metrics) do
if :percentage in metrics do
+ total_query = Query.set_property(query, nil, skip_refresh_imported_opts: true)
+
q
- |> select_merge(^%{__total_visitors: total_visitors_subquery(site, query)})
+ |> select_merge(^%{__total_visitors: total_visitors_subquery(site, total_query)})
|> select_merge(%{
percentage:
fragment(
@@ -329,7 +331,10 @@ defmodule Plausible.Stats.Base do
# filters.
def maybe_add_conversion_rate(q, site, query, metrics) do
if :conversion_rate in metrics do
- total_query = query |> Query.remove_filters(["event:goal", "event:props"])
+ total_query =
+ query
+ |> Query.remove_filters(["event:goal", "event:props"], skip_refresh_imported_opts: true)
+ |> Query.set_property(nil, skip_refresh_imported_opts: true)
# :TRICKY: Subquery is used due to event:goal breakdown above doing an UNION ALL
subquery(q)
diff --git a/lib/plausible/stats/breakdown.ex b/lib/plausible/stats/breakdown.ex
index 305d80838b3d..23db74a9e04c 100644
--- a/lib/plausible/stats/breakdown.ex
+++ b/lib/plausible/stats/breakdown.ex
@@ -37,11 +37,10 @@ defmodule Plausible.Stats.Breakdown do
{event_goals, pageview_goals} = Enum.split_with(site.goals, & &1.event_name)
events = Enum.map(event_goals, & &1.event_name)
- event_query = %Query{
+ event_query =
query
- | filters: query.filters ++ [[:member, "event:name", events]],
- property: "event:name"
- }
+ |> Query.put_filter([:member, "event:name", events])
+ |> Query.set_property("event:name")
if !Keyword.get(opts, :skip_tracing), do: Query.trace(query, metrics)
@@ -74,12 +73,14 @@ defmodule Plausible.Stats.Breakdown do
page_q =
if Enum.any?(pageview_goals) do
+ page_query = Query.set_property(query, "event:page")
+
page_exprs = Enum.map(pageview_goals, & &1.page_path)
page_regexes = Enum.map(page_exprs, &page_regex/1)
select_columns = metrics_to_select |> select_event_metrics |> mark_revenue_as_nil
- from(e in base_event_query(site, query),
+ from(e in base_event_query(site, page_query),
order_by: [desc: fragment("uniq(?)", e.user_id)],
where:
fragment(
@@ -94,7 +95,7 @@ defmodule Plausible.Stats.Breakdown do
}
)
|> select_merge(^select_columns)
- |> merge_imported_pageview_goals(site, query, page_exprs, metrics_to_select)
+ |> merge_imported_pageview_goals(site, page_query, page_exprs, metrics_to_select)
|> apply_pagination(pagination)
else
nil
@@ -180,8 +181,9 @@ defmodule Plausible.Stats.Breakdown do
pages ->
query
+ |> Query.remove_filters(["event:page"])
|> Query.put_filter([:member, "visit:entry_page", Enum.map(pages, & &1[:page])])
- |> struct!(property: "visit:entry_page")
+ |> Query.set_property("visit:entry_page")
end
if Enum.any?(event_metrics) && Enum.empty?(event_result) do
@@ -244,24 +246,25 @@ defmodule Plausible.Stats.Breakdown do
"visit:entry_page",
"visit:referrer"
] do
- update_hostname(query, "visit:entry_page_hostname")
+ update_hostname_filter_prop(query, "visit:entry_page_hostname")
end
defp maybe_update_breakdown_filters(%Query{property: "visit:exit_page"} = query) do
- update_hostname(query, "visit:exit_page_hostname")
+ update_hostname_filter_prop(query, "visit:exit_page_hostname")
end
defp maybe_update_breakdown_filters(query) do
query
end
- defp update_hostname(query, visit_prop) do
+ defp update_hostname_filter_prop(query, visit_prop) do
case Query.get_filter(query, "event:hostname") do
nil ->
query
[op, "event:hostname", value] ->
- Plausible.Stats.Query.put_filter(query, [op, visit_prop, value])
+ query
+ |> Query.put_filter([op, visit_prop, value])
end
end
diff --git a/lib/plausible/stats/comparisons.ex b/lib/plausible/stats/comparisons.ex
index 86ee0f3554c4..dcd34bcd7907 100644
--- a/lib/plausible/stats/comparisons.ex
+++ b/lib/plausible/stats/comparisons.ex
@@ -63,7 +63,7 @@ defmodule Plausible.Stats.Comparisons do
with :ok <- validate_mode(source_query, mode),
{:ok, comparison_query} <- do_compare(source_query, mode, opts),
- comparison_query <- maybe_include_imported(comparison_query, source_query, site),
+ comparison_query <- maybe_include_imported(comparison_query, source_query),
do: {:ok, comparison_query}
end
@@ -162,12 +162,10 @@ defmodule Plausible.Stats.Comparisons do
Date.add(date, -days_to_subtract)
end
- defp maybe_include_imported(query, %Query{imported_data_requested: false}, _site) do
- %Stats.Query{query | include_imported: false}
- end
+ defp maybe_include_imported(query, source_query) do
+ requested? = source_query.imported_data_requested
- defp maybe_include_imported(query, _source_query, site) do
- case Query.ensure_include_imported(query, site) do
+ case Query.ensure_include_imported(query, requested?) do
:ok ->
struct!(query,
imported_data_requested: true,
@@ -176,7 +174,7 @@ defmodule Plausible.Stats.Comparisons do
{:error, reason} ->
struct!(query,
- imported_data_requested: true,
+ imported_data_requested: requested?,
include_imported: false,
skip_imported_reason: reason
)
diff --git a/lib/plausible/stats/email_report.ex b/lib/plausible/stats/email_report.ex
index 63809e3af869..675597731b34 100644
--- a/lib/plausible/stats/email_report.ex
+++ b/lib/plausible/stats/email_report.ex
@@ -40,7 +40,7 @@ defmodule Plausible.Stats.EmailReport do
end
defp put_top_5_pages(stats, site, query) do
- query = struct!(query, property: "event:page")
+ query = Query.set_property(query, "event:page")
pages = Stats.breakdown(site, query, [:visitors], {5, 1})
Map.put(stats, :pages, pages)
end
@@ -49,7 +49,7 @@ defmodule Plausible.Stats.EmailReport do
query =
query
|> Query.put_filter([:is_not, "visit:source", "Direct / None"])
- |> struct!(property: "visit:source")
+ |> Query.set_property("visit:source")
sources = Stats.breakdown(site, query, [:visitors], {5, 1})
diff --git a/lib/plausible/stats/filter_suggestions.ex b/lib/plausible/stats/filter_suggestions.ex
index 6e38044f998c..8713919e3e4a 100644
--- a/lib/plausible/stats/filter_suggestions.ex
+++ b/lib/plausible/stats/filter_suggestions.ex
@@ -2,9 +2,12 @@ defmodule Plausible.Stats.FilterSuggestions do
use Plausible.Repo
use Plausible.ClickhouseRepo
use Plausible.Stats.Fragments
+
import Plausible.Stats.Base
import Ecto.Query
+
alias Plausible.Stats.Query
+ alias Plausible.Stats.Imported
def filter_suggestions(site, query, "country", filter_search) do
matches = Location.search_country(filter_search)
@@ -16,6 +19,7 @@ defmodule Plausible.Stats.FilterSuggestions do
order_by: [desc: fragment("count(*)")],
select: e.country_code
)
+ |> Imported.merge_imported_country_suggestions(site, query)
ClickhouseRepo.all(q)
|> Enum.map(fn c -> Enum.find(matches, fn x -> x.alpha_2 == c end) end)
@@ -35,33 +39,60 @@ defmodule Plausible.Stats.FilterSuggestions do
group_by: e.subdivision1_code,
order_by: [desc: fragment("count(*)")],
select: e.subdivision1_code,
- where: e.subdivision1_code != "",
- limit: 24
+ where: e.subdivision1_code != ""
)
+ |> Imported.merge_imported_region_suggestions(site, query)
+ |> limit(24)
|> ClickhouseRepo.all()
|> Enum.map(fn c ->
subdiv = Location.get_subdivision(c)
- %{
- value: c,
- label: subdiv.name
- }
+ if subdiv do
+ %{
+ value: c,
+ label: subdiv.name
+ }
+ else
+ %{
+ value: c,
+ label: c
+ }
+ end
end)
end
def filter_suggestions(site, query, "region", filter_search) do
matches = Location.search_subdivision(filter_search)
+ filter_search = String.downcase(filter_search)
q =
from(
e in query_sessions(site, query),
group_by: e.subdivision1_code,
order_by: [desc: fragment("count(*)")],
- select: e.subdivision1_code
+ select: e.subdivision1_code,
+ where: e.subdivision1_code != ""
)
+ |> Imported.merge_imported_region_suggestions(site, query)
ClickhouseRepo.all(q)
- |> Enum.map(fn c -> Enum.find(matches, fn x -> x.code == c end) end)
+ |> Enum.map(fn c ->
+ match = Enum.find(matches, fn x -> x.code == c end)
+
+ cond do
+ match ->
+ match
+
+ String.contains?(String.downcase(c), filter_search) ->
+ %{
+ code: c,
+ name: c
+ }
+
+ true ->
+ nil
+ end
+ end)
|> Enum.filter(& &1)
|> Enum.slice(0..24)
|> Enum.map(fn subdiv ->
@@ -78,9 +109,10 @@ defmodule Plausible.Stats.FilterSuggestions do
group_by: e.city_geoname_id,
order_by: [desc: fragment("count(*)")],
select: e.city_geoname_id,
- where: e.city_geoname_id != 0,
- limit: 24
+ where: e.city_geoname_id != 0
)
+ |> Imported.merge_imported_city_suggestions(site, query)
+ |> limit(24)
|> ClickhouseRepo.all()
|> Enum.map(fn c ->
city = Location.get_city(c)
@@ -101,9 +133,10 @@ defmodule Plausible.Stats.FilterSuggestions do
group_by: e.city_geoname_id,
order_by: [desc: fragment("count(*)")],
select: e.city_geoname_id,
- where: e.city_geoname_id != 0,
- limit: 5000
+ where: e.city_geoname_id != 0
)
+ |> Imported.merge_imported_city_suggestions(site, query)
+ |> limit(5000)
ClickhouseRepo.all(q)
|> Enum.map(fn c -> Location.get_city(c) end)
@@ -223,10 +256,16 @@ defmodule Plausible.Stats.FilterSuggestions do
where: fragment("? ilike ?", field(e, ^filter_name), ^filter_query),
select: field(e, ^filter_name),
group_by: ^filter_name,
- order_by: [desc: fragment("count(*)")],
- limit: 25
+ order_by: [desc: fragment("count(*)")]
)
|> apply_additional_filters(filter_name, site)
+ |> Imported.merge_imported_filter_suggestions(
+ site,
+ query,
+ filter_name,
+ filter_query
+ )
+ |> limit(25)
|> ClickhouseRepo.all()
|> Enum.filter(fn suggestion -> suggestion != "" end)
|> wrap_suggestions()
diff --git a/lib/plausible/stats/imported/base.ex b/lib/plausible/stats/imported/base.ex
new file mode 100644
index 000000000000..e15e09d62cdb
--- /dev/null
+++ b/lib/plausible/stats/imported/base.ex
@@ -0,0 +1,191 @@
+defmodule Plausible.Stats.Imported.Base do
+ @moduledoc """
+ A module for building the base of an imported stats query
+ """
+
+ import Ecto.Query
+
+ alias Plausible.Imported
+ alias Plausible.Stats.Filters
+ alias Plausible.Stats.Query
+
+ @property_to_table_mappings %{
+ "visit:source" => "imported_sources",
+ "visit:referrer" => "imported_sources",
+ "visit:utm_source" => "imported_sources",
+ "visit:utm_medium" => "imported_sources",
+ "visit:utm_campaign" => "imported_sources",
+ "visit:utm_term" => "imported_sources",
+ "visit:utm_content" => "imported_sources",
+ "visit:entry_page" => "imported_entry_pages",
+ "visit:exit_page" => "imported_exit_pages",
+ "visit:country" => "imported_locations",
+ "visit:region" => "imported_locations",
+ "visit:city" => "imported_locations",
+ "visit:device" => "imported_devices",
+ "visit:browser" => "imported_browsers",
+ "visit:browser_version" => "imported_browsers",
+ "visit:os" => "imported_operating_systems",
+ "visit:os_version" => "imported_operating_systems",
+ "event:page" => "imported_pages",
+ "event:name" => "imported_custom_events",
+ "event:props:url" => "imported_custom_events",
+ "event:props:path" => "imported_custom_events",
+
+ # NOTE: these properties can be only filtered by
+ "visit:screen" => "imported_devices",
+ "event:hostname" => "imported_pages"
+ }
+
+ @imported_custom_props Imported.imported_custom_props()
+
+ @db_field_mappings %{
+ referrer_source: :source,
+ screen_size: :device,
+ screen: :device,
+ os: :operating_system,
+ os_version: :operating_system_version,
+ country_code: :country,
+ subdivision1_code: :region,
+ city_geoname_id: :city,
+ entry_page_hostname: :hostname,
+ pathname: :page,
+ url: :link_url
+ }
+
+ def property_to_table_mappings(), do: @property_to_table_mappings
+
+ def query_imported(site, query) do
+ query
+ |> transform_filters()
+ |> decide_table()
+ |> query_imported(site, query)
+ end
+
+ def query_imported(table, site, query) do
+ query = transform_filters(query)
+ import_ids = site.complete_import_ids
+ %{first: date_from, last: date_to} = query.date_range
+
+ from(i in table,
+ where: i.site_id == ^site.id,
+ where: i.import_id in ^import_ids,
+ where: i.date >= ^date_from,
+ where: i.date <= ^date_to,
+ select: %{}
+ )
+ |> apply_filter(query)
+ end
+
+ def decide_table(query) do
+ query
+ |> transform_filters()
+ |> do_decide_table()
+ end
+
+ defp transform_filters(query) do
+ new_filters =
+ query.filters
+ |> Enum.reject(fn
+ [:is, "event:name", "pageview"] -> true
+ _ -> false
+ end)
+ |> Enum.flat_map(fn filter ->
+ case filter do
+ [op, "event:goal", {:event, name}] ->
+ [[op, "event:name", name]]
+
+ [op, "event:goal", {:page, page}] ->
+ [[op, "event:page", page]]
+
+ [op, "event:goal", events] ->
+ events
+ |> Enum.group_by(&elem(&1, 0), &elem(&1, 1))
+ |> Enum.map(fn
+ {:event, names} -> [op, "event:name", names]
+ {:page, pages} -> [op, "event:page", pages]
+ end)
+
+ filter ->
+ [filter]
+ end
+ end)
+
+ struct!(query, filters: new_filters)
+ end
+
+ defp do_decide_table(%Query{filters: [], property: nil}), do: "imported_visitors"
+
+ defp do_decide_table(%Query{filters: filters, property: "event:props:" <> prop_key = property})
+ when prop_key in @imported_custom_props do
+ has_required_name_filter? =
+ Enum.any?(filters, fn
+ [:is, "event:name", name] -> name in special_goals_for(prop_key)
+ _ -> false
+ end)
+
+ has_unsupported_filters? =
+ Enum.any?(filters, fn [_, filtered_prop | _] ->
+ filtered_prop not in [property, "event:name"]
+ end)
+
+ if has_required_name_filter? && not has_unsupported_filters? do
+ "imported_custom_events"
+ else
+ nil
+ end
+ end
+
+ defp do_decide_table(%Query{filters: [], property: "event:goal"}) do
+ "imported_custom_events"
+ end
+
+ defp do_decide_table(%Query{filters: [], property: property}) do
+ @property_to_table_mappings[property]
+ end
+
+ defp do_decide_table(%Query{filters: filters, property: "event:goal"}) do
+ filter_props = Enum.map(filters, &Enum.at(&1, 1))
+
+ any_event_name_filters? = "event:name" in filter_props
+ any_page_filters? = "event:page" in filter_props
+ any_other_filters? = Enum.any?(filter_props, &(&1 not in ["event:page", "event:name"]))
+
+ cond do
+ any_other_filters? -> nil
+ any_event_name_filters? and not any_page_filters? -> "imported_custom_events"
+ any_page_filters? and not any_event_name_filters? -> "imported_pages"
+ true -> nil
+ end
+ end
+
+ defp do_decide_table(%Query{filters: filters, property: property}) do
+ table_candidates =
+ filters
+ |> Enum.map(fn [_, prop | _] -> prop end)
+ |> Enum.concat(if property, do: [property], else: [])
+ |> Enum.map(fn
+ "visit:screen" -> "visit:device"
+ prop -> prop
+ end)
+ |> Enum.map(&@property_to_table_mappings[&1])
+
+ case Enum.uniq(table_candidates) do
+ [candidate] -> candidate
+ _ -> nil
+ end
+ end
+
+ defp apply_filter(q, %Query{filters: filters}) do
+ Enum.reduce(filters, q, fn [_, filtered_prop | _] = filter, q ->
+ db_field = Plausible.Stats.Filters.without_prefix(filtered_prop)
+ mapped_db_field = Map.get(@db_field_mappings, db_field, db_field)
+ condition = Filters.WhereBuilder.build_condition(mapped_db_field, filter)
+
+ where(q, ^condition)
+ end)
+ end
+
+ def special_goals_for("url"), do: Imported.goals_with_url()
+ def special_goals_for("path"), do: Imported.goals_with_path()
+end
diff --git a/lib/plausible/stats/imported.ex b/lib/plausible/stats/imported/imported.ex
similarity index 65%
rename from lib/plausible/stats/imported.ex
rename to lib/plausible/stats/imported/imported.ex
index b6623587aaeb..2589a238734d 100644
--- a/lib/plausible/stats/imported.ex
+++ b/lib/plausible/stats/imported/imported.ex
@@ -1,43 +1,29 @@
defmodule Plausible.Stats.Imported do
use Plausible.ClickhouseRepo
- alias Plausible.Stats.{Query, Base}
import Ecto.Query
import Plausible.Stats.Fragments
+ alias Plausible.Stats.Base
+ alias Plausible.Stats.Imported
+ alias Plausible.Stats.Query
+
@no_ref "Direct / None"
@not_set "(not set)"
@none "(none)"
- @property_to_table_mappings %{
- "visit:source" => "imported_sources",
- "visit:referrer" => "imported_sources",
- "visit:utm_source" => "imported_sources",
- "visit:utm_medium" => "imported_sources",
- "visit:utm_campaign" => "imported_sources",
- "visit:utm_term" => "imported_sources",
- "visit:utm_content" => "imported_sources",
- "visit:entry_page" => "imported_entry_pages",
- "visit:exit_page" => "imported_exit_pages",
- "visit:country" => "imported_locations",
- "visit:region" => "imported_locations",
- "visit:city" => "imported_locations",
- "visit:device" => "imported_devices",
- "visit:browser" => "imported_browsers",
- "visit:browser_version" => "imported_browsers",
- "visit:os" => "imported_operating_systems",
- "visit:os_version" => "imported_operating_systems",
- "event:page" => "imported_pages",
- "event:name" => "imported_custom_events",
- "event:props:url" => "imported_custom_events",
- "event:props:path" => "imported_custom_events"
- }
+ @property_to_table_mappings Imported.Base.property_to_table_mappings()
@imported_properties Map.keys(@property_to_table_mappings)
@goals_with_url Plausible.Imported.goals_with_url()
+
+ def goals_with_url(), do: @goals_with_url
+
@goals_with_path Plausible.Imported.goals_with_path()
+ def goals_with_path(), do: @goals_with_path
+
@doc """
Returns a boolean indicating whether the combination of filters and
breakdown property is possible to query from the imported tables.
@@ -49,17 +35,193 @@ defmodule Plausible.Stats.Imported do
(see `@goals_with_url` and `@goals_with_path`).
"""
def schema_supports_query?(query) do
- filter_count = length(query.filters)
-
- case {filter_count, query.property} do
- {0, "event:props:" <> _} -> false
- {0, _} -> true
- {1, "event:props:url"} -> has_special_goal_filter?(query, @goals_with_url)
- {1, "event:props:path"} -> has_special_goal_filter?(query, @goals_with_path)
- {_, _} -> false
+ not is_nil(Imported.Base.decide_table(query))
+ end
+
+ def merge_imported_country_suggestions(native_q, _site, %Plausible.Stats.Query{
+ include_imported: false
+ }) do
+ native_q
+ end
+
+ def merge_imported_country_suggestions(native_q, site, query) do
+ supports_filter_set? =
+ Enum.all?(query.filters, fn filter ->
+ [_, filtered_prop | _] = filter
+ @property_to_table_mappings[filtered_prop] == "imported_locations"
+ end)
+
+ if supports_filter_set? do
+ native_q =
+ native_q
+ |> exclude(:order_by)
+ |> exclude(:select)
+ |> select([e], %{country_code: e.country_code, count: fragment("count(*)")})
+
+ imported_q =
+ from i in Imported.Base.query_imported("imported_locations", site, query),
+ group_by: i.country,
+ select_merge: %{country_code: i.country, count: fragment("sum(?)", i.pageviews)}
+
+ from(s in subquery(native_q),
+ full_join: i in subquery(imported_q),
+ on: s.country_code == i.country_code,
+ select:
+ fragment("if(not empty(?), ?, ?)", s.country_code, s.country_code, i.country_code),
+ order_by: [desc: fragment("? + ?", s.count, i.count)]
+ )
+ else
+ native_q
+ end
+ end
+
+ def merge_imported_region_suggestions(native_q, _site, %Plausible.Stats.Query{
+ include_imported: false
+ }) do
+ native_q
+ end
+
+ def merge_imported_region_suggestions(native_q, site, query) do
+ supports_filter_set? =
+ Enum.all?(query.filters, fn filter ->
+ [_, filtered_prop | _] = filter
+ @property_to_table_mappings[filtered_prop] == "imported_locations"
+ end)
+
+ if supports_filter_set? do
+ native_q =
+ native_q
+ |> exclude(:order_by)
+ |> exclude(:select)
+ |> select([e], %{region_code: e.subdivision1_code, count: fragment("count(*)")})
+
+ imported_q =
+ from i in Imported.Base.query_imported("imported_locations", site, query),
+ where: i.region != "",
+ group_by: i.region,
+ select_merge: %{region_code: i.region, count: fragment("sum(?)", i.pageviews)}
+
+ from(s in subquery(native_q),
+ full_join: i in subquery(imported_q),
+ on: s.region_code == i.region_code,
+ select: fragment("if(not empty(?), ?, ?)", s.region_code, s.region_code, i.region_code),
+ order_by: [desc: fragment("? + ?", s.count, i.count)]
+ )
+ else
+ native_q
+ end
+ end
+
+ def merge_imported_city_suggestions(native_q, _site, %Plausible.Stats.Query{
+ include_imported: false
+ }) do
+ native_q
+ end
+
+ def merge_imported_city_suggestions(native_q, site, query) do
+ supports_filter_set? =
+ Enum.all?(query.filters, fn filter ->
+ [_, filtered_prop | _] = filter
+ @property_to_table_mappings[filtered_prop] == "imported_locations"
+ end)
+
+ if supports_filter_set? do
+ native_q =
+ native_q
+ |> exclude(:order_by)
+ |> exclude(:select)
+ |> select([e], %{city_id: e.city_geoname_id, count: fragment("count(*)")})
+
+ imported_q =
+ from i in Imported.Base.query_imported("imported_locations", site, query),
+ where: i.city != 0,
+ group_by: i.city,
+ select_merge: %{city_id: i.city, count: fragment("sum(?)", i.pageviews)}
+
+ from(s in subquery(native_q),
+ full_join: i in subquery(imported_q),
+ on: s.city_id == i.city_id,
+ select: fragment("if(? > 0, ?, ?)", s.city_id, s.city_id, i.city_id),
+ order_by: [desc: fragment("? + ?", s.count, i.count)]
+ )
+ else
+ native_q
+ end
+ end
+
+ def merge_imported_filter_suggestions(
+ native_q,
+ _site,
+ %Plausible.Stats.Query{include_imported: false},
+ _filter_name,
+ _filter_search
+ ) do
+ native_q
+ end
+
+ def merge_imported_filter_suggestions(
+ native_q,
+ site,
+ query,
+ filter_name,
+ filter_query
+ ) do
+ {table, db_field} = expand_suggestions_field(filter_name)
+
+ supports_filter_set? =
+ Enum.all?(query.filters, fn filter ->
+ [_, filtered_prop | _] = filter
+ @property_to_table_mappings[filtered_prop] == table
+ end)
+
+ if supports_filter_set? do
+ native_q =
+ native_q
+ |> exclude(:order_by)
+ |> exclude(:select)
+ |> select([e], %{name: field(e, ^filter_name), count: fragment("count(*)")})
+
+ imported_q =
+ from i in Imported.Base.query_imported(table, site, query),
+ where: fragment("? ilike ?", field(i, ^db_field), ^filter_query),
+ group_by: field(i, ^db_field),
+ select_merge: %{name: field(i, ^db_field), count: fragment("sum(?)", i.pageviews)}
+
+ from(s in subquery(native_q),
+ full_join: i in subquery(imported_q),
+ on: s.name == i.name,
+ select: fragment("if(not empty(?), ?, ?)", s.name, s.name, i.name),
+ order_by: [desc: fragment("? + ?", s.count, i.count)],
+ limit: 25
+ )
+ else
+ native_q
end
end
+ @filter_suggestions_mapping %{
+ referrer_source: :source,
+ screen_size: :device,
+ pathname: :page
+ }
+
+ defp expand_suggestions_field(filter_name) do
+ db_field = Map.get(@filter_suggestions_mapping, filter_name, filter_name)
+
+ property =
+ case db_field do
+ :operating_system -> :os
+ :operating_system_version -> :os_version
+ other -> other
+ end
+
+ table_by_visit = Map.get(@property_to_table_mappings, "visit:#{property}")
+ table_by_event = Map.get(@property_to_table_mappings, "event:#{property}")
+ table = table_by_visit || table_by_event
+
+ {table, db_field}
+ end
+
def merge_imported_timeseries(native_q, _, %Plausible.Stats.Query{include_imported: false}, _),
do: native_q
@@ -69,15 +231,9 @@ defmodule Plausible.Stats.Imported do
query,
metrics
) do
- import_ids = site.complete_import_ids
-
imported_q =
- from(v in "imported_visitors",
- where: v.site_id == ^site.id,
- where: v.import_id in ^import_ids,
- where: v.date >= ^query.date_range.first and v.date <= ^query.date_range.last,
- select: %{}
- )
+ site
+ |> Imported.Base.query_imported(query)
|> select_imported_metrics(metrics)
|> apply_interval(query, site)
@@ -111,19 +267,12 @@ defmodule Plausible.Stats.Imported do
def merge_imported(q, site, %Query{property: property} = query, metrics)
when property in @imported_properties do
- table = Map.fetch!(@property_to_table_mappings, property)
dim = Plausible.Stats.Filters.without_prefix(property)
- import_ids = site.complete_import_ids
imported_q =
- from(
- i in table,
- where: i.site_id == ^site.id,
- where: i.import_id in ^import_ids,
- where: i.date >= ^query.date_range.first and i.date <= ^query.date_range.last,
- where: i.visitors > 0,
- select: %{}
- )
+ site
+ |> Imported.Base.query_imported(query)
+ |> where([i], i.visitors > 0)
|> maybe_apply_filter(query, property, dim)
|> group_imported_by(dim)
|> select_imported_metrics(metrics)
@@ -155,7 +304,8 @@ defmodule Plausible.Stats.Imported do
def merge_imported(q, site, %Query{property: nil} = query, metrics) do
imported_q =
- imported_visitors(site, query)
+ site
+ |> Imported.Base.query_imported(query)
|> select_imported_metrics(metrics)
from(
@@ -171,69 +321,40 @@ defmodule Plausible.Stats.Imported do
def merge_imported_pageview_goals(q, _, %Query{include_imported: false}, _, _), do: q
def merge_imported_pageview_goals(q, site, query, page_exprs, metrics) do
- page_regexes = Enum.map(page_exprs, &Base.page_regex/1)
-
- imported_q =
- from(
- i in "imported_pages",
- where: i.site_id == ^site.id,
- where: i.import_id in ^site.complete_import_ids,
- where: i.date >= ^query.date_range.first and i.date <= ^query.date_range.last,
- where: i.visitors > 0,
- where:
- fragment(
- "notEmpty(multiMatchAllIndices(?, ?) as indices)",
- i.page,
- ^page_regexes
- ),
- array_join: index in fragment("indices"),
- group_by: index,
- select: %{
+ if Imported.Base.decide_table(query) == "imported_pages" do
+ page_regexes = Enum.map(page_exprs, &Base.page_regex/1)
+
+ imported_q =
+ "imported_pages"
+ |> Imported.Base.query_imported(site, query)
+ |> where([i], i.visitors > 0)
+ |> where(
+ [i],
+ fragment("notEmpty(multiMatchAllIndices(?, ?) as indices)", i.page, ^page_regexes)
+ )
+ |> join(:array, index in fragment("indices"))
+ |> group_by([_i, index], index)
+ |> select_merge([_i, index], %{
name: fragment("concat('Visit ', ?[?])", ^page_exprs, index)
- }
- )
- |> select_imported_metrics(metrics)
-
- from(s in Ecto.Query.subquery(q),
- full_join: i in subquery(imported_q),
- on: s.name == i.name,
- select: %{}
- )
- |> select_joined_dimension(:name)
- |> select_joined_metrics(metrics)
- end
-
- def total_imported_visitors(site, query) do
- imported_visitors(site, query)
- |> select_merge([i], %{total_visitors: fragment("sum(?)", i.visitors)})
- end
-
- defp imported_visitors(site, query) do
- import_ids = site.complete_import_ids
+ })
+ |> select_imported_metrics(metrics)
- from(
- i in "imported_visitors",
- where: i.site_id == ^site.id,
- where: i.import_id in ^import_ids,
- where: i.date >= ^query.date_range.first and i.date <= ^query.date_range.last,
- select: %{}
- )
- end
-
- defp maybe_apply_filter(q, query, "event:props:url", _) do
- if name = find_special_goal_filter(query, @goals_with_url) do
- where(q, [i], i.name == ^name)
+ from(s in Ecto.Query.subquery(q),
+ full_join: i in subquery(imported_q),
+ on: s.name == i.name,
+ select: %{}
+ )
+ |> select_joined_dimension(:name)
+ |> select_joined_metrics(metrics)
else
q
end
end
- defp maybe_apply_filter(q, query, "event:props:path", _) do
- if name = find_special_goal_filter(query, @goals_with_path) do
- where(q, [i], i.name == ^name)
- else
- q
- end
+ def total_imported_visitors(site, query) do
+ site
+ |> Imported.Base.query_imported(query)
+ |> select_merge([i], %{total_visitors: fragment("sum(?)", i.visitors)})
end
defp maybe_apply_filter(q, query, property, dim) do
@@ -243,20 +364,6 @@ defmodule Plausible.Stats.Imported do
end
end
- defp has_special_goal_filter?(query, event_names) do
- not is_nil(find_special_goal_filter(query, event_names))
- end
-
- defp find_special_goal_filter(query, event_names) do
- case Query.get_filter(query, "event:goal") do
- [:is, "event:goal", {:event, name}] ->
- if name in event_names, do: name, else: nil
-
- _ ->
- nil
- end
- end
-
defp select_imported_metrics(q, []), do: q
defp select_imported_metrics(q, [:visitors | rest]) do
@@ -320,6 +427,18 @@ defmodule Plausible.Stats.Imported do
|> select_imported_metrics(rest)
end
+ defp select_imported_metrics(
+ %Ecto.Query{from: %Ecto.Query.FromExpr{source: {"imported_pages", _}}} = q,
+ [:bounce_rate | rest]
+ ) do
+ q
+ |> select_merge([i], %{
+ bounces: 0,
+ __internal_visits: 0
+ })
+ |> select_imported_metrics(rest)
+ end
+
defp select_imported_metrics(
%Ecto.Query{from: %Ecto.Query.FromExpr{source: {"imported_entry_pages", _}}} = q,
[:bounce_rate | rest]
@@ -353,6 +472,18 @@ defmodule Plausible.Stats.Imported do
|> select_imported_metrics(rest)
end
+ defp select_imported_metrics(
+ %Ecto.Query{from: %Ecto.Query.FromExpr{source: {"imported_pages", _}}} = q,
+ [:visit_duration | rest]
+ ) do
+ q
+ |> select_merge([i], %{
+ visit_duration: 0,
+ __internal_visits: 0
+ })
+ |> select_imported_metrics(rest)
+ end
+
defp select_imported_metrics(
%Ecto.Query{from: %Ecto.Query.FromExpr{source: {"imported_entry_pages", _}}} = q,
[:visit_duration | rest]
@@ -386,6 +517,32 @@ defmodule Plausible.Stats.Imported do
|> select_imported_metrics(rest)
end
+ defp select_imported_metrics(
+ %Ecto.Query{from: %Ecto.Query.FromExpr{source: {"imported_entry_pages", _}}} = q,
+ [:views_per_visit | rest]
+ ) do
+ q
+ |> where([i], i.pageviews > 0)
+ |> select_merge([i], %{
+ pageviews: sum(i.pageviews),
+ __internal_visits: sum(i.entrances)
+ })
+ |> select_imported_metrics(rest)
+ end
+
+ defp select_imported_metrics(
+ %Ecto.Query{from: %Ecto.Query.FromExpr{source: {"imported_exit_pages", _}}} = q,
+ [:views_per_visit | rest]
+ ) do
+ q
+ |> where([i], i.pageviews > 0)
+ |> select_merge([i], %{
+ pageviews: sum(i.pageviews),
+ __internal_visits: sum(i.exits)
+ })
+ |> select_imported_metrics(rest)
+ end
+
defp select_imported_metrics(q, [:views_per_visit | rest]) do
q
|> where([i], i.pageviews > 0)
@@ -558,7 +715,7 @@ defmodule Plausible.Stats.Imported do
end
defp select_joined_metrics(q, []), do: q
- # TODO: Reverse-engineering the native data bounces and total visit
+ # NOTE: Reverse-engineering the native data bounces and total visit
# durations to combine with imported data is inefficient. Instead both
# queries should fetch bounces/total_visit_duration and visits and be
# used as subqueries to a main query that then find the bounce rate/avg
diff --git a/lib/plausible/stats/query.ex b/lib/plausible/stats/query.ex
index 90f347d61a4b..261840c8317d 100644
--- a/lib/plausible/stats/query.ex
+++ b/lib/plausible/stats/query.ex
@@ -12,13 +12,15 @@ defmodule Plausible.Stats.Query do
skip_imported_reason: nil,
now: nil,
experimental_session_count?: false,
- experimental_reduced_joins?: false
+ experimental_reduced_joins?: false,
+ latest_import_end_date: nil
require OpenTelemetry.Tracer, as: Tracer
alias Plausible.Stats.{Filters, Interval, Imported}
@type t :: %__MODULE__{}
+ @spec from(Plausible.Site.t(), map()) :: t()
def from(site, params) do
now = NaiveDateTime.utc_now(:second)
@@ -201,19 +203,43 @@ defmodule Plausible.Stats.Query do
struct!(query, filters: Filters.parse(params["filters"]))
end
+ @spec set_property(t(), String.t() | nil, Keyword.t()) :: t()
+ def set_property(query, property, opts \\ []) do
+ query = struct!(query, property: property)
+
+ if Keyword.get(opts, :skip_refresh_imported_opts),
+ do: query,
+ else: refresh_imported_opts(query)
+ end
+
def put_filter(query, filter) do
- struct!(query,
- filters: query.filters ++ [filter]
- )
+ query
+ |> struct!(filters: query.filters ++ [filter])
+ |> refresh_imported_opts()
end
- def remove_filters(query, prefixes) do
+ def remove_filters(query, prefixes, opts \\ []) do
new_filters =
Enum.reject(query.filters, fn [_, filter_key | _rest] ->
Enum.any?(prefixes, &String.starts_with?(filter_key, &1))
end)
- struct!(query, filters: new_filters)
+ query = struct!(query, filters: new_filters)
+
+ if Keyword.get(opts, :skip_refresh_imported_opts),
+ do: query,
+ else: refresh_imported_opts(query)
+ end
+
+ def exclude_imported(query) do
+ struct!(query,
+ include_imported: false,
+ skip_imported_reason: :manual_exclusion
+ )
+ end
+
+ defp refresh_imported_opts(query) do
+ put_imported_opts(query, nil, %{})
end
def has_event_filters?(query) do
@@ -247,13 +273,22 @@ defmodule Plausible.Stats.Query do
end
defp put_imported_opts(query, site, params) do
- requested? = params["with_imported"] == "true"
+ requested? = params["with_imported"] == "true" || query.imported_data_requested
+
+ latest_import_end_date =
+ if site do
+ site.latest_import_end_date
+ else
+ query.latest_import_end_date
+ end
- case ensure_include_imported(query, site) do
+ query = struct!(query, latest_import_end_date: latest_import_end_date)
+
+ case ensure_include_imported(query, requested?) do
:ok ->
struct!(query,
- imported_data_requested: requested?,
- include_imported: requested?
+ imported_data_requested: true,
+ include_imported: true
)
{:error, reason} ->
@@ -265,12 +300,13 @@ defmodule Plausible.Stats.Query do
end
end
- @spec ensure_include_imported(t(), Plausible.Site.t()) ::
- :ok | {:error, :no_imported_data | :out_of_range | :unsupported_query}
- def ensure_include_imported(query, site) do
+ @spec ensure_include_imported(t(), boolean()) ::
+ :ok | {:error, :not_requested | :no_imported_data | :out_of_range | :unsupported_query}
+ def ensure_include_imported(query, requested?) do
cond do
- is_nil(site.latest_import_end_date) -> {:error, :no_imported_data}
- Date.after?(query.date_range.first, site.latest_import_end_date) -> {:error, :out_of_range}
+ not requested? -> {:error, :not_requested}
+ is_nil(query.latest_import_end_date) -> {:error, :no_imported_data}
+ Date.after?(query.date_range.first, query.latest_import_end_date) -> {:error, :out_of_range}
not Imported.schema_supports_query?(query) -> {:error, :unsupported_query}
query.period == "realtime" -> {:error, :unsupported_query}
true -> :ok
diff --git a/lib/plausible/stats/timeseries.ex b/lib/plausible/stats/timeseries.ex
index 08dfaf18bf2f..bab37c6e2272 100644
--- a/lib/plausible/stats/timeseries.ex
+++ b/lib/plausible/stats/timeseries.ex
@@ -1,7 +1,7 @@
defmodule Plausible.Stats.Timeseries do
use Plausible.ClickhouseRepo
use Plausible
- alias Plausible.Stats.{Query, Util}
+ alias Plausible.Stats.{Query, Util, Imported}
import Plausible.Stats.{Base}
import Ecto.Query
use Plausible.Stats.Fragments
@@ -56,8 +56,8 @@ defmodule Plausible.Stats.Timeseries do
from(e in base_event_query(site, query), select: ^select_event_metrics(metrics))
|> select_bucket(:events, site, query)
+ |> Imported.merge_imported_timeseries(site, query, metrics)
|> maybe_add_timeseries_conversion_rate(site, query, metrics)
- |> Plausible.Stats.Imported.merge_imported_timeseries(site, query, metrics)
|> ClickhouseRepo.all()
end
@@ -67,7 +67,7 @@ defmodule Plausible.Stats.Timeseries do
from(e in query_sessions(site, query), select: ^select_session_metrics(metrics, query))
|> filter_converted_sessions(site, query)
|> select_bucket(:sessions, site, query)
- |> Plausible.Stats.Imported.merge_imported_timeseries(site, query, metrics)
+ |> Imported.merge_imported_timeseries(site, query, metrics)
|> ClickhouseRepo.all()
|> Util.keep_requested_metrics(metrics)
end
@@ -314,13 +314,16 @@ defmodule Plausible.Stats.Timeseries do
defp maybe_add_timeseries_conversion_rate(q, site, query, metrics) do
if :conversion_rate in metrics do
- totals_query = query |> Query.remove_filters(["event:goal", "event:props"])
+ totals_query =
+ query
+ |> Query.remove_filters(["event:goal", "event:props"], skip_refresh_imported_opts: true)
totals_timeseries_q =
from(e in base_event_query(site, totals_query),
select: ^select_event_metrics([:visitors])
)
- |> select_bucket(:events, site, query)
+ |> select_bucket(:events, site, totals_query)
+ |> Imported.merge_imported_timeseries(site, totals_query, [:visitors])
from(e in subquery(q),
left_join: c in subquery(totals_timeseries_q),
diff --git a/lib/plausible_web/controllers/api/external_stats_controller.ex b/lib/plausible_web/controllers/api/external_stats_controller.ex
index 77e3d51ca500..f8145b8ccfa0 100644
--- a/lib/plausible_web/controllers/api/external_stats_controller.ex
+++ b/lib/plausible_web/controllers/api/external_stats_controller.ex
@@ -380,7 +380,7 @@ defmodule PlausibleWeb.Api.ExternalStatsController do
end
defp maybe_add_warning(payload, %{skip_imported_reason: reason})
- when reason in [nil, :no_imported_data, :out_of_range] do
+ when reason in [nil, :not_requested, :no_imported_data, :out_of_range, :manual_exclusion] do
payload
end
diff --git a/lib/plausible_web/controllers/api/stats_controller.ex b/lib/plausible_web/controllers/api/stats_controller.ex
index bc9086ca848c..868194ffa7d6 100644
--- a/lib/plausible_web/controllers/api/stats_controller.ex
+++ b/lib/plausible_web/controllers/api/stats_controller.ex
@@ -65,7 +65,7 @@ defmodule PlausibleWeb.Api.StatsController do
* `interval` - the interval used for querying.
- * `with_imported` - boolean indicating whether the Google Analytics data
+ * `includes_imported` - boolean indicating whether imported data
was queried or not.
* `imports_exist` - boolean indicating whether there are any completed
@@ -92,7 +92,7 @@ defmodule PlausibleWeb.Api.StatsController do
"labels" => ["2021-09-01", "2021-10-01", "2021-11-01", "2021-12-01"],
"plot" => [0, 0, 0, 0],
"present_index" => nil,
- "with_imported" => false
+ "includes_imported" => false
}
```
@@ -137,7 +137,7 @@ defmodule PlausibleWeb.Api.StatsController do
comparison_labels: comparison_result && label_timeseries(comparison_result, nil),
present_index: present_index,
interval: query.interval,
- with_imported: with_imported?(query, comparison_query),
+ includes_imported: includes_imported?(query, comparison_query),
imports_exist: site.complete_import_ids != [],
full_intervals: full_intervals
})
@@ -217,7 +217,7 @@ defmodule PlausibleWeb.Api.StatsController do
top_stats: top_stats,
interval: query.interval,
sample_percent: sample_percent,
- with_imported: with_imported?(query, comparison_query),
+ includes_imported: includes_imported?(query, comparison_query),
imports_exist: site.complete_import_ids != [],
comparing_from: comparison_query && comparison_query.date_range.first,
comparing_to: comparison_query && comparison_query.date_range.last,
@@ -381,26 +381,15 @@ defmodule PlausibleWeb.Api.StatsController do
end
defp fetch_other_top_stats(site, query, comparison_query) do
+ page_filter? = Query.get_filter(query, "event:page")
+
+ metrics = [:visitors, :visits, :pageviews, :sample_percent]
+
metrics =
- if Query.get_filter(query, "event:page") do
- [
- :visitors,
- :visits,
- :pageviews,
- :bounce_rate,
- :time_on_page,
- :sample_percent
- ]
- else
- [
- :visitors,
- :visits,
- :pageviews,
- :views_per_visit,
- :bounce_rate,
- :visit_duration,
- :sample_percent
- ]
+ cond do
+ page_filter? && query.include_imported -> metrics
+ page_filter? -> metrics ++ [:bounce_rate, :time_on_page]
+ true -> metrics ++ [:views_per_visit, :bounce_rate, :visit_duration]
end
current_results = Stats.aggregate(site, query, metrics)
@@ -492,7 +481,10 @@ defmodule PlausibleWeb.Api.StatsController do
res |> to_csv([:name, :visitors, :bounce_rate, :visit_duration])
end
else
- json(conn, res)
+ json(conn, %{
+ results: res,
+ skip_imported_reason: query.skip_imported_reason
+ })
end
end
@@ -570,7 +562,10 @@ defmodule PlausibleWeb.Api.StatsController do
res |> to_csv([:name, :visitors, :bounce_rate, :visit_duration])
end
else
- json(conn, res)
+ json(conn, %{
+ results: res,
+ skip_imported_reason: query.skip_imported_reason
+ })
end
end
@@ -594,7 +589,10 @@ defmodule PlausibleWeb.Api.StatsController do
res |> to_csv([:name, :visitors, :bounce_rate, :visit_duration])
end
else
- json(conn, res)
+ json(conn, %{
+ results: res,
+ skip_imported_reason: query.skip_imported_reason
+ })
end
end
@@ -618,7 +616,10 @@ defmodule PlausibleWeb.Api.StatsController do
res |> to_csv([:name, :visitors, :bounce_rate, :visit_duration])
end
else
- json(conn, res)
+ json(conn, %{
+ results: res,
+ skip_imported_reason: query.skip_imported_reason
+ })
end
end
@@ -642,7 +643,10 @@ defmodule PlausibleWeb.Api.StatsController do
res |> to_csv([:name, :visitors, :bounce_rate, :visit_duration])
end
else
- json(conn, res)
+ json(conn, %{
+ results: res,
+ skip_imported_reason: query.skip_imported_reason
+ })
end
end
@@ -666,7 +670,10 @@ defmodule PlausibleWeb.Api.StatsController do
res |> to_csv([:name, :visitors, :bounce_rate, :visit_duration])
end
else
- json(conn, res)
+ json(conn, %{
+ results: res,
+ skip_imported_reason: query.skip_imported_reason
+ })
end
end
@@ -690,7 +697,10 @@ defmodule PlausibleWeb.Api.StatsController do
res |> to_csv([:name, :visitors, :bounce_rate, :visit_duration])
end
else
- json(conn, res)
+ json(conn, %{
+ results: res,
+ skip_imported_reason: query.skip_imported_reason
+ })
end
end
@@ -743,7 +753,10 @@ defmodule PlausibleWeb.Api.StatsController do
Stats.breakdown(site, query, metrics, pagination)
|> transform_keys(%{referrer: :name})
- json(conn, referrers)
+ json(conn, %{
+ results: referrers,
+ skip_imported_reason: query.skip_imported_reason
+ })
end
def pages(conn, params) do
@@ -772,7 +785,10 @@ defmodule PlausibleWeb.Api.StatsController do
pages |> to_csv([:name, :visitors, :pageviews, :bounce_rate, :time_on_page])
end
else
- json(conn, pages)
+ json(conn, %{
+ results: pages,
+ skip_imported_reason: query.skip_imported_reason
+ })
end
end
@@ -803,7 +819,10 @@ defmodule PlausibleWeb.Api.StatsController do
])
end
else
- json(conn, entry_pages)
+ json(conn, %{
+ results: entry_pages,
+ skip_imported_reason: query.skip_imported_reason
+ })
end
end
@@ -835,7 +854,10 @@ defmodule PlausibleWeb.Api.StatsController do
])
end
else
- json(conn, exit_pages)
+ json(conn, %{
+ results: exit_pages,
+ skip_imported_reason: query.skip_imported_reason
+ })
end
end
@@ -845,14 +867,15 @@ defmodule PlausibleWeb.Api.StatsController do
else
pages = Enum.map(breakdown_results, & &1[:exit_page])
- total_visits_query =
+ total_pageviews_query =
query
+ |> Query.remove_filters(["visit:exit_page"])
|> Query.put_filter([:member, "event:page", pages])
|> Query.put_filter([:is, "event:name", "pageview"])
- |> struct!(property: "event:page")
+ |> Query.set_property("event:page")
total_pageviews =
- Stats.breakdown(site, total_visits_query, [:pageviews], {limit, 1})
+ Stats.breakdown(site, total_pageviews_query, [:pageviews], {limit, 1})
Enum.map(breakdown_results, fn result ->
exit_rate =
@@ -917,7 +940,10 @@ defmodule PlausibleWeb.Api.StatsController do
end
end)
- json(conn, countries)
+ json(conn, %{
+ results: countries,
+ skip_imported_reason: query.skip_imported_reason
+ })
end
end
@@ -952,7 +978,10 @@ defmodule PlausibleWeb.Api.StatsController do
regions |> to_csv([:name, :visitors])
end
else
- json(conn, regions)
+ json(conn, %{
+ results: regions,
+ skip_imported_reason: query.skip_imported_reason
+ })
end
end
@@ -992,7 +1021,10 @@ defmodule PlausibleWeb.Api.StatsController do
cities |> to_csv([:name, :visitors])
end
else
- json(conn, cities)
+ json(conn, %{
+ results: cities,
+ skip_imported_reason: query.skip_imported_reason
+ })
end
end
@@ -1016,7 +1048,10 @@ defmodule PlausibleWeb.Api.StatsController do
browsers |> to_csv([:name, :visitors])
end
else
- json(conn, browsers)
+ json(conn, %{
+ results: browsers,
+ skip_imported_reason: query.skip_imported_reason
+ })
end
end
@@ -1046,7 +1081,10 @@ defmodule PlausibleWeb.Api.StatsController do
|> to_csv([:name, :version, :visitors])
end
else
- json(conn, versions)
+ json(conn, %{
+ results: versions,
+ skip_imported_reason: query.skip_imported_reason
+ })
end
end
@@ -1070,7 +1108,10 @@ defmodule PlausibleWeb.Api.StatsController do
systems |> to_csv([:name, :visitors])
end
else
- json(conn, systems)
+ json(conn, %{
+ results: systems,
+ skip_imported_reason: query.skip_imported_reason
+ })
end
end
@@ -1096,7 +1137,10 @@ defmodule PlausibleWeb.Api.StatsController do
|> to_csv([:name, :version, :visitors])
end
else
- json(conn, versions)
+ json(conn, %{
+ results: versions,
+ skip_imported_reason: query.skip_imported_reason
+ })
end
end
@@ -1120,7 +1164,10 @@ defmodule PlausibleWeb.Api.StatsController do
sizes |> to_csv([:name, :visitors])
end
else
- json(conn, sizes)
+ json(conn, %{
+ results: sizes,
+ skip_imported_reason: query.skip_imported_reason
+ })
end
end
@@ -1156,7 +1203,10 @@ defmodule PlausibleWeb.Api.StatsController do
:total_conversions
])
else
- json(conn, conversions)
+ json(conn, %{
+ results: conversions,
+ skip_imported_reason: query.skip_imported_reason
+ })
end
end
@@ -1166,8 +1216,7 @@ defmodule PlausibleWeb.Api.StatsController do
case Plausible.Props.ensure_prop_key_accessible(prop_key, site.owner) do
:ok ->
- props = breakdown_custom_prop_values(site, params)
- json(conn, props)
+ json(conn, breakdown_custom_prop_values(site, params))
{:error, :upgrade_required} ->
H.payment_required(
@@ -1195,6 +1244,7 @@ defmodule PlausibleWeb.Api.StatsController do
prop_names
|> Enum.map(fn prop_key ->
breakdown_custom_prop_values(site, Map.put(params, "prop_key", prop_key))
+ |> Map.get(:results)
|> Enum.map(&Map.put(&1, :property, prop_key))
|> transform_keys(%{:name => :value})
end)
@@ -1224,12 +1274,15 @@ defmodule PlausibleWeb.Api.StatsController do
[:visitors, :events, :percentage] ++ @revenue_metrics
end
- Stats.breakdown(site, query, metrics, pagination)
- |> transform_keys(%{prop_key => :name})
- |> Enum.map(fn entry ->
- Enum.map(entry, &format_revenue_metric/1)
- |> Map.new()
- end)
+ props =
+ Stats.breakdown(site, query, metrics, pagination)
+ |> transform_keys(%{prop_key => :name})
+ |> Enum.map(fn entry ->
+ Enum.map(entry, &format_revenue_metric/1)
+ |> Map.new()
+ end)
+
+ %{results: props, skip_imported_reason: query.skip_imported_reason}
end
def current_visitors(conn, _) do
@@ -1401,7 +1454,7 @@ defmodule PlausibleWeb.Api.StatsController do
]
end
- defp with_imported?(source_query, comparison_query) do
+ defp includes_imported?(source_query, comparison_query) do
cond do
source_query.include_imported -> true
comparison_query && comparison_query.include_imported -> true
diff --git a/test/plausible_web/controllers/api/external_stats_controller/aggregate_test.exs b/test/plausible_web/controllers/api/external_stats_controller/aggregate_test.exs
index 5dcbe84601b8..68cfefd85968 100644
--- a/test/plausible_web/controllers/api/external_stats_controller/aggregate_test.exs
+++ b/test/plausible_web/controllers/api/external_stats_controller/aggregate_test.exs
@@ -561,14 +561,14 @@ defmodule PlausibleWeb.Api.ExternalStatsController.AggregateTest do
}
end
- test "ignores imported data when filters are applied", %{
+ test "includes imported data in comparison when filter applied", %{
conn: conn,
site: site,
site_import: site_import
} do
populate_stats(site, site_import.id, [
build(:imported_visitors, date: ~D[2023-01-01]),
- build(:imported_sources, date: ~D[2023-01-01]),
+ build(:imported_sources, source: "Google", date: ~D[2023-01-01], visitors: 3),
build(:pageview,
referrer_source: "Google",
timestamp: ~N[2023-01-02 00:10:00]
@@ -587,7 +587,7 @@ defmodule PlausibleWeb.Api.ExternalStatsController.AggregateTest do
})
assert json_response(conn, 200)["results"] == %{
- "visitors" => %{"value" => 1, "change" => 100}
+ "visitors" => %{"value" => 1, "change" => -67}
}
end
@@ -681,6 +681,60 @@ defmodule PlausibleWeb.Api.ExternalStatsController.AggregateTest do
refute json_response(conn, 200)["warning"]
end
+
+ test "excludes imported data from conversion rate computation when query filters by non-imported props",
+ %{conn: conn, site: site, site_import: site_import} do
+ insert(:goal, site: site, event_name: "Purchase")
+
+ populate_stats(site, site_import.id, [
+ build(:event,
+ name: "Purchase",
+ "meta.key": ["package"],
+ "meta.value": ["large"]
+ ),
+ build(:imported_visitors, visitors: 9)
+ ])
+
+ conn =
+ get(conn, "/api/v1/stats/aggregate", %{
+ "site_id" => site.domain,
+ "period" => "day",
+ "metrics" => "visitors,conversion_rate",
+ "filters" => "event:goal==Purchase;event:props:package==large",
+ "with_imported" => "true"
+ })
+
+ assert json_response(conn, 200)["results"] == %{
+ "visitors" => %{"value" => 1},
+ "conversion_rate" => %{"value" => 100.0}
+ }
+ end
+
+ test "returns stats with page + pageview goal filter",
+ %{conn: conn, site: site, site_import: site_import} do
+ insert(:goal, site: site, page_path: "/blog/**")
+
+ populate_stats(site, site_import.id, [
+ build(:imported_pages, page: "/blog/1", visitors: 1, pageviews: 1),
+ build(:imported_pages, page: "/blog/2", visitors: 2, pageviews: 2),
+ build(:imported_pages, visitors: 3)
+ ])
+
+ conn =
+ get(conn, "/api/v1/stats/aggregate", %{
+ "site_id" => site.domain,
+ "period" => "day",
+ "metrics" => "visitors,events,conversion_rate",
+ "filters" => "event:page==/blog/2;event:goal==Visit /blog/**",
+ "with_imported" => "true"
+ })
+
+ assert json_response(conn, 200)["results"] == %{
+ "visitors" => %{"value" => 2},
+ "events" => %{"value" => 2},
+ "conversion_rate" => %{"value" => 100.0}
+ }
+ end
end
describe "filters" do
diff --git a/test/plausible_web/controllers/api/external_stats_controller/breakdown_test.exs b/test/plausible_web/controllers/api/external_stats_controller/breakdown_test.exs
index 1125722da236..8be42e318717 100644
--- a/test/plausible_web/controllers/api/external_stats_controller/breakdown_test.exs
+++ b/test/plausible_web/controllers/api/external_stats_controller/breakdown_test.exs
@@ -3158,6 +3158,37 @@ defmodule PlausibleWeb.Api.ExternalStatsController.BreakdownTest do
end
describe "imported data" do
+ test "returns screen sizes breakdown when filtering by screen size", %{conn: conn, site: site} do
+ site_import = insert(:site_import, site: site)
+
+ populate_stats(site, site_import.id, [
+ build(:pageview,
+ timestamp: ~N[2021-01-01 00:00:01],
+ screen_size: "Mobile"
+ ),
+ build(:imported_devices,
+ device: "Mobile",
+ visitors: 3,
+ pageviews: 5,
+ date: ~D[2021-01-01]
+ )
+ ])
+
+ conn =
+ get(conn, "/api/v1/stats/breakdown", %{
+ "site_id" => site.domain,
+ "period" => "day",
+ "date" => "2021-01-01",
+ "property" => "visit:device",
+ "filters" => "visit:device==Mobile",
+ "metrics" => "visitors,pageviews",
+ "with_imported" => "true"
+ })
+
+ assert [%{"pageviews" => 6, "visitors" => 4, "device" => "Mobile"}] =
+ json_response(conn, 200)["results"]
+ end
+
test "returns custom event goals and pageview goals", %{conn: conn, site: site} do
insert(:goal, site: site, event_name: "Purchase")
insert(:goal, site: site, page_path: "/test")
@@ -3478,5 +3509,64 @@ defmodule PlausibleWeb.Api.ExternalStatsController.BreakdownTest do
refute json_response(conn, 200)["warning"]
end
+
+ test "applies multiple filters if the properties belong to the same table", %{
+ conn: conn,
+ site: site
+ } do
+ site_import = insert(:site_import, site: site)
+
+ populate_stats(site, site_import.id, [
+ build(:imported_sources, source: "Google", utm_medium: "organic", utm_term: "one"),
+ build(:imported_sources, source: "Twitter", utm_medium: "organic", utm_term: "two"),
+ build(:imported_sources,
+ source: "Facebook",
+ utm_medium: "something_else",
+ utm_term: "one"
+ )
+ ])
+
+ conn =
+ get(conn, "/api/v1/stats/breakdown", %{
+ "site_id" => site.domain,
+ "period" => "day",
+ "property" => "visit:source",
+ "filters" => "visit:utm_medium==organic;visit:utm_term==one",
+ "with_imported" => "true"
+ })
+
+ assert json_response(conn, 200) == %{
+ "results" => [%{"source" => "Google", "visitors" => 1}]
+ }
+ end
+
+ test "ignores imported data if filtered property belongs to a different table than the breakdown property",
+ %{
+ conn: conn,
+ site: site
+ } do
+ site_import = insert(:site_import, site: site)
+
+ populate_stats(site, site_import.id, [
+ build(:imported_sources, source: "Google"),
+ build(:imported_devices, device: "Desktop")
+ ])
+
+ conn =
+ get(conn, "/api/v1/stats/breakdown", %{
+ "site_id" => site.domain,
+ "period" => "day",
+ "property" => "visit:source",
+ "filters" => "visit:device==Desktop",
+ "with_imported" => "true"
+ })
+
+ assert %{
+ "results" => [],
+ "warning" => warning
+ } = json_response(conn, 200)
+
+ assert warning =~ "Imported stats are not included in the results"
+ end
end
end
diff --git a/test/plausible_web/controllers/api/external_stats_controller/timeseries_test.exs b/test/plausible_web/controllers/api/external_stats_controller/timeseries_test.exs
index d1ecccfa326e..9149711aa3ab 100644
--- a/test/plausible_web/controllers/api/external_stats_controller/timeseries_test.exs
+++ b/test/plausible_web/controllers/api/external_stats_controller/timeseries_test.exs
@@ -1647,5 +1647,240 @@ defmodule PlausibleWeb.Api.ExternalStatsController.TimeseriesTest do
refute json_response(conn, 200)["warning"]
end
+
+ test "returns all metrics based on imported/native data when filtering by browser", %{
+ conn: conn,
+ site: site
+ } do
+ site_import = insert(:site_import, site: site)
+
+ populate_stats(site, site_import.id, [
+ build(:pageview, browser: "Chrome", user_id: 1, timestamp: ~N[2021-01-01 00:00:00]),
+ build(:pageview, browser: "Chrome", user_id: 1, timestamp: ~N[2021-01-01 00:03:00]),
+ build(:pageview, browser: "Firefox", timestamp: ~N[2021-01-01 00:00:00]),
+ build(:imported_browsers, browser: "Firefox", date: ~D[2021-01-02]),
+ build(:imported_browsers,
+ browser: "Chrome",
+ visitors: 1,
+ pageviews: 1,
+ bounces: 1,
+ visit_duration: 3,
+ visits: 1,
+ date: ~D[2021-01-03]
+ ),
+ build(:pageview, browser: "Chrome", user_id: 2, timestamp: ~N[2021-01-04 00:00:00]),
+ build(:event,
+ name: "Signup",
+ browser: "Chrome",
+ user_id: 2,
+ timestamp: ~N[2021-01-04 00:10:00]
+ ),
+ build(:imported_browsers,
+ browser: "Chrome",
+ visitors: 4,
+ pageviews: 6,
+ bounces: 1,
+ visit_duration: 300,
+ visits: 5,
+ date: ~D[2021-01-04]
+ )
+ ])
+
+ results =
+ conn
+ |> get("/api/v1/stats/timeseries", %{
+ "site_id" => site.domain,
+ "period" => "custom",
+ "date" => "2021-01-01,2021-01-04",
+ "metrics" =>
+ "visitors,pageviews,events,visits,views_per_visit,bounce_rate,visit_duration",
+ "filters" => "visit:browser==Chrome",
+ "with_imported" => "true"
+ })
+ |> json_response(200)
+ |> Map.get("results")
+
+ assert results == [
+ %{
+ "bounce_rate" => 0.0,
+ "date" => "2021-01-01",
+ "events" => 2,
+ "pageviews" => 2,
+ "views_per_visit" => 2.0,
+ "visit_duration" => 180.0,
+ "visitors" => 1,
+ "visits" => 1
+ },
+ %{
+ "bounce_rate" => nil,
+ "date" => "2021-01-02",
+ "events" => 0,
+ "pageviews" => 0,
+ "views_per_visit" => 0.0,
+ "visit_duration" => nil,
+ "visitors" => 0,
+ "visits" => 0
+ },
+ %{
+ "bounce_rate" => 100,
+ "date" => "2021-01-03",
+ "events" => 1,
+ "pageviews" => 1,
+ "views_per_visit" => 1.0,
+ "visit_duration" => 3,
+ "visitors" => 1,
+ "visits" => 1
+ },
+ %{
+ "bounce_rate" => 17.0,
+ "date" => "2021-01-04",
+ "events" => 8,
+ "pageviews" => 7,
+ "views_per_visit" => 1.17,
+ "visit_duration" => 150,
+ "visitors" => 5,
+ "visits" => 6
+ }
+ ]
+ end
+
+ test "returns conversion rate timeseries with a goal filter", %{
+ conn: conn,
+ site: site
+ } do
+ site_import = insert(:site_import, site: site)
+
+ insert(:goal, site: site, event_name: "Outbound Link: Click")
+
+ populate_stats(site, site_import.id, [
+ # 2021-01-01
+ build(:event, name: "Outbound Link: Click", timestamp: ~N[2021-01-01 00:00:00]),
+ build(:imported_custom_events, name: "Outbound Link: Click", date: ~D[2021-01-01]),
+ build(:imported_visitors, date: ~D[2021-01-01], visitors: 4),
+ # 2021-01-02
+ build(:event, name: "Outbound Link: Click", timestamp: ~N[2021-01-02 00:00:00]),
+ build(:pageview, timestamp: ~N[2021-01-02 00:00:00]),
+ # 2021-01-03
+ build(:imported_custom_events, name: "Outbound Link: Click", date: ~D[2021-01-03]),
+ build(:imported_visitors, date: ~D[2021-01-03]),
+ # 2021-01-04
+ build(:event, name: "Outbound Link: Click", timestamp: ~N[2021-01-04 00:00:00]),
+ build(:imported_visitors, date: ~D[2021-01-04], visitors: 2)
+ ])
+
+ results =
+ conn
+ |> get("/api/v1/stats/timeseries", %{
+ "site_id" => site.domain,
+ "period" => "custom",
+ "date" => "2021-01-01,2021-01-04",
+ "metrics" => "conversion_rate",
+ "filters" => "event:goal==Outbound Link: Click",
+ "with_imported" => "true"
+ })
+ |> json_response(200)
+ |> Map.get("results")
+
+ assert results == [
+ %{
+ "date" => "2021-01-01",
+ "conversion_rate" => 40.0
+ },
+ %{
+ "date" => "2021-01-02",
+ "conversion_rate" => 50.0
+ },
+ %{
+ "date" => "2021-01-03",
+ "conversion_rate" => 100.0
+ },
+ %{
+ "date" => "2021-01-04",
+ "conversion_rate" => 33.3
+ }
+ ]
+ end
+
+ test "returns conversion rate timeseries with a goal + custom prop filter", %{
+ conn: conn,
+ site: site
+ } do
+ site_import = insert(:site_import, site: site)
+
+ insert(:goal, site: site, event_name: "Outbound Link: Click")
+
+ populate_stats(site, site_import.id, [
+ # 2021-01-01
+ build(:event,
+ name: "Outbound Link: Click",
+ "meta.key": ["url"],
+ "meta.value": ["https://site.com"],
+ timestamp: ~N[2021-01-01 00:00:00]
+ ),
+ build(:imported_custom_events,
+ name: "Outbound Link: Click",
+ link_url: "https://site.com",
+ date: ~D[2021-01-01]
+ ),
+ build(:imported_custom_events,
+ name: "File Download",
+ link_url: "https://site.com",
+ date: ~D[2021-01-01]
+ ),
+ build(:imported_custom_events,
+ name: "Outbound Link: Click",
+ link_url: "https://notthis.com",
+ date: ~D[2021-01-01]
+ ),
+ build(:imported_visitors, date: ~D[2021-01-01], visitors: 4),
+ # 2021-01-03
+ build(:imported_custom_events,
+ name: "Outbound Link: Click",
+ link_url: "https://site.com",
+ date: ~D[2021-01-03]
+ ),
+ build(:imported_visitors, date: ~D[2021-01-03]),
+ # 2021-01-04
+ build(:event,
+ name: "Outbound Link: Click",
+ "meta.key": ["url"],
+ "meta.value": ["https://site.com"],
+ timestamp: ~N[2021-01-04 00:00:00]
+ ),
+ build(:imported_visitors, date: ~D[2021-01-04], visitors: 2)
+ ])
+
+ results =
+ conn
+ |> get("/api/v1/stats/timeseries", %{
+ "site_id" => site.domain,
+ "period" => "custom",
+ "date" => "2021-01-01,2021-01-04",
+ "metrics" => "conversion_rate",
+ "filters" => "event:goal==Outbound Link: Click;event:props:url==https://site.com",
+ "with_imported" => "true"
+ })
+ |> json_response(200)
+ |> Map.get("results")
+
+ assert results == [
+ %{
+ "date" => "2021-01-01",
+ "conversion_rate" => 40.0
+ },
+ %{
+ "date" => "2021-01-02",
+ "conversion_rate" => 0.0
+ },
+ %{
+ "date" => "2021-01-03",
+ "conversion_rate" => 100.0
+ },
+ %{
+ "date" => "2021-01-04",
+ "conversion_rate" => 33.3
+ }
+ ]
+ end
end
end
diff --git a/test/plausible_web/controllers/api/stats_controller/browsers_test.exs b/test/plausible_web/controllers/api/stats_controller/browsers_test.exs
index bd504c50bab0..1c2d8fc80637 100644
--- a/test/plausible_web/controllers/api/stats_controller/browsers_test.exs
+++ b/test/plausible_web/controllers/api/stats_controller/browsers_test.exs
@@ -13,7 +13,7 @@ defmodule PlausibleWeb.Api.StatsController.BrowsersTest do
conn = get(conn, "/api/stats/#{site.domain}/browsers?period=day")
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{"name" => "Chrome", "visitors" => 2, "percentage" => 66.7},
%{"name" => "Firefox", "visitors" => 1, "percentage" => 33.3}
]
@@ -47,7 +47,7 @@ defmodule PlausibleWeb.Api.StatsController.BrowsersTest do
filters = Jason.encode!(%{props: %{"author" => "John Doe"}})
conn = get(conn, "/api/stats/#{site.domain}/browsers?period=day&filters=#{filters}")
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{"name" => "Chrome", "visitors" => 1, "percentage" => 100}
]
end
@@ -82,7 +82,7 @@ defmodule PlausibleWeb.Api.StatsController.BrowsersTest do
filters = Jason.encode!(%{props: %{"author" => "!John Doe"}})
conn = get(conn, "/api/stats/#{site.domain}/browsers?period=day&filters=#{filters}")
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{"name" => "Firefox", "visitors" => 1, "percentage" => 50},
%{"name" => "Safari", "visitors" => 1, "percentage" => 50}
]
@@ -99,7 +99,7 @@ defmodule PlausibleWeb.Api.StatsController.BrowsersTest do
conn = get(conn, "/api/stats/#{site.domain}/browsers?period=day&filters=#{filters}")
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"name" => "Chrome",
"total_visitors" => 2,
@@ -123,13 +123,13 @@ defmodule PlausibleWeb.Api.StatsController.BrowsersTest do
conn = get(conn, "/api/stats/#{site.domain}/browsers?period=day")
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{"name" => "Chrome", "visitors" => 1, "percentage" => 100}
]
conn = get(conn, "/api/stats/#{site.domain}/browsers?period=day&with_imported=true")
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{"name" => "Chrome", "visitors" => 2, "percentage" => 66.7},
%{"name" => "Firefox", "visitors" => 1, "percentage" => 33.3}
]
@@ -154,7 +154,7 @@ defmodule PlausibleWeb.Api.StatsController.BrowsersTest do
conn = get(conn, "/api/stats/#{site.domain}/browsers?period=day&with_imported=true")
- assert json_response(conn, 200) == []
+ assert json_response(conn, 200)["results"] == []
end
test "returns (not set) when appropriate", %{conn: conn, site: site} do
@@ -167,7 +167,7 @@ defmodule PlausibleWeb.Api.StatsController.BrowsersTest do
conn = get(conn, "/api/stats/#{site.domain}/browsers?period=day")
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{"name" => "(not set)", "visitors" => 1, "percentage" => 100.0}
]
end
@@ -185,7 +185,7 @@ defmodule PlausibleWeb.Api.StatsController.BrowsersTest do
conn = get(conn, "/api/stats/#{site.domain}/browsers?period=day&with_imported=true")
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{"name" => "(not set)", "visitors" => 2, "percentage" => 100.0}
]
end
@@ -220,6 +220,7 @@ defmodule PlausibleWeb.Api.StatsController.BrowsersTest do
"/api/stats/#{site.domain}/browser-versions?period=day&filters=#{filters}"
)
|> json_response(200)
+ |> Map.get("results")
assert %{
"browser" => "Chrome",
@@ -254,7 +255,7 @@ defmodule PlausibleWeb.Api.StatsController.BrowsersTest do
"/api/stats/#{site.domain}/browser-versions?period=day&filters=#{filters}"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{"name" => "78.0", "visitors" => 2, "percentage" => 66.7, "browser" => "Chrome"},
%{"name" => "77.0", "visitors" => 1, "percentage" => 33.3, "browser" => "Chrome"}
]
@@ -273,7 +274,7 @@ defmodule PlausibleWeb.Api.StatsController.BrowsersTest do
"/api/stats/#{site.domain}/browser-versions?period=day&filters=#{filters}"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"name" => "(not set)",
"visitors" => 1,
@@ -317,7 +318,7 @@ defmodule PlausibleWeb.Api.StatsController.BrowsersTest do
"/api/stats/#{site.domain}/browser-versions?period=day&date=2021-01-01&with_imported=true"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"browser" => "(not set)",
"name" => "(not set)",
diff --git a/test/plausible_web/controllers/api/stats_controller/cities_test.exs b/test/plausible_web/controllers/api/stats_controller/cities_test.exs
index 903d6d625e60..0f97188f234f 100644
--- a/test/plausible_web/controllers/api/stats_controller/cities_test.exs
+++ b/test/plausible_web/controllers/api/stats_controller/cities_test.exs
@@ -37,7 +37,7 @@ defmodule PlausibleWeb.Api.StatsController.CitiesTest do
test "returns top cities by new visitors", %{conn: conn, site: site} do
conn = get(conn, "/api/stats/#{site.domain}/cities?period=day")
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{"code" => 588_409, "country_flag" => "🇪🇪", "name" => "Tallinn", "visitors" => 3},
%{"code" => 591_632, "country_flag" => "🇪🇪", "name" => "Kärdla", "visitors" => 2}
]
@@ -47,7 +47,7 @@ defmodule PlausibleWeb.Api.StatsController.CitiesTest do
filters = Jason.encode!(%{city: "591632"})
conn = get(conn, "/api/stats/#{site.domain}/cities?period=day&filters=#{filters}")
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{"code" => 591_632, "country_flag" => "🇪🇪", "name" => "Kärdla", "visitors" => 2}
]
end
@@ -61,7 +61,7 @@ defmodule PlausibleWeb.Api.StatsController.CitiesTest do
conn = get(conn, "/api/stats/#{site.domain}/cities?period=day&with_imported=true")
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{"code" => 588_409, "country_flag" => "🇪🇪", "name" => "Tallinn", "visitors" => 4},
%{"code" => 591_632, "country_flag" => "🇪🇪", "name" => "Kärdla", "visitors" => 2}
]
diff --git a/test/plausible_web/controllers/api/stats_controller/conversions_test.exs b/test/plausible_web/controllers/api/stats_controller/conversions_test.exs
index f932456dfbd2..24e18ed2da32 100644
--- a/test/plausible_web/controllers/api/stats_controller/conversions_test.exs
+++ b/test/plausible_web/controllers/api/stats_controller/conversions_test.exs
@@ -32,7 +32,7 @@ defmodule PlausibleWeb.Api.StatsController.ConversionsTest do
conn = get(conn, "/api/stats/#{site.domain}/conversions?period=day")
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"name" => "Signup",
"visitors" => 2,
@@ -79,7 +79,7 @@ defmodule PlausibleWeb.Api.StatsController.ConversionsTest do
filters = Jason.encode!(%{props: %{"logged_in" => "true"}})
conn = get(conn, "/api/stats/#{site.domain}/conversions?period=day&filters=#{filters}")
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"name" => "Payment",
"visitors" => 1,
@@ -119,7 +119,7 @@ defmodule PlausibleWeb.Api.StatsController.ConversionsTest do
filters = Jason.encode!(%{props: %{"logged_in" => "!true"}})
conn = get(conn, "/api/stats/#{site.domain}/conversions?period=day&filters=#{filters}")
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"name" => "Payment",
"visitors" => 2,
@@ -157,7 +157,7 @@ defmodule PlausibleWeb.Api.StatsController.ConversionsTest do
filters = Jason.encode!(%{props: %{"logged_in" => "(none)"}})
conn = get(conn, "/api/stats/#{site.domain}/conversions?period=day&filters=#{filters}")
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"name" => "Payment",
"visitors" => 2,
@@ -197,7 +197,7 @@ defmodule PlausibleWeb.Api.StatsController.ConversionsTest do
filters = Jason.encode!(%{props: %{"logged_in" => "!(none)"}})
conn = get(conn, "/api/stats/#{site.domain}/conversions?period=day&filters=#{filters}")
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"name" => "Payment",
"visitors" => 2,
@@ -215,6 +215,7 @@ defmodule PlausibleWeb.Api.StatsController.ConversionsTest do
conn
|> get("/api/stats/#{site.domain}/conversions?period=day&filters=#{filters}")
|> json_response(200)
+ |> Map.get("results")
assert resp == []
end
@@ -249,7 +250,7 @@ defmodule PlausibleWeb.Api.StatsController.ConversionsTest do
filters = Jason.encode!(%{browser: "Firefox"})
conn = get(conn, "/api/stats/#{site.domain}/conversions?period=day&filters=#{filters}")
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"name" => "Payment",
"visitors" => 1,
@@ -294,7 +295,7 @@ defmodule PlausibleWeb.Api.StatsController.ConversionsTest do
conn = get(conn, "/api/stats/#{site.domain}/conversions?period=day")
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"name" => "Payment",
"visitors" => 5,
@@ -340,7 +341,7 @@ defmodule PlausibleWeb.Api.StatsController.ConversionsTest do
conn = get(conn, "/api/stats/#{site.domain}/conversions?period=day")
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"name" => "Payment",
"visitors" => 5,
@@ -372,7 +373,7 @@ defmodule PlausibleWeb.Api.StatsController.ConversionsTest do
insert(:goal, %{site: site, event_name: "Payment", currency: :EUR})
conn = get(conn, "/api/stats/#{site.domain}/conversions?period=day")
- response = json_response(conn, 200)
+ response = json_response(conn, 200)["results"]
assert [
%{
@@ -414,7 +415,7 @@ defmodule PlausibleWeb.Api.StatsController.ConversionsTest do
conn = get(conn, "/api/stats/#{site.domain}/conversions?period=day")
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"name" => "Signup",
"visitors" => 1,
@@ -447,6 +448,7 @@ defmodule PlausibleWeb.Api.StatsController.ConversionsTest do
get(conn, path <> query)
|> json_response(200)
+ |> Map.get("results")
end
expected = [
@@ -488,6 +490,7 @@ defmodule PlausibleWeb.Api.StatsController.ConversionsTest do
get(conn, path <> query)
|> json_response(200)
+ |> Map.get("results")
end
expected = [
@@ -539,7 +542,7 @@ defmodule PlausibleWeb.Api.StatsController.ConversionsTest do
"/api/stats/#{site.domain}/conversions?period=day&filters=#{filters}"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"name" => "Signup",
"visitors" => 2,
@@ -573,7 +576,7 @@ defmodule PlausibleWeb.Api.StatsController.ConversionsTest do
"/api/stats/#{site.domain}/conversions?period=day&filters=#{filters}"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"name" => "Visit /blog/**",
"visitors" => 2,
@@ -611,7 +614,7 @@ defmodule PlausibleWeb.Api.StatsController.ConversionsTest do
"/api/stats/#{site.domain}/conversions?period=day&filters=#{filters}"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"name" => "Visit /blog**",
"visitors" => 2,
@@ -649,7 +652,7 @@ defmodule PlausibleWeb.Api.StatsController.ConversionsTest do
"/api/stats/#{site.domain}/conversions?period=day&filters=#{filters}"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"name" => "Signup",
"visitors" => 1,
@@ -713,7 +716,7 @@ defmodule PlausibleWeb.Api.StatsController.ConversionsTest do
"/api/stats/#{site.domain}/conversions?period=day&date=2019-07-01"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"conversion_rate" => 100.0,
"visitors" => 8,
@@ -801,7 +804,291 @@ defmodule PlausibleWeb.Api.StatsController.ConversionsTest do
"events" => 3,
"conversion_rate" => 37.5
}
- ] = json_response(conn, 200)
+ ] = json_response(conn, 200)["results"]
+ end
+
+ test "returns only custom event goals with a custom event goal filter", %{
+ conn: conn,
+ site: site
+ } do
+ insert(:goal, site: site, event_name: "Purchase")
+ insert(:goal, site: site, event_name: "Activation")
+ insert(:goal, site: site, page_path: "/test")
+
+ site_import = insert(:site_import, site: site)
+
+ populate_stats(site, site_import.id, [
+ build(:pageview,
+ timestamp: ~N[2021-01-01 00:00:01],
+ pathname: "/test"
+ ),
+ build(:event,
+ name: "Purchase",
+ timestamp: ~N[2021-01-01 00:00:03]
+ ),
+ build(:event,
+ name: "Purchase",
+ timestamp: ~N[2021-01-01 00:00:03]
+ ),
+ build(:imported_custom_events,
+ name: "Purchase",
+ visitors: 3,
+ events: 5,
+ date: ~D[2021-01-01]
+ ),
+ build(:imported_pages,
+ page: "/test",
+ visitors: 2,
+ pageviews: 2,
+ date: ~D[2021-01-01]
+ ),
+ build(:imported_visitors, visitors: 5, date: ~D[2021-01-01])
+ ])
+
+ filters = Jason.encode!(%{goal: "Purchase"})
+ url_query_params = "?filters=#{filters}&period=day&date=2021-01-01&with_imported=true"
+ conn = get(conn, "/api/stats/#{site.domain}/conversions#{url_query_params}")
+
+ assert [
+ %{
+ "name" => "Purchase",
+ "visitors" => 5,
+ "events" => 7,
+ "conversion_rate" => 62.5
+ }
+ ] = json_response(conn, 200)["results"]
+ end
+
+ test "returns custom event goals with more than one option in goal filter", %{
+ conn: conn,
+ site: site
+ } do
+ insert(:goal, site: site, event_name: "Purchase")
+ insert(:goal, site: site, event_name: "Activation")
+ insert(:goal, site: site, page_path: "/test")
+
+ site_import = insert(:site_import, site: site)
+
+ populate_stats(site, site_import.id, [
+ build(:pageview,
+ timestamp: ~N[2021-01-01 00:00:01],
+ pathname: "/test"
+ ),
+ build(:event,
+ name: "Purchase",
+ timestamp: ~N[2021-01-01 00:00:03]
+ ),
+ build(:event,
+ name: "Purchase",
+ timestamp: ~N[2021-01-01 00:00:03]
+ ),
+ build(:event,
+ name: "Activation",
+ timestamp: ~N[2021-01-01 00:00:03]
+ ),
+ build(:imported_custom_events,
+ name: "Purchase",
+ visitors: 3,
+ events: 5,
+ date: ~D[2021-01-01]
+ ),
+ build(:imported_custom_events,
+ name: "Activation",
+ visitors: 2,
+ events: 4,
+ date: ~D[2021-01-01]
+ ),
+ build(:imported_pages,
+ page: "/test",
+ visitors: 2,
+ pageviews: 2,
+ date: ~D[2021-01-01]
+ ),
+ build(:imported_visitors, visitors: 5, date: ~D[2021-01-01])
+ ])
+
+ filters = Jason.encode!(%{goal: "Purchase|Activation"})
+ url_query_params = "?filters=#{filters}&period=day&date=2021-01-01&with_imported=true"
+ conn = get(conn, "/api/stats/#{site.domain}/conversions#{url_query_params}")
+
+ assert [
+ %{
+ "name" => "Purchase",
+ "visitors" => 5,
+ "events" => 7,
+ "conversion_rate" => 55.6
+ },
+ %{
+ "name" => "Activation",
+ "visitors" => 3,
+ "events" => 5,
+ "conversion_rate" => 33.3
+ }
+ ] = json_response(conn, 200)["results"]
+ end
+
+ test "returns only pageview goals with a pageview goal filter", %{
+ conn: conn,
+ site: site
+ } do
+ insert(:goal, site: site, event_name: "Purchase")
+ insert(:goal, site: site, event_name: "Activation")
+ insert(:goal, site: site, page_path: "/test")
+
+ site_import = insert(:site_import, site: site)
+
+ populate_stats(site, site_import.id, [
+ build(:pageview,
+ timestamp: ~N[2021-01-01 00:00:01],
+ pathname: "/test"
+ ),
+ build(:event,
+ name: "Purchase",
+ timestamp: ~N[2021-01-01 00:00:03]
+ ),
+ build(:event,
+ name: "Purchase",
+ timestamp: ~N[2021-01-01 00:00:03]
+ ),
+ build(:imported_custom_events,
+ name: "Purchase",
+ visitors: 3,
+ events: 5,
+ date: ~D[2021-01-01]
+ ),
+ build(:imported_pages,
+ page: "/test",
+ visitors: 2,
+ pageviews: 2,
+ date: ~D[2021-01-01]
+ ),
+ build(:imported_visitors, visitors: 5, date: ~D[2021-01-01])
+ ])
+
+ filters = Jason.encode!(%{goal: "Visit /test"})
+ url_query_params = "?filters=#{filters}&period=day&date=2021-01-01&with_imported=true"
+ conn = get(conn, "/api/stats/#{site.domain}/conversions#{url_query_params}")
+
+ assert [
+ %{
+ "name" => "Visit /test",
+ "visitors" => 3,
+ "events" => 3,
+ "conversion_rate" => 37.5
+ }
+ ] = json_response(conn, 200)["results"]
+ end
+
+ test "returns pageview goals with more than one option in pageview goal filter", %{
+ conn: conn,
+ site: site
+ } do
+ insert(:goal, site: site, event_name: "Purchase")
+ insert(:goal, site: site, event_name: "Activation")
+ insert(:goal, site: site, page_path: "/test")
+ insert(:goal, site: site, page_path: "/blog")
+
+ site_import = insert(:site_import, site: site)
+
+ populate_stats(site, site_import.id, [
+ build(:pageview,
+ timestamp: ~N[2021-01-01 00:00:01],
+ pathname: "/test"
+ ),
+ build(:pageview,
+ timestamp: ~N[2021-01-01 00:00:01],
+ pathname: "/blog"
+ ),
+ build(:event,
+ name: "Purchase",
+ timestamp: ~N[2021-01-01 00:00:03]
+ ),
+ build(:event,
+ name: "Purchase",
+ timestamp: ~N[2021-01-01 00:00:03]
+ ),
+ build(:imported_custom_events,
+ name: "Purchase",
+ visitors: 3,
+ events: 5,
+ date: ~D[2021-01-01]
+ ),
+ build(:imported_pages,
+ page: "/test",
+ visitors: 2,
+ pageviews: 2,
+ date: ~D[2021-01-01]
+ ),
+ build(:imported_pages,
+ page: "/blog",
+ visitors: 1,
+ pageviews: 1,
+ date: ~D[2021-01-01]
+ ),
+ build(:imported_visitors, visitors: 5, date: ~D[2021-01-01])
+ ])
+
+ filters = Jason.encode!(%{goal: "Visit /test|Visit /blog"})
+ url_query_params = "?filters=#{filters}&period=day&date=2021-01-01&with_imported=true"
+ conn = get(conn, "/api/stats/#{site.domain}/conversions#{url_query_params}")
+
+ assert [
+ %{
+ "name" => "Visit /test",
+ "visitors" => 3,
+ "events" => 3,
+ "conversion_rate" => 33.3
+ },
+ %{
+ "name" => "Visit /blog",
+ "visitors" => 2,
+ "events" => 2,
+ "conversion_rate" => 22.2
+ }
+ ] = json_response(conn, 200)["results"]
+ end
+
+ test "returns pageview goals with a page filter", %{
+ conn: conn,
+ site: site
+ } do
+ insert(:goal, site: site, page_path: "/blog/two")
+ insert(:goal, site: site, page_path: "/blog/thr**")
+ insert(:goal, site: site, page_path: "/blog/*")
+
+ site_import = insert(:site_import, site: site)
+
+ populate_stats(site, site_import.id, [
+ build(:imported_pages, page: "/", visitors: 1, pageviews: 1, date: ~D[2021-01-01]),
+ build(:imported_pages, page: "/blog/one", visitors: 2, pageviews: 2, date: ~D[2021-01-01]),
+ build(:imported_pages, page: "/blog/two", visitors: 3, pageviews: 3, date: ~D[2021-01-01]),
+ build(:imported_pages,
+ page: "/blog/three",
+ visitors: 4,
+ pageviews: 4,
+ date: ~D[2021-01-01]
+ ),
+ build(:imported_visitors, visitors: 10, date: ~D[2021-01-01])
+ ])
+
+ filters = Jason.encode!(%{page: "/blog/one|/blog/two"})
+ q = "?filters=#{filters}&period=day&date=2021-01-01&with_imported=true"
+ conn = get(conn, "/api/stats/#{site.domain}/conversions#{q}")
+
+ assert [
+ %{
+ "name" => "Visit /blog/*",
+ "visitors" => 5,
+ "events" => 5,
+ "conversion_rate" => 100.0
+ },
+ %{
+ "name" => "Visit /blog/two",
+ "visitors" => 3,
+ "events" => 3,
+ "conversion_rate" => 60.0
+ }
+ ] = json_response(conn, 200)["results"]
end
test "calculates conversion_rate for goals with glob pattern with imported data", %{
@@ -832,7 +1119,7 @@ defmodule PlausibleWeb.Api.StatsController.ConversionsTest do
"/api/stats/#{site.domain}/conversions?period=day"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"name" => "Visit /blog**",
"visitors" => 2,
diff --git a/test/plausible_web/controllers/api/stats_controller/countries_test.exs b/test/plausible_web/controllers/api/stats_controller/countries_test.exs
index de616c28d1c3..587548782087 100644
--- a/test/plausible_web/controllers/api/stats_controller/countries_test.exs
+++ b/test/plausible_web/controllers/api/stats_controller/countries_test.exs
@@ -16,7 +16,7 @@ defmodule PlausibleWeb.Api.StatsController.CountriesTest do
conn = get(conn, "/api/stats/#{site.domain}/countries?period=day")
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"code" => "EE",
"alpha_3" => "EST",
@@ -37,7 +37,7 @@ defmodule PlausibleWeb.Api.StatsController.CountriesTest do
conn = get(conn, "/api/stats/#{site.domain}/countries?period=day&with_imported=true")
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"code" => "EE",
"alpha_3" => "EST",
@@ -65,7 +65,7 @@ defmodule PlausibleWeb.Api.StatsController.CountriesTest do
conn = get(conn, "/api/stats/#{site.domain}/countries?period=day&with_imported=true")
- assert json_response(conn, 200) == []
+ assert json_response(conn, 200)["results"] == []
end
test "calculates conversion_rate when filtering for goal", %{conn: conn, site: site} do
@@ -90,7 +90,7 @@ defmodule PlausibleWeb.Api.StatsController.CountriesTest do
conn = get(conn, "/api/stats/#{site.domain}/countries?period=day&filters=#{filters}")
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"code" => "EE",
"alpha_3" => "EST",
@@ -140,7 +140,7 @@ defmodule PlausibleWeb.Api.StatsController.CountriesTest do
filters = Jason.encode!(%{props: %{"author" => "John Doe"}})
conn = get(conn, "/api/stats/#{site.domain}/countries?period=day&filters=#{filters}")
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"code" => "EE",
"alpha_3" => "EST",
@@ -182,7 +182,7 @@ defmodule PlausibleWeb.Api.StatsController.CountriesTest do
filters = Jason.encode!(%{props: %{"author" => "!John Doe"}})
conn = get(conn, "/api/stats/#{site.domain}/countries?period=day&filters=#{filters}")
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"code" => "GB",
"alpha_3" => "GBR",
@@ -217,7 +217,7 @@ defmodule PlausibleWeb.Api.StatsController.CountriesTest do
filters = Jason.encode!(%{props: %{"author" => "(none)"}})
conn = get(conn, "/api/stats/#{site.domain}/countries?period=day&filters=#{filters}")
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"code" => "GB",
"alpha_3" => "GBR",
@@ -257,7 +257,7 @@ defmodule PlausibleWeb.Api.StatsController.CountriesTest do
filters = Jason.encode!(%{props: %{"author" => "!(none)"}})
conn = get(conn, "/api/stats/#{site.domain}/countries?period=day&filters=#{filters}")
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"code" => "EE",
"alpha_3" => "EST",
@@ -279,7 +279,7 @@ defmodule PlausibleWeb.Api.StatsController.CountriesTest do
filters = Jason.encode!(%{country: "GB"})
conn = get(conn, "/api/stats/#{site.domain}/countries?period=day&filters=#{filters}")
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"code" => "GB",
"alpha_3" => "GBR",
diff --git a/test/plausible_web/controllers/api/stats_controller/custom_prop_breakdown_test.exs b/test/plausible_web/controllers/api/stats_controller/custom_prop_breakdown_test.exs
index 9c1898a02c0f..d916347897ab 100644
--- a/test/plausible_web/controllers/api/stats_controller/custom_prop_breakdown_test.exs
+++ b/test/plausible_web/controllers/api/stats_controller/custom_prop_breakdown_test.exs
@@ -21,7 +21,7 @@ defmodule PlausibleWeb.Api.StatsController.CustomPropBreakdownTest do
"/api/stats/#{site.domain}/custom-prop-values/#{prop_key}?period=day"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"visitors" => 2,
"name" => "K2sna Kalle",
@@ -57,7 +57,7 @@ defmodule PlausibleWeb.Api.StatsController.CustomPropBreakdownTest do
"/api/stats/#{site.domain}/custom-prop-values/#{prop_key}?period=day&with_imported=true"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"visitors" => 1,
"name" => "K2sna Kalle",
@@ -65,6 +65,8 @@ defmodule PlausibleWeb.Api.StatsController.CustomPropBreakdownTest do
"percentage" => 100.0
}
]
+
+ refute json_response(conn, 200)["warning"]
end
test "returns (none) values in the breakdown", %{conn: conn, site: site} do
@@ -82,7 +84,7 @@ defmodule PlausibleWeb.Api.StatsController.CustomPropBreakdownTest do
"/api/stats/#{site.domain}/custom-prop-values/#{prop_key}?period=day"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"visitors" => 2,
"name" => "K2sna Kalle",
@@ -122,7 +124,7 @@ defmodule PlausibleWeb.Api.StatsController.CustomPropBreakdownTest do
"/api/stats/#{site.domain}/custom-prop-values/#{prop_key}?period=day&limit=2&page=2"
)
- assert json_response(conn1, 200) == [
+ assert json_response(conn1, 200)["results"] == [
%{
"visitors" => 3,
"name" => "Tiit",
@@ -137,7 +139,7 @@ defmodule PlausibleWeb.Api.StatsController.CustomPropBreakdownTest do
}
]
- assert json_response(conn2, 200) == [
+ assert json_response(conn2, 200)["results"] == [
%{
"visitors" => 1,
"name" => "(none)",
@@ -171,7 +173,7 @@ defmodule PlausibleWeb.Api.StatsController.CustomPropBreakdownTest do
"/api/stats/#{site.domain}/custom-prop-values/#{prop_key}?period=day&filters=#{filters}"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"visitors" => 2,
"name" => "B",
@@ -207,7 +209,7 @@ defmodule PlausibleWeb.Api.StatsController.CustomPropBreakdownTest do
"/api/stats/#{site.domain}/custom-prop-values/#{prop_key}?period=day&filters=#{filters}"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"visitors" => 2,
"name" => "(none)",
@@ -250,7 +252,7 @@ defmodule PlausibleWeb.Api.StatsController.CustomPropBreakdownTest do
"/api/stats/#{site.domain}/custom-prop-values/cost?period=day&filters=#{filters}"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"name" => "0",
"visitors" => 1,
@@ -287,7 +289,7 @@ defmodule PlausibleWeb.Api.StatsController.CustomPropBreakdownTest do
"/api/stats/#{site.domain}/custom-prop-values/cost?period=day&filters=#{filters}"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"name" => "(none)",
"visitors" => 1,
@@ -334,7 +336,7 @@ defmodule PlausibleWeb.Api.StatsController.CustomPropBreakdownTest do
"/api/stats/#{site.domain}/custom-prop-values/cost?period=day&filters=#{filters}"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"name" => "20",
"visitors" => 2,
@@ -377,7 +379,7 @@ defmodule PlausibleWeb.Api.StatsController.CustomPropBreakdownTest do
"/api/stats/#{site.domain}/custom-prop-values/cost?period=day&filters=#{filters}"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"name" => "0",
"visitors" => 1,
@@ -424,7 +426,7 @@ defmodule PlausibleWeb.Api.StatsController.CustomPropBreakdownTest do
"/api/stats/#{site.domain}/custom-prop-values/cost?period=day&filters=#{filters}"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"name" => "1",
"visitors" => 2,
@@ -475,7 +477,7 @@ defmodule PlausibleWeb.Api.StatsController.CustomPropBreakdownTest do
"/api/stats/#{site.domain}/custom-prop-values/cost?period=day&filters=#{filters}"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"name" => "1",
"visitors" => 2,
@@ -533,7 +535,7 @@ defmodule PlausibleWeb.Api.StatsController.CustomPropBreakdownTest do
"/api/stats/#{site.domain}/custom-prop-values/cost?period=day&filters=#{filters}"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"name" => "20",
"visitors" => 2,
@@ -584,7 +586,7 @@ defmodule PlausibleWeb.Api.StatsController.CustomPropBreakdownTest do
"/api/stats/#{site.domain}/custom-prop-values/cost?period=day&filters=#{filters}"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"name" => "20",
"visitors" => 2,
@@ -611,7 +613,7 @@ defmodule PlausibleWeb.Api.StatsController.CustomPropBreakdownTest do
"/api/stats/#{site.domain}/custom-prop-values/variant?period=day&filters=#{filters}"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"visitors" => 2,
"name" => "A",
@@ -645,7 +647,7 @@ defmodule PlausibleWeb.Api.StatsController.CustomPropBreakdownTest do
"/api/stats/#{site.domain}/custom-prop-values/#{prop_key}?period=day&filters=#{filters}"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"visitors" => 1,
"name" => "B",
@@ -690,7 +692,7 @@ defmodule PlausibleWeb.Api.StatsController.CustomPropBreakdownTest do
"/api/stats/#{site.domain}/custom-prop-values/#{prop_key}?period=day&filters=#{filters}"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"name" => "A",
"visitors" => 1,
@@ -735,7 +737,7 @@ defmodule PlausibleWeb.Api.StatsController.CustomPropBreakdownTest do
"/api/stats/#{site.domain}/custom-prop-values/#{prop_key}?period=day&filters=#{filters}"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"name" => "A",
"visitors" => 1,
@@ -783,7 +785,7 @@ defmodule PlausibleWeb.Api.StatsController.CustomPropBreakdownTest do
"/api/stats/#{site.domain}/custom-prop-values/#{prop_key}?period=day&filters=#{filters}"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"visitors" => 2,
"name" => "true",
@@ -844,7 +846,7 @@ defmodule PlausibleWeb.Api.StatsController.CustomPropBreakdownTest do
"/api/stats/#{site.domain}/custom-prop-values/#{prop_key}?period=day&filters=#{filters}"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"visitors" => 2,
"name" => "true",
@@ -889,6 +891,7 @@ defmodule PlausibleWeb.Api.StatsController.CustomPropBreakdownTest do
returned_metrics =
json_response(conn, 200)
+ |> Map.get("results")
|> List.first()
|> Map.keys()
@@ -916,7 +919,7 @@ defmodule PlausibleWeb.Api.StatsController.CustomPropBreakdownTest do
"/api/stats/#{site.domain}/custom-prop-values/#{prop_key}?period=day&filters=#{filters}"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"visitors" => 1,
"name" => "Sipsik",
@@ -946,7 +949,7 @@ defmodule PlausibleWeb.Api.StatsController.CustomPropBreakdownTest do
"/api/stats/#{site.domain}/custom-prop-values/#{prop_key}?period=day&filters=#{filters}"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"visitors" => 1,
"name" => "Sipsik",
@@ -973,7 +976,7 @@ defmodule PlausibleWeb.Api.StatsController.CustomPropBreakdownTest do
"/api/stats/#{site.domain}/custom-prop-values/#{prop_key}?period=day&filters=#{filters}"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"visitors" => 1,
"name" => "Sipsik",
@@ -1004,7 +1007,7 @@ defmodule PlausibleWeb.Api.StatsController.CustomPropBreakdownTest do
"/api/stats/#{site.domain}/custom-prop-values/#{prop_key}?period=day&filters=#{filters}"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"visitors" => 2,
"name" => "K2sna Kalle",
@@ -1040,7 +1043,7 @@ defmodule PlausibleWeb.Api.StatsController.CustomPropBreakdownTest do
"/api/stats/#{site.domain}/custom-prop-values/key?period=day&filters=#{filters}"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"visitors" => 2,
"name" => "bar",
@@ -1078,7 +1081,7 @@ defmodule PlausibleWeb.Api.StatsController.CustomPropBreakdownTest do
"/api/stats/#{site.domain}/custom-prop-values/key?period=day&filters=#{filters}"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"visitors" => 1,
"name" => "bar",
@@ -1116,11 +1119,13 @@ defmodule PlausibleWeb.Api.StatsController.CustomPropBreakdownTest do
conn
|> get("/api/stats/#{site.domain}/custom-prop-values/url?period=day")
|> json_response(200)
+ |> Map.get("results")
[%{"visitors" => 1, "name" => "two"}] =
conn
|> get("/api/stats/#{site.domain}/custom-prop-values/path?period=day")
|> json_response(200)
+ |> Map.get("results")
end
test "returns 402 'upgrade required' for any other prop key", %{conn: conn, site: site} do
@@ -1136,7 +1141,7 @@ defmodule PlausibleWeb.Api.StatsController.CustomPropBreakdownTest do
describe "with imported data" do
setup [:create_user, :log_in, :create_new_site]
- for goal_name <- ["Outbound Link: Click", "File Download"] do
+ for goal_name <- ["Outbound Link: Click", "File Download", "Cloaked Link: Click"] do
test "returns url breakdown for #{goal_name} goal", %{conn: conn, site: site} do
insert(:goal, event_name: unquote(goal_name), site: site)
site_import = insert(:site_import, site: site)
@@ -1175,7 +1180,7 @@ defmodule PlausibleWeb.Api.StatsController.CustomPropBreakdownTest do
"/api/stats/#{site.domain}/custom-prop-values/url?period=day&with_imported=true&filters=#{filters}"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"visitors" => 5,
"name" => "https://two.com",
@@ -1191,5 +1196,58 @@ defmodule PlausibleWeb.Api.StatsController.CustomPropBreakdownTest do
]
end
end
+
+ for goal_name <- ["Outbound Link: Click", "File Download", "Cloaked Link: Click"] do
+ test "returns url breakdown for #{goal_name} goal with a url filter", %{
+ conn: conn,
+ site: site
+ } do
+ insert(:goal, event_name: unquote(goal_name), site: site)
+ site_import = insert(:site_import, site: site)
+
+ populate_stats(site, site_import.id, [
+ build(:event,
+ name: unquote(goal_name),
+ "meta.key": ["url"],
+ "meta.value": ["https://one.com"]
+ ),
+ build(:imported_custom_events,
+ name: unquote(goal_name),
+ visitors: 2,
+ events: 5,
+ link_url: "https://one.com"
+ ),
+ build(:imported_custom_events,
+ name: unquote(goal_name),
+ visitors: 5,
+ events: 10,
+ link_url: "https://two.com"
+ ),
+ build(:imported_custom_events,
+ name: "view_search_results",
+ visitors: 100,
+ events: 200
+ ),
+ build(:imported_visitors, visitors: 9)
+ ])
+
+ filters = Jason.encode!(%{goal: unquote(goal_name), props: %{url: "https://two.com"}})
+
+ conn =
+ get(
+ conn,
+ "/api/stats/#{site.domain}/custom-prop-values/url?period=day&with_imported=true&filters=#{filters}"
+ )
+
+ assert json_response(conn, 200)["results"] == [
+ %{
+ "visitors" => 5,
+ "name" => "https://two.com",
+ "events" => 10,
+ "conversion_rate" => 50.0
+ }
+ ]
+ end
+ end
end
end
diff --git a/test/plausible_web/controllers/api/stats_controller/imported_test.exs b/test/plausible_web/controllers/api/stats_controller/imported_test.exs
index 42eafec8694c..370eb0ecf6b6 100644
--- a/test/plausible_web/controllers/api/stats_controller/imported_test.exs
+++ b/test/plausible_web/controllers/api/stats_controller/imported_test.exs
@@ -261,13 +261,16 @@ defmodule PlausibleWeb.Api.StatsController.ImportedTest do
"imported_sources"
)
- conn =
- get(
- conn,
+ results =
+ conn
+ |> get(
"/api/stats/#{site.domain}/sources?period=month&date=2021-01-01&with_imported=true"
)
+ |> json_response(200)
+ |> Map.get("results")
+ |> Enum.sort()
- assert conn |> json_response(200) |> Enum.sort() == [
+ assert results == [
%{"name" => "A Nice Newsletter", "visitors" => 1},
%{"name" => "Direct / None", "visitors" => 1},
%{"name" => "DuckDuckGo", "visitors" => 2},
@@ -338,7 +341,7 @@ defmodule PlausibleWeb.Api.StatsController.ImportedTest do
"/api/stats/#{site.domain}/utm_mediums?period=day&date=2021-01-01&with_imported=true"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"bounce_rate" => 100.0,
"name" => "social",
@@ -420,7 +423,7 @@ defmodule PlausibleWeb.Api.StatsController.ImportedTest do
"/api/stats/#{site.domain}/utm_campaigns?period=day&date=2021-01-01&with_imported=true"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"name" => "august",
"visitors" => 2,
@@ -509,7 +512,7 @@ defmodule PlausibleWeb.Api.StatsController.ImportedTest do
"/api/stats/#{site.domain}/utm_terms?period=day&date=2021-01-01&with_imported=true"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"name" => "Sweden",
"visitors" => 3,
@@ -597,7 +600,7 @@ defmodule PlausibleWeb.Api.StatsController.ImportedTest do
"/api/stats/#{site.domain}/utm_contents?period=day&date=2021-01-01&with_imported=true"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"name" => "blog",
"visitors" => 2,
@@ -703,7 +706,7 @@ defmodule PlausibleWeb.Api.StatsController.ImportedTest do
"/api/stats/#{site.domain}/pages?period=day&date=2021-01-01&detailed=true&with_imported=true"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"bounce_rate" => nil,
"time_on_page" => 60,
@@ -786,7 +789,7 @@ defmodule PlausibleWeb.Api.StatsController.ImportedTest do
"/api/stats/#{site.domain}/exit-pages?period=day&date=2021-01-01&with_imported=true"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"name" => "/page2",
"visitors" => 3,
@@ -859,7 +862,7 @@ defmodule PlausibleWeb.Api.StatsController.ImportedTest do
"/api/stats/#{site.domain}/cities?period=day&date=2021-01-01&with_imported=true"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{"code" => 588_335, "name" => "Tartu", "visitors" => 1, "country_flag" => "🇪🇪"},
%{
"code" => 2_650_225,
@@ -933,7 +936,7 @@ defmodule PlausibleWeb.Api.StatsController.ImportedTest do
"/api/stats/#{site.domain}/countries?period=day&date=2021-01-01&with_imported=true"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"code" => "EE",
"alpha_3" => "EST",
@@ -997,7 +1000,7 @@ defmodule PlausibleWeb.Api.StatsController.ImportedTest do
"/api/stats/#{site.domain}/screen-sizes?period=day&date=2021-01-01&with_imported=true"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{"name" => "Desktop", "visitors" => 2, "percentage" => 40},
%{"name" => "Laptop", "visitors" => 2, "percentage" => 40},
%{"name" => "Mobile", "visitors" => 1, "percentage" => 20}
@@ -1050,7 +1053,7 @@ defmodule PlausibleWeb.Api.StatsController.ImportedTest do
"/api/stats/#{site.domain}/browsers?period=day&date=2021-01-01&with_imported=true"
)
- assert stats = json_response(conn, 200)
+ assert stats = json_response(conn, 200)["results"]
assert length(stats) == 3
assert %{"name" => "Firefox", "visitors" => 2, "percentage" => 50.0} in stats
assert %{"name" => "Mobile App", "visitors" => 1, "percentage" => 25.0} in stats
@@ -1104,7 +1107,7 @@ defmodule PlausibleWeb.Api.StatsController.ImportedTest do
"/api/stats/#{site.domain}/operating-systems?period=day&date=2021-01-01&with_imported=true"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{"name" => "Mac", "visitors" => 3, "percentage" => 60},
%{"name" => "GNU/Linux", "visitors" => 2, "percentage" => 40}
]
diff --git a/test/plausible_web/controllers/api/stats_controller/main_graph_test.exs b/test/plausible_web/controllers/api/stats_controller/main_graph_test.exs
index b88fae0fa582..773bc8cbf534 100644
--- a/test/plausible_web/controllers/api/stats_controller/main_graph_test.exs
+++ b/test/plausible_web/controllers/api/stats_controller/main_graph_test.exs
@@ -53,7 +53,7 @@ defmodule PlausibleWeb.Api.StatsController.MainGraphTest do
)
zeroes = List.duplicate(0, 30)
- assert %{"plot" => ^zeroes, "with_imported" => false} = json_response(conn, 200)
+ assert %{"plot" => ^zeroes, "includes_imported" => false} = json_response(conn, 200)
end
test "displays visitors for a day with imported data", %{conn: conn, site: site} do
@@ -70,7 +70,7 @@ defmodule PlausibleWeb.Api.StatsController.MainGraphTest do
"/api/stats/#{site.domain}/main-graph?period=day&date=2021-01-01&with_imported=true"
)
- assert %{"plot" => plot, "imports_exist" => true, "with_imported" => true} =
+ assert %{"plot" => plot, "imports_exist" => true, "includes_imported" => true} =
json_response(conn, 200)
assert plot == [2] ++ List.duplicate(0, 23)
@@ -137,7 +137,7 @@ defmodule PlausibleWeb.Api.StatsController.MainGraphTest do
"/api/stats/#{site.domain}/main-graph?period=month&date=2021-01-01&with_imported=true"
)
- assert %{"plot" => plot, "imports_exist" => true, "with_imported" => true} =
+ assert %{"plot" => plot, "imports_exist" => true, "includes_imported" => true} =
json_response(conn, 200)
assert Enum.count(plot) == 31
@@ -158,7 +158,7 @@ defmodule PlausibleWeb.Api.StatsController.MainGraphTest do
"/api/stats/#{site.domain}/main-graph?period=month&date=2021-01-01&with_imported=true"
)
- assert %{"plot" => plot, "imports_exist" => true, "with_imported" => true} =
+ assert %{"plot" => plot, "imports_exist" => true, "includes_imported" => true} =
json_response(conn, 200)
assert Enum.count(plot) == 31
@@ -1157,7 +1157,7 @@ defmodule PlausibleWeb.Api.StatsController.MainGraphTest do
"plot" => plot,
"comparison_plot" => comparison_plot,
"imports_exist" => true,
- "with_imported" => true
+ "includes_imported" => true
} = json_response(conn, 200)
assert 4 == Enum.sum(plot)
@@ -1203,7 +1203,7 @@ defmodule PlausibleWeb.Api.StatsController.MainGraphTest do
"plot" => plot,
"comparison_plot" => comparison_plot,
"imports_exist" => true,
- "with_imported" => false
+ "includes_imported" => false
} = json_response(conn, 200)
assert 4 == Enum.sum(plot)
@@ -1233,7 +1233,7 @@ defmodule PlausibleWeb.Api.StatsController.MainGraphTest do
"plot" => this_week_plot,
"comparison_plot" => last_week_plot,
"imports_exist" => true,
- "with_imported" => false
+ "includes_imported" => false
} = json_response(conn, 200)
assert this_week_plot == [50.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
diff --git a/test/plausible_web/controllers/api/stats_controller/operating_systems_test.exs b/test/plausible_web/controllers/api/stats_controller/operating_systems_test.exs
index d383fca24b0f..ea9f8990a282 100644
--- a/test/plausible_web/controllers/api/stats_controller/operating_systems_test.exs
+++ b/test/plausible_web/controllers/api/stats_controller/operating_systems_test.exs
@@ -13,7 +13,7 @@ defmodule PlausibleWeb.Api.StatsController.OperatingSystemsTest do
conn = get(conn, "/api/stats/#{site.domain}/operating-systems?period=day")
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{"name" => "Mac", "visitors" => 2, "percentage" => 66.7},
%{"name" => "Android", "visitors" => 1, "percentage" => 33.3}
]
@@ -31,7 +31,7 @@ defmodule PlausibleWeb.Api.StatsController.OperatingSystemsTest do
conn = get(conn, "/api/stats/#{site.domain}/operating-systems?period=day")
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{"name" => "(not set)", "visitors" => 1, "percentage" => 50},
%{"name" => "Linux", "visitors" => 1, "percentage" => 50}
]
@@ -41,7 +41,7 @@ defmodule PlausibleWeb.Api.StatsController.OperatingSystemsTest do
conn =
get(conn, "/api/stats/#{site.domain}/operating-systems?period=day&filters=#{filters}")
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{"name" => "(not set)", "visitors" => 1, "percentage" => 100}
]
end
@@ -57,7 +57,7 @@ defmodule PlausibleWeb.Api.StatsController.OperatingSystemsTest do
conn =
get(conn, "/api/stats/#{site.domain}/operating-systems?period=day&with_imported=true")
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{"name" => "(not set)", "visitors" => 2, "percentage" => 100.0}
]
end
@@ -74,7 +74,7 @@ defmodule PlausibleWeb.Api.StatsController.OperatingSystemsTest do
conn =
get(conn, "/api/stats/#{site.domain}/operating-systems?period=day&filters=#{filters}")
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"name" => "Mac",
"total_visitors" => 2,
@@ -114,7 +114,7 @@ defmodule PlausibleWeb.Api.StatsController.OperatingSystemsTest do
conn =
get(conn, "/api/stats/#{site.domain}/operating-systems?period=day&filters=#{filters}")
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{"name" => "Mac", "visitors" => 1, "percentage" => 100}
]
end
@@ -151,7 +151,7 @@ defmodule PlausibleWeb.Api.StatsController.OperatingSystemsTest do
conn =
get(conn, "/api/stats/#{site.domain}/operating-systems?period=day&filters=#{filters}")
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{"name" => "Android", "visitors" => 1, "percentage" => 50},
%{"name" => "Mac", "visitors" => 1, "percentage" => 50}
]
@@ -172,7 +172,7 @@ defmodule PlausibleWeb.Api.StatsController.OperatingSystemsTest do
conn = get(conn, "/api/stats/#{site.domain}/operating-systems?period=day")
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{"name" => "Mac", "visitors" => 2, "percentage" => 66.7},
%{"name" => "Android", "visitors" => 1, "percentage" => 33.3}
]
@@ -180,7 +180,7 @@ defmodule PlausibleWeb.Api.StatsController.OperatingSystemsTest do
conn =
get(conn, "/api/stats/#{site.domain}/operating-systems?period=day&with_imported=true")
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{"name" => "Mac", "visitors" => 3, "percentage" => 60},
%{"name" => "Android", "visitors" => 2, "percentage" => 40}
]
@@ -199,7 +199,7 @@ defmodule PlausibleWeb.Api.StatsController.OperatingSystemsTest do
conn =
get(conn, "/api/stats/#{site.domain}/operating-systems?period=day&filters=#{filters}")
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"name" => "Mac",
"total_visitors" => 2,
@@ -241,7 +241,7 @@ defmodule PlausibleWeb.Api.StatsController.OperatingSystemsTest do
"/api/stats/#{site.domain}/operating-system-versions?period=day&filters=#{filters}"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{"name" => "10.16", "visitors" => 2, "percentage" => 66.7, "os" => "Mac"},
%{"name" => "10.15", "visitors" => 1, "percentage" => 33.3, "os" => "Mac"}
]
@@ -281,7 +281,7 @@ defmodule PlausibleWeb.Api.StatsController.OperatingSystemsTest do
"/api/stats/#{site.domain}/operating-system-versions?period=day&date=2021-01-01&with_imported=true"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"os" => "(not set)",
"name" => "(not set)",
diff --git a/test/plausible_web/controllers/api/stats_controller/pages_test.exs b/test/plausible_web/controllers/api/stats_controller/pages_test.exs
index ea9ccfe6ae3a..4af5c5cd0bd5 100644
--- a/test/plausible_web/controllers/api/stats_controller/pages_test.exs
+++ b/test/plausible_web/controllers/api/stats_controller/pages_test.exs
@@ -18,7 +18,7 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do
conn = get(conn, "/api/stats/#{site.domain}/pages?period=day")
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{"visitors" => 3, "name" => "/"},
%{"visitors" => 2, "name" => "/register"},
%{"visitors" => 1, "name" => "/contact"}
@@ -40,7 +40,7 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do
filters = Jason.encode!(%{"hostname" => "*.example.com"})
conn = get(conn1, "/api/stats/#{site.domain}/pages?period=day&filters=#{filters}")
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{"visitors" => 3, "name" => "/"},
%{"visitors" => 2, "name" => "/register"},
%{"visitors" => 1, "name" => "/contact"},
@@ -50,7 +50,7 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do
filters = Jason.encode!(%{"hostname" => "d.example.com"})
conn = get(conn1, "/api/stats/#{site.domain}/pages?period=day&filters=#{filters}")
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{"visitors" => 2, "name" => "/register"},
%{"visitors" => 1, "name" => "/"}
]
@@ -74,7 +74,7 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do
filters = Jason.encode!(%{props: %{"author" => "John Doe"}})
conn = get(conn, "/api/stats/#{site.domain}/pages?period=day&filters=#{filters}")
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{"visitors" => 1, "name" => "/blog/john-1"}
]
end
@@ -100,7 +100,7 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do
filters = Jason.encode!(%{props: %{"author" => "!John Doe"}})
conn = get(conn, "/api/stats/#{site.domain}/pages?period=day&filters=#{filters}")
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{"visitors" => 1, "name" => "/"},
%{"visitors" => 1, "name" => "/blog/other-post"}
]
@@ -137,7 +137,7 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do
filters = Jason.encode!(%{props: %{"prop" => "~bar"}})
conn = get(conn, "/api/stats/#{site.domain}/pages?period=day&filters=#{filters}")
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{"visitors" => 1, "name" => "/1"},
%{"visitors" => 1, "name" => "/2"}
]
@@ -179,7 +179,7 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do
filters = Jason.encode!(%{props: %{"prop" => "~bar|nea"}})
conn = get(conn, "/api/stats/#{site.domain}/pages?period=day&filters=#{filters}")
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{"visitors" => 1, "name" => "/1"},
%{"visitors" => 1, "name" => "/2"},
%{"visitors" => 1, "name" => "/6"}
@@ -217,7 +217,7 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do
filters = Jason.encode!(%{props: %{"prop" => "bar", "number" => "1"}})
conn = get(conn, "/api/stats/#{site.domain}/pages?period=day&filters=#{filters}")
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{"visitors" => 1, "name" => "/1"}
]
end
@@ -266,7 +266,7 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do
"/api/stats/#{site.domain}/pages?period=day&date=2021-01-01&filters=#{filters}&detailed=true"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"name" => "/blog/john-2",
"visitors" => 2,
@@ -328,7 +328,7 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do
"/api/stats/#{site.domain}/pages?period=day&date=2021-01-01&filters=#{filters}&detailed=true"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"name" => "/blog",
"visitors" => 2,
@@ -380,7 +380,7 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do
"/api/stats/#{site.domain}/pages?period=day&date=2021-01-01&filters=#{filters}&detailed=true"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"name" => "/blog",
"visitors" => 2,
@@ -436,7 +436,7 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do
"/api/stats/#{site.domain}/pages?period=day&date=2021-01-01&filters=#{filters}&detailed=true"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"name" => "/blog/other-post",
"visitors" => 2,
@@ -494,7 +494,7 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do
conn =
get(conn, "/api/stats/#{site.domain}/pages?period=day&date=2021-01-01&filters=#{filters}")
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"name" => "/firefox",
"visitors" => 2
@@ -534,7 +534,7 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do
conn =
get(conn, "/api/stats/#{site.domain}/pages?period=day&date=2021-01-01&filters=#{filters}")
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"name" => "/safari",
"visitors" => 1
@@ -578,7 +578,7 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do
"/api/stats/#{site.domain}/pages?period=day&date=2021-01-01&filters=#{filters}&detailed=true"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"name" => "/",
"visitors" => 2,
@@ -625,7 +625,7 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do
"/api/stats/#{site.domain}/pages?period=day&date=2021-01-01&filters=#{filters}&detailed=true"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"name" => "/",
"visitors" => 2,
@@ -679,7 +679,7 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do
"/api/stats/#{site.domain}/pages?period=day&date=2021-01-01&filters=#{filters}&detailed=true"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"name" => "/",
"visitors" => 2,
@@ -725,7 +725,7 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do
"/api/stats/#{site.domain}/pages?period=day&date=2021-01-01&filters=#{filters}&detailed=true"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"name" => "/articles/post-1",
"visitors" => 2,
@@ -777,7 +777,7 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do
"/api/stats/#{site.domain}/pages?period=day&date=2021-01-01&filters=#{filters}&detailed=true"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"name" => "/blog/(/post-1",
"visitors" => 1,
@@ -830,7 +830,7 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do
"/api/stats/#{site.domain}/pages?period=day&date=2021-01-01&filters=#{filters}&detailed=true"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"name" => "/",
"visitors" => 2,
@@ -862,7 +862,7 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do
conn = get(conn, "/api/stats/#{site.domain}/pages?period=day")
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{"visitors" => 3, "name" => "/"},
%{"visitors" => 2, "name" => "/register"},
%{"visitors" => 1, "name" => "/contact"}
@@ -870,13 +870,43 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do
conn = get(conn, "/api/stats/#{site.domain}/pages?period=day&with_imported=true")
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{"visitors" => 4, "name" => "/"},
%{"visitors" => 3, "name" => "/register"},
%{"visitors" => 1, "name" => "/contact"}
]
end
+ test "returns imported pages with a pageview goal filter", %{conn: conn, site: site} do
+ insert(:goal, site: site, page_path: "/blog**")
+
+ populate_stats(site, [
+ build(:imported_pages, page: "/blog"),
+ build(:imported_pages, page: "/not-this"),
+ build(:imported_pages, page: "/blog/post-1", visitors: 2),
+ build(:imported_visitors, visitors: 4)
+ ])
+
+ filters = Jason.encode!(%{goal: "Visit /blog**"})
+ q = "?period=day&filters=#{filters}&with_imported=true"
+ conn = get(conn, "/api/stats/#{site.domain}/pages#{q}")
+
+ assert json_response(conn, 200)["results"] == [
+ %{
+ "visitors" => 2,
+ "name" => "/blog/post-1",
+ "conversion_rate" => 100.0,
+ "total_visitors" => 2
+ },
+ %{
+ "visitors" => 1,
+ "name" => "/blog",
+ "conversion_rate" => 100.0,
+ "total_visitors" => 1
+ }
+ ]
+ end
+
test "calculates bounce rate and time on page for pages", %{conn: conn, site: site} do
populate_stats(site, [
build(:pageview,
@@ -901,7 +931,7 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do
"/api/stats/#{site.domain}/pages?period=day&date=2021-01-01&detailed=true"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"bounce_rate" => 50.0,
"time_on_page" => 900.0,
@@ -948,7 +978,7 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do
"/api/stats/#{site.domain}/pages?period=day&date=2021-01-01&detailed=true&filters=#{filters}"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"bounce_rate" => 50,
"name" => "/about",
@@ -1027,7 +1057,7 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do
"/api/stats/#{site.domain}/pages?period=day&date=2021-01-01&detailed=true&filters=#{filters}"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"bounce_rate" => 50,
"name" => "/about-blog",
@@ -1055,6 +1085,7 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do
conn
|> get("/api/stats/#{site.domain}/pages?period=day&date=2021-01-01&detailed=true")
|> json_response(200)
+ |> Map.get("results")
end
test "ignores page refresh when calculating time on page", %{conn: conn, site: site} do
@@ -1072,6 +1103,7 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do
conn
|> get("/api/stats/#{site.domain}/pages?period=day&date=2021-01-01&detailed=true")
|> json_response(200)
+ |> Map.get("results")
end
test "calculates time on page per unique transition within session", %{conn: conn, site: site} do
@@ -1105,6 +1137,7 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do
conn
|> get("/api/stats/#{site.domain}/pages?period=day&date=2021-01-01&detailed=true")
|> json_response(200)
+ |> Map.get("results")
end
test "calculates bounce rate and time on page for pages with imported data", %{
@@ -1150,7 +1183,7 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do
"/api/stats/#{site.domain}/pages?period=day&date=2021-01-01&detailed=true&with_imported=true"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"bounce_rate" => 40.0,
"time_on_page" => 800.0,
@@ -1177,7 +1210,7 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do
conn = get(conn, "/api/stats/#{site.domain}/pages?period=realtime")
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{"visitors" => 2, "name" => "/page1"},
%{"visitors" => 1, "name" => "/page2"}
]
@@ -1195,10 +1228,160 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do
conn = get(conn, "/api/stats/#{site.domain}/pages?period=day&filters=#{filters}")
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{"total_visitors" => 3, "visitors" => 1, "name" => "/", "conversion_rate" => 33.3}
]
end
+
+ test "filter by :is page with imported data", %{conn: conn, site: site} do
+ site_import = insert(:site_import, site: site)
+
+ populate_stats(site, site_import.id, [
+ build(:pageview, user_id: 1, pathname: "/", timestamp: ~N[2021-01-01 12:00:00]),
+ build(:pageview, user_id: 1, pathname: "/ignored", timestamp: ~N[2021-01-01 12:01:00]),
+ build(:imported_entry_pages,
+ entry_page: "/",
+ visitors: 1,
+ bounces: 1,
+ date: ~D[2021-01-01]
+ ),
+ build(:imported_pages,
+ page: "/",
+ visitors: 3,
+ pageviews: 3,
+ time_on_page: 300,
+ date: ~D[2021-01-01]
+ ),
+ build(:imported_pages, page: "/ignored", visitors: 10, date: ~D[2021-01-01])
+ ])
+
+ filters = Jason.encode!(%{"page" => "/"})
+ q = "?period=day&date=2021-01-01&filters=#{filters}&detailed=true&with_imported=true"
+
+ conn = get(conn, "/api/stats/#{site.domain}/pages#{q}")
+
+ assert json_response(conn, 200)["results"] == [
+ %{
+ "bounce_rate" => 50,
+ "name" => "/",
+ "pageviews" => 4,
+ "time_on_page" => 90.0,
+ "visitors" => 4
+ }
+ ]
+ end
+
+ test "filter by :member page with imported data", %{conn: conn, site: site} do
+ site_import = insert(:site_import, site: site)
+
+ populate_stats(site, site_import.id, [
+ build(:pageview, user_id: 1, pathname: "/", timestamp: ~N[2021-01-01 12:00:00]),
+ build(:pageview, user_id: 1, pathname: "/ignored", timestamp: ~N[2021-01-01 12:01:00]),
+ build(:imported_entry_pages,
+ entry_page: "/",
+ visitors: 1,
+ bounces: 1,
+ date: ~D[2021-01-01]
+ ),
+ build(:imported_entry_pages,
+ entry_page: "/a",
+ visitors: 1,
+ bounces: 1,
+ date: ~D[2021-01-01]
+ ),
+ build(:imported_pages,
+ page: "/",
+ visitors: 3,
+ pageviews: 3,
+ time_on_page: 300,
+ date: ~D[2021-01-01]
+ ),
+ build(:imported_pages,
+ page: "/a",
+ visitors: 1,
+ date: ~D[2021-01-01]
+ ),
+ build(:imported_pages, page: "/ignored", visitors: 10, date: ~D[2021-01-01])
+ ])
+
+ filters = Jason.encode!(%{"page" => "/|/a"})
+ q = "?period=day&date=2021-01-01&filters=#{filters}&detailed=true&with_imported=true"
+
+ conn = get(conn, "/api/stats/#{site.domain}/pages#{q}")
+
+ assert json_response(conn, 200)["results"] == [
+ %{
+ "bounce_rate" => 50,
+ "name" => "/",
+ "pageviews" => 4,
+ "time_on_page" => 90.0,
+ "visitors" => 4
+ },
+ %{
+ "bounce_rate" => 100,
+ "name" => "/a",
+ "pageviews" => 1,
+ "time_on_page" => 10.0,
+ "visitors" => 1
+ }
+ ]
+ end
+
+ test "filter by :matches page with imported data", %{conn: conn, site: site} do
+ site_import = insert(:site_import, site: site)
+
+ populate_stats(site, site_import.id, [
+ build(:pageview, user_id: 1, pathname: "/aaa", timestamp: ~N[2021-01-01 12:00:00]),
+ build(:pageview, user_id: 1, pathname: "/ignored", timestamp: ~N[2021-01-01 12:01:00]),
+ build(:imported_entry_pages,
+ entry_page: "/aaa",
+ visitors: 1,
+ bounces: 1,
+ date: ~D[2021-01-01]
+ ),
+ build(:imported_entry_pages,
+ entry_page: "/a",
+ visitors: 1,
+ bounces: 1,
+ date: ~D[2021-01-01]
+ ),
+ build(:imported_pages,
+ page: "/aaa",
+ visitors: 3,
+ pageviews: 3,
+ time_on_page: 300,
+ date: ~D[2021-01-01]
+ ),
+ build(:imported_pages,
+ page: "/a",
+ visitors: 1,
+ date: ~D[2021-01-01]
+ ),
+ build(:imported_pages, page: "/ignored", visitors: 10, date: ~D[2021-01-01])
+ ])
+
+ filters = Jason.encode!(%{"page" => "/a**"})
+ q = "?period=day&date=2021-01-01&filters=#{filters}&detailed=true&with_imported=true"
+
+ conn = get(conn, "/api/stats/#{site.domain}/pages#{q}")
+
+ assert json_response(conn, 200)["results"] == [
+ %{
+ "bounce_rate" => 50,
+ "name" => "/aaa",
+ "pageviews" => 4,
+ "time_on_page" => 90.0,
+ "visitors" => 4
+ },
+ %{
+ "bounce_rate" => 100,
+ "name" => "/a",
+ "pageviews" => 1,
+ "time_on_page" => 10.0,
+ "visitors" => 1
+ }
+ ]
+ end
end
describe "GET /api/stats/:domain/entry-pages" do
@@ -1236,7 +1419,7 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do
conn = get(conn, "/api/stats/#{site.domain}/entry-pages?period=day&date=2021-01-01")
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"visitors" => 2,
"visits" => 2,
@@ -1292,7 +1475,7 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do
"/api/stats/#{site.domain}/entry-pages?period=day&date=2021-01-01&filters=#{filters}"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"visitors" => 1,
"visits" => 1,
@@ -1347,7 +1530,7 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do
conn = get(conn, "/api/stats/#{site.domain}/entry-pages?period=day&date=2021-01-01")
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"visitors" => 2,
"visits" => 2,
@@ -1368,7 +1551,7 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do
"/api/stats/#{site.domain}/entry-pages?period=day&date=2021-01-01&with_imported=true"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"visitors" => 3,
"visits" => 5,
@@ -1431,7 +1614,7 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do
)
# We're going to only join sessions where the exit hostname matches the filter
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{"name" => "/page1", "visit_duration" => 0, "visitors" => 1, "visits" => 1},
%{"name" => "/page2", "visit_duration" => 0, "visitors" => 1, "visits" => 1}
]
@@ -1460,6 +1643,7 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do
"/api/stats/#{site.domain}/pages?date=2021-01-01&period=day&filters=#{filters}&limit=#{limit}&page=#{page}"
)
|> json_response(200)
+ |> Map.get("results")
|> Enum.map(fn %{"name" => "/signup/" <> seq} ->
seq
end)
@@ -1519,7 +1703,7 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do
"/api/stats/#{site.domain}/entry-pages?period=day&date=2021-01-01&filters=#{filters}"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"total_visitors" => 2,
"visitors" => 1,
@@ -1550,7 +1734,57 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do
"/api/stats/#{site.domain}/entry-pages?period=day&date=2021-01-01"
)
- assert json_response(conn, 200) == []
+ assert json_response(conn, 200)["results"] == []
+ end
+
+ test "filter by :matches_member entry_page with imported data", %{conn: conn, site: site} do
+ site_import = insert(:site_import, site: site)
+
+ populate_stats(site, site_import.id, [
+ build(:pageview, pathname: "/aaa", timestamp: ~N[2021-01-01 12:00:00]),
+ build(:pageview, pathname: "/a", timestamp: ~N[2021-01-01 12:00:00]),
+ build(:pageview, pathname: "/ignored", timestamp: ~N[2021-01-01 12:01:00]),
+ build(:imported_entry_pages,
+ entry_page: "/a",
+ visitors: 5,
+ entrances: 9,
+ visit_duration: 1000,
+ date: ~D[2021-01-01]
+ ),
+ build(:imported_entry_pages,
+ entry_page: "/bbb",
+ visitors: 2,
+ entrances: 2,
+ visit_duration: 100,
+ date: ~D[2021-01-01]
+ )
+ ])
+
+ filters = Jason.encode!(%{"entry_page" => "/a**|/b**"})
+ q = "?period=day&date=2021-01-01&filters=#{filters}&detailed=true&with_imported=true"
+
+ conn = get(conn, "/api/stats/#{site.domain}/entry-pages#{q}")
+
+ assert json_response(conn, 200)["results"] == [
+ %{
+ "visit_duration" => 100.0,
+ "name" => "/a",
+ "visits" => 10,
+ "visitors" => 6
+ },
+ %{
+ "visit_duration" => 50.0,
+ "name" => "/bbb",
+ "visits" => 2,
+ "visitors" => 2
+ },
+ %{
+ "visit_duration" => 0,
+ "name" => "/aaa",
+ "visits" => 1,
+ "visitors" => 1
+ }
+ ]
end
end
@@ -1581,7 +1815,7 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do
conn = get(conn, "/api/stats/#{site.domain}/exit-pages?period=day&date=2021-01-01")
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{"name" => "/page1", "visitors" => 2, "visits" => 2, "exit_rate" => 66},
%{"name" => "/page2", "visitors" => 1, "visits" => 1, "exit_rate" => 100}
]
@@ -1629,7 +1863,7 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do
)
# We're going to only join sessions where the entry hostname matches the filter
- assert json_response(conn, 200) ==
+ assert json_response(conn, 200)["results"] ==
[%{"name" => "/page1", "visitors" => 1, "visits" => 1}]
end
@@ -1667,7 +1901,7 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do
"/api/stats/#{site.domain}/exit-pages?period=day&date=2021-01-01&filters=#{filters}"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{"name" => "/", "visitors" => 1, "visits" => 1}
]
end
@@ -1711,7 +1945,7 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do
conn = get(conn, "/api/stats/#{site.domain}/exit-pages?period=day&date=2021-01-01")
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{"name" => "/page1", "visitors" => 2, "visits" => 2, "exit_rate" => 66},
%{"name" => "/page2", "visitors" => 1, "visits" => 1, "exit_rate" => 100}
]
@@ -1722,7 +1956,7 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do
"/api/stats/#{site.domain}/exit-pages?period=day&date=2021-01-01&with_imported=true"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"name" => "/page2",
"visitors" => 3,
@@ -1773,7 +2007,7 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do
"/api/stats/#{site.domain}/exit-pages?period=day&date=2021-01-01&filters=#{filters}"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"name" => "/exit1",
"visitors" => 1,
@@ -1826,7 +2060,7 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do
"/api/stats/#{site.domain}/exit-pages?period=day&date=2021-01-01&filters=#{filters}"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{"name" => "/exit1", "visitors" => 1, "visits" => 1},
%{"name" => "/exit2", "visitors" => 1, "visits" => 1}
]
@@ -1847,7 +2081,59 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do
"/api/stats/#{site.domain}/exit-pages?period=day&date=2021-01-01"
)
- assert json_response(conn, 200) == []
+ assert json_response(conn, 200)["results"] == []
+ end
+
+ test "filter by :is_not exit_page with imported data", %{conn: conn, site: site} do
+ site_import = insert(:site_import, site: site)
+
+ populate_stats(site, site_import.id, [
+ build(:pageview, pathname: "/aaa", timestamp: ~N[2021-01-01 12:00:00]),
+ build(:pageview, pathname: "/a", timestamp: ~N[2021-01-01 12:00:00]),
+ build(:pageview, pathname: "/ignored", timestamp: ~N[2021-01-01 12:01:00]),
+ build(:imported_exit_pages,
+ exit_page: "/a",
+ visitors: 5,
+ exits: 9,
+ visit_duration: 1000,
+ date: ~D[2021-01-01]
+ ),
+ build(:imported_exit_pages,
+ exit_page: "/bbb",
+ visitors: 2,
+ exits: 2,
+ visit_duration: 100,
+ date: ~D[2021-01-01]
+ ),
+ build(:imported_pages, page: "/a", pageviews: 19, date: ~D[2021-01-01]),
+ build(:imported_pages, page: "/bbb", pageviews: 2, date: ~D[2021-01-01])
+ ])
+
+ filters = Jason.encode!(%{"exit_page" => "!/ignored"})
+ q = "?period=day&date=2021-01-01&filters=#{filters}&detailed=true&with_imported=true"
+
+ conn = get(conn, "/api/stats/#{site.domain}/exit-pages#{q}")
+
+ assert json_response(conn, 200)["results"] == [
+ %{
+ "exit_rate" => 50.0,
+ "name" => "/a",
+ "visits" => 10,
+ "visitors" => 6
+ },
+ %{
+ "exit_rate" => 100.0,
+ "name" => "/bbb",
+ "visits" => 2,
+ "visitors" => 2
+ },
+ %{
+ "exit_rate" => 100.0,
+ "name" => "/aaa",
+ "visits" => 1,
+ "visitors" => 1
+ }
+ ]
end
end
end
diff --git a/test/plausible_web/controllers/api/stats_controller/regions_test.exs b/test/plausible_web/controllers/api/stats_controller/regions_test.exs
index 23ac063bab3d..711ae957f8eb 100644
--- a/test/plausible_web/controllers/api/stats_controller/regions_test.exs
+++ b/test/plausible_web/controllers/api/stats_controller/regions_test.exs
@@ -37,7 +37,7 @@ defmodule PlausibleWeb.Api.StatsController.RegionsTest do
test "returns top cities by new visitors", %{conn: conn, site: site} do
conn = get(conn, "/api/stats/#{site.domain}/regions?period=day")
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{"code" => "EE-37", "country_flag" => "🇪🇪", "name" => "Harjumaa", "visitors" => 3},
%{"code" => "EE-39", "country_flag" => "🇪🇪", "name" => "Hiiumaa", "visitors" => 2}
]
@@ -47,7 +47,7 @@ defmodule PlausibleWeb.Api.StatsController.RegionsTest do
filters = Jason.encode!(%{region: "EE-39"})
conn = get(conn, "/api/stats/#{site.domain}/regions?period=day&filters=#{filters}")
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{"code" => "EE-39", "country_flag" => "🇪🇪", "name" => "Hiiumaa", "visitors" => 2}
]
end
diff --git a/test/plausible_web/controllers/api/stats_controller/screen_sizes_test.exs b/test/plausible_web/controllers/api/stats_controller/screen_sizes_test.exs
index 128d1293df65..49c94c6e7d01 100644
--- a/test/plausible_web/controllers/api/stats_controller/screen_sizes_test.exs
+++ b/test/plausible_web/controllers/api/stats_controller/screen_sizes_test.exs
@@ -13,7 +13,7 @@ defmodule PlausibleWeb.Api.StatsController.ScreenSizesTest do
conn = get(conn, "/api/stats/#{site.domain}/screen-sizes?period=day")
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{"name" => "Desktop", "visitors" => 2, "percentage" => 66.7},
%{"name" => "Laptop", "visitors" => 1, "percentage" => 33.3}
]
@@ -39,7 +39,7 @@ defmodule PlausibleWeb.Api.StatsController.ScreenSizesTest do
"date" => "2021-01-01"
})
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{"name" => "Desktop", "visitors" => 1, "percentage" => 100},
%{"name" => "Laptop", "visitors" => 1, "percentage" => 100}
]
@@ -57,7 +57,7 @@ defmodule PlausibleWeb.Api.StatsController.ScreenSizesTest do
conn = get(conn, "/api/stats/#{site.domain}/screen-sizes?period=day")
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{"name" => "(not set)", "visitors" => 1, "percentage" => 50},
%{"name" => "Desktop", "visitors" => 1, "percentage" => 50}
]
@@ -67,7 +67,7 @@ defmodule PlausibleWeb.Api.StatsController.ScreenSizesTest do
filters = Jason.encode!(%{screen: "(not set)"})
conn = get(conn, "/api/stats/#{site.domain}/screen-sizes?period=day&filters=#{filters}")
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{"name" => "(not set)", "visitors" => 1, "percentage" => 100}
]
end
@@ -84,7 +84,7 @@ defmodule PlausibleWeb.Api.StatsController.ScreenSizesTest do
conn = get(conn, "/api/stats/#{site.domain}/screen-sizes?period=day&with_imported=true")
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{"name" => "(not set)", "visitors" => 2, "percentage" => 100.0}
]
end
@@ -117,7 +117,7 @@ defmodule PlausibleWeb.Api.StatsController.ScreenSizesTest do
filters = Jason.encode!(%{props: %{"author" => "John Doe"}})
conn = get(conn, "/api/stats/#{site.domain}/screen-sizes?period=day&filters=#{filters}")
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{"name" => "Desktop", "visitors" => 1, "percentage" => 100}
]
end
@@ -152,7 +152,7 @@ defmodule PlausibleWeb.Api.StatsController.ScreenSizesTest do
filters = Jason.encode!(%{props: %{"author" => "!John Doe"}})
conn = get(conn, "/api/stats/#{site.domain}/screen-sizes?period=day&filters=#{filters}")
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{"name" => "Mobile", "visitors" => 1, "percentage" => 50},
%{"name" => "Tablet", "visitors" => 1, "percentage" => 50}
]
@@ -173,20 +173,41 @@ defmodule PlausibleWeb.Api.StatsController.ScreenSizesTest do
conn = get(conn, "/api/stats/#{site.domain}/screen-sizes?period=day")
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{"name" => "Desktop", "visitors" => 2, "percentage" => 66.7},
%{"name" => "Laptop", "visitors" => 1, "percentage" => 33.3}
]
conn = get(conn, "/api/stats/#{site.domain}/screen-sizes?period=day&with_imported=true")
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{"name" => "Desktop", "visitors" => 2, "percentage" => 40},
%{"name" => "Laptop", "visitors" => 2, "percentage" => 40},
%{"name" => "Mobile", "visitors" => 1, "percentage" => 20}
]
end
+ test "returns screen sizes when filtering by imported screen size", %{conn: conn, site: site} do
+ populate_stats(site, [
+ build(:pageview, screen_size: "Desktop"),
+ build(:imported_devices, device: "Desktop"),
+ build(:imported_devices, device: "Laptop"),
+ build(:imported_visitors, visitors: 2)
+ ])
+
+ filters = Jason.encode!(%{screen: "Desktop"})
+
+ conn =
+ get(
+ conn,
+ "/api/stats/#{site.domain}/screen-sizes?filters=#{filters}&period=day&with_imported=true"
+ )
+
+ assert json_response(conn, 200)["results"] == [
+ %{"name" => "Desktop", "visitors" => 2, "percentage" => 100.0}
+ ]
+ end
+
test "returns screen sizes for user making multiple sessions by no of visitors with imported data",
%{conn: conn, site: site} do
populate_stats(site, [
@@ -215,7 +236,7 @@ defmodule PlausibleWeb.Api.StatsController.ScreenSizesTest do
"with_imported" => "true"
})
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{"name" => "Desktop", "visitors" => 2, "percentage" => 100},
%{"name" => "Laptop", "visitors" => 2, "percentage" => 100}
]
@@ -232,7 +253,7 @@ defmodule PlausibleWeb.Api.StatsController.ScreenSizesTest do
conn = get(conn, "/api/stats/#{site.domain}/screen-sizes?period=day&filters=#{filters}")
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"name" => "Desktop",
"total_visitors" => 2,
@@ -258,7 +279,7 @@ defmodule PlausibleWeb.Api.StatsController.ScreenSizesTest do
conn = get(conn, "/api/stats/#{site.domain}/screen-sizes?period=day&filters=#{filters}")
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{"name" => "Desktop", "visitors" => 2, "percentage" => 66.7},
%{"name" => "Mobile", "visitors" => 1, "percentage" => 33.3}
]
diff --git a/test/plausible_web/controllers/api/stats_controller/sources_test.exs b/test/plausible_web/controllers/api/stats_controller/sources_test.exs
index dfa9480c02a0..a76b1da27415 100644
--- a/test/plausible_web/controllers/api/stats_controller/sources_test.exs
+++ b/test/plausible_web/controllers/api/stats_controller/sources_test.exs
@@ -33,7 +33,7 @@ defmodule PlausibleWeb.Api.StatsController.SourcesTest do
conn = get(conn, "/api/stats/#{site.domain}/sources")
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{"name" => "Google", "visitors" => 3},
%{"name" => "DuckDuckGo", "visitors" => 2},
%{"name" => "Direct / None", "visitors" => 1}
@@ -83,7 +83,7 @@ defmodule PlausibleWeb.Api.StatsController.SourcesTest do
"/api/stats/#{site.domain}/sources?period=day&date=2021-01-01&filters=#{filters}"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{"name" => "Google", "visitors" => 2},
%{"name" => "DuckDuckGo", "visitors" => 1}
]
@@ -137,7 +137,7 @@ defmodule PlausibleWeb.Api.StatsController.SourcesTest do
"/api/stats/#{site.domain}/sources?period=day&date=2021-01-01&filters=#{filters}"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{"name" => "Google", "visitors" => 2},
%{"name" => "DuckDuckGo", "visitors" => 1}
]
@@ -187,7 +187,7 @@ defmodule PlausibleWeb.Api.StatsController.SourcesTest do
"/api/stats/#{site.domain}/sources?period=day&date=2021-01-01&filters=#{filters}"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{"name" => "Facebook", "visitors" => 2},
%{"name" => "DuckDuckGo", "visitors" => 1}
]
@@ -241,7 +241,7 @@ defmodule PlausibleWeb.Api.StatsController.SourcesTest do
"/api/stats/#{site.domain}/sources?period=day&date=2021-01-01&filters=#{filters}"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{"name" => "Google", "visitors" => 2},
%{"name" => "DuckDuckGo", "visitors" => 1}
]
@@ -270,14 +270,14 @@ defmodule PlausibleWeb.Api.StatsController.SourcesTest do
conn = get(conn, "/api/stats/#{site.domain}/sources")
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{"name" => "Google", "visitors" => 2},
%{"name" => "DuckDuckGo", "visitors" => 1}
]
conn = get(conn, "/api/stats/#{site.domain}/sources?with_imported=true")
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{"name" => "Google", "visitors" => 4},
%{"name" => "DuckDuckGo", "visitors" => 2}
]
@@ -310,7 +310,7 @@ defmodule PlausibleWeb.Api.StatsController.SourcesTest do
"/api/stats/#{site.domain}/sources?period=day&date=2021-01-01&detailed=true"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"name" => "DuckDuckGo",
"visitors" => 1,
@@ -375,7 +375,7 @@ defmodule PlausibleWeb.Api.StatsController.SourcesTest do
"/api/stats/#{site.domain}/sources?period=day&date=2021-01-01&detailed=true"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"name" => "DuckDuckGo",
"visitors" => 1,
@@ -396,7 +396,7 @@ defmodule PlausibleWeb.Api.StatsController.SourcesTest do
"/api/stats/#{site.domain}/sources?period=day&date=2021-01-01&detailed=true&with_imported=true"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"name" => "Google",
"visitors" => 3,
@@ -433,7 +433,7 @@ defmodule PlausibleWeb.Api.StatsController.SourcesTest do
conn = get(conn, "/api/stats/#{site.domain}/sources?period=realtime")
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{"name" => "Google", "visitors" => 2},
%{"name" => "DuckDuckGo", "visitors" => 1}
]
@@ -460,13 +460,13 @@ defmodule PlausibleWeb.Api.StatsController.SourcesTest do
conn = get(conn, "/api/stats/#{site.domain}/sources?limit=1&page=2")
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{"name" => "DuckDuckGo", "visitors" => 1}
]
conn = get(conn, "/api/stats/#{site.domain}/sources?limit=1&page=2&with_imported=true")
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{"name" => "DuckDuckGo", "visitors" => 2}
]
end
@@ -490,7 +490,7 @@ defmodule PlausibleWeb.Api.StatsController.SourcesTest do
filters = Jason.encode!(%{"page" => "/page1"})
conn = get(conn, "/api/stats/#{site.domain}/sources?filters=#{filters}")
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{"name" => "Google", "visitors" => 2},
%{"name" => "DuckDuckGo", "visitors" => 1}
]
@@ -538,7 +538,7 @@ defmodule PlausibleWeb.Api.StatsController.SourcesTest do
)
# nobody landed on one.example.com from utm_param=ad
- assert json_response(conn, 200) == []
+ assert json_response(conn, 200)["results"] == []
end
end
end
@@ -589,7 +589,7 @@ defmodule PlausibleWeb.Api.StatsController.SourcesTest do
"/api/stats/#{site.domain}/utm_mediums?period=day&date=2021-01-01"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"name" => "social",
"visitors" => 1,
@@ -610,7 +610,7 @@ defmodule PlausibleWeb.Api.StatsController.SourcesTest do
"/api/stats/#{site.domain}/utm_mediums?period=day&date=2021-01-01&with_imported=true"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"name" => "social",
"visitors" => 2,
@@ -669,7 +669,7 @@ defmodule PlausibleWeb.Api.StatsController.SourcesTest do
"/api/stats/#{site.domain}/utm_mediums?period=day&date=2021-01-01"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"name" => "social",
"visitors" => 1,
@@ -684,7 +684,7 @@ defmodule PlausibleWeb.Api.StatsController.SourcesTest do
"/api/stats/#{site.domain}/utm_mediums?period=day&date=2021-01-01&with_imported=true"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"name" => "social",
"visitors" => 2,
@@ -745,7 +745,7 @@ defmodule PlausibleWeb.Api.StatsController.SourcesTest do
"/api/stats/#{site.domain}/utm_campaigns?period=day&date=2021-01-01"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"name" => "august",
"visitors" => 2,
@@ -766,7 +766,7 @@ defmodule PlausibleWeb.Api.StatsController.SourcesTest do
"/api/stats/#{site.domain}/utm_campaigns?period=day&date=2021-01-01&with_imported=true"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"name" => "august",
"visitors" => 3,
@@ -829,7 +829,7 @@ defmodule PlausibleWeb.Api.StatsController.SourcesTest do
"/api/stats/#{site.domain}/utm_campaigns?period=day&date=2021-01-01"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"name" => "profile",
"visitors" => 1,
@@ -844,7 +844,7 @@ defmodule PlausibleWeb.Api.StatsController.SourcesTest do
"/api/stats/#{site.domain}/utm_campaigns?period=day&date=2021-01-01&with_imported=true"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"name" => "profile",
"visitors" => 2,
@@ -886,7 +886,7 @@ defmodule PlausibleWeb.Api.StatsController.SourcesTest do
"/api/stats/#{site.domain}/utm_sources?period=day&date=2021-01-01"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"name" => "newsletter",
"visitors" => 2,
@@ -953,7 +953,7 @@ defmodule PlausibleWeb.Api.StatsController.SourcesTest do
"/api/stats/#{site.domain}/utm_terms?period=day&date=2021-01-01"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"name" => "Sweden",
"visitors" => 2,
@@ -974,7 +974,7 @@ defmodule PlausibleWeb.Api.StatsController.SourcesTest do
"/api/stats/#{site.domain}/utm_terms?period=day&date=2021-01-01&with_imported=true"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"name" => "Sweden",
"visitors" => 3,
@@ -1037,7 +1037,7 @@ defmodule PlausibleWeb.Api.StatsController.SourcesTest do
"/api/stats/#{site.domain}/utm_terms?period=day&date=2021-01-01"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"name" => "oat milk",
"visitors" => 1,
@@ -1052,7 +1052,7 @@ defmodule PlausibleWeb.Api.StatsController.SourcesTest do
"/api/stats/#{site.domain}/utm_terms?period=day&date=2021-01-01&with_imported=true"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"name" => "oat milk",
"visitors" => 2,
@@ -1113,7 +1113,7 @@ defmodule PlausibleWeb.Api.StatsController.SourcesTest do
"/api/stats/#{site.domain}/utm_contents?period=day&date=2021-01-01"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"name" => "blog",
"visitors" => 2,
@@ -1134,7 +1134,7 @@ defmodule PlausibleWeb.Api.StatsController.SourcesTest do
"/api/stats/#{site.domain}/utm_contents?period=day&date=2021-01-01&with_imported=true"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"name" => "blog",
"visitors" => 3,
@@ -1197,7 +1197,7 @@ defmodule PlausibleWeb.Api.StatsController.SourcesTest do
"/api/stats/#{site.domain}/utm_contents?period=day&date=2021-01-01"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"name" => "ad",
"visitors" => 1,
@@ -1212,7 +1212,7 @@ defmodule PlausibleWeb.Api.StatsController.SourcesTest do
"/api/stats/#{site.domain}/utm_contents?period=day&date=2021-01-01&with_imported=true"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"name" => "ad",
"visitors" => 2,
@@ -1257,7 +1257,7 @@ defmodule PlausibleWeb.Api.StatsController.SourcesTest do
"/api/stats/#{site.domain}/sources?period=day&filters=#{filters}"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"name" => "Twitter",
"total_visitors" => 2,
@@ -1299,7 +1299,7 @@ defmodule PlausibleWeb.Api.StatsController.SourcesTest do
"/api/stats/#{site.domain}/sources?period=day&filters=#{filters}"
)
- assert json_response(conn, 200) == []
+ assert json_response(conn, 200)["results"] == []
end
test "returns top referrers for a custom goal and filtered by hostname (2)",
@@ -1330,7 +1330,7 @@ defmodule PlausibleWeb.Api.StatsController.SourcesTest do
"/api/stats/#{site.domain}/sources?period=day&filters=#{filters}"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"conversion_rate" => 100.0,
"name" => "Facebook",
@@ -1380,7 +1380,7 @@ defmodule PlausibleWeb.Api.StatsController.SourcesTest do
"/api/stats/#{site.domain}/sources?period=day&date=2021-01-01&filters=#{filters}"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"name" => "DuckDuckGo",
"visitors" => 1,
@@ -1431,7 +1431,7 @@ defmodule PlausibleWeb.Api.StatsController.SourcesTest do
"/api/stats/#{site.domain}/sources?period=day&date=2021-01-01&filters=#{filters}"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"name" => "DuckDuckGo",
"visitors" => 1,
@@ -1473,7 +1473,7 @@ defmodule PlausibleWeb.Api.StatsController.SourcesTest do
"/api/stats/#{site.domain}/sources?period=day&filters=#{filters}"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"name" => "Twitter",
"total_visitors" => 2,
@@ -1513,7 +1513,7 @@ defmodule PlausibleWeb.Api.StatsController.SourcesTest do
"/api/stats/#{site.domain}/referrers/10words?period=day"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{"name" => "10words.com", "visitors" => 2},
%{"name" => "10words.com/page1", "visitors" => 1}
]
@@ -1559,7 +1559,7 @@ defmodule PlausibleWeb.Api.StatsController.SourcesTest do
"/api/stats/#{site.domain}/referrers/example?period=day&filters=#{filters}"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{"name" => "example.com/page1", "visitors" => 1}
]
end
@@ -1596,7 +1596,7 @@ defmodule PlausibleWeb.Api.StatsController.SourcesTest do
"/api/stats/#{site.domain}/referrers/10words?period=day&date=2021-01-01&detailed=true"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"name" => "10words.com",
"visitors" => 2,
@@ -1649,13 +1649,13 @@ defmodule PlausibleWeb.Api.StatsController.SourcesTest do
conn = get(conn, "/api/stats/#{site.domain}/referrers/!Google?period=day")
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{"name" => "duckduckgo.com", "visitors" => 1}
]
conn = get(conn, "/api/stats/#{site.domain}/referrers/Google|DuckDuckGo?period=day")
- assert [entry1, entry2] = json_response(conn, 200)
+ assert [entry1, entry2] = json_response(conn, 200)["results"]
assert %{"name" => "google.com", "visitors" => 2} in [entry1, entry2]
assert %{"name" => "duckduckgo.com", "visitors" => 1} in [entry1, entry2]
end
@@ -1688,7 +1688,7 @@ defmodule PlausibleWeb.Api.StatsController.SourcesTest do
"/api/stats/#{site.domain}/referrers/10words?period=day&filters=#{filters}"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"name" => "10words.com",
"total_visitors" => 2,
@@ -1726,7 +1726,7 @@ defmodule PlausibleWeb.Api.StatsController.SourcesTest do
"/api/stats/#{site.domain}/referrers/10words?period=day&filters=#{filters}"
)
- assert json_response(conn, 200) == [
+ assert json_response(conn, 200)["results"] == [
%{
"name" => "10words.com",
"total_visitors" => 2,
diff --git a/test/plausible_web/controllers/api/stats_controller/suggestions_test.exs b/test/plausible_web/controllers/api/stats_controller/suggestions_test.exs
index fd77e5279d5a..bfe7ae16d488 100644
--- a/test/plausible_web/controllers/api/stats_controller/suggestions_test.exs
+++ b/test/plausible_web/controllers/api/stats_controller/suggestions_test.exs
@@ -634,4 +634,611 @@ defmodule PlausibleWeb.Api.StatsController.SuggestionsTest do
}
end
end
+
+ describe "imported data" do
+ setup [:create_user, :log_in, :create_site, :create_site_import]
+
+ test "merges country suggestions from native and imported data", %{
+ conn: conn,
+ site: site,
+ site_import: site_import
+ } do
+ populate_stats(site, site_import.id, [
+ build(:pageview, timestamp: ~N[2019-01-01 23:00:01], country_code: "US"),
+ build(:pageview, timestamp: ~N[2019-01-01 23:30:01], country_code: "US"),
+ build(:pageview, timestamp: ~N[2019-01-01 23:40:01], country_code: "US"),
+ build(:pageview, timestamp: ~N[2019-01-01 23:00:01], country_code: "GB"),
+ build(:imported_locations, date: ~D[2019-01-01], country: "GB", pageviews: 3)
+ ])
+
+ conn =
+ get(
+ conn,
+ "/api/stats/#{site.domain}/suggestions/country?period=month&date=2019-01-01&q=Unit&with_imported=true"
+ )
+
+ assert json_response(conn, 200) == [
+ %{"value" => "GB", "label" => "United Kingdom"},
+ %{"value" => "US", "label" => "United States"}
+ ]
+ end
+
+ test "ignores imported data in country suggestions when a different property is filtered by",
+ %{
+ conn: conn,
+ site: site,
+ site_import: site_import
+ } do
+ populate_stats(site, site_import.id, [
+ build(:pageview, country_code: "EE", referrer_source: "Bing"),
+ build(:imported_locations, country: "GB")
+ ])
+
+ filters = Jason.encode!(%{source: "Bing"})
+
+ conn =
+ get(
+ conn,
+ "/api/stats/#{site.domain}/suggestions/country?filters=#{filters}&q=&with_imported=true"
+ )
+
+ assert json_response(conn, 200) == [%{"value" => "EE", "label" => "Estonia"}]
+ end
+
+ test "queries imported countries when filtering by country", %{
+ conn: conn,
+ site: site,
+ site_import: site_import
+ } do
+ populate_stats(site, site_import.id, [
+ build(:imported_locations, date: ~D[2019-01-01], country: "EE")
+ ])
+
+ filters = Jason.encode!(%{country: "EE"})
+
+ conn =
+ get(
+ conn,
+ "/api/stats/#{site.domain}/suggestions/country?period=month&date=2019-01-01&filters=#{filters}&q=&with_imported=true"
+ )
+
+ assert json_response(conn, 200) == [%{"value" => "EE", "label" => "Estonia"}]
+ end
+
+ test "ignores imported country data when not requested", %{
+ conn: conn,
+ site: site,
+ site_import: site_import
+ } do
+ populate_stats(site, site_import.id, [
+ build(:imported_locations, date: ~D[2019-01-01], country: "GB", pageviews: 3)
+ ])
+
+ conn =
+ get(
+ conn,
+ "/api/stats/#{site.domain}/suggestions/country?period=month&date=2019-01-01&q="
+ )
+
+ assert json_response(conn, 200) == []
+ end
+
+ for {q, label} <- [{"", "without filter"}, {"H", "with filter"}] do
+ test "merges region suggestions from native and imported data #{label}", %{
+ conn: conn,
+ site: site,
+ site_import: site_import
+ } do
+ populate_stats(site, site_import.id, [
+ build(:pageview, country_code: "EE", subdivision1_code: "EE-37"),
+ build(:pageview, country_code: "EE", subdivision1_code: "EE-39"),
+ build(:pageview, country_code: "EE", subdivision1_code: "EE-39"),
+ build(:imported_locations, country: "EE", region: "EE-37", pageviews: 2)
+ ])
+
+ conn =
+ get(
+ conn,
+ "/api/stats/#{site.domain}/suggestions/region?q=#{unquote(q)}&with_imported=true"
+ )
+
+ assert json_response(conn, 200) == [
+ %{"value" => "EE-37", "label" => "Harjumaa"},
+ %{"value" => "EE-39", "label" => "Hiiumaa"}
+ ]
+ end
+ end
+
+ test "handles invalid region codes in imported data gracefully (GA4)", %{
+ conn: conn,
+ site: site,
+ site_import: site_import
+ } do
+ # NOTE: Currently, the regions imported from GA4 do not conform to region code standard
+ # we are using. Instead, literal region names are persisted. Those names often do not
+ # match the names from our region databases either. Regardless of that, we still consider
+ # them when filtering suggestions.
+
+ populate_stats(site, site_import.id, [
+ build(:imported_locations, country: "EE", region: "EE-37", pageviews: 2),
+ build(:imported_locations, country: "EE", region: "Hiiumaa", pageviews: 1)
+ ])
+
+ conn =
+ get(
+ conn,
+ "/api/stats/#{site.domain}/suggestions/region?q=&with_imported=true"
+ )
+
+ assert json_response(conn, 200) == [
+ %{"value" => "EE-37", "label" => "Harjumaa"},
+ %{"value" => "Hiiumaa", "label" => "Hiiumaa"}
+ ]
+
+ conn2 =
+ get(
+ conn,
+ "/api/stats/#{site.domain}/suggestions/region?q=H&with_imported=true"
+ )
+
+ assert json_response(conn2, 200) == [
+ %{"value" => "EE-37", "label" => "Harjumaa"},
+ %{"value" => "Hiiumaa", "label" => "Hiiumaa"}
+ ]
+ end
+
+ test "ignores imported data in region suggestions when a different property is filtered by",
+ %{
+ conn: conn,
+ site: site,
+ site_import: site_import
+ } do
+ populate_stats(site, site_import.id, [
+ build(:pageview,
+ country_code: "EE",
+ subdivision1_code: "EE-39",
+ referrer_source: "Bing"
+ ),
+ build(:imported_locations, country: "EE", region: "EE-37")
+ ])
+
+ filters = Jason.encode!(%{source: "Bing"})
+
+ conn =
+ get(
+ conn,
+ "/api/stats/#{site.domain}/suggestions/region?filters=#{filters}&q=&with_imported=true"
+ )
+
+ assert json_response(conn, 200) == [%{"value" => "EE-39", "label" => "Hiiumaa"}]
+ end
+
+ test "queries imported regions when filtering by region", %{
+ conn: conn,
+ site: site,
+ site_import: site_import
+ } do
+ populate_stats(site, site_import.id, [
+ build(:imported_locations, date: ~D[2019-01-01], region: "EE-39")
+ ])
+
+ filters = Jason.encode!(%{region: "EE-39"})
+
+ conn =
+ get(
+ conn,
+ "/api/stats/#{site.domain}/suggestions/region?period=month&date=2019-01-01&filters=#{filters}&q=&with_imported=true"
+ )
+
+ assert json_response(conn, 200) == [%{"value" => "EE-39", "label" => "Hiiumaa"}]
+ end
+
+ test "ignores imported region data when not requested", %{
+ conn: conn,
+ site: site,
+ site_import: site_import
+ } do
+ populate_stats(site, site_import.id, [
+ build(:imported_locations, country: "EE", region: "EE-37", pageviews: 2)
+ ])
+
+ conn =
+ get(
+ conn,
+ "/api/stats/#{site.domain}/suggestions/region?q="
+ )
+
+ assert json_response(conn, 200) == []
+ end
+
+ for {q, label} <- [{"", "without filter"}, {"l", "with filter"}] do
+ test "merges city suggestions from native and imported data #{label}", %{
+ conn: conn,
+ site: site,
+ site_import: site_import
+ } do
+ populate_stats(site, site_import.id, [
+ build(:pageview,
+ country_code: "EE",
+ subdivision1_code: "EE-37",
+ city_geoname_id: 588_409
+ ),
+ build(:pageview,
+ country_code: "EE",
+ subdivision1_code: "EE-39",
+ city_geoname_id: 591_632
+ ),
+ build(:pageview,
+ country_code: "EE",
+ subdivision1_code: "EE-39",
+ city_geoname_id: 591_632
+ ),
+ build(:imported_locations, country: "EE", region: "EE-37", city: 588_409, pageviews: 2)
+ ])
+
+ conn =
+ get(
+ conn,
+ "/api/stats/#{site.domain}/suggestions/city?q=#{unquote(q)}&with_imported=true"
+ )
+
+ assert json_response(conn, 200) == [
+ %{"value" => "588409", "label" => "Tallinn"},
+ %{"value" => "591632", "label" => "Kärdla"}
+ ]
+ end
+ end
+
+ test "ignores imported data in city suggestions when a different property is filtered by", %{
+ conn: conn,
+ site: site,
+ site_import: site_import
+ } do
+ populate_stats(site, site_import.id, [
+ build(:pageview,
+ country_code: "EE",
+ subdivision1_code: "EE-39",
+ city_geoname_id: 591_632,
+ referrer_source: "Bing"
+ ),
+ build(:imported_locations, country: "EE", region: "EE-37", city: 588_409)
+ ])
+
+ filters = Jason.encode!(%{source: "Bing"})
+
+ conn =
+ get(
+ conn,
+ "/api/stats/#{site.domain}/suggestions/city?filters=#{filters}&q=&with_imported=true"
+ )
+
+ assert json_response(conn, 200) == [%{"value" => "591632", "label" => "Kärdla"}]
+ end
+
+ test "queries imported cities when filtering by city", %{
+ conn: conn,
+ site: site,
+ site_import: site_import
+ } do
+ populate_stats(site, site_import.id, [
+ build(:imported_locations, date: ~D[2019-01-01], city: 591_632)
+ ])
+
+ filters = Jason.encode!(%{city: "591632"})
+
+ conn =
+ get(
+ conn,
+ "/api/stats/#{site.domain}/suggestions/city?period=month&date=2019-01-01&filters=#{filters}&q=&with_imported=true"
+ )
+
+ assert json_response(conn, 200) == [%{"value" => "591632", "label" => "Kärdla"}]
+ end
+
+ test "ignores imported city data when not requested", %{
+ conn: conn,
+ site: site,
+ site_import: site_import
+ } do
+ populate_stats(site, site_import.id, [
+ build(:imported_locations, country: "EE", region: "EE-37", city: 588_409, pageviews: 2)
+ ])
+
+ conn =
+ get(
+ conn,
+ "/api/stats/#{site.domain}/suggestions/city?q="
+ )
+
+ assert json_response(conn, 200) == []
+ end
+
+ test "ignores imported data when asking for prop key and value suggestions", %{
+ conn: conn,
+ site: site,
+ site_import: site_import
+ } do
+ populate_stats(site, site_import.id, [
+ build(:pageview,
+ "meta.key": ["url"],
+ "meta.value": ["http://example1.com"],
+ timestamp: ~N[2022-01-01 00:00:00]
+ ),
+ build(:imported_custom_events,
+ date: ~D[2022-01-01],
+ name: "Outbound Link: Click",
+ link_url: "http://example2.com"
+ ),
+ build(:imported_custom_events,
+ date: ~D[2022-01-01],
+ name: "404",
+ path: "/dev/null"
+ )
+ ])
+
+ key_conn =
+ get(
+ conn,
+ "/api/stats/#{site.domain}/suggestions/prop_key?period=day&date=2022-01-01&with_imported=true"
+ )
+
+ assert json_response(key_conn, 200) == [%{"label" => "url", "value" => "url"}]
+
+ filters = Jason.encode!(%{props: %{url: "!(none)"}})
+
+ value_conn =
+ get(
+ conn,
+ "/api/stats/#{site.domain}/suggestions/prop_value?period=day&date=2022-01-01&with_imported=true&filters=#{filters}"
+ )
+
+ assert json_response(value_conn, 200) == [
+ %{"label" => "http://example1.com", "value" => "http://example1.com"}
+ ]
+ end
+
+ for {q, label} <- [{"", "without filter"}, {"g", "with filter"}] do
+ test "merges source suggestions from native and imported data #{label}", %{
+ conn: conn,
+ site: site,
+ site_import: site_import
+ } do
+ populate_stats(site, site_import.id, [
+ build(:pageview, timestamp: ~N[2019-01-01 23:00:01], referrer_source: "Bing"),
+ build(:pageview, timestamp: ~N[2019-01-01 23:30:01], referrer_source: "Bing"),
+ build(:pageview, timestamp: ~N[2019-01-01 23:40:01], referrer_source: "Bing"),
+ build(:pageview, timestamp: ~N[2019-01-01 23:00:01], referrer_source: "Google"),
+ build(:imported_sources, date: ~D[2019-01-01], source: "Google", pageviews: 3)
+ ])
+
+ conn =
+ get(
+ conn,
+ "/api/stats/#{site.domain}/suggestions/source?period=month&date=2019-01-01&q=#{unquote(q)}&with_imported=true"
+ )
+
+ assert json_response(conn, 200) == [
+ %{"value" => "Google", "label" => "Google"},
+ %{"value" => "Bing", "label" => "Bing"}
+ ]
+ end
+ end
+
+ for {q, label} <- [{"", "without filter"}, {"o", "with filter"}] do
+ test "merges screen suggestions from native and imported data #{label}", %{
+ conn: conn,
+ site: site,
+ site_import: site_import
+ } do
+ populate_stats(site, site_import.id, [
+ build(:pageview, timestamp: ~N[2019-01-01 23:00:01], screen_size: "Mobile"),
+ build(:pageview, timestamp: ~N[2019-01-01 23:30:01], screen_size: "Mobile"),
+ build(:pageview, timestamp: ~N[2019-01-01 23:40:01], screen_size: "Mobile"),
+ build(:pageview, timestamp: ~N[2019-01-01 23:00:01], screen_size: "Desktop"),
+ build(:imported_devices, date: ~D[2019-01-01], device: "Desktop", pageviews: 3)
+ ])
+
+ conn =
+ get(
+ conn,
+ "/api/stats/#{site.domain}/suggestions/screen?period=month&date=2019-01-01&q=#{unquote(q)}&with_imported=true"
+ )
+
+ assert json_response(conn, 200) == [
+ %{"value" => "Desktop", "label" => "Desktop"},
+ %{"value" => "Mobile", "label" => "Mobile"}
+ ]
+ end
+ end
+
+ for {q, label} <- [{"", "without filter"}, {"o", "with filter"}] do
+ test "merges page suggestions from native and imported data #{label}", %{
+ conn: conn,
+ site: site,
+ site_import: site_import
+ } do
+ populate_stats(site, site_import.id, [
+ build(:pageview, timestamp: ~N[2019-01-01 23:00:01], pathname: "/welcome"),
+ build(:pageview, timestamp: ~N[2019-01-01 23:30:01], pathname: "/welcome"),
+ build(:pageview, timestamp: ~N[2019-01-01 23:40:01], pathname: "/welcome"),
+ build(:pageview, timestamp: ~N[2019-01-01 23:00:01], pathname: "/blog"),
+ build(:imported_pages, date: ~D[2019-01-01], page: "/blog", pageviews: 3)
+ ])
+
+ conn =
+ get(
+ conn,
+ "/api/stats/#{site.domain}/suggestions/page?period=month&date=2019-01-01&q=#{unquote(q)}&with_imported=true"
+ )
+
+ assert json_response(conn, 200) == [
+ %{"value" => "/blog", "label" => "/blog"},
+ %{"value" => "/welcome", "label" => "/welcome"}
+ ]
+ end
+ end
+
+ for {q, label} <- [{"", "without filter"}, {"o", "with filter"}] do
+ test "merges entry page suggestions from native and imported data #{label}", %{
+ conn: conn,
+ site: site,
+ site_import: site_import
+ } do
+ populate_stats(site, site_import.id, [
+ build(:pageview, timestamp: ~N[2019-01-01 23:00:01], pathname: "/welcome"),
+ build(:pageview, timestamp: ~N[2019-01-01 23:30:01], pathname: "/welcome"),
+ build(:pageview, timestamp: ~N[2019-01-01 23:40:01], pathname: "/welcome"),
+ build(:pageview, timestamp: ~N[2019-01-01 23:00:01], pathname: "/blog"),
+ build(:imported_entry_pages, date: ~D[2019-01-01], entry_page: "/blog", pageviews: 3)
+ ])
+
+ conn =
+ get(
+ conn,
+ "/api/stats/#{site.domain}/suggestions/entry_page?period=month&date=2019-01-01&q=#{unquote(q)}&with_imported=true"
+ )
+
+ assert json_response(conn, 200) == [
+ %{"value" => "/blog", "label" => "/blog"},
+ %{"value" => "/welcome", "label" => "/welcome"}
+ ]
+ end
+ end
+
+ for {q, label} <- [{"", "without filter"}, {"o", "with filter"}] do
+ test "merges exit page suggestions from native and imported data #{label}", %{
+ conn: conn,
+ site: site,
+ site_import: site_import
+ } do
+ populate_stats(site, site_import.id, [
+ build(:pageview, timestamp: ~N[2019-01-01 23:00:01], pathname: "/welcome"),
+ build(:pageview, timestamp: ~N[2019-01-01 23:30:01], pathname: "/welcome"),
+ build(:pageview, timestamp: ~N[2019-01-01 23:40:01], pathname: "/welcome"),
+ build(:pageview, timestamp: ~N[2019-01-01 23:00:01], pathname: "/blog"),
+ build(:imported_exit_pages, date: ~D[2019-01-01], exit_page: "/blog", pageviews: 3)
+ ])
+
+ conn =
+ get(
+ conn,
+ "/api/stats/#{site.domain}/suggestions/exit_page?period=month&date=2019-01-01&q=#{unquote(q)}&with_imported=true"
+ )
+
+ assert json_response(conn, 200) == [
+ %{"value" => "/blog", "label" => "/blog"},
+ %{"value" => "/welcome", "label" => "/welcome"}
+ ]
+ end
+ end
+
+ for {q, label} <- [{"", "without filter"}, {"o", "with filter"}] do
+ test "merges browser suggestions from native and imported data #{label}", %{
+ conn: conn,
+ site: site,
+ site_import: site_import
+ } do
+ populate_stats(site, site_import.id, [
+ build(:pageview, timestamp: ~N[2019-01-01 23:00:01], browser: "Chrome"),
+ build(:pageview, timestamp: ~N[2019-01-01 23:30:01], browser: "Chrome"),
+ build(:pageview, timestamp: ~N[2019-01-01 23:40:01], browser: "Chrome"),
+ build(:pageview, timestamp: ~N[2019-01-01 23:00:01], browser: "Firefox"),
+ build(:imported_browsers, date: ~D[2019-01-01], browser: "Firefox", pageviews: 3)
+ ])
+
+ conn =
+ get(
+ conn,
+ "/api/stats/#{site.domain}/suggestions/browser?period=month&date=2019-01-01&q=#{unquote(q)}&with_imported=true"
+ )
+
+ assert json_response(conn, 200) == [
+ %{"value" => "Firefox", "label" => "Firefox"},
+ %{"value" => "Chrome", "label" => "Chrome"}
+ ]
+ end
+ end
+
+ for {q, label} <- [{"", "without filter"}, {"i", "with filter"}] do
+ test "merges operating system suggestions from native and imported data #{label}", %{
+ conn: conn,
+ site: site,
+ site_import: site_import
+ } do
+ populate_stats(site, site_import.id, [
+ build(:pageview, timestamp: ~N[2019-01-01 23:00:01], operating_system: "Linux"),
+ build(:pageview, timestamp: ~N[2019-01-01 23:30:01], operating_system: "Linux"),
+ build(:pageview, timestamp: ~N[2019-01-01 23:40:01], operating_system: "Linux"),
+ build(:pageview, timestamp: ~N[2019-01-01 23:00:01], operating_system: "Windows"),
+ build(:imported_operating_systems,
+ date: ~D[2019-01-01],
+ operating_system: "Windows",
+ pageviews: 3
+ )
+ ])
+
+ conn =
+ get(
+ conn,
+ "/api/stats/#{site.domain}/suggestions/operating_system?period=month&date=2019-01-01&q=#{unquote(q)}&with_imported=true"
+ )
+
+ assert json_response(conn, 200) == [
+ %{"value" => "Windows", "label" => "Windows"},
+ %{"value" => "Linux", "label" => "Linux"}
+ ]
+ end
+ end
+
+ test "does not query imported data when a different property is filtered by", %{
+ conn: conn,
+ site: site,
+ site_import: site_import
+ } do
+ populate_stats(site, site_import.id, [
+ build(:pageview,
+ timestamp: ~N[2019-01-01 23:00:01],
+ pathname: "/blog",
+ operating_system: "Linux"
+ ),
+ build(:imported_operating_systems, date: ~D[2019-01-01], operating_system: "Windows")
+ ])
+
+ filters = Jason.encode!(%{page: "/blog"})
+
+ conn =
+ get(
+ conn,
+ "/api/stats/#{site.domain}/suggestions/operating_system?period=month&date=2019-01-01&filters=#{filters}&q=&with_imported=true"
+ )
+
+ assert json_response(conn, 200) == [%{"value" => "Linux", "label" => "Linux"}]
+ end
+
+ test "queries imported data when filtering by the same property", %{
+ conn: conn,
+ site: site,
+ site_import: site_import
+ } do
+ populate_stats(site, site_import.id, [
+ build(:pageview,
+ timestamp: ~N[2019-01-01 23:00:01],
+ pathname: "/blog",
+ operating_system: "Linux"
+ ),
+ build(:imported_operating_systems, date: ~D[2019-01-01], operating_system: "Windows"),
+ build(:imported_operating_systems, date: ~D[2019-01-01], operating_system: "Linux")
+ ])
+
+ filters = Jason.encode!(%{os: "!Linux"})
+
+ conn =
+ get(
+ conn,
+ "/api/stats/#{site.domain}/suggestions/operating_system?period=month&date=2019-01-01&filters=#{filters}&q=&with_imported=true"
+ )
+
+ assert json_response(conn, 200) == [%{"value" => "Windows", "label" => "Windows"}]
+ end
+ end
end
diff --git a/test/plausible_web/controllers/api/stats_controller/top_stats_test.exs b/test/plausible_web/controllers/api/stats_controller/top_stats_test.exs
index 4ec122086c76..6483b6619e54 100644
--- a/test/plausible_web/controllers/api/stats_controller/top_stats_test.exs
+++ b/test/plausible_web/controllers/api/stats_controller/top_stats_test.exs
@@ -530,6 +530,102 @@ defmodule PlausibleWeb.Api.StatsController.TopStatsTest do
%{"name" => "Visit duration", "value" => 303, "graph_metric" => "visit_duration"}
]
end
+
+ test ":member filter on country", %{
+ conn: conn,
+ site: site
+ } do
+ populate_stats(site, [
+ build(:pageview,
+ country_code: "EE",
+ user_id: @user_id,
+ timestamp: ~N[2021-01-01 00:00:00]
+ ),
+ build(:pageview,
+ country_code: "EE",
+ user_id: @user_id,
+ timestamp: ~N[2021-01-01 00:01:00]
+ ),
+ build(:imported_locations,
+ country: "EE",
+ date: ~D[2021-01-01],
+ visitors: 1,
+ visits: 3,
+ pageviews: 34,
+ bounces: 0,
+ visit_duration: 420
+ ),
+ build(:imported_locations,
+ country: "FR",
+ date: ~D[2021-01-01],
+ visitors: 3,
+ visits: 7,
+ pageviews: 65,
+ bounces: 1,
+ visit_duration: 300
+ ),
+ build(:imported_locations, country: "US", date: ~D[2021-01-01], visitors: 999)
+ ])
+
+ filters = Jason.encode!(%{country: "EE|FR"})
+ q = "?period=day&date=2021-01-01&with_imported=true&filters=#{filters}"
+
+ conn = get(conn, "/api/stats/#{site.domain}/top-stats#{q}")
+
+ res = json_response(conn, 200)
+
+ assert res["top_stats"] == [
+ %{"name" => "Unique visitors", "value" => 5, "graph_metric" => "visitors"},
+ %{"name" => "Total visits", "value" => 11, "graph_metric" => "visits"},
+ %{"name" => "Total pageviews", "value" => 101, "graph_metric" => "pageviews"},
+ %{
+ "name" => "Views per visit",
+ "value" => 9.18,
+ "graph_metric" => "views_per_visit"
+ },
+ %{"name" => "Bounce rate", "value" => 9, "graph_metric" => "bounce_rate"},
+ %{"name" => "Visit duration", "value" => 71, "graph_metric" => "visit_duration"}
+ ]
+ end
+
+ test ":is filter on page returns only visitors, visits and pageviews", %{
+ conn: conn,
+ site: site
+ } do
+ populate_stats(site, [
+ build(:pageview,
+ pathname: "/",
+ user_id: @user_id,
+ timestamp: ~N[2021-01-01 00:00:00]
+ ),
+ build(:pageview,
+ pathname: "/",
+ user_id: @user_id,
+ timestamp: ~N[2021-01-01 00:01:00]
+ ),
+ build(:imported_pages,
+ page: "/",
+ date: ~D[2021-01-01],
+ visitors: 1,
+ visits: 3,
+ pageviews: 34
+ ),
+ build(:imported_pages, page: "/ignored", date: ~D[2021-01-01], visitors: 999)
+ ])
+
+ filters = Jason.encode!(%{page: "/"})
+ q = "?period=day&date=2021-01-01&with_imported=true&filters=#{filters}"
+
+ conn = get(conn, "/api/stats/#{site.domain}/top-stats#{q}")
+
+ res = json_response(conn, 200)
+
+ assert res["top_stats"] == [
+ %{"name" => "Unique visitors", "value" => 2, "graph_metric" => "visitors"},
+ %{"name" => "Total visits", "value" => 4, "graph_metric" => "visits"},
+ %{"name" => "Total pageviews", "value" => 36, "graph_metric" => "pageviews"}
+ ]
+ end
end
describe "GET /api/stats/top-stats - realtime" do
@@ -1358,7 +1454,7 @@ defmodule PlausibleWeb.Api.StatsController.TopStatsTest do
"/api/stats/#{site.domain}/top-stats?period=month&date=2021-01-01&with_imported=true&comparison=year_over_year"
)
- assert %{"top_stats" => top_stats, "with_imported" => true} = json_response(conn, 200)
+ assert %{"top_stats" => top_stats, "includes_imported" => true} = json_response(conn, 200)
assert %{
"change" => 100,
@@ -1388,7 +1484,7 @@ defmodule PlausibleWeb.Api.StatsController.TopStatsTest do
"/api/stats/#{site.domain}/top-stats?period=month&date=2021-01-01&with_imported=false&comparison=year_over_year"
)
- assert %{"top_stats" => top_stats, "with_imported" => false} = json_response(conn, 200)
+ assert %{"top_stats" => top_stats, "includes_imported" => false} = json_response(conn, 200)
assert %{
"change" => 100,