diff --git a/src/sql/platform/connection/common/connectionProfileGroup.ts b/src/sql/platform/connection/common/connectionProfileGroup.ts
index b27484d55d..6248b4956b 100644
--- a/src/sql/platform/connection/common/connectionProfileGroup.ts
+++ b/src/sql/platform/connection/common/connectionProfileGroup.ts
@@ -23,6 +23,7 @@ export class ConnectionProfileGroup extends Disposable implements IConnectionPro
private _childConnections: ConnectionProfile[] = [];
public parentId?: string;
private _isRenamed = false;
+ public readonly isRoot: boolean = false;
public constructor(
public name: string,
public parent: ConnectionProfileGroup | undefined,
@@ -34,6 +35,7 @@ export class ConnectionProfileGroup extends Disposable implements IConnectionPro
this.parentId = parent ? parent.id : undefined;
if (this.name === ConnectionProfileGroup.RootGroupName) {
this.name = '';
+ this.isRoot = true;
}
}
diff --git a/src/sql/workbench/contrib/connection/browser/media/collapsed-dark.svg b/src/sql/workbench/contrib/connection/browser/media/collapsed-dark.svg
deleted file mode 100644
index cf5c3641aa..0000000000
--- a/src/sql/workbench/contrib/connection/browser/media/collapsed-dark.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/src/sql/workbench/contrib/connection/browser/media/connected_active_server.svg b/src/sql/workbench/contrib/connection/browser/media/connected_active_server.svg
deleted file mode 100644
index 315a9c0602..0000000000
--- a/src/sql/workbench/contrib/connection/browser/media/connected_active_server.svg
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/src/sql/workbench/contrib/connection/browser/media/connected_active_server_inverse.svg b/src/sql/workbench/contrib/connection/browser/media/connected_active_server_inverse.svg
deleted file mode 100644
index 50483cd3a5..0000000000
--- a/src/sql/workbench/contrib/connection/browser/media/connected_active_server_inverse.svg
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/src/sql/workbench/contrib/connection/browser/media/connectionViewlet.css b/src/sql/workbench/contrib/connection/browser/media/connectionViewlet.css
deleted file mode 100644
index 8355072d08..0000000000
--- a/src/sql/workbench/contrib/connection/browser/media/connectionViewlet.css
+++ /dev/null
@@ -1,138 +0,0 @@
-/*---------------------------------------------------------------------------------------------
- * Copyright (c) Microsoft Corporation. All rights reserved.
- * Licensed under the Source EULA. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
-
-/* --- Registered servers tree viewlet --- */
-.server-explorer-viewlet .monaco-tree .monaco-tree-row .content .server-group {
- cursor: default;
- width: 100%;
- display: flex;
- align-items: center;
-}
-
-/* Bold font style does not go well with CJK fonts */
-.server-explorer-viewlet:lang(zh-Hans) .monaco-tree .monaco-tree-row .server-group,
-.server-explorer-viewlet:lang(zh-Hant) .monaco-tree .monaco-tree-row .server-group,
-.server-explorer-viewlet:lang(ja) .monaco-tree .monaco-tree-row .server-group,
-.server-explorer-viewlet:lang(ko) .monaco-tree .monaco-tree-row .server-group { font-weight: normal; }
-
-/* High Contrast Theming */
-.monaco-workbench.hc-black .server-explorer-viewlet .server-group {
- line-height: 20px;
-}
-
-.monaco-workbench > .activitybar .monaco-action-bar .action-label.serverTree {
- background-size: 22px;
- background-repeat: no-repeat;
- background-position: 50% !important;
-}
-
-.server-explorer-viewlet .object-explorer-view {
- height: calc(100% - 36px);
-}
-
-.server-explorer-viewlet .server-group {
- height: 38px;
- line-height: 38px;
- color: #ffffff;
-}
-
-.server-explorer-viewlet .monaco-action-bar .action-label {
- margin-right: 0.3em;
- margin-left: 0.3em;
- line-height: 15px;
- width: 10px !important;
- height: 10px !important;
-}
-
-/* Add space beneath the button */
-.new-connection .monaco-text-button {
-margin-bottom: 2px;
-}
-
-/* display action buttons on hover */
-.server-explorer-viewlet .monaco-tree .monaco-tree-row > .content {
- display: flex;
-}
-
-/* Added to display the tree in connection dialog */
-.server-explorer-viewlet {
- height: 100%;
-}
-
-.explorer-servers {
- height: 100%;
-}
-
-/* search box */
-.server-explorer-viewlet .search-box {
- padding-bottom: 4px;
- margin: auto;
- width: 95%;
-}
-
-/* OE and connection element group */
-.monaco-tree .monaco-tree-rows > .monaco-tree-row > .content > .connection-tile,
-.monaco-tree .monaco-tree-rows > .monaco-tree-row > .content > .object-element-group {
- padding: 5px;
- overflow: hidden;
-}
-
-/* OE and connection label */
-.monaco-tree .monaco-tree-rows > .monaco-tree-row > .content > .connection-tile > .label,
-.monaco-tree .monaco-tree-rows > .monaco-tree-row > .content > .object-element-group > .label {
- text-overflow: ellipsis;
- overflow: hidden;
-}
-
-/* OE and connection icon */
-.monaco-tree .monaco-tree-rows > .monaco-tree-row > .content > .connection-tile > .icon,
-.monaco-tree .monaco-tree-rows > .monaco-tree-row > .content > .object-element-group > .icon {
- float: left;
- height: 16px;
- width: 16px;
- padding-right: 10px;
-}
-
-.monaco-tree .monaco-tree-rows > .monaco-tree-row > .content > .connection-tile > .icon.server-page.connected {
- background: url('connected_active_server.svg') center center no-repeat;
-}
-
-.vs-dark .monaco-tree .monaco-tree-rows > .monaco-tree-row > .content > .connection-tile > .icon.server-page.connected,
-.hc-black .monaco-tree .monaco-tree-rows > .monaco-tree-row > .content > .connection-tile > .icon.server-page.connected{
- background: url('connected_active_server_inverse.svg') center center no-repeat;
-}
-
-.monaco-tree .monaco-tree-rows > .monaco-tree-row > .content > .connection-tile > .icon.server-page.disconnected {
- background: url('disconnected_server.svg') center center no-repeat;
-}
-
-.vs-dark .monaco-tree .monaco-tree-rows > .monaco-tree-row > .content > .connection-tile > .icon.server-page.disconnected,
-.hc-black .monaco-tree .monaco-tree-rows > .monaco-tree-row > .content > .connection-tile > .icon.server-page.disconnected{
- background: url('disconnected_server_inverse.svg') center center no-repeat;
-}
-
-/* loading for OE node */
-.server-explorer-viewlet .monaco-tree .monaco-tree-rows > .monaco-tree-row > .codicon.in-progress .connection-tile:before,
-.server-explorer-viewlet .monaco-tree .monaco-tree-rows > .monaco-tree-row > .codicon.in-progress .object-element-group:before {
- position: absolute;
- display: block;
- width: 36px;
- height: 100%;
- top: 0;
- left: -35px;
-}
-
-.monaco-tree .monaco-tree-rows.show-twisties > .monaco-tree-row.expanded.has-children > .content.server-group:before {
- background: url('expanded-dark.svg') 50% 50% no-repeat;
-}
-
-.monaco-tree .monaco-tree-rows.show-twisties > .monaco-tree-row.has-children > .content.server-group:before {
- background: url('collapsed-dark.svg') 50% 50% no-repeat;
-}
-
-/* Add connection button */
-.server-explorer-viewlet .button-section {
- padding: 20px;
-}
diff --git a/src/sql/workbench/contrib/connection/browser/media/disconnected_server.svg b/src/sql/workbench/contrib/connection/browser/media/disconnected_server.svg
deleted file mode 100644
index 7ee461c1a7..0000000000
--- a/src/sql/workbench/contrib/connection/browser/media/disconnected_server.svg
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/src/sql/workbench/contrib/connection/browser/media/disconnected_server_inverse.svg b/src/sql/workbench/contrib/connection/browser/media/disconnected_server_inverse.svg
deleted file mode 100644
index 6cdf7983ba..0000000000
--- a/src/sql/workbench/contrib/connection/browser/media/disconnected_server_inverse.svg
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/src/sql/workbench/contrib/connection/browser/media/expanded-dark.svg b/src/sql/workbench/contrib/connection/browser/media/expanded-dark.svg
deleted file mode 100644
index 73d41e6399..0000000000
--- a/src/sql/workbench/contrib/connection/browser/media/expanded-dark.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/src/sql/workbench/contrib/dashboard/browser/dashboardActions.ts b/src/sql/workbench/contrib/dashboard/browser/dashboardActions.ts
index f188b7d897..f87d809792 100644
--- a/src/sql/workbench/contrib/dashboard/browser/dashboardActions.ts
+++ b/src/sql/workbench/contrib/dashboard/browser/dashboardActions.ts
@@ -14,7 +14,6 @@ import { Action } from 'vs/base/common/actions';
import { TreeSelectionHandler } from 'sql/workbench/services/objectExplorer/browser/treeSelectionHandler';
import { ObjectExplorerActionsContext, getTreeNode } from 'sql/workbench/services/objectExplorer/browser/objectExplorerActions';
import { TreeNode } from 'sql/workbench/services/objectExplorer/common/treeNode';
-import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
import { TreeUpdateUtils } from 'sql/workbench/services/objectExplorer/browser/treeUpdateUtils';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IObjectExplorerService } from 'sql/workbench/services/objectExplorer/browser/objectExplorerService';
@@ -91,7 +90,7 @@ export class OEManageConnectionAction extends Action {
private async doManage(actionContext: ObjectExplorerActionsContext): Promise {
let treeNode: TreeNode = undefined;
- let connectionProfile: IConnectionProfile = undefined;
+ let connectionProfile: ConnectionProfile = undefined;
if (actionContext instanceof ObjectExplorerActionsContext) {
// Must use a real connection profile for this action due to lookup
connectionProfile = ConnectionProfile.fromIConnectionProfile(this._capabilitiesService, actionContext.connectionProfile);
diff --git a/src/sql/workbench/contrib/dataExplorer/browser/connectionViewletPanel.ts b/src/sql/workbench/contrib/dataExplorer/browser/connectionViewletPanel.ts
index ecba62230a..79a6ecd388 100644
--- a/src/sql/workbench/contrib/dataExplorer/browser/connectionViewletPanel.ts
+++ b/src/sql/workbench/contrib/dataExplorer/browser/connectionViewletPanel.ts
@@ -18,13 +18,14 @@ import {
} from 'sql/workbench/services/objectExplorer/browser/connectionTreeAction';
import { IObjectExplorerService } from 'sql/workbench/services/objectExplorer/browser/objectExplorerService';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
-import { ITree } from 'vs/base/parts/tree/browser/tree';
import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer';
import { IViewDescriptorService } from 'vs/workbench/common/views';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { ILogService } from 'vs/platform/log/common/log';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
+import { ITree } from 'vs/base/parts/tree/browser/tree';
+import { AsyncServerTree } from 'sql/workbench/services/objectExplorer/browser/asyncServerTree';
export class ConnectionViewletPanel extends ViewPane {
@@ -82,7 +83,7 @@ export class ConnectionViewletPanel extends ViewPane {
this._root = container;
}
- get serversTree(): ITree {
+ get serversTree(): ITree | AsyncServerTree {
return this._serverTreeView.tree;
}
diff --git a/src/sql/workbench/contrib/dataExplorer/browser/media/connectionViewletPanel.css b/src/sql/workbench/contrib/dataExplorer/browser/media/connectionViewletPanel.css
index f0aef69dd4..60c470b96a 100644
--- a/src/sql/workbench/contrib/dataExplorer/browser/media/connectionViewletPanel.css
+++ b/src/sql/workbench/contrib/dataExplorer/browser/media/connectionViewletPanel.css
@@ -4,7 +4,8 @@
*--------------------------------------------------------------------------------------------*/
/* --- Registered servers tree viewlet --- */
-.server-explorer-viewlet .monaco-tree .monaco-tree-row .content .server-group {
+.server-explorer-viewlet .monaco-tree .monaco-tree-row .content .server-group,
+.server-explorer-viewlet .monaco-list .monaco-list-row .content .server-group {
cursor: default;
width: 100%;
display: flex;
@@ -15,7 +16,13 @@
.server-explorer-viewlet:lang(zh-Hans) .monaco-tree .monaco-tree-row .server-group,
.server-explorer-viewlet:lang(zh-Hant) .monaco-tree .monaco-tree-row .server-group,
.server-explorer-viewlet:lang(ja) .monaco-tree .monaco-tree-row .server-group,
-.server-explorer-viewlet:lang(ko) .monaco-tree .monaco-tree-row .server-group { font-weight: normal; }
+.server-explorer-viewlet:lang(ko) .monaco-tree .monaco-tree-row .server-group,
+.server-explorer-viewlet:lang(zh-Hans) .monaco-list .monaco-list-row .server-group,
+.server-explorer-viewlet:lang(zh-Hant) .monaco-list .monaco-list-row .server-group,
+.server-explorer-viewlet:lang(ja) .monaco-list .monaco-list-row .server-group,
+.server-explorer-viewlet:lang(ko) .monaco-list .monaco-list-row .server-group {
+ font-weight: normal;
+}
/* High Contrast Theming */
.monaco-workbench.hc-black .server-explorer-viewlet .server-group {
@@ -48,14 +55,19 @@
/* Add space beneath the button */
.new-connection .monaco-text-button {
-margin-bottom: 2px;
+ margin-bottom: 2px;
}
/* display action buttons on hover */
-.server-explorer-viewlet .monaco-tree .monaco-tree-row > .content {
+.server-explorer-viewlet .monaco-tree .monaco-tree-row > .content,
+.server-explorer-viewlet .monaco-list .monaco-list-row {
display: flex;
}
+.server-explorer-viewlet .monaco-tl-row {
+ width: 100%;
+}
+
/* Added to display the tree in connection dialog */
.server-explorer-viewlet {
height: 100%;
@@ -74,7 +86,9 @@ margin-bottom: 2px;
/* OE and connection element group */
.monaco-tree .monaco-tree-rows > .monaco-tree-row > .content > .connection-tile,
-.monaco-tree .monaco-tree-rows > .monaco-tree-row > .content > .object-element-group {
+.monaco-tree .monaco-tree-rows > .monaco-tree-row > .content > .object-element-group,
+.monaco-list .monaco-list-rows > .monaco-list-row .connection-tile,
+.monaco-list .monaco-list-rows > .monaco-list-row .object-element-group {
padding-left: 5px;
padding-right: 5px;
padding-top: 3px;
@@ -84,26 +98,33 @@ margin-bottom: 2px;
/* OE and connection label */
.monaco-tree .monaco-tree-rows > .monaco-tree-row > .content > .connection-tile > .label,
-.monaco-tree .monaco-tree-rows > .monaco-tree-row > .content > .object-element-group > .label {
+.monaco-tree .monaco-tree-rows > .monaco-tree-row > .content > .object-element-group > .label,
+.monaco-list .monaco-list-rows > .monaco-list-row .connection-tile > .label,
+.monaco-list .monaco-list-rows > .monaco-list-row .object-element-group > .label {
text-overflow: ellipsis;
overflow: hidden;
}
/* OE and connection icon */
.monaco-tree .monaco-tree-rows > .monaco-tree-row > .content > .connection-tile > .icon,
-.monaco-tree .monaco-tree-rows > .monaco-tree-row > .content > .object-element-group > .icon {
+.monaco-tree .monaco-tree-rows > .monaco-tree-row > .content > .object-element-group > .icon,
+.monaco-list .monaco-list-rows > .monaco-list-row .connection-tile > .icon,
+.monaco-list .monaco-list-rows > .monaco-list-row .object-element-group > .icon {
float: left;
height: 16px;
width: 16px;
padding-right: 10px;
}
-.monaco-tree .monaco-tree-rows > .monaco-tree-row > .content > .connection-tile > .icon.server-page {
+.monaco-tree .monaco-tree-rows > .monaco-tree-row > .content > .connection-tile > .icon.server-page,
+.monaco-list .monaco-list-rows > .monaco-list-row .connection-tile > .icon.server-page {
background: url('default_server.svg') center center no-repeat;
}
.vs-dark .monaco-tree .monaco-tree-rows > .monaco-tree-row > .content > .connection-tile > .icon.server-page,
-.hc-black .monaco-tree .monaco-tree-rows > .monaco-tree-row > .content > .connection-tile > .icon.server-page{
+.hc-black .monaco-tree .monaco-tree-rows > .monaco-tree-row > .content > .connection-tile > .icon.server-page,
+.vs-dark .monaco-list .monaco-list-rows > .monaco-list-row .connection-tile > .icon.server-page,
+.hc-black .monaco-list .monaco-list-rows > .monaco-list-row .connection-tile > .icon.server-page {
background: url('default_server_inverse.svg') center center no-repeat;
}
@@ -118,11 +139,40 @@ margin-bottom: 2px;
left: -35px;
}
-.monaco-tree .monaco-tree-rows.show-twisties > .monaco-tree-row.expanded.has-children > .content.server-group:before {
+.monaco-list .connection-tile > .icon.server-page::after {
+ position: absolute;
+ height: 0.25rem;
+ width: 0.25rem;
+ top: 14px;
+ left: 45px;
+ border-radius: 100%;
+ content:"";
+ font-size: 100%;
+ line-height: 100%;
+ color:white;
+ text-align:center;
+ vertical-align:middle;
+}
+
+/* Connected badge */
+.monaco-list .connection-tile > .icon.server-page.connected::after {
+ border: 0.12rem solid rgba(59, 180, 74, 100%);
+ background: rgba(59, 180, 74, 100%);
+}
+
+/* Disconnected badge */
+.monaco-list .connection-tile > .icon.server-page.disconnected::after {
+ border: 0.12rem solid rgba(208, 46, 0, 100%);
+ background: rgba(255, 255, 255, 80%);
+}
+
+.monaco-tree .monaco-tree-rows.show-twisties > .monaco-tree-row.expanded.has-children > .content.server-group:before,
+.monaco-list .monaco-list-rows.show-twisties > .monaco-list-row.expanded.has-children > .content.server-group:before {
background: url('expanded-dark.svg') 50% 50% no-repeat;
}
-.monaco-tree .monaco-tree-rows.show-twisties > .monaco-tree-row.has-children > .content.server-group:before {
+.monaco-tree .monaco-tree-rows.show-twisties > .monaco-tree-row.has-children > .content.server-group:before,
+.monaco-list .monaco-list-rows.show-twisties > .monaco-list-row.has-children > .content.server-group:before {
background: url('collapsed-dark.svg') 50% 50% no-repeat;
}
diff --git a/src/sql/workbench/contrib/objectExplorer/browser/serverTreeView.ts b/src/sql/workbench/contrib/objectExplorer/browser/serverTreeView.ts
index c38ad8d605..23e6496ca8 100644
--- a/src/sql/workbench/contrib/objectExplorer/browser/serverTreeView.ts
+++ b/src/sql/workbench/contrib/objectExplorer/browser/serverTreeView.ts
@@ -38,6 +38,11 @@ import { CommandsRegistry } from 'vs/platform/commands/common/commands';
import { startsWith } from 'vs/base/common/strings';
import { SERVER_GROUP_CONFIG } from 'sql/workbench/services/serverGroup/common/interfaces';
import { horizontalScrollingKey } from 'vs/platform/list/browser/listService';
+import { ITreeContextMenuEvent } from 'vs/base/browser/ui/tree/tree';
+import { ObjectExplorerActionsContext } from 'sql/workbench/services/objectExplorer/browser/objectExplorerActions';
+import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
+import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
+import { AsyncServerTree, ServerTreeElement } from 'sql/workbench/services/objectExplorer/browser/asyncServerTree';
/**
* ServerTreeview implements the dynamic tree view.
@@ -48,7 +53,7 @@ export class ServerTreeView extends Disposable implements IServerTreeView {
private _buttonSection: HTMLElement;
private _treeSelectionHandler: TreeSelectionHandler;
private _activeConnectionsFilterAction: ActiveConnectionsFilterAction;
- private _tree: ITree;
+ private _tree: ITree | AsyncServerTree;
private _onSelectionOrFocusChange: Emitter;
private _actionProvider: ServerTreeActionProvider;
@@ -59,7 +64,9 @@ export class ServerTreeView extends Disposable implements IServerTreeView {
@IThemeService private _themeService: IThemeService,
@IErrorMessageService private _errorMessageService: IErrorMessageService,
@IConfigurationService private _configurationService: IConfigurationService,
- @ICapabilitiesService capabilitiesService: ICapabilitiesService
+ @ICapabilitiesService capabilitiesService: ICapabilitiesService,
+ @IContextMenuService private _contextMenuService: IContextMenuService,
+ @IKeybindingService private _keybindingService: IKeybindingService
) {
super();
this._activeConnectionsFilterAction = this._instantiationService.createInstance(
@@ -71,9 +78,17 @@ export class ServerTreeView extends Disposable implements IServerTreeView {
this._onSelectionOrFocusChange = new Emitter();
this._actionProvider = this._instantiationService.createInstance(ServerTreeActionProvider);
capabilitiesService.onCapabilitiesRegistered(async () => {
- if (this._connectionManagementService.hasRegisteredServers()) {
- await this.refreshTree();
+ if (this._tree instanceof AsyncServerTree) {
+ // Refresh the tree input now that the capabilities are registered so that we can
+ // get the full ConnectionProfiles with the server info updated properly
+ const treeInput = TreeUpdateUtils.getTreeInput(this._connectionManagementService);
+ await this._tree.setInput(treeInput);
this._treeSelectionHandler.onTreeActionStateChange(false);
+ } else {
+ if (this._connectionManagementService.hasRegisteredServers()) {
+ await this.refreshTree();
+ this._treeSelectionHandler.onTreeActionStateChange(false);
+ }
}
});
this.registerCommands();
@@ -97,7 +112,7 @@ export class ServerTreeView extends Disposable implements IServerTreeView {
return this._actionProvider;
}
- public get tree(): ITree {
+ public get tree(): ITree | AsyncServerTree {
return this._tree;
}
@@ -143,11 +158,30 @@ export class ServerTreeView extends Disposable implements IServerTreeView {
}
const horizontalScrollEnabled: boolean = this._configurationService.getValue(horizontalScrollingKey) || false;
- this._tree = this._register(TreeCreationUtils.createRegisteredServersTree(container, this._instantiationService, horizontalScrollEnabled));
- //this._tree.setInput(undefined);
+ this._tree = this._register(TreeCreationUtils.createServersTree(container, this._instantiationService, this._configurationService, horizontalScrollEnabled));
this._register(this._tree.onDidChangeSelection((event) => this.onSelected(event)));
this._register(this._tree.onDidBlur(() => this._onSelectionOrFocusChange.fire()));
this._register(this._tree.onDidChangeFocus(() => this._onSelectionOrFocusChange.fire()));
+ if (this._tree instanceof AsyncServerTree) {
+ this._register(this._tree.onContextMenu(e => this.onContextMenu(e)));
+ this._register(this._tree.onMouseDblClick(e => {
+ // Open dashboard on double click for server and database nodes
+ let connectionProfile: ConnectionProfile;
+ if (e.element instanceof ConnectionProfile) {
+ connectionProfile = e.element;
+ } else if (e.element instanceof TreeNode) {
+ if (TreeUpdateUtils.isAvailableDatabaseNode(e.element)) {
+ connectionProfile = TreeUpdateUtils.getConnectionProfile(e.element);
+ }
+ }
+ if (connectionProfile) {
+ this._connectionManagementService.showDashboard(connectionProfile);
+ }
+ }));
+ this._register(this._connectionManagementService.onConnectionChanged(() => {
+ this.refreshTree().catch(err => errors.onUnexpectedError);
+ }));
+ }
// Theme styler
this._register(attachListStyler(this._tree, this._themeService));
@@ -171,7 +205,7 @@ export class ServerTreeView extends Disposable implements IServerTreeView {
this.showError(args.errorMessage);
}
if (args.connection) {
- this.onObjectExplorerSessionCreated(args.connection);
+ this.onObjectExplorerSessionCreated(args.connection).catch(err => errors.onUnexpectedError);
}
}));
}
@@ -182,7 +216,14 @@ export class ServerTreeView extends Disposable implements IServerTreeView {
const expandGroups: boolean = this._configurationService.getValue(SERVER_GROUP_CONFIG)[SERVER_GROUP_AUTOEXPAND_CONFIG];
if (expandGroups) {
- await this._tree.expandAll(ConnectionProfileGroup.getSubgroups(root));
+ if (this._tree instanceof AsyncServerTree) {
+ await Promise.all(ConnectionProfileGroup.getSubgroups(root).map(subgroup => {
+ return this._tree.expand(subgroup);
+ }));
+ } else {
+ await this._tree.expandAll(ConnectionProfileGroup.getSubgroups(root));
+ }
+
}
if (root && !root.hasValidConnections) {
@@ -200,30 +241,55 @@ export class ServerTreeView extends Disposable implements IServerTreeView {
return uri && startsWith(uri, ConnectionUtils.uriPrefixes.default) && !isBackupRestoreUri;
}
- private async handleAddConnectionProfile(newProfile: IConnectionProfile): Promise {
- if (newProfile) {
- const groups = this._connectionManagementService.getConnectionGroups();
- const profile = ConnectionUtils.findProfileInGroup(newProfile, groups);
- if (profile) {
- newProfile = profile;
- }
- }
-
+ private async handleAddConnectionProfile(newProfile?: IConnectionProfile): Promise {
if (this._buttonSection) {
hide(this._buttonSection);
this._activeConnectionsFilterAction.enabled = true;
}
- const currentSelections = this._tree.getSelection();
- const currentSelectedElement = currentSelections && currentSelections.length >= 1 ? currentSelections[0] : undefined;
- const newProfileIsSelected = currentSelectedElement && newProfile ? currentSelectedElement.id === newProfile.id : false;
- if (newProfile && currentSelectedElement && !newProfileIsSelected) {
- this._tree.clearSelection();
- }
- await this.refreshTree();
- if (newProfile && !newProfileIsSelected) {
- await this._tree.reveal(newProfile);
- this._tree.select(newProfile);
+
+ if (this._tree instanceof AsyncServerTree) {
+ // When new connection groups are added the event is fired with undefined so
+ // we still want to refresh the tree in that case to pick up the changes
+ await this.refreshTree();
+ if (newProfile) {
+ const currentSelections = this._tree.getSelection();
+ const currentSelectedElement = currentSelections && currentSelections.length >= 1 ? currentSelections[0] : undefined;
+ const newProfileIsSelected = currentSelectedElement && currentSelectedElement.id === newProfile.id;
+ // Clear any other selected elements first
+ if (currentSelectedElement && !newProfileIsSelected) {
+ this._tree.setSelection([]);
+ }
+ const newConnectionProfile = this.getConnectionInTreeInput(newProfile.id);
+ if (newConnectionProfile) {
+ // Re-render to update the connection status badge
+ this._tree.rerender(newConnectionProfile);
+ this._tree.setSelection([newConnectionProfile]);
+ this._tree.expand(newConnectionProfile);
+ }
+ }
+
+ } else {
+ if (newProfile) {
+ const groups = this._connectionManagementService.getConnectionGroups();
+ const profile = ConnectionUtils.findProfileInGroup(newProfile, groups);
+ if (profile) {
+ newProfile = profile;
+ }
+ }
+
+ const currentSelections = this._tree.getSelection();
+ const currentSelectedElement = currentSelections && currentSelections.length >= 1 ? currentSelections[0] : undefined;
+ const newProfileIsSelected = currentSelectedElement && newProfile ? currentSelectedElement.id === newProfile.id : false;
+ if (newProfile && currentSelectedElement && !newProfileIsSelected) {
+ this._tree.clearSelection();
+ }
+ await this.refreshTree();
+ if (newProfile && !newProfileIsSelected) {
+ await this._tree.reveal(newProfile);
+ this._tree.select(newProfile);
+ }
}
+
}
private showError(errorMessage: string) {
@@ -232,66 +298,83 @@ export class ServerTreeView extends Disposable implements IServerTreeView {
}
}
- private getConnectionInTreeInput(connectionId: string): ConnectionProfile {
- const root = TreeUpdateUtils.getTreeInput(this._connectionManagementService);
- const connections = ConnectionProfileGroup.getConnectionsInGroup(root);
- const results = connections.filter(con => {
- if (connectionId === con.id) {
- return true;
- } else {
- return false;
+ /**
+ * Gets the ConnectionProfile object in the tree for the specified ID, or undefined if it doesn't exist.
+ * @param connectionId The connection ID to search for
+ */
+ private getConnectionInTreeInput(connectionId: string): ConnectionProfile | undefined {
+ if (this._tree instanceof AsyncServerTree) {
+ const root = this._tree.getInput();
+ const connections = ConnectionProfileGroup.getConnectionsInGroup(root);
+ return connections.find(conn => conn.id === connectionId);
+ } else {
+ const root = TreeUpdateUtils.getTreeInput(this._connectionManagementService);
+ const connections = ConnectionProfileGroup.getConnectionsInGroup(root);
+ const results = connections.filter(con => {
+ if (connectionId === con.id) {
+ return true;
+ } else {
+ return false;
+ }
+ });
+ if (results && results.length > 0) {
+ return results[0];
}
- });
- if (results && results.length > 0) {
- return results[0];
+ return undefined;
}
- return null;
}
- private onObjectExplorerSessionCreated(connection: IConnectionProfile) {
- const conn = this.getConnectionInTreeInput(connection.id);
- if (conn) {
- this._tree.refresh(conn).then(() => {
- return this._tree.expand(conn).then(() => {
- return this._tree.reveal(conn, 0.5).then(() => {
- this._treeSelectionHandler.onTreeActionStateChange(false);
- });
- });
- }).then(null, errors.onUnexpectedError);
+ private async onObjectExplorerSessionCreated(connection: IConnectionProfile): Promise {
+ const element = this.getConnectionInTreeInput(connection.id);
+ if (element) {
+ if (this._tree instanceof AsyncServerTree) {
+ this._tree.rerender(element);
+ } else {
+ await this._tree.refresh(element);
+ }
+ await this._tree.expand(element);
+ await this._tree.reveal(element, 0.5);
+ this._treeSelectionHandler.onTreeActionStateChange(false);
}
}
public addObjectExplorerNodeAndRefreshTree(connection: IConnectionProfile): void {
hide(this.messages);
if (!this._objectExplorerService.getObjectExplorerNode(connection)) {
- this._objectExplorerService.updateObjectExplorerNodes(connection).then(() => {
- // The oe request is sent. an event will be raised when the session is created
- }, error => {
- });
+ this._objectExplorerService.updateObjectExplorerNodes(connection).catch(e => errors.onUnexpectedError(e));
}
}
- public deleteObjectExplorerNodeAndRefreshTree(connection: IConnectionProfile): Promise {
+ public async deleteObjectExplorerNodeAndRefreshTree(connection: IConnectionProfile): Promise {
if (connection) {
const conn = this.getConnectionInTreeInput(connection.id);
if (conn) {
- return this._objectExplorerService.deleteObjectExplorerNode(conn).then(async () => {
+ await this._objectExplorerService.deleteObjectExplorerNode(conn);
+ if (this._tree instanceof AsyncServerTree) {
+ // Collapse the node before refreshing so the refresh doesn't try to fetch
+ // the children again (which causes it to try and connect)
+ this._tree.collapse(conn);
+ await this.refreshTree();
+ } else {
await this._tree.collapse(conn);
return this._tree.refresh(conn);
- });
+ }
}
}
- return Promise.resolve();
}
- public refreshTree(): Promise {
+ public async refreshTree(): Promise {
hide(this.messages);
this.clearOtherActions();
return TreeUpdateUtils.registeredServerUpdate(this._tree, this._connectionManagementService);
}
- public refreshElement(element: any): Promise {
- return this._tree.refresh(element);
+ public async refreshElement(element: ServerTreeElement): Promise {
+ if (this._tree instanceof AsyncServerTree) {
+ return this._tree.updateChildren(element);
+ } else {
+ return this._tree.refresh(element);
+ }
}
/**
@@ -350,9 +433,19 @@ export class ServerTreeView extends Disposable implements IServerTreeView {
this._tree.setInput(treeInput).then(async () => {
if (isHidden(this.messages)) {
this._tree.getFocus();
- await this._tree.expandAll(ConnectionProfileGroup.getSubgroups(treeInput));
+ if (this._tree instanceof AsyncServerTree) {
+ await Promise.all(ConnectionProfileGroup.getSubgroups(treeInput).map(subgroup => {
+ this._tree.expand(subgroup);
+ }));
+ } else {
+ await this._tree.expandAll(ConnectionProfileGroup.getSubgroups(treeInput));
+ }
} else {
- this._tree.clearFocus();
+ if (this._tree instanceof AsyncServerTree) {
+ this._tree.setFocus([]);
+ } else {
+ this._tree.clearFocus();
+ }
}
}, errors.onUnexpectedError);
} else {
@@ -382,9 +475,19 @@ export class ServerTreeView extends Disposable implements IServerTreeView {
this._tree.setInput(treeInput).then(async () => {
if (isHidden(this.messages)) {
this._tree.getFocus();
- await this._tree.expandAll(ConnectionProfileGroup.getSubgroups(treeInput));
+ if (this._tree instanceof AsyncServerTree) {
+ await Promise.all(ConnectionProfileGroup.getSubgroups(treeInput).map(subgroup => {
+ this._tree.expand(subgroup);
+ }));
+ } else {
+ await this._tree.expandAll(ConnectionProfileGroup.getSubgroups(treeInput));
+ }
} else {
- this._tree.clearFocus();
+ if (this._tree instanceof AsyncServerTree) {
+ this._tree.setFocus([]);
+ } else {
+ this._tree.clearFocus();
+ }
}
}, errors.onUnexpectedError);
}
@@ -450,17 +553,6 @@ export class ServerTreeView extends Disposable implements IServerTreeView {
this._tree.layout(height);
}
- /**
- * set the visibility of the view
- */
- public setVisible(visible: boolean): void {
- if (visible) {
- this._tree.onVisible();
- } else {
- this._tree.onHidden();
- }
- }
-
/**
* Get the list of selected nodes in the tree
*/
@@ -472,48 +564,108 @@ export class ServerTreeView extends Disposable implements IServerTreeView {
* Get whether the tree view currently has focus
*/
public isFocused(): boolean {
- return this._tree.isDOMFocused();
+ return this._tree.getHTMLElement() === document.activeElement;
}
/**
* Set whether the given element is expanded or collapsed
*/
- public setExpandedState(element: TreeNode | ConnectionProfile, expandedState: TreeItemCollapsibleState): Promise {
+ public async setExpandedState(element: ServerTreeElement, expandedState: TreeItemCollapsibleState): Promise {
if (expandedState === TreeItemCollapsibleState.Collapsed) {
return this._tree.collapse(element);
} else if (expandedState === TreeItemCollapsibleState.Expanded) {
return this._tree.expand(element);
}
- return Promise.resolve();
}
/**
* Reveal the given element in the tree
*/
- public reveal(element: TreeNode | ConnectionProfile): Promise {
+ public async reveal(element: ServerTreeElement): Promise {
return this._tree.reveal(element);
}
/**
* Select the given element in the tree and clear any other selections
*/
- public setSelected(element: TreeNode | ConnectionProfile, selected: boolean, clearOtherSelections: boolean): Promise {
+ public async setSelected(element: ServerTreeElement, selected: boolean, clearOtherSelections: boolean): Promise {
if (clearOtherSelections || (selected && clearOtherSelections !== false)) {
- this._tree.clearSelection();
+ if (this._tree instanceof AsyncServerTree) {
+ this._tree.setSelection([]);
+ } else {
+ this._tree.clearSelection();
+ }
+
}
if (selected) {
- this._tree.select(element);
- return this._tree.reveal(element);
+ if (this._tree instanceof AsyncServerTree) {
+ this._tree.setSelection(this._tree.getSelection().concat(element));
+ this._tree.reveal(element);
+ } else {
+ this._tree.select(element);
+ return this._tree.reveal(element);
+ }
} else {
- this._tree.deselect(element);
- return Promise.resolve();
+ if (this._tree instanceof AsyncServerTree) {
+ this._tree.setSelection(this._tree.getSelection().filter(item => item !== element));
+ } else {
+ this._tree.deselect(element);
+ }
}
}
/**
* Check if the given element in the tree is expanded
*/
- public isExpanded(element: TreeNode | ConnectionProfile): boolean {
- return this._tree.isExpanded(element);
+ public isExpanded(element: ServerTreeElement): boolean {
+ if (this._tree instanceof AsyncServerTree) {
+ return !this._tree.getNode(element).collapsed;
+ } else {
+ return this._tree.isExpanded(element);
+ }
+
+ }
+
+ /**
+ * Return actions in the context menu
+ */
+ private onContextMenu(e: ITreeContextMenuEvent): boolean {
+ e.browserEvent.preventDefault();
+ e.browserEvent.stopPropagation();
+ this._tree.setSelection([e.element]);
+
+ let actionContext: any;
+ if (e.element instanceof TreeNode) {
+ let context = new ObjectExplorerActionsContext();
+ context.nodeInfo = e.element.toNodeInfo();
+ // Note: getting DB name before, but intentionally not using treeUpdateUtils.getConnectionProfile as it replaces
+ // the connection ID with a new one. This breaks a number of internal tasks
+ context.connectionProfile = e.element.getConnectionProfile().toIConnectionProfile();
+ context.connectionProfile.databaseName = e.element.getDatabaseName();
+ actionContext = context;
+ } else if (e.element instanceof ConnectionProfile) {
+ let context = new ObjectExplorerActionsContext();
+ context.connectionProfile = e.element.toIConnectionProfile();
+ context.isConnectionNode = true;
+ actionContext = context;
+ } else {
+ // TODO: because the connection group is used as a context object and isn't serializable,
+ // the Group-level context menu is not currently extensible
+ actionContext = e.element;
+ }
+
+ this._contextMenuService.showContextMenu({
+ getAnchor: () => e.anchor,
+ getActions: () => this._actionProvider.getActions(this._tree, e.element),
+ getKeyBinding: (action) => this._keybindingService.lookupKeybinding(action.id),
+ onHide: (wasCancelled?: boolean) => {
+ if (wasCancelled) {
+ this._tree.domFocus();
+ }
+ },
+ getActionsContext: () => (actionContext)
+ });
+
+ return true;
}
}
diff --git a/src/sql/workbench/contrib/objectExplorer/common/serverGroup.contribution.ts b/src/sql/workbench/contrib/objectExplorer/common/serverGroup.contribution.ts
index 48882f1c2d..15aa1ebe69 100644
--- a/src/sql/workbench/contrib/objectExplorer/common/serverGroup.contribution.ts
+++ b/src/sql/workbench/contrib/objectExplorer/common/serverGroup.contribution.ts
@@ -8,6 +8,7 @@ import { Registry } from 'vs/platform/registry/common/platform';
import { IJSONSchema } from 'vs/base/common/jsonSchema';
import { localize } from 'vs/nls';
import { SERVER_GROUP_CONFIG, SERVER_GROUP_COLORS_CONFIG } from 'sql/workbench/services/serverGroup/common/interfaces';
+import { DefaultServerGroupColor } from 'sql/workbench/services/serverGroup/common/serverGroupViewModel';
const configurationRegistry = Registry.as(Extensions.Configuration);
@@ -29,7 +30,7 @@ const serverGroupConfig: IConfigurationNode = {
'#98AFC7',
'#4452A6',
'#6A6599',
- '#515151'
+ DefaultServerGroupColor
]
},
[SERVER_GROUP_CONFIG + '.' + SERVER_GROUP_AUTOEXPAND_CONFIG]: {
@@ -40,4 +41,18 @@ const serverGroupConfig: IConfigurationNode = {
}
};
+const serverTreeConfig: IConfigurationNode = {
+ 'id': 'serverTree',
+ 'title': 'Server Tree',
+ 'type': 'object',
+ 'properties': {
+ 'serverTree.useAsyncServerTree': {
+ 'type': 'boolean',
+ 'default': true,
+ 'description': localize('serverTree.useAsyncServerTree', "(Preview) Use the new async server tree for the Servers view and Connection Dialog with support for new features such as dynamic node filtering.")
+ }
+ }
+};
+
configurationRegistry.registerConfiguration(serverGroupConfig);
+configurationRegistry.registerConfiguration(serverTreeConfig);
diff --git a/src/sql/workbench/contrib/objectExplorer/test/browser/connectionTreeActions.test.ts b/src/sql/workbench/contrib/objectExplorer/test/browser/connectionTreeActions.test.ts
index 6384b6454a..28725e9b61 100644
--- a/src/sql/workbench/contrib/objectExplorer/test/browser/connectionTreeActions.test.ts
+++ b/src/sql/workbench/contrib/objectExplorer/test/browser/connectionTreeActions.test.ts
@@ -20,8 +20,6 @@ import * as LocalizedConstants from 'sql/workbench/services/connection/browser/
import { ObjectExplorerService, ObjectExplorerNodeEventArgs } from 'sql/workbench/services/objectExplorer/browser/objectExplorerService';
import { TreeNode } from 'sql/workbench/services/objectExplorer/common/treeNode';
import { NodeType } from 'sql/workbench/services/objectExplorer/common/nodeType';
-import { Tree } from 'vs/base/parts/tree/browser/treeImpl';
-import { ServerTreeDataSource } from 'sql/workbench/services/objectExplorer/browser/serverTreeDataSource';
import { Emitter, Event } from 'vs/base/common/event';
import Severity from 'vs/base/common/severity';
import { ObjectExplorerActionsContext } from 'sql/workbench/services/objectExplorer/browser/objectExplorerActions';
@@ -35,6 +33,12 @@ import { IViewsService, IView, ViewContainerLocation, ViewContainer, IViewPaneCo
import { ConsoleLogService } from 'vs/platform/log/common/log';
import { IProgressIndicator } from 'vs/platform/progress/common/progress';
import { IPaneComposite } from 'vs/workbench/common/panecomposite';
+import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService';
+import { TestAccessibilityService, TestListService } from 'vs/workbench/test/browser/workbenchTestServices';
+import { TestConfigurationService } from 'sql/platform/connection/test/common/testConfigurationService';
+import { ServerTreeDataSource } from 'sql/workbench/services/objectExplorer/browser/serverTreeDataSource';
+import { Tree } from 'vs/base/parts/tree/browser/treeImpl';
+import { AsyncServerTree } from 'sql/workbench/services/objectExplorer/browser/asyncServerTree';
suite('SQL Connection Tree Action tests', () => {
let errorMessageService: TypeMoq.Mock;
@@ -152,8 +156,14 @@ suite('SQL Connection Tree Action tests', () => {
}
};
- let manageConnectionAction: OEManageConnectionAction = new OEManageConnectionAction(OEManageConnectionAction.ID,
- OEManageConnectionAction.LABEL, connectionManagementService.object, capabilitiesService, instantiationService.object, objectExplorerService.object, viewsService);
+ let manageConnectionAction: OEManageConnectionAction = new OEManageConnectionAction(
+ OEManageConnectionAction.ID,
+ OEManageConnectionAction.LABEL,
+ connectionManagementService.object,
+ capabilitiesService,
+ instantiationService.object,
+ objectExplorerService.object,
+ viewsService);
let actionContext = new ObjectExplorerActionsContext();
actionContext.connectionProfile = connection.toIConnectionProfile();
@@ -190,8 +200,14 @@ suite('SQL Connection Tree Action tests', () => {
return treeSelectionMock.object;
});
- let manageConnectionAction: OEManageConnectionAction = new OEManageConnectionAction(OEManageConnectionAction.ID,
- OEManageConnectionAction.LABEL, connectionManagementService.object, capabilitiesService, instantiationService.object, objectExplorerService.object, undefined);
+ let manageConnectionAction: OEManageConnectionAction = new OEManageConnectionAction(
+ OEManageConnectionAction.ID,
+ OEManageConnectionAction.LABEL,
+ connectionManagementService.object,
+ capabilitiesService,
+ instantiationService.object,
+ objectExplorerService.object,
+ undefined);
let actionContext = new ObjectExplorerActionsContext();
actionContext.connectionProfile = connection.toIConnectionProfile();
@@ -444,7 +460,7 @@ suite('SQL Connection Tree Action tests', () => {
tree.setup(x => x.refresh(TypeMoq.It.isAny())).returns(() => Promise.resolve(null));
tree.setup(x => x.expand(TypeMoq.It.isAny())).returns(() => Promise.resolve(null));
tree.setup(x => x.collapse(TypeMoq.It.isAny())).returns(() => Promise.resolve(null));
- let connectionAction: RefreshAction = new RefreshAction(RefreshAction.ID,
+ let refreshAction: RefreshAction = new RefreshAction(RefreshAction.ID,
RefreshAction.LABEL,
tree.object,
connection,
@@ -453,7 +469,7 @@ suite('SQL Connection Tree Action tests', () => {
undefined,
logService);
- return connectionAction.run().then((value) => {
+ return refreshAction.run().then((value) => {
connectionManagementService.verify(x => x.isConnected(undefined, TypeMoq.It.isAny()), TypeMoq.Times.atLeastOnce());
objectExplorerService.verify(x => x.getObjectExplorerNode(TypeMoq.It.isAny()), TypeMoq.Times.atLeastOnce());
objectExplorerService.verify(x => x.refreshTreeNode(TypeMoq.It.isAny(), TypeMoq.It.isAny()), TypeMoq.Times.atLeastOnce());
@@ -547,6 +563,200 @@ suite('SQL Connection Tree Action tests', () => {
});
});
+ // chgagnon TODO - skipping for now since mocking and instanceof don't work well together. Will re-enable once old tree is removed
+ test.skip('RefreshConnectionAction - AsyncServerTree - refresh should be called if connection status is connect', () => {
+ let isConnectedReturnValue: boolean = true;
+ let sqlProvider = {
+ providerId: mssqlProviderName,
+ displayName: 'MSSQL',
+ connectionOptions: [],
+ };
+
+ capabilitiesService.capabilities[mssqlProviderName] = { connection: sqlProvider };
+
+ let connection = new ConnectionProfile(capabilitiesService, {
+ connectionName: 'Test',
+ savePassword: false,
+ groupFullName: 'testGroup',
+ serverName: 'testServerName',
+ databaseName: 'testDatabaseName',
+ authenticationType: 'inetgrated',
+ password: 'test',
+ userName: 'testUsername',
+ groupId: undefined,
+ providerName: mssqlProviderName,
+ options: {},
+ saveProfile: true,
+ id: 'testID'
+ });
+ let conProfGroup = new ConnectionProfileGroup('testGroup', undefined, 'testGroup', undefined, undefined);
+ conProfGroup.connections = [connection];
+ let connectionManagementService = TypeMoq.Mock.ofType(TestConnectionManagementService, TypeMoq.MockBehavior.Strict);
+ connectionManagementService.callBase = true;
+ connectionManagementService.setup(x => x.getConnectionGroups()).returns(() => [conProfGroup]);
+ connectionManagementService.setup(x => x.getActiveConnections()).returns(() => [connection]);
+ connectionManagementService.setup(x => x.addSavedPassword(TypeMoq.It.isAny())).returns(() => new Promise((resolve) => {
+ resolve(connection);
+ }));
+ connectionManagementService.setup(x => x.isConnected(undefined, TypeMoq.It.isAny())).returns(() => isConnectedReturnValue);
+
+ let objectExplorerSession = {
+ success: true,
+ sessionId: '1234',
+ rootNode: {
+ nodePath: 'testServerName\tables',
+ nodeType: NodeType.Folder,
+ label: 'Tables',
+ isLeaf: false,
+ metadata: null,
+ nodeSubType: '',
+ nodeStatus: '',
+ errorMessage: ''
+ },
+ errorMessage: ''
+ };
+
+ let tablesNode = new TreeNode(NodeType.Folder, 'Tables', false, 'testServerName\Db1\tables', '', '', null, null, undefined, undefined);
+ tablesNode.connection = connection;
+ tablesNode.session = objectExplorerSession;
+ let table1Node = new TreeNode(NodeType.Table, 'dbo.Table1', false, 'testServerName\tables\dbo.Table1', '', '', tablesNode, null, undefined, undefined);
+ let table2Node = new TreeNode(NodeType.Table, 'dbo.Table1', false, 'testServerName\tables\dbo.Table1', '', '', tablesNode, null, undefined, undefined);
+ tablesNode.children = [table1Node, table2Node];
+ let objectExplorerService = TypeMoq.Mock.ofType(ObjectExplorerService, TypeMoq.MockBehavior.Loose, connectionManagementService.object);
+ objectExplorerService.callBase = true;
+ objectExplorerService.setup(x => x.getObjectExplorerNode(TypeMoq.It.isAny())).returns(() => tablesNode);
+ objectExplorerService.setup(x => x.refreshTreeNode(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve([table1Node, table2Node]));
+ const treeMock = TypeMoq.Mock.ofType(AsyncServerTree, TypeMoq.MockBehavior.Strict,
+ 'ConnectionTreeActionsTest', // user
+ $('div'), // container
+ {}, // delegate
+ [], // renderers
+ {}, // data source
+ {}, // options
+ new MockContextKeyService(), // IContextKeyService
+ new TestListService(), // IListService,
+ undefined, // IThemeService,
+ new TestConfigurationService(), // IConfigurationService,
+ undefined, // IKeybindingService,
+ new TestAccessibilityService()); // IAccessibilityService
+ treeMock.callBase = true;
+ treeMock.setup(x => x.expand(TypeMoq.It.isAny())).returns(() => Promise.resolve(null));
+ treeMock.setup(x => x.collapse(TypeMoq.It.isAny())).returns(() => true);
+ treeMock.setup(x => x.updateChildren(TypeMoq.It.isAny())).returns(() => Promise.resolve());
+ let refreshAction: RefreshAction = new RefreshAction(RefreshAction.ID,
+ RefreshAction.LABEL,
+ treeMock.instance,
+ connection,
+ connectionManagementService.object,
+ objectExplorerService.object,
+ undefined,
+ logService);
+
+ return refreshAction.run().then((value) => {
+ connectionManagementService.verify(x => x.isConnected(undefined, TypeMoq.It.isAny()), TypeMoq.Times.atLeastOnce());
+ objectExplorerService.verify(x => x.getObjectExplorerNode(TypeMoq.It.isAny()), TypeMoq.Times.atLeastOnce());
+ objectExplorerService.verify(x => x.refreshTreeNode(TypeMoq.It.isAny(), TypeMoq.It.isAny()), TypeMoq.Times.atLeastOnce());
+ treeMock.verify(x => x.updateChildren(TypeMoq.It.isAny()), TypeMoq.Times.atLeastOnce());
+ });
+ });
+
+ test('RefreshConnectionAction - AsyncServerTree - refresh should not be called if connection status is not connect', () => {
+ let isConnectedReturnValue: boolean = false;
+ let sqlProvider = {
+ providerId: mssqlProviderName,
+ displayName: 'MSSQL',
+ connectionOptions: []
+ };
+
+ capabilitiesService.capabilities[mssqlProviderName] = { connection: sqlProvider };
+
+ let connection = new ConnectionProfile(capabilitiesService, {
+ connectionName: 'Test',
+ savePassword: false,
+ groupFullName: 'testGroup',
+ serverName: 'testServerName',
+ databaseName: 'testDatabaseName',
+ authenticationType: 'inetgrated',
+ password: 'test',
+ userName: 'testUsername',
+ groupId: undefined,
+ providerName: mssqlProviderName,
+ options: {},
+ saveProfile: true,
+ id: 'testID'
+ });
+ let conProfGroup = new ConnectionProfileGroup('testGroup', undefined, 'testGroup', undefined, undefined);
+ conProfGroup.connections = [connection];
+ let connectionManagementService = TypeMoq.Mock.ofType(TestConnectionManagementService, TypeMoq.MockBehavior.Strict);
+ connectionManagementService.callBase = true;
+ connectionManagementService.setup(x => x.getConnectionGroups()).returns(() => [conProfGroup]);
+ connectionManagementService.setup(x => x.getActiveConnections()).returns(() => [connection]);
+ connectionManagementService.setup(x => x.addSavedPassword(TypeMoq.It.isAny())).returns(() => new Promise((resolve) => {
+ resolve(connection);
+ }));
+ connectionManagementService.setup(x => x.isConnected(undefined, TypeMoq.It.isAny())).returns(() => isConnectedReturnValue);
+
+ let objectExplorerSession = {
+ success: true,
+ sessionId: '1234',
+ rootNode: {
+ nodePath: 'testServerName\tables',
+ nodeType: NodeType.Folder,
+ label: 'Tables',
+ isLeaf: false,
+ metadata: null,
+ nodeSubType: '',
+ nodeStatus: '',
+ errorMessage: ''
+ },
+ errorMessage: ''
+ };
+
+ let tablesNode = new TreeNode(NodeType.Folder, 'Tables', false, 'testServerName\Db1\tables', '', '', null, null, undefined, undefined);
+ tablesNode.connection = connection;
+ tablesNode.session = objectExplorerSession;
+ let table1Node = new TreeNode(NodeType.Table, 'dbo.Table1', false, 'testServerName\tables\dbo.Table1', '', '', tablesNode, null, undefined, undefined);
+ let table2Node = new TreeNode(NodeType.Table, 'dbo.Table1', false, 'testServerName\tables\dbo.Table1', '', '', tablesNode, null, undefined, undefined);
+ tablesNode.children = [table1Node, table2Node];
+ let objectExplorerService = TypeMoq.Mock.ofType(ObjectExplorerService, TypeMoq.MockBehavior.Loose, connectionManagementService.object);
+ objectExplorerService.callBase = true;
+ objectExplorerService.setup(x => x.getObjectExplorerNode(TypeMoq.It.isAny())).returns(() => tablesNode);
+ objectExplorerService.setup(x => x.refreshTreeNode(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => Promise.resolve([table1Node, table2Node]));
+ let tree = TypeMoq.Mock.ofType(AsyncServerTree, TypeMoq.MockBehavior.Loose,
+ 'ConnectionTreeActionsTest', // user
+ $('div'), // container
+ {}, // delegate
+ [], // renderers
+ {}, // data source
+ {}, // options
+ new MockContextKeyService(), // IContextKeyService
+ new TestListService(), // IListService,
+ undefined, // IThemeService,
+ new TestConfigurationService(), // IConfigurationService,
+ undefined, // IKeybindingService,
+ new TestAccessibilityService()); // IAccessibilityService
+ tree.callBase = true;
+
+ tree.setup(x => x.updateChildren(TypeMoq.It.isAny())).returns(() => Promise.resolve());
+ tree.setup(x => x.expand(TypeMoq.It.isAny())).returns(() => Promise.resolve(null));
+ let connectionAction: RefreshAction = new RefreshAction(RefreshAction.ID,
+ RefreshAction.LABEL,
+ tree.object,
+ connection,
+ connectionManagementService.object,
+ objectExplorerService.object,
+ undefined,
+ logService);
+
+ return connectionAction.run().then((value) => {
+ connectionManagementService.verify(x => x.isConnected(undefined, TypeMoq.It.isAny()), TypeMoq.Times.atLeastOnce());
+ objectExplorerService.verify(x => x.getObjectExplorerNode(TypeMoq.It.isAny()), TypeMoq.Times.exactly(0));
+ objectExplorerService.verify(x => x.refreshTreeNode(TypeMoq.It.isAny(), TypeMoq.It.isAny()), TypeMoq.Times.exactly(0));
+ tree.verify(x => x.updateChildren(TypeMoq.It.isAny()), TypeMoq.Times.exactly(0));
+ tree.verify(x => x.expand(TypeMoq.It.isAny()), TypeMoq.Times.exactly(0));
+ });
+ });
+
test('EditConnectionAction - test if show connection dialog is called', () => {
let connectionManagementService = createConnectionManagementService(true, undefined);
diff --git a/src/sql/workbench/contrib/objectExplorer/test/browser/serverTreeView.test.ts b/src/sql/workbench/contrib/objectExplorer/test/browser/serverTreeView.test.ts
index dcef94428b..40a070e2f7 100644
--- a/src/sql/workbench/contrib/objectExplorer/test/browser/serverTreeView.test.ts
+++ b/src/sql/workbench/contrib/objectExplorer/test/browser/serverTreeView.test.ts
@@ -39,7 +39,7 @@ suite('ServerTreeView onAddConnectionProfile handler tests', () => {
);
mockConnectionManagementService.setup(x => x.getConnectionGroups()).returns(x => []);
mockConnectionManagementService.setup(x => x.hasRegisteredServers()).returns(() => true);
- serverTreeView = new ServerTreeView(mockConnectionManagementService.object, instantiationService, undefined, new TestThemeService(), undefined, undefined, capabilitiesService);
+ serverTreeView = new ServerTreeView(mockConnectionManagementService.object, instantiationService, undefined, new TestThemeService(), undefined, undefined, capabilitiesService, undefined, undefined);
mockTree = TypeMoq.Mock.ofType(TestTree);
(serverTreeView as any)._tree = mockTree.object;
mockRefreshTreeMethod = TypeMoq.Mock.ofType(Function);
@@ -64,15 +64,6 @@ suite('ServerTreeView onAddConnectionProfile handler tests', () => {
mockTree.verify(x => x.layout(TypeMoq.It.isAnyNumber()), TypeMoq.Times.once());
});
- test('setVisibility', async () => {
- mockTree.setup(x => x.onVisible());
- mockTree.setup(x => x.onHidden());
- serverTreeView.setVisible(true);
- mockTree.verify(x => x.onVisible(), TypeMoq.Times.once());
- serverTreeView.setVisible(false);
- mockTree.verify(x => x.onHidden(), TypeMoq.Times.once());
- });
-
test('getSelection', async () => {
mockTree.setup(x => x.getSelection());
@@ -81,9 +72,9 @@ suite('ServerTreeView onAddConnectionProfile handler tests', () => {
});
test('isFocused', async () => {
- mockTree.setup(x => x.isDOMFocused());
+ mockTree.setup(x => x.getHTMLElement());
serverTreeView.isFocused();
- mockTree.verify(x => x.isDOMFocused(), TypeMoq.Times.once());
+ mockTree.verify(x => x.getHTMLElement(), TypeMoq.Times.once());
});
test('reveal', async () => {
diff --git a/src/sql/workbench/contrib/scripting/browser/scriptingActions.ts b/src/sql/workbench/contrib/scripting/browser/scriptingActions.ts
index c3bda5f57a..685d416bff 100644
--- a/src/sql/workbench/contrib/scripting/browser/scriptingActions.ts
+++ b/src/sql/workbench/contrib/scripting/browser/scriptingActions.ts
@@ -27,6 +27,7 @@ import { getErrorMessage } from 'vs/base/common/errors';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { localize } from 'vs/nls';
import { ServicesAccessor } from 'vs/editor/browser/editorExtensions';
+import { AsyncServerTree } from 'sql/workbench/services/objectExplorer/browser/asyncServerTree';
//#region -- Data Explorer
export const SCRIPT_AS_CREATE_COMMAND_ID = 'dataExplorer.scriptAsCreate';
@@ -322,7 +323,11 @@ export async function handleOeRefreshCommand(accessor: ServicesAccessor, args: O
const tree = objectExplorerService.getServerTreeView().tree;
try {
await objectExplorerService.refreshTreeNode(treeNode.getSession(), treeNode);
- await tree.refresh(treeNode);
+ if (tree instanceof AsyncServerTree) {
+ await tree.updateChildren(treeNode);
+ } else {
+ await tree.refresh(treeNode);
+ }
} catch (err) {
// Display message to the user but also log the entire error to the console for the stack trace
notificationService.error(localize('refreshError', "An error occurred refreshing node '{0}': {1}", args.nodeInfo.label, getErrorMessage(err)));
diff --git a/src/sql/workbench/services/connection/browser/connectionDialogWidget.ts b/src/sql/workbench/services/connection/browser/connectionDialogWidget.ts
index a48fc71566..0b7647f5bd 100644
--- a/src/sql/workbench/services/connection/browser/connectionDialogWidget.ts
+++ b/src/sql/workbench/services/connection/browser/connectionDialogWidget.ts
@@ -10,6 +10,7 @@ import { SelectBox } from 'sql/base/browser/ui/selectBox/selectBox';
import { Modal } from 'sql/workbench/browser/modal/modal';
import { IConnectionManagementService, INewConnectionParams } from 'sql/platform/connection/common/connectionManagement';
import * as DialogHelper from 'sql/workbench/browser/modal/dialogHelper';
+import { TreeCreationUtils } from 'sql/workbench/services/objectExplorer/browser/treeCreationUtils';
import { TabbedPanel, PanelTabIdentifier } from 'sql/base/browser/ui/panel/panel';
import * as TelemetryKeys from 'sql/platform/telemetry/common/telemetryKeys';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
@@ -39,15 +40,16 @@ import { ViewPane, IPaneColors } from 'vs/workbench/browser/parts/views/viewPane
import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet';
import { ITreeView } from 'sql/workbench/common/views';
import { IConnectionProfile } from 'azdata';
-import { ITree } from 'vs/base/parts/tree/browser/tree';
import { TreeUpdateUtils, IExpandableTree } from 'sql/workbench/services/objectExplorer/browser/treeUpdateUtils';
import { SavedConnectionTreeController } from 'sql/workbench/services/connection/browser/savedConnectionTreeController';
-import { TreeCreationUtils } from 'sql/workbench/services/objectExplorer/browser/treeCreationUtils';
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
import { ICancelableEvent } from 'vs/base/parts/tree/browser/treeDefaults';
-import { RecentConnectionTreeController, RecentConnectionActionsProvider } from 'sql/workbench/services/connection/browser/recentConnectionTreeController';
+import { RecentConnectionActionsProvider, RecentConnectionTreeController } from 'sql/workbench/services/connection/browser/recentConnectionTreeController';
import { ClearRecentConnectionsAction } from 'sql/workbench/services/connection/browser/connectionActions';
import { combinedDisposable, IDisposable, dispose } from 'vs/base/common/lifecycle';
+import { ITree } from 'vs/base/parts/tree/browser/tree';
+import { AsyncServerTree, ServerTreeElement } from 'sql/workbench/services/objectExplorer/browser/asyncServerTree';
+import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
export interface OnShowUIResponse {
selectedProviderDisplayName: string;
@@ -65,8 +67,8 @@ export class ConnectionDialogWidget extends Modal implements IViewPaneContainer
private _closeButton: Button;
private _providerTypeSelectBox: SelectBox;
private _newConnectionParams: INewConnectionParams;
- private _recentConnectionTree: ITree;
- private _savedConnectionTree: ITree;
+ private _recentConnectionTree: AsyncServerTree | ITree;
+ private _savedConnectionTree: AsyncServerTree | ITree;
private _connectionUIContainer: HTMLElement;
private _databaseDropdownExpanded: boolean;
private _actionbar: ActionBar;
@@ -115,7 +117,8 @@ export class ConnectionDialogWidget extends Modal implements IViewPaneContainer
@IContextKeyService contextKeyService: IContextKeyService,
@IClipboardService clipboardService: IClipboardService,
@ILogService logService: ILogService,
- @ITextResourcePropertiesService textResourcePropertiesService: ITextResourcePropertiesService
+ @ITextResourcePropertiesService textResourcePropertiesService: ITextResourcePropertiesService,
+ @IConfigurationService private _configurationService: IConfigurationService
) {
super(
localize('connection', "Connection"),
@@ -222,21 +225,37 @@ export class ConnectionDialogWidget extends Modal implements IViewPaneContainer
});
this._panel.onTabChange(async c => {
- // convert to old VS Code tree interface with expandable methods
- const expandableTree: IExpandableTree = this._savedConnectionTree;
+ if (this._savedConnectionTree instanceof AsyncServerTree) {
+ if (c === savedConnectionTabId && this._savedConnectionTree.contentHeight === 0) {
+ // Update saved connection tree
+ await TreeUpdateUtils.structuralTreeUpdate(this._savedConnectionTree, 'saved', this.connectionManagementService, this._providers);
- if (c === savedConnectionTabId && expandableTree.getContentHeight() === 0) {
- // Update saved connection tree
- await TreeUpdateUtils.structuralTreeUpdate(this._savedConnectionTree, 'saved', this.connectionManagementService, this._providers);
-
- if (expandableTree.getContentHeight() > 0) {
- DOM.hide(this._noSavedConnection);
- DOM.show(this._savedConnection);
- } else {
- DOM.show(this._noSavedConnection);
- DOM.hide(this._savedConnection);
+ if (this._savedConnectionTree.contentHeight > 0) {
+ DOM.hide(this._noSavedConnection);
+ DOM.show(this._savedConnection);
+ } else {
+ DOM.show(this._noSavedConnection);
+ DOM.hide(this._savedConnection);
+ }
+ this._savedConnectionTree.layout(DOM.getTotalHeight(this._savedConnectionTree.getHTMLElement()));
+ }
+ } else {
+ // convert to old VS Code tree interface with expandable methods
+ const expandableTree: IExpandableTree = this._savedConnectionTree;
+
+ if (c === savedConnectionTabId && expandableTree.getContentHeight() === 0) {
+ // Update saved connection tree
+ await TreeUpdateUtils.structuralTreeUpdate(this._savedConnectionTree, 'saved', this.connectionManagementService, this._providers);
+
+ if (expandableTree.getContentHeight() > 0) {
+ DOM.hide(this._noSavedConnection);
+ DOM.show(this._savedConnection);
+ } else {
+ DOM.show(this._noSavedConnection);
+ DOM.hide(this._savedConnection);
+ }
+ this._savedConnectionTree.layout(DOM.getTotalHeight(this._savedConnectionTree.getHTMLElement()));
}
- this._savedConnectionTree.layout(DOM.getTotalHeight(this._savedConnectionTree.getHTMLElement()));
}
});
@@ -362,9 +381,7 @@ export class ConnectionDialogWidget extends Modal implements IViewPaneContainer
const leftClick = (element: any, eventish: ICancelableEvent, origin: string) => {
// element will be a server group if the tree is clicked rather than a item
const isDoubleClick = origin === 'mouse' && (eventish as MouseEvent).detail === 2;
- if (element instanceof ConnectionProfile) {
- this.onConnectionClick(element, isDoubleClick);
- }
+ this.onConnectionClick(element, isDoubleClick);
};
const actionProvider = this.instantiationService.createInstance(RecentConnectionActionsProvider);
const controller = new RecentConnectionTreeController(leftClick, actionProvider, this.connectionManagementService, this.contextMenuService);
@@ -380,7 +397,11 @@ export class ConnectionDialogWidget extends Modal implements IViewPaneContainer
// We're just using the connections to determine if there are connections to show, dispose them right after to clean up their handlers
recentConnections.forEach(conn => conn.dispose());
});
- this._recentConnectionTree = TreeCreationUtils.createConnectionTree(treeContainer, this.instantiationService, controller);
+ this._recentConnectionTree = TreeCreationUtils.createConnectionTree(treeContainer, this.instantiationService, this._configurationService, localize('connectionDialog.recentConnections', "Recent Connections"), controller);
+ if (this._recentConnectionTree instanceof AsyncServerTree) {
+ this._recentConnectionTree.onMouseClick(e => this.onConnectionClick(e.element, false));
+ this._recentConnectionTree.onMouseDblClick(e => this.onConnectionClick(e.element, true));
+ }
// Theme styler
this._register(styler.attachListStyler(this._recentConnectionTree, this._themeService));
@@ -406,7 +427,11 @@ export class ConnectionDialogWidget extends Modal implements IViewPaneContainer
};
const controller = new SavedConnectionTreeController(leftClick);
- this._savedConnectionTree = TreeCreationUtils.createConnectionTree(treeContainer, this.instantiationService, controller);
+ this._savedConnectionTree = TreeCreationUtils.createConnectionTree(treeContainer, this.instantiationService, this._configurationService, localize('connectionDialog.savedConnections', "Saved Connections"), controller);
+ if (this._savedConnectionTree instanceof AsyncServerTree) {
+ this._savedConnectionTree.onMouseClick(e => this.onConnectionClick(e.element, false));
+ this._savedConnectionTree.onMouseDblClick(e => this.onConnectionClick(e.element, true));
+ }
// Theme styler
this._register(styler.attachListStyler(this._savedConnectionTree, this._themeService));
@@ -419,7 +444,10 @@ export class ConnectionDialogWidget extends Modal implements IViewPaneContainer
DOM.append(noSavedConnectionContainer, DOM.$('.no-saved-connections')).innerText = noSavedConnectionLabel;
}
- private onConnectionClick(element: IConnectionProfile, connect: boolean = false) {
+ private onConnectionClick(element: ServerTreeElement, connect: boolean = false): void {
+ if (!(element instanceof ConnectionProfile)) {
+ return;
+ }
if (connect) {
this.connect(element);
} else {
@@ -443,9 +471,12 @@ export class ConnectionDialogWidget extends Modal implements IViewPaneContainer
DOM.show(this._noRecentConnection);
}
await TreeUpdateUtils.structuralTreeUpdate(this._recentConnectionTree, 'recent', this.connectionManagementService, this._providers);
+ this._recentConnectionTree.layout(DOM.getTotalHeight(this._recentConnectionTree.getHTMLElement()));
- // // reset saved connection tree
- await this._savedConnectionTree.setInput([]);
+ if (!(this._savedConnectionTree instanceof AsyncServerTree)) {
+ // reset saved connection tree
+ await this._savedConnectionTree.setInput([]);
+ }
// call layout with view height
this.initDialog();
@@ -616,7 +647,7 @@ export class ConnectionDialogWidget extends Modal implements IViewPaneContainer
const disposable = combinedDisposable(pane, paneStyler);
const paneItem = { pane, disposable };
treeView.onDidChangeSelection(e => {
- if (e.length > 0 && e[0].payload) {
+ if (e.length > 0 && e[0].payload instanceof ConnectionProfile) {
this.onConnectionClick(e[0].payload);
}
});
diff --git a/src/sql/workbench/services/connection/browser/connectionManagementService.ts b/src/sql/workbench/services/connection/browser/connectionManagementService.ts
index 4a41a673f8..ffcbc465b0 100644
--- a/src/sql/workbench/services/connection/browser/connectionManagementService.ts
+++ b/src/sql/workbench/services/connection/browser/connectionManagementService.ts
@@ -995,6 +995,7 @@ export class ConnectionManagementService extends Disposable implements IConnecti
let id = Utils.generateUri(source);
this._telemetryService.sendActionEvent(TelemetryKeys.TelemetryView.Shell, TelemetryKeys.MoveServerGroup);
return this._connectionStore.changeGroupIdForConnection(source, targetGroupId).then(result => {
+ this._onAddConnectionProfile.fire(source);
if (id && targetGroupId) {
source.groupId = targetGroupId;
}
diff --git a/src/sql/workbench/services/connection/browser/media/connectionDialog.css b/src/sql/workbench/services/connection/browser/media/connectionDialog.css
index 0907b33bdc..3f74be6da1 100644
--- a/src/sql/workbench/services/connection/browser/media/connectionDialog.css
+++ b/src/sql/workbench/services/connection/browser/media/connectionDialog.css
@@ -162,3 +162,8 @@
width: 8px;
height: 8px;
}
+
+.connection-dialog .explorer-servers .connection-profile .monaco-tl-twistie {
+ /* Hide twisties */
+ display: none !important;
+}
diff --git a/src/sql/workbench/services/objectExplorer/browser/asyncRecentConnectionTreeDataSource.ts b/src/sql/workbench/services/objectExplorer/browser/asyncRecentConnectionTreeDataSource.ts
new file mode 100644
index 0000000000..f31724bed2
--- /dev/null
+++ b/src/sql/workbench/services/objectExplorer/browser/asyncRecentConnectionTreeDataSource.ts
@@ -0,0 +1,35 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the Source EULA. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { ConnectionProfileGroup } from 'sql/platform/connection/common/connectionProfileGroup';
+import { IAsyncDataSource } from 'vs/base/browser/ui/tree/tree';
+import { ServerTreeElement } from 'sql/workbench/services/objectExplorer/browser/asyncServerTree';
+
+/**
+ * Implements the DataSource(that returns a parent/children of an element) for the recent connection tree
+ */
+export class AsyncRecentConnectionTreeDataSource implements IAsyncDataSource {
+
+ /**
+ * Returns a boolean value indicating whether the element has children.
+ */
+ public hasChildren(element: ServerTreeElement): boolean {
+ if (element instanceof ConnectionProfileGroup) {
+ return element.hasChildren();
+ }
+ return false;
+ }
+
+ /**
+ * Returns the element's children as an array in a promise.
+ */
+ public async getChildren(element: ServerTreeElement): Promise> {
+ if (element instanceof ConnectionProfileGroup) {
+ return element.getChildren();
+ } else {
+ return [];
+ }
+ }
+}
diff --git a/src/sql/workbench/services/objectExplorer/browser/asyncServerTree.ts b/src/sql/workbench/services/objectExplorer/browser/asyncServerTree.ts
new file mode 100644
index 0000000000..c2c698a98e
--- /dev/null
+++ b/src/sql/workbench/services/objectExplorer/browser/asyncServerTree.ts
@@ -0,0 +1,14 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the Source EULA. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { ConnectionProfileGroup } from 'sql/platform/connection/common/connectionProfileGroup';
+import { WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService';
+import { FuzzyScore } from 'vs/base/common/filters';
+import { TreeNode } from 'sql/workbench/services/objectExplorer/common/treeNode';
+import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
+
+export class AsyncServerTree extends WorkbenchAsyncDataTree { }
+
+export type ServerTreeElement = ConnectionProfile | ConnectionProfileGroup | TreeNode;
diff --git a/src/sql/workbench/services/objectExplorer/browser/asyncServerTreeDataSource.ts b/src/sql/workbench/services/objectExplorer/browser/asyncServerTreeDataSource.ts
new file mode 100644
index 0000000000..5cd182cd11
--- /dev/null
+++ b/src/sql/workbench/services/objectExplorer/browser/asyncServerTreeDataSource.ts
@@ -0,0 +1,76 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the Source EULA. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { ConnectionProfileGroup } from 'sql/platform/connection/common/connectionProfileGroup';
+import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
+import { TreeNode } from 'sql/workbench/services/objectExplorer/common/treeNode';
+import { IObjectExplorerService } from 'sql/workbench/services/objectExplorer/browser/objectExplorerService';
+import { TreeUpdateUtils } from 'sql/workbench/services/objectExplorer/browser/treeUpdateUtils';
+import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
+import Severity from 'vs/base/common/severity';
+import { IErrorMessageService } from 'sql/platform/errorMessage/common/errorMessageService';
+import { IAsyncDataSource } from 'vs/base/browser/ui/tree/tree';
+import { ServerTreeElement } from 'sql/workbench/services/objectExplorer/browser/asyncServerTree';
+
+/**
+ * Implements the DataSource(that returns a parent/children of an element) for the server tree
+ */
+export class AsyncServerTreeDataSource implements IAsyncDataSource {
+
+ constructor(
+ @IObjectExplorerService private _objectExplorerService: IObjectExplorerService,
+ @IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
+ @IErrorMessageService private _errorMessageService: IErrorMessageService
+ ) {
+ }
+ /**
+ * Returns a boolean value indicating whether the element has children.
+ */
+ public hasChildren(element: ServerTreeElement): boolean {
+ if (element instanceof ConnectionProfile) {
+ return true;
+ } else if (element instanceof ConnectionProfileGroup) {
+ return element.hasChildren();
+ } else if (element instanceof TreeNode) {
+ return !element.isAlwaysLeaf;
+ }
+ return false;
+ }
+
+ /**
+ * Returns the element's children as an array in a promise.
+ */
+ public async getChildren(element: ServerTreeElement): Promise {
+ try {
+ if (element instanceof ConnectionProfile) {
+ return await TreeUpdateUtils.getAsyncConnectionNodeChildren(element, this._connectionManagementService, this._objectExplorerService);
+ } else if (element instanceof ConnectionProfileGroup) {
+ return (element as ConnectionProfileGroup).getChildren();
+ } else if (element instanceof TreeNode) {
+ if (element.children) {
+ return element.children;
+ } else {
+ return await this._objectExplorerService.resolveTreeNodeChildren(element.getSession(), element);
+ }
+ }
+ } catch (err) {
+ if (element instanceof TreeNode) {
+ element.errorStateMessage = err.message ?? err;
+ }
+ if (err.message) {
+ this.showError(err.message);
+ }
+
+ throw err;
+ }
+ return [];
+ }
+
+ private showError(errorMessage: string) {
+ if (this._errorMessageService) {
+ this._errorMessageService.showDialog(Severity.Error, '', errorMessage);
+ }
+ }
+}
diff --git a/src/sql/workbench/services/objectExplorer/browser/asyncServerTreeDelegate.ts b/src/sql/workbench/services/objectExplorer/browser/asyncServerTreeDelegate.ts
new file mode 100644
index 0000000000..5fddf52397
--- /dev/null
+++ b/src/sql/workbench/services/objectExplorer/browser/asyncServerTreeDelegate.ts
@@ -0,0 +1,33 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the Source EULA. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
+import { ServerTreeRenderer } from 'sql/workbench/services/objectExplorer/browser/serverTreeRenderer';
+import { ConnectionProfileGroup } from 'sql/platform/connection/common/connectionProfileGroup';
+import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
+import { ServerTreeElement } from 'sql/workbench/services/objectExplorer/browser/asyncServerTree';
+
+export class AsyncServerTreeDelegate implements IListVirtualDelegate {
+
+ getHeight(element: ServerTreeElement): number {
+ if (element instanceof ConnectionProfileGroup) {
+ return ServerTreeRenderer.CONNECTION_GROUP_HEIGHT;
+ } else if (element instanceof ConnectionProfile) {
+ return ServerTreeRenderer.CONNECTION_HEIGHT;
+ } else {
+ return ServerTreeRenderer.OBJECTEXPLORER_HEIGHT;
+ }
+ }
+
+ getTemplateId(element: ServerTreeElement): string {
+ if (element instanceof ConnectionProfileGroup) {
+ return ServerTreeRenderer.CONNECTION_GROUP_TEMPLATE_ID;
+ } else if (element instanceof ConnectionProfile) {
+ return ServerTreeRenderer.CONNECTION_TEMPLATE_ID;
+ } else {
+ return ServerTreeRenderer.OBJECTEXPLORER_TEMPLATE_ID;
+ }
+ }
+}
diff --git a/src/sql/workbench/services/objectExplorer/browser/asyncServerTreeDragAndDrop.ts b/src/sql/workbench/services/objectExplorer/browser/asyncServerTreeDragAndDrop.ts
new file mode 100644
index 0000000000..f1689594e5
--- /dev/null
+++ b/src/sql/workbench/services/objectExplorer/browser/asyncServerTreeDragAndDrop.ts
@@ -0,0 +1,124 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the Source EULA. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { ConnectionProfileGroup } from 'sql/platform/connection/common/connectionProfileGroup';
+import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
+import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
+import { TreeUpdateUtils } from 'sql/workbench/services/objectExplorer/browser/treeUpdateUtils';
+import { IDragAndDropData } from 'vs/base/browser/dnd';
+import { ITreeDragAndDrop, ITreeDragOverReaction, TreeDragOverReactions } from 'vs/base/browser/ui/tree/tree';
+import { ServerTreeDragAndDrop } from 'sql/workbench/services/objectExplorer/browser/dragAndDropController';
+import { IDragAndDrop } from 'vs/base/parts/tree/browser/tree';
+import { ServerTreeElement } from 'sql/workbench/services/objectExplorer/browser/asyncServerTree';
+
+/**
+ * Implements drag and drop for the server tree
+ */
+export class AsyncServerTreeDragAndDrop implements ITreeDragAndDrop {
+
+ private _dragAndDrop: IDragAndDrop;
+
+ constructor(
+ @IConnectionManagementService connectionManagementService: IConnectionManagementService,
+ ) {
+ this._dragAndDrop = new ServerTreeDragAndDrop(connectionManagementService);
+ }
+
+ /**
+ * Returns a uri if the given element should be allowed to drag.
+ * Returns null, otherwise.
+ */
+ public getDragURI(element: ServerTreeElement): string {
+ return this._dragAndDrop.getDragURI(undefined, element);
+ }
+
+ /**
+ * Returns a label(name) to display when dragging the element.
+ */
+ public getDragLabel(elements: ServerTreeElement[]): string {
+ return this._dragAndDrop.getDragLabel(undefined, elements);
+ }
+
+ /**
+ * Called when the drag operation starts.
+ */
+ public onDragStart(dragAndDropData: IDragAndDropData, originalEvent: DragEvent): void {
+ // Force the event cast while in preview - we don't use any of the mouse properties on the
+ // implementation so this is fine for now
+ return this._dragAndDrop.onDragStart(undefined, dragAndDropData, originalEvent);
+ }
+
+ public onDragOver(data: IDragAndDropData, targetElement: ServerTreeElement, targetIndex: number, originalEvent: DragEvent): boolean | ITreeDragOverReaction {
+ // Force the event cast while in preview - we don't use any of the mouse properties on the
+ // implementation so this is fine for now
+ const canDragOver = this._dragAndDrop.onDragOver(undefined, data, targetElement, originalEvent);
+
+ if (canDragOver.accept) {
+ return TreeDragOverReactions.acceptBubbleDown(true);
+ } else {
+ return { accept: false };
+ }
+ }
+
+ /**
+ * Handle a drop in the server tree.
+ */
+ public drop(data: IDragAndDropData, targetElement: ServerTreeElement, targetIndex: number, originalEvent: DragEvent): void {
+ // Force the event cast while in preview - we don't use any of the mouse properties on the
+ // implementation so this is fine for now
+
+ // TODO: chgagnon Drop on root node
+ this._dragAndDrop.drop(undefined, data, targetElement, originalEvent);
+ }
+
+ public onDragEnd(originalEvent: DragEvent): void {
+ TreeUpdateUtils.isInDragAndDrop = false;
+ }
+}
+
+export class AsyncRecentConnectionsDragAndDrop implements ITreeDragAndDrop {
+
+ /**
+ * Returns a uri if the given element should be allowed to drag.
+ * Returns null, otherwise.
+ */
+ public getDragURI(element: ServerTreeElement): string | null {
+ if (element instanceof ConnectionProfile) {
+ return (element).id;
+ }
+ else if (element instanceof ConnectionProfileGroup) {
+ return (element).id;
+ }
+ return null;
+ }
+
+ /**
+ * Returns a label(name) to display when dragging the element.
+ */
+ public getDragLabel(elements: ServerTreeElement[]): string {
+ if (elements[0] instanceof ConnectionProfile) {
+ return elements[0].serverName;
+ }
+ else if (elements[0] instanceof ConnectionProfileGroup) {
+ return elements[0].name;
+ }
+ return undefined;
+ }
+
+ /**
+ * Returns a DragOverReaction indicating whether sources can be
+ * dropped into target or some parent of the target.
+ */
+ public onDragOver(data: IDragAndDropData, targetElement: ServerTreeElement, targetIndex: number, originalEvent: DragEvent): boolean | ITreeDragOverReaction {
+ return { accept: false };
+ }
+
+ /**
+ * Handle drop in the server tree.
+ */
+ public drop(data: IDragAndDropData, targetElement: ServerTreeElement, targetIndex: number, originalEvent: DragEvent): void {
+ // No op
+ }
+}
diff --git a/src/sql/workbench/services/objectExplorer/browser/asyncServerTreeIdentityProvider.ts b/src/sql/workbench/services/objectExplorer/browser/asyncServerTreeIdentityProvider.ts
new file mode 100644
index 0000000000..e1d21032bd
--- /dev/null
+++ b/src/sql/workbench/services/objectExplorer/browser/asyncServerTreeIdentityProvider.ts
@@ -0,0 +1,13 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the Source EULA. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { IIdentityProvider } from 'vs/base/browser/ui/list/list';
+import { ServerTreeElement } from 'sql/workbench/services/objectExplorer/browser/asyncServerTree';
+
+export class AsyncServerTreeIdentityProvider implements IIdentityProvider {
+ getId(element: ServerTreeElement): { toString(): string; } {
+ return element.id;
+ }
+}
diff --git a/src/sql/workbench/services/objectExplorer/browser/asyncServerTreeRenderer.ts b/src/sql/workbench/services/objectExplorer/browser/asyncServerTreeRenderer.ts
new file mode 100644
index 0000000000..9655a267c9
--- /dev/null
+++ b/src/sql/workbench/services/objectExplorer/browser/asyncServerTreeRenderer.ts
@@ -0,0 +1,296 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the Source EULA. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import 'vs/css!./media/objectTypes/objecttypes';
+
+import * as dom from 'vs/base/browser/dom';
+import { localize } from 'vs/nls';
+import { ConnectionProfileGroup } from 'sql/platform/connection/common/connectionProfileGroup';
+import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
+import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
+import { TreeNode } from 'sql/workbench/services/objectExplorer/common/treeNode';
+import { iconRenderer } from 'sql/workbench/services/objectExplorer/browser/iconRenderer';
+import { URI } from 'vs/base/common/uri';
+import { ITreeRenderer, ITreeNode } from 'vs/base/browser/ui/tree/tree';
+import { FuzzyScore } from 'vs/base/common/filters';
+import { Disposable } from 'vs/base/common/lifecycle';
+import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
+import { IKeyboardNavigationLabelProvider } from 'vs/base/browser/ui/list/list';
+import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget';
+import { ServerTreeRenderer } from 'sql/workbench/services/objectExplorer/browser/serverTreeRenderer';
+import { ServerTreeElement } from 'sql/workbench/services/objectExplorer/browser/asyncServerTree';
+import { DefaultServerGroupColor } from 'sql/workbench/services/serverGroup/common/serverGroupViewModel';
+
+class ConnectionProfileGroupTemplate extends Disposable {
+ private _root: HTMLElement;
+ private _nameContainer: HTMLElement;
+
+ constructor(
+ container: HTMLElement
+ ) {
+ super();
+ container.parentElement.classList.add('server-group');
+ container.classList.add('server-group');
+ this._root = dom.append(container, dom.$('.server-group'));
+ this._nameContainer = dom.append(this._root, dom.$('span.name'));
+ }
+
+ set(element: ConnectionProfileGroup) {
+ let rowElement = findParentElement(this._root, 'monaco-list-row');
+ if (rowElement) {
+ if (element.color) {
+ rowElement.style.background = element.color;
+ } else {
+ // If the group doesn't contain specific color, assign the default color
+ rowElement.style.background = DefaultServerGroupColor;
+ }
+ }
+ if (element.description && (element.description !== '')) {
+ this._root.title = element.description;
+ }
+ this._nameContainer.hidden = false;
+ this._nameContainer.textContent = element.name;
+ }
+}
+
+export class ConnectionProfileGroupRenderer implements ITreeRenderer {
+
+ readonly templateId: string = ServerTreeRenderer.CONNECTION_GROUP_TEMPLATE_ID;
+
+ constructor(@IInstantiationService private readonly _instantiationService: IInstantiationService) { }
+
+ renderTemplate(container: HTMLElement): ConnectionProfileGroupTemplate {
+ return this._instantiationService.createInstance(ConnectionProfileGroupTemplate, container);
+ }
+ renderElement(node: ITreeNode, index: number, template: ConnectionProfileGroupTemplate): void {
+ template.set(node.element);
+ }
+ disposeTemplate(templateData: ConnectionProfileGroupTemplate): void {
+ templateData.dispose();
+ }
+}
+
+class ConnectionProfileTemplate extends Disposable {
+
+ private _root: HTMLElement;
+ private _icon: HTMLElement;
+ private _label: HTMLElement;
+ /**
+ * _isCompact is used to render connections tiles with and without the action buttons.
+ * When set to true, like in the connection dialog recent connections tree, the connection
+ * tile is rendered without the action buttons( such as connect, new query).
+ */
+ constructor(
+ container: HTMLElement,
+ private _isCompact: boolean,
+ @IConnectionManagementService private _connectionManagementService: IConnectionManagementService
+ ) {
+ super();
+ container.parentElement.classList.add('connection-profile');
+ this._root = dom.append(container, dom.$('.connection-tile'));
+ this._icon = dom.append(this._root, dom.$('div.icon server-page'));
+ this._label = dom.append(this._root, dom.$('div.label'));
+ }
+
+ set(element: ConnectionProfile) {
+ if (!this._isCompact) {
+ let iconPath: IconPath = getIconPath(element, this._connectionManagementService);
+ if (this._connectionManagementService.isConnected(undefined, element)) {
+ this._icon.classList.remove('disconnected');
+ this._icon.classList.add('connected');
+ renderServerIcon(this._icon, iconPath, true);
+ } else {
+ this._icon.classList.remove('connected');
+ this._icon.classList.add('disconnected');
+ renderServerIcon(this._icon, iconPath, false);
+ }
+ }
+
+ let label = element.title;
+ if (!element.isConnectionOptionsValid) {
+ label = localize('loading', "Loading...");
+ }
+
+ this._label.textContent = label;
+ this._root.title = element.serverInfo;
+ }
+}
+
+export class ConnectionProfileRenderer implements ITreeRenderer {
+
+ readonly templateId: string = ServerTreeRenderer.CONNECTION_TEMPLATE_ID;
+
+ constructor(
+ private _isCompact: boolean,
+ @IInstantiationService private readonly _instantiationService: IInstantiationService) { }
+
+ renderTemplate(container: HTMLElement): ConnectionProfileTemplate {
+ return this._instantiationService.createInstance(ConnectionProfileTemplate, container, this._isCompact);
+ }
+ renderElement(node: ITreeNode, index: number, template: ConnectionProfileTemplate): void {
+ template.set(node.element);
+ }
+ disposeTemplate(templateData: ConnectionProfileTemplate): void {
+ templateData.dispose();
+ }
+}
+
+class TreeNodeTemplate extends Disposable {
+ private _root: HTMLElement;
+ private _icon: HTMLElement;
+ private _label: HTMLElement;
+
+ constructor(
+ container: HTMLElement
+ ) {
+ super();
+ this._root = dom.append(container, dom.$('.object-element-group'));
+ this._icon = dom.append(this._root, dom.$('div.object-icon'));
+ this._label = dom.append(this._root, dom.$('div.label'));
+ }
+
+ set(element: TreeNode) {
+ // Use an explicitly defined iconType first. If not defined, fall back to using nodeType and
+ // other compount indicators instead.
+ let iconName: string = undefined;
+ if (element.iconType) {
+ iconName = (typeof element.iconType === 'string') ? element.iconType : element.iconType.id;
+ } else {
+ iconName = element.nodeTypeId;
+ if (element.nodeStatus) {
+ iconName = element.nodeTypeId + '_' + element.nodeStatus;
+ }
+ if (element.nodeSubType) {
+ iconName = element.nodeTypeId + '_' + element.nodeSubType;
+ }
+ }
+
+ let tokens: string[] = [];
+ for (let index = 1; index < this._icon.classList.length; index++) {
+ tokens.push(this._icon.classList.item(index));
+ }
+ this._icon.classList.remove(...tokens);
+ this._icon.classList.add('icon');
+ let iconLowerCaseName = iconName.toLocaleLowerCase();
+ this._icon.classList.add(iconLowerCaseName);
+
+ if (element.iconPath) {
+ iconRenderer.putIcon(this._icon, element.iconPath);
+ }
+
+ this._label.textContent = element.label;
+ this._root.title = element.label;
+ }
+}
+
+export class TreeNodeRenderer implements ITreeRenderer {
+
+ readonly templateId: string = ServerTreeRenderer.OBJECTEXPLORER_TEMPLATE_ID;
+
+ constructor(@IInstantiationService private readonly _instantiationService: IInstantiationService) { }
+
+ renderTemplate(container: HTMLElement): TreeNodeTemplate {
+ return this._instantiationService.createInstance(TreeNodeTemplate, container);
+ }
+ renderElement(node: ITreeNode, index: number, template: TreeNodeTemplate): void {
+ template.set(node.element);
+ }
+ disposeTemplate(templateData: TreeNodeTemplate): void {
+ templateData.dispose();
+ }
+}
+
+export class ServerTreeKeyboardNavigationLabelProvider implements IKeyboardNavigationLabelProvider {
+
+ constructor() { }
+
+ getKeyboardNavigationLabel(element: ServerTreeElement): { toString(): string; } {
+ if (element instanceof ConnectionProfileGroup) {
+ return element.groupName;
+ } else if (element instanceof ConnectionProfile) {
+ return element.title;
+ } else {
+ return element.label;
+ }
+ }
+}
+
+export class ServerTreeAccessibilityProvider implements IListAccessibilityProvider {
+
+ constructor(private _widgetAriaLabel: string) { }
+
+ getWidgetAriaLabel(): string {
+ return this._widgetAriaLabel;
+ }
+
+ getAriaLabel(element: ServerTreeElement): string | null {
+ if (element instanceof ConnectionProfileGroup) {
+ return element.fullName;
+ } else if (element instanceof ConnectionProfile) {
+ return element.title;
+ }
+ return element.label;
+ }
+}
+
+/**
+ * Returns the first parent which contains the className
+ */
+function findParentElement(container: HTMLElement, className: string): HTMLElement {
+ let currentElement = container;
+ while (currentElement) {
+ if (currentElement.className.indexOf(className) > -1) {
+ break;
+ }
+ currentElement = currentElement.parentElement;
+ }
+ return currentElement;
+}
+
+function getIconPath(connection: ConnectionProfile, connectionManagementService: IConnectionManagementService): IconPath {
+ if (!connection) { return undefined; }
+
+ if (connection['iconPath']) {
+ return connection['iconPath'];
+ }
+
+ let iconId = connectionManagementService.getConnectionIconId(connection.id);
+ if (!iconId) { return undefined; }
+
+ let providerProperties = connectionManagementService.getProviderProperties(connection.providerName);
+ if (!providerProperties) { return undefined; }
+
+ let iconPath: IconPath = undefined;
+ let pathConfig: URI | IconPath | { id: string, path: IconPath }[] = providerProperties['iconPath'];
+ if (Array.isArray(pathConfig)) {
+ for (const e of pathConfig) {
+ if (!e.id || e.id === iconId) {
+ iconPath = e.path;
+ connection['iconPath'] = iconPath;
+ break;
+ }
+ }
+ } else if (pathConfig['light']) {
+ iconPath = pathConfig as IconPath;
+ connection['iconPath'] = iconPath;
+ } else {
+ let singlePath = pathConfig as URI;
+ iconPath = { light: singlePath, dark: singlePath };
+ connection['iconPath'] = iconPath;
+ }
+ return iconPath;
+}
+
+function renderServerIcon(element: HTMLElement, iconPath: IconPath, isConnected: boolean): void {
+ if (!element) { return; }
+ if (iconPath) {
+ iconRenderer.putIcon(element, iconPath);
+ }
+}
+
+interface IconPath {
+ light: URI;
+ dark: URI;
+}
diff --git a/src/sql/workbench/services/objectExplorer/browser/connectionTreeAction.ts b/src/sql/workbench/services/objectExplorer/browser/connectionTreeAction.ts
index 1c81aab1f0..3624cf97ba 100644
--- a/src/sql/workbench/services/objectExplorer/browser/connectionTreeAction.ts
+++ b/src/sql/workbench/services/objectExplorer/browser/connectionTreeAction.ts
@@ -18,6 +18,7 @@ import { IErrorMessageService } from 'sql/platform/errorMessage/common/errorMess
import { UNSAVED_GROUP_ID } from 'sql/platform/connection/common/constants';
import { IServerGroupController } from 'sql/platform/serverGroup/common/serverGroupController';
import { ILogService } from 'vs/platform/log/common/log';
+import { AsyncServerTree, ServerTreeElement } from 'sql/workbench/services/objectExplorer/browser/asyncServerTree';
export interface IServerView {
showFilteredTree(filter: string): void;
@@ -28,20 +29,18 @@ export class RefreshAction extends Action {
public static ID = 'objectExplorer.refresh';
public static LABEL = localize('connectionTree.refresh', "Refresh");
- private _tree: ITree;
constructor(
id: string,
label: string,
- tree: ITree,
- private element: IConnectionProfile | TreeNode,
+ private _tree: AsyncServerTree | ITree,
+ private element: ServerTreeElement,
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
@IObjectExplorerService private _objectExplorerService: IObjectExplorerService,
@IErrorMessageService private _errorMessageService: IErrorMessageService,
@ILogService private _logService: ILogService
) {
super(id, label);
- this._tree = tree;
}
public async run(): Promise {
let treeNode: TreeNode;
@@ -66,7 +65,11 @@ export class RefreshAction extends Action {
this.showError(error);
return true;
}
- await this._tree.refresh(this.element);
+ if (this._tree instanceof AsyncServerTree) {
+ await this._tree.updateChildren(this.element);
+ } else {
+ await this._tree.refresh(this.element);
+ }
} catch (ex) {
this._logService.error(ex);
return true;
diff --git a/src/sql/workbench/services/objectExplorer/browser/dragAndDropController.ts b/src/sql/workbench/services/objectExplorer/browser/dragAndDropController.ts
index 73a8c4ecaf..601e30bbfb 100644
--- a/src/sql/workbench/services/objectExplorer/browser/dragAndDropController.ts
+++ b/src/sql/workbench/services/objectExplorer/browser/dragAndDropController.ts
@@ -20,6 +20,17 @@ export function supportsNodeNameDrop(nodeId: string): boolean {
return false;
}
+export function supportsFolderNodeNameDrop(nodeId: string, label: string): boolean {
+ if (nodeId === 'Folder' && label === 'Columns') {
+ return true;
+ }
+ return false;
+}
+
+function escapeString(input: string | undefined): string | undefined {
+ return input?.replace(/]/g, ']]');
+}
+
/**
* Implements drag and drop for the server tree
*/
@@ -42,7 +53,7 @@ export class ServerTreeDragAndDrop implements IDragAndDrop {
return (element).id;
} else if (supportsNodeNameDrop(element.nodeTypeId)) {
return (element).id;
- } else if (element.nodeTypeId === 'Folder' && element.label === 'Columns' && element.children) {
+ } else if (supportsFolderNodeNameDrop(element.nodeTypeId, element.label) && element.children) {
return (element).id;
} else {
return undefined;
@@ -83,17 +94,17 @@ export class ServerTreeDragAndDrop implements IDragAndDrop {
const data = dragAndDropData.getData();
const element = data[0];
if (supportsNodeNameDrop(element.nodeTypeId)) {
- escapedSchema = this.escapeString(element.metadata.schema);
- escapedName = this.escapeString(element.metadata.name);
+ escapedSchema = escapeString(element.metadata.schema);
+ escapedName = escapeString(element.metadata.name);
finalString = escapedSchema ? `[${escapedSchema}].[${escapedName}]` : `[${escapedName}]`;
originalEvent.dataTransfer.setData(DataTransfers.RESOURCES, JSON.stringify([`${element.nodeTypeId}:${element.id}?${finalString}`]));
}
- if (element.nodeTypeId === 'Folder' && element.label === 'Columns') {
+ if (supportsFolderNodeNameDrop(element.nodeTypeId, element.label)) {
// get children
let returnString = '';
for (let child of element.children) {
- escapedSchema = this.escapeString(child.metadata.schema);
- escapedName = this.escapeString(child.metadata.name);
+ escapedSchema = escapeString(child.metadata.schema);
+ escapedName = escapeString(child.metadata.name);
finalString = escapedSchema ? `[${escapedSchema}].[${escapedName}]` : `[${escapedName}]`;
returnString = returnString ? `${returnString},${finalString}` : `${finalString}`;
}
@@ -103,14 +114,6 @@ export class ServerTreeDragAndDrop implements IDragAndDrop {
return;
}
- private escapeString(input: string | undefined): string | undefined {
- if (input) {
- let output = input.replace(/]/g, ']]');
- return output;
- }
- return undefined;
- }
-
public canDragToConnectionProfileGroup(source: any, targetConnectionProfileGroup: ConnectionProfileGroup) {
let canDragOver: boolean = true;
@@ -177,12 +180,18 @@ export class ServerTreeDragAndDrop implements IDragAndDrop {
if (source instanceof ConnectionProfile) {
// Change group id of profile
this._connectionManagementService.changeGroupIdForConnection(source, targetConnectionProfileGroup.id).then(() => {
- TreeUpdateUtils.registeredServerUpdate(tree, self._connectionManagementService, targetConnectionProfileGroup);
+ if (tree) {
+ TreeUpdateUtils.registeredServerUpdate(tree, self._connectionManagementService, targetConnectionProfileGroup);
+ }
+
});
} else if (source instanceof ConnectionProfileGroup) {
// Change parent id of group
this._connectionManagementService.changeGroupIdForConnectionGroup(source, targetConnectionProfileGroup).then(() => {
- TreeUpdateUtils.registeredServerUpdate(tree, self._connectionManagementService);
+ if (tree) {
+ TreeUpdateUtils.registeredServerUpdate(tree, self._connectionManagementService);
+ }
+
});
}
}
diff --git a/src/sql/workbench/services/objectExplorer/browser/objectExplorerService.ts b/src/sql/workbench/services/objectExplorer/browser/objectExplorerService.ts
index d827831ab1..557c4c8161 100644
--- a/src/sql/workbench/services/objectExplorer/browser/objectExplorerService.ts
+++ b/src/sql/workbench/services/objectExplorer/browser/objectExplorerService.ts
@@ -21,7 +21,9 @@ import { values } from 'vs/base/common/collections';
import { startsWith } from 'vs/base/common/strings';
import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry';
import { IAction } from 'vs/base/common/actions';
+import { ServerTreeActionProvider } from 'sql/workbench/services/objectExplorer/browser/serverTreeActionProvider';
import { ITree } from 'vs/base/parts/tree/browser/tree';
+import { AsyncServerTree, ServerTreeElement } from 'sql/workbench/services/objectExplorer/browser/asyncServerTree';
export const SERVICE_ID = 'ObjectExplorerService';
@@ -32,22 +34,22 @@ export interface NodeExpandInfoWithProviderId extends azdata.ObjectExplorerExpan
}
export interface IServerTreeView {
- readonly tree: ITree;
+ readonly tree: ITree | AsyncServerTree;
readonly onSelectionOrFocusChange: Event;
isObjectExplorerConnectionUri(uri: string): boolean;
deleteObjectExplorerNodeAndRefreshTree(profile: ConnectionProfile): Promise;
- getSelection(): Array;
+ getSelection(): Array;
isFocused(): boolean;
refreshElement(node: TreeNode): Promise;
- readonly treeActionProvider: { getActions: (tree: ITree, node: TreeNode | ConnectionProfile) => IAction[] }
- isExpanded(node: TreeNode | ConnectionProfile): boolean;
- reveal(node: TreeNode | ConnectionProfile): Promise;
- setExpandedState(node: TreeNode | ConnectionProfile, state: TreeItemCollapsibleState): Promise;
- setSelected(node: TreeNode | ConnectionProfile, selected: boolean, clearOtherSelections: boolean): Promise;
+ readonly treeActionProvider: ServerTreeActionProvider;
+ isExpanded(node: ServerTreeElement): boolean;
+ reveal(node: ServerTreeElement): Promise;
+ setExpandedState(node: ServerTreeElement, state: TreeItemCollapsibleState): Promise;
+ setSelected(node: ServerTreeElement, selected: boolean, clearOtherSelections: boolean): Promise;
refreshTree(): Promise;
readonly activeConnectionsFilterAction: IAction;
renderBody(container: HTMLElement): Promise;
- layout(size: number);
+ layout(size: number): void;
}
export interface IObjectExplorerService {
@@ -78,7 +80,7 @@ export interface IObjectExplorerService {
registerNodeProvider(expander: azdata.ObjectExplorerNodeProvider): void;
- getObjectExplorerNode(connection: IConnectionProfile): TreeNode;
+ getObjectExplorerNode(connection: IConnectionProfile): TreeNode | undefined;
updateObjectExplorerNodes(connectionProfile: IConnectionProfile): Promise;
@@ -203,7 +205,7 @@ export class ObjectExplorerService implements IObjectExplorerService {
public async updateObjectExplorerNodes(connection: IConnectionProfile): Promise {
const withPassword = await this._connectionManagementService.addSavedPassword(connection);
- let connectionProfile = ConnectionProfile.fromIConnectionProfile(this._capabilitiesService, withPassword);
+ const connectionProfile = ConnectionProfile.fromIConnectionProfile(this._capabilitiesService, withPassword);
return this.updateNewObjectExplorerNode(connectionProfile);
}
@@ -330,7 +332,7 @@ export class ObjectExplorerService implements IObjectExplorerService {
}
}
- public getObjectExplorerNode(connection: IConnectionProfile): TreeNode {
+ public getObjectExplorerNode(connection: IConnectionProfile): TreeNode | undefined {
return this._activeObjectExplorerNodes[connection.id];
}
@@ -678,7 +680,7 @@ export class ObjectExplorerService implements IObjectExplorerService {
*/
public async getNodeActions(connectionId: string, nodePath: string): Promise {
const node = await this.getTreeNode(connectionId, nodePath);
- let actions = this._serverTreeView.treeActionProvider.getActions(this._serverTreeView.tree, this.getTreeItem(node));
+ const actions = this._serverTreeView.treeActionProvider.getActions(this._serverTreeView.tree, this.getTreeItem(node));
return actions.filter(action => action.label).map(action => action.label);
}
diff --git a/src/sql/workbench/services/objectExplorer/browser/serverTreeActionProvider.ts b/src/sql/workbench/services/objectExplorer/browser/serverTreeActionProvider.ts
index 43245d1e7b..c5a253b0cb 100644
--- a/src/sql/workbench/services/objectExplorer/browser/serverTreeActionProvider.ts
+++ b/src/sql/workbench/services/objectExplorer/browser/serverTreeActionProvider.ts
@@ -25,6 +25,7 @@ import { IQueryManagementService } from 'sql/workbench/services/query/common/que
import { ServerInfoContextKey } from 'sql/workbench/services/connection/common/serverInfoContextKey';
import { fillInActions } from 'vs/platform/actions/browser/menuEntryActionViewItem';
import { firstIndex, find } from 'vs/base/common/arrays';
+import { AsyncServerTree, ServerTreeElement } from 'sql/workbench/services/objectExplorer/browser/asyncServerTree';
/**
* Provides actions for the server tree elements
@@ -40,19 +41,15 @@ export class ServerTreeActionProvider {
) {
}
- public hasActions(tree: ITree, element: any): boolean {
- return element instanceof ConnectionProfileGroup || (element instanceof ConnectionProfile) || (element instanceof TreeNode);
- }
-
/**
* Return actions given an element in the tree
*/
- public getActions(tree: ITree, element: any): IAction[] {
+ public getActions(tree: AsyncServerTree | ITree, element: ServerTreeElement): IAction[] {
if (element instanceof ConnectionProfile) {
return this.getConnectionActions(tree, element);
}
if (element instanceof ConnectionProfileGroup) {
- return this.getConnectionProfileGroupActions(tree, element);
+ return this.getConnectionProfileGroupActions(element);
}
if (element instanceof TreeNode) {
return this.getObjectExplorerNodeActions({
@@ -61,18 +58,13 @@ export class ServerTreeActionProvider {
treeNode: element
});
}
-
return [];
}
- public hasSecondaryActions(tree: ITree, element: any): boolean {
- return false;
- }
-
/**
* Return actions for connection elements
*/
- public getConnectionActions(tree: ITree, profile: ConnectionProfile): IAction[] {
+ private getConnectionActions(tree: AsyncServerTree | ITree, profile: ConnectionProfile): IAction[] {
let node = new TreeNode(NodeType.Server, '', false, '', '', '', undefined, undefined, undefined, undefined);
return this.getAllActions({
tree: tree,
@@ -158,7 +150,7 @@ export class ServerTreeActionProvider {
/**
* Return actions for connection group elements
*/
- public getConnectionProfileGroupActions(tree: ITree, element: ConnectionProfileGroup): IAction[] {
+ private getConnectionProfileGroupActions(element: ConnectionProfileGroup): IAction[] {
return [
this._instantiationService.createInstance(AddServerAction, AddServerAction.ID, AddServerAction.LABEL),
this._instantiationService.createInstance(EditServerGroupAction, EditServerGroupAction.ID, EditServerGroupAction.LABEL, element),
@@ -202,7 +194,7 @@ export class ServerTreeActionProvider {
}
interface ObjectExplorerContext {
- tree: ITree;
+ tree: AsyncServerTree | ITree;
profile: ConnectionProfile;
treeNode?: TreeNode;
}
diff --git a/src/sql/workbench/services/objectExplorer/browser/serverTreeDataSource.ts b/src/sql/workbench/services/objectExplorer/browser/serverTreeDataSource.ts
index b4d10aeff6..bdac74bbca 100644
--- a/src/sql/workbench/services/objectExplorer/browser/serverTreeDataSource.ts
+++ b/src/sql/workbench/services/objectExplorer/browser/serverTreeDataSource.ts
@@ -58,7 +58,7 @@ export class ServerTreeDataSource implements IDataSource {
*/
public async getChildren(tree: ITree, element: any): Promise<(ConnectionProfile | ConnectionProfileGroup | TreeNode)[]> {
if (element instanceof ConnectionProfile) {
- return TreeUpdateUtils.getObjectExplorerNode(element, this._connectionManagementService, this._objectExplorerService);
+ return TreeUpdateUtils.getConnectionNodeChildren(element, this._objectExplorerService);
} else if (element instanceof ConnectionProfileGroup) {
return (element as ConnectionProfileGroup).getChildren();
} else if (element instanceof TreeNode) {
diff --git a/src/sql/workbench/services/objectExplorer/browser/serverTreeRenderer.ts b/src/sql/workbench/services/objectExplorer/browser/serverTreeRenderer.ts
index 68626fe0fc..cc7403386c 100644
--- a/src/sql/workbench/services/objectExplorer/browser/serverTreeRenderer.ts
+++ b/src/sql/workbench/services/objectExplorer/browser/serverTreeRenderer.ts
@@ -15,6 +15,7 @@ import { TreeNode } from 'sql/workbench/services/objectExplorer/common/treeNode'
import { InputBox } from 'vs/base/browser/ui/inputbox/inputBox';
import { badgeRenderer, iconRenderer } from 'sql/workbench/services/objectExplorer/browser/iconRenderer';
import { URI } from 'vs/base/common/uri';
+import { DefaultServerGroupColor } from 'sql/workbench/services/serverGroup/common/serverGroupViewModel';
export interface IConnectionTemplateData {
root: HTMLElement;
@@ -44,10 +45,10 @@ export class ServerTreeRenderer implements IRenderer {
public static CONNECTION_HEIGHT = 23;
public static CONNECTION_GROUP_HEIGHT = 38;
- private static CONNECTION_TEMPLATE_ID = 'connectionProfile';
- private static CONNECTION_GROUP_TEMPLATE_ID = 'connectionProfileGroup';
+ public static CONNECTION_TEMPLATE_ID = 'connectionProfile';
+ public static CONNECTION_GROUP_TEMPLATE_ID = 'connectionProfileGroup';
public static OBJECTEXPLORER_HEIGHT = 23;
- private static OBJECTEXPLORER_TEMPLATE_ID = 'objectExplorer';
+ public static OBJECTEXPLORER_TEMPLATE_ID = 'objectExplorer';
/**
* _isCompact is used to render connections tiles with and without the action buttons.
* When set to true, like in the connection dialog recent connections tree, the connection
@@ -238,7 +239,7 @@ export class ServerTreeRenderer implements IRenderer {
rowElement.style.background = connectionProfileGroup.color;
} else {
// If the group doesn't contain specific color, assign the default color
- rowElement.style.background = '#515151';
+ rowElement.style.background = DefaultServerGroupColor;
}
}
if (connectionProfileGroup.description && (connectionProfileGroup.description !== '')) {
diff --git a/src/sql/workbench/services/objectExplorer/browser/treeCreationUtils.ts b/src/sql/workbench/services/objectExplorer/browser/treeCreationUtils.ts
index c54d0d8b61..0076c013df 100644
--- a/src/sql/workbench/services/objectExplorer/browser/treeCreationUtils.ts
+++ b/src/sql/workbench/services/objectExplorer/browser/treeCreationUtils.ts
@@ -15,48 +15,130 @@ import { IController } from 'vs/base/parts/tree/browser/tree';
import { ServerTreeDragAndDrop, RecentConnectionsDragAndDrop } from 'sql/workbench/services/objectExplorer/browser/dragAndDropController';
import { RecentConnectionDataSource } from 'sql/workbench/services/objectExplorer/browser/recentConnectionDataSource';
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
+import { IWorkbenchAsyncDataTreeOptions } from 'vs/platform/list/browser/listService';
+import { ConnectionProfileGroupRenderer, ConnectionProfileRenderer, TreeNodeRenderer, ServerTreeAccessibilityProvider, ServerTreeKeyboardNavigationLabelProvider } from 'sql/workbench/services/objectExplorer/browser/asyncServerTreeRenderer';
+import { AsyncServerTreeIdentityProvider } from 'sql/workbench/services/objectExplorer/browser/asyncServerTreeIdentityProvider';
+import { FuzzyScore } from 'vs/base/common/filters';
+import { AsyncServerTreeDelegate } from 'sql/workbench/services/objectExplorer/browser/asyncServerTreeDelegate';
+import { AsyncRecentConnectionsDragAndDrop, AsyncServerTreeDragAndDrop } from 'sql/workbench/services/objectExplorer/browser/asyncServerTreeDragAndDrop';
+import { AsyncRecentConnectionTreeDataSource } from 'sql/workbench/services/objectExplorer/browser/asyncRecentConnectionTreeDataSource';
+import { AsyncServerTreeDataSource } from 'sql/workbench/services/objectExplorer/browser/asyncServerTreeDataSource';
+import { AsyncServerTree, ServerTreeElement } from 'sql/workbench/services/objectExplorer/browser/asyncServerTree';
+import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
export class TreeCreationUtils {
/**
* Create a Recent Connections tree
*/
- public static createConnectionTree(treeContainer: HTMLElement, instantiationService: IInstantiationService, useController?: IController): Tree {
- const dataSource = instantiationService.createInstance(RecentConnectionDataSource);
- const renderer = instantiationService.createInstance(ServerTreeRenderer, true);
- const controller = useController ? useController : new DefaultController();
- const dnd = instantiationService.createInstance(RecentConnectionsDragAndDrop);
- const filter = new DefaultFilter();
- const sorter = undefined;
- const accessibilityProvider = new DefaultAccessibilityProvider();
+ public static createConnectionTree(treeContainer: HTMLElement, instantiationService: IInstantiationService, configurationService: IConfigurationService, ariaLabel: string, useController?: IController): Tree | AsyncServerTree {
+ if (useAsyncServerTree(configurationService)) {
+ const dataSource = instantiationService.createInstance(AsyncRecentConnectionTreeDataSource);
+ const connectionProfileGroupRender = instantiationService.createInstance(ConnectionProfileGroupRenderer);
+ const connectionProfileRenderer = instantiationService.createInstance(ConnectionProfileRenderer, true);
+ const treeNodeRenderer = instantiationService.createInstance(TreeNodeRenderer);
+ const dnd = instantiationService.createInstance(AsyncRecentConnectionsDragAndDrop);
+ const identityProvider = instantiationService.createInstance(AsyncServerTreeIdentityProvider);
- return new Tree(treeContainer, { dataSource, renderer, controller, dnd, filter, sorter, accessibilityProvider },
- {
- indentPixels: 0,
- twistiePixels: 0,
- ariaLabel: nls.localize('treeAriaLabel', "Recent Connections")
- });
+ const treeOptions: IWorkbenchAsyncDataTreeOptions = {
+ keyboardSupport: true,
+ accessibilityProvider: new ServerTreeAccessibilityProvider(ariaLabel),
+ keyboardNavigationLabelProvider: instantiationService.createInstance(ServerTreeKeyboardNavigationLabelProvider),
+ dnd: dnd,
+ identityProvider: identityProvider
+ };
+
+ return instantiationService.createInstance(
+ AsyncServerTree,
+ 'ServerTreeView',
+ treeContainer,
+ new AsyncServerTreeDelegate(),
+ [
+ connectionProfileGroupRender,
+ connectionProfileRenderer,
+ treeNodeRenderer
+ ],
+ dataSource,
+ treeOptions,
+ );
+ } else {
+ const dataSource = instantiationService.createInstance(RecentConnectionDataSource);
+ const renderer = instantiationService.createInstance(ServerTreeRenderer, true);
+ const controller = useController ? useController : new DefaultController();
+ const dnd = instantiationService.createInstance(RecentConnectionsDragAndDrop);
+ const filter = new DefaultFilter();
+ const sorter = undefined;
+ const accessibilityProvider = new DefaultAccessibilityProvider();
+
+ return new Tree(treeContainer, { dataSource, renderer, controller, dnd, filter, sorter, accessibilityProvider },
+ {
+ indentPixels: 0,
+ twistiePixels: 0,
+ ariaLabel: nls.localize('treeAriaLabel', "Recent Connections")
+ });
+ }
}
/**
* Create a Servers viewlet tree
*/
- public static createRegisteredServersTree(treeContainer: HTMLElement, instantiationService: IInstantiationService, horizontalScrollMode: boolean = false): Tree {
+ public static createServersTree(treeContainer: HTMLElement,
+ instantiationService: IInstantiationService,
+ configurationService: IConfigurationService,
+ horizontalScrollMode: boolean = false): Tree | AsyncServerTree {
- const dataSource = instantiationService.createInstance(ServerTreeDataSource);
- const actionProvider = instantiationService.createInstance(ServerTreeActionProvider);
- const renderer = instantiationService.createInstance(ServerTreeRenderer, false);
- const controller = instantiationService.createInstance(ServerTreeController, actionProvider);
- const dnd = instantiationService.createInstance(ServerTreeDragAndDrop);
- const filter = new DefaultFilter();
- const sorter = undefined;
- const accessibilityProvider = new DefaultAccessibilityProvider();
+ if (useAsyncServerTree(configurationService)) {
+ const dataSource = instantiationService.createInstance(AsyncServerTreeDataSource);
+ const connectionProfileGroupRender = instantiationService.createInstance(ConnectionProfileGroupRenderer);
+ const connectionProfileRenderer = instantiationService.createInstance(ConnectionProfileRenderer, false);
+ const treeNodeRenderer = instantiationService.createInstance(TreeNodeRenderer);
+ const dnd = instantiationService.createInstance(AsyncServerTreeDragAndDrop);
+ const identityProvider = instantiationService.createInstance(AsyncServerTreeIdentityProvider);
+
+ const treeOptions: IWorkbenchAsyncDataTreeOptions = {
+ keyboardSupport: true,
+ accessibilityProvider: new ServerTreeAccessibilityProvider(nls.localize('serversAriaLabel', "Servers")),
+ keyboardNavigationLabelProvider: instantiationService.createInstance(ServerTreeKeyboardNavigationLabelProvider),
+ openOnSingleClick: true,
+ openOnFocus: true,
+ dnd: dnd,
+ identityProvider: identityProvider
+ };
+
+ return instantiationService.createInstance(
+ AsyncServerTree,
+ 'ServerTreeView',
+ treeContainer,
+ new AsyncServerTreeDelegate(),
+ [
+ connectionProfileGroupRender,
+ connectionProfileRenderer,
+ treeNodeRenderer
+ ],
+ dataSource,
+ treeOptions
+ );
+ } else {
+ const dataSource = instantiationService.createInstance(ServerTreeDataSource);
+ const actionProvider = instantiationService.createInstance(ServerTreeActionProvider);
+ const renderer = instantiationService.createInstance(ServerTreeRenderer, false);
+ const controller = instantiationService.createInstance(ServerTreeController, actionProvider);
+ const dnd = instantiationService.createInstance(ServerTreeDragAndDrop);
+ const filter = new DefaultFilter();
+ const sorter = undefined;
+ const accessibilityProvider = new DefaultAccessibilityProvider();
+
+ return new Tree(treeContainer, { dataSource, renderer, controller, dnd, filter, sorter, accessibilityProvider },
+ {
+ indentPixels: 10,
+ twistiePixels: 20,
+ ariaLabel: nls.localize('treeCreation.regTreeAriaLabel', "Servers"),
+ horizontalScrollMode: horizontalScrollMode ? ScrollbarVisibility.Auto : ScrollbarVisibility.Hidden
+ });
+ }
- return new Tree(treeContainer, { dataSource, renderer, controller, dnd, filter, sorter, accessibilityProvider },
- {
- indentPixels: 10,
- twistiePixels: 20,
- ariaLabel: nls.localize('treeCreation.regTreeAriaLabel', "Servers"),
- horizontalScrollMode: horizontalScrollMode ? ScrollbarVisibility.Auto : ScrollbarVisibility.Hidden
- });
}
}
+
+function useAsyncServerTree(configurationService: IConfigurationService): boolean {
+ return configurationService.getValue('workbench.enablePreviewFeatures') && configurationService.getValue('serverTree.useAsyncServerTree');
+}
diff --git a/src/sql/workbench/services/objectExplorer/browser/treeSelectionHandler.ts b/src/sql/workbench/services/objectExplorer/browser/treeSelectionHandler.ts
index e1c5825b56..deb8a30a67 100644
--- a/src/sql/workbench/services/objectExplorer/browser/treeSelectionHandler.ts
+++ b/src/sql/workbench/services/objectExplorer/browser/treeSelectionHandler.ts
@@ -11,6 +11,7 @@ import { IObjectExplorerService } from 'sql/workbench/services/objectExplorer/br
// import { IProgressRunner, IProgressService } from 'vs/platform/progress/common/progress';
import { TreeNode } from 'sql/workbench/services/objectExplorer/common/treeNode';
import { TreeUpdateUtils } from 'sql/workbench/services/objectExplorer/browser/treeUpdateUtils';
+import { AsyncServerTree } from 'sql/workbench/services/objectExplorer/browser/asyncServerTree';
export class TreeSelectionHandler {
// progressRunner: IProgressRunner;
@@ -44,9 +45,9 @@ export class TreeSelectionHandler {
}
/**
- * Handle select ion of tree element
+ * Handle selection of tree element
*/
- public onTreeSelect(event: any, tree: ITree, connectionManagementService: IConnectionManagementService, objectExplorerService: IObjectExplorerService, connectionCompleteCallback: () => void) {
+ public onTreeSelect(event: any, tree: AsyncServerTree | ITree, connectionManagementService: IConnectionManagementService, objectExplorerService: IObjectExplorerService, connectionCompleteCallback: () => void) {
let sendSelectionEvent = ((event: any, selection: any, isDoubleClick: boolean, userInteraction: boolean) => {
// userInteraction: defensive - don't touch this something else is handling it.
if (userInteraction === true && this._lastClicked && this._lastClicked[0] === selection[0]) {
@@ -93,44 +94,50 @@ export class TreeSelectionHandler {
*
* @param connectionCompleteCallback A function that gets called after a connection is established due to the selection, if needed
*/
- private handleTreeItemSelected(connectionManagementService: IConnectionManagementService, objectExplorerService: IObjectExplorerService, isDoubleClick: boolean, isKeyboard: boolean, selection: any[], tree: ITree, connectionCompleteCallback: () => void): void {
- let connectionProfile: ConnectionProfile = undefined;
- let options: IConnectionCompletionOptions = {
- params: undefined,
- saveTheConnection: true,
- showConnectionDialogOnError: true,
- showFirewallRuleOnError: true,
- showDashboard: isDoubleClick // only show the dashboard if the action is double click
- };
- if (selection && selection.length > 0 && (selection[0] instanceof ConnectionProfile)) {
- connectionProfile = selection[0];
-
- if (connectionProfile) {
+ private handleTreeItemSelected(connectionManagementService: IConnectionManagementService, objectExplorerService: IObjectExplorerService, isDoubleClick: boolean, isKeyboard: boolean, selection: any[], tree: AsyncServerTree | ITree, connectionCompleteCallback: () => void): void {
+ if (tree instanceof AsyncServerTree) {
+ if (selection && selection.length > 0 && (selection[0] instanceof ConnectionProfile)) {
this.onTreeActionStateChange(true);
-
- TreeUpdateUtils.connectAndCreateOeSession(connectionProfile, options, connectionManagementService, objectExplorerService, tree).then(sessionCreated => {
- if (!sessionCreated) {
- this.onTreeActionStateChange(false);
- }
- if (connectionCompleteCallback) {
- connectionCompleteCallback();
- }
- }, error => {
- this.onTreeActionStateChange(false);
- });
}
- } else if (isDoubleClick && selection && selection.length > 0 && (selection[0] instanceof TreeNode)) {
- let treeNode = selection[0];
- if (TreeUpdateUtils.isAvailableDatabaseNode(treeNode)) {
- connectionProfile = TreeUpdateUtils.getConnectionProfile(treeNode);
+ } else {
+ let connectionProfile: ConnectionProfile = undefined;
+ let options: IConnectionCompletionOptions = {
+ params: undefined,
+ saveTheConnection: true,
+ showConnectionDialogOnError: true,
+ showFirewallRuleOnError: true,
+ showDashboard: isDoubleClick // only show the dashboard if the action is double click
+ };
+ if (selection && selection.length > 0 && (selection[0] instanceof ConnectionProfile)) {
+ connectionProfile = selection[0];
+
if (connectionProfile) {
- connectionManagementService.showDashboard(connectionProfile);
+ this.onTreeActionStateChange(true);
+
+ TreeUpdateUtils.connectAndCreateOeSession(connectionProfile, options, connectionManagementService, objectExplorerService, tree).then(sessionCreated => {
+ if (!sessionCreated) {
+ this.onTreeActionStateChange(false);
+ }
+ if (connectionCompleteCallback) {
+ connectionCompleteCallback();
+ }
+ }, error => {
+ this.onTreeActionStateChange(false);
+ });
+ }
+ } else if (isDoubleClick && selection && selection.length > 0 && (selection[0] instanceof TreeNode)) {
+ let treeNode = selection[0];
+ if (TreeUpdateUtils.isAvailableDatabaseNode(treeNode)) {
+ connectionProfile = TreeUpdateUtils.getConnectionProfile(treeNode);
+ if (connectionProfile) {
+ connectionManagementService.showDashboard(connectionProfile);
+ }
}
}
- }
- if (isKeyboard) {
- tree.toggleExpansion(selection[0]);
+ if (isKeyboard) {
+ tree.toggleExpansion(selection[0]);
+ }
}
}
}
diff --git a/src/sql/workbench/services/objectExplorer/browser/treeUpdateUtils.ts b/src/sql/workbench/services/objectExplorer/browser/treeUpdateUtils.ts
index 9631f151db..1f6ab57e84 100644
--- a/src/sql/workbench/services/objectExplorer/browser/treeUpdateUtils.ts
+++ b/src/sql/workbench/services/objectExplorer/browser/treeUpdateUtils.ts
@@ -11,9 +11,9 @@ import { IObjectExplorerService } from 'sql/workbench/services/objectExplorer/br
import { NodeType } from 'sql/workbench/services/objectExplorer/common/nodeType';
import { TreeNode } from 'sql/workbench/services/objectExplorer/common/treeNode';
-import * as errors from 'vs/base/common/errors';
-import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
import { Disposable } from 'vs/base/common/lifecycle';
+import { onUnexpectedError } from 'vs/base/common/errors';
+import { AsyncServerTree, ServerTreeElement } from 'sql/workbench/services/objectExplorer/browser/asyncServerTree';
export interface IExpandableTree extends ITree {
/**
@@ -47,13 +47,13 @@ export class TreeUpdateUtils {
/**
* Set input for the tree.
*/
- public static structuralTreeUpdate(tree: ITree, viewKey: 'recent' | 'active' | 'saved', connectionManagementService: IConnectionManagementService, providers?: string[]): Promise {
+ public static async structuralTreeUpdate(tree: AsyncServerTree | ITree, viewKey: 'recent' | 'active' | 'saved', connectionManagementService: IConnectionManagementService, providers?: string[]): Promise {
// convert to old VS Code tree interface with expandable methods
let expandableTree: IExpandableTree = tree;
let selectedElement: any;
let targetsToExpand: any[];
- if (tree) {
+ if (tree && !(tree instanceof AsyncServerTree)) {
let selection = tree.getSelection();
if (selection && selection.length === 1) {
selectedElement = selection[0];
@@ -72,10 +72,13 @@ export class TreeUpdateUtils {
treeInput = TreeUpdateUtils.getTreeInput(connectionManagementService, providers);
}
const previousTreeInput = tree.getInput();
- return tree.setInput(treeInput).then(async () => {
- if (previousTreeInput instanceof Disposable) {
- previousTreeInput.dispose();
- }
+ await tree.setInput(treeInput);
+
+ if (previousTreeInput instanceof Disposable) {
+ previousTreeInput.dispose();
+ }
+
+ if (tree && !(tree instanceof AsyncServerTree)) {
// Make sure to expand all folders that where expanded in the previous session
if (targetsToExpand) {
await tree.expandAll(targetsToExpand);
@@ -83,64 +86,64 @@ export class TreeUpdateUtils {
if (selectedElement) {
tree.select(selectedElement);
}
- });
+ }
}
/**
* Set input for the registered servers tree.
*/
- public static registeredServerUpdate(tree: ITree, connectionManagementService: IConnectionManagementService, elementToSelect?: any): Promise {
- // convert to old VS Code tree interface with expandable methods
- let expandableTree: IExpandableTree = tree;
+ public static async registeredServerUpdate(tree: ITree | AsyncServerTree, connectionManagementService: IConnectionManagementService, elementToSelect?: any): Promise {
+ if (tree instanceof AsyncServerTree) {
+ const treeInput = TreeUpdateUtils.getTreeInput(connectionManagementService);
+ await tree.setInput(treeInput);
+ tree.rerender();
+ } else {
+ // convert to old VS Code tree interface with expandable methods
+ let expandableTree: IExpandableTree = tree;
- let selectedElement: any = elementToSelect;
- let targetsToExpand: any[];
+ let selectedElement: any = elementToSelect;
+ let targetsToExpand: any[];
- // Focus
- tree.domFocus();
+ // Focus
+ tree.domFocus();
- if (tree) {
- let selection = tree.getSelection();
- if (!selectedElement) {
- if (selection && selection.length === 1) {
- selectedElement = selection[0];
+ if (tree) {
+ let selection = tree.getSelection();
+ if (!selectedElement) {
+ if (selection && selection.length === 1) {
+ selectedElement = selection[0];
+ }
+ }
+ targetsToExpand = expandableTree.getExpandedElements();
+ if (selectedElement && targetsToExpand.indexOf(selectedElement) === -1) {
+ targetsToExpand.push(selectedElement);
}
}
- targetsToExpand = expandableTree.getExpandedElements();
- if (selectedElement && targetsToExpand.indexOf(selectedElement) === -1) {
- targetsToExpand.push(selectedElement);
- }
- }
- let treeInput = TreeUpdateUtils.getTreeInput(connectionManagementService);
- if (treeInput) {
- if (treeInput !== tree.getInput()) {
- return tree.setInput(treeInput).then(async () => {
- // Make sure to expand all folders that where expanded in the previous session
- if (targetsToExpand) {
- await tree.expandAll(targetsToExpand);
- }
- if (selectedElement) {
- tree.select(selectedElement);
- }
- tree.getFocus();
- }, errors.onUnexpectedError);
+ let treeInput = TreeUpdateUtils.getTreeInput(connectionManagementService);
+ if (treeInput) {
+ if (treeInput !== tree.getInput()) {
+ return tree.setInput(treeInput).then(async () => {
+ // Make sure to expand all folders that where expanded in the previous session
+ if (targetsToExpand) {
+ await tree.expandAll(targetsToExpand);
+ }
+ if (selectedElement) {
+ tree.select(selectedElement);
+ }
+ tree.getFocus();
+ }, onUnexpectedError);
+ }
}
}
- return Promise.resolve();
}
- public static getTreeInput(connectionManagementService: IConnectionManagementService, providers?: string[]): ConnectionProfileGroup {
-
- let groups = connectionManagementService.getConnectionGroups(providers);
- if (groups && groups.length > 0) {
- let treeInput = groups[0];
- treeInput.name = 'root';
- groups.forEach(cpg => cpg.dispose());
- return treeInput;
- }
- // Should never get to this case.
- return undefined;
+ public static getTreeInput(connectionManagementService: IConnectionManagementService, providers?: string[]): ConnectionProfileGroup | undefined {
+ const groups = connectionManagementService.getConnectionGroups(providers);
+ const input = groups.find(group => group.isRoot);
+ // Dispose of the unused groups to clean up their handlers
+ groups.filter(g => g !== input).forEach(g => g.dispose());
+ return input;
}
public static hasObjectExplorerNode(connection: ConnectionProfile, connectionManagementService: IConnectionManagementService): boolean {
@@ -149,10 +152,10 @@ export class TreeUpdateUtils {
}
public static async connectIfNotConnected(
- connection: IConnectionProfile,
+ connection: ConnectionProfile,
options: IConnectionCompletionOptions,
connectionManagementService: IConnectionManagementService,
- tree: ITree): Promise {
+ tree: AsyncServerTree | ITree): Promise {
if (!connectionManagementService.isProfileConnected(connection)) {
// don't try to reconnect if currently connecting
if (connectionManagementService.isProfileConnecting(connection)) {
@@ -161,7 +164,15 @@ export class TreeUpdateUtils {
// else if we aren't connected or connecting then try to connect
} else {
let callbacks: IConnectionCallbacks = undefined;
- if (tree) {
+ if (tree instanceof AsyncServerTree) {
+ callbacks = {
+ onConnectStart: undefined,
+ onConnectReject: undefined,
+ onConnectSuccess: undefined,
+ onDisconnect: undefined,
+ onConnectCanceled: undefined,
+ };
+ } else if (tree) {
// Show the spinner in OE by adding the 'loading' trait to the connection, and set up callbacks to hide the spinner
tree.addTraits('loading', [connection]);
let rejectOrCancelCallback = () => {
@@ -176,12 +187,13 @@ export class TreeUpdateUtils {
onConnectCanceled: rejectOrCancelCallback,
};
}
+
const result = await connectionManagementService.connect(connection, undefined, options, callbacks);
if (result.connected) {
let existingConnection = connectionManagementService.findExistingConnection(connection);
return existingConnection;
} else {
- throw new Error('connection failed');
+ throw new Error(result.errorMessage);
}
}
} else {
@@ -202,8 +214,8 @@ export class TreeUpdateUtils {
* @param connectionManagementService Connection management service instance
* @param objectExplorerService Object explorer service instance
*/
- public static async connectAndCreateOeSession(connection: IConnectionProfile, options: IConnectionCompletionOptions,
- connectionManagementService: IConnectionManagementService, objectExplorerService: IObjectExplorerService, tree: ITree): Promise {
+ public static async connectAndCreateOeSession(connection: ConnectionProfile, options: IConnectionCompletionOptions,
+ connectionManagementService: IConnectionManagementService, objectExplorerService: IObjectExplorerService, tree: AsyncServerTree | ITree): Promise {
const connectedConnection = await TreeUpdateUtils.connectIfNotConnected(connection, options, connectionManagementService, tree);
if (connectedConnection) {
// append group ID and original display name to build unique OE session ID
@@ -213,7 +225,6 @@ export class TreeUpdateUtils {
let rootNode: TreeNode = objectExplorerService.getObjectExplorerNode(connectedConnection);
if (!rootNode) {
await objectExplorerService.updateObjectExplorerNodes(connectedConnection);
- rootNode = objectExplorerService.getObjectExplorerNode(connectedConnection);
return true;
// The oe request is sent. an event will be raised when the session is created
} else {
@@ -224,50 +235,75 @@ export class TreeUpdateUtils {
}
}
- public static getObjectExplorerNode(connection: ConnectionProfile, connectionManagementService: IConnectionManagementService, objectExplorerService: IObjectExplorerService): Promise {
- return new Promise((resolve, reject) => {
- if (connection.isDisconnecting) {
- resolve([]);
- } else {
- let rootNode = objectExplorerService.getObjectExplorerNode(connection);
- if (rootNode) {
- objectExplorerService.resolveTreeNodeChildren(rootNode.getSession(), rootNode).then(() => {
- resolve(rootNode.children);
- }, expandError => {
- resolve([]);
- });
-
- } else {
- resolve([]);
+ public static async getConnectionNodeChildren(connection: ConnectionProfile, objectExplorerService: IObjectExplorerService): Promise {
+ if (connection.isDisconnecting) {
+ return [];
+ } else {
+ let rootNode = objectExplorerService.getObjectExplorerNode(connection);
+ if (rootNode) {
+ try {
+ await objectExplorerService.resolveTreeNodeChildren(rootNode.getSession(), rootNode);
+ return rootNode.children;
+ } catch (err) {
+ onUnexpectedError(err);
+ return [];
}
+
+ } else {
+ return [];
}
- });
+ }
}
- public static getObjectExplorerParent(objectExplorerNode: TreeNode, connectionManagementService: IConnectionManagementService): any {
+ public static async getAsyncConnectionNodeChildren(connection: ConnectionProfile, connectionManagementService: IConnectionManagementService, objectExplorerService: IObjectExplorerService): Promise {
+ if (connection.isDisconnecting) {
+ return [];
+ } else {
+ let rootNode = objectExplorerService.getObjectExplorerNode(connection);
+ if (rootNode) {
+ await objectExplorerService.resolveTreeNodeChildren(rootNode.getSession(), rootNode);
+ return rootNode.children;
+ } else {
+ const options: IConnectionCompletionOptions = {
+ params: undefined,
+ saveTheConnection: true,
+ showConnectionDialogOnError: true,
+ showFirewallRuleOnError: true,
+ showDashboard: false
+ };
+ // Need to wait for the OE service to update its nodes in order to resolve the children
+ const nodesUpdatedPromise = new Promise((resolve, reject) => {
+ objectExplorerService.onUpdateObjectExplorerNodes(e => {
+ if (e.errorMessage) {
+ reject(new Error(e.errorMessage));
+ }
+ if (e.connection.id === connection.id) {
+ resolve();
+ }
+ });
+ });
+ await TreeUpdateUtils.connectAndCreateOeSession(connection, options, connectionManagementService, objectExplorerService, undefined);
+ await nodesUpdatedPromise;
+ let rootNode = objectExplorerService.getObjectExplorerNode(connection);
+ await objectExplorerService.resolveTreeNodeChildren(rootNode.getSession(), rootNode);
+ return rootNode.children;
+ }
+ }
+ }
+
+ public static getObjectExplorerParent(objectExplorerNode: TreeNode, connectionManagementService: IConnectionManagementService): ServerTreeElement | undefined {
if (objectExplorerNode && objectExplorerNode.parent) {
// if object explorer node's parent is root, return connection profile
if (!objectExplorerNode.parent.parent) {
- let connectionId = objectExplorerNode.getConnectionProfile().id;
-
+ const connectionId = objectExplorerNode.getConnectionProfile().id;
// get connection profile from connection profile groups
- let root = TreeUpdateUtils.getTreeInput(connectionManagementService);
- let connections = ConnectionProfileGroup.getConnectionsInGroup(root);
- let results = connections.filter(con => {
- if (connectionId === con.id) {
- return true;
- } else {
- return false;
- }
- });
- if (results && results.length > 0) {
- return results[0];
- }
+ const root = TreeUpdateUtils.getTreeInput(connectionManagementService);
+ return ConnectionProfileGroup.getConnectionsInGroup(root).find(conn => connectionId === conn.id);
} else {
return objectExplorerNode.parent;
}
}
- return null;
+ return undefined;
}
/**
diff --git a/src/sql/workbench/services/objectExplorer/test/browser/asyncServerTreeDragAndDrop.test.ts b/src/sql/workbench/services/objectExplorer/test/browser/asyncServerTreeDragAndDrop.test.ts
new file mode 100644
index 0000000000..d7b552305f
--- /dev/null
+++ b/src/sql/workbench/services/objectExplorer/test/browser/asyncServerTreeDragAndDrop.test.ts
@@ -0,0 +1,106 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the Source EULA. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
+import { ConnectionProfileGroup } from 'sql/platform/connection/common/connectionProfileGroup';
+import { ConnectionManagementService } from 'sql/workbench/services/connection/browser/connectionManagementService';
+import { TestCapabilitiesService } from 'sql/platform/capabilities/test/common/testCapabilitiesService';
+import { IStorageService } from 'vs/platform/storage/common/storage';
+import { TestStorageService } from 'vs/workbench/test/common/workbenchTestServices';
+import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
+import { ConnectionProviderProperties } from 'sql/platform/capabilities/common/capabilitiesService';
+import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
+import { mssqlProviderName } from 'sql/platform/connection/common/constants';
+import { TreeNode } from 'sql/workbench/services/objectExplorer/common/treeNode';
+import * as TypeMoq from 'typemoq';
+import * as assert from 'assert';
+import { AsyncServerTreeDragAndDrop } from 'sql/workbench/services/objectExplorer/browser/asyncServerTreeDragAndDrop';
+
+
+
+suite('AsyncServerTreeDragAndDrop', () => {
+ let serverTreeDragAndDrop: AsyncServerTreeDragAndDrop;
+ let msSQLCapabilities: ConnectionProviderProperties;
+ let capabilitiesService: TestCapabilitiesService;
+
+ let iConnectionProfileId: IConnectionProfile = {
+ connectionName: 'new name',
+ serverName: 'new server',
+ databaseName: 'database',
+ userName: 'user',
+ password: 'password',
+ authenticationType: '',
+ savePassword: true,
+ groupFullName: 'g2/g2-2',
+ groupId: 'group id',
+ getOptionsKey: undefined!,
+ matches: undefined!,
+ providerName: mssqlProviderName,
+ options: {},
+ saveProfile: true,
+ id: 'd936bb32-422b-49c3-963f-ae9532d63dc5'
+ };
+
+ let connectionProfile = new ConnectionProfile(capabilitiesService, iConnectionProfileId);
+ let connectionProfileArray = [connectionProfile];
+ let connectionProfileGroupId = new ConnectionProfileGroup('name', undefined, 'd936bb32-422b-49c3-963f-ae9532d63dc5', 'color', 'description');
+ let connectionProfileGroupArray = [connectionProfileGroupId];
+ let treeNode = new TreeNode('Column', 'label', undefined, undefined, undefined, undefined, undefined, undefined, undefined, undefined);
+ let treeNodeArray = [treeNode];
+
+ setup(() => {
+ let instantiationService = new TestInstantiationService();
+ instantiationService.stub(IStorageService, new TestStorageService());
+ let mockConnectionManagementService = TypeMoq.Mock.ofType(ConnectionManagementService, TypeMoq.MockBehavior.Strict,
+ undefined, //connection store
+ undefined, // connectionstatusmanager
+ undefined, // connectiondialog service
+ instantiationService, // instantiation service
+ undefined, // editor service
+ undefined, // telemetryservice
+ undefined, // configuration service
+ new TestCapabilitiesService(), // capabilities service
+ );
+ serverTreeDragAndDrop = new AsyncServerTreeDragAndDrop(mockConnectionManagementService.object);
+
+ capabilitiesService = new TestCapabilitiesService();
+ capabilitiesService.capabilities[mssqlProviderName] = { connection: msSQLCapabilities };
+ });
+
+
+ test('create new serverTreeDragAndDrop object should create serverTreeDragAndDrop object successfully', async () => {
+
+ assert.equal(serverTreeDragAndDrop !== null || serverTreeDragAndDrop !== undefined, true);
+ });
+
+ test('able to get DragURI', async () => {
+ let uri = serverTreeDragAndDrop.getDragURI(connectionProfile);
+ assert.equal(connectionProfile.id, uri);
+
+ let uriGroup = serverTreeDragAndDrop.getDragURI(connectionProfileGroupId);
+ assert.equal(connectionProfileGroupId.id, uriGroup);
+
+ let uriUndefined = serverTreeDragAndDrop.getDragURI(undefined);
+ assert.equal(null, uriUndefined);
+
+ });
+
+ test('able to get DragLabel', async () => {
+ let label = serverTreeDragAndDrop.getDragLabel(connectionProfileArray);
+ assert.equal(connectionProfileArray[0].serverName, label);
+
+ let labelGroup = serverTreeDragAndDrop.getDragLabel(connectionProfileGroupArray);
+ assert.equal(connectionProfileGroupArray[0].name, labelGroup);
+
+ let labelTreeNode = serverTreeDragAndDrop.getDragLabel(treeNodeArray);
+ assert.equal(treeNodeArray[0].label, labelTreeNode);
+
+ let labelUndefined = serverTreeDragAndDrop.getDragLabel(undefined);
+ assert.equal(undefined, labelUndefined);
+
+ });
+
+
+});
diff --git a/src/sql/workbench/services/serverGroup/common/serverGroupViewModel.ts b/src/sql/workbench/services/serverGroup/common/serverGroupViewModel.ts
index 1807d6b165..b9c1b839c7 100644
--- a/src/sql/workbench/services/serverGroup/common/serverGroupViewModel.ts
+++ b/src/sql/workbench/services/serverGroup/common/serverGroupViewModel.ts
@@ -9,17 +9,18 @@ import * as TypeChecker from 'vs/base/common/types';
import { localize } from 'vs/nls';
import * as strings from 'vs/base/common/strings';
+export const DefaultServerGroupColor = '#515151';
+
export class ServerGroupViewModel {
public groupName: string;
public groupDescription?: string;
public groupColor?: string;
- public colors: string[] = ['#515151', '#004760', '#771b00', '#700060', '#a17d01', '#006749', '#654502', '#3A0293'];
+ public colors: string[] = [DefaultServerGroupColor, '#004760', '#771b00', '#700060', '#a17d01', '#006749', '#654502', '#3A0293'];
private _domainModel?: IConnectionProfileGroup;
private _editMode: boolean;
private readonly _addServerGroupTitle: string = localize('serverGroup.addServerGroup', "Add server group");
private readonly _editServerGroupTitle: string = localize('serverGroup.editServerGroup', "Edit server group");
- private readonly _defaultColor: string = '#515151';
constructor(domainModel?: IConnectionProfileGroup, colors?: string[]) {
// keep reference to domain model to be able to see if there are pending changes
@@ -37,7 +38,7 @@ export class ServerGroupViewModel {
// initialize defaults for a new group
this.groupName = '';
this.groupDescription = '';
- this.groupColor = this._defaultColor;
+ this.groupColor = DefaultServerGroupColor;
this._editMode = false;
}