@@ -11,10 +11,13 @@ import type {
11
11
SearchedPullRequest ,
12
12
} from '../../../git/models/pullRequest' ;
13
13
import type { RepositoryMetadata } from '../../../git/models/repositoryMetadata' ;
14
+ import { getSettledValue } from '../../../system/promise' ;
14
15
import type { IntegrationAuthenticationProviderDescriptor } from '../authentication/integrationAuthenticationProvider' ;
16
+ import type { ProviderAuthenticationSession } from '../authentication/models' ;
15
17
import type { ResourceDescriptor } from '../integration' ;
16
18
import { HostingIntegration } from '../integration' ;
17
- import { providersMetadata } from './models' ;
19
+ import type { ProviderPullRequest } from './models' ;
20
+ import { fromProviderPullRequest , providersMetadata } from './models' ;
18
21
19
22
const metadata = providersMetadata [ HostingIntegrationId . Bitbucket ] ;
20
23
const authProvider = Object . freeze ( { id : metadata . id , scopes : metadata . scopes } ) ;
@@ -24,6 +27,24 @@ interface BitbucketRepositoryDescriptor extends ResourceDescriptor {
24
27
name : string ;
25
28
}
26
29
30
+ interface BitbucketWorkspaceDescriptor extends ResourceDescriptor {
31
+ id : string ;
32
+ name : string ;
33
+ slug : string ;
34
+ }
35
+
36
+ interface BitbucketRemoteRepositoryDescriptor extends ResourceDescriptor {
37
+ id : string ;
38
+ // nodeId?: string;
39
+ resourceName : string ;
40
+ owner : string ;
41
+ name : string ;
42
+ // projectName?: string;
43
+ // url?: string;
44
+ cloneUrlHttps ?: string ;
45
+ cloneUrlSsh ?: string ;
46
+ }
47
+
27
48
export class BitbucketIntegration extends HostingIntegration <
28
49
HostingIntegrationId . Bitbucket ,
29
50
BitbucketRepositoryDescriptor
@@ -136,11 +157,165 @@ export class BitbucketIntegration extends HostingIntegration<
136
157
return Promise . resolve ( undefined ) ;
137
158
}
138
159
160
+ private _accounts : Map < string , Account | undefined > | undefined ;
161
+ protected override async getProviderCurrentAccount ( {
162
+ accessToken,
163
+ } : AuthenticationSession ) : Promise < Account | undefined > {
164
+ this . _accounts ??= new Map < string , Account | undefined > ( ) ;
165
+
166
+ const cachedAccount = this . _accounts . get ( accessToken ) ;
167
+ if ( cachedAccount == null ) {
168
+ const api = await this . getProvidersApi ( ) ;
169
+ const user = await api . getCurrentUser ( this . id , { accessToken : accessToken } ) ;
170
+ this . _accounts . set (
171
+ accessToken ,
172
+ user
173
+ ? {
174
+ provider : this ,
175
+ id : user . id ,
176
+ name : user . name ?? undefined ,
177
+ email : user . email ?? undefined ,
178
+ avatarUrl : user . avatarUrl ?? undefined ,
179
+ username : user . username ?? undefined ,
180
+ }
181
+ : undefined ,
182
+ ) ;
183
+ }
184
+
185
+ return this . _accounts . get ( accessToken ) ;
186
+ }
187
+
188
+ private _workspaces : Map < string , BitbucketWorkspaceDescriptor [ ] | undefined > | undefined ;
189
+ private async getProviderResourcesForUser (
190
+ session : AuthenticationSession ,
191
+ force : boolean = false ,
192
+ ) : Promise < BitbucketWorkspaceDescriptor [ ] | undefined > {
193
+ this . _workspaces ??= new Map < string , BitbucketWorkspaceDescriptor [ ] | undefined > ( ) ;
194
+ const { accessToken } = session ;
195
+ const cachedResources = this . _workspaces . get ( accessToken ) ;
196
+
197
+ if ( cachedResources == null || force ) {
198
+ const api = await this . getProvidersApi ( ) ;
199
+ const account = await this . getProviderCurrentAccount ( session ) ;
200
+ if ( account ?. id == null ) return undefined ;
201
+
202
+ const resources = await api . getBitbucketResourcesForUser ( account . id , { accessToken : accessToken } ) ;
203
+ this . _workspaces . set (
204
+ accessToken ,
205
+ resources != null ? resources . map ( r => ( { ...r , key : r . id } ) ) : undefined ,
206
+ ) ;
207
+ }
208
+
209
+ return this . _workspaces . get ( accessToken ) ;
210
+ }
211
+
212
+ private _repositories : Map < string , BitbucketRemoteRepositoryDescriptor [ ] | undefined > | undefined ;
213
+ private get repositoryCache ( ) {
214
+ this . _repositories ??= new Map < string , BitbucketRemoteRepositoryDescriptor [ ] | undefined > ( ) ;
215
+ return this . _repositories ;
216
+ }
217
+ private async getProviderProjectsForResources (
218
+ session : AuthenticationSession ,
219
+ resources : BitbucketWorkspaceDescriptor [ ] ,
220
+ force : boolean = false ,
221
+ ) : Promise < BitbucketRemoteRepositoryDescriptor [ ] | undefined > {
222
+ let resourcesWithoutRepositories : BitbucketWorkspaceDescriptor [ ] = [ ] ;
223
+ if ( force ) {
224
+ resourcesWithoutRepositories = resources ;
225
+ } else {
226
+ for ( const resource of resources ) {
227
+ const resourceKey = `${ session . accessToken } :${ resource . id } ` ;
228
+ const cachedRepositories = this . repositoryCache . get ( resourceKey ) ;
229
+ if ( cachedRepositories == null ) {
230
+ resourcesWithoutRepositories . push ( resource ) ;
231
+ }
232
+ }
233
+ }
234
+
235
+ if ( resourcesWithoutRepositories . length > 0 ) {
236
+ await Promise . allSettled (
237
+ resourcesWithoutRepositories . map ( async resource => {
238
+ const resourceRepos = await this . getRepositoriesForWorkspace ( session , resource . slug ) ;
239
+ if ( resourceRepos == null ) return undefined ;
240
+ this . repositoryCache . set (
241
+ `${ session . accessToken } :${ resource . id } ` ,
242
+ resourceRepos . map ( r => ( {
243
+ id : `${ r . owner } /${ r . name } ` ,
244
+ resourceName : `${ r . owner } /${ r . name } ` ,
245
+ owner : r . owner ,
246
+ name : r . name ,
247
+ key : `${ r . owner } /${ r . name } ` ,
248
+ } ) ) ,
249
+ ) ;
250
+ } ) ,
251
+ ) ;
252
+ }
253
+
254
+ return resources . reduce < BitbucketRemoteRepositoryDescriptor [ ] > ( ( resultRepos , resource ) => {
255
+ const resourceRepos = this . repositoryCache . get ( `${ session . accessToken } :${ resource . id } ` ) ;
256
+ if ( resourceRepos != null ) {
257
+ resultRepos . push ( ...resourceRepos ) ;
258
+ }
259
+ return resultRepos ;
260
+ } , [ ] ) ;
261
+ }
262
+
263
+ private async getRepositoriesForWorkspace (
264
+ session : AuthenticationSession ,
265
+ workspaceSlug : string ,
266
+ ) : Promise < RepositoryMetadata [ ] | undefined > {
267
+ const api = await this . container . bitbucket ;
268
+ return api ?. getRepositoriesForWorkspace ( this , session . accessToken , workspaceSlug , {
269
+ baseUrl : this . apiBaseUrl ,
270
+ } ) ;
271
+ }
272
+
139
273
protected override async searchProviderMyPullRequests (
140
- _session : AuthenticationSession ,
141
- _repos ?: BitbucketRepositoryDescriptor [ ] ,
274
+ session : ProviderAuthenticationSession ,
275
+ requestedRepositories ?: BitbucketRepositoryDescriptor [ ] ,
142
276
) : Promise < SearchedPullRequest [ ] | undefined > {
143
- return Promise . resolve ( undefined ) ;
277
+ const api = await this . getProvidersApi ( ) ;
278
+ if ( requestedRepositories != null ) {
279
+ // TODO: implement repos version
280
+ return undefined ;
281
+ }
282
+
283
+ const user = await this . getProviderCurrentAccount ( session ) ;
284
+ if ( user ?. username == null ) return undefined ;
285
+
286
+ const workspaces = await this . getProviderResourcesForUser ( session ) ;
287
+ if ( workspaces == null || workspaces . length === 0 ) return undefined ;
288
+
289
+ const repos = await this . getProviderProjectsForResources ( session , workspaces ) ;
290
+ if ( repos == null || repos . length === 0 ) return undefined ;
291
+
292
+ // const wsPrs = await this.getPullRequestsForWorkspaces(workspaces, session.accessToken);
293
+ // const prsById = new Map<string, SearchedPullRequest>();
294
+
295
+ // for (const ws of workspaces) {
296
+ // if (wsPrs) {
297
+ // for (const pr of wsPrs) {
298
+ // prsById.set(pr.id, pr);
299
+ // }
300
+ // }
301
+ // }
302
+
303
+ const prs = await api . getPullRequestsForRepos (
304
+ HostingIntegrationId . Bitbucket ,
305
+ repos . map ( repo => ( { namespace : repo . owner , name : repo . name } ) ) ,
306
+ {
307
+ accessToken : session . accessToken ,
308
+ } ,
309
+ ) ;
310
+ return prs . values . map ( pr => ( {
311
+ pullRequest : this . fromBitbucketProviderPullRequest ( pr , repos ) ,
312
+ reasons : [ ] ,
313
+ } ) ) ;
314
+ // // api.getAzureProjectsForResource
315
+ // // const user = await this.getProviderCurrentAccount(session);
316
+ // // if (user?.username == null) return undefined;
317
+ // const prsById = new Map<string, SearchedPullRequest>();
318
+ // return Array.from(prsById.values());
144
319
}
145
320
146
321
protected override async searchProviderMyIssues (
@@ -149,6 +324,41 @@ export class BitbucketIntegration extends HostingIntegration<
149
324
) : Promise < SearchedIssue [ ] | undefined > {
150
325
return Promise . resolve ( undefined ) ;
151
326
}
327
+
328
+ private fromBitbucketProviderPullRequest (
329
+ remotePullRequest : ProviderPullRequest ,
330
+ repoDescriptors : BitbucketRemoteRepositoryDescriptor [ ] ,
331
+ ) : PullRequest {
332
+ const baseRepoDescriptor = repoDescriptors . find ( r => r . name === remotePullRequest . repository . name ) ;
333
+ const headRepoDescriptor =
334
+ remotePullRequest . headRepository != null
335
+ ? repoDescriptors . find ( r => r . name === remotePullRequest . headRepository ! . name )
336
+ : undefined ;
337
+ if ( baseRepoDescriptor != null ) {
338
+ remotePullRequest . repository . remoteInfo = {
339
+ ...remotePullRequest . repository . remoteInfo ,
340
+ cloneUrlHTTPS : baseRepoDescriptor . cloneUrlHttps ?? '' ,
341
+ cloneUrlSSH : baseRepoDescriptor . cloneUrlSsh ?? '' ,
342
+ } ;
343
+ }
344
+
345
+ if ( headRepoDescriptor != null ) {
346
+ remotePullRequest . headRepository = {
347
+ ...remotePullRequest . headRepository ,
348
+ id : remotePullRequest . headRepository ?. id ?? headRepoDescriptor . id ,
349
+ name : remotePullRequest . headRepository ?. name ?? headRepoDescriptor . name ,
350
+ owner : {
351
+ login : remotePullRequest . headRepository ?. owner . login ?? headRepoDescriptor . resourceName ,
352
+ } ,
353
+ remoteInfo : {
354
+ ...remotePullRequest . headRepository ?. remoteInfo ,
355
+ cloneUrlHTTPS : headRepoDescriptor . cloneUrlHttps ?? '' ,
356
+ cloneUrlSSH : headRepoDescriptor . cloneUrlSsh ?? '' ,
357
+ } ,
358
+ } ;
359
+ }
360
+ return fromProviderPullRequest ( remotePullRequest , this ) ;
361
+ }
152
362
}
153
363
154
364
const bitbucketCloudDomainRegex = / ^ b i t b u c k e t \. o r g $ / i;
0 commit comments