From f3c532bf811b61ad904a4f80de6b9fe260c4ad8e Mon Sep 17 00:00:00 2001 From: Robert Joonas Date: Tue, 14 Jul 2026 15:20:46 +0100 Subject: [PATCH 01/11] make verification work in a banner --- assets/js/dashboard/index.tsx | 6 +- .../dashboard/stats/graph/visitor-graph.tsx | 2 +- assets/js/dashboard/verification/portal.tsx | 9 + extra/lib/plausible_web/live/verification.ex | 128 ++++----- .../controllers/stats_controller.ex | 35 ++- lib/plausible_web/live/awaiting_pageviews.ex | 99 ------- .../live/components/verification.ex | 257 +++++++++--------- lib/plausible_web/live/installation.ex | 23 +- lib/plausible_web/live/sites.ex | 25 +- lib/plausible_web/router.ex | 9 - .../templates/stats/stats.html.heex | 6 + .../controllers/stats_controller_test.exs | 59 ++-- .../live/components/verification_test.exs | 14 +- test/plausible_web/live/installation_test.exs | 53 ++-- test/plausible_web/live/verification_test.exs | 96 +++---- 15 files changed, 388 insertions(+), 433 deletions(-) create mode 100644 assets/js/dashboard/verification/portal.tsx delete mode 100644 lib/plausible_web/live/awaiting_pageviews.ex diff --git a/assets/js/dashboard/index.tsx b/assets/js/dashboard/index.tsx index 1736f963d227..b4c8e0c1cb15 100644 --- a/assets/js/dashboard/index.tsx +++ b/assets/js/dashboard/index.tsx @@ -11,6 +11,7 @@ import { isRealTimeDashboard } from './util/filters' import { GraphIntervalProvider } from './stats/graph/graph-interval-context' import { ImportsIncludedProvider } from './stats/graph/imports-included-context' import { CurrentVisitorsProvider } from './current-visitors-context' +import { VerificationLiveViewPortal } from './verification/portal' function DashboardStats({ importedDataInView, @@ -21,7 +22,10 @@ function DashboardStats({ }) { return ( <> - +
+ + +
diff --git a/assets/js/dashboard/stats/graph/visitor-graph.tsx b/assets/js/dashboard/stats/graph/visitor-graph.tsx index e16a9d955eef..1483c712ad7e 100644 --- a/assets/js/dashboard/stats/graph/visitor-graph.tsx +++ b/assets/js/dashboard/stats/graph/visitor-graph.tsx @@ -113,7 +113,7 @@ export default function VisitorGraph({ !showFullLoader return ( -
+
<>
{ + return
+ + }, + () => true +) diff --git a/extra/lib/plausible_web/live/verification.ex b/extra/lib/plausible_web/live/verification.ex index 0fc148d3a2b0..21e4179aeb2a 100644 --- a/extra/lib/plausible_web/live/verification.ex +++ b/extra/lib/plausible_web/live/verification.ex @@ -1,8 +1,7 @@ defmodule PlausibleWeb.Live.Verification do @moduledoc """ - LiveView coordinating the site verification process. - Onboarding new sites, renders a standalone component. - Embedded modal variant is available for general site settings. + LiveView coordinating the site verification process. Rendered as a banner + on top of the React dashboard. """ use PlausibleWeb, :live_view @@ -12,10 +11,11 @@ defmodule PlausibleWeb.Live.Verification do @component PlausibleWeb.Live.Components.Verification @slowdown_for_frequent_checking :timer.seconds(5) + @use_portal? Mix.env() not in [:test, :ce_test] def mount( - %{"domain" => domain} = params, - _session, + _params, + %{"domain" => domain} = session, socket ) do current_user = socket.assigns.current_user @@ -38,8 +38,6 @@ defmodule PlausibleWeb.Live.Verification do super_admin? = Plausible.Auth.super_admin?(current_user) has_pageviews? = has_pageviews?(site) - custom_url_input? = params["custom_url"] == "true" - socket = assign(socket, url_to_verify: nil, @@ -48,18 +46,18 @@ defmodule PlausibleWeb.Live.Verification do domain: domain, has_pageviews?: has_pageviews?, component: @component, - installation_type: get_installation_type(params, site), + installation_type: get_installation_type(session["installation_type"], site), report_to: self(), delay: private[:delay] || 500, slowdown: private[:slowdown] || 500, - flow: params["flow"] || "", + flow: session["flow"] || "", checks_pid: nil, attempts: 0, polling_pageviews?: false, - custom_url_input?: custom_url_input? + custom_url_input?: false ) - if connected?(socket) and not custom_url_input? do + if connected?(socket) do launch_delayed(socket) end @@ -67,8 +65,23 @@ defmodule PlausibleWeb.Live.Verification do end def render(assigns) do + assigns = assign(assigns, :use_portal?, @use_portal?) + + ~H""" +
+ <%= if @use_portal? do %> + <.portal id="verification-portal-source" target="#verification-portal-target"> + <.verification_content {assigns} /> + + <% else %> + <.verification_content {assigns} /> + <% end %> +
+ """ + end + + defp verification_content(assigns) do ~H""" - <.custom_url_form :if={@custom_url_input?} domain={@domain} /> <.live_component :if={not @custom_url_input?} @@ -94,6 +107,10 @@ defmodule PlausibleWeb.Live.Verification do {:noreply, reset_component(socket)} end + def handle_event("show-custom-url-form", _, socket) do + {:noreply, assign(socket, custom_url_input?: true)} + end + def handle_event("verify-custom-url", %{"custom_url" => custom_url}, socket) do socket = socket @@ -166,24 +183,24 @@ defmodule PlausibleWeb.Live.Verification do end def handle_info(:check_pageviews, socket) do - socket = - if has_pageviews?(socket.assigns.site) do - redirect_to_stats(socket) - else + if has_pageviews?(socket.assigns.site) do + {:noreply, assign(socket, has_pageviews?: true, polling_pageviews?: false)} + else + socket = socket |> assign(polling_pageviews?: false) |> schedule_pageviews_check() - end - {:noreply, socket} + {:noreply, socket} + end end @supported_installation_types_atoms PlausibleWeb.Tracker.supported_installation_types() |> Enum.map(&String.to_atom/1) - defp get_installation_type(params, site) do + defp get_installation_type(installation_type, site) do cond do - params["installation_type"] in PlausibleWeb.Tracker.supported_installation_types() -> - params["installation_type"] + installation_type in PlausibleWeb.Tracker.supported_installation_types() -> + installation_type (saved_installation_type = get_saved_installation_type(site)) in @supported_installation_types_atoms -> Atom.to_string(saved_installation_type) @@ -212,11 +229,6 @@ defmodule PlausibleWeb.Live.Verification do end end - defp redirect_to_stats(socket) do - stats_url = Routes.stats_path(PlausibleWeb.Endpoint, :stats, socket.assigns.domain, []) - redirect(socket, to: stats_url) - end - defp reset_component(socket) do update_component(socket, message: "We're visiting your site to ensure that everything is working", @@ -245,44 +257,32 @@ defmodule PlausibleWeb.Live.Verification do defp custom_url_form(assigns) do ~H""" - <.focus_box> -
- -
-
-

- Enter Your Custom URL -

-

- Please enter the URL where your website with the Plausible script is located. -

-
-
- - -
- -
-
- + <.notice title="Enter your custom URL" theme={:gray} class="mb-4"> + <:icon> + + +

+ Please enter the URL where your website with the Plausible script is located. +

+
+ + + +
+ """ end end diff --git a/lib/plausible_web/controllers/stats_controller.ex b/lib/plausible_web/controllers/stats_controller.ex index 38e9169f66f9..f96cd2f94855 100644 --- a/lib/plausible_web/controllers/stats_controller.ex +++ b/lib/plausible_web/controllers/stats_controller.ex @@ -70,9 +70,6 @@ defmodule PlausibleWeb.StatsController do team_identifier = site.team.identifier - skip_to_dashboard? = - conn.params["skip_to_dashboard"] == "true" or consolidated_view? - {:ok, segments} = Plausible.Segments.get_all_for_site(site, site_role) segments = Enum.map(segments, &Plausible.Segments.to_response_map(&1, site)) @@ -80,9 +77,19 @@ defmodule PlausibleWeb.StatsController do consolidated_view? and not consolidated_view_available? and site_role != :super_admin -> redirect(conn, to: Routes.site_path(conn, :index)) - (stats_start_date && can_see_stats?) || (can_see_stats? && skip_to_dashboard?) -> + not can_see_stats? -> + site = Plausible.Repo.preload(site, :owners) + render(conn, "site_locked.html", site: site, dogfood_page_path: dogfood_page_path) + + true -> flags = get_flags(current_user, site) + verify_installation? = + ee?() and + not is_nil(current_user) and + not consolidated_view? and + conn.params["verify_installation"] == "true" + conn |> put_resp_header("x-robots-tag", "noindex, nofollow") |> render("stats.html", @@ -106,15 +113,15 @@ defmodule PlausibleWeb.StatsController do exploration_journey_end_event: exploration_journey_end_event, exploration_max_journey_steps: exploration_max_journey_steps, team_identifier: team_identifier, - limited_to_segment_id: nil + limited_to_segment_id: nil, + connect_live_socket: verify_installation?, + verify_installation?: verify_installation?, + verification_session: %{ + "domain" => site.domain, + "flow" => conn.params["flow"], + "installation_type" => conn.params["installation_type"] + } ) - - !stats_start_date && can_see_stats? -> - redirect(conn, to: Routes.site_path(conn, :verification, site.domain)) - - Teams.locked?(site.team) -> - site = Plausible.Repo.preload(site, :owners) - render(conn, "site_locked.html", site: site, dogfood_page_path: dogfood_page_path) end end @@ -422,7 +429,9 @@ defmodule PlausibleWeb.StatsController do exploration_journey_end_event: exploration_journey_end_event, exploration_max_journey_steps: exploration_max_journey_steps, team_identifier: team_identifier, - limited_to_segment_id: limited_to_segment_id + limited_to_segment_id: limited_to_segment_id, + verify_installation?: false, + verification_session: %{} ) end end diff --git a/lib/plausible_web/live/awaiting_pageviews.ex b/lib/plausible_web/live/awaiting_pageviews.ex deleted file mode 100644 index 29c4f3e731d4..000000000000 --- a/lib/plausible_web/live/awaiting_pageviews.ex +++ /dev/null @@ -1,99 +0,0 @@ -defmodule PlausibleWeb.Live.AwaitingPageviews do - @moduledoc """ - A replacement for installation verification on Community Edition. - """ - use PlausibleWeb, :live_view - - import PlausibleWeb.Components.Generic - - def mount( - %{"domain" => domain} = params, - _session, - socket - ) do - current_user = socket.assigns.current_user - - site = - Plausible.Sites.get_for_user!(current_user, domain, - roles: [ - :owner, - :admin, - :editor, - :super_admin, - :viewer - ] - ) - - private = Map.get(socket.private.connect_info, :private, %{}) - - has_pageviews? = has_pageviews?(site) - - socket = - assign(socket, - site: site, - domain: domain, - has_pageviews?: has_pageviews?, - delay: private[:delay] || 500, - flow: params["flow"] || "", - polling_pageviews?: false - ) - - socket = - if has_pageviews? do - redirect_to_stats(socket) - else - schedule_pageviews_check(socket) - end - - {:ok, socket} - end - - def render(assigns) do - ~H""" - - <.awaiting_pageviews /> - """ - end - - defp awaiting_pageviews(assigns) do - ~H""" - <.focus_box> -
-
-

Awaiting your first pageview …

-
- - """ - end - - def handle_info(:check_pageviews, socket) do - socket = - if has_pageviews?(socket.assigns.site) do - redirect_to_stats(socket) - else - socket - |> assign(polling_pageviews?: false) - |> schedule_pageviews_check() - end - - {:noreply, socket} - end - - defp schedule_pageviews_check(socket) do - if socket.assigns.polling_pageviews? do - socket - else - Process.send_after(self(), :check_pageviews, socket.assigns.delay * 2) - assign(socket, polling_pageviews?: true) - end - end - - defp redirect_to_stats(socket) do - stats_url = Routes.stats_path(PlausibleWeb.Endpoint, :stats, socket.assigns.domain, []) - redirect(socket, to: stats_url) - end - - defp has_pageviews?(site) do - Plausible.Stats.Clickhouse.has_pageviews?(site) - end -end diff --git a/lib/plausible_web/live/components/verification.ex b/lib/plausible_web/live/components/verification.ex index ef8113d0a0a8..a4fd779d3880 100644 --- a/lib/plausible_web/live/components/verification.ex +++ b/lib/plausible_web/live/components/verification.ex @@ -1,7 +1,7 @@ defmodule PlausibleWeb.Live.Components.Verification do @moduledoc """ This component is responsible for rendering the verification progress - and diagnostics. + and diagnostics as a compact banner on top of the dashboard. """ use Phoenix.LiveComponent use Plausible @@ -11,6 +11,17 @@ defmodule PlausibleWeb.Live.Components.Verification do import PlausibleWeb.Components.Generic + @container_id "verification-ui" + # Dismissing hides the banner immediately and strips `verify_installation` + # from the URL (the same param that got it rendered in the first place - + # see PlausibleWeb.StatsController), so a refresh doesn't bring it back. + @dismiss_onclick "document.getElementById('#{@container_id}').classList.add('hidden');" <> + "var u = new window.URL(window.location.href);" <> + "u.searchParams.delete('verify_installation');" <> + "u.searchParams.delete('installation_type');" <> + "u.searchParams.delete('flow');" <> + "window.history.replaceState(null, '', u);" + attr(:domain, :string, required: true) attr(:message, :string, @@ -28,13 +39,28 @@ defmodule PlausibleWeb.Live.Components.Verification do attr(:awaiting_first_pageview?, :boolean, default: false) def render(assigns) do + assigns = + assigns + |> assign(:dismiss_onclick, @dismiss_onclick) + |> assign(:container_id, @container_id) + ~H""" -
+
+ <.render_progress :if={not @finished?} message={@message} /> <.render_success :if={@finished? and @success?} awaiting_first_pageview?={@awaiting_first_pageview?} domain={@domain} + super_admin?={@super_admin?} + verification_state={@verification_state} /> <.render_failed :if={@finished? and not @success?} @@ -43,9 +69,7 @@ defmodule PlausibleWeb.Live.Components.Verification do domain={@domain} flow={@flow} installation_type={@installation_type} - /> - <.render_super_admin_diagnostics - :if={not is_nil(@verification_state) && @super_admin? && @finished?} + super_admin?={@super_admin?} verification_state={@verification_state} />
@@ -54,149 +78,122 @@ defmodule PlausibleWeb.Live.Components.Verification do defp render_progress(assigns) do ~H""" - <.focus_box> -
-
-
-
- <.title>Verifying your installation -

{@message}

-
- + <.notice title="Verifying your installation" theme={:gray}> + <:icon> +
+ +

{@message}

+ """ end defp render_success(assigns) do ~H""" - <.focus_box> -
- -
- -
- <.title>Success! -

- Your installation is working and visitors are being counted accurately. - - Awaiting your first pageview... - -

-
- <.button_link - href={"/#{URI.encode_www_form(@domain)}?skip_to_dashboard=true"} - class="w-full font-bold mb-4" - > - Go to the dashboard - - + <.notice title="Success!" theme={:gray} icon_class="text-green-600 dark:text-green-500"> + <:icon> + + + Your installation is working and visitors are being counted accurately. + + Awaiting your first pageview... + + <.super_admin_diagnostics + :if={@super_admin? and not is_nil(@verification_state)} + verification_state={@verification_state} + /> + """ end defp render_failed(assigns) do ~H""" - <.focus_box> -
- -
- -
- <.title>{List.first(@interpretation.errors)} -

- {List.first(@interpretation.recommendations).text}.  - <.styled_link href={List.first(@interpretation.recommendations).url} new_tab={true}> - Learn more - -

-
- -
- <.button_link mt?={false} href="#" phx-click="retry" class="w-full"> + <.notice + title={ + if @interpretation, + do: List.first(@interpretation.errors), + else: "We couldn't verify your installation" + } + theme={:red} + > + <:icon> + + + <:actions> + <.button_link mt?={false} href="#" phx-click="retry" size="sm"> Verify installation again -
- <:footer> - <.focus_list> - <:item :if={ - @interpretation && is_map(@interpretation.data) && - @interpretation.data[:offer_custom_url_input] + +

+ {List.first(@interpretation.recommendations).text}.  + <.styled_link href={List.first(@interpretation.recommendations).url} new_tab={true}> + Learn more + +

+

+ + Is your website located at a different URL? + <.styled_link href="#" phx-click="show-custom-url-form" id="verify-custom-url-link"> + Click here + + + = 3}> + Need further help with your installation? + <.styled_link href="https://plausible.io/contact"> + Contact us + + + + Need to see installation instructions again? + <.styled_link href={ + Routes.site_path(PlausibleWeb.Endpoint, :installation, @domain, + flow: @flow, + installation_type: @installation_type + ) }> - - Is your website located at a different URL? - <.styled_link href={ - Routes.site_path(PlausibleWeb.Endpoint, :verification, @domain, - flow: @flow, - installation_type: @installation_type, - custom_url: true - ) - }> - Click here - - - - <:item :if={ee?() and @attempts >= 3}> - Need further help with your installation? - <.styled_link href="https://plausible.io/contact"> - Contact us - - - <:item> - Need to see installation instructions again? - <.styled_link href={ - Routes.site_path(PlausibleWeb.Endpoint, :installation, @domain, - flow: @flow, - installation_type: @installation_type - ) - }> - Click here - - - <:item> - Run verification later and go to site settings? - <.styled_link href={"/#{URI.encode_www_form(@domain)}/settings/general"}> - Click here - - - - - + Click here + + +

+ <.super_admin_diagnostics + :if={@super_admin? and not is_nil(@verification_state)} + verification_state={@verification_state} + /> + """ end - defp render_super_admin_diagnostics(assigns) do + defp super_admin_diagnostics(assigns) do ~H""" - <.focus_box> -
-

- - As a super-admin, you're eligible to see diagnostics details. Click to expand. - -

-
- <.focus_list> - <:item :for={{diag, value} <- Map.from_struct(@verification_state.diagnostics)}> - - {Phoenix.Naming.humanize(diag)}: - {to_string_value(value)} - - - -
+
+

+ + As a super-admin, you're eligible to see diagnostics details. Click to expand. + +

+
+ <.focus_list> + <:item :for={{diag, value} <- Map.from_struct(@verification_state.diagnostics)}> + + {Phoenix.Naming.humanize(diag)}: {to_string_value(value)} + + +
- +
""" end diff --git a/lib/plausible_web/live/installation.ex b/lib/plausible_web/live/installation.ex index 4e865013b9ff..86126c09f179 100644 --- a/lib/plausible_web/live/installation.ex +++ b/lib/plausible_web/live/installation.ex @@ -324,14 +324,21 @@ defmodule PlausibleWeb.Live.Installation do :installation ) - {:noreply, - push_navigate(socket, - to: - Routes.site_path(socket, :verification, socket.assigns.site.domain, - flow: socket.assigns.flow, - installation_type: config.installation_type - ) - )} + domain = socket.assigns.site.domain + + destination = + on_ee do + Routes.stats_path(socket, :stats, domain, + verify_installation: true, + flow: socket.assigns.flow, + installation_type: config.installation_type + ) + else + _need_to_use_variable_on_ce = config + Routes.stats_path(socket, :stats, domain, []) + end + + {:noreply, push_navigate(socket, to: destination)} end defp initialize_installation_data(flow, site, params) do diff --git a/lib/plausible_web/live/sites.ex b/lib/plausible_web/live/sites.ex index 11217bd370a1..911835aacb1a 100644 --- a/lib/plausible_web/live/sites.ex +++ b/lib/plausible_web/live/sites.ex @@ -533,6 +533,13 @@ defmodule PlausibleWeb.Live.Sites do attr(:sparkline, :map, required: true) def site(assigns) do + assigns = + assign( + assigns, + :needs_verification?, + ee?() and is_nil(Plausible.Sites.stats_start_date(assigns.site)) + ) + ~H"""
  • <.unstyled_link - href={Routes.stats_path(PlausibleWeb.Endpoint, :stats, @site.domain, [])} + href={ + Routes.stats_path( + PlausibleWeb.Endpoint, + :stats, + @site.domain, + if(@needs_verification?, + do: [verify_installation: true, flow: PlausibleWeb.Flows.provisioning()], + else: [] + ) + ) + } class="block group-has-[.phx-click-loading]/sort:animate-pulse group-has-[.phx-click-loading]/sort:pointer-events-none" >
    @@ -565,6 +582,12 @@ defmodule PlausibleWeb.Live.Sites do > {@site.domain} + + Setup pending +
  • <.site_stats sparkline={@sparkline} /> diff --git a/lib/plausible_web/router.ex b/lib/plausible_web/router.ex index 6c1439f81f25..b88211a93809 100644 --- a/lib/plausible_web/router.ex +++ b/lib/plausible_web/router.ex @@ -602,15 +602,6 @@ defmodule PlausibleWeb.Router do live "/:domain/installation", Installation, :installation, as: :site end - scope assigns: %{ - dogfood_page_path: "/:website/verification" - } do - live "/:domain/verification", - on_ee(do: Verification, else: AwaitingPageviews), - :verification, - as: :site - end - scope assigns: %{ dogfood_page_path: "/:website/change-domain" } do diff --git a/lib/plausible_web/templates/stats/stats.html.heex b/lib/plausible_web/templates/stats/stats.html.heex index 6a8058bb7d73..aceb45a07ff2 100644 --- a/lib/plausible_web/templates/stats/stats.html.heex +++ b/lib/plausible_web/templates/stats/stats.html.heex @@ -57,6 +57,12 @@ data-limited-to-segment-id={Jason.encode!(@limited_to_segment_id)} >
    + <%= if @verify_installation? do %> + {live_render(@conn, PlausibleWeb.Live.Verification, + id: "live-verification", + session: @verification_session + )} + <% end %> <%= if ee?() && !@conn.assigns[:current_user] && @conn.assigns[:demo] do %>
    diff --git a/test/plausible_web/controllers/stats_controller_test.exs b/test/plausible_web/controllers/stats_controller_test.exs index 38155954a91d..fab0eef0ba75 100644 --- a/test/plausible_web/controllers/stats_controller_test.exs +++ b/test/plausible_web/controllers/stats_controller_test.exs @@ -104,28 +104,28 @@ defmodule PlausibleWeb.StatsControllerTest do assert resp =~ "Getting started" end - test "public site - redirect to /login when no stats because verification requires it", %{ - conn: conn - } do + test "public site - shows an empty dashboard without stats (no verification banner)", + %{ + conn: conn + } do new_site(domain: "some-other-public-site.io", public: true) - conn = get(conn, conn |> get("/some-other-public-site.io") |> redirected_to()) + resp = get(conn, "/some-other-public-site.io") |> html_response(200) - assert redirected_to(conn) == - Routes.auth_path(conn, :login_form, - return_to: "/some-other-public-site.io/verification" - ) + refute resp =~ "Verifying your installation" end - test "public site - no stats with skip_to_dashboard", %{ - conn: conn - } do + test "public site - anonymous visitors never see the verification banner, even with the param", + %{ + conn: conn + } do new_site(domain: "some-other-public-site.io", public: true) - conn = get(conn, "/some-other-public-site.io?skip_to_dashboard=true") - resp = html_response(conn, 200) + resp = + get(conn, "/some-other-public-site.io?verify_installation=true") |> html_response(200) assert text_of_attr(resp, @react_container, "data-logged-in") == "false" + refute resp =~ "Verifying your installation" end test "can not view stats of a private website", %{conn: conn} do @@ -147,15 +147,18 @@ defmodule PlausibleWeb.StatsControllerTest do assert text_of_attr(resp, @react_container, "data-current-user-id") == "#{user.id}" end - test "can view stats of a website I've created, enforcing pageviews check skip", %{ - conn: conn, - site: site - } do - resp = conn |> get(conn |> get("/" <> site.domain) |> redirected_to()) |> html_response(200) - refute text_of_attr(resp, @react_container, "data-logged-in") == "true" + test "can view stats of a website I've created; verification banner only shows with the explicit param", + %{ + conn: conn, + site: site + } do + resp = get(conn, "/" <> site.domain) |> html_response(200) + assert text_of_attr(resp, @react_container, "data-logged-in") == "true" + refute resp =~ "Verifying your installation" - resp = conn |> get("/" <> site.domain <> "?skip_to_dashboard=true") |> html_response(200) + resp = conn |> get("/" <> site.domain <> "?verify_installation=true") |> html_response(200) assert text_of_attr(resp, @react_container, "data-logged-in") == "true" + assert resp =~ "Verifying your installation" end on_ee do @@ -334,9 +337,9 @@ defmodule PlausibleWeb.StatsControllerTest do end test "does not show CRM link to the site", %{conn: conn, site: site} do - conn = get(conn, conn |> get("/" <> site.domain) |> redirected_to()) + resp = get(conn, "/" <> site.domain) |> html_response(200) - refute html_response(conn, 200) =~ "/cs/sites" + refute resp =~ "/cs/sites" end test "all segments (personal or site) are stuffed into dataset, with their associated owner_id and owner_name", @@ -390,8 +393,8 @@ defmodule PlausibleWeb.StatsControllerTest do test "can enter verification when site is without stats", %{conn: conn} do site = new_site() - conn = get(conn, conn |> get("/" <> site.domain) |> redirected_to()) - assert html_response(conn, 200) =~ "Verifying your installation" + resp = get(conn, "/#{site.domain}?verify_installation=true") |> html_response(200) + assert resp =~ "Verifying your installation" end test "can view a private locked dashboard with stats", %{conn: conn} do @@ -410,8 +413,8 @@ defmodule PlausibleWeb.StatsControllerTest do site = new_site(owner: user) site.team |> Ecto.Changeset.change(locked: true) |> Repo.update!() - conn = get(conn, conn |> get("/#{site.domain}") |> redirected_to()) - assert html_response(conn, 200) =~ "Verifying your installation" + resp = get(conn, "/#{site.domain}?verify_installation=true") |> html_response(200) + assert resp =~ "Verifying your installation" end test "can view a locked public dashboard", %{conn: conn} do @@ -427,9 +430,9 @@ defmodule PlausibleWeb.StatsControllerTest do on_ee do test "shows CRM link to the site", %{conn: conn} do site = new_site() - conn = get(conn, conn |> get("/" <> site.domain) |> redirected_to()) + resp = get(conn, "/" <> site.domain) |> html_response(200) - assert html_response(conn, 200) =~ + assert resp =~ Routes.customer_support_site_path(PlausibleWeb.Endpoint, :show, site.id) end end diff --git a/test/plausible_web/live/components/verification_test.exs b/test/plausible_web/live/components/verification_test.exs index 2645de32cc4e..bf3d6a1a479d 100644 --- a/test/plausible_web/live/components/verification_test.exs +++ b/test/plausible_web/live/components/verification_test.exs @@ -112,7 +112,7 @@ defmodule PlausibleWeb.Live.Components.VerificationTest do assert element_exists?(html, ~s|a[href="https://plausible.io/contact"]|) end - test "renders link to verify installation at a different URL" do + test "renders a click-to-show-form link to verify installation at a different URL" do interpretation = Verification.Checks.interpret_diagnostics(%State{ url: "example.com", @@ -125,9 +125,6 @@ defmodule PlausibleWeb.Live.Components.VerificationTest do assert interpretation.data.offer_custom_url_input == true - expected_link_href = - PlausibleWeb.Router.Helpers.site_path(PlausibleWeb.Endpoint, :verification, "example.com") - html = render_component(@component, domain: "example.com", @@ -136,12 +133,11 @@ defmodule PlausibleWeb.Live.Components.VerificationTest do interpretation: interpretation ) - assert text_of_element(html, "#verify-custom-url-link") =~ "different URL?" - assert text_of_attr(html, "#verify-custom-url-link a", "href") =~ expected_link_href - assert text_of_attr(html, "#verify-custom-url-link a", "href") =~ "custom_url=true" + assert text_of_element(html, "#verify-custom-url-link") =~ "Click here" + assert element_exists?(html, ~s|a#verify-custom-url-link[phx-click="show-custom-url-form"]|) end - test "offers escape paths: settings and installation instructions on failure" do + test "offers an installation-instructions escape path on failure, no more settings link" do html = render_component(@component, domain: "example.com", @@ -151,7 +147,7 @@ defmodule PlausibleWeb.Live.Components.VerificationTest do flow: PlausibleWeb.Flows.review() ) - assert element_exists?(html, ~s|a[href="/example.com/settings/general"]|) + refute element_exists?(html, ~s|a[href="/example.com/settings/general"]|) assert element_exists?( html, diff --git a/test/plausible_web/live/installation_test.exs b/test/plausible_web/live/installation_test.exs index a3ac3aae5f06..1dd3b0b42874 100644 --- a/test/plausible_web/live/installation_test.exs +++ b/test/plausible_web/live/installation_test.exs @@ -246,10 +246,11 @@ defmodule PlausibleWeb.Live.InstallationTest do {"gtm", "Verify Tag Manager installation"}, {"npm", "Verify NPM installation"} ] do - test "submitting form with #{type} redirects to verification (EE)", %{ - conn: conn, - site: site - } do + test "submitting form with #{type} redirects to the dashboard with the verification banner (EE)", + %{ + conn: conn, + site: site + } do stub_lookup_a_records(site.domain) stub_detection_manual() {lv, _html} = get_lv(conn, site, "?type=#{unquote(type)}") @@ -270,7 +271,8 @@ defmodule PlausibleWeb.Live.InstallationTest do assert_redirect( lv, - Routes.site_path(conn, :verification, site.domain, + Routes.stats_path(conn, :stats, site.domain, + verify_installation: true, flow: "provisioning", installation_type: unquote(type) ) @@ -280,7 +282,10 @@ defmodule PlausibleWeb.Live.InstallationTest do end @tag :ce_build_only - test "submitting the form redirects to verification (CE)", %{conn: conn, site: site} do + test "submitting the form redirects straight to the dashboard, no banner (CE)", %{ + conn: conn, + site: site + } do {lv, _html} = get_lv(conn, site) lv @@ -294,13 +299,7 @@ defmodule PlausibleWeb.Live.InstallationTest do } }) - assert_redirect( - lv, - Routes.site_path(conn, :verification, site.domain, - flow: "provisioning", - installation_type: "manual" - ) - ) + assert_redirect(lv, Routes.stats_path(conn, :stats, site.domain)) end test "404 goal gets created regardless of user options", %{conn: conn, site: site} do @@ -331,10 +330,11 @@ defmodule PlausibleWeb.Live.InstallationTest do assert Enum.any?(goals, &(&1.event_name == "404")) end - test "submitting form with review flow redirects to verification with flow param", %{ - conn: conn, - site: site - } do + test "submitting form with review flow redirects to the dashboard with the flow param preserved", + %{ + conn: conn, + site: site + } do on_ee do stub_lookup_a_records(site.domain) stub_detection_manual() @@ -356,13 +356,20 @@ defmodule PlausibleWeb.Live.InstallationTest do } }) - assert_redirect( - lv, - Routes.site_path(conn, :verification, site.domain, - flow: "review", - installation_type: "manual" + on_ee do + assert_redirect( + lv, + Routes.stats_path(conn, :stats, site.domain, + verify_installation: true, + flow: "review", + installation_type: "manual" + ) ) - ) + end + + on_ce do + assert_redirect(lv, Routes.stats_path(conn, :stats, site.domain)) + end end @tag :ee_only diff --git a/test/plausible_web/live/verification_test.exs b/test/plausible_web/live/verification_test.exs index 4580e932e96b..8e1c06e1e98d 100644 --- a/test/plausible_web/live/verification_test.exs +++ b/test/plausible_web/live/verification_test.exs @@ -9,18 +9,21 @@ defmodule PlausibleWeb.Live.VerificationTest do setup [:create_user, :log_in, :create_site] - # @verify_button ~s|button#launch-verification-button[phx-click="launch-verification"]| @retry_button ~s|a[phx-click="retry"]| - # @go_to_dashboard_button ~s|a[href$="?skip_to_dashboard=true"]| @progress ~s|#verification-ui p#progress| @awaiting ~s|#verification-ui span#awaiting| - @heading ~s|#verification-ui h2| + @heading ~s|#verification-ui h3| describe "GET /:domain" do @tag :ee_only - test "static verification screen renders", %{conn: conn, site: site} do + test "static verification banner renders on a freshly provisioned site", %{ + conn: conn, + site: site + } do resp = - get(conn, conn |> no_slowdown() |> get("/#{site.domain}") |> redirected_to) + conn + |> no_slowdown() + |> get("/#{site.domain}?verify_installation=true") |> html_response(200) assert text_of_element(resp, @progress) =~ @@ -30,12 +33,10 @@ defmodule PlausibleWeb.Live.VerificationTest do end @tag :ce_build_only - test "static verification screen renders (ce)", %{conn: conn, site: site} do - resp = - get(conn, conn |> no_slowdown() |> get("/#{site.domain}") |> redirected_to) - |> html_response(200) + test "no verification banner renders on CE", %{conn: conn, site: site} do + resp = get(conn, "/#{site.domain}") |> html_response(200) - assert resp =~ "Awaiting your first pageview …" + refute resp =~ "verification-ui" end end @@ -57,14 +58,11 @@ defmodule PlausibleWeb.Live.VerificationTest do "We're visiting your site to ensure that everything is working" end - @tag :ce_build_only - test "LiveView mounts (ce)", %{conn: conn, site: site} do - {_, html} = get_lv(conn, site) - assert html =~ "Awaiting your first pageview …" - end - @tag :ee_only - test "from custom URL input form to verification", %{conn: conn, site: site} do + test "clicking the custom URL link swaps in the form, submitting kicks off a new run", %{ + conn: conn, + site: site + } do stub_lookup_a_records(site.domain) stub_verification_result(%{ @@ -72,24 +70,21 @@ defmodule PlausibleWeb.Live.VerificationTest do "error" => %{"message" => "Error"} }) - # Get liveview with ?custom_url=true query param - {:ok, lv, html} = - conn |> no_slowdown() |> live("/#{site.domain}/verification?custom_url=true") + {lv, _html} = get_lv(conn, site) verifying_installation_text = "Verifying your installation" - # Assert form is rendered instead of kicking off verification automatically - assert html =~ "Enter Your Custom URL" + html = lv |> render_click("show-custom-url-form") + + assert html =~ "Enter your custom URL" assert html =~ ~s[value="https://#{site.domain}"] assert html =~ ~s[placeholder="https://#{site.domain}"] refute html =~ verifying_installation_text - # Submit custom URL form html = lv |> element("form") |> render_submit(%{"custom_url" => "https://abc.de"}) - # Should now show verification progress and hide custom URL form assert html =~ verifying_installation_text - refute html =~ "Enter Your Custom URL" + refute html =~ "Enter your custom URL" end @tag :ee_only @@ -161,10 +156,14 @@ defmodule PlausibleWeb.Live.VerificationTest do html = render(lv) refute text_of_element(html, @awaiting) =~ "Awaiting your first pageview" - refute_redirected(lv, "/#{URI.encode_www_form(site.domain)}/") + refute_redirected(lv, "/#{URI.encode_www_form(site.domain)}") end - test "will redirect when first pageview arrives", %{conn: conn, site: site} do + @tag :ee_only + test "shows success in place (no redirect) once first pageview arrives", %{ + conn: conn, + site: site + } do stub_lookup_a_records(site.domain) stub_verification_result(%{ @@ -184,30 +183,19 @@ defmodule PlausibleWeb.Live.VerificationTest do assert eventually(fn -> html = render(lv) - - { - text(html) =~ "Awaiting", - html - } + {text(html) =~ "Awaiting", html} end) populate_stats(site, [ build(:pageview) ]) - assert_redirect(lv, Routes.stats_path(PlausibleWeb.Endpoint, :stats, site.domain)) - end - - @tag :ce_build_only - test "will redirect when first pageview arrives (ce)", %{conn: conn, site: site} do - {:ok, lv} = kick_off_live_verification(conn, site) - - html = render(lv) - assert text(html) =~ "Awaiting your first pageview …" - - populate_stats(site, [build(:pageview)]) + assert eventually(fn -> + html = render(lv) + {not (text(html) =~ "Awaiting your first pageview"), html} + end) - assert_redirect(lv, Routes.stats_path(PlausibleWeb.Endpoint, :stats, site.domain)) + refute_redirected(lv, "/#{URI.encode_www_form(site.domain)}") end for {installation_type_param, expected_text, saved_installation_type} <- [ @@ -261,7 +249,7 @@ defmodule PlausibleWeb.Live.VerificationTest do kick_off_live_verification( conn, site, - "?installation_type=#{unquote(installation_type_param)}" + "installation_type=#{unquote(installation_type_param)}" ) assert html = @@ -285,17 +273,31 @@ defmodule PlausibleWeb.Live.VerificationTest do end defp get_lv(conn, site, qs \\ nil) do - {:ok, lv, html} = conn |> no_slowdown() |> live("/#{site.domain}/verification#{qs}") + {:ok, lv, html} = + conn |> no_slowdown() |> as_live() |> live(verification_path(site, qs)) + {lv, html} end defp kick_off_live_verification(conn, site, qs \\ nil) do {:ok, lv, _html} = - conn |> no_slowdown() |> no_delay() |> live("/#{site.domain}/verification#{qs}") + conn |> no_slowdown() |> no_delay() |> as_live() |> live(verification_path(site, qs)) {:ok, lv} end + # `PlausibleWeb.Live.Verification` is rendered via `live_render/3` from a + # plain controller-rendered page rather than a live router route, so it + # never carries the `:live_module` assign `Phoenix.LiveViewTest.live/2` + # looks for. Mirrors the same workaround used in other live_render-embedded + # LiveView tests (e.g. props_settings_test.exs). + defp as_live(conn), do: assign(conn, :live_module, PlausibleWeb.Live.Verification) + + defp verification_path(site, nil), do: "/#{site.domain}?verify_installation=true" + + defp verification_path(site, qs), + do: "/#{site.domain}?verify_installation=true&#{qs}" + defp no_slowdown(conn) do Plug.Conn.put_private(conn, :slowdown, 0) end From 54a620b4394559a412074b65d87b3a353d9fcf63 Mon Sep 17 00:00:00 2001 From: Robert Joonas Date: Wed, 15 Jul 2026 13:13:57 +0100 Subject: [PATCH 02/11] move custom url input into the banner --- extra/lib/plausible_web/live/verification.ex | 36 +------------ .../live/components/verification.ex | 27 +++++++++- .../live/components/verification_test.exs | 35 ++++++++++++ test/plausible_web/live/verification_test.exs | 54 +++++++++++++------ 4 files changed, 100 insertions(+), 52 deletions(-) diff --git a/extra/lib/plausible_web/live/verification.ex b/extra/lib/plausible_web/live/verification.ex index 21e4179aeb2a..7c1a2604f6a4 100644 --- a/extra/lib/plausible_web/live/verification.ex +++ b/extra/lib/plausible_web/live/verification.ex @@ -5,8 +5,6 @@ defmodule PlausibleWeb.Live.Verification do """ use PlausibleWeb, :live_view - import PlausibleWeb.Components.Generic - alias Plausible.InstallationSupport.{State, Verification} @component PlausibleWeb.Live.Components.Verification @@ -82,9 +80,7 @@ defmodule PlausibleWeb.Live.Verification do defp verification_content(assigns) do ~H""" - <.custom_url_form :if={@custom_url_input?} domain={@domain} /> <.live_component - :if={not @custom_url_input?} module={@component} installation_type={@installation_type} domain={@domain} @@ -93,6 +89,7 @@ defmodule PlausibleWeb.Live.Verification do flow={@flow} awaiting_first_pageview?={not @has_pageviews?} super_admin?={@super_admin?} + custom_url_input?={@custom_url_input?} /> """ end @@ -254,35 +251,4 @@ defmodule PlausibleWeb.Live.Verification do defp has_pageviews?(site) do Plausible.Stats.Clickhouse.has_pageviews?(site) end - - defp custom_url_form(assigns) do - ~H""" - <.notice title="Enter your custom URL" theme={:gray} class="mb-4"> - <:icon> - - -

    - Please enter the URL where your website with the Plausible script is located. -

    -
    - - - -
    - - """ - end end diff --git a/lib/plausible_web/live/components/verification.ex b/lib/plausible_web/live/components/verification.ex index a4fd779d3880..3692a7c2c75d 100644 --- a/lib/plausible_web/live/components/verification.ex +++ b/lib/plausible_web/live/components/verification.ex @@ -10,6 +10,7 @@ defmodule PlausibleWeb.Live.Components.Verification do alias Plausible.InstallationSupport.{State, Result} import PlausibleWeb.Components.Generic + import PlausibleWeb.Live.Components.Form @container_id "verification-ui" # Dismissing hides the banner immediately and strips `verify_installation` @@ -37,6 +38,7 @@ defmodule PlausibleWeb.Live.Components.Verification do attr(:flow, :string, default: "") attr(:installation_type, :string, default: nil) attr(:awaiting_first_pageview?, :boolean, default: false) + attr(:custom_url_input?, :boolean, default: false) def render(assigns) do assigns = @@ -71,6 +73,7 @@ defmodule PlausibleWeb.Live.Components.Verification do installation_type={@installation_type} super_admin?={@super_admin?} verification_state={@verification_state} + custom_url_input?={@custom_url_input?} />
    """ @@ -122,9 +125,29 @@ defmodule PlausibleWeb.Live.Components.Verification do /> <:actions> - <.button_link mt?={false} href="#" phx-click="retry" size="sm"> + <.button_link :if={not @custom_url_input?} mt?={false} href="#" phx-click="retry" size="sm"> Verify installation again +
    + <.input + type="url" + name="custom_url" + id="custom_url" + aria-label="Website URL" + required + mt?={false} + width="w-44" + placeholder={"https://#{@domain}"} + value={"https://#{@domain}"} + /> + <.button type="submit" mt?={false} size="sm"> + Verify installation again + +

    {List.first(@interpretation.recommendations).text}.  @@ -134,7 +157,7 @@ defmodule PlausibleWeb.Live.Components.Verification do

    Is your website located at a different URL? diff --git a/test/plausible_web/live/components/verification_test.exs b/test/plausible_web/live/components/verification_test.exs index bf3d6a1a479d..f897ecbfbe7e 100644 --- a/test/plausible_web/live/components/verification_test.exs +++ b/test/plausible_web/live/components/verification_test.exs @@ -137,6 +137,41 @@ defmodule PlausibleWeb.Live.Components.VerificationTest do assert element_exists?(html, ~s|a#verify-custom-url-link[phx-click="show-custom-url-form"]|) end + test "renders the custom URL input inline, retry button becomes the form's submit button, hides the prompt link" do + interpretation = + Verification.Checks.interpret_diagnostics(%State{ + url: "example.com", + diagnostics: %Verification.Diagnostics{ + plausible_is_on_window: false, + plausible_is_initialized: false, + service_error: %{code: :domain_not_found} + } + }) + + html = + render_component(@component, + domain: "example.com", + finished?: true, + success?: false, + interpretation: interpretation, + custom_url_input?: true + ) + + refute element_exists?(html, "#verify-custom-url-link") + refute element_exists?(html, ~s|a[phx-click="retry"]|) + + assert text_of_element(html, ~s|form[phx-submit="verify-custom-url"] button[type="submit"]|) =~ + "Verify installation again" + + assert element_exists?( + html, + ~s|form[phx-submit="verify-custom-url"] input[name="custom_url"]| + ) + + assert text_of_attr(html, ~s|form[phx-submit="verify-custom-url"] input|, "value") =~ + "https://example.com" + end + test "offers an installation-instructions escape path on failure, no more settings link" do html = render_component(@component, diff --git a/test/plausible_web/live/verification_test.exs b/test/plausible_web/live/verification_test.exs index 8e1c06e1e98d..1181068b93ab 100644 --- a/test/plausible_web/live/verification_test.exs +++ b/test/plausible_web/live/verification_test.exs @@ -14,6 +14,8 @@ defmodule PlausibleWeb.Live.VerificationTest do @awaiting ~s|#verification-ui span#awaiting| @heading ~s|#verification-ui h3| + @in_progress_text "Verifying your installation" + describe "GET /:domain" do @tag :ee_only test "static verification banner renders on a freshly provisioned site", %{ @@ -29,7 +31,7 @@ defmodule PlausibleWeb.Live.VerificationTest do assert text_of_element(resp, @progress) =~ "We're visiting your site to ensure that everything is working" - assert resp =~ "Verifying your installation" + assert resp =~ @in_progress_text end @tag :ce_build_only @@ -52,39 +54,61 @@ defmodule PlausibleWeb.Live.VerificationTest do {_, html} = get_lv(conn, site) - assert html =~ "Verifying your installation" + assert html =~ @in_progress_text assert text_of_element(html, @progress) =~ "We're visiting your site to ensure that everything is working" end @tag :ee_only - test "clicking the custom URL link swaps in the form, submitting kicks off a new run", %{ - conn: conn, - site: site - } do + test "clicking the custom URL link reveals an inline form next to the retry button, submitting kicks off a new run", + %{ + conn: conn, + site: site + } do stub_lookup_a_records(site.domain) stub_verification_result(%{ - "completed" => false, - "error" => %{"message" => "Error"} + "completed" => true, + "trackerIsInHtml" => false, + "plausibleIsOnWindow" => false, + "plausibleIsInitialized" => false }) - {lv, _html} = get_lv(conn, site) + {:ok, lv} = kick_off_live_verification(conn, site) - verifying_installation_text = "Verifying your installation" + assert eventually(fn -> + html = render(lv) + + { + text_of_element(html, @heading) =~ "We couldn't detect Plausible on your site", + html + } + end) html = lv |> render_click("show-custom-url-form") - assert html =~ "Enter your custom URL" + refute html =~ @in_progress_text + + refute element_exists?(html, @retry_button) + refute element_exists?(html, "#verify-custom-url-link") + + assert element_exists?( + html, + ~s|form[phx-submit="verify-custom-url"] input[name="custom_url"]| + ) + assert html =~ ~s[value="https://#{site.domain}"] assert html =~ ~s[placeholder="https://#{site.domain}"] - refute html =~ verifying_installation_text - html = lv |> element("form") |> render_submit(%{"custom_url" => "https://abc.de"}) + lv + |> element("form[phx-submit='verify-custom-url']") + |> render_submit(%{"custom_url" => "https://abc.de"}) - assert html =~ verifying_installation_text - refute html =~ "Enter your custom URL" + assert eventually(fn -> + html = render(lv) + {html =~ @in_progress_text, html} + end) end @tag :ee_only From 19a325cf7b3d344e93cfc159bde8509398060323 Mon Sep 17 00:00:00 2001 From: Robert Joonas Date: Wed, 15 Jul 2026 14:01:05 +0100 Subject: [PATCH 03/11] improve notice styles --- extra/lib/plausible_web/live/verification.ex | 2 +- .../live/components/verification.ex | 100 ++++++++++-------- .../live/components/verification_test.exs | 12 +-- 3 files changed, 61 insertions(+), 53 deletions(-) diff --git a/extra/lib/plausible_web/live/verification.ex b/extra/lib/plausible_web/live/verification.ex index 7c1a2604f6a4..38330c26fff8 100644 --- a/extra/lib/plausible_web/live/verification.ex +++ b/extra/lib/plausible_web/live/verification.ex @@ -8,7 +8,7 @@ defmodule PlausibleWeb.Live.Verification do alias Plausible.InstallationSupport.{State, Verification} @component PlausibleWeb.Live.Components.Verification - @slowdown_for_frequent_checking :timer.seconds(5) + @slowdown_for_frequent_checking :timer.seconds(0) @use_portal? Mix.env() not in [:test, :ce_test] def mount( diff --git a/lib/plausible_web/live/components/verification.ex b/lib/plausible_web/live/components/verification.ex index 3692a7c2c75d..466e23678f50 100644 --- a/lib/plausible_web/live/components/verification.ex +++ b/lib/plausible_web/live/components/verification.ex @@ -83,7 +83,9 @@ defmodule PlausibleWeb.Live.Components.Verification do ~H""" <.notice title="Verifying your installation" theme={:gray}> <:icon> -

    +
    +
    +

    {@message}

    @@ -116,17 +118,27 @@ defmodule PlausibleWeb.Live.Components.Verification do do: List.first(@interpretation.errors), else: "We couldn't verify your installation" } - theme={:red} + theme={:yellow} > <:icon> - + - <:actions> - <.button_link :if={not @custom_url_input?} mt?={false} href="#" phx-click="retry" size="sm"> - Verify installation again +

    + {List.first(@interpretation.recommendations).text}.  + <.styled_link href={List.first(@interpretation.recommendations).url} new_tab={true}> + Learn more + +

    +
    + <.button_link + :if={not @custom_url_input?} + mt?={false} + href="#" + phx-click="retry" + theme="secondary" + size="sm" + > + Check again
    - <.button type="submit" mt?={false} size="sm"> - Verify installation again + <.button type="submit" mt?={false} theme="secondary" size="sm"> + Check again
    - -

    - {List.first(@interpretation.recommendations).text}.  - <.styled_link href={List.first(@interpretation.recommendations).url} new_tab={true}> - Learn more - -

    -

    - - Is your website located at a different URL? - <.styled_link href="#" phx-click="show-custom-url-form" id="verify-custom-url-link"> - Click here - - - = 3}> - Need further help with your installation? - <.styled_link href="https://plausible.io/contact"> - Contact us - - - - Need to see installation instructions again? - <.styled_link href={ - Routes.site_path(PlausibleWeb.Endpoint, :installation, @domain, - flow: @flow, - installation_type: @installation_type - ) +

    +
    +
      +
    • - Click here - - -

      + Is your website located at a different URL? + <.styled_link href="#" phx-click="show-custom-url-form" id="verify-custom-url-link"> + Click here + +
    • +
    • = 3}> + Need further help with your installation? + <.styled_link href="https://plausible.io/contact"> + Contact us + +
    • +
    • + Need to see installation instructions again? + <.styled_link href={ + Routes.site_path(PlausibleWeb.Endpoint, :installation, @domain, + flow: @flow, + installation_type: @installation_type + ) + }> + Click here + +
    • +
    +
    <.super_admin_diagnostics :if={@super_admin? and not is_nil(@verification_state)} verification_state={@verification_state} @@ -194,7 +202,7 @@ defmodule PlausibleWeb.Live.Components.Verification do defp super_admin_diagnostics(assigns) do ~H"""
    diff --git a/test/plausible_web/live/components/verification_test.exs b/test/plausible_web/live/components/verification_test.exs index f897ecbfbe7e..a9fda0c55af4 100644 --- a/test/plausible_web/live/components/verification_test.exs +++ b/test/plausible_web/live/components/verification_test.exs @@ -11,7 +11,7 @@ defmodule PlausibleWeb.Live.Components.VerificationTest do @component PlausibleWeb.Live.Components.Verification @progress ~s|#verification-ui p#progress| - @pulsating_circle ~s|div#verification-ui div.pulsating-circle| + @loading_spinner ~s|div#verification-ui div.loading| @check_circle ~s|div#verification-ui #check-circle| @error_circle ~s|div#verification-ui #error-circle| @recommendations ~s|#recommendation| @@ -24,8 +24,8 @@ defmodule PlausibleWeb.Live.Components.VerificationTest do assert text_of_element(html, @progress) == "We're visiting your site to ensure that everything is working" - assert element_exists?(html, @pulsating_circle) - refute class_of_element(html, @pulsating_circle) =~ "hidden" + assert element_exists?(html, @loading_spinner) + refute class_of_element(html, @loading_spinner) =~ "hidden" refute element_exists?(html, @recommendations) refute element_exists?(html, @check_circle) refute element_exists?(html, @super_admin_report) @@ -33,7 +33,7 @@ defmodule PlausibleWeb.Live.Components.VerificationTest do test "renders error badge on error" do html = render_component(@component, domain: "example.com", success?: false, finished?: true) - refute element_exists?(html, @pulsating_circle) + refute element_exists?(html, @loading_spinner) refute element_exists?(html, @check_circle) refute element_exists?(html, @recommendations) assert element_exists?(html, @error_circle) @@ -92,7 +92,7 @@ defmodule PlausibleWeb.Live.Components.VerificationTest do finished?: true ) - refute element_exists?(html, @pulsating_circle) + refute element_exists?(html, @loading_spinner) assert element_exists?(html, @check_circle) end @@ -161,7 +161,7 @@ defmodule PlausibleWeb.Live.Components.VerificationTest do refute element_exists?(html, ~s|a[phx-click="retry"]|) assert text_of_element(html, ~s|form[phx-submit="verify-custom-url"] button[type="submit"]|) =~ - "Verify installation again" + "Check again" assert element_exists?( html, From 16c96ae03531411416a2dd2a2d504a90f75faff1 Mon Sep 17 00:00:00 2001 From: Robert Joonas Date: Thu, 16 Jul 2026 13:23:38 +0100 Subject: [PATCH 04/11] expandable instructions --- extra/lib/plausible_web/live/verification.ex | 5 + lib/plausible_web/components/generic.ex | 32 +++ .../live/components/verification.ex | 230 +++++++++++++----- .../live/installation/instructions.ex | 102 ++++---- 4 files changed, 258 insertions(+), 111 deletions(-) diff --git a/extra/lib/plausible_web/live/verification.ex b/extra/lib/plausible_web/live/verification.ex index 38330c26fff8..2fa2b5dd2392 100644 --- a/extra/lib/plausible_web/live/verification.ex +++ b/extra/lib/plausible_web/live/verification.ex @@ -36,6 +36,9 @@ defmodule PlausibleWeb.Live.Verification do super_admin? = Plausible.Auth.super_admin?(current_user) has_pageviews? = has_pageviews?(site) + tracker_script_configuration = + PlausibleWeb.Tracker.get_or_create_tracker_script_configuration!(site) + socket = assign(socket, url_to_verify: nil, @@ -44,6 +47,7 @@ defmodule PlausibleWeb.Live.Verification do domain: domain, has_pageviews?: has_pageviews?, component: @component, + tracker_script_configuration: tracker_script_configuration, installation_type: get_installation_type(session["installation_type"], site), report_to: self(), delay: private[:delay] || 500, @@ -90,6 +94,7 @@ defmodule PlausibleWeb.Live.Verification do awaiting_first_pageview?={not @has_pageviews?} super_admin?={@super_admin?} custom_url_input?={@custom_url_input?} + tracker_script_configuration={@tracker_script_configuration} /> """ end diff --git a/lib/plausible_web/components/generic.ex b/lib/plausible_web/components/generic.ex index ffdf5277c06a..2dd458386723 100644 --- a/lib/plausible_web/components/generic.ex +++ b/lib/plausible_web/components/generic.ex @@ -850,6 +850,38 @@ defmodule PlausibleWeb.Components.Generic do """ end + attr :id, :string, required: true + attr :text, :string, required: true + attr :rows, :integer, required: true + attr :resizable, :boolean, default: false + + def copyable_readonly_text_area(assigns) do + ~H""" +
    + + + + + + COPY + + +
    + """ + end + slot :title slot :subtitle slot :inner_block, required: true diff --git a/lib/plausible_web/live/components/verification.ex b/lib/plausible_web/live/components/verification.ex index 466e23678f50..f55c66a10a57 100644 --- a/lib/plausible_web/live/components/verification.ex +++ b/lib/plausible_web/live/components/verification.ex @@ -7,7 +7,10 @@ defmodule PlausibleWeb.Live.Components.Verification do use Plausible alias PlausibleWeb.Router.Helpers, as: Routes + alias PlausibleWeb.Components.Icons + alias PlausibleWeb.Live.Installation.Instructions alias Plausible.InstallationSupport.{State, Result} + alias Plausible.Site.TrackerScriptConfiguration import PlausibleWeb.Components.Generic import PlausibleWeb.Live.Components.Form @@ -39,6 +42,7 @@ defmodule PlausibleWeb.Live.Components.Verification do attr(:installation_type, :string, default: nil) attr(:awaiting_first_pageview?, :boolean, default: false) attr(:custom_url_input?, :boolean, default: false) + attr(:tracker_script_configuration, TrackerScriptConfiguration, default: nil) def render(assigns) do assigns = @@ -74,6 +78,7 @@ defmodule PlausibleWeb.Live.Components.Verification do super_admin?={@super_admin?} verification_state={@verification_state} custom_url_input?={@custom_url_input?} + tracker_script_configuration={@tracker_script_configuration} />
    """ @@ -111,6 +116,14 @@ defmodule PlausibleWeb.Live.Components.Verification do end defp render_failed(assigns) do + assigns = + assign( + assigns, + :expandable_instructions?, + assigns.installation_type in ["manual", "gtm"] and + not is_nil(assigns.tracker_script_configuration) + ) + ~H""" <.notice title={ @@ -129,68 +142,32 @@ defmodule PlausibleWeb.Live.Components.Verification do Learn more

    -
    - <.button_link - :if={not @custom_url_input?} - mt?={false} - href="#" - phx-click="retry" - theme="secondary" - size="sm" - > - Check again - -
    - <.input - type="url" - name="custom_url" - id="custom_url" - aria-label="Website URL" - required - mt?={false} - width="w-44" - placeholder={"https://#{@domain}"} - value={"https://#{@domain}"} +
    +
    + <.retry_form_or_button custom_url_input?={@custom_url_input?} domain={@domain} /> + <.expand_installation_instructions_button + :if={@expandable_instructions?} + installation_type={@installation_type} /> - <.button type="submit" mt?={false} theme="secondary" size="sm"> - Check again - - -
    -
    -
      -
    • - Is your website located at a different URL? - <.styled_link href="#" phx-click="show-custom-url-form" id="verify-custom-url-link"> - Click here - -
    • -
    • = 3}> - Need further help with your installation? - <.styled_link href="https://plausible.io/contact"> - Contact us - -
    • -
    • - Need to see installation instructions again? - <.styled_link href={ - Routes.site_path(PlausibleWeb.Endpoint, :installation, @domain, - flow: @flow, - installation_type: @installation_type - ) - }> - Click here - -
    • -
    + <.review_instructions_link + :if={@installation_type in ["wordpress", "npm"]} + installation_type={@installation_type} + /> +
    + <.expandable_installation_instructions + :if={@expandable_instructions?} + installation_type={@installation_type} + tracker_script_configuration={@tracker_script_configuration} + />
    + <.additional_help_links + custom_url_input?={@custom_url_input?} + interpretation={@interpretation} + attempts={@attempts} + domain={@domain} + flow={@flow} + installation_type={@installation_type} + /> <.super_admin_diagnostics :if={@super_admin? and not is_nil(@verification_state)} verification_state={@verification_state} @@ -199,6 +176,131 @@ defmodule PlausibleWeb.Live.Components.Verification do """ end + defp retry_form_or_button(%{custom_url_input?: true} = assigns) do + ~H""" +
    + <.input + type="url" + name="custom_url" + id="custom_url" + aria-label="Website URL" + required + mt?={false} + width="w-44" + placeholder={"https://#{@domain}"} + value={"https://#{@domain}"} + /> + <.button type="submit" mt?={false} theme="secondary" size="sm"> + Check again + +
    + """ + end + + defp retry_form_or_button(assigns) do + ~H""" + <.button_link + mt?={false} + href="#" + phx-click="retry" + theme="secondary" + size="sm" + > + Check again + + """ + end + + defp expand_installation_instructions_button(assigns) do + ~H""" + + <.button_link + mt?={false} + href="#" + theme="ghost" + size="sm" + class="hover:bg-gray-900/10 dark:hover:bg-white/10 hover:border-transparent dark:hover:border-transparent" + > + + {review_instructions_label(@installation_type)} + + + + + + + + + + """ + end + + defp review_instructions_link(assigns) do + ~H""" + <.button_link + mt?={false} + href={install_help_href(@installation_type)} + theme="ghost" + size="sm" + target="_blank" + rel="noopener noreferrer" + class="hover:bg-gray-900/10 dark:hover:bg-white/10 hover:border-transparent dark:hover:border-transparent" + > + Review instructions + + """ + end + + defp expandable_installation_instructions(assigns) do + ~H""" +
    + + +
    + """ + end + + defp additional_help_links(assigns) do + ~H""" +
    +
      +
    • + Is your website located at a different URL? + <.styled_link href="#" phx-click="show-custom-url-form" id="verify-custom-url-link"> + Click here + +
    • +
    • = 3}> + Need further help with your installation? + <.styled_link href="https://plausible.io/contact"> + Contact us + +
    • +
    • + Want to choose another installation method? + <.styled_link href={ + Routes.site_path(PlausibleWeb.Endpoint, :installation, @domain, + flow: @flow, + installation_type: @installation_type + ) + }> + Click here + +
    • +
    +
    + """ + end + defp super_admin_diagnostics(assigns) do ~H"""
    - <.snippet_form - text={render_snippet(@tracker_script_configuration_form.data)} - rows={6} - resizable={true} - /> + <.copy_snippet_box tracker_script_configuration={@tracker_script_configuration_form.data} /> <.h2 class="mt-8 text-sm font-medium">Optional measurements <.script_config_control field={@tracker_script_configuration_form[:outbound_links]} @@ -129,38 +127,56 @@ defmodule PlausibleWeb.Live.Installation.Instructions do Tag Manager installation
    - - We've detected your website is using Google Tag Manager. Here's how to integrate Plausible: - - - Using Google Tag Manager? Here's how to integrate Plausible: - -
    - <.focus_list> - <:item> - Copy your site's Script ID: - <.snippet_form - text={@tracker_script_configuration_form.data.id} - rows={1} - resizable={false} - /> - + <.gtm_instructions_content + recommended_installation_type={@recommended_installation_type} + tracker_script_configuration={@tracker_script_configuration_form.data} + /> +
    + """ + end - <:item> - <.styled_link href="https://plausible.io/gtm-template" new_tab={true}> - Install the Plausible template in GTM - - + attr :recommended_installation_type, :string, required: true + attr :tracker_script_configuration, TrackerScriptConfiguration, required: true - <:item> - Paste your Script ID into the template and click the button below to verify your installation. - - -
    + def gtm_instructions_content(assigns) do + ~H""" + + We've detected your website is using Google Tag Manager. Here's how to integrate Plausible: + + + Using Google Tag Manager? Here's how to integrate Plausible: + +
    + <.gtm_instructions_content_inner tracker_script_configuration={@tracker_script_configuration} />
    """ end + def gtm_instructions_content_inner(assigns) do + ~H""" + <.copyable_readonly_text_area + id="script-config-id" + text={@tracker_script_configuration.id} + rows={1} + /> + <.focus_list> + <:item> + Copy your site's Script ID from above + + + <:item> + <.styled_link href="https://plausible.io/gtm-template" new_tab={true}> + Install the Plausible template in GTM + + + + <:item> + Paste your Script ID into the template + + + """ + end + def npm_instructions(assigns) do ~H""" <.title class="my-4"> @@ -236,27 +252,11 @@ defmodule PlausibleWeb.Live.Installation.Instructions do """ end - defp snippet_form(assigns) do - ~H""" -
    - + def copy_snippet_box(assigns) do + assigns = assign(assigns, :text, render_snippet(assigns.tracker_script_configuration)) - - - - COPY - - -
    + ~H""" + <.copyable_readonly_text_area id="snippet" text={@text} rows={6} resizable={true} /> """ end From 2ea6f2c0d00bf743438d73502c173616083021bf Mon Sep 17 00:00:00 2001 From: Robert Joonas Date: Thu, 16 Jul 2026 13:45:32 +0100 Subject: [PATCH 05/11] stop polling pageviews --- extra/lib/plausible_web/live/verification.ex | 34 ------- lib/plausible/stats/clickhouse.ex | 16 ---- .../live/components/verification.ex | 5 -- test/plausible_web/live/verification_test.exs | 89 +------------------ 4 files changed, 1 insertion(+), 143 deletions(-) diff --git a/extra/lib/plausible_web/live/verification.ex b/extra/lib/plausible_web/live/verification.ex index 2fa2b5dd2392..2f1b2bc13bbe 100644 --- a/extra/lib/plausible_web/live/verification.ex +++ b/extra/lib/plausible_web/live/verification.ex @@ -34,7 +34,6 @@ defmodule PlausibleWeb.Live.Verification do private = Map.get(socket.private.connect_info, :private, %{}) super_admin? = Plausible.Auth.super_admin?(current_user) - has_pageviews? = has_pageviews?(site) tracker_script_configuration = PlausibleWeb.Tracker.get_or_create_tracker_script_configuration!(site) @@ -45,7 +44,6 @@ defmodule PlausibleWeb.Live.Verification do site: site, super_admin?: super_admin?, domain: domain, - has_pageviews?: has_pageviews?, component: @component, tracker_script_configuration: tracker_script_configuration, installation_type: get_installation_type(session["installation_type"], site), @@ -55,7 +53,6 @@ defmodule PlausibleWeb.Live.Verification do flow: session["flow"] || "", checks_pid: nil, attempts: 0, - polling_pageviews?: false, custom_url_input?: false ) @@ -91,7 +88,6 @@ defmodule PlausibleWeb.Live.Verification do id="verification-standalone" attempts={@attempts} flow={@flow} - awaiting_first_pageview?={not @has_pageviews?} super_admin?={@super_admin?} custom_url_input?={@custom_url_input?} tracker_script_configuration={@tracker_script_configuration} @@ -170,10 +166,6 @@ defmodule PlausibleWeb.Live.Verification do def handle_info({:all_checks_done, %State{} = state}, socket) do interpretation = Verification.Checks.interpret_diagnostics(state) - if not socket.assigns.has_pageviews? do - schedule_pageviews_check(socket) - end - update_component(socket, finished?: true, success?: interpretation.ok?, @@ -184,19 +176,6 @@ defmodule PlausibleWeb.Live.Verification do {:noreply, assign(socket, checks_pid: nil)} end - def handle_info(:check_pageviews, socket) do - if has_pageviews?(socket.assigns.site) do - {:noreply, assign(socket, has_pageviews?: true, polling_pageviews?: false)} - else - socket = - socket - |> assign(polling_pageviews?: false) - |> schedule_pageviews_check() - - {:noreply, socket} - end - end - @supported_installation_types_atoms PlausibleWeb.Tracker.supported_installation_types() |> Enum.map(&String.to_atom/1) defp get_installation_type(installation_type, site) do @@ -222,15 +201,6 @@ defmodule PlausibleWeb.Live.Verification do end end - defp schedule_pageviews_check(socket) do - if socket.assigns.polling_pageviews? do - socket - else - Process.send_after(self(), :check_pageviews, socket.assigns.delay * 2) - assign(socket, polling_pageviews?: true) - end - end - defp reset_component(socket) do update_component(socket, message: "We're visiting your site to ensure that everything is working", @@ -252,8 +222,4 @@ defmodule PlausibleWeb.Live.Verification do defp launch_delayed(socket) do Process.send_after(self(), {:start, socket.assigns.report_to}, socket.assigns.delay) end - - defp has_pageviews?(site) do - Plausible.Stats.Clickhouse.has_pageviews?(site) - end end diff --git a/lib/plausible/stats/clickhouse.ex b/lib/plausible/stats/clickhouse.ex index 375c9e9f47fc..fadd473f867d 100644 --- a/lib/plausible/stats/clickhouse.ex +++ b/lib/plausible/stats/clickhouse.ex @@ -191,20 +191,4 @@ defmodule Plausible.Stats.Clickhouse do def current_visitors_12h(site) do Stats.current_visitors(site, Duration.new!(hour: -12)) end - - def has_pageviews?(site) do - # This function is currently only used in installation verification - # which is not accessible for consolidated views. - true = Plausible.Sites.regular?(site) - - ClickhouseRepo.exists?( - from(e in "events_v2", - where: - e.site_id == ^site.id and - e.name == "pageview" and - e.timestamp >= - ^site.native_stats_start_at - ) - ) - end end diff --git a/lib/plausible_web/live/components/verification.ex b/lib/plausible_web/live/components/verification.ex index f55c66a10a57..c14dec4df100 100644 --- a/lib/plausible_web/live/components/verification.ex +++ b/lib/plausible_web/live/components/verification.ex @@ -40,7 +40,6 @@ defmodule PlausibleWeb.Live.Components.Verification do attr(:attempts, :integer, default: 0) attr(:flow, :string, default: "") attr(:installation_type, :string, default: nil) - attr(:awaiting_first_pageview?, :boolean, default: false) attr(:custom_url_input?, :boolean, default: false) attr(:tracker_script_configuration, TrackerScriptConfiguration, default: nil) @@ -63,7 +62,6 @@ defmodule PlausibleWeb.Live.Components.Verification do <.render_progress :if={not @finished?} message={@message} /> <.render_success :if={@finished? and @success?} - awaiting_first_pageview?={@awaiting_first_pageview?} domain={@domain} super_admin?={@super_admin?} verification_state={@verification_state} @@ -104,9 +102,6 @@ defmodule PlausibleWeb.Live.Components.Verification do Your installation is working and visitors are being counted accurately. - - Awaiting your first pageview... - <.super_admin_diagnostics :if={@super_admin? and not is_nil(@verification_state)} verification_state={@verification_state} diff --git a/test/plausible_web/live/verification_test.exs b/test/plausible_web/live/verification_test.exs index 1181068b93ab..6856a0208c81 100644 --- a/test/plausible_web/live/verification_test.exs +++ b/test/plausible_web/live/verification_test.exs @@ -11,7 +11,6 @@ defmodule PlausibleWeb.Live.VerificationTest do @retry_button ~s|a[phx-click="retry"]| @progress ~s|#verification-ui p#progress| - @awaiting ~s|#verification-ui span#awaiting| @heading ~s|#verification-ui h3| @in_progress_text "Verifying your installation" @@ -132,94 +131,8 @@ defmodule PlausibleWeb.Live.VerificationTest do assert eventually(fn -> html = render(lv) - - { - text_of_element(html, @awaiting) =~ - "Awaiting your first pageview", - html - } - end) - - html = render(lv) - assert html =~ "Success!" - assert html =~ "Awaiting your first pageview" - end - - @tag :ee_only - test "won't await first pageview if site has pageviews", %{conn: conn, site: site} do - populate_stats(site, [ - build(:pageview) - ]) - - stub_lookup_a_records(site.domain) - - stub_verification_result(%{ - "completed" => true, - "trackerIsInHtml" => true, - "plausibleIsOnWindow" => true, - "plausibleIsInitialized" => true, - "testEvent" => %{ - "normalizedBody" => %{ - "domain" => site.domain - }, - "responseStatus" => 200 - } - }) - - {:ok, lv} = kick_off_live_verification(conn, site) - - assert eventually(fn -> - html = render(lv) - - { - text(html) =~ "Success", - html - } + {html =~ "Success!", html} end) - - html = render(lv) - - refute text_of_element(html, @awaiting) =~ "Awaiting your first pageview" - refute_redirected(lv, "/#{URI.encode_www_form(site.domain)}") - end - - @tag :ee_only - test "shows success in place (no redirect) once first pageview arrives", %{ - conn: conn, - site: site - } do - stub_lookup_a_records(site.domain) - - stub_verification_result(%{ - "completed" => true, - "trackerIsInHtml" => true, - "plausibleIsOnWindow" => true, - "plausibleIsInitialized" => true, - "testEvent" => %{ - "normalizedBody" => %{ - "domain" => site.domain - }, - "responseStatus" => 200 - } - }) - - {:ok, lv} = kick_off_live_verification(conn, site) - - assert eventually(fn -> - html = render(lv) - {text(html) =~ "Awaiting", html} - end) - - populate_stats(site, [ - build(:pageview) - ]) - - assert eventually(fn -> - html = render(lv) - {not (text(html) =~ "Awaiting your first pageview"), html} - end) - - refute_redirected(lv, "/#{URI.encode_www_form(site.domain)}") end for {installation_type_param, expected_text, saved_installation_type} <- [ From 580c9d22c097d8b7d516c266211776c1283b955c Mon Sep 17 00:00:00 2001 From: Robert Joonas Date: Thu, 16 Jul 2026 14:33:15 +0100 Subject: [PATCH 06/11] rely on tracker_script_config object for installation_method --- extra/lib/plausible_web/live/verification.ex | 28 ++----- .../controllers/stats_controller.ex | 3 +- .../live/components/verification.ex | 7 +- lib/plausible_web/live/installation.ex | 11 +-- .../live/components/verification_test.exs | 2 +- test/plausible_web/live/installation_test.exs | 6 +- test/plausible_web/live/verification_test.exs | 77 +++++++------------ 7 files changed, 41 insertions(+), 93 deletions(-) diff --git a/extra/lib/plausible_web/live/verification.ex b/extra/lib/plausible_web/live/verification.ex index 2f1b2bc13bbe..e4ed5d8e91db 100644 --- a/extra/lib/plausible_web/live/verification.ex +++ b/extra/lib/plausible_web/live/verification.ex @@ -46,7 +46,6 @@ defmodule PlausibleWeb.Live.Verification do domain: domain, component: @component, tracker_script_configuration: tracker_script_configuration, - installation_type: get_installation_type(session["installation_type"], site), report_to: self(), delay: private[:delay] || 500, slowdown: private[:slowdown] || 500, @@ -83,7 +82,7 @@ defmodule PlausibleWeb.Live.Verification do ~H""" <.live_component module={@component} - installation_type={@installation_type} + installation_type={get_installation_type(@tracker_script_configuration)} domain={@domain} id="verification-standalone" attempts={@attempts} @@ -139,7 +138,7 @@ defmodule PlausibleWeb.Live.Verification do Verification.Checks.run( socket.assigns.url_to_verify, domain, - socket.assigns.installation_type, + get_installation_type(socket.assigns.tracker_script_configuration), report_to: report_to, slowdown: socket.assigns.slowdown ) @@ -178,26 +177,13 @@ defmodule PlausibleWeb.Live.Verification do @supported_installation_types_atoms PlausibleWeb.Tracker.supported_installation_types() |> Enum.map(&String.to_atom/1) - defp get_installation_type(installation_type, site) do - cond do - installation_type in PlausibleWeb.Tracker.supported_installation_types() -> - installation_type - - (saved_installation_type = get_saved_installation_type(site)) in @supported_installation_types_atoms -> - Atom.to_string(saved_installation_type) - - true -> - PlausibleWeb.Tracker.fallback_installation_type() - end - end - - defp get_saved_installation_type(site) do - case PlausibleWeb.Tracker.get_tracker_script_configuration(site) do - %{installation_type: installation_type} -> - installation_type + defp get_installation_type(tracker_script_configuration) do + case tracker_script_configuration.installation_type do + type when type in @supported_installation_types_atoms -> + Atom.to_string(type) _ -> - nil + PlausibleWeb.Tracker.fallback_installation_type() end end diff --git a/lib/plausible_web/controllers/stats_controller.ex b/lib/plausible_web/controllers/stats_controller.ex index f96cd2f94855..a0abc0f9665a 100644 --- a/lib/plausible_web/controllers/stats_controller.ex +++ b/lib/plausible_web/controllers/stats_controller.ex @@ -118,8 +118,7 @@ defmodule PlausibleWeb.StatsController do verify_installation?: verify_installation?, verification_session: %{ "domain" => site.domain, - "flow" => conn.params["flow"], - "installation_type" => conn.params["installation_type"] + "flow" => conn.params["flow"] } ) end diff --git a/lib/plausible_web/live/components/verification.ex b/lib/plausible_web/live/components/verification.ex index c14dec4df100..905539a2643d 100644 --- a/lib/plausible_web/live/components/verification.ex +++ b/lib/plausible_web/live/components/verification.ex @@ -22,7 +22,6 @@ defmodule PlausibleWeb.Live.Components.Verification do @dismiss_onclick "document.getElementById('#{@container_id}').classList.add('hidden');" <> "var u = new window.URL(window.location.href);" <> "u.searchParams.delete('verify_installation');" <> - "u.searchParams.delete('installation_type');" <> "u.searchParams.delete('flow');" <> "window.history.replaceState(null, '', u);" @@ -161,7 +160,6 @@ defmodule PlausibleWeb.Live.Components.Verification do attempts={@attempts} domain={@domain} flow={@flow} - installation_type={@installation_type} /> <.super_admin_diagnostics :if={@super_admin? and not is_nil(@verification_state)} @@ -283,10 +281,7 @@ defmodule PlausibleWeb.Live.Components.Verification do
  • Want to choose another installation method? <.styled_link href={ - Routes.site_path(PlausibleWeb.Endpoint, :installation, @domain, - flow: @flow, - installation_type: @installation_type - ) + Routes.site_path(PlausibleWeb.Endpoint, :installation, @domain, flow: @flow) }> Click here diff --git a/lib/plausible_web/live/installation.ex b/lib/plausible_web/live/installation.ex index 86126c09f179..8c7f070980b8 100644 --- a/lib/plausible_web/live/installation.ex +++ b/lib/plausible_web/live/installation.ex @@ -317,12 +317,7 @@ defmodule PlausibleWeb.Live.Installation do end def handle_event("submit", %{"tracker_script_configuration" => params}, socket) do - config = - PlausibleWeb.Tracker.update_script_configuration!( - socket.assigns.site, - params, - :installation - ) + PlausibleWeb.Tracker.update_script_configuration!(socket.assigns.site, params, :installation) domain = socket.assigns.site.domain @@ -330,11 +325,9 @@ defmodule PlausibleWeb.Live.Installation do on_ee do Routes.stats_path(socket, :stats, domain, verify_installation: true, - flow: socket.assigns.flow, - installation_type: config.installation_type + flow: socket.assigns.flow ) else - _need_to_use_variable_on_ce = config Routes.stats_path(socket, :stats, domain, []) end diff --git a/test/plausible_web/live/components/verification_test.exs b/test/plausible_web/live/components/verification_test.exs index a9fda0c55af4..da442e8de625 100644 --- a/test/plausible_web/live/components/verification_test.exs +++ b/test/plausible_web/live/components/verification_test.exs @@ -186,7 +186,7 @@ defmodule PlausibleWeb.Live.Components.VerificationTest do assert element_exists?( html, - ~s|a[href="/example.com/installation?flow=review&installation_type=wordpress"]| + ~s|a[href="/example.com/installation?flow=review"]| ) end end diff --git a/test/plausible_web/live/installation_test.exs b/test/plausible_web/live/installation_test.exs index 1dd3b0b42874..7c7adf0fe169 100644 --- a/test/plausible_web/live/installation_test.exs +++ b/test/plausible_web/live/installation_test.exs @@ -273,8 +273,7 @@ defmodule PlausibleWeb.Live.InstallationTest do lv, Routes.stats_path(conn, :stats, site.domain, verify_installation: true, - flow: "provisioning", - installation_type: unquote(type) + flow: "provisioning" ) ) end @@ -361,8 +360,7 @@ defmodule PlausibleWeb.Live.InstallationTest do lv, Routes.stats_path(conn, :stats, site.domain, verify_installation: true, - flow: "review", - installation_type: "manual" + flow: "review" ) ) end diff --git a/test/plausible_web/live/verification_test.exs b/test/plausible_web/live/verification_test.exs index 6856a0208c81..abe4eb73832e 100644 --- a/test/plausible_web/live/verification_test.exs +++ b/test/plausible_web/live/verification_test.exs @@ -135,34 +135,21 @@ defmodule PlausibleWeb.Live.VerificationTest do end) end - for {installation_type_param, expected_text, saved_installation_type} <- [ - {"manual", - "Please make sure you've copied the snippet to the head of your site, or verify your installation manually.", - nil}, - {"npm", - "Please make sure you've initialized Plausible on your site, or verify your installation manually.", - nil}, - {"gtm", - "Please make sure you've configured the GTM template correctly, or verify your installation manually.", - nil}, - {"wordpress", - "Please make sure you've enabled the plugin, or verify your installation manually.", - nil}, - # trusts param over saved installation type - {"wordpress", - "Please make sure you've enabled the plugin, or verify your installation manually.", + for {expected_text, saved_installation_type} <- [ + {"Please make sure you've copied the snippet to the head of your site, or verify your installation manually.", + "manual"}, + {"Please make sure you've initialized Plausible on your site, or verify your installation manually.", "npm"}, - # falls back to saved installation type if no param - {"", - "Please make sure you've initialized Plausible on your site, or verify your installation manually.", - "npm"}, - # falls back to manual if no param and no saved installation type - {"", - "Please make sure you've copied the snippet to the head of your site, or verify your installation manually.", + {"Please make sure you've configured the GTM template correctly, or verify your installation manually.", + "gtm"}, + {"Please make sure you've enabled the plugin, or verify your installation manually.", + "wordpress"}, + # falls back to manual when there's no saved installation type + {"Please make sure you've copied the snippet to the head of your site, or verify your installation manually.", nil} ] do @tag :ee_only - test "eventually fails to verify installation (?installation_type=#{installation_type_param}) if saved installation type is #{inspect(saved_installation_type)}", + test "eventually fails to verify installation if saved installation type is #{inspect(saved_installation_type)}", %{ conn: conn, site: site @@ -182,24 +169,17 @@ defmodule PlausibleWeb.Live.VerificationTest do }) end - {:ok, lv} = - kick_off_live_verification( - conn, - site, - "installation_type=#{unquote(installation_type_param)}" - ) - - assert html = - eventually(fn -> - html = render(lv) - {html =~ "", html} - - { - text_of_element(html, @heading) =~ - "We couldn't detect Plausible on your site", - html - } - end) + {:ok, lv} = kick_off_live_verification(conn, site) + + html = + eventually(fn -> + html = render(lv) + + { + text_of_element(html, @heading) =~ "We couldn't detect Plausible on your site", + html + } + end) assert element_exists?(html, @retry_button) @@ -209,16 +189,16 @@ defmodule PlausibleWeb.Live.VerificationTest do end end - defp get_lv(conn, site, qs \\ nil) do + defp get_lv(conn, site) do {:ok, lv, html} = - conn |> no_slowdown() |> as_live() |> live(verification_path(site, qs)) + conn |> no_slowdown() |> as_live() |> live(verification_path(site)) {lv, html} end - defp kick_off_live_verification(conn, site, qs \\ nil) do + defp kick_off_live_verification(conn, site) do {:ok, lv, _html} = - conn |> no_slowdown() |> no_delay() |> as_live() |> live(verification_path(site, qs)) + conn |> no_slowdown() |> no_delay() |> as_live() |> live(verification_path(site)) {:ok, lv} end @@ -230,10 +210,7 @@ defmodule PlausibleWeb.Live.VerificationTest do # LiveView tests (e.g. props_settings_test.exs). defp as_live(conn), do: assign(conn, :live_module, PlausibleWeb.Live.Verification) - defp verification_path(site, nil), do: "/#{site.domain}?verify_installation=true" - - defp verification_path(site, qs), - do: "/#{site.domain}?verify_installation=true&#{qs}" + defp verification_path(site), do: "/#{site.domain}?verify_installation=true" defp no_slowdown(conn) do Plug.Conn.put_private(conn, :slowdown, 0) From 50027a6cf41b67ddf400f92c1539d95c518be88d Mon Sep 17 00:00:00 2001 From: Robert Joonas Date: Mon, 20 Jul 2026 09:46:18 +0100 Subject: [PATCH 07/11] proper dismiss logic --- .../js/dashboard/verification/portal.test.tsx | 75 +++++++++++++++++++ assets/js/dashboard/verification/portal.tsx | 55 ++++++++++++-- assets/js/liveview/live_socket.js | 9 +++ extra/lib/plausible_web/live/verification.ex | 14 +++- .../controllers/stats_controller.ex | 8 +- .../live/components/verification.ex | 63 +++++++++++----- test/plausible_web/live/verification_test.exs | 63 ++++++++++++++++ 7 files changed, 256 insertions(+), 31 deletions(-) create mode 100644 assets/js/dashboard/verification/portal.test.tsx diff --git a/assets/js/dashboard/verification/portal.test.tsx b/assets/js/dashboard/verification/portal.test.tsx new file mode 100644 index 000000000000..48dca2a3cf01 --- /dev/null +++ b/assets/js/dashboard/verification/portal.test.tsx @@ -0,0 +1,75 @@ +import React from 'react' +import { act, render, screen } from '@testing-library/react' +import { useLocation } from 'react-router-dom' +import { TestContextProviders } from '../../../test-utils/app-context-providers' +import { + VERIFICATION_FINISHED_EVENT, + VerificationLiveViewPortal +} from './portal' + +function LocationDisplay() { + const location = useLocation() + return
    {location.pathname + location.search}
    +} + +function renderWithInitialEntry(initialEntry: string) { + render( + <> + + + , + { + wrapper: (props) => ( + + ) + } + ) +} + +function dispatchVerificationFinished(queryParams: string[]) { + act(() => { + window.dispatchEvent( + new CustomEvent(VERIFICATION_FINISHED_EVENT, { + detail: { queryParams } + }) + ) + }) +} + +test('drops exactly the query params named in the event detail, leaving every other param untouched', () => { + renderWithInitialEntry( + '/some-domain?f=contains,os,a&f=contains,page,/&verify_installation=true&flow=provisioning&comparison=year_over_year' + ) + + dispatchVerificationFinished(['verify_installation', 'flow']) + + expect(screen.getByTestId('location').textContent).toBe( + '/?f=contains,os,a&f=contains,page,/&comparison=year_over_year' + ) +}) + +test('does nothing when none of the named params are present', () => { + renderWithInitialEntry('/some-domain?comparison=year_over_year') + + dispatchVerificationFinished(['verify_installation', 'flow']) + + expect(screen.getByTestId('location').textContent).toBe( + '/?comparison=year_over_year' + ) +}) + +test('drops only verify_installation, keeping a real param that happens to be a prefix of it', () => { + renderWithInitialEntry( + '/some-domain?verify_installation=true&verify_installation_extra=keep-me' + ) + + dispatchVerificationFinished(['verify_installation']) + + expect(screen.getByTestId('location').textContent).toBe( + '/?verify_installation_extra=keep-me' + ) +}) diff --git a/assets/js/dashboard/verification/portal.tsx b/assets/js/dashboard/verification/portal.tsx index 8e9a54858276..f32aba92c2a6 100644 --- a/assets/js/dashboard/verification/portal.tsx +++ b/assets/js/dashboard/verification/portal.tsx @@ -1,9 +1,50 @@ -import React from 'react' +import React, { useEffect } from 'react' +import { useAppNavigate } from '../navigation/use-app-navigate' -export const VerificationLiveViewPortal = React.memo( - () => { - return
    +type VerificationFinishedDetail = { + /** + * Exact query param names to drop from the URL when verification banner + * disappears. See: PlausibleWeb.Live.Components.Verification.query_params/0 + */ + queryParams: string[] +} - }, - () => true -) +export const VERIFICATION_FINISHED_EVENT = 'verification-finished' + +/** + * Renders the portal target into which the verification LiveView (see + * lib/plausible_web/live/components/verification.ex) gets teleported. + * Also helps that LiveView out with cleaning up after itself: clearing + * its one-time query params through React Router. + */ +export const VerificationLiveViewPortal = React.memo(() => { + const navigate = useAppNavigate() + + useEffect(() => { + function handleVerificationFinished(event: Event) { + const { queryParams } = (event as CustomEvent) + .detail + + navigate({ + search: (search) => { + const nextSearch = { ...search } + queryParams.forEach((param) => delete nextSearch[param]) + return nextSearch + } + }) + } + + window.addEventListener( + VERIFICATION_FINISHED_EVENT, + handleVerificationFinished + ) + + return () => + window.removeEventListener( + VERIFICATION_FINISHED_EVENT, + handleVerificationFinished + ) + }, [navigate]) + + return
    +}) diff --git a/assets/js/liveview/live_socket.js b/assets/js/liveview/live_socket.js index 7df1ef9984c7..8cccfde07821 100644 --- a/assets/js/liveview/live_socket.js +++ b/assets/js/liveview/live_socket.js @@ -25,6 +25,15 @@ if (csrfToken && websocketUrl) { }) } } + // Lets a LiveView tell the client to tear down the websocket connection + // once it's done with it (e.g. PlausibleWeb.Live.Verification, once its + // banner has been dismissed) - the server-side process then terminates + // gracefully. + Hooks.DisconnectSocket = { + mounted() { + this.handleEvent('disconnect-liveview', () => liveSocket.disconnect()) + } + } let Uploaders = {} Uploaders.S3 = function (entries, onViewError) { entries.forEach((entry) => { diff --git a/extra/lib/plausible_web/live/verification.ex b/extra/lib/plausible_web/live/verification.ex index e4ed5d8e91db..cfe347b186c3 100644 --- a/extra/lib/plausible_web/live/verification.ex +++ b/extra/lib/plausible_web/live/verification.ex @@ -52,7 +52,8 @@ defmodule PlausibleWeb.Live.Verification do flow: session["flow"] || "", checks_pid: nil, attempts: 0, - custom_url_input?: false + custom_url_input?: false, + dismissed?: false ) if connected?(socket) do @@ -66,7 +67,7 @@ defmodule PlausibleWeb.Live.Verification do assigns = assign(assigns, :use_portal?, @use_portal?) ~H""" -
    +
    <%= if @use_portal? do %> <.portal id="verification-portal-source" target="#verification-portal-target"> <.verification_content {assigns} /> @@ -90,6 +91,7 @@ defmodule PlausibleWeb.Live.Verification do super_admin?={@super_admin?} custom_url_input?={@custom_url_input?} tracker_script_configuration={@tracker_script_configuration} + dismissed?={@dismissed?} /> """ end @@ -108,6 +110,14 @@ defmodule PlausibleWeb.Live.Verification do {:noreply, assign(socket, custom_url_input?: true)} end + # Once dismissed, this LiveView has nothing left to do - and since it's + # the only LiveView on the dashboard page, there's no reason to keep + # the websocket connection open for the rest of the browsing session. + def handle_event("dismiss", _, socket) do + update_component(socket, dismissed?: true) + {:noreply, socket |> assign(dismissed?: true) |> push_event("disconnect-liveview", %{})} + end + def handle_event("verify-custom-url", %{"custom_url" => custom_url}, socket) do socket = socket diff --git a/lib/plausible_web/controllers/stats_controller.ex b/lib/plausible_web/controllers/stats_controller.ex index a0abc0f9665a..f30053664b24 100644 --- a/lib/plausible_web/controllers/stats_controller.ex +++ b/lib/plausible_web/controllers/stats_controller.ex @@ -116,10 +116,10 @@ defmodule PlausibleWeb.StatsController do limited_to_segment_id: nil, connect_live_socket: verify_installation?, verify_installation?: verify_installation?, - verification_session: %{ - "domain" => site.domain, - "flow" => conn.params["flow"] - } + verification_session: + PlausibleWeb.Live.Components.Verification.query_params() + |> Map.new(&{&1, conn.params[&1]}) + |> Map.put("domain", site.domain) ) end end diff --git a/lib/plausible_web/live/components/verification.ex b/lib/plausible_web/live/components/verification.ex index 905539a2643d..74f27c706a02 100644 --- a/lib/plausible_web/live/components/verification.ex +++ b/lib/plausible_web/live/components/verification.ex @@ -16,14 +16,11 @@ defmodule PlausibleWeb.Live.Components.Verification do import PlausibleWeb.Live.Components.Form @container_id "verification-ui" - # Dismissing hides the banner immediately and strips `verify_installation` - # from the URL (the same param that got it rendered in the first place - - # see PlausibleWeb.StatsController), so a refresh doesn't bring it back. - @dismiss_onclick "document.getElementById('#{@container_id}').classList.add('hidden');" <> - "var u = new window.URL(window.location.href);" <> - "u.searchParams.delete('verify_installation');" <> - "u.searchParams.delete('flow');" <> - "window.history.replaceState(null, '', u);" + + # All query params the verification LiveView needs must be listed here, so + # they can be cleaned up from the URL once verification finishes. + @query_params ~w(verify_installation flow) + def query_params, do: @query_params attr(:domain, :string, required: true) @@ -41,23 +38,17 @@ defmodule PlausibleWeb.Live.Components.Verification do attr(:installation_type, :string, default: nil) attr(:custom_url_input?, :boolean, default: false) attr(:tracker_script_configuration, TrackerScriptConfiguration, default: nil) + attr(:dismissed?, :boolean, default: false) def render(assigns) do assigns = assigns - |> assign(:dismiss_onclick, @dismiss_onclick) |> assign(:container_id, @container_id) + |> assign(:query_params, @query_params) ~H""" -
    - +
    + <.dismiss_button container_id={@container_id} query_params={@query_params} /> <.render_progress :if={not @finished?} message={@message} /> <.render_success :if={@finished? and @success?} @@ -81,6 +72,42 @@ defmodule PlausibleWeb.Live.Components.Verification do """ end + # The action of dismissing the verification banner consists of 4 + # independent things: + # + # 1. Client-side: the inlined `onclick` instantly adds the `hidden` + # class straight to the container div. + # + # 2. Client-side: instantly dispatches a `verification-finished` + # window event so React router can clean up query params that are + # no longer needed (see assets/js/dashboard/verification/portal.tsx). + # Also makes sure that a refresh won't bring verification back. + # + # 3. Server-side (phx-click="dismiss"): sets `dismissed?` on this + # component's assigns, so it stays hidden even if a later + # `send_update` (e.g. :all_checks_done) re-renders it. + # + # 4. Server-side, same handler: tells the client to close the websocket + # connection, since the LiveView has nothing left to do. + defp dismiss_button(assigns) do + ~H""" + + """ + end + + defp dismiss_onclick(container_id, query_params) do + "document.getElementById('#{container_id}').classList.add('hidden');" <> + "window.dispatchEvent(new CustomEvent('verification-finished', { detail: { queryParams: #{Jason.encode!(query_params)} } }));" + end + defp render_progress(assigns) do ~H""" <.notice title="Verifying your installation" theme={:gray}> diff --git a/test/plausible_web/live/verification_test.exs b/test/plausible_web/live/verification_test.exs index abe4eb73832e..896b5edc46ca 100644 --- a/test/plausible_web/live/verification_test.exs +++ b/test/plausible_web/live/verification_test.exs @@ -12,6 +12,7 @@ defmodule PlausibleWeb.Live.VerificationTest do @retry_button ~s|a[phx-click="retry"]| @progress ~s|#verification-ui p#progress| @heading ~s|#verification-ui h3| + @banner ~s|#verification-ui| @in_progress_text "Verifying your installation" @@ -135,6 +136,68 @@ defmodule PlausibleWeb.Live.VerificationTest do end) end + @tag :ee_only + test "the dismissed flag keeps the banner hidden even if a late update arrives while still connected", + %{conn: conn, site: site} do + stub_lookup_a_records(site.domain) + + stub_verification_result(%{ + "completed" => true, + "trackerIsInHtml" => true, + "plausibleIsOnWindow" => true, + "plausibleIsInitialized" => true, + "testEvent" => %{ + "normalizedBody" => %{ + "domain" => site.domain + }, + "responseStatus" => 200 + } + }) + + {:ok, lv} = kick_off_live_verification(conn, site) + + html = render(lv) + assert html =~ @in_progress_text + refute class_of_element(html, @banner) =~ "hidden" + + html = render_click(lv, "dismiss") + assert class_of_element(html, @banner) =~ "hidden" + + # This might look a bit counter-intuitive -- dismissing the banner + # closes the websocket connection and the LV process would normally + # die before the component gets notified of success. + + # However, `Phoenix.LiveViewTest` can't simulate a real socket closing, + # so the process here just stays alive regardless. What this guards is + # the defensive `dismissed?` gate itself: if this process is ever still + # around when a late update arrives, for whatever reason, the banner + # must stay hidden. + assert eventually(fn -> + html = render(lv) + {html =~ "Success!", html} + end) + + html = render(lv) + assert class_of_element(html, @banner) =~ "hidden" + end + + @tag :ee_only + test "dismissing tells the client to close the websocket connection", + %{conn: conn, site: site} do + stub_lookup_a_records(site.domain) + + stub_verification_result(%{ + "completed" => false, + "error" => %{"message" => "Error"} + }) + + {:ok, lv} = kick_off_live_verification(conn, site) + + render_click(lv, "dismiss") + + assert_push_event(lv, "disconnect-liveview", %{}) + end + for {expected_text, saved_installation_type} <- [ {"Please make sure you've copied the snippet to the head of your site, or verify your installation manually.", "manual"}, From a95f08e8c3f69c0402d6c485c156a1744bad18e6 Mon Sep 17 00:00:00 2001 From: Robert Joonas Date: Mon, 20 Jul 2026 09:49:11 +0100 Subject: [PATCH 08/11] [revert me] debugging UI scenarios --- .../verification/checks.ex | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/extra/lib/plausible/installation_support/verification/checks.ex b/extra/lib/plausible/installation_support/verification/checks.ex index 2a3328d21bcb..6fe66711e03d 100644 --- a/extra/lib/plausible/installation_support/verification/checks.ex +++ b/extra/lib/plausible/installation_support/verification/checks.ex @@ -11,6 +11,24 @@ defmodule Plausible.InstallationSupport.Verification.Checks do @verify_installation_check_timeout 20_000 + # Local UI debugging only - set to one of the keys below to make every + # verification run return that canned interpretation, regardless of what + # the real check pipeline actually found. Handy for iterating on + # PlausibleWeb.Live.Verification's banner UI states. Must be `nil` on commit. + @debug_scenario nil + + @debug_scenarios %{ + 0 => :success, + 1 => %Verification.Diagnostics{}, + 2 => %Verification.Diagnostics{selected_installation_type: "wordpress"}, + 3 => %Verification.Diagnostics{ + plausible_is_on_window: false, + plausible_is_initialized: false, + service_error: %{code: :domain_not_found} + }, + 4 => %Verification.Diagnostics{disallowed_by_csp: true} + } + @spec run(String.t(), String.t(), String.t(), Keyword.t()) :: {:ok, pid()} | State.t() def run(url, data_domain, installation_type, opts \\ []) do # Timeout option for testing purposes @@ -59,6 +77,7 @@ defmodule Plausible.InstallationSupport.Verification.Checks do opts \\ [] ) do telemetry? = Keyword.get(opts, :telemetry?, true) + {diagnostics, url} = debug_override(diagnostics, data_domain, url) result = Verification.Diagnostics.interpret( @@ -97,4 +116,28 @@ defmodule Plausible.InstallationSupport.Verification.Checks do result end + + # Also overrides `url` to a clean, query-string-free one - otherwise the + # real check pipeline's cache-busting query param (?plausible_verification=...) + # leaks into canned error messages like "We couldn't find your website at ...". + defp debug_override(diagnostics, data_domain, url) do + case Map.get(@debug_scenarios, @debug_scenario) do + nil -> + {diagnostics, url} + + :success -> + { + %Verification.Diagnostics{ + test_event: %{ + "normalizedBody" => %{"domain" => data_domain}, + "responseStatus" => 200 + } + }, + "https://#{data_domain}" + } + + %Verification.Diagnostics{} = debug -> + {debug, "https://#{data_domain}"} + end + end end From 7f27b6d83c86802d071c5448b57032f425523b7a Mon Sep 17 00:00:00 2001 From: Robert Joonas Date: Mon, 20 Jul 2026 10:01:08 +0100 Subject: [PATCH 09/11] rename verification (component) to verification_banner --- assets/js/dashboard/verification/portal.tsx | 2 +- extra/lib/plausible_web/live/verification.ex | 2 +- lib/plausible_web/controllers/stats_controller.ex | 2 +- .../components/{verification.ex => verification_banner.ex} | 2 +- .../{verification_test.exs => verification_banner_test.exs} | 4 ++-- 5 files changed, 6 insertions(+), 6 deletions(-) rename lib/plausible_web/live/components/{verification.ex => verification_banner.ex} (99%) rename test/plausible_web/live/components/{verification_test.exs => verification_banner_test.exs} (98%) diff --git a/assets/js/dashboard/verification/portal.tsx b/assets/js/dashboard/verification/portal.tsx index f32aba92c2a6..19d482ecb111 100644 --- a/assets/js/dashboard/verification/portal.tsx +++ b/assets/js/dashboard/verification/portal.tsx @@ -4,7 +4,7 @@ import { useAppNavigate } from '../navigation/use-app-navigate' type VerificationFinishedDetail = { /** * Exact query param names to drop from the URL when verification banner - * disappears. See: PlausibleWeb.Live.Components.Verification.query_params/0 + * disappears. See: PlausibleWeb.Live.Components.VerificationBanner.query_params/0 */ queryParams: string[] } diff --git a/extra/lib/plausible_web/live/verification.ex b/extra/lib/plausible_web/live/verification.ex index cfe347b186c3..57611d11f436 100644 --- a/extra/lib/plausible_web/live/verification.ex +++ b/extra/lib/plausible_web/live/verification.ex @@ -7,7 +7,7 @@ defmodule PlausibleWeb.Live.Verification do alias Plausible.InstallationSupport.{State, Verification} - @component PlausibleWeb.Live.Components.Verification + @component PlausibleWeb.Live.Components.VerificationBanner @slowdown_for_frequent_checking :timer.seconds(0) @use_portal? Mix.env() not in [:test, :ce_test] diff --git a/lib/plausible_web/controllers/stats_controller.ex b/lib/plausible_web/controllers/stats_controller.ex index f30053664b24..d1cabbde2a1c 100644 --- a/lib/plausible_web/controllers/stats_controller.ex +++ b/lib/plausible_web/controllers/stats_controller.ex @@ -117,7 +117,7 @@ defmodule PlausibleWeb.StatsController do connect_live_socket: verify_installation?, verify_installation?: verify_installation?, verification_session: - PlausibleWeb.Live.Components.Verification.query_params() + PlausibleWeb.Live.Components.VerificationBanner.query_params() |> Map.new(&{&1, conn.params[&1]}) |> Map.put("domain", site.domain) ) diff --git a/lib/plausible_web/live/components/verification.ex b/lib/plausible_web/live/components/verification_banner.ex similarity index 99% rename from lib/plausible_web/live/components/verification.ex rename to lib/plausible_web/live/components/verification_banner.ex index 74f27c706a02..eecca8992d88 100644 --- a/lib/plausible_web/live/components/verification.ex +++ b/lib/plausible_web/live/components/verification_banner.ex @@ -1,4 +1,4 @@ -defmodule PlausibleWeb.Live.Components.Verification do +defmodule PlausibleWeb.Live.Components.VerificationBanner do @moduledoc """ This component is responsible for rendering the verification progress and diagnostics as a compact banner on top of the dashboard. diff --git a/test/plausible_web/live/components/verification_test.exs b/test/plausible_web/live/components/verification_banner_test.exs similarity index 98% rename from test/plausible_web/live/components/verification_test.exs rename to test/plausible_web/live/components/verification_banner_test.exs index da442e8de625..61e1a34af423 100644 --- a/test/plausible_web/live/components/verification_test.exs +++ b/test/plausible_web/live/components/verification_banner_test.exs @@ -1,4 +1,4 @@ -defmodule PlausibleWeb.Live.Components.VerificationTest do +defmodule PlausibleWeb.Live.Components.VerificationBannerTest do use PlausibleWeb.ConnCase, async: true on_ee do @@ -8,7 +8,7 @@ defmodule PlausibleWeb.Live.Components.VerificationTest do @moduletag :capture_log - @component PlausibleWeb.Live.Components.Verification + @component PlausibleWeb.Live.Components.VerificationBanner @progress ~s|#verification-ui p#progress| @loading_spinner ~s|div#verification-ui div.loading| From 158e6e8cd2225eacb4359fe136a27b2f2659d0d0 Mon Sep 17 00:00:00 2001 From: Sanne de Vries <65487235+sanne-san@users.noreply.github.com> Date: Wed, 22 Jul 2026 15:21:10 +0200 Subject: [PATCH 10/11] Refine verification banner UI and copy (#6525) * Refine verification banner UI and copy - Simplify failure CTAs: keep "Check again" as primary, add ghost "Review installation" (or "Try another URL" when a custom URL retry is offered); remove installation-guide, change-method, and view-snippet expandable logic - Render inline "verify your installation manually" / "review your installation" links inside recommendation text (offer_custom_url_input scenarios) via safe HTML helpers, avoiding HEEx whitespace pitfalls - Tighten diagnostics title and body copy - Move the "Setup pending" pill in the sites list to replace the percentage indicator; use the generic pill component - Extend the notice component with title_class override and a spinner slot; swap success icon to solid check-circle * improve templating logic --------- Co-authored-by: Robert Joonas --- .../plausible/installation_support/result.ex | 2 +- .../verification/diagnostics.ex | 129 +++++---- extra/lib/plausible_web/live/verification.ex | 2 - lib/plausible_web/components/generic.ex | 30 +- .../live/components/verification_banner.ex | 261 ++++++++---------- lib/plausible_web/live/sites.ex | 14 +- .../verification/checks_test.exs | 122 ++++---- .../verification/diagnostics_test.exs | 46 +++ .../components/verification_banner_test.exs | 88 ++++-- test/plausible_web/live/verification_test.exs | 16 +- 10 files changed, 415 insertions(+), 295 deletions(-) create mode 100644 test/plausible/installation_support/verification/diagnostics_test.exs diff --git a/extra/lib/plausible/installation_support/result.ex b/extra/lib/plausible/installation_support/result.ex index baa08d645ddd..a1fad54ca56b 100644 --- a/extra/lib/plausible/installation_support/result.ex +++ b/extra/lib/plausible/installation_support/result.ex @@ -6,7 +6,7 @@ defmodule Plausible.InstallationSupport.Result do ok?: false, data: nil, errors: [error.message], - recommendations: [%{text: error.recommendation, url: error.url}] + recommendations: [%{text: error.recommendation, inline_links: error.inline_links}] ok?: true, data: %{}, diff --git a/extra/lib/plausible/installation_support/verification/diagnostics.ex b/extra/lib/plausible/installation_support/verification/diagnostics.ex index 0773c07550f8..d464a0155fda 100644 --- a/extra/lib/plausible/installation_support/verification/diagnostics.ex +++ b/extra/lib/plausible/installation_support/verification/diagnostics.ex @@ -33,35 +33,56 @@ defmodule Plausible.InstallationSupport.Verification.Diagnostics do """ @enforce_keys [:message, :recommendation] - defstruct [:message, :recommendation, :url] + defstruct [:message, :recommendation, inline_links: []] + + @required_link_prefix "https://plausible.io/" def new!(attrs) do message = Map.fetch!(attrs, :message) + recommendation = Map.fetch!(attrs, :recommendation) + inline_links = Map.get(attrs, :inline_links, []) if String.ends_with?(message, ".") do raise ArgumentError, "Error message must not end with a period: #{inspect(message)}" end - if String.ends_with?(attrs[:recommendation], ".") do + if String.ends_with?(recommendation, ".") do raise ArgumentError, - "Error recommendation must not end with a period: #{inspect(attrs[:recommendation])}" + "Error recommendation must not end with a period: #{inspect(recommendation)}" end - if is_binary(attrs[:url]) and not String.starts_with?(attrs[:url], "https://plausible.io") do - raise ArgumentError, - "Recommendation url must start with 'https://plausible.io': #{inspect(attrs[:url])}" + for %{text: text, href: href} <- inline_links do + if length(String.split(recommendation, text)) - 1 != 1 do + raise ArgumentError, + "Recommendation inline_links text #{inspect(text)} must appear exactly once in: #{inspect(recommendation)}" + end + + if not String.starts_with?(href, @required_link_prefix) do + raise ArgumentError, + "Recommendation inline_links href must start with '#{@required_link_prefix}': #{inspect(href)}" + end end struct!(__MODULE__, attrs) end end + @verify_manually_inline_link %{ + text: "verify your installation manually", + href: @verify_manually_url + } + @error_succeeds_only_after_cache_bust Error.new!(%{ message: "We detected an issue with your site's cache", recommendation: - "Please clear the cache for your site to ensure that your visitors will load the latest version of your site that has Plausible correctly installed", - url: - "https://plausible.io/docs/troubleshoot-integration#have-you-cleared-the-cache-of-your-site" + "Clear the cache for your site to ensure your visitors load the latest version of your site with Plausible correctly installed. Learn more", + inline_links: [ + %{ + text: "Learn more", + href: + "https://plausible.io/docs/troubleshoot-integration#have-you-cleared-the-cache-of-your-site" + } + ] }) @spec interpret(t(), String.t(), String.t()) :: Result.t() @@ -121,17 +142,21 @@ defmodule Plausible.InstallationSupport.Verification.Diagnostics do end @error_proxy_network_error Error.new!(%{ - message: - "We got an unexpected response from the proxy you are using for Plausible", + message: "We couldn't verify your proxied installation", recommendation: - "Please check that you've configured the proxied /event route correctly", - url: "https://plausible.io/docs/proxy/introduction" + "We received an unexpected response from your proxy. Check that you've configured the proxied /event route correctly. Learn more", + inline_links: [ + %{ + text: "Learn more", + href: "https://plausible.io/docs/proxy/introduction" + } + ] }) @error_plausible_network_error Error.new!(%{ message: "We couldn't verify your website", recommendation: "Please try verifying again in a few minutes, or verify your installation manually", - url: @verify_manually_url + inline_links: [@verify_manually_inline_link] }) def interpret( @@ -174,11 +199,16 @@ defmodule Plausible.InstallationSupport.Verification.Diagnostics do @error_csp_disallowed Error.new!(%{ message: - "We encountered an issue with your site's Content Security Policy (CSP)", + "Your site's Content Security Policy (CSP) is blocking Plausible", recommendation: - "Please add plausible.io domain specifically to the allowed list of domains in your site's CSP", - url: - "https://plausible.io/docs/troubleshoot-integration#does-your-site-use-a-content-security-policy-csp" + "Add plausible.io to the list of allowed domains in your site's Content Security Policy to allow Plausible to collect analytics. Learn more", + inline_links: [ + %{ + text: "Learn more", + href: + "https://plausible.io/docs/troubleshoot-integration#does-your-site-use-a-content-security-policy-csp" + } + ] }) def interpret( %__MODULE__{ @@ -191,10 +221,10 @@ defmodule Plausible.InstallationSupport.Verification.Diagnostics do do: handled_error(@error_csp_disallowed) @error_domain_not_found Error.new!(%{ - message: "We couldn't find your website at <%= @attempted_url %>", + message: "We couldn't reach <%= @attempted_url %>", recommendation: - "Please check that the domain you entered is correct and reachable publicly. If it's intentionally private, you'll need to verify that Plausible works manually", - url: @verify_manually_url + "Check that the URL is correct and publicly accessible. If your site is intentionally private, you'll need to verify your installation manually", + inline_links: [@verify_manually_inline_link] }) def interpret(%__MODULE__{service_error: %{code: code}}, expected_domain, url) @@ -207,11 +237,10 @@ defmodule Plausible.InstallationSupport.Verification.Diagnostics do end @error_browserless_network Error.new!(%{ - message: - "We couldn't verify your website at <%= @attempted_url %>", + message: "We couldn't verify <%= @attempted_url %>", recommendation: - "Accessing the website resulted in a network error. Please verify your installation manually", - url: @verify_manually_url + "We encountered a network error while trying to access your website. You can verify your installation manually", + inline_links: [@verify_manually_inline_link] }) def interpret( @@ -228,11 +257,10 @@ defmodule Plausible.InstallationSupport.Verification.Diagnostics do end @error_browserless_temporary Error.new!(%{ - message: - "Our verification tool encountered a temporary service error", + message: "Our verification service is temporarily unavailable", recommendation: "Please try again in a few minutes or verify your installation manually", - url: @verify_manually_url + inline_links: [@verify_manually_inline_link] }) def interpret(%__MODULE__{service_error: %{code: code}}, _expected_domain, _url) @@ -241,11 +269,10 @@ defmodule Plausible.InstallationSupport.Verification.Diagnostics do end @error_unexpected_page_response Error.new!(%{ - message: - "We couldn't verify your website at <%= @attempted_url %>", + message: "We couldn't verify <%= @attempted_url %>", recommendation: - "Accessing the website resulted in an unexpected status code <%= @page_response_status %>. Please check for anything that might be blocking us from reaching your site, like a firewall, authentication requirements, or CDN rules. If you'd prefer, you can skip this and verify your installation manually", - url: @verify_manually_url + "Accessing your website returned an unexpected status code (<%= @page_response_status %>). Check for anything that might be blocking our access to your site, such as a firewall, authentication requirements, or CDN rules. You can also verify your installation manually", + inline_links: [@verify_manually_inline_link] }) def interpret( @@ -290,26 +317,26 @@ defmodule Plausible.InstallationSupport.Verification.Diagnostics do @error_plausible_not_found_for_manual Error.new!(%{ message: @message_plausible_not_found, recommendation: - "Please make sure you've copied the snippet to the head of your site, or verify your installation manually", - url: @verify_manually_url + "Make sure you've copied the snippet to the head of your site, or verify your installation manually", + inline_links: [@verify_manually_inline_link] }) @error_plausible_not_found_for_npm Error.new!(%{ message: @message_plausible_not_found, recommendation: - "Please make sure you've initialized Plausible on your site, or verify your installation manually", - url: @verify_manually_url + "Make sure you've initialized Plausible on your site, or verify your installation manually", + inline_links: [@verify_manually_inline_link] }) @error_plausible_not_found_for_gtm Error.new!(%{ message: @message_plausible_not_found, recommendation: - "Please make sure you've configured the GTM template correctly, or verify your installation manually", - url: @verify_manually_url + "Make sure you've configured the GTM template correctly, or verify your installation manually", + inline_links: [@verify_manually_inline_link] }) @error_plausible_not_found_for_wordpress Error.new!(%{ message: @message_plausible_not_found, recommendation: - "Please make sure you've enabled the plugin, or verify your installation manually", - url: @verify_manually_url + "Make sure you've enabled the WordPress plugin, or verify your installation manually", + inline_links: [@verify_manually_inline_link] }) defp error_plausible_not_found(selected_installation_type) do case selected_installation_type do @@ -320,33 +347,33 @@ defmodule Plausible.InstallationSupport.Verification.Diagnostics do end end - @unexpected_domain_message "Plausible test event is not for this site" + @unexpected_domain_message "Your Plausible snippet is configured for a different domain" @error_unexpected_domain_for_manual Error.new!(%{ message: @unexpected_domain_message, recommendation: - "Please check that the snippet on your site matches the installation instructions exactly, or verify your installation manually", - url: @verify_manually_url + "Check that the snippet on your site matches the one shown in the installation instructions, or verify your installation manually", + inline_links: [@verify_manually_inline_link] }) @error_unexpected_domain_for_npm Error.new!(%{ message: @unexpected_domain_message, recommendation: - "Please check that you've initialized Plausible with the correct domain, or verify your installation manually", - url: @verify_manually_url + "Check you've initialized Plausible with the correct domain, or verify your installation manually", + inline_links: [@verify_manually_inline_link] }) @error_unexpected_domain_for_gtm Error.new!(%{ message: @unexpected_domain_message, recommendation: - "Please check that you've entered the ID in the GTM template correctly, or verify your installation manually", - url: @verify_manually_url + "Check you've entered the ID in the GTM template correctly, or verify your installation manually", + inline_links: [@verify_manually_inline_link] }) @error_unexpected_domain_for_wordpress Error.new!(%{ message: @unexpected_domain_message, recommendation: - "Please check that you've installed the WordPress plugin correctly, or verify your installation manually", - url: @verify_manually_url + "Check you've installed the WordPress plugin correctly, or verify your installation manually", + inline_links: [@verify_manually_inline_link] }) defp error_unexpected_domain(selected_installation_type) do case selected_installation_type do @@ -372,7 +399,7 @@ defmodule Plausible.InstallationSupport.Verification.Diagnostics do %Result{ ok?: false, errors: [message], - recommendations: [%{text: recommendation, url: error.url}] + recommendations: [%{text: recommendation, inline_links: error.inline_links}] } end @@ -383,7 +410,7 @@ defmodule Plausible.InstallationSupport.Verification.Diagnostics do ok?: false, data: %{unhandled: true, browserless_issue: browserless_issue}, errors: [error.message], - recommendations: [%{text: error.recommendation, url: error.url}] + recommendations: [%{text: error.recommendation, inline_links: error.inline_links}] } end end diff --git a/extra/lib/plausible_web/live/verification.ex b/extra/lib/plausible_web/live/verification.ex index 57611d11f436..ff4742bf3a56 100644 --- a/extra/lib/plausible_web/live/verification.ex +++ b/extra/lib/plausible_web/live/verification.ex @@ -83,14 +83,12 @@ defmodule PlausibleWeb.Live.Verification do ~H""" <.live_component module={@component} - installation_type={get_installation_type(@tracker_script_configuration)} domain={@domain} id="verification-standalone" attempts={@attempts} flow={@flow} super_admin?={@super_admin?} custom_url_input?={@custom_url_input?} - tracker_script_configuration={@tracker_script_configuration} dismissed?={@dismissed?} /> """ diff --git a/lib/plausible_web/components/generic.ex b/lib/plausible_web/components/generic.ex index 2dd458386723..173e6a6a6f88 100644 --- a/lib/plausible_web/components/generic.ex +++ b/lib/plausible_web/components/generic.ex @@ -12,25 +12,37 @@ defmodule PlausibleWeb.Components.Generic do gray: %{ bg: "bg-gray-100 dark:bg-gray-800", icon: "text-gray-600 dark:text-gray-300", - title_text: "text-sm text-gray-900 dark:text-gray-100", + title_text: "text-gray-900 dark:text-gray-100", body_text: "text-sm text-gray-800 dark:text-gray-200 leading-5" }, + indigo: %{ + bg: "bg-indigo-100/60 dark:bg-indigo-900/40", + icon: "text-indigo-500", + title_text: "text-gray-900 dark:text-gray-100", + body_text: "text-sm text-gray-600 dark:text-gray-100/60 leading-5" + }, + green: %{ + bg: "bg-green-100/60 dark:bg-green-900/40", + icon: "text-green-500", + title_text: "text-gray-900 dark:text-gray-100", + body_text: "text-sm text-gray-600 dark:text-gray-100/60 leading-5" + }, yellow: %{ bg: "bg-yellow-100/60 dark:bg-yellow-900/40", icon: "text-yellow-500", - title_text: "text-sm text-gray-900 dark:text-gray-100", + title_text: "text-gray-900 dark:text-gray-100", body_text: "text-sm text-gray-600 dark:text-gray-100/60 leading-5" }, red: %{ bg: "bg-red-100 dark:bg-red-900/30", icon: "text-red-600 dark:text-red-500", - title_text: "text-sm text-gray-900 dark:text-gray-100", + title_text: "text-gray-900 dark:text-gray-100", body_text: "text-sm text-gray-600 dark:text-gray-100/60 leading-5" }, white: %{ bg: "bg-white dark:bg-gray-900 shadow-sm dark:shadow-none", icon: "text-gray-600 dark:text-gray-400", - title_text: "text-sm text-gray-900 dark:text-gray-100", + title_text: "text-gray-900 dark:text-gray-100", body_text: "text-sm text-gray-600 dark:text-gray-300 leading-5" } } @@ -222,6 +234,7 @@ defmodule PlausibleWeb.Components.Generic do attr(:show_icon, :boolean, default: true) attr(:class, :string, default: "") attr(:icon_class, :string, default: "") + attr(:title_class, :string, default: "") attr(:rest, :global) slot(:inner_block) slot(:actions) @@ -253,7 +266,14 @@ defmodule PlausibleWeb.Components.Generic do <% end %>
    -

    +

    {@title}

    diff --git a/lib/plausible_web/live/components/verification_banner.ex b/lib/plausible_web/live/components/verification_banner.ex index eecca8992d88..b40731c01db9 100644 --- a/lib/plausible_web/live/components/verification_banner.ex +++ b/lib/plausible_web/live/components/verification_banner.ex @@ -7,10 +7,7 @@ defmodule PlausibleWeb.Live.Components.VerificationBanner do use Plausible alias PlausibleWeb.Router.Helpers, as: Routes - alias PlausibleWeb.Components.Icons - alias PlausibleWeb.Live.Installation.Instructions alias Plausible.InstallationSupport.{State, Result} - alias Plausible.Site.TrackerScriptConfiguration import PlausibleWeb.Components.Generic import PlausibleWeb.Live.Components.Form @@ -35,9 +32,7 @@ defmodule PlausibleWeb.Live.Components.VerificationBanner do attr(:interpretation, Result, default: nil) attr(:attempts, :integer, default: 0) attr(:flow, :string, default: "") - attr(:installation_type, :string, default: nil) attr(:custom_url_input?, :boolean, default: false) - attr(:tracker_script_configuration, TrackerScriptConfiguration, default: nil) attr(:dismissed?, :boolean, default: false) def render(assigns) do @@ -62,11 +57,9 @@ defmodule PlausibleWeb.Live.Components.VerificationBanner do attempts={@attempts} domain={@domain} flow={@flow} - installation_type={@installation_type} super_admin?={@super_admin?} verification_state={@verification_state} custom_url_input?={@custom_url_input?} - tracker_script_configuration={@tracker_script_configuration} />
    """ @@ -94,7 +87,7 @@ defmodule PlausibleWeb.Live.Components.VerificationBanner do
    - <.site_stats sparkline={@sparkline} /> + <.site_stats sparkline={@sparkline} needs_verification?={@needs_verification?} />
    @@ -680,6 +674,7 @@ defmodule PlausibleWeb.Live.Sites do end attr(:sparkline, :any, required: true) + attr(:needs_verification?, :boolean, default: false) def site_stats(assigns) do ~H""" @@ -708,7 +703,10 @@ defmodule PlausibleWeb.Live.Sites do

    - <.percentage_change change={@sparkline.visitors_change} /> + <.pill :if={@needs_verification?} color={:yellow}> + Setup pending + + <.percentage_change :if={not @needs_verification?} change={@sparkline.visitors_change} />
  • diff --git a/test/plausible/installation_support/verification/checks_test.exs b/test/plausible/installation_support/verification/checks_test.exs index a0b7ef39a8dd..b9e0bde8e134 100644 --- a/test/plausible/installation_support/verification/checks_test.exs +++ b/test/plausible/installation_support/verification/checks_test.exs @@ -13,6 +13,11 @@ defmodule Plausible.InstallationSupport.Verification.ChecksTest do @expected_domain "example.com" @url_to_verify "https://#{@expected_domain}" + @verify_manually_url "https://plausible.io/docs/troubleshoot-integration#how-to-manually-check-your-integration" + @verify_manually_inline_link %{ + text: "verify your installation manually", + href: @verify_manually_url + } describe "URL check" do test "returns error when DNS check fails with domain not found error, offers custom URL input" do @@ -22,14 +27,13 @@ defmodule Plausible.InstallationSupport.Verification.ChecksTest do ok?: false, data: %{offer_custom_url_input: true}, errors: [ - ^any(:string, ~r/We couldn't find your website at #{@url_to_verify}$/) + ^any(:string, ~r/We couldn't reach #{@url_to_verify}$/) ], recommendations: [ %{ text: - "Please check that the domain you entered is correct and reachable publicly. If it's intentionally private, you'll need to verify that Plausible works manually", - url: - "https://plausible.io/docs/troubleshoot-integration#how-to-manually-check-your-integration" + "Check that the URL is correct and publicly accessible. If your site is intentionally private, you can verify your installation manually", + inline_links: [^@verify_manually_inline_link] } ] } = @@ -49,14 +53,13 @@ defmodule Plausible.InstallationSupport.Verification.ChecksTest do ok?: false, data: %{offer_custom_url_input: true}, errors: [ - ^any(:string, ~r/We couldn't find your website at #{url_to_verify}$/) + ^any(:string, ~r/We couldn't reach #{url_to_verify}$/) ], recommendations: [ %{ text: - "Please check that the domain you entered is correct and reachable publicly. If it's intentionally private, you'll need to verify that Plausible works manually", - url: - "https://plausible.io/docs/troubleshoot-integration#how-to-manually-check-your-integration" + "Check that the URL is correct and publicly accessible. If your site is intentionally private, you can verify your installation manually", + inline_links: [^@verify_manually_inline_link] } ] } = @@ -94,13 +97,13 @@ defmodule Plausible.InstallationSupport.Verification.ChecksTest do for {installation_type, expected_recommendation} <- [ {"wordpress", - "Please check that you've installed the WordPress plugin correctly, or verify your installation manually"}, + "Check you've installed the WordPress plugin correctly, or verify your installation manually"}, {"gtm", - "Please check that you've entered the ID in the GTM template correctly, or verify your installation manually"}, + "Check you've entered the ID in the GTM template correctly, or verify your installation manually"}, {"npm", - "Please check that you've initialized Plausible with the correct domain, or verify your installation manually"}, + "Check you've initialized Plausible with the correct domain, or verify your installation manually"}, {"manual", - "Please check that the snippet on your site matches the installation instructions exactly, or verify your installation manually"} + "Check that the snippet on your site matches the one shown in the installation instructions, or verify your installation manually"} ] do test "returns error when test event domain doesn't match the expected domain, with recommendation for installation type: #{installation_type}" do verification_stub = @@ -119,12 +122,11 @@ defmodule Plausible.InstallationSupport.Verification.ChecksTest do assert_matches %Result{ ok?: false, - errors: ["Plausible test event is not for this site"], + errors: ["Your Plausible snippet is configured for a different domain"], recommendations: [ %{ text: unquote(expected_recommendation), - url: - "https://plausible.io/docs/troubleshoot-integration#how-to-manually-check-your-integration" + inline_links: [^@verify_manually_inline_link] } ] } = @@ -154,11 +156,16 @@ defmodule Plausible.InstallationSupport.Verification.ChecksTest do assert_matches %Result{ ok?: false, - errors: [^any(:string, ~r/.*proxy.*/)], + errors: [^any(:string, ~r/.*proxied.*/)], recommendations: [ %{ text: ^any(:string, ~r/.*proxied.*/), - url: "https://plausible.io/docs/proxy/introduction" + inline_links: [ + %{ + text: "Learn more", + href: "https://plausible.io/docs/proxy/introduction" + } + ] } ] } = @@ -187,9 +194,9 @@ defmodule Plausible.InstallationSupport.Verification.ChecksTest do errors: [^any(:string, ~r/.*couldn't verify.*/)], recommendations: [ %{ - text: ^any(:string, ~r/.*try verifying again.*/), - url: - "https://plausible.io/docs/troubleshoot-integration#how-to-manually-check-your-integration" + text: + "Please try verifying again in a few minutes, or verify your installation manually", + inline_links: [^@verify_manually_inline_link] } ] } = @@ -213,9 +220,8 @@ defmodule Plausible.InstallationSupport.Verification.ChecksTest do recommendations: [ %{ text: - "Please make sure you've copied the snippet to the head of your site, or verify your installation manually", - url: - "https://plausible.io/docs/troubleshoot-integration#how-to-manually-check-your-integration" + "Make sure you've copied the snippet to the head of your site, or verify your installation manually", + inline_links: [^@verify_manually_inline_link] } ] } = @@ -236,14 +242,19 @@ defmodule Plausible.InstallationSupport.Verification.ChecksTest do assert_matches %Result{ ok?: false, errors: [ - "We encountered an issue with your site's Content Security Policy (CSP)" + "Your site's Content Security Policy (CSP) is blocking Plausible" ], recommendations: [ %{ text: - "Please add plausible.io domain specifically to the allowed list of domains in your site's CSP", - url: - "https://plausible.io/docs/troubleshoot-integration#does-your-site-use-a-content-security-policy-csp" + "Add plausible.io to the list of allowed domains in your site's Content Security Policy to allow Plausible to collect analytics. Learn more", + inline_links: [ + %{ + text: "Learn more", + href: + "https://plausible.io/docs/troubleshoot-integration#does-your-site-use-a-content-security-policy-csp" + } + ] } ] } = @@ -261,15 +272,12 @@ defmodule Plausible.InstallationSupport.Verification.ChecksTest do assert_matches %Result{ ok?: false, data: %{offer_custom_url_input: true}, - errors: [ - "We couldn't verify your website at https://example.com" - ], + errors: ["We couldn't verify https://example.com"], recommendations: [ %{ text: - "Accessing the website resulted in a network error. Please verify your installation manually", - url: - "https://plausible.io/docs/troubleshoot-integration#how-to-manually-check-your-integration" + "We encountered a network error while trying to access your website. You can verify your installation manually", + inline_links: [^@verify_manually_inline_link] } ] } = @@ -291,15 +299,12 @@ defmodule Plausible.InstallationSupport.Verification.ChecksTest do assert_matches %Result{ ok?: false, data: %{offer_custom_url_input: true}, - errors: [ - "We couldn't verify your website at https://example.com" - ], + errors: ["We couldn't verify https://example.com"], recommendations: [ %{ text: - "Accessing the website resulted in an unexpected status code 403. Please check for anything that might be blocking us from reaching your site, like a firewall, authentication requirements, or CDN rules. If you'd prefer, you can skip this and verify your installation manually", - url: - "https://plausible.io/docs/troubleshoot-integration#how-to-manually-check-your-integration" + "Accessing your website returned an unexpected status code (403). Check for anything that might be blocking our access to your site, such as a firewall, authentication requirements, or CDN rules. You can also verify your installation manually", + inline_links: [^@verify_manually_inline_link] } ] } = @@ -309,13 +314,13 @@ defmodule Plausible.InstallationSupport.Verification.ChecksTest do for {installation_type, expected_recommendation} <- [ {"wordpress", - "Please make sure you've enabled the plugin, or verify your installation manually"}, + "Make sure you've enabled the WordPress plugin, or verify your installation manually"}, {"gtm", - "Please make sure you've configured the GTM template correctly, or verify your installation manually"}, + "Make sure you've configured the GTM template correctly, or verify your installation manually"}, {"npm", - "Please make sure you've initialized Plausible on your site, or verify your installation manually"}, + "Make sure you've initialized Plausible on your site, or verify your installation manually"}, {"manual", - "Please make sure you've copied the snippet to the head of your site, or verify your installation manually"} + "Make sure you've copied the snippet to the head of your site, or verify your installation manually"} ] do test "returns error \"We couldn't detect Plausible on your site\" when plausible_is_on_window is false (with best guess recommendation for installation type: #{installation_type})" do verification_stub = @@ -334,8 +339,7 @@ defmodule Plausible.InstallationSupport.Verification.ChecksTest do recommendations: [ %{ text: unquote(expected_recommendation), - url: - "https://plausible.io/docs/troubleshoot-integration#how-to-manually-check-your-integration" + inline_links: [^@verify_manually_inline_link] } ] } = @@ -363,8 +367,7 @@ defmodule Plausible.InstallationSupport.Verification.ChecksTest do recommendations: [ %{ text: unquote(expected_recommendation), - url: - "https://plausible.io/docs/troubleshoot-integration#how-to-manually-check-your-integration" + inline_links: [^@verify_manually_inline_link] } ] } = @@ -426,8 +429,13 @@ defmodule Plausible.InstallationSupport.Verification.ChecksTest do recommendations: [ %{ text: ^any(:string, ~r/.*cache.*/), - url: - "https://plausible.io/docs/troubleshoot-integration#have-you-cleared-the-cache-of-your-site" + inline_links: [ + %{ + text: "Learn more", + href: + "https://plausible.io/docs/troubleshoot-integration#have-you-cleared-the-cache-of-your-site" + } + ] } ] } = run_checks(verification_stub) |> Checks.interpret_diagnostics() @@ -484,12 +492,12 @@ defmodule Plausible.InstallationSupport.Verification.ChecksTest do assert_matches %Result{ ok?: false, - errors: [^any(:string, ~r/.*temporary service error.*/)], + errors: [^any(:string, ~r/.*temporarily unavailable.*/)], recommendations: [ %{ - text: ^any(:string, ~r/.*in a few minutes.*/), - url: - "https://plausible.io/docs/troubleshoot-integration#how-to-manually-check-your-integration" + text: + "Please try again in a few minutes or verify your installation manually", + inline_links: [^@verify_manually_inline_link] } ] } = Checks.interpret_diagnostics(state) @@ -514,12 +522,12 @@ defmodule Plausible.InstallationSupport.Verification.ChecksTest do assert_matches %Result{ ok?: false, - errors: [^any(:string, ~r/.*temporary service error.*/)], + errors: [^any(:string, ~r/.*temporarily unavailable.*/)], recommendations: [ %{ - text: ^any(:string, ~r/.*in a few minutes.*/), - url: - "https://plausible.io/docs/troubleshoot-integration#how-to-manually-check-your-integration" + text: + "Please try again in a few minutes or verify your installation manually", + inline_links: [^@verify_manually_inline_link] } ] } = Checks.interpret_diagnostics(state) diff --git a/test/plausible/installation_support/verification/diagnostics_test.exs b/test/plausible/installation_support/verification/diagnostics_test.exs new file mode 100644 index 000000000000..bead8dfa1df0 --- /dev/null +++ b/test/plausible/installation_support/verification/diagnostics_test.exs @@ -0,0 +1,46 @@ +defmodule Plausible.InstallationSupport.Verification.DiagnosticsTest do + use ExUnit.Case, async: true + + alias Plausible.InstallationSupport.Verification.Diagnostics.Error + + describe "Error.new!/1" do + test "accepts a recommendation whose inline_links text appears exactly once" do + assert %Error{} = + Error.new!(%{ + message: "Something went wrong", + recommendation: "Check the docs for more info", + inline_links: [%{text: "docs", href: "https://plausible.io/docs"}] + }) + end + + test "raises when inline_links text isn't found in the recommendation" do + assert_raise ArgumentError, ~r/must appear exactly once/, fn -> + Error.new!(%{ + message: "Something went wrong", + recommendation: "Check the manual for more info", + inline_links: [%{text: "docs", href: "https://plausible.io/docs"}] + }) + end + end + + test "raises when inline_links text appears more than once in the recommendation" do + assert_raise ArgumentError, ~r/must appear exactly once/, fn -> + Error.new!(%{ + message: "Something went wrong", + recommendation: "Check the docs, or check the docs again", + inline_links: [%{text: "the docs", href: "https://plausible.io/docs"}] + }) + end + end + + test "raises when inline_links href doesn't point at plausible.io" do + assert_raise ArgumentError, ~r/must start with/, fn -> + Error.new!(%{ + message: "Something went wrong", + recommendation: "Check the docs for more info", + inline_links: [%{text: "docs", href: "https://example.com/docs"}] + }) + end + end + end +end diff --git a/test/plausible_web/live/components/verification_banner_test.exs b/test/plausible_web/live/components/verification_banner_test.exs index 61e1a34af423..0ecdf1bc8a58 100644 --- a/test/plausible_web/live/components/verification_banner_test.exs +++ b/test/plausible_web/live/components/verification_banner_test.exs @@ -11,9 +11,8 @@ defmodule PlausibleWeb.Live.Components.VerificationBannerTest do @component PlausibleWeb.Live.Components.VerificationBanner @progress ~s|#verification-ui p#progress| - @loading_spinner ~s|div#verification-ui div.loading| - @check_circle ~s|div#verification-ui #check-circle| - @error_circle ~s|div#verification-ui #error-circle| + @loading_spinner ~s|#verification-ui svg.animate-spin| + @check_circle ~s|#verification-ui #check-circle| @recommendations ~s|#recommendation| @super_admin_report ~s|#super-admin-report| @@ -22,24 +21,23 @@ defmodule PlausibleWeb.Live.Components.VerificationBannerTest do assert element_exists?(html, @progress) assert text_of_element(html, @progress) == - "We're visiting your site to ensure that everything is working" + "We're visiting your site to ensure that everything is working..." assert element_exists?(html, @loading_spinner) - refute class_of_element(html, @loading_spinner) =~ "hidden" refute element_exists?(html, @recommendations) refute element_exists?(html, @check_circle) refute element_exists?(html, @super_admin_report) end - test "renders error badge on error" do + test "renders failed state without progress spinner" do html = render_component(@component, domain: "example.com", success?: false, finished?: true) refute element_exists?(html, @loading_spinner) refute element_exists?(html, @check_circle) refute element_exists?(html, @recommendations) - assert element_exists?(html, @error_circle) + assert html =~ "We couldn't verify your installation" end - test "renders diagnostic interpretation" do + test "renders diagnostic interpretation with inline verify link and standalone review-installation sentence" do interpretation = Verification.Checks.interpret_diagnostics(%State{ url: "https://example.com", @@ -56,11 +54,60 @@ defmodule PlausibleWeb.Live.Components.VerificationBannerTest do ) assert [recommendation] = html |> find(@recommendations) |> Enum.map(&text/1) - assert recommendation =~ "check that the domain you entered is correct" + assert recommendation =~ "Check that the URL is correct and publicly accessible" + assert recommendation =~ "verify your installation manually" + refute recommendation =~ "review your installation" + assert recommendation =~ "See your installation instructions again here" + + assert element_exists?( + html, + ~s|#recommendation a[href="https://plausible.io/docs/troubleshoot-integration#how-to-manually-check-your-integration"]| + ) + + assert element_exists?( + html, + ~s|#recommendation a[href="/example.com/installation?flow="]| + ) refute element_exists?(html, @super_admin_report) end + test "renders inline verify-manually link when the recommendation mentions it (no custom URL retry)" do + interpretation = + Verification.Checks.interpret_diagnostics(%State{ + url: "https://example.com", + data_domain: "example.com", + diagnostics: %Verification.Diagnostics{ + plausible_is_on_window: false, + selected_installation_type: "manual" + } + }) + + refute Map.get(interpretation.data || %{}, :offer_custom_url_input) == true + + html = + render_component(@component, + domain: "example.com", + success?: false, + finished?: true, + interpretation: interpretation + ) + + assert [recommendation] = html |> find(@recommendations) |> Enum.map(&text/1) + assert recommendation =~ "Make sure you've copied the snippet" + assert recommendation =~ "verify your installation manually" + refute recommendation =~ "review your installation" + refute recommendation =~ "Learn more" + refute recommendation =~ "See your installation instructions again here" + + assert element_exists?( + html, + ~s|#recommendation a[href="https://plausible.io/docs/troubleshoot-integration#how-to-manually-check-your-integration"]| + ) + + refute element_exists?(html, ~s|#recommendation a[href^="/example.com/installation"]|) + end + test "renders super-admin report" do state = %State{ url: "https://example.com", @@ -99,20 +146,20 @@ defmodule PlausibleWeb.Live.Components.VerificationBannerTest do test "renders a progress message" do html = render_component(@component, domain: "example.com", message: "Arbitrary message") - assert text_of_element(html, @progress) == "Arbitrary message" + assert text_of_element(html, @progress) == "Arbitrary message..." end - test "renders contact link on >3 attempts" do + test "renders contact link on >=3 attempts" do html = render_component(@component, domain: "example.com", attempts: 2, finished?: true) - refute html =~ "Need further help with your installation?" + refute html =~ "Need help?" refute element_exists?(html, ~s|a[href="https://plausible.io/contact"]|) html = render_component(@component, domain: "example.com", attempts: 3, finished?: true) - assert html =~ "Need further help with your installation?" + assert html =~ "Need help?" assert element_exists?(html, ~s|a[href="https://plausible.io/contact"]|) end - test "renders a click-to-show-form link to verify installation at a different URL" do + test "renders a Try another URL ghost button when a custom URL retry is offered" do interpretation = Verification.Checks.interpret_diagnostics(%State{ url: "example.com", @@ -133,11 +180,12 @@ defmodule PlausibleWeb.Live.Components.VerificationBannerTest do interpretation: interpretation ) - assert text_of_element(html, "#verify-custom-url-link") =~ "Click here" + assert text_of_element(html, "#verify-custom-url-link") =~ "Try another URL" assert element_exists?(html, ~s|a#verify-custom-url-link[phx-click="show-custom-url-form"]|) + refute html =~ "Review installation" end - test "renders the custom URL input inline, retry button becomes the form's submit button, hides the prompt link" do + test "renders the custom URL input inline, replacing Check again with the Verify URL submit button, and hides the secondary action" do interpretation = Verification.Checks.interpret_diagnostics(%State{ url: "example.com", @@ -159,9 +207,10 @@ defmodule PlausibleWeb.Live.Components.VerificationBannerTest do refute element_exists?(html, "#verify-custom-url-link") refute element_exists?(html, ~s|a[phx-click="retry"]|) + refute html =~ "Review installation" assert text_of_element(html, ~s|form[phx-submit="verify-custom-url"] button[type="submit"]|) =~ - "Check again" + "Verify URL" assert element_exists?( html, @@ -172,13 +221,12 @@ defmodule PlausibleWeb.Live.Components.VerificationBannerTest do "https://example.com" end - test "offers an installation-instructions escape path on failure, no more settings link" do + test "offers a Review installation ghost button on failure by default" do html = render_component(@component, domain: "example.com", success?: false, finished?: true, - installation_type: "wordpress", flow: PlausibleWeb.Flows.review() ) @@ -188,6 +236,8 @@ defmodule PlausibleWeb.Live.Components.VerificationBannerTest do html, ~s|a[href="/example.com/installation?flow=review"]| ) + + assert html =~ "Review installation" end end end diff --git a/test/plausible_web/live/verification_test.exs b/test/plausible_web/live/verification_test.exs index 896b5edc46ca..4ba6ea3f823c 100644 --- a/test/plausible_web/live/verification_test.exs +++ b/test/plausible_web/live/verification_test.exs @@ -132,7 +132,7 @@ defmodule PlausibleWeb.Live.VerificationTest do assert eventually(fn -> html = render(lv) - {html =~ "Success!", html} + {html =~ "Tracking is active on your site", html} end) end @@ -174,7 +174,7 @@ defmodule PlausibleWeb.Live.VerificationTest do # must stay hidden. assert eventually(fn -> html = render(lv) - {html =~ "Success!", html} + {html =~ "Tracking is active on your site", html} end) html = render(lv) @@ -199,16 +199,16 @@ defmodule PlausibleWeb.Live.VerificationTest do end for {expected_text, saved_installation_type} <- [ - {"Please make sure you've copied the snippet to the head of your site, or verify your installation manually.", + {"Make sure you've copied the snippet to the head of your site, or verify your installation manually.", "manual"}, - {"Please make sure you've initialized Plausible on your site, or verify your installation manually.", + {"Make sure you've initialized Plausible on your site, or verify your installation manually.", "npm"}, - {"Please make sure you've configured the GTM template correctly, or verify your installation manually.", + {"Make sure you've configured the GTM template correctly, or verify your installation manually.", "gtm"}, - {"Please make sure you've enabled the plugin, or verify your installation manually.", + {"Make sure you've enabled the WordPress plugin, or verify your installation manually.", "wordpress"}, # falls back to manual when there's no saved installation type - {"Please make sure you've copied the snippet to the head of your site, or verify your installation manually.", + {"Make sure you've copied the snippet to the head of your site, or verify your installation manually.", nil} ] do @tag :ee_only @@ -246,7 +246,7 @@ defmodule PlausibleWeb.Live.VerificationTest do assert element_exists?(html, @retry_button) - assert html =~ htmlize_quotes(unquote(expected_text)) + assert text_of_element(html, "#recommendation") =~ unquote(expected_text) refute element_exists?(html, "#super-admin-report") end end From 01949f3e96e94cd0ec525f2f5467a5c0c1b127c9 Mon Sep 17 00:00:00 2001 From: Robert Joonas Date: Thu, 23 Jul 2026 10:45:20 +0100 Subject: [PATCH 11/11] emails CTA banner --- .../js/dashboard/email-reports-cta-banner.tsx | 124 ++++++++++++++++++ assets/js/dashboard/index.tsx | 2 + .../first_dashboard_launch_banner.ex | 52 -------- .../controllers/site_controller.ex | 6 +- lib/plausible_web/live/installation.ex | 2 - .../templates/stats/stats.html.heex | 2 - .../controllers/site_controller_test.exs | 12 +- 7 files changed, 133 insertions(+), 67 deletions(-) create mode 100644 assets/js/dashboard/email-reports-cta-banner.tsx delete mode 100644 lib/plausible_web/components/first_dashboard_launch_banner.ex diff --git a/assets/js/dashboard/email-reports-cta-banner.tsx b/assets/js/dashboard/email-reports-cta-banner.tsx new file mode 100644 index 000000000000..8defbd306b23 --- /dev/null +++ b/assets/js/dashboard/email-reports-cta-banner.tsx @@ -0,0 +1,124 @@ +import React, { useEffect, useRef, useState } from 'react' +import { XMarkIcon } from '@heroicons/react/24/outline' +import { useSiteContext } from './site-context' +import { useCurrentVisitorsContext } from './current-visitors-context' + +type CTAStorageState = 'pending' | 'visible' + +function getStorageKey(domain: string) { + return `email_reports_cta_${domain}` +} + +// CTA for configuring weekly email reports +// +// Renders only once, as soon as the first pageview lands. This can happen: +// +// 1. Automatically, when the dashboard stays open -- relying on the value +// of current-visitors changing to something other than 0. +// +// 2. Dashboard is refreshed and showing data for the very first time. +// +// Case 2 is the tricky one. By the time of the refresh, `site.statsBegin` +// is already set, so that value alone can't distinguish "stats just +// started" from "this site has always had stats". +// +// The sessionStorage entry closes that gap -- it's stamped 'pending' the +// moment stats are still absent, so a later reload can still recognize the +// transition. It is only ever stamped while stats are absent, so established +// sites never pick it up and can't retrigger the CTA. +// +// Once shown, the same entry is stamped 'visible', so a refresh mid-display +// resumes the CTA instead of re-deciding from scratch -- but only for three +// seconds -- past that, the sessionStorage entry clears itself out and a +// refresh won't bring the CTA back. +export function EmailReportsCTABanner() { + const site = useSiteContext() + const currentVisitors = useCurrentVisitorsContext() + const hasStats = !!site.statsBegin + const storageKey = getStorageKey(site.domain) + + const hasTriggeredRef = useRef(false) + const [visible, setVisible] = useState(false) + + useEffect(() => { + if (!hasStats && sessionStorage.getItem(storageKey) !== 'visible') { + const state: CTAStorageState = 'pending' + sessionStorage.setItem(storageKey, state) + } + }, [hasStats, storageKey]) + + useEffect(() => { + if (hasTriggeredRef.current) { + return + } + + const storedState = sessionStorage.getItem(storageKey) + + if (storedState === 'visible') { + hasTriggeredRef.current = true + setVisible(true) + return + } + + const firstPageviewJustLanded = hasStats + ? storedState === 'pending' + : !!currentVisitors + + if (!firstPageviewJustLanded) { + return + } + + hasTriggeredRef.current = true + const state: CTAStorageState = 'visible' + sessionStorage.setItem(storageKey, state) + setVisible(true) + }, [hasStats, currentVisitors, storageKey]) + + useEffect(() => { + if (!visible) { + return + } + + const timeout = setTimeout(() => { + sessionStorage.removeItem(storageKey) + }, 3000) + + return () => clearTimeout(timeout) + }, [visible, storageKey]) + + if (!visible) { + return null + } + + function dismiss() { + sessionStorage.removeItem(storageKey) + setVisible(false) + } + + return ( + + ) +} diff --git a/assets/js/dashboard/index.tsx b/assets/js/dashboard/index.tsx index b4c8e0c1cb15..bb2f7ac6be1f 100644 --- a/assets/js/dashboard/index.tsx +++ b/assets/js/dashboard/index.tsx @@ -12,6 +12,7 @@ import { GraphIntervalProvider } from './stats/graph/graph-interval-context' import { ImportsIncludedProvider } from './stats/graph/imports-included-context' import { CurrentVisitorsProvider } from './current-visitors-context' import { VerificationLiveViewPortal } from './verification/portal' +import { EmailReportsCTABanner } from './email-reports-cta-banner' function DashboardStats({ importedDataInView, @@ -23,6 +24,7 @@ function DashboardStats({ return ( <>
    +
    diff --git a/lib/plausible_web/components/first_dashboard_launch_banner.ex b/lib/plausible_web/components/first_dashboard_launch_banner.ex deleted file mode 100644 index 7646368a6fdd..000000000000 --- a/lib/plausible_web/components/first_dashboard_launch_banner.ex +++ /dev/null @@ -1,52 +0,0 @@ -defmodule PlausibleWeb.Components.FirstDashboardLaunchBanner do - @moduledoc """ - A banner that appears on the first dashboard launch - """ - - use PlausibleWeb, :component - - attr(:site, Plausible.Site, required: true) - - def set(assigns) do - ~H""" - - """ - end - - attr(:site, Plausible.Site, required: true) - - def render(assigns) do - ~H""" - - """ - end - - defp x_data(site) do - "{show: !!sessionStorage.getItem('#{storage_key(site)}')}" - end - - defp x_init(site) do - "setTimeout(() => sessionStorage.removeItem('#{storage_key(site)}'), 3000)" - end - - defp storage_key(site) do - "dashboard_seen_#{site.domain}" - end -end diff --git a/lib/plausible_web/controllers/site_controller.ex b/lib/plausible_web/controllers/site_controller.ex index 7413111898eb..108891f486e1 100644 --- a/lib/plausible_web/controllers/site_controller.ex +++ b/lib/plausible_web/controllers/site_controller.ex @@ -50,11 +50,7 @@ defmodule PlausibleWeb.SiteController do end redirect(conn, - to: - Routes.site_path(conn, :installation, site.domain, - site_created: true, - flow: flow - ) + to: Routes.site_path(conn, :installation, site.domain, flow: flow) ) {:error, _, :permission_denied, _} -> diff --git a/lib/plausible_web/live/installation.ex b/lib/plausible_web/live/installation.ex index 8c7f070980b8..e1b38e645664 100644 --- a/lib/plausible_web/live/installation.ex +++ b/lib/plausible_web/live/installation.ex @@ -82,7 +82,6 @@ defmodule PlausibleWeb.Live.Installation do {:ok, assign(socket, site: site, - site_created?: params["site_created"] == "true", flow: flow )} end @@ -104,7 +103,6 @@ defmodule PlausibleWeb.Live.Installation do def render(assigns) do ~H"""
    - <.focus_box> diff --git a/lib/plausible_web/templates/stats/stats.html.heex b/lib/plausible_web/templates/stats/stats.html.heex index aceb45a07ff2..cbd6602bd48b 100644 --- a/lib/plausible_web/templates/stats/stats.html.heex +++ b/lib/plausible_web/templates/stats/stats.html.heex @@ -1,6 +1,4 @@
    - - <%= if Plausible.Teams.locked?(@site.team) do %>