Skip to content

Commit d02636d

Browse files
authored
POC: Rollup view (#5719)
* POC rollup dashboard * add rollups to stats api * remove the irrelevant new controller action * use team_identifier instead of team_id
1 parent aceef2d commit d02636d

8 files changed

Lines changed: 82 additions & 4 deletions

File tree

lib/plausible/site.ex

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,9 +62,23 @@ defmodule Plausible.Site do
6262

6363
has_many :completed_imports, Plausible.Imported.SiteImport, where: [status: :completed]
6464

65+
field :rollup, :boolean, virtual: true, default: false
66+
6567
timestamps()
6668
end
6769

70+
def rollup(team) do
71+
%Plausible.Site{
72+
id: 0,
73+
native_stats_start_at: ~N[2018-01-01 00:00:00],
74+
stats_start_date: ~D[2018-01-01],
75+
rollup: true,
76+
domain: "rollup:#{team.identifier}",
77+
team: team,
78+
team_id: team.id
79+
}
80+
end
81+
6882
def new_for_team(team, params) do
6983
params
7084
|> new()

lib/plausible/stats/filters/query_parser.ex

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,10 @@ defmodule Plausible.Stats.Filters.QueryParser do
4747
{:ok, pagination} <- parse_pagination(Map.get(params, "pagination", %{})),
4848
{preloaded_goals, revenue_warning, revenue_currencies} <-
4949
preload_goals_and_revenue(site, metrics, filters, dimensions),
50+
rollup_site_ids = get_rollup_site_ids(site),
5051
query = %{
5152
now: now,
53+
rollup_site_ids: rollup_site_ids,
5254
input_date_range: Map.get(params, "date_range"),
5355
metrics: metrics,
5456
filters: filters,
@@ -75,6 +77,12 @@ defmodule Plausible.Stats.Filters.QueryParser do
7577
end
7678
end
7779

80+
def get_rollup_site_ids(%Plausible.Site{rollup: true} = site) do
81+
Plausible.Teams.owned_sites_ids(site.team)
82+
end
83+
84+
def get_rollup_site_ids(_site), do: nil
85+
7886
def parse_date_range_pair(site, [from, to]) when is_binary(from) and is_binary(to) do
7987
with {:ok, date_range} <- date_range_from_date_strings(site, from, to) do
8088
{:ok, date_range |> DateTimeRange.to_timezone("Etc/UTC")}

lib/plausible/stats/legacy/legacy_query_builder.ex

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ defmodule Plausible.Stats.Legacy.QueryBuilder do
2828
|> put_parsed_filters(params)
2929
|> resolve_segments(site)
3030
|> preload_goals_and_revenue(site)
31+
|> put_rollup_site_ids(site)
3132
|> put_order_by(params)
3233
|> put_include(site, params)
3334
|> Query.put_comparison_utc_time_range()
@@ -41,6 +42,13 @@ defmodule Plausible.Stats.Legacy.QueryBuilder do
4142
query
4243
end
4344

45+
defp put_rollup_site_ids(query, %Plausible.Site{rollup: true} = site) do
46+
site_ids = Plausible.Teams.owned_sites_ids(site.team)
47+
struct!(query, rollup_site_ids: site_ids)
48+
end
49+
50+
defp put_rollup_site_ids(query, _site), do: query
51+
4452
defp resolve_segments(query, site) do
4553
with {:ok, preloaded_segments} <-
4654
Plausible.Segments.Filters.preload_needed_segments(site, query.filters),

lib/plausible/stats/query.ex

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ defmodule Plausible.Stats.Query do
2626
revenue_warning: nil,
2727
remove_unavailable_revenue_metrics: false,
2828
site_id: nil,
29+
rollup_site_ids: nil,
2930
site_native_stats_start_at: nil,
3031
# Contains information to determine how to combine legacy and new time on page metrics
3132
time_on_page_data: %{},

lib/plausible/stats/sql/where_builder.ex

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,19 @@ defmodule Plausible.Stats.SQL.WhereBuilder do
4141
end
4242
end
4343

44+
defp filter_site_time_range(
45+
:events,
46+
%Plausible.Stats.Query{rollup_site_ids: [_ | _] = site_ids} = query
47+
) do
48+
{first_datetime, last_datetime} = utc_boundaries(query)
49+
50+
dynamic(
51+
[e],
52+
e.site_id in ^site_ids and e.timestamp >= ^first_datetime and
53+
e.timestamp <= ^last_datetime
54+
)
55+
end
56+
4457
defp filter_site_time_range(:events, query) do
4558
{first_datetime, last_datetime} = utc_boundaries(query)
4659

@@ -51,6 +64,21 @@ defmodule Plausible.Stats.SQL.WhereBuilder do
5164
)
5265
end
5366

67+
defp filter_site_time_range(
68+
:sessions,
69+
%Plausible.Stats.Query{rollup_site_ids: [_ | _] = site_ids} = query
70+
) do
71+
{first_datetime, last_datetime} = utc_boundaries(query)
72+
73+
dynamic(
74+
[s],
75+
s.site_id in ^site_ids and
76+
s.start >= ^NaiveDateTime.add(first_datetime, -7, :day) and
77+
s.timestamp >= ^first_datetime and
78+
s.start <= ^last_datetime
79+
)
80+
end
81+
5482
defp filter_site_time_range(:sessions, query) do
5583
{first_datetime, last_datetime} = utc_boundaries(query)
5684

lib/plausible_web/plugs/authorize_public_api.ex

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ defmodule PlausibleWeb.Plugs.AuthorizePublicAPI do
121121

122122
defp verify_by_scope(conn, api_key, "stats:read:" <> _ = scope) do
123123
with :ok <- check_scope(api_key, scope),
124-
{:ok, site} <- find_site(conn.params["site_id"]),
124+
{:ok, site} <- find_site(conn.params["site_id"], api_key),
125125
:ok <- verify_site_access(api_key, site) do
126126
Plausible.OpenTelemetry.add_site_attributes(site)
127127
site = Plausible.Repo.preload(site, :completed_imports)
@@ -173,9 +173,18 @@ defmodule PlausibleWeb.Plugs.AuthorizePublicAPI do
173173
end
174174
end
175175

176-
defp find_site(nil), do: {:error, :missing_site_id}
176+
defp find_site(nil, _api_key), do: {:error, :missing_site_id}
177177

178-
defp find_site(site_id) do
178+
defp find_site("rollup:" <> team_identifier, api_key) do
179+
with true <- Plausible.Auth.is_super_admin?(api_key.user),
180+
%Plausible.Teams.Team{} = team <- Plausible.Teams.get(team_identifier) do
181+
{:ok, Plausible.Site.rollup(team)}
182+
else
183+
_ -> {:error, :invalid_api_key}
184+
end
185+
end
186+
187+
defp find_site(site_id, _api_key) do
179188
domain_based_search =
180189
from s in Plausible.Site, where: s.domain == ^site_id or s.domain_changed_from == ^site_id
181190

lib/plausible_web/plugs/authorize_site_access.ex

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,15 @@ defmodule PlausibleWeb.Plugs.AuthorizeSiteAccess do
172172
end
173173
end
174174

175+
defp get_site_with_role(conn, current_user, "rollup:" <> team_identifier) do
176+
with true <- Plausible.Auth.is_super_admin?(current_user),
177+
%Plausible.Teams.Team{} = team <- Plausible.Teams.get(team_identifier) do
178+
{:ok, %{site: Plausible.Site.rollup(team), role: nil, member_type: nil}}
179+
else
180+
_ -> error_not_found(conn)
181+
end
182+
end
183+
175184
defp get_site_with_role(conn, current_user, domain) do
176185
site = Repo.get_by(Plausible.Site, domain: domain)
177186

test/plausible/stats/query_parser_test.exs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,8 @@ defmodule Plausible.Stats.Filters.QueryParserTest do
7777
:input_date_range,
7878
:preloaded_goals,
7979
:revenue_warning,
80-
:revenue_currencies
80+
:revenue_currencies,
81+
:rollup_site_ids
8182
])
8283

8384
assert result == expected_result

0 commit comments

Comments
 (0)