diff --git a/package.json b/package.json index 749447f1d1470..406cc17d699d1 100644 --- a/package.json +++ b/package.json @@ -21476,7 +21476,7 @@ "dependencies": { "@gitkraken/gitkraken-components": "10.7.0", "@gitkraken/gitkraken-components-next": "npm:@gitkraken/gitkraken-components@11.0.0-vnext.3", - "@gitkraken/provider-apis": "0.26.2", + "@gitkraken/provider-apis": "v0.28.2", "@gitkraken/shared-web-components": "0.1.1-rc.15", "@gk-nzaytsev/fast-string-truncated-width": "1.1.0", "@lit-labs/signals": "0.1.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 701e4b0af976c..90bfef17d2651 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -21,8 +21,8 @@ importers: specifier: npm:@gitkraken/gitkraken-components@11.0.0-vnext.3 version: '@gitkraken/gitkraken-components@11.0.0-vnext.3(@types/react@17.0.83)(react@16.8.4)' '@gitkraken/provider-apis': - specifier: 0.26.2 - version: 0.26.2(encoding@0.1.13) + specifier: v0.28.2 + version: 0.28.2(encoding@0.1.13) '@gitkraken/shared-web-components': specifier: 0.1.1-rc.15 version: 0.1.1-rc.15 @@ -632,8 +632,8 @@ packages: peerDependencies: react: 19.0.0 - '@gitkraken/provider-apis@0.26.2': - resolution: {integrity: sha512-1NknF+CaEd+XOl9i+kkDF+SWJ99HvOWhZPM/eEL6FPVc3LEjYLZZHopk4Rt+cqYfLF7YWVFgkp7tgAT6tJzTfA==} + '@gitkraken/provider-apis@0.28.2': + resolution: {integrity: sha512-+wgLHrk82o4yzk3GlZj75o1tqvH2Ja48wsZJccR+FYEmamyHsqwYIq0rNuFf2lEO9Y1uUhgKpo3ovg/BsZpyIA==} engines: {node: '>= 14'} '@gitkraken/shared-web-components@0.1.1-rc.15': @@ -6084,7 +6084,7 @@ snapshots: transitivePeerDependencies: - '@types/react' - '@gitkraken/provider-apis@0.26.2(encoding@0.1.13)': + '@gitkraken/provider-apis@0.28.2(encoding@0.1.13)': dependencies: js-base64: 3.7.5 node-fetch: 2.7.0(encoding@0.1.13) diff --git a/src/plus/integrations/providers/bitbucket.ts b/src/plus/integrations/providers/bitbucket.ts index 724c79127a766..ddc76d88b2c35 100644 --- a/src/plus/integrations/providers/bitbucket.ts +++ b/src/plus/integrations/providers/bitbucket.ts @@ -13,7 +13,7 @@ import type { IntegrationAuthenticationProviderDescriptor } from '../authenticat import type { ProviderAuthenticationSession } from '../authentication/models'; import { HostingIntegration } from '../integration'; import type { BitbucketRepositoryDescriptor, BitbucketWorkspaceDescriptor } from './bitbucket/models'; -import { providersMetadata } from './models'; +import { fromProviderPullRequest, providersMetadata } from './models'; const metadata = providersMetadata[HostingIntegrationId.Bitbucket]; const authProvider = Object.freeze({ id: metadata.id, scopes: metadata.scopes }); @@ -207,9 +207,18 @@ export class BitbucketIntegration extends HostingIntegration< return undefined; } + const api = await this.getProvidersApi(); + if (!api) { + return undefined; + } + const remotes = await flatSettled(this.container.git.openRepositories.map(r => r.git.remotes().getRemotes())); const workspaceRepos = await nonnullSettled( - remotes.map(async r => ((await r.getIntegration())?.id === this.id ? r.path : undefined)), + remotes.map(async r => { + const integration = await r.getIntegration(); + const [namespace, name] = r.path.split('/'); + return integration?.id === this.id ? { name: name, namespace: namespace } : undefined; + }), ); const user = await this.getProviderCurrentAccount(session); @@ -218,28 +227,23 @@ export class BitbucketIntegration extends HostingIntegration< const workspaces = await this.getProviderResourcesForUser(session); if (workspaces == null || workspaces.length === 0) return undefined; - const api = await this.container.bitbucket; - if (!api) return undefined; - - const authoredPrs = workspaces.map(ws => - api.getPullRequestsForWorkspaceAuthoredByUser(this, session.accessToken, user.id, ws.slug, this.apiBaseUrl), - ); - - const reviewingPrs = workspaceRepos.map(repo => { - const [owner, name] = repo.split('/'); - return api.getUsersReviewingPullRequestsForRepo( - this, - session.accessToken, - user.id, - owner, - name, - this.apiBaseUrl, - ); + const authoredPrs = workspaces.map(async ws => { + const prs = await api.getBitbucketPullRequestsAuthoredByUserForWorkspace(user.id, ws.slug, { + accessToken: session.accessToken, + }); + return prs?.map(pr => fromProviderPullRequest(pr, this)); }); + const reviewingPrs = api + .getPullRequestsForRepos(this.id, workspaceRepos, { + query: `state="OPEN" AND reviewers.uuid="${user.id}"`, + accessToken: session.accessToken, + }) + .then(r => r.values?.map(pr => fromProviderPullRequest(pr, this))); + return [ ...uniqueBy( - await flatSettled([...authoredPrs, ...reviewingPrs]), + await flatSettled([...authoredPrs, reviewingPrs]), pr => pr.url, (orig, _cur) => orig, ), @@ -330,7 +334,7 @@ export function isBitbucketCloudDomain(domain: string | undefined): boolean { return domain != null && bitbucketCloudDomainRegex.test(domain); } -type MaybePromiseArr = Promise[] | (T | undefined)[]; +type MaybePromiseArr = (Promise | T | undefined)[]; async function nonnullSettled(arr: MaybePromiseArr): Promise { const all = await Promise.allSettled(arr); diff --git a/src/plus/integrations/providers/bitbucket/bitbucket.ts b/src/plus/integrations/providers/bitbucket/bitbucket.ts index ee48a1d26975c..722c183204179 100644 --- a/src/plus/integrations/providers/bitbucket/bitbucket.ts +++ b/src/plus/integrations/providers/bitbucket/bitbucket.ts @@ -289,70 +289,6 @@ export class BitbucketApi implements Disposable { } } - async getPullRequestsForWorkspaceAuthoredByUser( - provider: Provider, - token: string, - userUuid: string, - workspace: string, - baseUrl: string, - ): Promise { - const scope = getLogScope(); - - const response = await this.request<{ - values: BitbucketPullRequest[]; - pagelen: number; - size: number; - page: number; - }>( - provider, - token, - baseUrl, - `workspaces/${workspace}/pullrequests/${userUuid}?state=OPEN&fields=%2Bvalues.reviewers,%2Bvalues.participants`, - { - method: 'GET', - }, - scope, - ); - - if (!response?.values?.length) { - return undefined; - } - return response.values.map(pr => fromBitbucketPullRequest(pr, provider)); - } - - async getUsersReviewingPullRequestsForRepo( - provider: Provider, - token: string, - userUuid: string, - owner: string, - repo: string, - baseUrl: string, - ): Promise { - const scope = getLogScope(); - - const query = encodeURIComponent(`state="OPEN" AND reviewers.uuid="${userUuid}"`); - const response = await this.request<{ - values: BitbucketPullRequest[]; - pagelen: number; - size: number; - page: number; - }>( - provider, - token, - baseUrl, - `repositories/${owner}/${repo}/pullrequests?q=${query}&state=OPEN&fields=%2Bvalues.reviewers,%2Bvalues.participants`, - { - method: 'GET', - }, - scope, - ); - - if (!response?.values?.length) { - return undefined; - } - return response.values.map(pr => fromBitbucketPullRequest(pr, provider)); - } - private async request( provider: Provider, token: string, diff --git a/src/plus/integrations/providers/models.ts b/src/plus/integrations/providers/models.ts index 2b5d342053530..10b5ae00bc680 100644 --- a/src/plus/integrations/providers/models.ts +++ b/src/plus/integrations/providers/models.ts @@ -18,6 +18,7 @@ import type { Jira, JiraProject, JiraResource, + NumberedPageInput, Issue as ProviderApiIssue, PullRequestWithUniqueID, RequestFunction, @@ -151,6 +152,7 @@ export interface GetPullRequestsOptions { assigneeLogins?: string[]; reviewRequestedLogin?: string; mentionLogin?: string; + query?: string; cursor?: string; // stringified JSON object of type { type: 'cursor' | 'page'; value: string | number } | {} baseUrl?: string; } @@ -342,6 +344,19 @@ export type GetBitbucketResourcesForUserFn = ( input: { userId: string }, options?: EnterpriseOptions, ) => Promise<{ data: BitbucketWorkspaceStub[] }>; +export type GetBitbucketPullRequestsAuthoredByUserForWorkspaceFn = ( + input: { + userId: string; + workspaceSlug: string; + } & NumberedPageInput, + options?: EnterpriseOptions, +) => Promise<{ + pageInfo: { + hasNextPage: boolean; + nextPage: number | null; + }; + data: GitPullRequest[]; +}>; export type GetIssuesForProjectFn = Jira['getIssuesForProject']; export type GetIssuesForResourceForCurrentUserFn = ( input: { resourceId: string }, @@ -364,6 +379,7 @@ export interface ProviderInfo extends ProviderMetadata { getJiraResourcesForCurrentUserFn?: GetJiraResourcesForCurrentUserFn; getAzureResourcesForUserFn?: GetAzureResourcesForUserFn; getBitbucketResourcesForUserFn?: GetBitbucketResourcesForUserFn; + getBitbucketPullRequestsAuthoredByUserForWorkspaceFn?: GetBitbucketPullRequestsAuthoredByUserForWorkspaceFn; getJiraProjectsForResourcesFn?: GetJiraProjectsForResourcesFn; getAzureProjectsForResourceFn?: GetAzureProjectsForResourceFn; getIssuesForProjectFn?: GetIssuesForProjectFn; @@ -912,7 +928,7 @@ export function fromProviderPullRequest( integration, fromProviderAccount(pr.author), pr.id, - pr.graphQLId, + pr.graphQLId || pr.id, pr.title, pr.url ?? '', { diff --git a/src/plus/integrations/providers/providersApi.ts b/src/plus/integrations/providers/providersApi.ts index 36f92accfb6aa..af969b2c4079f 100644 --- a/src/plus/integrations/providers/providersApi.ts +++ b/src/plus/integrations/providers/providersApi.ts @@ -19,6 +19,7 @@ import type { IntegrationAuthenticationService } from '../authentication/integra import type { GetAzureProjectsForResourceFn, GetAzureResourcesForUserFn, + GetBitbucketPullRequestsAuthoredByUserForWorkspaceFn, GetBitbucketResourcesForUserFn, GetCurrentUserFn, GetCurrentUserForInstanceFn, @@ -202,6 +203,10 @@ export class ProvidersApi { getBitbucketResourcesForUserFn: providerApis.bitbucket.getWorkspacesForUser.bind( providerApis.bitbucket, ) as GetBitbucketResourcesForUserFn, + getBitbucketPullRequestsAuthoredByUserForWorkspaceFn: + providerApis.bitbucket.getPullRequestsForUserAndWorkspace.bind( + providerApis.bitbucket, + ) as GetBitbucketPullRequestsAuthoredByUserForWorkspaceFn, getPullRequestsForReposFn: providerApis.bitbucket.getPullRequestsForRepos.bind( providerApis.bitbucket, ) as GetPullRequestsForReposFn, @@ -564,6 +569,29 @@ export class ProvidersApi { } } + async getBitbucketPullRequestsAuthoredByUserForWorkspace( + userId: string, + workspaceSlug: string, + options?: { accessToken?: string }, + ): Promise { + const { provider, token } = await this.ensureProviderTokenAndFunction( + HostingIntegrationId.Bitbucket, + 'getBitbucketPullRequestsAuthoredByUserForWorkspaceFn', + options?.accessToken, + ); + + try { + return ( + await provider.getBitbucketPullRequestsAuthoredByUserForWorkspaceFn?.( + { userId: userId, workspaceSlug: workspaceSlug }, + { token: token }, + ) + )?.data; + } catch (e) { + return this.handleProviderError(HostingIntegrationId.Bitbucket, token, e); + } + } + async getJiraProjectsForResources( resourceIds: string[], options?: { accessToken?: string },