+
0} class="border-gray-200 dark:border-gray-700" />
- {site.domain}
+
+ {site.domain}
+ <.tooltip centered?={true}>
+ <:tooltip_content>View billing period in dashboard
+ <.link
+ href={dashboard_url(site.domain, @usage.date_range)}
+ class="shrink-0 text-indigo-500 hover:text-indigo-600"
+ >
+ <.external_link_icon class="ml-0.5 size-3.5 [&_path]:stroke-2" />
+
+
+
{PlausibleWeb.TextHelpers.number_format(site.total)}
<.pageview_usage_row label="Pageviews" value={site.pageviews} />
@@ -174,6 +217,15 @@ defmodule PlausibleWeb.Components.Billing do
"""
end
+ defp dashboard_url(nil, _date_range), do: nil
+
+ defp dashboard_url(domain, date_range) do
+ base = Routes.stats_path(PlausibleWeb.Endpoint, :stats, domain, [])
+
+ base <>
+ "?period=custom&from=#{Date.to_iso8601(date_range.first)}&to=#{Date.to_iso8601(date_range.last)}"
+ end
+
defp cycle_label(:current_cycle), do: "(current cycle)"
defp cycle_label(:last_30_days), do: "(last 30 days)"
diff --git a/lib/plausible_web/controllers/settings_controller.ex b/lib/plausible_web/controllers/settings_controller.ex
index 2fdc8d72826d..aa39e396ad04 100644
--- a/lib/plausible_web/controllers/settings_controller.ex
+++ b/lib/plausible_web/controllers/settings_controller.ex
@@ -152,6 +152,14 @@ defmodule PlausibleWeb.SettingsController do
notification_type = Plausible.Billing.Quota.usage_notification_type(team, usage)
+ total_pageview_usage_domain =
+ if site_usage == 1 do
+ [site] = Plausible.Teams.owned_sites(team)
+ site.domain
+ else
+ on_ee(do: team && get_consolidated_view_domain(team), else: nil)
+ end
+
render(conn, :subscription,
layout: {PlausibleWeb.LayoutView, :settings},
subscription: subscription,
@@ -162,7 +170,8 @@ defmodule PlausibleWeb.SettingsController do
site_limit: Teams.Billing.site_limit(team),
team_member_limit: Teams.Billing.team_member_limit(team),
team_member_usage: team_member_usage,
- notification_type: notification_type
+ notification_type: notification_type,
+ total_pageview_usage_domain: total_pageview_usage_domain
)
end
@@ -461,6 +470,15 @@ defmodule PlausibleWeb.SettingsController do
end
end
+ on_ee do
+ defp get_consolidated_view_domain(team) do
+ case Plausible.ConsolidatedView.get(team) do
+ nil -> nil
+ view -> if Plausible.ConsolidatedView.ok_to_display?(team), do: view.domain
+ end
+ end
+ end
+
defp handle_email_updated(conn) do
conn
|> put_flash(:success, "Email updated")
diff --git a/lib/plausible_web/templates/settings/subscription.html.heex b/lib/plausible_web/templates/settings/subscription.html.heex
index e97f7b17f90c..b7a3e563e7e5 100644
--- a/lib/plausible_web/templates/settings/subscription.html.heex
+++ b/lib/plausible_web/templates/settings/subscription.html.heex
@@ -117,6 +117,7 @@
diff --git a/test/plausible/billing/quota_test.exs b/test/plausible/billing/quota_test.exs
index a080d2a97822..2e2ee5770b01 100644
--- a/test/plausible/billing/quota_test.exs
+++ b/test/plausible/billing/quota_test.exs
@@ -973,15 +973,15 @@ defmodule Plausible.Billing.QuotaTest do
build(:event, timestamp: ~N[2023-05-15 00:00:00], name: "pageview")
])
- %{sites: sites} = Plausible.Teams.Billing.usage_cycle(team, :last_30_days, nil, today)
+ %{per_site: per_site} = Plausible.Teams.Billing.usage_cycle(team, :last_30_days, nil, today)
- assert length(sites) == 2
+ assert length(per_site) == 2
assert %{pageviews: 1, custom_events: 1, total: 2} =
- Enum.find(sites, &(&1.domain == site1.domain))
+ Enum.find(per_site, &(&1.domain == site1.domain))
assert %{pageviews: 1, custom_events: 0, total: 1} =
- Enum.find(sites, &(&1.domain == site2.domain))
+ Enum.find(per_site, &(&1.domain == site2.domain))
end
test "sites with zero events in the period still appear in the breakdown" do
@@ -995,12 +995,12 @@ defmodule Plausible.Billing.QuotaTest do
build(:event, timestamp: ~N[2023-05-15 00:00:00], name: "pageview")
])
- %{sites: sites} = Plausible.Teams.Billing.usage_cycle(team, :last_30_days, nil, today)
+ %{per_site: per_site} = Plausible.Teams.Billing.usage_cycle(team, :last_30_days, nil, today)
- assert length(sites) == 2
+ assert length(per_site) == 2
assert %{pageviews: 0, custom_events: 0, total: 0} =
- Enum.find(sites, &(&1.domain == site2.domain))
+ Enum.find(per_site, &(&1.domain == site2.domain))
end
test "returns empty sites list when team has only one site" do
@@ -1009,7 +1009,8 @@ defmodule Plausible.Billing.QuotaTest do
team = team_of(user)
today = ~D[2023-06-01]
- assert %{sites: []} = Plausible.Teams.Billing.usage_cycle(team, :last_30_days, nil, today)
+ assert %{per_site: []} =
+ Plausible.Teams.Billing.usage_cycle(team, :last_30_days, nil, today)
end
test "returns empty sites list when team has more than 10 sites" do
@@ -1018,7 +1019,8 @@ defmodule Plausible.Billing.QuotaTest do
team = team_of(user)
today = ~D[2023-06-01]
- assert %{sites: []} = Plausible.Teams.Billing.usage_cycle(team, :last_30_days, nil, today)
+ assert %{per_site: []} =
+ Plausible.Teams.Billing.usage_cycle(team, :last_30_days, nil, today)
end
end
diff --git a/test/plausible_web/components/billing/billing_test.exs b/test/plausible_web/components/billing/billing_test.exs
index cbf9d7ad1f4c..93ee24f79c3a 100644
--- a/test/plausible_web/components/billing/billing_test.exs
+++ b/test/plausible_web/components/billing/billing_test.exs
@@ -184,7 +184,7 @@ defmodule PlausibleWeb.Components.BillingTest do
custom_events: 0,
total: 0,
date_range: Date.range(~D[2024-01-01], ~D[2024-01-31]),
- sites: []
+ per_site: []
}
test "only shows current cycle when neither last nor current cycle is exceeded" do
@@ -257,7 +257,7 @@ defmodule PlausibleWeb.Components.BillingTest do
test "shows 'Total pageviews' and 'Total custom events' labels when per-site breakdown is present" do
cycle_with_sites = %{
@cycle
- | sites: [
+ | per_site: [
%{domain: "example.com", pageviews: 100, custom_events: 50, total: 150},
%{domain: "app.example.com", pageviews: 200, custom_events: 30, total: 230}
]
@@ -274,7 +274,7 @@ defmodule PlausibleWeb.Components.BillingTest do
test "renders per-site breakdown when sites are present" do
cycle_with_sites = %{
@cycle
- | sites: [
+ | per_site: [
%{domain: "example.com", pageviews: 100, custom_events: 50, total: 150},
%{domain: "app.example.com", pageviews: 200, custom_events: 30, total: 230}
]
@@ -308,7 +308,7 @@ defmodule PlausibleWeb.Components.BillingTest do
test "current cycle is not expanded by default when per-site breakdown is present" do
cycle_with_sites = %{
@cycle
- | sites: [
+ | per_site: [
%{domain: "example.com", pageviews: 100, custom_events: 50, total: 150},
%{domain: "app.example.com", pageviews: 200, custom_events: 30, total: 230}
]
@@ -320,6 +320,55 @@ defmodule PlausibleWeb.Components.BillingTest do
refute html =~ "{ open: true }"
end
+
+ test "renders a total link when total_pageview_usage_domain is provided" do
+ usage = %{current_cycle: @cycle, last_cycle: @cycle, penultimate_cycle: @cycle}
+
+ html =
+ render_monthly_pageview_usage(usage, 10_000,
+ total_pageview_usage_domain: "my-site.example.com"
+ )
+
+ assert element_exists?(html, "[data-test-id='total-pageviews-dashboard-link']")
+ assert html =~ "/my-site.example.com/?period=custom"
+ end
+
+ test "renders no total link when no domain is provided" do
+ usage = %{current_cycle: @cycle, last_cycle: @cycle, penultimate_cycle: @cycle}
+
+ html = render_monthly_pageview_usage(usage, 10_000)
+
+ refute element_exists?(html, "[data-test-id='total-pageviews-dashboard-link']")
+ end
+
+ test "per-site breakdown shows a dashboard link for each site" do
+ cycle_with_sites = %{
+ @cycle
+ | per_site: [
+ %{domain: "example.com", pageviews: 100, custom_events: 50, total: 150},
+ %{domain: "app.example.com", pageviews: 200, custom_events: 30, total: 230}
+ ]
+ }
+
+ usage = %{current_cycle: cycle_with_sites, last_cycle: @cycle, penultimate_cycle: @cycle}
+
+ html = render_monthly_pageview_usage(usage, 10_000)
+
+ assert html =~ "/example.com/?period=custom"
+ assert html =~ "/app.example.com/?period=custom"
+ end
+
+ test "dashboard links include the billing cycle date range" do
+ usage = %{current_cycle: @cycle, last_cycle: @cycle, penultimate_cycle: @cycle}
+
+ html =
+ render_monthly_pageview_usage(usage, 10_000,
+ total_pageview_usage_domain: "my-site.example.com"
+ )
+
+ assert html =~
+ "/my-site.example.com/?period=custom&from=2024-01-01&to=2024-01-31"
+ end
end
defp render_progress_bar(usage, limit) do
@@ -334,13 +383,18 @@ defmodule PlausibleWeb.Components.BillingTest do
|> rendered_to_string()
end
- defp render_monthly_pageview_usage(usage, limit) do
- assigns = %{usage: usage, limit: limit}
+ defp render_monthly_pageview_usage(usage, limit, opts \\ []) do
+ assigns = %{
+ usage: usage,
+ limit: limit,
+ total_pageview_usage_domain: opts[:total_pageview_usage_domain]
+ }
~H"""
"""
|> rendered_to_string()
diff --git a/test/plausible_web/controllers/settings_controller_test.exs b/test/plausible_web/controllers/settings_controller_test.exs
index 48ea0d5287d5..102b54c60b32 100644
--- a/test/plausible_web/controllers/settings_controller_test.exs
+++ b/test/plausible_web/controllers/settings_controller_test.exs
@@ -532,6 +532,57 @@ defmodule PlausibleWeb.SettingsControllerTest do
assert html =~ "Invoices"
assert text(html) =~ "We couldn't retrieve your invoices"
end
+
+ @tag :ee_only
+ test "shows dashboard link to the site when team has exactly one site", %{
+ conn: conn,
+ user: user
+ } do
+ new_site(owner: user)
+
+ html =
+ conn
+ |> get(Routes.settings_path(conn, :subscription))
+ |> html_response(200)
+
+ assert element_exists?(html, "[data-test-id='total-pageviews-dashboard-link']")
+ end
+
+ @tag :ee_only
+ test "shows no total dashboard link when team has multiple sites and no consolidated view", %{
+ conn: conn,
+ user: user
+ } do
+ new_site(owner: user)
+ new_site(owner: user)
+
+ html =
+ conn
+ |> get(Routes.settings_path(conn, :subscription))
+ |> html_response(200)
+
+ refute element_exists?(html, "[data-test-id='total-pageviews-dashboard-link']")
+ end
+
+ on_ee do
+ test "shows consolidated view dashboard link when team has a consolidated view", %{
+ conn: conn,
+ user: user
+ } do
+ new_site(owner: user)
+ new_site(owner: user)
+ team = team_of(user)
+ new_consolidated_view(team)
+
+ html =
+ conn
+ |> set_current_team(team)
+ |> get(Routes.settings_path(conn, :subscription))
+ |> html_response(200)
+
+ assert element_exists?(html, "[data-test-id='total-pageviews-dashboard-link']")
+ end
+ end
end
describe "GET /security" do