| defmodule Plausible.Stats.TableDeciderTest do |
| use Plausible.DataCase, async: true |
| alias Plausible.Stats.{Query, QueryBuilder, QueryError} |
|
|
| import Plausible.Stats.TableDecider |
|
|
| describe "events_join_sessions?" do |
| test "with simple filters" do |
| assert not events_join_sessions?(make_query([])) |
| assert not events_join_sessions?(make_query(["event:name"])) |
| assert not events_join_sessions?(make_query(["visit:source"])) |
| assert events_join_sessions?(make_query(["visit:entry_page"])) |
| assert events_join_sessions?(make_query(["visit:exit_page"])) |
| end |
|
|
| test "with nested filter" do |
| assert not events_join_sessions?(make_query_full_filters([[:not, [:is, "event:name", []]]])) |
|
|
| assert events_join_sessions?( |
| make_query_full_filters([[:not, [:is, "visit:exit_page", []]]]) |
| ) |
| end |
| end |
|
|
| describe "partition_metrics" do |
| test "with no metrics or filters" do |
| query = make_query([]) |
|
|
| assert partition_metrics([], query) == [] |
| end |
|
|
| test "session-only metrics accordingly" do |
| query = make_query([]) |
|
|
| assert partition_metrics([:bounce_rate, :views_per_visit], query) == [ |
| sessions: [:bounce_rate, :views_per_visit] |
| ] |
| end |
|
|
| test "event-only metrics accordingly" do |
| query = make_query([]) |
|
|
| assert partition_metrics([:total_revenue, :visitors], query) == [ |
| events: [:total_revenue, :visitors] |
| ] |
| end |
|
|
| test "filters from both, event-only metrics" do |
| query = make_query(["event:name", "visit:source"]) |
|
|
| assert partition_metrics([:total_revenue], query) == [events: [:total_revenue]] |
| end |
|
|
| test "filters from both, session-only metrics" do |
| query = make_query(["event:name", "visit:source"]) |
|
|
| assert partition_metrics([:bounce_rate], query) == [sessions: [:bounce_rate]] |
| end |
|
|
| test "session filters but no session metrics" do |
| query = make_query(["visit:source"]) |
|
|
| assert partition_metrics([:total_revenue], query) == [events: [:total_revenue]] |
| end |
|
|
| test "sample_percent is added to both types of metrics" do |
| query = make_query([]) |
|
|
| assert partition_metrics([:total_revenue, :sample_percent], query) == [ |
| events: [:total_revenue, :sample_percent] |
| ] |
|
|
| assert partition_metrics([:bounce_rate, :sample_percent], query) == [ |
| sessions: [:bounce_rate, :sample_percent] |
| ] |
|
|
| assert partition_metrics([:total_revenue, :bounce_rate, :sample_percent], query) == [ |
| events: [:total_revenue, :sample_percent], |
| sessions: [:bounce_rate, :sample_percent] |
| ] |
| end |
|
|
| test "other metrics get ignored" do |
| query = make_query([]) |
|
|
| assert partition_metrics([:percentage, :total_visitors], query) == [sessions: [:percentage]] |
| end |
|
|
| test "metrics that can be calculated on either when event-only metrics" do |
| query = make_query([]) |
|
|
| assert partition_metrics([:total_revenue, :visitors], query) == [ |
| events: [:total_revenue, :visitors] |
| ] |
|
|
| assert partition_metrics([:pageviews, :visits], query) == [events: [:pageviews, :visits]] |
| end |
|
|
| test "metrics that can be calculated on either when session-only metrics" do |
| query = make_query([]) |
|
|
| assert partition_metrics([:bounce_rate, :visitors], query) == [ |
| sessions: [:bounce_rate, :visitors] |
| ] |
|
|
| assert partition_metrics([:visit_duration, :visits], query) == [ |
| sessions: [:visit_duration, :visits] |
| ] |
| end |
|
|
| test "metrics that can be calculated on either are biased to events" do |
| query = make_query([]) |
|
|
| assert partition_metrics([:bounce_rate, :total_revenue, :visitors], query) == [ |
| events: [:total_revenue, :visitors], |
| sessions: [:bounce_rate] |
| ] |
| end |
|
|
| test "sample_percent is handled with either metrics" do |
| query = make_query([]) |
|
|
| assert partition_metrics([:visitors, :sample_percent], query) == [ |
| sessions: [:visitors, :sample_percent] |
| ] |
| end |
|
|
| test "metric can be calculated on either, but filtering on events" do |
| query = make_query(["event:name"]) |
|
|
| assert partition_metrics([:visitors], query) == [events: [:visitors]] |
| end |
|
|
| test "metric can be calculated on either, but filtering on events and sessions" do |
| query = make_query(["event:name", "visit:exit_page"]) |
|
|
| assert partition_metrics([:visitors], query) == [sessions: [:visitors]] |
| end |
|
|
| test "metric can be calculated on either, filtering on either" do |
| query = make_query(["visit:source"]) |
|
|
| assert partition_metrics([:visitors], query) == [sessions: [:visitors]] |
| end |
|
|
| test "metric can be calculated on either, filtering on sessions" do |
| query = make_query(["visit:exit_page"]) |
|
|
| assert partition_metrics([:visitors], query) == [sessions: [:visitors]] |
| end |
|
|
| test "query dimensions lean metric" do |
| assert partition_metrics([:visitors], make_query([], ["event:name"])) == [ |
| events: [:visitors] |
| ] |
|
|
| assert partition_metrics([:visitors], make_query([], ["visit:source"])) == [ |
| sessions: [:visitors] |
| ] |
|
|
| assert partition_metrics([:visitors], make_query([], ["visit:exit_page"])) == [ |
| sessions: [:visitors] |
| ] |
| end |
|
|
| test "smearable metrics" do |
| assert partition_metrics( |
| [:visitors, :visits, :visit_duration, :pageviews], |
| make_query([], ["time:minute"]) |
| ) == [ |
| events: [:pageviews], |
| sessions: [:visit_duration], |
| sessions_smeared: [:visitors, :visits] |
| ] |
|
|
| assert partition_metrics([:visitors], make_query([], ["time:hour"])) == [ |
| sessions_smeared: [:visitors] |
| ] |
|
|
| assert partition_metrics([:visitors], make_query([], ["time:day"])) == [ |
| sessions: [:visitors] |
| ] |
|
|
| assert partition_metrics([:visitors], make_query([], [])) == [ |
| sessions: [:visitors] |
| ] |
| end |
| end |
|
|
| describe "validate_no_metrics_dimensions_conflict" do |
| for {metrics, dimensions, expected} <- [ |
| {[], [], :ok}, |
| {[:bounce_rate], [], :ok}, |
| {[:scroll_depth], [], :ok}, |
| {[:bounce_rate], ["visit:exit_page"], :ok}, |
| {[:scroll_depth], ["event:name"], :ok}, |
| {[:scroll_depth], ["visit:device"], :ok}, |
| {[:bounce_rate, :scroll_depth], ["event:name"], |
| {:error, |
| %QueryError{ |
| code: :invalid_metrics, |
| message: |
| "Session metric(s) `bounce_rate` cannot be queried along with event dimension(s) `event:name`" |
| }}}, |
| {[:visit_duration], ["event:props:foo"], |
| {:error, |
| %QueryError{ |
| code: :invalid_metrics, |
| message: |
| "Session metric(s) `visit_duration` cannot be queried along with event dimension(s) `event:props:foo`" |
| }}}, |
| {[:bounce_rate, :scroll_depth], ["visit:exit_page"], |
| {:error, |
| %QueryError{ |
| code: :invalid_metrics, |
| message: |
| "Event metric(s) `scroll_depth` cannot be queried along with session dimension(s) `visit:exit_page`" |
| }}}, |
| {[:bounce_rate, :scroll_depth], ["event:page"], :ok}, |
| {[:scroll_depth], ["visit:entry_page"], |
| {:error, |
| %QueryError{ |
| code: :invalid_metrics, |
| message: |
| "Event metric(s) `scroll_depth` cannot be queried along with session dimension(s) `visit:entry_page`" |
| }}}, |
| {[:time_on_page], ["visit:exit_page"], |
| {:error, |
| %QueryError{ |
| code: :invalid_metrics, |
| message: |
| "Event metric(s) `time_on_page` cannot be queried along with session dimension(s) `visit:exit_page`" |
| }}} |
| ] do |
| test "metrics #{inspect(metrics)} and dimensions #{inspect(dimensions)}" do |
| query = |
| make_query() |> Query.set(metrics: unquote(metrics), dimensions: unquote(dimensions)) |
|
|
| assert validate_no_metrics_dimensions_conflict(query) == unquote(Macro.escape(expected)) |
| end |
| end |
|
|
| for {metrics, dimensions} <- [ |
| {[:total_revenue], ["visit:entry_page"]}, |
| {[:average_revenue], ["visit:exit_page"]} |
| ] do |
| @tag :ee_only |
| test "revenue metrics #{inspect(metrics)} and dimensions #{inspect(dimensions)} are allowed with session dimensions" do |
| query = |
| make_query() |> Query.set(metrics: unquote(metrics), dimensions: unquote(dimensions)) |
|
|
| assert validate_no_metrics_dimensions_conflict(query) == :ok |
| end |
| end |
| end |
|
|
| defp make_query(filter_dimensions \\ [], dimensions \\ []) do |
| site = build(:site, id: :rand.uniform(100_000)) |
|
|
| QueryBuilder.build!(site, |
| metrics: [:visitors], |
| input_date_range: :all, |
| filters: |
| Enum.map(filter_dimensions, fn filter_dimension -> [:is, filter_dimension, []] end), |
| dimensions: dimensions |
| ) |
| end |
|
|
| defp make_query_full_filters(filters) do |
| site = build(:site, id: :rand.uniform(100_000)) |
|
|
| QueryBuilder.build!(site, |
| metrics: [:visitors], |
| input_date_range: :all, |
| dimensions: [], |
| filters: filters |
| ) |
| end |
| end |
|
|