Remove team adapters (#4877)

* wip

* wip

* 🍌

* WIP

* Draw the rest of the owl (well, almost)

* Remove obsolete unlimited trial logic

* Remove `allow_next_upgrade_override?` adapter

* Remove `Teams` adapter

* Remove /sites adapters

* Remove `Sites` adapter

* Remove `change_plan` adapter

* Fix up CE test

* Remove adapter for Billing.latest_enterprise_plan_with_price(s)

* Remove adapter for `Billing.has_active_subscription?`

* Remove adapter for `Billing.active_subscription_for`

* Remove remaining billing adapter

* Remove all_pending_transfers

* Remove `get_owner` adapter

* Remove `has_sites?` and `owns_sites?` adapters

* Remove `Ownership` adapter

* Remove `check_invitation_permissions` adapter

* Remove `check_team_member_limit` adapter

* Remove `ensure_transfer_valid` adapter

* Remove Invitations adapter

* Remove sole teams adapter

* Make dialyzer happy

* Consolidate `Billing.features_usage` definition

* Remove unused `Quota.Usage`

* Remove remains of `read_team_schema` FF and reduce number of CI passes

* Bang up the condition

* Include pending invitations when querying `has_sites?`

* Fix and improve conditional expression in `plan_box` component

* Update comments

---------

Co-authored-by: Adam Rutkowski <hq@mtod.org>
This commit is contained in:
Adrian Gruntkowski 2024-12-05 10:02:09 +01:00 committed by GitHub
parent 90e541be38
commit 3afec60d98
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
74 changed files with 1211 additions and 2430 deletions

View File

@ -16,22 +16,19 @@ env:
jobs:
build:
name: "Build and test (${{ matrix.mix_env }}, ${{ matrix.postgres_image }}${{ matrix.test_read_team_schemas == '1' && ', read_team_schemas' || '' }})"
name: "Build and test (${{ matrix.mix_env }}, ${{ matrix.postgres_image }})"
runs-on: ubuntu-latest
strategy:
matrix:
mix_env: ["test", "ce_test"]
postgres_image: ["postgres:16"]
test_read_team_schemas: ["0"]
mix_env: ["test"]
postgres_image: ["postgres:15"]
include:
- mix_env: "test"
postgres_image: "postgres:15"
test_read_team_schemas: "1"
- mix_env: "ce_test"
postgres_image: "postgres:16"
env:
MIX_ENV: ${{ matrix.mix_env }}
TEST_READ_TEAM_SCHEMAS: ${{ matrix.test_read_team_schemas }}
services:
postgres:
image: ${{ matrix.postgres_image }}

View File

@ -65,7 +65,7 @@ defmodule PlausibleWeb.Api.ExternalSitesController do
{:ok, %{site: site}} ->
json(conn, site)
{:error, {:over_limit, limit}} ->
{:error, _, {:over_limit, limit}, _} ->
conn
|> put_status(402)
|> json(%{
@ -206,7 +206,7 @@ defmodule PlausibleWeb.Api.ExternalSitesController do
end
defp get_site(user, site_id, roles) do
case Plausible.Teams.Adapter.Read.Sites.get_for_user(user, site_id, roles) do
case Plausible.Sites.get_for_user(user, site_id, roles) do
nil -> {:error, :site_not_found}
site -> {:ok, site}
end

View File

@ -16,7 +16,7 @@ defmodule PlausibleWeb.Live.FunnelSettings do
socket =
socket
|> assign_new(:site, fn %{current_user: current_user} ->
Plausible.Teams.Adapter.Read.Sites.get_for_user!(current_user, domain, [
Plausible.Sites.get_for_user!(current_user, domain, [
:owner,
:admin,
:super_admin
@ -106,7 +106,7 @@ defmodule PlausibleWeb.Live.FunnelSettings do
def handle_event("delete-funnel", %{"funnel-id" => id}, socket) do
site =
Plausible.Teams.Adapter.Read.Sites.get_for_user!(
Plausible.Sites.get_for_user!(
socket.assigns.current_user,
socket.assigns.domain,
[:owner, :admin]

View File

@ -13,7 +13,7 @@ defmodule PlausibleWeb.Live.FunnelSettings.Form do
def mount(_params, %{"domain" => domain} = session, socket) do
site =
Plausible.Teams.Adapter.Read.Sites.get_for_user!(socket.assigns.current_user, domain, [
Plausible.Sites.get_for_user!(socket.assigns.current_user, domain, [
:owner,
:admin,
:super_admin

View File

@ -117,17 +117,6 @@ defmodule Plausible.Auth do
end)
end
def user_owns_sites?(user) do
Repo.exists?(
from(s in Plausible.Site,
join: sm in Plausible.Site.Membership,
on: sm.site_id == s.id,
where: sm.user_id == ^user.id,
where: sm.role == :owner
)
)
end
on_ee do
def is_super_admin?(nil), do: false
def is_super_admin?(%Plausible.Auth.User{id: id}), do: is_super_admin?(id)
@ -139,21 +128,19 @@ defmodule Plausible.Auth do
def is_super_admin?(_), do: false
end
def enterprise_configured?(nil), do: false
def enterprise_configured?(%Plausible.Auth.User{} = user) do
user
|> Ecto.assoc(:enterprise_plan)
|> Repo.exists?()
end
@spec create_api_key(Auth.User.t(), String.t(), String.t()) ::
{:ok, Auth.ApiKey.t()} | {:error, Ecto.Changeset.t() | :upgrade_required}
def create_api_key(user, name, key) do
team =
case Plausible.Teams.get_by_owner(user) do
{:ok, team} -> team
_ -> nil
end
params = %{name: name, user_id: user.id, key: key}
changeset = Auth.ApiKey.changeset(%Auth.ApiKey{}, params)
with :ok <- Plausible.Billing.Feature.StatsAPI.check_availability(user),
with :ok <- Plausible.Billing.Feature.StatsAPI.check_availability(team),
do: Repo.insert(changeset)
end

View File

@ -2,20 +2,9 @@ defmodule Plausible.Billing do
use Plausible
use Plausible.Repo
require Plausible.Billing.Subscription.Status
alias Plausible.Billing.Subscriptions
alias Plausible.Billing.{Subscription, Plans, Quota}
alias Plausible.Billing.Subscription
alias Plausible.Auth.User
@spec active_subscription_for(User.t()) :: Subscription.t() | nil
def active_subscription_for(user) do
user |> active_subscription_query() |> Repo.one()
end
@spec has_active_subscription?(User.t()) :: boolean()
def has_active_subscription?(user) do
user |> active_subscription_query() |> Repo.exists?()
end
def subscription_created(params) do
Repo.transaction(fn ->
handle_subscription_created(params)
@ -40,44 +29,6 @@ defmodule Plausible.Billing do
end)
end
def change_plan(user, new_plan_id) do
subscription = active_subscription_for(user)
plan = Plans.find(new_plan_id)
limit_checking_opts =
if user.allow_next_upgrade_override do
[ignore_pageview_limit: true]
else
[]
end
with :ok <- Quota.ensure_within_plan_limits(user, plan, limit_checking_opts),
do: do_change_plan(subscription, new_plan_id)
end
@doc false
def do_change_plan(subscription, new_plan_id) do
res =
paddle_api().update_subscription(subscription.paddle_subscription_id, %{
plan_id: new_plan_id
})
case res do
{:ok, response} ->
amount = :erlang.float_to_binary(response["next_payment"]["amount"] / 1, decimals: 2)
Subscription.changeset(subscription, %{
paddle_plan_id: Integer.to_string(response["plan_id"]),
next_bill_amount: amount,
next_bill_date: response["next_payment"]["date"]
})
|> Repo.update()
e ->
e
end
end
def change_plan_preview(subscription, new_plan_id) do
case paddle_api().update_subscription_preview(
subscription.paddle_subscription_id,
@ -91,33 +42,6 @@ defmodule Plausible.Billing do
end
end
@spec check_needs_to_upgrade(User.t()) ::
{:needs_to_upgrade, :no_trial | :no_active_subscription | :grace_period_ended}
| :no_upgrade_needed
def check_needs_to_upgrade(user) do
user = Plausible.Users.with_subscription(user)
trial_over? =
not is_nil(user.trial_expiry_date) and
Date.before?(user.trial_expiry_date, Date.utc_today())
subscription_active? = Subscriptions.active?(user.subscription)
cond do
is_nil(user.trial_expiry_date) and not subscription_active? ->
{:needs_to_upgrade, :no_trial}
trial_over? and not subscription_active? ->
{:needs_to_upgrade, :no_active_subscription}
Plausible.Auth.GracePeriod.expired?(user) ->
{:needs_to_upgrade, :grace_period_ended}
true ->
:no_upgrade_needed
end
end
defp handle_subscription_created(params) do
params =
if present?(params["passthrough"]) do
@ -284,14 +208,6 @@ defmodule Plausible.Billing do
"subscription_cancelled__#{id}"
end
defp active_subscription_query(user) do
from(s in Subscription,
where: s.user_id == ^user.id and s.status == ^Subscription.Status.active(),
order_by: [desc: s.inserted_at],
limit: 1
)
end
defp after_subscription_update(subscription) do
user =
User

View File

@ -65,9 +65,9 @@ defmodule Plausible.Billing.Feature do
@callback opted_out?(Plausible.Site.t()) :: boolean()
@doc """
Checks whether the site owner or the user plan includes the given feature.
Checks whether the team or the team plan includes the given feature.
"""
@callback check_availability(Plausible.Auth.User.t() | Plausible.Teams.Team.t() | nil) ::
@callback check_availability(Plausible.Teams.Team.t() | nil) ::
:ok | {:error, :upgrade_required} | {:error, :not_implemented}
@features [
@ -120,8 +120,8 @@ defmodule Plausible.Billing.Feature do
@impl true
def enabled?(%Plausible.Site{} = site) do
site = Plausible.Repo.preload(site, :owner)
check_availability(site.owner) == :ok && !opted_out?(site)
site = Plausible.Repo.preload(site, :team)
check_availability(site.team) == :ok && !opted_out?(site)
end
@impl true
@ -130,8 +130,12 @@ defmodule Plausible.Billing.Feature do
end
@impl true
def check_availability(%Plausible.Auth.User{} = user) do
Plausible.Teams.Adapter.Read.Billing.check_feature_availability(__MODULE__, user)
def check_availability(team_or_nil) do
cond do
free?() -> :ok
__MODULE__ in Plausible.Teams.Billing.allowed_features_for(team_or_nil) -> :ok
true -> {:error, :upgrade_required}
end
end
@impl true
@ -140,11 +144,9 @@ defmodule Plausible.Billing.Feature do
end
defp do_toggle(%Plausible.Site{} = site, user, opts) do
owner = Plausible.Teams.Adapter.Read.Ownership.get_owner(site, user)
override = Keyword.get(opts, :override)
toggle = if is_boolean(override), do: override, else: !Map.fetch!(site, toggle_field())
availability = if toggle, do: check_availability(owner), else: :ok
availability = if toggle, do: check_availability(site.team), else: :ok
case availability do
:ok ->
@ -195,22 +197,10 @@ defmodule Plausible.Billing.Feature.Props do
end
defmodule Plausible.Billing.Feature.StatsAPI do
use Plausible
@moduledoc false
use Plausible.Billing.Feature,
name: :stats_api,
display_name: "Stats API"
@impl true
@doc """
Checks whether the user has access to Stats API or not.
Before the business tier, users who had not yet started their trial had
access to Stats API. With the business tier work, access is blocked and they
must either start their trial or subscribe to a plan. This is common when a
site owner invites a new user. In such cases, using the owner's API key is
recommended.
"""
def check_availability(%Plausible.Auth.User{} = user) do
Plausible.Teams.Adapter.Read.Billing.check_feature_availability_for_stats_api(user)
end
end

View File

@ -2,7 +2,7 @@ defmodule Plausible.Billing.Plans do
alias Plausible.Billing.Subscriptions
use Plausible.Repo
alias Plausible.Billing.{Subscription, Plan, EnterprisePlan}
alias Plausible.Auth.User
alias Plausible.Teams
for f <- [
:legacy_plans,
@ -25,9 +25,6 @@ defmodule Plausible.Billing.Plans do
Module.put_attribute(__MODULE__, :external_resource, path)
end
@business_tier_launch ~N[2023-11-08 12:00:00]
def business_tier_launch, do: @business_tier_launch
@spec growth_plans_for(Subscription.t()) :: [Plan.t()]
@doc """
Returns a list of growth plans available for the subscription to choose.
@ -115,19 +112,6 @@ defmodule Plausible.Billing.Plans do
end
end
def latest_enterprise_plan_with_price(user, customer_ip) do
enterprise_plan =
Repo.one!(
from(e in EnterprisePlan,
where: e.user_id == ^user.id,
order_by: [desc: e.inserted_at],
limit: 1
)
)
{enterprise_plan, get_price_for(enterprise_plan, customer_ip)}
end
def subscription_interval(subscription) do
case get_subscription_plan(subscription) do
%EnterprisePlan{billing_interval: interval} ->
@ -202,24 +186,30 @@ defmodule Plausible.Billing.Plans do
end
end
@enterprise_level_usage 10_000_000
@spec suggest(User.t(), non_neg_integer()) :: Plan.t()
@doc """
Returns the most appropriate plan for a user based on their usage during a
Returns the most appropriate plan for a team based on its usage during a
given cycle.
If the usage during the cycle exceeds the enterprise-level threshold, or if
the user already belongs to an enterprise plan, it suggests the :enterprise
the team already has an enterprise plan, it suggests the :enterprise
plan.
Otherwise, it recommends the plan where the cycle usage falls just under the
plan's limit from the available options for the user.
plan's limit from the available options for the team.
"""
def suggest(user, usage_during_cycle) do
@enterprise_level_usage 10_000_000
@spec suggest(Teams.Team.t(), non_neg_integer()) :: Plan.t()
def suggest(team, usage_during_cycle) do
cond do
usage_during_cycle > @enterprise_level_usage -> :enterprise
Plausible.Teams.Adapter.Read.Billing.enterprise_configured?(user) -> :enterprise
true -> Plausible.Teams.Adapter.Read.Billing.suggest_by_usage(user, usage_during_cycle)
usage_during_cycle > @enterprise_level_usage ->
:enterprise
Teams.Billing.enterprise_configured?(team) ->
:enterprise
true ->
subscription = Teams.Billing.get_subscription(team)
suggest_by_usage(subscription, usage_during_cycle)
end
end

View File

@ -1,120 +1,13 @@
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 Date.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() | nil) ::
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

@ -4,47 +4,21 @@ defmodule Plausible.Billing.Quota do
"""
use Plausible
alias Plausible.Users
alias Plausible.Auth.User
alias Plausible.Billing.{Plan, Plans, EnterprisePlan}
alias Plausible.Billing.Quota.{Usage, Limits}
alias Plausible.Billing.{Plan, EnterprisePlan}
alias Plausible.Billing.Quota.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
Ensures that the given 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()) ::
@spec ensure_within_plan_limits(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, opts \\ [])
def ensure_within_plan_limits(usage, %plan_mod{} = plan, opts)
when plan_mod in [Plan, EnterprisePlan] do
@ -121,13 +95,14 @@ defmodule Plausible.Billing.Quota do
end
end
@spec exceeds_last_two_usage_cycles?(Usage.cycles_usage(), non_neg_integer()) :: boolean()
@spec exceeds_last_two_usage_cycles?(Plausible.Teams.Billing.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()
@spec exceeded_cycles(Plausible.Teams.Billing.cycles_usage(), non_neg_integer()) :: list()
def exceeded_cycles(cycles_usage, allowed_volume) do
limit = Limits.pageview_limit_with_margin(allowed_volume)

View File

@ -1,294 +0,0 @@
defmodule Plausible.Billing.Quota.Usage do
@moduledoc false
use Plausible
import Ecto.Query
alias Plausible.Users
alias Plausible.Auth.User
alias Plausible.Site
alias Plausible.Billing.{Subscriptions, Feature}
@type cycles_usage() :: %{cycle() => usage_cycle()}
@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()
@typep usage_cycle :: %{
date_range: Date.Range.t(),
pageviews: non_neg_integer(),
custom_events: non_neg_integer(),
total: non_neg_integer()
}
@doc """
Returns a full usage report for the user.
### Options
* `pending_ownership_site_ids` - a list of site IDs from which to count
additional usage. This allows us to look at the total usage from pending
ownerships and owned sites at the same time, which is useful, for example,
when deciding whether to let the user upgrade to a plan, or accept a site
ownership.
* `with_features` - when `true`, the returned map will contain features
usage. Also counts usage from `pending_ownership_site_ids` if that option
is given.
"""
def usage(user, opts \\ []) do
owned_site_ids = Plausible.Sites.owned_site_ids(user)
pending_ownership_site_ids = Keyword.get(opts, :pending_ownership_site_ids, [])
all_site_ids = Enum.uniq(owned_site_ids ++ pending_ownership_site_ids)
basic_usage = %{
monthly_pageviews: monthly_pageview_usage(user, all_site_ids),
team_members:
team_member_usage(user, pending_ownership_site_ids: pending_ownership_site_ids),
sites: length(all_site_ids)
}
if Keyword.get(opts, :with_features) == true do
basic_usage
|> Map.put(:features, features_usage(user, all_site_ids))
else
basic_usage
end
end
@spec site_usage(User.t()) :: non_neg_integer()
@doc """
Returns the number of sites the given user owns.
"""
def site_usage(user) do
Plausible.Sites.owned_sites_count(user)
end
@doc """
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
with the following structure is returned:
```elixir
%{
current_cycle: usage_cycle(),
last_cycle: usage_cycle(),
penultimate_cycle: usage_cycle()
}
```
In all other cases of the subscription status (or a `free_10k` subscription which
does not have a `last_bill_date` defined) - the following structure is returned:
```elixir
%{last_30_days: usage_cycle()}
```
Given only a user as input, the usage is queried from across all the sites that the
user owns. Alternatively, given an optional argument of `site_ids`, the usage from
across all those sites is queried instead.
"""
@spec monthly_pageview_usage(User.t(), list() | nil) :: monthly_pageview_usage()
def monthly_pageview_usage(user, site_ids \\ nil)
def monthly_pageview_usage(user, nil) do
monthly_pageview_usage(user, Plausible.Sites.owned_site_ids(user))
end
def monthly_pageview_usage(user, site_ids) do
active_subscription? = Subscriptions.active?(user.subscription)
if active_subscription? && user.subscription.last_bill_date do
[:current_cycle, :last_cycle, :penultimate_cycle]
|> Task.async_stream(fn cycle ->
%{cycle => usage_cycle(user, cycle, site_ids)}
end)
|> Enum.map(fn {:ok, cycle_usage} -> cycle_usage end)
|> Enum.reduce(%{}, &Map.merge/2)
else
%{last_30_days: usage_cycle(user, :last_30_days, site_ids)}
end
end
@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 \\ Date.utc_today())
def usage_cycle(user, cycle, nil, today) do
usage_cycle(user, cycle, Plausible.Sites.owned_site_ids(user), today)
end
def usage_cycle(_user, :last_30_days, owned_site_ids, today) do
date_range = Date.range(Date.shift(today, day: -30), today)
{pageviews, custom_events} =
Plausible.Stats.Clickhouse.usage_breakdown(owned_site_ids, date_range)
%{
date_range: date_range,
pageviews: pageviews,
custom_events: custom_events,
total: pageviews + custom_events
}
end
def usage_cycle(user, cycle, owned_site_ids, today) do
user = Users.with_subscription(user)
last_bill_date = user.subscription.last_bill_date
normalized_last_bill_date =
Date.shift(last_bill_date, month: Timex.diff(today, last_bill_date, :months))
date_range =
case cycle do
:current_cycle ->
Date.range(
normalized_last_bill_date,
Date.shift(normalized_last_bill_date, month: 1, day: -1)
)
:last_cycle ->
Date.range(
Date.shift(normalized_last_bill_date, month: -1),
Date.shift(normalized_last_bill_date, day: -1)
)
:penultimate_cycle ->
Date.range(
Date.shift(normalized_last_bill_date, month: -2),
Date.shift(normalized_last_bill_date, day: -1, month: -1)
)
end
{pageviews, custom_events} =
Plausible.Stats.Clickhouse.usage_breakdown(owned_site_ids, date_range)
%{
date_range: date_range,
pageviews: pageviews,
custom_events: custom_events,
total: pageviews + custom_events
}
end
@spec team_member_usage(User.t(), Keyword.t()) :: non_neg_integer()
@doc """
Returns the total count of team members associated with the user's sites.
* The given user (i.e. the owner) is not counted as a team member.
* Pending invitations (but not ownership transfers) are counted as team
members even before accepted.
* Users are counted uniquely - i.e. even if an account is associated with
many sites owned by the given user, they still count as one team member.
### Options
* `exclude_emails` - a list of emails to not count towards the usage. This
allows us to exclude a user from being counted as a team member when
checking whether a site invitation can be created for that same user.
* `pending_ownership_site_ids` - a list of site IDs from which to count
additional team member usage. Without this option, usage is queried only
across sites owned by the given user.
"""
def team_member_usage(user, opts \\ [])
def team_member_usage(%User{} = user, opts) do
exclude_emails = Keyword.get(opts, :exclude_emails, []) ++ [user.email]
q =
user
|> Plausible.Sites.owned_site_ids()
|> query_team_member_emails()
q =
case Keyword.get(opts, :pending_ownership_site_ids) do
[_ | _] = site_ids -> union(q, ^query_team_member_emails(site_ids))
_ -> q
end
from(u in subquery(q),
where: u.email not in ^exclude_emails,
distinct: u.email
)
|> Plausible.Repo.aggregate(:count)
end
def query_team_member_emails(site_ids) do
memberships_q =
from sm in Site.Membership,
where: sm.site_id in ^site_ids,
inner_join: u in assoc(sm, :user),
select: %{email: u.email}
invitations_q =
from i in Plausible.Auth.Invitation,
where: i.site_id in ^site_ids and i.role != :owner,
select: %{email: i.email}
union(memberships_q, ^invitations_q)
end
@spec features_usage(User.t() | nil, list() | nil) :: [atom()]
@doc """
Given only a user, this function returns the features used across all the
sites this user owns + StatsAPI if the user has a configured Stats API key.
Given a user, and a list of site_ids, returns the features used by those
sites instead + StatsAPI if the user has a configured Stats API key.
The user can also be passed as `nil`, in which case we will never return
Stats API as a used feature.
"""
def features_usage(user, site_ids \\ nil)
def features_usage(%User{} = user, nil) do
site_ids = Plausible.Sites.owned_site_ids(user)
features_usage(user, site_ids)
end
def features_usage(%User{} = user, site_ids) when is_list(site_ids) do
site_scoped_feature_usage = features_usage(nil, site_ids)
stats_api_used? =
from(a in Plausible.Auth.ApiKey, where: a.user_id == ^user.id)
|> Plausible.Repo.exists?()
if stats_api_used? do
site_scoped_feature_usage ++ [Feature.StatsAPI]
else
site_scoped_feature_usage
end
end
def features_usage(nil, site_ids) when is_list(site_ids) do
props_usage_q =
from s in Site,
where: s.id in ^site_ids and fragment("cardinality(?) > 0", s.allowed_event_props)
revenue_goals_usage_q =
from g in Plausible.Goal,
where: g.site_id in ^site_ids and not is_nil(g.currency)
queries =
on_ee do
funnels_usage_q = from f in "funnels", where: f.site_id in ^site_ids
[
{Feature.Props, props_usage_q},
{Feature.Funnels, funnels_usage_q},
{Feature.RevenueGoals, revenue_goals_usage_q}
]
else
[
{Feature.Props, props_usage_q},
{Feature.RevenueGoals, revenue_goals_usage_q}
]
end
Enum.reduce(queries, [], fn {feature, query}, acc ->
if Plausible.Repo.exists?(query), do: acc ++ [feature], else: acc
end)
end
end

View File

@ -16,8 +16,13 @@ defmodule Plausible.Billing.SiteLocker do
user = Plausible.Users.with_subscription(user)
# TODO: Use team version once we start switching writes.
case Plausible.Billing.check_needs_to_upgrade(user) do
team =
case Plausible.Teams.get_by_owner(user) do
{:ok, team} -> team
_ -> nil
end
case Plausible.Teams.Billing.check_needs_to_upgrade(team) do
{:needs_to_upgrade, :grace_period_ended} ->
set_lock_status_for(user, true)
@ -67,8 +72,14 @@ defmodule Plausible.Billing.SiteLocker do
@spec send_grace_period_end_email(Plausible.Auth.User.t()) :: Plausible.Mailer.result()
def send_grace_period_end_email(user) do
usage = Plausible.Teams.Adapter.Read.Billing.monthly_pageview_usage(user)
suggested_plan = Plausible.Billing.Plans.suggest(user, usage.last_cycle.total)
team =
case Plausible.Teams.get_by_owner(user) do
{:ok, team} -> team
_ -> nil
end
usage = Plausible.Teams.Billing.monthly_pageview_usage(team)
suggested_plan = Plausible.Billing.Plans.suggest(team, usage.last_cycle.total)
user
|> PlausibleWeb.Email.dashboard_locked(usage, suggested_plan)

View File

@ -13,11 +13,11 @@ defmodule Plausible.Plugins.API.Capabilities do
features =
if site do
site = Plausible.Repo.preload(site, :owner)
site = Plausible.Repo.preload(site, :team)
Feature.list()
|> Enum.map(fn mod ->
result = mod.check_availability(site.owner)
result = mod.check_availability(site.team)
feature = mod |> Module.split() |> List.last()
{feature, result == :ok}
end)

View File

@ -131,13 +131,10 @@ defmodule Plausible.SiteAdmin do
{:error, "Please select at least one site from the list"}
end
defp transfer_ownership_direct(conn, sites, %{"email" => email}) do
current_user = conn.assigns.current_user
defp transfer_ownership_direct(_conn, sites, %{"email" => email}) do
with {:ok, new_owner} <- Plausible.Auth.get_user_by(email: email),
{:ok, _} <-
Plausible.Site.Memberships.bulk_transfer_ownership_direct(
current_user,
sites,
new_owner
) do

View File

@ -19,7 +19,7 @@ defmodule Plausible.Site.Memberships do
defdelegate bulk_create_invitation(sites, inviter, invitee_email, role, opts),
to: Memberships.CreateInvitation
defdelegate bulk_transfer_ownership_direct(current_user, sites, new_owner),
defdelegate bulk_transfer_ownership_direct(sites, new_owner),
to: Memberships.AcceptInvitation
@spec any?(Auth.User.t()) :: boolean()
@ -37,37 +37,4 @@ defmodule Plausible.Site.Memberships do
)
)
end
@spec all_pending_ownerships(String.t()) :: list()
def all_pending_ownerships(email) do
pending_ownership_invitation_q(email)
|> Repo.all()
end
@spec pending_ownerships?(String.t()) :: boolean()
def pending_ownerships?(email) do
pending_ownership_invitation_q(email)
|> Repo.exists?()
end
@spec any_or_pending?(Plausible.Auth.User.t()) :: boolean()
def any_or_pending?(user) do
invitation_query =
from(i in Plausible.Auth.Invitation,
where: i.email == ^user.email,
select: 1
)
from(sm in Plausible.Site.Membership,
where: sm.user_id == ^user.id or exists(invitation_query),
select: 1
)
|> Repo.exists?()
end
defp pending_ownership_invitation_q(email) do
from(i in Plausible.Auth.Invitation,
where: i.email == ^email and i.role == ^:owner
)
end
end

View File

@ -35,12 +35,12 @@ defmodule Plausible.Site.Memberships.AcceptInvitation do
| Ecto.Changeset.t()
| :no_plan
@spec bulk_transfer_ownership_direct(Auth.User.t(), [Site.t()], Auth.User.t()) ::
@spec bulk_transfer_ownership_direct([Site.t()], Auth.User.t()) ::
{:ok, [Site.Membership.t()]} | {:error, transfer_error()}
def bulk_transfer_ownership_direct(current_user, sites, new_owner) do
def bulk_transfer_ownership_direct(sites, new_owner) do
Repo.transaction(fn ->
for site <- sites do
case transfer_ownership(current_user, site, new_owner) do
case transfer_ownership(site, new_owner) do
{:ok, membership} ->
membership
@ -63,15 +63,17 @@ defmodule Plausible.Site.Memberships.AcceptInvitation do
end
end
defp transfer_ownership(current_user, site, new_owner) do
defp transfer_ownership(site, new_owner) do
site = Repo.preload(site, :team)
with :ok <-
Plausible.Teams.Adapter.Read.Invitations.ensure_transfer_valid(
current_user,
site,
Plausible.Teams.Invitations.ensure_transfer_valid(
site.team,
new_owner,
:owner
),
:ok <- Plausible.Teams.Adapter.Read.Ownership.ensure_can_take_ownership(site, new_owner) do
{:ok, new_team} = Plausible.Teams.get_or_create(new_owner),
:ok <- Plausible.Teams.Invitations.ensure_can_take_ownership(site, new_team) do
membership = get_or_create_owner_membership(site, new_owner)
multi = add_and_transfer_ownership(site, membership, new_owner)
@ -92,16 +94,16 @@ defmodule Plausible.Site.Memberships.AcceptInvitation do
defp do_accept_ownership_transfer(invitation, user) do
membership = get_or_create_membership(invitation, user)
site = invitation.site
site = Repo.preload(invitation.site, :team)
with :ok <-
Plausible.Teams.Adapter.Read.Invitations.ensure_transfer_valid(
user,
site,
Plausible.Teams.Invitations.ensure_transfer_valid(
site.team,
user,
:owner
),
:ok <- Plausible.Teams.Adapter.Read.Ownership.ensure_can_take_ownership(site, user) do
{:ok, team} = Plausible.Teams.get_or_create(user),
:ok <- Plausible.Teams.Invitations.ensure_can_take_ownership(site, team) do
site
|> add_and_transfer_ownership(membership, user)
|> Multi.delete(:invitation, invitation)

View File

@ -5,9 +5,9 @@ defmodule Plausible.Site.Memberships.CreateInvitation do
"""
alias Plausible.Auth.{User, Invitation}
alias Plausible.{Site, Sites, Site.Membership}
alias Plausible.Billing.Quota
import Ecto.Query
alias Plausible.Site
alias Plausible.Repo
alias Plausible.Teams
use Plausible
@type invite_error() ::
@ -32,7 +32,7 @@ defmodule Plausible.Site.Memberships.CreateInvitation do
as an ownership transfer and requires the inviter to be the owner of the site.
"""
def create_invitation(site, inviter, invitee_email, role) do
Plausible.Repo.transaction(fn ->
Repo.transaction(fn ->
do_invite(site, inviter, invitee_email, role)
end)
end
@ -40,7 +40,7 @@ defmodule Plausible.Site.Memberships.CreateInvitation do
@spec bulk_create_invitation([Site.t()], User.t(), String.t(), atom(), Keyword.t()) ::
{:ok, [Invitation.t()]} | {:error, invite_error()}
def bulk_create_invitation(sites, inviter, invitee_email, role, opts \\ []) do
Plausible.Repo.transaction(fn ->
Repo.transaction(fn ->
for site <- sites do
do_invite(site, inviter, invitee_email, role, opts)
end
@ -50,125 +50,56 @@ defmodule Plausible.Site.Memberships.CreateInvitation do
defp do_invite(site, inviter, invitee_email, role, opts \\ []) do
attrs = %{email: invitee_email, role: role, site_id: site.id, inviter_id: inviter.id}
with site <- Plausible.Repo.preload(site, :owner),
with site <- Repo.preload(site, [:owner, :team]),
:ok <-
Plausible.Teams.Adapter.Read.Invitations.check_invitation_permissions(
Teams.Invitations.check_invitation_permissions(
site,
inviter,
role,
opts
),
:ok <-
Plausible.Teams.Adapter.Read.Invitations.check_team_member_limit(
inviter,
site,
Teams.Invitations.check_team_member_limit(
site.team,
role,
invitee_email
),
invitee = Plausible.Auth.find_user_by(email: invitee_email),
:ok <-
Plausible.Teams.Adapter.Read.Invitations.ensure_transfer_valid(
inviter,
site,
Teams.Invitations.ensure_transfer_valid(
site.team,
invitee,
role
),
:ok <-
Plausible.Teams.Adapter.Read.Invitations.ensure_new_membership(
inviter,
Teams.Invitations.ensure_new_membership(
site,
invitee,
role
),
%Ecto.Changeset{} = changeset <- Invitation.new(attrs),
{:ok, invitation} <- Plausible.Repo.insert(changeset) do
Plausible.Teams.Invitations.invite_sync(site, invitation)
{:ok, invitation} <- Repo.insert(changeset) do
Teams.Invitations.invite_sync(site, invitation)
Plausible.Teams.Adapter.Read.Invitations.send_invitation_email(inviter, invitation, invitee)
send_invitation_email(inviter, invitation, invitee)
invitation
else
{:error, cause} -> Plausible.Repo.rollback(cause)
{:error, cause} -> Repo.rollback(cause)
end
end
@doc false
def check_invitation_permissions(site, inviter, requested_role, opts) do
check_permissions? = Keyword.get(opts, :check_permissions, true)
if check_permissions? do
required_roles = if requested_role == :owner, do: [:owner], else: [:admin, :owner]
membership_query =
from(m in Membership,
where: m.user_id == ^inviter.id and m.site_id == ^site.id and m.role in ^required_roles
)
if Plausible.Repo.exists?(membership_query), do: :ok, else: {:error, :forbidden}
defp send_invitation_email(inviter, invitation, invitee) do
if invitation.role == :owner do
Teams.SiteTransfer
|> Repo.get_by!(transfer_id: invitation.invitation_id, initiator_id: inviter.id)
|> Repo.preload([:site, :initiator])
|> Teams.Invitations.send_invitation_email(invitee)
else
:ok
Teams.GuestInvitation
|> Repo.get_by!(invitation_id: invitation.invitation_id)
|> Repo.preload([:site, team_invitation: :inviter])
|> Teams.Invitations.send_invitation_email(invitee)
end
end
@doc false
def send_invitation_email(invitation, invitee) do
invitation = Plausible.Repo.preload(invitation, [:site, :inviter])
email =
case {invitee, invitation.role} do
{invitee, :owner} ->
PlausibleWeb.Email.ownership_transfer_request(
invitation.email,
invitation.invitation_id,
invitation.site,
invitation.inviter,
invitee
)
{nil, _role} ->
PlausibleWeb.Email.new_user_invitation(
invitation.email,
invitation.invitation_id,
invitation.site,
invitation.inviter
)
{%User{}, _role} ->
PlausibleWeb.Email.existing_user_invitation(
invitation.email,
invitation.site,
invitation.inviter
)
end
Plausible.Mailer.send(email)
end
@doc false
def ensure_new_membership(_site, _invitee, :owner) do
:ok
end
def ensure_new_membership(site, invitee, _role) do
if invitee && Sites.is_member?(invitee.id, site) do
{:error, :already_a_member}
else
:ok
end
end
@doc false
def check_team_member_limit(_site, :owner, _invitee_email) do
:ok
end
def check_team_member_limit(site, _role, invitee_email) do
site = Plausible.Repo.preload(site, :owner)
limit = Quota.Limits.team_member_limit(site.owner)
usage = Quota.Usage.team_member_usage(site.owner, exclude_emails: [invitee_email])
if Quota.below_limit?(usage, limit),
do: :ok,
else: {:error, {:over_limit, limit}}
end
end

View File

@ -5,7 +5,6 @@ defmodule Plausible.Site.Memberships.Invitations do
import Ecto.Query, only: [from: 2]
alias Plausible.Site
alias Plausible.Auth
alias Plausible.Repo
alias Plausible.Billing.Feature
@ -48,45 +47,4 @@ defmodule Plausible.Site.Memberships.Invitations do
:ok
end
@spec ensure_transfer_valid(Site.t(), Auth.User.t() | nil, Site.Membership.role()) ::
:ok | {:error, :transfer_to_self}
def ensure_transfer_valid(%Site{} = site, %Auth.User{} = new_owner, :owner) do
if Plausible.Sites.role(new_owner.id, site) == :owner do
{:error, :transfer_to_self}
else
:ok
end
end
def ensure_transfer_valid(_site, _invitee, _role) do
:ok
end
on_ee do
alias Plausible.Billing.Quota
@spec ensure_can_take_ownership(Site.t(), Auth.User.t()) ::
:ok | {:error, Quota.Limits.over_limits_error() | :no_plan}
def ensure_can_take_ownership(site, new_owner) do
site = Repo.preload(site, :owner)
new_owner = Plausible.Users.with_subscription(new_owner)
plan = Plausible.Billing.Plans.get_subscription_plan(new_owner.subscription)
active_subscription? = Plausible.Billing.Subscriptions.active?(new_owner.subscription)
if active_subscription? && plan != :free_10k do
new_owner
|> Quota.Usage.usage(pending_ownership_site_ids: [site.id])
|> Quota.ensure_within_plan_limits(plan)
else
{:error, :no_plan}
end
end
else
@spec ensure_can_take_ownership(Site.t(), Auth.User.t()) :: :ok
def ensure_can_take_ownership(_site, _new_owner) do
:ok
end
end
end

View File

@ -8,6 +8,7 @@ defmodule Plausible.Sites do
alias Plausible.Auth
alias Plausible.Repo
alias Plausible.Site
alias Plausible.Teams
alias Plausible.Site.SharedLink
require Plausible.Site.UserPreference
@ -55,7 +56,7 @@ defmodule Plausible.Sites do
@spec set_option(Auth.User.t(), Site.t(), atom(), any()) :: Site.UserPreference.t()
def set_option(user, site, option, value) when option in Site.UserPreference.options() do
Plausible.Teams.Adapter.Read.Sites.get_for_user!(user, site.domain)
Plausible.Sites.get_for_user!(user, site.domain)
user
|> Site.UserPreference.changeset(site, %{option => value})
@ -68,10 +69,85 @@ defmodule Plausible.Sites do
)
end
defdelegate list(user, pagination_params, opts \\ []), to: Plausible.Teams.Adapter.Read.Sites
defdelegate list(user, pagination_params, opts \\ []), to: Plausible.Teams.Sites
defdelegate list_with_invitations(user, pagination_params, opts \\ []),
to: Plausible.Teams.Adapter.Read.Sites
to: Plausible.Teams.Sites
def list_people(site) do
owner_membership =
from(
tm in Teams.Membership,
where: tm.team_id == ^site.team_id,
where: tm.role == :owner,
select: %Plausible.Site.Membership{
user_id: tm.user_id,
role: tm.role
}
)
|> Repo.one!()
memberships =
from(
gm in Teams.GuestMembership,
inner_join: tm in assoc(gm, :team_membership),
where: gm.site_id == ^site.id,
select: %Plausible.Site.Membership{
user_id: tm.user_id,
role:
fragment(
"""
CASE
WHEN ? = 'editor' THEN 'admin'
ELSE ?
END
""",
gm.role,
gm.role
)
}
)
|> Repo.all()
memberships = Repo.preload([owner_membership | memberships], :user)
invitations =
from(
gi in Teams.GuestInvitation,
inner_join: ti in assoc(gi, :team_invitation),
where: gi.site_id == ^site.id,
select: %Plausible.Auth.Invitation{
invitation_id: gi.invitation_id,
email: ti.email,
role:
fragment(
"""
CASE
WHEN ? = 'editor' THEN 'admin'
ELSE ?
END
""",
gi.role,
gi.role
)
}
)
|> Repo.all()
site_transfers =
from(
st in Teams.SiteTransfer,
where: st.site_id == ^site.id,
select: %Plausible.Auth.Invitation{
invitation_id: st.transfer_id,
email: st.email,
role: :owner
}
)
|> Repo.all()
%{memberships: memberships, invitations: site_transfers ++ invitations}
end
@spec for_user_query(Auth.User.t()) :: Ecto.Query.t()
def for_user_query(user) do
@ -83,15 +159,20 @@ defmodule Plausible.Sites do
end
def create(user, params) 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 ->
Plausible.Teams.get_or_create(user)
end)
|> Ecto.Multi.run(:ensure_can_add_new_site, fn _repo, %{create_team: team} ->
case Plausible.Teams.Billing.ensure_can_add_new_site(team) do
:ok -> {:ok, :proceed}
error -> error
end
end)
|> Ecto.Multi.run(:clear_changed_from, fn
_repo, %{site_changeset: %{changes: %{domain: domain}}} ->
case Plausible.Teams.Adapter.Read.Sites.get_for_user(user, domain, [:owner]) do
case Plausible.Sites.get_for_user(user, domain, [:owner]) do
%Site{domain_changed_from: ^domain} = site ->
site
|> Ecto.Changeset.change()
@ -119,7 +200,6 @@ defmodule Plausible.Sites do
end)
|> Repo.transaction()
end
end
defp maybe_start_trial(multi, user) do
case user.trial_expiry_date do
@ -231,6 +311,55 @@ defmodule Plausible.Sites do
locked
end
def get_for_user!(user, domain, roles \\ [:owner, :admin, :viewer]) do
roles = translate_roles(roles)
site =
if :super_admin in roles and Plausible.Auth.is_super_admin?(user.id) do
get_by_domain!(domain)
else
user.id
|> get_for_user_query(domain, List.delete(roles, :super_admin))
|> Repo.one!()
end
Repo.preload(site, :team)
end
def get_for_user(user, domain, roles \\ [:owner, :admin, :viewer]) do
roles = translate_roles(roles)
if :super_admin in roles and Plausible.Auth.is_super_admin?(user.id) do
get_by_domain(domain)
else
user.id
|> get_for_user_query(domain, List.delete(roles, :super_admin))
|> Repo.one()
end
end
defp translate_roles(roles) do
Enum.map(roles, fn
:admin -> :editor
role -> role
end)
end
defp get_for_user_query(user_id, domain, roles) do
roles = Enum.map(roles, &to_string/1)
from(s in Plausible.Site,
join: t in assoc(s, :team),
join: tm in assoc(t, :team_memberships),
left_join: gm in assoc(tm, :guest_memberships),
where: tm.user_id == ^user_id,
where: coalesce(gm.role, tm.role) in ^roles,
where: s.domain == ^domain or s.domain_changed_from == ^domain,
where: is_nil(gm.id) or gm.site_id == s.id,
select: s
)
end
def role(user_id, site) do
Repo.one(
from(sm in Site.Membership,

View File

@ -9,6 +9,15 @@ defmodule Plausible.Teams do
alias Plausible.Repo
use Plausible
@spec get_owner(Teams.Team.t()) ::
{:ok, Plausible.Auth.User.t()} | {:error, :no_owner | :multiple_owners}
def get_owner(team) do
case Repo.preload(team, :owner).owner do
nil -> {:error, :no_owner}
owner_user -> {:ok, owner_user}
end
end
@spec on_trial?(Teams.Team.t() | nil) :: boolean()
on_ee do
def on_trial?(nil), do: false
@ -29,10 +38,6 @@ defmodule Plausible.Teams do
Date.diff(team.trial_expiry_date, Date.utc_today())
end
def read_team_schemas?(user) do
FunWithFlags.enabled?(:read_team_schemas, for: user)
end
def with_subscription(team) do
Repo.preload(team, subscription: last_subscription_query())
end
@ -77,6 +82,12 @@ defmodule Plausible.Teams do
)
end
def has_active_sites?(team) do
team
|> owned_sites()
|> Enum.any?(&Plausible.Sites.has_stats?/1)
end
@doc """
Create (when necessary) and load team relation for provided site.

View File

@ -1,48 +0,0 @@
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 user_or_team(user) do
switch(user,
team_fn: &Function.identity/1,
user_fn: &Function.identity/1
)
end
def switch(switch_on, opts \\ [])
def switch(%Plausible.Auth.User{} = 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 = Plausible.Teams.with_subscription(team)
team_fn.(team)
else
user = Plausible.Users.with_subscription(user)
user_fn.(user)
end
end
def switch(team_or_nil, opts) do
team_fn = Keyword.fetch!(opts, :team_fn)
team = Plausible.Teams.with_subscription(team_or_nil)
team_fn.(team)
end
end

View File

@ -1,190 +0,0 @@
defmodule Plausible.Teams.Adapter.Read.Billing do
@moduledoc """
Transition adapter for new schema reads
"""
use Plausible.Teams.Adapter
def quota_usage(user, opts \\ []) do
switch(user,
team_fn: &Plausible.Teams.Billing.quota_usage(&1, opts),
user_fn: &Plausible.Billing.Quota.Usage.usage(&1, opts)
)
end
def allow_next_upgrade_override?(user) do
switch(user,
team_fn: &(&1 && &1.allow_next_upgrade_override),
user_fn: & &1.allow_next_upgrade_override
)
end
def change_plan(user, new_plan_id) do
switch(user,
team_fn: &Plausible.Teams.Billing.change_plan(&1, new_plan_id),
user_fn: &Plausible.Billing.change_plan(&1, new_plan_id)
)
end
def enterprise_configured?(nil), do: false
def enterprise_configured?(user) do
switch(user,
team_fn: &Plausible.Teams.Billing.enterprise_configured?/1,
user_fn: &Plausible.Auth.enterprise_configured?/1
)
end
def latest_enterprise_plan_with_prices(user, customer_ip) do
switch(user,
team_fn: &Plausible.Teams.Billing.latest_enterprise_plan_with_price(&1, customer_ip),
user_fn: &Plausible.Billing.Plans.latest_enterprise_plan_with_price(&1, customer_ip)
)
end
def has_active_subscription?(user) do
switch(user,
team_fn: &Plausible.Teams.Billing.has_active_subscription?/1,
user_fn: &Plausible.Billing.has_active_subscription?/1
)
end
def active_subscription_for(user) do
switch(user,
team_fn: &Plausible.Teams.Billing.active_subscription_for/1,
user_fn: &Plausible.Billing.active_subscription_for/1
)
end
def get_subscription(user) do
case user_or_team(user) do
%{subscription: subscription} -> subscription
_ -> nil
end
end
def team_member_limit(user) do
switch(user,
team_fn: &Teams.Billing.team_member_limit/1,
user_fn: &Plausible.Billing.Quota.Limits.team_member_limit/1
)
end
def team_member_usage(user, opts \\ []) do
switch(user,
team_fn: &Teams.Billing.team_member_usage(&1, opts),
user_fn: &Plausible.Billing.Quota.Usage.team_member_usage(&1, opts)
)
end
def monthly_pageview_limit(user) do
switch(user,
team_fn: &Teams.Billing.monthly_pageview_limit/1,
user_fn: &Plausible.Billing.Quota.Limits.monthly_pageview_limit/1
)
end
def monthly_pageview_usage(user, site_ids \\ nil) do
switch(
user,
team_fn: &Teams.Billing.monthly_pageview_usage(&1, site_ids),
user_fn: &Plausible.Billing.Quota.Usage.monthly_pageview_usage(&1, site_ids)
)
end
def check_needs_to_upgrade(user) do
switch(
user,
team_fn: &Teams.Billing.check_needs_to_upgrade/1,
user_fn: &Plausible.Billing.check_needs_to_upgrade/1
)
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
use Plausible
on_ee do
def check_feature_availability_for_stats_api(user) do
{unlimited_trial?, subscription?} =
switch(user,
team_fn: fn team ->
team = Plausible.Teams.with_subscription(team)
unlimited_trial? = is_nil(team) or is_nil(team.trial_expiry_date)
subscription? =
not is_nil(team) and Plausible.Billing.Subscriptions.active?(team.subscription)
{unlimited_trial?, subscription?}
end,
user_fn: fn user ->
user = Plausible.Users.with_subscription(user)
unlimited_trial? = is_nil(user.trial_expiry_date)
subscription? = Plausible.Billing.Subscriptions.active?(user.subscription)
{unlimited_trial?, subscription?}
end
)
pre_business_tier_account? =
NaiveDateTime.before?(user.inserted_at, Plausible.Billing.Plans.business_tier_launch())
cond do
!subscription? && unlimited_trial? && pre_business_tier_account? ->
:ok
!subscription? && unlimited_trial? && !pre_business_tier_account? ->
{:error, :upgrade_required}
true ->
check_feature_availability(Plausible.Billing.Feature.StatsAPI, user)
end
end
else
def check_feature_availability_for_stats_api(_user), do: :ok
end
def check_feature_availability(feature, user) do
switch(user,
team_fn: fn team_or_nil ->
cond do
feature.free?() -> :ok
feature in Teams.Billing.allowed_features_for(team_or_nil) -> :ok
true -> {:error, :upgrade_required}
end
end,
user_fn: fn user ->
cond do
feature.free?() -> :ok
feature in Plausible.Billing.Quota.Limits.allowed_features_for(user) -> :ok
true -> {:error, :upgrade_required}
end
end
)
end
def suggest_by_usage(user, usage_during_cycle) do
subscription = get_subscription(user)
Plausible.Billing.Plans.suggest_by_usage(subscription, usage_during_cycle)
end
end

View File

@ -1,120 +0,0 @@
defmodule Plausible.Teams.Adapter.Read.Invitations do
@moduledoc """
Transition adapter for new schema reads
"""
use Plausible
use Plausible.Teams.Adapter
alias Plausible.Repo
def check_invitation_permissions(site, inviter, role, opts) do
switch(
inviter,
team_fn: fn _ ->
Plausible.Teams.Invitations.check_invitation_permissions(
site,
inviter,
role,
opts
)
end,
user_fn: fn _ ->
Plausible.Site.Memberships.CreateInvitation.check_invitation_permissions(
site,
inviter,
role,
opts
)
end
)
end
def check_team_member_limit(inviter, site, role, invitee_email) do
switch(
inviter,
team_fn: fn _ ->
site_team = Repo.preload(site, :team).team
Plausible.Teams.Invitations.check_team_member_limit(
site_team,
role,
invitee_email
)
end,
user_fn: fn _ ->
Plausible.Site.Memberships.CreateInvitation.check_team_member_limit(
site,
role,
invitee_email
)
end
)
end
def ensure_transfer_valid(current_user, site, invitee, role) do
switch(
current_user,
team_fn: fn _ ->
site_team = Repo.preload(site, :team).team
Plausible.Teams.Invitations.ensure_transfer_valid(
site_team,
invitee,
role
)
end,
user_fn: fn _ ->
Plausible.Site.Memberships.Invitations.ensure_transfer_valid(
site,
invitee,
role
)
end
)
end
def ensure_new_membership(inviter, site, invitee, role) do
switch(
inviter,
team_fn: fn _ ->
Plausible.Teams.Invitations.ensure_new_membership(
site,
invitee,
role
)
end,
user_fn: fn _ ->
Plausible.Site.Memberships.CreateInvitation.ensure_new_membership(
site,
invitee,
role
)
end
)
end
def send_invitation_email(inviter, invitation, invitee) do
switch(
inviter,
team_fn: fn _ ->
if invitation.role == :owner do
Teams.SiteTransfer
|> Repo.get_by!(transfer_id: invitation.invitation_id, initiator_id: inviter.id)
|> Repo.preload([:site, :initiator])
|> Plausible.Teams.Invitations.send_invitation_email(invitee)
else
Teams.GuestInvitation
|> Repo.get_by!(invitation_id: invitation.invitation_id)
|> Repo.preload([:site, team_invitation: :inviter])
|> Plausible.Teams.Invitations.send_invitation_email(invitee)
end
end,
user_fn: fn _ ->
Plausible.Site.Memberships.CreateInvitation.send_invitation_email(
invitation,
invitee
)
end
)
end
end

View File

@ -1,78 +0,0 @@
defmodule Plausible.Teams.Adapter.Read.Ownership do
@moduledoc """
Transition adapter for new schema reads
"""
use Plausible
use Plausible.Teams.Adapter
alias Plausible.Site
alias Plausible.Auth
alias Plausible.Site.Memberships.Invitations
def all_pending_site_transfers(email, user) do
switch(user,
team_fn: fn _ -> Plausible.Teams.Memberships.all_pending_site_transfers(email) end,
user_fn: fn _ -> Plausible.Site.Memberships.all_pending_ownerships(email) end
)
end
def get_owner(site, user) do
switch(user,
team_fn: fn team ->
case Teams.Sites.get_owner(team) do
{:ok, user} -> user
_ -> nil
end
end,
user_fn: fn _ ->
Plausible.Repo.preload(site, :owner).owner
end
)
end
def ensure_can_take_ownership(site, user) do
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
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
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
missing_features =
Plausible.Billing.Quota.Usage.features_usage(nil, [site.id])
|> Enum.filter(&(&1.check_availability(new_owner) != :ok))
if missing_features == [] do
:ok
else
{:error, {:missing_features, missing_features}}
end
end
else
def check_feature_access(_site, _new_owner) do
:ok
end
end
end

View File

@ -1,276 +0,0 @@
defmodule Plausible.Teams.Adapter.Read.Sites do
@moduledoc """
Transition adapter for new schema reads
"""
use Plausible.Teams.Adapter
import Ecto.Query
alias Plausible.Repo
alias Plausible.Site
alias Plausible.Teams
def list(user, pagination_params, opts \\ []) do
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
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
defp old_list(user, pagination_params, opts) do
domain_filter = Keyword.get(opts, :filter_by_domain)
from(s in Site,
left_join: up in Site.UserPreference,
on: up.site_id == s.id and up.user_id == ^user.id,
inner_join: sm in assoc(s, :memberships),
on: sm.user_id == ^user.id,
select: %{
s
| pinned_at: selected_as(up.pinned_at, :pinned_at),
entry_type:
selected_as(
fragment(
"""
CASE
WHEN ? IS NOT NULL THEN 'pinned_site'
ELSE 'site'
END
""",
up.pinned_at
),
:entry_type
)
},
order_by: [asc: selected_as(:entry_type), desc: selected_as(:pinned_at), asc: s.domain],
preload: [memberships: sm]
)
|> maybe_filter_by_domain(domain_filter)
|> Repo.paginate(pagination_params)
end
defp old_list_with_invitations(user, pagination_params, opts) do
domain_filter = Keyword.get(opts, :filter_by_domain)
result =
from(s in Site,
left_join: up in Site.UserPreference,
on: up.site_id == s.id and up.user_id == ^user.id,
left_join: i in assoc(s, :invitations),
on: i.email == ^user.email,
left_join: sm in assoc(s, :memberships),
on: sm.user_id == ^user.id,
where: not is_nil(sm.id) or not is_nil(i.id),
select: %{
s
| pinned_at: selected_as(up.pinned_at, :pinned_at),
entry_type:
selected_as(
fragment(
"""
CASE
WHEN ? IS NOT NULL THEN 'invitation'
WHEN ? IS NOT NULL THEN 'pinned_site'
ELSE 'site'
END
""",
i.id,
up.pinned_at
),
:entry_type
)
},
order_by: [asc: selected_as(:entry_type), desc: selected_as(:pinned_at), asc: s.domain],
preload: [memberships: sm, invitations: i]
)
|> maybe_filter_by_domain(domain_filter)
|> Repo.paginate(pagination_params)
# Populating `site` preload on `invitation`
# without requesting it from database.
# Necessary for invitation modals logic.
entries =
Enum.map(result.entries, fn
%{invitations: [invitation]} = site ->
site = %{site | invitations: [], memberships: []}
invitation = %{invitation | site: site}
%{site | invitations: [invitation]}
site ->
site
end)
%{result | entries: entries}
end
def list_people(site, user) do
if Plausible.Teams.read_team_schemas?(user) do
owner_membership =
from(
tm in Teams.Membership,
where: tm.team_id == ^site.team_id,
where: tm.role == :owner,
select: %Plausible.Site.Membership{
user_id: tm.user_id,
role: tm.role
}
)
|> Repo.one!()
memberships =
from(
gm in Teams.GuestMembership,
inner_join: tm in assoc(gm, :team_membership),
where: gm.site_id == ^site.id,
select: %Plausible.Site.Membership{
user_id: tm.user_id,
role:
fragment(
"""
CASE
WHEN ? = 'editor' THEN 'admin'
ELSE ?
END
""",
gm.role,
gm.role
)
}
)
|> Repo.all()
memberships = Repo.preload([owner_membership | memberships], :user)
invitations =
from(
gi in Teams.GuestInvitation,
inner_join: ti in assoc(gi, :team_invitation),
where: gi.site_id == ^site.id,
select: %Plausible.Auth.Invitation{
invitation_id: gi.invitation_id,
email: ti.email,
role:
fragment(
"""
CASE
WHEN ? = 'editor' THEN 'admin'
ELSE ?
END
""",
gi.role,
gi.role
)
}
)
|> Repo.all()
site_transfers =
from(
st in Teams.SiteTransfer,
where: st.site_id == ^site.id,
select: %Plausible.Auth.Invitation{
invitation_id: st.transfer_id,
email: st.email,
role: :owner
}
)
|> Repo.all()
%{memberships: memberships, invitations: site_transfers ++ invitations}
else
site
|> Repo.preload([:invitations, memberships: :user])
|> Map.take([:memberships, :invitations])
end
end
def get_for_user!(user, domain, roles \\ [:owner, :admin, :viewer]) do
{query_fn, roles} = for_user_query_and_roles(user, roles)
site =
if :super_admin in roles and Plausible.Auth.is_super_admin?(user.id) do
Plausible.Sites.get_by_domain!(domain)
else
user.id
|> query_fn.(domain, List.delete(roles, :super_admin))
|> Repo.one!()
end
Repo.preload(site, :team)
end
def get_for_user(user, domain, roles \\ [:owner, :admin, :viewer]) do
{query_fn, roles} = for_user_query_and_roles(user, roles)
if :super_admin in roles and Plausible.Auth.is_super_admin?(user.id) do
Plausible.Sites.get_by_domain(domain)
else
user.id
|> query_fn.(domain, List.delete(roles, :super_admin))
|> Repo.one()
end
end
defp for_user_query_and_roles(user, roles) do
switch(
user,
team_fn: fn _ ->
translated_roles =
Enum.map(roles, fn
:admin -> :editor
other -> other
end)
{&new_get_for_user_query/3, translated_roles}
end,
user_fn: fn _ ->
{&old_get_for_user_query/3, roles}
end
)
end
defp old_get_for_user_query(user_id, domain, roles) do
from(s in Plausible.Site,
join: sm in Plausible.Site.Membership,
on: sm.site_id == s.id,
where: sm.user_id == ^user_id,
where: sm.role in ^roles,
where: s.domain == ^domain or s.domain_changed_from == ^domain,
select: s
)
end
defp new_get_for_user_query(user_id, domain, roles) do
roles = Enum.map(roles, &to_string/1)
from(s in Plausible.Site,
join: t in assoc(s, :team),
join: tm in assoc(t, :team_memberships),
left_join: gm in assoc(tm, :guest_memberships),
where: tm.user_id == ^user_id,
where: coalesce(gm.role, tm.role) in ^roles,
where: s.domain == ^domain or s.domain_changed_from == ^domain,
where: is_nil(gm.id) or gm.site_id == s.id,
select: s
)
end
defp maybe_filter_by_domain(query, domain)
when byte_size(domain) >= 1 and byte_size(domain) <= 64 do
where(query, [s], ilike(s.domain, ^"%#{domain}%"))
end
defp maybe_filter_by_domain(query, _), do: query
end

View File

@ -1,27 +0,0 @@
defmodule Plausible.Teams.Adapter.Read.Teams do
@moduledoc """
Transition adapter for new schema reads
"""
use Plausible.Teams.Adapter
def trial_expiry_date(user) do
switch(user,
team_fn: &(&1 && &1.trial_expiry_date),
user_fn: & &1.trial_expiry_date
)
end
def on_trial?(user) do
switch(user,
team_fn: &Plausible.Teams.on_trial?/1,
user_fn: &Plausible.Users.on_trial?/1
)
end
def trial_days_left(user) do
switch(user,
team_fn: &Plausible.Teams.trial_days_left/1,
user_fn: &Plausible.Users.trial_days_left/1
)
end
end

View File

@ -1,10 +1,13 @@
defmodule Plausible.Teams.Billing do
@moduledoc false
use Plausible
import Ecto.Query
alias Plausible.Billing.EnterprisePlan
alias Plausible.Billing.Plans
alias Plausible.Billing.Subscription
alias Plausible.Billing.Subscriptions
alias Plausible.Repo
alias Plausible.Teams
@ -18,6 +21,29 @@ defmodule Plausible.Teams.Billing do
@limit_sites_since ~D[2021-05-05]
@site_limit_for_trials 10
@type cycles_usage() :: %{cycle() => usage_cycle()}
@typep cycle :: :current_cycle | :last_cycle | :penultimate_cycle
@typep usage_cycle :: %{
date_range: Date.Range.t(),
pageviews: non_neg_integer(),
custom_events: non_neg_integer(),
total: non_neg_integer()
}
@typep last_30_days_usage() :: %{:last_30_days => usage_cycle()}
@typep monthly_pageview_usage() :: cycles_usage() | last_30_days_usage()
def get_subscription(nil), do: nil
def get_subscription(%Teams.Team{subscription: %Subscription{} = subscription}),
do: subscription
def get_subscription(%Teams.Team{} = team) do
Teams.with_subscription(team).subscription
end
def change_plan(team, new_plan_id) do
subscription = active_subscription_for(team)
plan = Plausible.Billing.Plans.find(new_plan_id)
@ -33,7 +59,29 @@ defmodule Plausible.Teams.Billing do
with :ok <-
Plausible.Billing.Quota.ensure_within_plan_limits(usage, plan, limit_checking_opts),
do: Plausible.Billing.do_change_plan(subscription, new_plan_id)
do: do_change_plan(subscription, new_plan_id)
end
defp do_change_plan(subscription, new_plan_id) do
res =
Plausible.Billing.paddle_api().update_subscription(subscription.paddle_subscription_id, %{
plan_id: new_plan_id
})
case res do
{:ok, response} ->
amount = :erlang.float_to_binary(response["next_payment"]["amount"] / 1, decimals: 2)
Subscription.changeset(subscription, %{
paddle_plan_id: Integer.to_string(response["plan_id"]),
next_bill_amount: amount,
next_bill_date: response["next_payment"]["date"]
})
|> Repo.update()
e ->
e
end
end
def enterprise_configured?(nil), do: false
@ -65,12 +113,17 @@ defmodule Plausible.Teams.Billing do
|> Repo.exists?()
end
def active_subscription_for(nil), do: nil
def active_subscription_for(team) do
team
|> active_subscription_query()
|> Repo.one()
end
@spec check_needs_to_upgrade(Teams.Team.t() | nil) ::
{:needs_to_upgrade, :no_trial | :no_active_subscription | :grace_period_ended}
| :no_upgrade_needed
def check_needs_to_upgrade(nil), do: {:needs_to_upgrade, :no_trial}
def check_needs_to_upgrade(team) do
@ -97,6 +150,11 @@ defmodule Plausible.Teams.Billing do
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(nil) do
:ok
end
@ -132,6 +190,10 @@ defmodule Plausible.Teams.Billing do
end
end
@doc """
Returns the number of sites the given team owns.
"""
@spec site_usage(Teams.Team.t()) :: non_neg_integer()
def site_usage(nil), do: 0
def site_usage(team) do
@ -169,6 +231,21 @@ defmodule Plausible.Teams.Billing do
end
end
@doc """
Returns a full usage report for the team.
### Options
* `pending_ownership_site_ids` - a list of site IDs from which to count
additional usage. This allows us to look at the total usage from pending
ownerships and owned sites at the same time, which is useful, for example,
when deciding whether to let the team owner upgrade to a plan, or accept a
site ownership.
* `with_features` - when `true`, the returned map will contain features
usage. Also counts usage from `pending_ownership_site_ids` if that option
is given.
"""
def quota_usage(team, opts \\ []) do
team = Teams.with_subscription(team)
with_features? = Keyword.get(opts, :with_features, false)
@ -226,6 +303,31 @@ defmodule Plausible.Teams.Billing do
end
end
@doc """
Queries the ClickHouse database for the monthly pageview usage. If the given team's
subscription is `active`, `past_due`, or a `deleted` (but not yet expired), a map
with the following structure is returned:
```elixir
%{
current_cycle: usage_cycle(),
last_cycle: usage_cycle(),
penultimate_cycle: usage_cycle()
}
```
In all other cases of the subscription status (or a `free_10k` subscription which
does not have a `last_bill_date` defined) - the following structure is returned:
```elixir
%{last_30_days: usage_cycle()}
```
Given only a team as input, the usage is queried from across all the sites that the
team owns. Alternatively, given an optional argument of `site_ids`, the usage from
across all those sites is queried instead.
"""
@spec monthly_pageview_usage(Teams.Team.t(), list() | nil) :: monthly_pageview_usage()
def monthly_pageview_usage(team, site_ids \\ nil)
def monthly_pageview_usage(team, nil) do
@ -251,10 +353,33 @@ defmodule Plausible.Teams.Billing do
end
end
@spec team_member_usage(Teams.Team.t(), Keyword.t()) :: non_neg_integer()
@doc """
Returns the total count of team members associated with the team's sites.
* The given team's owner is not counted as a team member.
* Pending invitations (but not ownership transfers) are counted as team
members even before accepted.
* Users are counted uniquely - i.e. even if an account is associated with
many sites owned by the given user, they still count as one team member.
### Options
* `exclude_emails` - a list of emails to not count towards the usage. This
allows us to exclude a user from being counted as a team member when
checking whether a site invitation can be created for that same user.
* `pending_ownership_site_ids` - a list of site IDs from which to count
additional team member usage. Without this option, usage is queried only
across sites owned by the given user.
"""
def team_member_usage(team, opts \\ [])
def team_member_usage(nil, _), do: 0
def team_member_usage(team, opts) do
{:ok, owner} = Teams.Sites.get_owner(team)
{:ok, owner} = Teams.get_owner(team)
exclude_emails = Keyword.get(opts, :exclude_emails, []) ++ [owner.email]
pending_site_ids = Keyword.get(opts, :pending_ownership_site_ids, [])
@ -324,6 +449,17 @@ defmodule Plausible.Teams.Billing do
}
end
@spec features_usage(Teams.Team.t() | nil, list() | nil) :: [atom()]
@doc """
Given only a team, this function returns the features used across all the
sites this team owns + StatsAPI if any team user has a configured Stats API key.
Given a team, and a list of site_ids, returns the features used by those
sites instead + StatsAPI if any user in the team has a configured Stats API key.
The team can also be passed as `nil`, in which case we will never return
Stats API as a used feature.
"""
def features_usage(team, site_ids \\ nil)
def features_usage(nil, nil), do: []
@ -337,7 +473,7 @@ defmodule Plausible.Teams.Billing do
site_scoped_feature_usage = features_usage(nil, owned_site_ids)
stats_api_used? =
Plausible.Repo.exists?(
Repo.exists?(
from tm in Plausible.Teams.Membership,
as: :team_membership,
where: tm.team_id == ^team.id,
@ -355,8 +491,34 @@ defmodule Plausible.Teams.Billing do
end
end
def features_usage(nil, owned_site_ids) when is_list(owned_site_ids) do
Plausible.Billing.Quota.Usage.features_usage(nil, owned_site_ids)
def features_usage(nil, site_ids) when is_list(site_ids) do
props_usage_q =
from s in Plausible.Site,
where: s.id in ^site_ids and fragment("cardinality(?) > 0", s.allowed_event_props)
revenue_goals_usage_q =
from g in Plausible.Goal,
where: g.site_id in ^site_ids and not is_nil(g.currency)
queries =
on_ee do
funnels_usage_q = from f in "funnels", where: f.site_id in ^site_ids
[
{Feature.Props, props_usage_q},
{Feature.Funnels, funnels_usage_q},
{Feature.RevenueGoals, revenue_goals_usage_q}
]
else
[
{Feature.Props, props_usage_q},
{Feature.RevenueGoals, revenue_goals_usage_q}
]
end
Enum.reduce(queries, [], fn {feature, query}, acc ->
if Repo.exists?(query), do: acc ++ [feature], else: acc
end)
end
defp query_team_member_emails(team, pending_ownership_site_ids, exclude_emails) do

View File

@ -1,6 +1,8 @@
defmodule Plausible.Teams.Invitations do
@moduledoc false
use Plausible
import Ecto.Query
alias Plausible.Billing
@ -274,7 +276,7 @@ defmodule Plausible.Teams.Invitations do
Repo.delete_all(from gm in Teams.GuestMembership, where: gm.id in ^old_guest_ids)
:ok = Teams.Memberships.prune_guests(prior_team)
{:ok, prior_owner} = Teams.Sites.get_owner(prior_team)
{:ok, prior_owner} = Teams.get_owner(prior_team)
{:ok, prior_owner_team_membership} = create_team_membership(team, :guest, prior_owner, now)
@ -312,6 +314,7 @@ defmodule Plausible.Teams.Invitations do
:ok
end
on_ee do
def ensure_can_take_ownership(_site, nil), do: {:error, :no_plan}
def ensure_can_take_ownership(site, team) do
@ -327,6 +330,11 @@ defmodule Plausible.Teams.Invitations do
{:error, :no_plan}
end
end
else
def ensure_can_take_ownership(_site, _team) do
:ok
end
end
def send_transfer_accepted_email(site_transfer) do
PlausibleWeb.Email.ownership_transfer_accepted(

View File

@ -47,23 +47,6 @@ defmodule Plausible.Teams.Sites do
end
end
@spec get_owner(Teams.Team.t()) :: {:ok, Auth.User.t()} | {:error, :no_owner | :multiple_owners}
def get_owner(team) do
owner_query =
from(
tm in Teams.Membership,
inner_join: u in assoc(tm, :user),
where: tm.team_id == ^team.id and tm.role == :owner,
select: u
)
case Repo.all(owner_query) do
[owner_user] -> {:ok, owner_user}
[] -> {:error, :no_owner}
_ -> {:error, :multiple_owners}
end
end
@spec list(Auth.User.t(), map(), [list_opt()]) :: Scrivener.Page.t()
def list(user, pagination_params, opts \\ []) do
domain_filter = Keyword.get(opts, :filter_by_domain)

View File

@ -189,7 +189,7 @@ defmodule PlausibleWeb.Components.Billing do
</div>
<.styled_link
:if={
not (Plausible.Teams.Adapter.Read.Billing.enterprise_configured?(@user) &&
not (Plausible.Teams.Billing.enterprise_configured?(@team) &&
Subscriptions.halted?(@subscription))
}
id="#upgrade-or-change-plan-link"

View File

@ -71,7 +71,7 @@ defmodule PlausibleWeb.Components.Billing.Notice do
def premium_feature(assigns) do
~H"""
<.notice
:if={@feature_mod.check_availability(@billable_user) !== :ok}
:if={@feature_mod.check_availability(@current_team) !== :ok}
class="rounded-t-md rounded-b-none"
title="Notice"
{@rest}

View File

@ -175,7 +175,7 @@ defmodule PlausibleWeb.Components.Billing.PlanBox do
change_plan_link_text = change_plan_link_text(assigns)
subscription =
Plausible.Teams.Adapter.Read.Billing.get_subscription(assigns.current_user)
Plausible.Teams.Billing.get_subscription(assigns.current_team)
billing_details_expired =
Subscription.Status.in?(subscription, [
@ -264,22 +264,18 @@ defmodule PlausibleWeb.Components.Billing.PlanBox do
defp check_usage_within_plan_limits(%{
available: true,
usage: usage,
current_user: current_user,
current_team: current_team,
plan_to_render: plan
}) do
# At this point, the user is *not guaranteed* to have a `trial_expiry_date`,
# because in the past we've let users upgrade without that constraint, as
# well as transfer sites to those accounts. to these accounts we won't be
# offering an extra pageview limit allowance margin though.
invited_user? = is_nil(Plausible.Teams.Adapter.Read.Teams.trial_expiry_date(current_user))
# At this point, the user is *not guaranteed* to have a team,
# with ongoing trial.
trial_active_or_ended_recently? =
not invited_user? &&
Plausible.Teams.Adapter.Read.Teams.trial_days_left(current_user) >= -10
not is_nil(current_team) and not is_nil(current_team.trial_expiry_date) and
Plausible.Teams.trial_days_left(current_team) >= -10
limit_checking_opts =
cond do
Plausible.Teams.Adapter.Read.Billing.allow_next_upgrade_override?(current_user) ->
current_team && current_team.allow_next_upgrade_override ->
[ignore_pageview_limit: true]
trial_active_or_ended_recently? && plan.volume == "10k" ->

View File

@ -9,16 +9,15 @@ defmodule PlausibleWeb.BillingController do
plug PlausibleWeb.RequireAccountPlug
def ping_subscription(%Plug.Conn{} = conn, _params) do
subscribed? =
Plausible.Teams.Adapter.Read.Billing.has_active_subscription?(conn.assigns.current_user)
subscribed? = Plausible.Teams.Billing.has_active_subscription?(conn.assigns.current_team)
json(conn, %{is_subscribed: subscribed?})
end
def choose_plan(conn, _params) do
current_user = conn.assigns.current_user
current_team = conn.assigns.current_team
if Plausible.Teams.Adapter.Read.Billing.enterprise_configured?(current_user) do
if Plausible.Teams.Billing.enterprise_configured?(current_team) do
redirect(conn, to: Routes.billing_path(conn, :upgrade_to_enterprise_plan))
else
render(conn, "choose_plan.html",
@ -29,9 +28,8 @@ defmodule PlausibleWeb.BillingController do
end
def upgrade_to_enterprise_plan(conn, _params) do
current_user = conn.assigns.current_user
current_team = conn.assigns.current_team
subscription = Plausible.Teams.Adapter.Read.Billing.get_subscription(current_user)
subscription = Plausible.Teams.Billing.get_subscription(current_team)
{latest_enterprise_plan, price} =
Plausible.Teams.Billing.latest_enterprise_plan_with_price(
@ -72,8 +70,9 @@ defmodule PlausibleWeb.BillingController do
end
def change_plan_preview(conn, %{"plan_id" => new_plan_id}) do
current_team = conn.assigns.current_team
current_user = conn.assigns.current_user
subscription = Plausible.Teams.Adapter.Read.Billing.active_subscription_for(current_user)
subscription = Plausible.Teams.Billing.active_subscription_for(current_team)
case preview_subscription(subscription, new_plan_id) do
{:ok, {subscription, preview_info}} ->
@ -92,6 +91,7 @@ defmodule PlausibleWeb.BillingController do
extra: %{
message: msg,
new_plan_id: new_plan_id,
current_team: current_team.id,
user_id: current_user.id
}
)
@ -103,9 +103,9 @@ defmodule PlausibleWeb.BillingController do
end
def change_plan(conn, %{"new_plan_id" => new_plan_id}) do
current_user = conn.assigns.current_user
current_team = conn.assigns.current_team
case Plausible.Teams.Adapter.Read.Billing.change_plan(current_user, new_plan_id) do
case Plausible.Teams.Billing.change_plan(current_team, new_plan_id) do
{:ok, _subscription} ->
conn
|> put_flash(:success, "Plan changed successfully")
@ -133,7 +133,8 @@ defmodule PlausibleWeb.BillingController do
errors: inspect(e),
message: msg,
new_plan_id: new_plan_id,
user_id: current_user.id
current_team: current_team.id,
user_id: conn.assigns.current_user.id
}
)

View File

@ -4,6 +4,7 @@ defmodule PlausibleWeb.SettingsController do
alias Plausible.Auth
alias PlausibleWeb.UserAuth
alias Plausible.Teams
require Logger
@ -20,24 +21,23 @@ defmodule PlausibleWeb.SettingsController do
end
def subscription(conn, _params) do
current_user = conn.assigns.current_user
subscription = Plausible.Teams.Adapter.Read.Billing.get_subscription(current_user)
current_team = conn.assigns.current_team
subscription = Teams.Billing.get_subscription(current_team)
render(conn, :subscription,
layout: {PlausibleWeb.LayoutView, :settings},
subscription: subscription,
pageview_limit: Plausible.Teams.Adapter.Read.Billing.monthly_pageview_limit(current_user),
pageview_usage: Plausible.Teams.Adapter.Read.Billing.monthly_pageview_usage(current_user),
site_usage: Plausible.Teams.Adapter.Read.Billing.site_usage(current_user),
site_limit: Plausible.Teams.Adapter.Read.Billing.site_limit(current_user),
team_member_limit: Plausible.Teams.Adapter.Read.Billing.team_member_limit(current_user),
team_member_usage: Plausible.Teams.Adapter.Read.Billing.team_member_usage(current_user)
pageview_limit: Teams.Billing.monthly_pageview_limit(subscription),
pageview_usage: Teams.Billing.monthly_pageview_usage(current_team),
site_usage: Teams.Billing.site_usage(current_team),
site_limit: Teams.Billing.site_limit(current_team),
team_member_limit: Teams.Billing.team_member_limit(current_team),
team_member_usage: Teams.Billing.team_member_usage(current_team)
)
end
def invoices(conn, _params) do
subscription =
Plausible.Teams.Adapter.Read.Billing.get_subscription(conn.assigns.current_user)
subscription = Teams.Billing.get_subscription(conn.assigns.current_team)
invoices = Plausible.Billing.paddle_api().get_invoices(subscription)
render(conn, :invoices, layout: {PlausibleWeb.LayoutView, :settings}, invoices: invoices)

View File

@ -26,11 +26,11 @@ defmodule PlausibleWeb.Site.MembershipController do
def invite_member_form(conn, _params) do
site =
conn.assigns.current_user
|> Plausible.Teams.Adapter.Read.Sites.get_for_user!(conn.assigns.site.domain)
|> Plausible.Sites.get_for_user!(conn.assigns.site.domain)
|> Plausible.Repo.preload(:owner)
limit = Plausible.Teams.Adapter.Read.Billing.team_member_limit(site.owner)
usage = Plausible.Teams.Adapter.Read.Billing.team_member_usage(site.owner)
limit = Plausible.Teams.Billing.team_member_limit(site.team)
usage = Plausible.Teams.Billing.team_member_usage(site.team)
below_limit? = Plausible.Billing.Quota.below_limit?(usage, limit)
render(
@ -47,7 +47,7 @@ defmodule PlausibleWeb.Site.MembershipController do
site_domain = conn.assigns.site.domain
site =
Plausible.Teams.Adapter.Read.Sites.get_for_user!(conn.assigns.current_user, site_domain)
Plausible.Sites.get_for_user!(conn.assigns.current_user, site_domain)
|> Plausible.Repo.preload(:owner)
case Memberships.create_invitation(site, conn.assigns.current_user, email, role) do
@ -96,7 +96,7 @@ defmodule PlausibleWeb.Site.MembershipController do
site_domain = conn.assigns.site.domain
site =
Plausible.Teams.Adapter.Read.Sites.get_for_user!(conn.assigns.current_user, site_domain)
Plausible.Sites.get_for_user!(conn.assigns.current_user, site_domain)
render(
conn,
@ -110,7 +110,7 @@ defmodule PlausibleWeb.Site.MembershipController do
site_domain = conn.assigns.site.domain
site =
Plausible.Teams.Adapter.Read.Sites.get_for_user!(conn.assigns.current_user, site_domain)
Plausible.Sites.get_for_user!(conn.assigns.current_user, site_domain)
case Memberships.create_invitation(site, conn.assigns.current_user, email, :owner) do
{:ok, _invitation} ->

View File

@ -14,21 +14,21 @@ defmodule PlausibleWeb.SiteController do
def new(conn, params) do
flow = params["flow"] || PlausibleWeb.Flows.register()
current_user = conn.assigns[:current_user]
current_team = conn.assigns.current_team
render(conn, "new.html",
changeset: Plausible.Site.changeset(%Plausible.Site{}),
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,
site_limit: Plausible.Teams.Billing.site_limit(current_team),
site_limit_exceeded?: Plausible.Teams.Billing.ensure_can_add_new_site(current_team) != :ok,
form_submit_url: "/sites?flow=#{flow}",
flow: flow
)
end
def create_site(conn, %{"site" => site_params}) do
user = conn.assigns[:current_user]
first_site? = Plausible.Teams.Adapter.Read.Billing.site_usage(user) == 0
team = conn.assigns.current_team
user = conn.assigns.current_user
first_site? = Plausible.Teams.Billing.site_usage(team) == 0
flow = conn.params["flow"]
case Sites.create(user, site_params) do
@ -46,7 +46,7 @@ defmodule PlausibleWeb.SiteController do
)
)
{:error, {:over_limit, limit}} ->
{:error, _, {:over_limit, limit}, _} ->
render(conn, "new.html",
changeset: Plausible.Site.changeset(%Plausible.Site{}),
first_site?: first_site?,
@ -60,7 +60,7 @@ defmodule PlausibleWeb.SiteController do
render(conn, "new.html",
changeset: changeset,
first_site?: first_site?,
site_limit: Plausible.Teams.Adapter.Read.Billing.site_limit(user),
site_limit: Plausible.Teams.Billing.site_limit(team),
site_limit_exceeded?: false,
flow: flow,
form_submit_url: "/sites?flow=#{flow}"
@ -122,11 +122,10 @@ defmodule PlausibleWeb.SiteController do
end
def settings_people(conn, _params) do
current_user = conn.assigns.current_user
site = conn.assigns.site
%{memberships: memberships, invitations: invitations} =
Plausible.Teams.Adapter.Read.Sites.list_people(site, current_user)
Sites.list_people(site)
conn
|> render("settings_people.html",

View File

@ -94,7 +94,13 @@ defmodule PlausibleWeb.Email do
end
def trial_upgrade_email(user, day, usage) do
suggested_plan = Plausible.Billing.Plans.suggest(user, usage.total)
team =
case Plausible.Teams.get_by_owner(user) do
{:ok, team} -> team
_ -> nil
end
suggested_plan = Plausible.Billing.Plans.suggest(team, usage.total)
base_email()
|> to(user)

View File

@ -18,20 +18,20 @@ defmodule PlausibleWeb.Live.ChoosePlan do
socket
|> assign_new(:pending_ownership_site_ids, fn %{current_user: current_user} ->
current_user.email
|> Plausible.Teams.Adapter.Read.Ownership.all_pending_site_transfers(current_user)
|> Plausible.Teams.Memberships.all_pending_site_transfers()
|> Enum.map(& &1.site_id)
end)
|> assign_new(:usage, fn %{
current_user: current_user,
current_team: current_team,
pending_ownership_site_ids: pending_ownership_site_ids
} ->
Plausible.Teams.Adapter.Read.Billing.quota_usage(current_user,
Plausible.Teams.Billing.quota_usage(current_team,
with_features: true,
pending_ownership_site_ids: pending_ownership_site_ids
)
end)
|> assign_new(:subscription, fn %{current_user: current_user} ->
Plausible.Teams.Adapter.Read.Billing.get_subscription(current_user)
|> assign_new(:subscription, fn %{current_team: current_team} ->
Plausible.Teams.Billing.get_subscription(current_team)
end)
|> assign_new(:owned_plan, fn %{subscription: subscription} ->
Plans.get_regular_plan(subscription, only_non_expired: true)

View File

@ -16,7 +16,7 @@ defmodule PlausibleWeb.Live.GoalSettings do
socket
|> assign_new(:site, fn %{current_user: current_user} ->
current_user
|> Plausible.Teams.Adapter.Read.Sites.get_for_user!(domain, [:owner, :admin, :super_admin])
|> Plausible.Sites.get_for_user!(domain, [:owner, :admin, :super_admin])
|> Plausible.Imported.load_import_data()
end)
|> assign_new(:all_goals, fn %{site: site} ->

View File

@ -15,7 +15,7 @@ defmodule PlausibleWeb.Live.ImportsExportsSettings do
socket =
socket
|> assign_new(:site, fn %{current_user: current_user} ->
Plausible.Teams.Adapter.Read.Sites.get_for_user!(current_user, domain, [
Plausible.Sites.get_for_user!(current_user, domain, [
:owner,
:admin,
:super_admin

View File

@ -32,7 +32,7 @@ defmodule PlausibleWeb.Live.Installation do
socket
) do
site =
Plausible.Teams.Adapter.Read.Sites.get_for_user!(socket.assigns.current_user, domain, [
Plausible.Sites.get_for_user!(socket.assigns.current_user, domain, [
:owner,
:admin,
:super_admin,

View File

@ -11,7 +11,7 @@ defmodule PlausibleWeb.Live.Plugins.API.Settings do
socket =
socket
|> assign_new(:site, fn %{current_user: current_user} ->
Plausible.Teams.Adapter.Read.Sites.get_for_user!(current_user, domain, [
Plausible.Sites.get_for_user!(current_user, domain, [
:owner,
:admin,
:super_admin

View File

@ -19,7 +19,7 @@ defmodule PlausibleWeb.Live.Plugins.API.TokenForm do
socket =
socket
|> assign_new(:site, fn %{current_user: current_user} ->
Plausible.Teams.Adapter.Read.Sites.get_for_user!(current_user, domain, [
Plausible.Sites.get_for_user!(current_user, domain, [
:owner,
:admin,
:super_admin

View File

@ -11,7 +11,7 @@ defmodule PlausibleWeb.Live.PropsSettings do
socket =
socket
|> assign_new(:site, fn %{current_user: current_user} ->
Plausible.Teams.Adapter.Read.Sites.get_for_user!(current_user, domain, [
Plausible.Sites.get_for_user!(current_user, domain, [
:owner,
:admin,
:super_admin

View File

@ -18,7 +18,7 @@ defmodule PlausibleWeb.Live.PropsSettings.Form do
socket =
socket
|> assign_new(:site, fn %{current_user: current_user} ->
Plausible.Teams.Adapter.Read.Sites.get_for_user!(current_user, domain, [
Plausible.Sites.get_for_user!(current_user, domain, [
:owner,
:admin,
:super_admin

View File

@ -14,7 +14,7 @@ defmodule PlausibleWeb.Live.Shields.Countries do
socket =
socket
|> assign_new(:site, fn %{current_user: current_user} ->
Plausible.Teams.Adapter.Read.Sites.get_for_user!(current_user, domain, [
Plausible.Sites.get_for_user!(current_user, domain, [
:owner,
:admin,
:super_admin

View File

@ -10,7 +10,7 @@ defmodule PlausibleWeb.Live.Shields.Hostnames do
socket =
socket
|> assign_new(:site, fn %{current_user: current_user} ->
Plausible.Teams.Adapter.Read.Sites.get_for_user!(current_user, domain, [
Plausible.Sites.get_for_user!(current_user, domain, [
:owner,
:admin,
:super_admin

View File

@ -17,7 +17,7 @@ defmodule PlausibleWeb.Live.Shields.IPAddresses do
socket =
socket
|> assign_new(:site, fn %{current_user: current_user} ->
Plausible.Teams.Adapter.Read.Sites.get_for_user!(current_user, domain, [
Plausible.Sites.get_for_user!(current_user, domain, [
:owner,
:admin,
:super_admin

View File

@ -10,7 +10,7 @@ defmodule PlausibleWeb.Live.Shields.Pages do
socket =
socket
|> assign_new(:site, fn %{current_user: current_user} ->
Plausible.Teams.Adapter.Read.Sites.get_for_user!(current_user, domain, [
Plausible.Sites.get_for_user!(current_user, domain, [
:owner,
:admin,
:super_admin

View File

@ -27,10 +27,14 @@ defmodule PlausibleWeb.Live.Sites do
|> assign(:params, params)
|> load_sites()
|> assign_new(:has_sites?, fn %{current_user: current_user} ->
has_sites?(current_user)
Plausible.Teams.Users.has_sites?(current_user, include_pending?: true)
end)
|> assign_new(:needs_to_upgrade, fn %{current_user: current_user, sites: sites} ->
owns_sites?(current_user, sites) && check_needs_to_upgrade(current_user)
|> assign_new(:needs_to_upgrade, fn %{
current_user: current_user,
current_team: current_team
} ->
Plausible.Teams.Users.owns_sites?(current_user, include_pending?: true) &&
Plausible.Teams.Billing.check_needs_to_upgrade(current_team)
end)
{:noreply, socket}
@ -640,12 +644,6 @@ defmodule PlausibleWeb.Live.Sites do
{:noreply, socket}
end
defdelegate has_sites?(user), to: Plausible.Teams.Adapter.Read.Ownership
defdelegate owns_sites?(user, sites), to: Plausible.Teams.Adapter.Read.Ownership
defdelegate check_needs_to_upgrade(user), to: Plausible.Teams.Adapter.Read.Billing
defp load_sites(%{assigns: assigns} = socket) do
sites =
Sites.list_with_invitations(assigns.current_user, assigns.params,
@ -680,7 +678,13 @@ defmodule PlausibleWeb.Live.Sites do
end
defp check_limits(%{role: :owner, site: site} = invitation, user) do
case ensure_can_take_ownership(site, user) do
team =
case Plausible.Teams.get_by_owner(user) do
{:ok, team} -> team
_ -> nil
end
case ensure_can_take_ownership(site, team) do
:ok ->
check_features(invitation, user)
@ -695,9 +699,7 @@ defmodule PlausibleWeb.Live.Sites do
defp check_limits(invitation, _), do: %{invitation: invitation}
defdelegate ensure_can_take_ownership(site, user), to: Plausible.Teams.Adapter.Read.Ownership
defdelegate check_feature_access(site, user), to: Plausible.Teams.Adapter.Read.Ownership
defdelegate ensure_can_take_ownership(site, team), to: Plausible.Teams.Invitations
def check_features(%{role: :owner, site: site} = invitation, user) do
case check_feature_access(site, user) do
@ -714,6 +716,24 @@ defmodule PlausibleWeb.Live.Sites do
end
end
on_ee do
defp check_feature_access(site, new_owner) do
missing_features =
Plausible.Teams.Billing.features_usage(nil, [site.id])
|> Enum.filter(&(&1.check_availability(new_owner) != :ok))
if missing_features == [] do
:ok
else
{:error, {:missing_features, missing_features}}
end
end
else
defp check_feature_access(_site, _new_owner) do
:ok
end
end
defp set_filter_text(socket, filter_text) do
uri = socket.assigns.uri

View File

@ -18,7 +18,7 @@ defmodule PlausibleWeb.Live.Verification do
socket
) do
site =
Plausible.Teams.Adapter.Read.Sites.get_for_user!(socket.assigns.current_user, domain, [
Plausible.Sites.get_for_user!(socket.assigns.current_user, domain, [
:owner,
:admin,
:super_admin,

View File

@ -134,6 +134,12 @@ defmodule PlausibleWeb.Plugs.AuthorizePublicAPI do
end
defp verify_site_access(api_key, site) do
team =
case Plausible.Teams.get_by_owner(api_key.user) do
{:ok, team} -> team
_ -> nil
end
is_member? = Plausible.Teams.Memberships.site_member?(site, api_key.user)
is_super_admin? = Auth.is_super_admin?(api_key.user_id)
@ -144,8 +150,7 @@ defmodule PlausibleWeb.Plugs.AuthorizePublicAPI do
Sites.locked?(site) ->
{:error, :site_locked}
Plausible.Teams.Adapter.Read.Billing.check_feature_availability_for_stats_api(api_key.user) !==
:ok ->
Plausible.Billing.Feature.StatsAPI.check_availability(team) !== :ok ->
{:error, :upgrade_required}
is_member? ->

View File

@ -2,28 +2,18 @@
<%= render("_flash.html", assigns) %>
<% end %>
<%= if @conn.assigns[:current_team] do %>
<div class="flex flex-col gap-y-2">
<div :if={assigns[:current_team]} class="flex flex-col gap-y-2">
<Notice.active_grace_period
:if={Plausible.Auth.GracePeriod.active?(@conn.assigns.current_team)}
enterprise?={
Plausible.Teams.Adapter.Read.Billing.enterprise_configured?(@conn.assigns.current_team)
}
grace_period_end={grace_period_end(@conn.assigns.current_team)}
:if={Plausible.Auth.GracePeriod.active?(@current_team)}
enterprise?={Plausible.Teams.Billing.enterprise_configured?(@current_team)}
grace_period_end={grace_period_end(@current_team)}
/>
<Notice.dashboard_locked :if={Plausible.Auth.GracePeriod.expired?(@conn.assigns.current_team)} />
<Notice.dashboard_locked :if={Plausible.Auth.GracePeriod.expired?(@current_team)} />
<Notice.subscription_cancelled subscription={@conn.assigns.current_team.subscription} />
<Notice.subscription_cancelled subscription={@current_team.subscription} />
<Notice.subscription_past_due
subscription={@conn.assigns.current_team.subscription}
class="container"
/>
<Notice.subscription_past_due subscription={@current_team.subscription} class="container" />
<Notice.subscription_paused
subscription={@conn.assigns.current_team.subscription}
class="container"
/>
<Notice.subscription_paused subscription={@current_team.subscription} class="container" />
</div>
<% end %>

View File

@ -16,7 +16,7 @@
<.filter_bar filtering_enabled?={false}>
<.button_link
:if={Plausible.Billing.Feature.StatsAPI.check_availability(@current_user) == :ok}
:if={Plausible.Billing.Feature.StatsAPI.check_availability(@current_team) == :ok}
href={Routes.settings_path(@conn, :new_api_key)}
>
New API Key

View File

@ -33,7 +33,7 @@
<div class="flex flex-col items-center justify-between sm:flex-row sm:items-start">
<PlausibleWeb.Components.Billing.monthly_quota_box
user={@current_user}
team={@current_team}
subscription={@subscription}
/>
<div class="w-full md:w-1/3 h-32 px-2 py-4 my-4 text-center bg-gray-100 rounded dark:bg-gray-900">

View File

@ -8,7 +8,7 @@ defmodule PlausibleWeb.AuthView do
def subscription_quota(nil, _options), do: "Free trial"
def subscription_quota(subscription, options) do
pageview_limit = Plausible.Billing.Quota.Limits.monthly_pageview_limit(subscription)
pageview_limit = Plausible.Teams.Billing.monthly_pageview_limit(subscription)
quota =
if pageview_limit == :unlimited do

View File

@ -3,7 +3,7 @@ defmodule Plausible.Workers.CheckUsage do
use Oban.Worker, queue: :check_usage
require Plausible.Billing.Subscription.Status
alias Plausible.Billing.{Subscription, Quota}
alias Plausible.Auth.User
alias Plausible.Teams
defmacro yesterday() do
quote do
@ -33,26 +33,17 @@ defmodule Plausible.Workers.CheckUsage do
end
@impl Oban.Worker
def perform(_job, usage_mod \\ Quota.Usage, today \\ Date.utc_today()) do
def perform(_job, usage_mod \\ Teams.Billing, today \\ Date.utc_today()) do
yesterday = today |> Date.shift(day: -1)
last_subscription_query =
from(s in Subscription,
order_by: [desc: s.inserted_at],
where: s.user_id == parent_as(:user).id,
limit: 1
)
active_subscribers =
Repo.all(
from(u in User,
as: :user,
inner_join: s in Plausible.Billing.Subscription,
on: s.user_id == u.id,
inner_lateral_join: ls in subquery(last_subscription_query),
on: ls.id == s.id,
left_join: ep in Plausible.Billing.EnterprisePlan,
on: ep.user_id == u.id,
from(t in Teams.Team,
as: :team,
inner_join: o in assoc(t, :owner),
inner_lateral_join: s in subquery(Teams.last_subscription_join_query()),
on: true,
left_join: ep in assoc(t, :enterprise_plan),
where:
s.status in [
^Subscription.Status.active(),
@ -65,8 +56,8 @@ defmodule Plausible.Workers.CheckUsage do
where:
least(day_of_month(s.last_bill_date), day_of_month(last_day_of_month(^yesterday))) ==
day_of_month(^yesterday),
order_by: u.id,
preload: [subscription: s, enterprise_plan: ep]
order_by: t.id,
preload: [subscription: s, enterprise_plan: ep, owner: o]
)
)
@ -91,7 +82,8 @@ defmodule Plausible.Workers.CheckUsage do
defp check_site_usage_for_enterprise(subscriber) do
limit = subscriber.enterprise_plan.site_limit
usage = Quota.Usage.site_usage(subscriber)
usage = Teams.Billing.site_usage(subscriber)
if Quota.below_limit?(usage, limit) do
{:below_limit, {usage, limit}}
@ -103,7 +95,7 @@ defmodule Plausible.Workers.CheckUsage do
def maybe_remove_grace_period(subscriber, usage_mod) do
case check_pageview_usage_last_cycle(subscriber, usage_mod) do
{:below_limit, _} ->
Plausible.Users.remove_grace_period(subscriber)
Plausible.Users.remove_grace_period(subscriber.owner)
:ok
_ ->
@ -117,10 +109,10 @@ defmodule Plausible.Workers.CheckUsage do
suggested_plan =
Plausible.Billing.Plans.suggest(subscriber, pageview_usage.last_cycle.total)
PlausibleWeb.Email.over_limit_email(subscriber, pageview_usage, suggested_plan)
PlausibleWeb.Email.over_limit_email(subscriber.owner, pageview_usage, suggested_plan)
|> Plausible.Mailer.send()
Plausible.Users.start_grace_period(subscriber)
Plausible.Users.start_grace_period(subscriber.owner)
_ ->
nil
@ -137,20 +129,20 @@ defmodule Plausible.Workers.CheckUsage do
{{_, pageview_usage}, {_, {site_usage, site_allowance}}} ->
PlausibleWeb.Email.enterprise_over_limit_internal_email(
subscriber,
subscriber.owner,
pageview_usage,
site_usage,
site_allowance
)
|> Plausible.Mailer.send()
Plausible.Users.start_manual_lock_grace_period(subscriber)
Plausible.Users.start_manual_lock_grace_period(subscriber.owner)
end
end
defp check_pageview_usage_two_cycles(subscriber, usage_mod) do
usage = usage_mod.monthly_pageview_usage(subscriber)
limit = Quota.Limits.monthly_pageview_limit(subscriber.subscription)
limit = Teams.Billing.monthly_pageview_limit(subscriber.subscription)
if Quota.exceeds_last_two_usage_cycles?(usage, limit) do
{:over_limit, usage}
@ -161,7 +153,7 @@ defmodule Plausible.Workers.CheckUsage do
defp check_pageview_usage_last_cycle(subscriber, usage_mod) do
usage = usage_mod.monthly_pageview_usage(subscriber)
limit = Quota.Limits.monthly_pageview_limit(subscriber.subscription)
limit = Teams.Billing.monthly_pageview_limit(subscriber.subscription)
if :last_cycle in Quota.exceeded_cycles(usage, limit) do
{:over_limit, usage}

View File

@ -5,40 +5,43 @@ defmodule Plausible.Workers.SendTrialNotifications do
queue: :trial_notification_emails,
max_attempts: 1
alias Plausible.Teams
require Logger
@impl Oban.Worker
def perform(_job) do
users =
teams =
Repo.all(
from u in Plausible.Auth.User,
left_join: s in Plausible.Billing.Subscription,
on: s.user_id == u.id,
where: not is_nil(u.trial_expiry_date),
from t in Teams.Team,
inner_join: o in assoc(t, :owner),
left_join: s in assoc(t, :subscription),
where: not is_nil(t.trial_expiry_date),
where: is_nil(s.id),
order_by: u.inserted_at
order_by: t.inserted_at,
preload: [owner: o]
)
for user <- users do
case Date.diff(user.trial_expiry_date, Date.utc_today()) do
for team <- teams do
case Date.diff(team.trial_expiry_date, Date.utc_today()) do
7 ->
if Plausible.Auth.has_active_sites?(user, [:owner]) do
send_one_week_reminder(user)
if Teams.has_active_sites?(team) do
send_one_week_reminder(team.owner)
end
1 ->
if Plausible.Auth.has_active_sites?(user, [:owner]) do
send_tomorrow_reminder(user)
if Teams.has_active_sites?(team) do
send_tomorrow_reminder(team.owner, team)
end
0 ->
if Plausible.Auth.has_active_sites?(user, [:owner]) do
send_today_reminder(user)
if Teams.has_active_sites?(team) do
send_today_reminder(team.owner, team)
end
-1 ->
if Plausible.Auth.has_active_sites?(user, [:owner]) do
send_over_reminder(user)
if Teams.has_active_sites?(team) do
send_over_reminder(team.owner)
end
_ ->
@ -54,15 +57,15 @@ defmodule Plausible.Workers.SendTrialNotifications do
|> Plausible.Mailer.send()
end
defp send_tomorrow_reminder(user) do
usage = Plausible.Billing.Quota.Usage.usage_cycle(user, :last_30_days)
defp send_tomorrow_reminder(user, team) do
usage = Plausible.Teams.Billing.usage_cycle(team, :last_30_days)
PlausibleWeb.Email.trial_upgrade_email(user, "tomorrow", usage)
|> Plausible.Mailer.send()
end
defp send_today_reminder(user) do
usage = Plausible.Billing.Quota.Usage.usage_cycle(user, :last_30_days)
defp send_today_reminder(user, team) do
usage = Plausible.Teams.Billing.usage_cycle(team, :last_30_days)
PlausibleWeb.Email.trial_upgrade_email(user, "today", usage)
|> Plausible.Mailer.send()

View File

@ -49,14 +49,12 @@ defmodule Plausible.AuthTest do
user_with_plan_no_subscription =
new_user() |> subscribe_to_enterprise_plan(subscription?: false)
assert Plausible.Teams.Adapter.Read.Billing.enterprise_configured?(user_with_plan)
assert Plausible.Teams.Billing.enterprise_configured?(team_of(user_with_plan))
assert Plausible.Teams.Adapter.Read.Billing.enterprise_configured?(
user_with_plan_no_subscription
)
assert Plausible.Teams.Billing.enterprise_configured?(team_of(user_with_plan_no_subscription))
refute Plausible.Teams.Adapter.Read.Billing.enterprise_configured?(user_without_plan)
refute Plausible.Teams.Adapter.Read.Billing.enterprise_configured?(nil)
refute Plausible.Teams.Billing.enterprise_configured?(team_of(user_without_plan))
refute Plausible.Teams.Billing.enterprise_configured?(nil)
end
describe "create_api_key/3" do
@ -78,7 +76,7 @@ defmodule Plausible.AuthTest do
@tag :ce_build_only
test "defaults to 1000000 requests per hour limit in CE" do
user = insert(:user)
user = new_user()
{:ok, %Auth.ApiKey{hourly_request_limit: hourly_request_limit}} =
Auth.create_api_key(user, "my new CE key", Ecto.UUID.generate())

View File

@ -8,79 +8,94 @@ defmodule Plausible.BillingTest do
describe "check_needs_to_upgrade" do
test "is false for a trial user" do
user = insert(:user)
assert Billing.check_needs_to_upgrade(user) == :no_upgrade_needed
team = new_user() |> team_of()
assert Plausible.Teams.Billing.check_needs_to_upgrade(team) == :no_upgrade_needed
end
test "is true for a user with an expired trial" do
user = insert(:user, trial_expiry_date: Timex.shift(Timex.today(), days: -1))
team = new_user(trial_expiry_date: Date.shift(Date.utc_today(), day: -1)) |> team_of()
assert Billing.check_needs_to_upgrade(user) == {:needs_to_upgrade, :no_active_subscription}
assert Plausible.Teams.Billing.check_needs_to_upgrade(team) ==
{:needs_to_upgrade, :no_active_subscription}
end
test "is true for a user with empty trial expiry date" do
user = insert(:user, trial_expiry_date: nil)
assert Billing.check_needs_to_upgrade(user) == {:needs_to_upgrade, :no_trial}
assert Plausible.Teams.Billing.check_needs_to_upgrade(nil) ==
{:needs_to_upgrade, :no_trial}
end
test "is false for user with empty trial expiry date but with an active subscription" do
user = insert(:user, trial_expiry_date: nil)
insert(:subscription, user: user)
team =
new_user()
|> subscribe_to_growth_plan()
|> team_of()
|> Ecto.Changeset.change(trial_expiry_date: nil)
|> Repo.update!()
assert Billing.check_needs_to_upgrade(user) == :no_upgrade_needed
assert Plausible.Teams.Billing.check_needs_to_upgrade(team) == :no_upgrade_needed
end
test "is false for a user with an expired trial but an active subscription" do
user = insert(:user, trial_expiry_date: Timex.shift(Timex.today(), days: -1))
insert(:subscription, user: user)
team =
new_user(trial_expiry_date: Date.shift(Date.utc_today(), day: -1))
|> subscribe_to_growth_plan()
|> team_of()
assert Billing.check_needs_to_upgrade(user) == :no_upgrade_needed
assert Plausible.Teams.Billing.check_needs_to_upgrade(team) == :no_upgrade_needed
end
test "is false for a user with a cancelled subscription IF the billing cycle isn't completed yet" do
user = insert(:user, trial_expiry_date: Timex.shift(Timex.today(), days: -1))
insert(:subscription,
user: user,
team =
new_user(trial_expiry_date: Date.shift(Date.utc_today(), day: -1))
|> subscribe_to_growth_plan(
status: Subscription.Status.deleted(),
next_bill_date: Timex.today()
next_bill_date: Date.utc_today()
)
|> team_of()
assert Billing.check_needs_to_upgrade(user) == :no_upgrade_needed
assert Plausible.Teams.Billing.check_needs_to_upgrade(team) == :no_upgrade_needed
end
test "is true for a user with a cancelled subscription IF the billing cycle is complete" do
user = insert(:user, trial_expiry_date: Timex.shift(Timex.today(), days: -1))
insert(:subscription,
user: user,
team =
new_user(trial_expiry_date: Date.shift(Date.utc_today(), day: -1))
|> subscribe_to_growth_plan(
status: Subscription.Status.deleted(),
next_bill_date: Timex.shift(Timex.today(), days: -1)
next_bill_date: Date.shift(Date.utc_today(), day: -1)
)
|> team_of()
assert Billing.check_needs_to_upgrade(user) == {:needs_to_upgrade, :no_active_subscription}
assert Plausible.Teams.Billing.check_needs_to_upgrade(team) ==
{:needs_to_upgrade, :no_active_subscription}
end
test "is true for a deleted subscription if no next_bill_date specified" do
user = insert(:user, trial_expiry_date: Timex.shift(Timex.today(), days: -1))
insert(:subscription,
user: user,
team =
new_user(trial_expiry_date: Date.shift(Date.utc_today(), day: -1))
|> subscribe_to_growth_plan(
status: Subscription.Status.deleted(),
next_bill_date: nil
)
|> team_of()
assert Billing.check_needs_to_upgrade(user) == {:needs_to_upgrade, :no_active_subscription}
assert Plausible.Teams.Billing.check_needs_to_upgrade(team) ==
{:needs_to_upgrade, :no_active_subscription}
end
test "is true for a user past their grace period" do
user = insert(:user, trial_expiry_date: Timex.shift(Timex.today(), days: -1))
insert(:subscription, user: user, next_bill_date: Timex.today())
user =
new_user(trial_expiry_date: Date.shift(Date.utc_today(), day: -1))
|> subscribe_to_growth_plan(
status: Subscription.Status.deleted(),
next_bill_date: Date.utc_today()
)
user = Plausible.Users.end_grace_period(user)
assert Billing.check_needs_to_upgrade(user) == {:needs_to_upgrade, :grace_period_ended}
team = user |> Repo.reload!() |> team_of()
assert Plausible.Teams.Billing.check_needs_to_upgrade(team) ==
{:needs_to_upgrade, :grace_period_ended}
end
end
@ -514,12 +529,11 @@ defmodule Plausible.BillingTest do
describe "change_plan" do
test "sets the next bill amount and date" do
user = insert(:user)
insert(:subscription, user: user)
team = new_user() |> subscribe_to_growth_plan() |> team_of()
Billing.change_plan(user, "123123")
Plausible.Teams.Billing.change_plan(team, "123123")
subscription = Repo.get_by(Plausible.Billing.Subscription, user_id: user.id)
subscription = Repo.get_by(Plausible.Billing.Subscription, team_id: team.id)
assert subscription.paddle_plan_id == "123123"
assert subscription.next_bill_date == ~D[2019-07-10]
assert subscription.next_bill_amount == "6.00"
@ -527,22 +541,33 @@ defmodule Plausible.BillingTest do
end
test "active_subscription_for/1 returns active subscription" do
active = insert(:subscription, user: insert(:user), status: Subscription.Status.active())
paused = insert(:subscription, user: insert(:user), status: Subscription.Status.paused())
user_without_subscription = insert(:user)
active_team =
new_user()
|> subscribe_to_growth_plan(status: Subscription.Status.active())
|> team_of()
|> Plausible.Teams.with_subscription()
assert Billing.active_subscription_for(active.user).id == active.id
assert Billing.active_subscription_for(paused.user) == nil
assert Billing.active_subscription_for(user_without_subscription) == nil
paused_team =
new_user()
|> subscribe_to_growth_plan(status: Subscription.Status.paused())
|> team_of()
assert Plausible.Teams.Billing.active_subscription_for(active_team).id ==
active_team.subscription.id
assert Plausible.Teams.Billing.active_subscription_for(paused_team) == nil
assert Plausible.Teams.Billing.active_subscription_for(nil) == nil
end
test "has_active_subscription?/1 returns whether the user has an active subscription" do
active = insert(:subscription, user: insert(:user), status: Subscription.Status.active())
paused = insert(:subscription, user: insert(:user), status: Subscription.Status.paused())
user_without_subscription = insert(:user)
active_team =
new_user() |> subscribe_to_growth_plan(status: Subscription.Status.active()) |> team_of()
assert Billing.has_active_subscription?(active.user)
refute Billing.has_active_subscription?(paused.user)
refute Billing.has_active_subscription?(user_without_subscription)
paused_team =
new_user() |> subscribe_to_growth_plan(status: Subscription.Status.paused()) |> team_of()
assert Plausible.Teams.Billing.has_active_subscription?(active_team)
refute Plausible.Teams.Billing.has_active_subscription?(paused_team)
refute Plausible.Teams.Billing.has_active_subscription?(nil)
end
end

View File

@ -6,96 +6,88 @@ defmodule Plausible.Billing.FeatureTest do
for mod <- [Plausible.Billing.Feature.Funnels, Plausible.Billing.Feature.RevenueGoals] do
test "#{mod}.check_availability/1 returns :ok when site owner is on a enterprise plan" do
user =
team =
new_user()
|> subscribe_to_enterprise_plan(paddle_plan_id: "123321", features: [unquote(mod)])
|> team_of()
assert :ok == unquote(mod).check_availability(user)
assert :ok == unquote(mod).check_availability(team)
end
test "#{mod}.check_availability/1 returns :ok when site owner is on a business plan" do
user = new_user() |> subscribe_to_business_plan()
assert :ok == unquote(mod).check_availability(user)
team = new_user() |> subscribe_to_business_plan() |> team_of()
assert :ok == unquote(mod).check_availability(team)
end
test "#{mod}.check_availability/1 returns error when site owner is on a growth plan" do
user = new_user() |> subscribe_to_growth_plan()
assert {:error, :upgrade_required} == unquote(mod).check_availability(user)
team = new_user() |> subscribe_to_growth_plan() |> team_of()
assert {:error, :upgrade_required} == unquote(mod).check_availability(team)
end
test "#{mod}.check_availability/1 returns error when site owner is on an old plan" do
user = new_user() |> subscribe_to_plan(@v1_plan_id)
assert {:error, :upgrade_required} == unquote(mod).check_availability(user)
team = new_user() |> subscribe_to_plan(@v1_plan_id) |> team_of()
assert {:error, :upgrade_required} == unquote(mod).check_availability(team)
end
end
test "Plausible.Billing.Feature.StatsAPI.check_availability/2 returns :ok when user is on a business plan" do
user = new_user() |> subscribe_to_business_plan()
assert :ok == Plausible.Billing.Feature.StatsAPI.check_availability(user)
team = new_user() |> subscribe_to_business_plan() |> team_of()
assert :ok == Plausible.Billing.Feature.StatsAPI.check_availability(team)
end
test "Plausible.Billing.Feature.StatsAPI.check_availability/2 returns :ok when user is on an old plan" do
user = new_user() |> subscribe_to_plan(@v1_plan_id)
assert :ok == Plausible.Billing.Feature.StatsAPI.check_availability(user)
team = new_user() |> subscribe_to_plan(@v1_plan_id) |> team_of()
assert :ok == Plausible.Billing.Feature.StatsAPI.check_availability(team)
end
test "Plausible.Billing.Feature.StatsAPI.check_availability/2 returns :ok when user is on trial" do
user = new_user()
assert :ok == Plausible.Billing.Feature.StatsAPI.check_availability(user)
team = new_user() |> team_of()
assert :ok == Plausible.Billing.Feature.StatsAPI.check_availability(team)
end
test "Plausible.Billing.Feature.StatsAPI.check_availability/2 returns :ok when user is on an enterprise plan" do
user =
team =
new_user()
|> subscribe_to_enterprise_plan(
paddle_plan_id: "123321",
features: [Plausible.Billing.Feature.StatsAPI]
)
|> team_of()
assert :ok == Plausible.Billing.Feature.StatsAPI.check_availability(user)
assert :ok == Plausible.Billing.Feature.StatsAPI.check_availability(team)
end
@tag :ee_only
test "Plausible.Billing.Feature.StatsAPI.check_availability/2 returns error when user is on a growth plan" do
user = new_user() |> subscribe_to_growth_plan()
team = new_user() |> subscribe_to_growth_plan() |> team_of()
assert {:error, :upgrade_required} ==
Plausible.Billing.Feature.StatsAPI.check_availability(user)
end
test "Plausible.Billing.Feature.StatsAPI.check_availability/2 returns :ok when user trial hasn't started and was created before the business tier launch" do
user = new_user(inserted_at: ~N[2020-01-01T00:00:00], trial_expiry_date: nil)
assert :ok == Plausible.Billing.Feature.StatsAPI.check_availability(user)
end
test "Plausible.Billing.Feature.StatsAPI.check_availability/2 returns :ok if user is subscribed and account was created after business tier launch" do
user = new_user(trial_expiry_date: nil) |> subscribe_to_business_plan()
assert :ok == Plausible.Billing.Feature.StatsAPI.check_availability(user)
Plausible.Billing.Feature.StatsAPI.check_availability(team)
end
@tag :ee_only
test "Plausible.Billing.Feature.StatsAPI.check_availability/2 returns error when user trial hasn't started and was created after the business tier launch" do
user = new_user(trial_expiry_date: nil)
_user = new_user(trial_expiry_date: nil)
assert {:error, :upgrade_required} ==
Plausible.Billing.Feature.StatsAPI.check_availability(user)
Plausible.Billing.Feature.StatsAPI.check_availability(nil)
end
test "Plausible.Billing.Feature.Props.check_availability/1 applies grandfathering to old plans" do
user = new_user() |> subscribe_to_plan(@v1_plan_id)
assert :ok == Plausible.Billing.Feature.Props.check_availability(user)
team = new_user() |> subscribe_to_plan(@v1_plan_id) |> team_of()
assert :ok == Plausible.Billing.Feature.Props.check_availability(team)
end
test "Plausible.Billing.Feature.Goals.check_availability/2 always returns :ok" do
u1 = new_user() |> subscribe_to_plan(@v1_plan_id)
u2 = new_user() |> subscribe_to_growth_plan()
u3 = new_user() |> subscribe_to_business_plan()
u4 = new_user() |> subscribe_to_enterprise_plan(paddle_plan_id: "123321")
t1 = new_user() |> subscribe_to_plan(@v1_plan_id) |> team_of()
t2 = new_user() |> subscribe_to_growth_plan() |> team_of()
t3 = new_user() |> subscribe_to_business_plan() |> team_of()
t4 = new_user() |> subscribe_to_enterprise_plan(paddle_plan_id: "123321") |> team_of()
assert :ok == Plausible.Billing.Feature.Goals.check_availability(u1)
assert :ok == Plausible.Billing.Feature.Goals.check_availability(u2)
assert :ok == Plausible.Billing.Feature.Goals.check_availability(u3)
assert :ok == Plausible.Billing.Feature.Goals.check_availability(u4)
assert :ok == Plausible.Billing.Feature.Goals.check_availability(t1)
assert :ok == Plausible.Billing.Feature.Goals.check_availability(t2)
assert :ok == Plausible.Billing.Feature.Goals.check_availability(t3)
assert :ok == Plausible.Billing.Feature.Goals.check_availability(t4)
end
for {mod, property} <- [

View File

@ -155,22 +155,30 @@ defmodule Plausible.Billing.PlansTest do
end
test "latest_enterprise_plan_with_price/1" do
user = insert(:user)
insert(:enterprise_plan, user: user, paddle_plan_id: "123", inserted_at: Timex.now())
now = NaiveDateTime.utc_now()
user = new_user()
team = team_of(user)
insert(:enterprise_plan,
user: user,
subscribe_to_enterprise_plan(user,
paddle_plan_id: "123",
inserted_at: now,
subscription?: false
)
subscribe_to_enterprise_plan(user,
paddle_plan_id: "456",
inserted_at: Timex.shift(Timex.now(), hours: -10)
inserted_at: NaiveDateTime.shift(now, hour: -10),
subscription?: false
)
insert(:enterprise_plan,
user: user,
subscribe_to_enterprise_plan(user,
paddle_plan_id: "789",
inserted_at: Timex.shift(Timex.now(), minutes: -2)
inserted_at: NaiveDateTime.shift(now, minute: -2),
subscription?: false
)
{enterprise_plan, price} = Plans.latest_enterprise_plan_with_price(user, "127.0.0.1")
{enterprise_plan, price} =
Plausible.Teams.Billing.latest_enterprise_plan_with_price(team, "127.0.0.1")
assert enterprise_plan.paddle_plan_id == "123"
assert price == Money.new(:EUR, "10.0")
@ -210,7 +218,7 @@ defmodule Plausible.Billing.PlansTest do
describe "suggested_plan/2" do
test "returns suggested plan based on usage" do
user = new_user() |> subscribe_to_plan(@v1_plan_id)
team = new_user() |> subscribe_to_plan(@v1_plan_id) |> team_of()
assert %Plausible.Billing.Plan{
monthly_pageview_limit: 100_000,
@ -219,7 +227,7 @@ defmodule Plausible.Billing.PlansTest do
volume: "100k",
yearly_cost: nil,
yearly_product_id: "590752"
} = Plans.suggest(user, 10_000)
} = Plans.suggest(team, 10_000)
assert %Plausible.Billing.Plan{
monthly_pageview_limit: 200_000,
@ -228,21 +236,22 @@ defmodule Plausible.Billing.PlansTest do
volume: "200k",
yearly_cost: nil,
yearly_product_id: "597486"
} = Plans.suggest(user, 100_000)
} = Plans.suggest(team, 100_000)
end
test "returns nil when user has enterprise-level usage" do
user = new_user() |> subscribe_to_plan(@v1_plan_id)
assert :enterprise == Plans.suggest(user, 100_000_000)
team = new_user() |> subscribe_to_plan(@v1_plan_id) |> team_of()
assert :enterprise == Plans.suggest(team, 100_000_000)
end
test "returns nil when user is on an enterprise plan" do
user =
team =
new_user()
|> subscribe_to_plan(@v1_plan_id)
|> subscribe_to_enterprise_plan(billing_interval: :yearly, subscription?: false)
|> team_of()
assert :enterprise == Plans.suggest(user, 10_000)
assert :enterprise == Plans.suggest(team, 10_000)
end
end

View File

@ -24,54 +24,58 @@ defmodule Plausible.Billing.QuotaTest do
@describetag :ee_only
test "returns 50 when user is on an old plan" do
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)
team_on_v1 = new_user() |> subscribe_to_plan(@v1_plan_id) |> team_of()
team_on_v2 = new_user() |> subscribe_to_plan(@v2_plan_id) |> team_of()
team_on_v3 = new_user() |> subscribe_to_plan(@v3_plan_id) |> team_of()
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)
assert 50 == Plausible.Teams.Billing.site_limit(team_on_v1)
assert 50 == Plausible.Teams.Billing.site_limit(team_on_v2)
assert 50 == Plausible.Teams.Billing.site_limit(team_on_v3)
end
test "returns 50 when user is on free_10k plan" do
user = new_user() |> subscribe_to_plan("free_10k")
assert 50 == Plausible.Teams.Adapter.Read.Billing.site_limit(user)
team = new_user() |> subscribe_to_plan("free_10k") |> team_of()
assert 50 == Plausible.Teams.Billing.site_limit(team)
end
test "returns the configured site limit for enterprise plan" do
user = new_user() |> subscribe_to_enterprise_plan(site_limit: 500)
assert Plausible.Teams.Adapter.Read.Billing.site_limit(user) == 500
team = new_user() |> subscribe_to_enterprise_plan(site_limit: 500) |> team_of()
assert Plausible.Teams.Billing.site_limit(team) == 500
end
test "returns 10 when user in on trial" do
user = new_user(trial_expiry_date: Date.shift(Date.utc_today(), day: 7))
assert Plausible.Teams.Adapter.Read.Billing.site_limit(user) == 10
team = new_user(trial_expiry_date: Date.shift(Date.utc_today(), day: 7)) |> team_of()
assert Plausible.Teams.Billing.site_limit(team) == 10
end
test "returns the subscription limit for enterprise users who have not paid yet" do
user =
team =
new_user()
|> subscribe_to_plan(@v1_plan_id)
|> subscribe_to_enterprise_plan(paddle_plan_id: "123321", subscription?: false)
|> team_of()
assert Plausible.Teams.Adapter.Read.Billing.site_limit(user) == 50
assert Plausible.Teams.Billing.site_limit(team) == 50
end
test "returns 10 for enterprise users who have not upgraded yet and are on trial" do
user =
new_user() |> subscribe_to_enterprise_plan(paddle_plan_id: "123321", subscription?: false)
team =
new_user()
|> subscribe_to_enterprise_plan(paddle_plan_id: "123321", subscription?: false)
|> team_of()
assert Plausible.Teams.Adapter.Read.Billing.site_limit(user) == 10
assert Plausible.Teams.Billing.site_limit(team) == 10
end
end
test "site_usage/1 returns the amount of sites the user owns" do
user = insert(:user)
insert_list(3, :site, memberships: [build(:site_membership, user: user, role: :owner)])
insert(:site, memberships: [build(:site_membership, user: user, role: :admin)])
insert(:site, memberships: [build(:site_membership, user: user, role: :viewer)])
user = new_user()
for _ <- 1..3, do: new_site(owner: user)
add_guest(new_site(), user: user, role: :editor)
add_guest(new_site(), user: user, role: :viewer)
team = team_of(user)
assert Quota.Usage.site_usage(user) == 3
assert Plausible.Teams.Billing.site_usage(team) == 3
end
describe "below_limit?/2" do
@ -199,21 +203,33 @@ defmodule Plausible.Billing.QuotaTest do
describe "monthly_pageview_limit/1" 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))
team =
new_user()
|> subscribe_to_plan(@legacy_plan_id)
|> team_of()
|> Plausible.Teams.with_subscription()
assert Quota.Limits.monthly_pageview_limit(user.subscription) == 1_000_000
assert Plausible.Teams.Billing.monthly_pageview_limit(team.subscription) == 1_000_000
end
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))
team =
new_user()
|> subscribe_to_plan(@v1_plan_id)
|> team_of()
|> Plausible.Teams.with_subscription()
assert Quota.Limits.monthly_pageview_limit(user.subscription) == 10_000
assert Plausible.Teams.Billing.monthly_pageview_limit(team.subscription) == 10_000
end
test "free_10k has 10k monthly_pageview_limit" do
user = insert(:user, subscription: build(:subscription, paddle_plan_id: "free_10k"))
team =
new_user()
|> subscribe_to_plan("free_10k")
|> team_of()
|> Plausible.Teams.with_subscription()
assert Quota.Limits.monthly_pageview_limit(user.subscription) == 10_000
assert Plausible.Teams.Billing.monthly_pageview_limit(team.subscription) == 10_000
end
test "is based on the enterprise plan if user is on an enterprise plan" do
@ -226,7 +242,7 @@ defmodule Plausible.Billing.QuotaTest do
|> Repo.preload(:subscription)
|> Map.fetch!(:subscription)
assert Quota.Limits.monthly_pageview_limit(subscription) == 100_000
assert Plausible.Teams.Billing.monthly_pageview_limit(subscription) == 100_000
end
test "does not limit pageviews when user has a pending enterprise plan" do
@ -239,233 +255,169 @@ defmodule Plausible.Billing.QuotaTest do
|> Repo.preload(:subscription)
|> Map.fetch!(:subscription)
assert Quota.Limits.monthly_pageview_limit(subscription) == :unlimited
assert Plausible.Teams.Billing.monthly_pageview_limit(subscription) == :unlimited
end
end
describe "team_member_usage/2" do
test "returns the number of members in all of the sites the user owns" do
me = insert(:user)
me = new_user()
site_i_own_1 = new_site(owner: me)
add_guest(site_i_own_1, role: :viewer)
site_i_own_2 = new_site(owner: me)
add_guest(site_i_own_2, role: :editor)
add_guest(site_i_own_2, role: :viewer)
_site_i_own_3 = new_site(owner: me)
site_i_have_access = new_site()
add_guest(site_i_have_access, user: me, role: :viewer)
add_guest(site_i_have_access, role: :viewer)
add_guest(site_i_have_access, role: :viewer)
add_guest(site_i_have_access, role: :viewer)
team = team_of(me)
_site_i_own_1 =
insert(:site,
memberships: [
build(:site_membership, user: me, role: :owner),
build(:site_membership, user: build(:user), role: :viewer)
]
)
_site_i_own_2 =
insert(:site,
memberships: [
build(:site_membership, user: me, role: :owner),
build(:site_membership, user: build(:user), role: :admin),
build(:site_membership, user: build(:user), role: :viewer)
]
)
_site_i_own_3 =
insert(:site,
memberships: [
build(:site_membership, user: me, role: :owner)
]
)
_site_i_have_access =
insert(:site,
memberships: [
build(:site_membership, user: me, role: :viewer),
build(:site_membership, user: build(:user), role: :viewer),
build(:site_membership, user: build(:user), role: :viewer),
build(:site_membership, user: build(:user), role: :viewer)
]
)
assert Quota.Usage.team_member_usage(me) == 3
assert Plausible.Teams.Billing.team_member_usage(team) == 3
end
test "counts the same email address as one team member" do
me = insert(:user)
joe = insert(:user, email: "joe@plausible.test")
me = new_user()
joe = new_user(email: "joe@plausible.test")
site_i_own_1 = new_site(owner: me)
add_guest(site_i_own_1, user: joe, role: :viewer)
site_i_own_2 = new_site(owner: me)
add_guest(site_i_own_2, user: new_user(), role: :editor)
add_guest(site_i_own_2, user: joe, role: :viewer)
site_i_own_3 = new_site(owner: me)
invite_guest(site_i_own_3, joe, role: :viewer, inviter: me)
team = team_of(me)
_site_i_own_1 =
insert(:site,
memberships: [
build(:site_membership, user: me, role: :owner),
build(:site_membership, user: joe, role: :viewer)
]
)
_site_i_own_2 =
insert(:site,
memberships: [
build(:site_membership, user: me, role: :owner),
build(:site_membership, user: build(:user), role: :admin),
build(:site_membership, user: joe, role: :viewer)
]
)
site_i_own_3 = insert(:site, memberships: [build(:site_membership, user: me, role: :owner)])
insert(:invitation, site: site_i_own_3, inviter: me, email: "joe@plausible.test")
assert Quota.Usage.team_member_usage(me) == 2
assert Plausible.Teams.Billing.team_member_usage(team) == 2
end
test "counts pending invitations as team members" do
me = insert(:user)
member = insert(:user)
me = new_user()
member = new_user()
site_i_own = new_site(owner: me)
add_guest(site_i_own, user: member, role: :editor)
site_i_have_access = new_site()
add_guest(site_i_have_access, user: me, role: :editor)
team = team_of(me)
site_i_own =
insert(:site,
memberships: [
build(:site_membership, user: me, role: :owner),
build(:site_membership, user: member, role: :admin)
]
)
invite_guest(site_i_own, new_user(), role: :viewer, inviter: me)
invite_guest(site_i_own, new_user(), role: :viewer, inviter: member)
invite_guest(site_i_have_access, new_user(), role: :viewer, inviter: me)
site_i_have_access =
insert(:site, memberships: [build(:site_membership, user: me, role: :admin)])
insert(:invitation, site: site_i_own, inviter: me)
insert(:invitation, site: site_i_own, inviter: member)
insert(:invitation, site: site_i_have_access, inviter: me)
assert Quota.Usage.team_member_usage(me) == 3
assert Plausible.Teams.Billing.team_member_usage(team) == 3
end
test "does not count ownership transfer as a team member by default" do
me = insert(:user)
site_i_own = insert(:site, memberships: [build(:site_membership, user: me, role: :owner)])
me = new_user()
site_i_own = new_site(owner: me)
invite_transfer(site_i_own, new_user(), inviter: me)
team = team_of(me)
insert(:invitation, site: site_i_own, inviter: me, role: :owner)
assert Quota.Usage.team_member_usage(me) == 0
assert Plausible.Teams.Billing.team_member_usage(team) == 0
end
test "counts team members from pending ownerships when specified" do
me = insert(:user)
me = new_user()
my_team = team_of(me)
user_1 = insert(:user)
user_2 = insert(:user)
user_1 = new_user()
user_2 = new_user()
pending_ownership_site =
insert(:site,
memberships: [
build(:site_membership, user: user_1, role: :owner),
build(:site_membership, user: user_2, role: :admin)
]
)
pending_ownership_site = new_site(owner: user_1)
add_guest(pending_ownership_site, user: user_2, role: :editor)
insert(:invitation,
site: pending_ownership_site,
inviter: user_1,
email: me.email,
role: :owner
)
invite_transfer(pending_ownership_site, me, inviter: user_1)
assert Quota.Usage.team_member_usage(me,
assert Plausible.Teams.Billing.team_member_usage(my_team,
pending_ownership_site_ids: [pending_ownership_site.id]
) == 2
end
test "counts invitations towards team members from pending ownership sites" do
me = insert(:user)
me = new_user()
user_1 = new_user()
user_2 = new_user()
pending_ownership_site = new_site(owner: user_1)
invite_transfer(pending_ownership_site, me, inviter: user_1)
invite_guest(pending_ownership_site, user_2, role: :editor, inviter: user_1)
team = team_of(me)
user_1 = insert(:user)
user_2 = insert(:user)
pending_ownership_site =
insert(:site,
memberships: [build(:site_membership, user: user_1, role: :owner)]
)
insert(:invitation,
site: pending_ownership_site,
inviter: user_1,
email: me.email,
role: :owner
)
insert(:invitation,
site: pending_ownership_site,
inviter: user_1,
email: user_2.email,
role: :admin
)
assert Quota.Usage.team_member_usage(me,
assert Plausible.Teams.Billing.team_member_usage(team,
pending_ownership_site_ids: [pending_ownership_site.id]
) == 2
end
test "returns zero when user does not have any site" do
me = insert(:user)
assert Quota.Usage.team_member_usage(me) == 0
team = new_user() |> team_of()
assert Plausible.Teams.Billing.team_member_usage(team) == 0
end
test "does not count email report recipients as team members" do
me = insert(:user)
site = insert(:site, memberships: [build(:site_membership, user: me, role: :owner)])
me = new_user()
site = new_site(owner: me)
team = team_of(me)
insert(:weekly_report,
site: site,
recipients: ["adam@plausible.test", "vini@plausible.test"]
)
assert Quota.Usage.team_member_usage(me) == 0
assert Plausible.Teams.Billing.team_member_usage(team) == 0
end
test "excludes specific emails from limit calculation" do
me = insert(:user)
member = insert(:user)
me = new_user()
member = new_user()
site_i_own = new_site(owner: me)
add_guest(site_i_own, user: member, role: :editor)
team = team_of(me)
site_i_own =
insert(:site,
memberships: [
build(:site_membership, user: me, role: :owner),
build(:site_membership, user: member, role: :admin)
]
)
invite_guest(site_i_own, new_user(), role: :viewer, inviter: me)
invite_guest(site_i_own, new_user(), role: :viewer, inviter: member)
invitation = invite_guest(site_i_own, "foo@example.com", role: :viewer, inviter: me)
insert(:invitation, site: site_i_own, inviter: me)
insert(:invitation, site: site_i_own, inviter: member)
invitation = insert(:invitation, site: site_i_own, inviter: me, email: "foo@example.com")
assert Plausible.Teams.Billing.team_member_usage(team) == 4
assert Quota.Usage.team_member_usage(me) == 4
assert Quota.Usage.team_member_usage(me, exclude_emails: ["arbitrary@example.com"]) == 4
assert Quota.Usage.team_member_usage(me, exclude_emails: [member.email]) == 3
assert Quota.Usage.team_member_usage(me, exclude_emails: [invitation.email]) == 3
assert Plausible.Teams.Billing.team_member_usage(team,
exclude_emails: ["arbitrary@example.com"]
) == 4
assert Quota.Usage.team_member_usage(me, exclude_emails: [member.email, invitation.email]) ==
2
assert Plausible.Teams.Billing.team_member_usage(team, exclude_emails: [member.email]) == 3
assert Plausible.Teams.Billing.team_member_usage(team, exclude_emails: [invitation.email]) ==
3
assert Plausible.Teams.Billing.team_member_usage(team,
exclude_emails: [member.email, invitation.email]
) == 2
end
end
describe "team_member_limit/1" do
@describetag :ee_only
test "returns unlimited 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))
team_on_v1 = new_user() |> subscribe_to_plan(@v1_plan_id)
team_on_v2 = new_user() |> subscribe_to_plan(@v2_plan_id)
team_on_v3 = new_user() |> subscribe_to_plan(@v3_plan_id)
assert :unlimited == Quota.Limits.team_member_limit(user_on_v1)
assert :unlimited == Quota.Limits.team_member_limit(user_on_v2)
assert :unlimited == Quota.Limits.team_member_limit(user_on_v3)
assert :unlimited == Plausible.Teams.Billing.team_member_limit(team_on_v1)
assert :unlimited == Plausible.Teams.Billing.team_member_limit(team_on_v2)
assert :unlimited == Plausible.Teams.Billing.team_member_limit(team_on_v3)
end
test "returns unlimited when user is on free_10k plan" do
user = insert(:user, subscription: build(:subscription, paddle_plan_id: "free_10k"))
assert :unlimited == Quota.Limits.team_member_limit(user)
user = new_user()
subscribe_to_plan(user, "free_10k")
team = team_of(user)
assert :unlimited == Plausible.Teams.Billing.team_member_limit(team)
end
test "returns 5 when user in on trial" do
user =
insert(:user,
trial_expiry_date: Timex.shift(Timex.now(), days: 7)
)
team = new_user(trial_expiry_date: Date.shift(Date.utc_today(), day: 7)) |> team_of()
assert 3 == Quota.Limits.team_member_limit(user)
assert 3 == Plausible.Teams.Billing.team_member_limit(team)
end
test "returns the enterprise plan limit" do
@ -477,39 +429,33 @@ defmodule Plausible.Billing.QuotaTest do
end
test "reads from json file when the user is on a v4 plan" do
user_on_growth = insert(:user, subscription: build(:growth_subscription))
team_on_growth = new_user() |> subscribe_to_growth_plan() |> team_of()
team_on_business = new_user() |> subscribe_to_business_plan() |> team_of()
user_on_business = insert(:user, subscription: build(:business_subscription))
assert 3 == Quota.Limits.team_member_limit(user_on_growth)
assert 10 == Quota.Limits.team_member_limit(user_on_business)
assert 3 == Plausible.Teams.Billing.team_member_limit(team_on_growth)
assert 10 == Plausible.Teams.Billing.team_member_limit(team_on_business)
end
test "returns unlimited when user is on a v3 business plan" do
user =
insert(:user, subscription: build(:subscription, paddle_plan_id: @v3_business_plan_id))
team = new_user() |> subscribe_to_plan(@v3_business_plan_id) |> team_of()
assert :unlimited == Quota.Limits.team_member_limit(user)
assert :unlimited == Plausible.Teams.Billing.team_member_limit(team)
end
end
describe "features_usage/2" do
test "returns an empty list for a user/site who does not use any feature" do
assert [] == Quota.Usage.features_usage(insert(:user))
assert [] == Quota.Usage.features_usage(nil, [insert(:site).id])
assert [] == Plausible.Teams.Billing.features_usage(team_of(new_user()))
assert [] == Plausible.Teams.Billing.features_usage(nil, [new_site().id])
end
test "returns [Props] when user/site uses custom props" do
user = insert(:user)
user = new_user()
site = new_site(owner: user, allowed_event_props: ["dummy"])
team = team_of(user)
site =
insert(:site,
allowed_event_props: ["dummy"],
memberships: [build(:site_membership, user: user, role: :owner)]
)
assert [Props] == Quota.Usage.features_usage(nil, [site.id])
assert [Props] == Quota.Usage.features_usage(user)
assert [Props] == Plausible.Teams.Billing.features_usage(nil, [site.id])
assert [Props] == Plausible.Teams.Billing.features_usage(team)
end
on_ee do
@ -527,30 +473,32 @@ defmodule Plausible.Billing.QuotaTest do
end
test "returns [RevenueGoals] when user/site uses revenue goals" do
user = insert(:user)
site = insert(:site, memberships: [build(:site_membership, user: user, role: :owner)])
user = new_user()
team = team_of(user)
site = new_site(owner: user)
insert(:goal, currency: :USD, site: site, event_name: "Purchase")
assert [RevenueGoals] == Quota.Usage.features_usage(nil, [site.id])
assert [RevenueGoals] == Quota.Usage.features_usage(user)
assert [RevenueGoals] == Plausible.Teams.Billing.features_usage(nil, [site.id])
assert [RevenueGoals] == Plausible.Teams.Billing.features_usage(team)
end
end
test "returns [StatsAPI] when user has a stats api key" do
user = insert(:user)
user = new_user()
team = team_of(user)
insert(:api_key, user: user)
assert [StatsAPI] == Quota.Usage.features_usage(user)
assert [StatsAPI] == Plausible.Teams.Billing.features_usage(team)
end
test "returns feature usage based on a user and a custom list of site_ids" do
user = insert(:user)
user = new_user()
team = team_of(user)
insert(:api_key, user: user)
site_using_props = insert(:site, allowed_event_props: ["dummy"])
site_using_props = new_site(allowed_event_props: ["dummy"])
site_ids = [site_using_props.id]
assert [Props, StatsAPI] == Quota.Usage.features_usage(user, site_ids)
assert [Props, StatsAPI] == Plausible.Teams.Billing.features_usage(team, site_ids)
end
on_ee do
@ -585,24 +533,26 @@ defmodule Plausible.Billing.QuotaTest do
describe "allowed_features_for/1" do
on_ee do
test "users with expired trials have no access to subscription features" do
user = insert(:user, trial_expiry_date: ~D[2023-01-01])
assert [Goals] == Quota.Limits.allowed_features_for(user)
team = new_user(trial_expiry_date: ~D[2023-01-01]) |> team_of()
assert [Goals] == Plausible.Teams.Billing.allowed_features_for(team)
end
end
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))
team_on_v1 = new_user() |> subscribe_to_plan(@v1_plan_id) |> team_of()
team_on_v2 = new_user() |> subscribe_to_plan(@v2_plan_id) |> team_of()
team_on_v3 = new_user() |> subscribe_to_plan(@v3_plan_id) |> team_of()
assert [Goals, Props, StatsAPI] == Quota.Limits.allowed_features_for(user_on_v1)
assert [Goals, Props, StatsAPI] == Quota.Limits.allowed_features_for(user_on_v2)
assert [Goals, Props, StatsAPI] == Quota.Limits.allowed_features_for(user_on_v3)
assert [Goals, Props, StatsAPI] == Plausible.Teams.Billing.allowed_features_for(team_on_v1)
assert [Goals, Props, StatsAPI] == Plausible.Teams.Billing.allowed_features_for(team_on_v2)
assert [Goals, Props, StatsAPI] == Plausible.Teams.Billing.allowed_features_for(team_on_v3)
end
test "returns [Goals, Props, StatsAPI] when user is on free_10k plan" do
user = insert(:user, subscription: build(:subscription, paddle_plan_id: "free_10k"))
assert [Goals, Props, StatsAPI] == Quota.Limits.allowed_features_for(user)
user = new_user()
subscribe_to_plan(user, "free_10k")
team = team_of(user)
assert [Goals, Props, StatsAPI] == Plausible.Teams.Billing.allowed_features_for(team)
end
on_ee do
@ -623,29 +573,28 @@ defmodule Plausible.Billing.QuotaTest do
end
test "returns all features when user in on trial" do
user = insert(:user, trial_expiry_date: Timex.shift(Timex.now(), days: 7))
team = new_user(trial_expiry_date: Date.shift(Date.utc_today(), day: 7))
assert Plausible.Billing.Feature.list() == Quota.Limits.allowed_features_for(user)
assert Plausible.Billing.Feature.list() ==
Plausible.Teams.Billing.allowed_features_for(team)
end
test "returns previous plan limits 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(subscription?: false)
assert [Goals, Props, StatsAPI] == Quota.Limits.allowed_features_for(user)
team = team_of(user)
assert [Goals, Props, StatsAPI] == Plausible.Teams.Billing.allowed_features_for(team)
end
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
)
team = new_user() |> subscribe_to_enterprise_plan(subscription?: false) |> team_of()
assert Plausible.Billing.Feature.list() == Quota.Limits.allowed_features_for(user)
assert Plausible.Billing.Feature.list() ==
Plausible.Teams.Billing.allowed_features_for(team)
end
test "returns old plan features for enterprise customers who are due to change a plan" do
@ -670,9 +619,7 @@ defmodule Plausible.Billing.QuotaTest do
describe "monthly_pageview_usage/2" do
test "returns empty usage for user without subscription and without any sites" do
user =
insert(:user)
|> Plausible.Users.with_subscription()
team = new_user() |> team_of()
assert %{
last_30_days: %{
@ -681,18 +628,16 @@ defmodule Plausible.Billing.QuotaTest do
pageviews: 0,
date_range: date_range
}
} = Quota.Usage.monthly_pageview_usage(user)
} = Plausible.Teams.Billing.monthly_pageview_usage(team)
assert date_range.last == Date.utc_today()
assert Date.compare(date_range.first, date_range.last) == :lt
end
test "returns usage for user without subscription with a site" do
user =
insert(:user)
|> Plausible.Users.with_subscription()
site = insert(:site, members: [user])
user = new_user()
site = new_site(owner: user)
team = team_of(user)
now = NaiveDateTime.utc_now()
@ -712,12 +657,13 @@ defmodule Plausible.Billing.QuotaTest do
pageviews: 3,
date_range: %{}
}
} = Quota.Usage.monthly_pageview_usage(user)
} = Plausible.Teams.Billing.monthly_pageview_usage(team)
end
test "pageleave events are not counted towards monthly pageview usage" do
user = insert(:user) |> Plausible.Users.with_subscription()
site = insert(:site, members: [user])
user = new_user()
site = new_site(owner: user)
team = team_of(user)
now = NaiveDateTime.utc_now()
populate_stats(site, [
@ -733,18 +679,16 @@ defmodule Plausible.Billing.QuotaTest do
pageviews: 1,
date_range: %{}
}
} = Quota.Usage.monthly_pageview_usage(user)
} = Plausible.Teams.Billing.monthly_pageview_usage(team)
end
test "returns usage for user with subscription and a site" do
today = Date.utc_today()
user = new_user()
subscribe_to_growth_plan(user, last_bill_date: Date.shift(today, day: -8))
team = team_of(user)
user =
insert(:user,
subscription: build(:subscription, last_bill_date: Timex.shift(today, days: -8))
)
site = insert(:site, members: [user])
site = new_site(owner: user)
now = NaiveDateTime.utc_now()
@ -776,20 +720,19 @@ defmodule Plausible.Billing.QuotaTest do
pageviews: 0,
date_range: %{}
}
} = Quota.Usage.monthly_pageview_usage(user)
} = Plausible.Teams.Billing.monthly_pageview_usage(team)
end
test "returns usage for only a subset of site IDs" do
today = Date.utc_today()
user =
insert(:user,
subscription: build(:subscription, last_bill_date: Timex.shift(today, days: -8))
)
user = new_user()
subscribe_to_growth_plan(user, last_bill_date: Date.shift(today, day: -8))
team = team_of(user)
site1 = insert(:site, members: [user])
site2 = insert(:site, members: [user])
site3 = insert(:site, members: [user])
site1 = new_site(owner: user)
site2 = new_site(owner: user)
site3 = new_site(owner: user)
now = NaiveDateTime.utc_now()
@ -823,14 +766,15 @@ defmodule Plausible.Billing.QuotaTest do
pageviews: 0,
date_range: %{}
}
} = Quota.Usage.monthly_pageview_usage(user, [site1.id, site3.id])
} = Plausible.Teams.Billing.monthly_pageview_usage(team, [site1.id, site3.id])
end
end
describe "usage_cycle/1" do
setup do
user = insert(:user)
site = insert(:site, members: [user])
user = new_user()
site = new_site(owner: user)
team = team_of(user)
populate_stats(site, [
build(:event, timestamp: ~N[2023-04-01 00:00:00], name: "custom"),
@ -850,45 +794,41 @@ defmodule Plausible.Billing.QuotaTest do
build(:event, timestamp: ~N[2023-06-05 00:00:00], name: "custom")
])
{:ok, %{user: user}}
{:ok, %{user: user, team: team}}
end
test "returns usage and date_range for the given billing month", %{user: user} do
test "returns usage and date_range for the given billing month", %{user: user, team: team} do
last_bill_date = ~D[2023-06-03]
today = ~D[2023-06-05]
insert(:subscription, user_id: user.id, last_bill_date: last_bill_date)
subscribe_to_growth_plan(user, last_bill_date: last_bill_date)
assert %{date_range: penultimate_cycle, pageviews: 2, custom_events: 3, total: 5} =
Quota.Usage.usage_cycle(user, :penultimate_cycle, nil, today)
Plausible.Teams.Billing.usage_cycle(team, :penultimate_cycle, nil, today)
assert %{date_range: last_cycle, pageviews: 3, custom_events: 2, total: 5} =
Quota.Usage.usage_cycle(user, :last_cycle, nil, today)
Plausible.Teams.Billing.usage_cycle(team, :last_cycle, nil, today)
assert %{date_range: current_cycle, pageviews: 0, custom_events: 3, total: 3} =
Quota.Usage.usage_cycle(user, :current_cycle, nil, today)
Plausible.Teams.Billing.usage_cycle(team, :current_cycle, nil, today)
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 current_cycle == Date.range(~D[2023-06-03], ~D[2023-07-02])
end
test "returns usage and date_range for the last 30 days", %{user: user} do
test "returns usage and date_range for the last 30 days", %{team: team} do
today = ~D[2023-06-01]
assert %{date_range: last_30_days, pageviews: 4, custom_events: 1, total: 5} =
Quota.Usage.usage_cycle(user, :last_30_days, nil, today)
Plausible.Teams.Billing.usage_cycle(team, :last_30_days, nil, today)
assert last_30_days == Date.range(~D[2023-05-02], ~D[2023-06-01])
end
test "only considers sites that the user owns", %{user: user} do
different_site =
insert(:site,
memberships: [
build(:site_membership, user: user, role: :admin)
]
)
test "only considers sites that the user owns", %{user: user, team: team} do
different_site = new_site()
add_guest(different_site, user: user, role: :editor)
populate_stats(different_site, [
build(:event, timestamp: ~N[2023-05-05 00:00:00], name: "custom")
@ -897,10 +837,10 @@ defmodule Plausible.Billing.QuotaTest do
last_bill_date = ~D[2023-06-03]
today = ~D[2023-06-05]
insert(:subscription, user_id: user.id, last_bill_date: last_bill_date)
subscribe_to_growth_plan(user, last_bill_date: last_bill_date)
assert %{date_range: last_cycle, pageviews: 3, custom_events: 2, total: 5} =
Quota.Usage.usage_cycle(user, :last_cycle, nil, today)
Plausible.Teams.Billing.usage_cycle(team, :last_cycle, nil, today)
assert last_cycle == Date.range(~D[2023-05-03], ~D[2023-06-02])
end
@ -909,16 +849,18 @@ defmodule Plausible.Billing.QuotaTest do
last_bill_date = ~D[2020-09-01]
today = ~D[2021-02-02]
user = insert(:user, subscription: build(:subscription, last_bill_date: last_bill_date))
user = new_user()
subscribe_to_growth_plan(user, last_bill_date: last_bill_date)
team = team_of(user)
assert %{date_range: penultimate_cycle} =
Quota.Usage.usage_cycle(user, :penultimate_cycle, nil, today)
Plausible.Teams.Billing.usage_cycle(team, :penultimate_cycle, nil, today)
assert %{date_range: last_cycle} =
Quota.Usage.usage_cycle(user, :last_cycle, nil, today)
Plausible.Teams.Billing.usage_cycle(team, :last_cycle, nil, today)
assert %{date_range: current_cycle} =
Quota.Usage.usage_cycle(user, :current_cycle, nil, today)
Plausible.Teams.Billing.usage_cycle(team, :current_cycle, nil, today)
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])
@ -929,16 +871,18 @@ defmodule Plausible.Billing.QuotaTest do
last_bill_date = ~D[2021-01-01]
today = ~D[2021-01-02]
user = insert(:user, subscription: build(:subscription, last_bill_date: last_bill_date))
user = new_user()
subscribe_to_growth_plan(user, last_bill_date: last_bill_date)
team = team_of(user)
assert %{date_range: penultimate_cycle, total: 0} =
Quota.Usage.usage_cycle(user, :penultimate_cycle, nil, today)
Plausible.Teams.Billing.usage_cycle(team, :penultimate_cycle, nil, today)
assert %{date_range: last_cycle, total: 0} =
Quota.Usage.usage_cycle(user, :last_cycle, nil, today)
Plausible.Teams.Billing.usage_cycle(team, :last_cycle, nil, today)
assert %{date_range: current_cycle, total: 0} =
Quota.Usage.usage_cycle(user, :current_cycle, nil, today)
Plausible.Teams.Billing.usage_cycle(team, :current_cycle, nil, today)
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])
@ -948,16 +892,19 @@ defmodule Plausible.Billing.QuotaTest do
describe "suggest_tier/2" do
setup do
%{user: insert(:user) |> Plausible.Users.with_subscription()}
user = new_user()
team = user |> team_of() |> Plausible.Teams.with_subscription()
%{user: user, team: team}
end
test "returns nil if the monthly pageview limit exceeds regular plans",
%{user: user} do
%{team: team} do
highest_growth_plan = Plausible.Billing.Plans.find(@v4_10m_growth_plan_id)
highest_business_plan = Plausible.Billing.Plans.find(@v4_10m_business_plan_id)
usage =
Quota.Usage.usage(user)
team
|> Plausible.Teams.Billing.quota_usage()
|> Map.replace!(:monthly_pageviews, %{last_30_days: %{total: 12_000_000}})
suggested_tier =

View File

@ -7,15 +7,8 @@ defmodule Plausible.Billing.SiteLockerTest do
describe "update_sites_for/1" do
test "does not lock sites if user is on trial" do
user = insert(:user, trial_expiry_date: Timex.today())
site =
insert(:site,
locked: true,
memberships: [
build(:site_membership, user: user, role: :owner)
]
)
user = new_user(trial_expiry_date: Date.utc_today())
site = new_site(owner: user, locked: true)
assert SiteLocker.update_sites_for(user) == :unlocked
@ -23,16 +16,8 @@ defmodule Plausible.Billing.SiteLockerTest do
end
test "does not lock if user has an active subscription" do
user = insert(:user)
insert(:subscription, status: Subscription.Status.active(), user: user)
site =
insert(:site,
locked: true,
memberships: [
build(:site_membership, user: user, role: :owner)
]
)
user = new_user() |> subscribe_to_growth_plan()
site = new_site(owner: user)
assert SiteLocker.update_sites_for(user) == :unlocked
@ -40,15 +25,8 @@ defmodule Plausible.Billing.SiteLockerTest do
end
test "does not lock user who is past due" do
user = insert(:user)
insert(:subscription, status: Subscription.Status.past_due(), user: user)
site =
insert(:site,
memberships: [
build(:site_membership, user: user, role: :owner)
]
)
user = new_user() |> subscribe_to_growth_plan(status: Subscription.Status.past_due())
site = new_site(owner: user)
assert SiteLocker.update_sites_for(user) == :unlocked
@ -56,15 +34,8 @@ defmodule Plausible.Billing.SiteLockerTest do
end
test "does not lock user who cancelled subscription but it hasn't expired yet" do
user = insert(:user)
insert(:subscription, status: Subscription.Status.deleted(), user: user)
site =
insert(:site,
memberships: [
build(:site_membership, user: user, role: :owner)
]
)
user = new_user() |> subscribe_to_growth_plan(status: Subscription.Status.deleted())
site = new_site(owner: user)
assert SiteLocker.update_sites_for(user) == :unlocked
@ -73,16 +44,12 @@ defmodule Plausible.Billing.SiteLockerTest do
test "does not lock user who has an active subscription and is on grace period" do
grace_period = %Plausible.Auth.GracePeriod{end_date: Timex.shift(Timex.today(), days: 1)}
user = insert(:user, grace_period: grace_period)
insert(:subscription, status: Subscription.Status.active(), user: user)
user =
new_user(grace_period: grace_period, team: [grace_period: grace_period])
|> subscribe_to_growth_plan()
site =
insert(:site,
memberships: [
build(:site_membership, user: user, role: :owner)
]
)
site = new_site(owner: user)
assert SiteLocker.update_sites_for(user) == :unlocked
@ -90,20 +57,14 @@ defmodule Plausible.Billing.SiteLockerTest do
end
test "locks user who cancelled subscription and the cancelled subscription has expired" do
user = insert(:user, trial_expiry_date: Timex.shift(Timex.today(), days: -1))
insert(:subscription,
status: Subscription.Status.deleted(),
next_bill_date: Timex.today() |> Timex.shift(days: -1),
user: user
user =
new_user(trial_expiry_date: Date.shift(Date.utc_today(), day: -1))
|> subscribe_to_growth_plan(
next_bill_date: Date.utc_today() |> Date.shift(day: -1),
status: Subscription.Status.deleted()
)
site =
insert(:site,
memberships: [
build(:site_membership, user: user, role: :owner)
]
)
site = new_site(owner: user)
assert SiteLocker.update_sites_for(user) == {:locked, :no_active_subscription}
@ -112,7 +73,7 @@ defmodule Plausible.Billing.SiteLockerTest do
test "locks all sites if user has active subscription but grace period has ended" do
grace_period = %Plausible.Auth.GracePeriod{end_date: Timex.shift(Timex.today(), days: -1)}
user = new_user(grace_period: grace_period)
user = new_user(grace_period: grace_period, team: [grace_period: grace_period])
subscribe_to_plan(user, "123")
site = new_site(owner: user)
@ -124,7 +85,7 @@ defmodule Plausible.Billing.SiteLockerTest do
@tag :teams
test "syncs grace period end with teams" do
grace_period = %Plausible.Auth.GracePeriod{end_date: Timex.shift(Timex.today(), days: -1)}
user = new_user(grace_period: grace_period)
user = new_user(grace_period: grace_period, team: [grace_period: grace_period])
subscribe_to_plan(user, "123")
new_site(owner: user)
@ -138,7 +99,7 @@ defmodule Plausible.Billing.SiteLockerTest do
test "sends email if grace period has ended" do
grace_period = %Plausible.Auth.GracePeriod{end_date: Timex.shift(Timex.today(), days: -1)}
user = new_user(grace_period: grace_period)
user = new_user(grace_period: grace_period, team: [grace_period: grace_period])
subscribe_to_plan(user, "123")
new_site(owner: user)
@ -156,7 +117,8 @@ defmodule Plausible.Billing.SiteLockerTest do
is_over: false
}
user = new_user(grace_period: grace_period)
user = new_user(grace_period: grace_period, team: [grace_period: grace_period])
subscribe_to_plan(user, "123")
new_site(owner: user)
@ -174,14 +136,8 @@ defmodule Plausible.Billing.SiteLockerTest do
end
test "locks all sites if user has no trial or active subscription" do
user = insert(:user, trial_expiry_date: Timex.today() |> Timex.shift(days: -1))
site =
insert(:site,
memberships: [
build(:site_membership, user: user, role: :owner)
]
)
user = new_user(trial_expiry_date: Date.utc_today() |> Date.shift(day: -1))
site = new_site(owner: user)
assert SiteLocker.update_sites_for(user) == {:locked, :no_active_subscription}
@ -204,21 +160,11 @@ defmodule Plausible.Billing.SiteLockerTest do
end
test "only locks sites that the user owns" do
user = insert(:user, trial_expiry_date: Timex.today() |> Timex.shift(days: -1))
user = new_user(trial_expiry_date: Date.utc_today() |> Date.shift(day: -1))
owner_site =
insert(:site,
memberships: [
build(:site_membership, user: user, role: :owner)
]
)
viewer_site =
insert(:site,
memberships: [
build(:site_membership, user: user, role: :viewer)
]
)
owner_site = new_site(owner: user)
viewer_site = new_site()
add_guest(viewer_site, user: user, role: :viewer)
assert SiteLocker.update_sites_for(user) == {:locked, :no_active_subscription}

View File

@ -18,7 +18,6 @@ defmodule Plausible.Site.Memberships.AcceptInvitationTest do
assert {:ok, _} =
AcceptInvitation.bulk_transfer_ownership_direct(
current_owner,
[site1, site2],
new_owner
)
@ -39,7 +38,6 @@ defmodule Plausible.Site.Memberships.AcceptInvitationTest do
assert {:error, :transfer_to_self} =
AcceptInvitation.bulk_transfer_ownership_direct(
current_owner,
[site1, site2],
new_owner
)
@ -56,7 +54,7 @@ defmodule Plausible.Site.Memberships.AcceptInvitationTest do
for _ <- 1..3, do: add_guest(site, role: :editor)
assert {:error, {:over_plan_limits, [:team_member_limit]}} =
AcceptInvitation.bulk_transfer_ownership_direct(old_owner, [site], new_owner)
AcceptInvitation.bulk_transfer_ownership_direct([site], new_owner)
end
@tag :ee_only
@ -68,7 +66,7 @@ defmodule Plausible.Site.Memberships.AcceptInvitationTest do
for _ <- 1..2, do: add_guest(site, role: :editor)
assert {:ok, _} =
AcceptInvitation.bulk_transfer_ownership_direct(old_owner, [site], new_owner)
AcceptInvitation.bulk_transfer_ownership_direct([site], new_owner)
end
@tag :ee_only
@ -81,7 +79,7 @@ defmodule Plausible.Site.Memberships.AcceptInvitationTest do
site = new_site(owner: old_owner)
assert {:error, {:over_plan_limits, [:site_limit]}} =
AcceptInvitation.bulk_transfer_ownership_direct(old_owner, [site], new_owner)
AcceptInvitation.bulk_transfer_ownership_direct([site], new_owner)
end
@tag :ee_only
@ -102,7 +100,7 @@ defmodule Plausible.Site.Memberships.AcceptInvitationTest do
for _ <- 1..3, do: add_guest(site, role: :editor)
assert {:error, {:over_plan_limits, [:team_member_limit, :site_limit]}} =
AcceptInvitation.bulk_transfer_ownership_direct(old_owner, [site], new_owner)
AcceptInvitation.bulk_transfer_ownership_direct([site], new_owner)
end
end

View File

@ -162,15 +162,15 @@ defmodule Plausible.SitesTest do
patch_env(:super_admin_user_ids, [user2.id])
%{id: site_id, domain: domain} = new_site(owner: user1)
assert %{id: ^site_id} = Plausible.Teams.Adapter.Read.Sites.get_for_user(user1, domain)
assert %{id: ^site_id} = Plausible.Sites.get_for_user(user1, domain)
assert %{id: ^site_id} =
Plausible.Teams.Adapter.Read.Sites.get_for_user(user1, domain, [:owner])
Plausible.Sites.get_for_user(user1, domain, [:owner])
assert is_nil(Plausible.Teams.Adapter.Read.Sites.get_for_user(user2, domain))
assert is_nil(Plausible.Sites.get_for_user(user2, domain))
assert %{id: ^site_id} =
Plausible.Teams.Adapter.Read.Sites.get_for_user(user2, domain, [:super_admin])
Plausible.Sites.get_for_user(user2, domain, [:super_admin])
end
end

View File

@ -1063,12 +1063,9 @@ defmodule PlausibleWeb.SettingsControllerTest do
end
test "can't create api key into another site", %{conn: conn, user: me} do
_my_site = insert(:site, memberships: [build(:site_membership, user: me, role: "owner")])
other_user = insert(:user)
_other_site =
insert(:site, memberships: [build(:site_membership, user: other_user, role: "owner")])
_my_site = new_site(owner: me)
other_user = new_user()
_other_site = new_site(owner: other_user)
conn =
post(conn, Routes.settings_path(conn, :api_keys), %{

View File

@ -342,6 +342,7 @@ defmodule PlausibleWeb.SiteControllerTest do
user: user
} do
subscribe_to_enterprise_plan(user, site_limit: 1)
team = team_of(user)
new_site(owner: user)
new_site(owner: user)
@ -354,7 +355,7 @@ defmodule PlausibleWeb.SiteControllerTest do
})
assert redirected_to(conn) == "/example.com/installation?site_created=true&flow="
assert Plausible.Billing.Quota.Usage.site_usage(user) == 3
assert Plausible.Teams.Billing.site_usage(team) == 3
end
for url <- ["https://Example.com/", "HTTPS://EXAMPLE.COM/", "/Example.com/", "//Example.com/"] do

View File

@ -1,12 +1,13 @@
defmodule PlausibleWeb.UnsubscribeControllerTest do
use PlausibleWeb.ConnCase, async: true
use Plausible.Repo
use Plausible.Teams.Test
setup {PlausibleWeb.FirstLaunchPlug.Test, :skip}
describe "GET /sites/:domain/weekly-report/unsubscribe" do
test "removes a recipient from the weekly report without them having to log in", %{conn: conn} do
site = insert(:site)
site = new_site()
insert(:weekly_report, site: site, recipients: ["recipient@email.com"])
conn =
@ -35,7 +36,7 @@ defmodule PlausibleWeb.UnsubscribeControllerTest do
describe "GET /sites/:domain/monthly-report/unsubscribe" do
test "removes a recipient from the weekly report without them having to log in", %{conn: conn} do
site = insert(:site)
site = new_site()
insert(:monthly_report, site: site, recipients: ["recipient@email.com"])
conn =

View File

@ -281,7 +281,7 @@ defmodule PlausibleWeb.Live.ChoosePlanTest do
user: user
} do
for _ <- 1..9, do: new_site(owner: user)
assert 10 = Plausible.Teams.Adapter.Read.Billing.site_usage(user)
assert user |> team_of() |> Plausible.Teams.Billing.site_usage() == 10
another_user = new_user()
pending_ownership_site = new_site(owner: another_user)
@ -611,11 +611,13 @@ defmodule PlausibleWeb.Live.ChoosePlanTest do
conn: conn,
user: user
} do
team = team_of(user)
for _ <- 1..49 do
new_site(owner: user)
end
assert 50 = Plausible.Teams.Adapter.Read.Billing.quota_usage(user).sites
assert 50 = Plausible.Teams.Billing.quota_usage(team).sites
another_user = new_user()
pending_ownership_site = new_site(owner: another_user)

View File

@ -8,13 +8,6 @@ Application.ensure_all_started(:double)
FunWithFlags.enable(:channels)
FunWithFlags.enable(:scroll_depth)
# 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)
end
Ecto.Adapters.SQL.Sandbox.mode(Plausible.Repo, :manual)

View File

@ -29,7 +29,7 @@ defmodule Plausible.Workers.CheckUsageTest do
user: user
} do
usage_stub =
Plausible.Billing.Quota.Usage
Plausible.Teams.Billing
|> stub(:monthly_pageview_usage, fn _user ->
%{
penultimate_cycle: %{date_range: @date_range, total: 11_000},
@ -37,17 +37,17 @@ defmodule Plausible.Workers.CheckUsageTest do
}
end)
insert(:subscription,
user: user,
paddle_plan_id: @paddle_id_10k,
last_bill_date: Timex.shift(Timex.today(), days: -1),
subscribe_to_plan(
user,
@paddle_id_10k,
last_bill_date: Date.shift(Date.utc_today(), day: -1),
status: :active
)
insert(:subscription,
user: user,
paddle_plan_id: "wont-exist-should-crash",
last_bill_date: Timex.shift(Timex.today(), days: -1),
subscribe_to_plan(
user,
"wont-exist-should-crash",
last_bill_date: Date.shift(Date.utc_today(), day: -1),
inserted_at: DateTime.shift(DateTime.utc_now(), day: -2),
status: :deleted
)
@ -62,7 +62,7 @@ defmodule Plausible.Workers.CheckUsageTest do
test "sends more than one email", %{user: user} do
usage_stub =
Plausible.Billing.Quota.Usage
Plausible.Teams.Billing
|> stub(:monthly_pageview_usage, fn _user ->
%{
penultimate_cycle: %{date_range: @date_range, total: 11_000},
@ -74,11 +74,11 @@ defmodule Plausible.Workers.CheckUsageTest do
insert(:site, members: [user2])
for u <- [user, user2] do
insert(:subscription,
user: u,
paddle_plan_id: @paddle_id_10k,
last_bill_date: Timex.shift(Timex.today(), days: -1),
next_bill_date: Timex.shift(Timex.today(), days: +5),
subscribe_to_plan(
u,
@paddle_id_10k,
last_bill_date: Date.shift(Date.utc_today(), day: -1),
next_bill_date: Date.shift(Date.utc_today(), day: +5),
status: :active
)
end
@ -129,7 +129,7 @@ defmodule Plausible.Workers.CheckUsageTest do
user: user
} do
usage_stub =
Plausible.Billing.Quota.Usage
Plausible.Teams.Billing
|> stub(:monthly_pageview_usage, fn _user ->
%{
penultimate_cycle: %{date_range: @date_range, total: 9_000},
@ -154,7 +154,7 @@ defmodule Plausible.Workers.CheckUsageTest do
user: user
} do
usage_stub =
Plausible.Billing.Quota.Usage
Plausible.Teams.Billing
|> stub(:monthly_pageview_usage, fn _user ->
%{
penultimate_cycle: %{date_range: @date_range, total: 10_999},
@ -180,7 +180,7 @@ defmodule Plausible.Workers.CheckUsageTest do
user: user
} do
usage_stub =
Plausible.Billing.Quota.Usage
Plausible.Teams.Billing
|> stub(:monthly_pageview_usage, fn _user ->
%{
penultimate_cycle: %{date_range: @date_range, total: 11_000},
@ -188,10 +188,10 @@ defmodule Plausible.Workers.CheckUsageTest do
}
end)
insert(:subscription,
user: user,
paddle_plan_id: @paddle_id_10k,
last_bill_date: Timex.shift(Timex.today(), days: -1),
subscribe_to_plan(
user,
@paddle_id_10k,
last_bill_date: Date.shift(Date.utc_today(), day: -1),
status: unquote(status)
)
@ -211,7 +211,7 @@ defmodule Plausible.Workers.CheckUsageTest do
user: user
} do
usage_stub =
Plausible.Billing.Quota.Usage
Plausible.Teams.Billing
|> stub(:monthly_pageview_usage, fn _user ->
%{
penultimate_cycle: %{date_range: @date_range, total: 11_000},
@ -219,10 +219,10 @@ defmodule Plausible.Workers.CheckUsageTest do
}
end)
insert(:subscription,
user: user,
paddle_plan_id: @paddle_id_10k,
last_bill_date: Timex.shift(Timex.today(), days: -1),
subscribe_to_plan(
user,
@paddle_id_10k,
last_bill_date: Date.shift(Date.utc_today(), day: -1),
status: unquote(status)
)
@ -240,7 +240,7 @@ defmodule Plausible.Workers.CheckUsageTest do
user: user
} do
usage_stub =
Plausible.Billing.Quota.Usage
Plausible.Teams.Billing
|> stub(:monthly_pageview_usage, fn _user ->
%{
penultimate_cycle: %{date_range: @date_range, total: 11_000_000},
@ -248,9 +248,9 @@ defmodule Plausible.Workers.CheckUsageTest do
}
end)
insert(:subscription,
user: user,
paddle_plan_id: @paddle_id_10k,
subscribe_to_plan(
user,
@paddle_id_10k,
last_bill_date: Timex.shift(Timex.today(), days: -1),
status: unquote(status)
)
@ -267,7 +267,7 @@ defmodule Plausible.Workers.CheckUsageTest do
%{grace_period: existing_grace_period} = Plausible.Users.start_grace_period(user)
usage_stub =
Plausible.Billing.Quota.Usage
Plausible.Teams.Billing
|> stub(:monthly_pageview_usage, fn _user ->
%{
penultimate_cycle: %{date_range: @date_range, total: 11_000},
@ -292,7 +292,7 @@ defmodule Plausible.Workers.CheckUsageTest do
user: user
} do
usage_stub =
Plausible.Billing.Quota.Usage
Plausible.Teams.Billing
|> stub(:monthly_pageview_usage, fn _user ->
%{
penultimate_cycle: %{date_range: @date_range, total: 11_000},
@ -300,9 +300,9 @@ defmodule Plausible.Workers.CheckUsageTest do
}
end)
insert(:subscription,
user: user,
paddle_plan_id: @paddle_id_10k,
subscribe_to_plan(
user,
@paddle_id_10k,
last_bill_date: Timex.shift(Timex.today(), days: -1),
status: unquote(status)
)
@ -318,7 +318,7 @@ defmodule Plausible.Workers.CheckUsageTest do
test "clears grace period when plan is applicable again", %{user: user} do
usage_stub =
Plausible.Billing.Quota.Usage
Plausible.Teams.Billing
|> stub(:monthly_pageview_usage, fn _user ->
%{
penultimate_cycle: %{date_range: @date_range, total: 11_000},
@ -326,10 +326,10 @@ defmodule Plausible.Workers.CheckUsageTest do
}
end)
insert(:subscription,
user: user,
paddle_plan_id: @paddle_id_10k,
last_bill_date: Timex.shift(Timex.today(), days: -1),
subscribe_to_plan(
user,
@paddle_id_10k,
last_bill_date: Date.shift(Date.utc_today(), day: -1),
status: unquote(status)
)
@ -337,7 +337,7 @@ defmodule Plausible.Workers.CheckUsageTest do
assert user |> Repo.reload() |> Plausible.Auth.GracePeriod.active?()
usage_stub =
Plausible.Billing.Quota.Usage
Plausible.Teams.Billing
|> stub(:monthly_pageview_usage, fn _user ->
%{
penultimate_cycle: %{date_range: @date_range, total: 11_000},
@ -352,7 +352,7 @@ defmodule Plausible.Workers.CheckUsageTest do
@tag :teams
test "syncs clearing grace period with teams", %{user: user} do
usage_stub =
Plausible.Billing.Quota.Usage
Plausible.Teams.Billing
|> stub(:monthly_pageview_usage, fn _user ->
%{
penultimate_cycle: %{date_range: @date_range, total: 11_000},
@ -360,9 +360,7 @@ defmodule Plausible.Workers.CheckUsageTest do
}
end)
insert(:subscription,
user: user,
paddle_plan_id: @paddle_id_10k,
subscribe_to_plan(user, @paddle_id_10k,
last_bill_date: Timex.shift(Timex.today(), days: -1),
status: unquote(status)
)
@ -374,7 +372,7 @@ defmodule Plausible.Workers.CheckUsageTest do
assert Plausible.Auth.GracePeriod.active?(team)
usage_stub =
Plausible.Billing.Quota.Usage
Plausible.Teams.Billing
|> stub(:monthly_pageview_usage, fn _user ->
%{
penultimate_cycle: %{date_range: @date_range, total: 11_000},
@ -395,7 +393,7 @@ defmodule Plausible.Workers.CheckUsageTest do
Plausible.Users.start_manual_lock_grace_period(user)
usage_stub =
Plausible.Billing.Quota.Usage
Plausible.Teams.Billing
|> stub(:monthly_pageview_usage, fn _user ->
%{
penultimate_cycle: %{date_range: @date_range, total: 1_100_000},
@ -423,7 +421,7 @@ defmodule Plausible.Workers.CheckUsageTest do
user: user
} do
usage_stub =
Plausible.Billing.Quota.Usage
Plausible.Teams.Billing
|> stub(:monthly_pageview_usage, fn _user ->
%{
penultimate_cycle: %{date_range: @date_range, total: 1_100_000},
@ -453,7 +451,7 @@ defmodule Plausible.Workers.CheckUsageTest do
user: user
} do
usage_stub =
Plausible.Billing.Quota.Usage
Plausible.Teams.Billing
|> stub(:monthly_pageview_usage, fn _user ->
%{
penultimate_cycle: %{date_range: @date_range, total: 1},
@ -483,7 +481,7 @@ defmodule Plausible.Workers.CheckUsageTest do
test "manual lock grace period is synced with teams", %{user: user} do
usage_stub =
Plausible.Billing.Quota.Usage
Plausible.Teams.Billing
|> stub(:monthly_pageview_usage, fn _user ->
%{
penultimate_cycle: %{date_range: @date_range, total: 1_100_000},
@ -510,7 +508,7 @@ defmodule Plausible.Workers.CheckUsageTest do
test "starts grace period when plan is outgrown", %{user: user} do
usage_stub =
Plausible.Billing.Quota.Usage
Plausible.Teams.Billing
|> stub(:monthly_pageview_usage, fn _user ->
%{
penultimate_cycle: %{date_range: @date_range, total: 1_100_000},
@ -538,7 +536,7 @@ defmodule Plausible.Workers.CheckUsageTest do
user: user
} do
usage_stub =
Plausible.Billing.Quota.Usage
Plausible.Teams.Billing
|> stub(:monthly_pageview_usage, fn _user ->
%{
penultimate_cycle: %{date_range: @date_range, total: 11_000},
@ -546,10 +544,10 @@ defmodule Plausible.Workers.CheckUsageTest do
}
end)
insert(:subscription,
user: user,
paddle_plan_id: @paddle_id_10k,
last_bill_date: Timex.shift(Timex.today(), days: -1)
subscribe_to_plan(
user,
@paddle_id_10k,
last_bill_date: Date.shift(Date.utc_today(), day: -1)
)
CheckUsage.perform(nil, usage_stub)
@ -564,7 +562,7 @@ defmodule Plausible.Workers.CheckUsageTest do
user: user
} do
usage_stub =
Plausible.Billing.Quota.Usage
Plausible.Teams.Billing
|> stub(:monthly_pageview_usage, fn _user ->
%{
penultimate_cycle: %{date_range: @date_range, total: 11_000},
@ -588,7 +586,7 @@ defmodule Plausible.Workers.CheckUsageTest do
user: user
} do
usage_stub =
Plausible.Billing.Quota.Usage
Plausible.Teams.Billing
|> stub(:monthly_pageview_usage, fn _user ->
%{
penultimate_cycle: %{date_range: @date_range, total: 11_000},
@ -596,9 +594,9 @@ defmodule Plausible.Workers.CheckUsageTest do
}
end)
insert(:subscription,
user: user,
paddle_plan_id: @paddle_id_10k,
subscribe_to_plan(
user,
@paddle_id_10k,
last_bill_date: ~D[2021-06-29]
)

View File

@ -1,17 +1,16 @@
defmodule Plausible.Workers.LockSitesTest do
use Plausible.DataCase, async: true
use Plausible.Teams.Test
require Plausible.Billing.Subscription.Status
alias Plausible.Workers.LockSites
alias Plausible.Billing.Subscription
test "does not lock enterprise site on grace period" do
user =
:user
|> build()
|> Plausible.Auth.GracePeriod.start_manual_lock_changeset()
|> Plausible.Repo.insert!()
new_user()
|> Plausible.Users.start_manual_lock_grace_period()
site = insert(:site, members: [user])
site = new_site(owner: user)
LockSites.perform(nil)
@ -19,8 +18,8 @@ defmodule Plausible.Workers.LockSitesTest do
end
test "does not lock trial user's site" do
user = insert(:user, trial_expiry_date: Timex.today() |> Timex.shift(days: 1))
site = insert(:site, members: [user])
user = new_user(trial_expiry_date: Date.utc_today() |> Date.shift(day: 1))
site = new_site(owner: user)
LockSites.perform(nil)
@ -28,8 +27,8 @@ defmodule Plausible.Workers.LockSitesTest do
end
test "locks site for user whose trial has expired" do
user = insert(:user, trial_expiry_date: Timex.today() |> Timex.shift(days: -1))
site = insert(:site, members: [user])
user = new_user(trial_expiry_date: Date.utc_today() |> Date.shift(day: -1))
site = new_site(owner: user)
LockSites.perform(nil)
@ -37,9 +36,8 @@ defmodule Plausible.Workers.LockSitesTest do
end
test "does not lock active subsriber's sites" do
user = insert(:user)
insert(:subscription, status: Subscription.Status.active(), user: user)
site = insert(:site, members: [user])
user = new_user() |> subscribe_to_growth_plan(status: Subscription.Status.active())
site = new_site(owner: user)
LockSites.perform(nil)
@ -47,9 +45,8 @@ defmodule Plausible.Workers.LockSitesTest do
end
test "does not lock user who is past due" do
user = insert(:user)
insert(:subscription, status: Subscription.Status.past_due(), user: user)
site = insert(:site, members: [user])
user = new_user() |> subscribe_to_growth_plan(status: Subscription.Status.past_due())
site = new_site(owner: user)
LockSites.perform(nil)
@ -57,9 +54,8 @@ defmodule Plausible.Workers.LockSitesTest do
end
test "does not lock user who cancelled subscription but it hasn't expired yet" do
user = insert(:user)
insert(:subscription, status: Subscription.Status.deleted(), user: user)
site = insert(:site, members: [user])
user = new_user() |> subscribe_to_growth_plan(status: Subscription.Status.deleted())
site = new_site(owner: user)
LockSites.perform(nil)
@ -67,15 +63,14 @@ defmodule Plausible.Workers.LockSitesTest do
end
test "locks user who cancelled subscription and the cancelled subscription has expired" do
user = insert(:user, trial_expiry_date: Timex.today() |> Timex.shift(days: -1))
insert(:subscription,
user =
new_user(trial_expiry_date: Date.utc_today() |> Date.shift(day: -1))
|> subscribe_to_growth_plan(
status: Subscription.Status.deleted(),
next_bill_date: Timex.today() |> Timex.shift(days: -1),
user: user
next_bill_date: Date.utc_today() |> Date.shift(day: -1)
)
site = insert(:site, members: [user])
site = new_site(owner: user)
LockSites.perform(nil)
@ -83,18 +78,16 @@ defmodule Plausible.Workers.LockSitesTest do
end
test "does not lock if user has an old cancelled subscription and a new active subscription" do
user = insert(:user, trial_expiry_date: Timex.today() |> Timex.shift(days: -1))
insert(:subscription,
user =
new_user(trial_expiry_date: Date.utc_today() |> Date.shift(day: -1))
|> subscribe_to_growth_plan(
status: Subscription.Status.deleted(),
next_bill_date: Timex.today() |> Timex.shift(days: -1),
user: user,
inserted_at: Timex.now() |> Timex.shift(days: -1)
next_bill_date: Date.utc_today() |> Date.shift(day: -1),
inserted_at: NaiveDateTime.utc_now() |> NaiveDateTime.shift(day: -1)
)
|> subscribe_to_growth_plan(status: Subscription.Status.deleted())
insert(:subscription, status: Subscription.Status.active(), user: user)
site = insert(:site, members: [user])
site = new_site(owner: user)
LockSites.perform(nil)

View File

@ -6,10 +6,10 @@ defmodule Plausible.Workers.SendTrialNotificationsTest do
alias Plausible.Workers.SendTrialNotifications
test "does not send a notification if user didn't create a site" do
insert(:user, trial_expiry_date: Timex.now() |> Timex.shift(days: 7))
insert(:user, trial_expiry_date: Timex.now() |> Timex.shift(days: 1))
insert(:user, trial_expiry_date: Timex.now() |> Timex.shift(days: 0))
insert(:user, trial_expiry_date: Timex.now() |> Timex.shift(days: -1))
new_user(trial_expiry_date: Date.utc_today() |> Date.shift(day: 7))
new_user(trial_expiry_date: Date.utc_today() |> Date.shift(day: 1))
new_user(trial_expiry_date: Date.utc_today() |> Date.shift(day: 0))
new_user(trial_expiry_date: Date.utc_today() |> Date.shift(day: -1))
perform_job(SendTrialNotifications, %{})
@ -17,8 +17,8 @@ defmodule Plausible.Workers.SendTrialNotificationsTest do
end
test "does not send a notification if user does not have a trial" do
user = insert(:user, trial_expiry_date: nil)
insert(:site, members: [user])
user = new_user(trial_expiry_date: nil)
new_site(members: user)
perform_job(SendTrialNotifications, %{})
@ -26,8 +26,8 @@ defmodule Plausible.Workers.SendTrialNotificationsTest do
end
test "does not send a notification if user created a site but there are no pageviews" do
user = insert(:user, trial_expiry_date: Timex.now() |> Timex.shift(days: 7))
insert(:site, members: [user])
user = new_user(trial_expiry_date: Date.utc_today() |> Date.shift(day: 7))
new_site(owner: user)
perform_job(SendTrialNotifications, %{})
@ -35,14 +35,9 @@ defmodule Plausible.Workers.SendTrialNotificationsTest do
end
test "does not send a notification if user is a collaborator on sites but not an owner" do
user = insert(:user, trial_expiry_date: Timex.now())
site =
insert(:site,
memberships: [
build(:site_membership, user: user, role: :admin)
]
)
user = new_user(trial_expiry_date: Date.utc_today())
site = new_site()
add_guest(site, user: user, role: :editor)
populate_stats(site, [build(:pageview)])
@ -53,8 +48,8 @@ defmodule Plausible.Workers.SendTrialNotificationsTest do
describe "with site and pageviews" do
test "sends a reminder 7 days before trial ends (16 days after user signed up)" do
user = insert(:user, trial_expiry_date: Timex.now() |> Timex.shift(days: 7))
site = insert(:site, members: [user])
user = new_user(trial_expiry_date: Date.utc_today() |> Date.shift(day: 7))
site = new_site(owner: user)
populate_stats(site, [build(:pageview)])
perform_job(SendTrialNotifications, %{})
@ -63,8 +58,8 @@ defmodule Plausible.Workers.SendTrialNotificationsTest do
end
test "sends an upgrade email the day before the trial ends" do
user = insert(:user, trial_expiry_date: Timex.now() |> Timex.shift(days: 1))
site = insert(:site, members: [user])
user = new_user(trial_expiry_date: Date.utc_today() |> Date.shift(day: 1))
site = new_site(owner: user)
usage = %{total: 3, custom_events: 0}
populate_stats(site, [
@ -79,8 +74,8 @@ defmodule Plausible.Workers.SendTrialNotificationsTest do
end
test "sends an upgrade email the day the trial ends" do
user = insert(:user, trial_expiry_date: Timex.today())
site = insert(:site, members: [user])
user = new_user(trial_expiry_date: Date.utc_today())
site = new_site(owner: user)
usage = %{total: 3, custom_events: 0}
populate_stats(site, [
@ -95,7 +90,7 @@ defmodule Plausible.Workers.SendTrialNotificationsTest do
end
test "does not include custom event note if user has not used custom events" do
user = insert(:user, trial_expiry_date: Timex.today())
user = new_user(trial_expiry_date: Date.utc_today())
usage = %{total: 9_000, custom_events: 0}
email = PlausibleWeb.Email.trial_upgrade_email(user, "today", usage)
@ -105,7 +100,7 @@ defmodule Plausible.Workers.SendTrialNotificationsTest do
end
test "includes custom event note if user has used custom events" do
user = insert(:user, trial_expiry_date: Timex.today())
user = new_user(trial_expiry_date: Date.utc_today())
usage = %{total: 9_100, custom_events: 100}
email = PlausibleWeb.Email.trial_upgrade_email(user, "today", usage)
@ -115,8 +110,8 @@ defmodule Plausible.Workers.SendTrialNotificationsTest do
end
test "sends a trial over email the day after the trial ends" do
user = insert(:user, trial_expiry_date: Timex.today() |> Timex.shift(days: -1))
site = insert(:site, members: [user])
user = new_user(trial_expiry_date: Date.utc_today() |> Date.shift(day: -1))
site = new_site(owner: user)
populate_stats(site, [
build(:pageview),
@ -130,8 +125,8 @@ defmodule Plausible.Workers.SendTrialNotificationsTest do
end
test "does not send a notification if user has a subscription" do
user = insert(:user, trial_expiry_date: Timex.now() |> Timex.shift(days: 7))
site = insert(:site, members: [user])
user = new_user(trial_expiry_date: Date.utc_today() |> Date.shift(day: 7))
site = new_site(owner: user)
populate_stats(site, [
build(:pageview),
@ -139,7 +134,7 @@ defmodule Plausible.Workers.SendTrialNotificationsTest do
build(:pageview)
])
insert(:subscription, user: user)
subscribe_to_growth_plan(user)
perform_job(SendTrialNotifications, %{})
@ -149,7 +144,7 @@ defmodule Plausible.Workers.SendTrialNotificationsTest do
describe "Suggested plans" do
test "suggests 10k/mo plan" do
user = insert(:user)
user = new_user()
usage = %{total: 9_000, custom_events: 0}
email = PlausibleWeb.Email.trial_upgrade_email(user, "today", usage)
@ -157,7 +152,7 @@ defmodule Plausible.Workers.SendTrialNotificationsTest do
end
test "suggests 100k/mo plan" do
user = insert(:user)
user = new_user()
usage = %{total: 90_000, custom_events: 0}
email = PlausibleWeb.Email.trial_upgrade_email(user, "today", usage)
@ -165,7 +160,7 @@ defmodule Plausible.Workers.SendTrialNotificationsTest do
end
test "suggests 200k/mo plan" do
user = insert(:user)
user = new_user()
usage = %{total: 180_000, custom_events: 0}
email = PlausibleWeb.Email.trial_upgrade_email(user, "today", usage)
@ -173,7 +168,7 @@ defmodule Plausible.Workers.SendTrialNotificationsTest do
end
test "suggests 500k/mo plan" do
user = insert(:user)
user = new_user()
usage = %{total: 450_000, custom_events: 0}
email = PlausibleWeb.Email.trial_upgrade_email(user, "today", usage)
@ -181,7 +176,7 @@ defmodule Plausible.Workers.SendTrialNotificationsTest do
end
test "suggests 1m/mo plan" do
user = insert(:user)
user = new_user()
usage = %{total: 900_000, custom_events: 0}
email = PlausibleWeb.Email.trial_upgrade_email(user, "today", usage)
@ -189,7 +184,7 @@ defmodule Plausible.Workers.SendTrialNotificationsTest do
end
test "suggests 2m/mo plan" do
user = insert(:user)
user = new_user()
usage = %{total: 1_800_000, custom_events: 0}
email = PlausibleWeb.Email.trial_upgrade_email(user, "today", usage)
@ -197,7 +192,7 @@ defmodule Plausible.Workers.SendTrialNotificationsTest do
end
test "suggests 5m/mo plan" do
user = insert(:user)
user = new_user()
usage = %{total: 4_500_000, custom_events: 0}
email = PlausibleWeb.Email.trial_upgrade_email(user, "today", usage)
@ -205,7 +200,7 @@ defmodule Plausible.Workers.SendTrialNotificationsTest do
end
test "suggests 10m/mo plan" do
user = insert(:user)
user = new_user()
usage = %{total: 9_000_000, custom_events: 0}
email = PlausibleWeb.Email.trial_upgrade_email(user, "today", usage)
@ -213,7 +208,7 @@ defmodule Plausible.Workers.SendTrialNotificationsTest do
end
test "does not suggest a plan above that" do
user = insert(:user)
user = new_user()
usage = %{total: 20_000_000, custom_events: 0}
email = PlausibleWeb.Email.trial_upgrade_email(user, "today", usage)