Skip to content

Commit bc143d4

Browse files
authored
Add GET /api/v1/sites/teams endpoint and make related adjustments (#5220)
* List only sites under team in Sites API if team id provided * Add tests for Sites API create site with team_id provided * Implement `GET /api/v1/sites/teams` endpoint * Remove team identifier input from Team settings * Use feature availability function * Fix tests setup
1 parent 8d9461d commit bc143d4

File tree

8 files changed

+162
-24
lines changed

8 files changed

+162
-24
lines changed

extra/lib/plausible_web/controllers/api/external_sites_controller.ex

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,30 @@ defmodule PlausibleWeb.Api.ExternalSitesController do
5252
end
5353
end
5454

55+
def teams_index(conn, params) do
56+
user = conn.assigns.current_user
57+
58+
page =
59+
user
60+
|> Teams.Users.teams_query(order_by: :id_desc)
61+
|> paginate(params, @pagination_opts)
62+
63+
json(conn, %{
64+
teams:
65+
Enum.map(page.entries, fn team ->
66+
api_available? =
67+
Plausible.Billing.Feature.StatsAPI.check_availability(team) == :ok
68+
69+
%{
70+
id: team.identifier,
71+
name: Teams.name(team),
72+
api_available: api_available?
73+
}
74+
end),
75+
meta: pagination_meta(page.metadata)
76+
})
77+
end
78+
5579
def goals_index(conn, params) do
5680
user = conn.assigns.current_user
5781

lib/plausible/sites.ex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ defmodule Plausible.Sites do
231231
where(
232232
query,
233233
[team_memberships: tm, guest_memberships: gm, site: s],
234-
(tm.role != :guest and tm.team_id == ^team.id) or gm.site_id == s.id
234+
tm.role != :guest and tm.team_id == ^team.id
235235
)
236236
else
237237
where(

lib/plausible/teams/users.ex

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,18 +21,38 @@ defmodule Plausible.Teams.Users do
2121
end
2222

2323
def teams(user) do
24-
from(
25-
tm in Teams.Membership,
26-
inner_join: t in assoc(tm, :team),
27-
where: tm.user_id == ^user.id,
28-
where: tm.role != :guest,
29-
select: t,
30-
order_by: [t.name, t.id]
31-
)
24+
user
25+
|> teams_query(order_by: :name)
3226
|> Repo.all()
3327
|> Repo.preload(:owners)
3428
end
3529

30+
def teams_query(user, opts \\ []) do
31+
order_by = Keyword.get(opts, :order_by, :name)
32+
33+
query =
34+
from(
35+
tm in Teams.Membership,
36+
as: :team_membership,
37+
inner_join: t in assoc(tm, :team),
38+
as: :team,
39+
where: tm.user_id == ^user.id,
40+
where: tm.role != :guest,
41+
select: t
42+
)
43+
44+
case order_by do
45+
:name ->
46+
order_by(query, [team: t], [t.name, t.id])
47+
48+
:id_desc ->
49+
order_by(query, [team: t], desc: t.id)
50+
51+
_ ->
52+
query
53+
end
54+
end
55+
3656
def team_member?(user, opts \\ []) do
3757
excluded_team_ids = Keyword.get(opts, :except, [])
3858

lib/plausible_web/router.ex

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,7 @@ defmodule PlausibleWeb.Router do
266266
pipe_through PlausibleWeb.Plugs.AuthorizePublicAPI
267267

268268
get "/", ExternalSitesController, :index
269+
get "/teams", ExternalSitesController, :teams_index
269270
get "/goals", ExternalSitesController, :goals_index
270271
get "/guests", ExternalSitesController, :guests_index
271272
get "/:site_id", ExternalSitesController, :get_site

lib/plausible_web/templates/settings/team_general.html.heex

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,6 @@
1212
for={@team_name_changeset}
1313
method="post"
1414
>
15-
<div class="w-1/2">
16-
<.input_with_clipboard
17-
name="team-identifier"
18-
id="team-identifier"
19-
label="Team Identifier"
20-
value={@current_team.identifier}
21-
/>
22-
</div>
2315
<.input
2416
readonly={@current_team_role not in [:owner, :admin]}
2517
type="text"

test/plausible_web/controllers/api/external_sites_controller_test.exs

Lines changed: 102 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,68 @@ defmodule PlausibleWeb.Api.ExternalSitesControllerTest do
1414
{:ok, api_key: api_key, conn: conn}
1515
end
1616

17+
describe "GET /api/v1/sites/teams" do
18+
test "shows empty list when user is not a member of any team", %{conn: conn} do
19+
conn = get(conn, "/api/v1/sites/teams")
20+
21+
assert json_response(conn, 200) == %{
22+
"teams" => [],
23+
"meta" => %{
24+
"before" => nil,
25+
"after" => nil,
26+
"limit" => 100
27+
}
28+
}
29+
end
30+
31+
test "shows list of teams user is a member of with api availability reflecting team state",
32+
%{conn: conn, user: user} do
33+
user |> subscribe_to_growth_plan()
34+
35+
personal_team = team_of(user)
36+
37+
owner1 =
38+
new_user(
39+
trial_expiry_date: Date.add(Date.utc_today(), -1),
40+
team: [name: "Team Without Stats API"]
41+
)
42+
|> subscribe_to_enterprise_plan(features: [])
43+
44+
team_without_stats = owner1 |> team_of() |> Plausible.Teams.complete_setup()
45+
add_member(team_without_stats, user: user, role: :editor)
46+
owner2 = new_user(team: [name: "Team With Stats API"])
47+
team_with_stats = owner2 |> team_of() |> Plausible.Teams.complete_setup()
48+
add_member(team_with_stats, user: user, role: :owner)
49+
50+
conn = get(conn, "/api/v1/sites/teams")
51+
52+
assert json_response(conn, 200) == %{
53+
"teams" => [
54+
%{
55+
"id" => team_with_stats.identifier,
56+
"name" => "Team With Stats API",
57+
"api_available" => true
58+
},
59+
%{
60+
"id" => team_without_stats.identifier,
61+
"name" => "Team Without Stats API",
62+
"api_available" => false
63+
},
64+
%{
65+
"id" => personal_team.identifier,
66+
"name" => "My Personal Sites",
67+
"api_available" => false
68+
}
69+
],
70+
"meta" => %{
71+
"before" => nil,
72+
"after" => nil,
73+
"limit" => 100
74+
}
75+
}
76+
end
77+
end
78+
1779
describe "POST /api/v1/sites" do
1880
test "can create a site", %{conn: conn} do
1981
conn =
@@ -28,6 +90,45 @@ defmodule PlausibleWeb.Api.ExternalSitesControllerTest do
2890
}
2991
end
3092

93+
test "can't create site in a team where not permitted to", %{conn: conn, user: user} do
94+
owner = new_user() |> subscribe_to_growth_plan()
95+
team = owner |> team_of() |> Plausible.Teams.complete_setup()
96+
add_member(team, user: user, role: :viewer)
97+
98+
conn =
99+
post(conn, "/api/v1/sites", %{
100+
"team_id" => team.identifier,
101+
"domain" => "some-site.domain",
102+
"timezone" => "Europe/Tallinn"
103+
})
104+
105+
assert json_response(conn, 403) == %{
106+
"error" => "You can't add sites to the selected team."
107+
}
108+
end
109+
110+
test "can create a site under a specific team if permitted", %{conn: conn, user: user} do
111+
_site = new_site(owner: user)
112+
113+
owner = new_user() |> subscribe_to_growth_plan()
114+
team = owner |> team_of() |> Plausible.Teams.complete_setup()
115+
add_member(team, user: user, role: :owner)
116+
117+
conn =
118+
post(conn, "/api/v1/sites", %{
119+
"team_id" => team.identifier,
120+
"domain" => "some-site.domain",
121+
"timezone" => "Europe/Tallinn"
122+
})
123+
124+
assert json_response(conn, 200) == %{
125+
"domain" => "some-site.domain",
126+
"timezone" => "Europe/Tallinn"
127+
}
128+
129+
assert Repo.get_by(Plausible.Site, domain: "some-site.domain").team_id == team.id
130+
end
131+
31132
test "timezone is validated", %{conn: conn} do
32133
conn =
33134
post(conn, "/api/v1/sites", %{
@@ -580,8 +681,7 @@ defmodule PlausibleWeb.Api.ExternalSitesControllerTest do
580681

581682
assert_matches %{
582683
"sites" => [
583-
%{"domain" => ^other_team_site.domain},
584-
%{"domain" => ^other_site.domain}
684+
%{"domain" => ^other_team_site.domain}
585685
]
586686
} = json_response(conn, 200)
587687
end

test/plausible_web/controllers/auth_controller_test.exs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -707,7 +707,8 @@ defmodule PlausibleWeb.AuthControllerTest do
707707
} do
708708
another_owner = new_user()
709709
another_site = new_site(owner: another_owner)
710-
add_member(another_site.team, user: user, role: :admin)
710+
another_team = another_owner |> team_of() |> Plausible.Teams.complete_setup()
711+
add_member(another_team, user: user, role: :admin)
711712

712713
segment =
713714
insert(:segment,
@@ -728,7 +729,8 @@ defmodule PlausibleWeb.AuthControllerTest do
728729
} do
729730
another_owner = new_user()
730731
another_site = new_site(owner: another_owner)
731-
add_member(another_site.team, user: user, role: :admin)
732+
another_team = another_owner |> team_of() |> Plausible.Teams.complete_setup()
733+
add_member(another_team, user: user, role: :admin)
732734

733735
segment =
734736
insert(:segment,
@@ -749,7 +751,7 @@ defmodule PlausibleWeb.AuthControllerTest do
749751
} do
750752
another_owner = new_user()
751753
another_site = new_site(owner: another_owner)
752-
team = team_of(another_owner)
754+
team = another_owner |> team_of() |> Plausible.Teams.complete_setup()
753755
add_member(another_site.team, user: user, role: :owner)
754756

755757
delete(conn, "/me")
@@ -766,7 +768,7 @@ defmodule PlausibleWeb.AuthControllerTest do
766768
personal_team = team_of(user)
767769
another_owner = new_user()
768770
_another_site = new_site(owner: another_owner)
769-
another_team = team_of(another_owner)
771+
another_team = another_owner |> team_of() |> Plausible.Teams.complete_setup()
770772
add_member(another_team, user: user, role: :owner)
771773

772774
delete(conn, "/me")

test/plausible_web/controllers/settings_controller_test.exs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1182,7 +1182,6 @@ defmodule PlausibleWeb.SettingsControllerTest do
11821182
assert html =~ "Team Information"
11831183
assert html =~ "Change the name of your team"
11841184
assert text_of_attr(html, "input#team_name", "value") == team.name
1185-
assert text_of_attr(html, "input#team-identifier", "value") == team.identifier
11861185
end
11871186

11881187
test "POST /settings/team/general/name", %{conn: conn, user: user} do

0 commit comments

Comments
 (0)