From faebced5332d68ca86a5468ede1c6d360186c096 Mon Sep 17 00:00:00 2001 From: Robert Joonas Date: Thu, 19 Mar 2026 14:23:17 +0000 Subject: [PATCH 1/3] transform pages_test --- .../api/stats_controller/pages_test.exs | 1923 +++++++---------- 1 file changed, 743 insertions(+), 1180 deletions(-) diff --git a/test/plausible_web/controllers/api/stats_controller/pages_test.exs b/test/plausible_web/controllers/api/stats_controller/pages_test.exs index 748d378c3fc5..126ad2a64022 100644 --- a/test/plausible_web/controllers/api/stats_controller/pages_test.exs +++ b/test/plausible_web/controllers/api/stats_controller/pages_test.exs @@ -3,6 +3,34 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do @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", "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, @@ -21,16 +49,16 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do build(:pageview, pathname: "/contact") ]) - conn = get(conn, "/api/stats/#{site.domain}/pages?period=day") + response = query_pages(conn, site, date_range: "day") - assert json_response(conn, 200)["results"] == [ - %{"visitors" => 3, "name" => "/", "percentage" => 50.0}, - %{"visitors" => 2, "name" => "/register", "percentage" => 33.33}, - %{"visitors" => 1, "name" => "/contact", "percentage" => 16.67} + 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: conn1, site: site} do + 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"), @@ -42,22 +70,28 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do build(:pageview, pathname: "/contact", hostname: "e.example.com") ]) - filters = Jason.encode!([[:contains, "event:hostname", [".example.com"]]]) - conn = get(conn1, "/api/stats/#{site.domain}/pages?period=day&filters=#{filters}") + response = + query_pages(conn, site, + date_range: "day", + filters: [["contains", "event:hostname", [".example.com"]]] + ) - assert json_response(conn, 200)["results"] == [ - %{"visitors" => 3, "name" => "/", "percentage" => 50.0}, - %{"visitors" => 2, "name" => "/register", "percentage" => 33.33}, - %{"visitors" => 1, "name" => "/contact", "percentage" => 16.67}, - %{"visitors" => 1, "name" => "/landing", "percentage" => 16.67} + 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]} ] - filters = Jason.encode!([[:is, "event:hostname", ["d.example.com"]]]) - conn = get(conn1, "/api/stats/#{site.domain}/pages?period=day&filters=#{filters}") + response = + query_pages(conn, site, + date_range: "day", + filters: [["is", "event:hostname", ["d.example.com"]]] + ) - assert json_response(conn, 200)["results"] == [ - %{"visitors" => 2, "name" => "/register", "percentage" => 66.67}, - %{"visitors" => 1, "name" => "/", "percentage" => 33.33} + assert response["results"] == [ + %{"dimensions" => ["/register"], "metrics" => [2, 66.67]}, + %{"dimensions" => ["/"], "metrics" => [1, 33.33]} ] end @@ -76,11 +110,14 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do build(:pageview, user_id: 123, pathname: "/") ]) - filters = Jason.encode!([[:is, "event:props:author", ["John Doe"]]]) - conn = get(conn, "/api/stats/#{site.domain}/pages?period=day&filters=#{filters}") + response = + query_pages(conn, site, + date_range: "day", + filters: [["is", "event:props:author", ["John Doe"]]] + ) - assert json_response(conn, 200)["results"] == [ - %{"visitors" => 1, "name" => "/blog/john-1", "percentage" => 100.0} + assert response["results"] == [ + %{"dimensions" => ["/blog/john-1"], "metrics" => [1, 100.0]} ] end @@ -102,12 +139,15 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do build(:pageview, pathname: "/") ]) - filters = Jason.encode!([[:is_not, "event:props:author", ["John Doe"]]]) - conn = get(conn, "/api/stats/#{site.domain}/pages?period=day&filters=#{filters}") + response = + query_pages(conn, site, + date_range: "day", + filters: [["is_not", "event:props:author", ["John Doe"]]] + ) - assert json_response(conn, 200)["results"] == [ - %{"visitors" => 1, "name" => "/", "percentage" => 50.0}, - %{"visitors" => 1, "name" => "/blog/other-post", "percentage" => 50.0} + assert response["results"] == [ + %{"dimensions" => ["/"], "metrics" => [1, 50.0]}, + %{"dimensions" => ["/blog/other-post"], "metrics" => [1, 50.0]} ] end @@ -139,12 +179,15 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do build(:pageview, pathname: "/5") ]) - filters = Jason.encode!([[:contains, "event:props:prop", ["bar"]]]) - conn = get(conn, "/api/stats/#{site.domain}/pages?period=day&filters=#{filters}") + response = + query_pages(conn, site, + date_range: "day", + filters: [["contains", "event:props:prop", ["bar"]]] + ) - assert json_response(conn, 200)["results"] == [ - %{"visitors" => 1, "name" => "/1", "percentage" => 50.0}, - %{"visitors" => 1, "name" => "/2", "percentage" => 50.0} + assert response["results"] == [ + %{"dimensions" => ["/1"], "metrics" => [1, 50.0]}, + %{"dimensions" => ["/2"], "metrics" => [1, 50.0]} ] end @@ -181,13 +224,16 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do ) ]) - filters = Jason.encode!([[:contains, "event:props:prop", ["bar", "nea"]]]) - conn = get(conn, "/api/stats/#{site.domain}/pages?period=day&filters=#{filters}") + response = + query_pages(conn, site, + date_range: "day", + filters: [["contains", "event:props:prop", ["bar", "nea"]]] + ) - assert json_response(conn, 200)["results"] == [ - %{"visitors" => 1, "name" => "/1", "percentage" => 33.33}, - %{"visitors" => 1, "name" => "/2", "percentage" => 33.33}, - %{"visitors" => 1, "name" => "/6", "percentage" => 33.33} + assert response["results"] == [ + %{"dimensions" => ["/1"], "metrics" => [1, 33.33]}, + %{"dimensions" => ["/2"], "metrics" => [1, 33.33]}, + %{"dimensions" => ["/6"], "metrics" => [1, 33.33]} ] end @@ -219,16 +265,17 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do build(:pageview, pathname: "/5") ]) - filters = - Jason.encode!([ - [:is, "event:props:prop", ["bar"]], - [:is, "event:props:number", ["1"]] - ]) - - conn = get(conn, "/api/stats/#{site.domain}/pages?period=day&filters=#{filters}") + response = + query_pages(conn, site, + date_range: "day", + filters: [ + ["is", "event:props:prop", ["bar"]], + ["is", "event:props:number", ["1"]] + ] + ) - assert json_response(conn, 200)["results"] == [ - %{"visitors" => 1, "name" => "/1", "percentage" => 100.0} + assert response["results"] == [ + %{"dimensions" => ["/1"], "metrics" => [1, 100.0]} ] end @@ -304,33 +351,16 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do ) ]) - filters = Jason.encode!([[:is, "event:props:author", ["John Doe"]]]) - - conn = - get( - conn, - "/api/stats/#{site.domain}/pages?period=day&date=2021-01-01&filters=#{filters}&detailed=true" + response = + query_pages(conn, site, + date_range: ["2021-01-01", "2021-01-01"], + filters: [["is", "event:props:author", ["John Doe"]]], + metrics: @detailed_metrics ) - assert json_response(conn, 200)["results"] == [ - %{ - "name" => "/blog/john-2", - "visitors" => 2, - "pageviews" => 2, - "bounce_rate" => 0, - "time_on_page" => 315, - "scroll_depth" => 0, - "percentage" => 100.0 - }, - %{ - "name" => "/blog/john-1", - "visitors" => 1, - "pageviews" => 1, - "bounce_rate" => 0, - "time_on_page" => 60, - "scroll_depth" => 0, - "percentage" => 50.0 - } + 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 @@ -406,33 +436,16 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do ) ]) - filters = Jason.encode!([[:is_not, "event:props:author", ["John Doe"]]]) - - conn = - get( - conn, - "/api/stats/#{site.domain}/pages?period=day&date=2021-01-01&filters=#{filters}&detailed=true" + 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 json_response(conn, 200)["results"] == [ - %{ - "name" => "/blog", - "visitors" => 2, - "pageviews" => 2, - "bounce_rate" => 0, - "time_on_page" => 120, - "scroll_depth" => 0, - "percentage" => 100.0 - }, - %{ - "name" => "/blog/other-post", - "visitors" => 1, - "pageviews" => 1, - "bounce_rate" => 0, - "time_on_page" => 30, - "scroll_depth" => 0, - "percentage" => 50.0 - } + 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 @@ -489,33 +502,16 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do ) ]) - filters = Jason.encode!([[:is, "event:props:author", ["(none)"]]]) - - conn = - get( - conn, - "/api/stats/#{site.domain}/pages?period=day&date=2021-01-01&filters=#{filters}&detailed=true" + response = + query_pages(conn, site, + date_range: ["2021-01-01", "2021-01-01"], + filters: [["is", "event:props:author", ["(none)"]]], + metrics: @detailed_metrics ) - assert json_response(conn, 200)["results"] == [ - %{ - "name" => "/blog", - "visitors" => 2, - "pageviews" => 2, - "bounce_rate" => 50, - "time_on_page" => 45, - "scroll_depth" => 0, - "percentage" => 100.0 - }, - %{ - "name" => "/blog/other-post", - "visitors" => 1, - "pageviews" => 1, - "bounce_rate" => 0, - "time_on_page" => 30, - "scroll_depth" => 0, - "percentage" => 50.0 - } + 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 @@ -580,33 +576,16 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do ) ]) - filters = Jason.encode!([[:is_not, "event:props:author", ["(none)"]]]) - - conn = - get( - conn, - "/api/stats/#{site.domain}/pages?period=day&date=2021-01-01&filters=#{filters}&detailed=true" + response = + query_pages(conn, site, + date_range: ["2021-01-01", "2021-01-01"], + filters: [["is_not", "event:props:author", ["(none)"]]], + metrics: @detailed_metrics ) - assert json_response(conn, 200)["results"] == [ - %{ - "name" => "/blog/other-post", - "visitors" => 2, - "pageviews" => 2, - "bounce_rate" => 100, - "time_on_page" => 30, - "scroll_depth" => 0, - "percentage" => 100.0 - }, - %{ - "name" => "/blog/john-1", - "visitors" => 1, - "pageviews" => 1, - "bounce_rate" => 0, - "time_on_page" => 60, - "scroll_depth" => 0, - "percentage" => 50.0 - } + 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 @@ -645,17 +624,14 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do ) ]) - filters = Jason.encode!([[:is_not, "event:props:browser", ["Chrome", "Safari"]]]) - - conn = - get(conn, "/api/stats/#{site.domain}/pages?period=day&date=2021-01-01&filters=#{filters}") + response = + query_pages(conn, site, + date_range: ["2021-01-01", "2021-01-01"], + filters: [["is_not", "event:props:browser", ["Chrome", "Safari"]]] + ) - assert json_response(conn, 200)["results"] == [ - %{ - "name" => "/firefox", - "visitors" => 2, - "percentage" => 100.0 - } + assert response["results"] == [ + %{"dimensions" => ["/firefox"], "metrics" => [2, 100.0]} ] end @@ -686,17 +662,14 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do ) ]) - filters = Jason.encode!([[:is_not, "event:props:browser", ["Chrome", "(none)"]]]) - - conn = - get(conn, "/api/stats/#{site.domain}/pages?period=day&date=2021-01-01&filters=#{filters}") + response = + query_pages(conn, site, + date_range: ["2021-01-01", "2021-01-01"], + filters: [["is_not", "event:props:browser", ["Chrome", "(none)"]]] + ) - assert json_response(conn, 200)["results"] == [ - %{ - "name" => "/safari", - "visitors" => 1, - "percentage" => 100.0 - } + assert response["results"] == [ + %{"dimensions" => ["/safari"], "metrics" => [1, 100.0]} ] end @@ -753,24 +726,15 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do ) ]) - filters = Jason.encode!([[:is, "event:page", ["/"]]]) - - conn = - get( - conn, - "/api/stats/#{site.domain}/pages?period=day&date=2021-01-01&filters=#{filters}&detailed=true" + response = + query_pages(conn, site, + date_range: ["2021-01-01", "2021-01-01"], + filters: [["is", "event:page", ["/"]]], + metrics: @detailed_metrics ) - assert json_response(conn, 200)["results"] == [ - %{ - "name" => "/", - "visitors" => 2, - "pageviews" => 3, - "bounce_rate" => 50, - "time_on_page" => 90, - "scroll_depth" => 0, - "percentage" => 100.0 - } + assert response["results"] == [ + %{"dimensions" => ["/"], "metrics" => [2, 3, 50, 90, 0, 100.0]} ] end @@ -829,31 +793,16 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do ) ]) - conn = - get( - conn, - "/api/stats/#{site.domain}/pages?period=day&date=2020-01-01&detailed=true&order_by=#{Jason.encode!([["scroll_depth", "asc"]])}" + response = + query_pages(conn, site, + date_range: ["2020-01-01", "2020-01-01"], + metrics: @detailed_metrics, + order_by: [["scroll_depth", "asc"]] ) - assert json_response(conn, 200)["results"] == [ - %{ - "name" => "/another", - "visitors" => 2, - "pageviews" => 2, - "bounce_rate" => 0, - "time_on_page" => 60, - "scroll_depth" => 25, - "percentage" => 66.67 - }, - %{ - "name" => "/blog", - "visitors" => 3, - "pageviews" => 4, - "bounce_rate" => 33, - "time_on_page" => 80, - "scroll_depth" => 60, - "percentage" => 100.0 - } + assert response["results"] == [ + %{"dimensions" => ["/another"], "metrics" => [2, 2, 0, 60, 25, 66.67]}, + %{"dimensions" => ["/blog"], "metrics" => [3, 4, 33, 80, 60, 100.0]} ] end @@ -893,22 +842,16 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do build(:imported_visitors, date: ~D[2020-01-01]) ]) - conn = - get( - conn, - "/api/stats/#{site.domain}/pages?period=day&date=2020-01-01&detailed=true&with_imported=true&order_by=#{Jason.encode!([["scroll_depth", "desc"]])}" + 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 json_response(conn, 200)["results"] == [ - %{ - "name" => "/blog", - "visitors" => 4, - "pageviews" => 4, - "bounce_rate" => 100, - "time_on_page" => 28, - "scroll_depth" => 50, - "percentage" => 100.0 - } + assert response["results"] == [ + %{"dimensions" => ["/blog"], "metrics" => [4, 4, 100, 28, 50, 100.0]} ] end @@ -970,40 +913,18 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do for(_ <- 1..24, do: build(:imported_visitors, date: ~D[2020-01-01])) ) - conn = - get( - conn, - "/api/stats/#{site.domain}/pages?period=day&date=2020-01-01&detailed=true&with_imported=true&order_by=#{Jason.encode!([["scroll_depth", "desc"]])}" + 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 json_response(conn, 200)["results"] == [ - %{ - "name" => "/native-and-imported", - "visitors" => 5, - "pageviews" => 5, - "bounce_rate" => 0, - "time_on_page" => 48, - "scroll_depth" => 50, - "percentage" => 20.0 - }, - %{ - "name" => "/native-only", - "visitors" => 1, - "pageviews" => 1, - "bounce_rate" => 0, - "time_on_page" => 60, - "scroll_depth" => 40, - "percentage" => 4.0 - }, - %{ - "name" => "/imported-only", - "visitors" => 20, - "pageviews" => 30, - "bounce_rate" => 0, - "time_on_page" => 30, - "scroll_depth" => 10, - "percentage" => 80.0 - } + 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 @@ -1035,22 +956,16 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do ) ]) - conn = - get( - conn, - "/api/stats/#{site.domain}/pages?period=7d&date=2020-01-02&detailed=true&with_imported=true" + response = + query_pages(conn, site, + date_range: "7d", + relative_date: "2020-01-02", + metrics: @detailed_metrics, + include: %{"imports" => true} ) - assert json_response(conn, 200)["results"] == [ - %{ - "name" => "/blog", - "visitors" => 110, - "pageviews" => 160, - "bounce_rate" => 0, - "time_on_page" => 60, - "scroll_depth" => 10, - "percentage" => nil - } + assert response["results"] == [ + %{"dimensions" => ["/blog"], "metrics" => [110, 160, 0, 60, 10, nil]} ] end @@ -1114,33 +1029,16 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do ) ]) - filters = Jason.encode!([[:is, "event:page", ["/", "/about"]]]) - - conn = - get( - conn, - "/api/stats/#{site.domain}/pages?period=day&date=2021-01-01&filters=#{filters}&detailed=true" + response = + query_pages(conn, site, + date_range: ["2021-01-01", "2021-01-01"], + filters: [["is", "event:page", ["/", "/about"]]], + metrics: @detailed_metrics ) - assert json_response(conn, 200)["results"] == [ - %{ - "name" => "/", - "visitors" => 2, - "pageviews" => 3, - "bounce_rate" => 50, - "time_on_page" => 75, - "scroll_depth" => 0, - "percentage" => 66.67 - }, - %{ - "name" => "/about", - "visitors" => 1, - "pageviews" => 1, - "bounce_rate" => 100, - "time_on_page" => 30, - "scroll_depth" => 0, - "percentage" => 33.33 - } + assert response["results"] == [ + %{"dimensions" => ["/"], "metrics" => [2, 3, 50, 75, 0, 66.67]}, + %{"dimensions" => ["/about"], "metrics" => [1, 1, 100, 30, 0, 33.33]} ] end @@ -1204,24 +1102,15 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do ) ]) - filters = Jason.encode!([[:is_not, "event:page", ["/irrelevant", "/about"]]]) - - conn = - get( - conn, - "/api/stats/#{site.domain}/pages?period=day&date=2021-01-01&filters=#{filters}&detailed=true" + response = + query_pages(conn, site, + date_range: ["2021-01-01", "2021-01-01"], + filters: [["is_not", "event:page", ["/irrelevant", "/about"]]], + metrics: @detailed_metrics ) - assert json_response(conn, 200)["results"] == [ - %{ - "name" => "/", - "visitors" => 2, - "pageviews" => 3, - "bounce_rate" => 50, - "time_on_page" => 75, - "scroll_depth" => 0, - "percentage" => 100.0 - } + assert response["results"] == [ + %{"dimensions" => ["/"], "metrics" => [2, 3, 50, 75, 0, 100.0]} ] end @@ -1285,42 +1174,17 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do ) ]) - filters = Jason.encode!([[:contains, "event:page", ["/blog/", "/articles/"]]]) - - conn = - get( - conn, - "/api/stats/#{site.domain}/pages?period=day&date=2021-01-01&filters=#{filters}&detailed=true" + response = + query_pages(conn, site, + date_range: ["2021-01-01", "2021-01-01"], + filters: [["contains", "event:page", ["/blog/", "/articles/"]]], + metrics: @detailed_metrics ) - assert json_response(conn, 200)["results"] == [ - %{ - "name" => "/articles/post-1", - "visitors" => 2, - "pageviews" => 2, - "bounce_rate" => 100, - "time_on_page" => 30, - "scroll_depth" => 0, - "percentage" => 66.67 - }, - %{ - "name" => "/blog/post-1", - "visitors" => 1, - "pageviews" => 1, - "bounce_rate" => 0, - "time_on_page" => 60, - "scroll_depth" => 0, - "percentage" => 33.33 - }, - %{ - "name" => "/blog/post-2", - "visitors" => 1, - "pageviews" => 1, - "bounce_rate" => 0, - "time_on_page" => 30, - "scroll_depth" => 0, - "percentage" => 33.33 - } + 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 @@ -1362,33 +1226,16 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do ) ]) - filters = Jason.encode!([[:contains, "event:page", ["/blog/(/", "/blog/)/"]]]) - - conn = - get( - conn, - "/api/stats/#{site.domain}/pages?period=day&date=2021-01-01&filters=#{filters}&detailed=true" + response = + query_pages(conn, site, + date_range: ["2021-01-01", "2021-01-01"], + filters: [["contains", "event:page", ["/blog/(/", "/blog/)/"]]], + metrics: @detailed_metrics ) - assert json_response(conn, 200)["results"] == [ - %{ - "name" => "/blog/(/post-1", - "visitors" => 1, - "pageviews" => 1, - "bounce_rate" => 0, - "time_on_page" => 60, - "scroll_depth" => 0, - "percentage" => 100.0 - }, - %{ - "name" => "/blog/(/post-2", - "visitors" => 1, - "pageviews" => 1, - "bounce_rate" => 0, - "time_on_page" => 30, - "scroll_depth" => 0, - "percentage" => 100.0 - } + 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 @@ -1452,33 +1299,16 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do ) ]) - filters = Jason.encode!([[:contains_not, "event:page", ["/blog/", "/articles/"]]]) - - conn = - get( - conn, - "/api/stats/#{site.domain}/pages?period=day&date=2021-01-01&filters=#{filters}&detailed=true" + response = + query_pages(conn, site, + date_range: ["2021-01-01", "2021-01-01"], + filters: [["contains_not", "event:page", ["/blog/", "/articles/"]]], + metrics: @detailed_metrics ) - assert json_response(conn, 200)["results"] == [ - %{ - "name" => "/", - "visitors" => 2, - "pageviews" => 2, - "bounce_rate" => 50, - "time_on_page" => 600, - "scroll_depth" => 0, - "percentage" => 100.0 - }, - %{ - "name" => "/about", - "visitors" => 1, - "pageviews" => 1, - "bounce_rate" => 0, - "time_on_page" => 30, - "scroll_depth" => 0, - "percentage" => 50.0 - } + assert response["results"] == [ + %{"dimensions" => ["/"], "metrics" => [2, 2, 50, 600, 0, 100.0]}, + %{"dimensions" => ["/about"], "metrics" => [1, 1, 0, 30, 0, 50.0]} ] end @@ -1494,28 +1324,30 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do build(:pageview, pathname: "/contact") ]) - conn1 = get(conn, "/api/stats/#{site.domain}/pages?period=day") + response = query_pages(conn, site, date_range: "day") - assert json_response(conn1, 200)["results"] == [ - %{"visitors" => 3, "name" => "/", "percentage" => 50.0}, - %{"visitors" => 2, "name" => "/register", "percentage" => 33.33}, - %{"visitors" => 1, "name" => "/contact", "percentage" => 16.67} + assert response["results"] == [ + %{"dimensions" => ["/"], "metrics" => [3, 50.0]}, + %{"dimensions" => ["/register"], "metrics" => [2, 33.33]}, + %{"dimensions" => ["/contact"], "metrics" => [1, 16.67]} ] - conn2 = get(conn, "/api/stats/#{site.domain}/pages?period=day&with_imported=true") + response = query_pages(conn, site, date_range: "day", include: %{"imports" => true}) - assert json_response(conn2, 200)["results"] == [ - %{"visitors" => 4, "name" => "/", "percentage" => 66.67}, - %{"visitors" => 3, "name" => "/register", "percentage" => 50.0}, - %{"visitors" => 1, "name" => "/contact", "percentage" => 16.67} + 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 - conn = - get(conn, "/api/stats/#{site.domain}/pages?period=day&detailed=true&with_imported=true") - - response = json_response(conn, 200) + 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" @@ -1531,23 +1363,17 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do build(:imported_visitors, visitors: 4) ]) - filters = Jason.encode!([[:is, "event:goal", ["Visit /blog**"]]]) - q = "?period=day&filters=#{filters}&with_imported=true" - conn = get(conn, "/api/stats/#{site.domain}/pages#{q}") + response = + query_pages(conn, site, + date_range: "day", + filters: [["is", "event:goal", ["Visit /blog**"]]], + metrics: @goal_filter_metrics, + include: %{"imports" => true} + ) - assert json_response(conn, 200)["results"] == [ - %{ - "visitors" => 2, - "name" => "/blog/post-1", - "conversion_rate" => 100.0, - "total_visitors" => 2 - }, - %{ - "visitors" => 1, - "name" => "/blog", - "conversion_rate" => 100.0, - "total_visitors" => 1 - } + assert response["results"] == [ + %{"dimensions" => ["/blog/post-1"], "metrics" => [2, 100.0, 2]}, + %{"dimensions" => ["/blog"], "metrics" => [1, 100.0, 1]} ] end @@ -1588,31 +1414,15 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do ) ]) - conn = - get( - conn, - "/api/stats/#{site.domain}/pages?period=day&date=2021-01-01&detailed=true" + response = + query_pages(conn, site, + date_range: ["2021-01-01", "2021-01-01"], + metrics: @detailed_metrics ) - assert json_response(conn, 200)["results"] == [ - %{ - "bounce_rate" => 50.0, - "time_on_page" => 465.0, - "visitors" => 2, - "pageviews" => 2, - "name" => "/", - "scroll_depth" => 0, - "percentage" => 100.0 - }, - %{ - "bounce_rate" => 0, - "time_on_page" => 30, - "visitors" => 1, - "pageviews" => 1, - "name" => "/some-other-page", - "scroll_depth" => 0, - "percentage" => 50.0 - } + 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 @@ -1637,24 +1447,15 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do ) ]) - filters = Jason.encode!([[:is, "event:hostname", ["blog.example.com"]]]) - - conn = - get( - conn, - "/api/stats/#{site.domain}/pages?period=day&date=2021-01-01&detailed=true&filters=#{filters}" + response = + query_pages(conn, site, + date_range: ["2021-01-01", "2021-01-01"], + filters: [["is", "event:hostname", ["blog.example.com"]]], + metrics: @detailed_metrics ) - assert json_response(conn, 200)["results"] == [ - %{ - "bounce_rate" => 50, - "name" => "/about", - "pageviews" => 2, - "time_on_page" => nil, - "visitors" => 2, - "scroll_depth" => nil, - "percentage" => 100.0 - } + assert response["results"] == [ + %{"dimensions" => ["/about"], "metrics" => [2, 2, 50, nil, nil, 100.0]} ] end @@ -1774,33 +1575,16 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do ) ]) - filters = Jason.encode!([[:is, "event:hostname", ["blog.example.com"]]]) - - conn = - get( - conn, - "/api/stats/#{site.domain}/pages?period=day&date=2021-01-01&detailed=true&filters=#{filters}" + response = + query_pages(conn, site, + date_range: ["2021-01-01", "2021-01-01"], + filters: [["is", "event:hostname", ["blog.example.com"]]], + metrics: @detailed_metrics ) - assert json_response(conn, 200)["results"] == [ - %{ - "bounce_rate" => 50, - "name" => "/about-blog", - "pageviews" => 3, - "time_on_page" => 435, - "visitors" => 2, - "scroll_depth" => 0, - "percentage" => 100.0 - }, - %{ - "bounce_rate" => 0, - "name" => "/exit-blog", - "pageviews" => 1, - "time_on_page" => 120, - "visitors" => 1, - "scroll_depth" => 0, - "percentage" => 50.0 - } + 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 @@ -1869,31 +1653,16 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do build(:imported_visitors, date: ~D[2021-01-01]) ]) - conn = - get( - conn, - "/api/stats/#{site.domain}/pages?period=day&date=2021-01-01&detailed=true&with_imported=true" + response = + query_pages(conn, site, + date_range: ["2021-01-01", "2021-01-01"], + metrics: @detailed_metrics, + include: %{"imports" => true} ) - assert json_response(conn, 200)["results"] == [ - %{ - "bounce_rate" => 40.0, - "time_on_page" => 500, - "visitors" => 3, - "pageviews" => 3, - "scroll_depth" => 0, - "name" => "/", - "percentage" => 60.0 - }, - %{ - "bounce_rate" => 0, - "time_on_page" => 45, - "visitors" => 2, - "pageviews" => 2, - "scroll_depth" => 0, - "name" => "/some-other-page", - "percentage" => 40.0 - } + 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 @@ -1904,11 +1673,11 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do build(:pageview, pathname: "/page1") ]) - conn = get(conn, "/api/stats/#{site.domain}/pages?period=realtime") + response = query_pages(conn, site, date_range: "realtime") - assert json_response(conn, 200)["results"] == [ - %{"visitors" => 2, "name" => "/page1", "percentage" => 66.67}, - %{"visitors" => 1, "name" => "/page2", "percentage" => 33.33} + assert response["results"] == [ + %{"dimensions" => ["/page1"], "metrics" => [2, 66.67]}, + %{"dimensions" => ["/page2"], "metrics" => [1, 33.33]} ] end @@ -1921,17 +1690,16 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do ]) insert(:goal, site: site, event_name: "Signup") - filters = Jason.encode!([[:is, "event:goal", ["Signup"]]]) - conn = get(conn, "/api/stats/#{site.domain}/pages?period=day&filters=#{filters}") + response = + query_pages(conn, site, + date_range: "day", + filters: [["is", "event:goal", ["Signup"]]], + metrics: @goal_filter_metrics + ) - assert json_response(conn, 200)["results"] == [ - %{ - "total_visitors" => 3, - "visitors" => 1, - "name" => "/", - "conversion_rate" => 33.33 - } + assert response["results"] == [ + %{"dimensions" => ["/"], "metrics" => [1, 33.33, 3]} ] end @@ -1970,21 +1738,16 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do build(:imported_pages, page: "/ignored", visitors: 10, date: ~D[2021-01-01]) ]) - filters = Jason.encode!([[:is, "event:page", ["/"]]]) - q = "?period=day&date=2021-01-01&filters=#{filters}&detailed=true&with_imported=true" - - conn = get(conn, "/api/stats/#{site.domain}/pages#{q}") + response = + query_pages(conn, site, + date_range: ["2021-01-01", "2021-01-01"], + filters: [["is", "event:page", ["/"]]], + metrics: @detailed_metrics, + include: %{"imports" => true} + ) - assert json_response(conn, 200)["results"] == [ - %{ - "bounce_rate" => 50, - "name" => "/", - "pageviews" => 4, - "time_on_page" => 90.0, - "visitors" => 4, - "scroll_depth" => 0, - "percentage" => 100.0 - } + assert response["results"] == [ + %{"dimensions" => ["/"], "metrics" => [4, 4, 50, 90.0, 0, 100.0]} ] end @@ -2034,30 +1797,17 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do build(:imported_pages, page: "/ignored", visitors: 10, date: ~D[2021-01-01]) ]) - filters = Jason.encode!([[:is, "event:page", ["/", "/a"]]]) - q = "?period=day&date=2021-01-01&filters=#{filters}&detailed=true&with_imported=true" - - conn = get(conn, "/api/stats/#{site.domain}/pages#{q}") + 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 json_response(conn, 200)["results"] == [ - %{ - "bounce_rate" => 50, - "name" => "/", - "pageviews" => 4, - "time_on_page" => 90.0, - "visitors" => 4, - "scroll_depth" => 0, - "percentage" => 80.0 - }, - %{ - "bounce_rate" => 100, - "name" => "/a", - "pageviews" => 1, - "time_on_page" => 10.0, - "visitors" => 1, - "scroll_depth" => nil, - "percentage" => 20.0 - } + 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 @@ -2107,30 +1857,17 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do build(:imported_pages, page: "/ignored", visitors: 10, date: ~D[2021-01-01]) ]) - filters = Jason.encode!([[:contains, "event:page", ["/a"]]]) - q = "?period=day&date=2021-01-01&filters=#{filters}&detailed=true&with_imported=true" - - conn = get(conn, "/api/stats/#{site.domain}/pages#{q}") + 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 json_response(conn, 200)["results"] == [ - %{ - "bounce_rate" => 50.0, - "name" => "/aaa", - "pageviews" => 4, - "time_on_page" => 90, - "visitors" => 4, - "scroll_depth" => 0, - "percentage" => 80.0 - }, - %{ - "bounce_rate" => 100.0, - "name" => "/a", - "pageviews" => 1, - "time_on_page" => 10, - "visitors" => 1, - "scroll_depth" => nil, - "percentage" => 20.0 - } + 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 @@ -2154,61 +1891,30 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do ) ]) - conn = - get( - conn, - "/api/stats/#{site.domain}/pages?period=day&date=2021-01-02&comparison=previous_period&detailed=true" + response = + query_pages(conn, site, + date_range: ["2021-01-02", "2021-01-02"], + metrics: @detailed_metrics, + include: %{"compare" => "previous_period"} ) - assert json_response(conn, 200)["results"] == [ + assert response["results"] == [ %{ - "bounce_rate" => 100, + "dimensions" => ["/page2"], + "metrics" => [2, 2, 100, nil, nil, 66.67], "comparison" => %{ - "bounce_rate" => 0.0, - "pageviews" => 0, - "time_on_page" => nil, - "visitors" => 0, - "scroll_depth" => nil, - "percentage" => 0.0, - "change" => %{ - "bounce_rate" => nil, - "pageviews" => 100, - "time_on_page" => nil, - "visitors" => 100, - "scroll_depth" => nil, - "percentage" => 100 - } - }, - "name" => "/page2", - "pageviews" => 2, - "time_on_page" => nil, - "visitors" => 2, - "scroll_depth" => nil, - "percentage" => 66.67 + "dimensions" => ["/page2"], + "metrics" => [0, 0, 0.0, nil, nil, 0.0], + "change" => [100, 100, nil, nil, nil, 100] + } }, %{ - "bounce_rate" => 100, - "name" => "/page1", - "pageviews" => 1, - "time_on_page" => nil, - "visitors" => 1, - "scroll_depth" => nil, - "percentage" => 33.33, + "dimensions" => ["/page1"], + "metrics" => [1, 1, 100, nil, nil, 33.33], "comparison" => %{ - "bounce_rate" => 100, - "pageviews" => 1, - "time_on_page" => nil, - "visitors" => 1, - "scroll_depth" => nil, - "percentage" => 100.0, - "change" => %{ - "bounce_rate" => 0, - "pageviews" => 0, - "time_on_page" => nil, - "visitors" => 0, - "scroll_depth" => nil, - "percentage" => -67 - } + "dimensions" => ["/page1"], + "metrics" => [1, 1, 100, nil, nil, 100.0], + "change" => [0, 0, 0, nil, nil, -67] } } ] @@ -2231,40 +1937,16 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do build(:pageview, pathname: "/b1", timestamp: ~N[2021-01-01 00:00:00]) ]) - conn = - get( - conn, - "/api/stats/#{cv.domain}/pages?period=day&date=2021-01-01&detailed=true" + response = + query_pages(conn, cv, + date_range: ["2021-01-01", "2021-01-01"], + metrics: @detailed_metrics ) - assert json_response(conn, 200)["results"] == [ - %{ - "bounce_rate" => 100, - "name" => "/b1", - "pageviews" => 3, - "time_on_page" => nil, - "visitors" => 3, - "scroll_depth" => nil, - "percentage" => 50.0 - }, - %{ - "bounce_rate" => 100, - "name" => "/a2", - "pageviews" => 2, - "time_on_page" => nil, - "visitors" => 2, - "scroll_depth" => nil, - "percentage" => 33.33 - }, - %{ - "bounce_rate" => 100, - "name" => "/a1", - "pageviews" => 1, - "time_on_page" => nil, - "visitors" => 1, - "scroll_depth" => nil, - "percentage" => 16.67 - } + 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 @@ -2303,25 +1985,16 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do ) ]) - conn = get(conn, "/api/stats/#{site.domain}/entry-pages?period=day&date=2021-01-01") + 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 json_response(conn, 200)["results"] == [ - %{ - "visitors" => 2, - "visits" => 2, - "name" => "/page1", - "visit_duration" => 0, - "bounce_rate" => 100, - "percentage" => 66.67 - }, - %{ - "visitors" => 1, - "visits" => 2, - "name" => "/page2", - "visit_duration" => 450, - "bounce_rate" => 50, - "percentage" => 33.33 - } + assert response["results"] == [ + %{"dimensions" => ["/page1"], "metrics" => [2, 2, 100, 0, 66.67]}, + %{"dimensions" => ["/page2"], "metrics" => [1, 2, 50, 450, 33.33]} ] end @@ -2357,31 +2030,17 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do ) ]) - filters = Jason.encode!([[:is, "event:props:author", ["John Doe"]]]) - - conn = - get( - conn, - "/api/stats/#{site.domain}/entry-pages?period=day&date=2021-01-01&filters=#{filters}" + 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 json_response(conn, 200)["results"] == [ - %{ - "visitors" => 1, - "visits" => 1, - "name" => "/blog", - "visit_duration" => 60, - "bounce_rate" => 0, - "percentage" => 50.0 - }, - %{ - "visitors" => 1, - "visits" => 1, - "name" => "/blog/john-2", - "visit_duration" => 0, - "bounce_rate" => 100, - "percentage" => 50.0 - } + assert response["results"] == [ + %{"dimensions" => ["/blog"], "metrics" => [1, 1, 0, 60, 50.0]}, + %{"dimensions" => ["/blog/john-2"], "metrics" => [1, 1, 100, 0, 50.0]} ] end @@ -2422,50 +2081,29 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do ) ]) - conn1 = get(conn, "/api/stats/#{site.domain}/entry-pages?period=day&date=2021-01-01") + 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 json_response(conn1, 200)["results"] == [ - %{ - "visitors" => 2, - "visits" => 2, - "name" => "/page1", - "visit_duration" => 0, - "bounce_rate" => 100, - "percentage" => 66.67 - }, - %{ - "visitors" => 1, - "visits" => 2, - "name" => "/page2", - "visit_duration" => 450, - "bounce_rate" => 50, - "percentage" => 33.33 - } + assert response["results"] == [ + %{"dimensions" => ["/page1"], "metrics" => [2, 2, 100, 0, 66.67]}, + %{"dimensions" => ["/page2"], "metrics" => [1, 2, 50, 450, 33.33]} ] - conn2 = - get( - conn, - "/api/stats/#{site.domain}/entry-pages?period=day&date=2021-01-01&with_imported=true" + 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 json_response(conn2, 200)["results"] == [ - %{ - "visitors" => 3, - "visits" => 5, - "name" => "/page2", - "visit_duration" => 240.0, - "bounce_rate" => 20.0, - "percentage" => 60.0 - }, - %{ - "visitors" => 2, - "visits" => 2, - "name" => "/page1", - "visit_duration" => 0.0, - "bounce_rate" => 100.0, - "percentage" => 40.0 - } + 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 @@ -2507,32 +2145,18 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do ) ]) - filters = Jason.encode!([[:is, "event:hostname", ["es.example.com"]]]) - - conn = - get( - conn, - "/api/stats/#{site.domain}/entry-pages?period=day&date=2021-01-01&filters=#{filters}" + # 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"] ) - # We're going to only join sessions where the exit hostname matches the filter - assert json_response(conn, 200)["results"] == [ - %{ - "name" => "/page1", - "visit_duration" => 0, - "visitors" => 1, - "visits" => 1, - "bounce_rate" => 100, - "percentage" => 50.0 - }, - %{ - "name" => "/page2", - "visit_duration" => 0, - "visitors" => 1, - "visits" => 1, - "bounce_rate" => 100, - "percentage" => 50.0 - } + assert response["results"] == [ + %{"dimensions" => ["/page1"], "metrics" => [1, 1, 100, 0, 50.0]}, + %{"dimensions" => ["/page2"], "metrics" => [1, 1, 100, 0, 50.0]} ] end @@ -2552,31 +2176,32 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do insert(:goal, site: site, event_name: "Signup") request = fn conn, opts -> - page = Keyword.fetch!(opts, :page) - limit = Keyword.fetch!(opts, :limit) - filters = Jason.encode!([[:is, "event:goal", ["Signup"]]]) - conn - |> get( - "/api/stats/#{site.domain}/pages?date=2021-01-01&period=day&filters=#{filters}&limit=#{limit}&page=#{page}" + |> 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"]] ) - |> json_response(200) |> Map.get("results") - |> Enum.map(fn %{"name" => "/signup/" <> seq} -> + |> Enum.map(fn %{"dimensions" => ["/signup/" <> seq]} -> seq end) end - assert List.first(request.(conn, page: 1, limit: 100)) == "01" - assert List.last(request.(conn, page: 1, limit: 100)) == "30" - assert List.last(request.(conn, page: 1, limit: 29)) == "29" - assert ["01", "02"] = request.(conn, page: 1, limit: 2) - assert ["03", "04"] = request.(conn, page: 2, limit: 2) - assert ["01", "02", "03", "04", "05"] = request.(conn, page: 1, limit: 5) - assert ["06", "07", "08", "09", "10"] = request.(conn, page: 2, limit: 5) - assert ["11", "12", "13", "14", "15"] = request.(conn, page: 3, limit: 5) - assert ["20"] = request.(conn, page: 20, limit: 1) - assert [] = request.(conn, page: 31, limit: 1) + 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 @@ -2614,27 +2239,18 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do ]) insert(:goal, site: site, event_name: "Signup") - filters = Jason.encode!([[:is, "event:goal", ["Signup"]]]) - conn = - get( - conn, - "/api/stats/#{site.domain}/entry-pages?period=day&date=2021-01-01&filters=#{filters}" + response = + query_pages(conn, site, + date_range: ["2021-01-01", "2021-01-01"], + dimensions: ["visit:entry_page"], + filters: [["is", "event:goal", ["Signup"]]], + metrics: ["visitors", "conversion_rate", "total_visitors"] ) - assert json_response(conn, 200)["results"] == [ - %{ - "total_visitors" => 2, - "visitors" => 1, - "name" => "/page1", - "conversion_rate" => 50.0 - }, - %{ - "total_visitors" => 1, - "visitors" => 1, - "name" => "/page2", - "conversion_rate" => 100.0 - } + assert response["results"] == [ + %{"dimensions" => ["/page1"], "metrics" => [1, 50.0, 2]}, + %{"dimensions" => ["/page2"], "metrics" => [1, 100.0, 1]} ] end @@ -2647,13 +2263,14 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do ) ]) - conn = - get( - conn, - "/api/stats/#{site.domain}/entry-pages?period=day&date=2021-01-01" + 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 json_response(conn, 200)["results"] == [] + assert response["results"] == [] end test "filter by :matches_member entry_page with imported data", %{conn: conn, site: site} do @@ -2679,36 +2296,19 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do ) ]) - filters = Jason.encode!([[:contains, "visit:entry_page", ["/a", "/b"]]]) - q = "?period=day&date=2021-01-01&filters=#{filters}&detailed=true&with_imported=true" - - conn = get(conn, "/api/stats/#{site.domain}/entry-pages#{q}") + 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 json_response(conn, 200)["results"] == [ - %{ - "visit_duration" => 100.0, - "name" => "/a", - "visits" => 10, - "visitors" => 6, - "bounce_rate" => 10.0, - "percentage" => 66.67 - }, - %{ - "visit_duration" => 50.0, - "name" => "/bbb", - "visits" => 2, - "visitors" => 2, - "bounce_rate" => 0.0, - "percentage" => 22.22 - }, - %{ - "visit_duration" => 0, - "name" => "/aaa", - "visits" => 1, - "visitors" => 1, - "bounce_rate" => 100.0, - "percentage" => 11.11 - } + 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 end @@ -2738,23 +2338,16 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do ) ]) - conn = get(conn, "/api/stats/#{site.domain}/exit-pages?period=day&date=2021-01-01") + response = + query_pages(conn, site, + date_range: ["2021-01-01", "2021-01-01"], + dimensions: ["visit:exit_page"], + metrics: ["visitors", "visits", "exit_rate", "percentage"] + ) - assert json_response(conn, 200)["results"] == [ - %{ - "name" => "/page1", - "visitors" => 2, - "visits" => 2, - "exit_rate" => 66.7, - "percentage" => 66.67 - }, - %{ - "name" => "/page2", - "visitors" => 1, - "visits" => 1, - "exit_rate" => 100, - "percentage" => 33.33 - } + assert response["results"] == [ + %{"dimensions" => ["/page1"], "metrics" => [2, 2, 66.7, 66.67]}, + %{"dimensions" => ["/page2"], "metrics" => [1, 1, 100, 33.33]} ] end @@ -2780,27 +2373,17 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do ) ]) - conn = - get( - conn, - "/api/stats/#{site.domain}/exit-pages?period=day&date=2021-01-01&order_by=#{Jason.encode!([["visits", "asc"]])}" + 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 json_response(conn, 200)["results"] == [ - %{ - "name" => "/page2", - "visitors" => 1, - "visits" => 1, - "exit_rate" => 100.0, - "percentage" => 33.33 - }, - %{ - "name" => "/page1", - "visitors" => 2, - "visits" => 2, - "exit_rate" => 66.7, - "percentage" => 66.67 - } + assert response["results"] == [ + %{"dimensions" => ["/page2"], "metrics" => [1, 1, 100.0, 33.33]}, + %{"dimensions" => ["/page1"], "metrics" => [2, 2, 66.7, 66.67]} ] end @@ -2837,17 +2420,18 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do ) ]) - filters = Jason.encode!([[:is, "event:hostname", ["es.example.com"]]]) - - conn = - get( - conn, - "/api/stats/#{site.domain}/exit-pages?period=day&date=2021-01-01&filters=#{filters}" + 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 json_response(conn, 200)["results"] == - [%{"name" => "/page1", "visitors" => 1, "visits" => 1, "percentage" => 100.0}] + 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 @@ -2876,16 +2460,16 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do ) ]) - filters = Jason.encode!([[:is, "event:props:author", ["John Doe"]]]) - - conn = - get( - conn, - "/api/stats/#{site.domain}/exit-pages?period=day&date=2021-01-01&filters=#{filters}" + 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 json_response(conn, 200)["results"] == [ - %{"name" => "/", "visitors" => 1, "visits" => 1, "percentage" => 100.0} + assert response["results"] == [ + %{"dimensions" => ["/"], "metrics" => [1, 1, 100.0]} ] end @@ -2926,50 +2510,33 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do ) ]) - conn1 = get(conn, "/api/stats/#{site.domain}/exit-pages?period=day&date=2021-01-01") + response = + query_pages(conn, site, + date_range: ["2021-01-01", "2021-01-01"], + dimensions: ["visit:exit_page"], + metrics: ["visitors", "visits", "exit_rate", "percentage"] + ) - assert json_response(conn1, 200)["results"] == [ - %{ - "name" => "/page1", - "visitors" => 2, - "visits" => 2, - "exit_rate" => 66.7, - "percentage" => 66.67 - }, - %{ - "name" => "/page2", - "visitors" => 1, - "visits" => 1, - "exit_rate" => 100.0, - "percentage" => 33.33 - } + assert response["results"] == [ + %{"dimensions" => ["/page1"], "metrics" => [2, 2, 66.7, 66.67]}, + %{"dimensions" => ["/page2"], "metrics" => [1, 1, 100.0, 33.33]} ] - conn2 = - get( - conn, - "/api/stats/#{site.domain}/exit-pages?period=day&date=2021-01-01&with_imported=true" + response = + query_pages(conn, site, + date_range: ["2021-01-01", "2021-01-01"], + dimensions: ["visit:exit_page"], + metrics: ["visitors", "visits", "exit_rate", "percentage"], + include: %{"imports" => true} ) - assert json_response(conn2, 200)["results"] == [ - %{ - "name" => "/page2", - "visitors" => 3, - "visits" => 4, - "exit_rate" => 80.0, - "percentage" => 60.0 - }, - %{ - "name" => "/page1", - "visitors" => 2, - "visits" => 2, - "exit_rate" => 66.7, - "percentage" => 40.0 - } + assert response["results"] == [ + %{"dimensions" => ["/page2"], "metrics" => [3, 4, 80.0, 60.0]}, + %{"dimensions" => ["/page1"], "metrics" => [2, 2, 66.7, 40.0]} ] end - test "calculates correct exit rate and conversion_rate when filtering for goal", %{ + test "returns top exit pages when filtering for goal", %{ conn: conn, site: site } do @@ -3002,31 +2569,22 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do ]) insert(:goal, site: site, event_name: "Signup") - filters = Jason.encode!([[:is, "event:goal", ["Signup"]]]) - conn = - get( - conn, - "/api/stats/#{site.domain}/exit-pages?period=day&date=2021-01-01&filters=#{filters}" + response = + query_pages(conn, site, + date_range: ["2021-01-01", "2021-01-01"], + dimensions: ["visit:exit_page"], + filters: [["is", "event:goal", ["Signup"]]], + metrics: ["visitors", "conversion_rate", "total_visitors"] ) - assert json_response(conn, 200)["results"] == [ - %{ - "name" => "/exit1", - "visitors" => 1, - "total_visitors" => 1, - "conversion_rate" => 100.0 - }, - %{ - "name" => "/exit2", - "visitors" => 1, - "total_visitors" => 1, - "conversion_rate" => 100.0 - } + assert response["results"] == [ + %{"dimensions" => ["/exit1"], "metrics" => [1, 100.0, 1]}, + %{"dimensions" => ["/exit2"], "metrics" => [1, 100.0, 1]} ] end - test "calculates correct exit rate when filtering for page", %{conn: conn, site: site} do + test "returns top exit pages when filtering for page", %{conn: conn, site: site} do populate_stats(site, [ build(:pageview, user_id: 1, @@ -3055,17 +2613,17 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do ) ]) - filters = Jason.encode!([[:is, "event:page", ["/exit1"]]]) - - conn = - get( - conn, - "/api/stats/#{site.domain}/exit-pages?period=day&date=2021-01-01&filters=#{filters}" + 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 json_response(conn, 200)["results"] == [ - %{"name" => "/exit1", "visitors" => 1, "visits" => 1, "percentage" => 50.0}, - %{"name" => "/exit2", "visitors" => 1, "visits" => 1, "percentage" => 50.0} + assert response["results"] == [ + %{"dimensions" => ["/exit1"], "metrics" => [1, 1, 50.0]}, + %{"dimensions" => ["/exit2"], "metrics" => [1, 1, 50.0]} ] end @@ -3078,13 +2636,14 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do ) ]) - conn = - get( - conn, - "/api/stats/#{site.domain}/exit-pages?period=day&date=2021-01-01" + response = + query_pages(conn, site, + date_range: ["2021-01-01", "2021-01-01"], + dimensions: ["visit:exit_page"], + metrics: ["visitors", "visits", "exit_rate", "percentage"] ) - assert json_response(conn, 200)["results"] == [] + assert response["results"] == [] end test "filter by :is_not exit_page with imported data", %{conn: conn, site: site} do @@ -3112,33 +2671,19 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do build(:imported_pages, page: "/bbb", pageviews: 2, date: ~D[2021-01-01]) ]) - filters = Jason.encode!([[:is_not, "visit:exit_page", ["/ignored"]]]) - q = "?period=day&date=2021-01-01&filters=#{filters}&detailed=true&with_imported=true" - - conn = get(conn, "/api/stats/#{site.domain}/exit-pages#{q}") + 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 json_response(conn, 200)["results"] == [ - %{ - "exit_rate" => 50.0, - "name" => "/a", - "visits" => 10, - "visitors" => 6, - "percentage" => 66.67 - }, - %{ - "exit_rate" => 100.0, - "name" => "/bbb", - "visits" => 2, - "visitors" => 2, - "percentage" => 22.22 - }, - %{ - "exit_rate" => 100.0, - "name" => "/aaa", - "visits" => 1, - "visitors" => 1, - "percentage" => 11.11 - } + 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 @@ -3185,71 +2730,80 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do insert(:goal, %{site: site, event_name: "Payment", currency: :USD}) - filters = Jason.encode!([[:is, "event:goal", ["Payment"]]]) - order_by = Jason.encode!([["visitors", "desc"]]) - - q = "?filters=#{filters}&order_by=#{order_by}&detailed=true&period=day&page=1&limit=100" - - conn = - get( - conn, - "/api/stats/#{site.domain}/entry-pages#{q}" - ) - - assert json_response(conn, 200)["results"] == [ - %{ - "average_revenue" => %{ - "currency" => "USD", - "long" => "$1,500.00", - "short" => "$1.5K", - "value" => 1500.0 - }, - "conversion_rate" => 100.0, - "name" => "/first", - "total_revenue" => %{ - "currency" => "USD", - "long" => "$3,000.00", - "short" => "$3.0K", - "value" => 3000.0 - }, - "total_visitors" => 2, - "visitors" => 2 + response = + query_pages(conn, site, + date_range: "day", + dimensions: ["visit:entry_page"], + filters: [["is", "event:goal", ["Payment"]]], + metrics: [ + "visitors", + "conversion_rate", + "total_visitors", + "average_revenue", + "total_revenue" + ] + ) + + 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 + } + ] }, %{ - "average_revenue" => %{ - "currency" => "USD", - "long" => "$3,500.00", - "short" => "$3.5K", - "value" => 3500.0 - }, - "conversion_rate" => 100.0, - "name" => "/second", - "total_revenue" => %{ - "currency" => "USD", - "long" => "$7,000.00", - "short" => "$7.0K", - "value" => 7000.0 - }, - "total_visitors" => 1, - "visitors" => 1 + "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 + } + ] }, %{ - "average_revenue" => %{ - "currency" => "USD", - "long" => "$2,500.00", - "short" => "$2.5K", - "value" => 2500.0 - }, - "conversion_rate" => 100.0, - "name" => "/third", - "total_revenue" => %{ - "currency" => "USD", - "long" => "$2,500.00", - "short" => "$2.5K", - "value" => 2500.0 - }, - "total_visitors" => 1, - "visitors" => 1 + "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 @@ -3300,71 +2854,80 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do insert(:goal, %{site: site, event_name: "Payment", currency: :USD}) - filters = Jason.encode!([[:is, "event:goal", ["Payment"]]]) - order_by = Jason.encode!([["visitors", "desc"]]) - - q = "?filters=#{filters}&order_by=#{order_by}&detailed=true&period=day&page=1&limit=100" - - conn = - get( - conn, - "/api/stats/#{site.domain}/exit-pages#{q}" - ) - - assert json_response(conn, 200)["results"] == [ - %{ - "average_revenue" => %{ - "currency" => "USD", - "long" => "$1,500.00", - "short" => "$1.5K", - "value" => 1500.0 - }, - "conversion_rate" => 100.0, - "name" => "/exit_first", - "total_revenue" => %{ - "currency" => "USD", - "long" => "$3,000.00", - "short" => "$3.0K", - "value" => 3000.0 - }, - "total_visitors" => 2, - "visitors" => 2 + response = + query_pages(conn, site, + date_range: "day", + dimensions: ["visit:exit_page"], + filters: [["is", "event:goal", ["Payment"]]], + metrics: [ + "visitors", + "conversion_rate", + "total_visitors", + "average_revenue", + "total_revenue" + ] + ) + + 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 + } + ] }, %{ - "average_revenue" => %{ - "currency" => "USD", - "long" => "$3,500.00", - "short" => "$3.5K", - "value" => 3500.0 - }, - "conversion_rate" => 100.0, - "name" => "/exit_second", - "total_revenue" => %{ - "currency" => "USD", - "long" => "$7,000.00", - "short" => "$7.0K", - "value" => 7000.0 - }, - "total_visitors" => 1, - "visitors" => 1 + "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 + } + ] }, %{ - "average_revenue" => %{ - "currency" => "USD", - "long" => "$2,500.00", - "short" => "$2.5K", - "value" => 2500.0 - }, - "conversion_rate" => 100.0, - "name" => "/third", - "total_revenue" => %{ - "currency" => "USD", - "long" => "$2,500.00", - "short" => "$2.5K", - "value" => 2500.0 - }, - "total_visitors" => 1, - "visitors" => 1 + "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 @@ -3421,89 +2984,89 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do insert(:goal, %{site: site, event_name: "Payment", currency: :USD}) - filters = Jason.encode!([[:is, "event:goal", ["Payment"]]]) - order_by = Jason.encode!([["visitors", "desc"]]) - - q = "?filters=#{filters}&order_by=#{order_by}&detailed=true&period=day&page=1&limit=100" - - conn = - get( - conn, - "/api/stats/#{site.domain}/pages#{q}" - ) - - assert json_response(conn, 200)["results"] == [ - %{ - "average_revenue" => %{ - "currency" => "USD", - "long" => "$0.00", - "short" => "$0.0", - "value" => 0.0 - }, - "conversion_rate" => 100.0, - "name" => "/nopay", - "total_revenue" => %{ - "currency" => "USD", - "long" => "$0.00", - "short" => "$0.0", - "value" => 0.0 - }, - "total_visitors" => 3, - "visitors" => 3 + response = + query_pages(conn, site, + date_range: "day", + filters: [["is", "event:goal", ["Payment"]]], + metrics: [ + "visitors", + "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} + ] }, %{ - "average_revenue" => %{ - "currency" => "USD", - "long" => "$1,500.00", - "short" => "$1.5K", - "value" => 1500.0 - }, - "conversion_rate" => 100.0, - "name" => "/purchase/first", - "total_revenue" => %{ - "currency" => "USD", - "long" => "$3,000.00", - "short" => "$3.0K", - "value" => 3000.0 - }, - "total_visitors" => 2, - "visitors" => 2 + "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 + } + ] }, %{ - "average_revenue" => %{ - "currency" => "USD", - "long" => "$3,500.00", - "short" => "$3.5K", - "value" => 3500.0 - }, - "conversion_rate" => 100.0, - "name" => "/purchase/second", - "total_revenue" => %{ - "currency" => "USD", - "long" => "$7,000.00", - "short" => "$7.0K", - "value" => 7000.0 - }, - "total_visitors" => 1, - "visitors" => 1 + "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 + } + ] }, %{ - "average_revenue" => %{ - "currency" => "USD", - "long" => "$2,500.00", - "short" => "$2.5K", - "value" => 2500.0 - }, - "conversion_rate" => 100.0, - "name" => "/purchase/third", - "total_revenue" => %{ - "currency" => "USD", - "long" => "$2,500.00", - "short" => "$2.5K", - "value" => 2500.0 - }, - "total_visitors" => 1, - "visitors" => 1 + "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 From 8b4adc1e602ecaca38e74a1bbfa33a32de3fbddb Mon Sep 17 00:00:00 2001 From: Robert Joonas Date: Thu, 19 Mar 2026 16:15:37 +0000 Subject: [PATCH 2/3] make total_visitors an official metric --- lib/plausible/stats/metrics.ex | 1 + .../stats/query/query_parse_and_build_test.exs | 13 +++++++++++++ 2 files changed, 14 insertions(+) diff --git a/lib/plausible/stats/metrics.ex b/lib/plausible/stats/metrics.ex index c4673ae73818..3c4a7931a5c8 100644 --- a/lib/plausible/stats/metrics.ex +++ b/lib/plausible/stats/metrics.ex @@ -17,6 +17,7 @@ defmodule Plausible.Stats.Metrics do :visit_duration, :events, :conversion_rate, + :total_visitors, :group_conversion_rate, :time_on_page, :percentage, diff --git a/test/plausible/stats/query/query_parse_and_build_test.exs b/test/plausible/stats/query/query_parse_and_build_test.exs index eb4320b220db..1baabb70d0bd 100644 --- a/test/plausible/stats/query/query_parse_and_build_test.exs +++ b/test/plausible/stats/query/query_parse_and_build_test.exs @@ -88,6 +88,19 @@ defmodule Plausible.Stats.Query.QueryParseAndBuildTest do assert error =~ "Invalid metric" end + test "public API does not recognize total_visitors metric", %{site: site} do + params = %{ + "site_id" => site.domain, + "metrics" => ["total_visitors"], + "date_range" => "all" + } + + assert {:error, %QueryError{message: error}} = + Query.parse_and_build(site, params, now: @now) + + assert error =~ "Invalid metric" + end + test "valid metrics passed", %{site: site} do params = %{ "site_id" => site.domain, From 377ee03b6afe6caedca09f9c3bf7ebda99761852 Mon Sep 17 00:00:00 2001 From: Robert Joonas Date: Thu, 19 Mar 2026 16:26:11 +0000 Subject: [PATCH 3/3] Dashboard.QueryParser: add pagination and order_by --- lib/plausible/stats/api_query_parser.ex | 4 ++-- lib/plausible/stats/dashboard/query_parser.ex | 4 ++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/plausible/stats/api_query_parser.ex b/lib/plausible/stats/api_query_parser.ex index 53af31245ce8..35837f1eba5e 100644 --- a/lib/plausible/stats/api_query_parser.ex +++ b/lib/plausible/stats/api_query_parser.ex @@ -325,11 +325,11 @@ defmodule Plausible.Stats.ApiQueryParser do defp parse_include_entry(key, _value), do: {:error, %QueryError{code: :invalid_include, message: "Invalid include key'#{i(key)}'."}} - defp parse_pagination(pagination) when is_map(pagination) do + def parse_pagination(pagination) when is_map(pagination) do {:ok, Map.merge(@default_pagination, atomize_keys(pagination))} end - defp parse_pagination(nil), do: {:ok, @default_pagination} + def parse_pagination(nil), do: {:ok, @default_pagination} defp atomize_keys(map) when is_map(map) do Map.new(map, fn {key, value} -> diff --git a/lib/plausible/stats/dashboard/query_parser.ex b/lib/plausible/stats/dashboard/query_parser.ex index 618e4b59985b..4eb9dd2c61fe 100644 --- a/lib/plausible/stats/dashboard/query_parser.ex +++ b/lib/plausible/stats/dashboard/query_parser.ex @@ -20,6 +20,8 @@ defmodule Plausible.Stats.Dashboard.QueryParser do {:ok, dimensions} <- ApiQueryParser.parse_dimensions(params["dimensions"]), {:ok, filters} <- ApiQueryParser.parse_filters(params["filters"]), {:ok, metrics} <- parse_metrics(params), + {:ok, order_by} <- ApiQueryParser.parse_order_by(params["order_by"]), + {:ok, pagination} <- ApiQueryParser.parse_pagination(params["pagination"]), {:ok, include} <- parse_include(params) do {:ok, ParsedQueryParams.new!(%{ @@ -28,6 +30,8 @@ defmodule Plausible.Stats.Dashboard.QueryParser do dimensions: dimensions, filters: filters, metrics: metrics, + order_by: order_by, + pagination: pagination, include: include, skip_goal_existence_check: true, now: Keyword.get(opts, :now)