Skip to content

Commit 7b18ffc

Browse files
authored
Allow revenue metrics to be queried with session dimensions (#6211)
* Allow revenue metrics to be queried with session dimensions * Remove superfluous test cases * Add changelog entry * Run revenue tests in EE only * Naming
1 parent 6da7f85 commit 7b18ffc

4 files changed

Lines changed: 111 additions & 7 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@ All notable changes to this project will be documented in this file.
66

77
### Added
88

9+
- Allow querying revenue metrics (`total_revenue`, `average_revenue`) with visit dimensions in Stats API v2
910
- Allow querying `views_per_visit` with a time dimension in Stats API
1011
- Add `bounce_rate` to page-filtered Top Stats even when imports are included, but render a metric warning about imported data not included in `bounce_rate` tooltip.
11-
- Add `time_on_page` to page-filtered Top Stats even when imports are included, unless legacy time on page is in view.
12+
- Add `time_on_page` to page-filtered Top Stats even when imports are included, unless legacy time on page is in view.
1213
- Adds team_id to query debug metadata (saved in system.query_log log_comment column)
1314
- Add "Unknown" option to Countries shield, for when the country code is unrecognized
1415
- Add "Last 24 Hours" to dashboard time range picker and Stats API v2

lib/plausible/stats/table_decider.ex

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,25 @@ defmodule Plausible.Stats.TableDecider do
44
and metrics, with the purpose of reducing the number of queries and JOINs needed to perform.
55
"""
66

7+
use Plausible
8+
79
import Enum, only: [empty?: 1]
810
import Plausible.Stats.Filters, only: [dimensions_used_in_filters: 1]
911

1012
alias Plausible.Stats.{Query, QueryError}
1113

14+
@revenue_metrics on_ee(do: Plausible.Stats.Goal.Revenue.revenue_metrics(), else: [])
15+
1216
def events_join_sessions?(query) do
13-
query.filters
14-
|> dimensions_used_in_filters()
15-
|> Enum.any?(&(dimension_partitioner(query, &1) == :session))
17+
session_dims_in_filters? =
18+
query.filters
19+
|> dimensions_used_in_filters()
20+
|> Enum.any?(&(dimension_partitioner(query, &1) == :session))
21+
22+
session_dims? =
23+
Enum.any?(query.dimensions, &(dimension_partitioner(query, &1) == :session))
24+
25+
session_dims? or session_dims_in_filters?
1626
end
1727

1828
def sessions_join_events?(query) do
@@ -36,6 +46,8 @@ defmodule Plausible.Stats.TableDecider do
3646
%{event: event_only_dimensions, session: session_only_dimensions} =
3747
partition(query.dimensions, query, &dimension_partitioner/2)
3848

49+
conflicting_event_metrics = event_only_metrics -- @revenue_metrics
50+
3951
cond do
4052
# event:page is a special case handled in QueryOptimizer.split_sessions_query
4153
event_only_dimensions == ["event:page"] ->
@@ -49,12 +61,12 @@ defmodule Plausible.Stats.TableDecider do
4961
"Session metric(s) #{i(session_only_metrics)} cannot be queried along with event dimension(s) #{i(event_only_dimensions)}"
5062
}}
5163

52-
not empty?(event_only_metrics) and not empty?(session_only_dimensions) ->
64+
not empty?(conflicting_event_metrics) and not empty?(session_only_dimensions) ->
5365
{:error,
5466
%QueryError{
5567
code: :invalid_metrics,
5668
message:
57-
"Event metric(s) #{i(event_only_metrics)} cannot be queried along with session dimension(s) #{i(session_only_dimensions)}"
69+
"Event metric(s) #{i(conflicting_event_metrics)} cannot be queried along with session dimension(s) #{i(session_only_dimensions)}"
5870
}}
5971

6072
true ->

test/plausible/stats/table_decider_test.exs

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,7 +216,21 @@ defmodule Plausible.Stats.TableDeciderTest do
216216
message:
217217
"Event metric(s) `scroll_depth` cannot be queried along with session dimension(s) `visit:exit_page`"
218218
}}},
219-
{[:bounce_rate, :scroll_depth], ["event:page"], :ok}
219+
{[:bounce_rate, :scroll_depth], ["event:page"], :ok},
220+
{[:scroll_depth], ["visit:entry_page"],
221+
{:error,
222+
%QueryError{
223+
code: :invalid_metrics,
224+
message:
225+
"Event metric(s) `scroll_depth` cannot be queried along with session dimension(s) `visit:entry_page`"
226+
}}},
227+
{[:time_on_page], ["visit:exit_page"],
228+
{:error,
229+
%QueryError{
230+
code: :invalid_metrics,
231+
message:
232+
"Event metric(s) `time_on_page` cannot be queried along with session dimension(s) `visit:exit_page`"
233+
}}}
220234
] do
221235
test "metrics #{inspect(metrics)} and dimensions #{inspect(dimensions)}" do
222236
query =
@@ -225,6 +239,19 @@ defmodule Plausible.Stats.TableDeciderTest do
225239
assert validate_no_metrics_dimensions_conflict(query) == unquote(Macro.escape(expected))
226240
end
227241
end
242+
243+
for {metrics, dimensions} <- [
244+
{[:total_revenue], ["visit:entry_page"]},
245+
{[:average_revenue], ["visit:exit_page"]}
246+
] do
247+
@tag :ee_only
248+
test "revenue metrics #{inspect(metrics)} and dimensions #{inspect(dimensions)} are allowed with session dimensions" do
249+
query =
250+
make_query() |> Query.set(metrics: unquote(metrics), dimensions: unquote(dimensions))
251+
252+
assert validate_no_metrics_dimensions_conflict(query) == :ok
253+
end
254+
end
228255
end
229256

230257
defp make_query(filter_dimensions \\ [], dimensions \\ []) do

test/plausible_web/controllers/api/external_stats_controller/query_test.exs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4824,6 +4824,70 @@ defmodule PlausibleWeb.Api.ExternalStatsController.QueryTest do
48244824

48254825
assert "metric_warnings" not in json_response(conn, 200)["meta"]
48264826
end
4827+
4828+
test "breakdown by session dimension (entry page)", %{conn: conn, site: site} do
4829+
insert(:goal, site: site, event_name: "Purchase", currency: "USD")
4830+
4831+
populate_stats(site, [
4832+
build(:pageview, user_id: 1, pathname: "/blog"),
4833+
build(:event,
4834+
name: "Purchase",
4835+
user_id: 1,
4836+
revenue_reporting_amount: Decimal.new("100.00"),
4837+
revenue_reporting_currency: "USD"
4838+
),
4839+
build(:pageview, user_id: 2, pathname: "/blog"),
4840+
build(:event,
4841+
name: "Purchase",
4842+
user_id: 2,
4843+
revenue_reporting_amount: Decimal.new("50.00"),
4844+
revenue_reporting_currency: "USD"
4845+
),
4846+
build(:pageview, user_id: 3, pathname: "/home"),
4847+
build(:event,
4848+
name: "Purchase",
4849+
user_id: 3,
4850+
revenue_reporting_amount: Decimal.new("20.00"),
4851+
revenue_reporting_currency: "USD"
4852+
)
4853+
])
4854+
4855+
conn =
4856+
post(conn, "/api/v2/query", %{
4857+
"site_id" => site.domain,
4858+
"date_range" => "all",
4859+
"metrics" => ["total_revenue", "average_revenue"],
4860+
"dimensions" => ["visit:entry_page"],
4861+
"filters" => [["is", "event:goal", ["Purchase"]]]
4862+
})
4863+
4864+
assert json_response(conn, 200)["results"] == [
4865+
%{
4866+
"dimensions" => ["/blog"],
4867+
"metrics" => [
4868+
%{
4869+
"currency" => "USD",
4870+
"long" => "$150.00",
4871+
"short" => "$150.0",
4872+
"value" => 150.0
4873+
},
4874+
%{"currency" => "USD", "long" => "$75.00", "short" => "$75.0", "value" => 75.0}
4875+
]
4876+
},
4877+
%{
4878+
"dimensions" => ["/home"],
4879+
"metrics" => [
4880+
%{
4881+
"currency" => "USD",
4882+
"long" => "$20.00",
4883+
"short" => "$20.0",
4884+
"value" => 20.0
4885+
},
4886+
%{"currency" => "USD", "long" => "$20.00", "short" => "$20.0", "value" => 20.0}
4887+
]
4888+
}
4889+
]
4890+
end
48274891
end
48284892

48294893
describe "behavioral (has_done/has_not_done) filters" do

0 commit comments

Comments
 (0)