@@ -12,9 +12,11 @@ import type {
12
12
} from '../../../git/models/pullRequest' ;
13
13
import type { RepositoryMetadata } from '../../../git/models/repositoryMetadata' ;
14
14
import type { IntegrationAuthenticationProviderDescriptor } from '../authentication/integrationAuthenticationProvider' ;
15
+ import type { ProviderAuthenticationSession } from '../authentication/models' ;
15
16
import type { ResourceDescriptor } from '../integration' ;
16
17
import { HostingIntegration } from '../integration' ;
17
- import { providersMetadata } from './models' ;
18
+ import type { ProviderPullRequest } from './models' ;
19
+ import { fromProviderPullRequest , providersMetadata } from './models' ;
18
20
19
21
const metadata = providersMetadata [ HostingIntegrationId . Bitbucket ] ;
20
22
const authProvider = Object . freeze ( { id : metadata . id , scopes : metadata . scopes } ) ;
@@ -24,6 +26,19 @@ interface BitbucketRepositoryDescriptor extends ResourceDescriptor {
24
26
name : string ;
25
27
}
26
28
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
+
27
42
export class BitbucketIntegration extends HostingIntegration <
28
43
HostingIntegrationId . Bitbucket ,
29
44
BitbucketRepositoryDescriptor
@@ -141,11 +156,141 @@ export class BitbucketIntegration extends HostingIntegration<
141
156
return Promise . resolve ( undefined ) ;
142
157
}
143
158
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
+
144
264
protected override async searchProviderMyPullRequests (
145
- _session : AuthenticationSession ,
146
- _repos ?: BitbucketRepositoryDescriptor [ ] ,
265
+ session : ProviderAuthenticationSession ,
266
+ requestedRepositories ?: BitbucketRepositoryDescriptor [ ] ,
147
267
) : 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
+ } ) ) ;
149
294
}
150
295
151
296
protected override async searchProviderMyIssues (
@@ -154,6 +299,14 @@ export class BitbucketIntegration extends HostingIntegration<
154
299
) : Promise < SearchedIssue [ ] | undefined > {
155
300
return Promise . resolve ( undefined ) ;
156
301
}
302
+
303
+ private fromBitbucketProviderPullRequest (
304
+ remotePullRequest : ProviderPullRequest ,
305
+ // repoDescriptors: BitbucketRemoteRepositoryDescriptor[],
306
+ ) : PullRequest {
307
+ remotePullRequest . graphQLId = remotePullRequest . id ;
308
+ return fromProviderPullRequest ( remotePullRequest , this ) ;
309
+ }
157
310
}
158
311
159
312
const bitbucketCloudDomainRegex = / ^ b i t b u c k e t \. o r g $ / i;
0 commit comments