diff --git a/samples/sqlservices/README.md b/samples/sqlservices/README.md index f872792f22..de2b337fa0 100644 --- a/samples/sqlservices/README.md +++ b/samples/sqlservices/README.md @@ -1,4 +1,4 @@ -This is a sample extension that will show some basic model-backed UI scenarios. The long-term goal is to use SQL Service querying (e.g. see if Agent and other services are running) and visualize in interesting ways. Additional suggestions for improving this sample are welcome. +This is a sample extension that will show some basic model-backed UI scenarios and how to contribute feature providers(e.g. Connection, Object Explorer) in ADS. Note: only implement the providers this way if your data service has native JavaScript SDK available, otherwise use [data protocol client](https://github.com/microsoft/sqlops-dataprotocolclient), please refer to [SQL Tools Service] (https://github.com/microsoft/sqltoolsservice) or [PG Tools Service](https://github.com/microsoft/pgtoolsservice) as examples. ## Run the following commands to produce an extension installation package @@ -11,4 +11,4 @@ This is a sample extension that will show some basic model-backed UI scenarios. - `yarn build` - to build the code - Launch VSCode and open the azuredatastudio's code folder, run the 'Launch azuredatastudio' debug option (to work around the issue. The next step won't work without doing this first) - Launch VSCode and open this folder, run the 'Debug in enlistment' -- Once ADS launches, you should be able to run the sqlservices commands, for example: sqlservices.openDialog \ No newline at end of file +- Once ADS launches, you should be able to run the sqlservices commands, for example: sqlservices.openDialog diff --git a/samples/sqlservices/package.json b/samples/sqlservices/package.json index 4853275685..f2b256f805 100644 --- a/samples/sqlservices/package.json +++ b/samples/sqlservices/package.json @@ -82,7 +82,122 @@ ] } } - ] + ], + "connectionProvider": { + "providerId": "TESTPROVIDER", + "languageMode": "sql", + "notebookKernelAlias": "Test Provider", + "displayName": "Test Provider", + "iconPath": [ + { + "id": "myprovidericon", + "path": { + "light": "images/user.svg", + "dark": "images/user_inverse.svg" + }, + "default": true + } + ], + "connectionOptions": [ + { + "specialValueType": "connectionName", + "isIdentity": true, + "name": "connectionName", + "displayName": "", + "description": "", + "groupName": "Source", + "valueType": "string", + "defaultValue": null, + "objectType": null, + "categoryValues": null, + "isRequired": false, + "isArray": false + }, + { + "specialValueType": "serverName", + "isIdentity": true, + "name": "server", + "displayName": "Server name", + "description": "Server name", + "groupName": "Source", + "valueType": "string", + "defaultValue": null, + "objectType": null, + "categoryValues": null, + "isRequired": true, + "isArray": false + }, + { + "specialValueType": "authType", + "isIdentity": true, + "name": "authenticationType", + "displayName": "Authentication type", + "description": "", + "groupName": "Security", + "valueType": "category", + "defaultValue": "SqlLogin", + "objectType": null, + "categoryValues": [ + { + "displayName": "Basic", + "name": "SqlLogin" + } + ], + "isRequired": true, + "isArray": false + }, + { + "specialValueType": "userName", + "isIdentity": true, + "name": "user", + "displayName": "Username", + "description": "", + "groupName": "Security", + "valueType": "string", + "defaultValue": null, + "objectType": null, + "categoryValues": null, + "isRequired": true, + "isArray": false + }, + { + "specialValueType": "password", + "isIdentity": true, + "name": "password", + "displayName": "Password", + "description": "", + "groupName": "Security", + "valueType": "password", + "defaultValue": null, + "objectType": null, + "categoryValues": null, + "isRequired": true, + "isArray": false + }, + { + "specialValueType": "appName", + "isIdentity": false, + "name": "applicationName", + "displayName": "Application Name", + "description": "", + "groupName": "Context", + "valueType": "string", + "defaultValue": null, + "objectType": null, + "categoryValues": null, + "isRequired": false, + "isArray": false + } + ] + }, + "menus": { + "objectExplorer/item/context": [ + { + "command": "sqlservices.openDialog", + "when": "nodeType =~ /^(Database|Server)$/ && connectionProvider == TESTPROVIDER" + } + ] + } }, "scripts": { "build": "gulp build", diff --git a/samples/sqlservices/src/Providers/IconProvider.ts b/samples/sqlservices/src/Providers/IconProvider.ts new file mode 100644 index 0000000000..78f86d19e8 --- /dev/null +++ b/samples/sqlservices/src/Providers/IconProvider.ts @@ -0,0 +1,18 @@ +/*--------------------------------------------------------------------------------------------- + * 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'; +import { ProviderId } from './connectionProvider'; + +const IconId = 'myprovidericon'; +/** + * This class implements the IconProvider interface that allows users to contribute icons for the root tree node instead of using the generic server icon. + */ +export class IconProvider implements azdata.IconProvider { + public readonly providerId: string = ProviderId; + public handle?: number; + getConnectionIconId(connection: azdata.IConnectionProfile, serverInfo: azdata.ServerInfo): Thenable { + return Promise.resolve(IconId); + } +} diff --git a/samples/sqlservices/src/Providers/connectionProvider.ts b/samples/sqlservices/src/Providers/connectionProvider.ts new file mode 100644 index 0000000000..1f57a0f6b8 --- /dev/null +++ b/samples/sqlservices/src/Providers/connectionProvider.ts @@ -0,0 +1,86 @@ +/*--------------------------------------------------------------------------------------------- + * 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'; +import * as vscode from 'vscode'; + +export const ProviderId: string = 'TESTPROVIDER'; + +/** + * This class implements the ConnectionProvider interface that allows users to connect to the data services, this will be used by various features in ADS, e.g. connection dialog and query editor. + */ +export class ConnectionProvider implements azdata.ConnectionProvider { + private onConnectionCompleteEmitter: vscode.EventEmitter = new vscode.EventEmitter(); + onConnectionComplete: vscode.Event = this.onConnectionCompleteEmitter.event; + + private onIntelliSenseCacheCompleteEmitter: vscode.EventEmitter = new vscode.EventEmitter(); + onIntelliSenseCacheComplete: vscode.Event = this.onIntelliSenseCacheCompleteEmitter.event; + + private onConnectionChangedEmitter: vscode.EventEmitter = new vscode.EventEmitter(); + onConnectionChanged: vscode.Event = this.onConnectionChangedEmitter.event; + + connect(connectionUri: string, connectionInfo: azdata.ConnectionInfo): Promise { + this.onConnectionCompleteEmitter.fire({ + connectionId: '123', + ownerUri: connectionUri, + messages: '', + errorMessage: '', + errorNumber: 0, + connectionSummary: { + serverName: '', + userName: '' + }, + serverInfo: { + serverReleaseVersion: 1, + engineEditionId: 1, + serverVersion: '1.0', + serverLevel: '', + serverEdition: '', + isCloud: true, + azureVersion: 1, + osVersion: '', + options: {} + } + }); + return Promise.resolve(true); + } + disconnect(connectionUri: string): Promise { + return Promise.resolve(true); + } + cancelConnect(connectionUri: string): Promise { + return Promise.resolve(true); + } + listDatabases(connectionUri: string): Promise { + return Promise.resolve({ + databaseNames: ['master', 'msdb'] + }); + } + changeDatabase(connectionUri: string, newDatabase: string): Promise { + return Promise.resolve(true); + } + rebuildIntelliSenseCache(connectionUri: string): Promise { + return Promise.resolve(); + } + getConnectionString(connectionUri: string, includePassword: boolean): Promise { + return Promise.resolve('conn_string'); + } + buildConnectionInfo?(connectionString: string): Promise { + return Promise.resolve({ + options: [] + }); + } + registerOnConnectionComplete(handler: (connSummary: azdata.ConnectionInfoSummary) => any): void { + this.onConnectionComplete((e) => { + handler(e); + }); + } + registerOnIntelliSenseCacheComplete(handler: (connectionUri: string) => any): void { + console.log('IntellisenseCache complete'); + } + registerOnConnectionChanged(handler: (changedConnInfo: azdata.ChangedConnectionInfo) => any): void { + console.log('Connection changed'); + } + handle?: number; + providerId: string = ProviderId; +} diff --git a/samples/sqlservices/src/Providers/objectExplorerProvider.ts b/samples/sqlservices/src/Providers/objectExplorerProvider.ts new file mode 100644 index 0000000000..f6bba8a130 --- /dev/null +++ b/samples/sqlservices/src/Providers/objectExplorerProvider.ts @@ -0,0 +1,90 @@ +/*--------------------------------------------------------------------------------------------- + * 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'; +import * as vscode from 'vscode'; +import { ProviderId } from './connectionProvider'; + +/** + * This class implements the ObjectExplorerProvider interface that is responsible for providing the connection tree view content. + */ +export class ObjectExplorerProvider implements azdata.ObjectExplorerProvider { + onSessionCreatedEmitter: vscode.EventEmitter = new vscode.EventEmitter(); + onSessionCreated: vscode.Event = this.onSessionCreatedEmitter.event; + + onSessionDisconnectedEmitter: vscode.EventEmitter = new vscode.EventEmitter(); + onSessionDisconnected: vscode.Event = this.onSessionDisconnectedEmitter.event; + + onExpandCompletedEmitter: vscode.EventEmitter = new vscode.EventEmitter(); + onExpandCompleted: vscode.Event = this.onExpandCompletedEmitter.event; + + createNewSession(connInfo: azdata.ConnectionInfo): Thenable { + setTimeout(() => { + this.onSessionCreatedEmitter.fire({ + sessionId: '1', + success: true, + rootNode: { + nodePath: 'root', + nodeType: 'server', + label: 'abc', + isLeaf: false + } + }); + }, 500); + return Promise.resolve({ + sessionId: '1' + }); + } + closeSession(closeSessionInfo: azdata.ObjectExplorerCloseSessionInfo): Thenable { + return Promise.resolve({ + sessionId: '1', + success: true + }); + } + registerOnSessionCreated(handler: (response: azdata.ObjectExplorerSession) => any): void { + this.onSessionCreated((e) => { + handler(e); + }); + } + registerOnSessionDisconnected?(handler: (response: azdata.ObjectExplorerSession) => any): void { + this.onSessionDisconnected((e) => { + handler(e); + }); + } + expandNode(nodeInfo: azdata.ExpandNodeInfo): Thenable { + setTimeout(() => { + this.onExpandCompletedEmitter.fire({ + sessionId: nodeInfo.sessionId, + nodePath: nodeInfo.nodePath, + nodes: [ + { + nodePath: 'root/1', + nodeType: 'Database', + label: 'abc1', + isLeaf: false + }, { + nodePath: 'root/2', + nodeType: 'Database', + label: 'abc2', + isLeaf: false + } + ] + }); + }, 500); + return Promise.resolve(true); + } + refreshNode(nodeInfo: azdata.ExpandNodeInfo): Thenable { + return Promise.resolve(true); + } + findNodes(findNodesInfo: azdata.FindNodesInfo): Thenable { + throw new Error('Method not implemented.'); + } + registerOnExpandCompleted(handler: (response: azdata.ObjectExplorerExpandInfo) => any): void { + this.onExpandCompleted((e) => { + handler(e); + }); + } + handle?: number; + providerId: string = ProviderId; +} diff --git a/samples/sqlservices/src/controllers/connectionProvider.ts b/samples/sqlservices/src/controllers/connectionProvider.ts new file mode 100644 index 0000000000..7733ff9559 --- /dev/null +++ b/samples/sqlservices/src/controllers/connectionProvider.ts @@ -0,0 +1,82 @@ +/*--------------------------------------------------------------------------------------------- + * 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'; +import * as vscode from 'vscode'; + +export class ConnectionProvider implements azdata.ConnectionProvider { + private onConnectionCompleteEmitter: vscode.EventEmitter = new vscode.EventEmitter(); + onConnectionComplete: vscode.Event = this.onConnectionCompleteEmitter.event; + + private onIntelliSenseCacheCompleteEmitter: vscode.EventEmitter = new vscode.EventEmitter(); + onIntelliSenseCacheComplete: vscode.Event = this.onIntelliSenseCacheCompleteEmitter.event; + + private onConnectionChangedEmitter: vscode.EventEmitter = new vscode.EventEmitter(); + onConnectionChanged: vscode.Event = this.onConnectionChangedEmitter.event; + + connect(connectionUri: string, connectionInfo: azdata.ConnectionInfo): Promise { + this.onConnectionCompleteEmitter.fire({ + connectionId: '', + ownerUri: connectionUri, + messages: '', + errorMessage: '', + errorNumber: 0, + connectionSummary: { + serverName: '', + userName: '' + }, + serverInfo: { + serverReleaseVersion: 1, + engineEditionId: 1, + serverVersion: '1.0', + serverLevel: '', + serverEdition: '', + isCloud: true, + azureVersion: 1, + osVersion: '', + options: {} + } + }); + return Promise.resolve(true); + } + disconnect(connectionUri: string): Promise { + return Promise.resolve(true); + } + cancelConnect(connectionUri: string): Promise { + return Promise.resolve(true); + } + listDatabases(connectionUri: string): Promise { + return Promise.resolve({ + databaseNames: ['master', 'msdb'] + }); + } + changeDatabase(connectionUri: string, newDatabase: string): Promise { + return Promise.resolve(true); + } + rebuildIntelliSenseCache(connectionUri: string): Promise { + return Promise.resolve(); + } + getConnectionString(connectionUri: string, includePassword: boolean): Promise { + return Promise.resolve('conn_string'); + } + buildConnectionInfo?(connectionString: string): Promise { + return Promise.resolve({ + options: [] + }); + } + registerOnConnectionComplete(handler: (connSummary: azdata.ConnectionInfoSummary) => any): void { + this.onConnectionComplete((e) => { + handler(e); + }); + } + registerOnIntelliSenseCacheComplete(handler: (connectionUri: string) => any): void { + console.log('IntellisenseCache complete'); + } + registerOnConnectionChanged(handler: (changedConnInfo: azdata.ChangedConnectionInfo) => any): void { + console.log('Connection changed'); + } + handle?: number; + providerId: string = 'testProvider'; + +} diff --git a/samples/sqlservices/src/controllers/mainController.ts b/samples/sqlservices/src/controllers/mainController.ts index d97f2078be..13dd8e8012 100644 --- a/samples/sqlservices/src/controllers/mainController.ts +++ b/samples/sqlservices/src/controllers/mainController.ts @@ -13,6 +13,9 @@ import * as fs from 'fs'; import * as path from 'path'; import { TreeNode, TreeDataProvider } from './treeDataProvider'; import * as dashboard from './modelViewDashboard'; +import { ConnectionProvider } from '../providers/connectionProvider'; +import { IconProvider } from '../providers/IconProvider'; +import { ObjectExplorerProvider } from '../providers/objectExplorerProvider'; /** * The main controller class that initializes the extension @@ -37,6 +40,13 @@ export default class MainController implements vscode.Disposable { } public activate(): Promise { + const connectionProvider = new ConnectionProvider(); + const iconProvider = new IconProvider(); + const objectExplorer = new ObjectExplorerProvider(); + azdata.dataprotocol.registerConnectionProvider(connectionProvider); + azdata.dataprotocol.registerIconProvider(iconProvider); + azdata.dataprotocol.registerObjectExplorerProvider(objectExplorer); + const buttonHtml = fs.readFileSync(path.join(__dirname, 'button.html')).toString(); const counterHtml = fs.readFileSync(path.join(__dirname, 'counter.html')).toString(); this.registerSqlServicesModelView();