Refactor: Add 'goals' feature to JSON plan files (#3435)

* fix text color in dark mode

* rename a function

* use aliases in quota.ex

* rename a function

* make Goals a similar feature to others but with a free option

* rename a function

* mix format
This commit is contained in:
RobertJoonas 2023-10-18 11:29:13 +03:00 committed by GitHub
parent 52d5fac362
commit 7674c94ace
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 178 additions and 155 deletions

View File

@ -9,18 +9,25 @@ defmodule Plausible.Billing.Feature do
When defining new features, the following options are expected by the
`__using__` macro:
* `:name` - an atom representing the feature name in the plan JSON
file (see also Plausible.Billing.Plan).
* `:display_name` - human-readable display name of the feature
* `:toggle_field` - the field in the %Plausible.Site{} schema that toggles
the feature. If `nil` or not set, toggle/2 silently returns `:ok`
* `:extra_feature` - an atom representing the feature name in the plan JSON
file (see also Plausible.Billing.Plan). If `nil` or not set,
`check_availability/1` silently returns `:ok`
* `:free` - if set to `true`, makes the `check_availability/1` function
always return `:ok` (no matter the user's subscription status)
Functions defined by `__using__` can be overridden if needed.
"""
@doc """
Returns the atom representing the feature name in the plan JSON file.
"""
@callback name() :: atom()
@doc """
Returns the human-readable display name of the feature.
"""
@ -32,7 +39,7 @@ defmodule Plausible.Billing.Feature do
@callback toggle_field() :: atom()
@doc """
Toggles the feature on and off for a site. Returns
Toggles the feature on and off for a site. Returns
`{:error, :upgrade_required}` when toggling a feature the site owner does not
have access to.
"""
@ -51,9 +58,10 @@ defmodule Plausible.Billing.Feature do
:ok | {:error, :upgrade_required} | {:error, :not_implemented}
@features [
Plausible.Billing.Feature.Funnels,
Plausible.Billing.Feature.Goals,
Plausible.Billing.Feature.StatsAPI,
Plausible.Billing.Feature.Props,
Plausible.Billing.Feature.Funnels,
Plausible.Billing.Feature.RevenueGoals
]
@ -70,6 +78,9 @@ defmodule Plausible.Billing.Feature do
@behaviour Plausible.Billing.Feature
alias Plausible.Billing.Quota
@impl true
def name, do: Keyword.get(unquote(opts), :name)
@impl true
def display_name, do: Keyword.get(unquote(opts), :display_name)
@ -89,12 +100,10 @@ defmodule Plausible.Billing.Feature do
@impl true
def check_availability(%Plausible.Auth.User{} = user) do
extra_feature = Keyword.get(unquote(opts), :extra_feature)
cond do
is_nil(extra_feature) -> :ok
not FunWithFlags.enabled?(:business_tier, for: user) -> :ok
extra_feature in Quota.extra_features_limit(user) -> :ok
Keyword.get(unquote(opts), :free) -> :ok
__MODULE__ in Quota.allowed_features_for(user) -> :ok
true -> {:error, :upgrade_required}
end
end
@ -122,36 +131,38 @@ end
defmodule Plausible.Billing.Feature.Funnels do
@moduledoc false
use Plausible.Billing.Feature,
name: :funnels,
display_name: "Funnels",
toggle_field: :funnels_enabled,
extra_feature: :funnels
toggle_field: :funnels_enabled
end
defmodule Plausible.Billing.Feature.RevenueGoals do
@moduledoc false
use Plausible.Billing.Feature,
display_name: "Revenue Goals",
extra_feature: :revenue_goals
name: :revenue_goals,
display_name: "Revenue Goals"
end
defmodule Plausible.Billing.Feature.Goals do
@moduledoc false
use Plausible.Billing.Feature,
name: :goals,
display_name: "Goals",
toggle_field: :conversions_enabled
toggle_field: :conversions_enabled,
free: true
end
defmodule Plausible.Billing.Feature.Props do
@moduledoc false
use Plausible.Billing.Feature,
name: :props,
display_name: "Custom Properties",
toggle_field: :props_enabled,
extra_feature: :props
toggle_field: :props_enabled
end
defmodule Plausible.Billing.Feature.StatsAPI do
@moduledoc false
use Plausible.Billing.Feature,
display_name: "Stats API",
extra_feature: :stats_api
name: :stats_api,
display_name: "Stats API"
end

View File

@ -2,7 +2,7 @@ defmodule Plausible.Billing.Plan do
@moduledoc false
@derive Jason.Encoder
@enforce_keys ~w(kind site_limit monthly_pageview_limit team_member_limit extra_features volume monthly_product_id yearly_product_id)a
@enforce_keys ~w(kind site_limit monthly_pageview_limit team_member_limit features volume monthly_product_id yearly_product_id)a
defstruct @enforce_keys ++ [:monthly_cost, :yearly_cost]
@type t() ::
@ -16,7 +16,7 @@ defmodule Plausible.Billing.Plan do
yearly_cost: Money.t() | nil,
monthly_product_id: String.t() | nil,
yearly_product_id: String.t() | nil,
extra_features: [atom()]
features: [atom()]
}
| :enterprise
@ -31,8 +31,6 @@ defmodule Plausible.Billing.Plans do
alias Plausible.Billing.{Subscription, Plan, EnterprisePlan}
alias Plausible.Auth.User
@available_features ["props", "revenue_goals", "funnels", "stats_api"]
for f <- [
:plans_v1,
:plans_v2,
@ -56,20 +54,26 @@ defmodule Plausible.Billing.Plans do
_any -> raise ArgumentError, "Failed to parse team member limit from plan JSON files"
end
extra_features =
Enum.map(raw.extra_features, fn feature ->
if feature in @available_features,
do: String.to_atom(feature),
else: raise(ArgumentError, "Failed to parse extra features from plan JSON files")
features =
Plausible.Billing.Feature.list()
|> Enum.filter(fn module ->
to_string(module.name()) in raw.features
end)
if length(features) != length(raw.features),
do:
raise(
ArgumentError,
"Unrecognized feature(s) in #{inspect(raw.features)} (#{f}.json)"
)
volume = PlausibleWeb.StatsView.large_number_format(raw.monthly_pageview_limit)
raw
|> Map.put(:volume, volume)
|> Map.put(:kind, String.to_atom(raw.kind))
|> Map.put(:team_member_limit, team_member_limit)
|> Map.put(:extra_features, extra_features)
|> Map.put(:features, features)
|> Plan.new()
end)

View File

@ -4,7 +4,9 @@ defmodule Plausible.Billing.Quota do
"""
import Ecto.Query
alias Plausible.Billing.Plans
alias Plausible.Billing
alias Plausible.Billing.{Plan, Plans, Subscription, EnterprisePlan, Feature}
alias Plausible.Billing.Feature.{Goals, RevenueGoals, Funnels, Props}
@limit_sites_since ~D[2021-05-05]
@spec site_limit(Plausible.Auth.User.t()) :: non_neg_integer() | :unlimited
@ -28,8 +30,8 @@ defmodule Plausible.Billing.Quota do
user = Plausible.Users.with_subscription(user)
case Plans.get_subscription_plan(user.subscription) do
%Plausible.Billing.EnterprisePlan{} -> :unlimited
%Plausible.Billing.Plan{site_limit: site_limit} -> site_limit
%EnterprisePlan{} -> :unlimited
%Plan{site_limit: site_limit} -> site_limit
:free_10k -> @site_limit_for_free_10k
nil -> @site_limit_for_trials
end
@ -46,17 +48,17 @@ defmodule Plausible.Billing.Quota do
@monthly_pageview_limit_for_free_10k 10_000
@monthly_pageview_limit_for_trials :unlimited
@spec monthly_pageview_limit(Plausible.Billing.Subscription.t()) ::
@spec monthly_pageview_limit(Subscription.t()) ::
non_neg_integer() | :unlimited
@doc """
Returns the limit of pageviews for a subscription.
"""
def monthly_pageview_limit(subscription) do
case Plans.get_subscription_plan(subscription) do
%Plausible.Billing.EnterprisePlan{monthly_pageview_limit: limit} ->
%EnterprisePlan{monthly_pageview_limit: limit} ->
limit
%Plausible.Billing.Plan{monthly_pageview_limit: limit} ->
%Plan{monthly_pageview_limit: limit} ->
limit
:free_10k ->
@ -80,7 +82,7 @@ defmodule Plausible.Billing.Quota do
"""
def monthly_pageview_usage(user) do
user
|> Plausible.Billing.usage_breakdown()
|> Billing.usage_breakdown()
|> Tuple.sum()
end
@ -93,8 +95,8 @@ defmodule Plausible.Billing.Quota do
user = Plausible.Users.with_subscription(user)
case Plans.get_subscription_plan(user.subscription) do
%Plausible.Billing.EnterprisePlan{} -> :unlimited
%Plausible.Billing.Plan{team_member_limit: limit} -> limit
%EnterprisePlan{} -> :unlimited
%Plan{team_member_limit: limit} -> limit
:free_10k -> :unlimited
nil -> @team_member_limit_for_trials
end
@ -129,11 +131,13 @@ defmodule Plausible.Billing.Quota do
Plausible.Repo.one(query)
end
@spec extra_features_usage(Plausible.Auth.User.t()) :: [atom()]
@spec features_usage(Plausible.Auth.User.t()) :: [atom()]
@doc """
Returns a list of extra features the given user's sites uses.
Returns a list of features the given user is using. At the
current stage, the only features that we need to know the
usage for are `Props`, `Funnels`, and `RevenueGoals`
"""
def extra_features_usage(user) do
def features_usage(user) do
props_usage_query =
from s in Plausible.Site,
inner_join: os in subquery(owned_sites_query(user)),
@ -152,9 +156,9 @@ defmodule Plausible.Billing.Quota do
where: not is_nil(g.currency)
queries = [
props: props_usage_query,
funnels: funnels_usage_query,
revenue_goals: revenue_goals_usage
{Props, props_usage_query},
{Funnels, funnels_usage_query},
{RevenueGoals, revenue_goals_usage}
]
Enum.reduce(queries, [], fn {feature, query}, acc ->
@ -162,19 +166,18 @@ defmodule Plausible.Billing.Quota do
end)
end
@all_features [:props, :revenue_goals, :funnels, :stats_api]
@doc """
Returns a list of extra features the user can use. Trial users have the
Returns a list of features the user can use. Trial users have the
ability to use all features during their trial.
"""
def extra_features_limit(user) do
def allowed_features_for(user) do
user = Plausible.Users.with_subscription(user)
case Plans.get_subscription_plan(user.subscription) do
%Plausible.Billing.EnterprisePlan{} -> @all_features
%Plausible.Billing.Plan{extra_features: extra_features} -> extra_features
:free_10k -> []
nil -> @all_features
%EnterprisePlan{} -> Feature.list()
%Plan{features: features} -> features
:free_10k -> [Goals]
nil -> Feature.list()
end
end

View File

@ -15,7 +15,7 @@ defmodule PlausibleWeb.Components.Billing do
attr(:rest, :global)
# credo:disable-for-next-line Credo.Check.Refactor.CyclomaticComplexity
def extra_feature_notice(assigns) do
def premium_feature_notice(assigns) do
private_preview? = not FunWithFlags.enabled?(:business_tier, for: assigns.current_user)
display_upgrade_link? = assigns.current_user.id == assigns.billable_user.id
has_access? = assigns.feature_mod.check_availability(assigns.billable_user) == :ok

View File

@ -381,7 +381,10 @@ defmodule PlausibleWeb.Live.ChoosePlan do
</p>
<p class="h-4 mt-1"></p>
<.contact_button class="" />
<ul role="list" class="mt-8 space-y-3 text-sm leading-6 xl:mt-10 text-gray-300">
<ul
role="list"
class="mt-8 space-y-3 text-sm leading-6 xl:mt-10 text-gray-300 dark:text-gray-100"
>
<li class="flex gap-x-3">
<.check_icon class="text-white dark:text-green-600" /> Unlimited products
</li>

View File

@ -192,7 +192,7 @@ defmodule PlausibleWeb.Live.GoalSettings.Form do
</button>
<span x-show="active">
<PlausibleWeb.Components.Billing.extra_feature_notice
<PlausibleWeb.Components.Billing.premium_feature_notice
billable_user={@site.owner}
current_user={@current_user}
feature_mod={Plausible.Billing.Feature.RevenueGoals}

View File

@ -319,7 +319,7 @@
<h2 class="text-xl font-black dark:text-gray-100">API Keys</h2>
<div class="my-4 border-b border-gray-300 dark:border-gray-500"></div>
<PlausibleWeb.Components.Billing.extra_feature_notice
<PlausibleWeb.Components.Billing.premium_feature_notice
billable_user={@current_user}
current_user={@current_user}
feature_mod={Plausible.Billing.Feature.StatsAPI}

View File

@ -1,5 +1,5 @@
<section class="shadow bg-white dark:bg-gray-800 sm:rounded-md sm:overflow-hidden">
<PlausibleWeb.Components.Billing.extra_feature_notice
<PlausibleWeb.Components.Billing.premium_feature_notice
billable_user={@site.owner}
current_user={@current_user}
feature_mod={Plausible.Billing.Feature.Funnels}

View File

@ -1,5 +1,5 @@
<section class="shadow bg-white dark:bg-gray-800 sm:rounded-md sm:overflow-hidden">
<PlausibleWeb.Components.Billing.extra_feature_notice
<PlausibleWeb.Components.Billing.premium_feature_notice
billable_user={@site.owner}
current_user={@current_user}
feature_mod={Plausible.Billing.Feature.Props}

View File

@ -6,7 +6,7 @@
"yearly_product_id":"572810",
"site_limit":50,
"team_member_limit":"unlimited",
"extra_features":["props","stats_api"]
"features":["goals","props","stats_api"]
},
{
"kind":"growth",
@ -15,7 +15,7 @@
"yearly_product_id":"590752",
"site_limit":50,
"team_member_limit":"unlimited",
"extra_features":["props","stats_api"]
"features":["goals","props","stats_api"]
},
{
"kind":"growth",
@ -24,7 +24,7 @@
"yearly_product_id":"597486",
"site_limit":50,
"team_member_limit":"unlimited",
"extra_features":["props","stats_api"]
"features":["goals","props","stats_api"]
},
{
"kind":"growth",
@ -33,7 +33,7 @@
"yearly_product_id":"597488",
"site_limit":50,
"team_member_limit":"unlimited",
"extra_features":["props","stats_api"]
"features":["goals","props","stats_api"]
},
{
"kind":"growth",
@ -42,7 +42,7 @@
"yearly_product_id":"597643",
"site_limit":50,
"team_member_limit":"unlimited",
"extra_features":["props","stats_api"]
"features":["goals","props","stats_api"]
},
{
"kind":"growth",
@ -51,7 +51,7 @@
"yearly_product_id":"597310",
"site_limit":50,
"team_member_limit":"unlimited",
"extra_features":["props","stats_api"]
"features":["goals","props","stats_api"]
},
{
"kind":"growth",
@ -60,7 +60,7 @@
"yearly_product_id":"597312",
"site_limit":50,
"team_member_limit":"unlimited",
"extra_features":["props","stats_api"]
"features":["goals","props","stats_api"]
},
{
"kind":"growth",
@ -69,7 +69,7 @@
"yearly_product_id":"642354",
"site_limit":50,
"team_member_limit":"unlimited",
"extra_features":["props","stats_api"]
"features":["goals","props","stats_api"]
},
{
"kind":"growth",
@ -78,7 +78,7 @@
"yearly_product_id":"642356",
"site_limit":50,
"team_member_limit":"unlimited",
"extra_features":["props","stats_api"]
"features":["goals","props","stats_api"]
},
{
"kind":"growth",
@ -87,6 +87,6 @@
"yearly_product_id":"650653",
"site_limit":50,
"team_member_limit":"unlimited",
"extra_features":["props","stats_api"]
"features":["goals","props","stats_api"]
}
]

View File

@ -6,7 +6,7 @@
"yearly_product_id":"653232",
"site_limit":50,
"team_member_limit":"unlimited",
"extra_features":["props","stats_api"]
"features":["goals","props","stats_api"]
},
{
"kind":"growth",
@ -15,7 +15,7 @@
"yearly_product_id":"653234",
"site_limit":50,
"team_member_limit":"unlimited",
"extra_features":["props","stats_api"]
"features":["goals","props","stats_api"]
},
{
"kind":"growth",
@ -24,7 +24,7 @@
"yearly_product_id":"653236",
"site_limit":50,
"team_member_limit":"unlimited",
"extra_features":["props","stats_api"]
"features":["goals","props","stats_api"]
},
{
"kind":"growth",
@ -33,7 +33,7 @@
"yearly_product_id":"653239",
"site_limit":50,
"team_member_limit":"unlimited",
"extra_features":["props","stats_api"]
"features":["goals","props","stats_api"]
},
{
"kind":"growth",
@ -42,7 +42,7 @@
"yearly_product_id":"653242",
"site_limit":50,
"team_member_limit":"unlimited",
"extra_features":["props","stats_api"]
"features":["goals","props","stats_api"]
},
{
"kind":"growth",
@ -51,7 +51,7 @@
"yearly_product_id":"653254",
"site_limit":50,
"team_member_limit":"unlimited",
"extra_features":["props","stats_api"]
"features":["goals","props","stats_api"]
},
{
"kind":"growth",
@ -60,7 +60,7 @@
"yearly_product_id":"653256",
"site_limit":50,
"team_member_limit":"unlimited",
"extra_features":["props","stats_api"]
"features":["goals","props","stats_api"]
},
{
"kind":"growth",
@ -69,7 +69,7 @@
"yearly_product_id":"653257",
"site_limit":50,
"team_member_limit":"unlimited",
"extra_features":["props","stats_api"]
"features":["goals","props","stats_api"]
},
{
"kind":"growth",
@ -78,7 +78,7 @@
"yearly_product_id":"653258",
"site_limit":50,
"team_member_limit":"unlimited",
"extra_features":["props","stats_api"]
"features":["goals","props","stats_api"]
},
{
"kind":"growth",
@ -87,6 +87,6 @@
"yearly_product_id":"653259",
"site_limit":50,
"team_member_limit":"unlimited",
"extra_features":["props","stats_api"]
"features":["goals","props","stats_api"]
}
]

View File

@ -6,7 +6,7 @@
"yearly_product_id":"749343",
"site_limit":50,
"team_member_limit":"unlimited",
"extra_features":["props","stats_api"]
"features":["goals","props","stats_api"]
},
{
"kind":"growth",
@ -15,7 +15,7 @@
"yearly_product_id":"749345",
"site_limit":50,
"team_member_limit":"unlimited",
"extra_features":["props","stats_api"]
"features":["goals","props","stats_api"]
},
{
"kind":"growth",
@ -24,7 +24,7 @@
"yearly_product_id":"749347",
"site_limit":50,
"team_member_limit":"unlimited",
"extra_features":["props","stats_api"]
"features":["goals","props","stats_api"]
},
{
"kind":"growth",
@ -33,7 +33,7 @@
"yearly_product_id":"749349",
"site_limit":50,
"team_member_limit":"unlimited",
"extra_features":["props","stats_api"]
"features":["goals","props","stats_api"]
},
{
"kind":"growth",
@ -42,7 +42,7 @@
"yearly_product_id":"749352",
"site_limit":50,
"team_member_limit":"unlimited",
"extra_features":["props","stats_api"]
"features":["goals","props","stats_api"]
},
{
"kind":"growth",
@ -51,7 +51,7 @@
"yearly_product_id":"749355",
"site_limit":50,
"team_member_limit":"unlimited",
"extra_features":["props","stats_api"]
"features":["goals","props","stats_api"]
},
{
"kind":"growth",
@ -60,7 +60,7 @@
"yearly_product_id":"749357",
"site_limit":50,
"team_member_limit":"unlimited",
"extra_features":["props","stats_api"]
"features":["goals","props","stats_api"]
},
{
"kind":"growth",
@ -69,6 +69,6 @@
"yearly_product_id":"749359",
"site_limit":50,
"team_member_limit":"unlimited",
"extra_features":["props","stats_api"]
"features":["goals","props","stats_api"]
}
]

View File

@ -6,7 +6,7 @@
"yearly_product_id":"change-me-749343",
"site_limit":10,
"team_member_limit":5,
"extra_features":[]
"features":["goals"]
},
{
"kind":"growth",
@ -15,7 +15,7 @@
"yearly_product_id":"change-me-749345",
"site_limit":10,
"team_member_limit":5,
"extra_features":[]
"features":["goals"]
},
{
"kind":"growth",
@ -24,7 +24,7 @@
"yearly_product_id":"change-me-749347",
"site_limit":10,
"team_member_limit":5,
"extra_features":[]
"features":["goals"]
},
{
"kind":"growth",
@ -33,7 +33,7 @@
"yearly_product_id":"change-me-749349",
"site_limit":10,
"team_member_limit":5,
"extra_features":[]
"features":["goals"]
},
{
"kind":"growth",
@ -42,7 +42,7 @@
"yearly_product_id":"change-me-749352",
"site_limit":10,
"team_member_limit":5,
"extra_features":[]
"features":["goals"]
},
{
"kind":"growth",
@ -51,7 +51,7 @@
"yearly_product_id":"change-me-749355",
"site_limit":10,
"team_member_limit":5,
"extra_features":[]
"features":["goals"]
},
{
"kind":"growth",
@ -60,7 +60,7 @@
"yearly_product_id":"change-me-749357",
"site_limit":10,
"team_member_limit":5,
"extra_features":[]
"features":["goals"]
},
{
"kind":"growth",
@ -69,7 +69,7 @@
"yearly_product_id":"change-me-749359",
"site_limit":10,
"team_member_limit":5,
"extra_features":[]
"features":["goals"]
},
{
"kind":"business",
@ -78,7 +78,7 @@
"yearly_product_id":"change-me-b749343",
"site_limit":50,
"team_member_limit":50,
"extra_features":["props","revenue_goals","funnels","stats_api"]
"features":["goals","props","revenue_goals","funnels","stats_api"]
},
{
"kind":"business",
@ -87,7 +87,7 @@
"yearly_product_id":"change-me-b749345",
"site_limit":50,
"team_member_limit":50,
"extra_features":["props","revenue_goals","funnels","stats_api"]
"features":["goals","props","revenue_goals","funnels","stats_api"]
},
{
"kind":"business",
@ -96,7 +96,7 @@
"yearly_product_id":"change-me-b749347",
"site_limit":50,
"team_member_limit":50,
"extra_features":["props","revenue_goals","funnels","stats_api"]
"features":["goals","props","revenue_goals","funnels","stats_api"]
},
{
"kind":"business",
@ -105,7 +105,7 @@
"yearly_product_id":"change-me-b749349",
"site_limit":50,
"team_member_limit":50,
"extra_features":["props","revenue_goals","funnels","stats_api"]
"features":["goals","props","revenue_goals","funnels","stats_api"]
},
{
"kind":"business",
@ -114,7 +114,7 @@
"yearly_product_id":"change-me-b749352",
"site_limit":50,
"team_member_limit":50,
"extra_features":["props","revenue_goals","funnels","stats_api"]
"features":["goals","props","revenue_goals","funnels","stats_api"]
},
{
"kind":"business",
@ -123,7 +123,7 @@
"yearly_product_id":"change-me-b749355",
"site_limit":50,
"team_member_limit":50,
"extra_features":["props","revenue_goals","funnels","stats_api"]
"features":["goals","props","revenue_goals","funnels","stats_api"]
},
{
"kind":"business",
@ -132,7 +132,7 @@
"yearly_product_id":"change-me-b749357",
"site_limit":50,
"team_member_limit":50,
"extra_features":["props","revenue_goals","funnels","stats_api"]
"features":["goals","props","revenue_goals","funnels","stats_api"]
},
{
"kind":"business",
@ -141,6 +141,6 @@
"yearly_product_id":"change-me-b749359",
"site_limit":50,
"team_member_limit":50,
"extra_features":["props","revenue_goals","funnels","stats_api"]
"features":["goals","props","revenue_goals","funnels","stats_api"]
}
]

View File

@ -6,7 +6,7 @@
"yearly_product_id":"63859",
"site_limit":10,
"team_member_limit":5,
"extra_features":["props","stats_api"]
"features":["goals","props","stats_api"]
},
{
"kind":"growth",
@ -15,7 +15,7 @@
"yearly_product_id":"63860",
"site_limit":10,
"team_member_limit":5,
"extra_features":["props","stats_api"]
"features":["goals","props","stats_api"]
},
{
"kind":"growth",
@ -24,7 +24,7 @@
"yearly_product_id":"63861",
"site_limit":10,
"team_member_limit":5,
"extra_features":["props","stats_api"]
"features":["goals","props","stats_api"]
},
{
"kind":"growth",
@ -33,7 +33,7 @@
"yearly_product_id":"63862",
"site_limit":10,
"team_member_limit":5,
"extra_features":["props","stats_api"]
"features":["goals","props","stats_api"]
},
{
"kind":"growth",
@ -42,7 +42,7 @@
"yearly_product_id":"63863",
"site_limit":10,
"team_member_limit":5,
"extra_features":["props","stats_api"]
"features":["goals","props","stats_api"]
},
{
"kind":"growth",
@ -51,7 +51,7 @@
"yearly_product_id":"63864",
"site_limit":10,
"team_member_limit":5,
"extra_features":["props","stats_api"]
"features":["goals","props","stats_api"]
},
{
"kind":"growth",
@ -60,7 +60,7 @@
"yearly_product_id":"63865",
"site_limit":10,
"team_member_limit":5,
"extra_features":["props","stats_api"]
"features":["goals","props","stats_api"]
},
{
"kind":"growth",
@ -69,7 +69,7 @@
"yearly_product_id":"63866",
"site_limit":10,
"team_member_limit":5,
"extra_features":["props","stats_api"]
"features":["goals","props","stats_api"]
},
{
"kind":"business",
@ -78,7 +78,7 @@
"yearly_product_id":"63867",
"site_limit":50,
"team_member_limit":50,
"extra_features":["props", "revenue_goals", "funnels","stats_api"]
"features":["goals","props", "revenue_goals", "funnels","stats_api"]
},
{
"kind":"business",
@ -87,7 +87,7 @@
"yearly_product_id":"63868",
"site_limit":50,
"team_member_limit":50,
"extra_features":["props", "revenue_goals", "funnels","stats_api"]
"features":["goals","props", "revenue_goals", "funnels","stats_api"]
},
{
"kind":"business",
@ -96,7 +96,7 @@
"yearly_product_id":"63869",
"site_limit":50,
"team_member_limit":50,
"extra_features":["props", "revenue_goals", "funnels","stats_api"]
"features":["goals","props", "revenue_goals", "funnels","stats_api"]
},
{
"kind":"business",
@ -105,7 +105,7 @@
"yearly_product_id":"63870",
"site_limit":50,
"team_member_limit":50,
"extra_features":["props", "revenue_goals", "funnels","stats_api"]
"features":["goals","props", "revenue_goals", "funnels","stats_api"]
},
{
"kind":"business",
@ -114,7 +114,7 @@
"yearly_product_id":"63871",
"site_limit":50,
"team_member_limit":50,
"extra_features":["props", "revenue_goals", "funnels","stats_api"]
"features":["goals","props", "revenue_goals", "funnels","stats_api"]
},
{
"kind":"business",
@ -123,7 +123,7 @@
"yearly_product_id":"63872",
"site_limit":50,
"team_member_limit":50,
"extra_features":["props", "revenue_goals", "funnels","stats_api"]
"features":["goals","props", "revenue_goals", "funnels","stats_api"]
},
{
"kind":"business",
@ -132,7 +132,7 @@
"yearly_product_id":"63873",
"site_limit":50,
"team_member_limit":50,
"extra_features":["props", "revenue_goals", "funnels","stats_api"]
"features":["goals","props", "revenue_goals", "funnels","stats_api"]
},
{
"kind":"business",
@ -141,6 +141,6 @@
"yearly_product_id":"63874",
"site_limit":50,
"team_member_limit":50,
"extra_features":["props", "revenue_goals", "funnels","stats_api"]
"features":["goals","props", "revenue_goals", "funnels","stats_api"]
}
]

View File

@ -6,6 +6,6 @@
"monthly_product_id":null,
"site_limit":50,
"team_member_limit":"unlimited",
"extra_features":["props"]
"features":["goals","props"]
}
]

View File

@ -6,6 +6,6 @@
"yearly_product_id":null,
"site_limit":50,
"team_member_limit":"unlimited",
"extra_features":["props"]
"features":["goals","props"]
}
]

View File

@ -1,6 +1,7 @@
defmodule Plausible.Billing.QuotaTest do
use Plausible.DataCase, async: true
alias Plausible.Billing.Quota
alias Plausible.Billing.Feature.{Goals, RevenueGoals, Funnels, Props, StatsAPI}
@v1_plan_id "558018"
@v2_plan_id "654177"
@ -364,13 +365,13 @@ defmodule Plausible.Billing.QuotaTest do
end
end
describe "extra_features_usage/1" do
describe "features_usage/1" do
test "returns an empty list" do
user = insert(:user)
assert [] == Quota.extra_features_usage(user)
assert [] == Quota.features_usage(user)
end
test "returns :props when user uses custom props" do
test "returns [Props] when user uses custom props" do
user = insert(:user)
insert(:site,
@ -378,10 +379,10 @@ defmodule Plausible.Billing.QuotaTest do
memberships: [build(:site_membership, user: user, role: :owner)]
)
assert [:props] == Quota.extra_features_usage(user)
assert [Props] == Quota.features_usage(user)
end
test "returns :funnels when user uses funnels" do
test "returns [Funnels] when user uses funnels" do
user = insert(:user)
site = insert(:site, memberships: [build(:site_membership, user: user, role: :owner)])
@ -389,18 +390,18 @@ defmodule Plausible.Billing.QuotaTest do
steps = Enum.map(goals, &%{"goal_id" => &1.id})
Plausible.Funnels.create(site, "dummy", steps)
assert [:funnels] == Quota.extra_features_usage(user)
assert [Funnels] == Quota.features_usage(user)
end
test "returns :revenue_goals when user uses revenue goals" do
test "returns [RevenueGoals] when user uses revenue goals" do
user = insert(:user)
site = insert(:site, memberships: [build(:site_membership, user: user, role: :owner)])
insert(:goal, currency: :USD, site: site, event_name: "Purchase")
assert [:revenue_goals] == Quota.extra_features_usage(user)
assert [RevenueGoals] == Quota.features_usage(user)
end
test "returns multiple extra features" do
test "returns multiple features" do
user = insert(:user)
site =
@ -415,7 +416,7 @@ defmodule Plausible.Billing.QuotaTest do
steps = Enum.map(goals, &%{"goal_id" => &1.id})
Plausible.Funnels.create(site, "dummy", steps)
assert [:revenue_goals, :funnels, :props] == Quota.extra_features_usage(user)
assert [RevenueGoals, Funnels, Props] == Quota.features_usage(user)
end
test "accounts only for sites the user owns" do
@ -426,27 +427,27 @@ defmodule Plausible.Billing.QuotaTest do
memberships: [build(:site_membership, user: user, role: :admin)]
)
assert [] == Quota.extra_features_usage(user)
assert [] == Quota.features_usage(user)
end
end
describe "extra_features_limit/1" do
test "returns props when user is on an old plan" do
describe "allowed_features_for/1" do
test "returns all grandfathered features when user is on an old plan" do
user_on_v1 = insert(:user, subscription: build(:subscription, paddle_plan_id: @v1_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))
assert [:props, :stats_api] == Quota.extra_features_limit(user_on_v1)
assert [:props, :stats_api] == Quota.extra_features_limit(user_on_v2)
assert [:props, :stats_api] == Quota.extra_features_limit(user_on_v3)
assert [Goals, StatsAPI, Props] == Quota.allowed_features_for(user_on_v1)
assert [Goals, StatsAPI, Props] == Quota.allowed_features_for(user_on_v2)
assert [Goals, StatsAPI, Props] == Quota.allowed_features_for(user_on_v3)
end
test "returns an empty list when user is on free_10k plan" do
test "returns [Goals] when user is on free_10k plan" do
user = insert(:user, subscription: build(:subscription, paddle_plan_id: "free_10k"))
assert [] == Quota.extra_features_limit(user)
assert [Goals] == Quota.allowed_features_for(user)
end
test "returns all extra features when user is on an enterprise plan" do
test "returns all features when user is on an enterprise plan" do
user = insert(:user)
enterprise_plan =
@ -459,12 +460,13 @@ defmodule Plausible.Billing.QuotaTest do
_subscription =
insert(:subscription, user_id: user.id, paddle_plan_id: enterprise_plan.paddle_plan_id)
assert [:props, :revenue_goals, :funnels, :stats_api] == Quota.extra_features_limit(user)
assert Plausible.Billing.Feature.list() == Quota.allowed_features_for(user)
end
test "returns all extra 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))
assert [:props, :revenue_goals, :funnels, :stats_api] == Quota.extra_features_limit(user)
assert Plausible.Billing.Feature.list() == Quota.allowed_features_for(user)
end
test "returns previous plan limits for enterprise users who have not paid yet" do
@ -474,30 +476,30 @@ defmodule Plausible.Billing.QuotaTest do
subscription: build(:subscription, paddle_plan_id: @v1_plan_id)
)
assert [:props, :stats_api] == Quota.extra_features_limit(user)
assert [Goals, StatsAPI, Props] == Quota.allowed_features_for(user)
end
test "returns trial limits 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
user =
insert(:user,
enterprise_plan: build(:enterprise_plan, paddle_plan_id: "123321"),
subscription: nil
)
assert [:props, :revenue_goals, :funnels, :stats_api] == Quota.extra_features_limit(user)
assert Plausible.Billing.Feature.list() == Quota.allowed_features_for(user)
end
test "returns all extra features for enterprise customers" do
test "returns all features for enterprise customers" do
user =
insert(:user,
enterprise_plan: build(:enterprise_plan, paddle_plan_id: "123321"),
subscription: build(:subscription, paddle_plan_id: "123321")
)
assert [:props, :revenue_goals, :funnels, :stats_api] == Quota.extra_features_limit(user)
assert Plausible.Billing.Feature.list() == Quota.allowed_features_for(user)
end
test "returns all extra features for enterprise customers who are due to change a plan" do
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"),
@ -505,7 +507,7 @@ defmodule Plausible.Billing.QuotaTest do
)
insert(:enterprise_plan, user_id: user.id, paddle_plan_id: "new-paddle-plan-id")
assert [:props, :revenue_goals, :funnels, :stats_api] == Quota.extra_features_limit(user)
assert Plausible.Billing.Feature.list() == Quota.allowed_features_for(user)
end
end
end

View File

@ -6,10 +6,10 @@ defmodule PlausibleWeb.Components.BillingTest do
@v4_growth_plan_id "change-me-749342"
@v4_business_plan_id "change-me-b749342"
test "extra_feature_notice/1 renders a message when user is on trial" do
test "premium_feature_notice/1 renders a message when user is on trial" do
me = insert(:user)
assert render_component(&Billing.extra_feature_notice/1,
assert render_component(&Billing.premium_feature_notice/1,
billable_user: me,
current_user: me,
feature_mod: Plausible.Billing.Feature.Props
@ -17,11 +17,11 @@ defmodule PlausibleWeb.Components.BillingTest do
"Custom Properties is part of the Plausible Business plan. You can access it during your trial"
end
test "extra_feature_notice/1 renders an upgrade link when user is the site owner and does not have access to the feature" do
test "premium_feature_notice/1 renders an upgrade link when user is the site owner and does not have access to the feature" do
me = insert(:user, subscription: build(:subscription, paddle_plan_id: @v4_growth_plan_id))
rendered =
render_component(&Billing.extra_feature_notice/1,
render_component(&Billing.premium_feature_notice/1,
billable_user: me,
current_user: me,
feature_mod: Plausible.Billing.Feature.Props
@ -32,12 +32,12 @@ defmodule PlausibleWeb.Components.BillingTest do
assert rendered =~ "/billing/upgrade"
end
test "extra_feature_notice/1 does not render an upgrade link when user is not the site owner" do
test "premium_feature_notice/1 does not render an upgrade link when user is not the site owner" do
me = insert(:user)
owner = insert(:user, subscription: build(:subscription, paddle_plan_id: @v4_growth_plan_id))
rendered =
render_component(&Billing.extra_feature_notice/1,
render_component(&Billing.premium_feature_notice/1,
billable_user: owner,
current_user: me,
feature_mod: Plausible.Billing.Feature.Funnels
@ -49,11 +49,11 @@ defmodule PlausibleWeb.Components.BillingTest do
refute rendered =~ "/billing/upgrade"
end
test "extra_feature_notice/1 does not render a notice when the user has access to the feature" do
test "premium_feature_notice/1 does not render a notice when the user has access to the feature" do
me = insert(:user, subscription: build(:subscription, paddle_plan_id: @v4_business_plan_id))
rendered =
render_component(&Billing.extra_feature_notice/1,
render_component(&Billing.premium_feature_notice/1,
billable_user: me,
current_user: me,
feature_mod: Plausible.Billing.Feature.Funnels