Skip to content

Commit c46f9bf

Browse files
authored
[server] implement organization API (#18948)
1 parent ad7ef3b commit c46f9bf

File tree

18 files changed

+6441
-501
lines changed

18 files changed

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

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)