CMS Extension - 2 (#4908)

* first set of changes to experiment the registration of cms related apis

* Adding cms service entry to workbench

* Adding basic functionality for add remove reg servers and group

* Returning relative path as part of RegServerResult as string

* initial extension

* cleaned building with connecting to server

* get list of registered servers

* progress with registered servers tree

* cms base node with server selection

* removed unused services

* replaced azure stuff with cms

* removed cmsResourceService

* list servers progress

* Removing the cms apis from core. Having mssql extension expose them for cms extension

* create server working fine

* initial expansion and nodes

* Propogating the backend name changes to apis

* initial cms extension working

* cached connection needs change in api

* connect without dashboard in proposed

* Fixing some missing sqlops references

* add registered server bug found

* added refresh context menu option

* added payload

* server description not disabled after reject connection

* added more context actions and action icons

* added empty resource and error when same name server is added

* fixed connection issues with cms and normal connections

* added initial tests

* added cms icons

* removed azure readme

* test script revert

* fix build tests

* added more cms tests

* fixed test script

* fixed silent error when expanding servers

* added more cms tests

* removed cmsdialog from api

* cms dialog without object

* fixed theming issues

* initial connection dialog done

* can make connections

* PM asks for strings and icons

* removed search

* removed unused code and fixed 1 test

* fix connection management tests

* changed icons

* format file

* fixed hygiene

* initial cr comments

* refactored cms connection dialog

* fixed bug when switching dialogs

* localized connection provider options

* fixed cms provider name

* code review comments

* localized options in cms and mssql

* localized more options
This commit is contained in:
Aditya Bist
2019-04-29 15:16:59 -07:00
committed by GitHub
parent cbf3ca726f
commit 39772c2dbe
60 changed files with 4595 additions and 156 deletions

View File

@@ -0,0 +1,20 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { TreeDataProvider, TreeItem } from 'vscode';
import { DataProvider, Account } from 'azdata';
export namespace cmsResource {
export interface ICmsResourceProvider extends DataProvider {
getTreeDataProvider(): ICmsResourceTreeDataProvider;
}
export interface ICmsResourceTreeDataProvider extends TreeDataProvider<ICmsResourceNode> {
}
export interface ICmsResourceNode {
readonly treeItem: TreeItem;
}
}

View File

@@ -0,0 +1,185 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as nls from 'vscode-nls';
import * as azdata from 'azdata';
import { AppContext } from '../appContext';
import { TreeNode } from './treeNode';
import { CmsResourceTreeProvider } from './tree/treeProvider';
import { CmsResourceEmptyTreeNode } from './tree/cmsResourceEmptyTreeNode';
import { RegisteredServerTreeNode } from './tree/registeredServerTreeNode';
import { ServerGroupTreeNode } from './tree/serverGroupTreeNode';
import { CmsResourceTreeNode } from './tree/cmsResourceTreeNode';
const localize = nls.loadMessageBundle();
export function registerCmsServerCommand(appContext: AppContext, tree: CmsResourceTreeProvider): void {
// Create a CMS Server
appContext.apiWrapper.registerCommand('cms.resource.registerCmsServer', async (node?: TreeNode) => {
if (node && !(node instanceof CmsResourceEmptyTreeNode)) {
return;
}
await appContext.apiWrapper.connection.then(async (connection) => {
if (connection && connection.options) {
let registeredCmsServerName = connection.options.registeredServerName ?
connection.options.registeredServerName : connection.options.server;
// check if a CMS with the same name is registered or not
let cachedServers = appContext.apiWrapper.registeredCmsServers;
let serverExists: boolean = false;
if (cachedServers) {
serverExists = cachedServers.some((server) => {
return server.name === registeredCmsServerName;
});
}
if (!serverExists) {
// remove any group ID if user selects a connection from
// recent connection list
connection.options.groupId = null;
let registeredCmsServerDescription = connection.options.registeredServerDescription;
// remove server description from connection uri
connection.options.registeredCmsServerDescription = null;
let ownerUri = await azdata.connection.getUriForConnection(connection.connectionId);
appContext.apiWrapper.cacheRegisteredCmsServer(registeredCmsServerName, registeredCmsServerDescription, ownerUri, connection);
tree.notifyNodeChanged(undefined);
} else {
// error out for same server name
let errorText = localize('cms.errors.sameCmsServerName', 'Central Management Server Group already has a Registered Server with the name {0}', registeredCmsServerName);
appContext.apiWrapper.showErrorMessage(errorText);
return;
}
}
});
});
}
export function deleteCmsServerCommand(appContext: AppContext, tree: CmsResourceTreeProvider): void {
// Delete a CMS Server
appContext.apiWrapper.registerCommand('cms.resource.deleteCmsServer', async (node?: TreeNode) => {
if (!(node instanceof CmsResourceTreeNode)) {
return;
}
await appContext.apiWrapper.deleteCmsServer(node.name);
tree.isSystemInitialized = false;
tree.notifyNodeChanged(undefined);
});
}
export function addRegisteredServerCommand(appContext: AppContext, tree: CmsResourceTreeProvider): void {
// Add a registered server
appContext.apiWrapper.registerCommand('cms.resource.addRegisteredServer', async (node?: TreeNode) => {
if (!(node instanceof CmsResourceTreeNode || node instanceof ServerGroupTreeNode)) {
return;
}
let relativePath = node instanceof CmsResourceTreeNode ? '' : node.relativePath;
let serverName = node instanceof CmsResourceTreeNode ? node.connection.options.server : null;
await appContext.apiWrapper.addRegisteredServer(relativePath, node.ownerUri, serverName).then((result) => {
if (result) {
tree.notifyNodeChanged(undefined);
}
});
});
}
export function deleteRegisteredServerCommand(appContext: AppContext, tree: CmsResourceTreeProvider): void {
// Delete a registered server
appContext.apiWrapper.registerCommand('cms.resource.deleteRegisteredServer', async (node?: TreeNode) => {
if (!(node instanceof RegisteredServerTreeNode)) {
return;
}
appContext.apiWrapper.removeRegisteredServer(node.name, node.relativePath, node.ownerUri).then((result) => {
if (result) {
tree.notifyNodeChanged(undefined);
}
});
});
}
export function addServerGroupCommand(appContext: AppContext, tree: CmsResourceTreeProvider): void {
// Add a registered server group
appContext.apiWrapper.registerCommand('cms.resource.addServerGroup', async (node?: TreeNode) => {
if (!(node instanceof ServerGroupTreeNode || node instanceof CmsResourceTreeNode)) {
return;
}
// add a dialog for adding a group
let title = localize('cms.AddServerGroup', 'Add Server Group');
let dialog = azdata.window.createModelViewDialog(title, 'cms.addServerGroup');
dialog.okButton.label = localize('cms.OK', 'OK');
dialog.cancelButton.label = localize('cms.Cancel', 'Cancel');
let mainTab = azdata.window.createTab(title);
let serverGroupName: string = null;
let serverDescription: string = null;
mainTab.registerContent(async view => {
let nameTextBox = view.modelBuilder.inputBox().component();
nameTextBox.required = true;
nameTextBox.onTextChanged((e) => {
serverGroupName = e;
});
if (nameTextBox.value && nameTextBox.value.length > 0) {
dialog.message = null;
}
let descriptionTextBox = view.modelBuilder.inputBox().component();
descriptionTextBox.required = false;
descriptionTextBox.onTextChanged((e) => {
serverDescription = e;
});
let formModel = view.modelBuilder.formContainer()
.withFormItems([{
component: nameTextBox,
title: localize('cms.ServerGroupName', 'Server Group Name')
}, {
component: descriptionTextBox,
title: localize('cms.ServerGroupDescription', 'Server Group Description')
}]).withLayout({ width: '100%' }).component();
await view.initializeModel(formModel);
});
dialog.content = [mainTab];
azdata.window.openDialog(dialog);
let groupExists = false;
dialog.okButton.onClick(() => {
let path = node instanceof ServerGroupTreeNode ? node.relativePath : '';
if (node.serverGroupNodes.some(node => node.name === serverGroupName)) {
groupExists = true;
}
if (!groupExists) {
appContext.apiWrapper.addServerGroup(serverGroupName, serverDescription, path, node.ownerUri).then((result) => {
if (result) {
tree.notifyNodeChanged(undefined);
}
});
} else {
// error out for same server group
let errorText = localize('cms.errors.sameServerGroupName', '{0} already has a Server Group with the name {1}', node.name, serverGroupName);
appContext.apiWrapper.showErrorMessage(errorText);
return;
}
});
});
}
export function deleteServerGroupCommand(appContext: AppContext, tree: CmsResourceTreeProvider): void {
// Remove a registered server group
appContext.apiWrapper.registerCommand('cms.resource.deleteServerGroup', async (node?: TreeNode) => {
if (!(node instanceof ServerGroupTreeNode)) {
return;
}
appContext.apiWrapper.removeServerGroup(node.name, node.relativePath, node.ownerUri).then((result) => {
if (result) {
tree.notifyNodeChanged(undefined);
}
});
});
}
export function refreshCommand(appContext: AppContext, tree: CmsResourceTreeProvider): void {
// Refresh the cms resource
appContext.apiWrapper.registerCommand('cms.resource.refresh', async (node?: TreeNode) => {
if (!node) {
return;
}
tree.notifyNodeChanged(undefined);
});
}

View File

@@ -0,0 +1,25 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { AppContext } from '../appContext';
import { CmsResourceTreeProvider } from './tree/treeProvider';
import {
registerCmsServerCommand, deleteCmsServerCommand,
addRegisteredServerCommand, deleteRegisteredServerCommand,
deleteServerGroupCommand, addServerGroupCommand, refreshCommand
} from './cmsResourceCommands';
export function registerCmsResourceCommands(appContext: AppContext, tree: CmsResourceTreeProvider): void {
registerCmsServerCommand(appContext, tree);
deleteCmsServerCommand(appContext, tree);
addRegisteredServerCommand(appContext, tree);
deleteRegisteredServerCommand(appContext, tree);
addServerGroupCommand(appContext, tree);
deleteServerGroupCommand(appContext, tree);
refreshCommand(appContext, tree);
}

View File

@@ -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.
*--------------------------------------------------------------------------------------------*/
'use strict';
export enum CmsResourceItemType {
cmsMessageNodeContainer = 'cms.resource.itemType.cmsMessageNodeContainer',
cmsEmptyNodeContainer = 'cms.resource.itemType.cmsEmptyNodeContainer',
cmsNodeContainer = 'cms.resource.itemType.cmsNodeContainer',
registeredServer = 'cms.resource.itemType.registeredServer',
serverGroup = 'cms.resource.itemType.serverGroup'
}

View File

@@ -0,0 +1,60 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { TreeItem, TreeItemCollapsibleState } from 'vscode';
import { NodeInfo } from 'azdata';
import { TreeNode } from './treeNode';
import { CmsResourceItemType } from './constants';
export class CmsResourceMessageTreeNode extends TreeNode {
public constructor(
public readonly message: string,
parent: TreeNode
) {
super();
this.parent = parent;
this._id = `message_${CmsResourceMessageTreeNode._messageNum++}`;
}
public static create(message: string, parent: TreeNode): CmsResourceMessageTreeNode {
return new CmsResourceMessageTreeNode(message, parent);
}
public getChildren(): TreeNode[] | Promise<TreeNode[]> {
return [];
}
public getTreeItem(): TreeItem | Promise<TreeItem> {
let item = new TreeItem(this.message, TreeItemCollapsibleState.None);
item.contextValue = CmsResourceItemType.cmsMessageNodeContainer;
return item;
}
public getNodeInfo(): NodeInfo {
return {
label: this.message,
isLeaf: true,
errorMessage: undefined,
metadata: undefined,
nodePath: this.generateNodePath(),
nodeStatus: undefined,
nodeType: CmsResourceItemType.cmsMessageNodeContainer,
nodeSubType: undefined,
iconType: CmsResourceItemType.cmsMessageNodeContainer
};
}
public get nodePathValue(): string {
return this._id;
}
private _id: string;
private static _messageNum: number = 0;
}

View File

@@ -0,0 +1,44 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as azdata from 'azdata';
import { AppContext } from '../../appContext';
import { TreeNode } from '../treeNode';
import { ICmsResourceTreeChangeHandler } from './treeChangeHandler';
export abstract class CmsResourceTreeNodeBase extends TreeNode {
public constructor(
private _name: string,
private _description: string,
private _ownerUri: string,
public readonly appContext: AppContext,
public readonly treeChangeHandler: ICmsResourceTreeChangeHandler,
parent: TreeNode
) {
super();
this.parent = parent;
}
public get name(): string {
return this._name;
}
public get description(): string {
return this._description;
}
public get ownerUri(): string {
return this._ownerUri;
}
}
export interface ICmsResourceNodeInfo {
name: string;
description: string;
ownerUri: string;
connection: azdata.connection.Connection;
}

View File

@@ -0,0 +1,51 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { TreeItem, TreeItemCollapsibleState } from 'vscode';
import { NodeInfo } from 'azdata';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
import { TreeNode } from '../treeNode';
import { CmsResourceItemType } from '../constants';
export class CmsResourceEmptyTreeNode extends TreeNode {
public getChildren(): TreeNode[] | Promise<TreeNode[]> {
return [];
}
public getTreeItem(): TreeItem | Promise<TreeItem> {
let item = new TreeItem(CmsResourceEmptyTreeNode.addCmsServerLabel, TreeItemCollapsibleState.None);
item.command = {
title: CmsResourceEmptyTreeNode.addCmsServerLabel,
command: 'cms.resource.registerCMSServer',
arguments: [this]
};
item.contextValue = CmsResourceItemType.cmsEmptyNodeContainer;
return item;
}
public getNodeInfo(): NodeInfo {
return {
label: CmsResourceEmptyTreeNode.addCmsServerLabel,
isLeaf: true,
errorMessage: undefined,
metadata: undefined,
nodePath: this.generateNodePath(),
nodeStatus: undefined,
nodeType: CmsResourceItemType.cmsEmptyNodeContainer,
iconType: CmsResourceItemType.cmsEmptyNodeContainer,
nodeSubType: undefined
};
}
public get nodePathValue(): string {
return 'message_cmsTreeNode';
}
private static readonly addCmsServerLabel = localize('cms.resource.tree.CMSTreeNode.addCmsServerLabel', 'Add Central Management Server...');
}

View File

@@ -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.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as azdata from 'azdata';
import * as nls from 'vscode-nls';
import { TreeItemCollapsibleState } from 'vscode';
import { AppContext } from '../../appContext';
import { TreeNode } from '../treeNode';
import { CmsResourceTreeNodeBase } from './baseTreeNodes';
import { CmsResourceItemType } from '../constants';
import { ICmsResourceTreeChangeHandler } from './treeChangeHandler';
import { RegisteredServerTreeNode } from './registeredServerTreeNode';
import { ServerGroupTreeNode } from './serverGroupTreeNode';
import { CmsResourceMessageTreeNode } from '../messageTreeNode';
const localize = nls.loadMessageBundle();
export class CmsResourceTreeNode extends CmsResourceTreeNodeBase {
private _id: string = undefined;
private _serverGroupNodes: ServerGroupTreeNode[] = [];
public constructor(
name: string,
description: string,
ownerUri: string,
private _connection: azdata.connection.Connection,
appContext: AppContext,
treeChangeHandler: ICmsResourceTreeChangeHandler,
parent: TreeNode
) {
super(name, description, ownerUri, appContext, treeChangeHandler, parent);
this._id = `cms_cmsServer_${this.name}`;
}
public async getChildren(): Promise<TreeNode[]> {
try {
let nodes: TreeNode[] = [];
return this.appContext.apiWrapper.createCmsServer(this.connection, this.name, this.description).then(async (result) => {
if (result) {
if (result.registeredServersList) {
result.registeredServersList.forEach((registeredServer) => {
nodes.push(new RegisteredServerTreeNode(
registeredServer.name,
registeredServer.description,
registeredServer.serverName,
registeredServer.relativePath,
this.ownerUri,
this.appContext,
this.treeChangeHandler, this));
});
}
if (result.registeredServerGroups) {
if (result.registeredServerGroups) {
this._serverGroupNodes = [];
result.registeredServerGroups.forEach((serverGroup) => {
let serverGroupNode = new ServerGroupTreeNode(
serverGroup.name,
serverGroup.description,
serverGroup.relativePath,
this.ownerUri,
this.appContext,
this.treeChangeHandler, this);
nodes.push(serverGroupNode);
this._serverGroupNodes.push(serverGroupNode);
});
}
}
if (nodes.length > 0) {
return nodes;
} else {
return [CmsResourceMessageTreeNode.create(CmsResourceTreeNode.noResourcesLabel, undefined)];
}
}
});
} catch {
return [];
}
}
public getTreeItem(): azdata.TreeItem | Promise<azdata.TreeItem> {
const item = new azdata.TreeItem(this.name, TreeItemCollapsibleState.Collapsed);
item.contextValue = CmsResourceItemType.cmsNodeContainer;
item.id = this._id;
item.tooltip = this.description;
item.iconPath = {
dark: this.appContext.extensionContext.asAbsolutePath('resources/light/centralmanagement_server.svg'),
light: this.appContext.extensionContext.asAbsolutePath('resources/light/centralmanagement_server.svg')
};
return item;
}
public getNodeInfo(): azdata.NodeInfo {
return {
label: this.name,
isLeaf: false,
errorMessage: undefined,
metadata: undefined,
nodePath: this.generateNodePath(),
nodeStatus: undefined,
nodeType: CmsResourceItemType.cmsNodeContainer,
nodeSubType: undefined,
iconType: CmsResourceItemType.cmsNodeContainer
};
}
public get nodePathValue(): string {
return this._id;
}
public get connection(): azdata.connection.Connection {
return this._connection;
}
public get serverGroupNodes(): ServerGroupTreeNode[] {
return this._serverGroupNodes;
}
public static readonly noResourcesLabel = localize('cms.resource.cmsResourceTreeNode.noResourcesLabel', 'No resources found');
}

View File

@@ -0,0 +1,91 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as azdata from 'azdata';
import { TreeItemCollapsibleState } from 'vscode';
import { TreeNode } from '../treeNode';
import { CmsResourceItemType } from '../constants';
import { CmsResourceTreeNodeBase } from './baseTreeNodes';
import { AppContext } from '../../appContext';
import { ICmsResourceTreeChangeHandler } from './treeChangeHandler';
import { generateGuid } from '../utils';
export class RegisteredServerTreeNode extends CmsResourceTreeNodeBase {
private _id: string = undefined;
constructor(
name: string,
description: string,
private serverName: string,
private _relativePath: string,
ownerUri: string,
appContext: AppContext,
treeChangeHandler: ICmsResourceTreeChangeHandler,
parent: TreeNode
) {
super(name, description, ownerUri, appContext, treeChangeHandler, parent);
this._id = `cms_registeredServer_${this.name ? this.name : this.serverName}`;
}
public async getChildren(): Promise<TreeNode[]> {
return;
}
public getTreeItem(): azdata.TreeItem | Promise<azdata.TreeItem> {
let payload = {
id: generateGuid(),
connectionName: this.name ? this.name : this.serverName,
serverName: this.serverName,
databaseName: '',
userName: undefined,
password: undefined,
authenticationType: 'Integrated',
savePassword: false,
groupFullName: '',
groupId: '',
providerName: 'MSSQL',
saveProfile: false,
options: {}
};
let treeItem = {
payload: payload,
id: this._id,
tooltip: this.description,
contextValue: CmsResourceItemType.registeredServer,
collapsibleState: TreeItemCollapsibleState.Collapsed,
label: this.name ? this.name : this.serverName,
childProvider: 'MSSQL',
iconPath: {
dark: this.appContext.extensionContext.asAbsolutePath('resources/light/regserverserver.svg'),
light: this.appContext.extensionContext.asAbsolutePath('resources/light/regserverserver.svg')
}
};
return treeItem;
}
public getNodeInfo(): azdata.NodeInfo {
return {
label: this.name ? this.name : this.serverName,
isLeaf: false,
errorMessage: undefined,
metadata: undefined,
nodePath: this.generateNodePath(),
nodeStatus: undefined,
nodeType: CmsResourceItemType.registeredServer,
nodeSubType: undefined
};
}
public get nodePathValue(): string {
return this._id;
}
public get relativePath(): string {
return this._relativePath;
}
}

View File

@@ -0,0 +1,119 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { TreeItem, TreeItemCollapsibleState } from 'vscode';
import * as azdata from 'azdata';
import { TreeNode } from '../treeNode';
import { CmsResourceItemType } from '../constants';
import { CmsResourceTreeNodeBase } from './baseTreeNodes';
import { AppContext } from '../../appContext';
import { ICmsResourceTreeChangeHandler } from './treeChangeHandler';
import { RegisteredServerTreeNode } from './registeredServerTreeNode';
import { CmsResourceMessageTreeNode } from '../messageTreeNode';
import { CmsResourceTreeNode } from './cmsResourceTreeNode';
export class ServerGroupTreeNode extends CmsResourceTreeNodeBase {
private _id: string = undefined;
private _serverGroupNodes: ServerGroupTreeNode[] = [];
constructor(
name: string,
description: string,
private _relativePath: string,
ownerUri: string,
appContext: AppContext,
treeChangeHandler: ICmsResourceTreeChangeHandler,
parent: TreeNode
) {
super(name, description, ownerUri, appContext, treeChangeHandler, parent);
this._id = `cms_serverGroup_${this.name}`;
}
public getChildren(): TreeNode[] | Promise<TreeNode[]> {
try {
let nodes = [];
return this.appContext.apiWrapper.getRegisteredServers(this.ownerUri, this.relativePath).then((result) => {
if (result) {
if (result.registeredServersList) {
result.registeredServersList.forEach((registeredServer) => {
nodes.push(new RegisteredServerTreeNode(
registeredServer.name,
registeredServer.description,
registeredServer.serverName,
registeredServer.relativePath,
this.ownerUri,
this.appContext,
this.treeChangeHandler, this));
});
}
if (result.registeredServerGroups) {
if (result.registeredServerGroups) {
this._serverGroupNodes = [];
result.registeredServerGroups.forEach((serverGroup) => {
let serverGroupNode = new ServerGroupTreeNode(
serverGroup.name,
serverGroup.description,
serverGroup.relativePath,
this.ownerUri,
this.appContext, this.treeChangeHandler, this);
nodes.push(serverGroupNode);
this._serverGroupNodes.push(serverGroupNode);
});
}
}
if (nodes.length > 0) {
return nodes;
} else {
return [CmsResourceMessageTreeNode.create(CmsResourceTreeNode.noResourcesLabel, undefined)];
}
} else {
return [CmsResourceMessageTreeNode.create(CmsResourceTreeNode.noResourcesLabel, undefined)];
}
});
} catch {
return [];
}
}
public getTreeItem(): TreeItem | Promise<TreeItem> {
let item = new TreeItem(this.name, TreeItemCollapsibleState.Collapsed);
item.contextValue = CmsResourceItemType.serverGroup;
item.id = this._id;
item.tooltip = this.description;
item.iconPath = {
dark: this.appContext.extensionContext.asAbsolutePath('resources/light/folder.svg'),
light: this.appContext.extensionContext.asAbsolutePath('resources/light/folder.svg')
};
return item;
}
public getNodeInfo(): azdata.NodeInfo {
return {
label: this.name,
isLeaf: false,
errorMessage: undefined,
metadata: undefined,
nodePath: this.generateNodePath(),
nodeStatus: undefined,
nodeType: CmsResourceItemType.serverGroup,
nodeSubType: undefined
};
}
public get nodePathValue(): string {
return this._id;
}
public get relativePath(): string {
return this._relativePath;
}
public get serverGroupNodes(): ServerGroupTreeNode[] {
return this._serverGroupNodes;
}
}

View File

@@ -0,0 +1,12 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { TreeNode } from '../treeNode';
export interface ICmsResourceTreeChangeHandler {
notifyNodeChanged(node: TreeNode): void;
}

View File

@@ -0,0 +1,104 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { TreeDataProvider, EventEmitter, Event, TreeItem } from 'vscode';
import { AppContext } from '../../appContext';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
import { TreeNode } from '../treeNode';
import { CmsResourceEmptyTreeNode } from './cmsResourceEmptyTreeNode';
import { ICmsResourceTreeChangeHandler } from './treeChangeHandler';
import { CmsResourceMessageTreeNode } from '../messageTreeNode';
import { CmsResourceTreeNode } from './cmsResourceTreeNode';
export class CmsResourceTreeProvider implements TreeDataProvider<TreeNode>, ICmsResourceTreeChangeHandler {
private _appContext: AppContext;
public constructor(
public readonly appContext: AppContext
) {
this._appContext = appContext;
}
public async getChildren(element?: TreeNode): Promise<TreeNode[]> {
if (element) {
return element.getChildren(true);
}
if (!this.isSystemInitialized) {
try {
// Call to collect all locally saved CMS servers
// to determine whether the system has been initialized.
let cmsConfig = this._appContext.apiWrapper.getConfiguration();
let cachedServers = cmsConfig ? cmsConfig.cmsServers : [];
if (cachedServers && cachedServers.length > 0) {
let servers = [];
cachedServers.forEach((server) => {
servers.push(new CmsResourceTreeNode(
server.name,
server.description,
server.ownerUri,
server.connection,
this._appContext, this, null));
this.appContext.apiWrapper.cacheRegisteredCmsServer(server.name, server.description,
server.ownerUri, server.connection);
});
return servers;
}
this.isSystemInitialized = true;
this._onDidChangeTreeData.fire(undefined);
} catch (error) {
// System not initialized yet
this.isSystemInitialized = false;
}
return [CmsResourceMessageTreeNode.create(CmsResourceTreeProvider.loadingLabel, undefined)];
}
try {
let registeredCmsServers = this.appContext.apiWrapper.registeredCmsServers;
if (registeredCmsServers && registeredCmsServers.length > 0) {
this.isSystemInitialized = true;
// save the CMS Servers for future use
await this._appContext.apiWrapper.setConfiguration(registeredCmsServers);
return registeredCmsServers.map((server) => {
return new CmsResourceTreeNode(
server.name,
server.description,
server.ownerUri,
server.connection,
this._appContext, this, null);
});
} else {
return [new CmsResourceEmptyTreeNode()];
}
} catch (error) {
return [new CmsResourceEmptyTreeNode()];
}
}
public get onDidChangeTreeData(): Event<TreeNode> {
return this._onDidChangeTreeData.event;
}
public notifyNodeChanged(node: TreeNode): void {
this._onDidChangeTreeData.fire(node);
}
public async refresh(node: TreeNode): Promise<void> {
this._onDidChangeTreeData.fire(node);
}
public getTreeItem(element: TreeNode): TreeItem | Thenable<TreeItem> {
return element.getTreeItem();
}
public isSystemInitialized: boolean = false;
private _onDidChangeTreeData = new EventEmitter<TreeNode>();
private static readonly loadingLabel = localize('cms.resource.tree.treeProvider.loadingLabel', 'Loading ...');
}

View File

@@ -0,0 +1,77 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as azdata from 'azdata';
import * as vscode from 'vscode';
type TreeNodePredicate = (node: TreeNode) => boolean;
export abstract class TreeNode {
public generateNodePath(): string {
let path = undefined;
if (this.parent) {
path = this.parent.generateNodePath();
}
path = path ? `${path}/${this.nodePathValue}` : this.nodePathValue;
return path;
}
public findNodeByPath(path: string, expandIfNeeded: boolean = false): Promise<TreeNode> {
let condition: TreeNodePredicate = (node: TreeNode) => node.getNodeInfo().nodePath === path;
let filter: TreeNodePredicate = (node: TreeNode) => path.startsWith(node.getNodeInfo().nodePath);
return TreeNode.findNode(this, condition, filter, true);
}
public static async findNode(node: TreeNode, condition: TreeNodePredicate, filter: TreeNodePredicate, expandIfNeeded: boolean): Promise<TreeNode> {
if (!node) {
return undefined;
}
if (condition(node)) {
return node;
}
let nodeInfo = node.getNodeInfo();
if (nodeInfo.isLeaf) {
return undefined;
}
// TODO support filtering by already expanded / not yet expanded
let children = await node.getChildren(false);
if (children) {
for (let child of children) {
if (filter && filter(child)) {
let childNode = await this.findNode(child, condition, filter, expandIfNeeded);
if (childNode) {
return childNode;
}
}
}
}
return undefined;
}
public get parent(): TreeNode {
return this._parent;
}
public set parent(node: TreeNode) {
this._parent = node;
}
public abstract getChildren(refreshChildren: boolean): TreeNode[] | Promise<TreeNode[]>;
public abstract getTreeItem(): vscode.TreeItem | Promise<vscode.TreeItem>;
public abstract getNodeInfo(): azdata.NodeInfo;
/**
* The value to use for this node in the node path
*/
public abstract get nodePathValue(): string;
private _parent: TreeNode = undefined;
}

View File

@@ -0,0 +1,39 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as azdata from 'azdata';
export function generateGuid(): string {
let hexValues: string[] = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'];
let oct: string = '';
let tmp: number;
for (let a: number = 0; a < 4; a++) {
tmp = (4294967296 * Math.random()) | 0;
oct += hexValues[tmp & 0xF] +
hexValues[tmp >> 4 & 0xF] +
hexValues[tmp >> 8 & 0xF] +
hexValues[tmp >> 12 & 0xF] +
hexValues[tmp >> 16 & 0xF] +
hexValues[tmp >> 20 & 0xF] +
hexValues[tmp >> 24 & 0xF] +
hexValues[tmp >> 28 & 0xF];
}
let clockSequenceHi: string = hexValues[8 + (Math.random() * 4) | 0];
return oct.substr(0, 8) + '-' + oct.substr(9, 4) + '-4' + oct.substr(13, 3) + '-' + clockSequenceHi + oct.substr(16, 3) + '-' + oct.substr(19, 12);
}
export function toConnectionProfile(connectionInfo: azdata.connection.Connection): azdata.IConnectionProfile {
let options = connectionInfo.options;
let connProfile: azdata.IConnectionProfile = Object.assign(<azdata.IConnectionProfile>{},
connectionInfo,
{
serverName: `${options['server']}`,
userName: options['user'],
password: options['password'],
id: connectionInfo.connectionId,
}
);
return connProfile;
}