mirror of
https://github.com/plausible/analytics.git
synced 2024-09-11 18:07:33 +03:00
New private API to return custom prop values (#3111)
* copy prop_breakdown tests into a separate file * add a new endpoint for custom props Duplicate existing goal prop breakdown as a building base for the new endpoint * stick to original metric names + CR definitions We currently use CR with two different definitions, which is inconsistent and should be changed. This commit just documents the difference for the time being. * basic prop breakdown without goal filter * increase % metric precision to one decimal place * add some tests without goal filter * silence credo for TODO comment * use events metric instead of pageviews * review feedback * inline add_cr_a instead
This commit is contained in:
parent
e6996ccfda
commit
34fbc3d5bc
@ -496,7 +496,7 @@ defmodule PlausibleWeb.Api.StatsController do
|
||||
|
||||
res =
|
||||
Stats.breakdown(site, query, "visit:source", metrics, pagination)
|
||||
|> maybe_add_cr(site, query, pagination, :source, "visit:source")
|
||||
|> add_cr(site, query, pagination, :source, "visit:source")
|
||||
|> transform_keys(%{source: :name})
|
||||
|
||||
if params["csv"] do
|
||||
@ -571,7 +571,7 @@ defmodule PlausibleWeb.Api.StatsController do
|
||||
|
||||
res =
|
||||
Stats.breakdown(site, query, "visit:utm_medium", metrics, pagination)
|
||||
|> maybe_add_cr(site, query, pagination, :utm_medium, "visit:utm_medium")
|
||||
|> add_cr(site, query, pagination, :utm_medium, "visit:utm_medium")
|
||||
|> transform_keys(%{utm_medium: :name})
|
||||
|
||||
if params["csv"] do
|
||||
@ -600,7 +600,7 @@ defmodule PlausibleWeb.Api.StatsController do
|
||||
|
||||
res =
|
||||
Stats.breakdown(site, query, "visit:utm_campaign", metrics, pagination)
|
||||
|> maybe_add_cr(site, query, pagination, :utm_campaign, "visit:utm_campaign")
|
||||
|> add_cr(site, query, pagination, :utm_campaign, "visit:utm_campaign")
|
||||
|> transform_keys(%{utm_campaign: :name})
|
||||
|
||||
if params["csv"] do
|
||||
@ -628,7 +628,7 @@ defmodule PlausibleWeb.Api.StatsController do
|
||||
|
||||
res =
|
||||
Stats.breakdown(site, query, "visit:utm_content", metrics, pagination)
|
||||
|> maybe_add_cr(site, query, pagination, :utm_content, "visit:utm_content")
|
||||
|> add_cr(site, query, pagination, :utm_content, "visit:utm_content")
|
||||
|> transform_keys(%{utm_content: :name})
|
||||
|
||||
if params["csv"] do
|
||||
@ -656,7 +656,7 @@ defmodule PlausibleWeb.Api.StatsController do
|
||||
|
||||
res =
|
||||
Stats.breakdown(site, query, "visit:utm_term", metrics, pagination)
|
||||
|> maybe_add_cr(site, query, pagination, :utm_term, "visit:utm_term")
|
||||
|> add_cr(site, query, pagination, :utm_term, "visit:utm_term")
|
||||
|> transform_keys(%{utm_term: :name})
|
||||
|
||||
if params["csv"] do
|
||||
@ -685,7 +685,7 @@ defmodule PlausibleWeb.Api.StatsController do
|
||||
|
||||
res =
|
||||
Stats.breakdown(site, query, "visit:utm_source", metrics, pagination)
|
||||
|> maybe_add_cr(site, query, pagination, :utm_source, "visit:utm_source")
|
||||
|> add_cr(site, query, pagination, :utm_source, "visit:utm_source")
|
||||
|> transform_keys(%{utm_source: :name})
|
||||
|
||||
if params["csv"] do
|
||||
@ -752,7 +752,7 @@ defmodule PlausibleWeb.Api.StatsController do
|
||||
|
||||
referrers =
|
||||
Stats.breakdown(site, query, "visit:referrer", metrics, pagination)
|
||||
|> maybe_add_cr(site, query, pagination, :referrer, "visit:referrer")
|
||||
|> add_cr(site, query, pagination, :referrer, "visit:referrer")
|
||||
|> transform_keys(%{referrer: :name})
|
||||
|> Enum.map(&Map.drop(&1, [:visits]))
|
||||
|
||||
@ -773,7 +773,7 @@ defmodule PlausibleWeb.Api.StatsController do
|
||||
|
||||
pages =
|
||||
Stats.breakdown(site, query, "event:page", metrics, pagination)
|
||||
|> maybe_add_cr(site, query, pagination, :page, "event:page")
|
||||
|> add_cr(site, query, pagination, :page, "event:page")
|
||||
|> transform_keys(%{page: :name})
|
||||
|
||||
if params["csv"] do
|
||||
@ -797,7 +797,7 @@ defmodule PlausibleWeb.Api.StatsController do
|
||||
|
||||
entry_pages =
|
||||
Stats.breakdown(site, query, "visit:entry_page", metrics, pagination)
|
||||
|> maybe_add_cr(site, query, pagination, :entry_page, "visit:entry_page")
|
||||
|> add_cr(site, query, pagination, :entry_page, "visit:entry_page")
|
||||
|> transform_keys(%{
|
||||
entry_page: :name,
|
||||
visitors: :unique_entrances,
|
||||
@ -825,7 +825,7 @@ defmodule PlausibleWeb.Api.StatsController do
|
||||
|
||||
exit_pages =
|
||||
Stats.breakdown(site, query, "visit:exit_page", metrics, {limit, page})
|
||||
|> maybe_add_cr(site, query, {limit, page}, :exit_page, "visit:exit_page")
|
||||
|> add_cr(site, query, {limit, page}, :exit_page, "visit:exit_page")
|
||||
|> transform_keys(%{
|
||||
exit_page: :name,
|
||||
visitors: :unique_exits,
|
||||
@ -879,9 +879,9 @@ defmodule PlausibleWeb.Api.StatsController do
|
||||
|
||||
countries =
|
||||
Stats.breakdown(site, query, "visit:country", [:visitors], pagination)
|
||||
|> maybe_add_cr(site, query, {300, 1}, :country, "visit:country")
|
||||
|> add_cr(site, query, {300, 1}, :country, "visit:country")
|
||||
|> transform_keys(%{country: :code})
|
||||
|> maybe_add_percentages(query)
|
||||
|> add_percentages(query)
|
||||
|
||||
if params["csv"] do
|
||||
countries =
|
||||
@ -1002,9 +1002,9 @@ defmodule PlausibleWeb.Api.StatsController do
|
||||
|
||||
browsers =
|
||||
Stats.breakdown(site, query, "visit:browser", [:visitors], pagination)
|
||||
|> maybe_add_cr(site, query, pagination, :browser, "visit:browser")
|
||||
|> add_cr(site, query, pagination, :browser, "visit:browser")
|
||||
|> transform_keys(%{browser: :name})
|
||||
|> maybe_add_percentages(query)
|
||||
|> add_percentages(query)
|
||||
|
||||
if params["csv"] do
|
||||
if Map.has_key?(query.filters, "event:goal") do
|
||||
@ -1026,9 +1026,9 @@ defmodule PlausibleWeb.Api.StatsController do
|
||||
|
||||
versions =
|
||||
Stats.breakdown(site, query, "visit:browser_version", [:visitors], pagination)
|
||||
|> maybe_add_cr(site, query, pagination, :browser_version, "visit:browser_version")
|
||||
|> add_cr(site, query, pagination, :browser_version, "visit:browser_version")
|
||||
|> transform_keys(%{browser_version: :name})
|
||||
|> maybe_add_percentages(query)
|
||||
|> add_percentages(query)
|
||||
|
||||
json(conn, versions)
|
||||
end
|
||||
@ -1040,9 +1040,9 @@ defmodule PlausibleWeb.Api.StatsController do
|
||||
|
||||
systems =
|
||||
Stats.breakdown(site, query, "visit:os", [:visitors], pagination)
|
||||
|> maybe_add_cr(site, query, pagination, :os, "visit:os")
|
||||
|> add_cr(site, query, pagination, :os, "visit:os")
|
||||
|> transform_keys(%{os: :name})
|
||||
|> maybe_add_percentages(query)
|
||||
|> add_percentages(query)
|
||||
|
||||
if params["csv"] do
|
||||
if Map.has_key?(query.filters, "event:goal") do
|
||||
@ -1064,9 +1064,9 @@ defmodule PlausibleWeb.Api.StatsController do
|
||||
|
||||
versions =
|
||||
Stats.breakdown(site, query, "visit:os_version", [:visitors], pagination)
|
||||
|> maybe_add_cr(site, query, pagination, :os_version, "visit:os_version")
|
||||
|> add_cr(site, query, pagination, :os_version, "visit:os_version")
|
||||
|> transform_keys(%{os_version: :name})
|
||||
|> maybe_add_percentages(query)
|
||||
|> add_percentages(query)
|
||||
|
||||
json(conn, versions)
|
||||
end
|
||||
@ -1078,9 +1078,9 @@ defmodule PlausibleWeb.Api.StatsController do
|
||||
|
||||
sizes =
|
||||
Stats.breakdown(site, query, "visit:device", [:visitors], pagination)
|
||||
|> maybe_add_cr(site, query, pagination, :device, "visit:device")
|
||||
|> add_cr(site, query, pagination, :device, "visit:device")
|
||||
|> transform_keys(%{device: :name})
|
||||
|> maybe_add_percentages(query)
|
||||
|> add_percentages(query)
|
||||
|
||||
if params["csv"] do
|
||||
if Map.has_key?(query.filters, "event:goal") do
|
||||
@ -1166,6 +1166,34 @@ defmodule PlausibleWeb.Api.StatsController do
|
||||
end
|
||||
end
|
||||
|
||||
def custom_prop_values(conn, params) do
|
||||
site = conn.assigns[:site]
|
||||
query = Query.from(site, params) |> Filters.add_prefix()
|
||||
pagination = parse_pagination(params)
|
||||
|
||||
total_q = Query.remove_event_filters(query, [:goal, :props])
|
||||
|
||||
%{:visitors => %{value: total_unique_visitors}} = Stats.aggregate(site, total_q, [:visitors])
|
||||
|
||||
prefixed_prop = "event:props:" <> params["prop_key"]
|
||||
|
||||
props =
|
||||
Stats.breakdown(site, query, prefixed_prop, [:visitors, :events], pagination)
|
||||
|> transform_keys(%{params["prop_key"] => :name})
|
||||
|> add_percentages(query)
|
||||
|
||||
props =
|
||||
if Map.has_key?(query.filters, "event:goal") do
|
||||
Enum.map(props, fn prop ->
|
||||
Map.put(prop, :conversion_rate, calculate_cr(total_unique_visitors, prop.visitors))
|
||||
end)
|
||||
else
|
||||
props
|
||||
end
|
||||
|
||||
json(conn, props)
|
||||
end
|
||||
|
||||
def prop_breakdown(conn, params) do
|
||||
site = conn.assigns[:site]
|
||||
query = Query.from(site, params) |> Filters.add_prefix()
|
||||
@ -1271,49 +1299,46 @@ defmodule PlausibleWeb.Api.StatsController do
|
||||
|
||||
defp to_int(_, default), do: default
|
||||
|
||||
defp maybe_add_percentages(stat_list, query) do
|
||||
if Map.has_key?(query.filters, "event:goal") do
|
||||
stat_list
|
||||
else
|
||||
total = Enum.reduce(stat_list, 0, fn %{visitors: count}, total -> total + count end)
|
||||
|
||||
Enum.map(stat_list, fn stat ->
|
||||
Map.put(stat, :percentage, round(stat[:visitors] / total * 100))
|
||||
end)
|
||||
end
|
||||
defp add_percentages([_ | _] = breakdown_result, query)
|
||||
when not is_map_key(query.filters, "event:goal") do
|
||||
total = Enum.reduce(breakdown_result, 0, fn %{visitors: count}, total -> total + count end)
|
||||
do_add_percentages(breakdown_result, total)
|
||||
end
|
||||
|
||||
defp add_cr(list, list_without_goals, key_name) do
|
||||
Enum.map(list, fn item ->
|
||||
without_goal = Enum.find(list_without_goals, fn s -> s[key_name] === item[key_name] end)
|
||||
defp add_percentages(breakdown_result, _), do: breakdown_result
|
||||
|
||||
item
|
||||
|> Map.put(:total_visitors, without_goal[:visitors])
|
||||
|> Map.put(:conversion_rate, calculate_cr(without_goal[:visitors], item[:visitors]))
|
||||
defp do_add_percentages(stat_list, total) do
|
||||
Enum.map(stat_list, fn stat ->
|
||||
Map.put(stat, :percentage, Float.round(stat.visitors / total * 100, 1))
|
||||
end)
|
||||
end
|
||||
|
||||
defp maybe_add_cr([], _site, _query, _pagination, _key_name, _filter_name), do: []
|
||||
defp add_cr([_ | _] = breakdown_results, site, query, pagination, key_name, filter_name)
|
||||
when is_map_key(query.filters, "event:goal") do
|
||||
items = Enum.map(breakdown_results, fn item -> Map.fetch!(item, key_name) end)
|
||||
|
||||
defp maybe_add_cr(list, site, query, pagination, key_name, filter_name) do
|
||||
if Map.has_key?(query.filters, "event:goal") do
|
||||
items = Enum.map(list, fn item -> item[key_name] end)
|
||||
query_without_goal =
|
||||
query
|
||||
|> Query.put_filter(filter_name, {:member, items})
|
||||
|> Query.remove_event_filters([:goal, :props])
|
||||
|
||||
query_without_goal =
|
||||
query
|
||||
|> Query.put_filter(filter_name, {:member, items})
|
||||
|> Query.remove_event_filters([:goal, :props])
|
||||
res_without_goal =
|
||||
Stats.breakdown(site, query_without_goal, filter_name, [:visitors], pagination)
|
||||
|
||||
res_without_goal =
|
||||
Stats.breakdown(site, query_without_goal, filter_name, [:visitors], pagination)
|
||||
Enum.map(breakdown_results, fn item ->
|
||||
without_goal =
|
||||
Enum.find(res_without_goal, fn s ->
|
||||
Map.fetch!(s, key_name) == Map.fetch!(item, key_name)
|
||||
end)
|
||||
|
||||
list
|
||||
|> add_cr(res_without_goal, key_name)
|
||||
else
|
||||
list
|
||||
end
|
||||
item
|
||||
|> Map.put(:total_visitors, without_goal.visitors)
|
||||
|> Map.put(:conversion_rate, calculate_cr(without_goal.visitors, item.visitors))
|
||||
end)
|
||||
end
|
||||
|
||||
defp add_cr(breakdown_results, _, _, _, _, _), do: breakdown_results
|
||||
|
||||
defp to_csv(list, headers) do
|
||||
list
|
||||
|> Enum.map(fn row -> Enum.map(headers, &row[&1]) end)
|
||||
|
@ -88,6 +88,7 @@ defmodule PlausibleWeb.Router do
|
||||
get "/:domain/operating-system-versions", StatsController, :operating_system_versions
|
||||
get "/:domain/screen-sizes", StatsController, :screen_sizes
|
||||
get "/:domain/conversions", StatsController, :conversions
|
||||
get "/:domain/custom-prop-values/:prop_key", StatsController, :custom_prop_values
|
||||
get "/:domain/property/:prop_name", StatsController, :prop_breakdown
|
||||
get "/:domain/suggestions/:filter_name", StatsController, :filter_suggestions
|
||||
end
|
||||
|
@ -988,9 +988,9 @@ defmodule Plausible.ImportedTest do
|
||||
|
||||
assert stats = json_response(conn, 200)
|
||||
assert length(stats) == 3
|
||||
assert %{"name" => "Firefox", "visitors" => 2, "percentage" => 50} in stats
|
||||
assert %{"name" => "Mobile App", "visitors" => 1, "percentage" => 25} in stats
|
||||
assert %{"name" => "Chrome", "visitors" => 1, "percentage" => 25} in stats
|
||||
assert %{"name" => "Firefox", "visitors" => 2, "percentage" => 50.0} in stats
|
||||
assert %{"name" => "Mobile App", "visitors" => 1, "percentage" => 25.0} in stats
|
||||
assert %{"name" => "Chrome", "visitors" => 1, "percentage" => 25.0} in stats
|
||||
end
|
||||
|
||||
test "OS data imported from Google Analytics", %{conn: conn, site: site} do
|
||||
|
@ -14,8 +14,8 @@ defmodule PlausibleWeb.Api.StatsController.BrowsersTest do
|
||||
conn = get(conn, "/api/stats/#{site.domain}/browsers?period=day")
|
||||
|
||||
assert json_response(conn, 200) == [
|
||||
%{"name" => "Chrome", "visitors" => 2, "percentage" => 67},
|
||||
%{"name" => "Firefox", "visitors" => 1, "percentage" => 33}
|
||||
%{"name" => "Chrome", "visitors" => 2, "percentage" => 66.7},
|
||||
%{"name" => "Firefox", "visitors" => 1, "percentage" => 33.3}
|
||||
]
|
||||
end
|
||||
|
||||
@ -125,8 +125,8 @@ defmodule PlausibleWeb.Api.StatsController.BrowsersTest do
|
||||
conn = get(conn, "/api/stats/#{site.domain}/browsers?period=day&with_imported=true")
|
||||
|
||||
assert json_response(conn, 200) == [
|
||||
%{"name" => "Chrome", "visitors" => 2, "percentage" => 67},
|
||||
%{"name" => "Firefox", "visitors" => 1, "percentage" => 33}
|
||||
%{"name" => "Chrome", "visitors" => 2, "percentage" => 66.7},
|
||||
%{"name" => "Firefox", "visitors" => 1, "percentage" => 33.3}
|
||||
]
|
||||
end
|
||||
|
||||
@ -162,7 +162,7 @@ defmodule PlausibleWeb.Api.StatsController.BrowsersTest do
|
||||
conn = get(conn, "/api/stats/#{site.domain}/browsers?period=day")
|
||||
|
||||
assert json_response(conn, 200) == [
|
||||
%{"name" => "(not set)", "visitors" => 1, "percentage" => 100}
|
||||
%{"name" => "(not set)", "visitors" => 1, "percentage" => 100.0}
|
||||
]
|
||||
end
|
||||
end
|
||||
@ -187,8 +187,8 @@ defmodule PlausibleWeb.Api.StatsController.BrowsersTest do
|
||||
)
|
||||
|
||||
assert json_response(conn, 200) == [
|
||||
%{"name" => "78.0", "visitors" => 2, "percentage" => 67},
|
||||
%{"name" => "77.0", "visitors" => 1, "percentage" => 33}
|
||||
%{"name" => "78.0", "visitors" => 2, "percentage" => 66.7},
|
||||
%{"name" => "77.0", "visitors" => 1, "percentage" => 33.3}
|
||||
]
|
||||
end
|
||||
|
||||
|
@ -813,662 +813,6 @@ defmodule PlausibleWeb.Api.StatsController.ConversionsTest do
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET /api/stats/:domain/property/:key" do
|
||||
setup [:create_user, :log_in, :create_new_site]
|
||||
|
||||
test "returns property breakdown for goal", %{conn: conn, site: site} do
|
||||
populate_stats(site, [
|
||||
build(:pageview, pathname: "/"),
|
||||
build(:pageview, pathname: "/"),
|
||||
build(:pageview, pathname: "/register"),
|
||||
build(:event, name: "Signup", "meta.key": ["variant"], "meta.value": ["A"]),
|
||||
build(:event, name: "Signup", "meta.key": ["variant"], "meta.value": ["B"]),
|
||||
build(:event, name: "Signup", "meta.key": ["variant"], "meta.value": ["B"])
|
||||
])
|
||||
|
||||
insert(:goal, %{site: site, event_name: "Signup"})
|
||||
filters = Jason.encode!(%{goal: "Signup"})
|
||||
prop_key = "variant"
|
||||
|
||||
conn =
|
||||
get(
|
||||
conn,
|
||||
"/api/stats/#{site.domain}/property/#{prop_key}?period=day&filters=#{filters}"
|
||||
)
|
||||
|
||||
assert json_response(conn, 200) == [
|
||||
%{
|
||||
"unique_conversions" => 2,
|
||||
"name" => "B",
|
||||
"total_conversions" => 2,
|
||||
"conversion_rate" => 33.3
|
||||
},
|
||||
%{
|
||||
"unique_conversions" => 1,
|
||||
"name" => "A",
|
||||
"total_conversions" => 1,
|
||||
"conversion_rate" => 16.7
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
test "returns property breakdown for revenue goal", %{conn: conn, site: site} do
|
||||
populate_stats(site, [
|
||||
build(:pageview, pathname: "/"),
|
||||
build(:pageview, pathname: "/"),
|
||||
build(:pageview, pathname: "/register"),
|
||||
build(:event,
|
||||
name: "Purchase",
|
||||
revenue_reporting_amount: Decimal.new("132.21"),
|
||||
revenue_reporting_currency: "EUR",
|
||||
"meta.key": ["method"],
|
||||
"meta.value": ["card"]
|
||||
),
|
||||
build(:event,
|
||||
name: "Purchase",
|
||||
revenue_reporting_amount: Decimal.new("412.30"),
|
||||
revenue_reporting_currency: "EUR",
|
||||
"meta.key": ["method"],
|
||||
"meta.value": ["cash"]
|
||||
),
|
||||
build(:event,
|
||||
name: "Purchase",
|
||||
revenue_reporting_amount: Decimal.new("30.23"),
|
||||
revenue_reporting_currency: "EUR",
|
||||
"meta.key": ["method"],
|
||||
"meta.value": ["cash"]
|
||||
)
|
||||
])
|
||||
|
||||
insert(:goal, site: site, event_name: "Purchase", currency: "EUR")
|
||||
filters = Jason.encode!(%{goal: "Purchase"})
|
||||
prop_key = "method"
|
||||
|
||||
conn =
|
||||
get(
|
||||
conn,
|
||||
"/api/stats/#{site.domain}/property/#{prop_key}?period=day&filters=#{filters}"
|
||||
)
|
||||
|
||||
assert json_response(conn, 200) == [
|
||||
%{
|
||||
"average_revenue" => %{"long" => "€221.26", "short" => "€221.3"},
|
||||
"conversion_rate" => 33.3,
|
||||
"name" => "cash",
|
||||
"total_conversions" => 2,
|
||||
"total_revenue" => %{"long" => "€442.53", "short" => "€442.5"},
|
||||
"unique_conversions" => 2
|
||||
},
|
||||
%{
|
||||
"average_revenue" => %{"long" => "€132.21", "short" => "€132.2"},
|
||||
"conversion_rate" => 16.7,
|
||||
"name" => "card",
|
||||
"total_conversions" => 1,
|
||||
"total_revenue" => %{"long" => "€132.21", "short" => "€132.2"},
|
||||
"unique_conversions" => 1
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
test "returns (none) values in property breakdown for goal", %{conn: conn, site: site} do
|
||||
populate_stats(site, [
|
||||
build(:pageview, pathname: "/"),
|
||||
build(:pageview, pathname: "/"),
|
||||
build(:pageview, pathname: "/register"),
|
||||
build(:event, name: "Signup"),
|
||||
build(:event, name: "Signup"),
|
||||
build(:event, name: "Signup", "meta.key": ["variant"], "meta.value": ["A"])
|
||||
])
|
||||
|
||||
insert(:goal, %{site: site, event_name: "Signup"})
|
||||
filters = Jason.encode!(%{goal: "Signup"})
|
||||
prop_key = "variant"
|
||||
|
||||
conn =
|
||||
get(
|
||||
conn,
|
||||
"/api/stats/#{site.domain}/property/#{prop_key}?period=day&filters=#{filters}"
|
||||
)
|
||||
|
||||
assert json_response(conn, 200) == [
|
||||
%{
|
||||
"unique_conversions" => 2,
|
||||
"name" => "(none)",
|
||||
"total_conversions" => 2,
|
||||
"conversion_rate" => 33.3
|
||||
},
|
||||
%{
|
||||
"unique_conversions" => 1,
|
||||
"name" => "A",
|
||||
"total_conversions" => 1,
|
||||
"conversion_rate" => 16.7
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
test "does not return (none) value in property breakdown with is filter on prop_value", %{
|
||||
conn: conn,
|
||||
site: site
|
||||
} do
|
||||
populate_stats(site, [
|
||||
build(:event,
|
||||
name: "Purchase",
|
||||
"meta.key": ["cost"],
|
||||
"meta.value": ["0"]
|
||||
),
|
||||
build(:event, name: "Purchase")
|
||||
])
|
||||
|
||||
insert(:goal, %{site: site, event_name: "Purchase"})
|
||||
|
||||
filters =
|
||||
Jason.encode!(%{
|
||||
goal: "Purchase",
|
||||
props: %{cost: "0"}
|
||||
})
|
||||
|
||||
conn =
|
||||
get(
|
||||
conn,
|
||||
"/api/stats/#{site.domain}/property/cost?period=day&filters=#{filters}"
|
||||
)
|
||||
|
||||
assert json_response(conn, 200) == [
|
||||
%{
|
||||
"name" => "0",
|
||||
"unique_conversions" => 1,
|
||||
"total_conversions" => 1,
|
||||
"conversion_rate" => 50.0
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
test "returns only (none) value in property breakdown with is (none) filter", %{
|
||||
conn: conn,
|
||||
site: site
|
||||
} do
|
||||
populate_stats(site, [
|
||||
build(:event,
|
||||
name: "Purchase",
|
||||
"meta.key": ["cost"],
|
||||
"meta.value": ["0"]
|
||||
),
|
||||
build(:event, name: "Purchase")
|
||||
])
|
||||
|
||||
insert(:goal, %{site: site, event_name: "Purchase"})
|
||||
|
||||
filters =
|
||||
Jason.encode!(%{
|
||||
goal: "Purchase",
|
||||
props: %{cost: "(none)"}
|
||||
})
|
||||
|
||||
conn =
|
||||
get(
|
||||
conn,
|
||||
"/api/stats/#{site.domain}/property/cost?period=day&filters=#{filters}"
|
||||
)
|
||||
|
||||
assert json_response(conn, 200) == [
|
||||
%{
|
||||
"name" => "(none)",
|
||||
"unique_conversions" => 1,
|
||||
"total_conversions" => 1,
|
||||
"conversion_rate" => 50.0
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
test "returns (none) value in property breakdown with is_not filter on prop_value", %{
|
||||
conn: conn,
|
||||
site: site
|
||||
} do
|
||||
populate_stats(site, [
|
||||
build(:event,
|
||||
name: "Purchase",
|
||||
"meta.key": ["cost"],
|
||||
"meta.value": ["0"]
|
||||
),
|
||||
build(:event,
|
||||
name: "Purchase",
|
||||
"meta.key": ["cost"],
|
||||
"meta.value": ["20"]
|
||||
),
|
||||
build(:event,
|
||||
name: "Purchase",
|
||||
"meta.key": ["cost"],
|
||||
"meta.value": ["20"]
|
||||
),
|
||||
build(:event, name: "Purchase")
|
||||
])
|
||||
|
||||
insert(:goal, %{site: site, event_name: "Purchase"})
|
||||
|
||||
filters =
|
||||
Jason.encode!(%{
|
||||
goal: "Purchase",
|
||||
props: %{cost: "!0"}
|
||||
})
|
||||
|
||||
conn =
|
||||
get(
|
||||
conn,
|
||||
"/api/stats/#{site.domain}/property/cost?period=day&filters=#{filters}"
|
||||
)
|
||||
|
||||
assert json_response(conn, 200) == [
|
||||
%{
|
||||
"name" => "20",
|
||||
"unique_conversions" => 2,
|
||||
"total_conversions" => 2,
|
||||
"conversion_rate" => 50.0
|
||||
},
|
||||
%{
|
||||
"name" => "(none)",
|
||||
"unique_conversions" => 1,
|
||||
"total_conversions" => 1,
|
||||
"conversion_rate" => 25.0
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
test "does not return (none) value in property breakdown with is_not (none) filter", %{
|
||||
conn: conn,
|
||||
site: site
|
||||
} do
|
||||
populate_stats(site, [
|
||||
build(:event,
|
||||
name: "Purchase",
|
||||
"meta.key": ["cost"],
|
||||
"meta.value": ["0"]
|
||||
),
|
||||
build(:event, name: "Purchase")
|
||||
])
|
||||
|
||||
insert(:goal, %{site: site, event_name: "Purchase"})
|
||||
|
||||
filters =
|
||||
Jason.encode!(%{
|
||||
goal: "Purchase",
|
||||
props: %{cost: "!(none)"}
|
||||
})
|
||||
|
||||
conn =
|
||||
get(
|
||||
conn,
|
||||
"/api/stats/#{site.domain}/property/cost?period=day&filters=#{filters}"
|
||||
)
|
||||
|
||||
assert json_response(conn, 200) == [
|
||||
%{
|
||||
"name" => "0",
|
||||
"unique_conversions" => 1,
|
||||
"total_conversions" => 1,
|
||||
"conversion_rate" => 50.0
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
test "does not return (none) value in property breakdown with member filter on prop_value", %{
|
||||
conn: conn,
|
||||
site: site
|
||||
} do
|
||||
populate_stats(site, [
|
||||
build(:event,
|
||||
name: "Purchase",
|
||||
"meta.key": ["cost"],
|
||||
"meta.value": ["0"]
|
||||
),
|
||||
build(:event,
|
||||
name: "Purchase",
|
||||
"meta.key": ["cost"],
|
||||
"meta.value": ["1"]
|
||||
),
|
||||
build(:event,
|
||||
name: "Purchase",
|
||||
"meta.key": ["cost"],
|
||||
"meta.value": ["1"]
|
||||
),
|
||||
build(:event, name: "Purchase")
|
||||
])
|
||||
|
||||
insert(:goal, %{site: site, event_name: "Purchase"})
|
||||
|
||||
filters =
|
||||
Jason.encode!(%{
|
||||
goal: "Purchase",
|
||||
props: %{cost: "0|1"}
|
||||
})
|
||||
|
||||
conn =
|
||||
get(
|
||||
conn,
|
||||
"/api/stats/#{site.domain}/property/cost?period=day&filters=#{filters}"
|
||||
)
|
||||
|
||||
assert json_response(conn, 200) == [
|
||||
%{
|
||||
"name" => "1",
|
||||
"unique_conversions" => 2,
|
||||
"total_conversions" => 2,
|
||||
"conversion_rate" => 50.0
|
||||
},
|
||||
%{
|
||||
"name" => "0",
|
||||
"unique_conversions" => 1,
|
||||
"total_conversions" => 1,
|
||||
"conversion_rate" => 25.0
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
test "returns (none) value in property breakdown with member filter including a (none) value",
|
||||
%{conn: conn, site: site} do
|
||||
populate_stats(site, [
|
||||
build(:event,
|
||||
name: "Purchase",
|
||||
"meta.key": ["cost"],
|
||||
"meta.value": ["0"]
|
||||
),
|
||||
build(:event,
|
||||
name: "Purchase",
|
||||
"meta.key": ["cost"],
|
||||
"meta.value": ["1"]
|
||||
),
|
||||
build(:event,
|
||||
name: "Purchase",
|
||||
"meta.key": ["cost"],
|
||||
"meta.value": ["1"]
|
||||
),
|
||||
build(:event, name: "Purchase")
|
||||
])
|
||||
|
||||
insert(:goal, %{site: site, event_name: "Purchase"})
|
||||
|
||||
filters =
|
||||
Jason.encode!(%{
|
||||
goal: "Purchase",
|
||||
props: %{cost: "1|(none)"}
|
||||
})
|
||||
|
||||
conn =
|
||||
get(
|
||||
conn,
|
||||
"/api/stats/#{site.domain}/property/cost?period=day&filters=#{filters}"
|
||||
)
|
||||
|
||||
assert json_response(conn, 200) == [
|
||||
%{
|
||||
"name" => "1",
|
||||
"unique_conversions" => 2,
|
||||
"total_conversions" => 2,
|
||||
"conversion_rate" => 50.0
|
||||
},
|
||||
%{
|
||||
"name" => "(none)",
|
||||
"unique_conversions" => 1,
|
||||
"total_conversions" => 1,
|
||||
"conversion_rate" => 25.0
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
test "returns (none) value in property breakdown with not_member filter on prop_value", %{
|
||||
conn: conn,
|
||||
site: site
|
||||
} do
|
||||
populate_stats(site, [
|
||||
build(:event,
|
||||
name: "Purchase",
|
||||
"meta.key": ["cost"],
|
||||
"meta.value": ["0"]
|
||||
),
|
||||
build(:event,
|
||||
name: "Purchase",
|
||||
"meta.key": ["cost"],
|
||||
"meta.value": ["0.01"]
|
||||
),
|
||||
build(:event,
|
||||
name: "Purchase",
|
||||
"meta.key": ["cost"],
|
||||
"meta.value": ["20"]
|
||||
),
|
||||
build(:event,
|
||||
name: "Purchase",
|
||||
"meta.key": ["cost"],
|
||||
"meta.value": ["20"]
|
||||
),
|
||||
build(:event, name: "Purchase")
|
||||
])
|
||||
|
||||
insert(:goal, %{site: site, event_name: "Purchase"})
|
||||
|
||||
filters =
|
||||
Jason.encode!(%{
|
||||
goal: "Purchase",
|
||||
props: %{cost: "!0|0.01"}
|
||||
})
|
||||
|
||||
conn =
|
||||
get(
|
||||
conn,
|
||||
"/api/stats/#{site.domain}/property/cost?period=day&filters=#{filters}"
|
||||
)
|
||||
|
||||
assert json_response(conn, 200) == [
|
||||
%{
|
||||
"name" => "20",
|
||||
"unique_conversions" => 2,
|
||||
"total_conversions" => 2,
|
||||
"conversion_rate" => 40.0
|
||||
},
|
||||
%{
|
||||
"name" => "(none)",
|
||||
"unique_conversions" => 1,
|
||||
"total_conversions" => 1,
|
||||
"conversion_rate" => 20.0
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
test "does not return (none) value in property breakdown with not_member filter including a (none) value",
|
||||
%{conn: conn, site: site} do
|
||||
populate_stats(site, [
|
||||
build(:event,
|
||||
name: "Purchase",
|
||||
"meta.key": ["cost"],
|
||||
"meta.value": ["0"]
|
||||
),
|
||||
build(:event,
|
||||
name: "Purchase",
|
||||
"meta.key": ["cost"],
|
||||
"meta.value": ["20"]
|
||||
),
|
||||
build(:event,
|
||||
name: "Purchase",
|
||||
"meta.key": ["cost"],
|
||||
"meta.value": ["20"]
|
||||
),
|
||||
build(:event, name: "Purchase")
|
||||
])
|
||||
|
||||
insert(:goal, %{site: site, event_name: "Purchase"})
|
||||
|
||||
filters =
|
||||
Jason.encode!(%{
|
||||
goal: "Purchase",
|
||||
props: %{cost: "!0|(none)"}
|
||||
})
|
||||
|
||||
conn =
|
||||
get(
|
||||
conn,
|
||||
"/api/stats/#{site.domain}/property/cost?period=day&filters=#{filters}"
|
||||
)
|
||||
|
||||
assert json_response(conn, 200) == [
|
||||
%{
|
||||
"name" => "20",
|
||||
"unique_conversions" => 2,
|
||||
"total_conversions" => 2,
|
||||
"conversion_rate" => 50.0
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
test "returns property breakdown with a pageview goal filter", %{conn: conn, site: site} do
|
||||
populate_stats(site, [
|
||||
build(:pageview, pathname: "/"),
|
||||
build(:pageview, pathname: "/register"),
|
||||
build(:pageview, pathname: "/register", "meta.key": ["variant"], "meta.value": ["A"]),
|
||||
build(:pageview, pathname: "/register", "meta.key": ["variant"], "meta.value": ["A"])
|
||||
])
|
||||
|
||||
insert(:goal, %{site: site, page_path: "/register"})
|
||||
filters = Jason.encode!(%{goal: "Visit /register"})
|
||||
|
||||
conn =
|
||||
get(
|
||||
conn,
|
||||
"/api/stats/#{site.domain}/property/variant?period=day&filters=#{filters}"
|
||||
)
|
||||
|
||||
assert json_response(conn, 200) == [
|
||||
%{
|
||||
"unique_conversions" => 2,
|
||||
"name" => "A",
|
||||
"total_conversions" => 2,
|
||||
"conversion_rate" => 50.0
|
||||
},
|
||||
%{
|
||||
"unique_conversions" => 1,
|
||||
"name" => "(none)",
|
||||
"total_conversions" => 1,
|
||||
"conversion_rate" => 25.0
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
test "property breakdown with prop filter", %{conn: conn, site: site} do
|
||||
populate_stats(site, [
|
||||
build(:pageview, user_id: 1),
|
||||
build(:event, user_id: 1, name: "Signup", "meta.key": ["variant"], "meta.value": ["A"]),
|
||||
build(:pageview, user_id: 2),
|
||||
build(:event, user_id: 2, name: "Signup", "meta.key": ["variant"], "meta.value": ["B"])
|
||||
])
|
||||
|
||||
insert(:goal, %{site: site, event_name: "Signup"})
|
||||
filters = Jason.encode!(%{goal: "Signup", props: %{"variant" => "B"}})
|
||||
prop_key = "variant"
|
||||
|
||||
conn =
|
||||
get(
|
||||
conn,
|
||||
"/api/stats/#{site.domain}/property/#{prop_key}?period=day&filters=#{filters}"
|
||||
)
|
||||
|
||||
assert json_response(conn, 200) == [
|
||||
%{
|
||||
"unique_conversions" => 1,
|
||||
"name" => "B",
|
||||
"total_conversions" => 1,
|
||||
"conversion_rate" => 50.0
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
test "Property breakdown with prop and goal filter", %{conn: conn, site: site} do
|
||||
populate_stats(site, [
|
||||
build(:pageview, user_id: 1, utm_campaign: "campaignA"),
|
||||
build(:event,
|
||||
user_id: 1,
|
||||
name: "ButtonClick",
|
||||
"meta.key": ["variant"],
|
||||
"meta.value": ["A"]
|
||||
),
|
||||
build(:pageview, user_id: 2, utm_campaign: "campaignA"),
|
||||
build(:event,
|
||||
user_id: 2,
|
||||
name: "ButtonClick",
|
||||
"meta.key": ["variant"],
|
||||
"meta.value": ["B"]
|
||||
)
|
||||
])
|
||||
|
||||
insert(:goal, %{site: site, event_name: "ButtonClick"})
|
||||
|
||||
filters =
|
||||
Jason.encode!(%{
|
||||
goal: "ButtonClick",
|
||||
props: %{variant: "A"},
|
||||
utm_campaign: "campaignA"
|
||||
})
|
||||
|
||||
prop_key = "variant"
|
||||
|
||||
conn =
|
||||
get(
|
||||
conn,
|
||||
"/api/stats/#{site.domain}/property/#{prop_key}?period=day&filters=#{filters}"
|
||||
)
|
||||
|
||||
assert json_response(conn, 200) == [
|
||||
%{
|
||||
"name" => "A",
|
||||
"unique_conversions" => 1,
|
||||
"total_conversions" => 1,
|
||||
"conversion_rate" => 50.0
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
test "Property breakdown with goal and source filter", %{conn: conn, site: site} do
|
||||
populate_stats(site, [
|
||||
build(:pageview, user_id: 1, referrer_source: "Google"),
|
||||
build(:event,
|
||||
user_id: 1,
|
||||
name: "ButtonClick",
|
||||
"meta.key": ["variant"],
|
||||
"meta.value": ["A"]
|
||||
),
|
||||
build(:pageview, user_id: 2, referrer_source: "Google"),
|
||||
build(:pageview, user_id: 3, referrer_source: "ignore"),
|
||||
build(:event,
|
||||
user_id: 3,
|
||||
name: "ButtonClick",
|
||||
"meta.key": ["variant"],
|
||||
"meta.value": ["B"]
|
||||
)
|
||||
])
|
||||
|
||||
insert(:goal, %{site: site, event_name: "ButtonClick"})
|
||||
|
||||
filters =
|
||||
Jason.encode!(%{
|
||||
goal: "ButtonClick",
|
||||
source: "Google"
|
||||
})
|
||||
|
||||
prop_key = "variant"
|
||||
|
||||
conn =
|
||||
get(
|
||||
conn,
|
||||
"/api/stats/#{site.domain}/property/#{prop_key}?period=day&filters=#{filters}"
|
||||
)
|
||||
|
||||
assert json_response(conn, 200) == [
|
||||
%{
|
||||
"name" => "A",
|
||||
"unique_conversions" => 1,
|
||||
"total_conversions" => 1,
|
||||
"conversion_rate" => 50.0
|
||||
}
|
||||
]
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET /api/stats/:domain/conversions - with glob goals" do
|
||||
setup [:create_user, :log_in, :create_site]
|
||||
|
||||
|
@ -32,7 +32,7 @@ defmodule PlausibleWeb.Api.StatsController.CountriesTest do
|
||||
"name" => "Estonia",
|
||||
"flag" => "🇪🇪",
|
||||
"visitors" => 2,
|
||||
"percentage" => 67
|
||||
"percentage" => 66.7
|
||||
},
|
||||
%{
|
||||
"code" => "GB",
|
||||
@ -40,7 +40,7 @@ defmodule PlausibleWeb.Api.StatsController.CountriesTest do
|
||||
"name" => "United Kingdom",
|
||||
"flag" => "🇬🇧",
|
||||
"visitors" => 1,
|
||||
"percentage" => 33
|
||||
"percentage" => 33.3
|
||||
}
|
||||
]
|
||||
|
||||
|
@ -0,0 +1,795 @@
|
||||
defmodule PlausibleWeb.Api.StatsController.CustomPropBreakdownTest do
|
||||
use PlausibleWeb.ConnCase
|
||||
|
||||
describe "GET /api/stats/:domain/custom-prop-values/:prop_key" do
|
||||
setup [:create_user, :log_in, :create_new_site]
|
||||
|
||||
test "returns breakdown by a custom property", %{conn: conn, site: site} do
|
||||
prop_key = "parim_s6ber"
|
||||
|
||||
populate_stats(site, [
|
||||
build(:pageview, "meta.key": [prop_key], "meta.value": ["K2sna Kalle"]),
|
||||
build(:pageview, "meta.key": [prop_key], "meta.value": ["K2sna Kalle"]),
|
||||
build(:pageview, user_id: 123, "meta.key": [prop_key], "meta.value": ["Lotte"]),
|
||||
build(:pageview, user_id: 123, "meta.key": [prop_key], "meta.value": ["Lotte"]),
|
||||
build(:pageview, "meta.key": [prop_key], "meta.value": ["Sipsik"])
|
||||
])
|
||||
|
||||
conn =
|
||||
get(
|
||||
conn,
|
||||
"/api/stats/#{site.domain}/custom-prop-values/#{prop_key}?period=day"
|
||||
)
|
||||
|
||||
assert json_response(conn, 200) == [
|
||||
%{
|
||||
"visitors" => 2,
|
||||
"name" => "K2sna Kalle",
|
||||
"events" => 2,
|
||||
"percentage" => 50.0
|
||||
},
|
||||
%{
|
||||
"visitors" => 1,
|
||||
"name" => "Lotte",
|
||||
"events" => 2,
|
||||
"percentage" => 25.0
|
||||
},
|
||||
%{
|
||||
"visitors" => 1,
|
||||
"name" => "Sipsik",
|
||||
"events" => 1,
|
||||
"percentage" => 25.0
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
test "returns (none) values in the breakdown", %{conn: conn, site: site} do
|
||||
prop_key = "parim_s6ber"
|
||||
|
||||
populate_stats(site, [
|
||||
build(:pageview, "meta.key": [prop_key], "meta.value": ["K2sna Kalle"]),
|
||||
build(:pageview, "meta.key": [prop_key], "meta.value": ["K2sna Kalle"]),
|
||||
build(:pageview)
|
||||
])
|
||||
|
||||
conn =
|
||||
get(
|
||||
conn,
|
||||
"/api/stats/#{site.domain}/custom-prop-values/#{prop_key}?period=day"
|
||||
)
|
||||
|
||||
assert json_response(conn, 200) == [
|
||||
%{
|
||||
"visitors" => 2,
|
||||
"name" => "K2sna Kalle",
|
||||
"events" => 2,
|
||||
"percentage" => 66.7
|
||||
},
|
||||
%{
|
||||
"visitors" => 1,
|
||||
"name" => "(none)",
|
||||
"events" => 1,
|
||||
"percentage" => 33.3
|
||||
}
|
||||
]
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET /api/stats/:domain/custom-prop-values/:prop_key - with goal filter" do
|
||||
setup [:create_user, :log_in, :create_new_site]
|
||||
|
||||
test "returns property breakdown for goal", %{conn: conn, site: site} do
|
||||
populate_stats(site, [
|
||||
build(:pageview, pathname: "/"),
|
||||
build(:pageview, pathname: "/"),
|
||||
build(:pageview, pathname: "/register"),
|
||||
build(:event, name: "Signup", "meta.key": ["variant"], "meta.value": ["A"]),
|
||||
build(:event, name: "Signup", "meta.key": ["variant"], "meta.value": ["B"]),
|
||||
build(:event, name: "Signup", "meta.key": ["variant"], "meta.value": ["B"])
|
||||
])
|
||||
|
||||
insert(:goal, %{site: site, event_name: "Signup"})
|
||||
filters = Jason.encode!(%{goal: "Signup"})
|
||||
prop_key = "variant"
|
||||
|
||||
conn =
|
||||
get(
|
||||
conn,
|
||||
"/api/stats/#{site.domain}/custom-prop-values/#{prop_key}?period=day&filters=#{filters}"
|
||||
)
|
||||
|
||||
assert json_response(conn, 200) == [
|
||||
%{
|
||||
"visitors" => 2,
|
||||
"name" => "B",
|
||||
"events" => 2,
|
||||
"conversion_rate" => 33.3
|
||||
},
|
||||
%{
|
||||
"visitors" => 1,
|
||||
"name" => "A",
|
||||
"events" => 1,
|
||||
"conversion_rate" => 16.7
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
test "returns (none) values in property breakdown for goal", %{conn: conn, site: site} do
|
||||
populate_stats(site, [
|
||||
build(:pageview, pathname: "/"),
|
||||
build(:pageview, pathname: "/"),
|
||||
build(:pageview, pathname: "/register"),
|
||||
build(:event, name: "Signup"),
|
||||
build(:event, name: "Signup"),
|
||||
build(:event, name: "Signup", "meta.key": ["variant"], "meta.value": ["A"])
|
||||
])
|
||||
|
||||
insert(:goal, %{site: site, event_name: "Signup"})
|
||||
filters = Jason.encode!(%{goal: "Signup"})
|
||||
prop_key = "variant"
|
||||
|
||||
conn =
|
||||
get(
|
||||
conn,
|
||||
"/api/stats/#{site.domain}/custom-prop-values/#{prop_key}?period=day&filters=#{filters}"
|
||||
)
|
||||
|
||||
assert json_response(conn, 200) == [
|
||||
%{
|
||||
"visitors" => 2,
|
||||
"name" => "(none)",
|
||||
"events" => 2,
|
||||
"conversion_rate" => 33.3
|
||||
},
|
||||
%{
|
||||
"visitors" => 1,
|
||||
"name" => "A",
|
||||
"events" => 1,
|
||||
"conversion_rate" => 16.7
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
test "does not return (none) value in property breakdown with is filter on prop_value", %{
|
||||
conn: conn,
|
||||
site: site
|
||||
} do
|
||||
populate_stats(site, [
|
||||
build(:event,
|
||||
name: "Purchase",
|
||||
"meta.key": ["cost"],
|
||||
"meta.value": ["0"]
|
||||
),
|
||||
build(:event, name: "Purchase")
|
||||
])
|
||||
|
||||
insert(:goal, %{site: site, event_name: "Purchase"})
|
||||
|
||||
filters =
|
||||
Jason.encode!(%{
|
||||
goal: "Purchase",
|
||||
props: %{cost: "0"}
|
||||
})
|
||||
|
||||
conn =
|
||||
get(
|
||||
conn,
|
||||
"/api/stats/#{site.domain}/custom-prop-values/cost?period=day&filters=#{filters}"
|
||||
)
|
||||
|
||||
assert json_response(conn, 200) == [
|
||||
%{
|
||||
"name" => "0",
|
||||
"visitors" => 1,
|
||||
"events" => 1,
|
||||
"conversion_rate" => 50.0
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
test "returns only (none) value in property breakdown with is (none) filter", %{
|
||||
conn: conn,
|
||||
site: site
|
||||
} do
|
||||
populate_stats(site, [
|
||||
build(:event,
|
||||
name: "Purchase",
|
||||
"meta.key": ["cost"],
|
||||
"meta.value": ["0"]
|
||||
),
|
||||
build(:event, name: "Purchase")
|
||||
])
|
||||
|
||||
insert(:goal, %{site: site, event_name: "Purchase"})
|
||||
|
||||
filters =
|
||||
Jason.encode!(%{
|
||||
goal: "Purchase",
|
||||
props: %{cost: "(none)"}
|
||||
})
|
||||
|
||||
conn =
|
||||
get(
|
||||
conn,
|
||||
"/api/stats/#{site.domain}/custom-prop-values/cost?period=day&filters=#{filters}"
|
||||
)
|
||||
|
||||
assert json_response(conn, 200) == [
|
||||
%{
|
||||
"name" => "(none)",
|
||||
"visitors" => 1,
|
||||
"events" => 1,
|
||||
"conversion_rate" => 50.0
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
test "returns (none) value in property breakdown with is_not filter on prop_value", %{
|
||||
conn: conn,
|
||||
site: site
|
||||
} do
|
||||
populate_stats(site, [
|
||||
build(:event,
|
||||
name: "Purchase",
|
||||
"meta.key": ["cost"],
|
||||
"meta.value": ["0"]
|
||||
),
|
||||
build(:event,
|
||||
name: "Purchase",
|
||||
"meta.key": ["cost"],
|
||||
"meta.value": ["20"]
|
||||
),
|
||||
build(:event,
|
||||
name: "Purchase",
|
||||
"meta.key": ["cost"],
|
||||
"meta.value": ["20"]
|
||||
),
|
||||
build(:event, name: "Purchase")
|
||||
])
|
||||
|
||||
insert(:goal, %{site: site, event_name: "Purchase"})
|
||||
|
||||
filters =
|
||||
Jason.encode!(%{
|
||||
goal: "Purchase",
|
||||
props: %{cost: "!0"}
|
||||
})
|
||||
|
||||
conn =
|
||||
get(
|
||||
conn,
|
||||
"/api/stats/#{site.domain}/custom-prop-values/cost?period=day&filters=#{filters}"
|
||||
)
|
||||
|
||||
assert json_response(conn, 200) == [
|
||||
%{
|
||||
"name" => "20",
|
||||
"visitors" => 2,
|
||||
"events" => 2,
|
||||
"conversion_rate" => 50.0
|
||||
},
|
||||
%{
|
||||
"name" => "(none)",
|
||||
"visitors" => 1,
|
||||
"events" => 1,
|
||||
"conversion_rate" => 25.0
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
test "does not return (none) value in property breakdown with is_not (none) filter", %{
|
||||
conn: conn,
|
||||
site: site
|
||||
} do
|
||||
populate_stats(site, [
|
||||
build(:event,
|
||||
name: "Purchase",
|
||||
"meta.key": ["cost"],
|
||||
"meta.value": ["0"]
|
||||
),
|
||||
build(:event, name: "Purchase")
|
||||
])
|
||||
|
||||
insert(:goal, %{site: site, event_name: "Purchase"})
|
||||
|
||||
filters =
|
||||
Jason.encode!(%{
|
||||
goal: "Purchase",
|
||||
props: %{cost: "!(none)"}
|
||||
})
|
||||
|
||||
conn =
|
||||
get(
|
||||
conn,
|
||||
"/api/stats/#{site.domain}/custom-prop-values/cost?period=day&filters=#{filters}"
|
||||
)
|
||||
|
||||
assert json_response(conn, 200) == [
|
||||
%{
|
||||
"name" => "0",
|
||||
"visitors" => 1,
|
||||
"events" => 1,
|
||||
"conversion_rate" => 50.0
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
test "does not return (none) value in property breakdown with member filter on prop_value", %{
|
||||
conn: conn,
|
||||
site: site
|
||||
} do
|
||||
populate_stats(site, [
|
||||
build(:event,
|
||||
name: "Purchase",
|
||||
"meta.key": ["cost"],
|
||||
"meta.value": ["0"]
|
||||
),
|
||||
build(:event,
|
||||
name: "Purchase",
|
||||
"meta.key": ["cost"],
|
||||
"meta.value": ["1"]
|
||||
),
|
||||
build(:event,
|
||||
name: "Purchase",
|
||||
"meta.key": ["cost"],
|
||||
"meta.value": ["1"]
|
||||
),
|
||||
build(:event, name: "Purchase")
|
||||
])
|
||||
|
||||
insert(:goal, %{site: site, event_name: "Purchase"})
|
||||
|
||||
filters =
|
||||
Jason.encode!(%{
|
||||
goal: "Purchase",
|
||||
props: %{cost: "0|1"}
|
||||
})
|
||||
|
||||
conn =
|
||||
get(
|
||||
conn,
|
||||
"/api/stats/#{site.domain}/custom-prop-values/cost?period=day&filters=#{filters}"
|
||||
)
|
||||
|
||||
assert json_response(conn, 200) == [
|
||||
%{
|
||||
"name" => "1",
|
||||
"visitors" => 2,
|
||||
"events" => 2,
|
||||
"conversion_rate" => 50.0
|
||||
},
|
||||
%{
|
||||
"name" => "0",
|
||||
"visitors" => 1,
|
||||
"events" => 1,
|
||||
"conversion_rate" => 25.0
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
test "returns (none) value in property breakdown with member filter including a (none) value",
|
||||
%{conn: conn, site: site} do
|
||||
populate_stats(site, [
|
||||
build(:event,
|
||||
name: "Purchase",
|
||||
"meta.key": ["cost"],
|
||||
"meta.value": ["0"]
|
||||
),
|
||||
build(:event,
|
||||
name: "Purchase",
|
||||
"meta.key": ["cost"],
|
||||
"meta.value": ["1"]
|
||||
),
|
||||
build(:event,
|
||||
name: "Purchase",
|
||||
"meta.key": ["cost"],
|
||||
"meta.value": ["1"]
|
||||
),
|
||||
build(:event, name: "Purchase")
|
||||
])
|
||||
|
||||
insert(:goal, %{site: site, event_name: "Purchase"})
|
||||
|
||||
filters =
|
||||
Jason.encode!(%{
|
||||
goal: "Purchase",
|
||||
props: %{cost: "1|(none)"}
|
||||
})
|
||||
|
||||
conn =
|
||||
get(
|
||||
conn,
|
||||
"/api/stats/#{site.domain}/custom-prop-values/cost?period=day&filters=#{filters}"
|
||||
)
|
||||
|
||||
assert json_response(conn, 200) == [
|
||||
%{
|
||||
"name" => "1",
|
||||
"visitors" => 2,
|
||||
"events" => 2,
|
||||
"conversion_rate" => 50.0
|
||||
},
|
||||
%{
|
||||
"name" => "(none)",
|
||||
"visitors" => 1,
|
||||
"events" => 1,
|
||||
"conversion_rate" => 25.0
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
test "returns (none) value in property breakdown with not_member filter on prop_value", %{
|
||||
conn: conn,
|
||||
site: site
|
||||
} do
|
||||
populate_stats(site, [
|
||||
build(:event,
|
||||
name: "Purchase",
|
||||
"meta.key": ["cost"],
|
||||
"meta.value": ["0"]
|
||||
),
|
||||
build(:event,
|
||||
name: "Purchase",
|
||||
"meta.key": ["cost"],
|
||||
"meta.value": ["0.01"]
|
||||
),
|
||||
build(:event,
|
||||
name: "Purchase",
|
||||
"meta.key": ["cost"],
|
||||
"meta.value": ["20"]
|
||||
),
|
||||
build(:event,
|
||||
name: "Purchase",
|
||||
"meta.key": ["cost"],
|
||||
"meta.value": ["20"]
|
||||
),
|
||||
build(:event, name: "Purchase")
|
||||
])
|
||||
|
||||
insert(:goal, %{site: site, event_name: "Purchase"})
|
||||
|
||||
filters =
|
||||
Jason.encode!(%{
|
||||
goal: "Purchase",
|
||||
props: %{cost: "!0|0.01"}
|
||||
})
|
||||
|
||||
conn =
|
||||
get(
|
||||
conn,
|
||||
"/api/stats/#{site.domain}/custom-prop-values/cost?period=day&filters=#{filters}"
|
||||
)
|
||||
|
||||
assert json_response(conn, 200) == [
|
||||
%{
|
||||
"name" => "20",
|
||||
"visitors" => 2,
|
||||
"events" => 2,
|
||||
"conversion_rate" => 40.0
|
||||
},
|
||||
%{
|
||||
"name" => "(none)",
|
||||
"visitors" => 1,
|
||||
"events" => 1,
|
||||
"conversion_rate" => 20.0
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
test "does not return (none) value in property breakdown with not_member filter including a (none) value",
|
||||
%{conn: conn, site: site} do
|
||||
populate_stats(site, [
|
||||
build(:event,
|
||||
name: "Purchase",
|
||||
"meta.key": ["cost"],
|
||||
"meta.value": ["0"]
|
||||
),
|
||||
build(:event,
|
||||
name: "Purchase",
|
||||
"meta.key": ["cost"],
|
||||
"meta.value": ["20"]
|
||||
),
|
||||
build(:event,
|
||||
name: "Purchase",
|
||||
"meta.key": ["cost"],
|
||||
"meta.value": ["20"]
|
||||
),
|
||||
build(:event, name: "Purchase")
|
||||
])
|
||||
|
||||
insert(:goal, %{site: site, event_name: "Purchase"})
|
||||
|
||||
filters =
|
||||
Jason.encode!(%{
|
||||
goal: "Purchase",
|
||||
props: %{cost: "!0|(none)"}
|
||||
})
|
||||
|
||||
conn =
|
||||
get(
|
||||
conn,
|
||||
"/api/stats/#{site.domain}/custom-prop-values/cost?period=day&filters=#{filters}"
|
||||
)
|
||||
|
||||
assert json_response(conn, 200) == [
|
||||
%{
|
||||
"name" => "20",
|
||||
"visitors" => 2,
|
||||
"events" => 2,
|
||||
"conversion_rate" => 50.0
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
test "returns property breakdown with a pageview goal filter", %{conn: conn, site: site} do
|
||||
populate_stats(site, [
|
||||
build(:pageview, pathname: "/"),
|
||||
build(:pageview, pathname: "/register"),
|
||||
build(:pageview, pathname: "/register", "meta.key": ["variant"], "meta.value": ["A"]),
|
||||
build(:pageview, pathname: "/register", "meta.key": ["variant"], "meta.value": ["A"])
|
||||
])
|
||||
|
||||
insert(:goal, %{site: site, page_path: "/register"})
|
||||
filters = Jason.encode!(%{goal: "Visit /register"})
|
||||
|
||||
conn =
|
||||
get(
|
||||
conn,
|
||||
"/api/stats/#{site.domain}/custom-prop-values/variant?period=day&filters=#{filters}"
|
||||
)
|
||||
|
||||
assert json_response(conn, 200) == [
|
||||
%{
|
||||
"visitors" => 2,
|
||||
"name" => "A",
|
||||
"events" => 2,
|
||||
"conversion_rate" => 50.0
|
||||
},
|
||||
%{
|
||||
"visitors" => 1,
|
||||
"name" => "(none)",
|
||||
"events" => 1,
|
||||
"conversion_rate" => 25.0
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
test "property breakdown with prop filter", %{conn: conn, site: site} do
|
||||
populate_stats(site, [
|
||||
build(:pageview, user_id: 1),
|
||||
build(:event, user_id: 1, name: "Signup", "meta.key": ["variant"], "meta.value": ["A"]),
|
||||
build(:pageview, user_id: 2),
|
||||
build(:event, user_id: 2, name: "Signup", "meta.key": ["variant"], "meta.value": ["B"])
|
||||
])
|
||||
|
||||
insert(:goal, %{site: site, event_name: "Signup"})
|
||||
filters = Jason.encode!(%{goal: "Signup", props: %{"variant" => "B"}})
|
||||
prop_key = "variant"
|
||||
|
||||
conn =
|
||||
get(
|
||||
conn,
|
||||
"/api/stats/#{site.domain}/custom-prop-values/#{prop_key}?period=day&filters=#{filters}"
|
||||
)
|
||||
|
||||
assert json_response(conn, 200) == [
|
||||
%{
|
||||
"visitors" => 1,
|
||||
"name" => "B",
|
||||
"events" => 1,
|
||||
"conversion_rate" => 50.0
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
test "Property breakdown with prop and goal filter", %{conn: conn, site: site} do
|
||||
populate_stats(site, [
|
||||
build(:pageview, user_id: 1, utm_campaign: "campaignA"),
|
||||
build(:event,
|
||||
user_id: 1,
|
||||
name: "ButtonClick",
|
||||
"meta.key": ["variant"],
|
||||
"meta.value": ["A"]
|
||||
),
|
||||
build(:pageview, user_id: 2, utm_campaign: "campaignA"),
|
||||
build(:event,
|
||||
user_id: 2,
|
||||
name: "ButtonClick",
|
||||
"meta.key": ["variant"],
|
||||
"meta.value": ["B"]
|
||||
)
|
||||
])
|
||||
|
||||
insert(:goal, %{site: site, event_name: "ButtonClick"})
|
||||
|
||||
filters =
|
||||
Jason.encode!(%{
|
||||
goal: "ButtonClick",
|
||||
props: %{variant: "A"},
|
||||
utm_campaign: "campaignA"
|
||||
})
|
||||
|
||||
prop_key = "variant"
|
||||
|
||||
conn =
|
||||
get(
|
||||
conn,
|
||||
"/api/stats/#{site.domain}/custom-prop-values/#{prop_key}?period=day&filters=#{filters}"
|
||||
)
|
||||
|
||||
assert json_response(conn, 200) == [
|
||||
%{
|
||||
"name" => "A",
|
||||
"visitors" => 1,
|
||||
"events" => 1,
|
||||
"conversion_rate" => 50.0
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
test "Property breakdown with goal and source filter", %{conn: conn, site: site} do
|
||||
populate_stats(site, [
|
||||
build(:pageview, user_id: 1, referrer_source: "Google"),
|
||||
build(:event,
|
||||
user_id: 1,
|
||||
name: "ButtonClick",
|
||||
"meta.key": ["variant"],
|
||||
"meta.value": ["A"]
|
||||
),
|
||||
build(:pageview, user_id: 2, referrer_source: "Google"),
|
||||
build(:pageview, user_id: 3, referrer_source: "ignore"),
|
||||
build(:event,
|
||||
user_id: 3,
|
||||
name: "ButtonClick",
|
||||
"meta.key": ["variant"],
|
||||
"meta.value": ["B"]
|
||||
)
|
||||
])
|
||||
|
||||
insert(:goal, %{site: site, event_name: "ButtonClick"})
|
||||
|
||||
filters =
|
||||
Jason.encode!(%{
|
||||
goal: "ButtonClick",
|
||||
source: "Google"
|
||||
})
|
||||
|
||||
prop_key = "variant"
|
||||
|
||||
conn =
|
||||
get(
|
||||
conn,
|
||||
"/api/stats/#{site.domain}/custom-prop-values/#{prop_key}?period=day&filters=#{filters}"
|
||||
)
|
||||
|
||||
assert json_response(conn, 200) == [
|
||||
%{
|
||||
"name" => "A",
|
||||
"visitors" => 1,
|
||||
"events" => 1,
|
||||
"conversion_rate" => 50.0
|
||||
}
|
||||
]
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET /api/stats/:domain/custom-prop-values/:prop_key - other filters" do
|
||||
setup [:create_user, :log_in, :create_new_site]
|
||||
|
||||
test "returns prop-breakdown with a page filter", %{conn: conn, site: site} do
|
||||
prop_key = "parim_s6ber"
|
||||
|
||||
populate_stats(site, [
|
||||
build(:pageview, "meta.key": [prop_key], "meta.value": ["K2sna Kalle"]),
|
||||
build(:pageview, pathname: "/sipsik", "meta.key": [prop_key], "meta.value": ["Sipsik"])
|
||||
])
|
||||
|
||||
filters = Jason.encode!(%{page: "/sipsik"})
|
||||
|
||||
conn =
|
||||
get(
|
||||
conn,
|
||||
"/api/stats/#{site.domain}/custom-prop-values/#{prop_key}?period=day&filters=#{filters}"
|
||||
)
|
||||
|
||||
assert json_response(conn, 200) == [
|
||||
%{
|
||||
"visitors" => 1,
|
||||
"name" => "Sipsik",
|
||||
"events" => 1,
|
||||
"percentage" => 100.0
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
test "returns prop-breakdown with a session-level filter", %{conn: conn, site: site} do
|
||||
prop_key = "parim_s6ber"
|
||||
|
||||
populate_stats(site, [
|
||||
build(:pageview, "meta.key": [prop_key], "meta.value": ["K2sna Kalle"]),
|
||||
build(:pageview, browser: "Chrome", "meta.key": [prop_key], "meta.value": ["Sipsik"])
|
||||
])
|
||||
|
||||
filters = Jason.encode!(%{browser: "Chrome"})
|
||||
|
||||
conn =
|
||||
get(
|
||||
conn,
|
||||
"/api/stats/#{site.domain}/custom-prop-values/#{prop_key}?period=day&filters=#{filters}"
|
||||
)
|
||||
|
||||
assert json_response(conn, 200) == [
|
||||
%{
|
||||
"visitors" => 1,
|
||||
"name" => "Sipsik",
|
||||
"events" => 1,
|
||||
"percentage" => 100.0
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
test "returns prop-breakdown with a prop_value filter", %{conn: conn, site: site} do
|
||||
prop_key = "parim_s6ber"
|
||||
|
||||
populate_stats(site, [
|
||||
build(:pageview, "meta.key": [prop_key], "meta.value": ["K2sna Kalle"]),
|
||||
build(:pageview, "meta.key": [prop_key], "meta.value": ["K2sna Kalle"]),
|
||||
build(:pageview, "meta.key": [prop_key], "meta.value": ["Sipsik"])
|
||||
])
|
||||
|
||||
filters = Jason.encode!(%{props: %{parim_s6ber: "Sipsik"}})
|
||||
|
||||
conn =
|
||||
get(
|
||||
conn,
|
||||
"/api/stats/#{site.domain}/custom-prop-values/#{prop_key}?period=day&filters=#{filters}"
|
||||
)
|
||||
|
||||
assert json_response(conn, 200) == [
|
||||
%{
|
||||
"visitors" => 1,
|
||||
"name" => "Sipsik",
|
||||
"events" => 1,
|
||||
"percentage" => 100.0
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
test "returns prop-breakdown with a prop_value is_not (none) filter", %{
|
||||
conn: conn,
|
||||
site: site
|
||||
} do
|
||||
prop_key = "parim_s6ber"
|
||||
|
||||
populate_stats(site, [
|
||||
build(:pageview, "meta.key": [prop_key], "meta.value": ["K2sna Kalle"]),
|
||||
build(:pageview, "meta.key": [prop_key], "meta.value": ["K2sna Kalle"]),
|
||||
build(:pageview, "meta.key": [prop_key], "meta.value": ["Sipsik"]),
|
||||
build(:pageview)
|
||||
])
|
||||
|
||||
filters = Jason.encode!(%{props: %{parim_s6ber: "!(none)"}})
|
||||
|
||||
conn =
|
||||
get(
|
||||
conn,
|
||||
"/api/stats/#{site.domain}/custom-prop-values/#{prop_key}?period=day&filters=#{filters}"
|
||||
)
|
||||
|
||||
assert json_response(conn, 200) == [
|
||||
%{
|
||||
"visitors" => 2,
|
||||
"name" => "K2sna Kalle",
|
||||
"events" => 2,
|
||||
"percentage" => 66.7
|
||||
},
|
||||
%{
|
||||
"visitors" => 1,
|
||||
"name" => "Sipsik",
|
||||
"events" => 1,
|
||||
"percentage" => 33.3
|
||||
}
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
@ -14,8 +14,8 @@ defmodule PlausibleWeb.Api.StatsController.OperatingSystemsTest do
|
||||
conn = get(conn, "/api/stats/#{site.domain}/operating-systems?period=day")
|
||||
|
||||
assert json_response(conn, 200) == [
|
||||
%{"name" => "Mac", "visitors" => 2, "percentage" => 67},
|
||||
%{"name" => "Android", "visitors" => 1, "percentage" => 33}
|
||||
%{"name" => "Mac", "visitors" => 2, "percentage" => 66.7},
|
||||
%{"name" => "Android", "visitors" => 1, "percentage" => 33.3}
|
||||
]
|
||||
end
|
||||
|
||||
@ -156,8 +156,8 @@ defmodule PlausibleWeb.Api.StatsController.OperatingSystemsTest do
|
||||
conn = get(conn, "/api/stats/#{site.domain}/operating-systems?period=day")
|
||||
|
||||
assert json_response(conn, 200) == [
|
||||
%{"name" => "Mac", "visitors" => 2, "percentage" => 67},
|
||||
%{"name" => "Android", "visitors" => 1, "percentage" => 33}
|
||||
%{"name" => "Mac", "visitors" => 2, "percentage" => 66.7},
|
||||
%{"name" => "Android", "visitors" => 1, "percentage" => 33.3}
|
||||
]
|
||||
|
||||
conn =
|
||||
@ -213,8 +213,8 @@ defmodule PlausibleWeb.Api.StatsController.OperatingSystemsTest do
|
||||
)
|
||||
|
||||
assert json_response(conn, 200) == [
|
||||
%{"name" => "10.16", "visitors" => 2, "percentage" => 67},
|
||||
%{"name" => "10.15", "visitors" => 1, "percentage" => 33}
|
||||
%{"name" => "10.16", "visitors" => 2, "percentage" => 66.7},
|
||||
%{"name" => "10.15", "visitors" => 1, "percentage" => 33.3}
|
||||
]
|
||||
end
|
||||
end
|
||||
|
@ -0,0 +1,608 @@
|
||||
defmodule PlausibleWeb.Api.StatsController.PropBreakdownTest do
|
||||
use PlausibleWeb.ConnCase
|
||||
|
||||
# credo:disable-for-next-line
|
||||
# TODO: Remove this file once the new Properties feature is live.
|
||||
|
||||
# All tests in this file were copied into `custom_prop_breakdown_test.exs`
|
||||
# testing the exact same features for the new
|
||||
# `/api/stats/:domain/custom-prop-values/:key` route.
|
||||
|
||||
describe "GET /api/stats/:domain/property/:key" do
|
||||
setup [:create_user, :log_in, :create_new_site]
|
||||
|
||||
test "returns property breakdown for goal", %{conn: conn, site: site} do
|
||||
populate_stats(site, [
|
||||
build(:pageview, pathname: "/"),
|
||||
build(:pageview, pathname: "/"),
|
||||
build(:pageview, pathname: "/register"),
|
||||
build(:event, name: "Signup", "meta.key": ["variant"], "meta.value": ["A"]),
|
||||
build(:event, name: "Signup", "meta.key": ["variant"], "meta.value": ["B"]),
|
||||
build(:event, name: "Signup", "meta.key": ["variant"], "meta.value": ["B"])
|
||||
])
|
||||
|
||||
insert(:goal, %{site: site, event_name: "Signup"})
|
||||
filters = Jason.encode!(%{goal: "Signup"})
|
||||
prop_key = "variant"
|
||||
|
||||
conn =
|
||||
get(
|
||||
conn,
|
||||
"/api/stats/#{site.domain}/property/#{prop_key}?period=day&filters=#{filters}"
|
||||
)
|
||||
|
||||
assert json_response(conn, 200) == [
|
||||
%{
|
||||
"unique_conversions" => 2,
|
||||
"name" => "B",
|
||||
"total_conversions" => 2,
|
||||
"conversion_rate" => 33.3
|
||||
},
|
||||
%{
|
||||
"unique_conversions" => 1,
|
||||
"name" => "A",
|
||||
"total_conversions" => 1,
|
||||
"conversion_rate" => 16.7
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
test "returns (none) values in property breakdown for goal", %{conn: conn, site: site} do
|
||||
populate_stats(site, [
|
||||
build(:pageview, pathname: "/"),
|
||||
build(:pageview, pathname: "/"),
|
||||
build(:pageview, pathname: "/register"),
|
||||
build(:event, name: "Signup"),
|
||||
build(:event, name: "Signup"),
|
||||
build(:event, name: "Signup", "meta.key": ["variant"], "meta.value": ["A"])
|
||||
])
|
||||
|
||||
insert(:goal, %{site: site, event_name: "Signup"})
|
||||
filters = Jason.encode!(%{goal: "Signup"})
|
||||
prop_key = "variant"
|
||||
|
||||
conn =
|
||||
get(
|
||||
conn,
|
||||
"/api/stats/#{site.domain}/property/#{prop_key}?period=day&filters=#{filters}"
|
||||
)
|
||||
|
||||
assert json_response(conn, 200) == [
|
||||
%{
|
||||
"unique_conversions" => 2,
|
||||
"name" => "(none)",
|
||||
"total_conversions" => 2,
|
||||
"conversion_rate" => 33.3
|
||||
},
|
||||
%{
|
||||
"unique_conversions" => 1,
|
||||
"name" => "A",
|
||||
"total_conversions" => 1,
|
||||
"conversion_rate" => 16.7
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
test "does not return (none) value in property breakdown with is filter on prop_value", %{
|
||||
conn: conn,
|
||||
site: site
|
||||
} do
|
||||
populate_stats(site, [
|
||||
build(:event,
|
||||
name: "Purchase",
|
||||
"meta.key": ["cost"],
|
||||
"meta.value": ["0"]
|
||||
),
|
||||
build(:event, name: "Purchase")
|
||||
])
|
||||
|
||||
insert(:goal, %{site: site, event_name: "Purchase"})
|
||||
|
||||
filters =
|
||||
Jason.encode!(%{
|
||||
goal: "Purchase",
|
||||
props: %{cost: "0"}
|
||||
})
|
||||
|
||||
conn =
|
||||
get(
|
||||
conn,
|
||||
"/api/stats/#{site.domain}/property/cost?period=day&filters=#{filters}"
|
||||
)
|
||||
|
||||
assert json_response(conn, 200) == [
|
||||
%{
|
||||
"name" => "0",
|
||||
"unique_conversions" => 1,
|
||||
"total_conversions" => 1,
|
||||
"conversion_rate" => 50.0
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
test "returns only (none) value in property breakdown with is (none) filter", %{
|
||||
conn: conn,
|
||||
site: site
|
||||
} do
|
||||
populate_stats(site, [
|
||||
build(:event,
|
||||
name: "Purchase",
|
||||
"meta.key": ["cost"],
|
||||
"meta.value": ["0"]
|
||||
),
|
||||
build(:event, name: "Purchase")
|
||||
])
|
||||
|
||||
insert(:goal, %{site: site, event_name: "Purchase"})
|
||||
|
||||
filters =
|
||||
Jason.encode!(%{
|
||||
goal: "Purchase",
|
||||
props: %{cost: "(none)"}
|
||||
})
|
||||
|
||||
conn =
|
||||
get(
|
||||
conn,
|
||||
"/api/stats/#{site.domain}/property/cost?period=day&filters=#{filters}"
|
||||
)
|
||||
|
||||
assert json_response(conn, 200) == [
|
||||
%{
|
||||
"name" => "(none)",
|
||||
"unique_conversions" => 1,
|
||||
"total_conversions" => 1,
|
||||
"conversion_rate" => 50.0
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
test "returns (none) value in property breakdown with is_not filter on prop_value", %{
|
||||
conn: conn,
|
||||
site: site
|
||||
} do
|
||||
populate_stats(site, [
|
||||
build(:event,
|
||||
name: "Purchase",
|
||||
"meta.key": ["cost"],
|
||||
"meta.value": ["0"]
|
||||
),
|
||||
build(:event,
|
||||
name: "Purchase",
|
||||
"meta.key": ["cost"],
|
||||
"meta.value": ["20"]
|
||||
),
|
||||
build(:event,
|
||||
name: "Purchase",
|
||||
"meta.key": ["cost"],
|
||||
"meta.value": ["20"]
|
||||
),
|
||||
build(:event, name: "Purchase")
|
||||
])
|
||||
|
||||
insert(:goal, %{site: site, event_name: "Purchase"})
|
||||
|
||||
filters =
|
||||
Jason.encode!(%{
|
||||
goal: "Purchase",
|
||||
props: %{cost: "!0"}
|
||||
})
|
||||
|
||||
conn =
|
||||
get(
|
||||
conn,
|
||||
"/api/stats/#{site.domain}/property/cost?period=day&filters=#{filters}"
|
||||
)
|
||||
|
||||
assert json_response(conn, 200) == [
|
||||
%{
|
||||
"name" => "20",
|
||||
"unique_conversions" => 2,
|
||||
"total_conversions" => 2,
|
||||
"conversion_rate" => 50.0
|
||||
},
|
||||
%{
|
||||
"name" => "(none)",
|
||||
"unique_conversions" => 1,
|
||||
"total_conversions" => 1,
|
||||
"conversion_rate" => 25.0
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
test "does not return (none) value in property breakdown with is_not (none) filter", %{
|
||||
conn: conn,
|
||||
site: site
|
||||
} do
|
||||
populate_stats(site, [
|
||||
build(:event,
|
||||
name: "Purchase",
|
||||
"meta.key": ["cost"],
|
||||
"meta.value": ["0"]
|
||||
),
|
||||
build(:event, name: "Purchase")
|
||||
])
|
||||
|
||||
insert(:goal, %{site: site, event_name: "Purchase"})
|
||||
|
||||
filters =
|
||||
Jason.encode!(%{
|
||||
goal: "Purchase",
|
||||
props: %{cost: "!(none)"}
|
||||
})
|
||||
|
||||
conn =
|
||||
get(
|
||||
conn,
|
||||
"/api/stats/#{site.domain}/property/cost?period=day&filters=#{filters}"
|
||||
)
|
||||
|
||||
assert json_response(conn, 200) == [
|
||||
%{
|
||||
"name" => "0",
|
||||
"unique_conversions" => 1,
|
||||
"total_conversions" => 1,
|
||||
"conversion_rate" => 50.0
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
test "does not return (none) value in property breakdown with member filter on prop_value", %{
|
||||
conn: conn,
|
||||
site: site
|
||||
} do
|
||||
populate_stats(site, [
|
||||
build(:event,
|
||||
name: "Purchase",
|
||||
"meta.key": ["cost"],
|
||||
"meta.value": ["0"]
|
||||
),
|
||||
build(:event,
|
||||
name: "Purchase",
|
||||
"meta.key": ["cost"],
|
||||
"meta.value": ["1"]
|
||||
),
|
||||
build(:event,
|
||||
name: "Purchase",
|
||||
"meta.key": ["cost"],
|
||||
"meta.value": ["1"]
|
||||
),
|
||||
build(:event, name: "Purchase")
|
||||
])
|
||||
|
||||
insert(:goal, %{site: site, event_name: "Purchase"})
|
||||
|
||||
filters =
|
||||
Jason.encode!(%{
|
||||
goal: "Purchase",
|
||||
props: %{cost: "0|1"}
|
||||
})
|
||||
|
||||
conn =
|
||||
get(
|
||||
conn,
|
||||
"/api/stats/#{site.domain}/property/cost?period=day&filters=#{filters}"
|
||||
)
|
||||
|
||||
assert json_response(conn, 200) == [
|
||||
%{
|
||||
"name" => "1",
|
||||
"unique_conversions" => 2,
|
||||
"total_conversions" => 2,
|
||||
"conversion_rate" => 50.0
|
||||
},
|
||||
%{
|
||||
"name" => "0",
|
||||
"unique_conversions" => 1,
|
||||
"total_conversions" => 1,
|
||||
"conversion_rate" => 25.0
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
test "returns (none) value in property breakdown with member filter including a (none) value",
|
||||
%{conn: conn, site: site} do
|
||||
populate_stats(site, [
|
||||
build(:event,
|
||||
name: "Purchase",
|
||||
"meta.key": ["cost"],
|
||||
"meta.value": ["0"]
|
||||
),
|
||||
build(:event,
|
||||
name: "Purchase",
|
||||
"meta.key": ["cost"],
|
||||
"meta.value": ["1"]
|
||||
),
|
||||
build(:event,
|
||||
name: "Purchase",
|
||||
"meta.key": ["cost"],
|
||||
"meta.value": ["1"]
|
||||
),
|
||||
build(:event, name: "Purchase")
|
||||
])
|
||||
|
||||
insert(:goal, %{site: site, event_name: "Purchase"})
|
||||
|
||||
filters =
|
||||
Jason.encode!(%{
|
||||
goal: "Purchase",
|
||||
props: %{cost: "1|(none)"}
|
||||
})
|
||||
|
||||
conn =
|
||||
get(
|
||||
conn,
|
||||
"/api/stats/#{site.domain}/property/cost?period=day&filters=#{filters}"
|
||||
)
|
||||
|
||||
assert json_response(conn, 200) == [
|
||||
%{
|
||||
"name" => "1",
|
||||
"unique_conversions" => 2,
|
||||
"total_conversions" => 2,
|
||||
"conversion_rate" => 50.0
|
||||
},
|
||||
%{
|
||||
"name" => "(none)",
|
||||
"unique_conversions" => 1,
|
||||
"total_conversions" => 1,
|
||||
"conversion_rate" => 25.0
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
test "returns (none) value in property breakdown with not_member filter on prop_value", %{
|
||||
conn: conn,
|
||||
site: site
|
||||
} do
|
||||
populate_stats(site, [
|
||||
build(:event,
|
||||
name: "Purchase",
|
||||
"meta.key": ["cost"],
|
||||
"meta.value": ["0"]
|
||||
),
|
||||
build(:event,
|
||||
name: "Purchase",
|
||||
"meta.key": ["cost"],
|
||||
"meta.value": ["0.01"]
|
||||
),
|
||||
build(:event,
|
||||
name: "Purchase",
|
||||
"meta.key": ["cost"],
|
||||
"meta.value": ["20"]
|
||||
),
|
||||
build(:event,
|
||||
name: "Purchase",
|
||||
"meta.key": ["cost"],
|
||||
"meta.value": ["20"]
|
||||
),
|
||||
build(:event, name: "Purchase")
|
||||
])
|
||||
|
||||
insert(:goal, %{site: site, event_name: "Purchase"})
|
||||
|
||||
filters =
|
||||
Jason.encode!(%{
|
||||
goal: "Purchase",
|
||||
props: %{cost: "!0|0.01"}
|
||||
})
|
||||
|
||||
conn =
|
||||
get(
|
||||
conn,
|
||||
"/api/stats/#{site.domain}/property/cost?period=day&filters=#{filters}"
|
||||
)
|
||||
|
||||
assert json_response(conn, 200) == [
|
||||
%{
|
||||
"name" => "20",
|
||||
"unique_conversions" => 2,
|
||||
"total_conversions" => 2,
|
||||
"conversion_rate" => 40.0
|
||||
},
|
||||
%{
|
||||
"name" => "(none)",
|
||||
"unique_conversions" => 1,
|
||||
"total_conversions" => 1,
|
||||
"conversion_rate" => 20.0
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
test "does not return (none) value in property breakdown with not_member filter including a (none) value",
|
||||
%{conn: conn, site: site} do
|
||||
populate_stats(site, [
|
||||
build(:event,
|
||||
name: "Purchase",
|
||||
"meta.key": ["cost"],
|
||||
"meta.value": ["0"]
|
||||
),
|
||||
build(:event,
|
||||
name: "Purchase",
|
||||
"meta.key": ["cost"],
|
||||
"meta.value": ["20"]
|
||||
),
|
||||
build(:event,
|
||||
name: "Purchase",
|
||||
"meta.key": ["cost"],
|
||||
"meta.value": ["20"]
|
||||
),
|
||||
build(:event, name: "Purchase")
|
||||
])
|
||||
|
||||
insert(:goal, %{site: site, event_name: "Purchase"})
|
||||
|
||||
filters =
|
||||
Jason.encode!(%{
|
||||
goal: "Purchase",
|
||||
props: %{cost: "!0|(none)"}
|
||||
})
|
||||
|
||||
conn =
|
||||
get(
|
||||
conn,
|
||||
"/api/stats/#{site.domain}/property/cost?period=day&filters=#{filters}"
|
||||
)
|
||||
|
||||
assert json_response(conn, 200) == [
|
||||
%{
|
||||
"name" => "20",
|
||||
"unique_conversions" => 2,
|
||||
"total_conversions" => 2,
|
||||
"conversion_rate" => 50.0
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
test "returns property breakdown with a pageview goal filter", %{conn: conn, site: site} do
|
||||
populate_stats(site, [
|
||||
build(:pageview, pathname: "/"),
|
||||
build(:pageview, pathname: "/register"),
|
||||
build(:pageview, pathname: "/register", "meta.key": ["variant"], "meta.value": ["A"]),
|
||||
build(:pageview, pathname: "/register", "meta.key": ["variant"], "meta.value": ["A"])
|
||||
])
|
||||
|
||||
insert(:goal, %{site: site, page_path: "/register"})
|
||||
filters = Jason.encode!(%{goal: "Visit /register"})
|
||||
|
||||
conn =
|
||||
get(
|
||||
conn,
|
||||
"/api/stats/#{site.domain}/property/variant?period=day&filters=#{filters}"
|
||||
)
|
||||
|
||||
assert json_response(conn, 200) == [
|
||||
%{
|
||||
"unique_conversions" => 2,
|
||||
"name" => "A",
|
||||
"total_conversions" => 2,
|
||||
"conversion_rate" => 50.0
|
||||
},
|
||||
%{
|
||||
"unique_conversions" => 1,
|
||||
"name" => "(none)",
|
||||
"total_conversions" => 1,
|
||||
"conversion_rate" => 25.0
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
test "property breakdown with prop filter", %{conn: conn, site: site} do
|
||||
populate_stats(site, [
|
||||
build(:pageview, user_id: 1),
|
||||
build(:event, user_id: 1, name: "Signup", "meta.key": ["variant"], "meta.value": ["A"]),
|
||||
build(:pageview, user_id: 2),
|
||||
build(:event, user_id: 2, name: "Signup", "meta.key": ["variant"], "meta.value": ["B"])
|
||||
])
|
||||
|
||||
insert(:goal, %{site: site, event_name: "Signup"})
|
||||
filters = Jason.encode!(%{goal: "Signup", props: %{"variant" => "B"}})
|
||||
prop_key = "variant"
|
||||
|
||||
conn =
|
||||
get(
|
||||
conn,
|
||||
"/api/stats/#{site.domain}/property/#{prop_key}?period=day&filters=#{filters}"
|
||||
)
|
||||
|
||||
assert json_response(conn, 200) == [
|
||||
%{
|
||||
"unique_conversions" => 1,
|
||||
"name" => "B",
|
||||
"total_conversions" => 1,
|
||||
"conversion_rate" => 50.0
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
test "Property breakdown with prop and goal filter", %{conn: conn, site: site} do
|
||||
populate_stats(site, [
|
||||
build(:pageview, user_id: 1, utm_campaign: "campaignA"),
|
||||
build(:event,
|
||||
user_id: 1,
|
||||
name: "ButtonClick",
|
||||
"meta.key": ["variant"],
|
||||
"meta.value": ["A"]
|
||||
),
|
||||
build(:pageview, user_id: 2, utm_campaign: "campaignA"),
|
||||
build(:event,
|
||||
user_id: 2,
|
||||
name: "ButtonClick",
|
||||
"meta.key": ["variant"],
|
||||
"meta.value": ["B"]
|
||||
)
|
||||
])
|
||||
|
||||
insert(:goal, %{site: site, event_name: "ButtonClick"})
|
||||
|
||||
filters =
|
||||
Jason.encode!(%{
|
||||
goal: "ButtonClick",
|
||||
props: %{variant: "A"},
|
||||
utm_campaign: "campaignA"
|
||||
})
|
||||
|
||||
prop_key = "variant"
|
||||
|
||||
conn =
|
||||
get(
|
||||
conn,
|
||||
"/api/stats/#{site.domain}/property/#{prop_key}?period=day&filters=#{filters}"
|
||||
)
|
||||
|
||||
assert json_response(conn, 200) == [
|
||||
%{
|
||||
"name" => "A",
|
||||
"unique_conversions" => 1,
|
||||
"total_conversions" => 1,
|
||||
"conversion_rate" => 50.0
|
||||
}
|
||||
]
|
||||
end
|
||||
|
||||
test "Property breakdown with goal and source filter", %{conn: conn, site: site} do
|
||||
populate_stats(site, [
|
||||
build(:pageview, user_id: 1, referrer_source: "Google"),
|
||||
build(:event,
|
||||
user_id: 1,
|
||||
name: "ButtonClick",
|
||||
"meta.key": ["variant"],
|
||||
"meta.value": ["A"]
|
||||
),
|
||||
build(:pageview, user_id: 2, referrer_source: "Google"),
|
||||
build(:pageview, user_id: 3, referrer_source: "ignore"),
|
||||
build(:event,
|
||||
user_id: 3,
|
||||
name: "ButtonClick",
|
||||
"meta.key": ["variant"],
|
||||
"meta.value": ["B"]
|
||||
)
|
||||
])
|
||||
|
||||
insert(:goal, %{site: site, event_name: "ButtonClick"})
|
||||
|
||||
filters =
|
||||
Jason.encode!(%{
|
||||
goal: "ButtonClick",
|
||||
source: "Google"
|
||||
})
|
||||
|
||||
prop_key = "variant"
|
||||
|
||||
conn =
|
||||
get(
|
||||
conn,
|
||||
"/api/stats/#{site.domain}/property/#{prop_key}?period=day&filters=#{filters}"
|
||||
)
|
||||
|
||||
assert json_response(conn, 200) == [
|
||||
%{
|
||||
"name" => "A",
|
||||
"unique_conversions" => 1,
|
||||
"total_conversions" => 1,
|
||||
"conversion_rate" => 50.0
|
||||
}
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
@ -14,8 +14,8 @@ defmodule PlausibleWeb.Api.StatsController.ScreenSizesTest do
|
||||
conn = get(conn, "/api/stats/#{site.domain}/screen-sizes?period=day")
|
||||
|
||||
assert json_response(conn, 200) == [
|
||||
%{"name" => "Desktop", "visitors" => 2, "percentage" => 67},
|
||||
%{"name" => "Laptop", "visitors" => 1, "percentage" => 33}
|
||||
%{"name" => "Desktop", "visitors" => 2, "percentage" => 66.7},
|
||||
%{"name" => "Laptop", "visitors" => 1, "percentage" => 33.3}
|
||||
]
|
||||
end
|
||||
|
||||
@ -130,8 +130,8 @@ defmodule PlausibleWeb.Api.StatsController.ScreenSizesTest do
|
||||
conn = get(conn, "/api/stats/#{site.domain}/screen-sizes?period=day")
|
||||
|
||||
assert json_response(conn, 200) == [
|
||||
%{"name" => "Desktop", "visitors" => 2, "percentage" => 67},
|
||||
%{"name" => "Laptop", "visitors" => 1, "percentage" => 33}
|
||||
%{"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")
|
||||
@ -178,8 +178,8 @@ defmodule PlausibleWeb.Api.StatsController.ScreenSizesTest do
|
||||
conn = get(conn, "/api/stats/#{site.domain}/screen-sizes?period=day&filters=#{filters}")
|
||||
|
||||
assert json_response(conn, 200) == [
|
||||
%{"name" => "Desktop", "visitors" => 2, "percentage" => 67},
|
||||
%{"name" => "Mobile", "visitors" => 1, "percentage" => 33}
|
||||
%{"name" => "Desktop", "visitors" => 2, "percentage" => 66.7},
|
||||
%{"name" => "Mobile", "visitors" => 1, "percentage" => 33.3}
|
||||
]
|
||||
end
|
||||
end
|
||||
|
Loading…
Reference in New Issue
Block a user