Skip to content

Commit e24616b

Browse files
easyCZroboquat
authored andcommitted
[public-api] Implement experimental ListTeams
1 parent debb16b commit e24616b

File tree

2 files changed

+167
-22
lines changed

2 files changed

+167
-22
lines changed

components/public-api-server/pkg/apiv1/team.go

Lines changed: 61 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,15 @@ import (
88
"context"
99
"fmt"
1010

11-
protocol "github.com/gitpod-io/gitpod/gitpod-protocol"
12-
"github.com/relvacode/iso8601"
13-
"google.golang.org/protobuf/types/known/timestamppb"
14-
1511
connect "github.com/bufbuild/connect-go"
1612
"github.com/gitpod-io/gitpod/common-go/log"
13+
protocol "github.com/gitpod-io/gitpod/gitpod-protocol"
1714
"github.com/gitpod-io/gitpod/public-api-server/pkg/auth"
1815
"github.com/gitpod-io/gitpod/public-api-server/pkg/proxy"
1916
v1 "github.com/gitpod-io/gitpod/public-api/experimental/v1"
2017
"github.com/gitpod-io/gitpod/public-api/experimental/v1/v1connect"
18+
"github.com/relvacode/iso8601"
19+
"google.golang.org/protobuf/types/known/timestamppb"
2120
)
2221

2322
func NewTeamsService(pool proxy.ServerConnectionPool) *TeamService {
@@ -41,38 +40,82 @@ func (s *TeamService) CreateTeam(ctx context.Context, req *connect.Request[v1.Cr
4140
return nil, connect.NewError(connect.CodeInvalidArgument, fmt.Errorf("Name is a required argument when creating a team."))
4241
}
4342

44-
server, err := s.connectionPool.Get(ctx, token)
43+
conn, err := s.connectionPool.Get(ctx, token)
4544
if err != nil {
4645
log.Log.WithError(err).Error("Failed to get connection to server.")
4746
return nil, connect.NewError(connect.CodeInternal, err)
4847
}
4948

50-
team, err := server.CreateTeam(ctx, req.Msg.GetName())
49+
created, err := conn.CreateTeam(ctx, req.Msg.GetName())
5150
if err != nil {
51+
log.WithError(err).Error("Failed to create team.")
5252
return nil, proxy.ConvertError(err)
5353
}
5454

55-
members, err := server.GetTeamMembers(ctx, team.ID)
55+
team, err := s.toTeamAPIResponse(ctx, conn, created)
5656
if err != nil {
57+
log.WithError(err).Error("Failed to populate team with details.")
58+
return nil, err
59+
}
60+
61+
return connect.NewResponse(&v1.CreateTeamResponse{
62+
Team: team,
63+
}), nil
64+
}
65+
66+
func (s *TeamService) ListTeams(ctx context.Context, req *connect.Request[v1.ListTeamsRequest]) (*connect.Response[v1.ListTeamsResponse], error) {
67+
token := auth.TokenFromContext(ctx)
68+
69+
conn, err := s.connectionPool.Get(ctx, token)
70+
if err != nil {
71+
log.Log.WithError(err).Error("Failed to get connection to server.")
72+
return nil, connect.NewError(connect.CodeInternal, err)
73+
}
74+
75+
teams, err := conn.GetTeams(ctx)
76+
if err != nil {
77+
log.WithError(err).Error("Failed to list teams from server.")
78+
return nil, proxy.ConvertError(err)
79+
}
80+
81+
var response []*v1.Team
82+
for _, t := range teams {
83+
team, err := s.toTeamAPIResponse(ctx, conn, t)
84+
if err != nil {
85+
log.WithError(err).Error("Failed to populate team with details.")
86+
return nil, err
87+
}
88+
89+
response = append(response, team)
90+
}
91+
92+
return connect.NewResponse(&v1.ListTeamsResponse{
93+
Teams: response,
94+
}), nil
95+
}
96+
97+
func (s *TeamService) toTeamAPIResponse(ctx context.Context, conn protocol.APIInterface, team *protocol.Team) (*v1.Team, error) {
98+
members, err := conn.GetTeamMembers(ctx, team.ID)
99+
if err != nil {
100+
log.WithError(err).Error("Failed to get team members.")
57101
return nil, proxy.ConvertError(err)
58102
}
59103

60-
invite, err := server.GetGenericInvite(ctx, team.ID)
104+
invite, err := conn.GetGenericInvite(ctx, team.ID)
61105
if err != nil {
106+
log.WithError(err).Error("Failed to get generic invite.")
62107
return nil, proxy.ConvertError(err)
63108
}
64109

65-
return connect.NewResponse(&v1.CreateTeamResponse{
66-
Team: &v1.Team{
67-
Id: team.ID,
68-
Name: team.Name,
69-
Slug: team.Slug,
70-
Members: teamMembersToAPIResponse(members),
71-
TeamInvitation: &v1.TeamInvitation{
72-
Id: invite.ID,
73-
},
110+
return &v1.Team{
111+
Id: team.ID,
112+
Name: team.Name,
113+
Slug: team.Slug,
114+
Members: teamMembersToAPIResponse(members),
115+
TeamInvitation: &v1.TeamInvitation{
116+
Id: invite.ID,
74117
},
75-
}), nil
118+
}, nil
76119
}
77120

78121
func teamMembersToAPIResponse(members []*protocol.TeamMemberInfo) []*v1.TeamMember {

components/public-api-server/pkg/apiv1/team_test.go

Lines changed: 106 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,16 @@ import (
2525
"google.golang.org/protobuf/types/known/timestamppb"
2626
)
2727

28+
const (
29+
memberSince = "2022-10-10T10:10:10.000Z"
30+
)
31+
2832
func TestTeamsService_CreateTeam(t *testing.T) {
2933

3034
var (
31-
name = "Shiny New Team"
32-
slug = "shiny-new-team"
33-
id = uuid.New().String()
34-
memberSince = "2022-10-10T10:10:10.000Z"
35+
name = "Shiny New Team"
36+
slug = "shiny-new-team"
37+
id = uuid.New().String()
3538
)
3639

3740
t.Run("returns invalid argument when name is empty", func(t *testing.T) {
@@ -121,6 +124,105 @@ func TestTeamsService_CreateTeam(t *testing.T) {
121124
})
122125
}
123126

127+
func TestTeamsService_ListTeams(t *testing.T) {
128+
t.Run("returns teams with members and invite", func(t *testing.T) {
129+
ctx := context.Background()
130+
serverMock, client := setupTeamService(t)
131+
132+
teamMembers := []*protocol.TeamMemberInfo{
133+
{
134+
UserId: uuid.New().String(),
135+
FullName: "Alice Alice",
136+
PrimaryEmail: "[email protected]",
137+
AvatarUrl: "",
138+
Role: protocol.TeamMember_Owner,
139+
MemberSince: memberSince,
140+
}, {
141+
UserId: uuid.New().String(),
142+
FullName: "Bob Bob",
143+
PrimaryEmail: "[email protected]",
144+
AvatarUrl: "",
145+
Role: protocol.TeamMember_Member,
146+
MemberSince: memberSince,
147+
},
148+
}
149+
teams := []*protocol.Team{
150+
{
151+
ID: uuid.New().String(),
152+
Name: "Team A",
153+
Slug: "team_a",
154+
}, {
155+
ID: uuid.New().String(),
156+
Name: "Team B",
157+
Slug: "team_ba",
158+
},
159+
}
160+
inviteID := uuid.New().String()
161+
162+
serverMock.EXPECT().GetTeams(gomock.Any()).Return(teams, nil)
163+
164+
// Mocks for populating team A details
165+
serverMock.EXPECT().GetTeamMembers(gomock.Any(), teams[0].ID).Return(teamMembers, nil)
166+
serverMock.EXPECT().GetGenericInvite(gomock.Any(), teams[0].ID).Return(&protocol.TeamMembershipInvite{
167+
ID: inviteID,
168+
TeamID: teams[0].ID,
169+
}, nil)
170+
// Mock for populating team B details
171+
serverMock.EXPECT().GetTeamMembers(gomock.Any(), teams[1].ID).Return(teamMembers, nil)
172+
serverMock.EXPECT().GetGenericInvite(gomock.Any(), teams[1].ID).Return(&protocol.TeamMembershipInvite{
173+
ID: inviteID,
174+
TeamID: teams[1].ID,
175+
}, nil)
176+
177+
response, err := client.ListTeams(ctx, connect.NewRequest(&v1.ListTeamsRequest{}))
178+
require.NoError(t, err)
179+
requireEqualProto(t, &v1.ListTeamsResponse{
180+
Teams: []*v1.Team{
181+
{
182+
Id: teams[0].ID,
183+
Name: teams[0].Name,
184+
Slug: teams[0].Slug,
185+
Members: []*v1.TeamMember{
186+
{
187+
UserId: teamMembers[0].UserId,
188+
Role: teamRoleToAPIResponse(teamMembers[0].Role),
189+
MemberSince: parseTimeStamp(memberSince),
190+
},
191+
{
192+
UserId: teamMembers[1].UserId,
193+
Role: teamRoleToAPIResponse(teamMembers[1].Role),
194+
MemberSince: parseTimeStamp(memberSince),
195+
},
196+
},
197+
TeamInvitation: &v1.TeamInvitation{
198+
Id: inviteID,
199+
},
200+
},
201+
{
202+
Id: teams[1].ID,
203+
Name: teams[1].Name,
204+
Slug: teams[1].Slug,
205+
Members: []*v1.TeamMember{
206+
{
207+
UserId: teamMembers[0].UserId,
208+
Role: teamRoleToAPIResponse(teamMembers[0].Role),
209+
MemberSince: parseTimeStamp(memberSince),
210+
},
211+
{
212+
UserId: teamMembers[1].UserId,
213+
Role: teamRoleToAPIResponse(teamMembers[1].Role),
214+
MemberSince: parseTimeStamp(memberSince),
215+
},
216+
},
217+
TeamInvitation: &v1.TeamInvitation{
218+
Id: inviteID,
219+
},
220+
},
221+
},
222+
}, response.Msg)
223+
})
224+
}
225+
124226
func setupTeamService(t *testing.T) (*protocol.MockAPIInterface, v1connect.TeamsServiceClient) {
125227
t.Helper()
126228

0 commit comments

Comments
 (0)