| defmodule PlausibleWeb.Api.StatsController.PagesTest do |
| use PlausibleWeb.ConnCase |
|
|
| @user_id Enum.random(1000..9999) |
|
|
| @default_metrics ["visitors", "percentage"] |
| @detailed_metrics [ |
| "visitors", |
| "pageviews", |
| "bounce_rate", |
| "time_on_page", |
| "scroll_depth", |
| "percentage" |
| ] |
| @goal_filter_metrics ["visitors", "group_conversion_rate", "total_visitors"] |
|
|
| defp query_pages(conn, site, opts) do |
| params = %{ |
| "dimensions" => Keyword.get(opts, :dimensions, ["event:page"]), |
| "date_range" => Keyword.get(opts, :date_range, "all"), |
| "relative_date" => Keyword.get(opts, :relative_date, nil), |
| "filters" => Keyword.get(opts, :filters, []), |
| "metrics" => Keyword.get(opts, :metrics, @default_metrics), |
| "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 "GET /api/stats/:domain/pages" do |
| setup [ |
| :create_user, |
| :log_in, |
| :create_site, |
| :create_legacy_site_import |
| ] |
|
|
| test "returns top pages by visitors", %{conn: conn, site: site} do |
| populate_stats(site, [ |
| build(:pageview, pathname: "/"), |
| build(:pageview, pathname: "/"), |
| build(:pageview, pathname: "/"), |
| build(:pageview, pathname: "/register"), |
| build(:pageview, pathname: "/register"), |
| build(:pageview, pathname: "/contact") |
| ]) |
|
|
| response = query_pages(conn, site, date_range: "day") |
|
|
| assert response["results"] == [ |
| %{"dimensions" => ["/"], "metrics" => [3, 50.0]}, |
| %{"dimensions" => ["/register"], "metrics" => [2, 33.33]}, |
| %{"dimensions" => ["/contact"], "metrics" => [1, 16.67]} |
| ] |
| end |
|
|
| test "returns top pages by visitors by hostname", %{conn: conn, site: site} do |
| populate_stats(site, [ |
| build(:pageview, pathname: "/", hostname: "a.example.com"), |
| build(:pageview, pathname: "/", hostname: "b.example.com"), |
| build(:pageview, pathname: "/", hostname: "d.example.com"), |
| build(:pageview, pathname: "/landing", hostname: "x.example.com", user_id: 123), |
| build(:pageview, pathname: "/register", hostname: "d.example.com", user_id: 123), |
| build(:pageview, pathname: "/register", hostname: "d.example.com", user_id: 123), |
| build(:pageview, pathname: "/register", hostname: "d.example.com"), |
| build(:pageview, pathname: "/contact", hostname: "e.example.com") |
| ]) |
|
|
| response = |
| query_pages(conn, site, |
| date_range: "day", |
| filters: [["contains", "event:hostname", [".example.com"]]], |
| order_by: [["visitors", "desc"], ["event:page", "asc"]] |
| ) |
|
|
| assert response["results"] == [ |
| %{"dimensions" => ["/"], "metrics" => [3, 50.0]}, |
| %{"dimensions" => ["/register"], "metrics" => [2, 33.33]}, |
| %{"dimensions" => ["/contact"], "metrics" => [1, 16.67]}, |
| %{"dimensions" => ["/landing"], "metrics" => [1, 16.67]} |
| ] |
|
|
| response = |
| query_pages(conn, site, |
| date_range: "day", |
| filters: [["is", "event:hostname", ["d.example.com"]]] |
| ) |
|
|
| assert response["results"] == [ |
| %{"dimensions" => ["/register"], "metrics" => [2, 66.67]}, |
| %{"dimensions" => ["/"], "metrics" => [1, 33.33]} |
| ] |
| end |
|
|
| test "returns top pages broken down by hostname and page", %{conn: conn, site: site} do |
| populate_stats(site, [ |
| build(:pageview, pathname: "/docs", hostname: "main.example.com"), |
| build(:pageview, pathname: "/docs", hostname: "main.example.com"), |
| build(:pageview, pathname: "/docs", hostname: "secondary.example.com") |
| ]) |
|
|
| response = |
| query_pages(conn, site, |
| date_range: "day", |
| dimensions: ["event:hostname", "event:page"], |
| metrics: ["visitors", "bounce_rate"] |
| ) |
|
|
| assert response["results"] == [ |
| %{"dimensions" => ["main.example.com", "/docs"], "metrics" => [2, 100]}, |
| %{"dimensions" => ["secondary.example.com", "/docs"], "metrics" => [1, 100]} |
| ] |
| end |
|
|
| test "correctly computes bounce_rate when broken down by hostname and page", |
| %{conn: conn, site: site} do |
| populate_stats(site, [ |
| |
| build(:pageview, |
| pathname: "/docs", |
| hostname: "main.example.com", |
| user_id: 1, |
| timestamp: ~N[2021-01-01 00:00:00] |
| ), |
| build(:pageview, |
| pathname: "/docs", |
| hostname: "main.example.com", |
| user_id: 1, |
| timestamp: ~N[2021-01-01 00:01:00] |
| ), |
| |
| build(:pageview, |
| pathname: "/docs", |
| hostname: "main.example.com", |
| user_id: 2, |
| timestamp: ~N[2021-01-01 00:00:00] |
| ), |
| |
| build(:pageview, |
| pathname: "/docs", |
| hostname: "secondary.example.com", |
| user_id: 3, |
| timestamp: ~N[2021-01-01 00:00:00] |
| ) |
| ]) |
|
|
| response = |
| query_pages(conn, site, |
| dimensions: ["event:hostname", "event:page"], |
| metrics: ["visitors", "bounce_rate"] |
| ) |
|
|
| assert response["results"] == [ |
| %{"dimensions" => ["main.example.com", "/docs"], "metrics" => [2, 50]}, |
| %{"dimensions" => ["secondary.example.com", "/docs"], "metrics" => [1, 100]} |
| ] |
| end |
|
|
| test "returns top pages with :is filter on custom pageview props", %{conn: conn, site: site} do |
| populate_stats(site, [ |
| build(:pageview, |
| pathname: "/blog/john-1", |
| "meta.key": ["author"], |
| "meta.value": ["John Doe"] |
| ), |
| build(:pageview, |
| pathname: "/blog/other-post", |
| "meta.key": ["author"], |
| "meta.value": ["other"] |
| ), |
| build(:pageview, user_id: 123, pathname: "/") |
| ]) |
|
|
| response = |
| query_pages(conn, site, |
| date_range: "day", |
| filters: [["is", "event:props:author", ["John Doe"]]] |
| ) |
|
|
| assert response["results"] == [ |
| %{"dimensions" => ["/blog/john-1"], "metrics" => [1, 100.0]} |
| ] |
| end |
|
|
| test "returns top pages with :is_not filter on custom pageview props", %{ |
| conn: conn, |
| site: site |
| } do |
| populate_stats(site, [ |
| build(:pageview, |
| pathname: "/blog/john-1", |
| "meta.key": ["author"], |
| "meta.value": ["John Doe"] |
| ), |
| build(:pageview, |
| pathname: "/blog/other-post", |
| "meta.key": ["author"], |
| "meta.value": ["other"] |
| ), |
| build(:pageview, pathname: "/") |
| ]) |
|
|
| response = |
| query_pages(conn, site, |
| date_range: "day", |
| filters: [["is_not", "event:props:author", ["John Doe"]]] |
| ) |
|
|
| assert response["results"] == [ |
| %{"dimensions" => ["/"], "metrics" => [1, 50.0]}, |
| %{"dimensions" => ["/blog/other-post"], "metrics" => [1, 50.0]} |
| ] |
| end |
|
|
| test "returns top pages with :matches_wildcard filter on custom pageview props", %{ |
| conn: conn, |
| site: site |
| } do |
| populate_stats(site, [ |
| build(:pageview, |
| pathname: "/1", |
| "meta.key": ["prop"], |
| "meta.value": ["bar"] |
| ), |
| build(:pageview, |
| pathname: "/2", |
| "meta.key": ["prop"], |
| "meta.value": ["foobar"] |
| ), |
| build(:pageview, |
| pathname: "/3", |
| "meta.key": ["prop"], |
| "meta.value": ["baar"] |
| ), |
| build(:pageview, |
| pathname: "/4", |
| "meta.key": ["another"], |
| "meta.value": ["bar"] |
| ), |
| build(:pageview, pathname: "/5") |
| ]) |
|
|
| response = |
| query_pages(conn, site, |
| date_range: "day", |
| filters: [["contains", "event:props:prop", ["bar"]]], |
| order_by: [["visitors", "desc"], ["event:page", "asc"]] |
| ) |
|
|
| assert response["results"] == [ |
| %{"dimensions" => ["/1"], "metrics" => [1, 50.0]}, |
| %{"dimensions" => ["/2"], "metrics" => [1, 50.0]} |
| ] |
| end |
|
|
| test "returns top pages with :matches_member filter on custom pageview props", %{ |
| conn: conn, |
| site: site |
| } do |
| populate_stats(site, [ |
| build(:pageview, |
| pathname: "/1", |
| "meta.key": ["prop"], |
| "meta.value": ["bar"] |
| ), |
| build(:pageview, |
| pathname: "/2", |
| "meta.key": ["prop"], |
| "meta.value": ["foobar"] |
| ), |
| build(:pageview, |
| pathname: "/3", |
| "meta.key": ["prop"], |
| "meta.value": ["baar"] |
| ), |
| build(:pageview, |
| pathname: "/4", |
| "meta.key": ["another"], |
| "meta.value": ["bar"] |
| ), |
| build(:pageview, pathname: "/5"), |
| build(:pageview, |
| pathname: "/6", |
| "meta.key": ["prop"], |
| "meta.value": ["near"] |
| ) |
| ]) |
|
|
| response = |
| query_pages(conn, site, |
| date_range: "day", |
| filters: [["contains", "event:props:prop", ["bar", "nea"]]], |
| order_by: [["visitors", "desc"], ["event:page", "asc"]] |
| ) |
|
|
| assert response["results"] == [ |
| %{"dimensions" => ["/1"], "metrics" => [1, 33.33]}, |
| %{"dimensions" => ["/2"], "metrics" => [1, 33.33]}, |
| %{"dimensions" => ["/6"], "metrics" => [1, 33.33]} |
| ] |
| end |
|
|
| test "returns top pages with multiple filters on custom pageview props", %{ |
| conn: conn, |
| site: site |
| } do |
| populate_stats(site, [ |
| build(:pageview, |
| pathname: "/1", |
| "meta.key": ["prop", "number"], |
| "meta.value": ["bar", "1"] |
| ), |
| build(:pageview, |
| pathname: "/2", |
| "meta.key": ["prop", "number"], |
| "meta.value": ["bar", "2"] |
| ), |
| build(:pageview, |
| pathname: "/3", |
| "meta.key": ["prop"], |
| "meta.value": ["bar"] |
| ), |
| build(:pageview, |
| pathname: "/4", |
| "meta.key": ["number"], |
| "meta.value": ["bar"] |
| ), |
| build(:pageview, pathname: "/5") |
| ]) |
|
|
| response = |
| query_pages(conn, site, |
| date_range: "day", |
| filters: [ |
| ["is", "event:props:prop", ["bar"]], |
| ["is", "event:props:number", ["1"]] |
| ] |
| ) |
|
|
| assert response["results"] == [ |
| %{"dimensions" => ["/1"], "metrics" => [1, 100.0]} |
| ] |
| end |
|
|
| test "calculates bounce_rate and time_on_page with :is filter on custom pageview props", |
| %{conn: conn, site: site} do |
| populate_stats(site, [ |
| build(:pageview, |
| pathname: "/blog/john-1", |
| "meta.key": ["author"], |
| "meta.value": ["John Doe"], |
| user_id: @user_id, |
| timestamp: ~N[2021-01-01 00:00:00] |
| ), |
| build(:engagement, |
| pathname: "/blog/john-1", |
| "meta.key": ["author"], |
| "meta.value": ["John Doe"], |
| user_id: @user_id, |
| timestamp: ~N[2021-01-01 00:01:00], |
| engagement_time: 60_000 |
| ), |
| build(:pageview, |
| pathname: "/blog", |
| user_id: @user_id, |
| timestamp: ~N[2021-01-01 00:01:00] |
| ), |
| build(:engagement, |
| pathname: "/blog", |
| user_id: @user_id, |
| timestamp: ~N[2021-01-01 00:02:00], |
| engagement_time: 60_000 |
| ), |
| build(:pageview, |
| pathname: "/blog/john-2", |
| "meta.key": ["author"], |
| "meta.value": ["John Doe"], |
| user_id: @user_id, |
| timestamp: ~N[2021-01-01 00:02:00] |
| ), |
| build(:engagement, |
| pathname: "/blog/john-2", |
| "meta.key": ["author"], |
| "meta.value": ["John Doe"], |
| user_id: @user_id, |
| timestamp: ~N[2021-01-01 00:02:30], |
| engagement_time: 30_000 |
| ), |
| build(:pageview, |
| pathname: "/blog/john-2", |
| "meta.key": ["author"], |
| "meta.value": ["John Doe"], |
| user_id: 456, |
| timestamp: ~N[2021-01-01 00:00:00] |
| ), |
| build(:engagement, |
| pathname: "/blog/john-2", |
| "meta.key": ["author"], |
| "meta.value": ["John Doe"], |
| user_id: 456, |
| timestamp: ~N[2021-01-01 00:10:00], |
| engagement_time: 600_000 |
| ), |
| build(:pageview, |
| pathname: "/blog", |
| user_id: 456, |
| timestamp: ~N[2021-01-01 00:10:00] |
| ), |
| build(:engagement, |
| pathname: "/blog", |
| user_id: 456, |
| timestamp: ~N[2021-01-01 00:10:30], |
| engagement_time: 30_000 |
| ) |
| ]) |
|
|
| response = |
| query_pages(conn, site, |
| date_range: ["2021-01-01", "2021-01-01"], |
| filters: [["is", "event:props:author", ["John Doe"]]], |
| metrics: @detailed_metrics |
| ) |
|
|
| assert response["results"] == [ |
| %{"dimensions" => ["/blog/john-2"], "metrics" => [2, 2, 0, 315, 0, 100.0]}, |
| %{"dimensions" => ["/blog/john-1"], "metrics" => [1, 1, 0, 60, 0, 50.0]} |
| ] |
| end |
|
|
| test "calculates bounce_rate and time_on_page with :is_not filter on custom pageview props", |
| %{conn: conn, site: site} do |
| populate_stats(site, [ |
| build(:pageview, |
| pathname: "/blog", |
| user_id: @user_id, |
| timestamp: ~N[2021-01-01 00:00:00] |
| ), |
| build(:engagement, |
| pathname: "/blog", |
| user_id: @user_id, |
| timestamp: ~N[2021-01-01 00:01:00], |
| engagement_time: 60_000 |
| ), |
| build(:pageview, |
| pathname: "/blog/john-1", |
| user_id: @user_id, |
| "meta.key": ["author"], |
| "meta.value": ["John Doe"], |
| timestamp: ~N[2021-01-01 00:01:00] |
| ), |
| build(:engagement, |
| pathname: "/blog/john-1", |
| user_id: @user_id, |
| "meta.key": ["author"], |
| "meta.value": ["John Doe"], |
| timestamp: ~N[2021-01-01 00:02:00], |
| engagement_time: 60_000 |
| ), |
| build(:pageview, |
| pathname: "/blog/other-post", |
| user_id: @user_id, |
| "meta.key": ["author"], |
| "meta.value": ["other"], |
| timestamp: ~N[2021-01-01 00:02:00] |
| ), |
| build(:engagement, |
| pathname: "/blog/other-post", |
| user_id: @user_id, |
| "meta.key": ["author"], |
| "meta.value": ["other"], |
| timestamp: ~N[2021-01-01 00:02:30], |
| engagement_time: 30_000 |
| ), |
| build(:pageview, |
| pathname: "/blog", |
| user_id: 456, |
| timestamp: ~N[2021-01-01 00:00:00] |
| ), |
| build(:engagement, |
| pathname: "/blog", |
| user_id: 456, |
| timestamp: ~N[2021-01-01 00:03:00], |
| engagement_time: 180_000 |
| ), |
| build(:pageview, |
| pathname: "/blog/john-1", |
| "meta.key": ["author"], |
| "meta.value": ["John Doe"], |
| user_id: 456, |
| timestamp: ~N[2021-01-01 00:03:00] |
| ), |
| build(:engagement, |
| pathname: "/blog/john-1", |
| "meta.key": ["author"], |
| "meta.value": ["John Doe"], |
| user_id: 456, |
| timestamp: ~N[2021-01-01 00:03:30], |
| engagement_time: 30_000 |
| ) |
| ]) |
|
|
| response = |
| query_pages(conn, site, |
| date_range: ["2021-01-01", "2021-01-01"], |
| filters: [["is_not", "event:props:author", ["John Doe"]]], |
| metrics: @detailed_metrics |
| ) |
|
|
| assert response["results"] == [ |
| %{"dimensions" => ["/blog"], "metrics" => [2, 2, 0, 120, 0, 100.0]}, |
| %{"dimensions" => ["/blog/other-post"], "metrics" => [1, 1, 0, 30, 0, 50.0]} |
| ] |
| end |
|
|
| test "calculates bounce_rate and time_on_page with :is (none) filter on custom pageview props", |
| %{conn: conn, site: site} do |
| populate_stats(site, [ |
| build(:pageview, |
| pathname: "/blog", |
| user_id: @user_id, |
| timestamp: ~N[2021-01-01 00:00:00] |
| ), |
| build(:engagement, |
| pathname: "/blog", |
| user_id: @user_id, |
| timestamp: ~N[2021-01-01 00:01:00], |
| engagement_time: 60_000 |
| ), |
| build(:pageview, |
| pathname: "/blog/john-1", |
| user_id: @user_id, |
| "meta.key": ["author"], |
| "meta.value": ["John Doe"], |
| timestamp: ~N[2021-01-01 00:01:00] |
| ), |
| build(:engagement, |
| pathname: "/blog/john-1", |
| user_id: @user_id, |
| "meta.key": ["author"], |
| "meta.value": ["John Doe"], |
| timestamp: ~N[2021-01-01 00:02:00], |
| engagement_time: 60_000 |
| ), |
| build(:pageview, |
| pathname: "/blog/other-post", |
| user_id: @user_id, |
| timestamp: ~N[2021-01-01 00:02:00] |
| ), |
| build(:engagement, |
| pathname: "/blog/other-post", |
| user_id: @user_id, |
| timestamp: ~N[2021-01-01 00:02:30], |
| engagement_time: 30_000 |
| ), |
| build(:pageview, |
| pathname: "/blog", |
| user_id: 456, |
| timestamp: ~N[2021-01-01 00:00:00] |
| ), |
| build(:engagement, |
| pathname: "/blog", |
| user_id: 456, |
| timestamp: ~N[2021-01-01 00:00:30], |
| engagement_time: 30_000 |
| ) |
| ]) |
|
|
| response = |
| query_pages(conn, site, |
| date_range: ["2021-01-01", "2021-01-01"], |
| filters: [["is", "event:props:author", ["(none)"]]], |
| metrics: @detailed_metrics |
| ) |
|
|
| assert response["results"] == [ |
| %{"dimensions" => ["/blog"], "metrics" => [2, 2, 50, 45, 0, 100.0]}, |
| %{"dimensions" => ["/blog/other-post"], "metrics" => [1, 1, 0, 30, 0, 50.0]} |
| ] |
| end |
|
|
| test "calculates bounce_rate and time_on_page with :is_not (none) filter on custom pageview props", |
| %{conn: conn, site: site} do |
| populate_stats(site, [ |
| build(:pageview, |
| pathname: "/blog/john-1", |
| user_id: @user_id, |
| "meta.key": ["author"], |
| "meta.value": ["John Doe"], |
| timestamp: ~N[2021-01-01 00:00:00] |
| ), |
| build(:engagement, |
| pathname: "/blog/john-1", |
| user_id: @user_id, |
| "meta.key": ["author"], |
| "meta.value": ["John Doe"], |
| timestamp: ~N[2021-01-01 00:01:00], |
| engagement_time: 60_000 |
| ), |
| build(:pageview, |
| pathname: "/blog", |
| user_id: @user_id, |
| timestamp: ~N[2021-01-01 00:01:00] |
| ), |
| build(:engagement, |
| pathname: "/blog", |
| user_id: @user_id, |
| timestamp: ~N[2021-01-01 00:02:00], |
| engagement_time: 60_000 |
| ), |
| build(:pageview, |
| pathname: "/blog/other-post", |
| "meta.key": ["author"], |
| "meta.value": ["other"], |
| user_id: @user_id, |
| timestamp: ~N[2021-01-01 00:02:00] |
| ), |
| build(:engagement, |
| pathname: "/blog/other-post", |
| "meta.key": ["author"], |
| "meta.value": ["other"], |
| user_id: @user_id, |
| timestamp: ~N[2021-01-01 00:02:30], |
| engagement_time: 30_000 |
| ), |
| build(:pageview, |
| pathname: "/blog/other-post", |
| "meta.key": ["author"], |
| "meta.value": [""], |
| user_id: 456, |
| timestamp: ~N[2021-01-01 00:00:00] |
| ), |
| build(:engagement, |
| pathname: "/blog/other-post", |
| "meta.key": ["author"], |
| "meta.value": [""], |
| user_id: 456, |
| timestamp: ~N[2021-01-01 00:00:30], |
| engagement_time: 30_000 |
| ) |
| ]) |
|
|
| response = |
| query_pages(conn, site, |
| date_range: ["2021-01-01", "2021-01-01"], |
| filters: [["is_not", "event:props:author", ["(none)"]]], |
| metrics: @detailed_metrics |
| ) |
|
|
| assert response["results"] == [ |
| %{"dimensions" => ["/blog/other-post"], "metrics" => [2, 2, 100, 30, 0, 100.0]}, |
| %{"dimensions" => ["/blog/john-1"], "metrics" => [1, 1, 0, 60, 0, 50.0]} |
| ] |
| end |
|
|
| test "returns top pages with :not_member filter on custom pageview props", %{ |
| conn: conn, |
| site: site |
| } do |
| populate_stats(site, [ |
| build(:pageview, |
| pathname: "/chrome", |
| "meta.key": ["browser"], |
| "meta.value": ["Chrome"], |
| timestamp: ~N[2021-01-01 00:00:00] |
| ), |
| build(:pageview, |
| pathname: "/chrome", |
| "meta.key": ["browser"], |
| "meta.value": ["Chrome"], |
| timestamp: ~N[2021-01-01 00:00:00] |
| ), |
| build(:pageview, |
| pathname: "/safari", |
| "meta.key": ["browser"], |
| "meta.value": ["Safari"], |
| timestamp: ~N[2021-01-01 00:00:00] |
| ), |
| build(:pageview, |
| pathname: "/firefox", |
| "meta.key": ["browser"], |
| "meta.value": ["Firefox"], |
| timestamp: ~N[2021-01-01 00:00:00] |
| ), |
| build(:pageview, |
| pathname: "/firefox", |
| timestamp: ~N[2021-01-01 00:00:00] |
| ) |
| ]) |
|
|
| response = |
| query_pages(conn, site, |
| date_range: ["2021-01-01", "2021-01-01"], |
| filters: [["is_not", "event:props:browser", ["Chrome", "Safari"]]] |
| ) |
|
|
| assert response["results"] == [ |
| %{"dimensions" => ["/firefox"], "metrics" => [2, 100.0]} |
| ] |
|
|
| assert %{ |
| "date_range" => ["2021-01-01T00:00:00Z", "2021-01-01T23:59:59Z"] |
| } = response["query"] |
| end |
|
|
| test "returns top pages with :not_member filter on custom pageview props including (none) value", |
| %{conn: conn, site: site} do |
| populate_stats(site, [ |
| build(:pageview, |
| pathname: "/chrome", |
| "meta.key": ["browser"], |
| "meta.value": ["Chrome"], |
| timestamp: ~N[2021-01-01 00:00:00] |
| ), |
| build(:pageview, |
| pathname: "/chrome", |
| "meta.key": ["browser"], |
| "meta.value": ["Chrome"], |
| timestamp: ~N[2021-01-01 00:00:00] |
| ), |
| build(:pageview, |
| pathname: "/safari", |
| "meta.key": ["browser"], |
| "meta.value": ["Safari"], |
| timestamp: ~N[2021-01-01 00:00:00] |
| ), |
| build(:pageview, |
| pathname: "/no-browser-prop", |
| timestamp: ~N[2021-01-01 00:00:00] |
| ) |
| ]) |
|
|
| response = |
| query_pages(conn, site, |
| date_range: ["2021-01-01", "2021-01-01"], |
| filters: [["is_not", "event:props:browser", ["Chrome", "(none)"]]] |
| ) |
|
|
| assert response["results"] == [ |
| %{"dimensions" => ["/safari"], "metrics" => [1, 100.0]} |
| ] |
| end |
|
|
| test "calculates bounce_rate and time_on_page for pages filtered by page path", |
| %{conn: conn, site: site} do |
| populate_stats(site, [ |
| build(:pageview, |
| pathname: "/", |
| user_id: @user_id, |
| timestamp: ~N[2021-01-01 00:00:00] |
| ), |
| build(:engagement, |
| pathname: "/", |
| user_id: @user_id, |
| timestamp: ~N[2021-01-01 00:01:00], |
| engagement_time: 60_000 |
| ), |
| build(:pageview, |
| pathname: "/about", |
| user_id: @user_id, |
| timestamp: ~N[2021-01-01 00:01:00] |
| ), |
| build(:engagement, |
| pathname: "/about", |
| user_id: @user_id, |
| timestamp: ~N[2021-01-01 00:02:00], |
| engagement_time: 60_000 |
| ), |
| build(:pageview, |
| pathname: "/", |
| user_id: @user_id, |
| timestamp: ~N[2021-01-01 00:02:00] |
| ), |
| build(:engagement, |
| pathname: "/", |
| user_id: @user_id, |
| timestamp: ~N[2021-01-01 00:02:30], |
| engagement_time: 30_000 |
| ), |
| build(:pageview, |
| pathname: "/", |
| timestamp: ~N[2021-01-01 00:00:00] |
| ), |
| build(:pageview, |
| pathname: "/about", |
| user_id: 456, |
| timestamp: ~N[2021-01-01 00:10:00] |
| ), |
| build(:engagement, |
| pathname: "/about", |
| user_id: 456, |
| timestamp: ~N[2021-01-01 00:10:30], |
| engagement_time: 30_000 |
| ) |
| ]) |
|
|
| response = |
| query_pages(conn, site, |
| date_range: ["2021-01-01", "2021-01-01"], |
| filters: [["is", "event:page", ["/"]]], |
| metrics: @detailed_metrics |
| ) |
|
|
| assert response["results"] == [ |
| %{"dimensions" => ["/"], "metrics" => [2, 3, 50, 90, 0, 100.0]} |
| ] |
| end |
|
|
| test "calculates scroll_depth", %{conn: conn, site: site} do |
| t0 = ~N[2020-01-01 00:00:00] |
| [t1, t2, t3] = for i <- 1..3, do: NaiveDateTime.add(t0, i, :minute) |
|
|
| populate_stats(site, [ |
| build(:pageview, user_id: 12, pathname: "/blog", timestamp: t0), |
| build(:engagement, |
| user_id: 12, |
| pathname: "/blog", |
| timestamp: t1, |
| scroll_depth: 20, |
| engagement_time: 60_000 |
| ), |
| build(:pageview, user_id: 12, pathname: "/another", timestamp: t1), |
| build(:engagement, |
| user_id: 12, |
| pathname: "/another", |
| timestamp: t2, |
| scroll_depth: 24, |
| engagement_time: 60_000 |
| ), |
| build(:pageview, user_id: 34, pathname: "/blog", timestamp: t0), |
| build(:engagement, |
| user_id: 34, |
| pathname: "/blog", |
| timestamp: t1, |
| scroll_depth: 17, |
| engagement_time: 60_000 |
| ), |
| build(:pageview, user_id: 34, pathname: "/another", timestamp: t1), |
| build(:engagement, |
| user_id: 34, |
| pathname: "/another", |
| timestamp: t2, |
| scroll_depth: 26, |
| engagement_time: 60_000 |
| ), |
| build(:pageview, user_id: 34, pathname: "/blog", timestamp: t2), |
| build(:engagement, |
| user_id: 34, |
| pathname: "/blog", |
| timestamp: t3, |
| scroll_depth: 60, |
| engagement_time: 60_000 |
| ), |
| build(:pageview, user_id: 56, pathname: "/blog", timestamp: t0), |
| build(:engagement, |
| user_id: 56, |
| pathname: "/blog", |
| timestamp: t1, |
| scroll_depth: 100, |
| engagement_time: 60_000 |
| ) |
| ]) |
|
|
| response = |
| query_pages(conn, site, |
| date_range: ["2020-01-01", "2020-01-01"], |
| metrics: @detailed_metrics, |
| order_by: [["scroll_depth", "asc"]] |
| ) |
|
|
| assert response["results"] == [ |
| %{"dimensions" => ["/another"], "metrics" => [2, 2, 0, 60, 25, 66.67]}, |
| %{"dimensions" => ["/blog"], "metrics" => [3, 4, 33, 80, 60, 100.0]} |
| ] |
| end |
|
|
| test "calculates scroll_depth from native and imported data combined", %{ |
| conn: conn, |
| site: site, |
| site_import: site_import |
| } do |
| populate_stats(site, [ |
| build(:pageview, |
| user_id: @user_id, |
| pathname: "/blog", |
| timestamp: ~N[2020-01-01 00:00:00] |
| ), |
| build(:engagement, |
| user_id: @user_id, |
| pathname: "/blog", |
| timestamp: ~N[2020-01-01 00:00:00], |
| scroll_depth: 80, |
| engagement_time: 20_000 |
| ), |
| build(:imported_pages, |
| date: ~D[2020-01-01], |
| visitors: 3, |
| pageviews: 3, |
| total_time_on_page: 90, |
| total_time_on_page_visits: 3, |
| page: "/blog", |
| total_scroll_depth: 120, |
| total_scroll_depth_visits: 3 |
| ) |
| ]) |
|
|
| populate_stats(site, site_import.id, [ |
| build(:imported_visitors, date: ~D[2020-01-01]), |
| build(:imported_visitors, date: ~D[2020-01-01]), |
| build(:imported_visitors, date: ~D[2020-01-01]) |
| ]) |
|
|
| response = |
| query_pages(conn, site, |
| date_range: ["2020-01-01", "2020-01-01"], |
| metrics: @detailed_metrics, |
| include: %{"imports" => true}, |
| order_by: [["scroll_depth", "desc"]] |
| ) |
|
|
| assert response["results"] == [ |
| %{"dimensions" => ["/blog"], "metrics" => [4, 4, 100, 28, 50, 100.0]} |
| ] |
| end |
|
|
| test "handles missing scroll_depth data from native and imported sources", %{ |
| conn: conn, |
| site: site, |
| site_import: site_import |
| } do |
| populate_stats(site, [ |
| build(:pageview, |
| user_id: @user_id, |
| pathname: "/native-and-imported", |
| timestamp: ~N[2020-01-01 00:00:00] |
| ), |
| build(:engagement, |
| user_id: @user_id, |
| pathname: "/native-and-imported", |
| timestamp: ~N[2020-01-01 00:01:00], |
| scroll_depth: 80, |
| engagement_time: 60_000 |
| ), |
| build(:pageview, |
| user_id: @user_id, |
| pathname: "/native-only", |
| timestamp: ~N[2020-01-01 00:01:00] |
| ), |
| build(:engagement, |
| user_id: @user_id, |
| pathname: "/native-only", |
| timestamp: ~N[2020-01-01 00:02:00], |
| scroll_depth: 40, |
| engagement_time: 60_000 |
| ), |
| build(:imported_pages, |
| date: ~D[2020-01-01], |
| visitors: 4, |
| pageviews: 4, |
| total_time_on_page: 180, |
| total_time_on_page_visits: 4, |
| page: "/native-and-imported", |
| total_scroll_depth: 120, |
| total_scroll_depth_visits: 3 |
| ), |
| build(:imported_pages, |
| date: ~D[2020-01-01], |
| visitors: 20, |
| pageviews: 30, |
| total_time_on_page: 300, |
| total_time_on_page_visits: 10, |
| page: "/imported-only", |
| total_scroll_depth: 100, |
| total_scroll_depth_visits: 10 |
| ) |
| ]) |
|
|
| populate_stats( |
| site, |
| site_import.id, |
| for(_ <- 1..24, do: build(:imported_visitors, date: ~D[2020-01-01])) |
| ) |
|
|
| response = |
| query_pages(conn, site, |
| date_range: ["2020-01-01", "2020-01-01"], |
| metrics: @detailed_metrics, |
| include: %{"imports" => true}, |
| order_by: [["scroll_depth", "desc"]] |
| ) |
|
|
| assert response["results"] == [ |
| %{"dimensions" => ["/native-and-imported"], "metrics" => [5, 5, 0, 48, 50, 20.0]}, |
| %{"dimensions" => ["/native-only"], "metrics" => [1, 1, 0, 60, 40, 4.0]}, |
| %{"dimensions" => ["/imported-only"], "metrics" => [20, 30, 0, 30, 10, 80.0]} |
| ] |
| end |
|
|
| test "can query scroll depth and time-on-page only from imported data, ignoring rows where scroll depth doesn't exist", |
| %{ |
| conn: conn, |
| site: site |
| } do |
| populate_stats(site, [ |
| build(:imported_pages, |
| date: ~D[2020-01-01], |
| visitors: 10, |
| pageviews: 10, |
| page: "/blog", |
| total_scroll_depth: 100, |
| total_scroll_depth_visits: 10, |
| total_time_on_page: 300, |
| total_time_on_page_visits: 5 |
| ), |
| build(:imported_pages, |
| date: ~D[2020-01-01], |
| visitors: 100, |
| pageviews: 150, |
| page: "/blog", |
| total_scroll_depth: 0, |
| total_scroll_depth_visits: 0, |
| total_time_on_page: 0, |
| total_time_on_page_visits: 0 |
| ) |
| ]) |
|
|
| response = |
| query_pages(conn, site, |
| date_range: "7d", |
| relative_date: "2020-01-02", |
| metrics: @detailed_metrics, |
| include: %{"imports" => true} |
| ) |
|
|
| assert response["results"] == [ |
| %{"dimensions" => ["/blog"], "metrics" => [110, 160, 0, 60, 10, nil]} |
| ] |
| end |
|
|
| test "can filter using the | (OR) filter", |
| %{conn: conn, site: site} do |
| populate_stats(site, [ |
| build(:pageview, |
| pathname: "/", |
| user_id: @user_id, |
| timestamp: ~N[2021-01-01 00:00:00] |
| ), |
| build(:engagement, |
| pathname: "/", |
| user_id: @user_id, |
| timestamp: ~N[2021-01-01 00:01:00], |
| engagement_time: 60_000 |
| ), |
| build(:pageview, |
| pathname: "/irrelevant", |
| user_id: @user_id, |
| timestamp: ~N[2021-01-01 00:01:00] |
| ), |
| build(:engagement, |
| pathname: "/irrelevant", |
| user_id: @user_id, |
| timestamp: ~N[2021-01-01 00:02:00], |
| engagement_time: 60_000 |
| ), |
| build(:pageview, |
| pathname: "/", |
| user_id: @user_id, |
| timestamp: ~N[2021-01-01 00:02:00] |
| ), |
| build(:engagement, |
| pathname: "/", |
| user_id: @user_id, |
| timestamp: ~N[2021-01-01 00:02:30], |
| engagement_time: 30_000 |
| ), |
| build(:pageview, |
| pathname: "/", |
| user_id: 123, |
| timestamp: ~N[2021-01-01 00:00:00] |
| ), |
| build(:engagement, |
| pathname: "/", |
| user_id: 123, |
| timestamp: ~N[2021-01-01 00:01:00], |
| engagement_time: 60_000 |
| ), |
| build(:pageview, |
| pathname: "/about", |
| user_id: 456, |
| timestamp: ~N[2021-01-01 00:10:00] |
| ), |
| build(:engagement, |
| pathname: "/about", |
| user_id: 456, |
| timestamp: ~N[2021-01-01 00:10:30], |
| engagement_time: 30_000 |
| ) |
| ]) |
|
|
| response = |
| query_pages(conn, site, |
| date_range: ["2021-01-01", "2021-01-01"], |
| filters: [["is", "event:page", ["/", "/about"]]], |
| metrics: @detailed_metrics |
| ) |
|
|
| assert response["results"] == [ |
| %{"dimensions" => ["/"], "metrics" => [2, 3, 50, 75, 0, 66.67]}, |
| %{"dimensions" => ["/about"], "metrics" => [1, 1, 100, 30, 0, 33.33]} |
| ] |
| end |
|
|
| test "can filter using the not_member filter type", |
| %{conn: conn, site: site} do |
| populate_stats(site, [ |
| build(:pageview, |
| pathname: "/", |
| user_id: @user_id, |
| timestamp: ~N[2021-01-01 00:00:00] |
| ), |
| build(:engagement, |
| pathname: "/", |
| user_id: @user_id, |
| timestamp: ~N[2021-01-01 00:01:00], |
| engagement_time: 60_000 |
| ), |
| build(:pageview, |
| pathname: "/irrelevant", |
| user_id: @user_id, |
| timestamp: ~N[2021-01-01 00:01:00] |
| ), |
| build(:engagement, |
| pathname: "/irrelevant", |
| user_id: @user_id, |
| timestamp: ~N[2021-01-01 00:02:00], |
| engagement_time: 60_000 |
| ), |
| build(:pageview, |
| pathname: "/", |
| user_id: @user_id, |
| timestamp: ~N[2021-01-01 00:02:00] |
| ), |
| build(:engagement, |
| pathname: "/", |
| user_id: @user_id, |
| timestamp: ~N[2021-01-01 00:02:30], |
| engagement_time: 30_000 |
| ), |
| build(:pageview, |
| pathname: "/", |
| user_id: 123, |
| timestamp: ~N[2021-01-01 00:00:00] |
| ), |
| build(:engagement, |
| pathname: "/", |
| user_id: 123, |
| timestamp: ~N[2021-01-01 00:01:00], |
| engagement_time: 60_000 |
| ), |
| build(:pageview, |
| pathname: "/about", |
| user_id: 456, |
| timestamp: ~N[2021-01-01 00:10:00] |
| ), |
| build(:engagement, |
| pathname: "/about", |
| user_id: 456, |
| timestamp: ~N[2021-01-01 00:10:30], |
| engagement_time: 30_000 |
| ) |
| ]) |
|
|
| response = |
| query_pages(conn, site, |
| date_range: ["2021-01-01", "2021-01-01"], |
| filters: [["is_not", "event:page", ["/irrelevant", "/about"]]], |
| metrics: @detailed_metrics |
| ) |
|
|
| assert response["results"] == [ |
| %{"dimensions" => ["/"], "metrics" => [2, 3, 50, 75, 0, 100.0]} |
| ] |
| end |
|
|
| test "can filter using the matches_member filter type", |
| %{conn: conn, site: site} do |
| populate_stats(site, [ |
| build(:pageview, |
| pathname: "/blog/post-1", |
| user_id: @user_id, |
| timestamp: ~N[2021-01-01 00:00:00] |
| ), |
| build(:engagement, |
| pathname: "/blog/post-1", |
| user_id: @user_id, |
| timestamp: ~N[2021-01-01 00:01:00], |
| engagement_time: 60_000 |
| ), |
| build(:pageview, |
| pathname: "/blog/post-2", |
| user_id: @user_id, |
| timestamp: ~N[2021-01-01 00:01:00] |
| ), |
| build(:engagement, |
| pathname: "/blog/post-2", |
| user_id: @user_id, |
| timestamp: ~N[2021-01-01 00:01:30], |
| engagement_time: 30_000 |
| ), |
| build(:pageview, |
| pathname: "/", |
| user_id: 100, |
| timestamp: ~N[2021-01-01 00:00:00] |
| ), |
| build(:engagement, |
| pathname: "/", |
| user_id: 100, |
| timestamp: ~N[2021-01-01 00:00:30], |
| engagement_time: 30_000 |
| ), |
| build(:pageview, |
| pathname: "/articles/post-1", |
| user_id: 200, |
| timestamp: ~N[2021-01-01 00:10:00] |
| ), |
| build(:engagement, |
| pathname: "/articles/post-1", |
| user_id: 200, |
| timestamp: ~N[2021-01-01 00:10:30], |
| engagement_time: 30_000 |
| ), |
| build(:pageview, |
| pathname: "/articles/post-1", |
| user_id: 300, |
| timestamp: ~N[2021-01-01 00:10:00] |
| ), |
| build(:engagement, |
| pathname: "/articles/post-1", |
| user_id: 300, |
| timestamp: ~N[2021-01-01 00:10:30], |
| engagement_time: 30_000 |
| ) |
| ]) |
|
|
| response = |
| query_pages(conn, site, |
| date_range: ["2021-01-01", "2021-01-01"], |
| filters: [["contains", "event:page", ["/blog/", "/articles/"]]], |
| metrics: @detailed_metrics, |
| order_by: [["visitors", "desc"], ["event:page", "asc"]] |
| ) |
|
|
| assert response["results"] == [ |
| %{"dimensions" => ["/articles/post-1"], "metrics" => [2, 2, 100, 30, 0, 66.67]}, |
| %{"dimensions" => ["/blog/post-1"], "metrics" => [1, 1, 0, 60, 0, 33.33]}, |
| %{"dimensions" => ["/blog/post-2"], "metrics" => [1, 1, 0, 30, 0, 33.33]} |
| ] |
| end |
|
|
| test "page filter escapes brackets", |
| %{conn: conn, site: site} do |
| populate_stats(site, [ |
| build(:pageview, |
| pathname: "/blog/(/post-1", |
| user_id: @user_id, |
| timestamp: ~N[2021-01-01 00:00:00] |
| ), |
| build(:engagement, |
| pathname: "/blog/(/post-1", |
| user_id: @user_id, |
| timestamp: ~N[2021-01-01 00:01:00], |
| engagement_time: 60_000 |
| ), |
| build(:pageview, |
| pathname: "/blog/(/post-2", |
| user_id: @user_id, |
| timestamp: ~N[2021-01-01 00:01:00] |
| ), |
| build(:engagement, |
| pathname: "/blog/(/post-2", |
| user_id: @user_id, |
| timestamp: ~N[2021-01-01 00:01:30], |
| engagement_time: 30_000 |
| ), |
| build(:pageview, |
| pathname: "/", |
| user_id: 456, |
| timestamp: ~N[2021-01-01 00:00:00] |
| ), |
| build(:engagement, |
| pathname: "/", |
| user_id: 456, |
| timestamp: ~N[2021-01-01 00:00:30], |
| engagement_time: 30_000 |
| ) |
| ]) |
|
|
| response = |
| query_pages(conn, site, |
| date_range: ["2021-01-01", "2021-01-01"], |
| filters: [["contains", "event:page", ["/blog/(/", "/blog/)/"]]], |
| order_by: [["visitors", "desc"], ["event:page", "asc"]], |
| metrics: @detailed_metrics |
| ) |
|
|
| assert response["results"] == [ |
| %{"dimensions" => ["/blog/(/post-1"], "metrics" => [1, 1, 0, 60, 0, 100.0]}, |
| %{"dimensions" => ["/blog/(/post-2"], "metrics" => [1, 1, 0, 30, 0, 100.0]} |
| ] |
| end |
|
|
| test "can filter using the not_matches_member filter type", |
| %{conn: conn, site: site} do |
| populate_stats(site, [ |
| build(:pageview, |
| pathname: "/blog/post-1", |
| user_id: 100, |
| timestamp: ~N[2021-01-01 00:00:00] |
| ), |
| build(:engagement, |
| pathname: "/blog/post-1", |
| user_id: 100, |
| timestamp: ~N[2021-01-01 00:00:30], |
| engagement_time: 30_000 |
| ), |
| build(:pageview, |
| pathname: "/", |
| user_id: @user_id, |
| timestamp: ~N[2021-01-01 00:00:00] |
| ), |
| build(:engagement, |
| pathname: "/", |
| user_id: @user_id, |
| timestamp: ~N[2021-01-01 00:10:00], |
| engagement_time: 600_000 |
| ), |
| build(:pageview, |
| pathname: "/about", |
| user_id: @user_id, |
| timestamp: ~N[2021-01-01 00:10:00] |
| ), |
| build(:engagement, |
| pathname: "/about", |
| user_id: @user_id, |
| timestamp: ~N[2021-01-01 00:10:30], |
| engagement_time: 30_000 |
| ), |
| build(:pageview, |
| pathname: "/", |
| user_id: 200, |
| timestamp: ~N[2021-01-01 00:00:00] |
| ), |
| build(:engagement, |
| pathname: "/", |
| user_id: 200, |
| timestamp: ~N[2021-01-01 00:10:00], |
| engagement_time: 600_000 |
| ), |
| build(:pageview, |
| pathname: "/articles/post-1", |
| user_id: 300, |
| timestamp: ~N[2021-01-01 00:10:00] |
| ), |
| build(:engagement, |
| pathname: "/articles/post-1", |
| user_id: 300, |
| timestamp: ~N[2021-01-01 00:10:30], |
| engagement_time: 30_000 |
| ) |
| ]) |
|
|
| response = |
| query_pages(conn, site, |
| date_range: ["2021-01-01", "2021-01-01"], |
| filters: [["contains_not", "event:page", ["/blog/", "/articles/"]]], |
| metrics: @detailed_metrics |
| ) |
|
|
| assert response["results"] == [ |
| %{"dimensions" => ["/"], "metrics" => [2, 2, 50, 600, 0, 100.0]}, |
| %{"dimensions" => ["/about"], "metrics" => [1, 1, 0, 30, 0, 50.0]} |
| ] |
| end |
|
|
| test "returns top pages by visitors with imported data", %{conn: conn, site: site} do |
| populate_stats(site, [ |
| build(:pageview, pathname: "/"), |
| build(:pageview, pathname: "/"), |
| build(:pageview, pathname: "/"), |
| build(:imported_pages, page: "/"), |
| build(:pageview, pathname: "/register"), |
| build(:pageview, pathname: "/register"), |
| build(:imported_pages, page: "/register"), |
| build(:pageview, pathname: "/contact") |
| ]) |
|
|
| response = query_pages(conn, site, date_range: "day") |
|
|
| assert response["results"] == [ |
| %{"dimensions" => ["/"], "metrics" => [3, 50.0]}, |
| %{"dimensions" => ["/register"], "metrics" => [2, 33.33]}, |
| %{"dimensions" => ["/contact"], "metrics" => [1, 16.67]} |
| ] |
|
|
| response = query_pages(conn, site, date_range: "day", include: %{"imports" => true}) |
|
|
| assert response["results"] == [ |
| %{"dimensions" => ["/"], "metrics" => [4, 66.67]}, |
| %{"dimensions" => ["/register"], "metrics" => [3, 50.0]}, |
| %{"dimensions" => ["/contact"], "metrics" => [1, 16.67]} |
| ] |
| end |
|
|
| test "returns scroll depth warning code", %{conn: conn, site: site} do |
| response = |
| query_pages(conn, site, |
| date_range: "day", |
| metrics: @detailed_metrics, |
| include: %{"imports" => true} |
| ) |
|
|
| assert response["meta"]["metric_warnings"]["scroll_depth"]["code"] == |
| "no_imported_scroll_depth" |
| end |
|
|
| test "returns imported pages with a pageview goal filter", %{conn: conn, site: site} do |
| insert(:goal, site: site, page_path: "/blog**") |
|
|
| populate_stats(site, [ |
| build(:imported_pages, page: "/blog"), |
| build(:imported_pages, page: "/not-this"), |
| build(:imported_pages, page: "/blog/post-1", visitors: 2), |
| build(:imported_visitors, visitors: 4) |
| ]) |
|
|
| response = |
| query_pages(conn, site, |
| date_range: "day", |
| filters: [["is", "event:goal", ["Visit /blog**"]]], |
| metrics: @goal_filter_metrics, |
| include: %{"imports" => true} |
| ) |
|
|
| assert response["results"] == [ |
| %{"dimensions" => ["/blog/post-1"], "metrics" => [2, 100.0, 2]}, |
| %{"dimensions" => ["/blog"], "metrics" => [1, 100.0, 1]} |
| ] |
| end |
|
|
| test "calculates bounce rate and time on page for pages", %{conn: conn, site: site} do |
| populate_stats(site, [ |
| build(:pageview, |
| pathname: "/", |
| user_id: @user_id, |
| timestamp: ~N[2021-01-01 00:00:00] |
| ), |
| build(:engagement, |
| pathname: "/", |
| user_id: @user_id, |
| timestamp: ~N[2021-01-01 00:15:00], |
| engagement_time: 900_000 |
| ), |
| build(:pageview, |
| pathname: "/some-other-page", |
| user_id: @user_id, |
| timestamp: ~N[2021-01-01 00:15:00] |
| ), |
| build(:engagement, |
| pathname: "/some-other-page", |
| user_id: @user_id, |
| timestamp: ~N[2021-01-01 00:15:30], |
| engagement_time: 30_000 |
| ), |
| build(:pageview, |
| pathname: "/", |
| user_id: 123, |
| timestamp: ~N[2021-01-01 00:15:00] |
| ), |
| build(:engagement, |
| pathname: "/", |
| user_id: 123, |
| timestamp: ~N[2021-01-01 00:15:30], |
| engagement_time: 30_000 |
| ) |
| ]) |
|
|
| response = |
| query_pages(conn, site, |
| date_range: ["2021-01-01", "2021-01-01"], |
| metrics: @detailed_metrics |
| ) |
|
|
| assert response["results"] == [ |
| %{"dimensions" => ["/"], "metrics" => [2, 2, 50.0, 465.0, 0, 100.0]}, |
| %{"dimensions" => ["/some-other-page"], "metrics" => [1, 1, 0, 30, 0, 50.0]} |
| ] |
| end |
|
|
| test "filtering by hostname, excludes a page on different hostname", %{conn: conn, site: site} do |
| populate_stats(site, [ |
| build(:pageview, |
| timestamp: ~N[2021-01-01 05:01:00], |
| pathname: "/about", |
| hostname: "blog.example.com", |
| user_id: @user_id |
| ), |
| build(:pageview, |
| timestamp: ~N[2021-01-01 05:01:02], |
| pathname: "/hello", |
| hostname: "example.com", |
| user_id: @user_id |
| ), |
| build(:pageview, |
| timestamp: ~N[2021-01-01 05:01:02], |
| pathname: "/about", |
| hostname: "blog.example.com" |
| ) |
| ]) |
|
|
| response = |
| query_pages(conn, site, |
| date_range: ["2021-01-01", "2021-01-01"], |
| filters: [["is", "event:hostname", ["blog.example.com"]]], |
| metrics: @detailed_metrics |
| ) |
|
|
| assert response["results"] == [ |
| %{"dimensions" => ["/about"], "metrics" => [2, 2, 50, nil, nil, 100.0]} |
| ] |
| end |
|
|
| test "calculates bounce rate and time on page for pages when filtered by hostname", %{ |
| conn: conn, |
| site: site |
| } do |
| populate_stats(site, [ |
| |
| build(:pageview, |
| pathname: "/about-blog", |
| hostname: "blog.example.com", |
| user_id: @user_id + 1, |
| timestamp: ~N[2021-01-01 00:01:00] |
| ), |
| build(:engagement, |
| pathname: "/about-blog", |
| hostname: "blog.example.com", |
| user_id: @user_id + 1, |
| timestamp: ~N[2021-01-01 00:01:30], |
| engagement_time: 30_000 |
| ), |
|
|
| |
| build(:pageview, |
| pathname: "/about-blog", |
| hostname: "blog.example.com", |
| user_id: @user_id, |
| timestamp: ~N[2021-01-01 00:01:00] |
| ), |
| build(:engagement, |
| pathname: "/about-blog", |
| hostname: "blog.example.com", |
| user_id: @user_id, |
| timestamp: ~N[2021-01-01 00:10:00], |
| engagement_time: 540_000 |
| ), |
| build(:pageview, |
| pathname: "/about", |
| hostname: "example.com", |
| user_id: @user_id, |
| timestamp: ~N[2021-01-01 00:10:00] |
| ), |
| build(:engagement, |
| pathname: "/about", |
| hostname: "example.com", |
| user_id: @user_id, |
| timestamp: ~N[2021-01-01 00:15:00], |
| engagement_time: 300_000 |
| ), |
| build(:pageview, |
| pathname: "/about-blog", |
| hostname: "blog.example.com", |
| user_id: @user_id, |
| timestamp: ~N[2021-01-01 00:15:00] |
| ), |
| build(:engagement, |
| pathname: "/about-blog", |
| hostname: "blog.example.com", |
| user_id: @user_id, |
| timestamp: ~N[2021-01-01 00:20:00], |
| engagement_time: 300_000 |
| ), |
| build(:pageview, |
| pathname: "/exit-blog", |
| hostname: "blog.example.com", |
| user_id: @user_id, |
| timestamp: ~N[2021-01-01 00:20:00] |
| ), |
| build(:engagement, |
| pathname: "/exit-blog", |
| hostname: "blog.example.com", |
| user_id: @user_id, |
| timestamp: ~N[2021-01-01 00:22:00], |
| engagement_time: 120_000 |
| ), |
| build(:pageview, |
| pathname: "/about", |
| hostname: "example.com", |
| user_id: @user_id, |
| timestamp: ~N[2021-01-01 00:22:00] |
| ), |
| build(:engagement, |
| pathname: "/about", |
| hostname: "example.com", |
| user_id: @user_id, |
| timestamp: ~N[2021-01-01 00:25:00], |
| engagement_time: 180_000 |
| ), |
| build(:pageview, |
| pathname: "/exit", |
| hostname: "example.com", |
| user_id: @user_id, |
| timestamp: ~N[2021-01-01 00:25:00] |
| ), |
| build(:engagement, |
| pathname: "/exit", |
| hostname: "example.com", |
| user_id: @user_id, |
| timestamp: ~N[2021-01-01 00:25:30], |
| engagement_time: 30_000 |
| ), |
|
|
| |
| build(:pageview, |
| pathname: "/about", |
| hostname: "example.com", |
| user_id: @user_id + 2, |
| timestamp: ~N[2021-01-01 00:01:00] |
| ), |
| build(:engagement, |
| pathname: "/about", |
| hostname: "example.com", |
| user_id: @user_id + 2, |
| timestamp: ~N[2021-01-01 00:01:30], |
| engagement_time: 30_000 |
| ) |
| ]) |
|
|
| response = |
| query_pages(conn, site, |
| date_range: ["2021-01-01", "2021-01-01"], |
| filters: [["is", "event:hostname", ["blog.example.com"]]], |
| metrics: @detailed_metrics |
| ) |
|
|
| assert response["results"] == [ |
| %{"dimensions" => ["/about-blog"], "metrics" => [2, 3, 50, 435, 0, 100.0]}, |
| %{"dimensions" => ["/exit-blog"], "metrics" => [1, 1, 0, 120, 0, 50.0]} |
| ] |
| end |
|
|
| test "calculates bounce rate and time on page for pages with imported data", %{ |
| conn: conn, |
| site: site, |
| site_import: site_import |
| } do |
| populate_stats(site, [ |
| build(:pageview, |
| pathname: "/", |
| user_id: @user_id, |
| timestamp: ~N[2021-01-01 00:00:00] |
| ), |
| build(:engagement, |
| pathname: "/", |
| user_id: @user_id, |
| timestamp: ~N[2021-01-01 00:15:00], |
| engagement_time: 900_000 |
| ), |
| build(:pageview, |
| pathname: "/some-other-page", |
| user_id: @user_id, |
| timestamp: ~N[2021-01-01 00:15:00] |
| ), |
| build(:engagement, |
| pathname: "/some-other-page", |
| user_id: @user_id, |
| timestamp: ~N[2021-01-01 00:15:30], |
| engagement_time: 30_000 |
| ), |
| build(:pageview, |
| pathname: "/", |
| user_id: 123, |
| timestamp: ~N[2021-01-01 00:15:00] |
| ), |
| build(:engagement, |
| pathname: "/", |
| user_id: 123, |
| timestamp: ~N[2021-01-01 00:30:00], |
| engagement_time: 900_000 |
| ), |
| build(:imported_pages, |
| page: "/", |
| date: ~D[2021-01-01], |
| total_time_on_page: 700, |
| total_time_on_page_visits: 3 |
| ), |
| build(:imported_entry_pages, |
| entry_page: "/", |
| date: ~D[2021-01-01], |
| entrances: 3, |
| bounces: 1 |
| ), |
| build(:imported_pages, |
| page: "/some-other-page", |
| date: ~D[2021-01-01], |
| total_time_on_page: 60, |
| total_time_on_page_visits: 1 |
| ) |
| ]) |
|
|
| populate_stats(site, site_import.id, [ |
| build(:imported_visitors, date: ~D[2021-01-01]), |
| build(:imported_visitors, date: ~D[2021-01-01]), |
| build(:imported_visitors, date: ~D[2021-01-01]) |
| ]) |
|
|
| response = |
| query_pages(conn, site, |
| date_range: ["2021-01-01", "2021-01-01"], |
| metrics: @detailed_metrics, |
| include: %{"imports" => true} |
| ) |
|
|
| assert response["results"] == [ |
| %{"dimensions" => ["/"], "metrics" => [3, 3, 40.0, 500, 0, 60.0]}, |
| %{"dimensions" => ["/some-other-page"], "metrics" => [2, 2, 0, 45, 0, 40.0]} |
| ] |
| end |
|
|
| test "returns top pages in realtime report", %{conn: conn, site: site} do |
| populate_stats(site, [ |
| build(:pageview, pathname: "/page1"), |
| build(:pageview, pathname: "/page2"), |
| build(:pageview, pathname: "/page1") |
| ]) |
|
|
| response = query_pages(conn, site, date_range: "realtime") |
|
|
| assert response["results"] == [ |
| %{"dimensions" => ["/page1"], "metrics" => [2, 66.67]}, |
| %{"dimensions" => ["/page2"], "metrics" => [1, 33.33]} |
| ] |
| end |
|
|
| test "calculates conversion_rate when filtering for goal", %{conn: conn, site: site} do |
| populate_stats(site, [ |
| build(:pageview, user_id: 1, pathname: "/"), |
| build(:pageview, user_id: 2, pathname: "/"), |
| build(:pageview, user_id: 3, pathname: "/"), |
| build(:event, user_id: 3, name: "Signup") |
| ]) |
|
|
| insert(:goal, site: site, event_name: "Signup") |
|
|
| response = |
| query_pages(conn, site, |
| date_range: "day", |
| filters: [["is", "event:goal", ["Signup"]]], |
| metrics: @goal_filter_metrics |
| ) |
|
|
| assert response["results"] == [ |
| %{"dimensions" => ["/"], "metrics" => [1, 33.33, 3]} |
| ] |
| end |
|
|
| test "filter by :is page with imported data", %{conn: conn, site: site} do |
| site_import = insert(:site_import, site: site) |
|
|
| populate_stats(site, site_import.id, [ |
| build(:pageview, user_id: 1, pathname: "/", timestamp: ~N[2021-01-01 12:00:00]), |
| build(:engagement, |
| user_id: 1, |
| pathname: "/", |
| timestamp: ~N[2021-01-01 12:01:00], |
| engagement_time: 60_000 |
| ), |
| build(:pageview, user_id: 1, pathname: "/ignored", timestamp: ~N[2021-01-01 12:01:00]), |
| build(:engagement, |
| user_id: 1, |
| pathname: "/ignored", |
| timestamp: ~N[2021-01-01 12:02:00], |
| engagement_time: 60_000 |
| ), |
| build(:imported_entry_pages, |
| entry_page: "/", |
| visitors: 1, |
| bounces: 1, |
| date: ~D[2021-01-01] |
| ), |
| build(:imported_pages, |
| page: "/", |
| visitors: 3, |
| pageviews: 3, |
| total_time_on_page: 300, |
| total_time_on_page_visits: 3, |
| date: ~D[2021-01-01] |
| ), |
| build(:imported_pages, page: "/ignored", visitors: 10, date: ~D[2021-01-01]) |
| ]) |
|
|
| response = |
| query_pages(conn, site, |
| date_range: ["2021-01-01", "2021-01-01"], |
| filters: [["is", "event:page", ["/"]]], |
| metrics: @detailed_metrics, |
| include: %{"imports" => true} |
| ) |
|
|
| assert response["results"] == [ |
| %{"dimensions" => ["/"], "metrics" => [4, 4, 50, 90.0, 0, 100.0]} |
| ] |
| end |
|
|
| test "filter by :member page with imported data", %{conn: conn, site: site} do |
| site_import = insert(:site_import, site: site) |
|
|
| populate_stats(site, site_import.id, [ |
| build(:pageview, user_id: 1, pathname: "/", timestamp: ~N[2021-01-01 12:00:00]), |
| build(:engagement, |
| user_id: 1, |
| pathname: "/", |
| timestamp: ~N[2021-01-01 12:01:00], |
| engagement_time: 60_000 |
| ), |
| build(:pageview, user_id: 1, pathname: "/ignored", timestamp: ~N[2021-01-01 12:01:00]), |
| build(:engagement, |
| user_id: 1, |
| pathname: "/ignored", |
| timestamp: ~N[2021-01-01 12:02:00], |
| engagement_time: 60_000 |
| ), |
| build(:imported_entry_pages, |
| entry_page: "/", |
| visitors: 1, |
| bounces: 1, |
| date: ~D[2021-01-01] |
| ), |
| build(:imported_entry_pages, |
| entry_page: "/a", |
| visitors: 1, |
| bounces: 1, |
| date: ~D[2021-01-01] |
| ), |
| build(:imported_pages, |
| page: "/", |
| visitors: 3, |
| pageviews: 3, |
| total_time_on_page: 300, |
| total_time_on_page_visits: 3, |
| date: ~D[2021-01-01] |
| ), |
| build(:imported_pages, |
| page: "/a", |
| visitors: 1, |
| date: ~D[2021-01-01] |
| ), |
| build(:imported_pages, page: "/ignored", visitors: 10, date: ~D[2021-01-01]) |
| ]) |
|
|
| response = |
| query_pages(conn, site, |
| date_range: ["2021-01-01", "2021-01-01"], |
| filters: [["is", "event:page", ["/", "/a"]]], |
| metrics: @detailed_metrics, |
| include: %{"imports" => true} |
| ) |
|
|
| assert response["results"] == [ |
| %{"dimensions" => ["/"], "metrics" => [4, 4, 50, 90.0, 0, 80.0]}, |
| %{"dimensions" => ["/a"], "metrics" => [1, 1, 100, 10.0, nil, 20.0]} |
| ] |
| end |
|
|
| test "filter by :matches_wildcard page with imported data", %{conn: conn, site: site} do |
| site_import = insert(:site_import, site: site) |
|
|
| populate_stats(site, site_import.id, [ |
| build(:pageview, user_id: 1, pathname: "/aaa", timestamp: ~N[2021-01-01 12:00:00]), |
| build(:engagement, |
| user_id: 1, |
| pathname: "/aaa", |
| timestamp: ~N[2021-01-01 12:01:00], |
| engagement_time: 60_000 |
| ), |
| build(:pageview, user_id: 1, pathname: "/ignored", timestamp: ~N[2021-01-01 12:01:00]), |
| build(:engagement, |
| user_id: 1, |
| pathname: "/ignored", |
| timestamp: ~N[2021-01-01 12:02:00], |
| engagement_time: 60_000 |
| ), |
| build(:imported_entry_pages, |
| entry_page: "/aaa", |
| visitors: 1, |
| bounces: 1, |
| date: ~D[2021-01-01] |
| ), |
| build(:imported_entry_pages, |
| entry_page: "/a", |
| visitors: 1, |
| bounces: 1, |
| date: ~D[2021-01-01] |
| ), |
| build(:imported_pages, |
| page: "/aaa", |
| visitors: 3, |
| pageviews: 3, |
| total_time_on_page: 300, |
| total_time_on_page_visits: 3, |
| date: ~D[2021-01-01] |
| ), |
| build(:imported_pages, |
| page: "/a", |
| visitors: 1, |
| date: ~D[2021-01-01] |
| ), |
| build(:imported_pages, page: "/ignored", visitors: 10, date: ~D[2021-01-01]) |
| ]) |
|
|
| response = |
| query_pages(conn, site, |
| date_range: ["2021-01-01", "2021-01-01"], |
| filters: [["contains", "event:page", ["/a"]]], |
| metrics: @detailed_metrics, |
| include: %{"imports" => true} |
| ) |
|
|
| assert response["results"] == [ |
| %{"dimensions" => ["/aaa"], "metrics" => [4, 4, 50.0, 90, 0, 80.0]}, |
| %{"dimensions" => ["/a"], "metrics" => [1, 1, 100.0, 10, nil, 20.0]} |
| ] |
| end |
|
|
| test "can compare with previous period", %{conn: conn, site: site} do |
| populate_stats(site, [ |
| build(:pageview, |
| pathname: "/page1", |
| timestamp: ~N[2021-01-01 00:00:00] |
| ), |
| build(:pageview, |
| pathname: "/page1", |
| timestamp: ~N[2021-01-02 00:00:00] |
| ), |
| build(:pageview, |
| pathname: "/page2", |
| timestamp: ~N[2021-01-02 00:00:00] |
| ), |
| build(:pageview, |
| pathname: "/page2", |
| timestamp: ~N[2021-01-02 00:00:00] |
| ) |
| ]) |
|
|
| response = |
| query_pages(conn, site, |
| date_range: ["2021-01-02", "2021-01-02"], |
| metrics: @detailed_metrics, |
| include: %{"compare" => "previous_period"} |
| ) |
|
|
| assert response["results"] == [ |
| %{ |
| "dimensions" => ["/page2"], |
| "metrics" => [2, 2, 100, nil, nil, 66.67], |
| "comparison" => %{ |
| "dimensions" => ["/page2"], |
| "metrics" => [0, 0, 0.0, nil, nil, 0.0], |
| "change" => [100, 100, nil, nil, nil, 100] |
| } |
| }, |
| %{ |
| "dimensions" => ["/page1"], |
| "metrics" => [1, 1, 100, nil, nil, 33.33], |
| "comparison" => %{ |
| "dimensions" => ["/page1"], |
| "metrics" => [1, 1, 100, nil, nil, 100.0], |
| "change" => [0, 0, 0, nil, nil, -67] |
| } |
| } |
| ] |
|
|
| assert %{ |
| "comparison_date_range" => ["2021-01-01T00:00:00Z", "2021-01-01T23:59:59Z"], |
| "date_range" => ["2021-01-02T00:00:00Z", "2021-01-02T23:59:59Z"] |
| } = response["query"] |
| end |
|
|
| on_ee do |
| test "returns pages across all sites on a consolidated view", %{conn: conn, site: site} do |
| another_site = new_site(team: site.team) |
| cv = new_consolidated_view(site.team) |
|
|
| populate_stats(site, [ |
| build(:pageview, pathname: "/a1", timestamp: ~N[2021-01-01 00:00:00]), |
| build(:pageview, pathname: "/a2", timestamp: ~N[2021-01-01 00:00:00]), |
| build(:pageview, pathname: "/a2", timestamp: ~N[2021-01-01 00:00:00]) |
| ]) |
|
|
| populate_stats(another_site, [ |
| build(:pageview, pathname: "/b1", timestamp: ~N[2021-01-01 00:00:00]), |
| build(:pageview, pathname: "/b1", timestamp: ~N[2021-01-01 00:00:00]), |
| build(:pageview, pathname: "/b1", timestamp: ~N[2021-01-01 00:00:00]) |
| ]) |
|
|
| response = |
| query_pages(conn, cv, |
| date_range: ["2021-01-01", "2021-01-01"], |
| metrics: @detailed_metrics |
| ) |
|
|
| assert response["results"] == [ |
| %{"dimensions" => ["/b1"], "metrics" => [3, 3, 100, nil, nil, 50.0]}, |
| %{"dimensions" => ["/a2"], "metrics" => [2, 2, 100, nil, nil, 33.33]}, |
| %{"dimensions" => ["/a1"], "metrics" => [1, 1, 100, nil, nil, 16.67]} |
| ] |
| end |
| end |
| end |
|
|
| describe "GET /api/stats/:domain/entry-pages" do |
| setup [:create_user, :log_in, :create_site, :create_legacy_site_import] |
|
|
| test "returns top entry pages by visitors", %{conn: conn, site: site} do |
| populate_stats(site, [ |
| build(:pageview, |
| pathname: "/page1", |
| timestamp: ~N[2021-01-01 00:00:00] |
| ), |
| build(:pageview, |
| pathname: "/page1", |
| timestamp: ~N[2021-01-01 00:00:00] |
| ), |
| build(:pageview, |
| pathname: "/page2", |
| user_id: @user_id, |
| timestamp: ~N[2021-01-01 00:00:00] |
| ), |
| build(:pageview, |
| pathname: "/page2", |
| user_id: @user_id, |
| timestamp: ~N[2021-01-01 00:15:00] |
| ) |
| ]) |
|
|
| populate_stats(site, [ |
| build(:pageview, |
| pathname: "/page2", |
| user_id: @user_id, |
| timestamp: ~N[2021-01-01 23:15:00] |
| ) |
| ]) |
|
|
| response = |
| query_pages(conn, site, |
| date_range: ["2021-01-01", "2021-01-01"], |
| dimensions: ["visit:entry_page"], |
| metrics: ["visitors", "visits", "bounce_rate", "visit_duration", "percentage"] |
| ) |
|
|
| assert response["results"] == [ |
| %{"dimensions" => ["/page1"], "metrics" => [2, 2, 100, 0, 66.67]}, |
| %{"dimensions" => ["/page2"], "metrics" => [1, 2, 50, 450, 33.33]} |
| ] |
| end |
|
|
| test "returns top entry pages filtered by custom pageview props", %{conn: conn, site: site} do |
| populate_stats(site, [ |
| build(:pageview, |
| pathname: "/blog", |
| user_id: 123, |
| timestamp: ~N[2021-01-01 00:00:00] |
| ), |
| build(:pageview, |
| pathname: "/blog/john-1", |
| "meta.key": ["author"], |
| "meta.value": ["John Doe"], |
| user_id: 123, |
| timestamp: ~N[2021-01-01 00:01:00] |
| ), |
| build(:pageview, |
| pathname: "/blog/john-2", |
| "meta.key": ["author"], |
| "meta.value": ["John Doe"], |
| timestamp: ~N[2021-01-01 00:00:00] |
| ), |
| build(:pageview, |
| pathname: "/blog/other-post", |
| "meta.key": ["author"], |
| "meta.value": ["other"], |
| timestamp: ~N[2021-01-01 00:00:00] |
| ), |
| build(:pageview, |
| pathname: "/blog", |
| timestamp: ~N[2021-01-01 00:00:00] |
| ) |
| ]) |
|
|
| response = |
| query_pages(conn, site, |
| date_range: ["2021-01-01", "2021-01-01"], |
| dimensions: ["visit:entry_page"], |
| filters: [["is", "event:props:author", ["John Doe"]]], |
| metrics: ["visitors", "visits", "bounce_rate", "visit_duration", "percentage"] |
| ) |
|
|
| assert response["results"] == [ |
| %{"dimensions" => ["/blog"], "metrics" => [1, 1, 0, 60, 50.0]}, |
| %{"dimensions" => ["/blog/john-2"], "metrics" => [1, 1, 100, 0, 50.0]} |
| ] |
| end |
|
|
| test "returns top entry pages by visitors with imported data", %{conn: conn, site: site} do |
| populate_stats(site, [ |
| build(:pageview, |
| pathname: "/page1", |
| timestamp: ~N[2021-01-01 00:00:00] |
| ), |
| build(:pageview, |
| pathname: "/page1", |
| timestamp: ~N[2021-01-01 00:00:00] |
| ), |
| build(:pageview, |
| pathname: "/page2", |
| user_id: @user_id, |
| timestamp: ~N[2021-01-01 00:00:00] |
| ), |
| build(:pageview, |
| pathname: "/page2", |
| user_id: @user_id, |
| timestamp: ~N[2021-01-01 00:15:00] |
| ), |
| build(:pageview, |
| pathname: "/page2", |
| user_id: @user_id, |
| timestamp: ~N[2021-01-01 23:15:00] |
| ) |
| ]) |
|
|
| populate_stats(site, [ |
| build(:imported_visitors, |
| date: ~D[2021-01-01], |
| visitors: 2 |
| ), |
| build(:imported_entry_pages, |
| entry_page: "/page2", |
| date: ~D[2021-01-01], |
| entrances: 3, |
| visitors: 2, |
| visit_duration: 300 |
| ) |
| ]) |
|
|
| response = |
| query_pages(conn, site, |
| date_range: ["2021-01-01", "2021-01-01"], |
| dimensions: ["visit:entry_page"], |
| metrics: ["visitors", "visits", "bounce_rate", "visit_duration", "percentage"] |
| ) |
|
|
| assert response["results"] == [ |
| %{"dimensions" => ["/page1"], "metrics" => [2, 2, 100, 0, 66.67]}, |
| %{"dimensions" => ["/page2"], "metrics" => [1, 2, 50, 450, 33.33]} |
| ] |
|
|
| response = |
| query_pages(conn, site, |
| date_range: ["2021-01-01", "2021-01-01"], |
| dimensions: ["visit:entry_page"], |
| metrics: ["visitors", "visits", "bounce_rate", "visit_duration", "percentage"], |
| include: %{"imports" => true} |
| ) |
|
|
| assert response["results"] == [ |
| %{"dimensions" => ["/page2"], "metrics" => [3, 5, 20.0, 240.0, 60.0]}, |
| %{"dimensions" => ["/page1"], "metrics" => [2, 2, 100.0, 0.0, 40.0]} |
| ] |
| end |
|
|
| test "returns top entry pages by visitors filtered by hostname", |
| %{conn: conn, site: site} do |
| populate_stats(site, [ |
| build(:pageview, |
| pathname: "/page1", |
| hostname: "en.example.com", |
| timestamp: ~N[2021-01-01 00:00:00] |
| ), |
| build(:pageview, |
| pathname: "/page1", |
| hostname: "es.example.com", |
| timestamp: ~N[2021-01-01 00:00:00] |
| ), |
| build(:pageview, |
| pathname: "/page2", |
| hostname: "en.example.com", |
| user_id: @user_id, |
| timestamp: ~N[2021-01-01 00:00:00] |
| ), |
| build(:pageview, |
| pathname: "/page2", |
| hostname: "es.example.com", |
| user_id: @user_id, |
| timestamp: ~N[2021-01-01 00:15:00] |
| ), |
| build(:pageview, |
| pathname: "/exit", |
| hostname: "es.example.com", |
| user_id: @user_id, |
| timestamp: ~N[2021-01-01 00:16:00] |
| ), |
| build(:pageview, |
| pathname: "/page2", |
| hostname: "es.example.com", |
| timestamp: ~N[2021-01-01 23:15:00] |
| ) |
| ]) |
|
|
| |
| response = |
| query_pages(conn, site, |
| date_range: ["2021-01-01", "2021-01-01"], |
| dimensions: ["visit:entry_page"], |
| filters: [["is", "event:hostname", ["es.example.com"]]], |
| metrics: ["visitors", "visits", "bounce_rate", "visit_duration", "percentage"], |
| order_by: [["visitors", "desc"], ["visit:entry_page", "asc"]] |
| ) |
|
|
| assert response["results"] == [ |
| %{"dimensions" => ["/page1"], "metrics" => [1, 1, 100, 0, 50.0]}, |
| %{"dimensions" => ["/page2"], "metrics" => [1, 1, 100, 0, 50.0]} |
| ] |
| end |
|
|
| test "bugfix: pagination on /pages filtered by goal", %{conn: conn, site: site} do |
| populate_stats( |
| site, |
| for i <- 1..30 do |
| build(:event, |
| user_id: i, |
| name: "Signup", |
| pathname: "/signup/#{String.pad_leading(to_string(i), 2, "0")}", |
| timestamp: ~N[2021-01-01 00:01:00] |
| ) |
| end |
| ) |
|
|
| insert(:goal, site: site, event_name: "Signup") |
|
|
| request = fn conn, opts -> |
| conn |
| |> query_pages(site, |
| metrics: ["visitors"], |
| pagination: %{ |
| "offset" => Keyword.fetch!(opts, :offset), |
| "limit" => Keyword.fetch!(opts, :limit) |
| }, |
| filters: [["is", "event:goal", ["Signup"]]], |
| order_by: [["event:page", "asc"]] |
| ) |
| |> Map.get("results") |
| |> Enum.map(fn %{"dimensions" => ["/signup/" <> seq]} -> |
| seq |
| end) |
| end |
|
|
| assert List.first(request.(conn, offset: 0, limit: 100)) == "01" |
| assert List.last(request.(conn, offset: 0, limit: 100)) == "30" |
| assert List.last(request.(conn, offset: 0, limit: 29)) == "29" |
| assert ["01", "02"] = request.(conn, offset: 0, limit: 2) |
| assert ["03", "04"] = request.(conn, offset: 2, limit: 2) |
| assert ["01", "02", "03", "04", "05"] = request.(conn, offset: 0, limit: 5) |
| assert ["06", "07", "08", "09", "10"] = request.(conn, offset: 5, limit: 5) |
| assert ["11", "12", "13", "14", "15"] = request.(conn, offset: 10, limit: 5) |
| assert ["20"] = request.(conn, offset: 19, limit: 1) |
| assert [] = request.(conn, offset: 30, limit: 1) |
| end |
|
|
| test "calculates conversion_rate when filtering for goal", %{conn: conn, site: site} do |
| populate_stats(site, [ |
| build(:pageview, |
| pathname: "/page1", |
| user_id: 1, |
| timestamp: ~N[2021-01-01 00:00:00] |
| ), |
| build(:pageview, |
| pathname: "/page1", |
| user_id: 2, |
| timestamp: ~N[2021-01-01 00:00:00] |
| ), |
| build(:event, |
| name: "Signup", |
| user_id: 1, |
| timestamp: ~N[2021-01-01 00:00:00] |
| ), |
| build(:pageview, |
| pathname: "/page2", |
| user_id: 3, |
| timestamp: ~N[2021-01-01 00:00:00] |
| ), |
| build(:pageview, |
| pathname: "/page2", |
| user_id: 3, |
| timestamp: ~N[2021-01-01 00:15:00] |
| ), |
| build(:event, |
| name: "Signup", |
| user_id: 3, |
| timestamp: ~N[2021-01-01 00:15:00] |
| ) |
| ]) |
|
|
| insert(:goal, site: site, event_name: "Signup") |
|
|
| response = |
| query_pages(conn, site, |
| date_range: ["2021-01-01", "2021-01-01"], |
| dimensions: ["visit:entry_page"], |
| filters: [["is", "event:goal", ["Signup"]]], |
| metrics: ["visitors", "group_conversion_rate", "total_visitors"], |
| order_by: [["visitors", "desc"], ["visit:entry_page", "asc"]] |
| ) |
|
|
| assert response["results"] == [ |
| %{"dimensions" => ["/page1"], "metrics" => [1, 50.0, 2]}, |
| %{"dimensions" => ["/page2"], "metrics" => [1, 100.0, 1]} |
| ] |
| end |
|
|
| test "can filter out empty entry pages (sessions with only custom events)", %{ |
| conn: conn, |
| site: site |
| } do |
| populate_stats(site, [ |
| build(:event, |
| name: "Signup", |
| timestamp: ~N[2021-01-01 00:15:00], |
| pathname: "/" |
| ) |
| ]) |
|
|
| response = |
| query_pages(conn, site, |
| date_range: ["2021-01-01", "2021-01-01"], |
| dimensions: ["visit:entry_page"], |
| filters: [["is_not", "visit:entry_page", [""]]], |
| metrics: ["visitors", "visits", "bounce_rate", "visit_duration", "percentage"] |
| ) |
|
|
| assert response["results"] == [] |
| end |
|
|
| test "filter by :matches_member entry_page with imported data", %{conn: conn, site: site} do |
| site_import = insert(:site_import, site: site) |
|
|
| populate_stats(site, site_import.id, [ |
| build(:pageview, pathname: "/aaa", timestamp: ~N[2021-01-01 12:00:00]), |
| build(:pageview, pathname: "/a", timestamp: ~N[2021-01-01 12:00:00]), |
| build(:pageview, pathname: "/ignored", timestamp: ~N[2021-01-01 12:01:00]), |
| build(:imported_entry_pages, |
| entry_page: "/a", |
| visitors: 5, |
| entrances: 9, |
| visit_duration: 1000, |
| date: ~D[2021-01-01] |
| ), |
| build(:imported_entry_pages, |
| entry_page: "/bbb", |
| visitors: 2, |
| entrances: 2, |
| visit_duration: 100, |
| date: ~D[2021-01-01] |
| ) |
| ]) |
|
|
| response = |
| query_pages(conn, site, |
| date_range: ["2021-01-01", "2021-01-01"], |
| dimensions: ["visit:entry_page"], |
| filters: [["contains", "visit:entry_page", ["/a", "/b"]]], |
| metrics: ["visitors", "visits", "bounce_rate", "visit_duration", "percentage"], |
| include: %{"imports" => true} |
| ) |
|
|
| assert response["results"] == [ |
| %{"dimensions" => ["/a"], "metrics" => [6, 10, 10.0, 100.0, 66.67]}, |
| %{"dimensions" => ["/bbb"], "metrics" => [2, 2, 0.0, 50.0, 22.22]}, |
| %{"dimensions" => ["/aaa"], "metrics" => [1, 1, 100.0, 0, 11.11]} |
| ] |
| end |
|
|
| test "returns entry pages broken down by entry page hostname and page", |
| %{conn: conn, site: site} do |
| populate_stats(site, [ |
| build(:pageview, |
| pathname: "/landing", |
| hostname: "blog.example.com", |
| user_id: 1, |
| timestamp: ~N[2021-01-01 00:00:00] |
| ), |
| build(:pageview, |
| pathname: "/about", |
| hostname: "blog.example.com", |
| user_id: 1, |
| timestamp: ~N[2021-01-01 00:05:00] |
| ), |
| build(:pageview, |
| pathname: "/landing", |
| hostname: "blog.example.com", |
| user_id: 2, |
| timestamp: ~N[2021-01-01 00:00:00] |
| ), |
| build(:pageview, |
| pathname: "/landing", |
| hostname: "www.example.com", |
| user_id: 3, |
| timestamp: ~N[2021-01-01 00:00:00] |
| ) |
| ]) |
|
|
| response = |
| query_pages(conn, site, |
| date_range: ["2021-01-01", "2021-01-01"], |
| dimensions: ["visit:entry_page_hostname", "visit:entry_page"], |
| metrics: ["visitors", "visits", "bounce_rate", "percentage"], |
| filters: [["is_not", "visit:entry_page", [""]]], |
| order_by: [["visitors", "desc"], ["visit:entry_page_hostname", "asc"]] |
| ) |
|
|
| assert response["results"] == [ |
| %{ |
| "dimensions" => ["blog.example.com", "/landing"], |
| "metrics" => [2, 2, 50, 66.67] |
| }, |
| %{ |
| "dimensions" => ["www.example.com", "/landing"], |
| "metrics" => [1, 1, 100, 33.33] |
| } |
| ] |
| end |
| end |
|
|
| describe "GET /api/stats/:domain/exit-pages" do |
| setup [:create_user, :log_in, :create_site, :create_legacy_site_import] |
|
|
| test "returns top exit pages by visitors", %{conn: conn, site: site} do |
| populate_stats(site, [ |
| build(:pageview, |
| pathname: "/page1", |
| timestamp: ~N[2021-01-01 00:00:00] |
| ), |
| build(:pageview, |
| pathname: "/page1", |
| timestamp: ~N[2021-01-01 00:00:00] |
| ), |
| build(:pageview, |
| pathname: "/page1", |
| user_id: @user_id, |
| timestamp: ~N[2021-01-01 00:00:00] |
| ), |
| build(:pageview, |
| pathname: "/page2", |
| user_id: @user_id, |
| timestamp: ~N[2021-01-01 00:15:00] |
| ) |
| ]) |
|
|
| response = |
| query_pages(conn, site, |
| date_range: ["2021-01-01", "2021-01-01"], |
| dimensions: ["visit:exit_page"], |
| metrics: ["visitors", "visits", "exit_rate", "percentage"] |
| ) |
|
|
| assert response["results"] == [ |
| %{"dimensions" => ["/page1"], "metrics" => [2, 2, 66.7, 66.67]}, |
| %{"dimensions" => ["/page2"], "metrics" => [1, 1, 100, 33.33]} |
| ] |
| end |
|
|
| test "returns top exit pages by ascending visits", %{conn: conn, site: site} do |
| populate_stats(site, [ |
| build(:pageview, |
| pathname: "/page1", |
| timestamp: ~N[2021-01-01 00:00:00] |
| ), |
| build(:pageview, |
| pathname: "/page1", |
| timestamp: ~N[2021-01-01 00:00:00] |
| ), |
| build(:pageview, |
| pathname: "/page1", |
| user_id: @user_id, |
| timestamp: ~N[2021-01-01 00:00:00] |
| ), |
| build(:pageview, |
| pathname: "/page2", |
| user_id: @user_id, |
| timestamp: ~N[2021-01-01 00:15:00] |
| ) |
| ]) |
|
|
| response = |
| query_pages(conn, site, |
| date_range: ["2021-01-01", "2021-01-01"], |
| dimensions: ["visit:exit_page"], |
| metrics: ["visitors", "visits", "exit_rate", "percentage"], |
| order_by: [["visits", "asc"]] |
| ) |
|
|
| assert response["results"] == [ |
| %{"dimensions" => ["/page2"], "metrics" => [1, 1, 100.0, 33.33]}, |
| %{"dimensions" => ["/page1"], "metrics" => [2, 2, 66.7, 66.67]} |
| ] |
| end |
|
|
| test "returns top exit pages by visitors filtered by hostname", |
| %{conn: conn, site: site} do |
| populate_stats(site, [ |
| build(:pageview, |
| pathname: "/page1", |
| hostname: "en.example.com", |
| timestamp: ~N[2021-01-01 00:00:00] |
| ), |
| build(:pageview, |
| pathname: "/page1", |
| hostname: "es.example.com", |
| timestamp: ~N[2021-01-01 00:00:00] |
| ), |
| build(:pageview, |
| pathname: "/page1", |
| hostname: "en.example.com", |
| user_id: @user_id, |
| timestamp: ~N[2021-01-01 00:00:00] |
| ), |
| build(:pageview, |
| pathname: "/page2", |
| hostname: "es.example.com", |
| user_id: @user_id, |
| timestamp: ~N[2021-01-01 00:15:00] |
| ), |
| build(:pageview, |
| pathname: "/exit", |
| hostname: "en.example.com", |
| user_id: @user_id, |
| timestamp: ~N[2021-01-01 00:16:00] |
| ) |
| ]) |
|
|
| response = |
| query_pages(conn, site, |
| date_range: ["2021-01-01", "2021-01-01"], |
| dimensions: ["visit:exit_page"], |
| filters: [["is", "event:hostname", ["es.example.com"]]], |
| metrics: ["visitors", "visits", "percentage"] |
| ) |
|
|
| |
| assert response["results"] == [ |
| %{"dimensions" => ["/page1"], "metrics" => [1, 1, 100.0]} |
| ] |
| end |
|
|
| test "returns top exit pages filtered by custom pageview props", %{conn: conn, site: site} do |
| populate_stats(site, [ |
| build(:pageview, |
| pathname: "/blog/john-1", |
| "meta.key": ["author"], |
| "meta.value": ["John Doe"], |
| user_id: 123, |
| timestamp: ~N[2021-01-01 00:00:00] |
| ), |
| build(:pageview, |
| pathname: "/", |
| user_id: 123, |
| timestamp: ~N[2021-01-01 00:01:00] |
| ), |
| build(:pageview, |
| pathname: "/blog/other-post", |
| "meta.key": ["author"], |
| "meta.value": ["other"], |
| timestamp: ~N[2021-01-01 00:00:00] |
| ), |
| build(:pageview, |
| pathname: "/", |
| timestamp: ~N[2021-01-01 00:00:00] |
| ) |
| ]) |
|
|
| response = |
| query_pages(conn, site, |
| date_range: ["2021-01-01", "2021-01-01"], |
| dimensions: ["visit:exit_page"], |
| filters: [["is", "event:props:author", ["John Doe"]]], |
| metrics: ["visitors", "visits", "percentage"] |
| ) |
|
|
| assert response["results"] == [ |
| %{"dimensions" => ["/"], "metrics" => [1, 1, 100.0]} |
| ] |
| end |
|
|
| test "returns top exit pages by visitors with imported data", %{conn: conn, site: site} do |
| populate_stats(site, [ |
| build(:pageview, |
| pathname: "/page1", |
| timestamp: ~N[2021-01-01 00:00:00] |
| ), |
| build(:pageview, |
| pathname: "/page1", |
| timestamp: ~N[2021-01-01 00:00:00] |
| ), |
| build(:pageview, |
| pathname: "/page1", |
| user_id: @user_id, |
| timestamp: ~N[2021-01-01 00:00:00] |
| ), |
| build(:pageview, |
| pathname: "/page2", |
| user_id: @user_id, |
| timestamp: ~N[2021-01-01 00:15:00] |
| ) |
| ]) |
|
|
| populate_stats(site, [ |
| build(:imported_pages, |
| page: "/page2", |
| date: ~D[2021-01-01], |
| pageviews: 4, |
| visitors: 2 |
| ), |
| build(:imported_exit_pages, |
| exit_page: "/page2", |
| date: ~D[2021-01-01], |
| exits: 3, |
| visitors: 2 |
| ) |
| ]) |
|
|
| response = |
| query_pages(conn, site, |
| date_range: ["2021-01-01", "2021-01-01"], |
| dimensions: ["visit:exit_page"], |
| filters: [["is_not", "visit:exit_page", [""]]], |
| metrics: ["visitors", "visits", "exit_rate", "percentage"] |
| ) |
|
|
| assert response["results"] == [ |
| %{"dimensions" => ["/page1"], "metrics" => [2, 2, 66.7, 66.67]}, |
| %{"dimensions" => ["/page2"], "metrics" => [1, 1, 100.0, 33.33]} |
| ] |
|
|
| response = |
| query_pages(conn, site, |
| date_range: ["2021-01-01", "2021-01-01"], |
| dimensions: ["visit:exit_page"], |
| filters: [["is_not", "visit:exit_page", [""]]], |
| metrics: ["visitors", "visits", "exit_rate", "percentage"], |
| include: %{"imports" => true} |
| ) |
|
|
| assert response["results"] == [ |
| %{"dimensions" => ["/page2"], "metrics" => [3, 4, 80.0, 60.0]}, |
| %{"dimensions" => ["/page1"], "metrics" => [2, 2, 66.7, 40.0]} |
| ] |
| end |
|
|
| test "returns top exit pages when filtering for goal", %{ |
| conn: conn, |
| site: site |
| } do |
| populate_stats(site, [ |
| build(:event, |
| name: "Signup", |
| user_id: 1, |
| timestamp: ~N[2021-01-01 00:00:00] |
| ), |
| build(:pageview, |
| user_id: 1, |
| pathname: "/exit1", |
| timestamp: ~N[2021-01-01 00:00:00] |
| ), |
| build(:event, |
| name: "Signup", |
| user_id: 2, |
| timestamp: ~N[2021-01-01 00:00:00] |
| ), |
| build(:pageview, |
| user_id: 2, |
| pathname: "/exit1", |
| timestamp: ~N[2021-01-01 00:00:00] |
| ), |
| build(:pageview, |
| user_id: 2, |
| pathname: "/exit2", |
| timestamp: ~N[2021-01-01 00:00:00] |
| ) |
| ]) |
|
|
| insert(:goal, site: site, event_name: "Signup") |
|
|
| response = |
| query_pages(conn, site, |
| date_range: ["2021-01-01", "2021-01-01"], |
| dimensions: ["visit:exit_page"], |
| filters: [["is", "event:goal", ["Signup"]]], |
| metrics: ["visitors", "group_conversion_rate", "total_visitors"], |
| order_by: [["visitors", "desc"], ["visit:exit_page", "asc"]] |
| ) |
|
|
| assert response["results"] == [ |
| %{"dimensions" => ["/exit1"], "metrics" => [1, 100.0, 1]}, |
| %{"dimensions" => ["/exit2"], "metrics" => [1, 100.0, 1]} |
| ] |
| end |
|
|
| test "returns top exit pages when filtering for page", %{conn: conn, site: site} do |
| populate_stats(site, [ |
| build(:pageview, |
| user_id: 1, |
| pathname: "/exit1", |
| timestamp: ~N[2021-01-01 00:00:00] |
| ), |
| build(:pageview, |
| user_id: 2, |
| pathname: "/exit1", |
| timestamp: ~N[2021-01-01 00:00:00] |
| ), |
| build(:pageview, |
| user_id: 2, |
| pathname: "/exit2", |
| timestamp: ~N[2021-01-01 00:00:00] |
| ), |
| build(:pageview, |
| user_id: 3, |
| pathname: "/exit2", |
| timestamp: ~N[2021-01-01 00:00:00] |
| ), |
| build(:pageview, |
| user_id: 3, |
| pathname: "/should-not-appear", |
| timestamp: ~N[2021-01-01 00:00:00] |
| ) |
| ]) |
|
|
| response = |
| query_pages(conn, site, |
| date_range: ["2021-01-01", "2021-01-01"], |
| dimensions: ["visit:exit_page"], |
| filters: [["is", "event:page", ["/exit1"]]], |
| metrics: ["visitors", "visits", "percentage"] |
| ) |
|
|
| assert response["results"] == [ |
| %{"dimensions" => ["/exit1"], "metrics" => [1, 1, 50.0]}, |
| %{"dimensions" => ["/exit2"], "metrics" => [1, 1, 50.0]} |
| ] |
| end |
|
|
| test "can filter out empty exit pages (sessions with only custom events)", %{ |
| conn: conn, |
| site: site |
| } do |
| populate_stats(site, [ |
| build(:event, |
| name: "Signup", |
| timestamp: ~N[2021-01-01 00:15:00], |
| pathname: "/" |
| ) |
| ]) |
|
|
| response = |
| query_pages(conn, site, |
| date_range: ["2021-01-01", "2021-01-01"], |
| dimensions: ["visit:exit_page"], |
| filters: [["is_not", "visit:exit_page", [""]]], |
| metrics: ["visitors", "visits", "exit_rate", "percentage"] |
| ) |
|
|
| assert response["results"] == [] |
| end |
|
|
| test "returns exit pages broken down by exit page hostname and page with exit_rate", |
| %{conn: conn, site: site} do |
| populate_stats(site, [ |
| build(:pageview, |
| pathname: "/about", |
| hostname: "blog.example.com", |
| user_id: 1, |
| timestamp: ~N[2021-01-01 00:00:00] |
| ), |
| build(:pageview, |
| pathname: "/about", |
| hostname: "blog.example.com", |
| user_id: 2, |
| timestamp: ~N[2021-01-01 00:00:00] |
| ), |
| build(:pageview, |
| pathname: "/about", |
| hostname: "www.example.com", |
| user_id: 3, |
| timestamp: ~N[2021-01-01 00:00:00] |
| ), |
| build(:pageview, |
| pathname: "/docs", |
| hostname: "www.example.com", |
| user_id: 3, |
| timestamp: ~N[2021-01-01 00:05:00] |
| ) |
| ]) |
|
|
| response = |
| query_pages(conn, site, |
| date_range: ["2021-01-01", "2021-01-01"], |
| dimensions: ["visit:exit_page_hostname", "visit:exit_page"], |
| metrics: ["visitors", "visits", "exit_rate", "percentage"], |
| filters: [["is_not", "visit:exit_page", [""]]], |
| order_by: [["visitors", "desc"], ["visit:exit_page_hostname", "asc"]] |
| ) |
|
|
| assert response["results"] == [ |
| %{ |
| "dimensions" => ["blog.example.com", "/about"], |
| "metrics" => [2, 2, 66.7, 66.67] |
| }, |
| %{ |
| "dimensions" => ["www.example.com", "/docs"], |
| "metrics" => [1, 1, 100.0, 33.33] |
| } |
| ] |
| end |
|
|
| test "exit_rate works with exit page hostname and page dimensions in either order", |
| %{conn: conn, site: site} do |
| populate_stats(site, [ |
| build(:pageview, |
| pathname: "/about", |
| hostname: "blog.example.com", |
| user_id: 1, |
| timestamp: ~N[2021-01-01 00:00:00] |
| ), |
| build(:pageview, |
| pathname: "/about", |
| hostname: "blog.example.com", |
| user_id: 2, |
| timestamp: ~N[2021-01-01 00:00:00] |
| ) |
| ]) |
|
|
| for dimensions <- [ |
| ["visit:exit_page_hostname", "visit:exit_page"], |
| ["visit:exit_page", "visit:exit_page_hostname"] |
| ] do |
| response = |
| query_pages(conn, site, |
| date_range: ["2021-01-01", "2021-01-01"], |
| dimensions: dimensions, |
| metrics: ["visitors", "exit_rate"], |
| filters: [["is_not", "visit:exit_page", [""]]] |
| ) |
|
|
| assert response["results"] != [], |
| "expected results for dimensions #{inspect(dimensions)}" |
|
|
| assert Enum.all?(response["results"], fn r -> not is_nil(r["metrics"]) end), |
| "expected no errors for dimensions #{inspect(dimensions)}" |
| end |
| end |
|
|
| test "filter by :is_not exit_page with imported data", %{conn: conn, site: site} do |
| site_import = insert(:site_import, site: site) |
|
|
| populate_stats(site, site_import.id, [ |
| build(:pageview, pathname: "/aaa", timestamp: ~N[2021-01-01 12:00:00]), |
| build(:pageview, pathname: "/a", timestamp: ~N[2021-01-01 12:00:00]), |
| build(:pageview, pathname: "/ignored", timestamp: ~N[2021-01-01 12:01:00]), |
| build(:imported_exit_pages, |
| exit_page: "/a", |
| visitors: 5, |
| exits: 9, |
| visit_duration: 1000, |
| date: ~D[2021-01-01] |
| ), |
| build(:imported_exit_pages, |
| exit_page: "/bbb", |
| visitors: 2, |
| exits: 2, |
| visit_duration: 100, |
| date: ~D[2021-01-01] |
| ), |
| build(:imported_pages, page: "/a", pageviews: 19, date: ~D[2021-01-01]), |
| build(:imported_pages, page: "/bbb", pageviews: 2, date: ~D[2021-01-01]) |
| ]) |
|
|
| response = |
| query_pages(conn, site, |
| date_range: ["2021-01-01", "2021-01-01"], |
| dimensions: ["visit:exit_page"], |
| filters: [["is_not", "visit:exit_page", ["/ignored"]]], |
| metrics: ["visitors", "visits", "exit_rate", "percentage"], |
| include: %{"imports" => true} |
| ) |
|
|
| assert response["results"] == [ |
| %{"dimensions" => ["/a"], "metrics" => [6, 10, 50.0, 66.67]}, |
| %{"dimensions" => ["/bbb"], "metrics" => [2, 2, 100.0, 22.22]}, |
| %{"dimensions" => ["/aaa"], "metrics" => [1, 1, 100.0, 11.11]} |
| ] |
| end |
|
|
| @tag :ee_only |
| test "return revenue metrics for entry pages breakdown", %{conn: conn, site: site} do |
| populate_stats(site, [ |
| build(:pageview, user_id: 1, pathname: "/first"), |
| build(:event, |
| name: "Payment", |
| user_id: 1, |
| revenue_reporting_amount: Decimal.new("2000"), |
| revenue_reporting_currency: "USD" |
| ), |
| build(:pageview, user_id: 2, pathname: "/second"), |
| build(:event, |
| user_id: 2, |
| name: "Payment", |
| revenue_reporting_amount: Decimal.new("3000"), |
| revenue_reporting_currency: "USD" |
| ), |
| build(:event, |
| name: "Payment", |
| user_id: 2, |
| revenue_reporting_amount: Decimal.new("4000"), |
| revenue_reporting_currency: "USD" |
| ), |
| build(:pageview, user_id: 3, pathname: "/first"), |
| build(:event, |
| name: "Payment", |
| user_id: 3, |
| revenue_reporting_amount: Decimal.new("1000"), |
| revenue_reporting_currency: "USD" |
| ), |
| build(:pageview, user_id: 4, pathname: "/third"), |
| build(:event, |
| name: "Payment", |
| user_id: 4, |
| revenue_reporting_amount: Decimal.new("2500"), |
| 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: :USD}) |
|
|
| response = |
| query_pages(conn, site, |
| date_range: "day", |
| dimensions: ["visit:entry_page"], |
| filters: [["is", "event:goal", ["Payment"]], ["is_not", "visit:entry_page", [""]]], |
| metrics: [ |
| "visitors", |
| "group_conversion_rate", |
| "total_visitors", |
| "average_revenue", |
| "total_revenue" |
| ], |
| order_by: [["visitors", "desc"], ["visit:entry_page", "asc"]] |
| ) |
|
|
| assert response["results"] == [ |
| %{ |
| "dimensions" => ["/first"], |
| "metrics" => [ |
| 2, |
| 100.0, |
| 2, |
| %{ |
| "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" => ["/second"], |
| "metrics" => [ |
| 1, |
| 100.0, |
| 1, |
| %{ |
| "currency" => "USD", |
| "long" => "$3,500.00", |
| "short" => "$3.5K", |
| "value" => 3500.0 |
| }, |
| %{ |
| "currency" => "USD", |
| "long" => "$7,000.00", |
| "short" => "$7.0K", |
| "value" => 7000.0 |
| } |
| ] |
| }, |
| %{ |
| "dimensions" => ["/third"], |
| "metrics" => [ |
| 1, |
| 100.0, |
| 1, |
| %{ |
| "currency" => "USD", |
| "long" => "$2,500.00", |
| "short" => "$2.5K", |
| "value" => 2500.0 |
| }, |
| %{ |
| "currency" => "USD", |
| "long" => "$2,500.00", |
| "short" => "$2.5K", |
| "value" => 2500.0 |
| } |
| ] |
| } |
| ] |
| end |
|
|
| @tag :ee_only |
| test "return revenue metrics for exit pages breakdown", %{conn: conn, site: site} do |
| populate_stats(site, [ |
| build(:pageview, user_id: 1, pathname: "/first"), |
| build(:event, |
| name: "Payment", |
| user_id: 1, |
| revenue_reporting_amount: Decimal.new("2000"), |
| revenue_reporting_currency: "USD" |
| ), |
| build(:pageview, user_id: 1, pathname: "/exit_first"), |
| build(:pageview, user_id: 2, pathname: "/second"), |
| build(:event, |
| user_id: 2, |
| name: "Payment", |
| revenue_reporting_amount: Decimal.new("3000"), |
| revenue_reporting_currency: "USD" |
| ), |
| build(:event, |
| name: "Payment", |
| user_id: 2, |
| revenue_reporting_amount: Decimal.new("4000"), |
| revenue_reporting_currency: "USD" |
| ), |
| build(:pageview, user_id: 2, pathname: "/exit_second"), |
| build(:pageview, user_id: 3, pathname: "/first"), |
| build(:event, |
| name: "Payment", |
| user_id: 3, |
| revenue_reporting_amount: Decimal.new("1000"), |
| revenue_reporting_currency: "USD" |
| ), |
| build(:pageview, user_id: 3, pathname: "/exit_first"), |
| build(:pageview, user_id: 4, pathname: "/third"), |
| build(:event, |
| name: "Payment", |
| user_id: 4, |
| revenue_reporting_amount: Decimal.new("2500"), |
| 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: :USD}) |
|
|
| response = |
| query_pages(conn, site, |
| date_range: "day", |
| dimensions: ["visit:exit_page"], |
| filters: [["is", "event:goal", ["Payment"]], ["is_not", "visit:exit_page", [""]]], |
| metrics: [ |
| "visitors", |
| "group_conversion_rate", |
| "total_visitors", |
| "average_revenue", |
| "total_revenue" |
| ], |
| order_by: [["visitors", "desc"], ["visit:exit_page", "asc"]] |
| ) |
|
|
| assert response["results"] == [ |
| %{ |
| "dimensions" => ["/exit_first"], |
| "metrics" => [ |
| 2, |
| 100.0, |
| 2, |
| %{ |
| "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" => ["/exit_second"], |
| "metrics" => [ |
| 1, |
| 100.0, |
| 1, |
| %{ |
| "currency" => "USD", |
| "long" => "$3,500.00", |
| "short" => "$3.5K", |
| "value" => 3500.0 |
| }, |
| %{ |
| "currency" => "USD", |
| "long" => "$7,000.00", |
| "short" => "$7.0K", |
| "value" => 7000.0 |
| } |
| ] |
| }, |
| %{ |
| "dimensions" => ["/third"], |
| "metrics" => [ |
| 1, |
| 100.0, |
| 1, |
| %{ |
| "currency" => "USD", |
| "long" => "$2,500.00", |
| "short" => "$2.5K", |
| "value" => 2500.0 |
| }, |
| %{ |
| "currency" => "USD", |
| "long" => "$2,500.00", |
| "short" => "$2.5K", |
| "value" => 2500.0 |
| } |
| ] |
| } |
| ] |
| end |
|
|
| @tag :ee_only |
| test "return revenue metrics for pages breakdown", %{conn: conn, site: site} do |
| populate_stats(site, [ |
| build(:pageview, user_id: 1, pathname: "/first"), |
| build(:event, |
| name: "Payment", |
| pathname: "/purchase/first", |
| user_id: 1, |
| revenue_reporting_amount: Decimal.new("2000"), |
| revenue_reporting_currency: "USD" |
| ), |
| build(:pageview, user_id: 1, pathname: "/exit_first"), |
| build(:pageview, user_id: 2, pathname: "/second"), |
| build(:event, |
| user_id: 2, |
| name: "Payment", |
| pathname: "/purchase/second", |
| revenue_reporting_amount: Decimal.new("3000"), |
| revenue_reporting_currency: "USD" |
| ), |
| build(:event, |
| name: "Payment", |
| pathname: "/purchase/second", |
| user_id: 2, |
| revenue_reporting_amount: Decimal.new("4000"), |
| revenue_reporting_currency: "USD" |
| ), |
| build(:pageview, user_id: 2, pathname: "/exit_second"), |
| build(:pageview, user_id: 3, pathname: "/first"), |
| build(:event, |
| name: "Payment", |
| pathname: "/purchase/first", |
| user_id: 3, |
| revenue_reporting_amount: Decimal.new("1000"), |
| revenue_reporting_currency: "USD" |
| ), |
| build(:pageview, user_id: 3, pathname: "/exit_first"), |
| build(:pageview, user_id: 4, pathname: "/third"), |
| build(:event, |
| name: "Payment", |
| pathname: "/purchase/third", |
| user_id: 4, |
| revenue_reporting_amount: Decimal.new("2500"), |
| revenue_reporting_currency: "USD" |
| ), |
| build(:event, name: "Payment", pathname: "/nopay", revenue_reporting_amount: nil), |
| build(:event, name: "Payment", pathname: "/nopay", revenue_reporting_amount: nil), |
| build(:event, name: "Payment", pathname: "/nopay", revenue_reporting_amount: nil) |
| ]) |
|
|
| insert(:goal, %{site: site, event_name: "Payment", currency: :USD}) |
|
|
| response = |
| query_pages(conn, site, |
| date_range: "day", |
| filters: [["is", "event:goal", ["Payment"]]], |
| metrics: [ |
| "visitors", |
| "group_conversion_rate", |
| "total_visitors", |
| "average_revenue", |
| "total_revenue" |
| ] |
| ) |
|
|
| assert response["results"] == [ |
| %{ |
| "dimensions" => ["/nopay"], |
| "metrics" => [ |
| 3, |
| 100.0, |
| 3, |
| %{"currency" => "USD", "long" => "$0.00", "short" => "$0.0", "value" => 0.0}, |
| %{"currency" => "USD", "long" => "$0.00", "short" => "$0.0", "value" => 0.0} |
| ] |
| }, |
| %{ |
| "dimensions" => ["/purchase/first"], |
| "metrics" => [ |
| 2, |
| 100.0, |
| 2, |
| %{ |
| "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" => ["/purchase/second"], |
| "metrics" => [ |
| 1, |
| 100.0, |
| 1, |
| %{ |
| "currency" => "USD", |
| "long" => "$3,500.00", |
| "short" => "$3.5K", |
| "value" => 3500.0 |
| }, |
| %{ |
| "currency" => "USD", |
| "long" => "$7,000.00", |
| "short" => "$7.0K", |
| "value" => 7000.0 |
| } |
| ] |
| }, |
| %{ |
| "dimensions" => ["/purchase/third"], |
| "metrics" => [ |
| 1, |
| 100.0, |
| 1, |
| %{ |
| "currency" => "USD", |
| "long" => "$2,500.00", |
| "short" => "$2.5K", |
| "value" => 2500.0 |
| }, |
| %{ |
| "currency" => "USD", |
| "long" => "$2,500.00", |
| "short" => "$2.5K", |
| "value" => 2500.0 |
| } |
| ] |
| } |
| ] |
| end |
| end |
| end |
|
|