diff --git a/lib/plausible_web/components/generic.ex b/lib/plausible_web/components/generic.ex index 1d2a62b06..f5f0d5be4 100644 --- a/lib/plausible_web/components/generic.ex +++ b/lib/plausible_web/components/generic.ex @@ -71,13 +71,21 @@ defmodule PlausibleWeb.Components.Generic do def button_link(assigns) do theme_class = if assigns.disabled do - "bg-gray-400 text-white dark:text-white dark:text-gray-400 dark:bg-gray-700 pointer-events-none cursor-default" + "bg-gray-400 text-white dark:text-white dark:text-gray-400 dark:bg-gray-700 cursor-not-allowed" else @button_themes[assigns.theme] end + onclick = + if assigns.disabled do + "return false;" + else + assigns[:onclick] + end + assigns = assign(assigns, + onclick: onclick, button_base_class: @button_base_class, theme_class: theme_class ) @@ -85,6 +93,7 @@ defmodule PlausibleWeb.Components.Generic do ~H""" <.link href={@href} + onclick={@onclick} class={[ @button_base_class, @theme_class, diff --git a/lib/plausible_web/live/imports_exports_settings.ex b/lib/plausible_web/live/imports_exports_settings.ex index 56e5fa7b8..4e0851a75 100644 --- a/lib/plausible_web/live/imports_exports_settings.ex +++ b/lib/plausible_web/live/imports_exports_settings.ex @@ -48,35 +48,59 @@ defmodule PlausibleWeb.Live.ImportsExportsSettings do &(&1.live_status in [SiteImport.pending(), SiteImport.importing()]) ) - assigns = assign(assigns, :import_in_progress?, import_in_progress?) + at_maximum? = length(assigns.site_imports) >= assigns.max_imports + + csv_imports_exports_enabled? = FunWithFlags.enabled?(:csv_imports_exports, for: assigns.site) + + import_warning = + cond do + import_in_progress? -> + "No new imports can be started until the import in progress is completed or cancelled." + + at_maximum? -> + "Maximum of #{assigns.max_imports} imports is reached. " <> + "Delete or cancel an existing import to start a new one." + + true -> + nil + end + + assigns = + assign(assigns, + import_in_progress?: import_in_progress?, + at_maximum?: at_maximum?, + import_warning: import_warning, + csv_imports_exports_enabled?: csv_imports_exports_enabled? + ) ~H"""
<.button_link class="w-36 h-20" theme="bright" - disabled={@import_in_progress?} + disabled={@import_in_progress? or @at_maximum?} href={Plausible.Google.API.import_authorize_url(@site.id, "import", legacy: false)} > Google Analytics import <.button_link + :if={@csv_imports_exports_enabled?} class="w-36 h-20" theme="bright" - disabled={@import_in_progress?} + disabled={@import_in_progress? or @at_maximum?} href={"/#{URI.encode_www_form(@site.domain)}/settings/import"} > New CSV import
-

- No new imports can be started until the import in progress is completed or cancelled. +

+ <%= @import_warning %>

-

+

Existing Imports

@@ -94,12 +118,6 @@ defmodule PlausibleWeb.Live.ImportsExportsSettings do

  • - <%= Plausible.Imported.SiteImport.label(entry.site_import) %> - - (<%= PlausibleWeb.StatsView.large_number_format( - Map.get(@pageview_counts, entry.site_import.id, 0) - ) %> page views) - + <%= Plausible.Imported.SiteImport.label(entry.site_import) %> + + (<%= PlausibleWeb.StatsView.large_number_format( + Map.get(@pageview_counts, entry.site_import.id, 0) + ) %> page views) +

    From <%= format_date(entry.site_import.start_date) %> to <%= format_date( diff --git a/lib/plausible_web/templates/google_analytics/confirm.html.heex b/lib/plausible_web/templates/google_analytics/confirm.html.heex index cf128d901..c3d37d8db 100644 --- a/lib/plausible_web/templates/google_analytics/confirm.html.heex +++ b/lib/plausible_web/templates/google_analytics/confirm.html.heex @@ -1,4 +1,4 @@ -<%= form_for @conn, Routes.google_analytics_path(@conn, :import, @site.domain), [class: "max-w-md w-full mx-auto bg-white dark:bg-gray-800 shadow-md rounded px-8 pt-6 pb-8 mb-4 mt-8"], fn f -> %> +<%= form_for @conn, Routes.google_analytics_path(@conn, :import, @site.domain), [onsubmit: "confirmButton.disabled = true; return true;", class: "max-w-md w-full mx-auto bg-white dark:bg-gray-800 shadow-md rounded px-8 pt-6 pb-8 mb-4 mt-8"], fn f -> %>

    Import from Google Analytics

    <%= hidden_input(f, :access_token, value: @access_token) %> @@ -48,5 +48,33 @@
    - <%= submit("Confirm import", class: "button mt-6") %> +
    +

    + + Go back + +

    + + <%= submit(name: "confirmButton", class: "button sm:w-auto w-full [&>span.label-enabled]:block [&>span.label-disabled]:hidden [&[disabled]>span.label-enabled]:hidden [&[disabled]>span.label-disabled]:block") do %> + + Confirm import + + + + + Starting import... + + <% end %> +
    <% end %> diff --git a/lib/plausible_web/templates/google_analytics/property_or_view_form.html.heex b/lib/plausible_web/templates/google_analytics/property_or_view_form.html.heex index 993a2769e..775355947 100644 --- a/lib/plausible_web/templates/google_analytics/property_or_view_form.html.heex +++ b/lib/plausible_web/templates/google_analytics/property_or_view_form.html.heex @@ -1,4 +1,4 @@ -<%= form_for @conn, Routes.google_analytics_path(@conn, :property_or_view, @site.domain), [class: "max-w-md w-full mx-auto bg-white dark:bg-gray-800 shadow-md rounded px-8 pt-6 pb-8 mb-4 mt-8"], fn f -> %> +<%= form_for @conn, Routes.google_analytics_path(@conn, :property_or_view, @site.domain), [onsubmit: "continueButton.disabled = true; return true;", class: "max-w-md w-full mx-auto bg-white dark:bg-gray-800 shadow-md rounded px-8 pt-6 pb-8 mb-4 mt-8"], fn f -> %>

    Import from Google Analytics

    <%= hidden_input(f, :access_token, value: @access_token) %> @@ -19,5 +19,25 @@ <%= styled_error(@conn.assigns[:selected_property_or_view_error]) %> - <%= submit("Continue ->", class: "button mt-6") %> +
    +

    + + Go back + +

    + + <%= submit(name: "continueButton", class: "button sm:w-auto w-full [&>span.label-enabled]:block [&>span.label-disabled]:hidden [&[disabled]>span.label-enabled]:hidden [&[disabled]>span.label-disabled]:block") do %> + + Continue -> + + + + + Checking... + + <% end %> +
    <% end %> diff --git a/lib/plausible_web/templates/google_analytics/user_metric_form.html.heex b/lib/plausible_web/templates/google_analytics/user_metric_form.html.heex index e17a3ee2a..6a969b332 100644 --- a/lib/plausible_web/templates/google_analytics/user_metric_form.html.heex +++ b/lib/plausible_web/templates/google_analytics/user_metric_form.html.heex @@ -32,17 +32,36 @@

    - <%= link("Continue ->", - to: - Routes.google_analytics_path(@conn, :confirm, @site.domain, - property_or_view: @property_or_view, - access_token: @access_token, - refresh_token: @refresh_token, - expires_at: @expires_at, - start_date: @start_date, - end_date: @end_date, - legacy: @legacy - ), - class: "button mt-6" - ) %> +
    +

    + + Go back + +

    + + <%= link("Continue ->", + to: + Routes.google_analytics_path(@conn, :confirm, @site.domain, + property_or_view: @property_or_view, + access_token: @access_token, + refresh_token: @refresh_token, + expires_at: @expires_at, + start_date: @start_date, + end_date: @end_date, + legacy: @legacy + ), + class: "button sm:w-auto w-full" + ) %> +
    diff --git a/lib/plausible_web/templates/site/settings_imports_exports.html.heex b/lib/plausible_web/templates/site/settings_imports_exports.html.heex index 4dc9fbe08..439c0d065 100644 --- a/lib/plausible_web/templates/site/settings_imports_exports.html.heex +++ b/lib/plausible_web/templates/site/settings_imports_exports.html.heex @@ -15,21 +15,23 @@ ) %> -
    -
    -

    - Export Data -

    -

    - Export all your data into CSV format. -

    -
    +<%= if FunWithFlags.enabled?(:csv_imports_exports, for: @site) do %> +
    +
    +

    + Export Data +

    +

    + Export all your data into CSV format. +

    +
    - <%= live_render(@conn, PlausibleWeb.Live.CSVExport, - session: %{ - "site_id" => @site.id, - "email_to" => @current_user.email, - "storage" => on_full_build(do: "s3", else: "local") - } - ) %> -
    + <%= live_render(@conn, PlausibleWeb.Live.CSVExport, + session: %{ + "site_id" => @site.id, + "email_to" => @current_user.email, + "storage" => on_full_build(do: "s3", else: "local") + } + ) %> +
    +<% end %> diff --git a/lib/plausible_web/templates/site/settings_integrations.html.heex b/lib/plausible_web/templates/site/settings_integrations.html.heex index 59acd3b4e..adb56e0dd 100644 --- a/lib/plausible_web/templates/site/settings_integrations.html.heex +++ b/lib/plausible_web/templates/site/settings_integrations.html.heex @@ -2,10 +2,13 @@ site={@site} search_console_domains={@search_console_domains} /> - + +<%= if not FunWithFlags.enabled?(:imports_exports, for: @site) do %> + +<% end %>
    - if membership.role in [:owner, :admin] do - PlausibleWeb.Email.import_success(site_import, membership.user) - |> Plausible.Mailer.send() - end - end) + PlausibleWeb.Email.import_success(site_import, site_import.imported_by) + |> Plausible.Mailer.send() Plausible.Sites.clear_stats_start_date!(site_import.site) @@ -84,15 +80,11 @@ defmodule Plausible.Workers.ImportAnalytics do site_import = site_import |> import_api.mark_failed() - |> Repo.preload(site: [memberships: :user]) + |> Repo.preload([:site, :imported_by]) Importer.notify(site_import, :fail) - Enum.each(site_import.site.memberships, fn membership -> - if membership.role in [:owner, :admin] do - PlausibleWeb.Email.import_failure(site_import, membership.user) - |> Plausible.Mailer.send() - end - end) + PlausibleWeb.Email.import_failure(site_import, site_import.imported_by) + |> Plausible.Mailer.send() end end diff --git a/priv/repo/seeds.exs b/priv/repo/seeds.exs index 08f721459..5fb79c994 100644 --- a/priv/repo/seeds.exs +++ b/priv/repo/seeds.exs @@ -11,6 +11,7 @@ # and so on) as they will fail if something goes wrong. FunWithFlags.enable(:imports_exports) +FunWithFlags.enable(:csv_imports_exports) FunWithFlags.enable(:hostname_filter) FunWithFlags.enable(:shield_hostnames) diff --git a/test/plausible_web/controllers/site_controller_test.exs b/test/plausible_web/controllers/site_controller_test.exs index b4de6c3bb..0ed74bc18 100644 --- a/test/plausible_web/controllers/site_controller_test.exs +++ b/test/plausible_web/controllers/site_controller_test.exs @@ -681,6 +681,18 @@ defmodule PlausibleWeb.SiteControllerTest do assert resp =~ "(98 page views)" end + test "disables import buttons when imports are at maximum", %{conn: conn, site: site} do + insert_list(Plausible.Imported.max_complete_imports(), :site_import, + site: site, + status: SiteImport.completed() + ) + + conn = get(conn, "/#{site.domain}/settings/imports-exports") + + assert html_response(conn, 200) =~ + "Maximum of #{Plausible.Imported.max_complete_imports()} imports is reached." + end + test "disables import buttons when there's import in progress", %{conn: conn, site: site} do _site_import1 = insert(:site_import, site: site, status: SiteImport.completed()) _site_import2 = insert(:site_import, site: site, status: SiteImport.importing()) diff --git a/test/test_helper.exs b/test/test_helper.exs index 196f78065..4b11fbbfd 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -6,6 +6,7 @@ end Mox.defmock(Plausible.HTTPClient.Mock, for: Plausible.HTTPClient.Interface) Application.ensure_all_started(:double) FunWithFlags.enable(:imports_exports) +FunWithFlags.enable(:csv_imports_exports) # Temporary flag to test `experimental_reduced_joins` flag on all tests. if System.get_env("TEST_EXPERIMENTAL_REDUCED_JOINS") == "1" do diff --git a/test/workers/import_analytics_test.exs b/test/workers/import_analytics_test.exs index 91cf3d6fb..94f5d330a 100644 --- a/test/workers/import_analytics_test.exs +++ b/test/workers/import_analytics_test.exs @@ -80,6 +80,34 @@ defmodule Plausible.Workers.ImportAnalyticsTest do ) end + test "send email after successful import only to the user who ran the import", %{ + import_opts: import_opts + } do + owner = insert(:user, trial_expiry_date: Timex.today() |> Timex.shift(days: 1)) + site = insert(:site, members: [owner]) + + importing_user = insert(:user) + + insert(:site_membership, site: site, user: importing_user, role: :admin) + + {:ok, job} = Plausible.Imported.NoopImporter.new_import(site, importing_user, import_opts) + + assert :ok = + job + |> Repo.reload!() + |> ImportAnalytics.perform() + + assert_email_delivered_with( + to: [importing_user], + subject: "Noop data imported for #{site.domain}" + ) + + refute_email_delivered_with( + to: [owner], + subject: "Noop data imported for #{site.domain}" + ) + end + test "updates site import record after failed import", %{import_opts: import_opts} do user = insert(:user, trial_expiry_date: Timex.today() |> Timex.shift(days: 1)) site = insert(:site, members: [user]) @@ -137,6 +165,35 @@ defmodule Plausible.Workers.ImportAnalyticsTest do subject: "Noop import failed for #{site.domain}" ) end + + test "sends email after failed import only to the user who ran the import", %{ + import_opts: import_opts + } do + owner = insert(:user, trial_expiry_date: Timex.today() |> Timex.shift(days: 1)) + site = insert(:site, members: [owner]) + import_opts = Keyword.put(import_opts, :error, true) + + importing_user = insert(:user) + + insert(:site_membership, site: site, user: importing_user, role: :admin) + + {:ok, job} = Plausible.Imported.NoopImporter.new_import(site, importing_user, import_opts) + + assert {:discard, _} = + job + |> Repo.reload!() + |> ImportAnalytics.perform() + + assert_email_delivered_with( + to: [importing_user], + subject: "Noop import failed for #{site.domain}" + ) + + refute_email_delivered_with( + to: [owner], + subject: "Noop import failed for #{site.domain}" + ) + end end describe "perform/1 notifications" do