From 37cfe1a3fe8775877b5fc3104805bb35a94ae3e3 Mon Sep 17 00:00:00 2001 From: Sergei Shmakov Date: Wed, 21 May 2025 19:40:11 +0200 Subject: [PATCH 1/6] Syncs cloud integration on Authorizatin problems (#4324) --- .../integrations/models/gitHostIntegration.ts | 3 +- src/plus/integrations/models/integration.ts | 28 +++++++++++++++++-- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/src/plus/integrations/models/gitHostIntegration.ts b/src/plus/integrations/models/gitHostIntegration.ts index e64511a7cdb99..19fe30d96b870 100644 --- a/src/plus/integrations/models/gitHostIntegration.ts +++ b/src/plus/integrations/models/gitHostIntegration.ts @@ -665,8 +665,7 @@ export abstract class GitHostIntegration< ); return { value: pullRequests, duration: Date.now() - start }; } catch (ex) { - Logger.error(ex, scope); - return { error: ex, duration: Date.now() - start }; + return this.handleProviderException(ex, scope, { error: ex, duration: Date.now() - start }); } } diff --git a/src/plus/integrations/models/integration.ts b/src/plus/integrations/models/integration.ts index 3252bc34cae34..795bd8281a0a4 100644 --- a/src/plus/integrations/models/integration.ts +++ b/src/plus/integrations/models/integration.ts @@ -207,10 +207,22 @@ export abstract class IntegrationBase< void this.ensureSession({ createIfNeeded: false }); } + private static readonly requestExceptionLimit = 5; + private static readonly syncDueToRequestExceptionLimit = 1; + private syncCountDueToRequestException = 0; private requestExceptionCount = 0; resetRequestExceptionCount(): void { this.requestExceptionCount = 0; + this.syncCountDueToRequestException = 0; + } + + /** + * Resets request exceptions without resetting the amount of syncs + */ + smoothifyRequestExceptionCount(): void { + // On resync we reset exception count only to avoid infinitive syncs on failure + this.requestExceptionCount = 0; } async reset(): Promise { @@ -270,7 +282,17 @@ export abstract class IntegrationBase< Logger.error(ex, scope); - if (ex instanceof AuthenticationError || ex instanceof RequestClientError) { + if (ex instanceof AuthenticationError && this._session?.cloud) { + if (this.syncCountDueToRequestException < IntegrationBase.syncDueToRequestExceptionLimit) { + this.syncCountDueToRequestException++; + this._session = { + ...this._session, + expiresAt: new Date(Date.now() - 1), + }; + } else { + this.trackRequestException(); + } + } else if (ex instanceof AuthenticationError || ex instanceof RequestClientError) { this.trackRequestException(); } return defaultValue; @@ -304,7 +326,7 @@ export abstract class IntegrationBase< trackRequestException(): void { this.requestExceptionCount++; - if (this.requestExceptionCount >= 5 && this._session !== null) { + if (this.requestExceptionCount >= IntegrationBase.requestExceptionLimit && this._session !== null) { void showIntegrationDisconnectedTooManyFailedRequestsWarningMessage(this.name); void this.disconnect({ currentSessionOnly: true }); } @@ -370,7 +392,7 @@ export abstract class IntegrationBase< } this._session = session ?? null; - this.resetRequestExceptionCount(); + this.smoothifyRequestExceptionCount(); if (session != null) { await this.container.storage.storeWorkspace(this.connectedKey, true); From 87141b44ba94f2f001f65bc51925582fc7848420 Mon Sep 17 00:00:00 2001 From: Sergei Shmakov Date: Thu, 5 Jun 2025 16:12:09 +0200 Subject: [PATCH 2/6] Stores auth problem flag per usecase to avoid continuous refreshing (#4324) --- .../integrations/models/gitHostIntegration.ts | 58 ++++++++---- src/plus/integrations/models/integration.ts | 88 +++++++++++++++---- .../integrations/models/issuesIntegration.ts | 21 +++-- 3 files changed, 124 insertions(+), 43 deletions(-) diff --git a/src/plus/integrations/models/gitHostIntegration.ts b/src/plus/integrations/models/gitHostIntegration.ts index 19fe30d96b870..8b5b6f4ea628e 100644 --- a/src/plus/integrations/models/gitHostIntegration.ts +++ b/src/plus/integrations/models/gitHostIntegration.ts @@ -54,10 +54,10 @@ export abstract class GitHostIntegration< try { const author = await this.getProviderAccountForEmail(this._session!, repo, email, options); - this.resetRequestExceptionCount(); + this.resetRequestExceptionCount('getAccountForEmail'); return author; } catch (ex) { - return this.handleProviderException(ex, scope, undefined); + return this.handleProviderException('getAccountForEmail', ex, scope, undefined); } } @@ -84,10 +84,10 @@ export abstract class GitHostIntegration< try { const author = await this.getProviderAccountForCommit(this._session!, repo, rev, options); - this.resetRequestExceptionCount(); + this.resetRequestExceptionCount('getAccountForCommit'); return author; } catch (ex) { - return this.handleProviderException(ex, scope, undefined); + return this.handleProviderException('getAccountForCommit', ex, scope, undefined); } } @@ -117,10 +117,15 @@ export abstract class GitHostIntegration< value: (async () => { try { const result = await this.getProviderDefaultBranch(this._session!, repo, options?.cancellation); - this.resetRequestExceptionCount(); + this.resetRequestExceptionCount('getDefaultBranch'); return result; } catch (ex) { - return this.handleProviderException(ex, scope, undefined); + return this.handleProviderException( + 'getDefaultBranch', + ex, + scope, + undefined, + ); } })(), }), @@ -160,10 +165,15 @@ export abstract class GitHostIntegration< repo, options?.cancellation, ); - this.resetRequestExceptionCount(); + this.resetRequestExceptionCount('getRepositoryMetadata'); return result; } catch (ex) { - return this.handleProviderException(ex, scope, undefined); + return this.handleProviderException( + 'getRepositoryMetadata', + ex, + scope, + undefined, + ); } })(), }), @@ -188,10 +198,10 @@ export abstract class GitHostIntegration< try { const result = await this.mergeProviderPullRequest(this._session!, pr, options); - this.resetRequestExceptionCount(); + this.resetRequestExceptionCount('mergePullRequest'); return result; } catch (ex) { - return this.handleProviderException(ex, scope, false); + return this.handleProviderException('mergePullRequest', ex, scope, false); } } @@ -224,10 +234,15 @@ export abstract class GitHostIntegration< value: (async () => { try { const result = await this.getProviderPullRequestForBranch(this._session!, repo, branch, opts); - this.resetRequestExceptionCount(); + this.resetRequestExceptionCount('getPullRequestForBranch'); return result; } catch (ex) { - return this.handleProviderException(ex, scope, undefined); + return this.handleProviderException( + 'getPullRequestForBranch', + ex, + scope, + undefined, + ); } })(), }), @@ -264,10 +279,15 @@ export abstract class GitHostIntegration< value: (async () => { try { const result = await this.getProviderPullRequestForCommit(this._session!, repo, rev); - this.resetRequestExceptionCount(); + this.resetRequestExceptionCount('getPullRequestForCommit'); return result; } catch (ex) { - return this.handleProviderException(ex, scope, undefined); + return this.handleProviderException( + 'getPullRequestForCommit', + ex, + scope, + undefined, + ); } })(), }), @@ -663,9 +683,13 @@ export abstract class GitHostIntegration< cancellation, silent, ); + this.resetRequestExceptionCount('searchMyPullRequests'); return { value: pullRequests, duration: Date.now() - start }; } catch (ex) { - return this.handleProviderException(ex, scope, { error: ex, duration: Date.now() - start }); + return this.handleProviderException('searchMyPullRequests', ex, scope, { + error: ex, + duration: Date.now() - start, + }); } } @@ -705,10 +729,10 @@ export abstract class GitHostIntegration< repos != null ? (Array.isArray(repos) ? repos : [repos]) : undefined, cancellation, ); - this.resetRequestExceptionCount(); + this.resetRequestExceptionCount('searchPullRequests'); return prs; } catch (ex) { - return this.handleProviderException(ex, scope, undefined); + return this.handleProviderException('searchPullRequests', ex, scope, undefined); } } diff --git a/src/plus/integrations/models/integration.ts b/src/plus/integrations/models/integration.ts index 795bd8281a0a4..c1af3364add7e 100644 --- a/src/plus/integrations/models/integration.ts +++ b/src/plus/integrations/models/integration.ts @@ -50,6 +50,29 @@ export type IntegrationResult = | { error: Error; duration?: number; value?: never } | undefined; +type SyncReqUsecase = Exclude< + | 'getAccountForCommit' + | 'getAccountForEmail' + | 'getAccountForResource' + | 'getCurrentAccount' + | 'getDefaultBranch' + | 'getIssue' + | 'getIssueOrPullRequest' + | 'getIssuesForProject' + | 'getProjectsForResources' + | 'getPullRequest' + | 'getPullRequestForBranch' + | 'getPullRequestForCommit' + | 'getRepositoryMetadata' + | 'getResourcesForUser' + | 'mergePullRequest' + | 'searchMyIssues' + | 'searchMyPullRequests' + | 'searchPullRequests', + // excluding to show explicitly that we don't want to add 'all' key occasionally + 'all' +>; + export abstract class IntegrationBase< ID extends IntegrationIds = IntegrationIds, T extends ResourceDescriptor = ResourceDescriptor, @@ -174,7 +197,7 @@ export abstract class IntegrationBase< void authProvider.deleteSession(this.authProviderDescriptor); } - this.resetRequestExceptionCount(); + this.resetRequestExceptionCount('all'); this._session = null; if (connected) { @@ -207,14 +230,23 @@ export abstract class IntegrationBase< void this.ensureSession({ createIfNeeded: false }); } + private _syncRequestsPerFailedUsecase = new Set(); + hasSessionSyncRequests(): boolean { + return this._syncRequestsPerFailedUsecase.size > 0; + } + requestSessionSyncForUsecase(syncReqUsecase: SyncReqUsecase): void { + this._syncRequestsPerFailedUsecase.add(syncReqUsecase); + } private static readonly requestExceptionLimit = 5; - private static readonly syncDueToRequestExceptionLimit = 1; - private syncCountDueToRequestException = 0; private requestExceptionCount = 0; - resetRequestExceptionCount(): void { + resetRequestExceptionCount(syncReqUsecase: SyncReqUsecase | 'all'): void { this.requestExceptionCount = 0; - this.syncCountDueToRequestException = 0; + if (syncReqUsecase === 'all') { + this._syncRequestsPerFailedUsecase.clear(); + } else { + this._syncRequestsPerFailedUsecase.delete(syncReqUsecase); + } } /** @@ -277,14 +309,19 @@ export abstract class IntegrationBase< } } - protected handleProviderException(ex: Error, scope: LogScope | undefined, defaultValue: T): T { + protected handleProviderException( + syncReqUsecase: SyncReqUsecase, + ex: Error, + scope: LogScope | undefined, + defaultValue: T, + ): T { if (ex instanceof CancellationError) return defaultValue; Logger.error(ex, scope); if (ex instanceof AuthenticationError && this._session?.cloud) { - if (this.syncCountDueToRequestException < IntegrationBase.syncDueToRequestExceptionLimit) { - this.syncCountDueToRequestException++; + if (!this.hasSessionSyncRequests()) { + this.requestSessionSyncForUsecase(syncReqUsecase); this._session = { ...this._session, expiresAt: new Date(Date.now() - 1), @@ -436,10 +473,10 @@ export abstract class IntegrationBase< resources != null ? (Array.isArray(resources) ? resources : [resources]) : undefined, cancellation, ); - this.resetRequestExceptionCount(); + this.resetRequestExceptionCount('searchMyIssues'); return issues; } catch (ex) { - return this.handleProviderException(ex, scope, undefined); + return this.handleProviderException('searchMyIssues', ex, scope, undefined); } } @@ -476,10 +513,15 @@ export abstract class IntegrationBase< id, options?.type, ); - this.resetRequestExceptionCount(); + this.resetRequestExceptionCount('getIssueOrPullRequest'); return result; } catch (ex) { - return this.handleProviderException(ex, scope, undefined); + return this.handleProviderException( + 'getIssueOrPullRequest', + ex, + scope, + undefined, + ); } })(), }), @@ -516,10 +558,10 @@ export abstract class IntegrationBase< value: (async () => { try { const result = await this.getProviderIssue(this._session!, resource, id); - this.resetRequestExceptionCount(); + this.resetRequestExceptionCount('getIssue'); return result; } catch (ex) { - return this.handleProviderException(ex, scope, undefined); + return this.handleProviderException('getIssue', ex, scope, undefined); } })(), }), @@ -553,10 +595,15 @@ export abstract class IntegrationBase< value: (async () => { try { const account = await this.getProviderCurrentAccount?.(this._session!, opts); - this.resetRequestExceptionCount(); + this.resetRequestExceptionCount('getCurrentAccount'); return account; } catch (ex) { - return this.handleProviderException(ex, scope, undefined); + return this.handleProviderException( + 'getCurrentAccount', + ex, + scope, + undefined, + ); } })(), }), @@ -583,10 +630,15 @@ export abstract class IntegrationBase< value: (async () => { try { const result = await this.getProviderPullRequest?.(this._session!, resource, id); - this.resetRequestExceptionCount(); + this.resetRequestExceptionCount('getPullRequest'); return result; } catch (ex) { - return this.handleProviderException(ex, scope, undefined); + return this.handleProviderException( + 'getPullRequest', + ex, + scope, + undefined, + ); } })(), })); diff --git a/src/plus/integrations/models/issuesIntegration.ts b/src/plus/integrations/models/issuesIntegration.ts index 0ae4904b01324..5b8feef34071b 100644 --- a/src/plus/integrations/models/issuesIntegration.ts +++ b/src/plus/integrations/models/issuesIntegration.ts @@ -32,10 +32,10 @@ export abstract class IssuesIntegration< try { const account = await this.getProviderAccountForResource(this._session!, resource); - this.resetRequestExceptionCount(); + this.resetRequestExceptionCount('getAccountForResource'); return account; } catch (ex) { - return this.handleProviderException(ex, undefined, undefined); + return this.handleProviderException('getAccountForResource', ex, undefined, undefined); } } @@ -55,10 +55,10 @@ export abstract class IssuesIntegration< try { const resources = await this.getProviderResourcesForUser(this._session!); - this.resetRequestExceptionCount(); + this.resetRequestExceptionCount('getResourcesForUser'); return resources; } catch (ex) { - return this.handleProviderException(ex, undefined, undefined); + return this.handleProviderException('getResourcesForUser', ex, undefined, undefined); } } @@ -74,10 +74,10 @@ export abstract class IssuesIntegration< try { const projects = await this.getProviderProjectsForResources(this._session!, resources); - this.resetRequestExceptionCount(); + this.resetRequestExceptionCount('getProjectsForResources'); return projects; } catch (ex) { - return this.handleProviderException(ex, undefined, undefined); + return this.handleProviderException('getProjectsForResources', ex, undefined, undefined); } } @@ -106,10 +106,15 @@ export abstract class IssuesIntegration< try { const issues = await this.getProviderIssuesForProject(this._session!, project, options); - this.resetRequestExceptionCount(); + this.resetRequestExceptionCount('getIssuesForProject'); return issues; } catch (ex) { - return this.handleProviderException(ex, undefined, undefined); + return this.handleProviderException( + 'getIssuesForProject', + ex, + undefined, + undefined, + ); } } From 2ecfca53aabaede7d461b56affe8744997887b33 Mon Sep 17 00:00:00 2001 From: Sergei Shmakov Date: Thu, 5 Jun 2025 16:40:56 +0200 Subject: [PATCH 3/6] Avoids continuous refreshing when GKDev cannot renew expired session (#4324) --- src/plus/integrations/models/integration.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/plus/integrations/models/integration.ts b/src/plus/integrations/models/integration.ts index c1af3364add7e..a24da56cd423f 100644 --- a/src/plus/integrations/models/integration.ts +++ b/src/plus/integrations/models/integration.ts @@ -414,6 +414,10 @@ export abstract class IntegrationBase< source: source, }, ); + + if (session?.expiresAt != null && session.expiresAt < new Date()) { + session = null; + } } catch (ex) { await this.container.storage.deleteWorkspace(this.connectedKey); From 4d05594c7908a79d319d79b87a5568aa1e728200 Mon Sep 17 00:00:00 2001 From: Sergei Shmakov Date: Thu, 19 Jun 2025 15:46:10 +0200 Subject: [PATCH 4/6] Refactors exception handling for integrations - The `handleProviderException` method in `IntegrationBase` is updated to accept an options object instead of individual parameters. - The method now only returns void, requiring the calling functions to return the default value themselves. (#4324) --- .../integrations/models/gitHostIntegration.ts | 51 ++++++++----------- src/plus/integrations/models/integration.ts | 42 ++++++--------- .../integrations/models/issuesIntegration.ts | 17 +++---- 3 files changed, 44 insertions(+), 66 deletions(-) diff --git a/src/plus/integrations/models/gitHostIntegration.ts b/src/plus/integrations/models/gitHostIntegration.ts index 8b5b6f4ea628e..cee9233d99060 100644 --- a/src/plus/integrations/models/gitHostIntegration.ts +++ b/src/plus/integrations/models/gitHostIntegration.ts @@ -57,7 +57,8 @@ export abstract class GitHostIntegration< this.resetRequestExceptionCount('getAccountForEmail'); return author; } catch (ex) { - return this.handleProviderException('getAccountForEmail', ex, scope, undefined); + this.handleProviderException('getAccountForEmail', ex, { scope: scope }); + return undefined; } } @@ -87,7 +88,8 @@ export abstract class GitHostIntegration< this.resetRequestExceptionCount('getAccountForCommit'); return author; } catch (ex) { - return this.handleProviderException('getAccountForCommit', ex, scope, undefined); + this.handleProviderException('getAccountForCommit', ex, { scope: scope }); + return undefined; } } @@ -120,12 +122,8 @@ export abstract class GitHostIntegration< this.resetRequestExceptionCount('getDefaultBranch'); return result; } catch (ex) { - return this.handleProviderException( - 'getDefaultBranch', - ex, - scope, - undefined, - ); + this.handleProviderException('getDefaultBranch', ex, { scope: scope }); + return undefined; } })(), }), @@ -168,12 +166,8 @@ export abstract class GitHostIntegration< this.resetRequestExceptionCount('getRepositoryMetadata'); return result; } catch (ex) { - return this.handleProviderException( - 'getRepositoryMetadata', - ex, - scope, - undefined, - ); + this.handleProviderException('getRepositoryMetadata', ex, { scope: scope }); + return undefined; } })(), }), @@ -201,7 +195,8 @@ export abstract class GitHostIntegration< this.resetRequestExceptionCount('mergePullRequest'); return result; } catch (ex) { - return this.handleProviderException('mergePullRequest', ex, scope, false); + this.handleProviderException('mergePullRequest', ex, { scope: scope }); + return false; } } @@ -237,12 +232,8 @@ export abstract class GitHostIntegration< this.resetRequestExceptionCount('getPullRequestForBranch'); return result; } catch (ex) { - return this.handleProviderException( - 'getPullRequestForBranch', - ex, - scope, - undefined, - ); + this.handleProviderException('getPullRequestForBranch', ex, { scope: scope }); + return undefined; } })(), }), @@ -282,12 +273,8 @@ export abstract class GitHostIntegration< this.resetRequestExceptionCount('getPullRequestForCommit'); return result; } catch (ex) { - return this.handleProviderException( - 'getPullRequestForCommit', - ex, - scope, - undefined, - ); + this.handleProviderException('getPullRequestForCommit', ex, { scope: scope }); + return undefined; } })(), }), @@ -686,10 +673,13 @@ export abstract class GitHostIntegration< this.resetRequestExceptionCount('searchMyPullRequests'); return { value: pullRequests, duration: Date.now() - start }; } catch (ex) { - return this.handleProviderException('searchMyPullRequests', ex, scope, { + this.handleProviderException('searchMyPullRequests', ex, { + scope: scope, + }); + return { error: ex, duration: Date.now() - start, - }); + }; } } @@ -732,7 +722,8 @@ export abstract class GitHostIntegration< this.resetRequestExceptionCount('searchPullRequests'); return prs; } catch (ex) { - return this.handleProviderException('searchPullRequests', ex, scope, undefined); + this.handleProviderException('searchPullRequests', ex, { scope: scope }); + return undefined; } } diff --git a/src/plus/integrations/models/integration.ts b/src/plus/integrations/models/integration.ts index a24da56cd423f..d21b608005a25 100644 --- a/src/plus/integrations/models/integration.ts +++ b/src/plus/integrations/models/integration.ts @@ -309,15 +309,14 @@ export abstract class IntegrationBase< } } - protected handleProviderException( + protected handleProviderException( syncReqUsecase: SyncReqUsecase, ex: Error, - scope: LogScope | undefined, - defaultValue: T, - ): T { - if (ex instanceof CancellationError) return defaultValue; + options?: { scope?: LogScope | undefined }, + ): void { + if (ex instanceof CancellationError) return; - Logger.error(ex, scope); + Logger.error(ex, options?.scope); if (ex instanceof AuthenticationError && this._session?.cloud) { if (!this.hasSessionSyncRequests()) { @@ -332,7 +331,6 @@ export abstract class IntegrationBase< } else if (ex instanceof AuthenticationError || ex instanceof RequestClientError) { this.trackRequestException(); } - return defaultValue; } private missingExpirityReported = false; @@ -480,7 +478,8 @@ export abstract class IntegrationBase< this.resetRequestExceptionCount('searchMyIssues'); return issues; } catch (ex) { - return this.handleProviderException('searchMyIssues', ex, scope, undefined); + this.handleProviderException('searchMyIssues', ex, { scope: scope }); + return undefined; } } @@ -520,12 +519,8 @@ export abstract class IntegrationBase< this.resetRequestExceptionCount('getIssueOrPullRequest'); return result; } catch (ex) { - return this.handleProviderException( - 'getIssueOrPullRequest', - ex, - scope, - undefined, - ); + this.handleProviderException('getIssueOrPullRequest', ex, { scope: scope }); + return undefined; } })(), }), @@ -565,7 +560,8 @@ export abstract class IntegrationBase< this.resetRequestExceptionCount('getIssue'); return result; } catch (ex) { - return this.handleProviderException('getIssue', ex, scope, undefined); + this.handleProviderException('getIssue', ex, { scope: scope }); + return undefined; } })(), }), @@ -602,12 +598,8 @@ export abstract class IntegrationBase< this.resetRequestExceptionCount('getCurrentAccount'); return account; } catch (ex) { - return this.handleProviderException( - 'getCurrentAccount', - ex, - scope, - undefined, - ); + this.handleProviderException('getCurrentAccount', ex, { scope: scope }); + return undefined; } })(), }), @@ -637,12 +629,8 @@ export abstract class IntegrationBase< this.resetRequestExceptionCount('getPullRequest'); return result; } catch (ex) { - return this.handleProviderException( - 'getPullRequest', - ex, - scope, - undefined, - ); + this.handleProviderException('getPullRequest', ex, { scope: scope }); + return undefined; } })(), })); diff --git a/src/plus/integrations/models/issuesIntegration.ts b/src/plus/integrations/models/issuesIntegration.ts index 5b8feef34071b..717ac582eafc0 100644 --- a/src/plus/integrations/models/issuesIntegration.ts +++ b/src/plus/integrations/models/issuesIntegration.ts @@ -35,7 +35,8 @@ export abstract class IssuesIntegration< this.resetRequestExceptionCount('getAccountForResource'); return account; } catch (ex) { - return this.handleProviderException('getAccountForResource', ex, undefined, undefined); + this.handleProviderException('getAccountForResource', ex); + return undefined; } } @@ -58,7 +59,8 @@ export abstract class IssuesIntegration< this.resetRequestExceptionCount('getResourcesForUser'); return resources; } catch (ex) { - return this.handleProviderException('getResourcesForUser', ex, undefined, undefined); + this.handleProviderException('getResourcesForUser', ex); + return undefined; } } @@ -77,7 +79,8 @@ export abstract class IssuesIntegration< this.resetRequestExceptionCount('getProjectsForResources'); return projects; } catch (ex) { - return this.handleProviderException('getProjectsForResources', ex, undefined, undefined); + this.handleProviderException('getProjectsForResources', ex); + return undefined; } } @@ -109,12 +112,8 @@ export abstract class IssuesIntegration< this.resetRequestExceptionCount('getIssuesForProject'); return issues; } catch (ex) { - return this.handleProviderException( - 'getIssuesForProject', - ex, - undefined, - undefined, - ); + this.handleProviderException('getIssuesForProject', ex); + return undefined; } } From ff0c424da7e90b49ce7fd3f30698a117c46da4c0 Mon Sep 17 00:00:00 2001 From: Sergei Shmakov Date: Thu, 19 Jun 2025 14:59:49 +0200 Subject: [PATCH 5/6] Suppresses warning message on Launchpad load failure (#4324) --- src/plus/integrations/models/gitHostIntegration.ts | 1 + src/plus/integrations/models/integration.ts | 12 +++++++----- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/plus/integrations/models/gitHostIntegration.ts b/src/plus/integrations/models/gitHostIntegration.ts index cee9233d99060..211e7255835ed 100644 --- a/src/plus/integrations/models/gitHostIntegration.ts +++ b/src/plus/integrations/models/gitHostIntegration.ts @@ -675,6 +675,7 @@ export abstract class GitHostIntegration< } catch (ex) { this.handleProviderException('searchMyPullRequests', ex, { scope: scope, + silent: true, }); return { error: ex, diff --git a/src/plus/integrations/models/integration.ts b/src/plus/integrations/models/integration.ts index d21b608005a25..a161f723f50e5 100644 --- a/src/plus/integrations/models/integration.ts +++ b/src/plus/integrations/models/integration.ts @@ -312,7 +312,7 @@ export abstract class IntegrationBase< protected handleProviderException( syncReqUsecase: SyncReqUsecase, ex: Error, - options?: { scope?: LogScope | undefined }, + options?: { scope?: LogScope | undefined; silent?: boolean }, ): void { if (ex instanceof CancellationError) return; @@ -326,10 +326,10 @@ export abstract class IntegrationBase< expiresAt: new Date(Date.now() - 1), }; } else { - this.trackRequestException(); + this.trackRequestException(options); } } else if (ex instanceof AuthenticationError || ex instanceof RequestClientError) { - this.trackRequestException(); + this.trackRequestException(options); } } @@ -358,11 +358,13 @@ export abstract class IntegrationBase< } @debug() - trackRequestException(): void { + trackRequestException(options?: { silent?: boolean }): void { this.requestExceptionCount++; if (this.requestExceptionCount >= IntegrationBase.requestExceptionLimit && this._session !== null) { - void showIntegrationDisconnectedTooManyFailedRequestsWarningMessage(this.name); + if (!options?.silent) { + void showIntegrationDisconnectedTooManyFailedRequestsWarningMessage(this.name); + } void this.disconnect({ currentSessionOnly: true }); } } From aaa9497f2237972af0ae5923e1b56746ed1db348 Mon Sep 17 00:00:00 2001 From: Sergei Shmakov Date: Thu, 19 Jun 2025 16:31:02 +0200 Subject: [PATCH 6/6] Resets request exception count after token refresh Resets the request exception count when a new access token is obtained after refreshing the session. This prevents the integration from being prematurely disconnected due to request errors associated with the old token. (#4324) --- src/plus/integrations/models/integration.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/plus/integrations/models/integration.ts b/src/plus/integrations/models/integration.ts index a161f723f50e5..6d9b0515c4389 100644 --- a/src/plus/integrations/models/integration.ts +++ b/src/plus/integrations/models/integration.ts @@ -278,7 +278,8 @@ export abstract class IntegrationBase< } switch (state) { - case 'connected': + case 'connected': { + const oldSession = this._session; if (forceSync) { // Reset our stored session so that we get a new one from the cloud const authProvider = await this.authenticationService.get(this.authProvider.id); @@ -301,8 +302,14 @@ export abstract class IntegrationBase< // sync option, rather than createIfNeeded, makes sure we don't call connectCloudIntegrations and open a gkdev window // if there was no session or some problem fetching/refreshing the existing session from the cloud api - await this.ensureSession({ sync: forceSync }); + const newSession = await this.ensureSession({ sync: forceSync }); + + if (oldSession && newSession && newSession.accessToken !== oldSession.accessToken) { + this.resetRequestExceptionCount('all'); + } + break; + } case 'disconnected': await this.disconnect({ silent: true }); break;