Skip to content

Commit ef87246

Browse files
authored
chat - handle error situations and present to user (microsoft#236019)
1 parent 4e530c6 commit ef87246

File tree

1 file changed

+63
-7
lines changed

1 file changed

+63
-7
lines changed

src/vs/workbench/contrib/chat/browser/chatSetup.ts

Lines changed: 63 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ import { mainWindow } from '../../../../base/browser/window.js';
5959
import { IOpenerService } from '../../../../platform/opener/common/opener.js';
6060
import { URI } from '../../../../base/common/uri.js';
6161
import { IHostService } from '../../../services/host/browser/host.js';
62+
import Severity from '../../../../base/common/severity.js';
6263

6364
const defaultChat = {
6465
extensionId: product.defaultChatAgent?.extensionId ?? '',
@@ -365,6 +366,8 @@ class ChatSetupRequests extends Disposable {
365366
@ILogService private readonly logService: ILogService,
366367
@IRequestService private readonly requestService: IRequestService,
367368
@IChatQuotasService private readonly chatQuotasService: IChatQuotasService,
369+
@IDialogService private readonly dialogService: IDialogService,
370+
@IOpenerService private readonly openerService: IOpenerService
368371
) {
369372
super();
370373

@@ -486,7 +489,12 @@ class ChatSetupRequests extends Disposable {
486489
return { entitlement: ChatEntitlement.Unresolved };
487490
}
488491

489-
const responseText = await asText(response);
492+
let responseText: string | null = null;
493+
try {
494+
responseText = await asText(response);
495+
} catch (error) {
496+
// ignore - handled below
497+
}
490498
if (token.isCancellationRequested) {
491499
return undefined;
492500
}
@@ -589,18 +597,38 @@ class ChatSetupRequests extends Disposable {
589597

590598
const response = await this.request(defaultChat.entitlementSignupLimitedUrl, 'POST', body, session, CancellationToken.None);
591599
if (!response) {
592-
this.logService.error('[chat setup] sign-up: no response');
600+
this.onUnknownSignUpError('[chat setup] sign-up: no response');
593601
return false;
594602
}
595603

596604
if (response.res.statusCode && response.res.statusCode !== 200) {
597-
this.logService.error(`[chat setup] sign-up: unexpected status code ${response.res.statusCode}`);
605+
if (response.res.statusCode === 422) {
606+
try {
607+
const responseText = await asText(response);
608+
if (responseText) {
609+
const responseError: { message: string } = JSON.parse(responseText);
610+
if (typeof responseError.message === 'string' && responseError.message) {
611+
this.onUnprocessableSignUpError(`[chat setup] sign-up: unprocessable entity (${responseError.message})`, responseError.message);
612+
return false;
613+
}
614+
}
615+
} catch (error) {
616+
// ignore - handled below
617+
}
618+
}
619+
this.onUnknownSignUpError(`[chat setup] sign-up: unexpected status code ${response.res.statusCode}`);
598620
return false;
599621
}
600622

601-
const responseText = await asText(response);
623+
let responseText: string | null = null;
624+
try {
625+
responseText = await asText(response);
626+
} catch (error) {
627+
// ignore - handled below
628+
}
629+
602630
if (!responseText) {
603-
this.logService.error('[chat setup] sign-up: response has no content');
631+
this.onUnknownSignUpError('[chat setup] sign-up: response has no content');
604632
return false;
605633
}
606634

@@ -609,7 +637,7 @@ class ChatSetupRequests extends Disposable {
609637
parsedResult = JSON.parse(responseText);
610638
this.logService.trace(`[chat setup] sign-up: response is ${responseText}`);
611639
} catch (err) {
612-
this.logService.error(`[chat setup] sign-up: error parsing response (${err})`);
640+
this.onUnknownSignUpError(`[chat setup] sign-up: error parsing response (${err})`);
613641
}
614642

615643
const subscribed = Boolean(parsedResult?.subscribed);
@@ -626,6 +654,30 @@ class ChatSetupRequests extends Disposable {
626654
return subscribed;
627655
}
628656

657+
private onUnknownSignUpError(logMessage: string): void {
658+
this.dialogService.error(localize('unknownSignUpError', "An error occurred while signing up for Copilot Free."), localize('unknownSignUpErrorDetail', "Please try again."));
659+
this.logService.error(logMessage);
660+
}
661+
662+
private onUnprocessableSignUpError(logMessage: string, logDetails: string): void {
663+
this.dialogService.prompt({
664+
type: Severity.Error,
665+
message: localize('unprocessableSignUpError', "An error occurred while signing up for Copilot Free."),
666+
detail: logDetails,
667+
buttons: [
668+
{
669+
label: localize('ok', "OK"),
670+
run: () => { /* noop */ }
671+
},
672+
{
673+
label: localize('learnMore', "Learn More"),
674+
run: () => this.openerService.open(URI.parse(defaultChat.upgradePlanUrl))
675+
}
676+
],
677+
});
678+
this.logService.error(logMessage);
679+
}
680+
629681
override dispose(): void {
630682
this.pendingResolveCts.dispose(true);
631683

@@ -644,7 +696,7 @@ type InstallChatClassification = {
644696
signedIn: { classification: 'SystemMetaData'; purpose: 'FeatureInsight'; comment: 'Whether the user did sign in prior to installing the extension.' };
645697
};
646698
type InstallChatEvent = {
647-
installResult: 'installed' | 'cancelled' | 'failedInstall' | 'failedNotSignedIn';
699+
installResult: 'installed' | 'cancelled' | 'failedInstall' | 'failedNotSignedIn' | 'failedSignUp';
648700
signedIn: boolean;
649701
};
650702

@@ -790,6 +842,10 @@ class ChatSetupController extends Disposable {
790842

791843
if (entitlement !== ChatEntitlement.Limited && entitlement !== ChatEntitlement.Pro && entitlement !== ChatEntitlement.Unavailable) {
792844
didSignUp = await this.requests.signUpLimited(session);
845+
846+
if (!didSignUp) {
847+
this.telemetryService.publicLog2<InstallChatEvent, InstallChatClassification>('commandCenter.chatInstall', { installResult: 'failedSignUp', signedIn });
848+
}
793849
}
794850

795851
await this.extensionsWorkbenchService.install(defaultChat.extensionId, {

0 commit comments

Comments
 (0)