Skip to content

Commit

Permalink
New "Use a new account" option when switching preferences
Browse files Browse the repository at this point in the history
This option allows you to sign in to a new account and use if for an extension all in the same flow:

insert pic

Fixes #229496
  • Loading branch information
TylerLeonhardt committed Oct 7, 2024
1 parent 6f3888b commit 74cb594
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 13 deletions.
2 changes: 1 addition & 1 deletion src/vs/workbench/api/browser/mainThreadAuthentication.ts
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ export class MainThreadAuthentication extends Disposable implements MainThreadAu

if (session) {
this.sendProviderUsageTelemetry(extensionId, providerId);
this.authenticationUsageService.addAccountUsage(providerId, session.account.label, extensionId, extensionName);
this.authenticationUsageService.addAccountUsage(providerId, session.account.label, scopes, extensionId, extensionName);
}

return session;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ import { DisposableStore, IDisposable } from '../../../../../base/common/lifecyc
import { localize, localize2 } from '../../../../../nls.js';
import { Action2 } from '../../../../../platform/actions/common/actions.js';
import { IInstantiationService, ServicesAccessor } from '../../../../../platform/instantiation/common/instantiation.js';
import { ILogService } from '../../../../../platform/log/common/log.js';
import { IQuickInputService, IQuickPick, IQuickPickItem, QuickPickInput } from '../../../../../platform/quickinput/common/quickInput.js';
import { IAuthenticationUsageService } from '../../../../services/authentication/browser/authenticationUsageService.js';
import { IAccountUsage, IAuthenticationUsageService } from '../../../../services/authentication/browser/authenticationUsageService.js';
import { AuthenticationSessionAccount, IAuthenticationExtensionsService, IAuthenticationService } from '../../../../services/authentication/common/authentication.js';
import { IExtensionService } from '../../../../services/extensions/common/extensions.js';

Expand All @@ -28,8 +29,17 @@ export class ManageAccountPreferencesForExtensionAction extends Action2 {
}
}

interface AccountPreferenceQuickPickItem extends IQuickPickItem {
type AccountPreferenceQuickPickItem = NewAccountQuickPickItem | ExistingAccountQuickPickItem;

interface NewAccountQuickPickItem extends IQuickPickItem {
account?: undefined;
scopes: string[];
providerId: string;
}

interface ExistingAccountQuickPickItem extends IQuickPickItem {
account: AuthenticationSessionAccount;
scopes?: undefined;
providerId: string;
}

Expand All @@ -39,7 +49,8 @@ class ManageAccountPreferenceForExtensionActionImpl {
@IQuickInputService private readonly _quickInputService: IQuickInputService,
@IAuthenticationUsageService private readonly _authenticationUsageService: IAuthenticationUsageService,
@IAuthenticationExtensionsService private readonly _authenticationExtensionsService: IAuthenticationExtensionsService,
@IExtensionService private readonly _extensionService: IExtensionService
@IExtensionService private readonly _extensionService: IExtensionService,
@ILogService private readonly _logService: ILogService
) { }

async run(extensionId?: string, providerId?: string) {
Expand All @@ -52,7 +63,7 @@ class ManageAccountPreferenceForExtensionActionImpl {
}

const providerIds = new Array<string>();
const providerIdToAccounts = new Map<string, ReadonlyArray<AuthenticationSessionAccount>>();
const providerIdToAccounts = new Map<string, ReadonlyArray<AuthenticationSessionAccount & { lastUsed?: number }>>();
if (providerId) {
providerIds.push(providerId);
providerIdToAccounts.set(providerId, await this._authenticationService.getAccounts(providerId));
Expand Down Expand Up @@ -90,7 +101,27 @@ class ManageAccountPreferenceForExtensionActionImpl {
}

const currentAccountNamePreference = this._authenticationExtensionsService.getAccountPreference(extensionId, chosenProviderId);
const items: Array<QuickPickInput<AccountPreferenceQuickPickItem>> = this._getItems(providerIdToAccounts.get(chosenProviderId)!, chosenProviderId, currentAccountNamePreference);
const accounts = providerIdToAccounts.get(chosenProviderId)!;
const items: Array<QuickPickInput<AccountPreferenceQuickPickItem>> = this._getItems(accounts, chosenProviderId, currentAccountNamePreference);

// If the provider supports multiple accounts, add an option to use a new account
const provider = this._authenticationService.getProvider(chosenProviderId);
if (provider.supportsMultipleAccounts) {
// Get the last used scopes for the last used account. This will be used to pre-fill the scopes when adding a new account.
// If there's no scopes, then don't add this option.
const lastUsedScopes = accounts
.flatMap(account => this._authenticationUsageService.readAccountUsages(chosenProviderId!, account.label).find(u => u.extensionId === extensionId.toLowerCase()))
.filter((usage): usage is IAccountUsage => !!usage)
.sort((a, b) => b.lastUsed - a.lastUsed)?.[0]?.scopes;
if (lastUsedScopes) {
items.push({ type: 'separator' });
items.push({
providerId: chosenProviderId,
scopes: lastUsedScopes,
label: localize('use new account', "Use a new account..."),
});
}
}

const disposables = new DisposableStore();
const picker = this._createQuickPick(disposables, extensionId, extension.displayName ?? extension.name);
Expand All @@ -111,9 +142,9 @@ class ManageAccountPreferenceForExtensionActionImpl {
picker.placeholder = localize('placeholder', "Manage '{0}' account preferences...", extensionLabel);
picker.title = localize('title', "'{0}' Account Preferences For This Workspace", extensionLabel);
picker.sortByLabel = false;
disposableStore.add(picker.onDidAccept(() => {
this._accept(extensionId, picker.selectedItems);
disposableStore.add(picker.onDidAccept(async () => {
picker.hide();
await this._accept(extensionId, picker.selectedItems);
}));
return picker;
}
Expand Down Expand Up @@ -142,9 +173,20 @@ class ManageAccountPreferenceForExtensionActionImpl {
return Event.filter(picker.onDidTriggerButton, (e) => e === this._quickInputService.backButton)(() => this.run());
}

private _accept(extensionId: string, selectedItems: ReadonlyArray<AccountPreferenceQuickPickItem>) {
private async _accept(extensionId: string, selectedItems: ReadonlyArray<AccountPreferenceQuickPickItem>) {
for (const item of selectedItems) {
const account = item.account;
let account: AuthenticationSessionAccount;
if (!item.account) {
try {
const session = await this._authenticationService.createSession(item.providerId, item.scopes);
account = session.account;
} catch (e) {
this._logService.error(e);
continue;
}
} else {
account = item.account;
}
const providerId = item.providerId;
const currentAccountName = this._authenticationExtensionsService.getAccountPreference(extensionId, providerId);
if (currentAccountName === account.label) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -372,7 +372,7 @@ export class AuthenticationExtensionsService extends Disposable implements IAuth
}

if (session) {
this._authenticationUsageService.addAccountUsage(provider.id, session.account.label, extensionId, extensionName);
this._authenticationUsageService.addAccountUsage(provider.id, session.account.label, session.scopes, extensionId, extensionName);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export interface IAccountUsage {
extensionId: string;
extensionName: string;
lastUsed: number;
scopes?: string[];
}

export const IAuthenticationUsageService = createDecorator<IAuthenticationUsageService>('IAuthenticationUsageService');
Expand Down Expand Up @@ -49,7 +50,7 @@ export interface IAuthenticationUsageService {
* @param extensionId The id of the extension to add a usage for
* @param extensionName The name of the extension to add a usage for
*/
addAccountUsage(providerId: string, accountName: string, extensionId: string, extensionName: string): void;
addAccountUsage(providerId: string, accountName: string, scopes: ReadonlyArray<string>, extensionId: string, extensionName: string): void;
}

export class AuthenticationUsageService extends Disposable implements IAuthenticationUsageService {
Expand Down Expand Up @@ -116,7 +117,7 @@ export class AuthenticationUsageService extends Disposable implements IAuthentic
this._storageService.remove(accountKey, StorageScope.APPLICATION);
}

addAccountUsage(providerId: string, accountName: string, extensionId: string, extensionName: string): void {
addAccountUsage(providerId: string, accountName: string, scopes: string[], extensionId: string, extensionName: string): void {
const accountKey = `${providerId}-${accountName}-usages`;
const usages = this.readAccountUsages(providerId, accountName);

Expand All @@ -125,12 +126,14 @@ export class AuthenticationUsageService extends Disposable implements IAuthentic
usages.splice(existingUsageIndex, 1, {
extensionId,
extensionName,
scopes,
lastUsed: Date.now()
});
} else {
usages.push({
extensionId,
extensionName,
scopes,
lastUsed: Date.now()
});
}
Expand Down

0 comments on commit 74cb594

Please sign in to comment.