@@ -12,9 +12,11 @@ import type {
1212} from '../../../git/models/pullRequest' ;
1313import type { RepositoryMetadata } from '../../../git/models/repositoryMetadata' ;
1414import type { IntegrationAuthenticationProviderDescriptor } from '../authentication/integrationAuthenticationProvider' ;
15+ import type { ProviderAuthenticationSession } from '../authentication/models' ;
1516import type { ResourceDescriptor } from '../integration' ;
1617import { HostingIntegration } from '../integration' ;
17- import { providersMetadata } from './models' ;
18+ import type { ProviderPullRequest } from './models' ;
19+ import { fromProviderPullRequest , providersMetadata } from './models' ;
1820
1921const metadata = providersMetadata [ HostingIntegrationId . Bitbucket ] ;
2022const authProvider = Object . freeze ( { id : metadata . id , scopes : metadata . scopes } ) ;
@@ -24,6 +26,19 @@ interface BitbucketRepositoryDescriptor extends ResourceDescriptor {
2426 name : string ;
2527}
2628
29+ interface BitbucketWorkspaceDescriptor extends ResourceDescriptor {
30+ id : string ;
31+ name : string ;
32+ slug : string ;
33+ }
34+
35+ interface BitbucketRemoteRepositoryDescriptor extends ResourceDescriptor {
36+ owner : string ;
37+ name : string ;
38+ cloneUrlHttps ?: string ;
39+ cloneUrlSsh ?: string ;
40+ }
41+
2742export class BitbucketIntegration extends HostingIntegration <
2843 HostingIntegrationId . Bitbucket ,
2944 BitbucketRepositoryDescriptor
@@ -141,11 +156,141 @@ export class BitbucketIntegration extends HostingIntegration<
141156 return Promise . resolve ( undefined ) ;
142157 }
143158
159+ private _accounts : Map < string , Account | undefined > | undefined ;
160+ protected override async getProviderCurrentAccount ( {
161+ accessToken,
162+ } : AuthenticationSession ) : Promise < Account | undefined > {
163+ this . _accounts ??= new Map < string , Account | undefined > ( ) ;
164+
165+ const cachedAccount = this . _accounts . get ( accessToken ) ;
166+ if ( cachedAccount == null ) {
167+ const api = await this . getProvidersApi ( ) ;
168+ const user = await api . getCurrentUser ( this . id , { accessToken : accessToken } ) ;
169+ this . _accounts . set (
170+ accessToken ,
171+ user
172+ ? {
173+ provider : this ,
174+ id : user . id ,
175+ name : user . name ?? undefined ,
176+ email : user . email ?? undefined ,
177+ avatarUrl : user . avatarUrl ?? undefined ,
178+ username : user . username ?? undefined ,
179+ }
180+ : undefined ,
181+ ) ;
182+ }
183+
184+ return this . _accounts . get ( accessToken ) ;
185+ }
186+
187+ private _workspaces : Map < string , BitbucketWorkspaceDescriptor [ ] | undefined > | undefined ;
188+ private async getProviderResourcesForUser (
189+ session : AuthenticationSession ,
190+ force : boolean = false ,
191+ ) : Promise < BitbucketWorkspaceDescriptor [ ] | undefined > {
192+ this . _workspaces ??= new Map < string , BitbucketWorkspaceDescriptor [ ] | undefined > ( ) ;
193+ const { accessToken } = session ;
194+ const cachedResources = this . _workspaces . get ( accessToken ) ;
195+
196+ if ( cachedResources == null || force ) {
197+ const api = await this . getProvidersApi ( ) ;
198+ const account = await this . getProviderCurrentAccount ( session ) ;
199+ if ( account ?. id == null ) return undefined ;
200+
201+ const resources = await api . getBitbucketResourcesForUser ( account . id , { accessToken : accessToken } ) ;
202+ this . _workspaces . set (
203+ accessToken ,
204+ resources != null ? resources . map ( r => ( { ...r , key : r . id } ) ) : undefined ,
205+ ) ;
206+ }
207+
208+ return this . _workspaces . get ( accessToken ) ;
209+ }
210+
211+ private _repositories : Map < string , BitbucketRemoteRepositoryDescriptor [ ] | undefined > | undefined ;
212+ private async getProviderProjectsForResources (
213+ { accessToken } : AuthenticationSession ,
214+ resources : BitbucketWorkspaceDescriptor [ ] ,
215+ force : boolean = false ,
216+ ) : Promise < BitbucketRemoteRepositoryDescriptor [ ] | undefined > {
217+ this . _repositories ??= new Map < string , BitbucketRemoteRepositoryDescriptor [ ] | undefined > ( ) ;
218+ let resourcesWithoutRepositories : BitbucketWorkspaceDescriptor [ ] = [ ] ;
219+ if ( force ) {
220+ resourcesWithoutRepositories = resources ;
221+ } else {
222+ for ( const resource of resources ) {
223+ const resourceKey = `${ accessToken } :${ resource . id } ` ;
224+ const cachedRepositories = this . _repositories . get ( resourceKey ) ;
225+ if ( cachedRepositories == null ) {
226+ resourcesWithoutRepositories . push ( resource ) ;
227+ }
228+ }
229+ }
230+
231+ const cachedRepos = this . _repositories ;
232+ if ( resourcesWithoutRepositories . length > 0 ) {
233+ const api = await this . container . bitbucket ;
234+ if ( api == null ) return undefined ;
235+ await Promise . allSettled (
236+ resourcesWithoutRepositories . map ( async resource => {
237+ const resourceRepos = await api . getRepositoriesForWorkspace ( this , accessToken , resource . slug , {
238+ baseUrl : this . apiBaseUrl ,
239+ } ) ;
240+
241+ if ( resourceRepos == null ) return undefined ;
242+ cachedRepos . set (
243+ `${ accessToken } :${ resource . id } ` ,
244+ resourceRepos . map ( r => ( {
245+ id : `${ r . owner } /${ r . name } ` ,
246+ owner : r . owner ,
247+ name : r . name ,
248+ key : `${ r . owner } /${ r . name } ` ,
249+ } ) ) ,
250+ ) ;
251+ } ) ,
252+ ) ;
253+ }
254+
255+ return resources . reduce < BitbucketRemoteRepositoryDescriptor [ ] > ( ( resultRepos , resource ) => {
256+ const resourceRepos = cachedRepos . get ( `${ accessToken } :${ resource . id } ` ) ;
257+ if ( resourceRepos != null ) {
258+ resultRepos . push ( ...resourceRepos ) ;
259+ }
260+ return resultRepos ;
261+ } , [ ] ) ;
262+ }
263+
144264 protected override async searchProviderMyPullRequests (
145- _session : AuthenticationSession ,
146- _repos ?: BitbucketRepositoryDescriptor [ ] ,
265+ session : ProviderAuthenticationSession ,
266+ requestedRepositories ?: BitbucketRepositoryDescriptor [ ] ,
147267 ) : Promise < SearchedPullRequest [ ] | undefined > {
148- return Promise . resolve ( undefined ) ;
268+ const api = await this . getProvidersApi ( ) ;
269+ if ( requestedRepositories != null ) {
270+ // TODO: implement repos version
271+ return undefined ;
272+ }
273+
274+ const user = await this . getProviderCurrentAccount ( session ) ;
275+ if ( user ?. username == null ) return undefined ;
276+
277+ const workspaces = await this . getProviderResourcesForUser ( session ) ;
278+ if ( workspaces == null || workspaces . length === 0 ) return undefined ;
279+
280+ const repos = await this . getProviderProjectsForResources ( session , workspaces ) ;
281+ if ( repos == null || repos . length === 0 ) return undefined ;
282+
283+ const prs = await api . getPullRequestsForRepos (
284+ HostingIntegrationId . Bitbucket ,
285+ repos . map ( repo => ( { namespace : repo . owner , name : repo . name } ) ) ,
286+ {
287+ accessToken : session . accessToken ,
288+ } ,
289+ ) ;
290+ return prs . values . map ( pr => ( {
291+ pullRequest : this . fromBitbucketProviderPullRequest ( pr ) ,
292+ reasons : [ ] ,
293+ } ) ) ;
149294 }
150295
151296 protected override async searchProviderMyIssues (
@@ -154,6 +299,14 @@ export class BitbucketIntegration extends HostingIntegration<
154299 ) : Promise < SearchedIssue [ ] | undefined > {
155300 return Promise . resolve ( undefined ) ;
156301 }
302+
303+ private fromBitbucketProviderPullRequest (
304+ remotePullRequest : ProviderPullRequest ,
305+ // repoDescriptors: BitbucketRemoteRepositoryDescriptor[],
306+ ) : PullRequest {
307+ remotePullRequest . graphQLId = remotePullRequest . id ;
308+ return fromProviderPullRequest ( remotePullRequest , this ) ;
309+ }
157310}
158311
159312const bitbucketCloudDomainRegex = / ^ b i t b u c k e t \. o r g $ / i;
0 commit comments