analytics/test/plausible_web/controllers/api/stats_controller/suggestions_test.exs
RobertJoonas caeff41932
Prop filter modal (#2841)
* Add PropFilterModal (only UI)

* small variable refactor

* allow selecting prop value filter type

* allow selecting only one prop_key

* allow selecting many prop_values only when prop_key selected

* handle submitting filter

* get applied filters from query + remove option

* change prop filter label format

* support member and not_member filter types for pageview props

* show (none) value in filter suggestions

* refactor zip_results/4 and remove unused code

* fix displaying (none) values in goals section prop breakdown

* remove unnecessary functionality

* fix bug: returning prop names for goal :member filter

* fix bug: submitting regular filter modal with Enter key

* bugfix: disallow opening prop filter modal when feature flag disabled

* mix format

* break selected combobox values into multiple lines

* fix useEffect behavior for focusing on prop_key field

* support submitting prop filter with Enter key

* refactor getFormState in PropFilterModal

* separate fetchPropKey and fetchPropValue functions

* Allow querying props for pageview goals

* Make the internal props API only return a list of props (not map)
* Separate function for fetching all props in Stats API goal breakdown (this returns a map as before)

* ditch state for keeping search bar visible

* group by event_name in db query
2023-04-27 14:09:33 +03:00

390 lines
13 KiB
Elixir

defmodule PlausibleWeb.Api.StatsController.SuggestionsTest do
use PlausibleWeb.ConnCase
describe "GET /api/stats/:domain/suggestions/:filter_name" do
setup [:create_user, :log_in, :create_site]
test "returns suggestions for pages without a query", %{conn: conn, site: site} do
populate_stats(site, [
build(:pageview, timestamp: ~N[2019-01-01 23:00:00], pathname: "/"),
build(:pageview, timestamp: ~N[2019-01-01 23:00:00], pathname: "/register"),
build(:pageview, timestamp: ~N[2019-01-01 23:00:01], pathname: "/contact"),
build(:pageview, timestamp: ~N[2019-01-01 23:00:01], pathname: "/irrelevant")
])
conn = get(conn, "/api/stats/#{site.domain}/suggestions/page?period=month&date=2019-01-01")
assert json_response(conn, 200) == [
%{"label" => "/", "value" => "/"},
%{"label" => "/contact", "value" => "/contact"},
%{"label" => "/irrelevant", "value" => "/irrelevant"},
%{"label" => "/register", "value" => "/register"}
]
end
test "returns suggestions for pages with a query", %{conn: conn, site: site} do
populate_stats(site, [
build(:pageview, timestamp: ~N[2019-01-01 23:00:00], pathname: "/"),
build(:pageview, timestamp: ~N[2019-01-01 23:00:00], pathname: "/register"),
build(:pageview, timestamp: ~N[2019-01-01 23:00:01], pathname: "/contact"),
build(:pageview, timestamp: ~N[2019-01-01 23:00:01], pathname: "/irrelevant")
])
conn =
get(conn, "/api/stats/#{site.domain}/suggestions/page?period=month&date=2019-01-01&q=re")
assert json_response(conn, 200) == [
%{"label" => "/irrelevant", "value" => "/irrelevant"},
%{"label" => "/register", "value" => "/register"}
]
end
test "returns suggestions for pages without any suggestions", %{conn: conn, site: site} do
conn =
get(
conn,
"/api/stats/#{site.domain}/suggestions/page?period=month&date=2019-01-01&q=/123"
)
assert json_response(conn, 200) == []
end
test "returns suggestions for goals", %{conn: conn, site: site} do
conn = get(conn, "/api/stats/#{site.domain}/suggestions/goal?period=month&date=2019-01-01")
assert json_response(conn, 200) == []
end
test "returns suggestions for sources", %{conn: conn, site: site} do
populate_stats(site, [
build(:pageview, timestamp: ~N[2019-01-01 23:00:00], referrer_source: "Bing"),
build(:pageview, timestamp: ~N[2019-01-01 23:00:00], referrer_source: "Bing"),
build(:pageview, timestamp: ~N[2019-01-01 23:00:00], referrer_source: "10words")
])
conn =
get(conn, "/api/stats/#{site.domain}/suggestions/source?period=month&date=2019-01-01")
assert json_response(conn, 200) == [
%{"label" => "Bing", "value" => "Bing"},
%{"label" => "10words", "value" => "10words"}
]
end
test "returns suggestions for countries", %{conn: conn, site: site} do
populate_stats(site, [
build(:pageview, timestamp: ~N[2019-01-01 23:00:01], pathname: "/", country_code: "US")
])
conn =
get(
conn,
"/api/stats/#{site.domain}/suggestions/country?period=month&date=2019-01-01&q=Unit"
)
assert json_response(conn, 200) == [%{"value" => "US", "label" => "United States"}]
end
test "returns suggestions for regions", %{conn: conn, user: user} do
{:ok, [site: site]} = create_new_site(%{user: user})
populate_stats(site, [
build(:pageview, country_code: "EE", subdivision1_code: "EE-37"),
build(:pageview, country_code: "EE", subdivision1_code: "EE-39")
])
conn =
get(
conn,
"/api/stats/#{site.domain}/suggestions/region?q=Har"
)
assert json_response(conn, 200) == [%{"value" => "EE-37", "label" => "Harjumaa"}]
end
test "returns suggestions for cities", %{conn: conn, user: user} do
{:ok, [site: site]} = create_new_site(%{user: user})
populate_stats(site, [
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)
])
conn =
get(
conn,
"/api/stats/#{site.domain}/suggestions/city?q=Kär"
)
assert json_response(conn, 200) == [%{"value" => "591632", "label" => "Kärdla"}]
end
test "returns suggestions for countries without country in search", %{conn: conn, site: site} do
conn =
get(
conn,
"/api/stats/#{site.domain}/suggestions/country?period=month&date=2019-01-01&q=GBR,UKR,URY"
)
assert json_response(conn, 200) == []
end
test "returns suggestions for screen sizes", %{conn: conn, site: site} do
populate_stats(site, [
build(:pageview, timestamp: ~N[2019-01-01 23:00:00], pathname: "/", screen_size: "Desktop")
])
conn =
get(conn, "/api/stats/#{site.domain}/suggestions/screen?period=month&date=2019-01-01")
assert json_response(conn, 200) == [%{"value" => "Desktop", "label" => "Desktop"}]
end
test "returns suggestions for browsers", %{conn: conn, site: site} do
populate_stats(site, [
build(:pageview, timestamp: ~N[2019-01-01 23:00:00], pathname: "/", browser: "Chrome")
])
conn =
get(conn, "/api/stats/#{site.domain}/suggestions/browser?period=month&date=2019-01-01")
assert json_response(conn, 200) == [%{"label" => "Chrome", "value" => "Chrome"}]
end
test "returns suggestions for browser versions", %{conn: conn, site: site} do
filters = Jason.encode!(%{browser: "Chrome"})
populate_stats(site, [
build(:pageview,
timestamp: ~N[2019-01-01 00:00:00],
browser: "Chrome",
browser_version: "78.0"
)
])
conn =
get(
conn,
"/api/stats/#{site.domain}/suggestions/browser_version?period=month&date=2019-01-01&filters=#{filters}"
)
assert json_response(conn, 200) == [%{"value" => "78.0", "label" => "78.0"}]
end
test "returns suggestions for OS", %{conn: conn, site: site} do
populate_stats(site, [
build(:pageview, timestamp: ~N[2019-01-01 00:00:00], operating_system: "Mac")
])
conn = get(conn, "/api/stats/#{site.domain}/suggestions/os?period=month&date=2019-01-01")
assert json_response(conn, 200) == [%{"value" => "Mac", "label" => "Mac"}]
end
test "returns suggestions for OS versions", %{conn: conn, site: site} do
filters = Jason.encode!(%{os: "Mac"})
populate_stats(site, [
build(:pageview,
timestamp: ~N[2019-01-01 00:00:00],
operating_system: "Mac",
operating_system_version: "10.15"
)
])
conn =
get(
conn,
"/api/stats/#{site.domain}/suggestions/os_version?period=month&date=2019-01-01&filters=#{filters}"
)
assert json_response(conn, 200) == [%{"label" => "10.15", "value" => "10.15"}]
end
test "returns suggestions for OS versions with search", %{conn: conn, site: site} do
filters = Jason.encode!(%{os: "Mac"})
conn =
get(
conn,
"/api/stats/#{site.domain}/suggestions/os_version?period=month&date=2019-01-01&filters=#{filters}&q=11"
)
assert json_response(conn, 200) == []
end
test "returns suggestions for referrers", %{conn: conn, site: site} do
populate_stats(site, [
build(:pageview,
timestamp: ~N[2019-01-01 23:00:00],
pathname: "/",
referrer: "10words.com/page1"
)
])
conn =
get(conn, "/api/stats/#{site.domain}/suggestions/referrer?period=month&date=2019-01-01")
assert json_response(conn, 200) == [
%{"value" => "10words.com/page1", "label" => "10words.com/page1"}
]
end
end
describe "suggestions for props" do
setup [:create_user, :log_in, :create_new_site]
test "returns suggestions for prop key ordered by count", %{conn: conn, site: site} do
populate_stats(site, [
build(:pageview,
"meta.key": ["author"],
"meta.value": ["Uku Taht"],
timestamp: ~N[2022-01-01 00:00:00]
),
build(:pageview,
"meta.key": ["author"],
"meta.value": ["Uku Taht"],
timestamp: ~N[2022-01-01 00:00:00]
),
build(:pageview,
"meta.key": ["author"],
"meta.value": ["Uku Taht"],
timestamp: ~N[2022-01-01 00:00:00]
),
build(:pageview,
"meta.key": ["logged_in"],
"meta.value": ["false"],
timestamp: ~N[2022-01-01 00:00:00]
),
build(:pageview,
"meta.key": ["logged_in"],
"meta.value": ["false"],
timestamp: ~N[2022-01-01 00:00:00]
),
build(:pageview,
"meta.key": ["dark_mode"],
"meta.value": ["true"],
timestamp: ~N[2022-01-01 00:00:00]
)
])
conn =
get(conn, "/api/stats/#{site.domain}/suggestions/prop_key?period=day&date=2022-01-01")
assert json_response(conn, 200) == [
%{"label" => "author", "value" => "author"},
%{"label" => "logged_in", "value" => "logged_in"},
%{"label" => "dark_mode", "value" => "dark_mode"}
]
end
test "returns suggestions for prop value ordered by count, but (none) value is always first",
%{conn: conn, site: site} do
populate_stats(site, [
build(:pageview,
"meta.key": ["author"],
"meta.value": ["Uku Taht"],
timestamp: ~N[2022-01-01 00:00:00]
),
build(:pageview,
"meta.key": ["author"],
"meta.value": ["Uku Taht"],
timestamp: ~N[2022-01-01 00:00:00]
),
build(:pageview,
"meta.key": ["author"],
"meta.value": ["Marko Saric"],
timestamp: ~N[2022-01-01 00:00:00]
),
build(:pageview,
timestamp: ~N[2022-01-01 00:00:00]
)
])
filters = Jason.encode!(%{props: %{author: "!(none)"}})
conn =
get(
conn,
"/api/stats/#{site.domain}/suggestions/prop_value?period=day&date=2022-01-01&filters=#{filters}"
)
assert json_response(conn, 200) == [
%{"label" => "(none)", "value" => "(none)"},
%{"label" => "Uku Taht", "value" => "Uku Taht"},
%{"label" => "Marko Saric", "value" => "Marko Saric"}
]
end
test "does not show (none) value suggestion when all events have that prop_key", %{
conn: conn,
site: site
} do
populate_stats(site, [
build(:pageview,
"meta.key": ["author"],
"meta.value": ["Uku Taht"],
timestamp: ~N[2022-01-01 00:00:00]
),
build(:pageview,
"meta.key": ["author"],
"meta.value": ["Uku Taht"],
timestamp: ~N[2022-01-01 00:00:00]
),
build(:pageview,
"meta.key": ["author"],
"meta.value": ["Marko Saric"],
timestamp: ~N[2022-01-01 00:00:00]
)
])
filters = Jason.encode!(%{props: %{author: "!(none)"}})
conn =
get(
conn,
"/api/stats/#{site.domain}/suggestions/prop_value?period=day&date=2022-01-01&filters=#{filters}"
)
assert json_response(conn, 200) == [
%{"label" => "Uku Taht", "value" => "Uku Taht"},
%{"label" => "Marko Saric", "value" => "Marko Saric"}
]
end
test "when date is borked, bad request is returned", %{
conn: conn,
site: site
} do
today = (Date.utc_today() |> Date.to_iso8601()) <> " 00:00:00"
naive_today = NaiveDateTime.from_iso8601!(today)
populate_stats(site, [
build(:pageview,
"meta.key": ["author"],
"meta.value": ["Alice Bob"],
timestamp: naive_today
),
build(:pageview,
"meta.key": ["author"],
"meta.value": ["Cecil"],
timestamp: ~N[2022-01-01 00:00:00]
)
])
filters = Jason.encode!(%{props: %{author: "!(none)"}})
conn =
get(
conn,
"/api/stats/#{site.domain}/suggestions/prop_value?period=all&date=CLEVER_SECURITY_RESEARCH&filters=#{filters}"
)
assert json_response(conn, 400) == %{
"error" =>
"Failed to parse date argument. Only ISO 8601 dates are allowed, e.g. `2019-09-07`, `2020-01-01`"
}
end
end
end