Skip to content

Commit a997229

Browse files
jeanp413mustard-mh
andauthored
[public-api] Add installation service (#19150)
* Add installation service * Fix dashboard * Fix * Fix --------- Co-authored-by: Huiwen <[email protected]>
1 parent d327e5c commit a997229

27 files changed

+2845
-779
lines changed

components/dashboard/src/admin/BlockedEmailDomains.tsx

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,11 @@ import { ItemFieldContextMenu } from "../components/ItemsList";
1313
import Modal, { ModalBody, ModalFooter, ModalHeader } from "../components/Modal";
1414
import { CheckboxInputField } from "../components/forms/CheckboxInputField";
1515
import searchIcon from "../icons/search.svg";
16-
import { getGitpodService } from "../service/service";
1716
import { AdminPageHeader } from "./AdminPageHeader";
1817
import Pagination from "../Pagination/Pagination";
1918
import { Button } from "@podkit/buttons/Button";
19+
import { installationClient } from "../service/public-api";
20+
import { ListBlockedEmailDomainsResponse } from "@gitpod/public-api/lib/gitpod/v1/installation_pb";
2021

2122
export function BlockedEmailDomains() {
2223
return (
@@ -27,7 +28,7 @@ export function BlockedEmailDomains() {
2728
}
2829

2930
function useBlockedEmailDomains() {
30-
return useQuery(["blockedEmailDomains"], () => getGitpodService().server.adminGetBlockedEmailDomains(), {
31+
return useQuery(["blockedEmailDomains"], () => installationClient.listBlockedEmailDomains({}), {
3132
staleTime: 1000 * 60 * 5, // 5min
3233
});
3334
}
@@ -37,19 +38,21 @@ function useUpdateBlockedEmailDomainMutation() {
3738
const blockedEmailDomains = useBlockedEmailDomains();
3839
return useMutation(
3940
async (blockedDomain: EmailDomainFilterEntry) => {
40-
await getGitpodService().server.adminSaveBlockedEmailDomain(blockedDomain);
41+
await installationClient.createBlockedEmailDomain({
42+
domain: blockedDomain.domain,
43+
negative: blockedDomain.negative ?? false,
44+
});
4145
},
4246
{
4347
onSuccess: (_, blockedDomain) => {
44-
const updated = [];
45-
for (const entry of blockedEmailDomains.data || []) {
48+
const data = new ListBlockedEmailDomainsResponse(blockedEmailDomains.data);
49+
data.blockedEmailDomains.map((entry) => {
4650
if (entry.domain !== blockedDomain.domain) {
47-
updated.push(entry);
48-
} else {
49-
updated.push(blockedDomain);
51+
return entry;
5052
}
51-
}
52-
queryClient.setQueryData(["blockedEmailDomains"], updated);
53+
return blockedDomain;
54+
});
55+
queryClient.setQueryData(["blockedEmailDomains"], data);
5356
blockedEmailDomains.refetch();
5457
},
5558
},
@@ -74,7 +77,7 @@ export function BlockedEmailDomainsList(props: Props) {
7477
if (!blockedEmailDomains.data) {
7578
return [];
7679
}
77-
return blockedEmailDomains.data.filter((entry) =>
80+
return blockedEmailDomains.data.blockedEmailDomains.filter((entry) =>
7881
entry.domain.toLowerCase().includes(searchTerm.toLowerCase()),
7982
);
8083
}, [blockedEmailDomains.data, searchTerm]);

components/dashboard/src/admin/BlockedRepositories.tsx

Lines changed: 43 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,8 @@
44
* See License.AGPL.txt in the project root for license information.
55
*/
66

7-
import { AdminGetListResult } from "@gitpod/gitpod-protocol";
87
import { useCallback, useEffect, useRef, useState } from "react";
9-
import { getGitpodService } from "../service/service";
108
import { AdminPageHeader } from "./AdminPageHeader";
11-
import { BlockedRepository } from "@gitpod/gitpod-protocol/lib/blocked-repositories-protocol";
129
import ConfirmationModal from "../components/ConfirmationModal";
1310
import Modal, { ModalBody, ModalFooter, ModalHeader } from "../components/Modal";
1411
import { CheckboxInputField } from "../components/forms/CheckboxInputField";
@@ -18,6 +15,9 @@ import Alert from "../components/Alert";
1815
import { SpinnerLoader } from "../components/Loader";
1916
import searchIcon from "../icons/search.svg";
2017
import { Button } from "@podkit/buttons/Button";
18+
import { installationClient } from "../service/public-api";
19+
import { Sort, SortOrder } from "@gitpod/public-api/lib/gitpod/v1/sorting_pb";
20+
import { BlockedRepository, ListBlockedRepositoriesResponse } from "@gitpod/public-api/lib/gitpod/v1/installation_pb";
2121

2222
export function BlockedRepositories() {
2323
return (
@@ -33,27 +33,40 @@ type ExistingBlockedRepository = Pick<BlockedRepository, "id" | "urlRegexp" | "b
3333
interface Props {}
3434

3535
export function BlockedRepositoriesList(props: Props) {
36-
const [searchResult, setSearchResult] = useState<AdminGetListResult<BlockedRepository>>({ rows: [], total: 0 });
36+
const [searchResult, setSearchResult] = useState<ListBlockedRepositoriesResponse>(
37+
new ListBlockedRepositoriesResponse({
38+
blockedRepositories: [],
39+
}),
40+
);
3741
const [queryTerm, setQueryTerm] = useState("");
3842
const [searching, setSearching] = useState(false);
3943

4044
const [isAddModalVisible, setAddModalVisible] = useState(false);
4145
const [isDeleteModalVisible, setDeleteModalVisible] = useState(false);
4246

43-
const [currentBlockedRepository, setCurrentBlockedRepository] = useState<ExistingBlockedRepository>({
44-
id: 0,
45-
urlRegexp: "",
46-
blockUser: false,
47-
});
47+
const [currentBlockedRepository, setCurrentBlockedRepository] = useState<BlockedRepository>(
48+
new BlockedRepository({
49+
id: 0,
50+
urlRegexp: "",
51+
blockUser: false,
52+
}),
53+
);
4854

4955
const search = async () => {
5056
setSearching(true);
5157
try {
52-
const result = await getGitpodService().server.adminGetBlockedRepositories({
53-
limit: 100,
54-
orderBy: "urlRegexp",
55-
offset: 0,
56-
orderDir: "asc",
58+
const result = await installationClient.listBlockedRepositories({
59+
// Don't need, added it in json-rpc implement to make life easier.
60+
// pagination: new PaginationRequest({
61+
// token: Buffer.from(JSON.stringify({ offset: 0 })).toString("base64"),
62+
// pageSize: 100,
63+
// }),
64+
sort: [
65+
new Sort({
66+
field: "urlRegexp",
67+
order: SortOrder.ASC,
68+
}),
69+
],
5770
searchTerm: queryTerm,
5871
});
5972
setSearchResult(result);
@@ -67,19 +80,21 @@ export function BlockedRepositoriesList(props: Props) {
6780
}, []);
6881

6982
const add = () => {
70-
setCurrentBlockedRepository({
71-
id: 0,
72-
urlRegexp: "",
73-
blockUser: false,
74-
});
83+
setCurrentBlockedRepository(
84+
new BlockedRepository({
85+
id: 0,
86+
urlRegexp: "",
87+
blockUser: false,
88+
}),
89+
);
7590
setAddModalVisible(true);
7691
};
7792

7893
const save = async (blockedRepository: NewBlockedRepository) => {
79-
await getGitpodService().server.adminCreateBlockedRepository(
80-
blockedRepository.urlRegexp,
81-
blockedRepository.blockUser,
82-
);
94+
await installationClient.createBlockedRepository({
95+
urlRegexp: blockedRepository.urlRegexp ?? "",
96+
blockUser: blockedRepository.blockUser ?? false,
97+
});
8398
setAddModalVisible(false);
8499
search();
85100
};
@@ -91,11 +106,13 @@ export function BlockedRepositoriesList(props: Props) {
91106
};
92107

93108
const deleteBlockedRepository = async (blockedRepository: ExistingBlockedRepository) => {
94-
await getGitpodService().server.adminDeleteBlockedRepository(blockedRepository.id);
109+
await installationClient.deleteBlockedRepository({
110+
blockedRepositoryId: blockedRepository.id,
111+
});
95112
search();
96113
};
97114

98-
const confirmDeleteBlockedRepository = (blockedRepository: ExistingBlockedRepository) => {
115+
const confirmDeleteBlockedRepository = (blockedRepository: BlockedRepository) => {
99116
setCurrentBlockedRepository(blockedRepository);
100117
setAddModalVisible(false);
101118
setDeleteModalVisible(true);
@@ -158,7 +175,7 @@ export function BlockedRepositoriesList(props: Props) {
158175
<div className="w-1/12">Block Users</div>
159176
<div className="w-1/12"></div>
160177
</div>
161-
{searchResult.rows.map((br) => (
178+
{searchResult.blockedRepositories.map((br) => (
162179
<BlockedRepositoryEntry br={br} confirmedDelete={confirmDeleteBlockedRepository} />
163180
))}
164181
</div>

components/dashboard/src/data/setup.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,14 @@ import * as AuthProviderClasses from "@gitpod/public-api/lib/gitpod/v1/authprovi
2525
import * as EnvVarClasses from "@gitpod/public-api/lib/gitpod/v1/envvar_pb";
2626
import * as PrebuildClasses from "@gitpod/public-api/lib/gitpod/v1/prebuild_pb";
2727
import * as VerificationClasses from "@gitpod/public-api/lib/gitpod/v1/verification_pb";
28+
import * as InstallationClasses from "@gitpod/public-api/lib/gitpod/v1/installation_pb";
2829
import * as SCMClasses from "@gitpod/public-api/lib/gitpod/v1/scm_pb";
2930
import * as SSHClasses from "@gitpod/public-api/lib/gitpod/v1/ssh_pb";
3031

3132
// This is used to version the cache
3233
// If data we cache changes in a non-backwards compatible way, increment this version
3334
// That will bust any previous cache versions a client may have stored
34-
const CACHE_VERSION = "11";
35+
const CACHE_VERSION = "12";
3536

3637
export function noPersistence(queryKey: QueryKey): QueryKey {
3738
return [...queryKey, "no-persistence"];
@@ -154,6 +155,7 @@ function initializeMessages() {
154155
...Object.values(EnvVarClasses),
155156
...Object.values(PrebuildClasses),
156157
...Object.values(VerificationClasses),
158+
...Object.values(InstallationClasses),
157159
...Object.values(SCMClasses),
158160
...Object.values(SSHClasses),
159161
];
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
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 { CallOptions, PromiseClient } from "@connectrpc/connect";
8+
import { PartialMessage } from "@bufbuild/protobuf";
9+
import { InstallationService } from "@gitpod/public-api/lib/gitpod/v1/installation_connect";
10+
import {
11+
ListBlockedRepositoriesRequest,
12+
ListBlockedRepositoriesResponse,
13+
CreateBlockedRepositoryRequest,
14+
CreateBlockedRepositoryResponse,
15+
DeleteBlockedRepositoryRequest,
16+
DeleteBlockedRepositoryResponse,
17+
ListBlockedEmailDomainsRequest,
18+
ListBlockedEmailDomainsResponse,
19+
CreateBlockedEmailDomainRequest,
20+
CreateBlockedEmailDomainResponse,
21+
} from "@gitpod/public-api/lib/gitpod/v1/installation_pb";
22+
import { ApplicationError, ErrorCodes } from "@gitpod/gitpod-protocol/lib/messaging/error";
23+
import { getGitpodService } from "./service";
24+
import { converter } from "./public-api";
25+
import { PaginationResponse } from "@gitpod/public-api/lib/gitpod/v1/pagination_pb";
26+
27+
export class JsonRpcInstallationClient implements PromiseClient<typeof InstallationService> {
28+
async listBlockedRepositories(
29+
request: PartialMessage<ListBlockedRepositoriesRequest>,
30+
_options?: CallOptions | undefined,
31+
): Promise<ListBlockedRepositoriesResponse> {
32+
// dashboard params is constant, no need to implement
33+
const info = await getGitpodService().server.adminGetBlockedRepositories({
34+
limit: 100,
35+
offset: 0,
36+
orderBy: "urlRegexp",
37+
orderDir: "asc",
38+
searchTerm: request.searchTerm,
39+
});
40+
return new ListBlockedRepositoriesResponse({
41+
blockedRepositories: info.rows.map((item) => converter.toBlockedRepository(item)),
42+
pagination: new PaginationResponse(),
43+
});
44+
}
45+
46+
async createBlockedRepository(
47+
request: PartialMessage<CreateBlockedRepositoryRequest>,
48+
_options?: CallOptions | undefined,
49+
): Promise<CreateBlockedRepositoryResponse> {
50+
if (!request.urlRegexp) {
51+
throw new ApplicationError(ErrorCodes.BAD_REQUEST, "urlRegexp is required");
52+
}
53+
if (request.blockUser === undefined) {
54+
throw new ApplicationError(ErrorCodes.BAD_REQUEST, "blockUser is required");
55+
}
56+
const info = await getGitpodService().server.adminCreateBlockedRepository(request.urlRegexp, request.blockUser);
57+
return new CreateBlockedRepositoryResponse({
58+
blockedRepository: converter.toBlockedRepository(info),
59+
});
60+
}
61+
62+
async deleteBlockedRepository(
63+
request: PartialMessage<DeleteBlockedRepositoryRequest>,
64+
_options?: CallOptions | undefined,
65+
): Promise<DeleteBlockedRepositoryResponse> {
66+
if (!request.blockedRepositoryId) {
67+
throw new ApplicationError(ErrorCodes.BAD_REQUEST, "blockedRepositoryId is required");
68+
}
69+
await getGitpodService().server.adminDeleteBlockedRepository(request.blockedRepositoryId);
70+
return new DeleteBlockedRepositoryResponse();
71+
}
72+
73+
async listBlockedEmailDomains(
74+
request: PartialMessage<ListBlockedEmailDomainsRequest>,
75+
_options?: CallOptions | undefined,
76+
): Promise<ListBlockedEmailDomainsResponse> {
77+
const info = await getGitpodService().server.adminGetBlockedEmailDomains();
78+
return new ListBlockedEmailDomainsResponse({
79+
blockedEmailDomains: info.map((item) => converter.toBlockedEmailDomain(item)),
80+
pagination: new PaginationResponse(),
81+
});
82+
}
83+
84+
async createBlockedEmailDomain(
85+
request: PartialMessage<CreateBlockedEmailDomainRequest>,
86+
_options?: CallOptions | undefined,
87+
): Promise<CreateBlockedEmailDomainResponse> {
88+
if (!request.domain) {
89+
throw new ApplicationError(ErrorCodes.BAD_REQUEST, "domain is required");
90+
}
91+
if (request.negative === undefined) {
92+
throw new ApplicationError(ErrorCodes.BAD_REQUEST, "negative is required");
93+
}
94+
await getGitpodService().server.adminSaveBlockedEmailDomain({
95+
domain: request.domain,
96+
negative: request.negative,
97+
});
98+
// There's no way to get blockedEmailDomain, just ignore it since dashboard don't care about the response data
99+
return new CreateBlockedEmailDomainResponse({});
100+
}
101+
}

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ import { SSHService } from "@gitpod/public-api/lib/gitpod/v1/ssh_connect";
3838
import { JsonRpcSSHClient } from "./json-rpc-ssh-client";
3939
import { JsonRpcVerificationClient } from "./json-rpc-verification-client";
4040
import { VerificationService } from "@gitpod/public-api/lib/gitpod/v1/verification_connect";
41+
import { JsonRpcInstallationClient } from "./json-rpc-installation-client";
42+
import { InstallationService } from "@gitpod/public-api/lib/gitpod/v1/installation_connect";
4143

4244
const transport = createConnectTransport({
4345
baseUrl: `${window.location.protocol}//${window.location.host}/public-api`,
@@ -66,6 +68,7 @@ export const organizationClient = createServiceClient(OrganizationService, {
6668
client: new JsonRpcOrganizationClient(),
6769
featureFlagSuffix: "organization",
6870
});
71+
6972
// No jsonrcp client for the configuration service as it's only used in new UI of the dashboard
7073
export const configurationClient = createServiceClient(ConfigurationService);
7174
export const prebuildClient = createServiceClient(PrebuildService, {
@@ -98,6 +101,11 @@ export const verificationClient = createServiceClient(VerificationService, {
98101
featureFlagSuffix: "verification",
99102
});
100103

104+
export const installationClient = createServiceClient(InstallationService, {
105+
client: new JsonRpcInstallationClient(),
106+
featureFlagSuffix: "installation",
107+
});
108+
101109
export async function listAllProjects(opts: { orgId: string }): Promise<ProtocolProject[]> {
102110
let pagination = {
103111
page: 1,

components/gitpod-db/src/email-domain-filter-db.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { EmailDomainFilterEntry } from "@gitpod/gitpod-protocol";
88

99
export const EmailDomainFilterDB = Symbol("EmailDomainFilterDB");
1010
export interface EmailDomainFilterDB {
11-
storeFilterEntry(entry: EmailDomainFilterEntry): Promise<void>;
11+
storeFilterEntry(entry: EmailDomainFilterEntry): Promise<EmailDomainFilterEntry>;
1212
getFilterEntries(): Promise<EmailDomainFilterEntry[]>;
1313

1414
/**

components/gitpod-db/src/typeorm/email-domain-filter-db-impl.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@ export class EmailDomainFilterDBImpl implements EmailDomainFilterDB {
2424
return (await this.getManager()).getRepository<DBEmailDomainFilterEntry>(DBEmailDomainFilterEntry);
2525
}
2626

27-
async storeFilterEntry(entry: EmailDomainFilterEntry): Promise<void> {
27+
async storeFilterEntry(entry: EmailDomainFilterEntry): Promise<EmailDomainFilterEntry> {
2828
const repo = await this.getRepo();
29-
await repo.save(entry);
29+
return await repo.save(entry);
3030
}
3131

3232
async getFilterEntries(): Promise<EmailDomainFilterEntry[]> {

0 commit comments

Comments
 (0)