diff --git a/CHANGELOG.md b/CHANGELOG.md index 3355d253c2..cb370101eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ All notable changes to this project will be documented in this file. - Fixed [IPv6 problems](https://github.com/plausible/analytics/issues/3173) in data migration plausible/analytics#3179 - Fixed [long URLs display](https://github.com/plausible/analytics/issues/3158) in Outbound Link breakdown view - Fixed [Sentry reports](https://github.com/plausible/analytics/discussions/3166) for ingestion requests plausible/analytics#3182 +- Fix breakdown pagination bug in the dashboard details view when filtering by goals ## v2.0.0 - 2023-07-12 diff --git a/lib/plausible_web/controllers/api/stats_controller.ex b/lib/plausible_web/controllers/api/stats_controller.ex index 4b4c3b714f..8a6c9c19df 100644 --- a/lib/plausible_web/controllers/api/stats_controller.ex +++ b/lib/plausible_web/controllers/api/stats_controller.ex @@ -1399,6 +1399,11 @@ defmodule PlausibleWeb.Api.StatsController do |> Query.put_filter(filter_name, {:member, items}) |> Query.remove_event_filters([:goal, :props]) + # Here, we're always only interested in the first page of results + # - the :member filter makes sure that the results always match with + # the items in the given breakdown_results list + pagination = {elem(pagination, 0), 1} + res_without_goal = Stats.breakdown(site, query_without_goal, filter_name, [:visitors], pagination) 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 14fa1278b4..903b0e432f 100644 --- a/test/plausible_web/controllers/api/stats_controller/pages_test.exs +++ b/test/plausible_web/controllers/api/stats_controller/pages_test.exs @@ -1049,6 +1049,46 @@ defmodule PlausibleWeb.Api.StatsController.PagesTest do ] 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 + ) + + request = fn conn, opts -> + page = Keyword.fetch!(opts, :page) + limit = Keyword.fetch!(opts, :limit) + filters = Jason.encode!(%{"goal" => "Signup"}) + + conn + |> get( + "/api/stats/#{site.domain}/pages?date=2021-01-01&period=day&filters=#{filters}&limit=#{limit}&page=#{page}" + ) + |> json_response(200) + |> Enum.map(fn %{"name" => "/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) + end + test "calculates conversion_rate when filtering for goal", %{conn: conn, site: site} do populate_stats(site, [ build(:pageview,