Always recommend a suitable plan on the choose-plan page (#4222)

* pull last_bill_date from paddle sandbox in mix task

* move cycle usage checks to Quota module

* move quota.ex to a subfolder

* split up Quota module

* set choose-plan pageview slider according to usage

* silence credo
This commit is contained in:
RobertJoonas 2024-06-17 09:25:46 +03:00 committed by GitHub
parent 86d7031336
commit dd1d74ccb7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
22 changed files with 486 additions and 446 deletions

View File

@ -48,6 +48,7 @@ defmodule Mix.Tasks.PullSandboxSubscription do
update_url: res["update_url"], update_url: res["update_url"],
user_id: user.id, user_id: user.id,
status: res["state"], status: res["state"],
last_bill_date: res["last_payment"]["date"],
next_bill_date: res["next_payment"]["date"], next_bill_date: res["next_payment"]["date"],
next_bill_amount: res["next_payment"]["amount"] |> to_string(), next_bill_amount: res["next_payment"]["amount"] |> to_string(),
currency_code: res["next_payment"]["currency"] currency_code: res["next_payment"]["currency"]

View File

@ -132,7 +132,7 @@ defmodule Plausible.Billing.Feature do
def check_availability(%Plausible.Auth.User{} = user) do def check_availability(%Plausible.Auth.User{} = user) do
cond do cond do
free?() -> :ok free?() -> :ok
__MODULE__ in Quota.allowed_features_for(user) -> :ok __MODULE__ in Quota.Limits.allowed_features_for(user) -> :ok
true -> {:error, :upgrade_required} true -> {:error, :upgrade_required}
end end
end end

View File

@ -235,7 +235,7 @@ defmodule Plausible.Billing.Plans do
[] []
end end
if Enum.any?(Quota.features_usage(user), &(&1 not in growth_features)) do if Enum.any?(Quota.Usage.features_usage(user), &(&1 not in growth_features)) do
:business :business
else else
:growth :growth

View File

@ -0,0 +1,120 @@
defmodule Plausible.Billing.Quota.Limits do
@moduledoc false
use Plausible
alias Plausible.Users
alias Plausible.Auth.User
alias Plausible.Billing.{Plan, Plans, Subscription, EnterprisePlan, Feature}
alias Plausible.Billing.Feature.{Goals, Props, StatsAPI}
@type over_limits_error() :: {:over_plan_limits, [limit()]}
@typep limit() :: :site_limit | :pageview_limit | :team_member_limit
@pageview_allowance_margin 0.1
on_ee do
@limit_sites_since ~D[2021-05-05]
@site_limit_for_trials 10
@team_member_limit_for_trials 3
@spec site_limit(User.t()) :: non_neg_integer() | :unlimited
def site_limit(user) do
if Timex.before?(user.inserted_at, @limit_sites_since) do
:unlimited
else
get_site_limit_from_plan(user)
end
end
defp get_site_limit_from_plan(user) do
user = Users.with_subscription(user)
case Plans.get_subscription_plan(user.subscription) do
%{site_limit: site_limit} -> site_limit
:free_10k -> 50
nil -> @site_limit_for_trials
end
end
@spec team_member_limit(User.t()) :: non_neg_integer()
def team_member_limit(user) do
user = Users.with_subscription(user)
case Plans.get_subscription_plan(user.subscription) do
%{team_member_limit: limit} -> limit
:free_10k -> :unlimited
nil -> @team_member_limit_for_trials
end
end
else
def site_limit(_) do
:unlimited
end
def team_member_limit(_) do
:unlimited
end
end
@monthly_pageview_limit_for_free_10k 10_000
@monthly_pageview_limit_for_trials :unlimited
@spec monthly_pageview_limit(User.t() | Subscription.t()) ::
non_neg_integer() | :unlimited
def monthly_pageview_limit(%User{} = user) do
user = Users.with_subscription(user)
monthly_pageview_limit(user.subscription)
end
def monthly_pageview_limit(subscription) do
case Plans.get_subscription_plan(subscription) do
%EnterprisePlan{monthly_pageview_limit: limit} ->
limit
%Plan{monthly_pageview_limit: limit} ->
limit
:free_10k ->
@monthly_pageview_limit_for_free_10k
_any ->
if subscription do
Sentry.capture_message("Unknown monthly pageview limit for plan",
extra: %{paddle_plan_id: subscription.paddle_plan_id}
)
end
@monthly_pageview_limit_for_trials
end
end
def pageview_limit_with_margin(limit, margin \\ nil) do
margin = if margin, do: margin, else: @pageview_allowance_margin
ceil(limit * (1 + margin))
end
@doc """
Returns a list of features the user can use. Trial users have the
ability to use all features during their trial.
"""
def allowed_features_for(user) do
user = Users.with_subscription(user)
case Plans.get_subscription_plan(user.subscription) do
%EnterprisePlan{features: features} ->
features
%Plan{features: features} ->
features
:free_10k ->
[Goals, Props, StatsAPI]
nil ->
if Users.on_trial?(user) do
Feature.list()
else
[Goals]
end
end
end
end

View File

@ -0,0 +1,123 @@
defmodule Plausible.Billing.Quota do
@moduledoc """
This module provides functions to work with plans usage and limits.
"""
use Plausible
alias Plausible.Users
alias Plausible.Auth.User
alias Plausible.Billing.{Plan, Plans, EnterprisePlan}
alias Plausible.Billing.Quota.{Usage, Limits}
@doc """
Enterprise plans are always allowed to add more sites (even when
over limit) to avoid service disruption. Their usage is checked
in a background job instead (see `check_usage.ex`).
"""
def ensure_can_add_new_site(user) do
user = Users.with_subscription(user)
case Plans.get_subscription_plan(user.subscription) do
%EnterprisePlan{} ->
:ok
_ ->
usage = Usage.site_usage(user)
limit = Limits.site_limit(user)
if below_limit?(usage, limit), do: :ok, else: {:error, {:over_limit, limit}}
end
end
@doc """
Ensures that the given user (or the usage map) is within the limits
of the given plan.
An `opts` argument can be passed with `ignore_pageview_limit: true`
which bypasses the pageview limit check and returns `:ok` as long as
the other limits are not exceeded.
"""
@spec ensure_within_plan_limits(User.t() | map(), struct() | atom() | nil, Keyword.t()) ::
:ok | {:error, Limits.over_limits_error()}
def ensure_within_plan_limits(user_or_usage, plan, opts \\ [])
def ensure_within_plan_limits(%User{} = user, %plan_mod{} = plan, opts)
when plan_mod in [Plan, EnterprisePlan] do
ensure_within_plan_limits(Usage.usage(user), plan, opts)
end
def ensure_within_plan_limits(usage, %plan_mod{} = plan, opts)
when plan_mod in [Plan, EnterprisePlan] do
case exceeded_limits(usage, plan, opts) do
[] -> :ok
exceeded_limits -> {:error, {:over_plan_limits, exceeded_limits}}
end
end
def ensure_within_plan_limits(_, _, _), do: :ok
defp exceeded_limits(usage, plan, opts) do
for {limit, exceeded?} <- [
{:team_member_limit, not within_limit?(usage.team_members, plan.team_member_limit)},
{:site_limit, not within_limit?(usage.sites, plan.site_limit)},
{:monthly_pageview_limit,
exceeds_monthly_pageview_limit?(usage.monthly_pageviews, plan, opts)}
],
exceeded? do
limit
end
end
defp exceeds_monthly_pageview_limit?(usage, plan, opts) do
if Keyword.get(opts, :ignore_pageview_limit) do
false
else
case usage do
%{last_30_days: %{total: total}} ->
margin = Keyword.get(opts, :pageview_allowance_margin)
limit = Limits.pageview_limit_with_margin(plan.monthly_pageview_limit, margin)
!within_limit?(total, limit)
cycles_usage ->
exceeds_last_two_usage_cycles?(cycles_usage, plan.monthly_pageview_limit)
end
end
end
@spec exceeds_last_two_usage_cycles?(Usage.cycles_usage(), non_neg_integer()) :: boolean()
def exceeds_last_two_usage_cycles?(cycles_usage, allowed_volume) do
exceeded = exceeded_cycles(cycles_usage, allowed_volume)
:penultimate_cycle in exceeded && :last_cycle in exceeded
end
@spec exceeded_cycles(Usage.cycles_usage(), non_neg_integer()) :: list()
def exceeded_cycles(cycles_usage, allowed_volume) do
limit = Limits.pageview_limit_with_margin(allowed_volume)
Enum.reduce(cycles_usage, [], fn {cycle, %{total: total}}, exceeded_cycles ->
if below_limit?(total, limit) do
exceeded_cycles
else
exceeded_cycles ++ [cycle]
end
end)
end
@spec below_limit?(non_neg_integer(), non_neg_integer() | :unlimited) :: boolean()
@doc """
Returns whether the usage is below the limit or not.
Returns false if usage is equal to the limit.
"""
def below_limit?(usage, limit) do
if limit == :unlimited, do: true, else: usage < limit
end
@spec within_limit?(non_neg_integer(), non_neg_integer() | :unlimited) :: boolean()
@doc """
Returns whether the usage is within the limit or not.
Returns true if usage is equal to the limit.
"""
def within_limit?(usage, limit) do
if limit == :unlimited, do: true, else: usage <= limit
end
end

View File

@ -1,34 +1,26 @@
defmodule Plausible.Billing.Quota do defmodule Plausible.Billing.Quota.Usage do
@moduledoc """ @moduledoc false
This module provides functions to work with plans usage and limits.
"""
use Plausible use Plausible
import Ecto.Query import Ecto.Query
alias Plausible.Users alias Plausible.Users
alias Plausible.Auth.User alias Plausible.Auth.User
alias Plausible.Site alias Plausible.Site
alias Plausible.Billing.{Plan, Plans, Subscription, Subscriptions, EnterprisePlan, Feature} alias Plausible.Billing.{Subscriptions}
alias Plausible.Billing.Feature.{Goals, RevenueGoals, Funnels, Props, StatsAPI} alias Plausible.Billing.Feature.{RevenueGoals, Funnels, Props, StatsAPI}
@type limit() :: :site_limit | :pageview_limit | :team_member_limit @type cycles_usage() :: %{cycle() => usage_cycle()}
@type over_limits_error() :: {:over_plan_limits, [limit()]} @typep cycle :: :current_cycle | :last_cycle | :penultimate_cycle
@typep last_30_days_usage() :: %{:last_30_days => usage_cycle()}
@typep monthly_pageview_usage() :: cycles_usage() | last_30_days_usage()
@type monthly_pageview_usage() :: %{period() => usage_cycle()} @typep usage_cycle :: %{
date_range: Date.Range.t(),
@type period :: :last_30_days | :current_cycle | :last_cycle | :penultimate_cycle pageviews: non_neg_integer(),
custom_events: non_neg_integer(),
@type usage_cycle :: %{ total: non_neg_integer()
date_range: Date.Range.t(), }
pageviews: non_neg_integer(),
custom_events: non_neg_integer(),
total: non_neg_integer()
}
@pageview_allowance_margin 0.1
def pageview_allowance_margin(), do: @pageview_allowance_margin
def usage(user, opts \\ []) do def usage(user, opts \\ []) do
basic_usage = %{ basic_usage = %{
@ -45,50 +37,6 @@ defmodule Plausible.Billing.Quota do
end end
end end
on_ee do
@limit_sites_since ~D[2021-05-05]
@site_limit_for_trials 10
@team_member_limit_for_trials 3
@spec site_limit(User.t()) :: non_neg_integer() | :unlimited
def site_limit(user) do
if Timex.before?(user.inserted_at, @limit_sites_since) do
:unlimited
else
get_site_limit_from_plan(user)
end
end
defp get_site_limit_from_plan(user) do
user = Users.with_subscription(user)
case Plans.get_subscription_plan(user.subscription) do
%{site_limit: site_limit} -> site_limit
:free_10k -> 50
nil -> @site_limit_for_trials
end
end
@spec team_member_limit(User.t()) :: non_neg_integer()
def team_member_limit(user) do
user = Users.with_subscription(user)
case Plans.get_subscription_plan(user.subscription) do
%{team_member_limit: limit} -> limit
:free_10k -> :unlimited
nil -> @team_member_limit_for_trials
end
end
else
def site_limit(_) do
:unlimited
end
def team_member_limit(_) do
:unlimited
end
end
@spec site_usage(User.t()) :: non_neg_integer() @spec site_usage(User.t()) :: non_neg_integer()
@doc """ @doc """
Returns the number of sites the given user owns. Returns the number of sites the given user owns.
@ -97,58 +45,6 @@ defmodule Plausible.Billing.Quota do
Plausible.Sites.owned_sites_count(user) Plausible.Sites.owned_sites_count(user)
end end
@doc """
Enterprise plans are always allowed to add more sites (even when
over limit) to avoid service disruption. Their usage is checked
in a background job instead (see `check_usage.ex`).
"""
def ensure_can_add_new_site(user) do
user = Users.with_subscription(user)
case Plans.get_subscription_plan(user.subscription) do
%EnterprisePlan{} ->
:ok
_ ->
usage = site_usage(user)
limit = site_limit(user)
if below_limit?(usage, limit), do: :ok, else: {:error, {:over_limit, limit}}
end
end
@monthly_pageview_limit_for_free_10k 10_000
@monthly_pageview_limit_for_trials :unlimited
@spec monthly_pageview_limit(User.t() | Subscription.t()) ::
non_neg_integer() | :unlimited
def monthly_pageview_limit(%User{} = user) do
user = Users.with_subscription(user)
monthly_pageview_limit(user.subscription)
end
def monthly_pageview_limit(subscription) do
case Plans.get_subscription_plan(subscription) do
%EnterprisePlan{monthly_pageview_limit: limit} ->
limit
%Plan{monthly_pageview_limit: limit} ->
limit
:free_10k ->
@monthly_pageview_limit_for_free_10k
_any ->
if subscription do
Sentry.capture_message("Unknown monthly pageview limit for plan",
extra: %{paddle_plan_id: subscription.paddle_plan_id}
)
end
@monthly_pageview_limit_for_trials
end
end
@doc """ @doc """
Queries the ClickHouse database for the monthly pageview usage. If the given user's Queries the ClickHouse database for the monthly pageview usage. If the given user's
subscription is `active`, `past_due`, or a `deleted` (but not yet expired), a map subscription is `active`, `past_due`, or a `deleted` (but not yet expired), a map
@ -195,8 +91,7 @@ defmodule Plausible.Billing.Quota do
end end
end end
@spec usage_cycle(User.t(), period(), list() | nil, Date.t()) :: usage_cycle() @spec usage_cycle(User.t(), :last_30_days | cycle(), list() | nil, Date.t()) :: usage_cycle()
def usage_cycle(user, cycle, owned_site_ids \\ nil, today \\ Timex.today()) def usage_cycle(user, cycle, owned_site_ids \\ nil, today \\ Timex.today())
def usage_cycle(user, cycle, nil, today) do def usage_cycle(user, cycle, nil, today) do
@ -398,114 +293,9 @@ defmodule Plausible.Billing.Quota do
for {f_mod, used?} <- used_features, used?, f_mod.enabled?(site), do: f_mod for {f_mod, used?} <- used_features, used?, f_mod.enabled?(site), do: f_mod
end end
@doc """
Ensures that the given user (or the usage map) is within the limits
of the given plan.
An `opts` argument can be passed with `ignore_pageview_limit: true`
which bypasses the pageview limit check and returns `:ok` as long as
the other limits are not exceeded.
"""
@spec ensure_within_plan_limits(User.t() | map(), struct() | atom() | nil, Keyword.t()) ::
:ok | {:error, over_limits_error()}
def ensure_within_plan_limits(user_or_usage, plan, opts \\ [])
def ensure_within_plan_limits(%User{} = user, %plan_mod{} = plan, opts)
when plan_mod in [Plan, EnterprisePlan] do
ensure_within_plan_limits(usage(user), plan, opts)
end
def ensure_within_plan_limits(usage, %plan_mod{} = plan, opts)
when plan_mod in [Plan, EnterprisePlan] do
case exceeded_limits(usage, plan, opts) do
[] -> :ok
exceeded_limits -> {:error, {:over_plan_limits, exceeded_limits}}
end
end
def ensure_within_plan_limits(_, _, _), do: :ok
defp exceeded_limits(usage, plan, opts) do
for {limit, exceeded?} <- [
{:team_member_limit, not within_limit?(usage.team_members, plan.team_member_limit)},
{:site_limit, not within_limit?(usage.sites, plan.site_limit)},
{:monthly_pageview_limit,
exceeds_monthly_pageview_limit?(usage.monthly_pageviews, plan, opts)}
],
exceeded? do
limit
end
end
defp exceeds_monthly_pageview_limit?(usage, plan, opts) do
if Keyword.get(opts, :ignore_pageview_limit) do
false
else
case usage do
%{last_30_days: %{total: total}} ->
!within_limit?(total, pageview_limit_with_margin(plan, opts))
billing_cycles_usage ->
Plausible.Workers.CheckUsage.exceeds_last_two_usage_cycles?(
billing_cycles_usage,
plan.monthly_pageview_limit
)
end
end
end
defp pageview_limit_with_margin(%{monthly_pageview_limit: limit}, opts) do
margin = Keyword.get(opts, :pageview_allowance_margin, @pageview_allowance_margin)
ceil(limit * (1 + margin))
end
@doc """
Returns a list of features the user can use. Trial users have the
ability to use all features during their trial.
"""
def allowed_features_for(user) do
user = Users.with_subscription(user)
case Plans.get_subscription_plan(user.subscription) do
%EnterprisePlan{features: features} ->
features
%Plan{features: features} ->
features
:free_10k ->
[Goals, Props, StatsAPI]
nil ->
if Users.on_trial?(user) do
Feature.list()
else
[Goals]
end
end
end
defp owned_sites_query(user) do defp owned_sites_query(user) do
from sm in Site.Membership, from sm in Site.Membership,
where: sm.role == :owner and sm.user_id == ^user.id, where: sm.role == :owner and sm.user_id == ^user.id,
select: %{site_id: sm.site_id} select: %{site_id: sm.site_id}
end end
@spec below_limit?(non_neg_integer(), non_neg_integer() | :unlimited) :: boolean()
@doc """
Returns whether the usage is below the limit or not.
Returns false if usage is equal to the limit.
"""
def below_limit?(usage, limit) do
if limit == :unlimited, do: true, else: usage < limit
end
@spec within_limit?(non_neg_integer(), non_neg_integer() | :unlimited) :: boolean()
@doc """
Returns whether the usage is within the limit or not.
Returns true if usage is equal to the limit.
"""
def within_limit?(usage, limit) do
if limit == :unlimited, do: true, else: usage <= limit
end
end end

View File

@ -68,7 +68,7 @@ defmodule Plausible.Billing.SiteLocker do
@spec send_grace_period_end_email(Plausible.Auth.User.t()) :: Plausible.Mailer.result() @spec send_grace_period_end_email(Plausible.Auth.User.t()) :: Plausible.Mailer.result()
def send_grace_period_end_email(user) do def send_grace_period_end_email(user) do
usage = Plausible.Billing.Quota.monthly_pageview_usage(user) usage = Plausible.Billing.Quota.Usage.monthly_pageview_usage(user)
suggested_plan = Plausible.Billing.Plans.suggest(user, usage.last_cycle.total) suggested_plan = Plausible.Billing.Plans.suggest(user, usage.last_cycle.total)
PlausibleWeb.Email.dashboard_locked(user, usage, suggested_plan) PlausibleWeb.Email.dashboard_locked(user, usage, suggested_plan)

View File

@ -26,7 +26,7 @@ defmodule Plausible.Site.Memberships.AcceptInvitation do
@spec transfer_ownership(Site.t(), Auth.User.t()) :: @spec transfer_ownership(Site.t(), Auth.User.t()) ::
{:ok, Site.Membership.t()} {:ok, Site.Membership.t()}
| {:error, | {:error,
Billing.Quota.over_limits_error() Billing.Quota.Limits.over_limits_error()
| Ecto.Changeset.t() | Ecto.Changeset.t()
| :transfer_to_self | :transfer_to_self
| :no_plan} | :no_plan}
@ -54,7 +54,7 @@ defmodule Plausible.Site.Memberships.AcceptInvitation do
{:ok, Site.Membership.t()} {:ok, Site.Membership.t()}
| {:error, | {:error,
:invitation_not_found :invitation_not_found
| Billing.Quota.over_limits_error() | Billing.Quota.Limits.over_limits_error()
| Ecto.Changeset.t() | Ecto.Changeset.t()
| :no_plan} | :no_plan}
def accept_invitation(invitation_id, user) do def accept_invitation(invitation_id, user) do

View File

@ -41,7 +41,7 @@ defmodule Plausible.Site.Memberships.CreateInvitation do
{:ok, [Membership.t()]} {:ok, [Membership.t()]}
| {:error, | {:error,
invite_error() invite_error()
| Quota.over_limits_error()} | Quota.Limits.over_limits_error()}
def bulk_transfer_ownership_direct(sites, new_owner) do def bulk_transfer_ownership_direct(sites, new_owner) do
Plausible.Repo.transaction(fn -> Plausible.Repo.transaction(fn ->
for site <- sites do for site <- sites do
@ -134,8 +134,8 @@ defmodule Plausible.Site.Memberships.CreateInvitation do
defp check_team_member_limit(site, _role, invitee_email) do defp check_team_member_limit(site, _role, invitee_email) do
site = Plausible.Repo.preload(site, :owner) site = Plausible.Repo.preload(site, :owner)
limit = Quota.team_member_limit(site.owner) limit = Quota.Limits.team_member_limit(site.owner)
usage = Quota.team_member_usage(site.owner, exclude_emails: invitee_email) usage = Quota.Usage.team_member_usage(site.owner, exclude_emails: invitee_email)
if Quota.below_limit?(usage, limit), if Quota.below_limit?(usage, limit),
do: :ok, do: :ok,

View File

@ -66,7 +66,7 @@ defmodule Plausible.Site.Memberships.Invitations do
on_ee do on_ee do
@spec ensure_can_take_ownership(Site.t(), Auth.User.t()) :: @spec ensure_can_take_ownership(Site.t(), Auth.User.t()) ::
:ok | {:error, Quota.over_limits_error() | :no_plan} :ok | {:error, Quota.Limits.over_limits_error() | :no_plan}
def ensure_can_take_ownership(site, new_owner) do def ensure_can_take_ownership(site, new_owner) do
site = Repo.preload(site, :owner) site = Repo.preload(site, :owner)
new_owner = Plausible.Users.with_subscription(new_owner) new_owner = Plausible.Users.with_subscription(new_owner)
@ -78,7 +78,7 @@ defmodule Plausible.Site.Memberships.Invitations do
usage_after_transfer = %{ usage_after_transfer = %{
monthly_pageviews: monthly_pageview_usage_after_transfer(site, new_owner), monthly_pageviews: monthly_pageview_usage_after_transfer(site, new_owner),
team_members: team_member_usage_after_transfer(site, new_owner), team_members: team_member_usage_after_transfer(site, new_owner),
sites: Quota.site_usage(new_owner) + 1 sites: Quota.Usage.site_usage(new_owner) + 1
} }
Quota.ensure_within_plan_limits(usage_after_transfer, plan) Quota.ensure_within_plan_limits(usage_after_transfer, plan)
@ -88,8 +88,8 @@ defmodule Plausible.Site.Memberships.Invitations do
end end
defp team_member_usage_after_transfer(site, new_owner) do defp team_member_usage_after_transfer(site, new_owner) do
current_usage = Quota.team_member_usage(new_owner) current_usage = Quota.Usage.team_member_usage(new_owner)
site_usage = Quota.team_member_usage(site.owner, site: site) site_usage = Quota.Usage.team_member_usage(site.owner, site: site)
extra_usage = extra_usage =
if Plausible.Sites.is_member?(new_owner.id, site), do: 0, else: 1 if Plausible.Sites.is_member?(new_owner.id, site), do: 0, else: 1
@ -99,7 +99,7 @@ defmodule Plausible.Site.Memberships.Invitations do
defp monthly_pageview_usage_after_transfer(site, new_owner) do defp monthly_pageview_usage_after_transfer(site, new_owner) do
site_ids = Plausible.Sites.owned_site_ids(new_owner) ++ [site.id] site_ids = Plausible.Sites.owned_site_ids(new_owner) ++ [site.id]
Quota.monthly_pageview_usage(new_owner, site_ids) Quota.Usage.monthly_pageview_usage(new_owner, site_ids)
end end
else else
@spec ensure_can_take_ownership(Site.t(), Auth.User.t()) :: :ok @spec ensure_can_take_ownership(Site.t(), Auth.User.t()) :: :ok
@ -117,7 +117,7 @@ defmodule Plausible.Site.Memberships.Invitations do
def check_feature_access(site, new_owner, false = _selfhost?) do def check_feature_access(site, new_owner, false = _selfhost?) do
missing_features = missing_features =
site site
|> Quota.features_usage() |> Quota.Usage.features_usage()
|> Enum.filter(&(&1.check_availability(new_owner) != :ok)) |> Enum.filter(&(&1.check_availability(new_owner) != :ok))
if missing_features == [] do if missing_features == [] do

View File

@ -9,12 +9,12 @@ defmodule PlausibleWeb.AdminController do
|> String.to_integer() |> String.to_integer()
|> Plausible.Users.with_subscription() |> Plausible.Users.with_subscription()
usage = Quota.usage(user, with_features: true) usage = Quota.Usage.usage(user, with_features: true)
limits = %{ limits = %{
monthly_pageviews: Quota.monthly_pageview_limit(user), monthly_pageviews: Quota.Limits.monthly_pageview_limit(user),
sites: Quota.site_limit(user), sites: Quota.Limits.site_limit(user),
team_members: Quota.team_member_limit(user) team_members: Quota.Limits.team_member_limit(user)
} }
html_response = usage_and_limits_html(user, usage, limits) html_response = usage_and_limits_html(user, usage, limits)

View File

@ -633,12 +633,12 @@ defmodule PlausibleWeb.AuthController do
subscription: user.subscription, subscription: user.subscription,
invoices: Plausible.Billing.paddle_api().get_invoices(user.subscription), invoices: Plausible.Billing.paddle_api().get_invoices(user.subscription),
theme: user.theme || "system", theme: user.theme || "system",
team_member_limit: Quota.team_member_limit(user), team_member_limit: Quota.Limits.team_member_limit(user),
team_member_usage: Quota.team_member_usage(user), team_member_usage: Quota.Usage.team_member_usage(user),
site_limit: Quota.site_limit(user), site_limit: Quota.Limits.site_limit(user),
site_usage: Quota.site_usage(user), site_usage: Quota.Usage.site_usage(user),
pageview_limit: Quota.monthly_pageview_limit(user), pageview_limit: Quota.Limits.monthly_pageview_limit(user),
pageview_usage: Quota.monthly_pageview_usage(user), pageview_usage: Quota.Usage.monthly_pageview_usage(user),
totp_enabled?: Auth.TOTP.enabled?(user) totp_enabled?: Auth.TOTP.enabled?(user)
) )
end end

View File

@ -29,8 +29,8 @@ defmodule PlausibleWeb.Site.MembershipController do
|> Sites.get_for_user!(conn.assigns.site.domain) |> Sites.get_for_user!(conn.assigns.site.domain)
|> Plausible.Repo.preload(:owner) |> Plausible.Repo.preload(:owner)
limit = Plausible.Billing.Quota.team_member_limit(site.owner) limit = Plausible.Billing.Quota.Limits.team_member_limit(site.owner)
usage = Plausible.Billing.Quota.team_member_usage(site.owner) usage = Plausible.Billing.Quota.Usage.team_member_usage(site.owner)
below_limit? = Plausible.Billing.Quota.below_limit?(usage, limit) below_limit? = Plausible.Billing.Quota.below_limit?(usage, limit)
render( render(

View File

@ -18,8 +18,8 @@ defmodule PlausibleWeb.SiteController do
render(conn, "new.html", render(conn, "new.html",
changeset: Plausible.Site.changeset(%Plausible.Site{}), changeset: Plausible.Site.changeset(%Plausible.Site{}),
first_site?: Quota.site_usage(current_user) == 0, first_site?: Quota.Usage.site_usage(current_user) == 0,
site_limit: Quota.site_limit(current_user), site_limit: Quota.Limits.site_limit(current_user),
site_limit_exceeded?: Quota.ensure_can_add_new_site(current_user) != :ok, site_limit_exceeded?: Quota.ensure_can_add_new_site(current_user) != :ok,
layout: {PlausibleWeb.LayoutView, "focus.html"} layout: {PlausibleWeb.LayoutView, "focus.html"}
) )
@ -27,7 +27,7 @@ defmodule PlausibleWeb.SiteController do
def create_site(conn, %{"site" => site_params}) do def create_site(conn, %{"site" => site_params}) do
user = conn.assigns[:current_user] user = conn.assigns[:current_user]
first_site? = Quota.site_usage(user) == 0 first_site? = Quota.Usage.site_usage(user) == 0
case Sites.create(user, site_params) do case Sites.create(user, site_params) do
{:ok, %{site: site}} -> {:ok, %{site: site}} ->
@ -53,7 +53,7 @@ defmodule PlausibleWeb.SiteController do
render(conn, "new.html", render(conn, "new.html",
changeset: changeset, changeset: changeset,
first_site?: first_site?, first_site?: first_site?,
site_limit: Quota.site_limit(user), site_limit: Quota.Limits.site_limit(user),
site_limit_exceeded?: false, site_limit_exceeded?: false,
layout: {PlausibleWeb.LayoutView, "focus.html"} layout: {PlausibleWeb.LayoutView, "focus.html"}
) )

View File

@ -10,7 +10,7 @@ defmodule PlausibleWeb.Live.ChoosePlan do
alias PlausibleWeb.Components.Billing.{PlanBox, PlanBenefits, Notice, PageviewSlider} alias PlausibleWeb.Components.Billing.{PlanBox, PlanBenefits, Notice, PageviewSlider}
alias Plausible.Site alias Plausible.Site
alias Plausible.Users alias Plausible.Users
alias Plausible.Billing.{Plans, Plan, Quota} alias Plausible.Billing.{Plans, Quota}
@contact_link "https://plausible.io/contact" @contact_link "https://plausible.io/contact"
@billing_faq_link "https://plausible.io/docs/billing" @billing_faq_link "https://plausible.io/docs/billing"
@ -22,13 +22,7 @@ defmodule PlausibleWeb.Live.ChoosePlan do
Users.with_subscription(user_id) Users.with_subscription(user_id)
end) end)
|> assign_new(:usage, fn %{user: user} -> |> assign_new(:usage, fn %{user: user} ->
Quota.usage(user, with_features: true) Quota.Usage.usage(user, with_features: true)
end)
|> assign_new(:last_30_days_usage, fn %{user: user, usage: usage} ->
case usage do
%{last_30_days: usage_cycle} -> usage_cycle.total
_ -> Quota.usage_cycle(user, :last_30_days).total
end
end) end)
|> assign_new(:owned_plan, fn %{user: %{subscription: subscription}} -> |> assign_new(:owned_plan, fn %{user: %{subscription: subscription}} ->
Plans.get_regular_plan(subscription, only_non_expired: true) Plans.get_regular_plan(subscription, only_non_expired: true)
@ -63,11 +57,10 @@ defmodule PlausibleWeb.Live.ChoosePlan do
get_available_volumes(available_plans) get_available_volumes(available_plans)
end) end)
|> assign_new(:selected_volume, fn %{ |> assign_new(:selected_volume, fn %{
owned_plan: owned_plan, usage: usage,
last_30_days_usage: last_30_days_usage,
available_volumes: available_volumes available_volumes: available_volumes
} -> } ->
default_selected_volume(owned_plan, last_30_days_usage, available_volumes) default_selected_volume(usage.monthly_pageviews, available_volumes)
end) end)
|> assign_new(:selected_interval, fn %{current_interval: current_interval} -> |> assign_new(:selected_interval, fn %{current_interval: current_interval} ->
current_interval || :monthly current_interval || :monthly
@ -149,8 +142,7 @@ defmodule PlausibleWeb.Live.ChoosePlan do
<PlanBox.enterprise benefits={@enterprise_benefits} /> <PlanBox.enterprise benefits={@enterprise_benefits} />
</div> </div>
<p class="mx-auto mt-8 max-w-2xl text-center text-lg leading-8 text-gray-600 dark:text-gray-400"> <p class="mx-auto mt-8 max-w-2xl text-center text-lg leading-8 text-gray-600 dark:text-gray-400">
You have used <b><%= PlausibleWeb.AuthView.delimit_integer(@last_30_days_usage) %></b> <.render_usage pageview_usage={@usage.monthly_pageviews} />
billable pageviews in the last 30 days
</p> </p>
<.pageview_limit_notice :if={!@owned_plan} /> <.pageview_limit_notice :if={!@owned_plan} />
<.help_links /> <.help_links />
@ -160,6 +152,22 @@ defmodule PlausibleWeb.Live.ChoosePlan do
""" """
end end
defp render_usage(assigns) do
case assigns.pageview_usage do
%{last_30_days: _} ->
~H"""
You have used
<b><%= PlausibleWeb.AuthView.delimit_integer(@pageview_usage.last_30_days.total) %></b> billable pageviews in the last 30 days
"""
%{last_cycle: _} ->
~H"""
You have used
<b><%= PlausibleWeb.AuthView.delimit_integer(@pageview_usage.last_cycle.total) %></b> billable pageviews in the last billing cycle
"""
end
end
def handle_event("set_interval", %{"interval" => interval}, socket) do def handle_event("set_interval", %{"interval" => interval}, socket) do
new_interval = new_interval =
case interval do case interval do
@ -189,10 +197,14 @@ defmodule PlausibleWeb.Live.ChoosePlan do
)} )}
end end
defp default_selected_volume(%Plan{monthly_pageview_limit: limit}, _, _), do: limit defp default_selected_volume(pageview_usage, available_volumes) do
total =
case pageview_usage do
%{last_30_days: usage} -> usage.total
%{last_cycle: usage} -> usage.total
end
defp default_selected_volume(_, last_30_days_usage, available_volumes) do Enum.find(available_volumes, &(total < &1)) || :enterprise
Enum.find(available_volumes, &(last_30_days_usage < &1)) || :enterprise
end end
defp current_user_subscription_interval(subscription) do defp current_user_subscription_interval(subscription) do

View File

@ -10,7 +10,7 @@ defmodule PlausibleWeb.AuthView do
def subscription_quota(subscription, options) do def subscription_quota(subscription, options) do
subscription subscription
|> Plausible.Billing.Quota.monthly_pageview_limit() |> Plausible.Billing.Quota.Limits.monthly_pageview_limit()
|> PlausibleWeb.StatsView.large_number_format() |> PlausibleWeb.StatsView.large_number_format()
|> then(fn quota -> |> then(fn quota ->
if Keyword.get(options, :format) == :long, if Keyword.get(options, :format) == :long,

View File

@ -33,7 +33,7 @@ defmodule Plausible.Workers.CheckUsage do
end end
@impl Oban.Worker @impl Oban.Worker
def perform(_job, quota_mod \\ Quota, today \\ Timex.today()) do def perform(_job, usage_mod \\ Quota.Usage, today \\ Timex.today()) do
yesterday = today |> Timex.shift(days: -1) yesterday = today |> Timex.shift(days: -1)
active_subscribers = active_subscribers =
@ -56,13 +56,13 @@ defmodule Plausible.Workers.CheckUsage do
for subscriber <- active_subscribers do for subscriber <- active_subscribers do
case {subscriber.grace_period, subscriber.enterprise_plan} do case {subscriber.grace_period, subscriber.enterprise_plan} do
{nil, nil} -> {nil, nil} ->
check_regular_subscriber(subscriber, quota_mod) check_regular_subscriber(subscriber, usage_mod)
{nil, _} -> {nil, _} ->
check_enterprise_subscriber(subscriber, quota_mod) check_enterprise_subscriber(subscriber, usage_mod)
{_, nil} -> {_, nil} ->
maybe_remove_grace_period(subscriber, quota_mod) maybe_remove_grace_period(subscriber, usage_mod)
_ -> _ ->
:skip :skip
@ -72,29 +72,9 @@ defmodule Plausible.Workers.CheckUsage do
:ok :ok
end end
@spec exceeds_last_two_usage_cycles?(Quota.monthly_pageview_usage(), non_neg_integer()) ::
boolean()
def exceeds_last_two_usage_cycles?(usage, limit) when is_integer(limit) do
limit = ceil(limit * (1 + Quota.pageview_allowance_margin()))
Enum.all?([usage.last_cycle, usage.penultimate_cycle], fn usage ->
not Quota.below_limit?(usage.total, limit)
end)
end
@spec last_usage_cycle_below_limit?(Quota.monthly_pageview_usage(), non_neg_integer()) ::
boolean()
def last_usage_cycle_below_limit?(usage, limit) when is_integer(limit) do
limit = ceil(limit * (1 + Quota.pageview_allowance_margin()))
Quota.below_limit?(usage.last_cycle.total, limit)
end
defp check_site_usage_for_enterprise(subscriber) do defp check_site_usage_for_enterprise(subscriber) do
limit = subscriber.enterprise_plan.site_limit limit = subscriber.enterprise_plan.site_limit
usage = Quota.site_usage(subscriber) usage = Quota.Usage.site_usage(subscriber)
if Quota.below_limit?(usage, limit) do if Quota.below_limit?(usage, limit) do
{:below_limit, {usage, limit}} {:below_limit, {usage, limit}}
@ -103,8 +83,8 @@ defmodule Plausible.Workers.CheckUsage do
end end
end end
def maybe_remove_grace_period(subscriber, quota_mod) do def maybe_remove_grace_period(subscriber, usage_mod) do
case check_pageview_usage_last_cycle(subscriber, quota_mod) do case check_pageview_usage_last_cycle(subscriber, usage_mod) do
{:below_limit, _} -> {:below_limit, _} ->
subscriber subscriber
|> Plausible.Auth.GracePeriod.remove_changeset() |> Plausible.Auth.GracePeriod.remove_changeset()
@ -115,8 +95,8 @@ defmodule Plausible.Workers.CheckUsage do
end end
end end
defp check_regular_subscriber(subscriber, quota_mod) do defp check_regular_subscriber(subscriber, usage_mod) do
case check_pageview_usage_two_cycles(subscriber, quota_mod) do case check_pageview_usage_two_cycles(subscriber, usage_mod) do
{:over_limit, pageview_usage} -> {:over_limit, pageview_usage} ->
suggested_plan = suggested_plan =
Plausible.Billing.Plans.suggest(subscriber, pageview_usage.last_cycle.total) Plausible.Billing.Plans.suggest(subscriber, pageview_usage.last_cycle.total)
@ -133,8 +113,8 @@ defmodule Plausible.Workers.CheckUsage do
end end
end end
def check_enterprise_subscriber(subscriber, quota_mod) do def check_enterprise_subscriber(subscriber, usage_mod) do
pageview_usage = check_pageview_usage_two_cycles(subscriber, quota_mod) pageview_usage = check_pageview_usage_two_cycles(subscriber, usage_mod)
site_usage = check_site_usage_for_enterprise(subscriber) site_usage = check_site_usage_for_enterprise(subscriber)
case {pageview_usage, site_usage} do case {pageview_usage, site_usage} do
@ -156,25 +136,25 @@ defmodule Plausible.Workers.CheckUsage do
end end
end end
defp check_pageview_usage_two_cycles(subscriber, quota_mod) do defp check_pageview_usage_two_cycles(subscriber, usage_mod) do
usage = quota_mod.monthly_pageview_usage(subscriber) usage = usage_mod.monthly_pageview_usage(subscriber)
limit = Quota.monthly_pageview_limit(subscriber) limit = Quota.Limits.monthly_pageview_limit(subscriber)
if exceeds_last_two_usage_cycles?(usage, limit) do if Quota.exceeds_last_two_usage_cycles?(usage, limit) do
{:over_limit, usage} {:over_limit, usage}
else else
{:below_limit, usage} {:below_limit, usage}
end end
end end
defp check_pageview_usage_last_cycle(subscriber, quota_mod) do defp check_pageview_usage_last_cycle(subscriber, usage_mod) do
usage = quota_mod.monthly_pageview_usage(subscriber) usage = usage_mod.monthly_pageview_usage(subscriber)
limit = Quota.monthly_pageview_limit(subscriber) limit = Quota.Limits.monthly_pageview_limit(subscriber)
if last_usage_cycle_below_limit?(usage, limit) do if :last_cycle in Quota.exceeded_cycles(usage, limit) do
{:below_limit, usage}
else
{:over_limit, usage} {:over_limit, usage}
else
{:below_limit, usage}
end end
end end
end end

View File

@ -55,14 +55,14 @@ defmodule Plausible.Workers.SendTrialNotifications do
end end
defp send_tomorrow_reminder(user) do defp send_tomorrow_reminder(user) do
usage = Plausible.Billing.Quota.usage_cycle(user, :last_30_days) usage = Plausible.Billing.Quota.Usage.usage_cycle(user, :last_30_days)
PlausibleWeb.Email.trial_upgrade_email(user, "tomorrow", usage) PlausibleWeb.Email.trial_upgrade_email(user, "tomorrow", usage)
|> Plausible.Mailer.send() |> Plausible.Mailer.send()
end end
defp send_today_reminder(user) do defp send_today_reminder(user) do
usage = Plausible.Billing.Quota.usage_cycle(user, :last_30_days) usage = Plausible.Billing.Quota.Usage.usage_cycle(user, :last_30_days)
PlausibleWeb.Email.trial_upgrade_email(user, "today", usage) PlausibleWeb.Email.trial_upgrade_email(user, "today", usage)
|> Plausible.Mailer.send() |> Plausible.Mailer.send()

View File

@ -24,14 +24,14 @@ defmodule Plausible.Billing.QuotaTest do
user_on_v2 = insert(:user, subscription: build(:subscription, paddle_plan_id: @v2_plan_id)) user_on_v2 = insert(:user, subscription: build(:subscription, paddle_plan_id: @v2_plan_id))
user_on_v3 = insert(:user, subscription: build(:subscription, paddle_plan_id: @v3_plan_id)) user_on_v3 = insert(:user, subscription: build(:subscription, paddle_plan_id: @v3_plan_id))
assert 50 == Quota.site_limit(user_on_v1) assert 50 == Quota.Limits.site_limit(user_on_v1)
assert 50 == Quota.site_limit(user_on_v2) assert 50 == Quota.Limits.site_limit(user_on_v2)
assert 50 == Quota.site_limit(user_on_v3) assert 50 == Quota.Limits.site_limit(user_on_v3)
end end
test "returns 50 when user is on free_10k plan" do test "returns 50 when user is on free_10k plan" do
user = insert(:user, subscription: build(:subscription, paddle_plan_id: "free_10k")) user = insert(:user, subscription: build(:subscription, paddle_plan_id: "free_10k"))
assert 50 == Quota.site_limit(user) assert 50 == Quota.Limits.site_limit(user)
end end
test "returns the configured site limit for enterprise plan" do test "returns the configured site limit for enterprise plan" do
@ -40,7 +40,7 @@ defmodule Plausible.Billing.QuotaTest do
enterprise_plan = insert(:enterprise_plan, user_id: user.id, site_limit: 500) enterprise_plan = insert(:enterprise_plan, user_id: user.id, site_limit: 500)
insert(:subscription, user_id: user.id, paddle_plan_id: enterprise_plan.paddle_plan_id) insert(:subscription, user_id: user.id, paddle_plan_id: enterprise_plan.paddle_plan_id)
assert enterprise_plan.site_limit == Quota.site_limit(user) assert enterprise_plan.site_limit == Quota.Limits.site_limit(user)
end end
test "returns 10 when user in on trial" do test "returns 10 when user in on trial" do
@ -49,7 +49,7 @@ defmodule Plausible.Billing.QuotaTest do
trial_expiry_date: Timex.shift(Timex.now(), days: 7) trial_expiry_date: Timex.shift(Timex.now(), days: 7)
) )
assert 10 == Quota.site_limit(user) assert 10 == Quota.Limits.site_limit(user)
end end
test "returns the subscription limit for enterprise users who have not paid yet" do test "returns the subscription limit for enterprise users who have not paid yet" do
@ -59,7 +59,7 @@ defmodule Plausible.Billing.QuotaTest do
subscription: build(:subscription, paddle_plan_id: @v1_plan_id) subscription: build(:subscription, paddle_plan_id: @v1_plan_id)
) )
assert 50 == Quota.site_limit(user) assert 50 == Quota.Limits.site_limit(user)
end end
test "returns 10 for enterprise users who have not upgraded yet and are on trial" do test "returns 10 for enterprise users who have not upgraded yet and are on trial" do
@ -69,7 +69,7 @@ defmodule Plausible.Billing.QuotaTest do
subscription: nil subscription: nil
) )
assert 10 == Quota.site_limit(user) assert 10 == Quota.Limits.site_limit(user)
end end
end end
@ -79,7 +79,7 @@ defmodule Plausible.Billing.QuotaTest do
insert(:site, memberships: [build(:site_membership, user: user, role: :admin)]) insert(:site, memberships: [build(:site_membership, user: user, role: :admin)])
insert(:site, memberships: [build(:site_membership, user: user, role: :viewer)]) insert(:site, memberships: [build(:site_membership, user: user, role: :viewer)])
assert Quota.site_usage(user) == 3 assert Quota.Usage.site_usage(user) == 3
end end
describe "below_limit?/2" do describe "below_limit?/2" do
@ -209,19 +209,19 @@ defmodule Plausible.Billing.QuotaTest do
test "is based on the plan if user is on a legacy plan" do test "is based on the plan if user is on a legacy plan" do
user = insert(:user, subscription: build(:subscription, paddle_plan_id: @legacy_plan_id)) user = insert(:user, subscription: build(:subscription, paddle_plan_id: @legacy_plan_id))
assert Quota.monthly_pageview_limit(user.subscription) == 1_000_000 assert Quota.Limits.monthly_pageview_limit(user.subscription) == 1_000_000
end end
test "is based on the plan if user is on a standard plan" do test "is based on the plan if user is on a standard plan" do
user = insert(:user, subscription: build(:subscription, paddle_plan_id: @v1_plan_id)) user = insert(:user, subscription: build(:subscription, paddle_plan_id: @v1_plan_id))
assert Quota.monthly_pageview_limit(user.subscription) == 10_000 assert Quota.Limits.monthly_pageview_limit(user.subscription) == 10_000
end end
test "free_10k has 10k monthly_pageview_limit" do test "free_10k has 10k monthly_pageview_limit" do
user = insert(:user, subscription: build(:subscription, paddle_plan_id: "free_10k")) user = insert(:user, subscription: build(:subscription, paddle_plan_id: "free_10k"))
assert Quota.monthly_pageview_limit(user.subscription) == 10_000 assert Quota.Limits.monthly_pageview_limit(user.subscription) == 10_000
end end
test "is based on the enterprise plan if user is on an enterprise plan" do test "is based on the enterprise plan if user is on an enterprise plan" do
@ -233,14 +233,14 @@ defmodule Plausible.Billing.QuotaTest do
subscription = subscription =
insert(:subscription, user_id: user.id, paddle_plan_id: enterprise_plan.paddle_plan_id) insert(:subscription, user_id: user.id, paddle_plan_id: enterprise_plan.paddle_plan_id)
assert Quota.monthly_pageview_limit(subscription) == 100_000 assert Quota.Limits.monthly_pageview_limit(subscription) == 100_000
end end
test "does not limit pageviews when user has a pending enterprise plan" do test "does not limit pageviews when user has a pending enterprise plan" do
user = insert(:user) user = insert(:user)
subscription = insert(:subscription, user_id: user.id, paddle_plan_id: "pending-enterprise") subscription = insert(:subscription, user_id: user.id, paddle_plan_id: "pending-enterprise")
assert Quota.monthly_pageview_limit(subscription) == :unlimited assert Quota.Limits.monthly_pageview_limit(subscription) == :unlimited
end end
end end
@ -282,7 +282,7 @@ defmodule Plausible.Billing.QuotaTest do
] ]
) )
assert Quota.team_member_usage(me) == 3 assert Quota.Usage.team_member_usage(me) == 3
end end
test "counts the same email address as one team member" do test "counts the same email address as one team member" do
@ -310,7 +310,7 @@ defmodule Plausible.Billing.QuotaTest do
insert(:invitation, site: site_i_own_3, inviter: me, email: "joe@plausible.test") insert(:invitation, site: site_i_own_3, inviter: me, email: "joe@plausible.test")
assert Quota.team_member_usage(me) == 2 assert Quota.Usage.team_member_usage(me) == 2
end end
test "counts pending invitations as team members" do test "counts pending invitations as team members" do
@ -332,7 +332,7 @@ defmodule Plausible.Billing.QuotaTest do
insert(:invitation, site: site_i_own, inviter: member) insert(:invitation, site: site_i_own, inviter: member)
insert(:invitation, site: site_i_have_access, inviter: me) insert(:invitation, site: site_i_have_access, inviter: me)
assert Quota.team_member_usage(me) == 3 assert Quota.Usage.team_member_usage(me) == 3
end end
test "does not count ownership transfer as a team member" do test "does not count ownership transfer as a team member" do
@ -341,12 +341,12 @@ defmodule Plausible.Billing.QuotaTest do
insert(:invitation, site: site_i_own, inviter: me, role: :owner) insert(:invitation, site: site_i_own, inviter: me, role: :owner)
assert Quota.team_member_usage(me) == 0 assert Quota.Usage.team_member_usage(me) == 0
end end
test "returns zero when user does not have any site" do test "returns zero when user does not have any site" do
me = insert(:user) me = insert(:user)
assert Quota.team_member_usage(me) == 0 assert Quota.Usage.team_member_usage(me) == 0
end end
test "does not count email report recipients as team members" do test "does not count email report recipients as team members" do
@ -358,7 +358,7 @@ defmodule Plausible.Billing.QuotaTest do
recipients: ["adam@plausible.test", "vini@plausible.test"] recipients: ["adam@plausible.test", "vini@plausible.test"]
) )
assert Quota.team_member_usage(me) == 0 assert Quota.Usage.team_member_usage(me) == 0
end end
test "excludes specific emails from limit calculation" do test "excludes specific emails from limit calculation" do
@ -377,11 +377,13 @@ defmodule Plausible.Billing.QuotaTest do
insert(:invitation, site: site_i_own, inviter: member) insert(:invitation, site: site_i_own, inviter: member)
invitation = insert(:invitation, site: site_i_own, inviter: me, email: "foo@example.com") invitation = insert(:invitation, site: site_i_own, inviter: me, email: "foo@example.com")
assert Quota.team_member_usage(me) == 4 assert Quota.Usage.team_member_usage(me) == 4
assert Quota.team_member_usage(me, exclude_emails: "arbitrary@example.com") == 4 assert Quota.Usage.team_member_usage(me, exclude_emails: "arbitrary@example.com") == 4
assert Quota.team_member_usage(me, exclude_emails: member.email) == 3 assert Quota.Usage.team_member_usage(me, exclude_emails: member.email) == 3
assert Quota.team_member_usage(me, exclude_emails: invitation.email) == 3 assert Quota.Usage.team_member_usage(me, exclude_emails: invitation.email) == 3
assert Quota.team_member_usage(me, exclude_emails: [member.email, invitation.email]) == 2
assert Quota.Usage.team_member_usage(me, exclude_emails: [member.email, invitation.email]) ==
2
end end
end end
@ -392,14 +394,14 @@ defmodule Plausible.Billing.QuotaTest do
user_on_v2 = insert(:user, subscription: build(:subscription, paddle_plan_id: @v2_plan_id)) user_on_v2 = insert(:user, subscription: build(:subscription, paddle_plan_id: @v2_plan_id))
user_on_v3 = insert(:user, subscription: build(:subscription, paddle_plan_id: @v3_plan_id)) user_on_v3 = insert(:user, subscription: build(:subscription, paddle_plan_id: @v3_plan_id))
assert :unlimited == Quota.team_member_limit(user_on_v1) assert :unlimited == Quota.Limits.team_member_limit(user_on_v1)
assert :unlimited == Quota.team_member_limit(user_on_v2) assert :unlimited == Quota.Limits.team_member_limit(user_on_v2)
assert :unlimited == Quota.team_member_limit(user_on_v3) assert :unlimited == Quota.Limits.team_member_limit(user_on_v3)
end end
test "returns unlimited when user is on free_10k plan" do test "returns unlimited when user is on free_10k plan" do
user = insert(:user, subscription: build(:subscription, paddle_plan_id: "free_10k")) user = insert(:user, subscription: build(:subscription, paddle_plan_id: "free_10k"))
assert :unlimited == Quota.team_member_limit(user) assert :unlimited == Quota.Limits.team_member_limit(user)
end end
test "returns 5 when user in on trial" do test "returns 5 when user in on trial" do
@ -408,7 +410,7 @@ defmodule Plausible.Billing.QuotaTest do
trial_expiry_date: Timex.shift(Timex.now(), days: 7) trial_expiry_date: Timex.shift(Timex.now(), days: 7)
) )
assert 3 == Quota.team_member_limit(user) assert 3 == Quota.Limits.team_member_limit(user)
end end
test "returns the enterprise plan limit" do test "returns the enterprise plan limit" do
@ -419,7 +421,7 @@ defmodule Plausible.Billing.QuotaTest do
subscription: build(:subscription, paddle_plan_id: "123321") subscription: build(:subscription, paddle_plan_id: "123321")
) )
assert 27 == Quota.team_member_limit(user) assert 27 == Quota.Limits.team_member_limit(user)
end end
test "reads from json file when the user is on a v4 plan" do test "reads from json file when the user is on a v4 plan" do
@ -427,22 +429,22 @@ defmodule Plausible.Billing.QuotaTest do
user_on_business = insert(:user, subscription: build(:business_subscription)) user_on_business = insert(:user, subscription: build(:business_subscription))
assert 3 == Quota.team_member_limit(user_on_growth) assert 3 == Quota.Limits.team_member_limit(user_on_growth)
assert 10 == Quota.team_member_limit(user_on_business) assert 10 == Quota.Limits.team_member_limit(user_on_business)
end end
test "returns unlimited when user is on a v3 business plan" do test "returns unlimited when user is on a v3 business plan" do
user = user =
insert(:user, subscription: build(:subscription, paddle_plan_id: @v3_business_plan_id)) insert(:user, subscription: build(:subscription, paddle_plan_id: @v3_business_plan_id))
assert :unlimited == Quota.team_member_limit(user) assert :unlimited == Quota.Limits.team_member_limit(user)
end end
end end
describe "features_usage/1" do describe "features_usage/1" do
test "returns an empty list for a user/site who does not use any feature" do test "returns an empty list for a user/site who does not use any feature" do
assert [] == Quota.features_usage(insert(:user)) assert [] == Quota.Usage.features_usage(insert(:user))
assert [] == Quota.features_usage(insert(:site)) assert [] == Quota.Usage.features_usage(insert(:site))
end end
test "returns [Props] when user/site uses custom props" do test "returns [Props] when user/site uses custom props" do
@ -454,8 +456,8 @@ defmodule Plausible.Billing.QuotaTest do
memberships: [build(:site_membership, user: user, role: :owner)] memberships: [build(:site_membership, user: user, role: :owner)]
) )
assert [Props] == Quota.features_usage(site) assert [Props] == Quota.Usage.features_usage(site)
assert [Props] == Quota.features_usage(user) assert [Props] == Quota.Usage.features_usage(user)
end end
on_ee do on_ee do
@ -467,8 +469,8 @@ defmodule Plausible.Billing.QuotaTest do
steps = Enum.map(goals, &%{"goal_id" => &1.id}) steps = Enum.map(goals, &%{"goal_id" => &1.id})
Plausible.Funnels.create(site, "dummy", steps) Plausible.Funnels.create(site, "dummy", steps)
assert [Funnels] == Quota.features_usage(site) assert [Funnels] == Quota.Usage.features_usage(site)
assert [Funnels] == Quota.features_usage(user) assert [Funnels] == Quota.Usage.features_usage(user)
end end
test "returns [RevenueGoals] when user/site uses revenue goals" do test "returns [RevenueGoals] when user/site uses revenue goals" do
@ -476,8 +478,8 @@ defmodule Plausible.Billing.QuotaTest do
site = insert(:site, memberships: [build(:site_membership, user: user, role: :owner)]) site = insert(:site, memberships: [build(:site_membership, user: user, role: :owner)])
insert(:goal, currency: :USD, site: site, event_name: "Purchase") insert(:goal, currency: :USD, site: site, event_name: "Purchase")
assert [RevenueGoals] == Quota.features_usage(site) assert [RevenueGoals] == Quota.Usage.features_usage(site)
assert [RevenueGoals] == Quota.features_usage(user) assert [RevenueGoals] == Quota.Usage.features_usage(user)
end end
end end
@ -485,7 +487,7 @@ defmodule Plausible.Billing.QuotaTest do
user = insert(:user) user = insert(:user)
insert(:api_key, user: user) insert(:api_key, user: user)
assert [StatsAPI] == Quota.features_usage(user) assert [StatsAPI] == Quota.Usage.features_usage(user)
end end
on_ee do on_ee do
@ -504,8 +506,8 @@ defmodule Plausible.Billing.QuotaTest do
steps = Enum.map(goals, &%{"goal_id" => &1.id}) steps = Enum.map(goals, &%{"goal_id" => &1.id})
Plausible.Funnels.create(site, "dummy", steps) Plausible.Funnels.create(site, "dummy", steps)
assert [Props, Funnels, RevenueGoals] == Quota.features_usage(site) assert [Props, Funnels, RevenueGoals] == Quota.Usage.features_usage(site)
assert [Props, Funnels, RevenueGoals] == Quota.features_usage(user) assert [Props, Funnels, RevenueGoals] == Quota.Usage.features_usage(user)
end end
end end
@ -517,7 +519,7 @@ defmodule Plausible.Billing.QuotaTest do
memberships: [build(:site_membership, user: user, role: :admin)] memberships: [build(:site_membership, user: user, role: :admin)]
) )
assert [] == Quota.features_usage(user) assert [] == Quota.Usage.features_usage(user)
end end
end end
@ -525,7 +527,7 @@ defmodule Plausible.Billing.QuotaTest do
on_ee do on_ee do
test "users with expired trials have no access to subscription features" do test "users with expired trials have no access to subscription features" do
user = insert(:user, trial_expiry_date: ~D[2023-01-01]) user = insert(:user, trial_expiry_date: ~D[2023-01-01])
assert [Goals] == Quota.allowed_features_for(user) assert [Goals] == Quota.Limits.allowed_features_for(user)
end end
end end
@ -534,14 +536,14 @@ defmodule Plausible.Billing.QuotaTest do
user_on_v2 = insert(:user, subscription: build(:subscription, paddle_plan_id: @v2_plan_id)) user_on_v2 = insert(:user, subscription: build(:subscription, paddle_plan_id: @v2_plan_id))
user_on_v3 = insert(:user, subscription: build(:subscription, paddle_plan_id: @v3_plan_id)) user_on_v3 = insert(:user, subscription: build(:subscription, paddle_plan_id: @v3_plan_id))
assert [Goals, Props, StatsAPI] == Quota.allowed_features_for(user_on_v1) assert [Goals, Props, StatsAPI] == Quota.Limits.allowed_features_for(user_on_v1)
assert [Goals, Props, StatsAPI] == Quota.allowed_features_for(user_on_v2) assert [Goals, Props, StatsAPI] == Quota.Limits.allowed_features_for(user_on_v2)
assert [Goals, Props, StatsAPI] == Quota.allowed_features_for(user_on_v3) assert [Goals, Props, StatsAPI] == Quota.Limits.allowed_features_for(user_on_v3)
end end
test "returns [Goals, Props, StatsAPI] when user is on free_10k plan" do test "returns [Goals, Props, StatsAPI] when user is on free_10k plan" do
user = insert(:user, subscription: build(:subscription, paddle_plan_id: "free_10k")) user = insert(:user, subscription: build(:subscription, paddle_plan_id: "free_10k"))
assert [Goals, Props, StatsAPI] == Quota.allowed_features_for(user) assert [Goals, Props, StatsAPI] == Quota.Limits.allowed_features_for(user)
end end
on_ee do on_ee do
@ -560,14 +562,14 @@ defmodule Plausible.Billing.QuotaTest do
insert(:subscription, user_id: user.id, paddle_plan_id: enterprise_plan.paddle_plan_id) insert(:subscription, user_id: user.id, paddle_plan_id: enterprise_plan.paddle_plan_id)
assert [Plausible.Billing.Feature.StatsAPI, Plausible.Billing.Feature.Funnels] == assert [Plausible.Billing.Feature.StatsAPI, Plausible.Billing.Feature.Funnels] ==
Quota.allowed_features_for(user) Quota.Limits.allowed_features_for(user)
end end
end end
test "returns all features when user in on trial" do test "returns all features when user in on trial" do
user = insert(:user, trial_expiry_date: Timex.shift(Timex.now(), days: 7)) user = insert(:user, trial_expiry_date: Timex.shift(Timex.now(), days: 7))
assert Plausible.Billing.Feature.list() == Quota.allowed_features_for(user) assert Plausible.Billing.Feature.list() == Quota.Limits.allowed_features_for(user)
end end
test "returns previous plan limits for enterprise users who have not paid yet" do test "returns previous plan limits for enterprise users who have not paid yet" do
@ -577,7 +579,7 @@ defmodule Plausible.Billing.QuotaTest do
subscription: build(:subscription, paddle_plan_id: @v1_plan_id) subscription: build(:subscription, paddle_plan_id: @v1_plan_id)
) )
assert [Goals, Props, StatsAPI] == Quota.allowed_features_for(user) assert [Goals, Props, StatsAPI] == Quota.Limits.allowed_features_for(user)
end end
test "returns all features for enterprise users who have not upgraded yet and are on trial" do test "returns all features for enterprise users who have not upgraded yet and are on trial" do
@ -587,7 +589,7 @@ defmodule Plausible.Billing.QuotaTest do
subscription: nil subscription: nil
) )
assert Plausible.Billing.Feature.list() == Quota.allowed_features_for(user) assert Plausible.Billing.Feature.list() == Quota.Limits.allowed_features_for(user)
end end
test "returns old plan features for enterprise customers who are due to change a plan" do test "returns old plan features for enterprise customers who are due to change a plan" do
@ -602,7 +604,7 @@ defmodule Plausible.Billing.QuotaTest do
) )
insert(:enterprise_plan, user_id: user.id, paddle_plan_id: "new-paddle-plan-id") insert(:enterprise_plan, user_id: user.id, paddle_plan_id: "new-paddle-plan-id")
assert [Plausible.Billing.Feature.StatsAPI] == Quota.allowed_features_for(user) assert [Plausible.Billing.Feature.StatsAPI] == Quota.Limits.allowed_features_for(user)
end end
end end
@ -619,7 +621,7 @@ defmodule Plausible.Billing.QuotaTest do
pageviews: 0, pageviews: 0,
date_range: date_range date_range: date_range
} }
} = Quota.monthly_pageview_usage(user) } = Quota.Usage.monthly_pageview_usage(user)
assert date_range.last == Date.utc_today() assert date_range.last == Date.utc_today()
assert Date.compare(date_range.first, date_range.last) == :lt assert Date.compare(date_range.first, date_range.last) == :lt
@ -650,7 +652,7 @@ defmodule Plausible.Billing.QuotaTest do
pageviews: 3, pageviews: 3,
date_range: %{} date_range: %{}
} }
} = Quota.monthly_pageview_usage(user) } = Quota.Usage.monthly_pageview_usage(user)
end end
test "returns usage for user with subscription and a site" do test "returns usage for user with subscription and a site" do
@ -693,7 +695,7 @@ defmodule Plausible.Billing.QuotaTest do
pageviews: 0, pageviews: 0,
date_range: %{} date_range: %{}
} }
} = Quota.monthly_pageview_usage(user) } = Quota.Usage.monthly_pageview_usage(user)
end end
test "returns usage for only a subset of site IDs" do test "returns usage for only a subset of site IDs" do
@ -740,7 +742,7 @@ defmodule Plausible.Billing.QuotaTest do
pageviews: 0, pageviews: 0,
date_range: %{} date_range: %{}
} }
} = Quota.monthly_pageview_usage(user, [site1.id, site3.id]) } = Quota.Usage.monthly_pageview_usage(user, [site1.id, site3.id])
end end
end end
@ -777,13 +779,13 @@ defmodule Plausible.Billing.QuotaTest do
insert(:subscription, user_id: user.id, last_bill_date: last_bill_date) insert(:subscription, user_id: user.id, last_bill_date: last_bill_date)
assert %{date_range: penultimate_cycle, pageviews: 2, custom_events: 3, total: 5} = assert %{date_range: penultimate_cycle, pageviews: 2, custom_events: 3, total: 5} =
Quota.usage_cycle(user, :penultimate_cycle, nil, today) Quota.Usage.usage_cycle(user, :penultimate_cycle, nil, today)
assert %{date_range: last_cycle, pageviews: 3, custom_events: 2, total: 5} = assert %{date_range: last_cycle, pageviews: 3, custom_events: 2, total: 5} =
Quota.usage_cycle(user, :last_cycle, nil, today) Quota.Usage.usage_cycle(user, :last_cycle, nil, today)
assert %{date_range: current_cycle, pageviews: 0, custom_events: 3, total: 3} = assert %{date_range: current_cycle, pageviews: 0, custom_events: 3, total: 3} =
Quota.usage_cycle(user, :current_cycle, nil, today) Quota.Usage.usage_cycle(user, :current_cycle, nil, today)
assert penultimate_cycle == Date.range(~D[2023-04-03], ~D[2023-05-02]) assert penultimate_cycle == Date.range(~D[2023-04-03], ~D[2023-05-02])
assert last_cycle == Date.range(~D[2023-05-03], ~D[2023-06-02]) assert last_cycle == Date.range(~D[2023-05-03], ~D[2023-06-02])
@ -794,7 +796,7 @@ defmodule Plausible.Billing.QuotaTest do
today = ~D[2023-06-01] today = ~D[2023-06-01]
assert %{date_range: last_30_days, pageviews: 4, custom_events: 1, total: 5} = assert %{date_range: last_30_days, pageviews: 4, custom_events: 1, total: 5} =
Quota.usage_cycle(user, :last_30_days, nil, today) Quota.Usage.usage_cycle(user, :last_30_days, nil, today)
assert last_30_days == Date.range(~D[2023-05-02], ~D[2023-06-01]) assert last_30_days == Date.range(~D[2023-05-02], ~D[2023-06-01])
end end
@ -817,7 +819,7 @@ defmodule Plausible.Billing.QuotaTest do
insert(:subscription, user_id: user.id, last_bill_date: last_bill_date) insert(:subscription, user_id: user.id, last_bill_date: last_bill_date)
assert %{date_range: last_cycle, pageviews: 3, custom_events: 2, total: 5} = assert %{date_range: last_cycle, pageviews: 3, custom_events: 2, total: 5} =
Quota.usage_cycle(user, :last_cycle, nil, today) Quota.Usage.usage_cycle(user, :last_cycle, nil, today)
assert last_cycle == Date.range(~D[2023-05-03], ~D[2023-06-02]) assert last_cycle == Date.range(~D[2023-05-03], ~D[2023-06-02])
end end
@ -829,13 +831,13 @@ defmodule Plausible.Billing.QuotaTest do
user = insert(:user, subscription: build(:subscription, last_bill_date: last_bill_date)) user = insert(:user, subscription: build(:subscription, last_bill_date: last_bill_date))
assert %{date_range: penultimate_cycle} = assert %{date_range: penultimate_cycle} =
Quota.usage_cycle(user, :penultimate_cycle, nil, today) Quota.Usage.usage_cycle(user, :penultimate_cycle, nil, today)
assert %{date_range: last_cycle} = assert %{date_range: last_cycle} =
Quota.usage_cycle(user, :last_cycle, nil, today) Quota.Usage.usage_cycle(user, :last_cycle, nil, today)
assert %{date_range: current_cycle} = assert %{date_range: current_cycle} =
Quota.usage_cycle(user, :current_cycle, nil, today) Quota.Usage.usage_cycle(user, :current_cycle, nil, today)
assert penultimate_cycle == Date.range(~D[2020-12-01], ~D[2020-12-31]) assert penultimate_cycle == Date.range(~D[2020-12-01], ~D[2020-12-31])
assert last_cycle == Date.range(~D[2021-01-01], ~D[2021-01-31]) assert last_cycle == Date.range(~D[2021-01-01], ~D[2021-01-31])
@ -849,13 +851,13 @@ defmodule Plausible.Billing.QuotaTest do
user = insert(:user, subscription: build(:subscription, last_bill_date: last_bill_date)) user = insert(:user, subscription: build(:subscription, last_bill_date: last_bill_date))
assert %{date_range: penultimate_cycle, total: 0} = assert %{date_range: penultimate_cycle, total: 0} =
Quota.usage_cycle(user, :penultimate_cycle, nil, today) Quota.Usage.usage_cycle(user, :penultimate_cycle, nil, today)
assert %{date_range: last_cycle, total: 0} = assert %{date_range: last_cycle, total: 0} =
Quota.usage_cycle(user, :last_cycle, nil, today) Quota.Usage.usage_cycle(user, :last_cycle, nil, today)
assert %{date_range: current_cycle, total: 0} = assert %{date_range: current_cycle, total: 0} =
Quota.usage_cycle(user, :current_cycle, nil, today) Quota.Usage.usage_cycle(user, :current_cycle, nil, today)
assert penultimate_cycle == Date.range(~D[2020-11-01], ~D[2020-11-30]) assert penultimate_cycle == Date.range(~D[2020-11-01], ~D[2020-11-30])
assert last_cycle == Date.range(~D[2020-12-01], ~D[2020-12-31]) assert last_cycle == Date.range(~D[2020-12-01], ~D[2020-12-31])

View File

@ -364,7 +364,7 @@ defmodule PlausibleWeb.SiteControllerTest do
}) })
assert redirected_to(conn) == "/example.com/snippet?site_created=true" assert redirected_to(conn) == "/example.com/snippet?site_created=true"
assert Plausible.Billing.Quota.site_usage(user) == 3 assert Plausible.Billing.Quota.Usage.site_usage(user) == 3
end end
for url <- ["https://Example.com/", "HTTPS://EXAMPLE.COM/", "/Example.com/", "//Example.com/"] do for url <- ["https://Example.com/", "HTTPS://EXAMPLE.COM/", "/Example.com/", "//Example.com/"] do

View File

@ -8,6 +8,7 @@ defmodule PlausibleWeb.Live.ChoosePlanTest do
alias Plausible.{Repo, Billing.Subscription} alias Plausible.{Repo, Billing.Subscription}
@v1_10k_yearly_plan_id "572810" @v1_10k_yearly_plan_id "572810"
@v4_growth_10k_yearly_plan_id "857079"
@v4_growth_200k_yearly_plan_id "857081" @v4_growth_200k_yearly_plan_id "857081"
@v4_business_5m_monthly_plan_id "857111" @v4_business_5m_monthly_plan_id "857111"
@v3_business_10k_monthly_plan_id "857481" @v3_business_10k_monthly_plan_id "857481"
@ -358,16 +359,18 @@ defmodule PlausibleWeb.Live.ChoosePlanTest do
"https://plausible.io/white-label-web-analytics" "https://plausible.io/white-label-web-analytics"
end end
test "displays usage", %{conn: conn, site: site} do test "displays usage in the last cycle", %{conn: conn, site: site} do
yesterday = NaiveDateTime.utc_now() |> NaiveDateTime.add(-1, :day)
populate_stats(site, [ populate_stats(site, [
build(:pageview), build(:pageview, timestamp: yesterday),
build(:pageview) build(:pageview, timestamp: yesterday)
]) ])
{:ok, _lv, doc} = get_liveview(conn) {:ok, _lv, doc} = get_liveview(conn)
assert doc =~ "You have used" assert doc =~ "You have used"
assert doc =~ "<b>2</b>" assert doc =~ "<b>2</b>"
assert doc =~ "billable pageviews in the last 30 days" assert doc =~ "billable pageviews in the last billing cycle"
end end
test "gets default selected interval from current subscription plan", %{conn: conn} do test "gets default selected interval from current subscription plan", %{conn: conn} do
@ -375,9 +378,9 @@ defmodule PlausibleWeb.Live.ChoosePlanTest do
assert class_of_element(doc, @yearly_interval_button) =~ @interval_button_active_class assert class_of_element(doc, @yearly_interval_button) =~ @interval_button_active_class
end end
test "gets default pageview limit from current subscription plan", %{conn: conn} do test "sets pageview slider according to last cycle usage", %{conn: conn} do
{:ok, _lv, doc} = get_liveview(conn) {:ok, _lv, doc} = get_liveview(conn)
assert text_of_element(doc, @slider_value) == "200k" assert text_of_element(doc, @slider_value) == "10k"
end end
test "pageview slider changes selected volume", %{conn: conn} do test "pageview slider changes selected volume", %{conn: conn} do
@ -401,7 +404,9 @@ defmodule PlausibleWeb.Live.ChoosePlanTest do
end end
test "checkout button text and click-disabling CSS classes are dynamic", %{conn: conn} do test "checkout button text and click-disabling CSS classes are dynamic", %{conn: conn} do
{:ok, lv, doc} = get_liveview(conn) {:ok, lv, _doc} = get_liveview(conn)
doc = set_slider(lv, "200k")
assert text_of_element(doc, @growth_checkout_button) == "Currently on this plan" assert text_of_element(doc, @growth_checkout_button) == "Currently on this plan"
assert class_of_element(doc, @growth_checkout_button) =~ "pointer-events-none bg-gray-400" assert class_of_element(doc, @growth_checkout_button) =~ "pointer-events-none bg-gray-400"
@ -431,7 +436,7 @@ defmodule PlausibleWeb.Live.ChoosePlanTest do
growth_checkout_button = find(doc, @growth_checkout_button) growth_checkout_button = find(doc, @growth_checkout_button)
assert text_of_attr(growth_checkout_button, "onclick") =~ assert text_of_attr(growth_checkout_button, "onclick") =~
"if (true) {window.location = '#{Routes.billing_path(conn, :change_plan_preview, @v4_growth_200k_yearly_plan_id)}'}" "if (true) {window.location = '#{Routes.billing_path(conn, :change_plan_preview, @v4_growth_10k_yearly_plan_id)}'}"
set_slider(lv, "5M") set_slider(lv, "5M")
doc = element(lv, @monthly_interval_button) |> render_click() doc = element(lv, @monthly_interval_button) |> render_click()
@ -446,9 +451,9 @@ defmodule PlausibleWeb.Live.ChoosePlanTest do
describe "for a user with a v4 business subscription plan" do describe "for a user with a v4 business subscription plan" do
setup [:create_user, :create_site, :log_in, :subscribe_v4_business] setup [:create_user, :create_site, :log_in, :subscribe_v4_business]
test "gets default pageview limit from current subscription plan", %{conn: conn} do test "sets pageview slider according to last cycle usage", %{conn: conn} do
{:ok, _lv, doc} = get_liveview(conn) {:ok, _lv, doc} = get_liveview(conn)
assert text_of_element(doc, @slider_value) == "5M" assert text_of_element(doc, @slider_value) == "10k"
end end
test "makes it clear that the user is currently on a business tier", %{conn: conn} do test "makes it clear that the user is currently on a business tier", %{conn: conn} do
@ -462,11 +467,12 @@ defmodule PlausibleWeb.Live.ChoosePlanTest do
end end
test "checkout button text and click-disabling CSS classes are dynamic", %{conn: conn} do test "checkout button text and click-disabling CSS classes are dynamic", %{conn: conn} do
{:ok, lv, doc} = get_liveview(conn) {:ok, lv, _doc} = get_liveview(conn)
doc = set_slider(lv, "5M")
assert text_of_element(doc, @business_checkout_button) == "Currently on this plan" assert text_of_element(doc, @business_checkout_button) == "Currently on this plan"
assert class_of_element(doc, @business_checkout_button) =~ "pointer-events-none bg-gray-400" assert class_of_element(doc, @business_checkout_button) =~ "pointer-events-none bg-gray-400"
assert text_of_element(doc, @growth_checkout_button) == "Downgrade to Growth"
doc = element(lv, @yearly_interval_button) |> render_click() doc = element(lv, @yearly_interval_button) |> render_click()
@ -634,7 +640,10 @@ defmodule PlausibleWeb.Live.ChoosePlanTest do
test "checkout buttons are disabled + notice about billing details (unless plan owned already)", test "checkout buttons are disabled + notice about billing details (unless plan owned already)",
%{conn: conn} do %{conn: conn} do
{:ok, lv, doc} = get_liveview(conn) {:ok, lv, _doc} = get_liveview(conn)
doc = set_slider(lv, "200k")
assert class_of_element(doc, @growth_checkout_button) =~ "pointer-events-none bg-gray-400" assert class_of_element(doc, @growth_checkout_button) =~ "pointer-events-none bg-gray-400"
assert text_of_element(doc, @growth_checkout_button) =~ "Currently on this plan" assert text_of_element(doc, @growth_checkout_button) =~ "Currently on this plan"
refute element_exists?(doc, "#{@growth_checkout_button} + p") refute element_exists?(doc, "#{@growth_checkout_button} + p")
@ -664,7 +673,10 @@ defmodule PlausibleWeb.Live.ChoosePlanTest do
test "checkout buttons are disabled + notice about billing details when plan not owned already", test "checkout buttons are disabled + notice about billing details when plan not owned already",
%{conn: conn} do %{conn: conn} do
{:ok, lv, doc} = get_liveview(conn) {:ok, lv, _doc} = get_liveview(conn)
doc = set_slider(lv, "200k")
assert class_of_element(doc, @growth_checkout_button) =~ "pointer-events-none bg-gray-400" assert class_of_element(doc, @growth_checkout_button) =~ "pointer-events-none bg-gray-400"
assert text_of_element(doc, @growth_checkout_button) =~ "Currently on this plan" assert text_of_element(doc, @growth_checkout_button) =~ "Currently on this plan"
refute element_exists?(doc, "#{@growth_checkout_button} + p") refute element_exists?(doc, "#{@growth_checkout_button} + p")

View File

@ -31,8 +31,8 @@ defmodule Plausible.Workers.CheckUsageTest do
test "does not send an email if account has been over the limit for one billing month", %{ test "does not send an email if account has been over the limit for one billing month", %{
user: user user: user
} do } do
quota_stub = usage_stub =
Plausible.Billing.Quota Plausible.Billing.Quota.Usage
|> stub(:monthly_pageview_usage, fn _user -> |> stub(:monthly_pageview_usage, fn _user ->
%{ %{
penultimate_cycle: %{date_range: @date_range, total: 9_000}, penultimate_cycle: %{date_range: @date_range, total: 9_000},
@ -46,7 +46,7 @@ defmodule Plausible.Workers.CheckUsageTest do
last_bill_date: Timex.shift(Timex.today(), days: -1) last_bill_date: Timex.shift(Timex.today(), days: -1)
) )
CheckUsage.perform(nil, quota_stub) CheckUsage.perform(nil, usage_stub)
assert_no_emails_delivered() assert_no_emails_delivered()
assert Repo.reload(user).grace_period == nil assert Repo.reload(user).grace_period == nil
@ -55,8 +55,8 @@ defmodule Plausible.Workers.CheckUsageTest do
test "does not send an email if account is over the limit by less than 10%", %{ test "does not send an email if account is over the limit by less than 10%", %{
user: user user: user
} do } do
quota_stub = usage_stub =
Plausible.Billing.Quota Plausible.Billing.Quota.Usage
|> stub(:monthly_pageview_usage, fn _user -> |> stub(:monthly_pageview_usage, fn _user ->
%{ %{
penultimate_cycle: %{date_range: @date_range, total: 10_999}, penultimate_cycle: %{date_range: @date_range, total: 10_999},
@ -70,7 +70,7 @@ defmodule Plausible.Workers.CheckUsageTest do
last_bill_date: Timex.shift(Timex.today(), days: -1) last_bill_date: Timex.shift(Timex.today(), days: -1)
) )
CheckUsage.perform(nil, quota_stub) CheckUsage.perform(nil, usage_stub)
assert_no_emails_delivered() assert_no_emails_delivered()
assert Repo.reload(user).grace_period == nil assert Repo.reload(user).grace_period == nil
@ -79,8 +79,8 @@ defmodule Plausible.Workers.CheckUsageTest do
test "sends an email when an account is over their limit for two consecutive billing months", %{ test "sends an email when an account is over their limit for two consecutive billing months", %{
user: user user: user
} do } do
quota_stub = usage_stub =
Plausible.Billing.Quota Plausible.Billing.Quota.Usage
|> stub(:monthly_pageview_usage, fn _user -> |> stub(:monthly_pageview_usage, fn _user ->
%{ %{
penultimate_cycle: %{date_range: @date_range, total: 11_000}, penultimate_cycle: %{date_range: @date_range, total: 11_000},
@ -94,7 +94,7 @@ defmodule Plausible.Workers.CheckUsageTest do
last_bill_date: Timex.shift(Timex.today(), days: -1) last_bill_date: Timex.shift(Timex.today(), days: -1)
) )
CheckUsage.perform(nil, quota_stub) CheckUsage.perform(nil, usage_stub)
assert_email_delivered_with( assert_email_delivered_with(
to: [user], to: [user],
@ -107,8 +107,8 @@ defmodule Plausible.Workers.CheckUsageTest do
test "sends an email suggesting enterprise plan when usage is greater than 10M ", %{ test "sends an email suggesting enterprise plan when usage is greater than 10M ", %{
user: user user: user
} do } do
quota_stub = usage_stub =
Plausible.Billing.Quota Plausible.Billing.Quota.Usage
|> stub(:monthly_pageview_usage, fn _user -> |> stub(:monthly_pageview_usage, fn _user ->
%{ %{
penultimate_cycle: %{date_range: @date_range, total: 11_000_000}, penultimate_cycle: %{date_range: @date_range, total: 11_000_000},
@ -122,7 +122,7 @@ defmodule Plausible.Workers.CheckUsageTest do
last_bill_date: Timex.shift(Timex.today(), days: -1) last_bill_date: Timex.shift(Timex.today(), days: -1)
) )
CheckUsage.perform(nil, quota_stub) CheckUsage.perform(nil, usage_stub)
assert_delivered_email_matches(%{html_body: html_body}) assert_delivered_email_matches(%{html_body: html_body})
@ -136,8 +136,8 @@ defmodule Plausible.Workers.CheckUsageTest do
|> Plausible.Auth.GracePeriod.start_changeset() |> Plausible.Auth.GracePeriod.start_changeset()
|> Repo.update!() |> Repo.update!()
quota_stub = usage_stub =
Plausible.Billing.Quota Plausible.Billing.Quota.Usage
|> stub(:monthly_pageview_usage, fn _user -> |> stub(:monthly_pageview_usage, fn _user ->
%{ %{
penultimate_cycle: %{date_range: @date_range, total: 11_000}, penultimate_cycle: %{date_range: @date_range, total: 11_000},
@ -151,7 +151,7 @@ defmodule Plausible.Workers.CheckUsageTest do
last_bill_date: Timex.shift(Timex.today(), days: -1) last_bill_date: Timex.shift(Timex.today(), days: -1)
) )
CheckUsage.perform(nil, quota_stub) CheckUsage.perform(nil, usage_stub)
assert_no_emails_delivered() assert_no_emails_delivered()
assert Repo.reload(user).grace_period.id == existing_grace_period.id assert Repo.reload(user).grace_period.id == existing_grace_period.id
@ -160,8 +160,8 @@ defmodule Plausible.Workers.CheckUsageTest do
test "recommends a plan to upgrade to", %{ test "recommends a plan to upgrade to", %{
user: user user: user
} do } do
quota_stub = usage_stub =
Plausible.Billing.Quota Plausible.Billing.Quota.Usage
|> stub(:monthly_pageview_usage, fn _user -> |> stub(:monthly_pageview_usage, fn _user ->
%{ %{
penultimate_cycle: %{date_range: @date_range, total: 11_000}, penultimate_cycle: %{date_range: @date_range, total: 11_000},
@ -175,7 +175,7 @@ defmodule Plausible.Workers.CheckUsageTest do
last_bill_date: Timex.shift(Timex.today(), days: -1) last_bill_date: Timex.shift(Timex.today(), days: -1)
) )
CheckUsage.perform(nil, quota_stub) CheckUsage.perform(nil, usage_stub)
assert_delivered_email_matches(%{ assert_delivered_email_matches(%{
html_body: html_body html_body: html_body
@ -185,8 +185,8 @@ defmodule Plausible.Workers.CheckUsageTest do
end end
test "clears grace period when plan is applicable again", %{user: user} do test "clears grace period when plan is applicable again", %{user: user} do
quota_stub = usage_stub =
Plausible.Billing.Quota Plausible.Billing.Quota.Usage
|> stub(:monthly_pageview_usage, fn _user -> |> stub(:monthly_pageview_usage, fn _user ->
%{ %{
penultimate_cycle: %{date_range: @date_range, total: 11_000}, penultimate_cycle: %{date_range: @date_range, total: 11_000},
@ -200,11 +200,11 @@ defmodule Plausible.Workers.CheckUsageTest do
last_bill_date: Timex.shift(Timex.today(), days: -1) last_bill_date: Timex.shift(Timex.today(), days: -1)
) )
CheckUsage.perform(nil, quota_stub) CheckUsage.perform(nil, usage_stub)
assert user |> Repo.reload() |> Plausible.Auth.GracePeriod.active?() assert user |> Repo.reload() |> Plausible.Auth.GracePeriod.active?()
quota_stub = usage_stub =
Plausible.Billing.Quota Plausible.Billing.Quota.Usage
|> stub(:monthly_pageview_usage, fn _user -> |> stub(:monthly_pageview_usage, fn _user ->
%{ %{
penultimate_cycle: %{date_range: @date_range, total: 11_000}, penultimate_cycle: %{date_range: @date_range, total: 11_000},
@ -212,7 +212,7 @@ defmodule Plausible.Workers.CheckUsageTest do
} }
end) end)
CheckUsage.perform(nil, quota_stub) CheckUsage.perform(nil, usage_stub)
refute user |> Repo.reload() |> Plausible.Auth.GracePeriod.active?() refute user |> Repo.reload() |> Plausible.Auth.GracePeriod.active?()
end end
@ -223,8 +223,8 @@ defmodule Plausible.Workers.CheckUsageTest do
|> Plausible.Auth.GracePeriod.start_manual_lock_changeset() |> Plausible.Auth.GracePeriod.start_manual_lock_changeset()
|> Repo.update!() |> Repo.update!()
quota_stub = usage_stub =
Plausible.Billing.Quota Plausible.Billing.Quota.Usage
|> stub(:monthly_pageview_usage, fn _user -> |> stub(:monthly_pageview_usage, fn _user ->
%{ %{
penultimate_cycle: %{date_range: @date_range, total: 1_100_000}, penultimate_cycle: %{date_range: @date_range, total: 1_100_000},
@ -240,7 +240,7 @@ defmodule Plausible.Workers.CheckUsageTest do
last_bill_date: Timex.shift(Timex.today(), days: -1) last_bill_date: Timex.shift(Timex.today(), days: -1)
) )
CheckUsage.perform(nil, quota_stub) CheckUsage.perform(nil, usage_stub)
assert_no_emails_delivered() assert_no_emails_delivered()
assert Repo.reload(user).grace_period.id == existing_grace_period.id assert Repo.reload(user).grace_period.id == existing_grace_period.id
@ -250,8 +250,8 @@ defmodule Plausible.Workers.CheckUsageTest do
%{ %{
user: user user: user
} do } do
quota_stub = usage_stub =
Plausible.Billing.Quota Plausible.Billing.Quota.Usage
|> stub(:monthly_pageview_usage, fn _user -> |> stub(:monthly_pageview_usage, fn _user ->
%{ %{
penultimate_cycle: %{date_range: @date_range, total: 1_100_000}, penultimate_cycle: %{date_range: @date_range, total: 1_100_000},
@ -267,7 +267,7 @@ defmodule Plausible.Workers.CheckUsageTest do
last_bill_date: Timex.shift(Timex.today(), days: -1) last_bill_date: Timex.shift(Timex.today(), days: -1)
) )
CheckUsage.perform(nil, quota_stub) CheckUsage.perform(nil, usage_stub)
assert_email_delivered_with( assert_email_delivered_with(
to: [{nil, "enterprise@plausible.io"}], to: [{nil, "enterprise@plausible.io"}],
@ -279,8 +279,8 @@ defmodule Plausible.Workers.CheckUsageTest do
%{ %{
user: user user: user
} do } do
quota_stub = usage_stub =
Plausible.Billing.Quota Plausible.Billing.Quota.Usage
|> stub(:monthly_pageview_usage, fn _user -> |> stub(:monthly_pageview_usage, fn _user ->
%{ %{
penultimate_cycle: %{date_range: @date_range, total: 1}, penultimate_cycle: %{date_range: @date_range, total: 1},
@ -300,7 +300,7 @@ defmodule Plausible.Workers.CheckUsageTest do
last_bill_date: Timex.shift(Timex.today(), days: -1) last_bill_date: Timex.shift(Timex.today(), days: -1)
) )
CheckUsage.perform(nil, quota_stub) CheckUsage.perform(nil, usage_stub)
assert_email_delivered_with( assert_email_delivered_with(
to: [{nil, "enterprise@plausible.io"}], to: [{nil, "enterprise@plausible.io"}],
@ -309,8 +309,8 @@ defmodule Plausible.Workers.CheckUsageTest do
end end
test "starts grace period when plan is outgrown", %{user: user} do test "starts grace period when plan is outgrown", %{user: user} do
quota_stub = usage_stub =
Plausible.Billing.Quota Plausible.Billing.Quota.Usage
|> stub(:monthly_pageview_usage, fn _user -> |> stub(:monthly_pageview_usage, fn _user ->
%{ %{
penultimate_cycle: %{date_range: @date_range, total: 1_100_000}, penultimate_cycle: %{date_range: @date_range, total: 1_100_000},
@ -326,7 +326,7 @@ defmodule Plausible.Workers.CheckUsageTest do
last_bill_date: Timex.shift(Timex.today(), days: -1) last_bill_date: Timex.shift(Timex.today(), days: -1)
) )
CheckUsage.perform(nil, quota_stub) CheckUsage.perform(nil, usage_stub)
assert user |> Repo.reload() |> Plausible.Auth.GracePeriod.active?() assert user |> Repo.reload() |> Plausible.Auth.GracePeriod.active?()
end end
end end
@ -335,8 +335,8 @@ defmodule Plausible.Workers.CheckUsageTest do
test "checks usage one day after the last_bill_date", %{ test "checks usage one day after the last_bill_date", %{
user: user user: user
} do } do
quota_stub = usage_stub =
Plausible.Billing.Quota Plausible.Billing.Quota.Usage
|> stub(:monthly_pageview_usage, fn _user -> |> stub(:monthly_pageview_usage, fn _user ->
%{ %{
penultimate_cycle: %{date_range: @date_range, total: 11_000}, penultimate_cycle: %{date_range: @date_range, total: 11_000},
@ -350,7 +350,7 @@ defmodule Plausible.Workers.CheckUsageTest do
last_bill_date: Timex.shift(Timex.today(), days: -1) last_bill_date: Timex.shift(Timex.today(), days: -1)
) )
CheckUsage.perform(nil, quota_stub) CheckUsage.perform(nil, usage_stub)
assert_email_delivered_with( assert_email_delivered_with(
to: [user], to: [user],
@ -361,8 +361,8 @@ defmodule Plausible.Workers.CheckUsageTest do
test "does not check exactly one month after last_bill_date", %{ test "does not check exactly one month after last_bill_date", %{
user: user user: user
} do } do
quota_stub = usage_stub =
Plausible.Billing.Quota Plausible.Billing.Quota.Usage
|> stub(:monthly_pageview_usage, fn _user -> |> stub(:monthly_pageview_usage, fn _user ->
%{ %{
penultimate_cycle: %{date_range: @date_range, total: 11_000}, penultimate_cycle: %{date_range: @date_range, total: 11_000},
@ -376,7 +376,7 @@ defmodule Plausible.Workers.CheckUsageTest do
last_bill_date: ~D[2021-03-28] last_bill_date: ~D[2021-03-28]
) )
CheckUsage.perform(nil, quota_stub, ~D[2021-03-28]) CheckUsage.perform(nil, usage_stub, ~D[2021-03-28])
assert_no_emails_delivered() assert_no_emails_delivered()
end end
@ -385,8 +385,8 @@ defmodule Plausible.Workers.CheckUsageTest do
%{ %{
user: user user: user
} do } do
quota_stub = usage_stub =
Plausible.Billing.Quota Plausible.Billing.Quota.Usage
|> stub(:monthly_pageview_usage, fn _user -> |> stub(:monthly_pageview_usage, fn _user ->
%{ %{
penultimate_cycle: %{date_range: @date_range, total: 11_000}, penultimate_cycle: %{date_range: @date_range, total: 11_000},
@ -400,7 +400,7 @@ defmodule Plausible.Workers.CheckUsageTest do
last_bill_date: ~D[2021-06-29] last_bill_date: ~D[2021-06-29]
) )
CheckUsage.perform(nil, quota_stub, ~D[2021-08-30]) CheckUsage.perform(nil, usage_stub, ~D[2021-08-30])
assert_email_delivered_with( assert_email_delivered_with(
to: [user], to: [user],