Skip to content

Commit 3ba583c

Browse files
authored
[server] add searchString to getRepositoriesForAutomatedPrebuildsEXP-461 (#18533)
* [server] add `searchString` to `getRepositoriesForAutomatedPrebuilds` * reintroduce and fix paginated requests * using a default cap at 10 requests per paginated request * make getProviderRepositoriesForUser cancellable
1 parent 7e3ccd1 commit 3ba583c

File tree

7 files changed

+263
-61
lines changed

7 files changed

+263
-61
lines changed

components/dashboard/src/projects/NewProject.tsx

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

7-
import { AuthProviderInfo, Project, ProviderRepository, Team } from "@gitpod/gitpod-protocol";
7+
import {
8+
AuthProviderInfo,
9+
Disposable,
10+
DisposableCollection,
11+
Project,
12+
ProviderRepository,
13+
Team,
14+
} from "@gitpod/gitpod-protocol";
815
import dayjs from "dayjs";
9-
import { useCallback, useContext, useEffect, useState } from "react";
16+
import { useCallback, useContext, useEffect, useMemo, useState } from "react";
1017
import { trackEvent } from "../Analytics";
1118
import ContextMenu, { ContextMenuEntry } from "../components/ContextMenu";
1219
import ErrorMessage from "../components/ErrorMessage";
@@ -25,6 +32,8 @@ import { projectsPathNew } from "./projects.routes";
2532
import { Heading1, Subheading } from "../components/typography/headings";
2633
import { useAuthProviders } from "../data/auth-providers/auth-provider-query";
2734
import { AuthorizeGit, useNeedsGitAuthorization } from "../components/AuthorizeGit";
35+
import { useToast } from "../components/toasts/Toasts";
36+
import { CancellationTokenSource, CancellationToken } from "vscode-jsonrpc";
2837

2938
export default function NewProject() {
3039
const currentTeam = useCurrentOrg()?.data;
@@ -45,12 +54,20 @@ export default function NewProject() {
4554
const authProviders = useAuthProviders();
4655
const [isGitHubAppEnabled, setIsGitHubAppEnabled] = useState<boolean>();
4756
const [isGitHubWebhooksUnauthorized, setIsGitHubWebhooksUnauthorized] = useState<boolean>();
57+
const { toast } = useToast();
58+
const toDispose = useMemo(() => new DisposableCollection(), []);
4859

4960
useEffect(() => {
5061
const { server } = getGitpodService();
5162
Promise.all([server.isGitHubAppEnabled().then((v) => () => setIsGitHubAppEnabled(v))]).then((setters) =>
5263
setters.forEach((s) => s()),
5364
);
65+
return () => {
66+
if (!toDispose.disposed) {
67+
toDispose.dispose();
68+
}
69+
};
70+
// eslint-disable-next-line react-hooks/exhaustive-deps
5471
}, []);
5572

5673
useEffect(() => {
@@ -116,15 +133,43 @@ export default function NewProject() {
116133
setRepoSearchFilter("");
117134
}, [selectedAccount]);
118135

136+
const updateReposInAccounts = useCallback(
137+
async (cancellationToken: CancellationToken, installationId?: string) => {
138+
setLoaded(false);
139+
setReposInAccounts([]);
140+
if (!selectedProviderHost) {
141+
return [];
142+
}
143+
try {
144+
const repos = await getGitpodService().server.getProviderRepositoriesForUser(
145+
{
146+
provider: selectedProviderHost,
147+
hints: { installationId },
148+
},
149+
cancellationToken,
150+
);
151+
setReposInAccounts(repos);
152+
setLoaded(true);
153+
} catch (error) {
154+
console.log(error);
155+
setLoaded(true);
156+
toast(error?.message || "Oh no, there was a problem with fetching repositories.");
157+
}
158+
},
159+
[selectedProviderHost, toast],
160+
);
161+
119162
useEffect(() => {
120163
if (!selectedProviderHost) {
121164
return;
122165
}
166+
const cancellation = new CancellationTokenSource();
167+
toDispose.push(Disposable.create(() => cancellation.cancel()));
123168
(async () => {
124-
await updateReposInAccounts();
169+
await updateReposInAccounts(cancellation.token);
125170
})();
126-
// eslint-disable-next-line react-hooks/exhaustive-deps
127-
}, [selectedProviderHost]);
171+
return () => cancellation.cancel();
172+
}, [selectedProviderHost, updateReposInAccounts, toDispose]);
128173

129174
useEffect(() => {
130175
if (project) {
@@ -134,31 +179,11 @@ export default function NewProject() {
134179

135180
const isGitHub = () => selectedProviderHost === "github.com";
136181

137-
const updateReposInAccounts = async (installationId?: string) => {
138-
setLoaded(false);
139-
setReposInAccounts([]);
140-
if (!selectedProviderHost) {
141-
return [];
142-
}
143-
try {
144-
const repos = await getGitpodService().server.getProviderRepositoriesForUser({
145-
provider: selectedProviderHost,
146-
hints: { installationId },
147-
});
148-
setReposInAccounts(repos);
149-
setLoaded(true);
150-
return repos;
151-
} catch (error) {
152-
console.log(error);
153-
}
154-
return [];
155-
};
156-
157182
const reconfigure = () => {
158183
openReconfigureWindow({
159184
account: selectedAccount,
160185
onSuccess: (p: { installationId: string; setupAction?: string }) => {
161-
updateReposInAccounts(p.installationId);
186+
updateReposInAccounts(CancellationToken.None, p.installationId);
162187
trackEvent("organisation_authorised", {
163188
installation_id: p.installationId,
164189
setup_action: p.setupAction,

components/gitpod-protocol/src/gitpod-service.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ import {
4444
OrganizationSettings,
4545
} from "./teams-projects-protocol";
4646
import { JsonRpcProxy, JsonRpcServer } from "./messaging/proxy-factory";
47-
import { Disposable, CancellationTokenSource } from "vscode-jsonrpc";
47+
import { Disposable, CancellationTokenSource, CancellationToken } from "vscode-jsonrpc";
4848
import { HeadlessLogUrls } from "./headless-workspace-log";
4949
import {
5050
WorkspaceInstance,
@@ -175,7 +175,10 @@ export interface GitpodServer extends JsonRpcServer<GitpodClient>, AdminServer,
175175
getOnboardingState(): Promise<GitpodServer.OnboardingState>;
176176

177177
// Projects
178-
getProviderRepositoriesForUser(params: GetProviderRepositoriesParams): Promise<ProviderRepository[]>;
178+
getProviderRepositoriesForUser(
179+
params: GetProviderRepositoriesParams,
180+
cancellationToken?: CancellationToken,
181+
): Promise<ProviderRepository[]>;
179182
createProject(params: CreateProjectParams): Promise<Project>;
180183
deleteProject(projectId: string): Promise<void>;
181184
getTeamProjects(teamId: string): Promise<Project[]>;
@@ -291,6 +294,7 @@ export interface FindPrebuildsParams {
291294
export interface GetProviderRepositoriesParams {
292295
provider: string;
293296
hints?: { installationId: string } | object;
297+
searchString?: string;
294298
}
295299
export interface ProviderRepository {
296300
name: string;

components/server/src/bitbucket-server/bitbucket-server-api.spec.ts

Lines changed: 119 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ class TestBitbucketServerApi {
2828
id: "MyBitbucketServer",
2929
type: "BitbucketServer",
3030
verified: true,
31-
host: "bitbucket.gitpod-self-hosted.com",
31+
host: "bitbucket.gitpod-dev.com",
3232
oauth: {} as any,
3333
};
3434

@@ -77,12 +77,12 @@ class TestBitbucketServerApi {
7777
};
7878
}
7979

80-
@test async test_currentUsername_ok() {
80+
@test.skip async test_currentUsername_ok() {
8181
const result = await this.api.currentUsername(process.env["GITPOD_TEST_TOKEN_BITBUCKET_SERVER"]!);
8282
expect(result).to.equal("AlexTugarev");
8383
}
8484

85-
@test async test_getUserProfile_ok() {
85+
@test.skip async test_getUserProfile_ok() {
8686
const result = await this.api.getUserProfile(process.env["GITPOD_TEST_TOKEN_BITBUCKET_SERVER"]!, "AlexTugarev");
8787
expect(result).to.deep.include({
8888
id: 105, // Identity.authId
@@ -92,17 +92,126 @@ class TestBitbucketServerApi {
9292
});
9393
}
9494

95-
@test async test_getRepos_ok() {
95+
@test async test_getRepos_no_searchString() {
9696
const result = await this.api.getRepos(process.env["GITPOD_TEST_TOKEN_BITBUCKET_SERVER"]!, {
9797
permission: "REPO_READ",
9898
});
99-
expect(result.length).to.be.equal(28);
99+
expect(result.length).to.be.equal(9177);
100100

101-
// TestBitbucketServerApi
102-
// BBS: GET https://7990-alextugarev-bbs-6v0gqcpgvj7.ws-eu102.gitpod.io/rest/api/1.0/repos?permission=REPO_READ&start=0 - OK
103-
// BBS: GET https://7990-alextugarev-bbs-6v0gqcpgvj7.ws-eu102.gitpod.io/rest/api/1.0/repos?permission=REPO_READ&start=27 - OK
104-
// ✓ test_getRepos_ok (87ms)
105-
// 1 passing (93ms)
101+
// BBS: GET https://bitbucket.gitpod-dev.com/rest/api/1.0/repos?permission=REPO_READ&limit=1000&start=0&limit=1000 - OK
102+
// BBS: GET https://bitbucket.gitpod-dev.com/rest/api/1.0/repos?permission=REPO_READ&limit=1000&start=1002&limit=1000 - OK
103+
// BBS: GET https://bitbucket.gitpod-dev.com/rest/api/1.0/repos?permission=REPO_READ&limit=1000&start=2002&limit=1000 - OK
104+
// BBS: GET https://bitbucket.gitpod-dev.com/rest/api/1.0/repos?permission=REPO_READ&limit=1000&start=3002&limit=1000 - OK
105+
// BBS: GET https://bitbucket.gitpod-dev.com/rest/api/1.0/repos?permission=REPO_READ&limit=1000&start=4002&limit=1000 - OK
106+
// BBS: GET https://bitbucket.gitpod-dev.com/rest/api/1.0/repos?permission=REPO_READ&limit=1000&start=5002&limit=1000 - OK
107+
// BBS: GET https://bitbucket.gitpod-dev.com/rest/api/1.0/repos?permission=REPO_READ&limit=1000&start=6002&limit=1000 - OK
108+
// BBS: GET https://bitbucket.gitpod-dev.com/rest/api/1.0/repos?permission=REPO_READ&limit=1000&start=7002&limit=1000 - OK
109+
// BBS: GET https://bitbucket.gitpod-dev.com/rest/api/1.0/repos?permission=REPO_READ&limit=1000&start=8002&limit=1000 - OK
110+
// BBS: GET https://bitbucket.gitpod-dev.com/rest/api/1.0/repos?permission=REPO_READ&limit=1000&start=9002&limit=1000 - OK
111+
// ✓ test_getRepos_no_searchString (3746ms)
112+
}
113+
114+
@test async test_getRepos_cap_no_searchstring() {
115+
const result = await this.api.getRepos(process.env["GITPOD_TEST_TOKEN_BITBUCKET_SERVER"]!, {
116+
permission: "REPO_READ",
117+
cap: 3,
118+
});
119+
expect(result.length).to.be.equal(3000);
120+
121+
// BBS: GET https://bitbucket.gitpod-dev.com/rest/api/1.0/repos?permission=REPO_READ&limit=1000&start=0&limit=1000 - OK
122+
// BBS: GET https://bitbucket.gitpod-dev.com/rest/api/1.0/repos?permission=REPO_READ&limit=1000&start=1002&limit=1000 - OK
123+
// BBS: GET https://bitbucket.gitpod-dev.com/rest/api/1.0/repos?permission=REPO_READ&limit=1000&start=2002&limit=1000 - OK
124+
// ✓ test_getRepos_cap_no_searchstring (1007ms)
125+
}
126+
127+
@test async test_getRepos_searchString() {
128+
const result = await this.api.getRepos(process.env["GITPOD_TEST_TOKEN_BITBUCKET_SERVER"]!, {
129+
permission: "REPO_READ",
130+
searchString: "zero",
131+
});
132+
expect(result.length).to.be.equal(1000);
133+
expect(result[0].links.clone[0].href).to.equal("https://bitbucket.gitpod-dev.com/scm/tes/zero-minus-1.git");
134+
135+
// BBS: GET https://bitbucket.gitpod-dev.com/rest/api/1.0/repos?permission=REPO_READ&limit=1000&start=0&name=zero - OK
136+
// BBS: GET https://bitbucket.gitpod-dev.com/rest/api/1.0/repos?permission=REPO_READ&limit=1000&start=0&projectname=zero - OK
137+
// ✓ test_getRepos_searchString (552ms)
138+
}
139+
140+
@test async test_getRepos_searchString_2() {
141+
const result = await this.api.getRepos(process.env["GITPOD_TEST_TOKEN_BITBUCKET_SERVER"]!, {
142+
permission: "REPO_READ",
143+
searchString: "zero-minus-1",
144+
});
145+
expect(result.length).to.be.equal(112);
146+
147+
// BBS: GET https://bitbucket.gitpod-dev.com/rest/api/1.0/repos?permission=REPO_READ&limit=1000&start=0&name=zero-minus-1 - OK
148+
// BBS: GET https://bitbucket.gitpod-dev.com/rest/api/1.0/repos?permission=REPO_READ&limit=1000&start=0&projectname=zero-minus-1 - OK
149+
// ✓ test_getRepos_searchString_2 (172ms)
150+
}
151+
152+
@test async test_getRepos_searchString_works_for_prefix_only() {
153+
const result = await this.api.getRepos(process.env["GITPOD_TEST_TOKEN_BITBUCKET_SERVER"]!, {
154+
permission: "REPO_READ",
155+
searchString: "-minus-1",
156+
});
157+
expect(result.length).to.be.equal(0);
158+
159+
// BBS: GET https://bitbucket.gitpod-dev.com/rest/api/1.0/repos?permission=REPO_READ&limit=1000&start=0&name=zero-minus-1 - OK
160+
// BBS: GET https://bitbucket.gitpod-dev.com/rest/api/1.0/repos?permission=REPO_READ&limit=1000&start=0&projectname=zero-minus-1 - OK
161+
// ✓ test_getRepos_searchString_works_for_prefix_only (172ms)
162+
}
163+
164+
@test async test_getRepos_searchString_wildcards_are_not_supported() {
165+
const result = await this.api.getRepos(process.env["GITPOD_TEST_TOKEN_BITBUCKET_SERVER"]!, {
166+
permission: "REPO_READ",
167+
searchString: "*-minus-1",
168+
});
169+
expect(result.length).to.be.equal(0);
170+
171+
// BBS: GET https://bitbucket.gitpod-dev.com/rest/api/1.0/repos?permission=REPO_READ&limit=1000&start=0&name=zero-minus-1 - OK
172+
// BBS: GET https://bitbucket.gitpod-dev.com/rest/api/1.0/repos?permission=REPO_READ&limit=1000&start=0&projectname=zero-minus-1 - OK
173+
// ✓ test_getRepos_searchString_wildcards_are_not_supported (172ms)
174+
}
175+
176+
@test async test_getRepos_searchString_single_char_is_ignored() {
177+
const result = await this.api.getRepos(process.env["GITPOD_TEST_TOKEN_BITBUCKET_SERVER"]!, {
178+
permission: "REPO_READ",
179+
searchString: "t",
180+
});
181+
expect(result.length).to.be.equal(9177);
182+
183+
// BBS: GET https://bitbucket.gitpod-dev.com/rest/api/1.0/repos?permission=REPO_READ&limit=1000&start=0&name=t - OK
184+
// BBS: GET https://bitbucket.gitpod-dev.com/rest/api/1.0/repos?permission=REPO_READ&limit=1000&start=1000&name=t - OK
185+
// BBS: GET https://bitbucket.gitpod-dev.com/rest/api/1.0/repos?permission=REPO_READ&limit=1000&start=2000&name=t - OK
186+
// BBS: GET https://bitbucket.gitpod-dev.com/rest/api/1.0/repos?permission=REPO_READ&limit=1000&start=3000&name=t - OK
187+
// BBS: GET https://bitbucket.gitpod-dev.com/rest/api/1.0/repos?permission=REPO_READ&limit=1000&start=4000&name=t - OK
188+
// BBS: GET https://bitbucket.gitpod-dev.com/rest/api/1.0/repos?permission=REPO_READ&limit=1000&start=5000&name=t - OK
189+
// BBS: GET https://bitbucket.gitpod-dev.com/rest/api/1.0/repos?permission=REPO_READ&limit=1000&start=6000&name=t - OK
190+
// BBS: GET https://bitbucket.gitpod-dev.com/rest/api/1.0/repos?permission=REPO_READ&limit=1000&start=7000&name=t - OK
191+
// BBS: GET https://bitbucket.gitpod-dev.com/rest/api/1.0/repos?permission=REPO_READ&limit=1000&start=8000&name=t - OK
192+
// BBS: GET https://bitbucket.gitpod-dev.com/rest/api/1.0/repos?permission=REPO_READ&limit=1000&start=0&projectname=t - OK
193+
// BBS: GET https://bitbucket.gitpod-dev.com/rest/api/1.0/repos?permission=REPO_READ&limit=1000&start=1000&projectname=t - OK
194+
// BBS: GET https://bitbucket.gitpod-dev.com/rest/api/1.0/repos?permission=REPO_READ&limit=1000&start=2000&projectname=t - OK
195+
// BBS: GET https://bitbucket.gitpod-dev.com/rest/api/1.0/repos?permission=REPO_READ&limit=1000&start=3000&projectname=t - OK
196+
// BBS: GET https://bitbucket.gitpod-dev.com/rest/api/1.0/repos?permission=REPO_READ&limit=1000&start=4000&projectname=t - OK
197+
// BBS: GET https://bitbucket.gitpod-dev.com/rest/api/1.0/repos?permission=REPO_READ&limit=1000&start=5000&projectname=t - OK
198+
// BBS: GET https://bitbucket.gitpod-dev.com/rest/api/1.0/repos?permission=REPO_READ&limit=1000&start=6000&projectname=t - OK
199+
// BBS: GET https://bitbucket.gitpod-dev.com/rest/api/1.0/repos?permission=REPO_READ&limit=1000&start=7000&projectname=t - OK
200+
// BBS: GET https://bitbucket.gitpod-dev.com/rest/api/1.0/repos?permission=REPO_READ&limit=1000&start=8000&projectname=t - OK
201+
// BBS: GET https://bitbucket.gitpod-dev.com/rest/api/1.0/repos?permission=REPO_READ&limit=1000&start=9000&projectname=t - OK
202+
// ✓ test_getRepos_searchString_single_char_is_ignored (7329ms)
203+
}
204+
205+
@test async test_getRepos_searchString_unmatched() {
206+
const result = await this.api.getRepos(process.env["GITPOD_TEST_TOKEN_BITBUCKET_SERVER"]!, {
207+
permission: "REPO_READ",
208+
searchString: "RANDOM_asd8sdh7s8hsdhvisduh",
209+
});
210+
expect(result.length).to.be.equal(0);
211+
212+
// BBS: GET https://bitbucket.gitpod-dev.com/rest/api/1.0/repos?permission=REPO_READ&limit=1000&start=0&name=RANDOM_asd8sdh7s8hsdhvisduh - OK
213+
// BBS: GET https://bitbucket.gitpod-dev.com/rest/api/1.0/repos?permission=REPO_READ&limit=1000&start=0&projectname=RANDOM_asd8sdh7s8hsdhvisduh - OK
214+
// ✓ test_getRepos_searchString_unmatched (126ms)
106215
}
107216
}
108217

0 commit comments

Comments
 (0)