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
This commit is contained in:
Justin M
2021-07-06 15:27:19 -07:00
committed by GitHub
parent 9ac1f16cea
commit 48b7d96999
65 changed files with 3788 additions and 10 deletions

View File

@@ -251,6 +251,7 @@ const externalExtensions = [
'asde-deployment',
'azdata',
'azurehybridtoolkit',
'azuremonitor',
'cms',
'dacpac',
'import',

View File

@@ -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",

View File

@@ -0,0 +1,10 @@
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.16207 4.40796H5.45432V6.53124H6.16207V4.40796Z" fill="white"/>
<path d="M7.5776 5.11572H6.86984V6.53124H7.5776V5.11572Z" fill="white"/>
<path d="M8.9931 2.99243H8.28534V6.53123H8.9931V2.99243Z" fill="white"/>
<path d="M3.68494 4.42278C3.6848 3.64991 3.91385 2.89435 4.34313 2.25167C4.77241 1.60899 5.38264 1.10804 6.09664 0.812175C6.81063 0.516314 7.59633 0.438831 8.35437 0.589524C9.1124 0.740218 9.80872 1.11232 10.3553 1.65877C10.9018 2.20522 11.2741 2.90148 11.4249 3.65948C11.5757 4.41749 11.4984 5.2032 11.2026 5.91726C10.9069 6.63131 10.4061 7.24162 9.76346 7.67102C9.12086 8.10042 8.36535 8.32961 7.59248 8.32961" stroke="white" stroke-miterlimit="10"/>
<path d="M7.59248 8.32956H3.68494V4.42273" stroke="white" stroke-miterlimit="10"/>
<path d="M2.62328 6.53125H0.5V8.65453H2.62328V6.53125Z" fill="white"/>
<path d="M2.62328 9.36218H0.5V11.4855H2.62328V9.36218Z" fill="white"/>
<path d="M5.45433 9.36218H3.33105V11.4855H5.45433V9.36218Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,10 @@
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.16207 4.40796H5.45432V6.53124H6.16207V4.40796Z" fill="#323130"/>
<path d="M7.5776 5.11572H6.86984V6.53124H7.5776V5.11572Z" fill="#323130"/>
<path d="M8.9931 2.99243H8.28534V6.53123H8.9931V2.99243Z" fill="#323130"/>
<path d="M3.68494 4.42278C3.6848 3.64991 3.91385 2.89435 4.34313 2.25167C4.77241 1.60899 5.38264 1.10804 6.09664 0.812175C6.81063 0.516314 7.59633 0.438831 8.35437 0.589524C9.1124 0.740218 9.80872 1.11232 10.3553 1.65877C10.9018 2.20522 11.2741 2.90148 11.4249 3.65948C11.5757 4.41749 11.4984 5.2032 11.2026 5.91726C10.9069 6.63131 10.4061 7.24162 9.76346 7.67102C9.12086 8.10042 8.36535 8.32961 7.59248 8.32961" stroke="#323130" stroke-miterlimit="10"/>
<path d="M7.59248 8.32956H3.68494V4.42273" stroke="#323130" stroke-miterlimit="10"/>
<path d="M2.62328 6.53125H0.5V8.65453H2.62328V6.53125Z" fill="#323130"/>
<path d="M2.62328 9.36218H0.5V11.4855H2.62328V9.36218Z" fill="#323130"/>
<path d="M5.45433 9.36218H3.33105V11.4855H5.45433V9.36218Z" fill="#323130"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -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,
}
}
}

View File

@@ -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 {

View File

@@ -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<azureResource.AzureResourceDatabaseServer>,
private _extensionContext: ExtensionContext
) {
}
public getTreeDataProvider(): azureResource.IAzureResourceTreeDataProvider {
return new AzureMonitorTreeDataProvider(this._service, this._extensionContext);
}
public get providerId(): string {
return 'azure.resource.providers.azureMonitor';
}
}

View File

@@ -0,0 +1,41 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
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<AzureMonitorGraphData, azureResource.AzureResourceDatabaseServer> {
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
};
}
}

View File

@@ -0,0 +1,77 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
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<azureResource.AzureResourceDatabaseServer> {
private static readonly containerId = 'azure.resource.providers.AzureMonitorContainer';
private static readonly containerLabel = localize('azure.resource.providers.AzureMonitorContainerLabel', "Azure Monitor Workspace");
public constructor(
databaseServerService: IAzureResourceService<azureResource.AzureResourceDatabaseServer>,
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
}
};
}
}

View File

@@ -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<azurec
const arcFeaturedEnabled = vscode.workspace.getConfiguration(constants.extensionConfigSectionName).get('enableArcFeatures');
const providers: azureResource.IAzureResourceProvider[] = [
new KustoProvider(new KustoResourceService(), extensionContext),
new AzureMonitorProvider(new AzureMonitorResourceService(), extensionContext),
new AzureResourceDatabaseServerProvider(new AzureResourceDatabaseServerService(), extensionContext),
new AzureResourceDatabaseProvider(new AzureResourceDatabaseService(), extensionContext),
new SqlInstanceProvider(new SqlInstanceResourceService(), extensionContext),

1
extensions/azuremonitor/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
sqltoolsservice

View File

@@ -0,0 +1,8 @@
src/**
.gitignore
*.vsix
out/**
tsconfig.json
extension.webpack.config.js
yarn.lock
.vscode

View File

@@ -0,0 +1,41 @@
# Azure Monitor extension for Azure Data Studio (Preview)
Connect and query Azure Monitor workspaces with Azure Data Studio, a modern data editor available for Linux, MacOS, and Windows. This extension enables you to interact with Azure Monitor using Azure Data Studio features like:
- Connect to Azure Monitor.
- Searchable object explorer view for database objects.
- Query authoring and editing with Intellisense and syntax highlighting.
- Create a notebook with Azure Monitor.
- View query results and save to CSV, JSON, XML, Excel, or as a chart.
## Connect to an Azure Monitor workspace
You can connect to an Azure Monitor cluster with Azure Data Studio.
Select **New Connection** and choose **Azure Monitor Logs** for the **Connection Type**.
Once connected to a cluster, you can start writing a KQL query, run it and a view the results to save it to CSV, JSON, XML, Excel, or as a chart as shown below.
![KQL query in Azure Data Studio](https://raw.githubusercontent.com/microsoft/azuredatastudio/main/extensions/kusto/resources/images/kusto-readme-query.gif)
## Contributing to the Extension
This extension lives in the [azuredatastudio repo](https://github.com/microsoft/azuredatastudio) and follows the same guidelines for contribution. If you are interested in fixing issues and contributing directly to the code base, see the document [How to Contribute](https://github.com/Microsoft/azuredatastudio/wiki/How-to-Contribute), which covers the following:
- [How to build and run from source](https://github.com/Microsoft/azuredatastudio/wiki/How-to-Contribute#Build-and-Run-From-Source)
- [The development workflow, including debugging and running tests](https://github.com/Microsoft/azuredatastudio/wiki/How-to-Contribute#development-workflow)
- [Submitting pull requests](https://github.com/Microsoft/azuredatastudio/wiki/How-to-Contribute#pull-requests)
## Code of Conduct
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
## Privacy Statement
The [Microsoft Enterprise and Developer Privacy Statement](https://privacy.microsoft.com/privacystatement) describes the privacy statement of this software.
## License
Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the [Source EULA](https://raw.githubusercontent.com/Microsoft/azuredatastudio/main/LICENSE.txt).

View File

@@ -0,0 +1,29 @@
{
"downloadUrl": "https://github.com/Microsoft/sqltoolsservice/releases/download/v{#version#}/microsoft.sqltools.servicelayer-{#fileName#}",
"version": "3.0.0-release.110",
"downloadFileNames": {
"Windows_86": "win-x86-net5.0.zip",
"Windows_64": "win-x64-net5.0.zip",
"OSX": "osx-x64-net5.0.tar.gz",
"CentOS_7": "rhel-x64-net5.0.tar.gz",
"Debian_8": "rhel-x64-net5.0.tar.gz",
"Fedora_23": "rhel-x64-net5.0.tar.gz",
"OpenSUSE_13_2": "rhel-x64-net5.0.tar.gz",
"RHEL_7": "rhel-x64-net5.0.tar.gz",
"SLES_12_2": "rhel-x64-net5.0.tar.gz",
"Ubuntu_14": "rhel-x64-net5.0.tar.gz",
"Ubuntu_16": "rhel-x64-net5.0.tar.gz"
},
"installDirectory": "../sqltoolsservice/{#platform#}/{#version#}",
"executableFiles": [
"MicrosoftKustoServiceLayer.exe",
"MicrosoftKustoServiceLayer"
],
"retry": {
"retries": 15,
"factor": 2,
"minTimeout": 1000,
"maxTimeout": 300000,
"randomize": false
}
}

View File

@@ -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.
*--------------------------------------------------------------------------------------------*/
//@ts-check
'use strict';
const withDefaults = require('../shared.webpack.config');
module.exports = withDefaults({
context: __dirname,
entry: {
main: './src/extension.ts'
}
});

View File

@@ -0,0 +1,73 @@
// If changing this file please also submit the change in kusto-language-server language-configuration.json
{
"comments": {
// symbol used for single line comment. Remove this entry if your language does not support line comments
"lineComment": "//",
// symbols used for start and end a block comment. Remove this entry if your language does not support block comments
"blockComment": [
"/*",
"*/"
]
},
// symbols used as brackets
"brackets": [
[
"{",
"}"
],
[
"[",
"]"
],
[
"(",
")"
]
],
// symbols that are auto closed when typing
"autoClosingPairs": [
[
"{",
"}"
],
[
"[",
"]"
],
[
"(",
")"
],
[
"\"",
"\""
],
[
"'",
"'"
]
],
// symbols that that can be used to surround a selection
"surroundingPairs": [
[
"{",
"}"
],
[
"[",
"]"
],
[
"(",
")"
],
[
"\"",
"\""
],
[
"'",
"'"
]
]
}

View File

@@ -0,0 +1,212 @@
{
"name": "azuremonitor",
"description": "%azuremonitor.description%",
"version": "0.1.0",
"publisher": "Microsoft",
"aiKey": "AIF-444c3af9-8e69-4462-ab49-4191e6ad1916",
"activationEvents": [
"*"
],
"engines": {
"vscode": "*",
"azdata": ">=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"
}
}

View File

@@ -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"
}

View File

@@ -0,0 +1,10 @@
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.16207 4.40796H5.45432V6.53124H6.16207V4.40796Z" fill="white"/>
<path d="M7.5776 5.11572H6.86984V6.53124H7.5776V5.11572Z" fill="white"/>
<path d="M8.9931 2.99243H8.28534V6.53123H8.9931V2.99243Z" fill="white"/>
<path d="M3.68494 4.42278C3.6848 3.64991 3.91385 2.89435 4.34313 2.25167C4.77241 1.60899 5.38264 1.10804 6.09664 0.812175C6.81063 0.516314 7.59633 0.438831 8.35437 0.589524C9.1124 0.740218 9.80872 1.11232 10.3553 1.65877C10.9018 2.20522 11.2741 2.90148 11.4249 3.65948C11.5757 4.41749 11.4984 5.2032 11.2026 5.91726C10.9069 6.63131 10.4061 7.24162 9.76346 7.67102C9.12086 8.10042 8.36535 8.32961 7.59248 8.32961" stroke="white" stroke-miterlimit="10"/>
<path d="M7.59248 8.32956H3.68494V4.42273" stroke="white" stroke-miterlimit="10"/>
<path d="M2.62328 6.53125H0.5V8.65453H2.62328V6.53125Z" fill="white"/>
<path d="M2.62328 9.36218H0.5V11.4855H2.62328V9.36218Z" fill="white"/>
<path d="M5.45433 9.36218H3.33105V11.4855H5.45433V9.36218Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,10 @@
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6.16207 4.40796H5.45432V6.53124H6.16207V4.40796Z" fill="#323130"/>
<path d="M7.5776 5.11572H6.86984V6.53124H7.5776V5.11572Z" fill="#323130"/>
<path d="M8.9931 2.99243H8.28534V6.53123H8.9931V2.99243Z" fill="#323130"/>
<path d="M3.68494 4.42278C3.6848 3.64991 3.91385 2.89435 4.34313 2.25167C4.77241 1.60899 5.38264 1.10804 6.09664 0.812175C6.81063 0.516314 7.59633 0.438831 8.35437 0.589524C9.1124 0.740218 9.80872 1.11232 10.3553 1.65877C10.9018 2.20522 11.2741 2.90148 11.4249 3.65948C11.5757 4.41749 11.4984 5.2032 11.2026 5.91726C10.9069 6.63131 10.4061 7.24162 9.76346 7.67102C9.12086 8.10042 8.36535 8.32961 7.59248 8.32961" stroke="#323130" stroke-miterlimit="10"/>
<path d="M7.59248 8.32956H3.68494V4.42273" stroke="#323130" stroke-miterlimit="10"/>
<path d="M2.62328 6.53125H0.5V8.65453H2.62328V6.53125Z" fill="#323130"/>
<path d="M2.62328 9.36218H0.5V11.4855H2.62328V9.36218Z" fill="#323130"/>
<path d="M5.45433 9.36218H3.33105V11.4855H5.45433V9.36218Z" fill="#323130"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,34 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as vscode from 'vscode';
/**
* Global context for the application
*/
export class AppContext {
private serviceMap: Map<string, any> = new Map();
constructor(public readonly extensionContext: vscode.ExtensionContext) { }
public getService<T>(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<T>(serviceName: string, service: T): void {
if (this.serviceMap.has(serviceName)) {
console.warn('Multiple services ', serviceName, ' registered!');
} else {
this.serviceMap.set(serviceName, service);
}
}
}

View File

@@ -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<T extends ITreeNode>(objectExplorerContext: azdata.ObjectExplorerContext): Thenable<T>;
}
/**
* A tree node in the object explorer tree
*/
export interface ITreeNode {
getNodeInfo(): azdata.NodeInfo;
getChildren(refreshChildren: boolean): ITreeNode[] | Thenable<ITreeNode[]>;
}

View File

@@ -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<AzureMonitorObjectExplorerNodeProvider>(constants.ObjectExplorerService);
return <any>oeProvider?.findSqlClusterNodeByContext(explorerContext);
}
};
}
};
}

View File

@@ -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<SqlOpsDataClient> {
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<string> {
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<string>('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 {
}
}

View File

@@ -0,0 +1,25 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
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';

View File

@@ -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<vscode.Disposable>();
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());
}
}

View File

@@ -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<IExtension | undefined> {
// 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<void> {
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<void> {
// 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<void> {
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 {
}

View File

@@ -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<contracts.RequestSecurityTokenResponse | undefined> => {
return this.getToken(request);
});
}
protected async getToken(request: contracts.RequestSecurityTokenParams): Promise<contracts.RequestSecurityTokenResponse | undefined> {
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;
}
}

View File

@@ -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<TelemetryParams, void>('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<RequestSecurityTokenParams, RequestSecurityTokenResponse, void, void>('account/securityTokenRequest');
}
// ------------------------------- </ Security Token Request > ------------------------------------------
// ------------------------------- <Serialization> -----------------------------
export namespace SerializeDataStartRequest {
export const type = new RequestType<azdata.SerializeDataStartRequestParams, azdata.SerializeDataResult, void, void>('serialize/start');
}
export namespace SerializeDataContinueRequest {
export const type = new RequestType<azdata.SerializeDataContinueRequestParams, azdata.SerializeDataResult, void, void>('serialize/continue');
}
// ------------------------------- <Serialization> -----------------------------
// ------------------------------- < Load Completion Extension Request > ------------------------------------
/**
* Completion extension load parameters
*/
export class CompletionExtensionParams {
/// <summary>
/// Absolute path for the assembly containing the completion extension
/// </summary>
public assemblyPath?: string;
/// <summary>
/// The type name for the completion extension
/// </summary>
public typeName?: string;
/// <summary>
/// Property bag for initializing the completion extension
/// </summary>
public properties?: {};
}
export namespace CompletionExtLoadRequest {
export const type = new RequestType<CompletionExtensionParams, boolean, void, void>('completion/extLoad');
}

View File

@@ -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<undefined> {
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<azdata.SerializeDataResult> => {
return client.sendRequest(contracts.SerializeDataStartRequest.type, requestParams).then(
r => {
return r;
},
e => {
client.logFailedRequest(contracts.SerializeDataStartRequest.type, e);
return Promise.resolve(<azdata.SerializeDataResult>{
succeeded: false,
messages: Utils.getErrorMessage(e)
});
}
);
};
let continueSerialization = (requestParams: azdata.SerializeDataContinueRequestParams): Thenable<azdata.SerializeDataResult> => {
return client.sendRequest(contracts.SerializeDataContinueRequest.type, requestParams).then(
r => {
return r;
},
e => {
client.logFailedRequest(contracts.SerializeDataContinueRequest.type, e);
return Promise.resolve(<azdata.SerializeDataResult>{
succeeded: false,
messages: Utils.getErrorMessage(e)
});
}
);
};
return azdata.dataprotocol.registerSerializationProvider({
providerId: client.providerId,
startSerialization,
continueSerialization
});
}
}

View File

@@ -0,0 +1,25 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
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);
});
}
}

View File

@@ -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<string | undefined> {
let iconName: string | undefined;
if (connection.providerName === this.providerId) {
iconName = cloudIcon;
}
return Promise.resolve(iconName);
}
}

View File

@@ -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';

View File

@@ -0,0 +1,25 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
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();
}
}
}

View File

@@ -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<any> {
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<void>,
label: string,
isCancelable: boolean = false,
onCanceled?: () => void
): Promise<void> {
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<boolean> {
return (await this.prompter.promptSingle<boolean>(<IQuestion>{
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');
});
}

View File

@@ -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;
}
}

View File

@@ -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<azdata.ObjectExplorerExpandInfo>();
constructor(private appContext: AppContext) {
super();
this.appContext.registerService<AzureMonitorObjectExplorerNodeProvider>(constants.ObjectExplorerService, this);
}
handleSessionOpen(session: azdata.ObjectExplorerSession): Thenable<boolean> {
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<boolean> {
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<boolean> {
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<boolean> {
let response = {
sessionId: nodeInfo.sessionId!,
nodePath: nodeInfo.nodePath!,
errorMessage: undefined,
nodes: []
};
this.expandCompleteEmitter.fire(response);
return true;
}
refreshNode(nodeInfo: azdata.ExpandNodeInfo): Thenable<boolean> {
return this.expandNode(nodeInfo, true);
}
handleSessionClose(_closeSessionInfo: azdata.ObjectExplorerCloseSessionInfo): void {
}
findNodes(_findNodesInfo: azdata.FindNodesInfo): Thenable<azdata.ObjectExplorerFindNodesResponse> {
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<void> {
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<T extends TreeNode>(context: ICommandObjectExplorerContext | azdata.ObjectExplorerContext): Promise<T | undefined> {
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 = <T><any>session.rootNode;
} else {
// Find the node under the session
node = <T><any>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<TreeNode[]> {
if (refreshChildren || !this._children) {
return this.refreshChildren();
}
return this._children;
}
private async refreshChildren(): Promise<TreeNode[]> {
this._children = [];
return this._children;
}
getTreeItem(): vscode.TreeItem | Promise<vscode.TreeItem> {
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;
}
}

View File

@@ -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;
}

View File

@@ -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<TreeNode | undefined> {
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<TreeNode | undefined> {
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<TreeNode[]>;
abstract getTreeItem(): vscode.TreeItem | Promise<vscode.TreeItem>;
abstract getNodeInfo(): azdata.NodeInfo;
}

View File

@@ -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<ITreeNode[]>;
}

View File

@@ -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<T>(question: IQuestion, ignoreFocusOut?: boolean): Promise<T | undefined> {
let questions: IQuestion[] = [question];
const answers = await this.prompt<T>(questions, ignoreFocusOut);
if (answers) {
let response: T = answers[question.name];
return response || undefined;
}
return undefined;
}
public async prompt<T>(questions: IQuestion[], ignoreFocusOut?: boolean): Promise<Answers<T> | undefined> {
// Collapse multiple questions into a set of prompt steps
const promptResult = new Promise<Answers<T>>((resolve) => {
let answers: Answers<T> = {};
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);
}
});
}
}

View File

@@ -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;
}, []);
});
}
}

View File

@@ -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;
});
}
}

View File

@@ -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 {
}

View File

@@ -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;
}
}

View File

@@ -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}`);
}
}
}

View File

@@ -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;
});
}
}

View File

@@ -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];
});
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;

View File

@@ -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<vscode.QuickPickItem | INameValueChoice>;
// 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<T> = { [key: string]: T };
export interface IPrompter {
promptSingle<T>(question: IQuestion, ignoreFocusOut?: boolean): Promise<T | undefined>;
/**
* Prompts for multiple questions
*
* @returns Map of question IDs to results, or undefined if
* the user canceled the question session
*/
prompt<T>(questions: IQuestion[], ignoreFocusOut?: boolean): Promise<Answers<T> | undefined>;
promptCallback(questions: IQuestion[], callback: IPromptCallback): void;
}
export interface IPromptCallback {
(answers: { [id: string]: any }): void;
}

View File

@@ -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<boolean>('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();

View File

@@ -0,0 +1,14 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
/**
* @returns whether the provided parameter is undefined or null.
*/
export function isUndefinedOrNull(obj: any): boolean {
// Intentional ==
// eslint-disable-next-line eqeqeq
return obj == undefined;
}

View File

@@ -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;
}

View File

@@ -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.
*--------------------------------------------------------------------------------------------*/
/// <reference path='../../../../src/sql/azdata.d.ts'/>
/// <reference path='../../../../src/sql/azdata.proposed.d.ts'/>
/// <reference path='../../../../src/vs/vscode.d.ts'/>

View File

@@ -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<boolean> {
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<boolean> {
try {
await fs.access(path);
return true;
} catch (e) {
return false;
}
}

View File

@@ -0,0 +1,449 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>scopeName</key>
<string>source.kusto</string>
<key>fileTypes</key>
<array>
<string>kql</string>
<string>kusto</string>
<string>csl</string>
</array>
<key>name</key>
<string>kusto file</string>
<key>patterns</key>
<!-- Kusto Query Language
- https://docs.microsoft.com/en-us/azure/kusto/query/
-->
<array>
<dict>
<!-- Query statements
- https://docs.microsoft.com/en-us/azure/kusto/query/statements
-->
<key>match</key>
<string>\b(let|set|alias|declare|pattern|restrict|access|to|set)\b</string>
<key>name</key>
<string>keyword.control.kusto</string>
</dict>
<dict>
<!-- Special functions
- https://docs.microsoft.com/en-us/azure/kusto/query/clusterfunction
-->
<key>match</key>
<string>\b(cluster|database|materialize|table|toscalar)(?=\W*\()</string>
<key>name</key>
<string>support.function</string>
</dict>
<dict>
<!-- Scalar operators
- https://docs.microsoft.com/en-us/azure/kusto/query/binoperators
-->
<key>match</key>
<string>\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</string>
<key>name</key>
<string>keyword.operator.kusto</string>
</dict>
<!-- Scalar functions
- https://docs.microsoft.com/en-us/azure/kusto/query/scalarfunctions
-->
<dict>
<!-- Scalar function: Binary Functions
- https://docs.microsoft.com/en-us/azure/kusto/query/scalarfunctions#binary-functions
-->
<key>match</key>
<string>\b(binary_and|binary_not|binary_or|binary_shift_left|binary_shift_right|binary_xor)(?=\W*\()</string>
<key>name</key>
<string>support.function</string>
</dict>
<dict>
<!-- Scalar function: Conversion Functions
- https://docs.microsoft.com/en-us/azure/kusto/query/scalarfunctions#binary-functions
-->
<key>match</key>
<string>\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*\()</string>
<key>name</key>
<string>support.function</string>
</dict>
<dict>
<!-- timespan literals
- https://docs.microsoft.com/en-us/azure/kusto/query/scalar-data-types/timespan#timespan-literals
-->
<key>match</key>
<string>(?<=\.\d|\d|\d\W)(d|h|m|s|ms|microsecond|tick|seconds)\b</string>
<key>name</key>
<string>variable.language</string>
</dict>
<dict>
<!-- Scalar function: DateTime/Timespan Functions
- https://docs.microsoft.com/en-us/azure/kusto/query/scalarfunctions#datetimetimespan-functions
-->
<key>match</key>
<string>\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*\()</string>
<key>name</key>
<string>support.function</string>
</dict>
<dict>
<!-- Scalar function: Dynamic/Array Functions
- https://docs.microsoft.com/en-us/azure/kusto/query/scalarfunctions#dynamicarray-functions
-->
<key>match</key>
<string>\b(array_concat|array_length|array_slice|array_split|array_strcat|bag_keys|pack|pack_all|pack_array|repeat|treepath|zip)(?=\W*\()</string>
<key>name</key>
<string>support.function</string>
</dict>
<dict>
<!-- Scalar function: Window Scalar Functions
- https://docs.microsoft.com/en-us/azure/kusto/query/scalarfunctions#window-scalar-functions
-->
<key>match</key>
<string>\b(next|prev|row_cumsum|row_number)(?=\W*\()</string>
<key>name</key>
<string>support.function</string>
</dict>
<dict>
<!-- Scalar function: Flow Control Functions
- https://docs.microsoft.com/en-us/azure/kusto/query/scalarfunctions#flow-control-functions
-->
<key>match</key>
<string>\b(toscalar)(?=\W*\()</string>
<key>name</key>
<string>support.function</string>
</dict>
<dict>
<!-- Scalar function: Mathematical Functions
- https://docs.microsoft.com/en-us/azure/kusto/query/scalarfunctions#mathematical-functions
-->
<key>match</key>
<string>\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*\()</string>
<key>name</key>
<string>support.function</string>
</dict>
<dict>
<!-- Scalar function: Metadata Functions
- https://docs.microsoft.com/en-us/azure/kusto/query/scalarfunctions#metadata-functions
-->
<key>match</key>
<string>\b(column_ifexists|current_principal|cursor_after|extent_id|extent_tags|ingestion_time)(?=\W*\()</string>
<key>name</key>
<string>support.function</string>
</dict>
<dict>
<!-- Scalar function: Rounding Functions
- https://docs.microsoft.com/en-us/azure/kusto/query/scalarfunctions#rounding-functions
-->
<key>match</key>
<string>\b(bin|bin_at|ceiling|floor)(?=\W*\()</string>
<key>name</key>
<string>support.function</string>
</dict>
<dict>
<!-- Scalar function: Conditional Functions
- https://docs.microsoft.com/en-us/azure/kusto/query/scalarfunctions#conditional-functions
-->
<key>match</key>
<string>\b(case|coalesce|iif|iff|max_of|min_of)(?=\W*\()</string>
<key>name</key>
<string>support.function</string>
</dict>
<dict>
<!-- Scalar function: Series Element-wise Functions
- https://docs.microsoft.com/en-us/azure/kusto/query/scalarfunctions#series-element-wise-functions
-->
<key>match</key>
<string>\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*\()</string>
<key>name</key>
<string>support.function</string>
</dict>
<dict>
<!-- Scalar function: Series Processing Functions
- https://docs.microsoft.com/en-us/azure/kusto/query/scalarfunctions#series-processing-functions
-->
<key>match</key>
<string>\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*\()</string>
<key>name</key>
<string>support.function</string>
</dict>
<dict>
<!-- Scalar function: String Functions
- https://docs.microsoft.com/en-us/azure/kusto/query/scalarfunctions#string-functions
-->
<key>match</key>
<string>\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*\()</string>
<key>name</key>
<string>support.function</string>
</dict>
<dict>
<!-- Scalar function: Type Functions
- https://docs.microsoft.com/en-us/azure/kusto/query/scalarfunctions#type-functions
-->
<key>match</key>
<string>\b(gettype)(?=\W*\()</string>
<key>name</key>
<string>support.function</string>
</dict>
<dict>
<!-- Scalar function: Scalar Aggregation Functions
- https://docs.microsoft.com/en-us/azure/kusto/query/scalarfunctions#scalar-aggregation-functions
-->
<key>match</key>
<string>\b(dcount_hll|hll_merge|percentile_tdigest|percentrank_tdigest|rank_tdigest|tdigest_merge)(?=\W*\()</string>
<key>name</key>
<string>support.function</string>
</dict>
<dict>
<!-- Aggregation functions
- https://docs.microsoft.com/en-us/azure/kusto/query/any-aggfunction
-->
<key>match</key>
<string>\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*\()</string>
<key>name</key>
<string>support.function</string>
</dict>
<dict>
<!-- Window functions
- https://docs.microsoft.com/en-us/azure/kusto/query/windowsfunctions
-->
<key>match</key>
<string>\b(next|prev|row_cumsum|row_number)(?=\W*\()</string>
<key>name</key>
<string>support.function</string>
</dict>
<dict>
<!-- User Analytics (evaluate plugins)
- https://docs.microsoft.com/en-us/azure/kusto/query/useranalytics
-->
<key>match</key>
<string>\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*\()</string>
<key>name</key>
<string>support.function</string>
</dict>
<dict>
<!-- User-defined functions
- https://docs.microsoft.com/en-us/azure/kusto/query/functions/user-defined-functions
-->
<key>match</key>
<string>\.create-or-alter</string>
<key>name</key>
<string>keyword.control.kusto</string>
</dict>
<dict>
<!-- User-defined functions
- https://docs.microsoft.com/en-us/azure/kusto/query/functions/user-defined-functions
-->
<key>match</key>
<string>(?<=let ).+(?=\W*=)</string>
<key>name</key>
<string>entity.function.name.lambda.kusto</string>
</dict>
<dict>
<!-- User-defined functions
- https://docs.microsoft.com/en-us/azure/kusto/query/functions/user-defined-functions
-->
<key>match</key>
<string>\b(with|folder|docstring|skipvalidation)\b</string>
<key>name</key>
<string>keyword.operator.kusto</string>
</dict>
<dict>
<key>match</key>
<string>\b(function)\b</string>
<key>name</key>
<string>variable.language</string>
</dict>
<dict>
<!-- Data types
- https://docs.microsoft.com/en-us/azure/kusto/query/scalar-data-types/
-->
<key>match</key>
<string>\b(bool|datetime|decimal|dynamic|guid|int|long|real|string|timespan)\b</string>
<key>name</key>
<string>storage.type</string>
</dict>
<dict>
<key>match</key>
<string>\b(datatable)(?=\W*\()</string>
<key>name</key>
<string>support.function</string>
</dict>
<!-- Tabular operators
- https://docs.microsoft.com/en-us/azure/kusto/query/statements
-->
<dict>
<key>match</key>
<string>\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</string>
<key>name</key>
<string>keyword.operator.special.kusto</string>
</dict>
<dict>
<!-- Tabular operators: evalute (plugins)
- https://docs.microsoft.com/en-us/azure/kusto/query/evaluateoperator
-->
<key>match</key>
<string>\b(autocluster|bag_unpack|basket|dcount_intersect|diffpatterns|narrow|pivot|preview|rolling_percentile|sql_request)(?=\W*\()</string>
<key>name</key>
<string>support.function</string>
</dict>
<dict>
<!-- Tabular operators: join
- https://docs.microsoft.com/en-us/azure/kusto/query/joinoperator
- TODO $left, $right
-->
<key>match</key>
<string>\b(on|kind|hint\.remote|hint\.strategy)\b</string>
<key>name</key>
<string>keyword.operator.kusto</string>
</dict>
<dict>
<!-- Tabular operators: join ($left, $right)
- https://docs.microsoft.com/en-us/azure/kusto/query/joinoperator
-->
<key>match</key>
<string>(\$left|\$right)\b</string>
<key>name</key>
<string>keyword.other.kusto</string>
</dict>
<dict>
<!-- Tabular operators: join (kinds, strategies)
- https://docs.microsoft.com/en-us/azure/kusto/query/joinoperator
-->
<key>match</key>
<string>\b(innerunique|inner|leftouter|rightouter|fullouter|leftanti|anti|leftantisemi|rightanti|rightantisemi|leftsemi|rightsemi|shuffle|broadcast)\b</string>
<key>name</key>
<string>keyword.other.kusto</string>
</dict>
<dict>
<!-- Tabular operators: make-series (series analysis functions)
- https://docs.microsoft.com/en-us/azure/kusto/query/make-seriesoperator
-->
<key>match</key>
<string>\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*\()</string>
<key>name</key>
<string>support.function</string>
</dict>
<dict>
<!-- Tabular operators: make-series (series interpolation functions)
- https://docs.microsoft.com/en-us/azure/kusto/query/make-seriesoperator
-->
<key>match</key>
<string>\b(series_fill_backward|series_fill_const|series_fill_forward|series_fill_linear)(?=\W*\()</string>
<key>name</key>
<string>support.function</string>
</dict>
<dict>
<!-- Tabular operators: mv-expand (bagexpand options)
- https://docs.microsoft.com/en-us/azure/kusto/query/mvexpandoperator
-->
<key>match</key>
<string>\b(bag|array)\b</string>
<key>name</key>
<string>keyword.operator.kusto</string>
</dict>
<dict>
<!-- Tabular operators: order
- https://docs.microsoft.com/en-us/azure/kusto/query/orderoperator
-->
<key>match</key>
<string>\b(asc|desc|nulls first|nulls last)\b</string>
<key>name</key>
<string>keyword.other.kusto</string>
</dict>
<dict>
<!-- Tabular operators: parse
- https://docs.microsoft.com/en-us/azure/kusto/query/parseoperator
-->
<key>match</key>
<string>\b(regex|simple|relaxed)\b</string>
<key>name</key>
<string>keyword.other.kusto</string>
</dict>
<dict>
<!-- Tabular operators: render
- https://docs.microsoft.com/en-us/azure/kusto/query/renderoperator
-
- TODO Properties (accumulate ... anomalycolumns)
- TODO visualization kinds (areachart ... columnchart)
- TODO ysplit
-->
<key>match</key>
<string>\b(anomalychart|areachart|barchart|columnchart|ladderchart|linechart|piechart|pivotchart|scatterchart|stackedareachart|table|timechart|timepivot)\b</string>
<key>name</key>
<string>support.function</string>
</dict>
<dict>
<!-- Tabular operators: common helper operators
- https://docs.microsoft.com/en-us/azure/kusto/query/evaluateoperator
-->
<key>match</key>
<string>\b(by|from|in|of|to|step|with)\b</string>
<key>name</key>
<string>keyword.operator.word</string>
</dict>
<dict>
<!-- Strings
-->
<key>match</key>
<string>".*?"</string>
<key>name</key>
<string>string.quoted.double</string>
</dict>
<dict>
<key>match</key>
<string>\{.*?\}</string>
<key>name</key>
<string>string.variable</string>
</dict>
<dict>
<key>match</key>
<string>'.*?'</string>
<key>name</key>
<string>string.quoted.single</string>
</dict>
<dict>
<!-- Comments
-->
<key>match</key>
<string>//.*</string>
<key>name</key>
<string>comment.line</string>
</dict>
<dict>
<key>match</key>
<string>\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)</string>
<key>name</key>
<string>constant.numeric</string>
</dict>
<dict>
<key>match</key>
<string>\b(true|false)\b</string>
<key>name</key>
<string>constant.language</string>
</dict>
<dict>
<!-- Deprecated functions
-->
<key>match</key>
<string>\b(array_strcat|make_dictionary|makelist|makeset|mvexpand|todynamic)(?=\W*\(|\b)</string>
<key>name</key>
<string>invalid.deprecated</string>
</dict>
<dict>
<!-- Illegal keywords
-->
<key>match</key>
<string></string>
<key>name</key>
<string>invalid.illegal</string>
</dict>
</array>
<key>uuid</key>
<string>FF0550E0-3A29-11E3-AA6E-0800200C9B77</string>
</dict>
</plist>

View File

@@ -0,0 +1,12 @@
{
"extends": "../tsconfig.base.json",
"compilerOptions": {
"outDir": "./out",
"strict": true,
"noUnusedParameters": true,
"noImplicitAny": true
},
"include": [
"src/**/*"
]
}

View File

@@ -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=

View File

@@ -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']
};
}

View File

@@ -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 = [

View File

@@ -28,7 +28,7 @@ const editorInputFactoryRegistry = Registry.as<IEditorInputFactoryRegistry>(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,

View File

@@ -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),

View File

@@ -101,8 +101,8 @@ export class QueryEditorService implements IQueryEditorService {
////// Private functions
private createUntitledSqlFilePath(providerName?: string): Promise<string> {
if (providerName === 'KUSTO') {
return this.createPrefixedSqlFilePath(providerName + 'Query');
if (providerName === 'LOGANALYTICS' || providerName === 'KUSTO') {
return this.createPrefixedSqlFilePath('KQLQuery');
}
return this.createPrefixedSqlFilePath('SQLQuery');