Compare commits

..

8 Commits
1.3.5 ... 1.3.6

Author SHA1 Message Date
Anthony Dresser
efd809971f fix row select (#3390) 2018-12-03 14:10:50 -08:00
Karl Burtram
38ae14cc4d Use UTF8 for Azure token cache (#3391)
* Switch token cache encryption encoding to UTF8

* Try to parse as binary in fallback

* Code review feedback
2018-12-03 14:09:42 -08:00
Chris LaFreniere
c7e33a90fe Notebook toolbar extensibility (#3362)
* Notebook Toolbar Contribution

* Address CR comments, ensure CSS can be passed in for contributed items
2018-12-03 11:15:14 -08:00
Yurong He
5add835750 Fixed issue: Notebook: Can't use PySpark3 even though having big data cluster connected #3363 (#3380) 2018-12-03 10:53:04 -08:00
Karl Burtram
734c614cba Update installer to regkeys for per-user install (#3376) 2018-11-30 16:56:19 -08:00
Karl Burtram
f6b347fa62 Bump Azure Data Studio to 1.3.6 2018-11-30 16:51:27 -08:00
Alan Ren
08d2f3125e Fix for issue 3133 (#3375)
* Fix for issue 3133

* fix test error
2018-11-30 15:53:36 -08:00
Matt Irvine
385c48dcad Wait for account provider registration when using account service (#3221) 2018-11-30 11:52:01 -08:00
15 changed files with 194 additions and 64 deletions

View File

@@ -91,17 +91,17 @@ Filename: "{app}\{#ExeBasename}.exe"; Description: "{cm:LaunchProgram,{#NameLong
#else
#define SoftwareClassesRootKey "HKLM"
#endif
Root: HKCR; Subkey: "{#RegValueName}SourceFile"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,{#NameLong}}"; Flags: uninsdeletekey
Root: HKCR; Subkey: "{#RegValueName}SourceFile\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\code_file.ico"
Root: HKCR; Subkey: "{#RegValueName}SourceFile\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""
Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}SourceFile"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,{#NameLong}}"; Flags: uninsdeletekey
Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}SourceFile\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\code_file.ico"
Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}SourceFile\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""
Root: HKCU; Subkey: "Environment"; ValueType: expandsz; ValueName: "Path"; ValueData: "{olddata};{app}\bin"; Tasks: addtopath; Check: NeedsAddPath(ExpandConstant('{app}\bin'))
Root: HKCU; Subkey: "Software\Classes\.sql\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles
Root: HKCU; Subkey: "Software\Classes\.sql\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.sql"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles
Root: HKCU; Subkey: "Software\Classes\{#RegValueName}.sql"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,SQL}"; Flags: uninsdeletekey; Tasks: associatewithfiles
Root: HKCU; Subkey: "Software\Classes\{#RegValueName}.sql"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles
Root: HKCU; Subkey: "Software\Classes\{#RegValueName}.sql\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\code_file.ico"; Tasks: associatewithfiles
Root: HKCU; Subkey: "Software\Classes\{#RegValueName}.sql\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles
Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.sql\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles
Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\.sql\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.sql"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles
Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.sql"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,SQL}"; Flags: uninsdeletekey; Tasks: associatewithfiles
Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.sql"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles
Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.sql\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\code_file.ico"; Tasks: associatewithfiles
Root: {#SoftwareClassesRootKey}; Subkey: "Software\Classes\{#RegValueName}.sql\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles
; Environment
#if "user" == InstallTarget
#define EnvironmentRootKey "HKCU"

View File

@@ -223,22 +223,16 @@ export default class TokenCache implements adal.TokenCache {
return this.getOrCreateEncryptionParams()
.then(encryptionParams => {
try {
let cacheCipher = fs.readFileSync(self._cacheSerializationPath, TokenCache.FsOptions);
let decipher = crypto.createDecipheriv(TokenCache.CipherAlgorithm, encryptionParams.key, encryptionParams.initializationVector);
let cacheJson = decipher.update(cacheCipher, 'hex', 'binary');
cacheJson += decipher.final('binary');
// Deserialize the JSON into the array of tokens
let cacheObj = <adal.TokenResponse[]>JSON.parse(cacheJson);
for (let objIndex in cacheObj) {
// Rehydrate Date objects since they will always serialize as a string
cacheObj[objIndex].expiresOn = new Date(<string>cacheObj[objIndex].expiresOn);
}
return cacheObj;
return self.decryptCache('utf8', encryptionParams);
} catch (e) {
throw e;
try {
// try to parse using 'binary' encoding and rewrite cache as UTF8
let response = self.decryptCache('binary', encryptionParams);
self.writeCache(response);
return response;
} catch (e) {
throw e;
}
}
})
.then(null, err => {
@@ -248,6 +242,22 @@ export default class TokenCache implements adal.TokenCache {
});
}
private decryptCache(encoding: crypto.Utf8AsciiBinaryEncoding, encryptionParams: EncryptionParams): adal.TokenResponse[] {
let cacheCipher = fs.readFileSync(this._cacheSerializationPath, TokenCache.FsOptions);
let decipher = crypto.createDecipheriv(TokenCache.CipherAlgorithm, encryptionParams.key, encryptionParams.initializationVector);
let cacheJson = decipher.update(cacheCipher, 'hex', encoding);
cacheJson += decipher.final(encoding);
// Deserialize the JSON into the array of tokens
let cacheObj = <adal.TokenResponse[]>JSON.parse(cacheJson);
for (let objIndex in cacheObj) {
// Rehydrate Date objects since they will always serialize as a string
cacheObj[objIndex].expiresOn = new Date(<string>cacheObj[objIndex].expiresOn);
}
return cacheObj;
}
private removeFromCache(cache: adal.TokenResponse[], entries: adal.TokenResponse[]): adal.TokenResponse[] {
entries.forEach((entry: adal.TokenResponse) => {
// Check to see if the entry exists
@@ -274,7 +284,7 @@ export default class TokenCache implements adal.TokenCache {
let cacheJson = JSON.stringify(cache);
let cipher = crypto.createCipheriv(TokenCache.CipherAlgorithm, encryptionParams.key, encryptionParams.initializationVector);
let cacheCipher = cipher.update(cacheJson, 'binary', 'hex');
let cacheCipher = cipher.update(cacheJson, 'utf8', 'hex');
cacheCipher += cipher.final('hex');
fs.writeFileSync(self._cacheSerializationPath, cacheCipher, TokenCache.FsOptions);

View File

@@ -1,6 +1,6 @@
{
"name": "azuredatastudio",
"version": "1.3.5",
"version": "1.3.6",
"distro": "8c3e97e3425cc9814496472ab73e076de2ba99ee",
"author": {
"name": "Microsoft Corporation"

View File

@@ -68,7 +68,7 @@ export class RowNumberColumn<T> implements Slick.Plugin<T> {
width: this.currentColumnWidth,
resizable: false,
cssClass: this.options.cssClass,
focusable: true,
focusable: false,
selectable: false,
formatter: (r, c, v, cd, dc) => this.formatter(r, c, v, cd, dc)
};

View File

@@ -63,7 +63,7 @@ export class CommandLineService implements ICommandLineProcessing {
// prompt the user for a new connection on startup if no profiles are registered
this._connectionManagementService.showConnectionDialog();
} else if (this._connectionProfile) {
this._connectionManagementService.connectIfNotConnected(this._connectionProfile, 'connection')
this._connectionManagementService.connectIfNotConnected(this._connectionProfile, 'connection', true)
.then(result => TaskUtilities.newQuery(this._connectionProfile,
this._connectionManagementService,
this._queryEditorService,

View File

@@ -121,7 +121,7 @@ export interface IConnectionManagementService {
* otherwise tries to make a connection and returns the owner uri when connection is complete
* The purpose is connection by default
*/
connectIfNotConnected(connection: IConnectionProfile, purpose?: 'dashboard' | 'insights' | 'connection'): Promise<string>;
connectIfNotConnected(connection: IConnectionProfile, purpose?: 'dashboard' | 'insights' | 'connection', saveConnection?: boolean): Promise<string>;
/**
* Adds the successful connection to MRU and send the connection error back to the connection handler for failed connections

View File

@@ -380,14 +380,14 @@ export class ConnectionManagementService extends Disposable implements IConnecti
* otherwise tries to make a connection and returns the owner uri when connection is complete
* The purpose is connection by default
*/
public connectIfNotConnected(connection: IConnectionProfile, purpose?: 'dashboard' | 'insights' | 'connection'): Promise<string> {
public connectIfNotConnected(connection: IConnectionProfile, purpose?: 'dashboard' | 'insights' | 'connection', saveConnection: boolean = false): Promise<string> {
return new Promise<string>((resolve, reject) => {
let ownerUri: string = Utils.generateUri(connection, purpose);
if (this._connectionStatusManager.isConnected(ownerUri)) {
resolve(this._connectionStatusManager.getOriginalOwnerUri(ownerUri));
} else {
const options: IConnectionCompletionOptions = {
saveTheConnection: false,
saveTheConnection: saveConnection,
showConnectionDialogOnError: true,
showDashboard: purpose === 'dashboard',
params: undefined,

View File

@@ -9,7 +9,6 @@ import { nb } from 'sqlops';
import { OnInit, Component, Inject, forwardRef, ElementRef, ChangeDetectorRef, ViewChild } from '@angular/core';
import URI from 'vs/base/common/uri';
import { IColorTheme, IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
import * as themeColors from 'vs/workbench/common/theme';
import { INotificationService, INotification } from 'vs/platform/notification/common/notification';
@@ -18,12 +17,12 @@ import { localize } from 'vs/nls';
import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service';
import { AngularDisposable } from 'sql/base/common/lifecycle';
import { CellTypes, CellType, NotebookChangeType } from 'sql/parts/notebook/models/contracts';
import { ICellModel, IModelFactory } from 'sql/parts/notebook/models/modelInterfaces';
import { CellTypes, CellType } from 'sql/parts/notebook/models/contracts';
import { ICellModel, IModelFactory, notebookConstants } from 'sql/parts/notebook/models/modelInterfaces';
import { IConnectionManagementService, IConnectionDialogService } from 'sql/parts/connection/common/connectionManagement';
import { INotebookService, INotebookParams, INotebookManager } from 'sql/services/notebook/notebookService';
import { IBootstrapParams } from 'sql/services/bootstrap/bootstrapService';
import { NotebookModel, ErrorInfo, MessageLevel, NotebookContentChange } from 'sql/parts/notebook/models/notebookModel';
import { NotebookModel, NotebookContentChange } from 'sql/parts/notebook/models/notebookModel';
import { ModelFactory } from 'sql/parts/notebook/models/modelFactory';
import * as notebookUtils from './notebookUtils';
import { Deferred } from 'sql/base/common/promise';
@@ -33,6 +32,14 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { KernelsDropdown, AttachToDropdown, AddCellAction, TrustedAction, SaveNotebookAction } from 'sql/parts/notebook/notebookActions';
import { attachSelectBoxStyler } from 'vs/platform/theme/common/styler';
import { MenuId, IMenuService, MenuItemAction } from 'vs/platform/actions/common/actions';
import { IAction, Action, IActionItem } from 'vs/base/common/actions';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { fillInActions, LabeledMenuItemActionItem } from 'vs/platform/actions/browser/menuItemActionItem';
import { IObjectExplorerService } from 'sql/parts/objectExplorer/common/objectExplorerService';
import * as TaskUtilities from 'sql/workbench/common/taskUtilities';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
export const NOTEBOOK_SELECTOR: string = 'notebook-component';
@@ -62,19 +69,42 @@ export class NotebookComponent extends AngularDisposable implements OnInit {
@Inject(forwardRef(() => ChangeDetectorRef)) private _changeRef: ChangeDetectorRef,
@Inject(IWorkbenchThemeService) private themeService: IWorkbenchThemeService,
@Inject(IConnectionManagementService) private connectionManagementService: IConnectionManagementService,
@Inject(IObjectExplorerService) private objectExplorerService: IObjectExplorerService,
@Inject(IEditorService) private editorService: IEditorService,
@Inject(INotificationService) private notificationService: INotificationService,
@Inject(INotebookService) private notebookService: INotebookService,
@Inject(IBootstrapParams) private notebookParams: INotebookParams,
@Inject(IInstantiationService) private instantiationService: IInstantiationService,
@Inject(IContextMenuService) private contextMenuService: IContextMenuService,
@Inject(IContextViewService) private contextViewService: IContextViewService,
@Inject(IConnectionDialogService) private connectionDialogService: IConnectionDialogService
@Inject(IConnectionDialogService) private connectionDialogService: IConnectionDialogService,
@Inject(IContextKeyService) private contextKeyService: IContextKeyService,
@Inject(IMenuService) private menuService: IMenuService,
@Inject(IKeybindingService) private keybindingService: IKeybindingService
) {
super();
this.profile = this.notebookParams!.profile;
this.updateProfile();
this.isLoading = true;
}
private updateProfile(): void {
this.profile = this.notebookParams!.profile;
if (!this.profile) {
// use global connection if possible
let profile = TaskUtilities.getCurrentGlobalConnection(this.objectExplorerService, this.connectionManagementService, this.editorService);
// TODO use generic method to match kernel with valid connection that's compatible. For now, we only have 1
if (profile && profile.providerName === notebookConstants.hadoopKnoxProviderName) {
this.profile = profile;
} else {
// if not, try 1st active connection that matches our filter
let profiles = this.connectionManagementService.getActiveConnections([notebookConstants.hadoopKnoxProviderName]);
if (profiles && profiles.length > 0) {
this.profile = profiles[0];
}
}
}
}
ngOnInit() {
this._register(this.themeService.onDidColorThemeChange(this.updateTheme, this));
this.updateTheme(this.themeService.getColorTheme());
@@ -251,8 +281,19 @@ export class NotebookComponent extends AngularDisposable implements OnInit {
let saveNotebookButton = this.instantiationService.createInstance(SaveNotebookAction, 'notebook.SaveNotebook', localize('save', 'Save'), 'notebook-button icon-save');
// Get all of the menu contributions that use the ID 'notebook/toolbar'.
// Then, find all groups (currently we don't leverage the contributed
// groups functionality for the notebook toolbar), and fill in the 'primary'
// array with items that don't list a group. Finally, add any actions from
// the primary array to the end of the toolbar.
const notebookBarMenu = this.menuService.createMenu(MenuId.NotebookToolbar, this.contextKeyService);
let groups = notebookBarMenu.getActions({ arg: null, shouldForwardArgs: true });
let primary: IAction[] = [];
let secondary: IAction[] = [];
fillInActions(groups, {primary, secondary}, false, (group: string) => group === undefined);
let taskbar = <HTMLElement>this.toolbar.nativeElement;
this._actionBar = new Taskbar(taskbar, this.contextMenuService);
this._actionBar = new Taskbar(taskbar, this.contextMenuService, { actionItemProvider: action => this.actionItemProvider(action as Action)});
this._actionBar.context = this;
this._actionBar.setContent([
{ element: kernelContainer },
@@ -262,6 +303,12 @@ export class NotebookComponent extends AngularDisposable implements OnInit {
{ action: saveNotebookButton },
{ action: this._trustedAction }
]);
// Primary actions are categorized as those that are added to the 'horizontal' group.
// For the vertical toolbar, we can do the same thing and instead use the 'vertical' group.
for (let action of primary) {
this._actionBar.addAction(action);
}
}
public async save(): Promise<boolean> {
@@ -281,5 +328,13 @@ export class NotebookComponent extends AngularDisposable implements OnInit {
// }
}
private actionItemProvider(action: Action): IActionItem {
// Check extensions to create ActionItem; otherwise, return undefined
// This is similar behavior that exists in MenuItemActionItem
if (action instanceof MenuItemAction) {
return new LabeledMenuItemActionItem(action, this.keybindingService, this.notificationService, this.contextMenuService, 'notebook-button');
}
return undefined;
}
}

View File

@@ -6,11 +6,11 @@
'use strict';
import * as sqlops from 'sqlops';
import * as nls from 'vs/nls';
import * as platform from 'vs/platform/registry/common/platform';
import * as statusbar from 'vs/workbench/browser/parts/statusbar/statusbar';
import { Event, Emitter } from 'vs/base/common/event';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { Memento, Scope as MementoScope } from 'vs/workbench/common/memento';
@@ -20,8 +20,8 @@ import { AccountDialogController } from 'sql/parts/accountManagement/accountDial
import { AutoOAuthDialogController } from 'sql/parts/accountManagement/autoOAuthDialog/autoOAuthDialogController';
import { AccountListStatusbarItem } from 'sql/parts/accountManagement/accountListStatusbar/accountListStatusbarItem';
import { AccountProviderAddedEventParams, UpdateAccountListEventParams } from 'sql/services/accountManagement/eventTypes';
import { IAccountManagementService, AzureResource } from 'sql/services/accountManagement/interfaces';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { IAccountManagementService } from 'sql/services/accountManagement/interfaces';
import { Deferred } from 'sql/base/common/promise';
export class AccountManagementService implements IAccountManagementService {
// CONSTANTS ///////////////////////////////////////////////////////////
@@ -198,11 +198,6 @@ export class AccountManagementService implements IAccountManagementService {
public getAccountsForProvider(providerId: string): Thenable<sqlops.Account[]> {
let self = this;
// Make sure the provider exists before attempting to retrieve accounts
if (!this._providers[providerId]) {
return Promise.reject(new Error(nls.localize('accountManagementNoProvider', 'Account provider does not exist'))).then();
}
// 1) Get the accounts from the store
// 2) Update our local cache of accounts
return this.doWithProvider(providerId, provider => {
@@ -217,7 +212,7 @@ export class AccountManagementService implements IAccountManagementService {
/**
* Generates a security token by asking the account's provider
* @param {Account} account Account to generate security token for
* @param {AzureResource} resource The resource to get the security token for
* @param {sqlops.AzureResource} resource The resource to get the security token for
* @return {Thenable<{}>} Promise to return the security token
*/
public getSecurityToken(account: sqlops.Account, resource: sqlops.AzureResource): Thenable<{}> {
@@ -389,10 +384,17 @@ export class AccountManagementService implements IAccountManagementService {
// PRIVATE HELPERS /////////////////////////////////////////////////////
private doWithProvider<T>(providerId: string, op: (provider: AccountProviderWithMetadata) => Thenable<T>): Thenable<T> {
// Make sure the provider exists before attempting to retrieve accounts
let provider = this._providers[providerId];
if (!provider) {
return Promise.reject(new Error(nls.localize('accountManagementNoProvider', 'Account provider does not exist'))).then();
// If the provider doesn't already exist wait until it gets registered
let deferredPromise = new Deferred<T>();
let toDispose = this.addAccountProviderEvent(params => {
if (params.addedProvider.id === providerId) {
toDispose.dispose();
deferredPromise.resolve(op(this._providers[providerId]));
}
});
return deferredPromise;
}
return op(provider);

View File

@@ -164,7 +164,7 @@ suite('commandLineService tests', () => {
environmentService.setup(e => e.args).returns(() => args).verifiable(TypeMoq.Times.atLeastOnce());
connectionManagementService.setup((c) => c.showConnectionDialog()).verifiable(TypeMoq.Times.never());
connectionManagementService.setup(c => c.hasRegisteredServers()).returns(() => true).verifiable(TypeMoq.Times.atMostOnce());
connectionManagementService.setup(c => c.connectIfNotConnected(TypeMoq.It.isAny(), 'connection'))
connectionManagementService.setup(c => c.connectIfNotConnected(TypeMoq.It.isAny(), 'connection', true))
.returns(() => new Promise<string>((resolve, reject) => { reject('unused');}))
.verifiable(TypeMoq.Times.once());
let service = getCommandLineService(connectionManagementService.object, environmentService.object, capabilitiesService);

View File

@@ -221,13 +221,13 @@ suite('Account Management Service Tests:', () => {
let ams = getTestState().accountManagementService;
// If: I add an account when the provider doesn't exist
// Then: It should be rejected
ams.addAccount('doesNotExist')
.then(
() => done('Promise resolved when it should have rejected'),
() => done()
);
// Then: It should not resolve
Promise.race([
new Promise((resolve, reject) => setTimeout(() => resolve(), 100)),
ams.addAccount('doesNotExist').then((
() => done('Promise resolved when the provider did not exist')
))
]).then(() => done(), err => done(err));
});
test('Add account - provider exists, provider fails', done => {
@@ -305,12 +305,13 @@ suite('Account Management Service Tests:', () => {
let ams = getTestState().accountManagementService;
// If: I get accounts when the provider doesn't exist
// Then: It should be rejected
ams.getAccountsForProvider('doesNotExist')
.then(
() => done('Promise resolved when it should have rejected'),
() => done()
);
// Then: It should not resolve
Promise.race([
new Promise((resolve, reject) => setTimeout(() => resolve(), 100)),
ams.getAccountsForProvider('doesNotExist').then((
() => done('Promise resolved when the provider did not exist')
))
]).then(() => done(), err => done(err));
});
test('Get accounts by provider - provider exists, no accounts', done => {

View File

@@ -234,7 +234,7 @@ export class TestConnectionManagementService implements IConnectionManagementSer
return [];
}
connectIfNotConnected(connection: IConnectionProfile, purpose?: 'dashboard' | 'insights' | 'connection'): Promise<string> {
connectIfNotConnected(connection: IConnectionProfile, purpose?: 'dashboard' | 'insights' | 'connection', saveConnection: boolean = false): Promise<string> {
return undefined;
}

View File

@@ -288,3 +288,63 @@ export class ContextAwareMenuItemActionItem extends MenuItemActionItem {
.done(undefined, err => this._notificationService.error(err));
}
}
// Always show label for action items, instead of whether they don't have
// an icon/CSS class. Useful for some toolbar scenarios in particular with
// contributed actions from other extensions
export class LabeledMenuItemActionItem extends MenuItemActionItem {
private _labeledItemClassDispose: IDisposable;
constructor(
public _action: MenuItemAction,
@IKeybindingService private readonly _labeledkeybindingService: IKeybindingService,
@INotificationService protected _notificationService: INotificationService,
@IContextMenuService private readonly _labeledcontextMenuService: IContextMenuService,
private readonly _defaultCSSClassToAdd: string = ''
) {
super(_action, _labeledkeybindingService, _notificationService, _labeledcontextMenuService);
}
_updateLabel(): void {
this.$e.text(this._commandAction.label);
}
// Overwrite item class to ensure that we can pass in a CSS class that other items use
// Leverages the _defaultCSSClassToAdd property that's passed into the constructor
_updateItemClass(item: ICommandAction): void {
dispose(this._labeledItemClassDispose);
this._labeledItemClassDispose = undefined;
if (item.iconLocation) {
let iconClass: string;
const iconPathMapKey = item.iconLocation.dark.toString();
if (MenuItemActionItem.ICON_PATH_TO_CSS_RULES.has(iconPathMapKey)) {
iconClass = MenuItemActionItem.ICON_PATH_TO_CSS_RULES.get(iconPathMapKey);
} else {
iconClass = ids.nextId();
createCSSRule(`.icon.${iconClass}`, `background-image: url("${(item.iconLocation.light || item.iconLocation.dark).toString()}")`);
createCSSRule(`.vs-dark .icon.${iconClass}, .hc-black .icon.${iconClass}`, `background-image: url("${item.iconLocation.dark.toString()}")`);
MenuItemActionItem.ICON_PATH_TO_CSS_RULES.set(iconPathMapKey, iconClass);
}
this.$e.getHTMLElement().classList.add('icon', iconClass);
this.$e.getHTMLElement().classList.add(this._defaultCSSClassToAdd);
this._labeledItemClassDispose = {
dispose: () => {
this.$e.getHTMLElement().classList.remove('icon', iconClass);
this.$e.getHTMLElement().classList.remove(this._defaultCSSClassToAdd);
}
};
}
}
dispose(): void {
if (this._labeledItemClassDispose) {
dispose(this._labeledItemClassDispose);
this._labeledItemClassDispose = undefined;
}
super.dispose();
}
}

View File

@@ -102,6 +102,7 @@ export class MenuId {
static readonly MenubarHelpMenu = new MenuId();
// {{SQL CARBON EDIT}}
static readonly ObjectExplorerItemContext = new MenuId();
static readonly NotebookToolbar = new MenuId();
readonly id: string = String(MenuId.ID++);

View File

@@ -43,6 +43,7 @@ namespace schema {
case 'view/item/context': return MenuId.ViewItemContext;
// {{SQL CARBON EDIT}}
case 'objectExplorer/item/context': return MenuId.ObjectExplorerItemContext;
case 'notebook/toolbar': return MenuId.NotebookToolbar;
}
return void 0;