mirror of
https://github.com/plausible/analytics.git
synced 2024-12-22 17:11:36 +03:00
Configurable limits for enterprise plans (#3527)
This commit is contained in:
parent
d66322e12d
commit
cfaa5be8f4
78
lib/plausible/billing/ecto/feature.ex
Normal file
78
lib/plausible/billing/ecto/feature.ex
Normal file
@ -0,0 +1,78 @@
|
||||
defmodule Plausible.Billing.Ecto.Feature do
|
||||
@moduledoc """
|
||||
Ecto type representing a feature. Features are cast and stored in the
|
||||
database as strings and loaded as modules, for example: `"props"` is loaded
|
||||
as `Plausible.Billing.Feature.Props`.
|
||||
"""
|
||||
|
||||
use Ecto.Type
|
||||
|
||||
def type, do: :string
|
||||
|
||||
def cast(feature) when is_binary(feature) do
|
||||
found =
|
||||
Enum.find(Plausible.Billing.Feature.list(), fn mod ->
|
||||
Atom.to_string(mod.name()) == feature
|
||||
end)
|
||||
|
||||
if found, do: {:ok, found}, else: :error
|
||||
end
|
||||
|
||||
def cast(mod) when is_atom(mod) do
|
||||
{:ok, mod}
|
||||
end
|
||||
|
||||
def load(feature) when is_binary(feature) do
|
||||
cast(feature)
|
||||
end
|
||||
|
||||
def dump(mod) when is_atom(mod) do
|
||||
{:ok, Atom.to_string(mod.name())}
|
||||
end
|
||||
end
|
||||
|
||||
defmodule Plausible.Billing.Ecto.FeatureList do
|
||||
@moduledoc """
|
||||
Ecto type representing a list of features. This is a proxy for
|
||||
`{:array, Plausible.Billing.Ecto.Feature}` and is required for Kaffy to
|
||||
render the HTML input correctly.
|
||||
"""
|
||||
|
||||
use Ecto.Type
|
||||
|
||||
def type, do: {:array, Plausible.Billing.Ecto.Feature}
|
||||
def cast(list), do: Ecto.Type.cast(type(), list)
|
||||
def load(list), do: Ecto.Type.load(type(), list)
|
||||
def dump(list), do: Ecto.Type.dump(type(), list)
|
||||
|
||||
def render_form(_conn, changeset, form, field, _options) do
|
||||
features = Ecto.Changeset.get_field(changeset, field)
|
||||
|
||||
checkboxes =
|
||||
for mod <- Plausible.Billing.Feature.list() do
|
||||
[
|
||||
{:safe, ~s(<label style="padding-right: 15px;">)},
|
||||
Phoenix.HTML.Tag.tag(
|
||||
:input,
|
||||
name: Phoenix.HTML.Form.input_name(form, field) <> "[]",
|
||||
id: Phoenix.HTML.Form.input_id(form, field, mod.name()),
|
||||
type: "checkbox",
|
||||
value: mod.name(),
|
||||
style: "margin-right: 3px;",
|
||||
checked: mod in features
|
||||
),
|
||||
mod.display_name(),
|
||||
{:safe, ~s(</label>)}
|
||||
]
|
||||
end
|
||||
|
||||
[
|
||||
{:safe, ~s(<div class="form-group">)},
|
||||
Phoenix.HTML.Form.label(form, field),
|
||||
{:safe, ~s(<div class="form-control">)},
|
||||
checkboxes,
|
||||
{:safe, ~s(</div>)},
|
||||
{:safe, ~s(</div>)}
|
||||
]
|
||||
end
|
||||
end
|
39
lib/plausible/billing/ecto/limit.ex
Normal file
39
lib/plausible/billing/ecto/limit.ex
Normal file
@ -0,0 +1,39 @@
|
||||
defmodule Plausible.Billing.Ecto.Limit do
|
||||
@moduledoc """
|
||||
Ecto type representing a limit, that can be either a number or unlimited.
|
||||
Unlimited is dumped to the database as `-1` and loaded as `:unlimited` to
|
||||
keep compatibility with the rest of the codebase.
|
||||
"""
|
||||
|
||||
use Ecto.Type
|
||||
|
||||
def type, do: :integer
|
||||
|
||||
def cast(-1), do: {:ok, :unlimited}
|
||||
def cast(:unlimited), do: {:ok, :unlimited}
|
||||
def cast(other), do: Ecto.Type.cast(:integer, other)
|
||||
|
||||
def load(-1), do: {:ok, :unlimited}
|
||||
def load(other), do: Ecto.Type.load(:integer, other)
|
||||
|
||||
def dump(:unlimited), do: {:ok, -1}
|
||||
def dump(other), do: Ecto.Type.dump(:integer, other)
|
||||
|
||||
def render_form(_conn, changeset, form, field, _options) do
|
||||
{:ok, value} = changeset |> Ecto.Changeset.get_field(field) |> dump()
|
||||
|
||||
[
|
||||
{:safe, ~s(<div class="form-group">)},
|
||||
Phoenix.HTML.Form.label(form, field),
|
||||
Phoenix.HTML.Form.number_input(form, field,
|
||||
class: "form-control",
|
||||
name: "#{form.name}[#{field}]",
|
||||
id: "#{form.name}_#{field}",
|
||||
value: value,
|
||||
min: -1
|
||||
),
|
||||
{:safe, ~s(<p class="help_text">Use -1 for unlimited.</p>)},
|
||||
{:safe, ~s(</div>)}
|
||||
]
|
||||
end
|
||||
end
|
@ -8,15 +8,19 @@ defmodule Plausible.Billing.EnterprisePlan do
|
||||
:billing_interval,
|
||||
:monthly_pageview_limit,
|
||||
:hourly_api_request_limit,
|
||||
:site_limit
|
||||
:site_limit,
|
||||
:features,
|
||||
:team_member_limit
|
||||
]
|
||||
|
||||
schema "enterprise_plans" do
|
||||
field :paddle_plan_id, :string
|
||||
field :billing_interval, Ecto.Enum, values: [:monthly, :yearly]
|
||||
field :monthly_pageview_limit, :integer
|
||||
field :hourly_api_request_limit, :integer
|
||||
field :site_limit, :integer
|
||||
field :team_member_limit, Plausible.Billing.Ecto.Limit
|
||||
field :features, Plausible.Billing.Ecto.FeatureList, default: []
|
||||
field :hourly_api_request_limit, :integer
|
||||
|
||||
belongs_to :user, Plausible.Auth.User
|
||||
|
||||
|
@ -8,14 +8,16 @@ defmodule Plausible.Billing.EnterprisePlanAdmin do
|
||||
]
|
||||
end
|
||||
|
||||
def form_fields(_) do
|
||||
def form_fields(_schema) do
|
||||
[
|
||||
user_id: nil,
|
||||
paddle_plan_id: nil,
|
||||
billing_interval: %{choices: [{"Yearly", "yearly"}, {"Monthly", "monthly"}]},
|
||||
monthly_pageview_limit: nil,
|
||||
site_limit: nil,
|
||||
team_member_limit: nil,
|
||||
hourly_api_request_limit: nil,
|
||||
site_limit: nil
|
||||
features: nil
|
||||
]
|
||||
end
|
||||
|
||||
@ -30,10 +32,16 @@ defmodule Plausible.Billing.EnterprisePlanAdmin do
|
||||
paddle_plan_id: nil,
|
||||
billing_interval: nil,
|
||||
monthly_pageview_limit: nil,
|
||||
hourly_api_request_limit: nil,
|
||||
site_limit: nil
|
||||
site_limit: nil,
|
||||
team_member_limit: nil,
|
||||
hourly_api_request_limit: nil
|
||||
]
|
||||
end
|
||||
|
||||
defp get_user_email(plan), do: plan.user.email
|
||||
|
||||
def update_changeset(enterprise_plan, attrs) do
|
||||
attrs = Map.put_new(attrs, "features", [])
|
||||
Plausible.Billing.EnterprisePlan.changeset(enterprise_plan, attrs)
|
||||
end
|
||||
end
|
||||
|
@ -102,7 +102,6 @@ defmodule Plausible.Billing.Feature do
|
||||
def check_availability(%Plausible.Auth.User{} = user) do
|
||||
cond do
|
||||
Keyword.get(unquote(opts), :free) -> :ok
|
||||
FunWithFlags.enabled?(:premium_features_private_preview) -> :ok
|
||||
__MODULE__ in Quota.allowed_features_for(user) -> :ok
|
||||
true -> {:error, :upgrade_required}
|
||||
end
|
||||
|
@ -123,8 +123,8 @@ defmodule Plausible.Billing.Quota do
|
||||
user = Plausible.Users.with_subscription(user)
|
||||
|
||||
case Plans.get_subscription_plan(user.subscription) do
|
||||
%EnterprisePlan{} ->
|
||||
:unlimited
|
||||
%EnterprisePlan{team_member_limit: limit} ->
|
||||
limit
|
||||
|
||||
%Plan{team_member_limit: limit} ->
|
||||
limit
|
||||
@ -252,7 +252,7 @@ defmodule Plausible.Billing.Quota do
|
||||
user = Plausible.Users.with_subscription(user)
|
||||
|
||||
case Plans.get_subscription_plan(user.subscription) do
|
||||
%EnterprisePlan{} -> Feature.list()
|
||||
%EnterprisePlan{features: features} -> features
|
||||
%Plan{features: features} -> features
|
||||
:free_10k -> [Goals, Props, StatsAPI]
|
||||
nil -> Feature.list()
|
||||
|
@ -5,10 +5,8 @@ defmodule PlausibleWeb.Components.Billing do
|
||||
import PlausibleWeb.Components.Generic
|
||||
require Plausible.Billing.Subscription.Status
|
||||
alias Plausible.Auth.User
|
||||
alias Plausible.Billing.Feature.{RevenueGoals, Funnels}
|
||||
alias Plausible.Billing.Feature.{Props, StatsAPI}
|
||||
alias PlausibleWeb.Router.Helpers, as: Routes
|
||||
alias Plausible.Billing.{Subscription, Plans, Plan, Subscriptions}
|
||||
alias Plausible.Billing.{Subscription, Plans, Subscriptions}
|
||||
|
||||
attr(:billable_user, User, required: true)
|
||||
attr(:current_user, User, required: true)
|
||||
@ -19,42 +17,18 @@ defmodule PlausibleWeb.Components.Billing do
|
||||
|
||||
# credo:disable-for-next-line Credo.Check.Refactor.CyclomaticComplexity
|
||||
def premium_feature_notice(assigns) do
|
||||
legacy_feature_access? =
|
||||
Timex.before?(assigns.billable_user.inserted_at, Plans.business_tier_launch()) &&
|
||||
assigns.feature_mod in [StatsAPI, Props]
|
||||
|
||||
has_access? = assigns.feature_mod.check_availability(assigns.billable_user) == :ok
|
||||
|
||||
cond do
|
||||
legacy_feature_access? ->
|
||||
~H""
|
||||
|
||||
Plausible.Billing.on_trial?(assigns.billable_user) ->
|
||||
~H""
|
||||
|
||||
not has_access? ->
|
||||
~H"""
|
||||
<.notice class="rounded-t-md rounded-b-none" size={@size} {@rest} title="Notice">
|
||||
<%= account_label(@current_user, @billable_user) %> does not have access to <%= assigns.feature_mod.display_name() %>. To get access to this feature,
|
||||
<.upgrade_call_to_action current_user={@current_user} billable_user={@billable_user} />.
|
||||
</.notice>
|
||||
"""
|
||||
|
||||
true ->
|
||||
~H""
|
||||
end
|
||||
end
|
||||
|
||||
defp private_preview_end do
|
||||
private_preview_ends_at = Timex.shift(Plausible.Billing.Plans.business_tier_launch(), days: 8)
|
||||
|
||||
days_remaining = Timex.diff(private_preview_ends_at, NaiveDateTime.utc_now(), :day)
|
||||
|
||||
cond do
|
||||
days_remaining <= 0 -> "today"
|
||||
days_remaining == 1 -> "tomorrow"
|
||||
true -> "in #{days_remaining} days"
|
||||
end
|
||||
~H"""
|
||||
<.notice
|
||||
:if={@feature_mod.check_availability(@billable_user) !== :ok}
|
||||
class="rounded-t-md rounded-b-none"
|
||||
size={@size}
|
||||
title="Notice"
|
||||
{@rest}
|
||||
>
|
||||
<%= account_label(@current_user, @billable_user) %> does not have access to <%= @feature_mod.display_name() %>. To get access to this feature,
|
||||
<.upgrade_call_to_action current_user={@current_user} billable_user={@billable_user} />.
|
||||
</.notice>
|
||||
"""
|
||||
end
|
||||
|
||||
attr(:billable_user, User, required: true)
|
||||
@ -238,8 +212,7 @@ defmodule PlausibleWeb.Components.Billing do
|
||||
%{
|
||||
dismissable: true,
|
||||
user: %User{subscription: %Subscription{status: Subscription.Status.deleted()}}
|
||||
} =
|
||||
assigns
|
||||
} = assigns
|
||||
) do
|
||||
~H"""
|
||||
<aside id="global-subscription-cancelled-notice" class="container">
|
||||
@ -259,8 +232,7 @@ defmodule PlausibleWeb.Components.Billing do
|
||||
%{
|
||||
dismissable: false,
|
||||
user: %User{subscription: %Subscription{status: Subscription.Status.deleted()}}
|
||||
} =
|
||||
assigns
|
||||
} = assigns
|
||||
) do
|
||||
assigns = assign(assigns, :container_id, "local-subscription-cancelled-notice")
|
||||
|
||||
@ -336,49 +308,6 @@ defmodule PlausibleWeb.Components.Billing do
|
||||
|
||||
def subscription_paused_notice(assigns), do: ~H""
|
||||
|
||||
def private_preview_end_notice(assigns) do
|
||||
user = assigns.user |> Plausible.Users.with_subscription()
|
||||
|
||||
features_to_lose =
|
||||
case Plans.get_subscription_plan(user.subscription) do
|
||||
nil ->
|
||||
[]
|
||||
|
||||
%Plan{kind: :business} ->
|
||||
[]
|
||||
|
||||
_free_10k_or_enterprise_or_growth ->
|
||||
used_features = Plausible.Billing.Quota.features_usage(assigns.user)
|
||||
Enum.filter([Funnels, RevenueGoals], &(&1 in used_features))
|
||||
end
|
||||
|
||||
assigns = assign(assigns, :features_to_lose, features_to_lose)
|
||||
|
||||
~H"""
|
||||
<div
|
||||
:if={FunWithFlags.enabled?(:premium_features_private_preview) && @features_to_lose != []}
|
||||
class="container"
|
||||
>
|
||||
<.notice
|
||||
class="shadow-md dark:shadow-none"
|
||||
title="Notice"
|
||||
dismissable_id={"premium_features_private_preview_end__#{@user.id}"}
|
||||
>
|
||||
Business plans are now live! The private preview of <%= PlausibleWeb.TextHelpers.pretty_join(
|
||||
Enum.map(@features_to_lose, & &1.display_name())
|
||||
) %> ends <b><%= private_preview_end() %></b>. If you wish to continue using <%= if length(
|
||||
@features_to_lose
|
||||
) == 1,
|
||||
do:
|
||||
"this feature",
|
||||
else:
|
||||
"these features" %>,
|
||||
<.upgrade_call_to_action current_user={@user} billable_user={@user} />.
|
||||
</.notice>
|
||||
</div>
|
||||
"""
|
||||
end
|
||||
|
||||
def present_enterprise_plan(assigns) do
|
||||
~H"""
|
||||
<ul class="w-full py-4">
|
||||
|
@ -108,7 +108,5 @@
|
||||
subscription={@conn.assigns.current_user.subscription}
|
||||
class="container"
|
||||
/>
|
||||
|
||||
<.private_preview_end_notice user={@conn.assigns.current_user} />
|
||||
</div>
|
||||
<% end %>
|
||||
|
2
mix.exs
2
mix.exs
@ -84,7 +84,7 @@ defmodule Plausible.MixProject do
|
||||
{:hammer, "~> 6.0"},
|
||||
{:httpoison, "~> 1.4"},
|
||||
{:jason, "~> 1.3"},
|
||||
{:kaffy, "~> 0.9.4"},
|
||||
{:kaffy, "~> 0.10.2"},
|
||||
{:location, git: "https://github.com/plausible/location.git"},
|
||||
{:mox, "~> 1.0", only: :test},
|
||||
{:nanoid, "~> 2.0.2"},
|
||||
|
2
mix.lock
2
mix.lock
@ -68,7 +68,7 @@
|
||||
"joken": {:hex, :joken, "2.6.0", "b9dd9b6d52e3e6fcb6c65e151ad38bf4bc286382b5b6f97079c47ade6b1bcc6a", [:mix], [{:jose, "~> 1.11.5", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "5a95b05a71cd0b54abd35378aeb1d487a23a52c324fa7efdffc512b655b5aaa7"},
|
||||
"jose": {:hex, :jose, "1.11.6", "613fda82552128aa6fb804682e3a616f4bc15565a048dabd05b1ebd5827ed965", [:mix, :rebar3], [], "hexpm", "6275cb75504f9c1e60eeacb771adfeee4905a9e182103aa59b53fed651ff9738"},
|
||||
"jumper": {:hex, :jumper, "1.0.1", "3c00542ef1a83532b72269fab9f0f0c82bf23a35e27d278bfd9ed0865cecabff", [:mix], [], "hexpm", "318c59078ac220e966d27af3646026db9b5a5e6703cb2aa3e26bcfaba65b7433"},
|
||||
"kaffy": {:hex, :kaffy, "0.9.4", "6a5446cd2c782b8e122061eab409254eb1fa412adb5824169f0528d16775dc45", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.5", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}], "hexpm", "91736c9ddc34a94ed76cb56058fdb6b206c9d777b71856c90ef4554f485f13b9"},
|
||||
"kaffy": {:hex, :kaffy, "0.10.2", "72e807c525323bd0cbc3ac0c127b7bde61caffdc576fb6554964d3fe6a2a6100", [:mix], [{:ecto, "~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.6", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0.2", [hex: :phoenix_view, repo: "hexpm", optional: false]}], "hexpm", "651cad5f3bcc91510a671c13c7a273b8b8195fdf2d809208708baecbb77300bf"},
|
||||
"location": {:git, "https://github.com/plausible/location.git", "31f91c4df430cfc16219000ac667cc665826b77c", []},
|
||||
"locus": {:hex, :locus, "2.3.6", "c9f53fd5df872fca66a54dc0aa2f8b2d3640388e56a0c39a741be0df6d8854bf", [:rebar3], [{:tls_certificate_check, "~> 1.9", [hex: :tls_certificate_check, repo: "hexpm", optional: false]}], "hexpm", "6087aa9a69673e7011837fb4b3d7f756560adde76892c32f5f93904ee30064e2"},
|
||||
"makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"},
|
||||
|
@ -0,0 +1,13 @@
|
||||
defmodule Plausible.Repo.Migrations.AddLimitsToEnterprisePlans do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
alter table(:enterprise_plans) do
|
||||
modify :hourly_api_request_limit, :integer, null: false
|
||||
modify :monthly_pageview_limit, :integer, null: false
|
||||
modify :site_limit, :integer, null: false
|
||||
add :team_member_limit, :integer, null: false, default: -1
|
||||
add :features, {:array, :string}, null: false, default: ["props", "stats_api"]
|
||||
end
|
||||
end
|
||||
end
|
51
test/plausible/billing/enterprise_plan_test.exs
Normal file
51
test/plausible/billing/enterprise_plan_test.exs
Normal file
@ -0,0 +1,51 @@
|
||||
defmodule Plausible.Billing.EnterprisePlanTest do
|
||||
use Plausible.DataCase
|
||||
alias Plausible.Billing.EnterprisePlan
|
||||
|
||||
test "changeset/2 loads and dumps the list of features" do
|
||||
plan = build(:enterprise_plan, user_id: insert(:user).id)
|
||||
attrs = %{features: ["props", "stats_api"]}
|
||||
|
||||
assert {:ok, enterprise_plan} =
|
||||
plan
|
||||
|> EnterprisePlan.changeset(attrs)
|
||||
|> Plausible.Repo.insert()
|
||||
|
||||
assert %EnterprisePlan{
|
||||
features: [Plausible.Billing.Feature.Props, Plausible.Billing.Feature.StatsAPI]
|
||||
} = enterprise_plan
|
||||
|
||||
assert %EnterprisePlan{
|
||||
features: [Plausible.Billing.Feature.Props, Plausible.Billing.Feature.StatsAPI]
|
||||
} = Plausible.Repo.get(EnterprisePlan, enterprise_plan.id)
|
||||
end
|
||||
|
||||
test "changeset/2 fails when feature does not exist" do
|
||||
plan = build(:enterprise_plan, user_id: insert(:user).id)
|
||||
attrs = %{features: ["ga4_import"]}
|
||||
|
||||
assert {:error, changeset} =
|
||||
plan
|
||||
|> EnterprisePlan.changeset(attrs)
|
||||
|> Plausible.Repo.insert()
|
||||
|
||||
assert {"is invalid", [type: Plausible.Billing.Ecto.FeatureList, validation: :cast]} ==
|
||||
changeset.errors[:features]
|
||||
end
|
||||
|
||||
test "changeset/2 loads and dumps limits" do
|
||||
plan = build(:enterprise_plan, user_id: insert(:user).id)
|
||||
attrs = %{team_member_limit: :unlimited, monthly_pageview_limit: 10_000}
|
||||
|
||||
assert {:ok, enterprise_plan} =
|
||||
plan
|
||||
|> EnterprisePlan.changeset(attrs)
|
||||
|> Plausible.Repo.insert()
|
||||
|
||||
assert %EnterprisePlan{team_member_limit: :unlimited, monthly_pageview_limit: 10_000} =
|
||||
enterprise_plan
|
||||
|
||||
assert %EnterprisePlan{team_member_limit: :unlimited, monthly_pageview_limit: 10_000} =
|
||||
Plausible.Repo.get(EnterprisePlan, enterprise_plan.id)
|
||||
end
|
||||
end
|
@ -7,7 +7,8 @@ defmodule Plausible.Billing.FeatureTest do
|
||||
test "#{mod}.check_availability/1 returns :ok when site owner is on a enterprise plan" do
|
||||
user =
|
||||
insert(:user,
|
||||
enterprise_plan: build(:enterprise_plan, paddle_plan_id: "123321"),
|
||||
enterprise_plan:
|
||||
build(:enterprise_plan, paddle_plan_id: "123321", features: [unquote(mod)]),
|
||||
subscription: build(:subscription, paddle_plan_id: "123321")
|
||||
)
|
||||
|
||||
@ -49,7 +50,11 @@ defmodule Plausible.Billing.FeatureTest do
|
||||
test "Plausible.Billing.Feature.StatsAPI.check_availability/2 returns :ok when user is on an enterprise plan" do
|
||||
user =
|
||||
insert(:user,
|
||||
enterprise_plan: build(:enterprise_plan, paddle_plan_id: "123321"),
|
||||
enterprise_plan:
|
||||
build(:enterprise_plan,
|
||||
paddle_plan_id: "123321",
|
||||
features: [Plausible.Billing.Feature.StatsAPI]
|
||||
),
|
||||
subscription: build(:subscription, paddle_plan_id: "123321")
|
||||
)
|
||||
|
||||
|
@ -376,16 +376,6 @@ defmodule Plausible.Billing.QuotaTest do
|
||||
assert :unlimited == Quota.team_member_limit(user)
|
||||
end
|
||||
|
||||
test "returns unlimited when user is on an enterprise plan" do
|
||||
user = insert(:user)
|
||||
enterprise_plan = insert(:enterprise_plan, user_id: user.id)
|
||||
|
||||
_subscription =
|
||||
insert(:subscription, user_id: user.id, paddle_plan_id: enterprise_plan.paddle_plan_id)
|
||||
|
||||
assert :unlimited == Quota.team_member_limit(user)
|
||||
end
|
||||
|
||||
test "returns 5 when user in on trial" do
|
||||
user =
|
||||
insert(:user,
|
||||
@ -406,14 +396,15 @@ defmodule Plausible.Billing.QuotaTest do
|
||||
assert :unlimited == Quota.team_member_limit(user)
|
||||
end
|
||||
|
||||
test "is unlimited for enterprise customers" do
|
||||
test "returns the enterprise plan limit" do
|
||||
user =
|
||||
insert(:user,
|
||||
enterprise_plan: build(:enterprise_plan, paddle_plan_id: "123321"),
|
||||
enterprise_plan:
|
||||
build(:enterprise_plan, paddle_plan_id: "123321", team_member_limit: 27),
|
||||
subscription: build(:subscription, paddle_plan_id: "123321")
|
||||
)
|
||||
|
||||
assert :unlimited == Quota.team_member_limit(user)
|
||||
assert 27 == Quota.team_member_limit(user)
|
||||
end
|
||||
|
||||
test "reads from json file when the user is on a v4 plan" do
|
||||
@ -522,20 +513,22 @@ defmodule Plausible.Billing.QuotaTest do
|
||||
assert [Goals, Props, StatsAPI] == Quota.allowed_features_for(user)
|
||||
end
|
||||
|
||||
test "returns all features when user is on an enterprise plan" do
|
||||
test "returns the enterprise plan features" do
|
||||
user = insert(:user)
|
||||
|
||||
enterprise_plan =
|
||||
insert(:enterprise_plan,
|
||||
user_id: user.id,
|
||||
monthly_pageview_limit: 100_000,
|
||||
site_limit: 500
|
||||
site_limit: 500,
|
||||
features: [Plausible.Billing.Feature.StatsAPI, Plausible.Billing.Feature.Funnels]
|
||||
)
|
||||
|
||||
_subscription =
|
||||
insert(:subscription, user_id: user.id, paddle_plan_id: enterprise_plan.paddle_plan_id)
|
||||
|
||||
assert Plausible.Billing.Feature.list() == Quota.allowed_features_for(user)
|
||||
assert [Plausible.Billing.Feature.StatsAPI, Plausible.Billing.Feature.Funnels] ==
|
||||
Quota.allowed_features_for(user)
|
||||
end
|
||||
|
||||
test "returns all features when user in on trial" do
|
||||
@ -564,25 +557,19 @@ defmodule Plausible.Billing.QuotaTest do
|
||||
assert Plausible.Billing.Feature.list() == Quota.allowed_features_for(user)
|
||||
end
|
||||
|
||||
test "returns all features for enterprise customers" do
|
||||
test "returns old plan features for enterprise customers who are due to change a plan" do
|
||||
user =
|
||||
insert(:user,
|
||||
enterprise_plan: build(:enterprise_plan, paddle_plan_id: "123321"),
|
||||
subscription: build(:subscription, paddle_plan_id: "123321")
|
||||
)
|
||||
|
||||
assert Plausible.Billing.Feature.list() == Quota.allowed_features_for(user)
|
||||
end
|
||||
|
||||
test "returns all features for enterprise customers who are due to change a plan" do
|
||||
user =
|
||||
insert(:user,
|
||||
enterprise_plan: build(:enterprise_plan, paddle_plan_id: "old-paddle-plan-id"),
|
||||
enterprise_plan:
|
||||
build(:enterprise_plan,
|
||||
paddle_plan_id: "old-paddle-plan-id",
|
||||
features: [Plausible.Billing.Feature.StatsAPI]
|
||||
),
|
||||
subscription: build(:subscription, paddle_plan_id: "old-paddle-plan-id")
|
||||
)
|
||||
|
||||
insert(:enterprise_plan, user_id: user.id, paddle_plan_id: "new-paddle-plan-id")
|
||||
assert Plausible.Billing.Feature.list() == Quota.allowed_features_for(user)
|
||||
assert [Plausible.Billing.Feature.StatsAPI] == Quota.allowed_features_for(user)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -121,7 +121,8 @@ defmodule Plausible.Factory do
|
||||
billing_interval: :monthly,
|
||||
monthly_pageview_limit: 1_000_000,
|
||||
hourly_api_request_limit: 3000,
|
||||
site_limit: 100
|
||||
site_limit: 100,
|
||||
team_member_limit: 10
|
||||
}
|
||||
end
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user