analytics/lib/plausible_web/live/installation.ex

352 lines
11 KiB
Elixir

defmodule PlausibleWeb.Live.Installation do
@moduledoc """
User assistance module around Plausible installation instructions/onboarding
"""
use Plausible
use PlausibleWeb, :live_view
require Logger
alias PlausibleWeb.Flows
alias Phoenix.LiveView.AsyncResult
alias PlausibleWeb.Live.Installation.Icons
alias PlausibleWeb.Live.Installation.Instructions
on_ee do
alias Plausible.InstallationSupport.{Detection, Result}
end
def mount(
%{"domain" => domain} = params,
_session,
socket
) do
site =
Plausible.Sites.get_for_user!(socket.assigns.current_user, domain,
roles: [
:owner,
:admin,
:editor,
:super_admin,
:viewer
]
)
flow = params["flow"] || Flows.provisioning()
socket =
on_ee do
if connected?(socket) do
assign_async(
socket,
[
:recommended_installation_type,
:installation_type,
:tracker_script_configuration_form,
:v1_detected
],
fn -> initialize_installation_data(flow, site, params) end
)
else
assign_loading_states(socket)
end
else
# On Community Edition, there's no v1 detection, nor pre-installation
# site scan - we just default the pre-selected tab to "manual".
# Although it's functionally unnecessary, we stick to using `%AsyncResult{}`
# for these assigns to minimize branching out the CE code and maintain only
# a single `render` function.
{:ok, installation_data} = initialize_installation_data(flow, site, params)
assign(socket,
recommended_installation_type: %AsyncResult{
result: installation_data.recommended_installation_type,
ok?: true
},
installation_type: %AsyncResult{
result: installation_data.installation_type,
ok?: true
},
tracker_script_configuration_form: %AsyncResult{
result: installation_data.tracker_script_configuration_form,
ok?: true
},
v1_detected: %AsyncResult{
result: installation_data.v1_detected,
ok?: true
}
)
end
{:ok,
assign(socket,
site: site,
flow: flow
)}
end
def handle_params(params, _url, socket) do
socket =
if connected?(socket) && socket.assigns.recommended_installation_type.result &&
params["type"] in PlausibleWeb.Tracker.supported_installation_types() do
assign(socket,
installation_type: %AsyncResult{result: params["type"]}
)
else
socket
end
{:noreply, socket}
end
def render(assigns) do
~H"""
<div>
<PlausibleWeb.Components.FlowProgress.render flow={@flow} current_step="Install Plausible" />
<.focus_box>
<.async_result :let={recommended_installation_type} assign={@recommended_installation_type}>
<:loading>
<div class="text-center text-gray-500">
{if(@flow == Flows.review(),
do: "Scanning your site to detect how Plausible is integrated...",
else: "Determining the simplest integration path for your website..."
)}
</div>
<div class="flex items-center justify-center py-8">
<.spinner class="w-6 h-6" />
</div>
</:loading>
<div class="grid grid-cols-2 sm:flex sm:flex-row gap-1 rounded-md">
<.tab
patch={"?type=manual&flow=#{@flow}"}
selected={@installation_type.result == "manual"}
>
<Icons.script_icon /> Script
</.tab>
<.tab
patch={"?type=wordpress&flow=#{@flow}"}
selected={@installation_type.result == "wordpress"}
>
<Icons.wordpress_icon /> WordPress
</.tab>
<%= on_ee do %>
<.tab patch={"?type=gtm&flow=#{@flow}"} selected={@installation_type.result == "gtm"}>
<Icons.tag_manager_icon /> Tag Manager
</.tab>
<% end %>
<.tab patch={"?type=npm&flow=#{@flow}"} selected={@installation_type.result == "npm"}>
<Icons.npm_icon /> NPM
</.tab>
</div>
<%= on_ee do %>
<.outdated_script_notice
:if={@v1_detected.result == true}
recommended_installation_type={@recommended_installation_type}
installation_type={@installation_type}
/>
<% end %>
<.form for={@tracker_script_configuration_form.result} phx-submit="submit" class="mt-4">
<.input
type="hidden"
field={@tracker_script_configuration_form.result[:installation_type]}
value={@installation_type.result}
/>
<Instructions.manual_instructions
:if={@installation_type.result == "manual"}
tracker_script_configuration_form={@tracker_script_configuration_form.result}
/>
<Instructions.wordpress_instructions
:if={@installation_type.result == "wordpress"}
flow={@flow}
recommended_installation_type={recommended_installation_type}
/>
<%= on_ee do %>
<Instructions.gtm_instructions
:if={@installation_type.result == "gtm"}
recommended_installation_type={recommended_installation_type}
tracker_script_configuration_form={@tracker_script_configuration_form.result}
/>
<% end %>
<Instructions.npm_instructions :if={@installation_type.result == "npm"} />
<.button type="submit" class="w-full mt-8">
{verify_cta(@installation_type.result)}
</.button>
</.form>
</.async_result>
<:footer :if={ce?() and @installation_type.result == "manual"}>
<.focus_list>
<:item>
Still using the legacy snippet with the data-domain attribute? See
<.styled_link href="https://plausible.io/docs/script-update-guide">
migration guide
</.styled_link>
</:item>
</.focus_list>
</:footer>
</.focus_box>
</div>
"""
end
defp verify_cta("manual"), do: "Verify Script installation"
defp verify_cta("wordpress"), do: "Verify WordPress installation"
defp verify_cta("gtm"), do: "Verify Tag Manager installation"
defp verify_cta("npm"), do: "Verify NPM installation"
on_ee do
defp detect_recommended_installation_type(flow, site) do
with {:ok, detection_result} <-
Detection.Checks.run_with_rate_limit(nil, site.domain,
detect_v1?: flow == Flows.review(),
report_to: nil,
slowdown: 0,
async?: false
),
%Result{ok?: true, data: data} <-
Detection.Checks.interpret_diagnostics(detection_result) do
{data.suggested_technology, data.v1_detected}
else
_ -> {PlausibleWeb.Tracker.fallback_installation_type(), false}
end
end
else
defp detect_recommended_installation_type(_flow, _site) do
{PlausibleWeb.Tracker.fallback_installation_type(), false}
end
end
on_ee do
defp outdated_script_notice(assigns) do
~H"""
<div :if={
@recommended_installation_type.result == "manual" and
@installation_type.result == "manual"
}>
<.notice class="mt-4" theme={:yellow}>
Your website is running an outdated version of the tracking script. Please
<.styled_link new_tab href="https://plausible.io/docs/script-update-guide">
update
</.styled_link>
your tracking script before continuing
</.notice>
</div>
<div :if={
@recommended_installation_type.result == "gtm" and
@installation_type.result == "gtm"
}>
<.notice class="mt-4" theme={:yellow}>
Your website might be using an outdated version of our Google Tag Manager template.
If so,
<.styled_link new_tab href="https://plausible.io/docs/script-update-guide#gtm">
update
</.styled_link>
your Google Tag Manager template before continuing
</.notice>
</div>
"""
end
defp assign_loading_states(socket) do
assign(socket,
recommended_installation_type: AsyncResult.loading(),
v1_detected: AsyncResult.loading(),
installation_type: AsyncResult.loading(),
tracker_script_configuration_form: AsyncResult.loading()
)
end
end
attr :selected, :boolean, default: false
attr :patch, :string, required: true
slot :inner_block, required: true
defp tab(assigns) do
base_classes =
"rounded-md px-3.5 py-2.5 text-sm font-medium flex items-center flex-1 justify-center whitespace-nowrap hover:bg-gray-100 dark:hover:bg-gray-750 transition-colors duration-150"
selected_class =
if assigns[:selected] do
"bg-gray-150 dark:bg-gray-700"
else
"bg-transparent cursor-pointer"
end
assigns = assign(assigns, class: "#{selected_class} #{base_classes}")
~H"""
<.link patch={@patch} class={@class}>
{render_slot(@inner_block)}
</.link>
"""
end
def handle_event("submit", %{"tracker_script_configuration" => params}, socket) do
config =
PlausibleWeb.Tracker.update_script_configuration!(
socket.assigns.site,
params,
:installation
)
{:noreply,
push_navigate(socket,
to:
Routes.site_path(socket, :verification, socket.assigns.site.domain,
flow: socket.assigns.flow,
installation_type: config.installation_type
)
)}
end
defp initialize_installation_data(flow, site, params) do
{recommended_installation_type, v1_detected} =
detect_recommended_installation_type(flow, site)
tracker_script_configuration =
PlausibleWeb.Tracker.get_or_create_tracker_script_configuration!(site, %{
outbound_links: true,
form_submissions: true,
file_downloads: true,
track_404_pages: true,
installation_type: recommended_installation_type
})
selected_installation_type =
cond do
params["type"] in PlausibleWeb.Tracker.supported_installation_types() ->
params["type"]
flow == Flows.review() and
not is_nil(tracker_script_configuration.installation_type) ->
Atom.to_string(tracker_script_configuration.installation_type)
true ->
recommended_installation_type
end
{:ok,
%{
recommended_installation_type: recommended_installation_type,
v1_detected: v1_detected,
installation_type: selected_installation_type,
tracker_script_configuration_form:
to_form(
Plausible.Site.TrackerScriptConfiguration.installation_changeset(
tracker_script_configuration,
%{}
)
)
}}
end
end