diff --git a/extra/lib/plausible_web/controllers/api/external_sites_controller.ex b/extra/lib/plausible_web/controllers/api/external_sites_controller.ex
index 1ea08a3e08b4..4e3a0bb930c2 100644
--- a/extra/lib/plausible_web/controllers/api/external_sites_controller.ex
+++ b/extra/lib/plausible_web/controllers/api/external_sites_controller.ex
@@ -52,6 +52,30 @@ defmodule PlausibleWeb.Api.ExternalSitesController do
end
end
+ def teams_index(conn, params) do
+ user = conn.assigns.current_user
+
+ page =
+ user
+ |> Teams.Users.teams_query(order_by: :id_desc)
+ |> paginate(params, @pagination_opts)
+
+ json(conn, %{
+ teams:
+ Enum.map(page.entries, fn team ->
+ api_available? =
+ Plausible.Billing.Feature.StatsAPI.check_availability(team) == :ok
+
+ %{
+ id: team.identifier,
+ name: Teams.name(team),
+ api_available: api_available?
+ }
+ end),
+ meta: pagination_meta(page.metadata)
+ })
+ end
+
def goals_index(conn, params) do
user = conn.assigns.current_user
diff --git a/lib/plausible/sites.ex b/lib/plausible/sites.ex
index a3c801bab5aa..0d09a458e6f5 100644
--- a/lib/plausible/sites.ex
+++ b/lib/plausible/sites.ex
@@ -231,7 +231,7 @@ defmodule Plausible.Sites do
where(
query,
[team_memberships: tm, guest_memberships: gm, site: s],
- (tm.role != :guest and tm.team_id == ^team.id) or gm.site_id == s.id
+ tm.role != :guest and tm.team_id == ^team.id
)
else
where(
diff --git a/lib/plausible/teams/users.ex b/lib/plausible/teams/users.ex
index 60f01886f9bb..b4601e53518e 100644
--- a/lib/plausible/teams/users.ex
+++ b/lib/plausible/teams/users.ex
@@ -21,18 +21,38 @@ defmodule Plausible.Teams.Users do
end
def teams(user) do
- from(
- tm in Teams.Membership,
- inner_join: t in assoc(tm, :team),
- where: tm.user_id == ^user.id,
- where: tm.role != :guest,
- select: t,
- order_by: [t.name, t.id]
- )
+ user
+ |> teams_query(order_by: :name)
|> Repo.all()
|> Repo.preload(:owners)
end
+ def teams_query(user, opts \\ []) do
+ order_by = Keyword.get(opts, :order_by, :name)
+
+ query =
+ from(
+ tm in Teams.Membership,
+ as: :team_membership,
+ inner_join: t in assoc(tm, :team),
+ as: :team,
+ where: tm.user_id == ^user.id,
+ where: tm.role != :guest,
+ select: t
+ )
+
+ case order_by do
+ :name ->
+ order_by(query, [team: t], [t.name, t.id])
+
+ :id_desc ->
+ order_by(query, [team: t], desc: t.id)
+
+ _ ->
+ query
+ end
+ end
+
def team_member?(user, opts \\ []) do
excluded_team_ids = Keyword.get(opts, :except, [])
diff --git a/lib/plausible_web/router.ex b/lib/plausible_web/router.ex
index 10c5048cc075..ebde84bfcdc5 100644
--- a/lib/plausible_web/router.ex
+++ b/lib/plausible_web/router.ex
@@ -266,6 +266,7 @@ defmodule PlausibleWeb.Router do
pipe_through PlausibleWeb.Plugs.AuthorizePublicAPI
get "/", ExternalSitesController, :index
+ get "/teams", ExternalSitesController, :teams_index
get "/goals", ExternalSitesController, :goals_index
get "/guests", ExternalSitesController, :guests_index
get "/:site_id", ExternalSitesController, :get_site
diff --git a/lib/plausible_web/templates/settings/team_general.html.heex b/lib/plausible_web/templates/settings/team_general.html.heex
index 7315c75cf59a..e8585895a09d 100644
--- a/lib/plausible_web/templates/settings/team_general.html.heex
+++ b/lib/plausible_web/templates/settings/team_general.html.heex
@@ -12,14 +12,6 @@
for={@team_name_changeset}
method="post"
>
-
- <.input_with_clipboard
- name="team-identifier"
- id="team-identifier"
- label="Team Identifier"
- value={@current_team.identifier}
- />
-
<.input
readonly={@current_team_role not in [:owner, :admin]}
type="text"
diff --git a/test/plausible_web/controllers/api/external_sites_controller_test.exs b/test/plausible_web/controllers/api/external_sites_controller_test.exs
index 4253930557fe..5cc2d3e1f961 100644
--- a/test/plausible_web/controllers/api/external_sites_controller_test.exs
+++ b/test/plausible_web/controllers/api/external_sites_controller_test.exs
@@ -14,6 +14,68 @@ defmodule PlausibleWeb.Api.ExternalSitesControllerTest do
{:ok, api_key: api_key, conn: conn}
end
+ describe "GET /api/v1/sites/teams" do
+ test "shows empty list when user is not a member of any team", %{conn: conn} do
+ conn = get(conn, "/api/v1/sites/teams")
+
+ assert json_response(conn, 200) == %{
+ "teams" => [],
+ "meta" => %{
+ "before" => nil,
+ "after" => nil,
+ "limit" => 100
+ }
+ }
+ end
+
+ test "shows list of teams user is a member of with api availability reflecting team state",
+ %{conn: conn, user: user} do
+ user |> subscribe_to_growth_plan()
+
+ personal_team = team_of(user)
+
+ owner1 =
+ new_user(
+ trial_expiry_date: Date.add(Date.utc_today(), -1),
+ team: [name: "Team Without Stats API"]
+ )
+ |> subscribe_to_enterprise_plan(features: [])
+
+ team_without_stats = owner1 |> team_of() |> Plausible.Teams.complete_setup()
+ add_member(team_without_stats, user: user, role: :editor)
+ owner2 = new_user(team: [name: "Team With Stats API"])
+ team_with_stats = owner2 |> team_of() |> Plausible.Teams.complete_setup()
+ add_member(team_with_stats, user: user, role: :owner)
+
+ conn = get(conn, "/api/v1/sites/teams")
+
+ assert json_response(conn, 200) == %{
+ "teams" => [
+ %{
+ "id" => team_with_stats.identifier,
+ "name" => "Team With Stats API",
+ "api_available" => true
+ },
+ %{
+ "id" => team_without_stats.identifier,
+ "name" => "Team Without Stats API",
+ "api_available" => false
+ },
+ %{
+ "id" => personal_team.identifier,
+ "name" => "My Personal Sites",
+ "api_available" => false
+ }
+ ],
+ "meta" => %{
+ "before" => nil,
+ "after" => nil,
+ "limit" => 100
+ }
+ }
+ end
+ end
+
describe "POST /api/v1/sites" do
test "can create a site", %{conn: conn} do
conn =
@@ -28,6 +90,45 @@ defmodule PlausibleWeb.Api.ExternalSitesControllerTest do
}
end
+ test "can't create site in a team where not permitted to", %{conn: conn, user: user} do
+ owner = new_user() |> subscribe_to_growth_plan()
+ team = owner |> team_of() |> Plausible.Teams.complete_setup()
+ add_member(team, user: user, role: :viewer)
+
+ conn =
+ post(conn, "/api/v1/sites", %{
+ "team_id" => team.identifier,
+ "domain" => "some-site.domain",
+ "timezone" => "Europe/Tallinn"
+ })
+
+ assert json_response(conn, 403) == %{
+ "error" => "You can't add sites to the selected team."
+ }
+ end
+
+ test "can create a site under a specific team if permitted", %{conn: conn, user: user} do
+ _site = new_site(owner: user)
+
+ owner = new_user() |> subscribe_to_growth_plan()
+ team = owner |> team_of() |> Plausible.Teams.complete_setup()
+ add_member(team, user: user, role: :owner)
+
+ conn =
+ post(conn, "/api/v1/sites", %{
+ "team_id" => team.identifier,
+ "domain" => "some-site.domain",
+ "timezone" => "Europe/Tallinn"
+ })
+
+ assert json_response(conn, 200) == %{
+ "domain" => "some-site.domain",
+ "timezone" => "Europe/Tallinn"
+ }
+
+ assert Repo.get_by(Plausible.Site, domain: "some-site.domain").team_id == team.id
+ end
+
test "timezone is validated", %{conn: conn} do
conn =
post(conn, "/api/v1/sites", %{
@@ -580,8 +681,7 @@ defmodule PlausibleWeb.Api.ExternalSitesControllerTest do
assert_matches %{
"sites" => [
- %{"domain" => ^other_team_site.domain},
- %{"domain" => ^other_site.domain}
+ %{"domain" => ^other_team_site.domain}
]
} = json_response(conn, 200)
end
diff --git a/test/plausible_web/controllers/auth_controller_test.exs b/test/plausible_web/controllers/auth_controller_test.exs
index 8e67e1969b00..dd4dc6d95ad2 100644
--- a/test/plausible_web/controllers/auth_controller_test.exs
+++ b/test/plausible_web/controllers/auth_controller_test.exs
@@ -707,7 +707,8 @@ defmodule PlausibleWeb.AuthControllerTest do
} do
another_owner = new_user()
another_site = new_site(owner: another_owner)
- add_member(another_site.team, user: user, role: :admin)
+ another_team = another_owner |> team_of() |> Plausible.Teams.complete_setup()
+ add_member(another_team, user: user, role: :admin)
segment =
insert(:segment,
@@ -728,7 +729,8 @@ defmodule PlausibleWeb.AuthControllerTest do
} do
another_owner = new_user()
another_site = new_site(owner: another_owner)
- add_member(another_site.team, user: user, role: :admin)
+ another_team = another_owner |> team_of() |> Plausible.Teams.complete_setup()
+ add_member(another_team, user: user, role: :admin)
segment =
insert(:segment,
@@ -749,7 +751,7 @@ defmodule PlausibleWeb.AuthControllerTest do
} do
another_owner = new_user()
another_site = new_site(owner: another_owner)
- team = team_of(another_owner)
+ team = another_owner |> team_of() |> Plausible.Teams.complete_setup()
add_member(another_site.team, user: user, role: :owner)
delete(conn, "/me")
@@ -766,7 +768,7 @@ defmodule PlausibleWeb.AuthControllerTest do
personal_team = team_of(user)
another_owner = new_user()
_another_site = new_site(owner: another_owner)
- another_team = team_of(another_owner)
+ another_team = another_owner |> team_of() |> Plausible.Teams.complete_setup()
add_member(another_team, user: user, role: :owner)
delete(conn, "/me")
diff --git a/test/plausible_web/controllers/settings_controller_test.exs b/test/plausible_web/controllers/settings_controller_test.exs
index dd51e4e839aa..6aac420b09c4 100644
--- a/test/plausible_web/controllers/settings_controller_test.exs
+++ b/test/plausible_web/controllers/settings_controller_test.exs
@@ -1182,7 +1182,6 @@ defmodule PlausibleWeb.SettingsControllerTest do
assert html =~ "Team Information"
assert html =~ "Change the name of your team"
assert text_of_attr(html, "input#team_name", "value") == team.name
- assert text_of_attr(html, "input#team-identifier", "value") == team.identifier
end
test "POST /settings/team/general/name", %{conn: conn, user: user} do