Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve Continue On sign in flow #179042

Merged
merged 1 commit into from
Apr 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo
hasApplicationLaunchedFromContinueOnFlow === false
) {
this.storageService.store(EditSessionsContribution.APPLICATION_LAUNCHED_VIA_CONTINUE_ON_STORAGE_KEY, true, StorageScope.APPLICATION, StorageTarget.MACHINE);
await this.editSessionsStorageService.initialize(true);
await this.editSessionsStorageService.initialize();
if (this.editSessionsStorageService.isSignedIn) {
await this.progressService.withProgress(resumeProgressOptions, async (progress) => await this.resumeEditSession(undefined, true, undefined, progress));
} else {
Expand Down Expand Up @@ -451,7 +451,7 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo

this.logService.info(ref !== undefined ? `Resuming changes from cloud with ref ${ref}...` : 'Checking for pending cloud changes...');

if (silent && !(await this.editSessionsStorageService.initialize(false, true))) {
if (silent && !(await this.editSessionsStorageService.initialize(true))) {
return;
}

Expand Down Expand Up @@ -759,7 +759,32 @@ export class EditSessionsContribution extends Disposable implements IWorkbenchCo

// Prompt the user to use edit sessions if they currently could benefit from using it
if (this.hasEditSession()) {
const initialized = await this.editSessionsStorageService.initialize(true);
const quickpick = this.quickInputService.createQuickPick<IQuickPickItem>();
quickpick.placeholder = localize('continue with cloud changes', "Select whether to bring your working changes with you");
quickpick.ok = false;
quickpick.ignoreFocusOut = true;
const withCloudChanges = { label: localize('with cloud changes', "Yes, continue with my working changes") };
const withoutCloudChanges = { label: localize('without cloud changes', "No, continue without my working changes") };
quickpick.items = [withCloudChanges, withoutCloudChanges];

const continueWithCloudChanges = await new Promise<boolean>((resolve, reject) => {
quickpick.onDidAccept(() => {
resolve(quickpick.selectedItems[0] === withCloudChanges);
quickpick.hide();
});
quickpick.onDidHide(() => {
reject(new CancellationError());
quickpick.hide();
});
quickpick.show();
});

if (!continueWithCloudChanges) {
this.telemetryService.publicLog2<EditSessionsAuthCheckEvent, EditSessionsAuthCheckClassification>('continueOn.editSessions.canStore.outcome', { outcome: 'didNotEnableEditSessionsWhenPrompted' });
return continueWithCloudChanges;
}

const initialized = await this.editSessionsStorageService.initialize();
if (!initialized) {
this.telemetryService.publicLog2<EditSessionsAuthCheckEvent, EditSessionsAuthCheckClassification>('continueOn.editSessions.canStore.outcome', { outcome: 'didNotEnableEditSessionsWhenPrompted' });
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,13 @@ import { generateUuid } from 'vs/base/common/uuid';
import { ICredentialsService } from 'vs/platform/credentials/common/credentials';
import { getCurrentAuthenticationSessionInfo } from 'vs/workbench/services/authentication/browser/authenticationService';
import { isWeb } from 'vs/base/common/platform';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { Codicon } from 'vs/base/common/codicons';
import { ThemeIcon } from 'vs/base/common/themables';
import { IUserDataSyncMachinesService, UserDataSyncMachinesService } from 'vs/platform/userDataSync/common/userDataSyncMachines';
import { Emitter } from 'vs/base/common/event';
import { CancellationError } from 'vs/base/common/errors';

type ExistingSession = IQuickPickItem & { session: AuthenticationSession & { providerId: string } };
type AuthenticationProviderOption = IQuickPickItem & { provider: IAuthenticationProvider };

const configureContinueOnPreference = { iconClass: ThemeIcon.asClassName(Codicon.settingsGear), tooltip: localize('configure continue on', 'Configure this preference in settings') };
export class EditSessionsWorkbenchService extends Disposable implements IEditSessionsStorageService {

declare _serviceBrand: undefined;
Expand Down Expand Up @@ -75,8 +71,7 @@ export class EditSessionsWorkbenchService extends Disposable implements IEditSes
@IContextKeyService private readonly contextKeyService: IContextKeyService,
@IRequestService private readonly requestService: IRequestService,
@IDialogService private readonly dialogService: IDialogService,
@ICredentialsService private readonly credentialsService: ICredentialsService,
@ICommandService private readonly commandService: ICommandService
@ICredentialsService private readonly credentialsService: ICredentialsService
) {
super();

Expand Down Expand Up @@ -169,11 +164,11 @@ export class EditSessionsWorkbenchService extends Disposable implements IEditSes
return [];
}

public async initialize(fromContinueOn: boolean, silent: boolean = false) {
public async initialize(silent: boolean = false) {
if (this.initialized) {
return true;
}
this.initialized = await this.doInitialize(fromContinueOn, silent);
this.initialized = await this.doInitialize(silent);
this.signedInContext.set(this.initialized);
if (this.initialized) {
this._didSignIn.fire();
Expand All @@ -188,7 +183,7 @@ export class EditSessionsWorkbenchService extends Disposable implements IEditSes
* meaning that authentication is configured and it
* can be used to communicate with the remote storage service
*/
private async doInitialize(fromContinueOn: boolean, silent: boolean): Promise<boolean> {
private async doInitialize(silent: boolean): Promise<boolean> {
// Wait for authentication extensions to be registered
await this.extensionService.whenInstalledExtensionsRegistered();

Expand All @@ -213,7 +208,7 @@ export class EditSessionsWorkbenchService extends Disposable implements IEditSes
return true;
}

const authenticationSession = await this.getAuthenticationSession(fromContinueOn, silent);
const authenticationSession = await this.getAuthenticationSession(silent);
if (authenticationSession !== undefined) {
this.#authenticationInfo = authenticationSession;
this.storeClient.setAuthToken(authenticationSession.token, authenticationSession.providerId);
Expand Down Expand Up @@ -246,7 +241,7 @@ export class EditSessionsWorkbenchService extends Disposable implements IEditSes
return currentMachineId;
}

private async getAuthenticationSession(fromContinueOn: boolean, silent: boolean) {
private async getAuthenticationSession(silent: boolean) {
// If the user signed in previously and the session is still available, reuse that without prompting the user again
if (this.existingSessionId) {
this.logService.info(`Searching for existing authentication session with ID ${this.existingSessionId}`);
Expand Down Expand Up @@ -277,7 +272,7 @@ export class EditSessionsWorkbenchService extends Disposable implements IEditSes
}

// Ask the user to pick a preferred account
const authenticationSession = await this.getAccountPreference(fromContinueOn);
const authenticationSession = await this.getAccountPreference();
if (authenticationSession !== undefined) {
this.existingSessionId = authenticationSession.id;
return { sessionId: authenticationSession.id, token: authenticationSession.idToken ?? authenticationSession.accessToken, providerId: authenticationSession.providerId };
Expand All @@ -294,12 +289,12 @@ export class EditSessionsWorkbenchService extends Disposable implements IEditSes
*
* Prompts the user to pick an authentication option for storing and getting edit sessions.
*/
private async getAccountPreference(fromContinueOn: boolean): Promise<AuthenticationSession & { providerId: string } | undefined> {
private async getAccountPreference(): Promise<AuthenticationSession & { providerId: string } | undefined> {
const quickpick = this.quickInputService.createQuickPick<ExistingSession | AuthenticationProviderOption | IQuickPickItem>();
quickpick.ok = false;
quickpick.placeholder = localize('choose account placeholder', "Select an account to store your working changes in the cloud");
quickpick.ignoreFocusOut = true;
quickpick.items = await this.createQuickpickItems(fromContinueOn);
quickpick.items = await this.createQuickpickItems();

return new Promise((resolve, reject) => {
quickpick.onDidHide((e) => {
Expand All @@ -314,17 +309,11 @@ export class EditSessionsWorkbenchService extends Disposable implements IEditSes
quickpick.hide();
});

quickpick.onDidTriggerItemButton(async (e) => {
if (e.button.tooltip === configureContinueOnPreference.tooltip) {
await this.commandService.executeCommand('workbench.action.openSettings', 'workbench.editSessions.continueOn');
}
});

quickpick.show();
});
}

private async createQuickpickItems(fromContinueOn: boolean): Promise<(ExistingSession | AuthenticationProviderOption | IQuickPickSeparator | IQuickPickItem & { canceledAuthentication: boolean })[]> {
private async createQuickpickItems(): Promise<(ExistingSession | AuthenticationProviderOption | IQuickPickSeparator | IQuickPickItem & { canceledAuthentication: boolean })[]> {
const options: (ExistingSession | AuthenticationProviderOption | IQuickPickSeparator | IQuickPickItem & { canceledAuthentication: boolean })[] = [];

options.push({ type: 'separator', label: localize('signed in', "Signed In") });
Expand All @@ -342,14 +331,6 @@ export class EditSessionsWorkbenchService extends Disposable implements IEditSes
}
}

if (fromContinueOn) {
return options.concat([{ type: 'separator' }, {
label: localize('continue without', 'Continue without my working changes'),
canceledAuthentication: true,
buttons: [configureContinueOnPreference]
}]);
}

return options;
}

Expand Down Expand Up @@ -382,7 +363,7 @@ export class EditSessionsWorkbenchService extends Disposable implements IEditSes
accounts.set(currentSession.session.account.id, currentSession);
}

return [...accounts.values()];
return [...accounts.values()].sort((a, b) => a.label.localeCompare(b.label));
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export interface IEditSessionsStorageService {
readonly onDidSignIn: Event<void>;
readonly onDidSignOut: Event<void>;

initialize(fromContinueOn: boolean, silent?: boolean): Promise<boolean>;
initialize(silent?: boolean): Promise<boolean>;
read(ref: string | undefined): Promise<{ ref: string; editSession: EditSession } | undefined>;
write(editSession: EditSession): Promise<string>;
delete(ref: string | null): Promise<void>;
Expand Down