| defmodule PlausibleWeb.Api.StatsController.CountriesTest do |
| use PlausibleWeb.ConnCase |
|
|
| defp query_countries(conn, site, opts) do |
| always_on_filters = ["is_not", "visit:country", [<<0, 0>>, "ZZ"]] |
|
|
| params = %{ |
| "dimensions" => Keyword.get(opts, :dimensions, ["visit:country", "visit:country_name"]), |
| "date_range" => Keyword.get(opts, :date_range, "all"), |
| "filters" => [ |
| always_on_filters |
| | Keyword.get(opts, :filters, []) |
| ], |
| "metrics" => Keyword.get(opts, :metrics, ["visitors", "percentage"]), |
| "include" => Keyword.get(opts, :include, nil), |
| "pagination" => Keyword.get(opts, :pagination, nil), |
| "order_by" => Keyword.get(opts, :order_by, nil) |
| } |
|
|
| conn |
| |> post("/api/stats/#{site.domain}/query", params) |
| |> json_response(200) |
| end |
|
|
| describe "querying for countries with POST /query" do |
| setup [:create_user, :log_in, :create_site, :create_legacy_site_import] |
|
|
| test "returns top countries by new visitors", %{conn: conn, site: site} do |
| populate_stats(site, [ |
| build(:pageview, country_code: "EE"), |
| build(:pageview, country_code: "EE"), |
| build(:pageview, country_code: "GB"), |
| build(:imported_locations, country: "EE"), |
| build(:imported_locations, country: "GB"), |
| build(:imported_visitors, visitors: 2) |
| ]) |
|
|
| response1 = query_countries(conn, site, date_range: "day") |
|
|
| assert response1["results"] == [ |
| %{"dimensions" => ["EE", "Estonia"], "metrics" => [2, 66.67]}, |
| %{"dimensions" => ["GB", "United Kingdom"], "metrics" => [1, 33.33]} |
| ] |
|
|
| response2 = |
| query_countries(conn, site, date_range: "day", include: %{"imports" => true}) |
|
|
| assert response2["results"] == [ |
| %{"dimensions" => ["EE", "Estonia"], "metrics" => [3, 60.0]}, |
| %{"dimensions" => ["GB", "United Kingdom"], "metrics" => [2, 40.0]} |
| ] |
| end |
|
|
| test "ignores unknown country code ZZ", %{conn: conn, site: site} do |
| populate_stats(site, [ |
| build(:pageview, country_code: "ZZ"), |
| build(:imported_locations, country: "ZZ") |
| ]) |
|
|
| response = |
| query_countries(conn, site, date_range: "day", include: %{"imports" => true}) |
|
|
| assert response["results"] == [] |
| end |
|
|
| test "includes anonymous VPN country code A1", %{conn: conn, site: site} do |
| populate_stats(site, [ |
| build(:pageview, country_code: "A1"), |
| build(:pageview, country_code: "A1"), |
| build(:pageview, country_code: "EE") |
| ]) |
|
|
| response = |
| query_countries(conn, site, |
| date_range: "day", |
| order_by: [["visitors", "desc"]] |
| ) |
|
|
| assert response["results"] == [ |
| %{"dimensions" => ["A1", "Anonymous VPN Service"], "metrics" => [2, 66.67]}, |
| %{"dimensions" => ["EE", "Estonia"], "metrics" => [1, 33.33]} |
| ] |
| end |
|
|
| test "searching for 'Anonymous' returns VPN visitors", %{ |
| conn: conn, |
| site: site |
| } do |
| populate_stats(site, [ |
| build(:pageview, country_code: "A1"), |
| build(:pageview, country_code: "A1"), |
| build(:pageview, country_code: "EE"), |
| build(:pageview, country_code: "GB") |
| ]) |
|
|
| response = |
| query_countries(conn, site, |
| date_range: "day", |
| filters: [["contains", "visit:country_name", ["Anonymous"]]] |
| ) |
|
|
| assert response["results"] == [ |
| %{"dimensions" => ["A1", "Anonymous VPN Service"], "metrics" => [2, 100.0]} |
| ] |
| end |
|
|
| test "calculates conversion_rate when filtering for goal", %{conn: conn, site: site} do |
| populate_stats(site, [ |
| build(:pageview, |
| user_id: 1, |
| country_code: "EE" |
| ), |
| build(:event, user_id: 1, name: "Signup"), |
| build(:pageview, |
| user_id: 2, |
| country_code: "EE" |
| ), |
| build(:pageview, |
| user_id: 3, |
| country_code: "GB" |
| ), |
| build(:event, user_id: 3, name: "Signup") |
| ]) |
|
|
| insert(:goal, site: site, event_name: "Signup") |
|
|
| response = |
| query_countries(conn, site, |
| date_range: "day", |
| filters: [["is", "event:goal", ["Signup"]]], |
| metrics: ["visitors", "total_visitors", "group_conversion_rate"], |
| order_by: [["visit:country", "asc"]] |
| ) |
|
|
| assert response["results"] == [ |
| %{"dimensions" => ["EE", "Estonia"], "metrics" => [1, 2, 50.0]}, |
| %{"dimensions" => ["GB", "United Kingdom"], "metrics" => [1, 1, 100.0]} |
| ] |
| end |
|
|
| test "handles conversion_rate sort directive", %{conn: conn, site: site} do |
| populate_stats(site, [ |
| build(:pageview, |
| user_id: 1, |
| country_code: "EE" |
| ), |
| build(:event, user_id: 1, name: "Signup"), |
| build(:pageview, |
| user_id: 2, |
| country_code: "EE" |
| ), |
| build(:pageview, |
| user_id: 3, |
| country_code: "GB" |
| ), |
| build(:event, user_id: 3, name: "Signup") |
| ]) |
|
|
| insert(:goal, site: site, event_name: "Signup") |
|
|
| response = |
| query_countries(conn, site, |
| date_range: "day", |
| filters: [["is", "event:goal", ["Signup"]]], |
| metrics: ["visitors", "total_visitors", "group_conversion_rate"], |
| order_by: [["group_conversion_rate", "desc"]] |
| ) |
|
|
| assert response["results"] == [ |
| %{"dimensions" => ["GB", "United Kingdom"], "metrics" => [1, 1, 100.0]}, |
| %{"dimensions" => ["EE", "Estonia"], "metrics" => [1, 2, 50.0]} |
| ] |
| end |
|
|
| test "returns top countries with :is filter on custom pageview props", %{ |
| conn: conn, |
| site: site |
| } do |
| populate_stats(site, [ |
| build(:pageview, |
| user_id: 123, |
| country_code: "EE" |
| ), |
| build(:pageview, |
| user_id: 123, |
| country_code: "EE", |
| "meta.key": ["author"], |
| "meta.value": ["John Doe"] |
| ), |
| build(:pageview, |
| country_code: "GB", |
| "meta.key": ["author"], |
| "meta.value": ["other"] |
| ), |
| build(:pageview, |
| country_code: "US" |
| ) |
| ]) |
|
|
| response = |
| query_countries(conn, site, |
| date_range: "day", |
| filters: [["is", "event:props:author", ["John Doe"]]] |
| ) |
|
|
| assert response["results"] == [ |
| %{"dimensions" => ["EE", "Estonia"], "metrics" => [1, 100.0]} |
| ] |
| end |
|
|
| test "returns top countries with :is_not filter on custom pageview props", %{ |
| conn: conn, |
| site: site |
| } do |
| populate_stats(site, [ |
| build(:pageview, |
| user_id: 123, |
| country_code: "EE", |
| "meta.key": ["author"], |
| "meta.value": ["John Doe"] |
| ), |
| build(:pageview, |
| user_id: 123, |
| country_code: "EE", |
| "meta.key": ["author"], |
| "meta.value": ["John Doe"] |
| ), |
| build(:pageview, |
| country_code: "GB", |
| "meta.key": ["author"], |
| "meta.value": ["other"] |
| ), |
| build(:pageview, |
| country_code: "GB" |
| ) |
| ]) |
|
|
| response = |
| query_countries(conn, site, |
| date_range: "day", |
| filters: [["is_not", "event:props:author", ["John Doe"]]] |
| ) |
|
|
| assert response["results"] == [ |
| %{"dimensions" => ["GB", "United Kingdom"], "metrics" => [2, 100.0]} |
| ] |
| end |
|
|
| test "returns top countries with :is (none) filter on custom pageview props", %{ |
| conn: conn, |
| site: site |
| } do |
| populate_stats(site, [ |
| build(:pageview, |
| country_code: "EE", |
| "meta.key": ["author"], |
| "meta.value": ["John Doe"] |
| ), |
| build(:pageview, |
| country_code: "GB", |
| "meta.key": ["logged_in"], |
| "meta.value": ["true"] |
| ), |
| build(:pageview, |
| country_code: "GB" |
| ) |
| ]) |
|
|
| response = |
| query_countries(conn, site, |
| date_range: "day", |
| filters: [["is", "event:props:author", ["(none)"]]] |
| ) |
|
|
| assert response["results"] == [ |
| %{"dimensions" => ["GB", "United Kingdom"], "metrics" => [2, 100.0]} |
| ] |
| end |
|
|
| test "returns top countries with :is_not (none) filter on custom pageview props", %{ |
| conn: conn, |
| site: site |
| } do |
| populate_stats(site, [ |
| build(:pageview, |
| country_code: "EE", |
| "meta.key": ["author"], |
| "meta.value": ["John Doe"] |
| ), |
| build(:pageview, |
| country_code: "EE", |
| "meta.key": ["author"], |
| "meta.value": [""] |
| ), |
| build(:pageview, |
| country_code: "GB", |
| "meta.key": ["logged_in"], |
| "meta.value": ["true"] |
| ), |
| build(:pageview, |
| country_code: "GB" |
| ) |
| ]) |
|
|
| response = |
| query_countries(conn, site, |
| date_range: "day", |
| filters: [["is_not", "event:props:author", ["(none)"]]] |
| ) |
|
|
| assert response["results"] == [ |
| %{"dimensions" => ["EE", "Estonia"], "metrics" => [2, 100.0]} |
| ] |
| end |
|
|
| test "when list is filtered by country returns one country only", %{conn: conn, site: site} do |
| populate_stats(site, [ |
| build(:pageview, country_code: "EE"), |
| build(:pageview, country_code: "EE"), |
| build(:pageview, country_code: "GB") |
| ]) |
|
|
| response = |
| query_countries(conn, site, |
| date_range: "day", |
| filters: [["is", "visit:country", ["GB"]]] |
| ) |
|
|
| assert response["results"] == [ |
| %{"dimensions" => ["GB", "United Kingdom"], "metrics" => [1, 100.0]} |
| ] |
| end |
|
|
| test "handles multiple segment filters", %{conn: conn, site: site, user: user} do |
| %{id: segment_alfa} = |
| insert(:segment, |
| site_id: site.id, |
| owner_id: user.id, |
| name: "Ireland and Britain (excl London)", |
| type: :site, |
| segment_data: %{ |
| "filters" => [ |
| ["is", "visit:country", ["IE", "GB"]], |
| ["is_not", "visit:city", [2_643_743]] |
| ] |
| } |
| ) |
|
|
| %{id: segment_beta} = |
| insert(:segment, |
| site_id: site.id, |
| owner_id: user.id, |
| name: "Entered on root or blog", |
| type: :personal, |
| segment_data: %{ |
| "filters" => [ |
| ["is", "visit:entry_page", ["/", "/blog"]] |
| ] |
| } |
| ) |
|
|
| populate_stats(site, [ |
| build(:pageview, country_code: "EE"), |
| build(:pageview, |
| country_code: "GB", |
| |
| city_geoname_id: 2_643_743 |
| ), |
| build(:pageview, |
| country_code: "GB", |
| |
| city_geoname_id: 2_643_743 |
| ), |
| build(:pageview, country_code: "GB"), |
| build(:pageview, country_code: "IE", pathname: "/other"), |
| build(:pageview, country_code: "IE") |
| ]) |
|
|
| response = |
| query_countries(conn, site, |
| date_range: "day", |
| filters: [ |
| ["is", "segment", [segment_alfa]], |
| ["is", "segment", [segment_beta]] |
| ], |
| order_by: [["visitors", "desc"], ["visit:country", "asc"]] |
| ) |
|
|
| assert response["results"] == [ |
| %{"dimensions" => ["GB", "United Kingdom"], "metrics" => [1, 50.0]}, |
| %{"dimensions" => ["IE", "Ireland"], "metrics" => [1, 50.0]} |
| ] |
| end |
|
|
| @tag :ee_only |
| test "return revenue metrics for countries breakdown", %{conn: conn, site: site} do |
| populate_stats(site, [ |
| build(:pageview, |
| user_id: 1, |
| country_code: "EE" |
| ), |
| build(:event, |
| name: "Payment", |
| user_id: 1, |
| revenue_reporting_amount: Decimal.new("1000"), |
| revenue_reporting_currency: "USD" |
| ), |
| build(:pageview, |
| user_id: 2, |
| country_code: "EE" |
| ), |
| build(:event, |
| name: "Payment", |
| user_id: 2, |
| revenue_reporting_amount: Decimal.new("2000"), |
| revenue_reporting_currency: "USD" |
| ), |
| build(:pageview, |
| user_id: 3, |
| country_code: "EE" |
| ), |
| build(:pageview, |
| user_id: 4, |
| country_code: "GB" |
| ), |
| build(:event, |
| name: "Payment", |
| user_id: 4, |
| revenue_reporting_amount: Decimal.new("500"), |
| revenue_reporting_currency: "USD" |
| ), |
| build(:pageview, |
| user_id: 5, |
| country_code: "GB" |
| ), |
| build(:pageview, user_id: 6), |
| build(:event, |
| name: "Payment", |
| user_id: 6, |
| revenue_reporting_amount: Decimal.new("600"), |
| revenue_reporting_currency: "USD" |
| ), |
| build(:pageview, user_id: 7), |
| build(:event, |
| name: "Payment", |
| user_id: 7, |
| revenue_reporting_amount: nil |
| ) |
| ]) |
|
|
| insert(:goal, %{site: site, event_name: "Payment", currency: :USD}) |
|
|
| response = |
| query_countries(conn, site, |
| date_range: "day", |
| filters: [["is", "event:goal", ["Payment"]]], |
| metrics: [ |
| "visitors", |
| "total_visitors", |
| "group_conversion_rate", |
| "average_revenue", |
| "total_revenue" |
| ], |
| order_by: [["visitors", "desc"], ["visit:country", "asc"]] |
| ) |
|
|
| assert response["results"] == [ |
| %{ |
| "dimensions" => ["EE", "Estonia"], |
| "metrics" => [ |
| 2, |
| 3, |
| 66.67, |
| %{ |
| "currency" => "USD", |
| "long" => "$1,500.00", |
| "short" => "$1.5K", |
| "value" => 1500.0 |
| }, |
| %{ |
| "currency" => "USD", |
| "long" => "$3,000.00", |
| "short" => "$3.0K", |
| "value" => 3000.0 |
| } |
| ] |
| }, |
| %{ |
| "dimensions" => ["GB", "United Kingdom"], |
| "metrics" => [ |
| 1, |
| 2, |
| 50.0, |
| %{ |
| "currency" => "USD", |
| "long" => "$500.00", |
| "short" => "$500.0", |
| "value" => 500.0 |
| }, |
| %{ |
| "currency" => "USD", |
| "long" => "$500.00", |
| "short" => "$500.0", |
| "value" => 500.0 |
| } |
| ] |
| } |
| ] |
| end |
| end |
| end |
|
|