mirror of
https://github.com/outbackdingo/firezone.git
synced 2026-01-27 10:18:54 +00:00
chore(portal): Add users limit and use it as default limit for accounts (#4527)
A manual migration will be needed (run `Domain.Ops.sync_pricing_plans()`) to sync the limits for all the accounts.
This commit is contained in:
@@ -3,6 +3,7 @@ defmodule Domain.Accounts.Limits do
|
||||
|
||||
@primary_key false
|
||||
embedded_schema do
|
||||
field :users_count, :integer
|
||||
field :monthly_active_users_count, :integer
|
||||
field :service_accounts_count, :integer
|
||||
field :gateway_groups_count, :integer
|
||||
|
||||
@@ -2,11 +2,16 @@ defmodule Domain.Accounts.Limits.Changeset do
|
||||
use Domain, :changeset
|
||||
alias Domain.Accounts.Limits
|
||||
|
||||
@fields ~w[monthly_active_users_count service_accounts_count gateway_groups_count account_admin_users_count]a
|
||||
@fields ~w[users_count
|
||||
monthly_active_users_count
|
||||
service_accounts_count
|
||||
gateway_groups_count
|
||||
account_admin_users_count]a
|
||||
|
||||
def changeset(limits \\ %Limits{}, attrs) do
|
||||
limits
|
||||
|> cast(attrs, @fields)
|
||||
|> validate_number(:users_count, greater_than_or_equal_to: 0)
|
||||
|> validate_number(:monthly_active_users_count, greater_than_or_equal_to: 0)
|
||||
|> validate_number(:service_accounts_count, greater_than_or_equal_to: 0)
|
||||
|> validate_number(:gateway_groups_count, greater_than_or_equal_to: 0)
|
||||
|
||||
@@ -312,6 +312,13 @@ defmodule Domain.Actors do
|
||||
|
||||
# Actors
|
||||
|
||||
def count_users_for_account(%Accounts.Account{} = account) do
|
||||
Actor.Query.not_disabled()
|
||||
|> Actor.Query.by_account_id(account.id)
|
||||
|> Actor.Query.by_type({:in, [:account_admin_user, :account_user]})
|
||||
|> Repo.aggregate(:count)
|
||||
end
|
||||
|
||||
def count_account_admin_users_for_account(%Accounts.Account{} = account) do
|
||||
Actor.Query.not_disabled()
|
||||
|> Actor.Query.by_account_id(account.id)
|
||||
|
||||
@@ -30,6 +30,10 @@ defmodule Domain.Actors.Actor.Query do
|
||||
where(queryable, [actors: actors], actors.account_id == ^account_id)
|
||||
end
|
||||
|
||||
def by_type(queryable, {:in, types}) do
|
||||
where(queryable, [actors: actors], actors.type in ^types)
|
||||
end
|
||||
|
||||
def by_type(queryable, type) do
|
||||
where(queryable, [actors: actors], actors.type == ^type)
|
||||
end
|
||||
|
||||
@@ -36,17 +36,33 @@ defmodule Domain.Billing do
|
||||
|
||||
# Limits and Features
|
||||
|
||||
def users_limit_exceeded?(%Accounts.Account{} = account, users_count) do
|
||||
not is_nil(account.limits.users_count) and
|
||||
users_count > account.limits.users_count
|
||||
end
|
||||
|
||||
def seats_limit_exceeded?(%Accounts.Account{} = account, active_users_count) do
|
||||
not is_nil(account.limits.monthly_active_users_count) and
|
||||
active_users_count > account.limits.monthly_active_users_count
|
||||
end
|
||||
|
||||
def can_create_users?(%Accounts.Account{} = account) do
|
||||
users_count = Actors.count_users_for_account(account)
|
||||
active_users_count = Clients.count_1m_active_users_for_account(account)
|
||||
|
||||
Accounts.account_active?(account) and
|
||||
(is_nil(account.limits.monthly_active_users_count) or
|
||||
active_users_count < account.limits.monthly_active_users_count)
|
||||
cond do
|
||||
not Accounts.account_active?(account) ->
|
||||
false
|
||||
|
||||
not is_nil(account.limits.monthly_active_users_count) ->
|
||||
active_users_count < account.limits.monthly_active_users_count
|
||||
|
||||
not is_nil(account.limits.users_count) ->
|
||||
users_count < account.limits.users_count
|
||||
|
||||
true ->
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
def service_accounts_limit_exceeded?(%Accounts.Account{} = account, service_accounts_count) do
|
||||
|
||||
@@ -354,12 +354,9 @@ defmodule Domain.Billing.EventHandler do
|
||||
end)
|
||||
|> Enum.into(%{})
|
||||
|
||||
{monthly_active_users_count, features_and_limits} =
|
||||
Map.pop(features_and_limits, "monthly_active_users_count", quantity)
|
||||
|
||||
{users_count, features_and_limits} = Map.pop(features_and_limits, "users_count", quantity)
|
||||
{limits, features} = Map.split(features_and_limits, limit_fields)
|
||||
|
||||
limits = Map.merge(limits, %{"monthly_active_users_count" => monthly_active_users_count})
|
||||
limits = Map.merge(limits, %{"users_count" => users_count})
|
||||
|
||||
%{
|
||||
features: features,
|
||||
|
||||
@@ -7,6 +7,7 @@ defmodule Domain.Billing.Jobs do
|
||||
|> Enum.each(fn account ->
|
||||
if Billing.enabled?() and Billing.account_provisioned?(account) do
|
||||
[]
|
||||
|> check_users_limit(account)
|
||||
|> check_seats_limit(account)
|
||||
|> check_service_accounts_limit(account)
|
||||
|> check_gateway_groups_limit(account)
|
||||
@@ -41,6 +42,16 @@ defmodule Domain.Billing.Jobs do
|
||||
end)
|
||||
end
|
||||
|
||||
defp check_users_limit(limits_exceeded, account) do
|
||||
users_count = Actors.count_users_for_account(account)
|
||||
|
||||
if Billing.users_limit_exceeded?(account, users_count) do
|
||||
limits_exceeded ++ ["users"]
|
||||
else
|
||||
limits_exceeded
|
||||
end
|
||||
end
|
||||
|
||||
defp check_seats_limit(limits_exceeded, account) do
|
||||
active_users_count = Clients.count_1m_active_users_for_account(account)
|
||||
|
||||
|
||||
@@ -45,6 +45,7 @@ account =
|
||||
}
|
||||
},
|
||||
limits: %{
|
||||
users_count: 15,
|
||||
monthly_active_users_count: 10,
|
||||
service_accounts_count: 10,
|
||||
gateway_groups_count: 3,
|
||||
|
||||
@@ -1947,6 +1947,41 @@ defmodule Domain.ActorsTest do
|
||||
end
|
||||
end
|
||||
|
||||
describe "count_users_for_account/1" do
|
||||
test "returns 0 when actors are in another account", %{} do
|
||||
account = Fixtures.Accounts.create_account()
|
||||
Fixtures.Actors.create_actor(type: :account_admin_user)
|
||||
|
||||
assert count_users_for_account(account) == 0
|
||||
end
|
||||
|
||||
test "returns count of account users" do
|
||||
account = Fixtures.Accounts.create_account()
|
||||
Fixtures.Actors.create_actor(type: :account_admin_user, account: account)
|
||||
Fixtures.Actors.create_actor(type: :account_user, account: account)
|
||||
|
||||
assert count_users_for_account(account) == 2
|
||||
end
|
||||
|
||||
test "does not count disabled" do
|
||||
account = Fixtures.Accounts.create_account()
|
||||
|
||||
Fixtures.Actors.create_actor(type: :account_user, account: account)
|
||||
|> Fixtures.Actors.disable()
|
||||
|
||||
assert count_users_for_account(account) == 0
|
||||
end
|
||||
|
||||
test "does not count deleted" do
|
||||
account = Fixtures.Accounts.create_account()
|
||||
|
||||
Fixtures.Actors.create_actor(type: :account_admin_user, account: account)
|
||||
|> Fixtures.Actors.delete()
|
||||
|
||||
assert count_users_for_account(account) == 0
|
||||
end
|
||||
end
|
||||
|
||||
describe "count_account_admin_users_for_account/1" do
|
||||
test "returns 0 when actors are in another account", %{} do
|
||||
account = Fixtures.Accounts.create_account()
|
||||
|
||||
@@ -47,6 +47,7 @@ defmodule Domain.Billing.JobsTest do
|
||||
|
||||
Domain.Accounts.update_account(account, %{
|
||||
limits: %{
|
||||
users_count: 1,
|
||||
monthly_active_users_count: 1,
|
||||
service_accounts_count: 1,
|
||||
gateway_groups_count: 1,
|
||||
@@ -59,6 +60,7 @@ defmodule Domain.Billing.JobsTest do
|
||||
account = Repo.get!(Domain.Accounts.Account, account.id)
|
||||
|
||||
assert account.warning =~ "You have exceeded the following limits:"
|
||||
assert account.warning =~ "users"
|
||||
assert account.warning =~ "monthly active users"
|
||||
assert account.warning =~ "service accounts"
|
||||
assert account.warning =~ "sites"
|
||||
|
||||
@@ -44,6 +44,30 @@ defmodule Domain.BillingTest do
|
||||
end
|
||||
end
|
||||
|
||||
describe "users_limit_exceeded?/2" do
|
||||
test "returns false when seats limit is not exceeded", %{account: account} do
|
||||
{:ok, account} =
|
||||
Domain.Accounts.update_account(account, %{
|
||||
limits: %{monthly_active_users_count: 10}
|
||||
})
|
||||
|
||||
assert users_limit_exceeded?(account, 10) == false
|
||||
end
|
||||
|
||||
test "returns true when seats limit is exceeded", %{account: account} do
|
||||
{:ok, account} =
|
||||
Domain.Accounts.update_account(account, %{
|
||||
limits: %{users_count: 10}
|
||||
})
|
||||
|
||||
assert users_limit_exceeded?(account, 11) == true
|
||||
end
|
||||
|
||||
test "returns false when seats limit is not set", %{account: account} do
|
||||
assert users_limit_exceeded?(account, 0) == false
|
||||
end
|
||||
end
|
||||
|
||||
describe "seats_limit_exceeded?/2" do
|
||||
test "returns false when seats limit is not exceeded", %{account: account} do
|
||||
{:ok, account} =
|
||||
@@ -63,7 +87,7 @@ defmodule Domain.BillingTest do
|
||||
assert seats_limit_exceeded?(account, 11) == true
|
||||
end
|
||||
|
||||
test "returns true when seats limit is not set", %{account: account} do
|
||||
test "returns false when seats limit is not set", %{account: account} do
|
||||
assert seats_limit_exceeded?(account, 0) == false
|
||||
end
|
||||
end
|
||||
@@ -101,6 +125,18 @@ defmodule Domain.BillingTest do
|
||||
assert can_create_users?(account) == false
|
||||
end
|
||||
|
||||
test "returns false when users limit is exceeded", %{account: account} do
|
||||
{:ok, account} =
|
||||
Domain.Accounts.update_account(account, %{
|
||||
limits: %{users_count: 1}
|
||||
})
|
||||
|
||||
Fixtures.Actors.create_actor(type: :account_admin_user, account: account)
|
||||
Fixtures.Actors.create_actor(type: :account_user, account: account)
|
||||
|
||||
assert can_create_users?(account) == false
|
||||
end
|
||||
|
||||
test "returns false when account is disabled", %{account: account} do
|
||||
{:ok, account} =
|
||||
Domain.Accounts.update_account(account, %{
|
||||
@@ -111,7 +147,7 @@ defmodule Domain.BillingTest do
|
||||
assert can_create_users?(account) == false
|
||||
end
|
||||
|
||||
test "returns true when seats limit is not set", %{account: account} do
|
||||
test "returns false when seats limit is not set", %{account: account} do
|
||||
assert can_create_users?(account) == true
|
||||
end
|
||||
end
|
||||
@@ -741,7 +777,8 @@ defmodule Domain.BillingTest do
|
||||
"self_hosted_relays" => "true",
|
||||
"monthly_active_users_count" => "15",
|
||||
"service_accounts_count" => "unlimited",
|
||||
"gateway_groups_count" => 1
|
||||
"gateway_groups_count" => 1,
|
||||
"users_count" => 14
|
||||
}
|
||||
})
|
||||
|
||||
@@ -771,7 +808,8 @@ defmodule Domain.BillingTest do
|
||||
assert account.limits == %Domain.Accounts.Limits{
|
||||
monthly_active_users_count: 15,
|
||||
gateway_groups_count: 5,
|
||||
service_accounts_count: nil
|
||||
service_accounts_count: nil,
|
||||
users_count: 14
|
||||
}
|
||||
|
||||
assert account.features == %Domain.Accounts.Features{
|
||||
|
||||
@@ -7,6 +7,7 @@ defmodule Web.Settings.Billing do
|
||||
if Billing.account_provisioned?(socket.assigns.account) do
|
||||
admins_count = Actors.count_account_admin_users_for_account(socket.assigns.account)
|
||||
service_accounts_count = Actors.count_service_accounts_for_account(socket.assigns.account)
|
||||
users_count = Actors.count_users_for_account(socket.assigns.account)
|
||||
active_users_count = Clients.count_1m_active_users_for_account(socket.assigns.account)
|
||||
gateway_groups_count = Gateways.count_groups_for_account(socket.assigns.account)
|
||||
|
||||
@@ -15,6 +16,7 @@ defmodule Web.Settings.Billing do
|
||||
page_title: "Billing",
|
||||
error: nil,
|
||||
admins_count: admins_count,
|
||||
users_count: users_count,
|
||||
active_users_count: active_users_count,
|
||||
service_accounts_count: service_accounts_count,
|
||||
gateway_groups_count: gateway_groups_count
|
||||
@@ -83,6 +85,21 @@ defmodule Web.Settings.Billing do
|
||||
</:value>
|
||||
</.vertical_table_row>
|
||||
|
||||
<.vertical_table_row :if={not is_nil(@account.limits.users_count)}>
|
||||
<:label>
|
||||
<p>Users</p>
|
||||
</:label>
|
||||
<:value>
|
||||
<span class={[
|
||||
not is_nil(@users_count) and
|
||||
@users_count > @account.limits.users_count && "text-red-500"
|
||||
]}>
|
||||
<%= @users_count %> used
|
||||
</span>
|
||||
/ <%= @account.limits.users_count %> allowed
|
||||
</:value>
|
||||
</.vertical_table_row>
|
||||
|
||||
<.vertical_table_row :if={not is_nil(@account.limits.monthly_active_users_count)}>
|
||||
<:label>
|
||||
<p>Seats</p>
|
||||
@@ -111,7 +128,6 @@ defmodule Web.Settings.Billing do
|
||||
<%= @service_accounts_count %> used
|
||||
</span>
|
||||
/ <%= @account.limits.service_accounts_count %> allowed
|
||||
<p class="text-xs">users with at least one device signed-in within last month</p>
|
||||
</:value>
|
||||
</.vertical_table_row>
|
||||
|
||||
|
||||
@@ -18,7 +18,8 @@ defmodule Web.Live.Settings.BillingTest do
|
||||
monthly_active_users_count: 100,
|
||||
service_accounts_count: 100,
|
||||
gateway_groups_count: 10,
|
||||
account_admin_users_count: 2
|
||||
account_admin_users_count: 2,
|
||||
users_count: 200
|
||||
}
|
||||
)
|
||||
|
||||
@@ -75,6 +76,7 @@ defmodule Web.Live.Settings.BillingTest do
|
||||
|
||||
assert rows["billing email"] =~ account.metadata.stripe.billing_email
|
||||
assert rows["current plan"] =~ account.metadata.stripe.product_name
|
||||
assert rows["users"] =~ "1 used / 200 allowed"
|
||||
assert rows["seats"] =~ "0 used / 100 allowed"
|
||||
assert rows["sites"] =~ "0 used / 10 allowed"
|
||||
assert rows["admins"] =~ "1 used / 2 allowed"
|
||||
|
||||
Reference in New Issue
Block a user