| defmodule PlausibleWeb.Live.Installation do |
| @moduledoc """ |
| User assistance module around Plausible installation instructions/onboarding |
| """ |
|
|
| use Plausible |
| use PlausibleWeb, :live_view |
|
|
| 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 |
| |
| |
|
|
| |
| |
| |
|
|
| {: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, |
| site_created?: params["site_created"] == "true", |
| 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.FirstDashboardLaunchBanner.set :if={@site_created?} site={@site} /> |
| <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-lg p-1 border border-gray-300 dark:border-gray-700"> |
| <.tab |
| patch={"?type=manual&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 " <> |
| install_method_event_classes( |
| @installation_type.result, |
| recommended_installation_type |
| ) |
| } |
| > |
| {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" |
| |
| defp install_method_event_classes(installation_type, recommended) do |
| method = installation_method_label(installation_type) |
| match = if installation_type == recommended, do: "true", else: "false" |
| |
| Enum.join( |
| [ |
| "plausible-event-name=Site+installation+method", |
| "plausible-event-method=#{method}", |
| "plausible-event-recommended_match=#{match}" |
| ], |
| " " |
| ) |
| end |
| |
| defp installation_method_label("manual"), do: "script" |
| defp installation_method_label(other), do: other |
| |
| 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 py-2 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 |
| |