diff --git a/.github/workflows/build-public-images-ghcr.yml b/.github/workflows/build-public-images-ghcr.yml index 8b03f10efeef..5ed49a4041d5 100644 --- a/.github/workflows/build-public-images-ghcr.yml +++ b/.github/workflows/build-public-images-ghcr.yml @@ -2,33 +2,39 @@ name: Build Public Images GHCR on: push: - tags: ['v*'] + tags: ["v*"] concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true +env: + GHCR_REPO: ghcr.io/plausible/community-edition + jobs: build: - runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + include: + - platform: linux/amd64 + runner: ubuntu-22.04 + - platform: linux/arm64 + runner: ubuntu-22.04-arm - permissions: - contents: read - packages: write + runs-on: ${{ matrix.runner || 'ubuntu-22.04' }} steps: + - name: Prepare + run: | + platform=${{ matrix.platform }} + echo "PLATFORM_PAIR=${platform//\//-}" >> $GITHUB_ENV + - name: Docker meta id: meta uses: docker/metadata-action@v5 with: - images: ghcr.io/plausible/community-edition - tags: | - type=semver,pattern={{version}},prefix=v - type=semver,pattern={{major}}.{{minor}},prefix=v - type=semver,pattern={{major}},prefix=v - - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 + images: ${{ env.GHCR_REPO }} - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 @@ -40,14 +46,12 @@ jobs: username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - - name: Build and push + - name: Build id: docker_build uses: docker/build-push-action@v6 with: - push: true - tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} - platforms: linux/amd64, linux/arm64 + outputs: type=image,name=${{ env.GHCR_REPO }},push-by-digest=true,name-canonical=true,push=true cache-from: type=gha cache-to: type=gha,mode=max build-args: | @@ -58,11 +62,65 @@ jobs: - name: Image digest run: echo ${{ steps.docker_build.outputs.digest }} + - name: Export digest + run: | + mkdir -p ${{ runner.temp }}/digests + digest="${{ steps.docker_build.outputs.digest }}" + touch "${{ runner.temp }}/digests/${digest#sha256:}" + + - name: Upload digest + uses: actions/upload-artifact@v4 + with: + name: digests-${{ env.PLATFORM_PAIR }} + path: ${{ runner.temp }}/digests/* + if-no-files-found: error + retention-days: 1 + - name: Notify team on failure if: ${{ failure() }} uses: fjogeleit/http-request-action@v1 with: url: ${{ secrets.BUILD_NOTIFICATION_URL }} - method: 'POST' + method: "POST" customHeaders: '{"Content-Type": "application/json"}' - data: '{"content": "Build failed"}' \ No newline at end of file + data: '{"content": "Build failed"}' + + push: + runs-on: ubuntu-latest + needs: + - build + + steps: + - name: Download digests + uses: actions/download-artifact@v4 + with: + path: ${{ runner.temp }}/digests + pattern: digests-* + merge-multiple: true + + - uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - uses: docker/setup-buildx-action@v3 + + - uses: docker/metadata-action@v5 + id: meta + with: + images: ${{ env.GHCR_REPO }} + tags: | + type=semver,pattern={{version}},prefix=v + type=semver,pattern={{major}}.{{minor}},prefix=v + type=semver,pattern={{major}},prefix=v + + - name: Create manifest list and push + working-directory: ${{ runner.temp }}/digests + run: | + docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ + $(printf '${{ env.GHCR_REPO }}@sha256:%s ' *) + + - name: Inspect image + run: | + docker buildx imagetools inspect ${{ env.GHCR_REPO }}:${{ steps.meta.outputs.version }} diff --git a/.github/workflows/build-public-images.yml b/.github/workflows/build-public-images.yml deleted file mode 100644 index f62748ad722d..000000000000 --- a/.github/workflows/build-public-images.yml +++ /dev/null @@ -1,63 +0,0 @@ -name: Build Public Images - -on: - push: - tags: ['v*'] - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - build: - runs-on: ubuntu-latest - - steps: - - name: Docker meta - id: meta - uses: docker/metadata-action@v5 - with: - images: plausible/community-edition - tags: | - type=semver,pattern={{version}},prefix=v - type=semver,pattern={{major}}.{{minor}},prefix=v - type=semver,pattern={{major}},prefix=v - - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Login to DockerHub - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Build and push - id: docker_build - uses: docker/build-push-action@v6 - with: - push: true - tags: ${{ steps.meta.outputs.tags }} - labels: ${{ steps.meta.outputs.labels }} - platforms: linux/amd64, linux/arm64 - cache-from: type=gha - cache-to: type=gha,mode=max - build-args: | - MIX_ENV=ce - BUILD_METADATA=${{ steps.meta.outputs.json }} - ERL_FLAGS=+JMsingle true - - - name: Image digest - run: echo ${{ steps.docker_build.outputs.digest }} - - - name: Notify team on failure - if: ${{ failure() }} - uses: fjogeleit/http-request-action@v1 - with: - url: ${{ secrets.BUILD_NOTIFICATION_URL }} - method: 'POST' - customHeaders: '{"Content-Type": "application/json"}' - data: '{"content": "Build failed"}' diff --git a/.github/workflows/elixir.yml b/.github/workflows/elixir.yml index bc8cb0e8c9e7..86ccddbfabac 100644 --- a/.github/workflows/elixir.yml +++ b/.github/workflows/elixir.yml @@ -3,7 +3,6 @@ name: Elixir CI on: pull_request: push: - branches: [master, stable] merge_group: concurrency: @@ -11,7 +10,7 @@ concurrency: cancel-in-progress: true env: - CACHE_VERSION: v8 + CACHE_VERSION: release-v215 PERSISTENT_CACHE_DIR: cached jobs: diff --git a/CHANGELOG.md b/CHANGELOG.md index ab7442ad3e70..a717b441d91a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,15 @@ # Changelog All notable changes to this project will be documented in this file. -## Unreleased +## v2.1.5 - 2025-01-17 ### Added + +- Add text version to emails plausible/analytics#4674 + ### Removed -### Changed -### Fixed + +- Removed billing/trial/etc. messages from HTML templates and emails in CE plausible/analytics#4766 plausible/analytics#4897 plausible/analytics#4668 ## v2.1.4 - 2024-10-08 diff --git a/Dockerfile b/Dockerfile index f3a62a570e57..34d594ca937c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -20,8 +20,7 @@ RUN mkdir /app WORKDIR /app # install build dependencies -RUN apk add --no-cache git nodejs yarn python3 npm ca-certificates wget gnupg make gcc libc-dev brotli && \ - npm install npm@latest -g +RUN apk add --no-cache git "nodejs-current=21.7.3-r0" yarn npm python3 ca-certificates wget gnupg make gcc libc-dev brotli COPY mix.exs ./ COPY mix.lock ./ diff --git a/assets/js/dashboard/stats/modals/breakdown-modal.tsx b/assets/js/dashboard/stats/modals/breakdown-modal.tsx index 1d56df1d82ae..2d3550a5a235 100644 --- a/assets/js/dashboard/stats/modals/breakdown-modal.tsx +++ b/assets/js/dashboard/stats/modals/breakdown-modal.tsx @@ -131,7 +131,7 @@ export default function BreakdownModal({ { label: reportInfo.dimensionLabel, key: 'name', - width: 'w-48 md:w-80 flex items-center break-all', + width: 'w-48 md:w-full flex items-center break-all', align: 'left', renderItem: (item) => ( get_var_from_path_or_env("ENABLE_EMAIL_VERIFICATION", "false") - |> String.to_existing_atom() + get_bool_from_path_or_env(config_dir, "ENABLE_EMAIL_VERIFICATION", false) -is_selfhost = - config_dir - |> get_var_from_path_or_env("SELFHOST", "true") - |> String.to_existing_atom() +is_selfhost = get_bool_from_path_or_env(config_dir, "SELFHOST", true) # by default, only registration from invites is enabled in CE disable_registration_default = @@ -279,15 +274,10 @@ custom_script_name = config_dir |> get_var_from_path_or_env("CUSTOM_SCRIPT_NAME", "script") -disable_cron = - config_dir - |> get_var_from_path_or_env("DISABLE_CRON", "false") - |> String.to_existing_atom() +disable_cron = get_bool_from_path_or_env(config_dir, "DISABLE_CRON", false) log_failed_login_attempts = - config_dir - |> get_var_from_path_or_env("LOG_FAILED_LOGIN_ATTEMPTS", "false") - |> String.to_existing_atom() + get_bool_from_path_or_env(config_dir, "LOG_FAILED_LOGIN_ATTEMPTS", false) websocket_url = get_var_from_path_or_env(config_dir, "WEBSOCKET_URL", "") @@ -426,7 +416,7 @@ if config_env() in [:ce, :ce_dev, :ce_test] do end db_maybe_ipv6 = - if get_var_from_path_or_env(config_dir, "ECTO_IPV6") do + if get_bool_from_path_or_env(config_dir, "ECTO_IPV6") do if config_env() in [:ce, :ce_dev, :ce_test] do Logger.warning( "ECTO_IPV6 is no longer necessary as all TCP connections now try IPv6 automatically with IPv4 fallback" @@ -527,9 +517,7 @@ config :plausible, Plausible.HelpScout, config :plausible, :imported, max_buffer_size: get_int_from_path_or_env(config_dir, "IMPORTED_MAX_BUFFER_SIZE", 10_000) -maybe_ch_ipv6 = - get_var_from_path_or_env(config_dir, "ECTO_CH_IPV6", "false") - |> String.to_existing_atom() +maybe_ch_ipv6 = get_bool_from_path_or_env(config_dir, "ECTO_CH_IPV6", false) if maybe_ch_ipv6 && config_env() in [:ce, :ce_dev, :ce_test] do Logger.warning( @@ -637,27 +625,22 @@ case mailer_adapter do password: get_var_from_path_or_env(config_dir, "SMTP_USER_PWD"), tls: :if_available, allowed_tls_versions: [:tlsv1, :"tlsv1.1", :"tlsv1.2"], - ssl: get_var_from_path_or_env(config_dir, "SMTP_HOST_SSL_ENABLED") || false, + ssl: get_bool_from_path_or_env(config_dir, "SMTP_HOST_SSL_ENABLED", false), retries: get_var_from_path_or_env(config_dir, "SMTP_RETRIES") || 2, - no_mx_lookups: get_var_from_path_or_env(config_dir, "SMTP_MX_LOOKUPS_ENABLED") || true + no_mx_lookups: get_bool_from_path_or_env(config_dir, "SMTP_MX_LOOKUPS_ENABLED", true) "Bamboo.Mua" -> config :plausible, Plausible.Mailer, adapter: Bamboo.Mua # prevents common problems with Erlang's TLS v1.3 middlebox_comp_mode = - get_var_from_path_or_env(config_dir, "SMTP_MIDDLEBOX_COMP_MODE", "false") + get_bool_from_path_or_env(config_dir, "SMTP_MIDDLEBOX_COMP_MODE", false) - middlebox_comp_mode = String.to_existing_atom(middlebox_comp_mode) config :plausible, Plausible.Mailer, ssl: [middlebox_comp_mode: middlebox_comp_mode] if relay = get_var_from_path_or_env(config_dir, "SMTP_HOST_ADDR") do port = get_int_from_path_or_env(config_dir, "SMTP_HOST_PORT", 587) - - ssl_enabled = - if ssl_enabled = get_var_from_path_or_env(config_dir, "SMTP_HOST_SSL_ENABLED") do - String.to_existing_atom(ssl_enabled) - end + ssl_enabled = get_bool_from_path_or_env(config_dir, "SMTP_HOST_SSL_ENABLED") protocol = cond do @@ -897,10 +880,7 @@ end config :tzdata, :data_dir, Path.join(persistent_cache_dir || System.tmp_dir!(), "tzdata_data") -promex_disabled? = - config_dir - |> get_var_from_path_or_env("PROMEX_DISABLED", "true") - |> String.to_existing_atom() +promex_disabled? = get_bool_from_path_or_env(config_dir, "PROMEX_DISABLED", true) config :plausible, Plausible.PromEx, disabled: promex_disabled?, @@ -927,10 +907,7 @@ if not is_selfhost do config :plausible, Plausible.Site, default_ingest_threshold: site_default_ingest_threshold end -s3_disabled? = - config_dir - |> get_var_from_path_or_env("S3_DISABLED", "true") - |> String.to_existing_atom() +s3_disabled? = get_bool_from_path_or_env(config_dir, "S3_DISABLED", true) unless s3_disabled? do s3_env = [ diff --git a/lib/plausible/billing/site_locker.ex b/lib/plausible/billing/site_locker.ex index 11b2c4228009..49bcc94670eb 100644 --- a/lib/plausible/billing/site_locker.ex +++ b/lib/plausible/billing/site_locker.ex @@ -66,7 +66,7 @@ defmodule Plausible.Billing.SiteLocker do {:ok, num_updated} end - @spec send_grace_period_end_email(Plausible.Auth.User.t()) :: Plausible.Mailer.result() + @spec send_grace_period_end_email(Plausible.Auth.User.t()) :: :ok | {:error, :unknown_error} def send_grace_period_end_email(user) do usage = Plausible.Billing.Quota.Usage.monthly_pageview_usage(user) suggested_plan = Plausible.Billing.Plans.suggest(user, usage.last_cycle.total) diff --git a/lib/plausible/helpers/config.ex b/lib/plausible/helpers/config.ex index aa28255d03f4..ed5833bd088b 100644 --- a/lib/plausible/helpers/config.ex +++ b/lib/plausible/helpers/config.ex @@ -23,4 +23,31 @@ defmodule Plausible.ConfigHelpers do end end end + + def get_bool_from_path_or_env(config_dir, var_name, default \\ nil) do + case get_var_from_path_or_env(config_dir, var_name) do + nil -> default + var -> parse_bool(var) + end + end + + @var_true ["1", "t", "true", "y", "yes", "on"] + @var_false ["0", "f", "false", "n", "no", "off"] + @var_bool_message Enum.zip_with(@var_true, @var_false, fn t, f -> [t, f] end) + |> List.flatten() + |> Enum.join(", ") + + defp parse_bool(var) do + case String.downcase(var) do + t when t in @var_true -> + true + + f when f in @var_false -> + false + + _ -> + raise ArgumentError, + "Invalid boolean value: #{inspect(var)}. Expected one of: " <> @var_bool_message + end + end end diff --git a/lib/plausible/mailer.ex b/lib/plausible/mailer.ex index f7c6acb0cfce..6fc23fb97ff2 100644 --- a/lib/plausible/mailer.ex +++ b/lib/plausible/mailer.ex @@ -2,34 +2,21 @@ defmodule Plausible.Mailer do use Bamboo.Mailer, otp_app: :plausible require Logger - @type result() :: :ok | {:error, :hard_bounce} | {:error, :unknown_error} - - @spec send(Bamboo.Email.t()) :: result() + @spec send(Bamboo.Email.t()) :: :ok | {:error, :unknown_error} def send(email) do - case deliver_now(email) do - {:ok, _email} -> :ok - {:ok, _email, _response} -> :ok - {:error, error} -> handle_error(error) - end - end - - defp handle_error(%{response: response}) when is_binary(response) do - case Jason.decode(response) do - {:ok, %{"ErrorCode" => 406}} -> - {:error, :hard_bounce} + try do + deliver_now!(email) + rescue + e -> + # this message is ignored by Sentry, only appears in logs + log = "Failed to send e-mail:\n\n " <> Exception.format(:error, e, __STACKTRACE__) + # Sentry report is built entirely from crash_reason + crash_reason = {e, __STACKTRACE__} - {:ok, response} -> - Logger.error("Failed to send e-mail", sentry: %{extra: %{response: response}}) - {:error, :unknown_error} - - {:error, _any} -> - Logger.error("Failed to send e-mail", sentry: %{extra: %{response: response}}) + Logger.error(log, crash_reason: crash_reason) {:error, :unknown_error} + else + _sent_email -> :ok end end - - defp handle_error(error) do - Logger.error("Failed to send e-mail", sentry: %{extra: %{response: error}}) - {:error, :unknown_error} - end end diff --git a/lib/plausible/site.ex b/lib/plausible/site.ex index 5d0ab1391611..5b9f35736991 100644 --- a/lib/plausible/site.ex +++ b/lib/plausible/site.ex @@ -3,6 +3,7 @@ defmodule Plausible.Site do Site schema """ use Ecto.Schema + use Plausible import Ecto.Changeset alias Plausible.Auth.User alias Plausible.Site.GoogleAuth @@ -71,9 +72,15 @@ defmodule Plausible.Site do def new(params), do: changeset(%__MODULE__{}, params) - @domain_unique_error """ - This domain cannot be registered. Perhaps one of your colleagues registered it? If that's not the case, please contact support@plausible.io - """ + on_ee do + @domain_unique_error """ + This domain cannot be registered. Perhaps one of your colleagues registered it? If that's not the case, please contact support@plausible.io + """ + else + @domain_unique_error """ + This domain cannot be registered. Perhaps one of your colleagues registered it? + """ + end def changeset(site, attrs \\ %{}) do site diff --git a/lib/plausible_web/email.ex b/lib/plausible_web/email.ex index 1ee0eeb1bc3c..79a5c7b3e675 100644 --- a/lib/plausible_web/email.ex +++ b/lib/plausible_web/email.ex @@ -1,6 +1,6 @@ defmodule PlausibleWeb.Email do use Plausible - use Bamboo.Phoenix, view: PlausibleWeb.EmailView + import Bamboo.Email import Bamboo.PostmarkHelper def mailer_email_from do @@ -443,24 +443,83 @@ defmodule PlausibleWeb.Email do def priority_email(), do: priority_email(%{layout: "priority_email.html"}) def priority_email(%{layout: layout}) do - base_email(%{layout: layout}) - |> put_param("MessageStream", "priority") + email = base_email(%{layout: layout}) + + if Plausible.ee?() do + put_param(email, "MessageStream", "priority") + else + email + end end def base_email(), do: base_email(%{layout: "base_email.html"}) def base_email(%{layout: layout}) do - mailer_from = Application.get_env(:plausible, :mailer_email) - new_email() |> put_param("TrackOpens", false) - |> from(mailer_from) + |> from(mailer_email_from()) |> maybe_put_layout(layout) end defp maybe_put_layout(email, nil), do: email - defp maybe_put_layout(email, layout) do - put_html_layout(email, {PlausibleWeb.LayoutView, layout}) + defp maybe_put_layout(%{assigns: assigns} = email, layout) do + %{email | assigns: Map.put(assigns, :layout, {PlausibleWeb.LayoutView, layout})} + end + + @doc false + def render(email, template, assigns \\ []) do + assigns = Map.merge(email.assigns, Map.new(assigns)) + html = Phoenix.View.render_to_string(PlausibleWeb.EmailView, template, assigns) + email |> html_body(html) |> text_body(textify(html)) + end + + defp textify(html) do + Floki.parse_fragment!(html) + |> traverse_and_textify() + |> Floki.text() + |> collapse_whitespace() + end + + defp traverse_and_textify([head | tail]) do + [traverse_and_textify(head) | traverse_and_textify(tail)] + end + + defp traverse_and_textify(text) when is_binary(text) do + String.replace(text, "\n", "\s") + end + + defp traverse_and_textify({"a" = tag, attrs, children}) do + href = with {"href", href} <- List.keyfind(attrs, "href", 0), do: href + children = traverse_and_textify(children) + + if href do + text = Floki.text(children) + + if text == href do + # avoids rendering "http://localhost:8000 (http://localhost:8000)" in base_email footer + text + else + IO.iodata_to_binary([text, " (", href, ?)]) + end + else + {tag, attrs, children} + end + end + + defp traverse_and_textify({tag, attrs, children}) do + {tag, attrs, traverse_and_textify(children)} + end + + defp traverse_and_textify(other), do: other + + defp collapse_whitespace(text) do + text + |> String.split("\n") + |> Enum.map_join("\n", fn line -> + line + |> String.split(" ", trim: true) + |> Enum.join(" ") + end) end end diff --git a/lib/plausible_web/mjml/templates/stats_report.mjml.eex b/lib/plausible_web/mjml/templates/stats_report.mjml.eex index 51782cba66c7..ec1016856adc 100644 --- a/lib/plausible_web/mjml/templates/stats_report.mjml.eex +++ b/lib/plausible_web/mjml/templates/stats_report.mjml.eex @@ -15,9 +15,21 @@ + <%= if Plausible.ee?() do %> <%= Plausible.product_name() %> + <% else %> + + <%= Plausible.product_name() %> + + + + Plausible CE is funded by our cloud subscribers. If you enjoy using Plausible + and know someone who might benefit from it, please spread the word. + + + <% end %> <%= @site.domain %> diff --git a/lib/plausible_web/templates/email/create_site_email.html.eex b/lib/plausible_web/templates/email/create_site_email.html.eex index d6c2ca196787..426c2d1fed3f 100644 --- a/lib/plausible_web/templates/email/create_site_email.html.eex +++ b/lib/plausible_web/templates/email/create_site_email.html.eex @@ -1,5 +1,7 @@ -You've activated your free 30-day trial of Plausible, a simple and privacy-friendly website analytics tool. +You've activated<%= if Plausible.ee?() do %> your free 30-day trial of<% end %> <%= Plausible.product_name() %>, a simple and privacy-friendly website analytics tool.

<%= link("Click here", to: "#{plausible_url()}/sites/new") %> to add your website URL, your timezone and install our one-line JavaScript snippet to start collecting visitor statistics. +<%= if Plausible.ee?() do %>

Do reply back to this email if you have any questions or need some guidance. +<% end %> diff --git a/lib/plausible_web/templates/email/site_setup_help_email.html.eex b/lib/plausible_web/templates/email/site_setup_help_email.html.eex index e78e9d85d2c4..26982a252056 100644 --- a/lib/plausible_web/templates/email/site_setup_help_email.html.eex +++ b/lib/plausible_web/templates/email/site_setup_help_email.html.eex @@ -1,4 +1,4 @@ -<%= if Plausible.Users.on_trial?(@user) do %> +<%= if Plausible.ee?() and Plausible.Users.on_trial?(@user) do %> You signed up for a free 30-day trial of Plausible, a simple and privacy-friendly website analytics tool.

<% end %> @@ -7,5 +7,7 @@ To finish your setup for <%= @site.domain %>, review <%= link("your installation This Plausible script is 45 times smaller than Google Analytics script so you’ll have a fast loading site while getting all the important traffic insights on one single page.

On WordPress? We have a <%= link("WordPress plugin", to: "https://plausible.io/wordpress-analytics-plugin") %> that makes the process simpler. We also have <%= link("integration guides", to: "https://plausible.io/docs/integration-guides") %> for different site builders to help you start counting stats in no time. +<%= if Plausible.ee?() do %>

Do reply back to this email if you have any questions or need some guidance. +<% end %> diff --git a/lib/plausible_web/templates/email/spike_notification.html.eex b/lib/plausible_web/templates/email/spike_notification.html.eex index 1b7b031d8894..92fd89465b07 100644 --- a/lib/plausible_web/templates/email/spike_notification.html.eex +++ b/lib/plausible_web/templates/email/spike_notification.html.eex @@ -15,3 +15,9 @@ View dashboard: <%= link(@link, to: @link) %>

Congrats on the spike in traffic! + +<%= if Plausible.ce? do %> +

Plausible CE is funded by our cloud subscribers. If you + enjoy using Plausible + and know someone who might benefit from it, please spread the word. +<% end %> diff --git a/lib/plausible_web/templates/layout/settings.html.heex b/lib/plausible_web/templates/layout/settings.html.heex index 4ec71e06b79f..999a7243d54d 100644 --- a/lib/plausible_web/templates/layout/settings.html.heex +++ b/lib/plausible_web/templates/layout/settings.html.heex @@ -6,7 +6,14 @@ %{key: "Invoices", value: "billing/invoices", icon: :banknotes}, %{key: "API Keys", value: "api-keys", icon: :key}, %{key: "Danger Zone", value: "danger-zone", icon: :exclamation_triangle} - ] %> + ] + + options = + if Plausible.ee?() do + options + else + Enum.reject(options, fn option -> String.contains?(option.value, "billing") end) + end %>
<%= link("← Back to Sites", diff --git a/lib/plausible_web/templates/site/settings_people.html.heex b/lib/plausible_web/templates/site/settings_people.html.heex index 74f2b569643e..496852f14289 100644 --- a/lib/plausible_web/templates/site/settings_people.html.heex +++ b/lib/plausible_web/templates/site/settings_people.html.heex @@ -1,5 +1,5 @@ <.settings_tiles> - <.tile docs="user-roles"> + <.tile docs="users-roles"> <:title>People <:subtitle>Invite your friends or coworkers diff --git a/mix.exs b/mix.exs index ee9adbf52447..bff10a52e8a9 100644 --- a/mix.exs +++ b/mix.exs @@ -63,13 +63,12 @@ defmodule Plausible.MixProject do defp deps do [ {:bamboo, "~> 2.3", override: true}, - {:bamboo_phoenix, "~> 1.0.0"}, {:bamboo_postmark, git: "https://github.com/plausible/bamboo_postmark.git", branch: "main"}, {:bamboo_smtp, "~> 4.1"}, {:bamboo_mua, "~> 0.2.0"}, {:bcrypt_elixir, "~> 3.0"}, {:bypass, "~> 2.1", only: [:dev, :test, :ce_test]}, - {:ecto_ch, "~> 0.3.9"}, + {:ecto_ch, "~> 0.5.0"}, {:cloak, "~> 1.1"}, {:cloak_ecto, "~> 1.2"}, {:combination, "~> 0.0.3"}, diff --git a/mix.lock b/mix.lock index bc915b942b49..59456e4ff966 100644 --- a/mix.lock +++ b/mix.lock @@ -2,15 +2,14 @@ "acceptor_pool": {:hex, :acceptor_pool, "1.0.0", "43c20d2acae35f0c2bcd64f9d2bde267e459f0f3fd23dab26485bf518c281b21", [:rebar3], [], "hexpm", "0cbcd83fdc8b9ad2eee2067ef8b91a14858a5883cb7cd800e6fcd5803e158788"}, "bamboo": {:hex, :bamboo, "2.3.0", "d2392a2cabe91edf488553d3c70638b532e8db7b76b84b0a39e3dfe492ffd6fc", [:mix], [{:hackney, ">= 1.15.2", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.4 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "dd0037e68e108fd04d0e8773921512c940e35d981e097b5793543e3b2f9cd3f6"}, "bamboo_mua": {:hex, :bamboo_mua, "0.2.2", "c50cd41ef684155669e2d99d428fbb87e13797a80829a162b47d3c0a7f7e7ecd", [:mix], [{:bamboo, "~> 2.0", [hex: :bamboo, repo: "hexpm", optional: false]}, {:mail, "~> 0.3.0", [hex: :mail, repo: "hexpm", optional: false]}, {:mua, "~> 0.2.3", [hex: :mua, repo: "hexpm", optional: false]}], "hexpm", "5fe6e3676640578c6fe8f040b34dda8991ebef8566c0601a984eb4771b85b11f"}, - "bamboo_phoenix": {:hex, :bamboo_phoenix, "1.0.0", "f3cc591ffb163ed0bf935d256f1f4645cd870cf436545601215745fb9cc9953f", [:mix], [{:bamboo, ">= 2.0.0", [hex: :bamboo, repo: "hexpm", optional: false]}, {:phoenix, ">= 1.3.0", [hex: :phoenix, repo: "hexpm", optional: false]}], "hexpm", "6db88fbb26019c84a47994bb2bd879c0887c29ce6c559bc6385fd54eb8b37dee"}, "bamboo_postmark": {:git, "https://github.com/plausible/bamboo_postmark.git", "5eac6dfdffacd273bd9aacdd3e494a600bb0b170", [branch: "main"]}, "bamboo_smtp": {:hex, :bamboo_smtp, "4.2.2", "e9f57a2300df9cb496c48751bd7668a86a2b89aa2e79ccaa34e0c46a5f64c3ae", [:mix], [{:bamboo, "~> 2.2.0", [hex: :bamboo, repo: "hexpm", optional: false]}, {:gen_smtp, "~> 1.2.0", [hex: :gen_smtp, repo: "hexpm", optional: false]}], "hexpm", "28cac2ec8adaae02aed663bf68163992891a3b44cfd7ada0bebe3e09bed7207f"}, "bcrypt_elixir": {:hex, :bcrypt_elixir, "3.1.0", "0b110a9a6c619b19a7f73fa3004aa11d6e719a67e672d1633dc36b6b2290a0f7", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "2ad2acb5a8bc049e8d5aa267802631912bb80d5f4110a178ae7999e69dca1bf7"}, "bunt": {:hex, :bunt, "1.0.0", "081c2c665f086849e6d57900292b3a161727ab40431219529f13c4ddcf3e7a44", [:mix], [], "hexpm", "dc5f86aa08a5f6fa6b8096f0735c4e76d54ae5c9fa2c143e5a1fc7c1cd9bb6b5"}, "bypass": {:hex, :bypass, "2.1.0", "909782781bf8e20ee86a9cabde36b259d44af8b9f38756173e8f5e2e1fabb9b1", [:mix], [{:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: false]}, {:ranch, "~> 1.3", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "d9b5df8fa5b7a6efa08384e9bbecfe4ce61c77d28a4282f79e02f1ef78d96b80"}, - "castore": {:hex, :castore, "1.0.8", "dedcf20ea746694647f883590b82d9e96014057aff1d44d03ec90f36a5c0dc6e", [:mix], [], "hexpm", "0b2b66d2ee742cb1d9cb8c8be3b43c3a70ee8651f37b75a8b982e036752983f1"}, + "castore": {:hex, :castore, "1.0.9", "5cc77474afadf02c7c017823f460a17daa7908e991b0cc917febc90e466a375c", [:mix], [], "hexpm", "5ea956504f1ba6f2b4eb707061d8e17870de2bee95fb59d512872c2ef06925e7"}, "certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"}, - "ch": {:hex, :ch, "0.2.7", "29565d4ee8b0ae11df03f308f1cf4dfc04dbebe169a3d565d7c9283c18384af2", [:mix], [{:db_connection, "~> 2.0", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.12", [hex: :ecto, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: false]}], "hexpm", "3a906077411b0f39fb6d19f599b91f746d6a55674333f83272c6bed12055efa5"}, + "ch": {:hex, :ch, "0.2.9", "8273e27b741f2a31410c0c6291700abfbd262d0d9bd70b51c0deef624d6079b8", [:mix], [{:db_connection, "~> 2.0", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:ecto, "~> 3.12", [hex: :ecto, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: false]}], "hexpm", "889a12ad2dae69a6136b7109cbc73ba4f0d719f48d1a4c567ad3f95ad752a9c9"}, "chatterbox": {:hex, :ts_chatterbox, "0.15.1", "5cac4d15dd7ad61fc3c4415ce4826fc563d4643dee897a558ec4ea0b1c835c9c", [:rebar3], [{:hpack, "~> 0.3.0", [hex: :hpack_erl, repo: "hexpm", optional: false]}], "hexpm", "4f75b91451338bc0da5f52f3480fa6ef6e3a2aeecfc33686d6b3d0a0948f31aa"}, "cldr_utils": {:hex, :cldr_utils, "2.27.0", "a75d5cdaaf6b7432eb10f547e6abe635c94746985c5b78e35bbbd08b16473b6c", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.5", [hex: :certifi, repo: "hexpm", optional: true]}, {:decimal, "~> 1.9 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}], "hexpm", "516f601e28da10b8f1f3af565321c4e3da3b898a0b50a5e5be425eff76d587e1"}, "cloak": {:hex, :cloak, "1.1.2", "7e0006c2b0b98d976d4f559080fabefd81f0e0a50a3c4b621f85ceeb563e80bb", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "940d5ac4fcd51b252930fd112e319ea5ae6ab540b722f3ca60a85666759b9585"}, @@ -31,10 +30,10 @@ "digital_token": {:hex, :digital_token, "0.6.0", "13e6de581f0b1f6c686f7c7d12ab11a84a7b22fa79adeb4b50eec1a2d278d258", [:mix], [{:cldr_utils, "~> 2.17", [hex: :cldr_utils, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "2455d626e7c61a128b02a4a8caddb092548c3eb613ac6f6a85e4cbb6caddc4d1"}, "double": {:hex, :double, "0.8.2", "8e1cfcccdaef76c18846bc08e555555a2a699b806fa207b6468572a60513cc6a", [:mix], [], "hexpm", "90287642b2ec86125e0457aaba2ab0e80f7d7050cc80a0cef733e59bd70aa67c"}, "earmark_parser": {:hex, :earmark_parser, "1.4.39", "424642f8335b05bb9eb611aa1564c148a8ee35c9c8a8bba6e129d51a3e3c6769", [:mix], [], "hexpm", "06553a88d1f1846da9ef066b87b57c6f605552cfbe40d20bd8d59cc6bde41944"}, - "ecto": {:hex, :ecto, "3.12.2", "bae2094f038e9664ce5f089e5f3b6132a535d8b018bd280a485c2f33df5c0ce1", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "492e67c70f3a71c6afe80d946d3ced52ecc57c53c9829791bfff1830ff5a1f0c"}, - "ecto_ch": {:hex, :ecto_ch, "0.3.9", "220ef4452aaccbc2bcb80f02a31ac24ef4febf095d672f17fcd22ec0c626b63e", [:mix], [{:ch, "~> 0.2.7", [hex: :ch, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.12", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "7ab1909d06462e20eb5fbff9564042942ee72589983bc3789d354e88a2fa6b7c"}, + "ecto": {:hex, :ecto, "3.12.4", "267c94d9f2969e6acc4dd5e3e3af5b05cdae89a4d549925f3008b2b7eb0b93c3", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ef04e4101688a67d061e1b10d7bc1fbf00d1d13c17eef08b71d070ff9188f747"}, + "ecto_ch": {:hex, :ecto_ch, "0.5.0", "f65dcc3b7b0c85726259471a91c34045852ce8b8817ea29becb3afdb28b3e987", [:mix], [{:ch, "~> 0.2.7", [hex: :ch, repo: "hexpm", optional: false]}, {:ecto_sql, "~> 3.12", [hex: :ecto_sql, repo: "hexpm", optional: false]}], "hexpm", "c8bb1c75e20983f16b285d7df21825ce2e3ba15d690bab930ea4133568ae2136"}, "ecto_network": {:hex, :ecto_network, "1.5.0", "a930c910975e7a91237b858ebf0f4ad7b2aae32fa846275aa203cb858459ec73", [:mix], [{:ecto_sql, ">= 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:phoenix_html, ">= 0.0.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:postgrex, ">= 0.14.0", [hex: :postgrex, repo: "hexpm", optional: false]}], "hexpm", "4d614434ae3e6d373a2f693d56aafaa3f3349714668ffd6d24e760caf578aa2f"}, - "ecto_sql": {:hex, :ecto_sql, "3.12.0", "73cea17edfa54bde76ee8561b30d29ea08f630959685006d9c6e7d1e59113b7d", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.12", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.19 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dc9e4d206f274f3947e96142a8fdc5f69a2a6a9abb4649ef5c882323b6d512f0"}, + "ecto_sql": {:hex, :ecto_sql, "3.12.1", "c0d0d60e85d9ff4631f12bafa454bc392ce8b9ec83531a412c12a0d415a3a4d0", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.12", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.7", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.19 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "aff5b958a899762c5f09028c847569f7dfb9cc9d63bdb8133bff8a5546de6bf5"}, "elixir_make": {:hex, :elixir_make, "0.7.7", "7128c60c2476019ed978210c245badf08b03dbec4f24d05790ef791da11aa17c", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm", "5bc19fff950fad52bbe5f211b12db9ec82c6b34a9647da0c2224b8b8464c7e6c"}, "envy": {:hex, :envy, "1.1.1", "0bc9bd654dec24fcdf203f7c5aa1b8f30620f12cfb28c589d5e9c38fe1b07475", [:mix], [], "hexpm", "7061eb1a47415fd757145d8dec10dc0b1e48344960265cb108f194c4252c3a89"}, "eqrcode": {:hex, :eqrcode, "0.1.10", "6294fece9d68ad64eef1c3c92cf111cfd6469f4fbf230a2d4cc905a682178f3f", [:mix], [], "hexpm", "da30e373c36a0fd37ab6f58664b16029919896d6c45a68a95cc4d713e81076f1"}, @@ -86,7 +85,7 @@ "mjml": {:hex, :mjml, "3.1.0", "549e985bc03be1af563c62a34c8e62bdb8d0baaa6b31af705a5bdf67e20f22b7", [:mix], [{:rustler, ">= 0.0.0", [hex: :rustler, repo: "hexpm", optional: true]}, {:rustler_precompiled, "~> 0.7.0", [hex: :rustler_precompiled, repo: "hexpm", optional: false]}], "hexpm", "987674d296b14b628e5e5d2d8b910e6501cdfafa0239527d8b633880dc595344"}, "mjml_eex": {:hex, :mjml_eex, "0.11.0", "f0845730f4caccddea7c98ab5ad1485831446b7c09896fa5ed54b3fa0c431e72", [:mix], [{:erlexec, "~> 2.0", [hex: :erlexec, repo: "hexpm", optional: true]}, {:mjml, "~> 1.0 or ~> 2.0 or ~> 3.0", [hex: :mjml, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.2 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "0c60732fe766336ec504a94cad4ebf30405f05fa8920a544ff0ef936252438ac"}, "mox": {:hex, :mox, "1.1.0", "0f5e399649ce9ab7602f72e718305c0f9cdc351190f72844599545e4996af73c", [:mix], [], "hexpm", "d44474c50be02d5b72131070281a5d3895c0e7a95c780e90bc0cfe712f633a13"}, - "mua": {:hex, :mua, "0.2.3", "46b29b7b2bb14105c0b7be9526f7c452df17a7841b30b69871c024a822ff551c", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm", "7fe861a87fcc06a980d3941bbcb2634e5f0f30fd6ad15ef6c0423ff9dc7e46de"}, + "mua": {:hex, :mua, "0.2.4", "a9172ab0a1ac8732cf2699d739ceac3febcb9b4ffc540260ad2e32c0b6632af9", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm", "e7e4dacd5ad65f13e3542772e74a159c00bd2d5579e729e9bb72d2c73a266fb7"}, "nanoid": {:hex, :nanoid, "2.1.0", "d192a5bf1d774258bc49762b480fca0e3128178fa6d35a464af2a738526607fd", [:mix], [], "hexpm", "ebc7a342d02d213534a7f93a091d569b9fea7f26fcd3a638dc655060fc1f76ac"}, "nimble_csv": {:hex, :nimble_csv, "1.2.0", "4e26385d260c61eba9d4412c71cea34421f296d5353f914afe3f2e71cce97722", [:mix], [], "hexpm", "d0628117fcc2148178b034044c55359b26966c6eaa8e2ce15777be3bbc91b12a"}, "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, @@ -124,7 +123,7 @@ "plug": {:hex, :plug, "1.16.1", "40c74619c12f82736d2214557dedec2e9762029b2438d6d175c5074c933edc9d", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a13ff6b9006b03d7e33874945b2755253841b238c34071ed85b0e86057f8cddc"}, "plug_cowboy": {:hex, :plug_cowboy, "2.7.2", "fdadb973799ae691bf9ecad99125b16625b1c6039999da5fe544d99218e662e4", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "245d8a11ee2306094840c000e8816f0cbed69a23fc0ac2bcf8d7835ae019bb2f"}, "plug_crypto": {:hex, :plug_crypto, "1.2.5", "918772575e48e81e455818229bf719d4ab4181fcbf7f85b68a35620f78d89ced", [:mix], [], "hexpm", "26549a1d6345e2172eb1c233866756ae44a9609bd33ee6f99147ab3fd87fd842"}, - "postgrex": {:hex, :postgrex, "0.19.1", "73b498508b69aded53907fe48a1fee811be34cc720e69ef4ccd568c8715495ea", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "8bac7885a18f381e091ec6caf41bda7bb8c77912bb0e9285212829afe5d8a8f8"}, + "postgrex": {:hex, :postgrex, "0.19.2", "34d6884a332c7bf1e367fc8b9a849d23b43f7da5c6e263def92784d03f9da468", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "618988886ab7ae8561ebed9a3c7469034bf6a88b8995785a3378746a4b9835ec"}, "prom_ex": {:hex, :prom_ex, "1.9.0", "63e6dda6c05cdeec1f26c48443dcc38ffd2118b3665ae8d2bd0e5b79f2aea03e", [:mix], [{:absinthe, ">= 1.6.0", [hex: :absinthe, repo: "hexpm", optional: true]}, {:broadway, ">= 1.0.2", [hex: :broadway, repo: "hexpm", optional: true]}, {:ecto, ">= 3.5.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:finch, "~> 0.15", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: false]}, {:oban, ">= 2.4.0", [hex: :oban, repo: "hexpm", optional: true]}, {:octo_fetch, "~> 0.3", [hex: :octo_fetch, repo: "hexpm", optional: false]}, {:phoenix, ">= 1.5.0", [hex: :phoenix, repo: "hexpm", optional: true]}, {:phoenix_live_view, ">= 0.14.0", [hex: :phoenix_live_view, repo: "hexpm", optional: true]}, {:plug, ">= 1.12.1", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, "~> 2.5", [hex: :plug_cowboy, repo: "hexpm", optional: false]}, {:telemetry, ">= 1.0.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}, {:telemetry_metrics_prometheus_core, "~> 1.0", [hex: :telemetry_metrics_prometheus_core, repo: "hexpm", optional: false]}, {:telemetry_poller, "~> 1.0", [hex: :telemetry_poller, repo: "hexpm", optional: false]}], "hexpm", "01f3d4f69ec93068219e686cc65e58a29c42bea5429a8ff4e2121f19db178ee6"}, "public_suffix": {:git, "https://github.com/axelson/publicsuffix-elixir", "fa40c243d4b5d8598b90cff268bc4e33f3bb63f1", []}, "ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"}, diff --git a/test/plausible/config_test.exs b/test/plausible/config_test.exs index 07ae08c1c1a2..46eaa2c8d185 100644 --- a/test/plausible/config_test.exs +++ b/test/plausible/config_test.exs @@ -1,5 +1,45 @@ defmodule Plausible.ConfigTest do use ExUnit.Case + import Plausible.ConfigHelpers + + describe "get_bool_from_path_or_env/3" do + test "parses truthy vars" do + truthy = ["1", "t", "true", "y", "yes", "on"] + + for var <- truthy do + env = [{"ENABLE_EMAIL_VERIFICATION", var}] + config = runtime_config(env) + assert get_in(config, [:plausible, :selfhost, :enable_email_verification]) == true + end + end + + test "parses false vars" do + falsy = ["0", "f", "false", "n", "no", "off"] + + for var <- falsy do + env = [{"ENABLE_EMAIL_VERIFICATION", var}] + config = runtime_config(env) + assert get_in(config, [:plausible, :selfhost, :enable_email_verification]) == false + end + end + + test "supports defaults" do + put_system_env_undo([{"ENABLE_EMAIL_VERIFICATION", nil}]) + config_dir = "/run/secrets" + + assert get_bool_from_path_or_env(config_dir, "ENABLE_EMAIL_VERIFICATION") == nil + assert get_bool_from_path_or_env(config_dir, "ENABLE_EMAIL_VERIFICATION", true) == true + assert get_bool_from_path_or_env(config_dir, "ENABLE_EMAIL_VERIFICATION", false) == false + end + + test "raises on invalid var" do + env = [{"ENABLE_EMAIL_VERIFICATION", "YOLO"}] + + assert_raise ArgumentError, + "Invalid boolean value: \"YOLO\". Expected one of: 1, 0, t, f, true, false, y, n, yes, no, on, off", + fn -> runtime_config(env) end + end + end describe "mailer" do test "mailer email default" do @@ -132,9 +172,9 @@ defmodule Plausible.ConfigTest do {:password, "one"}, {:tls, :if_available}, {:allowed_tls_versions, [:tlsv1, :"tlsv1.1", :"tlsv1.2"]}, - {:ssl, "true"}, + {:ssl, true}, {:retries, "3"}, - {:no_mx_lookups, "true"} + {:no_mx_lookups, true} ] end diff --git a/test/plausible/imported/csv_importer_test.exs b/test/plausible/imported/csv_importer_test.exs index 3bb6f8ca08a2..2153e85b204c 100644 --- a/test/plausible/imported/csv_importer_test.exs +++ b/test/plausible/imported/csv_importer_test.exs @@ -541,6 +541,9 @@ defmodule Plausible.Imported.CSVImporterTest do assert email.html_body =~ ~s[Please click here to start the download process.] + assert email.text_body =~ + ~r[Please click here \(http://localhost:8000/#{URI.encode_www_form(exported_site.domain)}/download/export\) to start the download process.] + # download archive on_ee do ExAws.request!( diff --git a/test/plausible_web/controllers/site_controller_test.exs b/test/plausible_web/controllers/site_controller_test.exs index 277d2a295c9a..ec5dee748279 100644 --- a/test/plausible_web/controllers/site_controller_test.exs +++ b/test/plausible_web/controllers/site_controller_test.exs @@ -416,6 +416,12 @@ defmodule PlausibleWeb.SiteControllerTest do assert html_response(conn, 200) =~ "This domain cannot be registered. Perhaps one of your colleagues registered it?" + + if Plausible.ee?() do + assert html_response(conn, 200) =~ "support@plausible.io" + else + refute html_response(conn, 200) =~ "support@plausible.io" + end end test "renders form again when domain was changed from elsewhere", %{conn: conn} do @@ -433,6 +439,12 @@ defmodule PlausibleWeb.SiteControllerTest do assert html_response(conn, 200) =~ "This domain cannot be registered. Perhaps one of your colleagues registered it?" + + if Plausible.ee?() do + assert html_response(conn, 200) =~ "support@plausible.io" + else + refute html_response(conn, 200) =~ "support@plausible.io" + end end test "allows creating the site if domain was changed by the owner", %{ diff --git a/test/plausible_web/email_test.exs b/test/plausible_web/email_test.exs index e161d2a99a01..0de0455c52ad 100644 --- a/test/plausible_web/email_test.exs +++ b/test/plausible_web/email_test.exs @@ -13,6 +13,7 @@ defmodule PlausibleWeb.EmailTest do }) assert email.html_body =~ "Hey John," + assert email.text_body =~ "Hey John," end test "greets impersonally when user not in template assigns" do @@ -21,6 +22,7 @@ defmodule PlausibleWeb.EmailTest do |> Email.render("welcome_email.html") assert email.html_body =~ "Hey," + assert email.text_body =~ "Hey," end test "renders plausible link" do @@ -29,6 +31,7 @@ defmodule PlausibleWeb.EmailTest do |> Email.render("welcome_email.html") assert email.html_body =~ plausible_link() + assert email.text_body =~ plausible_url() end @tag :ee_only @@ -49,11 +52,15 @@ defmodule PlausibleWeb.EmailTest do refute email.html_body =~ "Hey John," refute email.html_body =~ plausible_link() + + refute email.text_body =~ "Hey John," + refute email.text_body =~ plausible_url() end end describe "priority email layout" do - test "uses the `priority` message stream in Postmark" do + @tag :ee_only + test "uses the `priority` message stream in Postmark in EE" do email = Email.priority_email() |> Email.render("activation_email.html", %{ @@ -64,6 +71,18 @@ defmodule PlausibleWeb.EmailTest do assert %{"MessageStream" => "priority"} = email.private[:message_params] end + @tag :ce_build_only + test "doesn't use the `priority` message stream in Postmark in CE" do + email = + Email.priority_email() + |> Email.render("activation_email.html", %{ + user: build(:user, name: "John Doe"), + code: "123" + }) + + refute email.private[:message_params]["MessageStream"] + end + test "greets user by first name if user in template assigns" do email = Email.priority_email() @@ -73,6 +92,7 @@ defmodule PlausibleWeb.EmailTest do }) assert email.html_body =~ "Hey John," + assert email.text_body =~ "Hey John," end test "greets impersonally when user not in template assigns" do @@ -83,6 +103,7 @@ defmodule PlausibleWeb.EmailTest do }) assert email.html_body =~ "Hey," + assert email.text_body =~ "Hey," end test "renders plausible link" do @@ -93,6 +114,7 @@ defmodule PlausibleWeb.EmailTest do }) assert email.html_body =~ plausible_link() + assert email.text_body =~ plausible_url() end test "does not render unsubscribe placeholder" do @@ -114,6 +136,9 @@ defmodule PlausibleWeb.EmailTest do refute email.html_body =~ "Hey John," refute email.html_body =~ plausible_link() + + refute email.text_body =~ "Hey John," + refute email.text_body =~ plausible_url() end end @@ -275,7 +300,7 @@ defmodule PlausibleWeb.EmailTest do end end - describe "site_setup_success" do + describe "site setup emails" do setup do trial_user = build(:user, @@ -285,31 +310,122 @@ defmodule PlausibleWeb.EmailTest do ) site = build(:site, members: [trial_user]) - email = PlausibleWeb.Email.site_setup_success(trial_user, site) - {:ok, email: email} + + emails = [ + PlausibleWeb.Email.create_site_email(trial_user), + PlausibleWeb.Email.site_setup_help(trial_user, site), + PlausibleWeb.Email.site_setup_success(trial_user, site) + ] + + {:ok, emails: emails} end + @trial_message "trial" + @reply_message "reply back" + @tag :ee_only - test "renders 'trial' and 'reply' blocks", %{email: email} do - assert email.html_body =~ - "You're on a 30-day free trial with no obligations so do take your time to explore Plausible." + test "has 'trial' and 'reply' blocks, correct product name", %{emails: emails} do + for email <- emails do + assert email.html_body =~ @trial_message + assert email.html_body =~ @reply_message + refute email.html_body =~ "Plausible CE" + end + + assert Enum.any?(emails, fn email -> email.html_body =~ "Plausible Analytics" end) + end + + @tag :ce_build_only + test "no 'trial' or 'reply' blocks, correct product name", %{emails: emails} do + for email <- emails do + refute email.html_body =~ @trial_message + refute email.html_body =~ @reply_message + refute email.html_body =~ "Plausible Analytics" + end + + assert Enum.any?(emails, fn email -> email.html_body =~ "Plausible CE" end) + end + end + + describe "text_body" do + @tag :ee_only + test "welcome_email (EE)" do + email = + Email.base_email() + |> Email.render("welcome_email.html", %{ + user: build(:user, name: "John Doe"), + code: "123" + }) + + assert email.text_body == """ + Hey John, + + We are building Plausible to provide a simple and ethical approach to tracking website visitors. We're super excited to have you on board! + + Here's how to get the most out of your Plausible experience: + + * Enable email reports (https://plausible.io/docs/email-reports) and notifications for traffic spikes (https://plausible.io/docs/traffic-spikes) + * Integrate with Search Console (https://plausible.io/docs/google-search-console-integration) to get keyword phrases people find your site with + * Invite team members and other collaborators (https://plausible.io/docs/users-roles) + * Set up easy goals including 404 error pages (https://plausible.io/docs/error-pages-tracking-404), file downloads (https://plausible.io/docs/file-downloads-tracking) and outbound link clicks (https://plausible.io/docs/outbound-link-click-tracking) + * Opt out from counting your own visits (https://plausible.io/docs/excluding) + * If you're concerned about adblockers, set up a proxy to bypass them (https://plausible.io/docs/proxy/introduction) + - assert email.html_body =~ - "Do reply back to this email if you have any questions. We're here to help." + Then you're ready to start exploring your fast loading, ethical and actionable Plausible dashboard (https://plausible.io/sites). + + Have a question, feedback or need some guidance? Do reply back to this email. + + Regards, + The Plausible Team 💌 + + -- + + http://localhost:8000 + {{{ pm:unsubscribe }}}\ + """ end @tag :ce_build_only - test "does not render 'trial' and 'reply' blocks", %{email: email} do - refute email.html_body =~ - "You're on a 30-day free trial with no obligations so do take your time to explore Plausible." + test "welcome_email (CE)" do + email = + Email.base_email() + |> Email.render("welcome_email.html", %{ + user: build(:user, name: "John Doe"), + code: "123" + }) + + assert email.text_body == """ + Hey John, + + We are building Plausible to provide a simple and ethical approach to tracking website visitors. We're super excited to have you on board! + + Here's how to get the most out of your Plausible experience: + + * Enable email reports (https://plausible.io/docs/email-reports) and notifications for traffic spikes (https://plausible.io/docs/traffic-spikes) + * Integrate with Search Console (https://plausible.io/docs/google-search-console-integration) to get keyword phrases people find your site with + * Invite team members and other collaborators (https://plausible.io/docs/users-roles) + * Set up easy goals including 404 error pages (https://plausible.io/docs/error-pages-tracking-404), file downloads (https://plausible.io/docs/file-downloads-tracking) and outbound link clicks (https://plausible.io/docs/outbound-link-click-tracking) + * Opt out from counting your own visits (https://plausible.io/docs/excluding) + * If you're concerned about adblockers, set up a proxy to bypass them (https://plausible.io/docs/proxy/introduction) - refute email.html_body =~ - "Do reply back to this email if you have any questions. We're here to help." + + Then you're ready to start exploring your fast loading, ethical and actionable Plausible dashboard (https://plausible.io/sites). + + Have a question, feedback or need some guidance? Do reply back to this email. + + -- + + http://localhost:8000 + """ end end + def plausible_url do + PlausibleWeb.EmailView.plausible_url() + end + def plausible_link() do - plausible_url = PlausibleWeb.EmailView.plausible_url() + plausible_url = plausible_url() "#{plausible_url}" end end diff --git a/test/workers/notify_exported_analytics_test.exs b/test/workers/notify_exported_analytics_test.exs index b3caecbe075d..c869c909cd9c 100644 --- a/test/workers/notify_exported_analytics_test.exs +++ b/test/workers/notify_exported_analytics_test.exs @@ -27,6 +27,7 @@ defmodule Plausible.Workers.NotifyExportedAnalyticsTest do assert_receive {:delivered_email, email} assert email.html_body =~ "was unsuccessful." + assert email.text_body =~ "was unsuccessful." end end end