mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-04 17:23:45 -05:00
Merge from vscode 718331d6f3ebd1b571530ab499edb266ddd493d5
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
|
||||
import { IUserDataSyncService, SyncStatus, SyncSource, CONTEXT_SYNC_STATE, IUserDataSyncStore, registerConfiguration, getUserDataSyncStore, ISyncConfiguration, IUserDataAuthTokenService, IUserDataAutoSyncService, USER_DATA_SYNC_SCHEME, toRemoteContentResource, getSyncSourceFromRemoteContentResource, UserDataSyncErrorCode } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { IUserDataSyncService, SyncStatus, SyncSource, CONTEXT_SYNC_STATE, IUserDataSyncStore, registerConfiguration, getUserDataSyncStore, ISyncConfiguration, IUserDataAuthTokenService, IUserDataAutoSyncService, USER_DATA_SYNC_SCHEME, toRemoteContentResource, getSyncSourceFromRemoteContentResource, UserDataSyncErrorCode, UserDataSyncError } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { localize } from 'vs/nls';
|
||||
import { Disposable, MutableDisposable, toDisposable, DisposableStore, dispose } from 'vs/base/common/lifecycle';
|
||||
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
|
||||
@@ -23,13 +23,12 @@ import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
|
||||
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
|
||||
import { isWeb } from 'vs/base/common/platform';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { UserDataAutoSync } from 'vs/workbench/contrib/userDataSync/browser/userDataAutoSync';
|
||||
import { UserDataSyncTrigger } from 'vs/workbench/contrib/userDataSync/browser/userDataSyncTrigger';
|
||||
import { timeout } from 'vs/base/common/async';
|
||||
import { IOutputService } from 'vs/workbench/contrib/output/common/output';
|
||||
import * as Constants from 'vs/workbench/contrib/logs/common/logConstants';
|
||||
import { IAuthenticationService } from 'vs/workbench/services/authentication/browser/authenticationService';
|
||||
import { Session } from 'vs/editor/common/modes';
|
||||
import { AuthenticationSession } from 'vs/editor/common/modes';
|
||||
import { isPromiseCanceledError, canceled } from 'vs/base/common/errors';
|
||||
import { toErrorMessage } from 'vs/base/common/errorMessage';
|
||||
import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput';
|
||||
@@ -49,7 +48,8 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
const enum AuthStatus {
|
||||
Initializing = 'Initializing',
|
||||
SignedIn = 'SignedIn',
|
||||
SignedOut = 'SignedOut'
|
||||
SignedOut = 'SignedOut',
|
||||
Unavailable = 'Unavailable'
|
||||
}
|
||||
const CONTEXT_AUTH_TOKEN_STATE = new RawContextKey<string>('authTokenStatus', AuthStatus.Initializing);
|
||||
|
||||
@@ -68,10 +68,6 @@ type FirstTimeSyncClassification = {
|
||||
action: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true };
|
||||
};
|
||||
|
||||
type SyncErrorClassification = {
|
||||
source: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true };
|
||||
};
|
||||
|
||||
export class UserDataSyncWorkbenchContribution extends Disposable implements IWorkbenchContribution {
|
||||
|
||||
private static readonly ENABLEMENT_SETTING = 'sync.enable';
|
||||
@@ -82,7 +78,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
private readonly badgeDisposable = this._register(new MutableDisposable());
|
||||
private readonly conflictsWarningDisposable = this._register(new MutableDisposable());
|
||||
private readonly signInNotificationDisposable = this._register(new MutableDisposable());
|
||||
private _activeAccount: Session | undefined;
|
||||
private _activeAccount: AuthenticationSession | undefined;
|
||||
|
||||
constructor(
|
||||
@IUserDataSyncService private readonly userDataSyncService: IUserDataSyncService,
|
||||
@@ -118,9 +114,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
this._register(userDataAutoSyncService.onError(({ code, source }) => this.onAutoSyncError(code, source)));
|
||||
this.registerActions();
|
||||
this.initializeActiveAccount().then(_ => {
|
||||
if (isWeb) {
|
||||
this._register(instantiationService.createInstance(UserDataAutoSync));
|
||||
} else {
|
||||
if (!isWeb) {
|
||||
this._register(instantiationService.createInstance(UserDataSyncTrigger).onDidTriggerSync(() => userDataAutoSyncService.triggerAutoSync()));
|
||||
}
|
||||
});
|
||||
@@ -131,44 +125,66 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
}
|
||||
|
||||
private async initializeActiveAccount(): Promise<void> {
|
||||
const accounts = await this.authenticationService.getSessions(this.userDataSyncStore!.authenticationProviderId);
|
||||
const sessions = await this.authenticationService.getSessions(this.userDataSyncStore!.authenticationProviderId);
|
||||
// Auth provider has not yet been registered
|
||||
if (!accounts) {
|
||||
if (!sessions) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (accounts.length === 0) {
|
||||
this.activeAccount = undefined;
|
||||
if (sessions.length === 0) {
|
||||
this.setActiveAccount(undefined);
|
||||
return;
|
||||
}
|
||||
|
||||
if (accounts.length === 1) {
|
||||
this.activeAccount = accounts[0];
|
||||
if (sessions.length === 1) {
|
||||
this.logAuthenticatedEvent(sessions[0]);
|
||||
this.setActiveAccount(sessions[0]);
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedAccount = await this.quickInputService.pick(accounts.map(account => {
|
||||
const selectedAccount = await this.quickInputService.pick(sessions.map(session => {
|
||||
return {
|
||||
id: account.id,
|
||||
label: account.displayName
|
||||
id: session.id,
|
||||
label: session.accountName
|
||||
};
|
||||
}), { canPickMany: false });
|
||||
|
||||
if (selectedAccount) {
|
||||
this.activeAccount = accounts.filter(account => selectedAccount.id === account.id)[0];
|
||||
const selected = sessions.filter(account => selectedAccount.id === account.id)[0];
|
||||
this.logAuthenticatedEvent(selected);
|
||||
this.setActiveAccount(selected);
|
||||
}
|
||||
}
|
||||
|
||||
get activeAccount(): Session | undefined {
|
||||
private logAuthenticatedEvent(session: AuthenticationSession): void {
|
||||
type UserAuthenticatedClassification = {
|
||||
id: { classification: 'EndUserPseudonymizedInformation', purpose: 'BusinessInsight' };
|
||||
};
|
||||
|
||||
type UserAuthenticatedEvent = {
|
||||
id: string;
|
||||
};
|
||||
|
||||
const id = session.id.split('/')[1];
|
||||
this.telemetryService.publicLog2<UserAuthenticatedEvent, UserAuthenticatedClassification>('user.authenticated', { id });
|
||||
}
|
||||
|
||||
get activeAccount(): AuthenticationSession | undefined {
|
||||
return this._activeAccount;
|
||||
}
|
||||
|
||||
set activeAccount(account: Session | undefined) {
|
||||
async setActiveAccount(account: AuthenticationSession | undefined) {
|
||||
this._activeAccount = account;
|
||||
|
||||
if (account) {
|
||||
this.userDataAuthTokenService.setToken(account.accessToken);
|
||||
this.authenticationState.set(AuthStatus.SignedIn);
|
||||
try {
|
||||
const token = await account.accessToken();
|
||||
this.userDataAuthTokenService.setToken(token);
|
||||
this.authenticationState.set(AuthStatus.SignedIn);
|
||||
} catch (e) {
|
||||
this.userDataAuthTokenService.setToken(undefined);
|
||||
this.authenticationState.set(AuthStatus.Unavailable);
|
||||
}
|
||||
} else {
|
||||
this.userDataAuthTokenService.setToken(undefined);
|
||||
this.authenticationState.set(AuthStatus.SignedOut);
|
||||
@@ -183,7 +199,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
// Try to update existing account, case where access token has been refreshed
|
||||
const accounts = (await this.authenticationService.getSessions(this.userDataSyncStore!.authenticationProviderId) || []);
|
||||
const matchingAccount = accounts.filter(a => a.id === this.activeAccount?.id)[0];
|
||||
this.activeAccount = matchingAccount;
|
||||
this.setActiveAccount(matchingAccount);
|
||||
} else {
|
||||
this.initializeActiveAccount();
|
||||
}
|
||||
@@ -198,7 +214,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
|
||||
private onDidUnregisterAuthenticationProvider(providerId: string) {
|
||||
if (providerId === this.userDataSyncStore!.authenticationProviderId) {
|
||||
this.activeAccount = undefined;
|
||||
this.setActiveAccount(undefined);
|
||||
this.authenticationState.reset();
|
||||
}
|
||||
}
|
||||
@@ -263,27 +279,14 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
|
||||
private onAutoSyncError(code: UserDataSyncErrorCode, source?: SyncSource): void {
|
||||
switch (code) {
|
||||
case UserDataSyncErrorCode.TooManyFailures:
|
||||
this.telemetryService.publicLog2('sync/errorTooMany');
|
||||
this.disableSync();
|
||||
this.notificationService.notify({
|
||||
severity: Severity.Error,
|
||||
message: localize('too many errors', "Turned off sync because of too many failure attempts. Please open Sync log to check the failures and turn on sync."),
|
||||
actions: {
|
||||
primary: [new Action('open sync log', localize('open log', "Show logs"), undefined, true, () => this.showSyncLog())]
|
||||
}
|
||||
});
|
||||
return;
|
||||
case UserDataSyncErrorCode.TooLarge:
|
||||
this.telemetryService.publicLog2<{ source: string }, SyncErrorClassification>('sync/errorTooLarge', { source: source! });
|
||||
if (source === SyncSource.Keybindings || source === SyncSource.Settings) {
|
||||
const sourceArea = getSyncAreaLabel(source);
|
||||
this.disableSync();
|
||||
this.notificationService.notify({
|
||||
severity: Severity.Error,
|
||||
message: localize('too large', "Turned off sync because size of the {0} file to sync is larger than {1}. Please open the file and reduce the size and turn on sync", sourceArea, '1MB'),
|
||||
message: localize('too large', "Disabled synchronizing {0} because size of the {1} file to sync is larger than {2}. Please open the file and reduce the size and enable sync", sourceArea, sourceArea, '100kb'),
|
||||
actions: {
|
||||
primary: [new Action('open sync log', localize('open file', "Show {0} file", sourceArea), undefined, true,
|
||||
primary: [new Action('open sync file', localize('open file', "Show {0} file", sourceArea), undefined, true,
|
||||
() => source === SyncSource.Settings ? this.preferencesService.openGlobalSettings(true) : this.preferencesService.openGlobalKeybindingSettings(true))]
|
||||
}
|
||||
});
|
||||
@@ -368,7 +371,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
}, {
|
||||
id: 'sync.enableUIState',
|
||||
label: getSyncAreaLabel(SyncSource.GlobalState),
|
||||
description: localize('ui state description', "Display Language (Only)")
|
||||
description: localize('ui state description', "only 'Display Language' for now")
|
||||
}];
|
||||
}
|
||||
|
||||
@@ -427,13 +430,12 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
],
|
||||
{
|
||||
cancelId: 1,
|
||||
detail: localize('first time sync detail', "Synchronizing from this device for the first time.\nWould you like to merge or replace with the data from cloud?"),
|
||||
detail: localize('first time sync detail', "Synchronizing from this device for the first time.\nWould you like to merge or replace with the data from the cloud?"),
|
||||
}
|
||||
);
|
||||
switch (result.choice) {
|
||||
case 0:
|
||||
this.telemetryService.publicLog2<{ action: string }, FirstTimeSyncClassification>('sync/firstTimeSync', { action: 'merge' });
|
||||
await this.userDataSyncService.sync();
|
||||
break;
|
||||
case 1:
|
||||
this.telemetryService.publicLog2<{ action: string }, FirstTimeSyncClassification>('sync/firstTimeSync', { action: 'cancelled' });
|
||||
@@ -464,17 +466,26 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
if (result.checkboxChecked) {
|
||||
this.telemetryService.publicLog2('sync/turnOffEveryWhere');
|
||||
await this.userDataSyncService.reset();
|
||||
} else {
|
||||
await this.userDataSyncService.resetLocal();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private disableSync(): Promise<void> {
|
||||
return this.configurationService.updateValue(UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING, undefined, ConfigurationTarget.USER);
|
||||
private disableSync(source?: SyncSource): Promise<void> {
|
||||
let key: string = UserDataSyncWorkbenchContribution.ENABLEMENT_SETTING;
|
||||
switch (source) {
|
||||
case SyncSource.Settings: key = 'sync.enableSettings'; break;
|
||||
case SyncSource.Keybindings: key = 'sync.enableKeybindings'; break;
|
||||
case SyncSource.Extensions: key = 'sync.enableExtensions'; break;
|
||||
case SyncSource.GlobalState: key = 'sync.enableUIState'; break;
|
||||
}
|
||||
return this.configurationService.updateValue(key, false, ConfigurationTarget.USER);
|
||||
}
|
||||
|
||||
private async signIn(): Promise<void> {
|
||||
try {
|
||||
this.activeAccount = await this.authenticationService.login(this.userDataSyncStore!.authenticationProviderId, ['https://management.core.windows.net/.default', 'offline_access']);
|
||||
this.setActiveAccount(await this.authenticationService.login(this.userDataSyncStore!.authenticationProviderId, ['https://management.core.windows.net/.default', 'offline_access']));
|
||||
} catch (e) {
|
||||
this.notificationService.error(e);
|
||||
throw e;
|
||||
@@ -484,7 +495,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
private async signOut(): Promise<void> {
|
||||
if (this.activeAccount) {
|
||||
await this.authenticationService.logout(this.userDataSyncStore!.authenticationProviderId, this.activeAccount.id);
|
||||
this.activeAccount = undefined;
|
||||
this.setActiveAccount(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -671,10 +682,10 @@ class UserDataRemoteContentProvider implements ITextModelContentProvider {
|
||||
provideTextContent(uri: URI): Promise<ITextModel> | null {
|
||||
let promise: Promise<string | null> | undefined;
|
||||
if (isEqual(uri, toRemoteContentResource(SyncSource.Settings))) {
|
||||
promise = this.userDataSyncService.getRemoteContent(SyncSource.Settings);
|
||||
promise = this.userDataSyncService.getRemoteContent(SyncSource.Settings, true);
|
||||
}
|
||||
if (isEqual(uri, toRemoteContentResource(SyncSource.Keybindings))) {
|
||||
promise = this.userDataSyncService.getRemoteContent(SyncSource.Keybindings);
|
||||
promise = this.userDataSyncService.getRemoteContent(SyncSource.Keybindings, true);
|
||||
}
|
||||
if (promise) {
|
||||
return promise.then(content => this.modelService.createModel(content || '', this.modeService.create('jsonc'), uri));
|
||||
@@ -759,35 +770,40 @@ class AcceptChangesContribution extends Disposable implements IEditorContributio
|
||||
|
||||
private createAcceptChangesWidgetRenderer(): void {
|
||||
if (!this.acceptChangesButton) {
|
||||
const replaceLabel = localize('accept remote', "Replace (Overwrite Local)");
|
||||
const applyLabel = localize('accept local', "Apply");
|
||||
this.acceptChangesButton = this.instantiationService.createInstance(FloatingClickWidget, this.editor, getSyncSourceFromRemoteContentResource(this.editor.getModel()!.uri) !== undefined ? replaceLabel : applyLabel, null);
|
||||
const acceptRemoteLabel = localize('accept remote', "Accept Remote");
|
||||
const acceptLocalLabel = localize('accept local', "Accept Local");
|
||||
this.acceptChangesButton = this.instantiationService.createInstance(FloatingClickWidget, this.editor, getSyncSourceFromRemoteContentResource(this.editor.getModel()!.uri) !== undefined ? acceptRemoteLabel : acceptLocalLabel, null);
|
||||
this._register(this.acceptChangesButton.onClick(async () => {
|
||||
const model = this.editor.getModel();
|
||||
if (model) {
|
||||
const conflictsSource = this.userDataSyncService.conflictsSource;
|
||||
const syncSource = getSyncSourceFromRemoteContentResource(model.uri);
|
||||
this.telemetryService.publicLog2<{ source: string, action: string }, SyncConflictsClassification>('sync/handleConflicts', { source: conflictsSource!, action: syncSource !== undefined ? 'replaceLocal' : 'apply' });
|
||||
if (syncSource !== undefined) {
|
||||
const syncAreaLabel = getSyncAreaLabel(syncSource);
|
||||
const result = await this.dialogService.confirm({
|
||||
type: 'info',
|
||||
title: localize('Sync overwrite local', "Sync: {0}", replaceLabel),
|
||||
message: localize('confirm replace and overwrite local', "Would you like to replace Local {0} with Remote {1}?", syncAreaLabel, syncAreaLabel),
|
||||
primaryButton: replaceLabel
|
||||
});
|
||||
if (!result.confirmed) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
try {
|
||||
await this.userDataSyncService.resolveConflictsAndContinueSync(model.getValue());
|
||||
} catch (e) {
|
||||
this.userDataSyncService.restart().then(() => {
|
||||
if (conflictsSource === this.userDataSyncService.conflictsSource) {
|
||||
this.notificationService.warn(localize('update conflicts', "Could not resolve conflicts as there is new local version available. Please try again."));
|
||||
this.telemetryService.publicLog2<{ source: string, action: string }, SyncConflictsClassification>('sync/handleConflicts', { source: conflictsSource!, action: syncSource !== undefined ? 'acceptRemote' : 'acceptLocal' });
|
||||
const syncAreaLabel = getSyncAreaLabel(conflictsSource!);
|
||||
const result = await this.dialogService.confirm({
|
||||
type: 'info',
|
||||
title: syncSource !== undefined
|
||||
? localize('Sync accept remote', "Sync: {0}", acceptRemoteLabel)
|
||||
: localize('Sync accept local', "Sync: {0}", acceptLocalLabel),
|
||||
message: syncSource !== undefined
|
||||
? localize('confirm replace and overwrite local', "Would you like to accept Remote {0} and replace Local {1}?", syncAreaLabel, syncAreaLabel)
|
||||
: localize('confirm replace and overwrite remote', "Would you like to accept Local {0} and replace Remote {1}?", syncAreaLabel, syncAreaLabel),
|
||||
primaryButton: syncSource !== undefined ? acceptRemoteLabel : acceptLocalLabel
|
||||
});
|
||||
if (result.confirmed) {
|
||||
try {
|
||||
await this.userDataSyncService.accept(conflictsSource!, model.getValue());
|
||||
} catch (e) {
|
||||
if (e instanceof UserDataSyncError && e.code === UserDataSyncErrorCode.NewLocal) {
|
||||
this.userDataSyncService.restart().then(() => {
|
||||
if (conflictsSource === this.userDataSyncService.conflictsSource) {
|
||||
this.notificationService.warn(localize('update conflicts', "Could not resolve conflicts as there is new local version available. Please try again."));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this.notificationService.error(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
Reference in New Issue
Block a user