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

New "Use a new account" option when switching preferences #230746

Merged
merged 1 commit into from
Oct 7, 2024
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
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
Loading