mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 10:58:30 -05:00
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:
330
extensions/cms/src/apiWrapper.ts
Normal file
330
extensions/cms/src/apiWrapper.ts
Normal file
@@ -0,0 +1,330 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 vscode from 'vscode';
|
||||
import * as azdata from 'azdata';
|
||||
import * as mssql from '../../mssql/src/api/mssqlapis';
|
||||
import * as Utils from './cmsResource/utils';
|
||||
import { ICmsResourceNodeInfo } from './cmsResource/tree/baseTreeNodes';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
/**
|
||||
* Wrapper class to act as a facade over VSCode and Data APIs and allow us to test / mock callbacks into
|
||||
* this API from our code
|
||||
*
|
||||
* @export
|
||||
* ApiWrapper
|
||||
*/
|
||||
export class ApiWrapper {
|
||||
|
||||
private _cmsService: mssql.CmsService;
|
||||
private _registeredCmsServers: ICmsResourceNodeInfo[];
|
||||
|
||||
// Data APIs
|
||||
public registerConnectionProvider(provider: azdata.ConnectionProvider): vscode.Disposable {
|
||||
return azdata.dataprotocol.registerConnectionProvider(provider);
|
||||
}
|
||||
|
||||
public registerObjectExplorerProvider(provider: azdata.ObjectExplorerProvider): vscode.Disposable {
|
||||
return azdata.dataprotocol.registerObjectExplorerProvider(provider);
|
||||
}
|
||||
|
||||
public registerTaskServicesProvider(provider: azdata.TaskServicesProvider): vscode.Disposable {
|
||||
return azdata.dataprotocol.registerTaskServicesProvider(provider);
|
||||
}
|
||||
|
||||
public registerCapabilitiesServiceProvider(provider: azdata.CapabilitiesProvider): vscode.Disposable {
|
||||
return azdata.dataprotocol.registerCapabilitiesServiceProvider(provider);
|
||||
}
|
||||
|
||||
public registerTaskHandler(taskId: string, handler: (profile: azdata.IConnectionProfile) => void): void {
|
||||
azdata.tasks.registerTask(taskId, handler);
|
||||
}
|
||||
|
||||
public startBackgroundOperation(operationInfo: azdata.BackgroundOperationInfo): void {
|
||||
azdata.tasks.startBackgroundOperation(operationInfo);
|
||||
}
|
||||
|
||||
public getActiveConnections(): Thenable<azdata.connection.Connection[]> {
|
||||
return azdata.connection.getActiveConnections();
|
||||
}
|
||||
|
||||
public getCurrentConnection(): Thenable<azdata.connection.ConnectionProfile> {
|
||||
return azdata.connection.getCurrentConnection();
|
||||
}
|
||||
|
||||
// VSCode APIs
|
||||
public createTerminal(name?: string, shellPath?: string, shellArgs?: string[]): vscode.Terminal {
|
||||
return vscode.window.createTerminal(name, shellPath, shellArgs);
|
||||
}
|
||||
|
||||
public createTerminalWithOptions(options: vscode.TerminalOptions): vscode.Terminal {
|
||||
return vscode.window.createTerminal(options);
|
||||
}
|
||||
|
||||
public executeCommand(command: string, ...rest: any[]): Thenable<any> {
|
||||
return vscode.commands.executeCommand(command, ...rest);
|
||||
}
|
||||
|
||||
public getFilePathRelativeToWorkspace(uri: vscode.Uri): string {
|
||||
return vscode.workspace.asRelativePath(uri);
|
||||
}
|
||||
|
||||
public getWorkspaceFolders(): vscode.WorkspaceFolder[] {
|
||||
return vscode.workspace.workspaceFolders;
|
||||
}
|
||||
|
||||
public getWorkspacePathFromUri(uri: vscode.Uri): string | undefined {
|
||||
let workspaceFolder = vscode.workspace.getWorkspaceFolder(uri);
|
||||
return workspaceFolder ? workspaceFolder.uri.fsPath : undefined;
|
||||
}
|
||||
|
||||
public registerCommand(command: string, callback: (...args: any[]) => any, thisArg?: any): vscode.Disposable {
|
||||
return vscode.commands.registerCommand(command, callback, thisArg);
|
||||
}
|
||||
|
||||
public registerDocumentOpenHandler(handler: (doc: vscode.TextDocument) => any): vscode.Disposable {
|
||||
return vscode.workspace.onDidOpenTextDocument(handler);
|
||||
}
|
||||
|
||||
public registerTreeDataProvider<T>(viewId: string, treeDataProvider: vscode.TreeDataProvider<T>): vscode.Disposable {
|
||||
return vscode.window.registerTreeDataProvider(viewId, treeDataProvider);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the configuration for a extensionName
|
||||
* @param extensionName The string name of the extension to get the configuration for
|
||||
* @param resource The optional URI, as a URI object or a string, to use to get resource-scoped configurations
|
||||
*/
|
||||
public getConfiguration(): vscode.WorkspaceConfiguration {
|
||||
return vscode.workspace.getConfiguration('cms');
|
||||
}
|
||||
|
||||
public async setConfiguration(value: any): Promise<void> {
|
||||
await vscode.workspace.getConfiguration('cms').update('cmsServers', value, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse uri
|
||||
*/
|
||||
public parseUri(uri: string): vscode.Uri {
|
||||
return vscode.Uri.parse(uri);
|
||||
}
|
||||
|
||||
public showOpenDialog(options: vscode.OpenDialogOptions): Thenable<vscode.Uri[] | undefined> {
|
||||
return vscode.window.showOpenDialog(options);
|
||||
}
|
||||
|
||||
public showSaveDialog(options: vscode.SaveDialogOptions): Thenable<vscode.Uri> {
|
||||
return vscode.window.showSaveDialog(options);
|
||||
}
|
||||
|
||||
public createDialog(title: string): azdata.window.Dialog {
|
||||
return azdata.window.createModelViewDialog(title);
|
||||
}
|
||||
|
||||
public openDialog(dialog: azdata.window.Dialog): void {
|
||||
return azdata.window.openDialog(dialog);
|
||||
}
|
||||
|
||||
public closeDialog(dialog: azdata.window.Dialog): void {
|
||||
return azdata.window.closeDialog(dialog);
|
||||
}
|
||||
|
||||
public openTextDocument(uri: vscode.Uri): Thenable<vscode.TextDocument>;
|
||||
public openTextDocument(options: { language?: string; content?: string; }): Thenable<vscode.TextDocument>;
|
||||
public openTextDocument(uriOrOptions): Thenable<vscode.TextDocument> {
|
||||
return vscode.workspace.openTextDocument(uriOrOptions);
|
||||
}
|
||||
|
||||
public showTextDocument(document: vscode.TextDocument, column?: vscode.ViewColumn, preserveFocus?: boolean, preview?: boolean): Thenable<vscode.TextEditor> {
|
||||
let options: vscode.TextDocumentShowOptions = {
|
||||
viewColumn: column,
|
||||
preserveFocus: preserveFocus,
|
||||
preview: preview
|
||||
};
|
||||
return vscode.window.showTextDocument(document, options);
|
||||
}
|
||||
|
||||
public showErrorMessage(message: string, ...items: string[]): Thenable<string | undefined> {
|
||||
return vscode.window.showErrorMessage(message, ...items);
|
||||
}
|
||||
|
||||
public showWarningMessage(message: string, ...items: string[]): Thenable<string | undefined> {
|
||||
return vscode.window.showWarningMessage(message, ...items);
|
||||
}
|
||||
|
||||
public showInformationMessage(message: string, ...items: string[]): Thenable<string | undefined> {
|
||||
return vscode.window.showInformationMessage(message, ...items);
|
||||
}
|
||||
|
||||
public createStatusBarItem(alignment?: vscode.StatusBarAlignment, priority?: number): vscode.StatusBarItem {
|
||||
return vscode.window.createStatusBarItem(alignment, priority);
|
||||
}
|
||||
|
||||
public get workspaceFolders(): vscode.WorkspaceFolder[] {
|
||||
return vscode.workspace.workspaceFolders;
|
||||
}
|
||||
|
||||
public createOutputChannel(name: string): vscode.OutputChannel {
|
||||
return vscode.window.createOutputChannel(name);
|
||||
}
|
||||
|
||||
public registerCompletionItemProvider(selector: vscode.DocumentSelector, provider: vscode.CompletionItemProvider, ...triggerCharacters: string[]): vscode.Disposable {
|
||||
return vscode.languages.registerCompletionItemProvider(selector, provider, ...triggerCharacters);
|
||||
}
|
||||
|
||||
// Connection APIs
|
||||
public openConnectionDialog(providers: string[], initialConnectionProfile?: azdata.IConnectionProfile, connectionCompletionOptions?: azdata.IConnectionCompletionOptions): Thenable<azdata.connection.Connection> {
|
||||
return azdata.connection.openConnectionDialog(providers, initialConnectionProfile, connectionCompletionOptions);
|
||||
}
|
||||
|
||||
// CMS APIs
|
||||
public async getCmsService(): Promise<mssql.CmsService> {
|
||||
if (!this._cmsService) {
|
||||
let extensionApi: mssql.MssqlExtensionApi = vscode.extensions.getExtension('Microsoft.mssql').exports;
|
||||
this._cmsService = await extensionApi.getCmsServiceProvider();
|
||||
}
|
||||
return this._cmsService;
|
||||
}
|
||||
|
||||
public async getRegisteredServers(ownerUri: string, relativePath: string): Promise<mssql.ListRegisteredServersResult> {
|
||||
return this.getCmsService().then((service) => {
|
||||
return service.getRegisteredServers(ownerUri, relativePath).then((result) => {
|
||||
if (result && result.registeredServersList && result.registeredServersList) {
|
||||
return result;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public async createCmsServer(connection: azdata.connection.Connection,
|
||||
name: string, description: string): Promise<mssql.ListRegisteredServersResult> {
|
||||
let provider = await this.getCmsService();
|
||||
connection.providerName = connection.providerName === 'MSSQL-CMS' ? 'MSSQL' : connection.providerName;
|
||||
let ownerUri = await azdata.connection.getUriForConnection(connection.connectionId);
|
||||
if (!ownerUri) {
|
||||
// Make a connection if it's not already connected
|
||||
await azdata.connection.connect(Utils.toConnectionProfile(connection), false, false).then(async (result) => {
|
||||
ownerUri = await azdata.connection.getUriForConnection(result.connectionId);
|
||||
});
|
||||
}
|
||||
return provider.createCmsServer(name, description, connection, ownerUri).then((result) => {
|
||||
if (result) {
|
||||
return result;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public async deleteCmsServer(cmsServer: any) {
|
||||
let config = this.getConfiguration();
|
||||
if (config && config.cmsServers) {
|
||||
let newServers = config.cmsServers.filter((cachedServer) => {
|
||||
return cachedServer.name !== cmsServer;
|
||||
});
|
||||
await this.setConfiguration(newServers);
|
||||
this._registeredCmsServers = this._registeredCmsServers.filter((cachedServer) => {
|
||||
return cachedServer.name !== cmsServer;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public cacheRegisteredCmsServer(name: string, description: string, ownerUri: string, connection: azdata.connection.Connection): void {
|
||||
if (!this._registeredCmsServers) {
|
||||
this._registeredCmsServers = [];
|
||||
}
|
||||
let cmsServerNode: ICmsResourceNodeInfo = {
|
||||
name: name,
|
||||
description: description,
|
||||
ownerUri: ownerUri,
|
||||
connection: connection
|
||||
};
|
||||
this._registeredCmsServers.push(cmsServerNode);
|
||||
}
|
||||
|
||||
public async addRegisteredServer(relativePath: string, ownerUri: string,
|
||||
parentServerName?: string): Promise<any> {
|
||||
let provider = await this.getCmsService();
|
||||
// Initial profile to disallow SQL Login without
|
||||
// changing provider.
|
||||
let initialProfile: azdata.IConnectionProfile = {
|
||||
connectionName: undefined,
|
||||
serverName: undefined,
|
||||
databaseName: undefined,
|
||||
userName: undefined,
|
||||
password: undefined,
|
||||
authenticationType: undefined,
|
||||
savePassword: undefined,
|
||||
groupFullName: undefined,
|
||||
groupId: undefined,
|
||||
providerName: undefined,
|
||||
saveProfile: undefined,
|
||||
id: undefined,
|
||||
options: {
|
||||
authTypeChanged: true
|
||||
}
|
||||
};
|
||||
return this.openConnectionDialog(['MSSQL-CMS'], initialProfile, undefined).then((connection) => {
|
||||
if (connection && connection.options) {
|
||||
if (connection.options.server === parentServerName) {
|
||||
// error out for same server registration
|
||||
let errorText = localize('cms.errors.sameServerUnderCMS', 'You cannot add a shared registered server with the same name as the Configuration Server');
|
||||
this.showErrorMessage(errorText);
|
||||
return;
|
||||
} else {
|
||||
return provider.addRegisteredServer(ownerUri, relativePath,
|
||||
connection.options.registeredServerName, connection.options.registeredServerDescription, connection).then((result) => {
|
||||
if (result) {
|
||||
return connection.options.server;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public async removeRegisteredServer(registeredServerName: string, relativePath: string, ownerUri: string): Promise<boolean> {
|
||||
let provider = await this.getCmsService();
|
||||
return provider.removeRegisteredServer(ownerUri, relativePath, registeredServerName).then((result) => {
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
public async addServerGroup(groupName: string, groupDescription: string, relativePath: string, ownerUri: string): Promise<boolean> {
|
||||
let provider = await this.getCmsService();
|
||||
return provider.addServerGroup(ownerUri, relativePath, groupName, groupDescription).then((result) => {
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
public async removeServerGroup(groupName: string, relativePath: string, ownerUri: string): Promise<boolean> {
|
||||
let provider = await this.getCmsService();
|
||||
return provider.removeServerGroup(ownerUri, relativePath, groupName).then((result) => {
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
// Getters
|
||||
|
||||
public get registeredCmsServers(): ICmsResourceNodeInfo[] {
|
||||
return this._registeredCmsServers;
|
||||
}
|
||||
|
||||
public get connection(): Thenable<azdata.connection.Connection> {
|
||||
return this.openConnectionDialog(['MSSQL-CMS'], undefined, undefined).then((connection) => {
|
||||
if (connection) {
|
||||
// remove group ID from connection if a user chose connection
|
||||
// from the recent connections list
|
||||
connection.options['groupId'] = null;
|
||||
connection.providerName = 'MSSQL';
|
||||
return connection;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
19
extensions/cms/src/appContext.ts
Normal file
19
extensions/cms/src/appContext.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 vscode from 'vscode';
|
||||
import { ApiWrapper } from './apiWrapper';
|
||||
|
||||
/**
|
||||
* Global context for the application
|
||||
*/
|
||||
export class AppContext {
|
||||
|
||||
constructor(public readonly extensionContext: vscode.ExtensionContext, public readonly apiWrapper: ApiWrapper) {
|
||||
this.apiWrapper = apiWrapper || new ApiWrapper();
|
||||
}
|
||||
}
|
||||
20
extensions/cms/src/cmsResource/cms-resource.d.ts
vendored
Normal file
20
extensions/cms/src/cmsResource/cms-resource.d.ts
vendored
Normal 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;
|
||||
}
|
||||
}
|
||||
185
extensions/cms/src/cmsResource/cmsResourceCommands.ts
Normal file
185
extensions/cms/src/cmsResource/cmsResourceCommands.ts
Normal 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);
|
||||
});
|
||||
}
|
||||
|
||||
25
extensions/cms/src/cmsResource/commands.ts
Normal file
25
extensions/cms/src/cmsResource/commands.ts
Normal 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);
|
||||
|
||||
}
|
||||
14
extensions/cms/src/cmsResource/constants.ts
Normal file
14
extensions/cms/src/cmsResource/constants.ts
Normal 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'
|
||||
}
|
||||
60
extensions/cms/src/cmsResource/messageTreeNode.ts
Normal file
60
extensions/cms/src/cmsResource/messageTreeNode.ts
Normal 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;
|
||||
}
|
||||
44
extensions/cms/src/cmsResource/tree/baseTreeNodes.ts
Normal file
44
extensions/cms/src/cmsResource/tree/baseTreeNodes.ts
Normal 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;
|
||||
}
|
||||
@@ -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...');
|
||||
}
|
||||
124
extensions/cms/src/cmsResource/tree/cmsResourceTreeNode.ts
Normal file
124
extensions/cms/src/cmsResource/tree/cmsResourceTreeNode.ts
Normal 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');
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
119
extensions/cms/src/cmsResource/tree/serverGroupTreeNode.ts
Normal file
119
extensions/cms/src/cmsResource/tree/serverGroupTreeNode.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
12
extensions/cms/src/cmsResource/tree/treeChangeHandler.ts
Normal file
12
extensions/cms/src/cmsResource/tree/treeChangeHandler.ts
Normal 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;
|
||||
}
|
||||
104
extensions/cms/src/cmsResource/tree/treeProvider.ts
Normal file
104
extensions/cms/src/cmsResource/tree/treeProvider.ts
Normal 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 ...');
|
||||
}
|
||||
77
extensions/cms/src/cmsResource/treeNode.ts
Normal file
77
extensions/cms/src/cmsResource/treeNode.ts
Normal 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;
|
||||
}
|
||||
39
extensions/cms/src/cmsResource/utils.ts
Normal file
39
extensions/cms/src/cmsResource/utils.ts
Normal 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;
|
||||
}
|
||||
24
extensions/cms/src/controllers/cmsResourceController.ts
Normal file
24
extensions/cms/src/controllers/cmsResourceController.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 ControllerBase from './controllerBase';
|
||||
import { CmsResourceTreeProvider } from '../cmsResource/tree/treeProvider';
|
||||
import { registerCmsResourceCommands } from '../cmsResource/commands';
|
||||
|
||||
export default class CmsResourceController extends ControllerBase {
|
||||
public activate(): Promise<boolean> {
|
||||
|
||||
const cmsResourceTree = new CmsResourceTreeProvider(this.appContext);
|
||||
this.extensionContext.subscriptions.push(this.apiWrapper.registerTreeDataProvider('cmsResourceExplorer', cmsResourceTree));
|
||||
registerCmsResourceCommands(this.appContext, cmsResourceTree);
|
||||
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
public deactivate(): void {
|
||||
}
|
||||
}
|
||||
34
extensions/cms/src/controllers/controllerBase.ts
Normal file
34
extensions/cms/src/controllers/controllerBase.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 vscode from 'vscode';
|
||||
|
||||
import { AppContext } from '../appContext';
|
||||
import { ApiWrapper } from '../apiWrapper';
|
||||
|
||||
export default abstract class ControllerBase implements vscode.Disposable {
|
||||
|
||||
public constructor(protected appContext: AppContext) {
|
||||
}
|
||||
|
||||
protected get apiWrapper(): ApiWrapper {
|
||||
return this.appContext.apiWrapper;
|
||||
}
|
||||
|
||||
public get extensionContext(): vscode.ExtensionContext {
|
||||
return this.appContext && this.appContext.extensionContext;
|
||||
}
|
||||
|
||||
abstract activate(): Promise<boolean>;
|
||||
|
||||
abstract deactivate(): void;
|
||||
|
||||
public dispose(): void {
|
||||
this.deactivate();
|
||||
}
|
||||
}
|
||||
|
||||
34
extensions/cms/src/extension.ts
Normal file
34
extensions/cms/src/extension.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 vscode from 'vscode';
|
||||
import CmsResourceController from './controllers/cmsResourceController';
|
||||
import { AppContext } from './appContext';
|
||||
import ControllerBase from './controllers/controllerBase';
|
||||
import { ApiWrapper } from './apiWrapper';
|
||||
|
||||
let controllers: ControllerBase[] = [];
|
||||
|
||||
// this method is called when your extension is activated
|
||||
// your extension is activated the very first time the command is executed
|
||||
export function activate(extensionContext: vscode.ExtensionContext) {
|
||||
const apiWrapper = new ApiWrapper();
|
||||
let appContext = new AppContext(extensionContext, apiWrapper);
|
||||
let activations: Thenable<boolean>[] = [];
|
||||
|
||||
const cmsResourceController = new CmsResourceController(appContext);
|
||||
controllers.push(cmsResourceController);
|
||||
extensionContext.subscriptions.push(cmsResourceController);
|
||||
activations.push(cmsResourceController.activate());
|
||||
}
|
||||
|
||||
// this method is called when your extension is deactivated
|
||||
export function deactivate() {
|
||||
for (let controller of controllers) {
|
||||
controller.deactivate();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 should from 'should';
|
||||
import * as vscode from 'vscode';
|
||||
import 'mocha';
|
||||
import { CmsResourceEmptyTreeNode } from '../../../cmsResource/tree/cmsResourceEmptyTreeNode';
|
||||
import { CmsResourceItemType } from '../../../cmsResource/constants';
|
||||
|
||||
|
||||
describe('CmsResourceEmptyTreeNode.info', function(): void {
|
||||
it('Should be correct.', async function(): Promise<void> {
|
||||
const label = 'Add Central Management Server...';
|
||||
|
||||
const treeNode = new CmsResourceEmptyTreeNode();
|
||||
let children = await treeNode.getChildren();
|
||||
should.equal(0, children.length);
|
||||
should(treeNode.nodePathValue).equal('message_cmsTreeNode');
|
||||
|
||||
const treeItem = await treeNode.getTreeItem();
|
||||
should(treeItem.label).equal(label);
|
||||
should(treeItem.contextValue).equal(CmsResourceItemType.cmsEmptyNodeContainer);
|
||||
should(treeItem.collapsibleState).equal(vscode.TreeItemCollapsibleState.None);
|
||||
should(treeItem.command).not.undefined();
|
||||
should(treeItem.command.title).equal(label);
|
||||
should(treeItem.command.command).equal('cms.resource.registerCMSServer');
|
||||
|
||||
const nodeInfo = treeNode.getNodeInfo();
|
||||
should(nodeInfo.isLeaf).true();
|
||||
should(nodeInfo.label).equal(label);
|
||||
should(nodeInfo.nodeType).equal(CmsResourceItemType.cmsEmptyNodeContainer);
|
||||
should(nodeInfo.iconType).equal(CmsResourceItemType.cmsEmptyNodeContainer);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,62 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 TypeMoq from 'typemoq';
|
||||
import * as should from 'should';
|
||||
import * as vscode from 'vscode';
|
||||
import 'mocha';
|
||||
import { AppContext } from '../../../appContext';
|
||||
import { ApiWrapper } from '../../../apiWrapper';
|
||||
|
||||
import { CmsResourceItemType } from '../../../cmsResource/constants';
|
||||
import { ServerGroupTreeNode } from '../../../cmsResource/tree/serverGroupTreeNode';
|
||||
import { ICmsResourceTreeChangeHandler } from '../../../cmsResource/tree/treeChangeHandler';
|
||||
import { cmsResource } from '../../../cmsResource/cms-resource';
|
||||
|
||||
// Mock services
|
||||
let mockAppContext: AppContext;
|
||||
|
||||
let mockExtensionContext: TypeMoq.IMock<vscode.ExtensionContext>;
|
||||
let mockApiWrapper: TypeMoq.IMock<ApiWrapper>;
|
||||
let mockTreeChangeHandler: TypeMoq.IMock<ICmsResourceTreeChangeHandler>;
|
||||
|
||||
let mockResourceTreeDataProvider1: TypeMoq.IMock<cmsResource.ICmsResourceTreeDataProvider>;
|
||||
let mockResourceProvider1: TypeMoq.IMock<cmsResource.ICmsResourceProvider>;
|
||||
|
||||
describe('ServerGroupTreeNode.info', function(): void {
|
||||
beforeEach(() => {
|
||||
mockExtensionContext = TypeMoq.Mock.ofType<vscode.ExtensionContext>();
|
||||
mockApiWrapper = TypeMoq.Mock.ofType<ApiWrapper>();
|
||||
mockAppContext = new AppContext(mockExtensionContext.object, mockApiWrapper.object);
|
||||
mockTreeChangeHandler = TypeMoq.Mock.ofType<ICmsResourceTreeChangeHandler>();
|
||||
mockResourceTreeDataProvider1 = TypeMoq.Mock.ofType<cmsResource.ICmsResourceTreeDataProvider>();
|
||||
mockResourceTreeDataProvider1.setup((o) => o.getChildren()).returns(() => Promise.resolve([TypeMoq.Mock.ofType<cmsResource.ICmsResourceNode>().object]));
|
||||
mockResourceTreeDataProvider1.setup((o) => o.getTreeItem(TypeMoq.It.isAny())).returns(() => Promise.resolve(TypeMoq.It.isAny()));
|
||||
mockResourceProvider1 = TypeMoq.Mock.ofType<cmsResource.ICmsResourceProvider>();
|
||||
mockResourceProvider1.setup((o) => o.providerId).returns(() => 'mockResourceProvider1');
|
||||
mockResourceProvider1.setup((o) => o.getTreeDataProvider()).returns(() => mockResourceTreeDataProvider1.object);
|
||||
});
|
||||
|
||||
it('Should be correct.', async function(): Promise<void> {
|
||||
const label = 'test';
|
||||
|
||||
const treeNode = new ServerGroupTreeNode('test', 'test', 'test_path', 'test_ownerUri', mockAppContext, mockTreeChangeHandler.object, null);
|
||||
|
||||
should(treeNode.nodePathValue).equal('cms_serverGroup_test');
|
||||
should(treeNode.relativePath).equal('test_path');
|
||||
|
||||
const treeItem = await treeNode.getTreeItem();
|
||||
should(treeItem.label).equal(label);
|
||||
should(treeItem.contextValue).equal(CmsResourceItemType.serverGroup);
|
||||
should(treeItem.collapsibleState).equal(vscode.TreeItemCollapsibleState.Collapsed);
|
||||
should(treeItem.command).undefined();
|
||||
|
||||
const nodeInfo = treeNode.getNodeInfo();
|
||||
should(nodeInfo.isLeaf).false();
|
||||
should(nodeInfo.label).equal(label);
|
||||
should(nodeInfo.nodeType).equal(CmsResourceItemType.serverGroup);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,62 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 TypeMoq from 'typemoq';
|
||||
import * as should from 'should';
|
||||
import * as vscode from 'vscode';
|
||||
import 'mocha';
|
||||
import { AppContext } from '../../../appContext';
|
||||
import { ApiWrapper } from '../../../apiWrapper';
|
||||
|
||||
import { CmsResourceItemType } from '../../../cmsResource/constants';
|
||||
import { RegisteredServerTreeNode } from '../../../cmsResource/tree/registeredServerTreeNode';
|
||||
import { ICmsResourceTreeChangeHandler } from '../../../cmsResource/tree/treeChangeHandler';
|
||||
import { cmsResource } from '../../../cmsResource/cms-resource';
|
||||
|
||||
// Mock services
|
||||
let mockAppContext: AppContext;
|
||||
|
||||
let mockExtensionContext: TypeMoq.IMock<vscode.ExtensionContext>;
|
||||
let mockApiWrapper: TypeMoq.IMock<ApiWrapper>;
|
||||
let mockTreeChangeHandler: TypeMoq.IMock<ICmsResourceTreeChangeHandler>;
|
||||
|
||||
let mockResourceTreeDataProvider1: TypeMoq.IMock<cmsResource.ICmsResourceTreeDataProvider>;
|
||||
let mockResourceProvider1: TypeMoq.IMock<cmsResource.ICmsResourceProvider>;
|
||||
|
||||
describe('RegisteredServerTreeNode.info', function(): void {
|
||||
beforeEach(() => {
|
||||
mockExtensionContext = TypeMoq.Mock.ofType<vscode.ExtensionContext>();
|
||||
mockApiWrapper = TypeMoq.Mock.ofType<ApiWrapper>();
|
||||
mockAppContext = new AppContext(mockExtensionContext.object, mockApiWrapper.object);
|
||||
mockTreeChangeHandler = TypeMoq.Mock.ofType<ICmsResourceTreeChangeHandler>();
|
||||
mockResourceTreeDataProvider1 = TypeMoq.Mock.ofType<cmsResource.ICmsResourceTreeDataProvider>();
|
||||
mockResourceTreeDataProvider1.setup((o) => o.getChildren()).returns(() => Promise.resolve([TypeMoq.Mock.ofType<cmsResource.ICmsResourceNode>().object]));
|
||||
mockResourceTreeDataProvider1.setup((o) => o.getTreeItem(TypeMoq.It.isAny())).returns(() => Promise.resolve(TypeMoq.It.isAny()));
|
||||
mockResourceProvider1 = TypeMoq.Mock.ofType<cmsResource.ICmsResourceProvider>();
|
||||
mockResourceProvider1.setup((o) => o.providerId).returns(() => 'mockResourceProvider1');
|
||||
mockResourceProvider1.setup((o) => o.getTreeDataProvider()).returns(() => mockResourceTreeDataProvider1.object);
|
||||
});
|
||||
|
||||
it('Should be correct.', async function(): Promise<void> {
|
||||
const label = 'test';
|
||||
|
||||
const treeNode = new RegisteredServerTreeNode('test', 'test', 'test_server', 'test_path', 'test_ownerUri', mockAppContext, mockTreeChangeHandler.object, null);
|
||||
|
||||
should(treeNode.nodePathValue).equal('cms_registeredServer_test');
|
||||
should(treeNode.relativePath).equal('test_path');
|
||||
|
||||
const treeItem = await treeNode.getTreeItem();
|
||||
should(treeItem.label).equal(label);
|
||||
should(treeItem.contextValue).equal(CmsResourceItemType.serverGroup);
|
||||
should(treeItem.collapsibleState).equal(vscode.TreeItemCollapsibleState.Collapsed);
|
||||
should(treeItem.command).undefined();
|
||||
|
||||
const nodeInfo = treeNode.getNodeInfo();
|
||||
should(nodeInfo.isLeaf).false();
|
||||
should(nodeInfo.label).equal(label);
|
||||
should(nodeInfo.nodeType).equal(CmsResourceItemType.serverGroup);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,62 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 TypeMoq from 'typemoq';
|
||||
import * as should from 'should';
|
||||
import * as vscode from 'vscode';
|
||||
import 'mocha';
|
||||
import { AppContext } from '../../../appContext';
|
||||
import { ApiWrapper } from '../../../apiWrapper';
|
||||
|
||||
import { CmsResourceItemType } from '../../../cmsResource/constants';
|
||||
import { ServerGroupTreeNode } from '../../../cmsResource/tree/serverGroupTreeNode';
|
||||
import { ICmsResourceTreeChangeHandler } from '../../../cmsResource/tree/treeChangeHandler';
|
||||
import { cmsResource } from '../../../cmsResource/cms-resource';
|
||||
|
||||
// Mock services
|
||||
let mockAppContext: AppContext;
|
||||
|
||||
let mockExtensionContext: TypeMoq.IMock<vscode.ExtensionContext>;
|
||||
let mockApiWrapper: TypeMoq.IMock<ApiWrapper>;
|
||||
let mockTreeChangeHandler: TypeMoq.IMock<ICmsResourceTreeChangeHandler>;
|
||||
|
||||
let mockResourceTreeDataProvider1: TypeMoq.IMock<cmsResource.ICmsResourceTreeDataProvider>;
|
||||
let mockResourceProvider1: TypeMoq.IMock<cmsResource.ICmsResourceProvider>;
|
||||
|
||||
describe('ServerGroupTreeNode.info', function(): void {
|
||||
beforeEach(() => {
|
||||
mockExtensionContext = TypeMoq.Mock.ofType<vscode.ExtensionContext>();
|
||||
mockApiWrapper = TypeMoq.Mock.ofType<ApiWrapper>();
|
||||
mockAppContext = new AppContext(mockExtensionContext.object, mockApiWrapper.object);
|
||||
mockTreeChangeHandler = TypeMoq.Mock.ofType<ICmsResourceTreeChangeHandler>();
|
||||
mockResourceTreeDataProvider1 = TypeMoq.Mock.ofType<cmsResource.ICmsResourceTreeDataProvider>();
|
||||
mockResourceTreeDataProvider1.setup((o) => o.getChildren()).returns(() => Promise.resolve([TypeMoq.Mock.ofType<cmsResource.ICmsResourceNode>().object]));
|
||||
mockResourceTreeDataProvider1.setup((o) => o.getTreeItem(TypeMoq.It.isAny())).returns(() => Promise.resolve(TypeMoq.It.isAny()));
|
||||
mockResourceProvider1 = TypeMoq.Mock.ofType<cmsResource.ICmsResourceProvider>();
|
||||
mockResourceProvider1.setup((o) => o.providerId).returns(() => 'mockResourceProvider1');
|
||||
mockResourceProvider1.setup((o) => o.getTreeDataProvider()).returns(() => mockResourceTreeDataProvider1.object);
|
||||
});
|
||||
|
||||
it('Should be correct.', async function(): Promise<void> {
|
||||
const label = 'test';
|
||||
|
||||
const treeNode = new ServerGroupTreeNode('test', 'test', 'test_path', 'test_ownerUri', mockAppContext, mockTreeChangeHandler.object, null);
|
||||
|
||||
should(treeNode.nodePathValue).equal('cms_serverGroup_test');
|
||||
should(treeNode.relativePath).equal('test_path');
|
||||
|
||||
const treeItem = await treeNode.getTreeItem();
|
||||
should(treeItem.label).equal(label);
|
||||
should(treeItem.contextValue).equal(CmsResourceItemType.serverGroup);
|
||||
should(treeItem.collapsibleState).equal(vscode.TreeItemCollapsibleState.Collapsed);
|
||||
should(treeItem.command).undefined();
|
||||
|
||||
const nodeInfo = treeNode.getNodeInfo();
|
||||
should(nodeInfo.isLeaf).false();
|
||||
should(nodeInfo.label).equal(label);
|
||||
should(nodeInfo.nodeType).equal(CmsResourceItemType.serverGroup);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,65 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 vscode from 'vscode';
|
||||
import * as should from 'should';
|
||||
import * as TypeMoq from 'typemoq';
|
||||
import 'mocha';
|
||||
import { AppContext } from '../../../appContext';
|
||||
import { ApiWrapper } from '../../../apiWrapper';
|
||||
|
||||
import { CmsResourceTreeProvider } from '../../../cmsResource/tree/treeProvider';
|
||||
import { CmsResourceMessageTreeNode } from '../../../cmsResource/messageTreeNode';
|
||||
import { CmsResourceEmptyTreeNode } from '../../../cmsResource/tree/cmsResourceEmptyTreeNode';
|
||||
import { CmsResourceTreeNode } from '../../../cmsResource/tree/cmsResourceTreeNode';
|
||||
|
||||
// Mock services
|
||||
let mockAppContext: AppContext;
|
||||
let mockExtensionContext: TypeMoq.IMock<vscode.ExtensionContext>;
|
||||
let mockApiWrapper: TypeMoq.IMock<ApiWrapper>;
|
||||
|
||||
|
||||
describe('CmsResourceTreeProvider.getChildren', function (): void {
|
||||
beforeEach(() => {
|
||||
mockExtensionContext = TypeMoq.Mock.ofType<vscode.ExtensionContext>();
|
||||
mockApiWrapper = TypeMoq.Mock.ofType<ApiWrapper>();
|
||||
mockAppContext = new AppContext(mockExtensionContext.object, mockApiWrapper.object);
|
||||
});
|
||||
|
||||
it('Should not be initialized.', async function (): Promise<void> {
|
||||
const treeProvider = new CmsResourceTreeProvider(mockAppContext);
|
||||
should.notEqual(treeProvider.isSystemInitialized, true);
|
||||
const children = await treeProvider.getChildren(undefined);
|
||||
should.equal(children.length, 1);
|
||||
should.equal(children[0].parent, undefined);
|
||||
should.equal(children[0] instanceof CmsResourceMessageTreeNode, true);
|
||||
});
|
||||
|
||||
it('Should not be loading after initialized.'), async function (): Promise<void> {
|
||||
const treeProvider = new CmsResourceTreeProvider(mockAppContext);
|
||||
treeProvider.isSystemInitialized = true;
|
||||
should.equal(true, treeProvider.isSystemInitialized);
|
||||
mockApiWrapper.setup(x => x.registeredCmsServers).returns(null);
|
||||
const children = await treeProvider.getChildren(undefined);
|
||||
should.equal(children[0] instanceof CmsResourceEmptyTreeNode, true);
|
||||
};
|
||||
|
||||
it('Should show CMS nodes if there are cached servers'), async function (): Promise<void> {
|
||||
const treeProvider = new CmsResourceTreeProvider(mockAppContext);
|
||||
treeProvider.isSystemInitialized = true;
|
||||
mockApiWrapper.setup(x => x.registeredCmsServers).returns(() => {
|
||||
return [{
|
||||
name: 'name',
|
||||
description: 'description',
|
||||
ownerUri: 'ownerUri',
|
||||
connection: null
|
||||
}];
|
||||
});
|
||||
const children = await treeProvider.getChildren(undefined);
|
||||
should.equal(children[0] instanceof CmsResourceTreeNode, true);
|
||||
};
|
||||
});
|
||||
30
extensions/cms/src/test/index.ts
Normal file
30
extensions/cms/src/test/index.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
const path = require('path');
|
||||
const testRunner = require('vscode/lib/testrunner');
|
||||
|
||||
const suite = 'CMS Unit Tests';
|
||||
|
||||
const options: any = {
|
||||
ui: 'bdd',
|
||||
useColors: true,
|
||||
timeout: 60000
|
||||
};
|
||||
|
||||
if (process.env.BUILD_ARTIFACTSTAGINGDIRECTORY) {
|
||||
options.reporter = 'mocha-multi-reporters';
|
||||
options.reporterOptions = {
|
||||
reporterEnabled: 'spec, mocha-junit-reporter',
|
||||
mochaJunitReporterReporterOptions: {
|
||||
testsuitesTitle: `${suite} ${process.platform}`,
|
||||
mochaFile: path.join(process.env.BUILD_ARTIFACTSTAGINGDIRECTORY, `test-results/${process.platform}-${suite.toLowerCase().replace(/[^\w]/g, '-')}-results.xml`)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
testRunner.configure(options);
|
||||
|
||||
export = testRunner;
|
||||
41
extensions/cms/src/test/messageTreeNode.test.ts
Normal file
41
extensions/cms/src/test/messageTreeNode.test.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 should from 'should';
|
||||
import * as vscode from 'vscode';
|
||||
import 'mocha';
|
||||
|
||||
import { CmsResourceItemType } from '../cmsResource/constants';
|
||||
import { CmsResourceMessageTreeNode } from '../cmsResource/messageTreeNode';
|
||||
|
||||
describe('CmsResourceMessageTreeNode.info', function (): void {
|
||||
it('Should be correct when created.', async function (): Promise<void> {
|
||||
const mockMessage = 'Test message';
|
||||
const treeNode = new CmsResourceMessageTreeNode(mockMessage, undefined);
|
||||
|
||||
should(treeNode.nodePathValue).startWith('message_');
|
||||
|
||||
const treeItem = await treeNode.getTreeItem();
|
||||
should(treeItem.label).equal(mockMessage);
|
||||
should(treeItem.contextValue).equal(CmsResourceItemType.cmsMessageNodeContainer);
|
||||
should(treeItem.collapsibleState).equal(vscode.TreeItemCollapsibleState.None);
|
||||
|
||||
const nodeInfo = treeNode.getNodeInfo();
|
||||
should(nodeInfo.isLeaf).true();
|
||||
should(nodeInfo.label).equal(mockMessage);
|
||||
should(nodeInfo.nodeType).equal(CmsResourceItemType.cmsMessageNodeContainer);
|
||||
should(nodeInfo.iconType).equal(CmsResourceItemType.cmsMessageNodeContainer);
|
||||
});
|
||||
});
|
||||
|
||||
describe('CmsResourceMessageTreeNode.create', function (): void {
|
||||
it('Should create a message node.', async function (): Promise<void> {
|
||||
const mockMessage = 'Test messagse';
|
||||
const treeNode = CmsResourceMessageTreeNode.create(mockMessage, undefined);
|
||||
should(treeNode).instanceof(CmsResourceMessageTreeNode);
|
||||
});
|
||||
});
|
||||
9
extensions/cms/src/typings/ref.d.ts
vendored
Normal file
9
extensions/cms/src/typings/ref.d.ts
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
/// <reference path='../../../../src/vs/vscode.d.ts'/>
|
||||
/// <reference path='../../../../src/sql/azdata.d.ts'/>
|
||||
/// <reference path='../../../../src/sql/azdata.proposed.d.ts'/>
|
||||
/// <reference types='@types/node'/>
|
||||
Reference in New Issue
Block a user