Skip to content

Commit 756073a

Browse files
committed
Implements issue retrieval for Bitbucket to show on StartWork list
(#4047, #4119)
1 parent 9895a6d commit 756073a

File tree

3 files changed

+106
-4
lines changed

3 files changed

+106
-4
lines changed

src/plus/integrations/providers/bitbucket.ts

+32-3
Original file line numberDiff line numberDiff line change
@@ -283,10 +283,39 @@ export class BitbucketIntegration extends HostingIntegration<
283283
}
284284

285285
protected override async searchProviderMyIssues(
286-
_session: AuthenticationSession,
287-
_repos?: BitbucketRepositoryDescriptor[],
286+
session: AuthenticationSession,
287+
requestedRepositories?: BitbucketRepositoryDescriptor[],
288288
): Promise<IssueShape[] | undefined> {
289-
return Promise.resolve(undefined);
289+
let repoPaths: undefined | Map<string, boolean> = undefined;
290+
if (requestedRepositories != null) {
291+
repoPaths = new Map<string, boolean>();
292+
for (const repo of requestedRepositories) {
293+
repoPaths.set(`${repo.owner}/${repo.name}`, true);
294+
}
295+
}
296+
297+
const user = await this.getProviderCurrentAccount(session);
298+
if (user?.username == null) return undefined;
299+
300+
const workspaces = await this.getProviderResourcesForUser(session);
301+
if (workspaces == null || workspaces.length === 0) return undefined;
302+
303+
const repos = (await this.getProviderProjectsForResources(session, workspaces))?.filter(
304+
r => repoPaths === undefined || repoPaths.has(`${r.owner}/${r.name}`),
305+
);
306+
if (repos == null || repos.length === 0) return undefined;
307+
308+
const api = await this.container.bitbucket;
309+
if (!api) return undefined;
310+
const issueResult = await Promise.allSettled(
311+
repos.map(repo =>
312+
api.getUsersIssuesForRepo(this, session.accessToken, user.id, repo.owner, repo.name, this.apiBaseUrl),
313+
),
314+
);
315+
return issueResult
316+
.map(r => getSettledValue(r))
317+
.filter(r => r != null)
318+
.flat();
290319
}
291320

292321
protected override async providerOnConnect(): Promise<void> {

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

+69-1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
RequestClientError,
1414
RequestNotFoundError,
1515
} from '../../../../errors';
16+
import type { Issue } from '../../../../git/models/issue';
1617
import type { IssueOrPullRequest, IssueOrPullRequestType } from '../../../../git/models/issueOrPullRequest';
1718
import type { PullRequest } from '../../../../git/models/pullRequest';
1819
import type { Provider } from '../../../../git/models/remoteProvider';
@@ -25,7 +26,7 @@ import type { LogScope } from '../../../../system/logger.scope';
2526
import { getLogScope } from '../../../../system/logger.scope';
2627
import { maybeStopWatch } from '../../../../system/stopwatch';
2728
import type { BitbucketIssue, BitbucketPullRequest, BitbucketRepository } from './models';
28-
import { bitbucketIssueStateToState, fromBitbucketPullRequest } from './models';
29+
import { bitbucketIssueStateToState, fromBitbucketIssue, fromBitbucketPullRequest } from './models';
2930

3031
export class BitbucketApi implements Disposable {
3132
private readonly _disposable: Disposable;
@@ -92,6 +93,73 @@ export class BitbucketApi implements Disposable {
9293
return fromBitbucketPullRequest(response.values[0], provider);
9394
}
9495

96+
@debug<BitbucketApi['getUsersIssuesForRepo']>({ args: { 0: p => p.name, 1: '<token>' } })
97+
async getUsersIssuesForRepo(
98+
provider: Provider,
99+
token: string,
100+
userUuid: string,
101+
owner: string,
102+
repo: string,
103+
baseUrl: string,
104+
): Promise<Issue[] | undefined> {
105+
const scope = getLogScope();
106+
const query = encodeURIComponent(`assignee.uuid="${userUuid}" OR reporter.uuid="${userUuid}"`);
107+
108+
const response = await this.request<{
109+
values: BitbucketIssue[];
110+
pagelen: number;
111+
size: number;
112+
page: number;
113+
}>(
114+
provider,
115+
token,
116+
baseUrl,
117+
`repositories/${owner}/${repo}/issues?q=${query}`,
118+
{
119+
method: 'GET',
120+
},
121+
scope,
122+
);
123+
124+
if (!response?.values?.length) {
125+
return undefined;
126+
}
127+
return response.values.map(issue => fromBitbucketIssue(issue, provider));
128+
}
129+
130+
@debug<BitbucketApi['getIssue']>({ args: { 0: p => p.name, 1: '<token>' } })
131+
async getIssue(
132+
provider: Provider,
133+
token: string,
134+
owner: string,
135+
repo: string,
136+
id: string,
137+
baseUrl: string,
138+
): Promise<Issue | undefined> {
139+
const scope = getLogScope();
140+
141+
try {
142+
const response = await this.request<BitbucketIssue>(
143+
provider,
144+
token,
145+
baseUrl,
146+
`repositories/${owner}/${repo}/issues/${id}`,
147+
{
148+
method: 'GET',
149+
},
150+
scope,
151+
);
152+
153+
if (response) {
154+
return fromBitbucketIssue(response, provider);
155+
}
156+
return undefined;
157+
} catch (ex) {
158+
Logger.error(ex, scope);
159+
return undefined;
160+
}
161+
}
162+
95163
@debug<BitbucketApi['getIssueOrPullRequest']>({ args: { 0: p => p.name, 1: '<token>' } })
96164
public async getIssueOrPullRequest(
97165
provider: Provider,

src/plus/startWork/startWork.ts

+5
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import {
2121
import {
2222
ConnectIntegrationButton,
2323
OpenOnAzureDevOpsQuickInputButton,
24+
OpenOnBitbucketQuickInputButton,
2425
OpenOnGitHubQuickInputButton,
2526
OpenOnGitLabQuickInputButton,
2627
OpenOnJiraQuickInputButton,
@@ -96,6 +97,7 @@ export const supportedStartWorkIntegrations = [
9697
HostingIntegrationId.GitLab,
9798
SelfHostedIntegrationId.CloudGitLabSelfHosted,
9899
HostingIntegrationId.AzureDevOps,
100+
HostingIntegrationId.Bitbucket,
99101
IssueIntegrationId.Jira,
100102
];
101103
export type SupportedStartWorkIntegrationIds = (typeof supportedStartWorkIntegrations)[number];
@@ -483,6 +485,7 @@ export abstract class StartWorkBaseCommand extends QuickCommand<State> {
483485
onDidClickItemButton: (_quickpick, button, { item }) => {
484486
switch (button) {
485487
case OpenOnAzureDevOpsQuickInputButton:
488+
case OpenOnBitbucketQuickInputButton:
486489
case OpenOnGitHubQuickInputButton:
487490
case OpenOnGitLabQuickInputButton:
488491
case OpenOnJiraQuickInputButton:
@@ -716,6 +719,8 @@ function getOpenOnWebQuickInputButton(integrationId: string): QuickInputButton |
716719
switch (integrationId) {
717720
case HostingIntegrationId.AzureDevOps:
718721
return OpenOnAzureDevOpsQuickInputButton;
722+
case HostingIntegrationId.Bitbucket:
723+
return OpenOnBitbucketQuickInputButton;
719724
case HostingIntegrationId.GitHub:
720725
case SelfHostedIntegrationId.CloudGitHubEnterprise:
721726
return OpenOnGitHubQuickInputButton;

0 commit comments

Comments
 (0)