mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-17 02:51:36 -05:00
Port VS Code telemetry opt-in dialog (#1130)
This commit is contained in:
@@ -16,6 +16,8 @@
|
|||||||
"darwinBundleIdentifier": "com.sqlopsstudio.oss",
|
"darwinBundleIdentifier": "com.sqlopsstudio.oss",
|
||||||
"reportIssueUrl": "https://github.com/Microsoft/sqlopsstudio/issues/new?labels=customer%20reported%20issue",
|
"reportIssueUrl": "https://github.com/Microsoft/sqlopsstudio/issues/new?labels=customer%20reported%20issue",
|
||||||
"requestFeatureUrl": "https://github.com/Microsoft/sqlopsstudio/issues/new?labels=feature-request",
|
"requestFeatureUrl": "https://github.com/Microsoft/sqlopsstudio/issues/new?labels=feature-request",
|
||||||
|
"privacyStatementUrl": "https://privacy.microsoft.com/en-us/privacystatement",
|
||||||
|
"telemetryOptOutUrl": "https://github.com/Microsoft/sqlopsstudio/wiki/How-to-Disable-Telemetry-Reporting",
|
||||||
"urlProtocol": "sqlops",
|
"urlProtocol": "sqlops",
|
||||||
"enableTelemetry": true,
|
"enableTelemetry": true,
|
||||||
"aiConfig": {
|
"aiConfig": {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import { SyncActionDescriptor } from 'vs/platform/actions/common/actions';
|
|||||||
import { Registry } from 'vs/platform/registry/common/platform';
|
import { Registry } from 'vs/platform/registry/common/platform';
|
||||||
import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions';
|
import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions';
|
||||||
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions';
|
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions';
|
||||||
import { ShowCurrentReleaseNotesAction, ProductContribution } from 'sql/workbench/update/releaseNotes';
|
import { ShowCurrentReleaseNotesAction } from 'sql/workbench/update/releaseNotes';
|
||||||
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
|
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
|
||||||
|
|
||||||
new Actions.BackupAction().registerTask();
|
new Actions.BackupAction().registerTask();
|
||||||
@@ -21,8 +21,5 @@ new Actions.NewQueryAction().registerTask();
|
|||||||
new Actions.ConfigureDashboardAction().registerTask();
|
new Actions.ConfigureDashboardAction().registerTask();
|
||||||
|
|
||||||
// add product update and release notes contributions
|
// add product update and release notes contributions
|
||||||
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench)
|
|
||||||
.registerWorkbenchContribution(ProductContribution, LifecyclePhase.Running);
|
|
||||||
|
|
||||||
Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions)
|
Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions)
|
||||||
.registerWorkbenchAction(new SyncActionDescriptor(ShowCurrentReleaseNotesAction, ShowCurrentReleaseNotesAction.ID, ShowCurrentReleaseNotesAction.LABEL), 'Show Getting Started');
|
.registerWorkbenchAction(new SyncActionDescriptor(ShowCurrentReleaseNotesAction, ShowCurrentReleaseNotesAction.ID, ShowCurrentReleaseNotesAction.LABEL), 'Show Getting Started');
|
||||||
|
|||||||
@@ -49,39 +49,3 @@ export class ShowCurrentReleaseNotesAction extends AbstractShowReleaseNotesActio
|
|||||||
super(id, label, pkg.version, editorService, instantiationService);
|
super(id, label, pkg.version, editorService, instantiationService);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ProductContribution implements IWorkbenchContribution {
|
|
||||||
|
|
||||||
private static KEY = 'releaseNotes/carbonLastVersion';
|
|
||||||
getId() { return 'carbon.product'; }
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
@IStorageService storageService: IStorageService,
|
|
||||||
@IInstantiationService instantiationService: IInstantiationService,
|
|
||||||
@INotificationService notificationService: INotificationService,
|
|
||||||
@IWorkbenchEditorService editorService: IWorkbenchEditorService
|
|
||||||
) {
|
|
||||||
const lastVersion = storageService.get(ProductContribution.KEY, StorageScope.GLOBAL, '');
|
|
||||||
|
|
||||||
// was there an update? if so, open release notes
|
|
||||||
if (product.releaseNotesUrl && pkg.version !== lastVersion) {
|
|
||||||
instantiationService.invokeFunction(loadReleaseNotes, pkg.version).then(
|
|
||||||
text => editorService.openEditor(instantiationService.createInstance(ReleaseNotesInput, pkg.version, text), { pinned: true }),
|
|
||||||
() => {
|
|
||||||
const actions: INotificationActions = {
|
|
||||||
primary: [
|
|
||||||
instantiationService.createInstance(OpenGettingStartedInBrowserAction)
|
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|
||||||
notificationService.notify({
|
|
||||||
severity: Severity.Info,
|
|
||||||
message: nls.localize('read the release notes', "Welcome to {0} April Public Preview! Would you like to view the Getting Started Guide?", product.nameLong, pkg.version),
|
|
||||||
actions
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
storageService.store(ProductContribution.KEY, pkg.version, StorageScope.GLOBAL);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKe
|
|||||||
import { OS } from 'vs/base/common/platform';
|
import { OS } from 'vs/base/common/platform';
|
||||||
import { IRange } from 'vs/editor/common/core/range';
|
import { IRange } from 'vs/editor/common/core/range';
|
||||||
import { ITextModel } from 'vs/editor/common/model';
|
import { ITextModel } from 'vs/editor/common/model';
|
||||||
import { INotificationService, INotification, INotificationHandle, NoOpNotification } from 'vs/platform/notification/common/notification';
|
import { INotificationService, INotification, INotificationHandle, NoOpNotification, IPromptChoice } from 'vs/platform/notification/common/notification';
|
||||||
import { IConfirmation, IConfirmationResult, IConfirmationService } from 'vs/platform/dialogs/common/dialogs';
|
import { IConfirmation, IConfirmationResult, IConfirmationService } from 'vs/platform/dialogs/common/dialogs';
|
||||||
import { IPosition, Position as Pos } from 'vs/editor/common/core/position';
|
import { IPosition, Position as Pos } from 'vs/editor/common/core/position';
|
||||||
|
|
||||||
@@ -292,6 +292,10 @@ export class SimpleNotificationService implements INotificationService {
|
|||||||
|
|
||||||
return SimpleNotificationService.NO_OP;
|
return SimpleNotificationService.NO_OP;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public prompt(severity: Severity, message: string, choices: IPromptChoice[], onCancel?: () => void): INotificationHandle {
|
||||||
|
return SimpleNotificationService.NO_OP;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class StandaloneCommandService implements ICommandService {
|
export class StandaloneCommandService implements ICommandService {
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import { ResolvedKeybindingItem } from 'vs/platform/keybinding/common/resolvedKe
|
|||||||
import { OS } from 'vs/base/common/platform';
|
import { OS } from 'vs/base/common/platform';
|
||||||
import { IKeyboardEvent } from 'vs/platform/keybinding/common/keybinding';
|
import { IKeyboardEvent } from 'vs/platform/keybinding/common/keybinding';
|
||||||
import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils';
|
import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils';
|
||||||
import { INotificationService, NoOpNotification, INotification } from 'vs/platform/notification/common/notification';
|
import { INotificationService, NoOpNotification, INotification, IPromptChoice } from 'vs/platform/notification/common/notification';
|
||||||
|
|
||||||
function createContext(ctx: any) {
|
function createContext(ctx: any) {
|
||||||
return {
|
return {
|
||||||
@@ -138,6 +138,9 @@ suite('AbstractKeybindingService', () => {
|
|||||||
error: (message: any) => {
|
error: (message: any) => {
|
||||||
showMessageCalls.push({ sev: Severity.Error, message });
|
showMessageCalls.push({ sev: Severity.Error, message });
|
||||||
return new NoOpNotification();
|
return new NoOpNotification();
|
||||||
|
},
|
||||||
|
prompt(severity: Severity, message: string, choices: IPromptChoice[], onCancel?: () => void): any {
|
||||||
|
throw new Error('not implemented');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ export interface IProductConfiguration {
|
|||||||
reportIssueUrl: string;
|
reportIssueUrl: string;
|
||||||
licenseUrl: string;
|
licenseUrl: string;
|
||||||
privacyStatementUrl: string;
|
privacyStatementUrl: string;
|
||||||
|
telemetryOptOutUrl: string;
|
||||||
npsSurveyUrl: string;
|
npsSurveyUrl: string;
|
||||||
surveys: ISurveyData[];
|
surveys: ISurveyData[];
|
||||||
checksums: { [path: string]: string; };
|
checksums: { [path: string]: string; };
|
||||||
|
|||||||
@@ -89,12 +89,12 @@ export interface INotificationProgress {
|
|||||||
done(): void;
|
done(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface INotificationHandle extends IDisposable {
|
export interface INotificationHandle {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Will be fired once the notification is disposed.
|
* Will be fired once the notification is closed.
|
||||||
*/
|
*/
|
||||||
readonly onDidDispose: Event<void>;
|
readonly onDidClose: Event<void>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allows to indicate progress on the notification even after the
|
* Allows to indicate progress on the notification even after the
|
||||||
@@ -118,6 +118,36 @@ export interface INotificationHandle extends IDisposable {
|
|||||||
* notification is already visible.
|
* notification is already visible.
|
||||||
*/
|
*/
|
||||||
updateActions(actions?: INotificationActions): void;
|
updateActions(actions?: INotificationActions): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hide the notification and remove it from the notification center.
|
||||||
|
*/
|
||||||
|
close(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IPromptChoice {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Label to show for the choice to the user.
|
||||||
|
*/
|
||||||
|
label: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Primary choices show up as buttons in the notification below the message.
|
||||||
|
* Secondary choices show up under the gear icon in the header of the notification.
|
||||||
|
*/
|
||||||
|
isSecondary?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wether to keep the notification open after the choice was selected
|
||||||
|
* by the user. By default, will close the notification upon click.
|
||||||
|
*/
|
||||||
|
keepOpen?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Triggered when the user selects the choice.
|
||||||
|
*/
|
||||||
|
run: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface INotificationService {
|
export interface INotificationService {
|
||||||
@@ -151,23 +181,34 @@ export interface INotificationService {
|
|||||||
* method if you need more control over the notification.
|
* method if you need more control over the notification.
|
||||||
*/
|
*/
|
||||||
error(message: NotificationMessage | NotificationMessage[]): void;
|
error(message: NotificationMessage | NotificationMessage[]): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows a prompt in the notification area with the provided choices. The prompt
|
||||||
|
* is non-modal. If you want to show a modal dialog instead, use `IDialogService`.
|
||||||
|
*
|
||||||
|
* @param onCancel will be called if the user closed the notification without picking
|
||||||
|
* any of the provided choices.
|
||||||
|
*
|
||||||
|
* @returns a handle on the notification to e.g. hide it or update message, buttons, etc.
|
||||||
|
*/
|
||||||
|
prompt(severity: Severity, message: string, choices: IPromptChoice[], onCancel?: () => void): INotificationHandle;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class NoOpNotification implements INotificationHandle {
|
export class NoOpNotification implements INotificationHandle {
|
||||||
readonly progress = new NoOpProgress();
|
readonly progress = new NoOpProgress();
|
||||||
|
|
||||||
private _onDidDispose: Emitter<void> = new Emitter();
|
private readonly _onDidClose: Emitter<void> = new Emitter();
|
||||||
|
|
||||||
public get onDidDispose(): Event<void> {
|
public get onDidClose(): Event<void> {
|
||||||
return this._onDidDispose.event;
|
return this._onDidClose.event;
|
||||||
}
|
}
|
||||||
|
|
||||||
updateSeverity(severity: Severity): void { }
|
updateSeverity(severity: Severity): void { }
|
||||||
updateMessage(message: NotificationMessage): void { }
|
updateMessage(message: NotificationMessage): void { }
|
||||||
updateActions(actions?: INotificationActions): void { }
|
updateActions(actions?: INotificationActions): void { }
|
||||||
|
|
||||||
dispose(): void {
|
close(): void {
|
||||||
this._onDidDispose.dispose();
|
this._onDidClose.dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ export class MainThreadMessageService implements MainThreadMessageServiceShape {
|
|||||||
|
|
||||||
// if promise has not been resolved yet, now is the time to ensure a return value
|
// if promise has not been resolved yet, now is the time to ensure a return value
|
||||||
// otherwise if already resolved it means the user clicked one of the buttons
|
// otherwise if already resolved it means the user clicked one of the buttons
|
||||||
once(messageHandle.onDidDispose)(() => {
|
once(messageHandle.onDidClose)(() => {
|
||||||
resolve(undefined);
|
resolve(undefined);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -282,7 +282,7 @@ export class NotificationsCenter extends Themable {
|
|||||||
|
|
||||||
// Dispose all
|
// Dispose all
|
||||||
while (this.model.notifications.length) {
|
while (this.model.notifications.length) {
|
||||||
this.model.notifications[0].dispose();
|
this.model.notifications[0].close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -115,7 +115,7 @@ export function registerNotificationCommands(center: INotificationsCenterControl
|
|||||||
handler: (accessor, args?: any) => {
|
handler: (accessor, args?: any) => {
|
||||||
const notification = getNotificationFromContext(accessor.get(IListService), args);
|
const notification = getNotificationFromContext(accessor.get(IListService), args);
|
||||||
if (notification) {
|
if (notification) {
|
||||||
notification.dispose();
|
notification.close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -164,7 +164,7 @@ export class NotificationsToasts extends Themable {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
// Remove when item gets disposed
|
// Remove when item gets disposed
|
||||||
once(item.onDidDispose)(() => {
|
once(item.onDidClose)(() => {
|
||||||
this.removeToast(item);
|
this.removeToast(item);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -440,7 +440,7 @@ export class NotificationTemplateRenderer {
|
|||||||
this.actionRunner.run(action, notification);
|
this.actionRunner.run(action, notification);
|
||||||
|
|
||||||
// Hide notification
|
// Hide notification
|
||||||
notification.dispose();
|
notification.close();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this.inputDisposeables.push(attachButtonStyler(button, this.themeService));
|
this.inputDisposeables.push(attachButtonStyler(button, this.themeService));
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
import { INotification, INotificationHandle, INotificationActions, INotificationProgress, NoOpNotification, Severity, NotificationMessage } from 'vs/platform/notification/common/notification';
|
import { INotification, INotificationHandle, INotificationActions, INotificationProgress, NoOpNotification, Severity, NotificationMessage } from 'vs/platform/notification/common/notification';
|
||||||
import { toErrorMessage } from 'vs/base/common/errorMessage';
|
import { toErrorMessage } from 'vs/base/common/errorMessage';
|
||||||
import Event, { Emitter, once } from 'vs/base/common/event';
|
import Event, {Emitter, once } from 'vs/base/common/event';
|
||||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||||
import { isPromiseCanceledError, isErrorWithActions } from 'vs/base/common/errors';
|
import { isPromiseCanceledError, isErrorWithActions } from 'vs/base/common/errors';
|
||||||
|
|
||||||
@@ -44,21 +44,21 @@ export interface INotificationChangeEvent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class NotificationHandle implements INotificationHandle {
|
export class NotificationHandle implements INotificationHandle {
|
||||||
private _onDidDispose: Emitter<void> = new Emitter();
|
private readonly _onDidClose: Emitter<void> = new Emitter();
|
||||||
|
|
||||||
constructor(private item: INotificationViewItem, private disposeItem: (item: INotificationViewItem) => void) {
|
constructor(private item: INotificationViewItem, private closeItem: (item: INotificationViewItem) => void) {
|
||||||
this.registerListeners();
|
this.registerListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
private registerListeners(): void {
|
private registerListeners(): void {
|
||||||
once(this.item.onDidDispose)(() => {
|
once(this.item.onDidClose)(() => {
|
||||||
this._onDidDispose.fire();
|
this._onDidClose.fire();
|
||||||
this._onDidDispose.dispose();
|
this._onDidClose.dispose();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public get onDidDispose(): Event<void> {
|
public get onDidClose(): Event<void> {
|
||||||
return this._onDidDispose.event;
|
return this._onDidClose.event;
|
||||||
}
|
}
|
||||||
|
|
||||||
public get progress(): INotificationProgress {
|
public get progress(): INotificationProgress {
|
||||||
@@ -77,9 +77,9 @@ export class NotificationHandle implements INotificationHandle {
|
|||||||
this.item.updateActions(actions);
|
this.item.updateActions(actions);
|
||||||
}
|
}
|
||||||
|
|
||||||
public dispose(): void {
|
public close(): void {
|
||||||
this.disposeItem(this.item);
|
this.closeItem(this.item);
|
||||||
this._onDidDispose.dispose();
|
this._onDidClose.dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,7 +89,7 @@ export class NotificationsModel implements INotificationsModel {
|
|||||||
|
|
||||||
private _notifications: INotificationViewItem[];
|
private _notifications: INotificationViewItem[];
|
||||||
|
|
||||||
private _onDidNotificationChange: Emitter<INotificationChangeEvent>;
|
private readonly _onDidNotificationChange: Emitter<INotificationChangeEvent>;
|
||||||
private toDispose: IDisposable[];
|
private toDispose: IDisposable[];
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -117,7 +117,7 @@ export class NotificationsModel implements INotificationsModel {
|
|||||||
// Deduplicate
|
// Deduplicate
|
||||||
const duplicate = this.findNotification(item);
|
const duplicate = this.findNotification(item);
|
||||||
if (duplicate) {
|
if (duplicate) {
|
||||||
duplicate.dispose();
|
duplicate.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add to list as first entry
|
// Add to list as first entry
|
||||||
@@ -127,15 +127,15 @@ export class NotificationsModel implements INotificationsModel {
|
|||||||
this._onDidNotificationChange.fire({ item, index: 0, kind: NotificationChangeType.ADD });
|
this._onDidNotificationChange.fire({ item, index: 0, kind: NotificationChangeType.ADD });
|
||||||
|
|
||||||
// Wrap into handle
|
// Wrap into handle
|
||||||
return new NotificationHandle(item, item => this.disposeItem(item));
|
return new NotificationHandle(item, item => this.closeItem(item));
|
||||||
}
|
}
|
||||||
|
|
||||||
private disposeItem(item: INotificationViewItem): void {
|
private closeItem(item: INotificationViewItem): void {
|
||||||
const liveItem = this.findNotification(item);
|
const liveItem = this.findNotification(item);
|
||||||
if (liveItem && liveItem !== item) {
|
if (liveItem && liveItem !== item) {
|
||||||
liveItem.dispose(); // item could have been replaced with another one, make sure to dispose the live item
|
liveItem.close(); // item could have been replaced with another one, make sure to close the live item
|
||||||
} else {
|
} else {
|
||||||
item.dispose(); // otherwise just dispose the item that was passed in
|
item.close(); // otherwise just close the item that was passed in
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -174,7 +174,7 @@ export class NotificationsModel implements INotificationsModel {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
once(item.onDidDispose)(() => {
|
once(item.onDidClose)(() => {
|
||||||
itemExpansionChangeListener.dispose();
|
itemExpansionChangeListener.dispose();
|
||||||
itemLabelChangeListener.dispose();
|
itemLabelChangeListener.dispose();
|
||||||
|
|
||||||
@@ -204,7 +204,7 @@ export interface INotificationViewItem {
|
|||||||
readonly canCollapse: boolean;
|
readonly canCollapse: boolean;
|
||||||
|
|
||||||
readonly onDidExpansionChange: Event<void>;
|
readonly onDidExpansionChange: Event<void>;
|
||||||
readonly onDidDispose: Event<void>;
|
readonly onDidClose: Event<void>;
|
||||||
readonly onDidLabelChange: Event<INotificationViewItemLabelChangeEvent>;
|
readonly onDidLabelChange: Event<INotificationViewItemLabelChangeEvent>;
|
||||||
|
|
||||||
expand(): void;
|
expand(): void;
|
||||||
@@ -217,7 +217,7 @@ export interface INotificationViewItem {
|
|||||||
updateMessage(message: NotificationMessage): void;
|
updateMessage(message: NotificationMessage): void;
|
||||||
updateActions(actions?: INotificationActions): void;
|
updateActions(actions?: INotificationActions): void;
|
||||||
|
|
||||||
dispose(): void;
|
close(): void;
|
||||||
|
|
||||||
equals(item: INotificationViewItem);
|
equals(item: INotificationViewItem);
|
||||||
}
|
}
|
||||||
@@ -253,7 +253,7 @@ export interface INotificationViewItemProgress extends INotificationProgress {
|
|||||||
export class NotificationViewItemProgress implements INotificationViewItemProgress {
|
export class NotificationViewItemProgress implements INotificationViewItemProgress {
|
||||||
private _state: INotificationViewItemProgressState;
|
private _state: INotificationViewItemProgressState;
|
||||||
|
|
||||||
private _onDidChange: Emitter<void>;
|
private readonly _onDidChange: Emitter<void>;
|
||||||
private toDispose: IDisposable[];
|
private toDispose: IDisposable[];
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
@@ -358,9 +358,9 @@ export class NotificationViewItem implements INotificationViewItem {
|
|||||||
private _actions: INotificationActions;
|
private _actions: INotificationActions;
|
||||||
private _progress: NotificationViewItemProgress;
|
private _progress: NotificationViewItemProgress;
|
||||||
|
|
||||||
private _onDidExpansionChange: Emitter<void>;
|
private readonly _onDidExpansionChange: Emitter<void>;
|
||||||
private _onDidDispose: Emitter<void>;
|
private readonly _onDidClose: Emitter<void>;
|
||||||
private _onDidLabelChange: Emitter<INotificationViewItemLabelChangeEvent>;
|
private readonly _onDidLabelChange: Emitter<INotificationViewItemLabelChangeEvent>;
|
||||||
|
|
||||||
public static create(notification: INotification): INotificationViewItem {
|
public static create(notification: INotification): INotificationViewItem {
|
||||||
if (!notification || !notification.message || isPromiseCanceledError(notification.message)) {
|
if (!notification || !notification.message || isPromiseCanceledError(notification.message)) {
|
||||||
@@ -435,8 +435,8 @@ export class NotificationViewItem implements INotificationViewItem {
|
|||||||
this._onDidLabelChange = new Emitter<INotificationViewItemLabelChangeEvent>();
|
this._onDidLabelChange = new Emitter<INotificationViewItemLabelChangeEvent>();
|
||||||
this.toDispose.push(this._onDidLabelChange);
|
this.toDispose.push(this._onDidLabelChange);
|
||||||
|
|
||||||
this._onDidDispose = new Emitter<void>();
|
this._onDidClose = new Emitter<void>();
|
||||||
this.toDispose.push(this._onDidDispose);
|
this.toDispose.push(this._onDidClose);
|
||||||
}
|
}
|
||||||
|
|
||||||
private setActions(actions: INotificationActions): void {
|
private setActions(actions: INotificationActions): void {
|
||||||
@@ -454,9 +454,6 @@ export class NotificationViewItem implements INotificationViewItem {
|
|||||||
|
|
||||||
this._actions = actions;
|
this._actions = actions;
|
||||||
this._expanded = actions.primary.length > 0;
|
this._expanded = actions.primary.length > 0;
|
||||||
|
|
||||||
this.toDispose.push(...actions.primary);
|
|
||||||
this.toDispose.push(...actions.secondary);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public get onDidExpansionChange(): Event<void> {
|
public get onDidExpansionChange(): Event<void> {
|
||||||
@@ -467,8 +464,8 @@ export class NotificationViewItem implements INotificationViewItem {
|
|||||||
return this._onDidLabelChange.event;
|
return this._onDidLabelChange.event;
|
||||||
}
|
}
|
||||||
|
|
||||||
public get onDidDispose(): Event<void> {
|
public get onDidClose(): Event<void> {
|
||||||
return this._onDidDispose.event;
|
return this._onDidClose.event;
|
||||||
}
|
}
|
||||||
|
|
||||||
public get canCollapse(): boolean {
|
public get canCollapse(): boolean {
|
||||||
@@ -559,13 +556,17 @@ export class NotificationViewItem implements INotificationViewItem {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public dispose(): void {
|
public close(): void {
|
||||||
this._onDidDispose.fire();
|
this._onDidClose.fire();
|
||||||
|
|
||||||
this.toDispose = dispose(this.toDispose);
|
this.toDispose = dispose(this.toDispose);
|
||||||
}
|
}
|
||||||
|
|
||||||
public equals(other: INotificationViewItem): boolean {
|
public equals(other: INotificationViewItem): boolean {
|
||||||
|
if (this.hasProgress() || other.hasProgress()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (this._source !== other.source) {
|
if (this._source !== other.source) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -581,7 +582,7 @@ export class NotificationViewItem implements INotificationViewItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (let i = 0; i < primaryActions.length; i++) {
|
for (let i = 0; i < primaryActions.length; i++) {
|
||||||
if (primaryActions[i].id !== otherPrimaryActions[i].id) {
|
if ((primaryActions[i].id + primaryActions[i].label) !== (otherPrimaryActions[i].id + otherPrimaryActions[i].label)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ export class SaveErrorHandler implements ISaveErrorHandler, IWorkbenchContributi
|
|||||||
private onFileSavedOrReverted(resource: URI): void {
|
private onFileSavedOrReverted(resource: URI): void {
|
||||||
const messageHandle = this.messages.get(resource);
|
const messageHandle = this.messages.get(resource);
|
||||||
if (messageHandle) {
|
if (messageHandle) {
|
||||||
messageHandle.dispose();
|
messageHandle.close();
|
||||||
this.messages.delete(resource);
|
this.messages.delete(resource);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -190,7 +190,7 @@ export class SaveErrorHandler implements ISaveErrorHandler, IWorkbenchContributi
|
|||||||
const pendingResolveSaveConflictMessages: INotificationHandle[] = [];
|
const pendingResolveSaveConflictMessages: INotificationHandle[] = [];
|
||||||
function clearPendingResolveSaveConflictMessages(): void {
|
function clearPendingResolveSaveConflictMessages(): void {
|
||||||
while (pendingResolveSaveConflictMessages.length > 0) {
|
while (pendingResolveSaveConflictMessages.length > 0) {
|
||||||
pendingResolveSaveConflictMessages.pop().dispose();
|
pendingResolveSaveConflictMessages.pop().close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,9 +6,15 @@
|
|||||||
|
|
||||||
import { Registry } from 'vs/platform/registry/common/platform';
|
import { Registry } from 'vs/platform/registry/common/platform';
|
||||||
import { GettingStarted } from './gettingStarted';
|
import { GettingStarted } from './gettingStarted';
|
||||||
|
import { TelemetryOptOut } from './telemetryOptOut';
|
||||||
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions';
|
import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions';
|
||||||
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
|
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
|
||||||
|
|
||||||
|
// {{SQL CARBON EDIT}}
|
||||||
|
// Registry
|
||||||
|
// .as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench)
|
||||||
|
// .registerWorkbenchContribution(GettingStarted, LifecyclePhase.Running);
|
||||||
|
|
||||||
Registry
|
Registry
|
||||||
.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench)
|
.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench)
|
||||||
.registerWorkbenchContribution(GettingStarted, LifecyclePhase.Running);
|
.registerWorkbenchContribution(TelemetryOptOut, LifecyclePhase.Eventually);
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
|
||||||
|
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||||
|
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||||
|
import product from 'vs/platform/node/product';
|
||||||
|
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||||
|
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
|
||||||
|
import URI from 'vs/base/common/uri';
|
||||||
|
import { localize } from 'vs/nls';
|
||||||
|
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||||
|
import { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows';
|
||||||
|
|
||||||
|
export class TelemetryOptOut implements IWorkbenchContribution {
|
||||||
|
|
||||||
|
private static TELEMETRY_OPT_OUT_SHOWN = 'workbench.telemetryOptOutShown';
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@IStorageService storageService: IStorageService,
|
||||||
|
@IOpenerService openerService: IOpenerService,
|
||||||
|
@INotificationService notificationService: INotificationService,
|
||||||
|
@IWindowService windowService: IWindowService,
|
||||||
|
@IWindowsService windowsService: IWindowsService,
|
||||||
|
@ITelemetryService telemetryService: ITelemetryService
|
||||||
|
) {
|
||||||
|
if (!product.telemetryOptOutUrl || storageService.get(TelemetryOptOut.TELEMETRY_OPT_OUT_SHOWN)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Promise.all([
|
||||||
|
windowService.isFocused(),
|
||||||
|
windowsService.getWindowCount()
|
||||||
|
]).then(([focused, count]) => {
|
||||||
|
if (!focused && count > 1) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
storageService.store(TelemetryOptOut.TELEMETRY_OPT_OUT_SHOWN, true);
|
||||||
|
|
||||||
|
const optOutUrl = product.telemetryOptOutUrl;
|
||||||
|
const privacyUrl = product.privacyStatementUrl || product.telemetryOptOutUrl;
|
||||||
|
// {{SQL CARBON EDIT}}
|
||||||
|
const optOutNotice = localize('telemetryOptOut.optOutNotice', "Help improve SQL Operations Studio by allowing Microsoft to collect usage data. Read our [privacy statement]({0}) and how to [opt out]({1}).", privacyUrl, optOutUrl);
|
||||||
|
const optInNotice = localize('telemetryOptOut.optInNotice', "Help improve SQL Operations Studio by allowing Microsoft to collect usage data. Read our [privacy statement]({0}) and how to [opt in]({1}).", privacyUrl, optOutUrl);
|
||||||
|
|
||||||
|
notificationService.prompt(
|
||||||
|
Severity.Info,
|
||||||
|
telemetryService.isOptedIn ? optOutNotice : optInNotice,
|
||||||
|
[{
|
||||||
|
label: localize('telemetryOptOut.readMore', "Read More"),
|
||||||
|
run: () => openerService.open(URI.parse(optOutUrl))
|
||||||
|
}]
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.then(null, onUnexpectedError);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -134,7 +134,7 @@ export class DialogService implements IChoiceService, IConfirmationService {
|
|||||||
c(index);
|
c(index);
|
||||||
|
|
||||||
if (closeNotification) {
|
if (closeNotification) {
|
||||||
handle.dispose();
|
handle.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
return TPromise.as(void 0);
|
return TPromise.as(void 0);
|
||||||
@@ -171,9 +171,9 @@ export class DialogService implements IChoiceService, IConfirmationService {
|
|||||||
handle = this.notificationService.notify({ severity, message, actions });
|
handle = this.notificationService.notify({ severity, message, actions });
|
||||||
|
|
||||||
// Cancel promise when notification gets disposed
|
// Cancel promise when notification gets disposed
|
||||||
once(handle.onDidDispose)(() => promise.cancel());
|
once(handle.onDidClose)(() => promise.cancel());
|
||||||
|
|
||||||
}, () => handle.dispose());
|
}, () => handle.close());
|
||||||
|
|
||||||
return promise;
|
return promise;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,11 +3,12 @@
|
|||||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
'use strict';
|
import { INotificationService, INotification, INotificationHandle, Severity, NotificationMessage, INotificationActions, IPromptChoice } from 'vs/platform/notification/common/notification';
|
||||||
|
|
||||||
import { INotificationService, INotification, INotificationHandle, Severity, NotificationMessage } from 'vs/platform/notification/common/notification';
|
|
||||||
import { INotificationsModel, NotificationsModel } from 'vs/workbench/common/notifications';
|
import { INotificationsModel, NotificationsModel } from 'vs/workbench/common/notifications';
|
||||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||||
|
import { TPromise } from 'vs/base/common/winjs.base';
|
||||||
|
import { Action } from 'vs/base/common/actions';
|
||||||
|
import { once } from 'vs/base/common/event';
|
||||||
|
|
||||||
export class NotificationService implements INotificationService {
|
export class NotificationService implements INotificationService {
|
||||||
|
|
||||||
@@ -62,6 +63,51 @@ export class NotificationService implements INotificationService {
|
|||||||
return this.model.notify(notification);
|
return this.model.notify(notification);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public prompt(severity: Severity, message: string, choices: IPromptChoice[], onCancel?: () => void): INotificationHandle {
|
||||||
|
let handle: INotificationHandle;
|
||||||
|
let choiceClicked = false;
|
||||||
|
|
||||||
|
// Convert choices into primary/secondary actions
|
||||||
|
const actions: INotificationActions = { primary: [], secondary: [] };
|
||||||
|
choices.forEach((choice, index) => {
|
||||||
|
const action = new Action(`workbench.dialog.choice.${index}`, choice.label, null, true, () => {
|
||||||
|
choiceClicked = true;
|
||||||
|
|
||||||
|
// Pass to runner
|
||||||
|
choice.run();
|
||||||
|
|
||||||
|
// Close notification unless we are told to keep open
|
||||||
|
if (!choice.keepOpen) {
|
||||||
|
handle.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
return TPromise.as(void 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!choice.isSecondary) {
|
||||||
|
actions.primary.push(action);
|
||||||
|
} else {
|
||||||
|
actions.secondary.push(action);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Show notification with actions
|
||||||
|
handle = this.notify({ severity, message, actions });
|
||||||
|
|
||||||
|
once(handle.onDidClose)(() => {
|
||||||
|
|
||||||
|
// Cleanup when notification gets disposed
|
||||||
|
dispose(...actions.primary, ...actions.secondary);
|
||||||
|
|
||||||
|
// Indicate cancellation to the outside if no action was executed
|
||||||
|
if (!choiceClicked && typeof onCancel === 'function') {
|
||||||
|
onCancel();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
|
||||||
public dispose(): void {
|
public dispose(): void {
|
||||||
this.toDispose = dispose(this.toDispose);
|
this.toDispose = dispose(this.toDispose);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,11 +96,11 @@ suite('Notifications', () => {
|
|||||||
assert.equal(called, 1);
|
assert.equal(called, 1);
|
||||||
|
|
||||||
called = 0;
|
called = 0;
|
||||||
item1.onDidDispose(() => {
|
item1.onDidClose(() => {
|
||||||
called++;
|
called++;
|
||||||
});
|
});
|
||||||
|
|
||||||
item1.dispose();
|
item1.close();
|
||||||
assert.equal(called, 1);
|
assert.equal(called, 1);
|
||||||
|
|
||||||
// Error with Action
|
// Error with Action
|
||||||
@@ -157,11 +157,11 @@ suite('Notifications', () => {
|
|||||||
assert.equal(model.notifications.length, 3);
|
assert.equal(model.notifications.length, 3);
|
||||||
|
|
||||||
let called = 0;
|
let called = 0;
|
||||||
item1Handle.onDidDispose(() => {
|
item1Handle.onDidClose(() => {
|
||||||
called++;
|
called++;
|
||||||
});
|
});
|
||||||
|
|
||||||
item1Handle.dispose();
|
item1Handle.close();
|
||||||
assert.equal(called, 1);
|
assert.equal(called, 1);
|
||||||
assert.equal(model.notifications.length, 2);
|
assert.equal(model.notifications.length, 2);
|
||||||
assert.equal(lastEvent.item.severity, item1.severity);
|
assert.equal(lastEvent.item.severity, item1.severity);
|
||||||
@@ -176,7 +176,7 @@ suite('Notifications', () => {
|
|||||||
assert.equal(lastEvent.index, 0);
|
assert.equal(lastEvent.index, 0);
|
||||||
assert.equal(lastEvent.kind, NotificationChangeType.ADD);
|
assert.equal(lastEvent.kind, NotificationChangeType.ADD);
|
||||||
|
|
||||||
item2Handle.dispose();
|
item2Handle.close();
|
||||||
assert.equal(model.notifications.length, 1);
|
assert.equal(model.notifications.length, 1);
|
||||||
assert.equal(lastEvent.item.severity, item2Duplicate.severity);
|
assert.equal(lastEvent.item.severity, item2Duplicate.severity);
|
||||||
assert.equal(lastEvent.item.message.value, item2Duplicate.message);
|
assert.equal(lastEvent.item.message.value, item2Duplicate.message);
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import * as assert from 'assert';
|
|||||||
import { MainThreadMessageService } from 'vs/workbench/api/electron-browser/mainThreadMessageService';
|
import { MainThreadMessageService } from 'vs/workbench/api/electron-browser/mainThreadMessageService';
|
||||||
import { TPromise as Promise, TPromise } from 'vs/base/common/winjs.base';
|
import { TPromise as Promise, TPromise } from 'vs/base/common/winjs.base';
|
||||||
import { IChoiceService } from 'vs/platform/dialogs/common/dialogs';
|
import { IChoiceService } from 'vs/platform/dialogs/common/dialogs';
|
||||||
import { INotificationService, INotification, NoOpNotification, INotificationHandle } from 'vs/platform/notification/common/notification';
|
import { INotificationService, INotification, NoOpNotification, INotificationHandle, IPromptChoice, Severity } from 'vs/platform/notification/common/notification';
|
||||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||||
|
|
||||||
const emptyChoiceService = new class implements IChoiceService {
|
const emptyChoiceService = new class implements IChoiceService {
|
||||||
@@ -41,6 +41,9 @@ const emptyNotificationService = new class implements INotificationService {
|
|||||||
error(...args: any[]): never {
|
error(...args: any[]): never {
|
||||||
throw new Error('not implemented');
|
throw new Error('not implemented');
|
||||||
}
|
}
|
||||||
|
prompt(severity: Severity, message: string, choices: IPromptChoice[], onCancel?: () => void): INotificationHandle {
|
||||||
|
throw new Error('not implemented');
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
class EmptyNotificationService implements INotificationService {
|
class EmptyNotificationService implements INotificationService {
|
||||||
@@ -64,6 +67,9 @@ class EmptyNotificationService implements INotificationService {
|
|||||||
error(message: any): void {
|
error(message: any): void {
|
||||||
throw new Error('Method not implemented.');
|
throw new Error('Method not implemented.');
|
||||||
}
|
}
|
||||||
|
prompt(severity: Severity, message: string, choices: IPromptChoice[], onCancel?: () => void): INotificationHandle {
|
||||||
|
throw new Error('not implemented');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suite('ExtHostMessageService', function () {
|
suite('ExtHostMessageService', function () {
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKe
|
|||||||
import { ITextBufferFactory, DefaultEndOfLine, EndOfLinePreference } from 'vs/editor/common/model';
|
import { ITextBufferFactory, DefaultEndOfLine, EndOfLinePreference } from 'vs/editor/common/model';
|
||||||
import { Range } from 'vs/editor/common/core/range';
|
import { Range } from 'vs/editor/common/core/range';
|
||||||
import { IChoiceService, IConfirmation, IConfirmationResult, IConfirmationService } from 'vs/platform/dialogs/common/dialogs';
|
import { IChoiceService, IConfirmation, IConfirmationResult, IConfirmationService } from 'vs/platform/dialogs/common/dialogs';
|
||||||
import { INotificationService, INotificationHandle, INotification, NoOpNotification } from 'vs/platform/notification/common/notification';
|
import { INotificationService, INotificationHandle, INotification, NoOpNotification, IPromptChoice } from 'vs/platform/notification/common/notification';
|
||||||
|
|
||||||
export function createFileInput(instantiationService: IInstantiationService, resource: URI): FileEditorInput {
|
export function createFileInput(instantiationService: IInstantiationService, resource: URI): FileEditorInput {
|
||||||
return instantiationService.createInstance(FileEditorInput, resource, void 0);
|
return instantiationService.createInstance(FileEditorInput, resource, void 0);
|
||||||
@@ -331,6 +331,10 @@ export class TestNotificationService implements INotificationService {
|
|||||||
public notify(notification: INotification): INotificationHandle {
|
public notify(notification: INotification): INotificationHandle {
|
||||||
return TestNotificationService.NO_OP;
|
return TestNotificationService.NO_OP;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public prompt(severity: Severity, message: string, choices: IPromptChoice[], onCancel?: () => void): INotificationHandle {
|
||||||
|
return TestNotificationService.NO_OP;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TestConfirmationService implements IConfirmationService {
|
export class TestConfirmationService implements IConfirmationService {
|
||||||
|
|||||||
@@ -112,8 +112,7 @@ import 'vs/workbench/parts/themes/electron-browser/themes.contribution';
|
|||||||
|
|
||||||
import 'vs/workbench/parts/feedback/electron-browser/feedback.contribution';
|
import 'vs/workbench/parts/feedback/electron-browser/feedback.contribution';
|
||||||
|
|
||||||
// {{SQL CARBON EDIT}}
|
import 'vs/workbench/parts/welcome/gettingStarted/electron-browser/gettingStarted.contribution';
|
||||||
// import 'vs/workbench/parts/welcome/gettingStarted/electron-browser/gettingStarted.contribution';
|
|
||||||
|
|
||||||
import 'vs/workbench/parts/update/electron-browser/update.contribution';
|
import 'vs/workbench/parts/update/electron-browser/update.contribution';
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user