| defmodule PlausibleWeb.Api.StatsController.ConversionsTest do |
| use PlausibleWeb.ConnCase |
|
|
| @user_id Enum.random(1000..9999) |
|
|
| defp query_conversions(conn, site, opts) do |
| params = %{ |
| "dimensions" => Keyword.get(opts, :dimensions, ["event:goal"]), |
| "date_range" => Keyword.get(opts, :date_range, "all"), |
| "filters" => Keyword.get(opts, :filters, []), |
| "metrics" => Keyword.get(opts, :metrics, ["visitors", "events", "conversion_rate"]), |
| "include" => Keyword.get(opts, :include, nil), |
| "order_by" => Keyword.get(opts, :order_by, nil) |
| } |
|
|
| conn |
| |> post("/api/stats/#{site.domain}/query", params) |
| |> json_response(200) |
| end |
|
|
| describe "GET /api/stats/:domain/conversions" do |
| setup [:create_user, :log_in, :create_site] |
|
|
| test "returns mixed pageview and custom event goal conversions ordered by count", %{ |
| conn: conn, |
| site: site |
| } do |
| populate_stats(site, [ |
| build(:pageview, pathname: "/"), |
| build(:pageview, pathname: "/"), |
| build(:pageview, pathname: "/register"), |
| build(:pageview, pathname: "/register"), |
| build(:event, name: "Signup", "meta.key": ["variant"], "meta.value": ["A"]), |
| build(:event, |
| user_id: @user_id, |
| name: "Signup", |
| "meta.key": ["variant"], |
| "meta.value": ["A"] |
| ), |
| build(:event, |
| user_id: @user_id, |
| name: "Signup", |
| "meta.key": ["variant"], |
| "meta.value": ["B"] |
| ), |
| build(:event, name: "Signup") |
| ]) |
|
|
| insert(:goal, %{site: site, page_path: "/register"}) |
| insert(:goal, %{site: site, event_name: "Signup"}) |
|
|
| response = query_conversions(conn, site, date_range: "day") |
|
|
| assert response["results"] == [ |
| %{"dimensions" => ["Signup"], "metrics" => [3, 4, 42.86]}, |
| %{"dimensions" => ["Visit /register"], "metrics" => [2, 2, 28.57]} |
| ] |
| end |
|
|
| test "returns page scroll goals ordered by count", %{conn: conn, site: site} do |
| populate_stats(site, [ |
| |
| build(:pageview, user_id: 1, pathname: "/blog", timestamp: ~N[2020-01-01 00:00:00]), |
| build(:engagement, |
| user_id: 1, |
| pathname: "/blog", |
| timestamp: ~N[2020-01-01 00:01:00], |
| scroll_depth: 20 |
| ), |
| build(:pageview, user_id: 1, pathname: "/another", timestamp: ~N[2020-01-01 00:01:00]), |
| build(:engagement, |
| user_id: 1, |
| pathname: "/another", |
| timestamp: ~N[2020-01-01 00:02:00], |
| scroll_depth: 100 |
| ), |
| build(:pageview, |
| user_id: 1, |
| pathname: "/blog/posts/1", |
| timestamp: ~N[2020-01-01 00:02:00] |
| ), |
| build(:engagement, |
| user_id: 1, |
| pathname: "/blog/posts/1", |
| timestamp: ~N[2020-01-01 00:03:00], |
| scroll_depth: 55 |
| ), |
| |
| build(:pageview, user_id: 2, pathname: "/blog", timestamp: ~N[2020-01-01 00:00:00]), |
| build(:engagement, |
| user_id: 2, |
| pathname: "/blog", |
| timestamp: ~N[2020-01-01 00:01:00], |
| scroll_depth: 60 |
| ), |
| build(:pageview, |
| user_id: 2, |
| pathname: "/blog/posts/1", |
| timestamp: ~N[2020-01-01 00:02:00] |
| ), |
| build(:engagement, |
| user_id: 2, |
| pathname: "/blog/posts/1", |
| timestamp: ~N[2020-01-01 00:03:00], |
| scroll_depth: 100 |
| ), |
| build(:pageview, |
| user_id: 2, |
| pathname: "/blog/posts/2", |
| timestamp: ~N[2020-01-01 00:02:00] |
| ), |
| build(:engagement, |
| user_id: 2, |
| pathname: "/blog/posts/2", |
| timestamp: ~N[2020-01-01 00:03:00], |
| scroll_depth: 100 |
| ) |
| ]) |
|
|
| insert(:goal, %{ |
| site: site, |
| page_path: "/blog/**", |
| scroll_threshold: 50, |
| display_name: "Scroll 50 /blog/**" |
| }) |
|
|
| insert(:goal, %{ |
| site: site, |
| page_path: "/blog/posts/1", |
| scroll_threshold: 75, |
| display_name: "Scroll 75 /blog/posts/1" |
| }) |
|
|
| response = query_conversions(conn, site, date_range: ["2020-01-01", "2020-01-01"]) |
|
|
| assert response["results"] == [ |
| %{"dimensions" => ["Scroll 50 /blog/**"], "metrics" => [2, nil, 100.0]}, |
| %{"dimensions" => ["Scroll 75 /blog/posts/1"], "metrics" => [1, nil, 50.0]} |
| ] |
| end |
|
|
| test "returns conversions when a direct :is filter on event prop", %{conn: conn, site: site} do |
| populate_stats(site, [ |
| build(:event, |
| user_id: @user_id, |
| name: "Payment", |
| "meta.key": ["amount", "logged_in"], |
| "meta.value": ["100", "true"] |
| ), |
| build(:event, |
| user_id: @user_id, |
| name: "Payment", |
| "meta.key": ["amount", "logged_in"], |
| "meta.value": ["500", "true"] |
| ), |
| build(:event, |
| name: "Payment", |
| "meta.key": ["amount", "logged_in"], |
| "meta.value": ["100", "false"] |
| ), |
| build(:event, |
| name: "Payment", |
| "meta.key": ["amount"], |
| "meta.value": ["200"] |
| ) |
| ]) |
|
|
| insert(:goal, %{site: site, event_name: "Payment"}) |
|
|
| response = |
| query_conversions(conn, site, |
| date_range: "day", |
| filters: [["is", "event:props:logged_in", ["true"]]] |
| ) |
|
|
| assert response["results"] == [ |
| %{"dimensions" => ["Payment"], "metrics" => [1, 2, 33.33]} |
| ] |
| end |
|
|
| test "returns conversions when a direct :is_not filter on event prop", %{ |
| conn: conn, |
| site: site |
| } do |
| populate_stats(site, [ |
| build(:event, |
| user_id: @user_id, |
| name: "Payment", |
| "meta.key": ["amount", "logged_in"], |
| "meta.value": ["100", "true"] |
| ), |
| build(:event, |
| user_id: @user_id, |
| name: "Payment", |
| "meta.key": ["amount", "logged_in"], |
| "meta.value": ["500", "true"] |
| ), |
| build(:event, |
| name: "Payment", |
| "meta.key": ["amount", "logged_in"], |
| "meta.value": ["100", "false"] |
| ), |
| build(:event, name: "Payment") |
| ]) |
|
|
| insert(:goal, %{site: site, event_name: "Payment"}) |
|
|
| response = |
| query_conversions(conn, site, |
| date_range: "day", |
| filters: [["is_not", "event:props:logged_in", ["true"]]] |
| ) |
|
|
| assert response["results"] == [ |
| %{"dimensions" => ["Payment"], "metrics" => [2, 2, 66.67]} |
| ] |
| end |
|
|
| test "returns conversions when a direct :is (none) filter on event prop", %{ |
| conn: conn, |
| site: site |
| } do |
| populate_stats(site, [ |
| build(:event, |
| user_id: @user_id, |
| name: "Payment" |
| ), |
| build(:event, |
| user_id: @user_id, |
| name: "Payment", |
| "meta.key": ["amount"], |
| "meta.value": ["500"] |
| ), |
| build(:event, |
| name: "Payment", |
| "meta.key": ["amount", "logged_in"], |
| "meta.value": ["100", "false"] |
| ), |
| build(:event, name: "Payment") |
| ]) |
|
|
| insert(:goal, %{site: site, event_name: "Payment"}) |
|
|
| response = |
| query_conversions(conn, site, |
| date_range: "day", |
| filters: [["is", "event:props:logged_in", ["(none)"]]] |
| ) |
|
|
| assert response["results"] == [ |
| %{"dimensions" => ["Payment"], "metrics" => [2, 3, 66.67]} |
| ] |
| end |
|
|
| test "returns conversions when a direct :is_not (none) filter on event prop", %{ |
| conn: conn, |
| site: site |
| } do |
| populate_stats(site, [ |
| build(:event, |
| user_id: @user_id, |
| name: "Payment", |
| "meta.key": ["amount", "logged_in"], |
| "meta.value": ["500", "false"] |
| ), |
| build(:event, |
| user_id: @user_id, |
| name: "Payment", |
| "meta.key": ["amount", "logged_in"], |
| "meta.value": ["500", "true"] |
| ), |
| build(:event, |
| name: "Payment", |
| "meta.key": ["amount", "logged_in"], |
| "meta.value": ["100", "false"] |
| ), |
| build(:event, name: "Payment") |
| ]) |
|
|
| insert(:goal, %{site: site, event_name: "Payment"}) |
|
|
| response = |
| query_conversions(conn, site, |
| date_range: "day", |
| filters: [["is_not", "event:props:logged_in", ["(none)"]]] |
| ) |
|
|
| assert response["results"] == [ |
| %{"dimensions" => ["Payment"], "metrics" => [2, 3, 66.67]} |
| ] |
| end |
|
|
| test "garbage filters result in a 400 response", %{conn: conn, site: site} do |
| params = %{ |
| "date_range" => "all", |
| "metrics" => ["visitors", "events", "conversion_rate"], |
| "filters" => [ |
| ["is", "visit:city AND 2*3*8=6*8 AND 'L9sv'!='L9sv%", ["a"]] |
| ] |
| } |
|
|
| response = |
| conn |
| |> post("/api/stats/#{site.domain}/query", params) |
| |> json_response(400) |
|
|
| assert response["error"] =~ "filter" |
| end |
|
|
| test "filtering by session attribute and multiple goals", %{ |
| conn: conn, |
| site: site |
| } do |
| populate_stats(site, [ |
| build(:event, |
| user_id: @user_id, |
| name: "Payment", |
| browser: "Firefox" |
| ), |
| build(:event, |
| user_id: @user_id, |
| name: "Payment", |
| browser: "Firefox" |
| ), |
| build(:event, |
| name: "Payment", |
| browser: "Chrome" |
| ), |
| build(:event, name: "Payment"), |
| build(:pageview, browser: "Firefox", pathname: "/"), |
| build(:pageview, browser: "Firefox", pathname: "/register") |
| ]) |
|
|
| insert(:goal, %{site: site, event_name: "Payment"}) |
| insert(:goal, %{site: site, page_path: "/register"}) |
|
|
| response = |
| query_conversions(conn, site, |
| date_range: "day", |
| filters: [["is", "visit:browser", ["Firefox"]]] |
| ) |
|
|
| assert response["results"] == [ |
| %{"dimensions" => ["Payment"], "metrics" => [1, 2, 33.33]}, |
| %{"dimensions" => ["Visit /register"], "metrics" => [1, 1, 33.33]} |
| ] |
| end |
|
|
| @tag :ee_only |
| test "returns formatted average and total values for a conversion with revenue value", %{ |
| conn: conn, |
| site: site |
| } do |
| site_import = insert(:site_import, site: site) |
|
|
| populate_stats(site, site_import.id, [ |
| build(:event, |
| name: "Payment", |
| revenue_reporting_amount: Decimal.new("200100300.123"), |
| revenue_reporting_currency: "USD" |
| ), |
| build(:event, |
| name: "Payment", |
| revenue_reporting_amount: Decimal.new("300100400.123"), |
| revenue_reporting_currency: "USD" |
| ), |
| build(:event, |
| name: "Payment", |
| revenue_reporting_amount: Decimal.new("0"), |
| revenue_reporting_currency: "USD" |
| ), |
| build(:event, name: "Payment", revenue_reporting_amount: nil), |
| build(:event, name: "Payment", revenue_reporting_amount: nil) |
| ]) |
|
|
| insert(:goal, %{site: site, event_name: "Payment", currency: :EUR}) |
|
|
| response = |
| query_conversions(conn, site, |
| date_range: "day", |
| metrics: ["visitors", "events", "conversion_rate", "average_revenue", "total_revenue"], |
| include: %{"imports" => true} |
| ) |
|
|
| assert response["results"] == [ |
| %{ |
| "dimensions" => ["Payment"], |
| "metrics" => [ |
| 5, |
| 5, |
| 100.0, |
| %{ |
| "short" => "€166.7M", |
| "long" => "€166,733,566.75", |
| "value" => 166_733_566.748, |
| "currency" => "EUR" |
| }, |
| %{ |
| "short" => "€500.2M", |
| "long" => "€500,200,700.25", |
| "value" => 500_200_700.246, |
| "currency" => "EUR" |
| } |
| ] |
| } |
| ] |
| end |
|
|
| @tag :ee_only |
| test "returns revenue goals as custom events if the plan doesn't cover the feature", %{ |
| conn: conn, |
| site: site, |
| user: user |
| } do |
| user |
| |> team_of() |
| |> Plausible.Teams.Team.end_trial() |
| |> Plausible.Repo.update!() |
|
|
| populate_stats(site, [ |
| build(:event, |
| name: "Payment", |
| revenue_reporting_amount: Decimal.new("200100300.123"), |
| revenue_reporting_currency: "USD" |
| ), |
| build(:event, |
| name: "Payment", |
| revenue_reporting_amount: Decimal.new("300100400.123"), |
| revenue_reporting_currency: "USD" |
| ), |
| build(:event, |
| name: "Payment", |
| revenue_reporting_amount: Decimal.new("0"), |
| revenue_reporting_currency: "USD" |
| ), |
| build(:event, name: "Payment", revenue_reporting_amount: nil), |
| build(:event, name: "Payment", revenue_reporting_amount: nil) |
| ]) |
|
|
| insert(:goal, %{site: site, event_name: "Payment", currency: :EUR}) |
|
|
| response = |
| query_conversions(conn, site, |
| date_range: "day", |
| metrics: ["visitors", "events", "conversion_rate", "average_revenue", "total_revenue"] |
| ) |
|
|
| assert response["query"]["metrics"] == ["visitors", "events", "conversion_rate"] |
|
|
| assert response["results"] == [ |
| %{"dimensions" => ["Payment"], "metrics" => [5, 5, 100.0]} |
| ] |
| end |
|
|
| @tag :ee_only |
| test "excludes goals with custom props when Props feature is unavailable", %{ |
| conn: conn, |
| site: site, |
| user: user |
| } do |
| user |
| |> team_of() |
| |> Plausible.Teams.Team.end_trial() |
| |> Plausible.Repo.update!() |
|
|
| populate_stats(site, [ |
| build(:event, name: "Signup"), |
| build(:event, name: "Signup"), |
| build(:event, name: "Signup"), |
| build(:event, name: "Purchase", "meta.key": ["product"], "meta.value": ["Shirt"]) |
| ]) |
|
|
| {:ok, _goal_with_props} = |
| Plausible.Goals.create(site, %{ |
| "event_name" => "Purchase", |
| "custom_props" => %{"product" => "Shirt"} |
| }) |
|
|
| insert(:goal, %{site: site, event_name: "Signup"}) |
|
|
| response = query_conversions(conn, site, date_range: "day") |
|
|
| assert response["results"] == [ |
| %{"dimensions" => ["Signup"], "metrics" => [3, 3, 75.0]} |
| ] |
| end |
|
|
| @tag :ee_only |
| test "includes goals with custom props when Props feature is available", %{ |
| conn: conn, |
| site: site |
| } do |
| populate_stats(site, [ |
| build(:event, name: "Purchase", "meta.key": ["product"], "meta.value": ["Shirt"]), |
| build(:event, name: "Purchase", "meta.key": ["product"], "meta.value": ["Shirt"]) |
| ]) |
|
|
| {:ok, _goal_with_props} = |
| Plausible.Goals.create(site, %{ |
| "event_name" => "Purchase", |
| "custom_props" => %{"product" => "Shirt"} |
| }) |
|
|
| response = query_conversions(conn, site, date_range: "day") |
|
|
| assert response["results"] == [ |
| %{"dimensions" => ["Purchase"], "metrics" => [2, 2, 100.0]} |
| ] |
| end |
|
|
| test "returns correct conversion stats for goals with and without custom properties", %{ |
| conn: conn, |
| site: site |
| } do |
| populate_stats(site, [ |
| build(:event, name: "Purchase", "meta.key": ["product"], "meta.value": ["Shirt"]), |
| build(:event, name: "Purchase", "meta.key": ["product"], "meta.value": ["Shirt"]), |
| build(:event, name: "Purchase", "meta.key": ["product"], "meta.value": ["Jacket"]) |
| ]) |
|
|
| {:ok, _} = |
| Plausible.Goals.create( |
| site, |
| %{ |
| "event_name" => "Purchase", |
| "display_name" => "Purchase - Shirt", |
| "custom_props" => %{"product" => "Shirt"} |
| } |
| ) |
|
|
| {:ok, _} = |
| Plausible.Goals.create( |
| site, |
| %{ |
| "event_name" => "Purchase", |
| "display_name" => "Purchase - Jacket", |
| "custom_props" => %{"product" => "Jacket"} |
| } |
| ) |
|
|
| {:ok, _} = |
| Plausible.Goals.create( |
| site, |
| %{ |
| "event_name" => "Purchase", |
| "display_name" => "Purchase - All" |
| } |
| ) |
|
|
| response = query_conversions(conn, site, date_range: "day") |
|
|
| assert response["results"] == [ |
| %{"dimensions" => ["Purchase - All"], "metrics" => [3, 3, 100.0]}, |
| %{"dimensions" => ["Purchase - Shirt"], "metrics" => [2, 2, 66.67]}, |
| %{"dimensions" => ["Purchase - Jacket"], "metrics" => [1, 1, 33.33]} |
| ] |
| end |
|
|
| @tag :ee_only |
| test "handles mixed goals with and without custom props (2)", %{ |
| conn: conn, |
| site: site |
| } do |
| populate_stats(site, [ |
| build(:event, name: "Signup"), |
| build(:event, name: "Purchase", "meta.key": ["product"], "meta.value": ["Shirt"]), |
| build(:event, name: "Purchase", "meta.key": ["product"], "meta.value": ["Shirt"]) |
| ]) |
|
|
| {:ok, _goal_with_props} = |
| Plausible.Goals.create( |
| site, |
| %{ |
| "event_name" => "Purchase", |
| "custom_props" => %{"product" => "Shirt"} |
| } |
| ) |
|
|
| insert(:goal, %{site: site, event_name: "Signup"}) |
|
|
| response = query_conversions(conn, site, date_range: "day") |
|
|
| assert [ |
| %{"dimensions" => ["Purchase"], "metrics" => [2, 2, 66.67]}, |
| %{"dimensions" => ["Signup"], "metrics" => [1, 1, 33.33]} |
| ] = response["results"] |
| end |
|
|
| @tag :ee_only |
| test "returns revenue metrics as nil for non-revenue goals", %{ |
| conn: conn, |
| site: site |
| } do |
| [ |
| build(:event, |
| name: "Payment", |
| pathname: "/checkout", |
| revenue_reporting_amount: Decimal.new("10.00"), |
| revenue_reporting_currency: "EUR" |
| ) |
| ] |
| |> Enum.concat(build_list(2, :event, name: "Signup")) |
| |> Enum.concat(build_list(3, :pageview, pathname: "/checkout")) |
| |> then(fn events -> populate_stats(site, events) end) |
|
|
| insert(:goal, %{site: site, event_name: "Signup"}) |
| insert(:goal, %{site: site, page_path: "/checkout"}) |
| insert(:goal, %{site: site, event_name: "Payment", currency: :EUR}) |
|
|
| response = |
| query_conversions(conn, site, |
| date_range: "day", |
| metrics: ["visitors", "events", "conversion_rate", "average_revenue", "total_revenue"], |
| order_by: [["visitors", "asc"]] |
| ) |
|
|
| assert response["results"] == [ |
| %{ |
| "dimensions" => ["Payment"], |
| "metrics" => [ |
| 1, |
| 1, |
| 16.67, |
| %{ |
| "long" => "€10.00", |
| "short" => "€10.0", |
| "value" => 10.0, |
| "currency" => "EUR" |
| }, |
| %{ |
| "long" => "€10.00", |
| "short" => "€10.0", |
| "value" => 10.0, |
| "currency" => "EUR" |
| } |
| ] |
| }, |
| %{"dimensions" => ["Signup"], "metrics" => [2, 2, 33.33, nil, nil]}, |
| %{"dimensions" => ["Visit /checkout"], "metrics" => [3, 3, 50.0, nil, nil]} |
| ] |
| end |
|
|
| @tag :ee_only |
| test "does not return revenue metrics if no revenue goals are returned", %{ |
| conn: conn, |
| site: site |
| } do |
| populate_stats(site, [ |
| build(:event, name: "Signup") |
| ]) |
|
|
| insert(:goal, %{site: site, event_name: "Signup"}) |
|
|
| response = |
| query_conversions(conn, site, |
| date_range: "day", |
| metrics: [ |
| "visitors", |
| "events", |
| "conversion_rate", |
| "average_revenue", |
| "total_revenue" |
| ] |
| ) |
|
|
| assert response["query"]["metrics"] == ["visitors", "events", "conversion_rate"] |
|
|
| assert response["results"] == [ |
| %{"dimensions" => ["Signup"], "metrics" => [1, 1, 100.0]} |
| ] |
| end |
| end |
|
|
| describe "GET /api/stats/:domain/conversions - with goal filter" do |
| setup [:create_user, :log_in, :create_site] |
|
|
| test "does not consider custom event pathname as a pageview goal completion", %{ |
| conn: conn, |
| site: site |
| } do |
| populate_stats(site, [ |
| build(:pageview, pathname: "/"), |
| build(:pageview, pathname: "/register"), |
| build(:event, pathname: "/register", name: "Signup") |
| ]) |
|
|
| insert(:goal, %{site: site, page_path: "/register"}) |
| insert(:goal, %{site: site, event_name: "Signup"}) |
|
|
| get_with_filters = fn filters -> |
| query_conversions(conn, site, date_range: "day", filters: filters) |
| |> Map.get("results") |
| end |
|
|
| expected = [ |
| %{"dimensions" => ["Signup"], "metrics" => [1, 1, 33.33]} |
| ] |
|
|
| |
| assert get_with_filters.([["is", "event:goal", ["Signup"]]]) == expected |
|
|
| |
| assert get_with_filters.([["is", "event:goal", ["Signup", "Whatever"]]]) == expected |
|
|
| |
| assert get_with_filters.([["is", "event:goal", ["Signup", "Visit /whatever*"]]]) == expected |
| end |
|
|
| test "does not return custom events with the filtered pageview goal pathname", %{ |
| conn: conn, |
| site: site |
| } do |
| populate_stats(site, [ |
| build(:pageview, pathname: "/"), |
| build(:pageview, pathname: "/register"), |
| build(:event, pathname: "/register", name: "Signup") |
| ]) |
|
|
| insert(:goal, %{site: site, page_path: "/register"}) |
| insert(:goal, %{site: site, event_name: "Signup"}) |
|
|
| response = |
| query_conversions(conn, site, |
| date_range: "day", |
| filters: [["is", "event:goal", ["Visit /register"]]] |
| ) |
|
|
| assert response["results"] == [ |
| %{"dimensions" => ["Visit /register"], "metrics" => [1, 1, 33.33]} |
| ] |
| end |
|
|
| test "can filter by multiple mixed goals", %{conn: conn, site: site} do |
| populate_stats(site, [ |
| build(:pageview, pathname: "/"), |
| build(:pageview, pathname: "/"), |
| build(:pageview, pathname: "/another"), |
| build(:pageview, pathname: "/register"), |
| build(:event, name: "Signup"), |
| build(:event, name: "Signup") |
| ]) |
|
|
| insert(:goal, %{site: site, page_path: "/register"}) |
| insert(:goal, %{site: site, event_name: "Signup"}) |
|
|
| response = |
| query_conversions(conn, site, |
| date_range: "day", |
| filters: [["is", "event:goal", ["Signup", "Visit /register"]]] |
| ) |
|
|
| assert response["results"] == [ |
| %{"dimensions" => ["Signup"], "metrics" => [2, 2, 33.33]}, |
| %{"dimensions" => ["Visit /register"], "metrics" => [1, 1, 16.67]} |
| ] |
| end |
|
|
| test "can combine wildcard and no wildcard in matches_member", %{conn: conn, site: site} do |
| populate_stats(site, [ |
| build(:pageview, pathname: "/blog/post-1"), |
| build(:pageview, pathname: "/blog/post-2"), |
| build(:pageview, pathname: "/billing/upgrade") |
| ]) |
|
|
| insert(:goal, %{site: site, page_path: "/blog/**"}) |
| insert(:goal, %{site: site, page_path: "/billing/upgrade"}) |
|
|
| response = |
| query_conversions(conn, site, |
| date_range: "day", |
| filters: [["is", "event:goal", ["Visit /blog/**", "Visit /billing/upgrade"]]] |
| ) |
|
|
| assert response["results"] == [ |
| %{"dimensions" => ["Visit /blog/**"], "metrics" => [2, 2, 66.67]}, |
| %{"dimensions" => ["Visit /billing/upgrade"], "metrics" => [1, 1, 33.33]} |
| ] |
| end |
|
|
| test "can filter by matches_member filter type on goals", %{conn: conn, site: site} do |
| populate_stats(site, [ |
| build(:pageview, pathname: "/"), |
| build(:pageview, pathname: "/another"), |
| build(:pageview, pathname: "/blog/post-1"), |
| build(:pageview, pathname: "/blog/post-2"), |
| build(:event, name: "CTA"), |
| build(:event, name: "Signup") |
| ]) |
|
|
| insert(:goal, %{site: site, page_path: "/blog**"}) |
| insert(:goal, %{site: site, event_name: "CTA"}) |
| insert(:goal, %{site: site, event_name: "Signup"}) |
|
|
| response = |
| query_conversions(conn, site, |
| date_range: "day", |
| filters: [["is", "event:goal", ["Signup", "Visit /blog**"]]] |
| ) |
|
|
| assert response["results"] == [ |
| %{"dimensions" => ["Visit /blog**"], "metrics" => [2, 2, 33.33]}, |
| %{"dimensions" => ["Signup"], "metrics" => [1, 1, 16.67]} |
| ] |
| end |
| end |
|
|
| describe "GET /api/stats/:domain/conversions - with goal and prop=(none) filter" do |
| setup [:create_user, :log_in, :create_site] |
|
|
| test "returns only the conversion that is filtered for", %{conn: conn, site: site} do |
| populate_stats(site, [ |
| build(:pageview, pathname: "/", user_id: 1), |
| build(:pageview, pathname: "/", user_id: 2), |
| build(:event, name: "Signup", user_id: 1, "meta.key": ["variant"], "meta.value": ["A"]), |
| build(:event, name: "Signup", user_id: 2) |
| ]) |
|
|
| insert(:goal, %{site: site, event_name: "Signup"}) |
|
|
| response = |
| query_conversions(conn, site, |
| date_range: "day", |
| filters: [ |
| ["is", "event:goal", ["Signup"]], |
| ["is", "event:props:variant", ["(none)"]] |
| ] |
| ) |
|
|
| assert response["results"] == [ |
| %{"dimensions" => ["Signup"], "metrics" => [1, 1, 50.0]} |
| ] |
| end |
| end |
|
|
| describe "GET /api/stats/:domain/conversions - with glob goals" do |
| setup [:create_user, :log_in, :create_site] |
|
|
| test "returns correct and sorted glob goal counts", %{conn: conn, site: site} do |
| insert(:goal, %{site: site, page_path: "/register"}) |
| insert(:goal, %{site: site, page_path: "/reg*"}) |
| insert(:goal, %{site: site, page_path: "/*/register"}) |
| insert(:goal, %{site: site, page_path: "/billing*/success"}) |
| insert(:goal, %{site: site, page_path: "/signup"}) |
| insert(:goal, %{site: site, page_path: "/signup/*"}) |
| insert(:goal, %{site: site, page_path: "/*"}) |
|
|
| populate_stats(site, [ |
| build(:pageview, |
| pathname: "/hum", |
| timestamp: ~N[2019-07-01 23:00:00] |
| ), |
| build(:pageview, |
| pathname: "/register", |
| timestamp: ~N[2019-07-01 23:00:00] |
| ), |
| build(:pageview, |
| user_id: @user_id, |
| pathname: "/reg", |
| timestamp: ~N[2019-07-01 23:00:00] |
| ), |
| build(:pageview, |
| user_id: @user_id, |
| pathname: "/reg123", |
| timestamp: ~N[2019-07-01 23:10:00] |
| ), |
| build(:pageview, |
| pathname: "/billing/success", |
| timestamp: ~N[2019-07-01 23:00:00] |
| ), |
| build(:pageview, |
| pathname: "/billing/upgrade/success", |
| timestamp: ~N[2019-07-01 23:00:00] |
| ), |
| build(:pageview, |
| pathname: "/signup/new", |
| timestamp: ~N[2019-07-01 23:00:00] |
| ), |
| build(:pageview, |
| pathname: "/signup/new/2", |
| timestamp: ~N[2019-07-01 23:00:00] |
| ), |
| build(:pageview, |
| pathname: "/signup/new/3", |
| timestamp: ~N[2019-07-01 23:00:00] |
| ) |
| ]) |
|
|
| response = |
| query_conversions(conn, site, |
| date_range: ["2019-07-01", "2019-07-01"], |
| order_by: [["visitors", "desc"], ["events", "asc"]] |
| ) |
|
|
| assert response["results"] == [ |
| %{"dimensions" => ["Visit /*"], "metrics" => [8, 9, 100.0]}, |
| %{"dimensions" => ["Visit /signup/*"], "metrics" => [3, 3, 37.5]}, |
| %{"dimensions" => ["Visit /billing*/success"], "metrics" => [2, 2, 25.0]}, |
| %{"dimensions" => ["Visit /reg*"], "metrics" => [2, 3, 25.0]}, |
| %{"dimensions" => ["Visit /register"], "metrics" => [1, 1, 12.5]} |
| ] |
| end |
| end |
|
|
| describe "GET /api/stats/:domain/conversions - with imported data" do |
| setup [:create_user, :log_in, :create_site] |
|
|
| test "returns custom event goals and pageview goals", %{conn: conn, site: site} do |
| insert(:goal, site: site, event_name: "Purchase") |
| insert(:goal, site: site, page_path: "/test") |
|
|
| site_import = insert(:site_import, site: site) |
|
|
| populate_stats(site, site_import.id, [ |
| build(:pageview, |
| timestamp: ~N[2021-01-01 00:00:01], |
| pathname: "/test" |
| ), |
| build(:event, |
| name: "Purchase", |
| timestamp: ~N[2021-01-01 00:00:03] |
| ), |
| build(:event, |
| name: "Purchase", |
| timestamp: ~N[2021-01-01 00:00:03] |
| ), |
| build(:imported_custom_events, |
| name: "Purchase", |
| visitors: 3, |
| events: 5, |
| date: ~D[2021-01-01] |
| ), |
| build(:imported_pages, |
| page: "/test", |
| visitors: 2, |
| pageviews: 2, |
| date: ~D[2021-01-01] |
| ), |
| build(:imported_visitors, visitors: 5, date: ~D[2021-01-01]) |
| ]) |
|
|
| response = |
| query_conversions(conn, site, |
| date_range: ["2021-01-01", "2021-01-01"], |
| include: %{"imports" => true} |
| ) |
|
|
| assert [ |
| %{"dimensions" => ["Purchase"], "metrics" => [5, 7, 62.5]}, |
| %{"dimensions" => ["Visit /test"], "metrics" => [3, 3, 37.5]} |
| ] = response["results"] |
| end |
|
|
| test "returns only custom event goals with a custom event goal filter", %{ |
| conn: conn, |
| site: site |
| } do |
| insert(:goal, site: site, event_name: "Purchase") |
| insert(:goal, site: site, event_name: "Activation") |
| insert(:goal, site: site, page_path: "/test") |
|
|
| site_import = insert(:site_import, site: site) |
|
|
| populate_stats(site, site_import.id, [ |
| build(:pageview, |
| timestamp: ~N[2021-01-01 00:00:01], |
| pathname: "/test" |
| ), |
| build(:event, |
| name: "Purchase", |
| timestamp: ~N[2021-01-01 00:00:03] |
| ), |
| build(:event, |
| name: "Purchase", |
| timestamp: ~N[2021-01-01 00:00:03] |
| ), |
| build(:imported_custom_events, |
| name: "Purchase", |
| visitors: 3, |
| events: 5, |
| date: ~D[2021-01-01] |
| ), |
| build(:imported_pages, |
| page: "/test", |
| visitors: 2, |
| pageviews: 2, |
| date: ~D[2021-01-01] |
| ), |
| build(:imported_visitors, visitors: 5, date: ~D[2021-01-01]) |
| ]) |
|
|
| response = |
| query_conversions(conn, site, |
| date_range: ["2021-01-01", "2021-01-01"], |
| filters: [["is", "event:goal", ["Purchase"]]], |
| include: %{"imports" => true} |
| ) |
|
|
| assert [ |
| %{"dimensions" => ["Purchase"], "metrics" => [5, 7, 62.5]} |
| ] = response["results"] |
| end |
|
|
| test "returns custom event goals with more than one option in goal filter", %{ |
| conn: conn, |
| site: site |
| } do |
| insert(:goal, site: site, event_name: "Purchase") |
| insert(:goal, site: site, event_name: "Activation") |
| insert(:goal, site: site, page_path: "/test") |
|
|
| site_import = insert(:site_import, site: site) |
|
|
| populate_stats(site, site_import.id, [ |
| build(:pageview, |
| timestamp: ~N[2021-01-01 00:00:01], |
| pathname: "/test" |
| ), |
| build(:event, |
| name: "Purchase", |
| timestamp: ~N[2021-01-01 00:00:03] |
| ), |
| build(:event, |
| name: "Purchase", |
| timestamp: ~N[2021-01-01 00:00:03] |
| ), |
| build(:event, |
| name: "Activation", |
| timestamp: ~N[2021-01-01 00:00:03] |
| ), |
| build(:imported_custom_events, |
| name: "Purchase", |
| visitors: 3, |
| events: 5, |
| date: ~D[2021-01-01] |
| ), |
| build(:imported_custom_events, |
| name: "Activation", |
| visitors: 2, |
| events: 4, |
| date: ~D[2021-01-01] |
| ), |
| build(:imported_pages, |
| page: "/test", |
| visitors: 2, |
| pageviews: 2, |
| date: ~D[2021-01-01] |
| ), |
| build(:imported_visitors, visitors: 5, date: ~D[2021-01-01]) |
| ]) |
|
|
| response = |
| query_conversions(conn, site, |
| date_range: ["2021-01-01", "2021-01-01"], |
| filters: [["is", "event:goal", ["Purchase", "Activation"]]], |
| include: %{"imports" => true} |
| ) |
|
|
| assert [ |
| %{"dimensions" => ["Purchase"], "metrics" => [5, 7, 55.56]}, |
| %{"dimensions" => ["Activation"], "metrics" => [3, 5, 33.33]} |
| ] = response["results"] |
| end |
|
|
| test "returns only pageview goals with a pageview goal filter", %{ |
| conn: conn, |
| site: site |
| } do |
| insert(:goal, site: site, event_name: "Purchase") |
| insert(:goal, site: site, event_name: "Activation") |
| insert(:goal, site: site, page_path: "/test") |
|
|
| site_import = insert(:site_import, site: site) |
|
|
| populate_stats(site, site_import.id, [ |
| build(:pageview, |
| timestamp: ~N[2021-01-01 00:00:01], |
| pathname: "/test" |
| ), |
| build(:event, |
| name: "Purchase", |
| timestamp: ~N[2021-01-01 00:00:03] |
| ), |
| build(:event, |
| name: "Purchase", |
| timestamp: ~N[2021-01-01 00:00:03] |
| ), |
| build(:imported_custom_events, |
| name: "Purchase", |
| visitors: 3, |
| events: 5, |
| date: ~D[2021-01-01] |
| ), |
| build(:imported_pages, |
| page: "/test", |
| visitors: 2, |
| pageviews: 2, |
| date: ~D[2021-01-01] |
| ), |
| build(:imported_visitors, visitors: 5, date: ~D[2021-01-01]) |
| ]) |
|
|
| response = |
| query_conversions(conn, site, |
| date_range: ["2021-01-01", "2021-01-01"], |
| filters: [["is", "event:goal", ["Visit /test"]]], |
| include: %{"imports" => true} |
| ) |
|
|
| assert [ |
| %{"dimensions" => ["Visit /test"], "metrics" => [3, 3, 37.5]} |
| ] = response["results"] |
| end |
|
|
| test "returns pageview goals with more than one option in pageview goal filter", %{ |
| conn: conn, |
| site: site |
| } do |
| insert(:goal, site: site, event_name: "Purchase") |
| insert(:goal, site: site, event_name: "Activation") |
| insert(:goal, site: site, page_path: "/test") |
| insert(:goal, site: site, page_path: "/blog") |
|
|
| site_import = insert(:site_import, site: site) |
|
|
| populate_stats(site, site_import.id, [ |
| build(:pageview, |
| timestamp: ~N[2021-01-01 00:00:01], |
| pathname: "/test" |
| ), |
| build(:pageview, |
| timestamp: ~N[2021-01-01 00:00:01], |
| pathname: "/blog" |
| ), |
| build(:event, |
| name: "Purchase", |
| timestamp: ~N[2021-01-01 00:00:03] |
| ), |
| build(:event, |
| name: "Purchase", |
| timestamp: ~N[2021-01-01 00:00:03] |
| ), |
| build(:imported_custom_events, |
| name: "Purchase", |
| visitors: 3, |
| events: 5, |
| date: ~D[2021-01-01] |
| ), |
| build(:imported_pages, |
| page: "/test", |
| visitors: 2, |
| pageviews: 2, |
| date: ~D[2021-01-01] |
| ), |
| build(:imported_pages, |
| page: "/blog", |
| visitors: 1, |
| pageviews: 1, |
| date: ~D[2021-01-01] |
| ), |
| build(:imported_visitors, visitors: 5, date: ~D[2021-01-01]) |
| ]) |
|
|
| response = |
| query_conversions(conn, site, |
| date_range: ["2021-01-01", "2021-01-01"], |
| filters: [["is", "event:goal", ["Visit /test", "Visit /blog"]]], |
| include: %{"imports" => true} |
| ) |
|
|
| assert [ |
| %{"dimensions" => ["Visit /test"], "metrics" => [3, 3, 33.33]}, |
| %{"dimensions" => ["Visit /blog"], "metrics" => [2, 2, 22.22]} |
| ] = response["results"] |
| end |
|
|
| test "returns pageview goals with a page filter", %{ |
| conn: conn, |
| site: site |
| } do |
| insert(:goal, site: site, page_path: "/blog/two") |
| insert(:goal, site: site, page_path: "/blog/thr**") |
| insert(:goal, site: site, page_path: "/blog/*") |
|
|
| site_import = insert(:site_import, site: site) |
|
|
| populate_stats(site, site_import.id, [ |
| build(:imported_pages, page: "/", visitors: 1, pageviews: 1, date: ~D[2021-01-01]), |
| build(:imported_pages, |
| page: "/blog/one", |
| visitors: 2, |
| pageviews: 2, |
| date: ~D[2021-01-01] |
| ), |
| build(:imported_pages, |
| page: "/blog/two", |
| visitors: 3, |
| pageviews: 3, |
| date: ~D[2021-01-01] |
| ), |
| build(:imported_pages, |
| page: "/blog/three", |
| visitors: 4, |
| pageviews: 4, |
| date: ~D[2021-01-01] |
| ), |
| build(:imported_visitors, visitors: 10, date: ~D[2021-01-01]) |
| ]) |
|
|
| response = |
| query_conversions(conn, site, |
| date_range: ["2021-01-01", "2021-01-01"], |
| filters: [["is", "event:page", ["/blog/one", "/blog/two"]]], |
| include: %{"imports" => true} |
| ) |
|
|
| assert [ |
| %{"dimensions" => ["Visit /blog/*"], "metrics" => [5, 5, 100.0]}, |
| %{"dimensions" => ["Visit /blog/two"], "metrics" => [3, 3, 60.0]} |
| ] = response["results"] |
| end |
|
|
| test "calculates conversion_rate for goals with glob pattern with imported data", %{ |
| conn: conn, |
| site: site |
| } do |
| site_import = |
| insert(:site_import, |
| site: site, |
| start_date: ~D[2005-01-01], |
| end_date: Date.utc_today(), |
| source: :universal_analytics |
| ) |
|
|
| populate_stats(site, site_import.id, [ |
| build(:pageview, pathname: "/"), |
| build(:pageview, pathname: "/another"), |
| build(:pageview, pathname: "/blog/post-1"), |
| build(:pageview, pathname: "/blog/post-2"), |
| build(:imported_pages, page: "/blog/post-1"), |
| build(:imported_visitors) |
| ]) |
|
|
| insert(:goal, %{site: site, page_path: "/blog**"}) |
|
|
| response = query_conversions(conn, site, date_range: "day", include: %{"imports" => true}) |
|
|
| assert response["meta"]["imports_included"] |
|
|
| assert response["results"] == [ |
| %{"dimensions" => ["Visit /blog**"], "metrics" => [3, 3, 60.0]} |
| ] |
| end |
|
|
| test "filtering with goal contains filter", %{ |
| conn: conn, |
| site: site |
| } do |
| site_import = |
| insert(:site_import, |
| site: site, |
| start_date: ~D[2005-01-01], |
| end_date: Date.utc_today(), |
| source: :universal_analytics |
| ) |
|
|
| insert(:goal, site: site, event_name: "Onboarding: Step 1") |
| insert(:goal, site: site, event_name: "Onboarding: Step 2") |
| insert(:goal, site: site, event_name: "Unrelated") |
|
|
| populate_stats(site, site_import.id, [ |
| build(:event, name: "Onboarding: Step 1"), |
| build(:event, name: "Onboarding: Step 1"), |
| build(:event, name: "Onboarding: Step 2"), |
| build(:event, name: "Unrelated"), |
| build(:imported_custom_events, name: "Onboarding: Step 1", visitors: 2, events: 2), |
| build(:imported_custom_events, name: "Onboarding: Step 2"), |
| build(:imported_custom_events, name: "Unrelated"), |
| build(:imported_visitors, visitors: 4) |
| ]) |
|
|
| response = |
| query_conversions(conn, site, |
| date_range: "day", |
| filters: [["contains", "event:goal", ["Onboarding"]]], |
| include: %{"imports" => true} |
| ) |
|
|
| assert response["results"] == [ |
| %{"dimensions" => ["Onboarding: Step 1"], "metrics" => [4, 4, 50.0]}, |
| %{"dimensions" => ["Onboarding: Step 2"], "metrics" => [2, 2, 25.0]} |
| ] |
| end |
| end |
| end |
|
|