Test new imported metrics (GA4) (#4014)

* test fixture imported data with stats requests

* take visits metric from the events table in event:page breakdown

* Remove assert_referrers after all

pageReferrer is an event scoped property in GA4, which when queried
along with session-level dimensions will return unexpected data.

Adding the pageReferrer dimension to the GA4 Data API request, it will
cause the selected metric totals to increase significantly, even though
they shouldn't.

* Adjust sources and utm_mediums assertions

* adjust assert_pages

* Make formatter happy

---------

Co-authored-by: Adrian Gruntkowski <adrian.gruntkowski@gmail.com>
This commit is contained in:
RobertJoonas 2024-04-18 11:12:24 +01:00 committed by GitHub
parent 9849743407
commit 3a371fdf4d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 334 additions and 5 deletions

View File

@ -11,11 +11,11 @@ defmodule Plausible.Stats.Breakdown do
@no_ref "Direct / None"
@not_set "(not set)"
@session_metrics [:visits, :bounce_rate, :visit_duration]
@session_metrics [:bounce_rate, :visit_duration]
@revenue_metrics on_full_build(do: Plausible.Stats.Goal.Revenue.revenue_metrics(), else: [])
@event_metrics [:visitors, :pageviews, :events, :percentage] ++ @revenue_metrics
@event_metrics [:visits, :visitors, :pageviews, :events, :percentage] ++ @revenue_metrics
# These metrics can be asked from the `breakdown/5` function,
# but they are different from regular metrics such as `visitors`,

View File

@ -1,10 +1,10 @@
defmodule Plausible.Imported.GoogleAnalytics4Test do
use Plausible.DataCase, async: true
use PlausibleWeb.ConnCase, async: true
import Mox
import Ecto.Query, only: [from: 2]
alias Plausible.Repo
alias Plausible.Imported.GoogleAnalytics4
@refresh_token_body Jason.decode!(File.read!("fixture/ga_refresh_token.json"))
@ -27,7 +27,7 @@ defmodule Plausible.Imported.GoogleAnalytics4Test do
describe "parse_args/1 and import_data/2" do
setup [:create_user, :create_new_site]
test "imports data returned from GA4 Data API", %{user: user, site: site} do
test "imports data returned from GA4 Data API", %{conn: conn, user: user, site: site} do
past = DateTime.add(DateTime.utc_now(), -3600, :second)
{:ok, job} =
@ -96,6 +96,335 @@ defmodule Plausible.Imported.GoogleAnalytics4Test do
query = from(imported in table, where: imported.site_id == ^site.id)
assert await_clickhouse_count(query, count)
end)
# NOTE: Consider using GoogleAnalytics.run_import instead of import_data
# to avoid having to set SiteImport to completed manually
site_import
|> Plausible.Imported.SiteImport.complete_changeset()
|> Repo.update!()
# Assert the actual data via Stats API requests
common_params = %{
"site_id" => site.domain,
"period" => "custom",
"date" => "2024-01-01,2024-01-31",
"with_imported" => "true"
}
breakdown_params =
common_params
|> Map.put("metrics", "visitors,visits,pageviews,visit_duration,bounce_rate")
|> Map.put("limit", 1000)
%{key: api_key} = insert(:api_key, user: user)
conn = put_req_header(conn, "authorization", "Bearer #{api_key}")
assert_timeseries(conn, common_params)
assert_pages(conn, common_params)
assert_sources(conn, breakdown_params)
assert_utm_mediums(conn, breakdown_params)
assert_entry_pages(conn, breakdown_params)
assert_cities(conn, breakdown_params)
assert_devices(conn, breakdown_params)
assert_browsers(conn, breakdown_params)
assert_os(conn, breakdown_params)
assert_os_versions(conn, breakdown_params)
end
end
defp assert_timeseries(conn, params) do
params =
Map.put(
params,
"metrics",
"visitors,visits,pageviews,views_per_visit,visit_duration,bounce_rate"
)
%{"results" => results} =
get(conn, "/api/v1/stats/timeseries", params) |> json_response(200)
assert length(results) == 31
assert List.first(results) == %{
"bounce_rate" => 36.0,
"date" => "2024-01-01",
"pageviews" => 224,
"views_per_visit" => 1.14,
"visit_duration" => 41.0,
"visitors" => 191,
"visits" => 197
}
assert List.last(results) == %{
"bounce_rate" => 38.0,
"date" => "2024-01-31",
"pageviews" => 195,
"views_per_visit" => 1.34,
"visit_duration" => 33.0,
"visitors" => 141,
"visits" => 146
}
end
defp assert_sources(conn, params) do
params = Map.put(params, "property", "visit:source")
%{"results" => results} =
get(conn, "/api/v1/stats/breakdown", params) |> json_response(200)
assert length(results) == 26
assert List.first(results) == %{
"bounce_rate" => 35.0,
"pageviews" => 6229,
"visit_duration" => 40.0,
"visitors" => 4671,
"visits" => 4917,
"source" => "Google"
}
assert List.last(results) == %{
"bounce_rate" => 100.0,
"pageviews" => 1,
"visit_duration" => 0.0,
"visitors" => 1,
"visits" => 1,
"source" => "petalsearch.com"
}
end
defp assert_utm_mediums(conn, params) do
params = Map.put(params, "property", "visit:utm_medium")
%{"results" => results} =
get(conn, "/api/v1/stats/breakdown", params) |> json_response(200)
assert [
%{
"bounce_rate" => 35.0,
"pageviews" => 6399,
"utm_medium" => "organic",
"visit_duration" => 40.0,
"visitors" => 4787,
"visits" => 5042
},
%{
"bounce_rate" => 58.0,
"pageviews" => 491,
"utm_medium" => "referral",
"visit_duration" => 27.5,
"visitors" => 294,
"visits" => 298
}
] = results
end
defp assert_entry_pages(conn, params) do
params = Map.put(params, "property", "visit:entry_page")
%{"results" => results} =
get(conn, "/api/v1/stats/breakdown", params) |> json_response(200)
assert length(results) == 629
assert List.first(results) == %{
"bounce_rate" => 35.0,
"pageviews" => 838,
"visit_duration" => 43.1,
"visitors" => 675,
"visits" => 712,
"entry_page" => "/brza-kukuruza"
}
assert List.last(results) == %{
"bounce_rate" => 0.0,
"pageviews" => 1,
"visit_duration" => 27.0,
"visitors" => 1,
"visits" => 1,
"entry_page" => "/kad-lisce-pada"
}
end
defp assert_cities(conn, params) do
params = Map.put(params, "property", "visit:city")
%{"results" => results} =
get(conn, "/api/v1/stats/breakdown", params) |> json_response(200)
assert length(results) == 488
assert List.first(results) == %{
"bounce_rate" => 35.0,
"city" => 792_680,
"pageviews" => 1650,
"visit_duration" => 38.9,
"visitors" => 1233,
"visits" => 1273
}
assert List.last(results) == %{
"bounce_rate" => 0.0,
"city" => 4_399_605,
"pageviews" => 7,
"visit_duration" => 128.0,
"visitors" => 1,
"visits" => 1
}
end
defp assert_devices(conn, params) do
params = Map.put(params, "property", "visit:device")
%{"results" => results} =
get(conn, "/api/v1/stats/breakdown", params) |> json_response(200)
assert length(results) == 3
assert List.first(results) == %{
"bounce_rate" => 38.0,
"pageviews" => 7041,
"visit_duration" => 36.6,
"visitors" => 5277,
"visits" => 5532,
"device" => "Mobile"
}
assert List.last(results) == %{
"bounce_rate" => 37.0,
"pageviews" => 143,
"visit_duration" => 59.8,
"visitors" => 97,
"visits" => 100,
"device" => "Tablet"
}
end
defp assert_browsers(conn, params) do
params = Map.put(params, "property", "visit:browser")
%{"results" => results} =
get(conn, "/api/v1/stats/breakdown", params) |> json_response(200)
assert length(results) == 11
assert List.first(results) == %{
"bounce_rate" => 33.0,
"pageviews" => 8143,
"visit_duration" => 50.2,
"visitors" => 4625,
"visits" => 4655,
"browser" => "Chrome"
}
assert List.last(results) == %{
"bounce_rate" => 0.0,
"pageviews" => 6,
"visit_duration" => 0.0,
"visitors" => 1,
"visits" => 1,
"browser" => "Opera Mini"
}
end
defp assert_os(conn, params) do
params = Map.put(params, "property", "visit:os")
%{"results" => results} =
get(conn, "/api/v1/stats/breakdown", params) |> json_response(200)
assert length(results) == 7
assert List.first(results) == %{
"bounce_rate" => 34.0,
"pageviews" => 5827,
"visit_duration" => 40.6,
"visitors" => 4319,
"visits" => 4495,
"os" => "Android"
}
assert List.last(results) == %{
"bounce_rate" => 0.0,
"pageviews" => 6,
"visit_duration" => 0.0,
"visitors" => 1,
"visits" => 1,
"os" => "(not set)"
}
end
defp assert_os_versions(conn, params) do
params = Map.put(params, "property", "visit:os_version")
%{"results" => results} =
get(conn, "/api/v1/stats/breakdown", params) |> json_response(200)
assert length(results) == 107
assert List.first(results) == %{
"bounce_rate" => 32.0,
"os" => "Android",
"os_version" => "13.0.0",
"pageviews" => 1673,
"visit_duration" => 42.4,
"visitors" => 1247,
"visits" => 1295
}
assert List.last(results) == %{
"bounce_rate" => 0.0,
"os" => "iOS",
"os_version" => "15.1",
"pageviews" => 1,
"visit_duration" => 54.0,
"visitors" => 1,
"visits" => 1
}
end
defp assert_pages(conn, params) do
metrics = "visitors,visits,pageviews,time_on_page,visit_duration,bounce_rate"
params =
params
|> Map.put("metrics", metrics)
|> Map.put("limit", 1000)
|> Map.put("property", "event:page")
%{"results" => results} =
get(conn, "/api/v1/stats/breakdown", params) |> json_response(200)
assert length(results) == 729
# The `event:page` breakdown is currently using the `entry_page`
# property to allow querying session metrics.
#
# We assert on the 3rd element of the results, because that page
# was also an entry page somewhere along the queried period. So
# it will allow us to assert on the session metrics as well.
assert Enum.at(results, 2) == %{
"page" => "/",
"pageviews" => 5537,
"time_on_page" => 17.677262055264585,
"visitors" => 371,
"visits" => 212,
"bounce_rate" => 54.0,
"visit_duration" => 45.0
}
# This page was never an entry_page in the imported data, and
# therefore the session metrics are returned as `nil`.
assert List.last(results) == %{
"page" => "/5-dobrih-razloga-zasto-zapoceti-dan-zobenom-kasom/",
"pageviews" => 2,
"time_on_page" => 10.0,
"visitors" => 1,
"visits" => 1,
"bounce_rate" => nil,
"visit_duration" => nil
}
end
end