Skip to content

Commit 5a26631

Browse files
Add dashboard links to monthly pageview usage breakdown (#6117)
* Add dashboard links to monthly pageview usage breakdown - On the subscription settings page, each billing period's "Total billable pageviews" row and each individual site in the breakdown now show an external link icon that deep-links to the dashboard filtered to that exact billing cycle date range. - For the total link, the consolidated view domain is preferred when available; otherwise the site's own domain is used when the team has exactly one site. * Fix CI failures from nested consolidated view logic in settings controller * Fix Dialyzer CI failure * Extract consolidated view domain lookup to avoid Dialyzer warning * fix dialyzer * Incorporate feedback - Merge `site_domain` and `consolidated_view_domain` into a single`total_pageview_usage_domain` assign, resolving the precedence logic in the controller rather than the component - Rename `sites` to `per_site` in the usage cycle map to distinguish it from the site count field in quota usage - Remove `team_identifier` from controller assigns, template, and billing component — the session already carries the correct team context when the user is on the subscription settings page * Simplify single-site domain lookup to a strict pattern match --------- Co-authored-by: Robert Joonas <robertjoonas16@gmail.com>
1 parent 0442403 commit 5a26631

7 files changed

Lines changed: 205 additions & 27 deletions

File tree

lib/plausible/teams/billing.ex

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,7 @@ defmodule Plausible.Teams.Billing do
219219
@doc """
220220
Returns the number of sites the given team owns.
221221
"""
222-
@spec site_usage(Teams.Team.t()) :: non_neg_integer()
222+
@spec site_usage(Teams.Team.t() | nil) :: non_neg_integer()
223223
def site_usage(nil), do: 0
224224

225225
def site_usage(team) do
@@ -350,7 +350,7 @@ defmodule Plausible.Teams.Billing do
350350
team owns. Alternatively, given an optional argument of `site_ids`, the usage from
351351
across all those sites is queried instead.
352352
"""
353-
@spec monthly_pageview_usage(Teams.Team.t(), list() | nil) :: monthly_pageview_usage()
353+
@spec monthly_pageview_usage(Teams.Team.t() | nil, list() | nil) :: monthly_pageview_usage()
354354
def monthly_pageview_usage(team, site_ids \\ nil)
355355

356356
def monthly_pageview_usage(team, nil) do
@@ -376,7 +376,7 @@ defmodule Plausible.Teams.Billing do
376376
end
377377
end
378378

379-
@spec team_member_usage(Teams.Team.t(), Keyword.t()) :: non_neg_integer()
379+
@spec team_member_usage(Teams.Team.t() | nil, Keyword.t()) :: non_neg_integer()
380380
@doc """
381381
Returns the total count of team members associated with the team's sites.
382382
@@ -430,7 +430,7 @@ defmodule Plausible.Teams.Billing do
430430
pageviews: pageviews,
431431
custom_events: custom_events,
432432
total: pageviews + custom_events,
433-
sites: per_site_usage(owned_site_ids, date_range)
433+
per_site: per_site_usage(owned_site_ids, date_range)
434434
}
435435
end
436436

@@ -470,7 +470,7 @@ defmodule Plausible.Teams.Billing do
470470
pageviews: pageviews,
471471
custom_events: custom_events,
472472
total: pageviews + custom_events,
473-
sites: per_site_usage(owned_site_ids, date_range)
473+
per_site: per_site_usage(owned_site_ids, date_range)
474474
}
475475
end
476476

lib/plausible_web/components/billing/billing.ex

Lines changed: 58 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ defmodule PlausibleWeb.Components.Billing do
44
use PlausibleWeb, :component
55
use Plausible
66

7+
import PlausibleWeb.Components.Icons
8+
79
require Plausible.Billing.Subscription.Status
810
alias Plausible.Billing.{Plan, Plans, EnterprisePlan}
911

@@ -49,6 +51,10 @@ defmodule PlausibleWeb.Components.Billing do
4951
"""
5052
end
5153

54+
attr :usage, :map, required: true
55+
attr :limit, :any, required: true
56+
attr :total_pageview_usage_domain, :string, default: nil
57+
5258
def render_monthly_pageview_usage(%{usage: usage} = assigns)
5359
when is_map_key(usage, :last_30_days) do
5460
~H"""
@@ -57,6 +63,7 @@ defmodule PlausibleWeb.Components.Billing do
5763
limit={@limit}
5864
period={:last_30_days}
5965
expanded={true}
66+
total_pageview_usage_domain={@total_pageview_usage_domain}
6067
/>
6168
"""
6269
end
@@ -75,20 +82,23 @@ defmodule PlausibleWeb.Components.Billing do
7582
usage={@usage.current_cycle}
7683
limit={@limit}
7784
period={:current_cycle}
78-
expanded={not @show_all and Enum.empty?(@usage.current_cycle.sites)}
85+
expanded={not @show_all and Enum.empty?(@usage.current_cycle.per_site)}
86+
total_pageview_usage_domain={@total_pageview_usage_domain}
7987
/>
8088
<%= if @show_all do %>
8189
<.monthly_pageview_usage_breakdown
8290
usage={@usage.last_cycle}
8391
limit={@limit}
8492
period={:last_cycle}
8593
expanded={false}
94+
total_pageview_usage_domain={@total_pageview_usage_domain}
8695
/>
8796
<.monthly_pageview_usage_breakdown
8897
usage={@usage.penultimate_cycle}
8998
limit={@limit}
9099
period={:penultimate_cycle}
91100
expanded={false}
101+
total_pageview_usage_domain={@total_pageview_usage_domain}
92102
/>
93103
<% end %>
94104
</div>
@@ -99,8 +109,19 @@ defmodule PlausibleWeb.Components.Billing do
99109
attr(:limit, :any, required: true)
100110
attr(:period, :atom, required: true)
101111
attr(:expanded, :boolean, required: true)
112+
attr(:total_pageview_usage_domain, :string, default: nil)
102113

103114
defp monthly_pageview_usage_breakdown(assigns) do
115+
assigns =
116+
assign(
117+
assigns,
118+
:total_link,
119+
dashboard_url(
120+
assigns.total_pageview_usage_domain,
121+
assigns.usage.date_range
122+
)
123+
)
124+
104125
~H"""
105126
<div class="flex flex-col gap-3" x-data={"{ open: #{@expanded} }"}>
106127
<div class="flex flex-col gap-2">
@@ -120,6 +141,17 @@ defmodule PlausibleWeb.Components.Billing do
120141
class="size-4 transition-transform"
121142
x-bind:class="open ? 'rotate-90' : ''"
122143
/> Total billable pageviews
144+
<.tooltip :if={@total_link} centered?={true}>
145+
<:tooltip_content>View billing period in dashboard</:tooltip_content>
146+
<.link
147+
href={@total_link}
148+
class="text-indigo-500 hover:text-indigo-600"
149+
data-test-id="total-pageviews-dashboard-link"
150+
x-on:click.stop
151+
>
152+
<.external_link_icon class="ml-0.5 size-3.5 [&_path]:stroke-2" />
153+
</.link>
154+
</.tooltip>
123155
</span>
124156
<span class="ml-5 text-sm font-medium text-gray-900 dark:text-gray-100">
125157
{PlausibleWeb.TextHelpers.number_format(@usage.total)}
@@ -130,23 +162,34 @@ defmodule PlausibleWeb.Components.Billing do
130162
<div x-show="open" class="flex flex-col gap-3 text-sm text-gray-900 dark:text-gray-100">
131163
<.pageview_usage_row
132164
id={"pageviews_#{@period}"}
133-
label={if Enum.empty?(@usage.sites), do: "Pageviews", else: "Total pageviews"}
165+
label={if Enum.empty?(@usage.per_site), do: "Pageviews", else: "Total pageviews"}
134166
value={@usage.pageviews}
135167
/>
136168
<.pageview_usage_row
137169
id={"custom_events_#{@period}"}
138-
label={if Enum.empty?(@usage.sites), do: "Custom events", else: "Total custom events"}
170+
label={if Enum.empty?(@usage.per_site), do: "Custom events", else: "Total custom events"}
139171
value={@usage.custom_events}
140172
/>
141173
<div
142-
:if={not Enum.empty?(@usage.sites)}
174+
:if={not Enum.empty?(@usage.per_site)}
143175
id={"per_site_breakdown_#{@period}"}
144176
class="flex flex-col gap-3 border-t border-gray-200 dark:border-gray-700 pt-3 pl-5"
145177
>
146-
<div :for={{site, index} <- Enum.with_index(@usage.sites)} class="flex flex-col gap-3">
178+
<div :for={{site, index} <- Enum.with_index(@usage.per_site)} class="flex flex-col gap-3">
147179
<hr :if={index > 0} class="border-gray-200 dark:border-gray-700" />
148180
<div class="flex justify-between flex-wrap font-medium">
149-
<span class="truncate">{site.domain}</span>
181+
<span class="flex items-center gap-1 min-w-0">
182+
<span class="truncate">{site.domain}</span>
183+
<.tooltip centered?={true}>
184+
<:tooltip_content>View billing period in dashboard</:tooltip_content>
185+
<.link
186+
href={dashboard_url(site.domain, @usage.date_range)}
187+
class="shrink-0 text-indigo-500 hover:text-indigo-600"
188+
>
189+
<.external_link_icon class="ml-0.5 size-3.5 [&_path]:stroke-2" />
190+
</.link>
191+
</.tooltip>
192+
</span>
150193
<span class="shrink-0">{PlausibleWeb.TextHelpers.number_format(site.total)}</span>
151194
</div>
152195
<.pageview_usage_row label="Pageviews" value={site.pageviews} />
@@ -174,6 +217,15 @@ defmodule PlausibleWeb.Components.Billing do
174217
"""
175218
end
176219

220+
defp dashboard_url(nil, _date_range), do: nil
221+
222+
defp dashboard_url(domain, date_range) do
223+
base = Routes.stats_path(PlausibleWeb.Endpoint, :stats, domain, [])
224+
225+
base <>
226+
"?period=custom&from=#{Date.to_iso8601(date_range.first)}&to=#{Date.to_iso8601(date_range.last)}"
227+
end
228+
177229
defp cycle_label(:current_cycle), do: "(current cycle)"
178230
defp cycle_label(:last_30_days), do: "(last 30 days)"
179231

lib/plausible_web/controllers/settings_controller.ex

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,14 @@ defmodule PlausibleWeb.SettingsController do
152152

153153
notification_type = Plausible.Billing.Quota.usage_notification_type(team, usage)
154154

155+
total_pageview_usage_domain =
156+
if site_usage == 1 do
157+
[site] = Plausible.Teams.owned_sites(team)
158+
site.domain
159+
else
160+
on_ee(do: team && get_consolidated_view_domain(team), else: nil)
161+
end
162+
155163
render(conn, :subscription,
156164
layout: {PlausibleWeb.LayoutView, :settings},
157165
subscription: subscription,
@@ -162,7 +170,8 @@ defmodule PlausibleWeb.SettingsController do
162170
site_limit: Teams.Billing.site_limit(team),
163171
team_member_limit: Teams.Billing.team_member_limit(team),
164172
team_member_usage: team_member_usage,
165-
notification_type: notification_type
173+
notification_type: notification_type,
174+
total_pageview_usage_domain: total_pageview_usage_domain
166175
)
167176
end
168177

@@ -461,6 +470,15 @@ defmodule PlausibleWeb.SettingsController do
461470
end
462471
end
463472

473+
on_ee do
474+
defp get_consolidated_view_domain(team) do
475+
case Plausible.ConsolidatedView.get(team) do
476+
nil -> nil
477+
view -> if Plausible.ConsolidatedView.ok_to_display?(team), do: view.domain
478+
end
479+
end
480+
end
481+
464482
defp handle_email_updated(conn) do
465483
conn
466484
|> put_flash(:success, "Email updated")

lib/plausible_web/templates/settings/subscription.html.heex

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@
117117
<PlausibleWeb.Components.Billing.render_monthly_pageview_usage
118118
usage={@pageview_usage}
119119
limit={@pageview_limit}
120+
total_pageview_usage_domain={@total_pageview_usage_domain}
120121
/>
121122
</div>
122123
</.tile>

test/plausible/billing/quota_test.exs

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -973,15 +973,15 @@ defmodule Plausible.Billing.QuotaTest do
973973
build(:event, timestamp: ~N[2023-05-15 00:00:00], name: "pageview")
974974
])
975975

976-
%{sites: sites} = Plausible.Teams.Billing.usage_cycle(team, :last_30_days, nil, today)
976+
%{per_site: per_site} = Plausible.Teams.Billing.usage_cycle(team, :last_30_days, nil, today)
977977

978-
assert length(sites) == 2
978+
assert length(per_site) == 2
979979

980980
assert %{pageviews: 1, custom_events: 1, total: 2} =
981-
Enum.find(sites, &(&1.domain == site1.domain))
981+
Enum.find(per_site, &(&1.domain == site1.domain))
982982

983983
assert %{pageviews: 1, custom_events: 0, total: 1} =
984-
Enum.find(sites, &(&1.domain == site2.domain))
984+
Enum.find(per_site, &(&1.domain == site2.domain))
985985
end
986986

987987
test "sites with zero events in the period still appear in the breakdown" do
@@ -995,12 +995,12 @@ defmodule Plausible.Billing.QuotaTest do
995995
build(:event, timestamp: ~N[2023-05-15 00:00:00], name: "pageview")
996996
])
997997

998-
%{sites: sites} = Plausible.Teams.Billing.usage_cycle(team, :last_30_days, nil, today)
998+
%{per_site: per_site} = Plausible.Teams.Billing.usage_cycle(team, :last_30_days, nil, today)
999999

1000-
assert length(sites) == 2
1000+
assert length(per_site) == 2
10011001

10021002
assert %{pageviews: 0, custom_events: 0, total: 0} =
1003-
Enum.find(sites, &(&1.domain == site2.domain))
1003+
Enum.find(per_site, &(&1.domain == site2.domain))
10041004
end
10051005

10061006
test "returns empty sites list when team has only one site" do
@@ -1009,7 +1009,8 @@ defmodule Plausible.Billing.QuotaTest do
10091009
team = team_of(user)
10101010
today = ~D[2023-06-01]
10111011

1012-
assert %{sites: []} = Plausible.Teams.Billing.usage_cycle(team, :last_30_days, nil, today)
1012+
assert %{per_site: []} =
1013+
Plausible.Teams.Billing.usage_cycle(team, :last_30_days, nil, today)
10131014
end
10141015

10151016
test "returns empty sites list when team has more than 10 sites" do
@@ -1018,7 +1019,8 @@ defmodule Plausible.Billing.QuotaTest do
10181019
team = team_of(user)
10191020
today = ~D[2023-06-01]
10201021

1021-
assert %{sites: []} = Plausible.Teams.Billing.usage_cycle(team, :last_30_days, nil, today)
1022+
assert %{per_site: []} =
1023+
Plausible.Teams.Billing.usage_cycle(team, :last_30_days, nil, today)
10221024
end
10231025
end
10241026

0 commit comments

Comments
 (0)