Skip to content

Commit 8c41b80

Browse files
committed
[server] implement organization API
1 parent c7f8c35 commit 8c41b80

File tree

18 files changed

+6459
-501
lines changed

18 files changed

+6459
-501
lines changed
Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
/**
2+
* Copyright (c) 2023 Gitpod GmbH. All rights reserved.
3+
* Licensed under the GNU Affero General Public License (AGPL).
4+
* See License.AGPL.txt in the project root for license information.
5+
*/
6+
7+
import { PartialMessage } from "@bufbuild/protobuf";
8+
import { CallOptions, Code, ConnectError, PromiseClient } from "@connectrpc/connect";
9+
import { PublicAPIConverter } from "@gitpod/gitpod-protocol/lib/public-api-converter";
10+
import { OrganizationService } from "@gitpod/public-api/lib/gitpod/experimental/v2/organization_connect";
11+
import {
12+
CreateOrganizationRequest,
13+
CreateOrganizationResponse,
14+
DeleteOrganizationMemberRequest,
15+
DeleteOrganizationMemberResponse,
16+
DeleteOrganizationRequest,
17+
DeleteOrganizationResponse,
18+
GetOrganizationInvitationRequest,
19+
GetOrganizationInvitationResponse,
20+
GetOrganizationRequest,
21+
GetOrganizationResponse,
22+
GetOrganizationSettingsRequest,
23+
GetOrganizationSettingsResponse,
24+
JoinOrganizationRequest,
25+
JoinOrganizationResponse,
26+
ListOrganizationMembersRequest,
27+
ListOrganizationMembersResponse,
28+
ListOrganizationsRequest,
29+
ListOrganizationsResponse,
30+
ResetOrganizationInvitationRequest,
31+
ResetOrganizationInvitationResponse,
32+
UpdateOrganizationMemberRequest,
33+
UpdateOrganizationMemberResponse,
34+
UpdateOrganizationRequest,
35+
UpdateOrganizationResponse,
36+
UpdateOrganizationSettingsRequest,
37+
UpdateOrganizationSettingsResponse,
38+
} from "@gitpod/public-api/lib/gitpod/experimental/v2/organization_pb";
39+
import { getGitpodService } from "./service";
40+
41+
export class JsonRpcOrganizationClient implements PromiseClient<typeof OrganizationService> {
42+
// createOrganization: (request: PartialMessage<CreateOrganizationRequest>, options?: CallOptions | undefined) => Promise<...>;
43+
// getOrganization: (request: PartialMessage<GetOrganizationRequest>, options?: CallOptions | undefined) => Promise<...>;
44+
// updateOrganization: (request: PartialMessage<UpdateOrganizationRequest>, options?: CallOptions | undefined) => Promise<...>;
45+
// listOrganizations: (request: PartialMessage<ListOrganizationsRequest>, options?: CallOptions | undefined) => Promise<...>;
46+
// deleteOrganization: (request: PartialMessage<DeleteOrganizationRequest>, options?: CallOptions | undefined) => Promise<...>;
47+
// getOrganizationInvitation: (request: PartialMessage<GetOrganizationInvitationRequest>, options?: CallOptions | undefined) => Promise<...>;
48+
// joinOrganization: (request: PartialMessage<JoinOrganizationRequest>, options?: CallOptions | undefined) => Promise<...>;
49+
// resetOrganizationInvitation: (request: PartialMessage<ResetOrganizationInvitationRequest>, options?: CallOptions | undefined) => Promise<...>;
50+
// listOrganizationMembers: (request: PartialMessage<ListOrganizationMembersRequest>, options?: CallOptions | undefined) => Promise<...>;
51+
// updateOrganizationMember: (request: PartialMessage<UpdateOrganizationMemberRequest>, options?: CallOptions | undefined) => Promise<...>;
52+
// deleteOrganizationMember: (request: PartialMessage<DeleteOrganizationMemberRequest>, options?: CallOptions | undefined) => Promise<...>;
53+
// getOrganizationSettings: (request: PartialMessage<GetOrganizationSettingsRequest>, options?: CallOptions | undefined) => Promise<...>;
54+
// updateOrganizationSettings: (request: PartialMessage<UpdateOrganizationSettingsRequest>, options?: CallOptions | undefined) => Promise<...>;
55+
56+
private converter = new PublicAPIConverter();
57+
58+
async createOrganization(
59+
request: PartialMessage<CreateOrganizationRequest>,
60+
options?: CallOptions | undefined,
61+
): Promise<CreateOrganizationResponse> {
62+
if (!request.name) {
63+
throw new ConnectError("name is required", Code.InvalidArgument);
64+
}
65+
const result = await getGitpodService().server.createTeam(request.name);
66+
return new CreateOrganizationResponse({
67+
organization: this.converter.toOrganization(result),
68+
});
69+
}
70+
71+
async getOrganization(
72+
request: PartialMessage<GetOrganizationRequest>,
73+
options?: CallOptions | undefined,
74+
): Promise<GetOrganizationResponse> {
75+
if (!request.organizationId) {
76+
throw new ConnectError("id is required", Code.InvalidArgument);
77+
}
78+
const result = await getGitpodService().server.getTeam(request.organizationId);
79+
80+
return new GetOrganizationResponse({
81+
organization: this.converter.toOrganization(result),
82+
});
83+
}
84+
85+
async updateOrganization(
86+
request: PartialMessage<UpdateOrganizationRequest>,
87+
options?: CallOptions | undefined,
88+
): Promise<UpdateOrganizationResponse> {
89+
if (!request.organizationId) {
90+
throw new ConnectError("id is required", Code.InvalidArgument);
91+
}
92+
if (!request.name) {
93+
throw new ConnectError("name is required", Code.InvalidArgument);
94+
}
95+
await getGitpodService().server.updateTeam(request.organizationId, {
96+
name: request.name,
97+
});
98+
return new UpdateOrganizationResponse();
99+
}
100+
101+
async listOrganizations(
102+
request: PartialMessage<ListOrganizationsRequest>,
103+
options?: CallOptions | undefined,
104+
): Promise<ListOrganizationsResponse> {
105+
const result = await getGitpodService().server.getTeams();
106+
return new ListOrganizationsResponse({
107+
organizations: result.map((team) => this.converter.toOrganization(team)),
108+
});
109+
}
110+
111+
async deleteOrganization(
112+
request: PartialMessage<DeleteOrganizationRequest>,
113+
options?: CallOptions | undefined,
114+
): Promise<DeleteOrganizationResponse> {
115+
if (!request.organizationId) {
116+
throw new ConnectError("id is required", Code.InvalidArgument);
117+
}
118+
await getGitpodService().server.deleteTeam(request.organizationId);
119+
return new DeleteOrganizationResponse();
120+
}
121+
122+
async getOrganizationInvitation(
123+
request: PartialMessage<GetOrganizationInvitationRequest>,
124+
options?: CallOptions | undefined,
125+
): Promise<GetOrganizationInvitationResponse> {
126+
if (!request.organizationId) {
127+
throw new ConnectError("id is required", Code.InvalidArgument);
128+
}
129+
const result = await getGitpodService().server.getGenericInvite(request.organizationId);
130+
return new GetOrganizationInvitationResponse({
131+
invitationId: result.id,
132+
});
133+
}
134+
135+
async joinOrganization(
136+
request: PartialMessage<JoinOrganizationRequest>,
137+
options?: CallOptions | undefined,
138+
): Promise<JoinOrganizationResponse> {
139+
if (!request.invitationId) {
140+
throw new ConnectError("invitationId is required", Code.InvalidArgument);
141+
}
142+
const result = await getGitpodService().server.joinTeam(request.invitationId);
143+
return new JoinOrganizationResponse({
144+
organizationId: result.id,
145+
});
146+
}
147+
148+
async resetOrganizationInvitation(
149+
request: PartialMessage<ResetOrganizationInvitationRequest>,
150+
options?: CallOptions | undefined,
151+
): Promise<ResetOrganizationInvitationResponse> {
152+
if (!request.organizationId) {
153+
throw new ConnectError("id is required", Code.InvalidArgument);
154+
}
155+
const newInvite = await getGitpodService().server.resetGenericInvite(request.organizationId);
156+
return new ResetOrganizationInvitationResponse({
157+
invitationId: newInvite.id,
158+
});
159+
}
160+
161+
async listOrganizationMembers(
162+
request: PartialMessage<ListOrganizationMembersRequest>,
163+
options?: CallOptions | undefined,
164+
): Promise<ListOrganizationMembersResponse> {
165+
if (!request.organizationId) {
166+
throw new ConnectError("id is required", Code.InvalidArgument);
167+
}
168+
const result = await getGitpodService().server.getTeamMembers(request.organizationId);
169+
return new ListOrganizationMembersResponse({
170+
members: result.map((member) => this.converter.toOrganizationMember(member)),
171+
});
172+
}
173+
174+
async updateOrganizationMember(
175+
request: PartialMessage<UpdateOrganizationMemberRequest>,
176+
options?: CallOptions | undefined,
177+
): Promise<UpdateOrganizationMemberResponse> {
178+
if (!request.organizationId) {
179+
throw new ConnectError("id is required", Code.InvalidArgument);
180+
}
181+
if (!request.userId) {
182+
throw new ConnectError("userId is required", Code.InvalidArgument);
183+
}
184+
if (!request.role) {
185+
throw new ConnectError("role is required", Code.InvalidArgument);
186+
}
187+
await getGitpodService().server.setTeamMemberRole(
188+
request.organizationId,
189+
request.userId,
190+
this.converter.fromOrgMemberRole(request.role),
191+
);
192+
return new UpdateOrganizationMemberResponse();
193+
}
194+
195+
async deleteOrganizationMember(
196+
request: PartialMessage<DeleteOrganizationMemberRequest>,
197+
options?: CallOptions | undefined,
198+
): Promise<DeleteOrganizationMemberResponse> {
199+
if (!request.organizationId) {
200+
throw new ConnectError("id is required", Code.InvalidArgument);
201+
}
202+
if (!request.userId) {
203+
throw new ConnectError("userId is required", Code.InvalidArgument);
204+
}
205+
await getGitpodService().server.removeTeamMember(request.organizationId, request.userId);
206+
return new DeleteOrganizationMemberResponse();
207+
}
208+
209+
async getOrganizationSettings(
210+
request: PartialMessage<GetOrganizationSettingsRequest>,
211+
options?: CallOptions | undefined,
212+
): Promise<GetOrganizationSettingsResponse> {
213+
if (!request.organizationId) {
214+
throw new ConnectError("id is required", Code.InvalidArgument);
215+
}
216+
const result = await getGitpodService().server.getOrgSettings(request.organizationId);
217+
return new GetOrganizationSettingsResponse({
218+
settings: this.converter.toOrganizationSettings(result),
219+
});
220+
}
221+
222+
async updateOrganizationSettings(
223+
request: PartialMessage<UpdateOrganizationSettingsRequest>,
224+
options?: CallOptions | undefined,
225+
): Promise<UpdateOrganizationSettingsResponse> {
226+
if (!request.organizationId) {
227+
throw new ConnectError("id is required", Code.InvalidArgument);
228+
}
229+
await getGitpodService().server.updateOrgSettings(request.organizationId, {
230+
workspaceSharingDisabled: request.settings?.workspaceSharingDisabled,
231+
defaultWorkspaceImage: request.settings?.defaultWorkspaceImage,
232+
});
233+
return new UpdateOrganizationSettingsResponse();
234+
}
235+
}

components/dashboard/src/service/public-api.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ import { WorkspaceService } from "@gitpod/public-api/lib/gitpod/experimental/v2/
2222
import { getMetricsInterceptor } from "@gitpod/public-api/lib/metrics";
2323
import { getExperimentsClient } from "../experiments/client";
2424
import { JsonRpcWorkspaceClient } from "./json-rpc-workspace-client";
25+
import { JsonRpcOrganizationClient } from "./json-rpc-organization-client";
26+
import { OrganizationService } from "@gitpod/public-api/lib/gitpod/experimental/v2/organization_connect";
2527

2628
const transport = createConnectTransport({
2729
baseUrl: `${window.location.protocol}//${window.location.host}/public-api`,
@@ -41,6 +43,7 @@ export const workspacesService = createPromiseClient(WorkspaceV1Service, transpo
4143
export const oidcService = createPromiseClient(OIDCService, transport);
4244

4345
export const workspaceClient = createServiceClient(WorkspaceService, new JsonRpcWorkspaceClient());
46+
export const organizationClient = createServiceClient(OrganizationService, new JsonRpcOrganizationClient());
4447

4548
export function publicApiTeamToProtocol(team: Team): ProtocolTeam {
4649
return {
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
{
2+
"mochaExplorer.files": [
3+
"dist/**/*.spec.js",
4+
"dist/**/*.spec.db.js"
5+
],
6+
"mochaExplorer.require": [
7+
"source-map-support/register",
8+
"reflect-metadata/Reflect"
9+
],
10+
"mochaExplorer.watch": [
11+
"dist/**/*.spec.js",
12+
"dist/**/*.spec.db.js"
13+
],
14+
"mochaExplorer.exit": true,
15+
"mochaExplorer.timeout": 60000
16+
}

components/gitpod-protocol/BUILD.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ packages:
2020
packaging: library
2121
yarnLock: ${coreYarnLockBase}/yarn.lock
2222
tsconfig: tsconfig.json
23+
commands:
24+
# leeway executes the build and test step in the wrong order, so we need to call a special script that builds before testing
25+
test: ["yarn", "test:leeway"]
2326
- name: gitpod-schema
2427
type: generic
2528
srcs:

components/gitpod-protocol/package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,8 +41,9 @@
4141
"build": "yarn lint && tsc",
4242
"lint": "yarn eslint src/*.ts src/**/*.ts",
4343
"lint:fix": "yarn eslint src/*.ts src/**/*.ts --fix",
44-
"test": "mocha './**/*.spec.ts' --exclude './node_modules/**'",
45-
"test-debug": "mocha --inspect-brk './**/*.spec.ts' --exclude './node_modules/**' --exit",
44+
"test": "mocha './**/*.spec.js' --exclude './node_modules/**' --exit",
45+
"test:leeway": "yarn build && yarn test",
46+
"test-debug": "mocha --inspect-brk './**/*.spec.js' --exclude './node_modules/**' --exit",
4647
"watch": "leeway exec --package .:lib --transitive-dependencies --filter-type yarn --components --parallel -- tsc -w --preserveWatchOutput"
4748
},
4849
"mocha": {

0 commit comments

Comments
 (0)