Skip to content

Commit e778a40

Browse files
committed
Retrieves authoredPRs per workspace and reviewingPRs for current repos only
(#4046, #4099)
1 parent 3766b97 commit e778a40

File tree

3 files changed

+75
-84
lines changed

3 files changed

+75
-84
lines changed

src/plus/integrations/providers/bitbucket.ts

+42-75
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,12 @@ import type { Issue, IssueShape } from '../../../git/models/issue';
77
import type { IssueOrPullRequest, IssueOrPullRequestType } from '../../../git/models/issueOrPullRequest';
88
import type { PullRequest, PullRequestMergeMethod, PullRequestState } from '../../../git/models/pullRequest';
99
import type { RepositoryMetadata } from '../../../git/models/repositoryMetadata';
10+
import { uniqueBy } from '../../../system/iterable';
1011
import { getSettledValue } from '../../../system/promise';
1112
import type { IntegrationAuthenticationProviderDescriptor } from '../authentication/integrationAuthenticationProvider';
1213
import type { ProviderAuthenticationSession } from '../authentication/models';
1314
import { HostingIntegration } from '../integration';
14-
import type {
15-
BitbucketRemoteRepositoryDescriptor,
16-
BitbucketRepositoryDescriptor,
17-
BitbucketWorkspaceDescriptor,
18-
} from './bitbucket/models';
15+
import type { BitbucketRepositoryDescriptor, BitbucketWorkspaceDescriptor } from './bitbucket/models';
1916
import { providersMetadata } from './models';
2017

2118
const metadata = providersMetadata[HostingIntegrationId.Bitbucket];
@@ -190,58 +187,6 @@ export class BitbucketIntegration extends HostingIntegration<
190187
return this._workspaces.get(accessToken);
191188
}
192189

193-
private async getProviderProjectsForResources(
194-
{ accessToken }: AuthenticationSession,
195-
resources: BitbucketWorkspaceDescriptor[],
196-
force: boolean = false,
197-
): Promise<BitbucketRemoteRepositoryDescriptor[] | undefined> {
198-
const repositories = new Map<string, BitbucketRemoteRepositoryDescriptor[] | undefined>();
199-
let resourcesWithoutRepositories: BitbucketWorkspaceDescriptor[] = [];
200-
if (force) {
201-
resourcesWithoutRepositories = resources;
202-
} else {
203-
for (const resource of resources) {
204-
const resourceKey = `${accessToken}:${resource.id}`;
205-
const cachedRepositories = repositories.get(resourceKey);
206-
if (cachedRepositories == null) {
207-
resourcesWithoutRepositories.push(resource);
208-
}
209-
}
210-
}
211-
212-
if (resourcesWithoutRepositories.length > 0) {
213-
const api = await this.container.bitbucket;
214-
if (api == null) return undefined;
215-
await Promise.allSettled(
216-
resourcesWithoutRepositories.map(async resource => {
217-
const resourceRepos = await api.getRepositoriesForWorkspace(this, accessToken, resource.slug, {
218-
baseUrl: this.apiBaseUrl,
219-
});
220-
221-
if (resourceRepos == null) return undefined;
222-
repositories.set(
223-
`${accessToken}:${resource.id}`,
224-
resourceRepos.map(r => ({
225-
id: `${r.owner}/${r.name}`,
226-
resourceId: r.owner,
227-
owner: r.owner,
228-
name: r.name,
229-
key: `${r.owner}/${r.name}`,
230-
})),
231-
);
232-
}),
233-
);
234-
}
235-
236-
return resources.reduce<BitbucketRemoteRepositoryDescriptor[]>((resultRepos, resource) => {
237-
const resourceRepos = repositories.get(`${accessToken}:${resource.id}`);
238-
if (resourceRepos != null) {
239-
resultRepos.push(...resourceRepos);
240-
}
241-
return resultRepos;
242-
}, []);
243-
}
244-
245190
protected override async searchProviderMyPullRequests(
246191
session: ProviderAuthenticationSession,
247192
repos?: BitbucketRepositoryDescriptor[],
@@ -251,33 +196,43 @@ export class BitbucketIntegration extends HostingIntegration<
251196
return undefined;
252197
}
253198

199+
const remotes = await flatSettled(this.container.git.openRepositories.map(r => r.git.remotes().getRemotes()));
200+
const workspaceRepos = await nonnullSettled(
201+
remotes.map(async r => ((await r.getIntegration())?.id === this.id ? r.path : undefined)),
202+
);
203+
254204
const user = await this.getProviderCurrentAccount(session);
255205
if (user?.username == null) return undefined;
256206

257207
const workspaces = await this.getProviderResourcesForUser(session);
258208
if (workspaces == null || workspaces.length === 0) return undefined;
259209

260-
const allBitbucketRepos = await this.getProviderProjectsForResources(session, workspaces);
261-
if (allBitbucketRepos == null || allBitbucketRepos.length === 0) return undefined;
262-
263210
const api = await this.container.bitbucket;
264211
if (!api) return undefined;
265-
const prsResult = await Promise.allSettled(
266-
allBitbucketRepos.map(repo =>
267-
api.getUsersPullRequestsForRepo(
268-
this,
269-
session.accessToken,
270-
user.id,
271-
repo.owner,
272-
repo.name,
273-
this.apiBaseUrl,
274-
),
275-
),
212+
213+
const authoredPrs = workspaces.map(ws =>
214+
api.getPullRequestsForWorkspaceAuthoredByUser(this, session.accessToken, user.id, ws.slug, this.apiBaseUrl),
276215
);
277-
return prsResult
278-
.map(r => getSettledValue(r))
279-
.filter(r => r != null)
280-
.flat();
216+
217+
const reviewingPrs = workspaceRepos.map(repo => {
218+
const [owner, name] = repo.split('/');
219+
return api.getUsersReviewingPullRequestsForRepo(
220+
this,
221+
session.accessToken,
222+
user.id,
223+
owner,
224+
name,
225+
this.apiBaseUrl,
226+
);
227+
});
228+
229+
return [
230+
...uniqueBy(
231+
await flatSettled([...authoredPrs, ...reviewingPrs]),
232+
pr => pr.url,
233+
(orig, _cur) => orig,
234+
),
235+
];
281236
}
282237

283238
protected override async searchProviderMyIssues(
@@ -341,3 +296,15 @@ const bitbucketCloudDomainRegex = /^bitbucket\.org$/i;
341296
export function isBitbucketCloudDomain(domain: string | undefined): boolean {
342297
return domain != null && bitbucketCloudDomainRegex.test(domain);
343298
}
299+
300+
type MaybePromiseArr<T> = Promise<T | undefined>[] | (T | undefined)[];
301+
302+
async function nonnullSettled<T>(arr: MaybePromiseArr<T>): Promise<T[]> {
303+
const all = await Promise.allSettled(arr);
304+
return all.map(r => getSettledValue(r)).filter(v => v != null);
305+
}
306+
307+
async function flatSettled<T>(arr: MaybePromiseArr<(T | undefined)[]>): Promise<T[]> {
308+
const all = await nonnullSettled(arr);
309+
return all.flat().filter(v => v != null);
310+
}

src/plus/integrations/providers/bitbucket/bitbucket.ts

+33-2
Original file line numberDiff line numberDiff line change
@@ -221,7 +221,38 @@ export class BitbucketApi implements Disposable {
221221
}
222222
}
223223

224-
async getUsersPullRequestsForRepo(
224+
async getPullRequestsForWorkspaceAuthoredByUser(
225+
provider: Provider,
226+
token: string,
227+
userUuid: string,
228+
workspace: string,
229+
baseUrl: string,
230+
): Promise<PullRequest[] | undefined> {
231+
const scope = getLogScope();
232+
233+
const response = await this.request<{
234+
values: BitbucketPullRequest[];
235+
pagelen: number;
236+
size: number;
237+
page: number;
238+
}>(
239+
provider,
240+
token,
241+
baseUrl,
242+
`workspaces/${workspace}/pullrequests/${userUuid}?state=OPEN&fields=%2Bvalues.reviewers,%2Bvalues.participants`,
243+
{
244+
method: 'GET',
245+
},
246+
scope,
247+
);
248+
249+
if (!response?.values?.length) {
250+
return undefined;
251+
}
252+
return response.values.map(pr => fromBitbucketPullRequest(pr, provider));
253+
}
254+
255+
async getUsersReviewingPullRequestsForRepo(
225256
provider: Provider,
226257
token: string,
227258
userUuid: string,
@@ -231,7 +262,7 @@ export class BitbucketApi implements Disposable {
231262
): Promise<PullRequest[] | undefined> {
232263
const scope = getLogScope();
233264

234-
const query = encodeURIComponent(`reviewers.uuid="${userUuid}" OR author.uuid="${userUuid}"`);
265+
const query = encodeURIComponent(`state="OPEN" AND reviewers.uuid="${userUuid}"`);
235266
const response = await this.request<{
236267
values: BitbucketPullRequest[];
237268
pagelen: number;

src/plus/integrations/providers/bitbucket/models.ts

-7
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,6 @@ export interface BitbucketWorkspaceDescriptor extends ResourceDescriptor {
1616
slug: string;
1717
}
1818

19-
export interface BitbucketRemoteRepositoryDescriptor extends ResourceDescriptor {
20-
owner: string;
21-
name: string;
22-
cloneUrlHttps?: string;
23-
cloneUrlSsh?: string;
24-
}
25-
2619
export type BitbucketPullRequestState = 'OPEN' | 'DECLINED' | 'MERGED' | 'SUPERSEDED';
2720

2821
interface BitbucketLink {

0 commit comments

Comments
 (0)