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, [ # 1: 2 pageviews on primary (no bounce) 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] ), # 2: single pageview on primary (bounce) build(:pageview, pathname: "/docs", hostname: "main.example.com", user_id: 2, timestamp: ~N[2021-01-01 00:00:00] ), # 3: single pageview on secondary (bounce) 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, [ # session 1 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 ), # session 2 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 ), # session 3 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] ) ]) # We're going to only join sessions where the exit hostname matches the filter 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"] ) # We're going to only join sessions where the entry hostname matches the filter 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