mirror of
https://github.com/plausible/analytics.git
synced 2024-12-23 01:22:15 +03:00
Switch sites creation to read teams schemas (#4823)
* Expose site limit, usage, ensure_can_add_new_site via Adapter * Print to stdout if TEST_READ_TEAM_SCHEMAS is enabled * Add factory wrappers for remaining subscription types * Ensure consistent ordering when fetching latest subscription * Switch creating new site to read team schemas * Dedup code based on read team schemas switching * Switch to transitional factory where necessary * Update yet another test requiring transitional factory
This commit is contained in:
parent
9b6961ce9b
commit
0d6bec1bbe
@ -6,7 +6,6 @@ defmodule Plausible.Sites do
|
||||
import Ecto.Query
|
||||
|
||||
alias Plausible.Auth
|
||||
alias Plausible.Billing.Quota
|
||||
alias Plausible.Repo
|
||||
alias Plausible.Site
|
||||
alias Plausible.Site.SharedLink
|
||||
@ -84,7 +83,7 @@ defmodule Plausible.Sites do
|
||||
end
|
||||
|
||||
def create(user, params) do
|
||||
with :ok <- Quota.ensure_can_add_new_site(user) do
|
||||
with :ok <- Plausible.Teams.Adapter.Read.Billing.ensure_can_add_new_site(user) do
|
||||
Ecto.Multi.new()
|
||||
|> Ecto.Multi.put(:site_changeset, Site.new(params))
|
||||
|> Ecto.Multi.run(:create_team, fn _repo, _context ->
|
||||
|
@ -138,7 +138,7 @@ defmodule Plausible.Teams do
|
||||
|
||||
defp last_subscription_query() do
|
||||
from(subscription in Plausible.Billing.Subscription,
|
||||
order_by: [desc: subscription.inserted_at],
|
||||
order_by: [desc: subscription.inserted_at, desc: subscription.id],
|
||||
limit: 1
|
||||
)
|
||||
end
|
||||
|
38
lib/plausible/teams/adapter.ex
Normal file
38
lib/plausible/teams/adapter.ex
Normal file
@ -0,0 +1,38 @@
|
||||
defmodule Plausible.Teams.Adapter do
|
||||
@moduledoc """
|
||||
Commonly used teams-transition functions
|
||||
"""
|
||||
alias Plausible.Teams
|
||||
|
||||
defmacro __using__(_) do
|
||||
quote do
|
||||
alias Plausible.Teams
|
||||
import Teams.Adapter
|
||||
end
|
||||
end
|
||||
|
||||
def team_or_user(user) do
|
||||
switch(
|
||||
user,
|
||||
team_fn: &Function.identity/1,
|
||||
user_fn: &Function.identity/1
|
||||
)
|
||||
end
|
||||
|
||||
def switch(user, opts \\ []) do
|
||||
team_fn = Keyword.fetch!(opts, :team_fn)
|
||||
user_fn = Keyword.fetch!(opts, :user_fn)
|
||||
|
||||
if Teams.read_team_schemas?(user) do
|
||||
team =
|
||||
case Teams.get_by_owner(user) do
|
||||
{:ok, team} -> team
|
||||
{:error, _} -> nil
|
||||
end
|
||||
|
||||
team_fn.(team)
|
||||
else
|
||||
user_fn.(user)
|
||||
end
|
||||
end
|
||||
end
|
@ -2,19 +2,36 @@ defmodule Plausible.Teams.Adapter.Read.Billing do
|
||||
@moduledoc """
|
||||
Transition adapter for new schema reads
|
||||
"""
|
||||
alias Plausible.Teams
|
||||
use Plausible.Teams.Adapter
|
||||
|
||||
def check_needs_to_upgrade(user) do
|
||||
if Teams.read_team_schemas?(user) do
|
||||
team =
|
||||
case Teams.get_by_owner(user) do
|
||||
{:ok, team} -> team
|
||||
{:error, _} -> nil
|
||||
end
|
||||
switch(
|
||||
user,
|
||||
team_fn: &Teams.Billing.check_needs_to_upgrade/1,
|
||||
user_fn: &Plausible.Billing.check_needs_to_upgrade/1
|
||||
)
|
||||
end
|
||||
|
||||
Teams.Billing.check_needs_to_upgrade(team)
|
||||
else
|
||||
Plausible.Billing.check_needs_to_upgrade(user)
|
||||
end
|
||||
def site_limit(user) do
|
||||
switch(
|
||||
user,
|
||||
team_fn: &Teams.Billing.site_limit/1,
|
||||
user_fn: &Plausible.Billing.Quota.Limits.site_limit/1
|
||||
)
|
||||
end
|
||||
|
||||
def ensure_can_add_new_site(user) do
|
||||
switch(
|
||||
user,
|
||||
team_fn: &Teams.Billing.ensure_can_add_new_site/1,
|
||||
user_fn: &Plausible.Billing.Quota.ensure_can_add_new_site/1
|
||||
)
|
||||
end
|
||||
|
||||
def site_usage(user) do
|
||||
switch(user,
|
||||
team_fn: &Teams.Billing.site_usage/1,
|
||||
user_fn: &Plausible.Billing.Quota.Usage.site_usage/1
|
||||
)
|
||||
end
|
||||
end
|
||||
|
@ -3,59 +3,47 @@ defmodule Plausible.Teams.Adapter.Read.Ownership do
|
||||
Transition adapter for new schema reads
|
||||
"""
|
||||
use Plausible
|
||||
use Plausible.Teams.Adapter
|
||||
alias Plausible.Site
|
||||
alias Plausible.Auth
|
||||
alias Plausible.Teams
|
||||
alias Plausible.Site.Memberships.Invitations
|
||||
|
||||
def ensure_can_take_ownership(site, user) do
|
||||
if Teams.read_team_schemas?(user) do
|
||||
team =
|
||||
case Teams.get_by_owner(user) do
|
||||
{:ok, team} -> team
|
||||
{:error, _} -> nil
|
||||
end
|
||||
|
||||
Teams.Invitations.ensure_can_take_ownership(site, team)
|
||||
else
|
||||
Invitations.ensure_can_take_ownership(site, user)
|
||||
end
|
||||
switch(
|
||||
user,
|
||||
team_fn: &Teams.Invitations.ensure_can_take_ownership(site, &1),
|
||||
user_fn: &Invitations.ensure_can_take_ownership(site, &1)
|
||||
)
|
||||
end
|
||||
|
||||
def has_sites?(user) do
|
||||
if Teams.read_team_schemas?(user) do
|
||||
Teams.Users.has_sites?(user, include_pending?: true)
|
||||
else
|
||||
Site.Memberships.any_or_pending?(user)
|
||||
end
|
||||
switch(
|
||||
user,
|
||||
team_fn: fn _ -> Teams.Users.has_sites?(user, include_pending?: true) end,
|
||||
user_fn: &Site.Memberships.any_or_pending?/1
|
||||
)
|
||||
end
|
||||
|
||||
def owns_sites?(user, sites) do
|
||||
if Teams.read_team_schemas?(user) do
|
||||
Teams.Users.owns_sites?(user, include_pending?: true)
|
||||
else
|
||||
Enum.any?(sites.entries, fn site ->
|
||||
length(site.invitations) > 0 && List.first(site.invitations).role == :owner
|
||||
end) ||
|
||||
Auth.user_owns_sites?(user)
|
||||
end
|
||||
switch(
|
||||
user,
|
||||
team_fn: fn _ -> Teams.Users.owns_sites?(user, include_pending?: true) end,
|
||||
user_fn: fn user ->
|
||||
Enum.any?(sites.entries, fn site ->
|
||||
length(site.invitations) > 0 && List.first(site.invitations).role == :owner
|
||||
end) ||
|
||||
Auth.user_owns_sites?(user)
|
||||
end
|
||||
)
|
||||
end
|
||||
|
||||
on_ee do
|
||||
def check_feature_access(site, new_owner) do
|
||||
user_or_team =
|
||||
if Teams.read_team_schemas?(new_owner) do
|
||||
case Teams.get_by_owner(new_owner) do
|
||||
{:ok, team} -> team
|
||||
{:error, _} -> nil
|
||||
end
|
||||
else
|
||||
new_owner
|
||||
end
|
||||
team_or_user = team_or_user(new_owner)
|
||||
|
||||
missing_features =
|
||||
Plausible.Billing.Quota.Usage.features_usage(nil, [site.id])
|
||||
|> Enum.filter(&(&1.check_availability(user_or_team) != :ok))
|
||||
|> Enum.filter(&(&1.check_availability(team_or_user) != :ok))
|
||||
|
||||
if missing_features == [] do
|
||||
:ok
|
||||
|
@ -5,30 +5,29 @@ defmodule Plausible.Teams.Adapter.Read.Sites do
|
||||
|
||||
import Ecto.Query
|
||||
|
||||
alias Plausible.Auth
|
||||
alias Plausible.Repo
|
||||
alias Plausible.Site
|
||||
alias Plausible.Teams
|
||||
use Plausible.Teams.Adapter
|
||||
|
||||
def list(user, pagination_params, opts \\ []) do
|
||||
if Plausible.Teams.read_team_schemas?(user) do
|
||||
Plausible.Teams.Sites.list(user, pagination_params, opts)
|
||||
else
|
||||
old_list(user, pagination_params, opts)
|
||||
end
|
||||
switch(
|
||||
user,
|
||||
team_fn: fn _ -> Plausible.Teams.Sites.list(user, pagination_params, opts) end,
|
||||
user_fn: fn _ -> old_list(user, pagination_params, opts) end
|
||||
)
|
||||
end
|
||||
|
||||
def list_with_invitations(user, pagination_params, opts \\ []) do
|
||||
if Plausible.Teams.read_team_schemas?(user) do
|
||||
Plausible.Teams.Sites.list_with_invitations(user, pagination_params, opts)
|
||||
else
|
||||
old_list_with_invitations(user, pagination_params, opts)
|
||||
end
|
||||
switch(
|
||||
user,
|
||||
team_fn: fn _ ->
|
||||
Plausible.Teams.Sites.list_with_invitations(user, pagination_params, opts)
|
||||
end,
|
||||
user_fn: fn _ -> old_list_with_invitations(user, pagination_params, opts) end
|
||||
)
|
||||
end
|
||||
|
||||
@type list_opt() :: {:filter_by_domain, String.t()}
|
||||
@spec old_list(Auth.User.t(), map(), [list_opt()]) :: Scrivener.Page.t()
|
||||
def old_list(user, pagination_params, opts \\ []) do
|
||||
defp old_list(user, pagination_params, opts) do
|
||||
domain_filter = Keyword.get(opts, :filter_by_domain)
|
||||
|
||||
from(s in Site,
|
||||
@ -60,8 +59,7 @@ defmodule Plausible.Teams.Adapter.Read.Sites do
|
||||
|> Repo.paginate(pagination_params)
|
||||
end
|
||||
|
||||
@spec old_list_with_invitations(Auth.User.t(), map(), [list_opt()]) :: Scrivener.Page.t()
|
||||
def old_list_with_invitations(user, pagination_params, opts \\ []) do
|
||||
defp old_list_with_invitations(user, pagination_params, opts) do
|
||||
domain_filter = Keyword.get(opts, :filter_by_domain)
|
||||
|
||||
result =
|
||||
|
@ -42,6 +42,10 @@ defmodule Plausible.Teams.Billing do
|
||||
end
|
||||
end
|
||||
|
||||
def ensure_can_add_new_site(nil) do
|
||||
:ok
|
||||
end
|
||||
|
||||
def ensure_can_add_new_site(team) do
|
||||
team = Teams.with_subscription(team)
|
||||
|
||||
@ -61,6 +65,10 @@ defmodule Plausible.Teams.Billing do
|
||||
end
|
||||
end
|
||||
|
||||
def site_limit(nil) do
|
||||
@site_limit_for_trials
|
||||
end
|
||||
|
||||
def site_limit(team) do
|
||||
if Timex.before?(team.inserted_at, @limit_sites_since) do
|
||||
:unlimited
|
||||
@ -69,6 +77,8 @@ defmodule Plausible.Teams.Billing do
|
||||
end
|
||||
end
|
||||
|
||||
def site_usage(nil), do: 0
|
||||
|
||||
def site_usage(team) do
|
||||
team
|
||||
|> Teams.owned_sites()
|
||||
@ -76,7 +86,8 @@ defmodule Plausible.Teams.Billing do
|
||||
end
|
||||
|
||||
defp get_site_limit_from_plan(team) do
|
||||
team = Teams.with_subscription(team)
|
||||
team =
|
||||
Teams.with_subscription(team)
|
||||
|
||||
case Plans.get_subscription_plan(team.subscription) do
|
||||
%{site_limit: site_limit} -> site_limit
|
||||
|
@ -4,7 +4,6 @@ defmodule PlausibleWeb.SiteController do
|
||||
use Plausible
|
||||
|
||||
alias Plausible.Sites
|
||||
alias Plausible.Billing.Quota
|
||||
|
||||
plug(PlausibleWeb.RequireAccountPlug)
|
||||
|
||||
@ -19,8 +18,9 @@ defmodule PlausibleWeb.SiteController do
|
||||
|
||||
render(conn, "new.html",
|
||||
changeset: Plausible.Site.changeset(%Plausible.Site{}),
|
||||
site_limit: Quota.Limits.site_limit(current_user),
|
||||
site_limit_exceeded?: Quota.ensure_can_add_new_site(current_user) != :ok,
|
||||
site_limit: Plausible.Teams.Adapter.Read.Billing.site_limit(current_user),
|
||||
site_limit_exceeded?:
|
||||
Plausible.Teams.Adapter.Read.Billing.ensure_can_add_new_site(current_user) != :ok,
|
||||
form_submit_url: "/sites?flow=#{flow}",
|
||||
flow: flow
|
||||
)
|
||||
@ -28,7 +28,7 @@ defmodule PlausibleWeb.SiteController do
|
||||
|
||||
def create_site(conn, %{"site" => site_params}) do
|
||||
user = conn.assigns[:current_user]
|
||||
first_site? = Quota.Usage.site_usage(user) == 0
|
||||
first_site? = Plausible.Teams.Adapter.Read.Billing.site_usage(user) == 0
|
||||
flow = conn.params["flow"]
|
||||
|
||||
case Sites.create(user, site_params) do
|
||||
@ -60,7 +60,7 @@ defmodule PlausibleWeb.SiteController do
|
||||
render(conn, "new.html",
|
||||
changeset: changeset,
|
||||
first_site?: first_site?,
|
||||
site_limit: Quota.Limits.site_limit(user),
|
||||
site_limit: Plausible.Teams.Adapter.Read.Billing.site_limit(user),
|
||||
site_limit_exceeded?: false,
|
||||
flow: flow,
|
||||
form_submit_url: "/sites?flow=#{flow}"
|
||||
|
@ -4,6 +4,8 @@ defmodule Plausible.Billing.QuotaTest do
|
||||
alias Plausible.Billing.{Quota, Plans}
|
||||
alias Plausible.Billing.Feature.{Goals, Props, StatsAPI}
|
||||
|
||||
use Plausible.Teams.Test
|
||||
|
||||
on_ee do
|
||||
alias Plausible.Billing.Feature.Funnels
|
||||
alias Plausible.Billing.Feature.RevenueGoals
|
||||
@ -22,56 +24,44 @@ defmodule Plausible.Billing.QuotaTest do
|
||||
@describetag :ee_only
|
||||
|
||||
test "returns 50 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))
|
||||
user_on_v1 = new_user() |> subscribe_to_plan(@v1_plan_id)
|
||||
user_on_v2 = new_user() |> subscribe_to_plan(@v2_plan_id)
|
||||
user_on_v3 = new_user() |> subscribe_to_plan(@v3_plan_id)
|
||||
|
||||
assert 50 == Quota.Limits.site_limit(user_on_v1)
|
||||
assert 50 == Quota.Limits.site_limit(user_on_v2)
|
||||
assert 50 == Quota.Limits.site_limit(user_on_v3)
|
||||
assert 50 == Plausible.Teams.Adapter.Read.Billing.site_limit(user_on_v1)
|
||||
assert 50 == Plausible.Teams.Adapter.Read.Billing.site_limit(user_on_v2)
|
||||
assert 50 == Plausible.Teams.Adapter.Read.Billing.site_limit(user_on_v3)
|
||||
end
|
||||
|
||||
test "returns 50 when user is on free_10k plan" do
|
||||
user = insert(:user, subscription: build(:subscription, paddle_plan_id: "free_10k"))
|
||||
assert 50 == Quota.Limits.site_limit(user)
|
||||
user = new_user() |> subscribe_to_plan("free_10k")
|
||||
assert 50 == Plausible.Teams.Adapter.Read.Billing.site_limit(user)
|
||||
end
|
||||
|
||||
test "returns the configured site limit for enterprise plan" do
|
||||
user = insert(:user)
|
||||
|
||||
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)
|
||||
|
||||
assert enterprise_plan.site_limit == Quota.Limits.site_limit(user)
|
||||
user = new_user() |> subscribe_to_enterprise_plan(site_limit: 500)
|
||||
assert Plausible.Teams.Adapter.Read.Billing.site_limit(user) == 500
|
||||
end
|
||||
|
||||
test "returns 10 when user in on trial" do
|
||||
user =
|
||||
insert(:user,
|
||||
trial_expiry_date: Timex.shift(Timex.now(), days: 7)
|
||||
)
|
||||
|
||||
assert 10 == Quota.Limits.site_limit(user)
|
||||
user = new_user(trial_expiry_date: Date.shift(Date.utc_today(), day: 7))
|
||||
assert Plausible.Teams.Adapter.Read.Billing.site_limit(user) == 10
|
||||
end
|
||||
|
||||
test "returns the subscription limit for enterprise users who have not paid yet" do
|
||||
user =
|
||||
insert(:user,
|
||||
enterprise_plan: build(:enterprise_plan, paddle_plan_id: "123321"),
|
||||
subscription: build(:subscription, paddle_plan_id: @v1_plan_id)
|
||||
)
|
||||
new_user()
|
||||
|> subscribe_to_plan(@v1_plan_id)
|
||||
|> subscribe_to_enterprise_plan(paddle_plan_id: "123321", subscription?: false)
|
||||
|
||||
assert 50 == Quota.Limits.site_limit(user)
|
||||
assert Plausible.Teams.Adapter.Read.Billing.site_limit(user) == 50
|
||||
end
|
||||
|
||||
test "returns 10 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
|
||||
)
|
||||
new_user() |> subscribe_to_enterprise_plan(paddle_plan_id: "123321", subscription?: false)
|
||||
|
||||
assert 10 == Quota.Limits.site_limit(user)
|
||||
assert Plausible.Teams.Adapter.Read.Billing.site_limit(user) == 10
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -2,6 +2,7 @@ defmodule PlausibleWeb.Api.ExternalSitesControllerTest do
|
||||
use Plausible
|
||||
use PlausibleWeb.ConnCase, async: false
|
||||
use Plausible.Repo
|
||||
use Plausible.Teams.Test
|
||||
|
||||
on_ee do
|
||||
setup :create_user
|
||||
@ -77,7 +78,7 @@ defmodule PlausibleWeb.Api.ExternalSitesControllerTest do
|
||||
end
|
||||
|
||||
test "does not allow creating more sites than the limit", %{conn: conn, user: user} do
|
||||
insert_list(50, :site, members: [user])
|
||||
for _ <- 1..10, do: new_site(owner: user)
|
||||
|
||||
conn =
|
||||
post(conn, "/api/v1/sites", %{
|
||||
|
@ -284,7 +284,7 @@ defmodule PlausibleWeb.SiteControllerTest do
|
||||
conn: conn,
|
||||
user: user
|
||||
} do
|
||||
insert(:site, members: [user])
|
||||
new_site(owner: user)
|
||||
|
||||
post(conn, "/sites", %{
|
||||
"site" => %{
|
||||
@ -301,7 +301,7 @@ defmodule PlausibleWeb.SiteControllerTest do
|
||||
conn: conn,
|
||||
user: user
|
||||
} do
|
||||
insert_list(10, :site, members: [user])
|
||||
for _ <- 1..10, do: new_site(owner: user)
|
||||
|
||||
conn =
|
||||
post(conn, "/sites", %{
|
||||
|
@ -139,6 +139,32 @@ defmodule Plausible.Teams.Test do
|
||||
{:ok, team} = Teams.get_or_create(user)
|
||||
|
||||
insert(:growth_subscription, user: user, team: team)
|
||||
user
|
||||
end
|
||||
|
||||
def subscribe_to_plan(user, paddle_plan_id) do
|
||||
{:ok, team} = Teams.get_or_create(user)
|
||||
|
||||
insert(:subscription, user: user, team: team, paddle_plan_id: paddle_plan_id)
|
||||
user
|
||||
end
|
||||
|
||||
def subscribe_to_enterprise_plan(user, attrs) do
|
||||
{:ok, team} = Teams.get_or_create(user)
|
||||
|
||||
{subscription?, attrs} = Keyword.pop(attrs, :subscription?, true)
|
||||
|
||||
enterprise_plan = insert(:enterprise_plan, Keyword.merge([user: user, team: team], attrs))
|
||||
|
||||
if subscription? do
|
||||
insert(:subscription,
|
||||
team: team,
|
||||
user: user,
|
||||
paddle_plan_id: enterprise_plan.paddle_plan_id
|
||||
)
|
||||
end
|
||||
|
||||
user
|
||||
end
|
||||
|
||||
def assert_team_exists(user, team_id \\ nil) do
|
||||
|
@ -9,6 +9,7 @@ Application.ensure_all_started(:double)
|
||||
FunWithFlags.enable(:channels)
|
||||
# Temporary flag to test `read_team_schemas` flag on all tests.
|
||||
if System.get_env("TEST_READ_TEAM_SCHEMAS") == "1" do
|
||||
IO.puts("READS TEAM SCHEMAS")
|
||||
FunWithFlags.enable(:read_team_schemas)
|
||||
else
|
||||
FunWithFlags.disable(:read_team_schemas)
|
||||
|
Loading…
Reference in New Issue
Block a user