From 48b7d96999acbeefee3aaff959d623814bac4d2b Mon Sep 17 00:00:00 2001
From: Justin M <63619224+JustinMDotNet@users.noreply.github.com>
Date: Tue, 6 Jul 2021 15:27:19 -0700
Subject: [PATCH] Add Azure Monitor Extension (#15397)
* Added Azure Log Analytics resource for generating AAD Token.
* Fixed AzureResource
* Removed debug code from connectionManagementService
* Moved AzureLogAnalytics from AzureResource enum in azdata.d.ts to azdata.proposed.d.ts. Added azureLogAnalyticsResource to all azureSettings in providerSettings.ts
* Updated endpoint for generating AAD Token for LogAnalytics for UsGov, UsNat, and China
* Initial Commit of Azure Monitor Extension
* Added extension name to azuremonitor package strings
* Removed azureMonitor resource from germanyCloud in providerSettings
* Added logic to exclude menuItems in object explorer for LogAnalytics
* Changed exe from AzureMonitor to Kusto
* Added if clause for queryName for new queries
* Changed queryWindow name from KustoQuery to KQLQuery for Kusto and LogAnalytics.
* Added LogAnalytics for setTaskBarContent
* Added serialization and telemetry feature classes to AzureMonitor. Added references for azdata and vscode.
* Added azure monitor light and dark icons
* Added config for Dashboard in package.json
* Added workspace information to dashboard
* Added language support for LogAnalytics
* Added Notebook support
* Added Hide flag to package.json for databaseName
* Changed providerId from LogAnalytics to LOGANALYTICS
* Changed Workspace to Workspace ID in package.nls.json
* Added support for Azure Widget browser
* Changed fullName to use workspaceId when connecting
* Changed providerId from alertsManagement to azureMonitor
* Added .gitignore and *.vsix to vscodeignore.
* Removed unused devDependencies
* Code Review Feedback
* Changed tsconfig.json to match Kusto and Sql
* Changed package.json to match kusto package.
* Changed tsconfig to validate unused params and implictAny. Changed existing code to satisfy build.
* Fixed tsconfig to use the correct base class.
* Added objectExplorerNodeProvider and all related classes.
* Removed unused tmLanguage file
* Added logic to to download extension from toolservice
* Fixed launchArgs. Removed commented code from extension.ts. Changed config.json to use net5.0
* Added displayName to package.nls.json. Removed hide flag from databaseName. Other code review feedback.
* Added readme info to AzureMonitor
* Removed unused client-error-handler and ui-references files. Combined outputChannel in azuremonitorServer. Removed TODO from contextProvider. Renamed function in extension.ts. Removed unneeded 'use strict' from cancelableStream.ts. Removed second outputChannel from objectExplorerNodeProvider.
* Removed unused files
---
build/lib/extensions.ts | 1 +
extensions/azurecore/package.json | 5 +
.../resources/dark/azure_monitor_dark.svg | 10 +
.../resources/light/azure_monitor_light.svg | 10 +
.../src/azureResource/azure-resource.d.ts | 3 +-
.../azurecore/src/azureResource/constants.ts | 4 +-
.../azuremonitor/azuremonitorProvider.ts | 26 +
.../azuremonitor/azuremonitorService.ts | 41 ++
.../azuremonitorTreeDataProvider.ts | 77 +++
extensions/azurecore/src/extension.ts | 3 +
extensions/azuremonitor/.gitignore | 1 +
extensions/azuremonitor/.vscodeignore | 8 +
extensions/azuremonitor/README.md | 41 ++
extensions/azuremonitor/config.json | 29 ++
.../azuremonitor/extension.webpack.config.js | 17 +
.../azuremonitor/language-configuration.json | 73 +++
extensions/azuremonitor/package.json | 212 +++++++++
extensions/azuremonitor/package.nls.json | 31 ++
.../resources/dark/azure_monitor_dark.svg | 10 +
.../resources/light/azure_monitor_light.svg | 10 +
extensions/azuremonitor/src/appContext.ts | 34 ++
extensions/azuremonitor/src/azuremonitor.d.ts | 38 ++
.../src/azuremonitorApiFactory.ts | 23 +
.../azuremonitor/src/azuremonitorServer.ts | 167 +++++++
extensions/azuremonitor/src/constants.ts | 25 +
.../azuremonitor/src/contextProvider.ts | 76 +++
extensions/azuremonitor/src/extension.ts | 137 ++++++
.../src/features/accountFeature.ts | 65 +++
.../azuremonitor/src/features/contracts.ts | 81 ++++
.../src/features/serializationFeature.ts | 68 +++
.../src/features/telemetryFeature.ts | 25 +
extensions/azuremonitor/src/iconProvider.ts | 21 +
extensions/azuremonitor/src/index.ts | 8 +
.../cancelableStream.ts | 25 +
.../src/objectExplorerNodeProvider/command.ts | 187 ++++++++
.../objectExplorerNodeProvider/connection.ts | 82 ++++
.../objectExplorerNodeProvider.ts | 219 +++++++++
.../providerBase.ts | 13 +
.../objectExplorerNodeProvider/treeNodes.ts | 87 ++++
.../src/objectExplorerNodeProvider/types.d.ts | 17 +
.../azuremonitor/src/prompts/adapter.ts | 77 +++
.../azuremonitor/src/prompts/checkbox.ts | 52 ++
.../azuremonitor/src/prompts/confirm.ts | 36 ++
.../src/prompts/escapeException.ts | 8 +
extensions/azuremonitor/src/prompts/expand.ts | 78 +++
.../azuremonitor/src/prompts/factory.ts | 35 ++
extensions/azuremonitor/src/prompts/input.ts | 59 +++
extensions/azuremonitor/src/prompts/list.ts | 33 ++
.../azuremonitor/src/prompts/password.ts | 15 +
.../src/prompts/progressIndicator.ts | 70 +++
extensions/azuremonitor/src/prompts/prompt.ts | 33 ++
.../azuremonitor/src/prompts/question.ts | 73 +++
extensions/azuremonitor/src/telemetry.ts | 167 +++++++
extensions/azuremonitor/src/types.ts | 14 +
.../azuremonitor/src/typings/findRemove.d.ts | 17 +
extensions/azuremonitor/src/typings/refs.d.ts | 8 +
extensions/azuremonitor/src/utils.ts | 147 ++++++
.../syntaxes/azuremonitor.tmLanguage | 449 ++++++++++++++++++
extensions/azuremonitor/tsconfig.json | 12 +
extensions/azuremonitor/yarn.lock | 370 +++++++++++++++
.../browser/services/breadcrumb.service.ts | 4 +-
.../contrib/query/browser/queryEditor.ts | 14 +
.../query/browser/queryInputFactory.ts | 2 +-
.../browser/scripting.contribution.ts | 11 +-
.../queryEditor/browser/queryEditorService.ts | 4 +-
65 files changed, 3788 insertions(+), 10 deletions(-)
create mode 100644 extensions/azurecore/resources/dark/azure_monitor_dark.svg
create mode 100644 extensions/azurecore/resources/light/azure_monitor_light.svg
create mode 100644 extensions/azurecore/src/azureResource/providers/azuremonitor/azuremonitorProvider.ts
create mode 100644 extensions/azurecore/src/azureResource/providers/azuremonitor/azuremonitorService.ts
create mode 100644 extensions/azurecore/src/azureResource/providers/azuremonitor/azuremonitorTreeDataProvider.ts
create mode 100644 extensions/azuremonitor/.gitignore
create mode 100644 extensions/azuremonitor/.vscodeignore
create mode 100644 extensions/azuremonitor/README.md
create mode 100644 extensions/azuremonitor/config.json
create mode 100644 extensions/azuremonitor/extension.webpack.config.js
create mode 100644 extensions/azuremonitor/language-configuration.json
create mode 100644 extensions/azuremonitor/package.json
create mode 100644 extensions/azuremonitor/package.nls.json
create mode 100644 extensions/azuremonitor/resources/dark/azure_monitor_dark.svg
create mode 100644 extensions/azuremonitor/resources/light/azure_monitor_light.svg
create mode 100644 extensions/azuremonitor/src/appContext.ts
create mode 100644 extensions/azuremonitor/src/azuremonitor.d.ts
create mode 100644 extensions/azuremonitor/src/azuremonitorApiFactory.ts
create mode 100644 extensions/azuremonitor/src/azuremonitorServer.ts
create mode 100644 extensions/azuremonitor/src/constants.ts
create mode 100644 extensions/azuremonitor/src/contextProvider.ts
create mode 100644 extensions/azuremonitor/src/extension.ts
create mode 100644 extensions/azuremonitor/src/features/accountFeature.ts
create mode 100644 extensions/azuremonitor/src/features/contracts.ts
create mode 100644 extensions/azuremonitor/src/features/serializationFeature.ts
create mode 100644 extensions/azuremonitor/src/features/telemetryFeature.ts
create mode 100644 extensions/azuremonitor/src/iconProvider.ts
create mode 100644 extensions/azuremonitor/src/index.ts
create mode 100644 extensions/azuremonitor/src/objectExplorerNodeProvider/cancelableStream.ts
create mode 100644 extensions/azuremonitor/src/objectExplorerNodeProvider/command.ts
create mode 100644 extensions/azuremonitor/src/objectExplorerNodeProvider/connection.ts
create mode 100644 extensions/azuremonitor/src/objectExplorerNodeProvider/objectExplorerNodeProvider.ts
create mode 100644 extensions/azuremonitor/src/objectExplorerNodeProvider/providerBase.ts
create mode 100644 extensions/azuremonitor/src/objectExplorerNodeProvider/treeNodes.ts
create mode 100644 extensions/azuremonitor/src/objectExplorerNodeProvider/types.d.ts
create mode 100644 extensions/azuremonitor/src/prompts/adapter.ts
create mode 100644 extensions/azuremonitor/src/prompts/checkbox.ts
create mode 100644 extensions/azuremonitor/src/prompts/confirm.ts
create mode 100644 extensions/azuremonitor/src/prompts/escapeException.ts
create mode 100644 extensions/azuremonitor/src/prompts/expand.ts
create mode 100644 extensions/azuremonitor/src/prompts/factory.ts
create mode 100644 extensions/azuremonitor/src/prompts/input.ts
create mode 100644 extensions/azuremonitor/src/prompts/list.ts
create mode 100644 extensions/azuremonitor/src/prompts/password.ts
create mode 100644 extensions/azuremonitor/src/prompts/progressIndicator.ts
create mode 100644 extensions/azuremonitor/src/prompts/prompt.ts
create mode 100644 extensions/azuremonitor/src/prompts/question.ts
create mode 100644 extensions/azuremonitor/src/telemetry.ts
create mode 100644 extensions/azuremonitor/src/types.ts
create mode 100644 extensions/azuremonitor/src/typings/findRemove.d.ts
create mode 100644 extensions/azuremonitor/src/typings/refs.d.ts
create mode 100644 extensions/azuremonitor/src/utils.ts
create mode 100644 extensions/azuremonitor/syntaxes/azuremonitor.tmLanguage
create mode 100644 extensions/azuremonitor/tsconfig.json
create mode 100644 extensions/azuremonitor/yarn.lock
diff --git a/build/lib/extensions.ts b/build/lib/extensions.ts
index a8ac5f40e0..cb10797cd3 100644
--- a/build/lib/extensions.ts
+++ b/build/lib/extensions.ts
@@ -251,6 +251,7 @@ const externalExtensions = [
'asde-deployment',
'azdata',
'azurehybridtoolkit',
+ 'azuremonitor',
'cms',
'dacpac',
'import',
diff --git a/extensions/azurecore/package.json b/extensions/azurecore/package.json
index 2d9024d568..7f5b327b5b 100644
--- a/extensions/azurecore/package.json
+++ b/extensions/azurecore/package.json
@@ -283,6 +283,11 @@
"when": "viewItem == azure.resource.itemType.azureDataExplorer",
"group": "inline"
},
+ {
+ "command": "azure.resource.connectsqlserver",
+ "when": "viewItem == azure.resource.itemType.azureMonitor",
+ "group": "inline"
+ },
{
"command": "azure.resource.startterminal",
"when": "viewItem == azure.resource.itemType.account",
diff --git a/extensions/azurecore/resources/dark/azure_monitor_dark.svg b/extensions/azurecore/resources/dark/azure_monitor_dark.svg
new file mode 100644
index 0000000000..7b5ef94c5b
--- /dev/null
+++ b/extensions/azurecore/resources/dark/azure_monitor_dark.svg
@@ -0,0 +1,10 @@
+
diff --git a/extensions/azurecore/resources/light/azure_monitor_light.svg b/extensions/azurecore/resources/light/azure_monitor_light.svg
new file mode 100644
index 0000000000..b390b521cf
--- /dev/null
+++ b/extensions/azurecore/resources/light/azure_monitor_light.svg
@@ -0,0 +1,10 @@
+
diff --git a/extensions/azurecore/src/azureResource/azure-resource.d.ts b/extensions/azurecore/src/azureResource/azure-resource.d.ts
index 5c4d766a73..89ce017dda 100644
--- a/extensions/azurecore/src/azureResource/azure-resource.d.ts
+++ b/extensions/azurecore/src/azureResource/azure-resource.d.ts
@@ -24,6 +24,7 @@ declare module 'azureResource' {
postgresServer = 'microsoft.dbforpostgresql/servers',
azureArcService = 'microsoft.azuredata/datacontrollers',
storageAccount = 'microsoft.storage/storageaccounts',
+ logAnalytics = 'microsoft.operationalinsights/workspaces'
}
export interface IAzureResourceProvider extends DataProvider {
@@ -121,7 +122,7 @@ declare module 'azureResource' {
proxyOverride: string,
vCores: number,
dnsZone: string,
- }
+ }
}
diff --git a/extensions/azurecore/src/azureResource/constants.ts b/extensions/azurecore/src/azureResource/constants.ts
index 17b1070200..660918fc3c 100644
--- a/extensions/azurecore/src/azureResource/constants.ts
+++ b/extensions/azurecore/src/azureResource/constants.ts
@@ -13,7 +13,9 @@ export enum AzureResourceItemType {
azureDataExplorerContainer = 'azure.resource.itemType.azureDataExplorerContainer',
azureDataExplorer = 'azure.resource.itemType.azureDataExplorer',
sqlInstance = 'azure.resource.itemType.sqlInstance',
- message = 'azure.resource.itemType.message'
+ message = 'azure.resource.itemType.message',
+ azureMonitor = 'azure.resource.itemType.azureMonitor',
+ azureMonitorContainer = 'azure.resource.itemType.azureMonitorContainer',
}
export enum AzureResourceServiceNames {
diff --git a/extensions/azurecore/src/azureResource/providers/azuremonitor/azuremonitorProvider.ts b/extensions/azurecore/src/azureResource/providers/azuremonitor/azuremonitorProvider.ts
new file mode 100644
index 0000000000..60c1af45d3
--- /dev/null
+++ b/extensions/azurecore/src/azureResource/providers/azuremonitor/azuremonitorProvider.ts
@@ -0,0 +1,26 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the Source EULA. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { ExtensionContext } from 'vscode';
+
+import { azureResource } from 'azureResource';
+import { IAzureResourceService } from '../../interfaces';
+import { AzureMonitorTreeDataProvider as AzureMonitorTreeDataProvider } from './azuremonitorTreeDataProvider';
+
+export class AzureMonitorProvider implements azureResource.IAzureResourceProvider {
+ public constructor(
+ private _service: IAzureResourceService,
+ private _extensionContext: ExtensionContext
+ ) {
+ }
+
+ public getTreeDataProvider(): azureResource.IAzureResourceTreeDataProvider {
+ return new AzureMonitorTreeDataProvider(this._service, this._extensionContext);
+ }
+
+ public get providerId(): string {
+ return 'azure.resource.providers.azureMonitor';
+ }
+}
diff --git a/extensions/azurecore/src/azureResource/providers/azuremonitor/azuremonitorService.ts b/extensions/azurecore/src/azureResource/providers/azuremonitor/azuremonitorService.ts
new file mode 100644
index 0000000000..0151933c75
--- /dev/null
+++ b/extensions/azurecore/src/azureResource/providers/azuremonitor/azuremonitorService.ts
@@ -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.
+ *--------------------------------------------------------------------------------------------*/
+
+import { azureResource } from 'azureResource';
+import { ResourceServiceBase, GraphData } from '../resourceTreeDataProviderBase';
+
+export interface AzureMonitorGraphData extends GraphData {
+ properties: {
+ fullyQualifiedDomainName: string;
+ administratorLogin: string;
+ uri: string;
+ customerId: string
+ };
+}
+
+const instanceQuery = `where type == "${azureResource.AzureResourceType.logAnalytics}"`;
+
+export class AzureMonitorResourceService extends ResourceServiceBase {
+
+ protected get query(): string {
+ return instanceQuery;
+ }
+
+ protected convertResource(resource: AzureMonitorGraphData): azureResource.AzureResourceDatabaseServer {
+ return {
+ id: resource.id,
+ name: resource.name,
+ fullName: resource.properties.customerId,
+ loginName: '',
+ defaultDatabaseName: '',
+ subscription: {
+ id: resource.subscriptionId,
+ name: resource.subscriptionName
+ },
+ tenant: resource.tenantId,
+ resourceGroup: resource.resourceGroup
+ };
+ }
+}
diff --git a/extensions/azurecore/src/azureResource/providers/azuremonitor/azuremonitorTreeDataProvider.ts b/extensions/azurecore/src/azureResource/providers/azuremonitor/azuremonitorTreeDataProvider.ts
new file mode 100644
index 0000000000..02827a5f67
--- /dev/null
+++ b/extensions/azurecore/src/azureResource/providers/azuremonitor/azuremonitorTreeDataProvider.ts
@@ -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.
+ *--------------------------------------------------------------------------------------------*/
+
+import { ExtensionNodeType, TreeItem, Account } from 'azdata';
+import { TreeItemCollapsibleState, ExtensionContext } from 'vscode';
+import * as nls from 'vscode-nls';
+const localize = nls.loadMessageBundle();
+
+import { AzureResourceItemType } from '../../constants';
+import { generateGuid } from '../../utils';
+import { IAzureResourceService } from '../../interfaces';
+import { ResourceTreeDataProviderBase } from '../resourceTreeDataProviderBase';
+import { azureResource } from 'azureResource';
+
+export class AzureMonitorTreeDataProvider extends ResourceTreeDataProviderBase {
+ private static readonly containerId = 'azure.resource.providers.AzureMonitorContainer';
+ private static readonly containerLabel = localize('azure.resource.providers.AzureMonitorContainerLabel', "Azure Monitor Workspace");
+
+ public constructor(
+ databaseServerService: IAzureResourceService,
+ private _extensionContext: ExtensionContext
+ ) {
+ super(databaseServerService);
+ }
+
+
+ protected getTreeItemForResource(databaseServer: azureResource.AzureResourceDatabaseServer, account: Account): TreeItem {
+ return {
+ id: `LogAnalytics_${databaseServer.id ? databaseServer.id : databaseServer.name}`,
+ label: this.browseConnectionMode ? `${databaseServer.name} (${AzureMonitorTreeDataProvider.containerLabel}, ${databaseServer.subscription.name})` : databaseServer.name,
+ iconPath: {
+ dark: this._extensionContext.asAbsolutePath('resources/dark/azure_monitor_dark.svg'),
+ light: this._extensionContext.asAbsolutePath('resources/light/azure_monitor_light.svg')
+ },
+ collapsibleState: this.browseConnectionMode ? TreeItemCollapsibleState.None : TreeItemCollapsibleState.Collapsed,
+ contextValue: AzureResourceItemType.azureMonitor,
+ payload: {
+ id: generateGuid(),
+ connectionName: undefined,
+ serverName: databaseServer.fullName,
+ databaseName: databaseServer.defaultDatabaseName,
+ userName: databaseServer.loginName,
+ password: '',
+ authenticationType: 'AzureMFA',
+ savePassword: true,
+ groupFullName: '',
+ groupId: '',
+ providerName: 'LOGANALYTICS',
+ saveProfile: false,
+ options: {},
+ azureAccount: account.key.accountId
+ },
+ childProvider: 'LOGANALYTICS',
+ type: ExtensionNodeType.Server
+ };
+ }
+
+ protected createContainerNode(): azureResource.IAzureResourceNode {
+ return {
+ account: undefined,
+ subscription: undefined,
+ tenantId: undefined,
+ treeItem: {
+ id: AzureMonitorTreeDataProvider.containerId,
+ label: AzureMonitorTreeDataProvider.containerLabel,
+ iconPath: {
+ dark: this._extensionContext.asAbsolutePath('resources/dark/folder_inverse.svg'),
+ light: this._extensionContext.asAbsolutePath('resources/light/folder.svg')
+ },
+ collapsibleState: TreeItemCollapsibleState.Collapsed,
+ contextValue: AzureResourceItemType.databaseServerContainer
+ }
+ };
+ }
+}
diff --git a/extensions/azurecore/src/extension.ts b/extensions/azurecore/src/extension.ts
index c03447998b..4799b04b77 100644
--- a/extensions/azurecore/src/extension.ts
+++ b/extensions/azurecore/src/extension.ts
@@ -29,6 +29,8 @@ import { SqlInstanceResourceService } from './azureResource/providers/sqlinstanc
import { SqlInstanceProvider } from './azureResource/providers/sqlinstance/sqlInstanceProvider';
import { KustoResourceService } from './azureResource/providers/kusto/kustoService';
import { KustoProvider } from './azureResource/providers/kusto/kustoProvider';
+import { AzureMonitorResourceService } from './azureResource/providers/azuremonitor/azuremonitorService';
+import { AzureMonitorProvider } from './azureResource/providers/azuremonitor/azuremonitorProvider';
import { PostgresServerProvider } from './azureResource/providers/postgresServer/postgresServerProvider';
import { PostgresServerService } from './azureResource/providers/postgresServer/postgresServerService';
import { AzureTerminalService } from './azureResource/services/terminalService';
@@ -156,6 +158,7 @@ export async function activate(context: vscode.ExtensionContext): Promise=1.31.0"
+ },
+ "main": "./out/extension",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/Microsoft/azuredatastudio.git"
+ },
+ "typings": "./src/azuremonitor",
+ "contributes": {
+ "connectionProvider": {
+ "providerId": "LOGANALYTICS",
+ "languageMode": "loganalytics",
+ "displayName": "%azuremonitor.displayName%",
+ "notebookKernelAlias": "LogAnalytics",
+ "azureResource": "AzureLogAnalytics",
+ "iconPath": [
+ {
+ "id": "azuremonitor:cloud",
+ "path": {
+ "light": "resources/light/azure_monitor_light.svg",
+ "dark": "resources/dark/azure_monitor_dark.svg"
+ },
+ "default": true
+ }
+ ],
+ "connectionOptions": [
+ {
+ "specialValueType": "serverName",
+ "isIdentity": true,
+ "name": "server",
+ "displayName": "%azuremonitor.connectionProperties.serverName.displayName%",
+ "description": "%azuremonitor.connectionProperties.serverName.description%",
+ "groupName": "Source",
+ "valueType": "string",
+ "defaultValue": null,
+ "objectType": null,
+ "categoryValues": [],
+ "isRequired": true,
+ "isArray": false
+ },
+ {
+ "specialValueType": "authType",
+ "isIdentity": true,
+ "name": "authenticationType",
+ "displayName": "%azuremonitor.connectionProperties.authType.displayName%",
+ "description": "%azuremonitor.connectionProperties.authType.description%",
+ "groupName": "Security",
+ "valueType": "category",
+ "defaultValue": "AzureMFA",
+ "objectType": null,
+ "categoryValues": [
+ {
+ "displayName": "%azuremonitor.connectionProperties.authType.categoryValues.azureMFA%",
+ "name": "AzureMFA"
+ }
+ ],
+ "isRequired": true,
+ "isArray": false
+ },
+ {
+ "specialValueType": "connectionName",
+ "isIdentity": true,
+ "name": "connectionName",
+ "displayName": "%azuremonitor.connectionProperties.connectionName.displayName%",
+ "description": "%azuremonitor.connectionProperties.connectionName.description%",
+ "groupName": "Source",
+ "valueType": "string",
+ "defaultValue": null,
+ "objectType": null,
+ "categoryValues": null,
+ "isRequired": false,
+ "isArray": false
+ },
+ {
+ "specialValueType": "userName",
+ "isIdentity": true,
+ "name": "user",
+ "displayName": "%azuremonitor.connectionProperties.userName.displayName%",
+ "description": "%azuremonitor.connectionProperties.userName.description%",
+ "groupName": "Security",
+ "valueType": "string",
+ "defaultValue": null,
+ "objectType": null,
+ "categoryValues": null,
+ "isRequired": true,
+ "isArray": false
+ },
+ {
+ "specialValueType": "password",
+ "isIdentity": true,
+ "name": "password",
+ "displayName": "%azuremonitor.connectionProperties.password.displayName%",
+ "description": "%azuremonitor.connectionProperties.password.description%",
+ "groupName": "Security",
+ "valueType": "password",
+ "defaultValue": null,
+ "objectType": null,
+ "categoryValues": null,
+ "isRequired": true,
+ "isArray": false
+ },
+ {
+ "specialValueType": "databaseName",
+ "isIdentity": true,
+ "name": "database",
+ "displayName": "%azuremonitor.connectionOptions.databaseName.displayName%",
+ "description": "%azuremonitor.connectionOptions.databaseName.description%",
+ "groupName": "Source",
+ "valueType": "string",
+ "defaultValue": null,
+ "objectType": null,
+ "categoryValues": null,
+ "isRequired": false,
+ "isArray": false
+ }
+ ]
+ },
+ "dashboard": {
+ "provider": "LOGANALYTICS",
+ "flavors": [
+ {
+ "flavor": "cloud",
+ "conditions": [
+ {
+ "field": "isCloud",
+ "operator": "==",
+ "value": true
+ }
+ ],
+ "databaseProperties": [
+ {
+ "displayName": "%azuremonitor.cloud.workspaceProperties.name%",
+ "value": "name"
+ },
+ {
+ "displayName": "%azuremonitor.cloud.workspaceProperties.id%",
+ "value": "id"
+ }
+ ],
+ "serverProperties": [],
+ "databasesListProperties": [
+ {
+ "displayName": "%azuremonitor.databasesListProperties.name%",
+ "value": "name",
+ "widthWeight": 60
+ },
+ {
+ "displayName": "%azuremonitor.databasesListProperties.size%",
+ "value": "sizeInMB",
+ "widthWeight": 20
+ }
+ ],
+ "objectsListProperties": [
+ {
+ "displayName": "%azuremonitor.objectsListProperties.name%",
+ "value": "name",
+ "widthWeight": 60
+ },
+ {
+ "displayName": "%azuremonitor.objectsListProperties.metadataTypeName%",
+ "value": "metadataTypeName",
+ "widthWeight": 20
+ }
+ ]
+ }
+ ]
+ },
+ "languages": [
+ {
+ "id": "loganalytics",
+ "aliases": [
+ "LogAnalytics",
+ "loganalytics"
+ ],
+ "extensions": [
+ ".loganalytics"
+ ],
+ "configuration": "./language-configuration.json"
+ }
+ ]
+ },
+ "scripts": {
+ "compile": "gulp compile-extension:azuremonitor-client",
+ "update-grammar": "node ../../build/npm/update-grammar.js Microsoft/vscode-azuremonitor ./syntaxes/azuremonitor.tmLanguage"
+ },
+ "dependencies": {
+ "dataprotocol-client": "github:Microsoft/sqlops-dataprotocolclient#1.2.2",
+ "figures": "^2.0.0",
+ "find-remove": "1.2.1",
+ "service-downloader": "0.2.1",
+ "vscode-extension-telemetry": "0.1.0",
+ "vscode-languageclient": "5.2.1",
+ "vscode-nls": "^4.0.0"
+ },
+ "devDependencies": {},
+ "__metadata": {
+ "id": "",
+ "publisherDisplayName": "Microsoft",
+ "publisherId": "Microsoft"
+ }
+}
diff --git a/extensions/azuremonitor/package.nls.json b/extensions/azuremonitor/package.nls.json
new file mode 100644
index 0000000000..b1b8c0088b
--- /dev/null
+++ b/extensions/azuremonitor/package.nls.json
@@ -0,0 +1,31 @@
+{
+ "azuremonitor.description": "Azure Monitor access for Azure Data Studio",
+ "azuremonitor.displayName": "Azure Monitor Logs",
+ "azuremonitor.cloud.workspaceProperties.name": "Workspace Name",
+ "azuremonitor.cloud.workspaceProperties.id": "Workspace ID",
+ "azuremonitor.databasesListProperties.name": "Name",
+ "azuremonitor.databasesListProperties.size": "Size (MB)",
+ "azuremonitor.objectsListProperties.name": "Name",
+ "azuremonitor.objectsListProperties.metadataTypeName": "Type",
+ "azuremonitor.connectionProperties.connectionName.displayName": "Name (optional)",
+ "azuremonitor.connectionProperties.connectionName.description": "Custom name of the connection",
+ "azuremonitor.connectionProperties.userName.displayName": "User name",
+ "azuremonitor.connectionProperties.userName.description": "Indicates the user ID to be used when connecting to the data source",
+ "azuremonitor.connectionProperties.password.displayName": "Password",
+ "azuremonitor.connectionProperties.password.description": "Indicates the password to be used when connecting to the data source",
+ "azuremonitor.connectionProperties.dbaPrivilege.label": "Administrative privileges",
+ "azuremonitor.connectionProperties.dbaPrivilege.description": "Administrative privileges: SYSDBA or SYSOPER",
+ "azuremonitor.connectionProperties.metadataPooling.label": "Metadata Pooling",
+ "azuremonitor.connectionProperties.metadataPooling.description": "This attribute indicates whether the metadata information for executed queries is cached for improved performance.",
+ "azuremonitor.connectionProperties.proxyUserId.label": "Proxy User Id",
+ "azuremonitor.connectionProperties.proxyUserId.description": "User name of the proxy user.",
+ "azuremonitor.connectionProperties.proxyUserPassword.label": "Proxy Password",
+ "azuremonitor.connectionProperties.proxyUserPassword.description": "Password of the proxy user.",
+ "azuremonitor.connectionProperties.serverName.displayName": "Workspace ID",
+ "azuremonitor.connectionProperties.serverName.description": "Log Analytics Workspace ID",
+ "azuremonitor.connectionOptions.databaseName.displayName": "Database",
+ "azuremonitor.connectionOptions.databaseName.description": "The name of the initial catalog or database in the data source",
+ "azuremonitor.connectionProperties.authType.displayName": "Authentication type",
+ "azuremonitor.connectionProperties.authType.description": "Specifies the method of authenticating with Azure Monitor",
+ "azuremonitor.connectionProperties.authType.categoryValues.azureMFA": "Azure Active Directory - Universal with MFA support"
+}
diff --git a/extensions/azuremonitor/resources/dark/azure_monitor_dark.svg b/extensions/azuremonitor/resources/dark/azure_monitor_dark.svg
new file mode 100644
index 0000000000..7b5ef94c5b
--- /dev/null
+++ b/extensions/azuremonitor/resources/dark/azure_monitor_dark.svg
@@ -0,0 +1,10 @@
+
diff --git a/extensions/azuremonitor/resources/light/azure_monitor_light.svg b/extensions/azuremonitor/resources/light/azure_monitor_light.svg
new file mode 100644
index 0000000000..b390b521cf
--- /dev/null
+++ b/extensions/azuremonitor/resources/light/azure_monitor_light.svg
@@ -0,0 +1,10 @@
+
diff --git a/extensions/azuremonitor/src/appContext.ts b/extensions/azuremonitor/src/appContext.ts
new file mode 100644
index 0000000000..0fc5eb23d3
--- /dev/null
+++ b/extensions/azuremonitor/src/appContext.ts
@@ -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';
+
+/**
+ * Global context for the application
+ */
+export class AppContext {
+
+ private serviceMap: Map = new Map();
+
+ constructor(public readonly extensionContext: vscode.ExtensionContext) { }
+
+ public getService(serviceName: string): T | undefined {
+ const service = this.serviceMap.get(serviceName) as T;
+ if (!service) {
+ console.warn('Service ', serviceName, ' is not registered');
+ }
+ return service;
+ }
+
+ public registerService(serviceName: string, service: T): void {
+ if (this.serviceMap.has(serviceName)) {
+ console.warn('Multiple services ', serviceName, ' registered!');
+ } else {
+ this.serviceMap.set(serviceName, service);
+ }
+ }
+}
diff --git a/extensions/azuremonitor/src/azuremonitor.d.ts b/extensions/azuremonitor/src/azuremonitor.d.ts
new file mode 100644
index 0000000000..76dee761f4
--- /dev/null
+++ b/extensions/azuremonitor/src/azuremonitor.d.ts
@@ -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.
+ *--------------------------------------------------------------------------------------------*/
+
+// This is the place for extensions to expose APIs.
+
+import * as azdata from 'azdata';
+
+/**
+* The APIs provided by AzureMonitor extension
+*/
+export interface IExtension {
+ /**
+ * Gets the object explorer API that supports querying over the connections supported by this extension
+ *
+ */
+ getAzureMonitorObjectExplorerBrowser(): AzureMonitorObjectExplorerBrowser;
+}
+
+/**
+ * A browser supporting actions over the object explorer connections provided by this extension.
+ * Currently this is the
+ */
+export interface AzureMonitorObjectExplorerBrowser {
+ /**
+ * Gets the matching node given a context object, e.g. one from a right-click on a node in Object Explorer
+ */
+ getNode(objectExplorerContext: azdata.ObjectExplorerContext): Thenable;
+}
+
+/**
+ * A tree node in the object explorer tree
+ */
+export interface ITreeNode {
+ getNodeInfo(): azdata.NodeInfo;
+ getChildren(refreshChildren: boolean): ITreeNode[] | Thenable;
+}
diff --git a/extensions/azuremonitor/src/azuremonitorApiFactory.ts b/extensions/azuremonitor/src/azuremonitorApiFactory.ts
new file mode 100644
index 0000000000..04c6d6abba
--- /dev/null
+++ b/extensions/azuremonitor/src/azuremonitorApiFactory.ts
@@ -0,0 +1,23 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the Source EULA. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { AppContext } from './appContext';
+import { IExtension, AzureMonitorObjectExplorerBrowser } from './azuremonitor';
+import * as constants from './constants';
+import { AzureMonitorObjectExplorerNodeProvider } from './objectExplorerNodeProvider/objectExplorerNodeProvider';
+import * as azdata from 'azdata';
+
+export function createAzureMonitorApi(context: AppContext): IExtension {
+ return {
+ getAzureMonitorObjectExplorerBrowser(): AzureMonitorObjectExplorerBrowser {
+ return {
+ getNode: (explorerContext: azdata.ObjectExplorerContext) => {
+ let oeProvider = context.getService(constants.ObjectExplorerService);
+ return oeProvider?.findSqlClusterNodeByContext(explorerContext);
+ }
+ };
+ }
+ };
+}
diff --git a/extensions/azuremonitor/src/azuremonitorServer.ts b/extensions/azuremonitor/src/azuremonitorServer.ts
new file mode 100644
index 0000000000..224fa261f3
--- /dev/null
+++ b/extensions/azuremonitor/src/azuremonitorServer.ts
@@ -0,0 +1,167 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the Source EULA. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { ServerProvider, IConfig, Events } from 'service-downloader';
+import { ServerOptions, TransportKind } from 'vscode-languageclient';
+import * as Constants from './constants';
+import * as vscode from 'vscode';
+import * as nls from 'vscode-nls';
+import * as path from 'path';
+import { getCommonLaunchArgsAndCleanupOldLogFiles } from './utils';
+import { Telemetry, LanguageClientErrorHandler } from './telemetry';
+import { SqlOpsDataClient, ClientOptions } from 'dataprotocol-client';
+import { SerializationFeature } from './features/serializationFeature';
+import { TelemetryFeature } from './features/telemetryFeature';
+import { AccountFeature } from './features/accountFeature';
+import { AppContext } from './appContext';
+import { CompletionExtensionParams, CompletionExtLoadRequest } from './features/contracts';
+import { promises as fs } from 'fs';
+
+const localize = nls.loadMessageBundle();
+
+export const outputChannel = vscode.window.createOutputChannel(Constants.serviceName);
+const statusView = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left);
+
+export class AzureMonitorServer {
+
+ private client!: SqlOpsDataClient;
+ private config!: IConfig;
+ private disposables: vscode.Disposable[] = [];
+
+ public async start(context: AppContext): Promise {
+ try {
+ const installationStart = Date.now();
+ const path = await this.download(context);
+ const installationComplete = Date.now();
+ let serverOptions = generateServerOptions(context.extensionContext.logPath, path);
+ let clientOptions = getClientOptions(context);
+ this.client = new SqlOpsDataClient(Constants.serviceName, serverOptions, clientOptions);
+ const processStart = Date.now();
+ const clientReadyPromise = this.client.onReady().then(() => {
+ const processEnd = Date.now();
+ statusView.text = localize('serviceStartedStatusMsg', "{0} Started", Constants.serviceName);
+ setTimeout(() => {
+ statusView.hide();
+ }, 1500);
+ vscode.commands.registerCommand('azuremonitor.loadCompletionExtension', (params: CompletionExtensionParams) => {
+ this.client.sendRequest(CompletionExtLoadRequest.type, params);
+ });
+ Telemetry.sendTelemetryEvent('startup/LanguageClientStarted', {
+ installationTime: String(installationComplete - installationStart),
+ processStartupTime: String(processEnd - processStart),
+ totalTime: String(processEnd - installationStart),
+ beginningTimestamp: String(installationStart)
+ });
+ });
+ statusView.show();
+ statusView.text = localize('startingServiceStatusMsg', "Starting {0}", Constants.serviceName);
+ this.client.start();
+ await Promise.all([clientReadyPromise]);
+ return this.client;
+ } catch (e) {
+ Telemetry.sendTelemetryEvent('ServiceInitializingFailed');
+ vscode.window.showErrorMessage(localize('failedToStartServiceErrorMsg', "Failed to start {0}", Constants.serviceName));
+ throw e;
+ }
+ }
+
+ private async download(context: AppContext): Promise {
+ const rawConfig = await fs.readFile(path.join(context.extensionContext.extensionPath, 'config.json'));
+ this.config = JSON.parse(rawConfig.toString())!;
+ this.config.installDirectory = path.join(__dirname, this.config.installDirectory);
+ this.config.proxy = vscode.workspace.getConfiguration('http').get('proxy')!;
+ this.config.strictSSL = vscode.workspace.getConfiguration('http').get('proxyStrictSSL') || true;
+
+ const serverdownloader = new ServerProvider(this.config);
+ serverdownloader.eventEmitter.onAny(() => generateHandleServerProviderEvent());
+ return serverdownloader.getOrDownloadServer();
+ }
+
+ dispose() {
+ this.disposables.forEach(d => d.dispose());
+ if (this.client) {
+ this.client.stop();
+ }
+ }
+}
+
+function generateServerOptions(logPath: string, executablePath: string): ServerOptions {
+ const launchArgs = getCommonLaunchArgsAndCleanupOldLogFiles(logPath, 'azuremonitorService.log', executablePath);
+ return { command: executablePath, args: launchArgs, transport: TransportKind.stdio };
+}
+
+function generateHandleServerProviderEvent() {
+ let dots = 0;
+ return (e: string, ...args: any[]) => {
+ switch (e) {
+ case Events.INSTALL_START:
+ outputChannel.show(true);
+ statusView.show();
+ outputChannel.appendLine(localize('installingServiceChannelMsg', "Installing {0} to {1}", Constants.serviceName, args[0]));
+ statusView.text = localize('installingServiceStatusMsg', "Installing {0}", Constants.serviceName);
+ break;
+ case Events.INSTALL_END:
+ outputChannel.appendLine(localize('installedServiceChannelMsg', "Installed {0}", Constants.serviceName));
+ break;
+ case Events.DOWNLOAD_START:
+ outputChannel.appendLine(localize('downloadingServiceChannelMsg', "Downloading {0}", args[0]));
+ outputChannel.append(localize('downloadingServiceSizeChannelMsg', "({0} KB)", Math.ceil(args[1] / 1024).toLocaleString(vscode.env.language)));
+ statusView.text = localize('downloadingServiceStatusMsg', "Downloading {0}", Constants.serviceName);
+ break;
+ case Events.DOWNLOAD_PROGRESS:
+ let newDots = Math.ceil(args[0] / 5);
+ if (newDots > dots) {
+ outputChannel.append('.'.repeat(newDots - dots));
+ dots = newDots;
+ }
+ break;
+ case Events.DOWNLOAD_END:
+ outputChannel.appendLine(localize('downloadServiceDoneChannelMsg', "Done installing {0}", Constants.serviceName));
+ break;
+ default:
+ console.error(`Unknown event from Server Provider ${e}`);
+ break;
+ }
+ };
+}
+
+function getClientOptions(_context: AppContext): ClientOptions {
+ return {
+ documentSelector: ['loganalytics'],
+ synchronize: {
+ configurationSection: Constants.extensionConfigSectionName
+ },
+ providerId: Constants.providerId,
+ errorHandler: new LanguageClientErrorHandler(),
+ features: [
+ // we only want to add new features
+ ...SqlOpsDataClient.defaultFeatures,
+ TelemetryFeature,
+ AccountFeature,
+ SerializationFeature
+ ],
+ outputChannel: new CustomOutputChannel()
+ };
+}
+
+class CustomOutputChannel implements vscode.OutputChannel {
+ name!: string;
+ append(value: string): void {
+ console.log(value);
+ }
+ appendLine(value: string): void {
+ console.log(value);
+ }
+ clear(): void {
+ }
+ show(preserveFocus?: boolean): void;
+ show(column?: vscode.ViewColumn, preserveFocus?: boolean): void;
+ show(_column?: any, _preserveFocus?: any) {
+ }
+ hide(): void {
+ }
+ dispose(): void {
+ }
+}
diff --git a/extensions/azuremonitor/src/constants.ts b/extensions/azuremonitor/src/constants.ts
new file mode 100644
index 0000000000..5aedc95ff8
--- /dev/null
+++ b/extensions/azuremonitor/src/constants.ts
@@ -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.
+ *--------------------------------------------------------------------------------------------*/
+export const serviceName = 'AzureMonitor Tools Service';
+export const providerId = 'LOGANALYTICS';
+export const serviceCrashLink = 'https://github.com/Microsoft/azuredatastudio/issues';
+export const extensionConfigSectionName = 'azuremonitor';
+
+// DATA PROTOCOL VALUES ///////////////////////////////////////////////////////////
+export const azureMonitorClusterProviderName = 'azureMonitorCluster';
+export const authenticationTypePropName = 'authenticationType';
+export const integratedAuth = 'integrated';
+export const serverPropName = 'server';
+export const userPropName = 'user';
+export const passwordPropName = 'password';
+export const azuremonitorProviderName = 'LOGANALYTICS';
+
+// SERVICE NAMES //////////////////////////////////////////////////////////
+export const ObjectExplorerService = 'objectexplorer';
+export const objectExplorerPrefix: string = 'objectexplorer://';
+export const ViewType = 'view';
+
+export const azuremonitorClusterNewNotebookTask = 'azuremonitorCluster.task.newNotebook';
+export const azuremonitorClusterOpenNotebookTask = 'azuremonitorCluster.task.openNotebook';
\ No newline at end of file
diff --git a/extensions/azuremonitor/src/contextProvider.ts b/extensions/azuremonitor/src/contextProvider.ts
new file mode 100644
index 0000000000..847cb36867
--- /dev/null
+++ b/extensions/azuremonitor/src/contextProvider.ts
@@ -0,0 +1,76 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the Source EULA. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+import * as vscode from 'vscode';
+import * as azdata from 'azdata';
+
+import * as types from './types';
+
+export enum BuiltInCommands {
+ SetContext = 'setContext',
+}
+
+export enum ContextKeys {
+ ISCLOUD = 'azuremonitor:iscloud',
+ EDITIONID = 'azuremonitor:engineedition',
+ ISCLUSTER = 'azuremonitor:iscluster',
+ SERVERMAJORVERSION = 'azuremonitor:servermajorversion'
+}
+
+const isCloudEditions = [
+ azdata.DatabaseEngineEdition.SqlDatabase,
+ azdata.DatabaseEngineEdition.SqlDataWarehouse,
+ azdata.DatabaseEngineEdition.SqlOnDemand
+];
+
+export function setCommandContext(key: ContextKeys | string, value: any) {
+ return vscode.commands.executeCommand(BuiltInCommands.SetContext, key, value);
+}
+
+export default class ContextProvider {
+ private _disposables = new Array();
+
+ constructor() {
+ this._disposables.push(azdata.workspace.onDidOpenDashboard(this.onDashboardOpen, this));
+ this._disposables.push(azdata.workspace.onDidChangeToDashboard(this.onDashboardOpen, this));
+ }
+
+ public onDashboardOpen(e: azdata.DashboardDocument): void {
+ let iscloud: boolean = false;
+ let edition: number | undefined;
+ let isCluster: boolean = false;
+ let serverMajorVersion: number | undefined;
+ if (e.profile.providerName.toLowerCase() === 'loganalytics' && !types.isUndefinedOrNull(e.serverInfo) && !types.isUndefinedOrNull(e.serverInfo.engineEditionId)) {
+ if (isCloudEditions.some(i => i === e.serverInfo.engineEditionId)) {
+ iscloud = true;
+ } else {
+ iscloud = false;
+ }
+
+ edition = e.serverInfo.engineEditionId;
+
+ serverMajorVersion = e.serverInfo.serverMajorVersion;
+ }
+
+ if (iscloud === true || iscloud === false) {
+ setCommandContext(ContextKeys.ISCLOUD, iscloud);
+ }
+
+ if (!types.isUndefinedOrNull(edition)) {
+ setCommandContext(ContextKeys.EDITIONID, edition);
+ }
+
+ if (!types.isUndefinedOrNull(isCluster)) {
+ setCommandContext(ContextKeys.ISCLUSTER, isCluster);
+ }
+
+ if (!types.isUndefinedOrNull(serverMajorVersion)) {
+ setCommandContext(ContextKeys.SERVERMAJORVERSION, serverMajorVersion);
+ }
+ }
+
+ dispose(): void {
+ this._disposables = this._disposables.map(i => i.dispose());
+ }
+}
diff --git a/extensions/azuremonitor/src/extension.ts b/extensions/azuremonitor/src/extension.ts
new file mode 100644
index 0000000000..b42bcd0655
--- /dev/null
+++ b/extensions/azuremonitor/src/extension.ts
@@ -0,0 +1,137 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the Source EULA. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import * as vscode from 'vscode';
+import * as nls from 'vscode-nls';
+import * as azdata from 'azdata';
+import * as path from 'path';
+
+import * as Constants from './constants';
+import ContextProvider from './contextProvider';
+import * as Utils from './utils';
+import { AppContext } from './appContext';
+import { IExtension } from './azuremonitor';
+import { AzureMonitorObjectExplorerNodeProvider } from './objectExplorerNodeProvider/objectExplorerNodeProvider';
+import { registerSearchServerCommand } from './objectExplorerNodeProvider/command';
+import { AzureMonitorIconProvider } from './iconProvider';
+import { createAzureMonitorApi } from './azuremonitorApiFactory';
+import { AzureMonitorServer } from './azuremonitorServer';
+import { promises as fs } from 'fs';
+
+const localize = nls.loadMessageBundle();
+
+export async function activate(context: vscode.ExtensionContext): Promise {
+ // lets make sure we support this platform first
+ let supported = await Utils.verifyPlatform();
+
+ if (!supported) {
+ vscode.window.showErrorMessage(localize('azuremonitor.unsupportedPlatform', 'Unsupported platform'));
+ return undefined;
+ }
+
+ // ensure our log path exists
+ if (!(await Utils.exists(context.logPath))) {
+ await fs.mkdir(context.logPath);
+ }
+
+ let appContext = new AppContext(context);
+
+ let nodeProvider = new AzureMonitorObjectExplorerNodeProvider(appContext);
+ azdata.dataprotocol.registerObjectExplorerNodeProvider(nodeProvider);
+ let iconProvider = new AzureMonitorIconProvider();
+ azdata.dataprotocol.registerIconProvider(iconProvider);
+
+ registerTasks();
+
+ registerSearchServerCommand();
+ context.subscriptions.push(new ContextProvider());
+
+ registerLogCommand(context);
+
+ // initialize client last so we don't have features stuck behind it
+ const server = new AzureMonitorServer();
+ context.subscriptions.push(server);
+ await server.start(appContext);
+
+ return createAzureMonitorApi(appContext);
+}
+
+const logFiles = ['resourceprovider.log', 'azuremonitorservice.log', 'credentialstore.log'];
+function registerLogCommand(context: vscode.ExtensionContext) {
+ context.subscriptions.push(vscode.commands.registerCommand('azuremonitor.showLogFile', async () => {
+ const choice = await vscode.window.showQuickPick(logFiles);
+ if (choice) {
+ const document = await vscode.workspace.openTextDocument(vscode.Uri.file(path.join(context.logPath, choice)));
+ if (document) {
+ vscode.window.showTextDocument(document);
+ }
+ }
+ }));
+}
+
+function registerTasks(): void {
+ azdata.tasks.registerTask(Constants.azuremonitorClusterNewNotebookTask, (profile: azdata.IConnectionProfile) => {
+ return saveProfileAndCreateNotebook(profile);
+ });
+ azdata.tasks.registerTask(Constants.azuremonitorClusterOpenNotebookTask, (profile: azdata.IConnectionProfile) => {
+ return handleOpenNotebookTask(profile);
+ });
+}
+
+function saveProfileAndCreateNotebook(profile: azdata.IConnectionProfile): Promise {
+ return handleNewNotebookTask(undefined, profile);
+}
+
+function findNextUntitledEditorName(): string {
+ let nextVal = 0;
+ // Note: this will go forever if it's coded wrong, or you have inifinite Untitled notebooks!
+ while (true) {
+ let title = `Notebook-${nextVal}`;
+ let hasNotebookDoc = azdata.nb.notebookDocuments.findIndex(doc => doc.isUntitled && doc.fileName === title) > -1;
+ if (!hasNotebookDoc) {
+ return title;
+ }
+ nextVal++;
+ }
+}
+
+async function handleNewNotebookTask(_oeContext?: azdata.ObjectExplorerContext, profile?: azdata.IConnectionProfile): Promise {
+ // Ensure we get a unique ID for the notebook. For now we're using a different prefix to the built-in untitled files
+ // to handle this. We should look into improving this in the future
+ let title = findNextUntitledEditorName();
+ let untitledUri = vscode.Uri.parse(`untitled:${title}`);
+ await azdata.nb.showNotebookDocument(untitledUri, {
+ connectionProfile: profile,
+ preview: false
+ });
+}
+
+async function handleOpenNotebookTask(profile: azdata.IConnectionProfile): Promise {
+ let notebookFileTypeName = localize('notebookFileType', "Notebooks");
+ let filter: any = {};
+ filter[notebookFileTypeName] = 'ipynb';
+ let uris = await vscode.window.showOpenDialog({
+ filters: filter,
+ canSelectFiles: true,
+ canSelectMany: false
+ });
+ if (uris && uris.length > 0) {
+ let fileUri = uris[0];
+ // Verify this is a .ipynb file since this isn't actually filtered on Mac/Linux
+ if (path.extname(fileUri.fsPath) !== '.ipynb') {
+ // in the future might want additional supported types
+ vscode.window.showErrorMessage(localize('unsupportedFileType', "Only .ipynb Notebooks are supported"));
+ } else {
+ await azdata.nb.showNotebookDocument(fileUri, {
+ connectionProfile: profile,
+ preview: false
+ });
+ }
+ }
+}
+
+// this method is called when your extension is deactivated
+export function deactivate(): void {
+}
diff --git a/extensions/azuremonitor/src/features/accountFeature.ts b/extensions/azuremonitor/src/features/accountFeature.ts
new file mode 100644
index 0000000000..71b6e58f81
--- /dev/null
+++ b/extensions/azuremonitor/src/features/accountFeature.ts
@@ -0,0 +1,65 @@
+import * as nls from 'vscode-nls';
+import { SqlOpsDataClient } from 'dataprotocol-client';
+import { ClientCapabilities, StaticFeature } from 'vscode-languageclient';
+import { window } from 'vscode';
+import * as contracts from './contracts';
+import * as azdata from 'azdata';
+
+const localize = nls.loadMessageBundle();
+
+export class AccountFeature implements StaticFeature {
+
+ constructor(private _client: SqlOpsDataClient) { }
+
+ fillClientCapabilities(_capabilities: ClientCapabilities): void { }
+
+ initialize(): void {
+ this._client.onRequest(contracts.SecurityTokenRequest.type, async (request): Promise => {
+ return this.getToken(request);
+ });
+ }
+
+ protected async getToken(request: contracts.RequestSecurityTokenParams): Promise {
+ const accountList = await azdata.accounts.getAllAccounts();
+ let account: azdata.Account | undefined;
+
+ if (accountList.length < 1) {
+ // TODO: Prompt user to add account
+ window.showErrorMessage(localize('kusto.missingLinkedAzureAccount', "Azure Data Studio needs to contact Azure Key Vault to access a column master key for Always Encrypted, but no linked Azure account is available. Please add a linked Azure account and retry the query."));
+ return undefined;
+ } else {
+ account = accountList.find(a => a.key.accountId === request.accountId);
+ }
+
+ if (!account) {
+ window.showErrorMessage(localize('kusto.accountDoesNotExist', "Account does not exist."));
+ return undefined;
+ }
+
+ const unauthorizedMessage = localize('kusto.insufficientlyPrivelagedAzureAccount', "The configured Azure account for {0} does not have sufficient permissions for Azure Key Vault to access a column master key for Always Encrypted.", account.key.accountId);
+
+ let tenantId: string = '';
+ if (request.provider !== 'dstsAuth') {
+ const tenant = account.properties.tenants.find((t: { [key: string]: string }) => request.authority.includes(t.id));
+ if (!tenant) {
+ window.showErrorMessage(unauthorizedMessage);
+ return undefined;
+ }
+ tenantId = tenant.id;
+ }
+
+ const securityToken = await azdata.accounts.getAccountSecurityToken(account, tenantId, azdata.AzureResource.Sql);
+
+ if (!securityToken?.token) {
+ window.showErrorMessage(unauthorizedMessage);
+ return undefined;
+ }
+
+ let params: contracts.RequestSecurityTokenResponse = {
+ accountKey: JSON.stringify(account.key),
+ token: securityToken.token
+ };
+
+ return params;
+ }
+}
\ No newline at end of file
diff --git a/extensions/azuremonitor/src/features/contracts.ts b/extensions/azuremonitor/src/features/contracts.ts
new file mode 100644
index 0000000000..1b9912de2d
--- /dev/null
+++ b/extensions/azuremonitor/src/features/contracts.ts
@@ -0,0 +1,81 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the Source EULA. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import { NotificationType, RequestType } from 'vscode-languageclient';
+import { ITelemetryEventProperties, ITelemetryEventMeasures } from '../telemetry';
+import * as azdata from 'azdata';
+
+// ------------------------------- < Telemetry Sent Event > ------------------------------------
+
+/**
+ * Event sent when the language service send a telemetry event
+ */
+export namespace TelemetryNotification {
+ export const type = new NotificationType('telemetry/sqlevent');
+}
+
+/**
+ * Update event parameters
+ */
+export class TelemetryParams {
+ public params!: {
+ eventName: string;
+ properties: ITelemetryEventProperties;
+ measures: ITelemetryEventMeasures;
+ };
+}
+
+// ------------------------------- Telemetry Sent Event > ----------------------------------
+
+// ------------------------------- < Security Token Request > ------------------------------------------
+export interface RequestSecurityTokenParams {
+ authority: string;
+ provider: string;
+ resource: string;
+ accountId: string;
+}
+
+export interface RequestSecurityTokenResponse {
+ accountKey: string;
+ token: string;
+}
+
+export namespace SecurityTokenRequest {
+ export const type = new RequestType('account/securityTokenRequest');
+}
+// ------------------------------- Security Token Request > ------------------------------------------
+
+// ------------------------------- -----------------------------
+export namespace SerializeDataStartRequest {
+ export const type = new RequestType('serialize/start');
+}
+
+export namespace SerializeDataContinueRequest {
+ export const type = new RequestType('serialize/continue');
+}
+// ------------------------------- -----------------------------
+
+// ------------------------------- < Load Completion Extension Request > ------------------------------------
+/**
+ * Completion extension load parameters
+ */
+export class CompletionExtensionParams {
+ ///
+ /// Absolute path for the assembly containing the completion extension
+ ///
+ public assemblyPath?: string;
+ ///
+ /// The type name for the completion extension
+ ///
+ public typeName?: string;
+ ///
+ /// Property bag for initializing the completion extension
+ ///
+ public properties?: {};
+}
+
+export namespace CompletionExtLoadRequest {
+ export const type = new RequestType('completion/extLoad');
+}
diff --git a/extensions/azuremonitor/src/features/serializationFeature.ts b/extensions/azuremonitor/src/features/serializationFeature.ts
new file mode 100644
index 0000000000..489c8bb059
--- /dev/null
+++ b/extensions/azuremonitor/src/features/serializationFeature.ts
@@ -0,0 +1,68 @@
+import { SqlOpsDataClient, SqlOpsFeature } from 'dataprotocol-client';
+import { ClientCapabilities, RPCMessageType, ServerCapabilities } from 'vscode-languageclient';
+import { Disposable } from 'vscode';
+import * as contracts from './contracts';
+import * as azdata from 'azdata';
+import * as Utils from '../utils';
+import * as UUID from 'vscode-languageclient/lib/utils/uuid';
+
+export class SerializationFeature extends SqlOpsFeature {
+ private static readonly messageTypes: RPCMessageType[] = [
+ contracts.SerializeDataStartRequest.type,
+ contracts.SerializeDataContinueRequest.type,
+ ];
+
+ constructor(client: SqlOpsDataClient) {
+ super(client, SerializationFeature.messageTypes);
+ }
+
+ public fillClientCapabilities(_capabilities: ClientCapabilities): void {
+ }
+
+ public initialize(_capabilities: ServerCapabilities): void {
+ this.register(this.messages, {
+ id: UUID.generateUuid(),
+ registerOptions: undefined
+ });
+ }
+
+ protected registerProvider(_options: undefined): Disposable {
+ const client = this._client;
+
+ let startSerialization = (requestParams: azdata.SerializeDataStartRequestParams): Thenable => {
+ return client.sendRequest(contracts.SerializeDataStartRequest.type, requestParams).then(
+ r => {
+ return r;
+ },
+ e => {
+ client.logFailedRequest(contracts.SerializeDataStartRequest.type, e);
+ return Promise.resolve({
+ succeeded: false,
+ messages: Utils.getErrorMessage(e)
+ });
+ }
+ );
+ };
+
+ let continueSerialization = (requestParams: azdata.SerializeDataContinueRequestParams): Thenable => {
+ return client.sendRequest(contracts.SerializeDataContinueRequest.type, requestParams).then(
+ r => {
+ return r;
+ },
+ e => {
+ client.logFailedRequest(contracts.SerializeDataContinueRequest.type, e);
+ return Promise.resolve({
+ succeeded: false,
+ messages: Utils.getErrorMessage(e)
+ });
+ }
+ );
+ };
+
+ return azdata.dataprotocol.registerSerializationProvider({
+ providerId: client.providerId,
+ startSerialization,
+ continueSerialization
+ });
+ }
+}
diff --git a/extensions/azuremonitor/src/features/telemetryFeature.ts b/extensions/azuremonitor/src/features/telemetryFeature.ts
new file mode 100644
index 0000000000..7723f8fb64
--- /dev/null
+++ b/extensions/azuremonitor/src/features/telemetryFeature.ts
@@ -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.
+ *--------------------------------------------------------------------------------------------*/
+
+import { SqlOpsDataClient } from 'dataprotocol-client';
+import { ClientCapabilities, StaticFeature } from 'vscode-languageclient';
+import { Telemetry } from '../telemetry';
+import * as contracts from './contracts';
+import * as Utils from '../utils';
+
+export class TelemetryFeature implements StaticFeature {
+
+ constructor(private _client: SqlOpsDataClient) { }
+
+ fillClientCapabilities(capabilities: ClientCapabilities): void {
+ Utils.ensure(capabilities, 'telemetry')!.telemetry = true;
+ }
+
+ initialize(): void {
+ this._client.onNotification(contracts.TelemetryNotification.type, e => {
+ Telemetry.sendTelemetryEvent(e.params.eventName, e.params.properties, e.params.measures);
+ });
+ }
+}
diff --git a/extensions/azuremonitor/src/iconProvider.ts b/extensions/azuremonitor/src/iconProvider.ts
new file mode 100644
index 0000000000..c674a54bdf
--- /dev/null
+++ b/extensions/azuremonitor/src/iconProvider.ts
@@ -0,0 +1,21 @@
+/*---------------------------------------------------------------------------------------------
+ * 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 constants from './constants';
+
+const cloudIcon = 'azuremonitor:cloud';
+
+export class AzureMonitorIconProvider implements azdata.IconProvider {
+ public readonly providerId: string = constants.azuremonitorProviderName;
+ public handle?: number;
+ getConnectionIconId(connection: azdata.IConnectionProfile, _serverInfo: azdata.ServerInfo): Thenable {
+ let iconName: string | undefined;
+ if (connection.providerName === this.providerId) {
+ iconName = cloudIcon;
+ }
+ return Promise.resolve(iconName);
+ }
+}
diff --git a/extensions/azuremonitor/src/index.ts b/extensions/azuremonitor/src/index.ts
new file mode 100644
index 0000000000..415fc08af0
--- /dev/null
+++ b/extensions/azuremonitor/src/index.ts
@@ -0,0 +1,8 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+/*
+ * This file imports all public APIs for this extension and used to generate index.d.ts.
+ * Generated typing file can be used by other extensions to access public APIs.
+ */
+export { providerId } from './constants';
diff --git a/extensions/azuremonitor/src/objectExplorerNodeProvider/cancelableStream.ts b/extensions/azuremonitor/src/objectExplorerNodeProvider/cancelableStream.ts
new file mode 100644
index 0000000000..3efb7e8e56
--- /dev/null
+++ b/extensions/azuremonitor/src/objectExplorerNodeProvider/cancelableStream.ts
@@ -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.
+ *--------------------------------------------------------------------------------------------*/
+
+import { Transform } from 'stream';
+import * as vscode from 'vscode';
+import * as nls from 'vscode-nls';
+
+const localize = nls.loadMessageBundle();
+
+export class CancelableStream extends Transform {
+ constructor(private cancelationToken: vscode.CancellationTokenSource) {
+ super();
+ }
+
+ public override _transform(chunk: any, _encoding: string, callback: Function): void {
+ if (this.cancelationToken && this.cancelationToken.token.isCancellationRequested) {
+ callback(new Error(localize('streamCanceled', 'Stream operation canceled by the user')));
+ } else {
+ this.push(chunk);
+ callback();
+ }
+ }
+}
diff --git a/extensions/azuremonitor/src/objectExplorerNodeProvider/command.ts b/extensions/azuremonitor/src/objectExplorerNodeProvider/command.ts
new file mode 100644
index 0000000000..f878669c70
--- /dev/null
+++ b/extensions/azuremonitor/src/objectExplorerNodeProvider/command.ts
@@ -0,0 +1,187 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the Source EULA. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import * as vscode from 'vscode';
+import * as azdata from 'azdata';
+import * as nls from 'vscode-nls';
+const localize = nls.loadMessageBundle();
+
+import { TreeNode } from './treeNodes';
+import { QuestionTypes, IPrompter, IQuestion } from '../prompts/question';
+import * as utils from '../utils';
+import * as constants from '../constants';
+import { AppContext } from '../appContext';
+
+export interface ICommandContextParsingOptions {
+ editor: boolean;
+ uri: boolean;
+}
+
+export interface ICommandBaseContext {
+ command: string;
+ editor?: vscode.TextEditor;
+ uri?: vscode.Uri;
+}
+
+export interface ICommandUnknownContext extends ICommandBaseContext {
+ type: 'unknown';
+}
+
+export interface ICommandUriContext extends ICommandBaseContext {
+ type: 'uri';
+}
+
+export interface ICommandViewContext extends ICommandBaseContext {
+ type: 'view';
+ node: TreeNode;
+}
+
+export interface ICommandObjectExplorerContext extends ICommandBaseContext {
+ type: 'objectexplorer';
+ explorerContext: azdata.ObjectExplorerContext;
+}
+
+export type CommandContext = ICommandObjectExplorerContext | ICommandViewContext | ICommandUriContext | ICommandUnknownContext;
+
+function isTextEditor(editor: any): editor is vscode.TextEditor {
+ if (editor === undefined) { return false; }
+
+ return editor.id !== undefined && ((editor as vscode.TextEditor).edit !== undefined || (editor as vscode.TextEditor).document !== undefined);
+}
+
+export abstract class Command extends vscode.Disposable {
+
+
+ protected readonly contextParsingOptions: ICommandContextParsingOptions = { editor: false, uri: false };
+
+ private disposable: vscode.Disposable;
+
+ constructor(command: string | string[], protected appContext: AppContext) {
+ super(() => this.dispose());
+
+ if (typeof command === 'string') {
+ this.disposable = vscode.commands.registerCommand(command, (...args: any[]) => this._execute(command, ...args), this);
+
+ return;
+ }
+
+ const subscriptions = command.map(cmd => vscode.commands.registerCommand(cmd, (...args: any[]) => this._execute(cmd, ...args), this));
+ this.disposable = vscode.Disposable.from(...subscriptions);
+ }
+
+ override dispose(): void {
+ if (this.disposable) {
+ this.disposable.dispose();
+ }
+ }
+
+ protected async preExecute(...args: any[]): Promise {
+ return this.execute(...args);
+ }
+
+ abstract execute(...args: any[]): any;
+
+ protected _execute(command: string, ...args: any[]): any {
+ // TODO consider using Telemetry.trackEvent(command);
+
+ const [context, rest] = Command.parseContext(command, this.contextParsingOptions, ...args);
+ return this.preExecute(context, ...rest);
+ }
+
+ private static parseContext(command: string, options: ICommandContextParsingOptions, ...args: any[]): [CommandContext, any[]] {
+ let editor: vscode.TextEditor | undefined = undefined;
+
+ let firstArg = args[0];
+ if (options.editor && (firstArg === undefined || isTextEditor(firstArg))) {
+ editor = firstArg;
+ args = args.slice(1);
+ firstArg = args[0];
+ }
+
+ if (options.uri && (firstArg === undefined || firstArg instanceof vscode.Uri)) {
+ const [uri, ...rest] = args as [vscode.Uri, any];
+ return [{ command: command, type: 'uri', editor: editor, uri: uri }, rest];
+ }
+
+ if (firstArg instanceof TreeNode) {
+ const [node, ...rest] = args as [TreeNode, any];
+ return [{ command: command, type: constants.ViewType, node: node }, rest];
+ }
+
+ if (firstArg && utils.isObjectExplorerContext(firstArg)) {
+ const [explorerContext, ...rest] = args as [azdata.ObjectExplorerContext, any];
+ return [{ command: command, type: constants.ObjectExplorerService, explorerContext: explorerContext }, rest];
+ }
+
+ return [{ command: command, type: 'unknown', editor: editor }, args];
+ }
+}
+
+export abstract class ProgressCommand extends Command {
+ static progressId = 0;
+ constructor(command: string, protected prompter: IPrompter, appContext: AppContext) {
+ super(command, appContext);
+ }
+
+ protected async executeWithProgress(
+ execution: (cancelToken: vscode.CancellationTokenSource) => Promise,
+ label: string,
+ isCancelable: boolean = false,
+ onCanceled?: () => void
+ ): Promise {
+ let disposables: vscode.Disposable[] = [];
+ const tokenSource = new vscode.CancellationTokenSource();
+ const statusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left);
+ disposables.push(vscode.Disposable.from(statusBarItem));
+ statusBarItem.text = localize('progress', '$(sync~spin) {0}...', label);
+ if (isCancelable) {
+ const cancelCommandId = `cancelProgress${ProgressCommand.progressId++}`;
+ disposables.push(vscode.commands.registerCommand(cancelCommandId, async () => {
+ if (await this.confirmCancel()) {
+ tokenSource.cancel();
+ }
+ }));
+ statusBarItem.tooltip = localize('cancelTooltip', 'Cancel');
+ statusBarItem.command = cancelCommandId;
+ }
+ statusBarItem.show();
+
+ try {
+ await execution(tokenSource);
+ } catch (error) {
+ if (isCancelable && onCanceled && tokenSource.token.isCancellationRequested) {
+ // The error can be assumed to be due to cancelation occurring. Do the callback
+ onCanceled();
+ } else {
+ throw error;
+ }
+ } finally {
+ disposables.forEach(d => d.dispose());
+ }
+ }
+
+ private async confirmCancel(): Promise {
+ return (await this.prompter.promptSingle({
+ type: QuestionTypes.confirm,
+ message: localize('cancel', 'Cancel operation?'),
+ default: true
+ }))!;
+ }
+}
+
+export function registerSearchServerCommand(): void {
+ vscode.commands.registerCommand('azureMonitor.searchServers', () => {
+ vscode.window.showInputBox({
+ placeHolder: localize('azureMonitor.searchServers', 'Search Server Names')
+ }).then((stringSearch) => {
+ if (stringSearch) {
+ vscode.commands.executeCommand('registeredServers.searchServer', (stringSearch));
+ }
+ });
+ });
+ vscode.commands.registerCommand('azureMonitor.clearSearchServerResult', () => {
+ vscode.commands.executeCommand('registeredServers.clearSearchServerResult');
+ });
+}
diff --git a/extensions/azuremonitor/src/objectExplorerNodeProvider/connection.ts b/extensions/azuremonitor/src/objectExplorerNodeProvider/connection.ts
new file mode 100644
index 0000000000..1de000065a
--- /dev/null
+++ b/extensions/azuremonitor/src/objectExplorerNodeProvider/connection.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 nls from 'vscode-nls';
+const localize = nls.loadMessageBundle();
+
+import * as constants from '../constants';
+
+export class AzureMonitorClusterConnection {
+ private _connection: azdata.connection.Connection;
+ private _profile!: azdata.IConnectionProfile;
+ private _user: string;
+ private _password: string;
+
+ constructor(connectionInfo: azdata.connection.Connection | azdata.IConnectionProfile) {
+ this.validate(connectionInfo);
+
+ if ('id' in connectionInfo) {
+ this._profile = connectionInfo;
+ this._connection = this.toConnection(this._profile);
+ } else {
+ this._connection = connectionInfo;
+ }
+ this._user = this._connection.options[constants.userPropName];
+ this._password = this._connection.options[constants.passwordPropName];
+ }
+
+ public get connection(): azdata.connection.Connection { return this._connection; }
+ public get user(): string { return this._user; }
+ public get password(): string { return this._password; }
+
+ public isMatch(connection: AzureMonitorClusterConnection | azdata.ConnectionInfo): boolean {
+ if (!connection) { return false; }
+ let options1 = connection instanceof AzureMonitorClusterConnection ?
+ connection._connection.options : connection.options;
+ let options2 = this._connection.options;
+ return [constants.serverPropName, constants.userPropName]
+ .every(e => options1[e] === options2[e]);
+ }
+
+ public isIntegratedAuth(): boolean {
+ let authType: string = this._connection.options[constants.authenticationTypePropName];
+ return authType?.toLowerCase() === constants.integratedAuth;
+ }
+
+ public updatePassword(password: string): void {
+ if (password) {
+ this._password = password;
+ }
+ }
+
+ private validate(connectionInfo: azdata.ConnectionInfo): void {
+ if (!connectionInfo) {
+ throw new Error(localize('connectionInfoUndefined', 'ConnectionInfo is undefined.'));
+ }
+ if (!connectionInfo.options) {
+ throw new Error(localize('connectionInfoOptionsUndefined', 'ConnectionInfo.options is undefined.'));
+ }
+ let missingProperties: string[] = this.getMissingProperties(connectionInfo)!;
+ if (missingProperties && missingProperties.length > 0) {
+ throw new Error(localize('connectionInfoOptionsMissingProperties',
+ 'Some missing properties in connectionInfo.options: {0}',
+ missingProperties.join(', ')));
+ }
+ }
+
+ private getMissingProperties(connectionInfo: azdata.ConnectionInfo): string[] | undefined {
+ if (!connectionInfo || !connectionInfo.options) { return undefined; }
+ let requiredProps = [constants.serverPropName];
+ requiredProps.push(constants.userPropName);
+ return requiredProps.filter(e => connectionInfo.options[e] === undefined);
+ }
+
+ private toConnection(connProfile: azdata.IConnectionProfile): azdata.connection.Connection {
+ let connection: azdata.connection.Connection = Object.assign(connProfile,
+ { connectionId: this._profile.id });
+ return connection;
+ }
+}
diff --git a/extensions/azuremonitor/src/objectExplorerNodeProvider/objectExplorerNodeProvider.ts b/extensions/azuremonitor/src/objectExplorerNodeProvider/objectExplorerNodeProvider.ts
new file mode 100644
index 0000000000..c347804dc7
--- /dev/null
+++ b/extensions/azuremonitor/src/objectExplorerNodeProvider/objectExplorerNodeProvider.ts
@@ -0,0 +1,219 @@
+/*---------------------------------------------------------------------------------------------
+ * 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 * as nls from 'vscode-nls';
+const localize = nls.loadMessageBundle();
+
+import { ProviderBase } from './providerBase';
+import { AzureMonitorClusterConnection } from './connection';
+import { TreeNode } from './treeNodes';
+import { AppContext } from '../appContext';
+import * as constants from '../constants';
+import { ICommandObjectExplorerContext } from './command';
+import { outputChannel } from '../azuremonitorServer';
+
+export interface ITreeChangeHandler {
+ notifyNodeChanged(node: TreeNode): void;
+}
+
+export class AzureMonitorObjectExplorerNodeProvider extends ProviderBase implements azdata.ObjectExplorerNodeProvider, ITreeChangeHandler {
+ public readonly supportedProviderId: string = constants.providerId;
+ private expandCompleteEmitter = new vscode.EventEmitter();
+
+ constructor(private appContext: AppContext) {
+ super();
+ this.appContext.registerService(constants.ObjectExplorerService, this);
+ }
+
+ handleSessionOpen(session: azdata.ObjectExplorerSession): Thenable {
+ return new Promise((resolve, reject) => {
+ if (!session) {
+ reject('handleSessionOpen requires a session object to be passed');
+ } else {
+ resolve(this.doSessionOpen(session));
+ }
+ });
+ }
+
+ private async doSessionOpen(session: azdata.ObjectExplorerSession): Promise {
+ if (!session || !session.sessionId) { return false; }
+
+ let connProfile = await azdata.objectexplorer.getSessionConnectionProfile(session.sessionId);
+ if (!connProfile) { return false; }
+
+ return true;
+ }
+
+ expandNode(nodeInfo: azdata.ExpandNodeInfo, isRefresh: boolean = false): Thenable {
+ return new Promise((resolve, reject) => {
+ if (!nodeInfo) {
+ reject('expandNode requires a nodeInfo object to be passed');
+ } else {
+ resolve(this.doExpandNode(nodeInfo, isRefresh));
+ }
+ });
+ }
+
+ private async doExpandNode(nodeInfo: azdata.ExpandNodeInfo, _isRefresh: boolean = false): Promise {
+ let response = {
+ sessionId: nodeInfo.sessionId!,
+ nodePath: nodeInfo.nodePath!,
+ errorMessage: undefined,
+ nodes: []
+ };
+
+ this.expandCompleteEmitter.fire(response);
+
+ return true;
+ }
+
+ refreshNode(nodeInfo: azdata.ExpandNodeInfo): Thenable {
+ return this.expandNode(nodeInfo, true);
+ }
+
+ handleSessionClose(_closeSessionInfo: azdata.ObjectExplorerCloseSessionInfo): void {
+ }
+
+ findNodes(_findNodesInfo: azdata.FindNodesInfo): Thenable {
+ let response: azdata.ObjectExplorerFindNodesResponse = {
+ nodes: []
+ };
+ return Promise.resolve(response);
+ }
+
+ registerOnExpandCompleted(handler: (response: azdata.ObjectExplorerExpandInfo) => any): void {
+ this.expandCompleteEmitter.event(handler);
+ }
+
+ notifyNodeChanged(node: TreeNode): void {
+ this.notifyNodeChangesAsync(node);
+ }
+
+ private async notifyNodeChangesAsync(node: TreeNode): Promise {
+ try {
+ let session = this.getSqlClusterSessionForNode(node);
+ if (!session) {
+ vscode.window.showErrorMessage(localize('sessionNotFound', "Session for node {0} does not exist", node.nodePathValue));
+ } else {
+ let nodeInfo = node.getNodeInfo();
+ let expandInfo: azdata.ExpandNodeInfo = {
+ nodePath: nodeInfo.nodePath,
+ sessionId: session.sessionId
+ };
+ await this.refreshNode(expandInfo);
+ }
+ } catch (err) {
+ outputChannel.appendLine(localize('notifyError', "Error notifying of node change: {0}", err));
+ }
+ }
+
+ private getSqlClusterSessionForNode(node?: TreeNode): SqlClusterSession | undefined {
+ let sqlClusterSession: SqlClusterSession | undefined = undefined;
+ while (node !== undefined) {
+ if (node instanceof SqlClusterRootNode) {
+ sqlClusterSession = node.session;
+ break;
+ } else {
+ node = node.parent;
+ }
+ }
+ return sqlClusterSession;
+ }
+
+ async findSqlClusterNodeByContext(context: ICommandObjectExplorerContext | azdata.ObjectExplorerContext): Promise {
+ let node: T | undefined = undefined;
+ let explorerContext = 'explorerContext' in context ? context.explorerContext : context;
+ let sqlConnProfile = explorerContext.connectionProfile;
+ let session = this.findSqlClusterSessionBySqlConnProfile(sqlConnProfile!);
+ if (session) {
+ if (explorerContext.isConnectionNode) {
+ // Note: ideally fix so we verify T matches RootNode and go from there
+ node = session.rootNode;
+ } else {
+ // Find the node under the session
+ node = await session.rootNode.findNodeByPath(explorerContext?.nodeInfo?.nodePath!, true);
+ }
+ }
+ return node;
+ }
+
+ public findSqlClusterSessionBySqlConnProfile(_connectionProfile: azdata.IConnectionProfile): SqlClusterSession | undefined {
+ return undefined;
+ }
+}
+
+export class SqlClusterSession {
+ private _rootNode: SqlClusterRootNode;
+
+ constructor(
+ private _sqlClusterConnection: AzureMonitorClusterConnection,
+ private _sqlSession: azdata.ObjectExplorerSession,
+ private _sqlConnectionProfile: azdata.IConnectionProfile
+ ) {
+ this._rootNode = new SqlClusterRootNode(this,
+ this._sqlSession.rootNode.nodePath!);
+ }
+
+ public get sqlClusterConnection(): AzureMonitorClusterConnection { return this._sqlClusterConnection; }
+ public get sqlSession(): azdata.ObjectExplorerSession { return this._sqlSession; }
+ public get sqlConnectionProfile(): azdata.IConnectionProfile { return this._sqlConnectionProfile; }
+ public get sessionId(): string { return this._sqlSession.sessionId!; }
+ public get rootNode(): SqlClusterRootNode { return this._rootNode; }
+
+ public isMatchedSqlConnection(sqlConnProfile: azdata.IConnectionProfile): boolean {
+ return this._sqlConnectionProfile.id === sqlConnProfile.id;
+ }
+}
+
+class SqlClusterRootNode extends TreeNode {
+ private _children: TreeNode[] = [];
+ constructor(
+ private _session: SqlClusterSession,
+ private _nodePathValue: string
+ ) {
+ super();
+ }
+
+ public get session(): SqlClusterSession {
+ return this._session;
+ }
+
+ public get nodePathValue(): string {
+ return this._nodePathValue;
+ }
+
+ public getChildren(refreshChildren: boolean): TreeNode[] | Promise {
+ if (refreshChildren || !this._children) {
+ return this.refreshChildren();
+ }
+ return this._children;
+ }
+
+ private async refreshChildren(): Promise {
+ this._children = [];
+ return this._children;
+ }
+
+ getTreeItem(): vscode.TreeItem | Promise {
+ throw new Error('Not intended for use in a file explorer view.');
+ }
+
+ getNodeInfo(): azdata.NodeInfo {
+ let nodeInfo: azdata.NodeInfo = {
+ label: localize('rootLabel', "Root")!,
+ isLeaf: false,
+ errorMessage: undefined,
+ metadata: undefined,
+ nodePath: this.generateNodePath()!,
+ nodeStatus: undefined,
+ nodeType: 'sqlCluster:root',
+ nodeSubType: undefined,
+ iconType: 'folder'
+ };
+ return nodeInfo;
+ }
+}
diff --git a/extensions/azuremonitor/src/objectExplorerNodeProvider/providerBase.ts b/extensions/azuremonitor/src/objectExplorerNodeProvider/providerBase.ts
new file mode 100644
index 0000000000..6157cbd453
--- /dev/null
+++ b/extensions/azuremonitor/src/objectExplorerNodeProvider/providerBase.ts
@@ -0,0 +1,13 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the Source EULA. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+'use strict';
+
+import * as constants from '../constants';
+
+export abstract class ProviderBase {
+ public readonly providerId: string = constants.azureMonitorClusterProviderName;
+ public handle?: number;
+}
diff --git a/extensions/azuremonitor/src/objectExplorerNodeProvider/treeNodes.ts b/extensions/azuremonitor/src/objectExplorerNodeProvider/treeNodes.ts
new file mode 100644
index 0000000000..b78036ecc0
--- /dev/null
+++ b/extensions/azuremonitor/src/objectExplorerNodeProvider/treeNodes.ts
@@ -0,0 +1,87 @@
+/*---------------------------------------------------------------------------------------------
+ * 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';
+import { ITreeNode } from './types';
+
+type TreeNodePredicate = (node: TreeNode) => boolean;
+
+export abstract class TreeNode implements ITreeNode {
+ private _parent?: TreeNode;
+ private _errorStatusCode?: number;
+
+ public get parent(): TreeNode | undefined {
+ return this._parent;
+ }
+
+ public set parent(node: TreeNode | undefined) {
+ this._parent = node;
+ }
+
+ public get errorStatusCode(): number | undefined {
+ return this._errorStatusCode;
+ }
+
+ public set errorStatusCode(error: number | undefined) {
+ this._errorStatusCode = error;
+ }
+
+ public generateNodePath(): string | undefined {
+ let path: string | 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 {
+ let condition: TreeNodePredicate = (node: TreeNode) => node.getNodeInfo().nodePath === path || node.getNodeInfo().nodePath.startsWith(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 {
+ if (!node) {
+ return undefined;
+ }
+
+ if (condition(node)) {
+ return node;
+ }
+
+ let nodeInfo = node.getNodeInfo();
+ if (nodeInfo.isLeaf) {
+ return undefined;
+ }
+
+ // TODO #3813 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;
+ }
+
+ /**
+ * The value to use for this node in the node path
+ */
+ public abstract get nodePathValue(): string;
+
+ abstract getChildren(refreshChildren: boolean): TreeNode[] | Promise;
+ abstract getTreeItem(): vscode.TreeItem | Promise;
+
+ abstract getNodeInfo(): azdata.NodeInfo;
+}
diff --git a/extensions/azuremonitor/src/objectExplorerNodeProvider/types.d.ts b/extensions/azuremonitor/src/objectExplorerNodeProvider/types.d.ts
new file mode 100644
index 0000000000..4b82aeaae8
--- /dev/null
+++ b/extensions/azuremonitor/src/objectExplorerNodeProvider/types.d.ts
@@ -0,0 +1,17 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the Source EULA. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import * as vscode from 'vscode';
+import * as azdata from 'azdata';
+
+/**
+ * A tree node in the object explorer tree
+ *
+ * @export
+ */
+export interface ITreeNode {
+ getNodeInfo(): azdata.NodeInfo;
+ getChildren(refreshChildren: boolean): ITreeNode[] | Promise;
+}
diff --git a/extensions/azuremonitor/src/prompts/adapter.ts b/extensions/azuremonitor/src/prompts/adapter.ts
new file mode 100644
index 0000000000..83a8816c3a
--- /dev/null
+++ b/extensions/azuremonitor/src/prompts/adapter.ts
@@ -0,0 +1,77 @@
+// This code is originally from https://github.com/DonJayamanne/bowerVSCode
+// License: https://github.com/DonJayamanne/bowerVSCode/blob/master/LICENSE
+
+import { window } from 'vscode';
+import PromptFactory from './factory';
+import EscapeException from './escapeException';
+import { IQuestion, IPrompter, IPromptCallback, Answers } from './question';
+
+// Supports simple pattern for prompting for user input and acting on this
+export default class CodeAdapter implements IPrompter {
+
+ // TODO define question interface
+ private fixQuestion(question: IQuestion): any {
+ if (question.type === 'checkbox' && Array.isArray(question.choices)) {
+ // For some reason when there's a choice of checkboxes, they aren't formatted properly
+ // Not sure where the issue is
+ question.choices = question.choices.map(item => {
+ if (typeof (item) === 'string') {
+ return { checked: false, name: item, value: item };
+ } else {
+ return item;
+ }
+ });
+ }
+ }
+
+ public async promptSingle(question: IQuestion, ignoreFocusOut?: boolean): Promise {
+ let questions: IQuestion[] = [question];
+ const answers = await this.prompt(questions, ignoreFocusOut);
+ if (answers) {
+ let response: T = answers[question.name];
+ return response || undefined;
+ }
+ return undefined;
+ }
+
+ public async prompt(questions: IQuestion[], ignoreFocusOut?: boolean): Promise | undefined> {
+ // Collapse multiple questions into a set of prompt steps
+ const promptResult = new Promise>((resolve) => {
+ let answers: Answers = {};
+ questions.forEach(async (question: IQuestion) => {
+ this.fixQuestion(question);
+ const prompt = await PromptFactory.createPrompt(question, ignoreFocusOut);
+ if (!question.shouldPrompt || question.shouldPrompt(answers) === true) {
+ const result = await prompt.render();
+ answers[question.name] = result;
+
+ if (question.onAnswered) {
+ question.onAnswered(result);
+ }
+ return;
+ }
+ });
+
+ resolve(answers);
+ });
+
+ try {
+ return await promptResult;
+ } catch (err) {
+ if (err instanceof EscapeException || err instanceof TypeError) {
+ window.showErrorMessage(err.message);
+ }
+ return undefined;
+ }
+ }
+
+ // Helper to make it possible to prompt using callback pattern. Generally Promise is a preferred flow
+ public promptCallback(questions: IQuestion[], callback: IPromptCallback | undefined): void {
+ // Collapse multiple questions into a set of prompt steps
+ this.prompt(questions).then(answers => {
+ if (callback && answers) {
+ callback(answers);
+ }
+ });
+ }
+}
diff --git a/extensions/azuremonitor/src/prompts/checkbox.ts b/extensions/azuremonitor/src/prompts/checkbox.ts
new file mode 100644
index 0000000000..815f374732
--- /dev/null
+++ b/extensions/azuremonitor/src/prompts/checkbox.ts
@@ -0,0 +1,52 @@
+'use strict';
+
+// This code is originally from https://github.com/DonJayamanne/bowerVSCode
+// License: https://github.com/DonJayamanne/bowerVSCode/blob/master/LICENSE
+
+import { window } from 'vscode';
+import Prompt from './prompt';
+import EscapeException from './escapeException';
+
+const figures = require('figures');
+
+export default class CheckboxPrompt extends Prompt {
+
+ constructor(question: any, ignoreFocusOut?: boolean) {
+ super(question, ignoreFocusOut);
+ }
+
+ public render(): any {
+ let choices = this._question.choices.reduce((result: { [x: string]: any; }, choice: { name: any; checked: boolean; }) => {
+ let choiceName = choice.name || choice;
+ result[`${choice.checked === true ? figures.radioOn : figures.radioOff} ${choiceName}`] = choice;
+ return result;
+ }, {});
+
+ let options = this.defaultQuickPickOptions;
+ options.placeHolder = this._question.message;
+
+ let quickPickOptions = Object.keys(choices);
+ quickPickOptions.push(figures.tick);
+
+ return window.showQuickPick(quickPickOptions, options)
+ .then(result => {
+ if (result === undefined) {
+ throw new EscapeException();
+ }
+
+ if (result !== figures.tick) {
+ choices[result].checked = !choices[result].checked;
+
+ return this.render();
+ }
+
+ return this._question.choices.reduce((result2: any[], choice: { checked: boolean; value: any; }) => {
+ if (choice.checked === true) {
+ result2.push(choice.value);
+ }
+
+ return result2;
+ }, []);
+ });
+ }
+}
diff --git a/extensions/azuremonitor/src/prompts/confirm.ts b/extensions/azuremonitor/src/prompts/confirm.ts
new file mode 100644
index 0000000000..055d95a4e7
--- /dev/null
+++ b/extensions/azuremonitor/src/prompts/confirm.ts
@@ -0,0 +1,36 @@
+'use strict';
+
+// This code is originally from https://github.com/DonJayamanne/bowerVSCode
+// License: https://github.com/DonJayamanne/bowerVSCode/blob/master/LICENSE
+
+import * as nls from 'vscode-nls';
+const localize = nls.loadMessageBundle();
+
+import { window } from 'vscode';
+import Prompt from './prompt';
+import EscapeException from './escapeException';
+
+export default class ConfirmPrompt extends Prompt {
+
+ constructor(question: any, ignoreFocusOut?: boolean) {
+ super(question, ignoreFocusOut);
+ }
+
+ public render(): any {
+ let choices: { [id: string]: boolean } = {};
+ choices[localize('msgYes', 'Yes')] = true;
+ choices[localize('msgNo', 'No')] = false;
+
+ let options = this.defaultQuickPickOptions;
+ options.placeHolder = this._question.message;
+
+ return window.showQuickPick(Object.keys(choices), options)
+ .then(result => {
+ if (result === undefined) {
+ throw new EscapeException();
+ }
+
+ return choices[result] || false;
+ });
+ }
+}
diff --git a/extensions/azuremonitor/src/prompts/escapeException.ts b/extensions/azuremonitor/src/prompts/escapeException.ts
new file mode 100644
index 0000000000..11bcfc4360
--- /dev/null
+++ b/extensions/azuremonitor/src/prompts/escapeException.ts
@@ -0,0 +1,8 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the Source EULA. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+export default class EscapeException extends Error {
+
+}
diff --git a/extensions/azuremonitor/src/prompts/expand.ts b/extensions/azuremonitor/src/prompts/expand.ts
new file mode 100644
index 0000000000..ee2caf0284
--- /dev/null
+++ b/extensions/azuremonitor/src/prompts/expand.ts
@@ -0,0 +1,78 @@
+'use strict';
+
+// This code is originally from https://github.com/DonJayamanne/bowerVSCode
+// License: https://github.com/DonJayamanne/bowerVSCode/blob/master/LICENSE
+
+import vscode = require('vscode');
+import Prompt from './prompt';
+import EscapeException from './escapeException';
+import { INameValueChoice } from './question';
+
+const figures = require('figures');
+
+export default class ExpandPrompt extends Prompt {
+
+ constructor(question: any, ignoreFocusOut?: boolean) {
+ super(question, ignoreFocusOut);
+ }
+
+ public render(): any {
+ // label indicates this is a quickpick item. Otherwise it's a name-value pair
+ if (this._question.choices[0].label) {
+ return this.renderQuickPick(this._question.choices);
+ } else {
+ return this.renderNameValueChoice(this._question.choices);
+ }
+ }
+
+ private renderQuickPick(choices: vscode.QuickPickItem[]): any {
+ let options = this.defaultQuickPickOptions;
+ options.placeHolder = this._question.message;
+
+ return vscode.window.showQuickPick(choices, options)
+ .then(result => {
+ if (result === undefined) {
+ throw new EscapeException();
+ }
+
+ return this.validateAndReturn(result || false);
+ });
+ }
+ private renderNameValueChoice(_choices: INameValueChoice[]): any {
+ const choiceMap = this._question.choices.reduce((result: { [x: string]: any; }, choice: { name: string | number; value: any; }) => {
+ result[choice.name] = choice.value;
+ return result;
+ }, {});
+
+ let options = this.defaultQuickPickOptions;
+ options.placeHolder = this._question.message;
+
+ return vscode.window.showQuickPick(Object.keys(choiceMap), options)
+ .then(result => {
+ if (result === undefined) {
+ throw new EscapeException();
+ }
+
+ // Note: cannot be used with 0 or false responses
+ let returnVal = choiceMap[result] || false;
+ return this.validateAndReturn(returnVal);
+ });
+ }
+
+ private validateAndReturn(value: any): any {
+ if (!this.validate(value)) {
+ return this.render();
+ }
+ return value;
+ }
+
+ private validate(value: any): boolean {
+ const validationError = this._question.validate ? this._question.validate(value || '') : undefined;
+
+ if (validationError) {
+ this._question.message = `${figures.warning} ${validationError}`;
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/extensions/azuremonitor/src/prompts/factory.ts b/extensions/azuremonitor/src/prompts/factory.ts
new file mode 100644
index 0000000000..1b9db3dda5
--- /dev/null
+++ b/extensions/azuremonitor/src/prompts/factory.ts
@@ -0,0 +1,35 @@
+'use strict';
+
+// This code is originally from https://github.com/DonJayamanne/bowerVSCode
+// License: https://github.com/DonJayamanne/bowerVSCode/blob/master/LICENSE
+
+import Prompt from './prompt';
+import InputPrompt from './input';
+import PasswordPrompt from './password';
+import ListPrompt from './list';
+import ConfirmPrompt from './confirm';
+import CheckboxPrompt from './checkbox';
+import ExpandPrompt from './expand';
+
+export default class PromptFactory {
+
+ public static createPrompt(question: any, ignoreFocusOut?: boolean): Prompt {
+ switch (question.type || 'input') {
+ case 'string':
+ case 'input':
+ return new InputPrompt(question, ignoreFocusOut);
+ case 'password':
+ return new PasswordPrompt(question, ignoreFocusOut);
+ case 'list':
+ return new ListPrompt(question, ignoreFocusOut);
+ case 'confirm':
+ return new ConfirmPrompt(question, ignoreFocusOut);
+ case 'checkbox':
+ return new CheckboxPrompt(question, ignoreFocusOut);
+ case 'expand':
+ return new ExpandPrompt(question, ignoreFocusOut);
+ default:
+ throw new Error(`Could not find a prompt for question type ${question.type}`);
+ }
+ }
+}
diff --git a/extensions/azuremonitor/src/prompts/input.ts b/extensions/azuremonitor/src/prompts/input.ts
new file mode 100644
index 0000000000..eb57f3faee
--- /dev/null
+++ b/extensions/azuremonitor/src/prompts/input.ts
@@ -0,0 +1,59 @@
+'use strict';
+
+// This code is originally from https://github.com/DonJayamanne/bowerVSCode
+// License: https://github.com/DonJayamanne/bowerVSCode/blob/master/LICENSE
+
+import { window, InputBoxOptions } from 'vscode';
+import Prompt from './prompt';
+import EscapeException from './escapeException';
+
+const figures = require('figures');
+
+export default class InputPrompt extends Prompt {
+
+ protected _options: InputBoxOptions;
+
+ constructor(question: any, ignoreFocusOut?: boolean) {
+ super(question, ignoreFocusOut);
+
+ this._options = this.defaultInputBoxOptions;
+ this._options.prompt = this._question.message;
+ }
+
+ // Helper for callers to know the right type to get from the type factory
+ public static get promptType(): string { return 'input'; }
+
+ public render(): any {
+ // Prefer default over the placeHolder, if specified
+ let placeHolder = this._question.default ? this._question.default : this._question.placeHolder;
+
+ if (this._question.default instanceof Error) {
+ placeHolder = this._question.default.message;
+ this._question.default = undefined;
+ }
+
+ this._options.placeHolder = placeHolder;
+
+ return window.showInputBox(this._options)
+ .then(result => {
+ if (result === undefined) {
+ throw new EscapeException();
+ }
+
+ if (result === '') {
+ // Use the default value, if defined
+ result = this._question.default || '';
+ }
+
+ const validationError = this._question.validate ? this._question.validate(result || '') : undefined;
+
+ if (validationError) {
+ this._question.default = new Error(`${figures.warning} ${validationError}`);
+
+ return this.render();
+ }
+
+ return result;
+ });
+ }
+}
diff --git a/extensions/azuremonitor/src/prompts/list.ts b/extensions/azuremonitor/src/prompts/list.ts
new file mode 100644
index 0000000000..539c5c5557
--- /dev/null
+++ b/extensions/azuremonitor/src/prompts/list.ts
@@ -0,0 +1,33 @@
+'use strict';
+
+// This code is originally from https://github.com/DonJayamanne/bowerVSCode
+// License: https://github.com/DonJayamanne/bowerVSCode/blob/master/LICENSE
+
+import { window } from 'vscode';
+import Prompt from './prompt';
+import EscapeException from './escapeException';
+
+export default class ListPrompt extends Prompt {
+ constructor(question: any, ignoreFocusOut?: boolean) {
+ super(question, ignoreFocusOut);
+ }
+
+ public render(): any {
+ const choices = this._question.choices.reduce((result: { [x: string]: any; }, choice: { name: string | number; value: any; }) => {
+ result[choice.name] = choice.value;
+ return result;
+ }, {});
+
+ let options = this.defaultQuickPickOptions;
+ options.placeHolder = this._question.message;
+
+ return window.showQuickPick(Object.keys(choices), options)
+ .then(result => {
+ if (result === undefined) {
+ throw new EscapeException();
+ }
+
+ return choices[result];
+ });
+ }
+}
diff --git a/extensions/azuremonitor/src/prompts/password.ts b/extensions/azuremonitor/src/prompts/password.ts
new file mode 100644
index 0000000000..ef6de55886
--- /dev/null
+++ b/extensions/azuremonitor/src/prompts/password.ts
@@ -0,0 +1,15 @@
+'use strict';
+
+// This code is originally from https://github.com/DonJayamanne/bowerVSCode
+// License: https://github.com/DonJayamanne/bowerVSCode/blob/master/LICENSE
+
+import InputPrompt from './input';
+
+export default class PasswordPrompt extends InputPrompt {
+
+ constructor(question: any, ignoreFocusOut?: boolean) {
+ super(question, ignoreFocusOut);
+
+ this._options.password = true;
+ }
+}
diff --git a/extensions/azuremonitor/src/prompts/progressIndicator.ts b/extensions/azuremonitor/src/prompts/progressIndicator.ts
new file mode 100644
index 0000000000..c6f20574a0
--- /dev/null
+++ b/extensions/azuremonitor/src/prompts/progressIndicator.ts
@@ -0,0 +1,70 @@
+'use strict';
+
+// This code is originally from https://github.com/DonJayamanne/bowerVSCode
+// License: https://github.com/DonJayamanne/bowerVSCode/blob/master/LICENSE
+
+import { window, StatusBarItem, StatusBarAlignment } from 'vscode';
+
+export default class ProgressIndicator {
+
+ private _statusBarItem: StatusBarItem;
+
+ constructor() {
+ this._statusBarItem = window.createStatusBarItem(StatusBarAlignment.Left);
+ }
+
+ private _tasks: string[] = [];
+ public beginTask(task: string): void {
+ this._tasks.push(task);
+ this.displayProgressIndicator();
+ }
+
+ public endTask(_task: string): void {
+ if (this._tasks.length > 0) {
+ this._tasks.pop();
+ }
+
+ this.setMessage();
+ }
+
+ private setMessage(): void {
+ if (this._tasks.length === 0) {
+ this._statusBarItem.text = '';
+ this.hideProgressIndicator();
+ return;
+ }
+
+ this._statusBarItem.text = this._tasks[this._tasks.length - 1];
+ this._statusBarItem.show();
+ }
+
+ private _interval: any;
+ private displayProgressIndicator(): void {
+ this.setMessage();
+ this.hideProgressIndicator();
+ this._interval = setInterval(() => this.onDisplayProgressIndicator(), 100);
+ }
+ private hideProgressIndicator(): void {
+ if (this._interval) {
+ clearInterval(this._interval);
+ this._interval = undefined;
+ }
+ this.ProgressCounter = 0;
+ }
+
+ private ProgressText = ['|', '/', '-', '\\', '|', '/', '-', '\\'];
+ private ProgressCounter = 0;
+ private onDisplayProgressIndicator(): void {
+ if (this._tasks.length === 0) {
+ return;
+ }
+
+ let txt = this.ProgressText[this.ProgressCounter];
+ this._statusBarItem.text = this._tasks[this._tasks.length - 1] + ' ' + txt;
+ this.ProgressCounter++;
+
+ if (this.ProgressCounter >= this.ProgressText.length - 1) {
+ this.ProgressCounter = 0;
+ }
+ }
+}
diff --git a/extensions/azuremonitor/src/prompts/prompt.ts b/extensions/azuremonitor/src/prompts/prompt.ts
new file mode 100644
index 0000000000..700f577d66
--- /dev/null
+++ b/extensions/azuremonitor/src/prompts/prompt.ts
@@ -0,0 +1,33 @@
+'use strict';
+
+// This code is originally from https://github.com/DonJayamanne/bowerVSCode
+// License: https://github.com/DonJayamanne/bowerVSCode/blob/master/LICENSE
+
+import { InputBoxOptions, QuickPickOptions } from 'vscode';
+
+abstract class Prompt {
+
+ protected _question: any;
+ protected _ignoreFocusOut?: boolean;
+
+ constructor(question: any, ignoreFocusOut?: boolean) {
+ this._question = question;
+ this._ignoreFocusOut = ignoreFocusOut ? ignoreFocusOut : false;
+ }
+
+ public abstract render(): any;
+
+ protected get defaultQuickPickOptions(): QuickPickOptions {
+ return {
+ ignoreFocusOut: this._ignoreFocusOut
+ };
+ }
+
+ protected get defaultInputBoxOptions(): InputBoxOptions {
+ return {
+ ignoreFocusOut: this._ignoreFocusOut
+ };
+ }
+}
+
+export default Prompt;
diff --git a/extensions/azuremonitor/src/prompts/question.ts b/extensions/azuremonitor/src/prompts/question.ts
new file mode 100644
index 0000000000..b0678c668a
--- /dev/null
+++ b/extensions/azuremonitor/src/prompts/question.ts
@@ -0,0 +1,73 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the Source EULA. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+// TODO: Common file with mssql
+import vscode = require('vscode');
+
+export class QuestionTypes {
+ public static get input(): string { return 'input'; }
+ public static get password(): string { return 'password'; }
+ public static get list(): string { return 'list'; }
+ public static get confirm(): string { return 'confirm'; }
+ public static get checkbox(): string { return 'checkbox'; }
+ public static get expand(): string { return 'expand'; }
+}
+
+// Question interface to clarify how to use the prompt feature
+// based on Bower Question format: https://github.com/bower/bower/blob/89069784bb46bfd6639b4a75e98a0d7399a8c2cb/packages/bower-logger/README.md
+export interface IQuestion {
+ // Type of question (see QuestionTypes)
+ type: string;
+ // Name of the question for disambiguation
+ name: string;
+ // Message to display to the user
+ message: string;
+ // Optional placeHolder to give more detailed information to the user
+ placeHolder?: any;
+ // Optional default value - this will be used instead of placeHolder
+ default?: any;
+ // optional set of choices to be used. Can be QuickPickItems or a simple name-value pair
+ choices?: Array;
+ // Optional validation function that returns an error string if validation fails
+ validate?: (value: any) => string;
+ // Optional pre-prompt function. Takes in set of answers so far, and returns true if prompt should occur
+ shouldPrompt?: (answers: { [id: string]: any }) => boolean;
+ // Optional action to take on the question being answered
+ onAnswered?: (value: any) => void;
+ // Optional set of options to support matching choices.
+ matchOptions?: vscode.QuickPickOptions;
+}
+
+// Pair used to display simple choices to the user
+export interface INameValueChoice {
+ name: string;
+ value: any;
+}
+
+// Generic object that can be used to define a set of questions and handle the result
+export interface IQuestionHandler {
+ // Set of questions to be answered
+ questions: IQuestion[];
+ // Optional callback, since questions may handle themselves
+ callback?: IPromptCallback;
+}
+
+export type Answers = { [key: string]: T };
+
+export interface IPrompter {
+ promptSingle(question: IQuestion, ignoreFocusOut?: boolean): Promise;
+ /**
+ * Prompts for multiple questions
+ *
+ * @returns Map of question IDs to results, or undefined if
+ * the user canceled the question session
+ */
+ prompt(questions: IQuestion[], ignoreFocusOut?: boolean): Promise | undefined>;
+ promptCallback(questions: IQuestion[], callback: IPromptCallback): void;
+}
+
+export interface IPromptCallback {
+ (answers: { [id: string]: any }): void;
+}
diff --git a/extensions/azuremonitor/src/telemetry.ts b/extensions/azuremonitor/src/telemetry.ts
new file mode 100644
index 0000000000..433abf95d1
--- /dev/null
+++ b/extensions/azuremonitor/src/telemetry.ts
@@ -0,0 +1,167 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the Source EULA. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+import * as vscode from 'vscode';
+import * as nls from 'vscode-nls';
+import TelemetryReporter from 'vscode-extension-telemetry';
+import { ErrorAction, ErrorHandler, Message, CloseAction } from 'vscode-languageclient';
+
+import * as Utils from './utils';
+import * as Constants from './constants';
+
+const localize = nls.loadMessageBundle();
+
+const packageJson = require('../package.json');
+const viewKnownIssuesAction = localize('viewKnownIssuesText', "View Known Issues");
+
+export interface ITelemetryEventProperties {
+ [key: string]: string;
+}
+
+export interface ITelemetryEventMeasures {
+ [key: string]: number;
+}
+
+/**
+ * Filters error paths to only include source files. Exported to support testing
+ */
+export function FilterErrorPath(line: string): string | undefined {
+ if (line) {
+ let values: string[] = line.split('/out/');
+ if (values.length <= 1) {
+ // Didn't match expected format
+ return line;
+ } else {
+ return values[1];
+ }
+ }
+ return undefined;
+}
+
+export class Telemetry {
+ private static reporter: TelemetryReporter;
+ private static disabled: boolean;
+
+ /**
+ * Disable telemetry reporting
+ */
+ public static disable(): void {
+ this.disabled = true;
+ }
+
+ /**
+ * Initialize the telemetry reporter for use.
+ */
+ public static initialize(): void {
+ if (typeof this.reporter === 'undefined') {
+ // Check if the user has opted out of telemetry
+ if (!vscode.workspace.getConfiguration('telemetry').get('enableTelemetry', true)) {
+ this.disable();
+ return;
+ }
+
+ let packageInfo = Utils.getPackageInfo(packageJson)!;
+ this.reporter = new TelemetryReporter(packageInfo.name, packageInfo.version, packageInfo.aiKey);
+ }
+ }
+
+ /**
+ * Send a telemetry event for an exception
+ */
+ public static sendTelemetryEventForException(
+ err: any, methodName: string, _extensionConfigName: string): void {
+ let stackArray: string[];
+ let firstLine: string = '';
+ if (err !== undefined && err.stack !== undefined) {
+ stackArray = err.stack.split('\n');
+ if (stackArray !== undefined && stackArray.length >= 2) {
+ firstLine = stackArray[1]; // The fist line is the error message and we don't want to send that telemetry event
+ firstLine = FilterErrorPath(firstLine)!;
+ }
+ }
+
+ // Only adding the method name and the fist line of the stack trace. We don't add the error message because it might have PII
+ this.sendTelemetryEvent('Exception', { methodName: methodName, errorLine: firstLine });
+ }
+
+ /**
+ * Send a telemetry event using application insights
+ */
+ public static sendTelemetryEvent(
+ eventName: string,
+ properties?: ITelemetryEventProperties,
+ measures?: ITelemetryEventMeasures): void {
+
+ if (typeof this.disabled === 'undefined') {
+ this.disabled = false;
+ }
+
+ if (this.disabled || typeof (this.reporter) === 'undefined') {
+ // Don't do anything if telemetry is disabled
+ return;
+ }
+
+ if (!properties || typeof properties === 'undefined') {
+ properties = {};
+ }
+
+ try {
+ this.reporter.sendTelemetryEvent(eventName, properties, measures);
+ } catch (telemetryErr) {
+ // If sending telemetry event fails ignore it so it won't break the extension
+ console.error('Failed to send telemetry event. error: ' + telemetryErr);
+ }
+
+ }
+}
+
+/**
+ * Handle Language Service client errors
+ */
+export class LanguageClientErrorHandler implements ErrorHandler {
+
+ /**
+ * Show an error message prompt with a link to known issues wiki page
+ * @memberOf LanguageClientErrorHandler
+ */
+ showOnErrorPrompt(): void {
+ Telemetry.sendTelemetryEvent(Constants.serviceName + 'Crash');
+ vscode.window.showErrorMessage(
+ localize('serviceCrashMessage', "{0} component exited unexpectedly. Please restart Azure Data Studio.", Constants.serviceName),
+ viewKnownIssuesAction).then(action => {
+ if (action && action === viewKnownIssuesAction) {
+ vscode.env.openExternal(vscode.Uri.parse(Constants.serviceCrashLink));
+ }
+ });
+ }
+
+ /**
+ * Callback for language service client error
+ *
+ * @memberOf LanguageClientErrorHandler
+ */
+ error(_error: Error, _message: Message, _count: number): ErrorAction {
+ this.showOnErrorPrompt();
+
+ // we don't retry running the service since crashes leave the extension
+ // in a bad, unrecovered state
+ return ErrorAction.Shutdown;
+ }
+
+ /**
+ * Callback for language service client closed
+ *
+ * @memberOf LanguageClientErrorHandler
+ */
+ closed(): CloseAction {
+ this.showOnErrorPrompt();
+
+ // we don't retry running the service since crashes leave the extension
+ // in a bad, unrecovered state
+ return CloseAction.DoNotRestart;
+ }
+}
+
+Telemetry.initialize();
diff --git a/extensions/azuremonitor/src/types.ts b/extensions/azuremonitor/src/types.ts
new file mode 100644
index 0000000000..b09b7d25f4
--- /dev/null
+++ b/extensions/azuremonitor/src/types.ts
@@ -0,0 +1,14 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the Source EULA. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+'use strict';
+
+/**
+ * @returns whether the provided parameter is undefined or null.
+ */
+export function isUndefinedOrNull(obj: any): boolean {
+ // Intentional ==
+ // eslint-disable-next-line eqeqeq
+ return obj == undefined;
+}
diff --git a/extensions/azuremonitor/src/typings/findRemove.d.ts b/extensions/azuremonitor/src/typings/findRemove.d.ts
new file mode 100644
index 0000000000..c49686d1b3
--- /dev/null
+++ b/extensions/azuremonitor/src/typings/findRemove.d.ts
@@ -0,0 +1,17 @@
+declare module 'find-remove' {
+ namespace findRemove {
+ interface FindRemoveApi {
+ (path: string, options: FindRemoveOptions): JSON;
+ }
+
+ interface FindRemoveOptions {
+ age?: {
+ seconds?: number;
+ };
+ limit?: number;
+ }
+ }
+
+ const findRemove: findRemove.FindRemoveApi;
+ export = findRemove;
+}
diff --git a/extensions/azuremonitor/src/typings/refs.d.ts b/extensions/azuremonitor/src/typings/refs.d.ts
new file mode 100644
index 0000000000..dad0d96412
--- /dev/null
+++ b/extensions/azuremonitor/src/typings/refs.d.ts
@@ -0,0 +1,8 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the Source EULA. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+///
+///
+///
diff --git a/extensions/azuremonitor/src/utils.ts b/extensions/azuremonitor/src/utils.ts
new file mode 100644
index 0000000000..e850d3b088
--- /dev/null
+++ b/extensions/azuremonitor/src/utils.ts
@@ -0,0 +1,147 @@
+/*---------------------------------------------------------------------------------------------
+ * Copyright (c) Microsoft Corporation. All rights reserved.
+ * Licensed under the Source EULA. See License.txt in the project root for license information.
+ *--------------------------------------------------------------------------------------------*/
+
+//TODO: This is the same file from mssql. Move this into a common place.
+import * as azdata from 'azdata';
+import * as vscode from 'vscode';
+import * as path from 'path';
+import * as os from 'os';
+import * as findRemoveSync from 'find-remove';
+import { promises as fs } from 'fs';
+
+const configTracingLevel = 'tracingLevel';
+const configLogRetentionMinutes = 'logRetentionMinutes';
+const configLogFilesRemovalLimit = 'logFilesRemovalLimit';
+const extensionConfigSectionName = 'azuremonitor';
+
+export function removeOldLogFiles(logPath: string, _prefix: string): JSON {
+ return findRemoveSync(logPath, { age: { seconds: getConfigLogRetentionSeconds() }, limit: getConfigLogFilesRemovalLimit() });
+}
+
+export function getConfiguration(_config: string = extensionConfigSectionName): vscode.WorkspaceConfiguration {
+ return vscode.workspace.getConfiguration(extensionConfigSectionName);
+}
+
+export function getConfigLogFilesRemovalLimit(): number | undefined {
+ let config = getConfiguration();
+ if (config && config[configLogFilesRemovalLimit]) {
+ return Number((config[configLogFilesRemovalLimit]).toFixed(0));
+ }
+ else {
+ return undefined;
+ }
+}
+
+export function getConfigLogRetentionSeconds(): number | undefined {
+ let config = getConfiguration();
+ if (config && config[configLogRetentionMinutes]) {
+ return Number((config[configLogRetentionMinutes] * 60).toFixed(0));
+ }
+ else {
+ return undefined;
+ }
+}
+
+export function getConfigTracingLevel(): string | undefined {
+ let config = getConfiguration();
+ if (config) {
+ return config[configTracingLevel];
+ }
+ else {
+ return undefined;
+ }
+}
+
+export function getLogFileName(prefix: string, pid: number): string {
+ return `${prefix}_${pid}.log`;
+}
+
+export function getCommonLaunchArgsAndCleanupOldLogFiles(logPath: string, fileName: string, executablePath: string): string[] {
+ let launchArgs: string[] = [];
+ launchArgs.push(`--locale`, vscode.env.language);
+
+ launchArgs.push('--log-file');
+ let logFile = path.join(logPath, fileName);
+ launchArgs.push(logFile);
+
+ console.log(`logFile for ${path.basename(executablePath)} is ${logFile}`);
+ console.log(`This process (ui Extenstion Host) is pid: ${process.pid}`);
+ // Delete old log files
+ let deletedLogFiles = removeOldLogFiles(logPath, fileName);
+ console.log(`Old log files deletion report: ${JSON.stringify(deletedLogFiles)}`);
+ let config = getConfigTracingLevel();
+ if (config) {
+ launchArgs.push('--tracing-level');
+ launchArgs.push(config);
+ }
+
+ launchArgs.push('--service-name');
+ launchArgs.push('AzureMonitor');
+
+ return launchArgs;
+}
+
+export function ensure(target: any, key: string): any {
+ if (target[key] === void 0) {
+ target[key] = {} as any;
+ }
+ return target[key];
+}
+
+export interface IPackageInfo {
+ name: string;
+ version: string;
+ aiKey: string;
+}
+
+export function getPackageInfo(packageJson: IPackageInfo): IPackageInfo | undefined {
+ if (packageJson) {
+ return {
+ name: packageJson.name,
+ version: packageJson.version,
+ aiKey: packageJson.aiKey
+ };
+ }
+ return undefined;
+}
+
+export function verifyPlatform(): Thenable {
+ if (os.platform() === 'darwin' && parseFloat(os.release()) < 16) {
+ return Promise.resolve(false);
+ } else {
+ return Promise.resolve(true);
+ }
+}
+
+export function getErrorMessage(error: Error | any, removeHeader: boolean = false): string {
+ let errorMessage: string = (error instanceof Error) ? error.message : error.toString();
+ if (removeHeader) {
+ errorMessage = removeErrorHeader(errorMessage);
+ }
+ return errorMessage;
+}
+
+export function removeErrorHeader(errorMessage: string): string {
+ if (errorMessage && errorMessage !== '') {
+ let header: string = 'Error:';
+ if (errorMessage.startsWith(header)) {
+ errorMessage = errorMessage.substring(header.length);
+ }
+ }
+ return errorMessage;
+}
+
+export function isObjectExplorerContext(object: any): object is azdata.ObjectExplorerContext {
+ return 'connectionProfile' in object && 'isConnectionNode' in object;
+}
+
+export async function exists(path: string): Promise {
+ try {
+ await fs.access(path);
+ return true;
+ } catch (e) {
+ return false;
+ }
+}
diff --git a/extensions/azuremonitor/syntaxes/azuremonitor.tmLanguage b/extensions/azuremonitor/syntaxes/azuremonitor.tmLanguage
new file mode 100644
index 0000000000..15c627641e
--- /dev/null
+++ b/extensions/azuremonitor/syntaxes/azuremonitor.tmLanguage
@@ -0,0 +1,449 @@
+
+
+
+
+ scopeName
+ source.kusto
+
+ fileTypes
+
+ kql
+ kusto
+ csl
+
+
+ name
+ kusto file
+
+ patterns
+
+
+
+
+ match
+ \b(let|set|alias|declare|pattern|restrict|access|to|set)\b
+ name
+ keyword.control.kusto
+
+
+
+ match
+ \b(cluster|database|materialize|table|toscalar)(?=\W*\()
+ name
+ support.function
+
+
+
+ match
+ \b(and|or|!in|has|has_cs|hasprefix|hasprefix_cs|hassuffix|hassuffix_cs|contains|contains_cs|startswith|startswith_cs|endswith|endswith_cs|matches|regex|in|between)\b
+ name
+ keyword.operator.kusto
+
+
+
+
+ match
+ \b(binary_and|binary_not|binary_or|binary_shift_left|binary_shift_right|binary_xor)(?=\W*\()
+ name
+ support.function
+
+
+
+ match
+ \b(tobool|todatetime|todecimal|todouble|toguid|tohex|toreal|toint|tolong|tolower|toreal|tostring|totimespan|toupper|to_utf8|translate|treepath|trim|trim_end|trim_start|url_decode|url_encode|weekofyear|welch_test|zip)(?=\W*\()
+ name
+ support.function
+
+
+
+ match
+ (?<=\.\d|\d|\d\W)(d|h|m|s|ms|microsecond|tick|seconds)\b
+ name
+ variable.language
+
+
+
+ match
+ \b(ago|datetime_add|datetime_part|datetime_diff|dayofmonth|dayofweek|dayofyear|endofday|endofmonth|endofweek|endofyear|format_datetime|format_timespan|getmonth|getyear|hourofday|make_datetime|make_timespan|monthofyear|now|startofday|startofmonth|startofweek|startofyear|todatetime|totimespan|weekofyear)(?=\W*\()
+ name
+ support.function
+
+
+
+ match
+ \b(array_concat|array_length|array_slice|array_split|array_strcat|bag_keys|pack|pack_all|pack_array|repeat|treepath|zip)(?=\W*\()
+ name
+ support.function
+
+
+
+ match
+ \b(next|prev|row_cumsum|row_number)(?=\W*\()
+ name
+ support.function
+
+
+
+ match
+ \b(toscalar)(?=\W*\()
+ name
+ support.function
+
+
+
+ match
+ \b(abs|acos|asin|atan|atan2|beta_cdf|beta_inv|beta_pdf|cos|cot|degrees|exp|exp100|exp2|gamma|hash|isfinite|isinf|isnan|log|log10|log2|loggamma|not|pi|pow|radians|rand|range|round|sign|sin|sqrt|tan|welch_test)(?=\W*\()
+ name
+ support.function
+
+
+
+ match
+ \b(column_ifexists|current_principal|cursor_after|extent_id|extent_tags|ingestion_time)(?=\W*\()
+ name
+ support.function
+
+
+
+ match
+ \b(bin|bin_at|ceiling|floor)(?=\W*\()
+ name
+ support.function
+
+
+
+ match
+ \b(case|coalesce|iif|iff|max_of|min_of)(?=\W*\()
+ name
+ support.function
+
+
+
+ match
+ \b(series_add|series_divide|series_equals|series_greater|series_greater_equals|series_less|series_less_equals|series_multiply|series_not_equals|series_subtract)(?=\W*\()
+ name
+ support.function
+
+
+
+ match
+ \b(series_decompose|series_decompose_anomalies|series_decompose_forecast|series_fill_backward|series_fill_const|series_fill_forward|series_fill_linear|series_fir|series_fit_2lines|series_fit_2lines_dynamic|series_fit_line|series_fit_line_dynamic|series_iir|series_outliers|series_periods_detect|series_periods_validate|series_seasonal|series_stats|series_stats_dynamic)(?=\W*\()
+ name
+ support.function
+
+
+
+ match
+ \b(base64_decodestring|base64_encodestring|countof|extract|extract_all|extractjson|indexof|isempty|isnotempty|isnotnull|isnull|parse_ipv4|parse_json|parse_url|parse_urlquery|parse_version|replace|reverse|split|strcat|strcat_delim|strcmp|strlen|strrep|substring|toupper|translate|trim|trim_end|trim_start|url_decode|url_encode)(?=\W*\()
+ name
+ support.function
+
+
+
+ match
+ \b(gettype)(?=\W*\()
+ name
+ support.function
+
+
+
+ match
+ \b(dcount_hll|hll_merge|percentile_tdigest|percentrank_tdigest|rank_tdigest|tdigest_merge)(?=\W*\()
+ name
+ support.function
+
+
+
+ match
+ \b(any|arg_max|arg_min|avg|avgif|buildschema|count|countif|dcount|dcountif|hll|hll_merge|make_bag|make_list|make_set|max|min|percentiles|stdev|stdevif|stdevp|sum|sumif|tdigest|tdigest_merge|variance|varianceif|variancep)(?=\W*\()
+ name
+ support.function
+
+
+
+ match
+ \b(next|prev|row_cumsum|row_number)(?=\W*\()
+ name
+ support.function
+
+
+
+ match
+ \b(activity_counts_metrics|sliding_window_counts|activity_metrics|new_activity_metrics|activity_engagement|active_users_count|session_count|funnel_sequence|funnel_sequence_completion)(?=\W*\()
+ name
+ support.function
+
+
+
+ match
+ \.create-or-alter
+ name
+ keyword.control.kusto
+
+
+
+ match
+ (?<=let ).+(?=\W*=)
+ name
+ entity.function.name.lambda.kusto
+
+
+
+ match
+ \b(with|folder|docstring|skipvalidation)\b
+ name
+ keyword.operator.kusto
+
+
+ match
+ \b(function)\b
+ name
+ variable.language
+
+
+
+
+ match
+ \b(bool|datetime|decimal|dynamic|guid|int|long|real|string|timespan)\b
+ name
+ storage.type
+
+
+
+ match
+ \b(datatable)(?=\W*\()
+ name
+ support.function
+
+
+
+
+ match
+ \b(as|consume|count|datatable|distinct|evaluate|extend|externaldata|facet|find|fork|getschema|invoke|join|limit|take|lookup|make-series|mv-expand|order|sort|project-away|project-rename|project|parse|partition|print|range|reduce|render|sample|sample-distinct|search|serialize|sort|summarize|take|top-nested|top|top-hitters|union|where)\b
+ name
+ keyword.operator.special.kusto
+
+
+
+ match
+ \b(autocluster|bag_unpack|basket|dcount_intersect|diffpatterns|narrow|pivot|preview|rolling_percentile|sql_request)(?=\W*\()
+ name
+ support.function
+
+
+
+ match
+ \b(on|kind|hint\.remote|hint\.strategy)\b
+ name
+ keyword.operator.kusto
+
+
+
+ match
+ (\$left|\$right)\b
+ name
+ keyword.other.kusto
+
+
+
+ match
+ \b(innerunique|inner|leftouter|rightouter|fullouter|leftanti|anti|leftantisemi|rightanti|rightantisemi|leftsemi|rightsemi|shuffle|broadcast)\b
+ name
+ keyword.other.kusto
+
+
+
+ match
+ \b(series_fir|series_iir|series_fit_line|series_fit_line_dynamic|series_fit_2lines|series_fit_2lines_dynamic|series_outliers|series_periods_detect|series_periods_validate|series_stats_dynamic|series_stats)(?=\W*\()
+ name
+ support.function
+
+
+
+ match
+ \b(series_fill_backward|series_fill_const|series_fill_forward|series_fill_linear)(?=\W*\()
+ name
+ support.function
+
+
+
+ match
+ \b(bag|array)\b
+ name
+ keyword.operator.kusto
+
+
+
+ match
+ \b(asc|desc|nulls first|nulls last)\b
+ name
+ keyword.other.kusto
+
+
+
+ match
+ \b(regex|simple|relaxed)\b
+ name
+ keyword.other.kusto
+
+
+
+ match
+ \b(anomalychart|areachart|barchart|columnchart|ladderchart|linechart|piechart|pivotchart|scatterchart|stackedareachart|table|timechart|timepivot)\b
+ name
+ support.function
+
+
+
+ match
+ \b(by|from|in|of|to|step|with)\b
+ name
+ keyword.operator.word
+
+
+
+
+ match
+ ".*?"
+ name
+ string.quoted.double
+
+
+ match
+ \{.*?\}
+ name
+ string.variable
+
+
+ match
+ '.*?'
+ name
+ string.quoted.single
+
+
+
+ match
+ //.*
+ name
+ comment.line
+
+
+ match
+ \b((0(x|X)[0-9a-fA-F]*)|(([0-9]+\.?[0-9]*+)|(\.[0-9]+))((e|E)(\+|-)?[0-9]+)?)(L|l|UL|ul|u|U|F|f|ll|LL|ull|ULL)?(?=\b|\w)
+ name
+ constant.numeric
+
+
+ match
+ \b(true|false)\b
+ name
+ constant.language
+
+
+
+ match
+ \b(array_strcat|make_dictionary|makelist|makeset|mvexpand|todynamic)(?=\W*\(|\b)
+ name
+ invalid.deprecated
+
+
+
+ match
+
+ name
+ invalid.illegal
+
+
+ uuid
+ FF0550E0-3A29-11E3-AA6E-0800200C9B77
+
+
diff --git a/extensions/azuremonitor/tsconfig.json b/extensions/azuremonitor/tsconfig.json
new file mode 100644
index 0000000000..75605f1a69
--- /dev/null
+++ b/extensions/azuremonitor/tsconfig.json
@@ -0,0 +1,12 @@
+{
+ "extends": "../tsconfig.base.json",
+ "compilerOptions": {
+ "outDir": "./out",
+ "strict": true,
+ "noUnusedParameters": true,
+ "noImplicitAny": true
+ },
+ "include": [
+ "src/**/*"
+ ]
+}
diff --git a/extensions/azuremonitor/yarn.lock b/extensions/azuremonitor/yarn.lock
new file mode 100644
index 0000000000..965d4fa714
--- /dev/null
+++ b/extensions/azuremonitor/yarn.lock
@@ -0,0 +1,370 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+agent-base@4, agent-base@^4.3.0:
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.3.0.tgz#8165f01c436009bccad0b1d122f05ed770efc6ee"
+ integrity sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==
+ dependencies:
+ es6-promisify "^5.0.0"
+
+applicationinsights@1.0.6:
+ version "1.0.6"
+ resolved "https://registry.yarnpkg.com/applicationinsights/-/applicationinsights-1.0.6.tgz#bc201810de91cea910dab34e8ad35ecde488edeb"
+ integrity sha512-VQT3kBpJVPw5fCO5n+WUeSx0VHjxFtD7znYbILBlVgOS9/cMDuGFmV2Br3ObzFyZUDGNbEfW36fD1y2/vAiCKw==
+ dependencies:
+ diagnostic-channel "0.2.0"
+ diagnostic-channel-publishers "0.2.1"
+ zone.js "0.7.6"
+
+async-retry@^1.2.3:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/async-retry/-/async-retry-1.3.1.tgz#139f31f8ddce50c0870b0ba558a6079684aaed55"
+ integrity sha512-aiieFW/7h3hY0Bq5d+ktDBejxuwR78vRu9hDUdR8rNhSaQ29VzPL4AoIRG7D/c7tdenwOcKvgPM6tIxB3cB6HA==
+ dependencies:
+ retry "0.12.0"
+
+balanced-match@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
+ integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c=
+
+brace-expansion@^1.1.7:
+ version "1.1.11"
+ resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
+ integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==
+ dependencies:
+ balanced-match "^1.0.0"
+ concat-map "0.0.1"
+
+buffer-crc32@~0.2.3:
+ version "0.2.13"
+ resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
+ integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=
+
+chownr@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/chownr/-/chownr-2.0.0.tgz#15bfbe53d2eab4cf70f18a8cd68ebe5b3cb1dece"
+ integrity sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==
+
+concat-map@0.0.1:
+ version "0.0.1"
+ resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
+ integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=
+
+"dataprotocol-client@github:Microsoft/sqlops-dataprotocolclient#1.2.2":
+ version "1.2.2"
+ resolved "https://codeload.github.com/Microsoft/sqlops-dataprotocolclient/tar.gz/3ffb03ba8b892cffe6ad363a2269c1f9439cf350"
+ dependencies:
+ vscode-languageclient "5.2.1"
+
+debug@3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
+ integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==
+ dependencies:
+ ms "2.0.0"
+
+debug@^3.1.0:
+ version "3.2.7"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a"
+ integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==
+ dependencies:
+ ms "^2.1.1"
+
+diagnostic-channel-publishers@0.2.1:
+ version "0.2.1"
+ resolved "https://registry.yarnpkg.com/diagnostic-channel-publishers/-/diagnostic-channel-publishers-0.2.1.tgz#8e2d607a8b6d79fe880b548bc58cc6beb288c4f3"
+ integrity sha1-ji1geottef6IC1SLxYzGvrKIxPM=
+
+diagnostic-channel@0.2.0:
+ version "0.2.0"
+ resolved "https://registry.yarnpkg.com/diagnostic-channel/-/diagnostic-channel-0.2.0.tgz#cc99af9612c23fb1fff13612c72f2cbfaa8d5a17"
+ integrity sha1-zJmvlhLCP7H/8TYSxy8sv6qNWhc=
+ dependencies:
+ semver "^5.3.0"
+
+es6-promise@^4.0.3:
+ version "4.2.8"
+ resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.8.tgz#4eb21594c972bc40553d276e510539143db53e0a"
+ integrity sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==
+
+es6-promisify@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203"
+ integrity sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=
+ dependencies:
+ es6-promise "^4.0.3"
+
+escape-string-regexp@^1.0.5:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
+ integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
+
+eventemitter2@^5.0.1:
+ version "5.0.1"
+ resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-5.0.1.tgz#6197a095d5fb6b57e8942f6fd7eaad63a09c9452"
+ integrity sha1-YZegldX7a1folC9v1+qtY6CclFI=
+
+fd-slicer@~1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e"
+ integrity sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=
+ dependencies:
+ pend "~1.2.0"
+
+figures@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962"
+ integrity sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=
+ dependencies:
+ escape-string-regexp "^1.0.5"
+
+find-remove@1.2.1:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/find-remove/-/find-remove-1.2.1.tgz#afd93400d23890e018ea197591e9d850d3d049a2"
+ integrity sha512-zcspBi9mWAyM9YTcVJLkI/x6rbjSDqHijjPa0vTwEmVZnYSmvYMtixDkUnSnuv2xAAkc9fblpkCg91paBIJaLw==
+ dependencies:
+ fmerge "1.2.0"
+ rimraf "2.6.2"
+
+fmerge@1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/fmerge/-/fmerge-1.2.0.tgz#36e99d2ae255e3ee1af666b4df780553671cf692"
+ integrity sha1-NumdKuJV4+4a9ma033gFU2cc9pI=
+
+fs-minipass@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb"
+ integrity sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==
+ dependencies:
+ minipass "^3.0.0"
+
+fs.realpath@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
+ integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8=
+
+glob@^7.0.5:
+ version "7.1.6"
+ resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6"
+ integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==
+ dependencies:
+ fs.realpath "^1.0.0"
+ inflight "^1.0.4"
+ inherits "2"
+ minimatch "^3.0.4"
+ once "^1.3.0"
+ path-is-absolute "^1.0.0"
+
+http-proxy-agent@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz#e4821beef5b2142a2026bd73926fe537631c5405"
+ integrity sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg==
+ dependencies:
+ agent-base "4"
+ debug "3.1.0"
+
+https-proxy-agent@^2.2.3:
+ version "2.2.4"
+ resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz#4ee7a737abd92678a293d9b34a1af4d0d08c787b"
+ integrity sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==
+ dependencies:
+ agent-base "^4.3.0"
+ debug "^3.1.0"
+
+inflight@^1.0.4:
+ version "1.0.6"
+ resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
+ integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=
+ dependencies:
+ once "^1.3.0"
+ wrappy "1"
+
+inherits@2:
+ version "2.0.4"
+ resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
+ integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
+
+minimatch@^3.0.4:
+ version "3.0.4"
+ resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083"
+ integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
+ dependencies:
+ brace-expansion "^1.1.7"
+
+minimist@^1.2.5:
+ version "1.2.5"
+ resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
+ integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
+
+minipass@^3.0.0:
+ version "3.1.3"
+ resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.1.3.tgz#7d42ff1f39635482e15f9cdb53184deebd5815fd"
+ integrity sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg==
+ dependencies:
+ yallist "^4.0.0"
+
+minizlib@^2.1.1:
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-2.1.2.tgz#e90d3466ba209b932451508a11ce3d3632145931"
+ integrity sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==
+ dependencies:
+ minipass "^3.0.0"
+ yallist "^4.0.0"
+
+mkdirp@^0.5.1:
+ version "0.5.5"
+ resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def"
+ integrity sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==
+ dependencies:
+ minimist "^1.2.5"
+
+mkdirp@^1.0.3:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
+ integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
+
+ms@2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
+ integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
+
+ms@^2.1.1:
+ version "2.1.3"
+ resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2"
+ integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==
+
+once@^1.3.0:
+ version "1.4.0"
+ resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
+ integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
+ dependencies:
+ wrappy "1"
+
+os-tmpdir@~1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
+ integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=
+
+path-is-absolute@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
+ integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18=
+
+pend@~1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
+ integrity sha1-elfrVQpng/kRUzH89GY9XI4AelA=
+
+retry@0.12.0:
+ version "0.12.0"
+ resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b"
+ integrity sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=
+
+rimraf@2.6.2:
+ version "2.6.2"
+ resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36"
+ integrity sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==
+ dependencies:
+ glob "^7.0.5"
+
+semver@^5.3.0, semver@^5.5.0:
+ version "5.7.1"
+ resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
+ integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
+
+service-downloader@0.2.1:
+ version "0.2.1"
+ resolved "https://registry.yarnpkg.com/service-downloader/-/service-downloader-0.2.1.tgz#8bd756bc4bc0cbfdf04fe71d4337f19ce6196203"
+ integrity sha512-5IEy2nyMJj/f41pI65b8RMeJyCecGNrMmNCpUW8hckZ9cBMyX+VCp8GjYoM6Mz/X0XSaGVz7V5gtCWjfeJI7gA==
+ dependencies:
+ async-retry "^1.2.3"
+ eventemitter2 "^5.0.1"
+ http-proxy-agent "^2.1.0"
+ https-proxy-agent "^2.2.3"
+ mkdirp "^0.5.1"
+ tar "^6.0.1"
+ tmp "^0.0.33"
+ yauzl "^2.10.0"
+
+tar@^6.0.1:
+ version "6.1.0"
+ resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.0.tgz#d1724e9bcc04b977b18d5c573b333a2207229a83"
+ integrity sha512-DUCttfhsnLCjwoDoFcI+B2iJgYa93vBnDUATYEeRx6sntCTdN01VnqsIuTlALXla/LWooNg0yEGeB+Y8WdFxGA==
+ dependencies:
+ chownr "^2.0.0"
+ fs-minipass "^2.0.0"
+ minipass "^3.0.0"
+ minizlib "^2.1.1"
+ mkdirp "^1.0.3"
+ yallist "^4.0.0"
+
+tmp@^0.0.33:
+ version "0.0.33"
+ resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"
+ integrity sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==
+ dependencies:
+ os-tmpdir "~1.0.2"
+
+vscode-extension-telemetry@0.1.0:
+ version "0.1.0"
+ resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.1.0.tgz#3cdcb61d03829966bd04b5f11471a1e40d6abaad"
+ integrity sha512-WVCnP+uLxlqB6UD98yQNV47mR5Rf79LFxpuZhSPhEf0Sb4tPZed3a63n003/dchhOwyCTCBuNN4n8XKJkLEI1Q==
+ dependencies:
+ applicationinsights "1.0.6"
+
+vscode-jsonrpc@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-4.0.0.tgz#a7bf74ef3254d0a0c272fab15c82128e378b3be9"
+ integrity sha512-perEnXQdQOJMTDFNv+UF3h1Y0z4iSiaN9jIlb0OqIYgosPCZGYh/MCUlkFtV2668PL69lRDO32hmvL2yiidUYg==
+
+vscode-languageclient@5.2.1:
+ version "5.2.1"
+ resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-5.2.1.tgz#7cfc83a294c409f58cfa2b910a8cfeaad0397193"
+ integrity sha512-7jrS/9WnV0ruqPamN1nE7qCxn0phkH5LjSgSp9h6qoJGoeAKzwKz/PF6M+iGA/aklx4GLZg1prddhEPQtuXI1Q==
+ dependencies:
+ semver "^5.5.0"
+ vscode-languageserver-protocol "3.14.1"
+
+vscode-languageserver-protocol@3.14.1:
+ version "3.14.1"
+ resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.14.1.tgz#b8aab6afae2849c84a8983d39a1cf742417afe2f"
+ integrity sha512-IL66BLb2g20uIKog5Y2dQ0IiigW0XKrvmWiOvc0yXw80z3tMEzEnHjaGAb3ENuU7MnQqgnYJ1Cl2l9RvNgDi4g==
+ dependencies:
+ vscode-jsonrpc "^4.0.0"
+ vscode-languageserver-types "3.14.0"
+
+vscode-languageserver-types@3.14.0:
+ version "3.14.0"
+ resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.14.0.tgz#d3b5952246d30e5241592b6dde8280e03942e743"
+ integrity sha512-lTmS6AlAlMHOvPQemVwo3CezxBp0sNB95KNPkqp3Nxd5VFEnuG1ByM0zlRWos0zjO3ZWtkvhal0COgiV1xIA4A==
+
+vscode-nls@^4.0.0:
+ version "4.1.2"
+ resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.1.2.tgz#ca8bf8bb82a0987b32801f9fddfdd2fb9fd3c167"
+ integrity sha512-7bOHxPsfyuCqmP+hZXscLhiHwe7CSuFE4hyhbs22xPIhQ4jv99FcR4eBzfYYVLP356HNFpdvz63FFb/xw6T4Iw==
+
+wrappy@1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
+ integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
+
+yallist@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
+ integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
+
+yauzl@^2.10.0:
+ version "2.10.0"
+ resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9"
+ integrity sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=
+ dependencies:
+ buffer-crc32 "~0.2.3"
+ fd-slicer "~1.1.0"
+
+zone.js@0.7.6:
+ version "0.7.6"
+ resolved "https://registry.yarnpkg.com/zone.js/-/zone.js-0.7.6.tgz#fbbc39d3e0261d0986f1ba06306eb3aeb0d22009"
+ integrity sha1-+7w50+AmHQmG8boGMG6zrrDSIAk=
diff --git a/src/sql/workbench/contrib/dashboard/browser/services/breadcrumb.service.ts b/src/sql/workbench/contrib/dashboard/browser/services/breadcrumb.service.ts
index bbc32f6d6d..a3335967dc 100644
--- a/src/sql/workbench/contrib/dashboard/browser/services/breadcrumb.service.ts
+++ b/src/sql/workbench/contrib/dashboard/browser/services/breadcrumb.service.ts
@@ -61,8 +61,10 @@ export class BreadcrumbService implements IBreadcrumbService {
}
private getDbBreadcrumb(profile: ConnectionProfile): MenuItem {
+ let defaultDatabaseName = profile.providerName === 'LOGANALYTICS' ? 'workspace-name' : 'database-name';
+
return {
- label: profile.databaseName ? profile.databaseName : 'database-name',
+ label: profile.databaseName ? profile.databaseName : defaultDatabaseName,
routerLink: ['database-dashboard']
};
}
diff --git a/src/sql/workbench/contrib/query/browser/queryEditor.ts b/src/sql/workbench/contrib/query/browser/queryEditor.ts
index 8f1a0c4bf1..3815861f1b 100644
--- a/src/sql/workbench/contrib/query/browser/queryEditor.ts
+++ b/src/sql/workbench/contrib/query/browser/queryEditor.ts
@@ -287,6 +287,20 @@ export class QueryEditor extends EditorPane {
{ action: this._listDatabasesAction }
];
}
+ else if (providerId === 'LOGANALYTICS' || this.modeService.getExtensions('LogAnalytics').indexOf(fileExtension) > -1) {
+ if (this.input instanceof UntitledQueryEditorInput) {
+ this.input.setMode('loganalytics');
+ }
+
+ content = [
+ { action: this._runQueryAction },
+ { action: this._cancelQueryAction },
+ { element: separator },
+ { action: this._toggleConnectDatabaseAction },
+ { action: this._changeConnectionAction },
+ { action: this._listDatabasesAction }
+ ];
+ }
else {
if (previewFeaturesEnabled) {
content = [
diff --git a/src/sql/workbench/contrib/query/browser/queryInputFactory.ts b/src/sql/workbench/contrib/query/browser/queryInputFactory.ts
index db80d47c0e..2714ad56fc 100644
--- a/src/sql/workbench/contrib/query/browser/queryInputFactory.ts
+++ b/src/sql/workbench/contrib/query/browser/queryInputFactory.ts
@@ -28,7 +28,7 @@ const editorInputFactoryRegistry = Registry.as(Edit
export class QueryEditorLanguageAssociation implements ILanguageAssociation {
static readonly isDefault = true;
- static readonly languages = ['sql', 'kusto']; //TODO Add language id here for new languages supported in query editor. Make it easier to contribute new extension's languageID
+ static readonly languages = ['sql', 'kusto', 'loganalytics']; //TODO Add language id here for new languages supported in query editor. Make it easier to contribute new extension's languageID
constructor(@IInstantiationService private readonly instantiationService: IInstantiationService,
diff --git a/src/sql/workbench/contrib/scripting/browser/scripting.contribution.ts b/src/sql/workbench/contrib/scripting/browser/scripting.contribution.ts
index bbc5483f7e..d416676252 100644
--- a/src/sql/workbench/contrib/scripting/browser/scripting.contribution.ts
+++ b/src/sql/workbench/contrib/scripting/browser/scripting.contribution.ts
@@ -101,6 +101,7 @@ MenuRegistry.appendMenuItem(MenuId.ObjectExplorerItemContext, {
},
when: ContextKeyExpr.and(
ConnectionContextKey.Provider.notEqualsTo('KUSTO'),
+ ConnectionContextKey.Provider.notEqualsTo('LOGANALYTICS'),
ContextKeyExpr.or(
TreeNodeContextKey.NodeType.isEqualTo('Table'),
TreeNodeContextKey.NodeType.isEqualTo('View')
@@ -115,10 +116,9 @@ MenuRegistry.appendMenuItem(MenuId.ObjectExplorerItemContext, {
id: commands.OE_SCRIPT_AS_SELECT_COMMAND_ID,
title: localize('scriptKustoSelect', "Take 10")
},
- when: ContextKeyExpr.and(
- ConnectionContextKey.Provider.isEqualTo('KUSTO'),
- TreeNodeContextKey.NodeType.isEqualTo('Table')
- )
+ when: ContextKeyExpr.or(
+ ContextKeyExpr.and(ConnectionContextKey.Provider.isEqualTo('KUSTO'), TreeNodeContextKey.NodeType.isEqualTo('Table')),
+ ContextKeyExpr.and(ConnectionContextKey.Provider.isEqualTo('LOGANALYTICS'), TreeNodeContextKey.NodeType.isEqualTo('Table')))
});
MenuRegistry.appendMenuItem(MenuId.ObjectExplorerItemContext, {
@@ -132,6 +132,7 @@ MenuRegistry.appendMenuItem(MenuId.ObjectExplorerItemContext, {
ContextKeyExpr.and(
TreeNodeContextKey.NodeType.isEqualTo('Table'),
ConnectionContextKey.Provider.notEqualsTo('KUSTO'),
+ ConnectionContextKey.Provider.notEqualsTo('LOGANALYTICS'),
MssqlNodeContext.EngineEdition.notEqualsTo(DatabaseEngineEdition.SqlOnDemand.toString()),
MssqlNodeContext.EngineEdition.notEqualsTo(DatabaseEngineEdition.SqlDataWarehouse.toString())
)
@@ -147,6 +148,7 @@ MenuRegistry.appendMenuItem(MenuId.ObjectExplorerItemContext, {
when:
ContextKeyExpr.and(
ConnectionContextKey.Provider.notEqualsTo('KUSTO'),
+ ConnectionContextKey.Provider.notEqualsTo('LOGANALYTICS'),
ContextKeyExpr.or(
TreeNodeContextKey.NodeType.isEqualTo('Table'),
TreeNodeContextKey.NodeType.isEqualTo('View'),
@@ -218,6 +220,7 @@ MenuRegistry.appendMenuItem(MenuId.ObjectExplorerItemContext, {
when:
ContextKeyExpr.and(
ConnectionContextKey.Provider.notEqualsTo('KUSTO'),
+ ConnectionContextKey.Provider.notEqualsTo('LOGANALYTICS'),
ContextKeyExpr.or(
TreeNodeContextKey.NodeType.isEqualTo(NodeType.Table),
TreeNodeContextKey.NodeType.isEqualTo(NodeType.View),
diff --git a/src/sql/workbench/services/queryEditor/browser/queryEditorService.ts b/src/sql/workbench/services/queryEditor/browser/queryEditorService.ts
index 08012fd4fd..5f7213ae25 100644
--- a/src/sql/workbench/services/queryEditor/browser/queryEditorService.ts
+++ b/src/sql/workbench/services/queryEditor/browser/queryEditorService.ts
@@ -101,8 +101,8 @@ export class QueryEditorService implements IQueryEditorService {
////// Private functions
private createUntitledSqlFilePath(providerName?: string): Promise {
- if (providerName === 'KUSTO') {
- return this.createPrefixedSqlFilePath(providerName + 'Query');
+ if (providerName === 'LOGANALYTICS' || providerName === 'KUSTO') {
+ return this.createPrefixedSqlFilePath('KQLQuery');
}
return this.createPrefixedSqlFilePath('SQLQuery');