Compare commits

..

24 Commits

Author SHA1 Message Date
Karl Burtram
cccb9327c7 Bump version for Oct Hotfix (#7541) 2019-10-07 11:55:29 -07:00
Chris LaFreniere
a0db32a688 Notebooks: Ensure quotes and backslashes are escaped properly in text editor model (#7497) (#7540)
* Merge conflicts

* Conflicts due to imports moving around
2019-10-07 11:54:29 -07:00
Charles Gagnon
f0b6180e15 Fix (#7434)
* Fix query history icons

* Capitalize description text
2019-10-01 10:07:27 -07:00
Karl Burtram
cd4660abaa Bump Import extension to 0.11.0 for Oct 2019-09-30 12:43:12 -07:00
Karl Burtram
8320686170 Bump agent extension to 0.43.0 for Oct 2019-09-30 12:42:12 -07:00
Charles Gagnon
0a6779f96e Add toggle query history capture command/action (#7427)
* Add toggle query history capture command/action

* Add extension updates
2019-09-30 12:03:15 -07:00
Charles Gagnon
3e17618056 Add Clear All Query History command/action (#7408)
* Add clear all query history action/command

* Fix display issue when clearing

* Change localize ID and fix registration
2019-09-30 12:01:53 -07:00
Charles Gagnon
7d27d2f4f4 Fix endpoint text overflowing and add title tooltip (#7390)
* Fix endpoint text overflowing and add title tooltip

* Remove unneeded string interning
2019-09-27 12:43:53 -07:00
Chris LaFreniere
30ae023d73 Stop clearing out connecting and connected state when cancelling out of connection dialog (#7254)
* Stop clearing out connecting and connected state

* tweaks

* Handling cancel while connecting

* fix typo

* PR comments
2019-09-27 12:36:02 -07:00
Chris LaFreniere
c36596d3b3 Notebook Tokenization Fixes (#7375)
* Fix don't like; unclear if grammar necessssary too

* Cleanup and sanity check

* Cleanup and sanity check

* Add test

* Call onBeforeAttached for 3 types of editor models
2019-09-27 12:35:53 -07:00
Anthony Dresser
afbe004c4f add sql folding (#7270) 2019-09-27 12:35:39 -07:00
Chris LaFreniere
539f2b2b2e Add Default File Type when Saving Chart (#7235)
* add file filter

* Code cleanup
2019-09-27 12:35:28 -07:00
Charles Gagnon
43e360165f Add enable logs setting for Flat File Import (#7342)
* Add config for enabling Flat File Import logging

* Move logs to default log location for extensions

* Add localized strings
2019-09-27 12:35:20 -07:00
Amir Omidi
c2727264ad EoL chars (#7225) 2019-09-27 12:35:11 -07:00
Karl Burtram
3c17ac1333 Activate XML features when SQL loads (#7228) 2019-09-27 12:35:03 -07:00
Charles Gagnon
218dad1b17 Update whoIsActive extension to use azdata (#7287)
* Update whoIsActive extension to use azdata

* Change path

* Update package-lock
2019-09-27 12:34:54 -07:00
jamesrod817
2ff4b51fe6 Tempdb (#7022)
* Server changes by James

* tempdb
2019-09-27 12:34:34 -07:00
Amir Omidi
423ad40210 Better cell selection (#6914)
* Better cell selection

* Explicit return type and undefined assignment

* More complex copy/paste

* Get TS to be less mad at me

* Remove EoL

* Fail safe if statement

* strict null check
2019-09-27 12:34:15 -07:00
Charles Gagnon
788103b2d3 Update query history README (#7164)
* Update query history README

* Fix typos
2019-09-27 12:34:07 -07:00
Charles Gagnon
c4ce709dfb Query History feature (#6579)
* Initial commit

* Fix up QueryEventType

* Making query history visible in view and open query command (#6479)

* Add QueryInfo to query event events

* Pull actual query text/connection info for displaying

* cons and expand (#6489)

* Making query history visible in view and open query command

* expand and icons

* Failure icon enabled (#6491)

* Making query history visible in view and open query command

* expand and icons

* failure icon enabled

* Minor cleanup

* Open query with connection and add run query (#6496)

* Add initial query-history extension

* Fix issues caused by master merge, cleanup and add query-history extension (#6567)

* Open query with connection and add run query

* Fix issues caused by latest master merges, cleanup and add query-history extension

* Remove child nodes (#6568)

* Open query with connection and add run query

* Fix issues caused by latest master merges, cleanup and add query-history extension

* Remove child node expansion

* Layering movement and add delete action (#6574)

* Open query with connection and add run query

* Fix issues caused by latest master merges, cleanup and add query-history extension

* Remove child node expansion

* Some layering movement and add delete action

* Move query tracking into service (#6578)

* Open query with connection and add run query

* Fix issues caused by latest master merges, cleanup and add query-history extension

* Remove child node expansion

* Some layering movement and add delete action

* Move query history tracking into service

* Add comment

* Fix actions

* Remove unnecessary type

* cleanup

* Remove unused section of README

* Fix merge issues and address PR comments

* Fix compile and tslint errors

* Change startup function name
2019-09-27 12:33:57 -07:00
Karl Burtram
e875e4a271 Bump to SQL Tools 2.0.0.17 for Notebook Agent fixes 2019-09-27 12:32:18 -07:00
Aasim Khan
ba99be6ec1 Added opening latest notebook run to context menu from notebooks pane (#7066)
* added agent notebooks, notebook history view and view materialized notebook button

* Got a basic UI running for viewing notebook history

* made some changes to make UI look good

* Added new notebook dialog

* Added new notebook Dialog

* Added create notebook dialog

* Added edit and delete notebook job

* Added some notebook history features

* Added new notebook job icons, fixed a minor bug
in openmaterializednotebookAPI and added fixed the
schedule Picker API.

* Fixed Bugs in Notebook Grid expansion

* Fixed Notebook table highlighting and
grid generation is done using code.

* fixed some UI bugs

* Added changes to reflect sqltoolservice api

* Fixed some localize keys

* Made changes in the PR and added
ability to open Template Notebooks from
notebook history view.

* Added pin and renaming to notebook history

* made some library calls async

* fixed an import bug caused by merging from master

* Validation in NotebookJobDialog

* Added entry points for scheduling notebooks
on file explorer and notebook editor

* Handled no active connections and
a small bug in collapsing grid

* fix a bug in scheduling notebook from explorer
and toolbar

* setting up agent providers from connection now

* changed modals

* Reupload edited template

* Add dialog info, solved an edit bug and localized
UI strings.

* Bug fixes in UI, notebook renaming and
editing template on fly.

* fixed a bug that failed editing notebook jobs from notebook jobs table

* Fixed a cyclic dependency, made strings const and
some other changes in the PR

* Made some cyclic dependency and some fixes from PR

* made some changes mentioned in the PR

* Changed storage database health text

* Changed the sqltoolservice version to the point to the latest build.

* Added open Latest notebook notebook run to notebooks view context menu

* Fixed a small compilation error

* fixed a spelling mistake in function name

* made changes mentioned in the PR added open Notebook Functionality to charts

* Changed some context menues strings and order

* made some changes from the PR and fixed an API call

* made some changes mentioned in the PR

* Changed sqltoolsservice version to point to the latest build
2019-09-27 11:28:19 -07:00
Karl Burtram
146aa48c51 Update SQL Tools to 2.0.0-release.16 for IntelliSense fix 2019-09-25 12:56:43 -07:00
Karl Burtram
60f82785c2 Bump package.json for October release 2019-09-11 15:33:55 -07:00
117 changed files with 15240 additions and 7296 deletions

View File

@@ -65,6 +65,7 @@ const indentationFilter = [
// except multiple specific files
'!**/package.json',
'!**/package-lock.json', // {{SQL CARBON EDIT}}
'!**/yarn.lock',
'!**/yarn-error.log',
@@ -158,6 +159,7 @@ const copyrightFilter = [
'!extensions/notebook/src/prompts/**',
'!extensions/mssql/src/prompts/**',
'!extensions/notebook/resources/jupyter_config/**',
'!extensions/query-history/images/**',
'!**/*.gif',
'!**/*.xlf',
'!**/*.dacpac',

View File

@@ -199,7 +199,8 @@ const sqlBuiltInExtensions = [
'admin-pack',
'dacpac',
'schema-compare',
'cms'
'cms',
'query-history'
];
// make resource deployment and BDC extension only available in insiders
if (process.env['VSCODE_QUALITY'] === 'stable') {

View File

@@ -235,7 +235,8 @@ const sqlBuiltInExtensions = [
'admin-pack',
'dacpac',
'schema-compare',
'cms'
'cms',
'query-history'
];
// make resource deployment and BDC extension only available in insiders

View File

@@ -1,93 +1,93 @@
{
"name": "agent",
"displayName": "SQL Server Agent",
"description": "Manage and troubleshoot SQL Server Agent jobs",
"version": "0.42.0",
"publisher": "Microsoft",
"preview": true,
"license": "https://raw.githubusercontent.com/Microsoft/azuredatastudio/master/LICENSE.txt",
"icon": "images/sqlserver.png",
"aiKey": "AIF-5574968e-856d-40d2-af67-c89a14e76412",
"engines": {
"vscode": "^1.25.0"
},
"activationEvents": [
"*"
],
"main": "./out/main",
"repository": {
"type": "git",
"url": "https://github.com/Microsoft/azuredatastudio.git"
},
"extensionDependencies": [
"Microsoft.mssql"
],
"contributes": {
"outputChannels": [
"sqlagent"
],
"dashboard.tabs": [
{
"id": "data-management-agent",
"description": "Manage and troubleshoot SQL Agent jobs",
"provider": "MSSQL",
"title": "SQL Agent",
"when": "connectionProvider == 'MSSQL' && !mssql:iscloud",
"container": {
"controlhost-container": {
"type": "agent"
}
}
}
],
"commands": [
{
"command": "agent.openNotebookDialog",
"title": "Schedule Notebook",
"icon": {
"dark": "resources/dark/open_notebook_inverse.svg",
"light": "resources/light/open_notebook.svg"
}
},
{
"command": "agent.reuploadTemplate",
"title": "Reupload Template",
"icon": {
"dark": "resources/dark/open_notebook_inverse.svg",
"light": "resources/light/open_notebook.svg"
}
}
],
"menus": {
"notebook/toolbar": [
{
"command": "agent.openNotebookDialog",
"when": "providerId == sql"
},
{
"command": "agent.reuploadTemplate",
"when": "agent:trackedTemplate"
}
],
"explorer/context": [
{
"command": "agent.openNotebookDialog",
"when": "resourceExtname == .ipynb"
}
]
}
},
"dependencies": {
"vscode-nls": "^3.2.1"
},
"devDependencies": {
"mocha-junit-reporter": "^1.17.0",
"mocha-multi-reporters": "^1.1.7",
"@types/mocha": "^5.2.5",
"@types/node": "^8.10.25",
"mocha": "^5.2.0",
"should": "^13.2.1",
"typemoq": "^2.1.0",
"name": "agent",
"displayName": "SQL Server Agent",
"description": "Manage and troubleshoot SQL Server Agent jobs",
"version": "0.43.0",
"publisher": "Microsoft",
"preview": true,
"license": "https://raw.githubusercontent.com/Microsoft/azuredatastudio/master/LICENSE.txt",
"icon": "images/sqlserver.png",
"aiKey": "AIF-5574968e-856d-40d2-af67-c89a14e76412",
"engines": {
"vscode": "^1.25.0"
},
"activationEvents": [
"*"
],
"main": "./out/main",
"repository": {
"type": "git",
"url": "https://github.com/Microsoft/azuredatastudio.git"
},
"extensionDependencies": [
"Microsoft.mssql"
],
"contributes": {
"outputChannels": [
"sqlagent"
],
"dashboard.tabs": [
{
"id": "data-management-agent",
"description": "Manage and troubleshoot SQL Agent jobs",
"provider": "MSSQL",
"title": "SQL Agent",
"when": "connectionProvider == 'MSSQL' && !mssql:iscloud",
"container": {
"controlhost-container": {
"type": "agent"
}
}
}
],
"commands": [
{
"command": "agent.openNotebookDialog",
"title": "Schedule Notebook",
"icon": {
"dark": "resources/dark/open_notebook_inverse.svg",
"light": "resources/light/open_notebook.svg"
}
},
{
"command": "agent.reuploadTemplate",
"title": "Update Template",
"icon": {
"dark": "resources/dark/open_notebook_inverse.svg",
"light": "resources/light/open_notebook.svg"
}
}
],
"menus": {
"notebook/toolbar": [
{
"command": "agent.openNotebookDialog",
"when": "providerId == sql && !agent:trackedTemplate"
},
{
"command": "agent.reuploadTemplate",
"when": "agent:trackedTemplate"
}
],
"explorer/context": [
{
"command": "agent.openNotebookDialog",
"when": "resourceExtname == .ipynb"
}
]
}
},
"dependencies": {
"vscode-nls": "^3.2.1"
},
"devDependencies": {
"mocha-junit-reporter": "^1.17.0",
"mocha-multi-reporters": "^1.1.7",
"@types/mocha": "^5.2.5",
"@types/node": "^8.10.25",
"mocha": "^5.2.0",
"should": "^13.2.1",
"typemoq": "^2.1.0",
"vscode": "1.1.5"
}
}

View File

@@ -723,4 +723,4 @@ export class JobDialog extends AgentDialog<JobData> {
this.model.alerts = this.alerts;
this.model.categoryId = +this.model.jobCategoryIdsMap.find(cat => cat.name === this.model.category).id;
}
}
}

View File

@@ -66,8 +66,6 @@ export class NotebookDialog extends AgentDialog<NotebookData> {
private removeScheduleButton: azdata.ButtonComponent;
private descriptionTextBox: azdata.InputBoxComponent;
private isEdit: boolean = false;
// Job objects

View File

@@ -70,3 +70,8 @@ export class IconPathHelper {
};
}
}
export namespace cssStyles {
export const hyperlink = { 'user-select': 'text', 'color': '#0078d4', 'text-decoration': 'underline', 'cursor': 'pointer' };
export const text = { 'margin-block-start': '0px', 'margin-block-end': '0px' };
}

View File

@@ -9,7 +9,7 @@ import * as azdata from 'azdata';
import * as vscode from 'vscode';
import * as nls from 'vscode-nls';
import { BdcDashboardModel } from './bdcDashboardModel';
import { IconPathHelper } from '../constants';
import { IconPathHelper, cssStyles } from '../constants';
import { getStateDisplayText, getHealthStatusDisplayText, getEndpointDisplayText, getHealthStatusIcon, getServiceNameDisplayText, Endpoint } from '../utils';
import { EndpointModel, ServiceStatusModel, BdcStatusModel } from '../controller/apiGenerated';
import { BdcDashboard } from './bdcDashboard';
@@ -250,13 +250,21 @@ function createServiceEndpointRow(modelBuilder: azdata.ModelBuilder, container:
endPointRow.addItem(nameCell, { CSSStyles: { 'width': serviceEndpointRowServiceNameCellWidth, 'min-width': serviceEndpointRowServiceNameCellWidth, 'user-select': 'text', 'text-align': 'center' } });
if (isHyperlink) {
const endpointCell = modelBuilder.hyperlink()
.withProperties({ label: endpoint.endpoint, url: endpoint.endpoint, CSSStyles: { 'height': '15px' } })
.withProperties<azdata.HyperlinkComponentProperties>({
label: endpoint.endpoint,
title: endpoint.endpoint,
url: endpoint.endpoint, CSSStyles: { 'height': '15px' }
})
.component();
endPointRow.addItem(endpointCell, { CSSStyles: { 'width': serviceEndpointRowEndpointCellWidth, 'min-width': serviceEndpointRowEndpointCellWidth, 'color': '#0078d4', 'text-decoration': 'underline', 'overflow': 'hidden', 'padding-left': '10px' } });
endPointRow.addItem(endpointCell, { CSSStyles: { 'width': serviceEndpointRowEndpointCellWidth, 'min-width': serviceEndpointRowEndpointCellWidth, 'overflow': 'hidden', 'text-overflow': 'ellipsis', ...cssStyles.hyperlink } });
}
else if (endpoint.name === Endpoint.sqlServerMaster) {
const endpointCell = modelBuilder.text()
.withProperties({ value: endpoint.endpoint, CSSStyles: { 'margin-block-start': '0px', 'margin-block-end': '0px', 'user-select': 'text', 'cursor': 'pointer', 'color': '#0078d4', 'text-decoration': 'underline' } })
.withProperties<azdata.TextComponentProperties>({
value: endpoint.endpoint,
title: endpoint.endpoint,
CSSStyles: { 'overflow': 'hidden', 'text-overflow': 'ellipsis', ...cssStyles.text, ...cssStyles.hyperlink }
})
.component();
endpointCell.onDidClick(async () => {
const connProfile = bdcModel.getSqlServerMasterConnectionProfile();
@@ -271,13 +279,17 @@ function createServiceEndpointRow(modelBuilder: azdata.ModelBuilder, container:
azdata.connection.openConnectionDialog(undefined, connProfile);
}
});
endPointRow.addItem(endpointCell, { CSSStyles: { 'width': serviceEndpointRowEndpointCellWidth, 'min-width': serviceEndpointRowEndpointCellWidth, 'overflow': 'hidden', 'padding-left': '10px' } });
endPointRow.addItem(endpointCell, { CSSStyles: { 'width': serviceEndpointRowEndpointCellWidth, 'min-width': serviceEndpointRowEndpointCellWidth } });
}
else {
const endpointCell = modelBuilder.text()
.withProperties({ value: endpoint.endpoint, CSSStyles: { 'margin-block-start': '0px', 'margin-block-end': '0px', 'user-select': 'text' } })
.withProperties<azdata.TextComponentProperties>({
value: endpoint.endpoint,
title: endpoint.endpoint,
CSSStyles: { 'overflow': 'hidden', 'text-overflow': 'ellipsis', ...cssStyles.text }
})
.component();
endPointRow.addItem(endpointCell, { CSSStyles: { 'width': serviceEndpointRowEndpointCellWidth, 'min-width': serviceEndpointRowEndpointCellWidth, 'overflow': 'hidden', 'padding-left': '10px' } });
endPointRow.addItem(endpointCell, { CSSStyles: { 'width': serviceEndpointRowEndpointCellWidth, 'min-width': serviceEndpointRowEndpointCellWidth } });
}
const copyValueCell = modelBuilder.button().component();
copyValueCell.iconPath = IconPathHelper.copy;

View File

@@ -2,7 +2,7 @@
"name": "import",
"displayName": "SQL Server Import",
"description": "SQL Server Import for Azure Data Studio supports importing CSV or JSON files into SQL Server.",
"version": "0.10.0",
"version": "0.11.0",
"publisher": "Microsoft",
"preview": true,
"engines": {
@@ -50,6 +50,17 @@
"group": "import"
}
]
},
"configuration":{
"type": "object",
"title": "%flatfileimport.configuration.title%",
"properties": {
"flatFileImport.logDebugInfo": {
"type": "boolean",
"default": false,
"description": "%flatfileimport.logDebugInfo%"
}
}
}
},
"dependencies": {

View File

@@ -0,0 +1,4 @@
{
"flatfileimport.configuration.title": "Flat File Import configuration",
"flatfileimport.logDebugInfo": "[Optional] Log debug output to the console (View -> Output) and then select appropriate output channel from the dropdown"
}

View File

@@ -44,7 +44,7 @@ export class ServiceClient {
return new Promise((resolve, reject) => {
serverdownloader.getOrDownloadServer().then(e => {
const installationComplete = Date.now();
let serverOptions = this.generateServerOptions(e);
let serverOptions = this.generateServerOptions(e, context);
client = new SqlOpsDataClient(Constants.serviceName, serverOptions, clientOptions);
const processStart = Date.now();
client.onReady().then(() => {
@@ -90,10 +90,10 @@ export class ServiceClient {
};
}
private generateServerOptions(executablePath: string): ServerOptions {
private generateServerOptions(executablePath: string, context: vscode.ExtensionContext): ServerOptions {
let launchArgs = [];
launchArgs.push('--log-dir');
let logFileLocation = path.join(serviceUtils.getDefaultLogLocation(), 'flatfileimport');
let logFileLocation = context.logPath;
launchArgs.push(logFileLocation);
let config = vscode.workspace.getConfiguration(Constants.extensionConfigSectionName);
if (config) {

View File

@@ -20,10 +20,6 @@ export function getAppDataPath(): string {
}
}
export function getDefaultLogLocation(): string {
return path.join(getAppDataPath(), 'azuredatastudio');
}
export function ensure(target: object, key: string): any {
if (target[key] === void 0) {
target[key] = {} as any;

View File

@@ -1,6 +1,6 @@
{
"downloadUrl": "https://github.com/Microsoft/sqltoolsservice/releases/download/v{#version#}/microsoft.sqltools.servicelayer-{#fileName#}",
"version": "2.0.0-release.10",
"version": "2.0.0-release.17",
"downloadFileNames": {
"Windows_86": "win-x86-netcoreapp2.2.zip",
"Windows_64": "win-x64-netcoreapp2.2.zip",

View File

@@ -298,7 +298,7 @@ export namespace UpdateAgentNotebookRunNameRequest {
}
export namespace DeleteMaterializedNotebookRequest {
export const type = new RequestType<DeleteAgentMaterializedNotebookParams, azdata.ResultStatus, void, void>('agent/deletenotebookmaterialized');
export const type = new RequestType<DeleteAgentMaterializedNotebookParams, azdata.ResultStatus, void, void>('agent/deletematerializednotebook');
}
export namespace UpdateAgentNotebookRunPinRequest {

View File

@@ -60,16 +60,28 @@ export function registerServiceEndpoints(context: vscode.ExtensionContext): void
const container = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'column', width: '100%', height: '100%', alignItems: 'left' }).component();
endpointsArray.forEach(endpointInfo => {
const endPointRow = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'row' }).component();
const nameCell = view.modelBuilder.text().withProperties<azdata.TextComponentProperties>({ value: endpointInfo.description }).component();
endPointRow.addItem(nameCell, { CSSStyles: { 'width': '35%', 'font-weight': '600', 'user-select': 'text' } });
if (hyperlinkedEndpoints.findIndex(e => e === endpointInfo.serviceName) >= 0) {
const linkCell = view.modelBuilder.hyperlink().withProperties<azdata.HyperlinkComponentProperties>({ label: endpointInfo.endpoint, url: endpointInfo.endpoint }).component();
endPointRow.addItem(linkCell, { CSSStyles: { 'width': '62%', 'color': '#0078d4', 'text-decoration': 'underline', 'padding-top': '10px' } });
const linkCell = view.modelBuilder.hyperlink()
.withProperties<azdata.HyperlinkComponentProperties>({
label: endpointInfo.endpoint,
title: endpointInfo.endpoint,
url: endpointInfo.endpoint
}).component();
endPointRow.addItem(linkCell, { CSSStyles: { 'width': '62%', 'color': '#0078d4', 'text-decoration': 'underline', 'padding-top': '10px', 'overflow': 'hidden', 'text-overflow': 'ellipsis' } });
}
else {
const endpointCell = view.modelBuilder.text().withProperties<azdata.TextComponentProperties>({ value: endpointInfo.endpoint }).component();
const endpointCell =
view.modelBuilder.text()
.withProperties<azdata.TextComponentProperties>(
{
value: endpointInfo.endpoint,
title: endpointInfo.endpoint,
CSSStyles: { 'overflow': 'hidden', 'text-overflow': 'ellipsis' }
})
.component();
endPointRow.addItem(endpointCell, { CSSStyles: { 'width': '62%', 'user-select': 'text' } });
}
const copyValueCell = view.modelBuilder.button().component();

View File

@@ -153,6 +153,13 @@
"configuration": "./language-configuration.json"
}
],
"grammars": [
{
"language": "notebook",
"scopeName": "source.notebook",
"path": "./syntaxes/notebook.tmLanguage.json"
}
],
"menus": {
"commandPalette": [
{

View File

@@ -0,0 +1,213 @@
{
"information_for_contributors": [
"This file has been converted from https://github.com/Microsoft/vscode-JSON.tmLanguage/blob/master/JSON.tmLanguage",
"If you want to provide a fix or improvement, please create a pull request against the original repository.",
"Once accepted there, we are happy to receive an update request."
],
"version": "https://github.com/Microsoft/vscode-JSON.tmLanguage/commit/9bd83f1c252b375e957203f21793316203f61f70",
"name": "notebook",
"scopeName": "source.notebook",
"patterns": [
{
"include": "#value"
}
],
"repository": {
"array": {
"begin": "\\[",
"beginCaptures": {
"0": {
"name": "punctuation.definition.array.begin.notebook"
}
},
"end": "\\]",
"endCaptures": {
"0": {
"name": "punctuation.definition.array.end.notebook"
}
},
"name": "meta.structure.array.notebook",
"patterns": [
{
"include": "#value"
},
{
"match": ",",
"name": "punctuation.separator.array.notebook"
},
{
"match": "[^\\s\\]]",
"name": "invalid.illegal.expected-array-separator.notebook"
}
]
},
"comments": {
"patterns": [
{
"begin": "/\\*\\*(?!/)",
"captures": {
"0": {
"name": "punctuation.definition.comment.notebook"
}
},
"end": "\\*/",
"name": "comment.block.documentation.notebook"
},
{
"begin": "/\\*",
"captures": {
"0": {
"name": "punctuation.definition.comment.notebook"
}
},
"end": "\\*/",
"name": "comment.block.notebook"
},
{
"captures": {
"1": {
"name": "punctuation.definition.comment.notebook"
}
},
"match": "(//).*$\\n?",
"name": "comment.line.double-slash.js"
}
]
},
"constant": {
"match": "\\b(?:true|false|null)\\b",
"name": "constant.language.notebook"
},
"number": {
"match": "(?x) # turn on extended mode\n -? # an optional minus\n (?:\n 0 # a zero\n | # ...or...\n [1-9] # a 1-9 character\n \\d* # followed by zero or more digits\n )\n (?:\n (?:\n \\. # a period\n \\d+ # followed by one or more digits\n )?\n (?:\n [eE] # an e character\n [+-]? # followed by an option +/-\n \\d+ # followed by one or more digits\n )? # make exponent optional\n )? # make decimal portion optional",
"name": "constant.numeric.notebook"
},
"object": {
"begin": "\\{",
"beginCaptures": {
"0": {
"name": "punctuation.definition.dictionary.begin.notebook"
}
},
"end": "\\}",
"endCaptures": {
"0": {
"name": "punctuation.definition.dictionary.end.notebook"
}
},
"name": "meta.structure.dictionary.notebook",
"patterns": [
{
"comment": "the notebook object key",
"include": "#objectkey"
},
{
"include": "#comments"
},
{
"begin": ":",
"beginCaptures": {
"0": {
"name": "punctuation.separator.dictionary.key-value.notebook"
}
},
"end": "(,)|(?=\\})",
"endCaptures": {
"1": {
"name": "punctuation.separator.dictionary.pair.notebook"
}
},
"name": "meta.structure.dictionary.value.notebook",
"patterns": [
{
"comment": "the notebook object value",
"include": "#value"
},
{
"match": "[^\\s,]",
"name": "invalid.illegal.expected-dictionary-separator.notebook"
}
]
},
{
"match": "[^\\s\\}]",
"name": "invalid.illegal.expected-dictionary-separator.notebook"
}
]
},
"string": {
"begin": "\"",
"beginCaptures": {
"0": {
"name": "punctuation.definition.string.begin.notebook"
}
},
"end": "\"",
"endCaptures": {
"0": {
"name": "punctuation.definition.string.end.notebook"
}
},
"name": "string.quoted.double.notebook",
"patterns": [
{
"include": "#stringcontent"
}
]
},
"objectkey": {
"begin": "\"",
"beginCaptures": {
"0": {
"name": "punctuation.support.type.property-name.begin.notebook"
}
},
"end": "\"",
"endCaptures": {
"0": {
"name": "punctuation.support.type.property-name.end.notebook"
}
},
"name": "string.notebook support.type.property-name.notebook",
"patterns": [
{
"include": "#stringcontent"
}
]
},
"stringcontent": {
"patterns": [
{
"match": "(?x) # turn on extended mode\n \\\\ # a literal backslash\n (?: # ...followed by...\n [\"\\\\/bfnrt] # one of these characters\n | # ...or...\n u # a u\n [0-9a-fA-F]{4}) # and four hex digits",
"name": "constant.character.escape.notebook"
},
{
"match": "\\\\.",
"name": "invalid.illegal.unrecognized-string-escape.notebook"
}
]
},
"value": {
"patterns": [
{
"include": "#constant"
},
{
"include": "#number"
},
{
"include": "#string"
},
{
"include": "#array"
},
{
"include": "#object"
},
{
"include": "#comments"
}
]
}
}
}

1
extensions/query-history/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
*.vsix

View File

@@ -0,0 +1,5 @@
src/**
.gitignore
tsconfig.json
cgmanifest.json
.vscode

View File

@@ -0,0 +1,50 @@
# Query History *(preview)*
Adds a Query History panel for viewing and running past executed queries.
### How do I view the history?
Query History is displayed as a tab in the tab panel, which is toggled by the *View: Toggle Panel* command.
![Query History tab](https://github.com/microsoft/azuredatastudio/tree/master/extensions/query-history/images/QueryHistoryTab.png)
Initially this view will be empty but once you execute a query editor that will be captured in the window - with a separate row displayed for every query executed.
![Query History tab with queries](https://github.com/microsoft/azuredatastudio/tree/master/extensions/query-history/images/QueryHistoryTabWithQueries.png)
Each row consists of 3 parts :
1. Status icon : This will be a ✔️ if the query executed successfully. If any errors occurred a ❌is shown.
2. Query Text : This is the text of the query that was executed
3. Connection Info : The Server and Database the query was executed against
4. Timestamp : The date and time the query was executed
### Query History row actions
Right clicking on a history row will bring up a menu with a number of actions available.
![Query History action menu](https://github.com/microsoft/azuredatastudio/tree/master/extensions/query-history/images/QueryHistoryActionMenu.png)
#### Open Query
This will open a new query editor window populated with the query text from the query executed and using the connection of that query.
#### Run Query
This will do the same thing as Open Query but will additionally run the statement immediately.
#### Delete
This will permanently delete the row from the view.
## 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/en-us/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/master/LICENSE.txt).

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

View File

@@ -0,0 +1,54 @@
{
"name": "query-history",
"displayName": "%queryHistory.displayName%",
"description": "%queryHistory.description%",
"version": "0.1.0",
"publisher": "Microsoft",
"preview": true,
"license": "https://raw.githubusercontent.com/Microsoft/azuredatastudio/master/LICENSE.txt",
"icon": "images/sqlserver.png",
"aiKey": "AIF-5574968e-856d-40d2-af67-c89a14e76412",
"engines": {
"vscode": "^1.30.1",
"azdata": ">=1.12.0"
},
"activationEvents": [
"*"
],
"main": "./out/main",
"repository": {
"type": "git",
"url": "https://github.com/Microsoft/azuredatastudio.git"
},
"extensionDependencies": [
],
"contributes": {
"commands": [
{
"command": "queryHistory.clear",
"title": "%queryHistory.clear%",
"category": "%queryHistory.displayName%"
},
{
"command": "queryHistory.toggleCapture",
"title": "%queryHistory.toggleCapture%",
"category": "%queryHistory.displayName%"
}
],
"menus": {
"commandPalette": [
{
"command": "queryHistory.clear"
},
{
"command": "queryHistory.toggleCapture"
}
]
}
},
"dependencies": {
},
"devDependencies": {
"vscode": "1.0.1"
}
}

View File

@@ -0,0 +1,6 @@
{
"queryHistory.displayName": "Query History",
"queryHistory.description": "View and run previously executed queries",
"queryHistory.clear": "Clear All History",
"queryHistory.toggleCapture": "Toggle Query History Capture"
}

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';
export async function activate(context: vscode.ExtensionContext): Promise<void> {
// Currently all the functionality for this is contained within the core ADS
// code as the extensibility API doesn't currently support all the required
// functionality (such as contributing tab panels)
vscode.commands.executeCommand('queryHistory.enableQueryHistory');
}
export async function deactivate(): Promise<void> {
}

View File

@@ -0,0 +1,6 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/// <reference path='../../../../src/vs/vscode.d.ts'/>

View File

@@ -0,0 +1,21 @@
{
"compileOnSave": true,
"compilerOptions": {
"module": "commonjs",
"target": "es6",
"outDir": "./out",
"lib": [
"es6", "es2015.promise"
],
"typeRoots": [
"./node_modules/@types"
],
"sourceMap": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"moduleResolution": "node"
},
"exclude": [
"node_modules"
]
}

File diff suppressed because it is too large Load Diff

View File

@@ -23,7 +23,13 @@
["\"", "\""],
["'", "'"],
["`", "`"]
]
],
"folding": {
"markers": {
"start": "^\\s*--\\s*#region\\s*.*$",
"end": "^\\s*--\\s*#endregion\\s*.*$"
}
}
// enhancedBrackets:[
// { openTrigger: 'n', open: /begin$/i, closeComplete: 'end', matchCase: true },
@@ -31,4 +37,4 @@
// { openTrigger: 'n', open: /when$/i, closeComplete: 'then', matchCase: true }
// ],
// noindentBrackets: '()',
}
}

View File

@@ -12,6 +12,7 @@
"Programming Languages"
],
"activationEvents": [
"onLanguage:sql",
"onLanguage:xml"
],
"scripts": {

View File

@@ -1,6 +1,6 @@
{
"name": "azuredatastudio",
"version": "1.11.0",
"version": "1.12.1",
"distro": "8c3e97e3425cc9814496472ab73e076de2ba99ee",
"author": {
"name": "Microsoft Corporation"

File diff suppressed because it is too large Load Diff

View File

@@ -1,236 +1,404 @@
{
"name": "server-report",
"displayName": "Server Reports",
"description": "Server Reports",
"version": "0.1.5",
"publisher": "Microsoft",
"preview": true,
"engines": {
"vscode": "^1.26.0",
"azdata": "*"
},
"icon": "images/sqlserver.png",
"license": "SEE LICENSE IN LICENSE.txt",
"repository": "https://github.com/Microsoft/azuredatastudio",
"categories": [
"Other"
],
"activationEvents": [
"*"
],
"main": "./out/src/extension",
"contributes": {
"configuration": [],
"commands": [],
"views": {},
"menus": {},
"dashboard.tabs": [
{
"id": "Server-Reports",
"title": "Server Reports",
"description": "This extension shows useful reports for a server.",
"container": {
"nav-section": [
{
"id": "server-reports-monitoring",
"title": "Monitor",
"icon": {
"light": "./out/src/media/monitor.svg",
"dark": "./out/src/media/monitor_inverse.svg"
},
"container": {
"server-reports-monitoring-container": {}
}
},
{
"id": "server-reports-performance",
"title": "Performance",
"icon": {
"light": "./out/src/media/performance.svg",
"dark": "./out/src/media/performance_inverse.svg"
},
"container": {
"server-reports-performance-container": {}
}
}
]
}
}
],
"dashboard.insights": [
{
"id": "extension-dbspace-usage",
"contrib": {
"type": {
"horizontalBar": {
"dataDirection": "vertical",
"dataType": "number",
"legendPosition": "top",
"labelFirstColumn": false,
"columnsAsLabels": true
}
},
"queryFile": "./out/src/sql/all_db_space_used.sql"
}
},
{
"id": "extension-cpu-utilization",
"contrib": {
"type": {
"timeSeries": {
"dataDirection": "horizontal",
"dataType": "point",
"legendPosition": "top",
"labelFirstColumn": false,
"columnsAsLabels": false
}
},
"queryFile": "./out/src/sql/cpumetric.sql"
}
},
{
"id": "extension-backup-growth-trend",
"details": "Abbie wants it",
"contrib": {
"type": {
"timeSeries": {
"dataDirection": "horizontal",
"dataType": "point",
"legendPosition": "none",
"labelFirstColumn": false,
"columnsAsLabels": false
}
},
"queryFile": "./out/src/sql/backup_size_trend.sql"
}
},
{
"id": "extension-wait-counts-by-Paul-Randal",
"contrib": {
"type": {
"horizontalBar": {
"dataDirection": "vertical",
"dataType": "number",
"legendPosition": "none",
"labelFirstColumn": false,
"columnsAsLabels": true
}
},
"queryFile": "./out/src/sql/waits_paul_randal.sql",
"details": {
"queryFile": "./out/src/sql/waits_detail_paul_randal.sql",
"label": {
"column": "WaitType",
"state": []
},
"value": "Percentage"
}
}
},
{
"id": "extension-dbbuffer-usage",
"contrib": {
"type": {
"horizontalBar": {
"dataDirection": "vertical",
"dataType": "number",
"legendPosition": "top",
"labelFirstColumn": false,
"columnsAsLabels": true
}
},
"queryFile": "./out/src/sql/memorybydb.sql"
}
}
],
"dashboard.containers": [
{
"id": "server-reports-monitoring-container",
"container": {
"widgets-container": [
{
"name": "Top 10 DB Space Usage",
"gridItemConfig": {
"sizex": 2,
"sizey": 2
},
"widget": {
"extension-dbspace-usage": {}
}
},
{
"name": "Top 10 DB Buffer Usage",
"gridItemConfig": {
"sizex": 2,
"sizey": 2
},
"widget": {
"extension-dbbuffer-usage": {}
}
},
{
"name": "CPU Utilization",
"gridItemConfig": {
"sizex": 2,
"sizey": 1
},
"widget": {
"extension-cpu-utilization": {}
}
},
{
"name": "Backup Growth Trend",
"gridItemConfig": {
"sizex": 2,
"sizey": 1
},
"widget": {
"extension-backup-growth-trend": {}
}
}
]
}
},
{
"id": "server-reports-performance-container",
"container": {
"widgets-container": [
{
"name": "Wait Counts by Paul Randal",
"gridItemConfig": {
"sizex": 2,
"sizey": 2
},
"widget": {
"extension-wait-counts-by-Paul-Randal": {}
}
}
]
}
}
],
"snippets": []
},
"scripts": {
"build": "gulp build",
"compile": "gulp compile",
"watch": "gulp watch",
"postinstall": "node ./node_modules/vscode/bin/install"
},
"dependencies": {},
"devDependencies": {
"child-process-promise": "^2.2.1",
"del": "^3.0.0",
"gulp": "^4.0.0",
"gulp-color": "0.0.1",
"gulp-sourcemaps": "^2.6.4",
"gulp-tslint": "^6.0.2",
"gulp-typescript": "^3.2.4",
"should": "^13.2.1",
"tslint": "^3.14.0",
"typemoq": "^2.1.0",
"typescript": "^2.6.1",
"vsce": "1.36.2",
"vscode": "^1.1.6"
}
}
"name": "server-report",
"displayName": "Server Reports",
"description": "Server Reports",
"version": "0.1.5",
"publisher": "Microsoft",
"preview": true,
"engines": {
"vscode": "^1.26.0",
"azdata": "*"
},
"icon": "images/sqlserver.png",
"license": "SEE LICENSE IN LICENSE.txt",
"repository": "https://github.com/Microsoft/azuredatastudio",
"categories": [
"Other"
],
"activationEvents": [
"*"
],
"main": "./out/src/extension",
"contributes": {
"commands": [
{
"command": "tempdb.startEvent",
"title": "Start",
"icon": {
"light": "./out/src/media/launch.svg",
"dark": "./out/src/media/launch_inverse.svg"
}
},
{
"command": "tempdb.stopEvent",
"title": "Stop",
"icon": {
"light": "./out/src/media/blocker.svg",
"dark": "./out/src/media/blocker_inverse.svg"
}
},
{
"command": "tempdb.contention",
"title": "Contention Help for Tempdb",
"icon": {
"light": "./out/src/media/documentation.svg",
"dark": "./out/src/media/documentation_inverse.svg"
}
},
{
"command": "tempdb.pauseEvent",
"title": "Pause",
"icon": {
"light": "./out/src/media/insights.svg",
"dark": "./out/src/media/insights_inverse.svg"
}
}
],
"configuration": [],
"views": {},
"menus": {},
"dashboard.tabs": [
{
"id": "Server-Reports",
"title": "Server Reports",
"description": "This extension shows useful reports for a server.",
"container": {
"nav-section": [
{
"id": "server-reports-monitoring",
"title": "Monitor",
"icon": {
"light": "./out/src/media/monitor.svg",
"dark": "./out/src/media/monitor_inverse.svg"
},
"container": {
"server-reports-monitoring-container": {}
}
},
{
"id": "server-reports-performance",
"title": "Performance",
"icon": {
"light": "./out/src/media/performance.svg",
"dark": "./out/src/media/performance_inverse.svg"
},
"container": {
"server-reports-performance-container": {}
}
},
{
"id": "server-reports-tempdb",
"title": "TempDB",
"container": {
"server-reports-tempdb-container": {}
},
"icon": {
"light": "./out/src/media/tempdb.svg",
"dark": "./out/src/media/tempdb_inverse.svg"
}
}
]
}
}
],
"dashboard.insights": [
{
"id": "extension-dbspace-usage",
"contrib": {
"type": {
"horizontalBar": {
"dataDirection": "vertical",
"dataType": "number",
"legendPosition": "top",
"labelFirstColumn": false,
"columnsAsLabels": true
}
},
"queryFile": "./out/src/sql/all_db_space_used.sql"
}
},
{
"id": "extension-cpu-utilization",
"contrib": {
"type": {
"timeSeries": {
"dataDirection": "horizontal",
"dataType": "point",
"legendPosition": "top",
"labelFirstColumn": false,
"columnsAsLabels": false
}
},
"queryFile": "./out/src/sql/cpumetric.sql"
}
},
{
"id": "extension-backup-growth-trend",
"details": "Abbie wants it",
"contrib": {
"type": {
"timeSeries": {
"dataDirection": "horizontal",
"dataType": "point",
"legendPosition": "none",
"labelFirstColumn": false,
"columnsAsLabels": false
}
},
"queryFile": "./out/src/sql/backup_size_trend.sql"
}
},
{
"id": "extension-wait-counts-by-Paul-Randal",
"contrib": {
"type": {
"horizontalBar": {
"dataDirection": "vertical",
"dataType": "number",
"legendPosition": "none",
"labelFirstColumn": false,
"columnsAsLabels": true
}
},
"queryFile": "./out/src/sql/waits_paul_randal.sql",
"details": {
"queryFile": "./out/src/sql/waits_detail_paul_randal.sql",
"label": {
"column": "WaitType",
"state": []
},
"value": "Percentage"
}
}
},
{
"id": "extension-wait-resource-statistics",
"contrib": {
"type": {
"bar": {
"dataDirection": "horizontal",
"dataType": "number",
"legendPosition": "none",
"labelFirstColumn": false,
"columnsAsLabels": true
}
},
"queryFile": "./out/src/sql/wait_resources.sql",
"details": {
"queryFile": "./out/src/sql/wait_resources.sql",
"label": {
"column": "WaitType",
"state": []
},
"value": "Percentage"
}
}
},
{
"id": "extension-dbbuffer-usage",
"contrib": {
"type": {
"horizontalBar": {
"dataDirection": "vertical",
"dataType": "number",
"legendPosition": "top",
"labelFirstColumn": false,
"columnsAsLabels": true
}
},
"queryFile": "./out/src/sql/memorybydb.sql"
}
},
{
"id": "type-of-contention",
"contrib": {
"type": {
"bar": {
"dataDirection": "vertical",
"columnsAsLabels": true,
"labelFirstColumn": false,
"legendPosition": "none"
}
},
"queryFile": "./out/src/sql/typeofContentions.sql",
"autoRefreshInterval": 0.05
}
},
{
"id": "metadata-contention",
"contrib": {
"type": {
"bar": {
"dataDirection": "vertical",
"columnsAsLabels": true,
"labelFirstColumn": false,
"legendPosition": "none",
"xAxisLabel": "Object Ids for System Tables"
}
},
"queryFile": "./out/src/sql/metadataContention.sql",
"autoRefreshInterval": 0.05
}
},
{
"id": "allocation-contention",
"contrib": {
"type": {
"bar": {
"dataDirection": "vertical",
"columnsAsLabels": true,
"labelFirstColumn": false,
"legendPosition": "none",
"xAxisLabel": "Page Types"
}
},
"queryFile": "./out/src/sql/allocationContention.sql",
"autoRefreshInterval": 0.05
}
}
],
"dashboard.containers": [
{
"id": "server-reports-monitoring-container",
"container": {
"widgets-container": [
{
"name": "Top 10 DB Space Usage",
"gridItemConfig": {
"sizex": 2,
"sizey": 2
},
"widget": {
"extension-dbspace-usage": {}
}
},
{
"name": "Top 10 DB Buffer Usage",
"gridItemConfig": {
"sizex": 2,
"sizey": 2
},
"widget": {
"extension-dbbuffer-usage": {}
}
},
{
"name": "CPU Utilization",
"gridItemConfig": {
"sizex": 2,
"sizey": 1
},
"widget": {
"extension-cpu-utilization": {}
}
},
{
"name": "Backup Growth Trend",
"gridItemConfig": {
"sizex": 2,
"sizey": 1
},
"widget": {
"extension-backup-growth-trend": {}
}
}
]
}
},
{
"id": "server-reports-performance-container",
"container": {
"widgets-container": [
{
"name": "Wait Counts by Paul Randal",
"gridItemConfig": {
"sizex": 2,
"sizey": 2
},
"widget": {
"extension-wait-counts-by-Paul-Randal": {}
}
}
]
}
},
{
"id": "server-reports-tempdb-container",
"container": {
"widgets-container": [
{
"name": "Tasks",
"widget": {
"tasks-widget": [
"tempdb.startEvent",
"tempdb.contention",
"tempdb.pauseEvent",
"tempdb.stopEvent"
]
}
},
{
"name": "Overall Contention",
"gridItemConfig": {
"sizex": 2,
"sizey": 1
},
"widget": {
"type-of-contention": {}
}
},
{
"name": "Metadata Contention",
"gridItemConfig": {
"sizex": 2,
"sizey": 1
},
"widget": {
"metadata-contention": {}
}
},
{
"name": "Allocation Contention",
"gridItemConfig": {
"sizex": 2,
"sizey": 1
},
"widget": {
"allocation-contention": {}
}
}
]
}
}
],
"snippets": []
},
"scripts": {
"build": "gulp build",
"compile": "gulp compile",
"watch": "gulp watch",
"postinstall": "node ./node_modules/vscode/bin/install"
},
"dependencies": {
"fs": "0.0.1-security",
"fs-extra": "^8.1.0",
"generator-sqlops": "^0.10.4",
"handlebars": "^4.1.2",
"openurl": "^1.1.1"
},
"devDependencies": {
"child-process-promise": "^2.2.1",
"del": "^3.0.0",
"gulp": "^4.0.0",
"gulp-color": "0.0.1",
"gulp-sourcemaps": "^2.6.4",
"gulp-tslint": "^6.0.2",
"gulp-typescript": "^3.2.4",
"should": "^13.2.1",
"tslint": "^3.14.0",
"typemoq": "^2.1.0",
"typescript": "^2.9.2",
"vsce": "1.36.2",
"vscode": "^1.1.6"
}
}

View File

@@ -0,0 +1,25 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as vscode from 'vscode';
export default abstract class ControllerBase implements vscode.Disposable {
protected _context: vscode.ExtensionContext;
public constructor(context: vscode.ExtensionContext) {
this._context = context;
}
abstract activate(): Promise<boolean>;
abstract deactivate(): void;
public dispose(): void {
this.deactivate();
}
}

View File

@@ -0,0 +1,64 @@
/*---------------------------------------------------------------------------------------------
* 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 sqlops from 'sqlops';
import * as azdata from 'azdata';
import * as Utils from '../utils';
import ControllerBase from './controllerBase';
import * as fs from 'fs';
import * as path from 'path';
import * as vscode from 'vscode';
import * as openurl from 'openurl';
/**
* The main controller class that initializes the extension
*/
export default class MainController extends ControllerBase {
public apiWrapper;
// PUBLIC METHODS //////////////////////////////////////////////////////
/**
* Deactivates the extension
*/
public deactivate(): void {
Utils.logDebug('Main controller deactivated');
}
public activate(): Promise<boolean> {
azdata.tasks.registerTask("tempdb.startEvent", e => this.onExecute(e, 'startEvent.sql'));
azdata.tasks.registerTask("tempdb.stopEvent", e => this.onExecute(e, 'stopEvent.sql'));
azdata.tasks.registerTask("tempdb.contention", () => this.openurl('https://aka.ms/tempdbblog'));
azdata.tasks.registerTask("tempdb.pauseEvent", e => this.stopAutoRefresh(e));
return Promise.resolve(true);
}
private openurl(link: string): void {
openurl.open(link);
}
private onExecute(connection: azdata.IConnectionProfile, fileName: string): void {
//Command to start/stop autorefresh and run the query
vscode.commands.executeCommand('azdata.widget.setAutoRefreshState', 'type-of-contention', connection.id, true);
vscode.commands.executeCommand('azdata.widget.setAutoRefreshState', 'metadata-contention', connection.id, true);
vscode.commands.executeCommand('azdata.widget.setAutoRefreshState', 'allocation-contention', connection.id, true);
let sqlContent = fs.readFileSync(path.join(__dirname, '..', 'sql', fileName)).toString();
vscode.workspace.openTextDocument({ language: 'sql', content: sqlContent }).then(doc => {
vscode.window.showTextDocument(doc, vscode.ViewColumn.Active, false).then(() => {
let filePath = doc.uri.toString();
azdata.queryeditor.connect(filePath, connection.id).then(() => azdata.queryeditor.runQuery(filePath, undefined, false));
});
});
}
private stopAutoRefresh(connection: azdata.IConnectionProfile) {
vscode.commands.executeCommand('azdata.widget.setAutoRefreshState', 'type-of-contention', connection.id, false);
vscode.commands.executeCommand('azdata.widget.setAutoRefreshState', 'metadata-contention', connection.id, false);
vscode.commands.executeCommand('azdata.widget.setAutoRefreshState', 'allocation-contention', connection.id, false);
}
}

View File

@@ -7,10 +7,33 @@
import * as vscode from 'vscode';
export function activate(context: vscode.ExtensionContext): void {
// No-op
import ControllerBase from './controllers/controllerBase';
import MainController from './controllers/mainController';
let controllers: ControllerBase[] = [];
export function activate(context: vscode.ExtensionContext): Promise<boolean> {
let activations: Promise<boolean>[] = [];
// Start the main controller
let mainController = new MainController(context);
controllers.push(mainController);
context.subscriptions.push(mainController);
activations.push(mainController.activate());
return Promise.all(activations)
.then((results: boolean[]) => {
for (let result of results) {
if (!result) {
return false;
}
}
return true;
});
}
export function deactivate(): void {
// No-op
for (let controller of controllers) {
controller.deactivate();
}
}

View File

@@ -4,4 +4,4 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
// TODO: localize!
export const msgErrorLoadingTab = 'An error occurred while loading the tab.';

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#212121;}</style></defs><title>blocker</title><polygon class="cls-1" points="0.99 3.99 -0.01 3.99 -0.01 0.03 3.98 0.03 3.98 1.03 0.99 1.03 0.99 3.99"/><polygon class="cls-1" points="16.01 3.99 15.01 3.99 15.01 1.03 12.02 1.03 12.02 0.03 16.01 0.03 16.01 3.99"/><polygon class="cls-1" points="16.01 15.97 12.02 15.97 12.02 14.97 15.01 14.97 15.01 12.01 16.01 12.01 16.01 15.97"/><polygon class="cls-1" points="4 15.97 0.01 15.97 0.01 12.01 1.01 12.01 1.01 14.97 4 14.97 4 15.97"/><path class="cls-1" d="M8.41,3.18A4.82,4.82,0,1,0,13.23,8,4.83,4.83,0,0,0,8.41,3.18Zm0,.74A4.08,4.08,0,0,1,12.49,8a4,4,0,0,1-.85,2.47L5.69,5A4,4,0,0,1,8.41,3.93Zm0,8.15A4.08,4.08,0,0,1,4.34,8a4,4,0,0,1,.85-2.47L11.14,11A4,4,0,0,1,8.41,12.07Z"/></svg>

After

Width:  |  Height:  |  Size: 847 B

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#fff;}</style></defs><title>blocker_inverse</title><polygon class="cls-1" points="0.99 3.99 -0.01 3.99 -0.01 0.03 3.98 0.03 3.98 1.03 0.99 1.03 0.99 3.99"/><polygon class="cls-1" points="16.01 3.99 15.01 3.99 15.01 1.03 12.02 1.03 12.02 0.03 16.01 0.03 16.01 3.99"/><polygon class="cls-1" points="16.01 15.97 12.02 15.97 12.02 14.97 15.01 14.97 15.01 12.01 16.01 12.01 16.01 15.97"/><polygon class="cls-1" points="4 15.97 0.01 15.97 0.01 12.01 1.01 12.01 1.01 14.97 4 14.97 4 15.97"/><path class="cls-1" d="M8.41,3.18A4.82,4.82,0,1,0,13.23,8,4.83,4.83,0,0,0,8.41,3.18Zm0,.74A4.08,4.08,0,0,1,12.49,8a4,4,0,0,1-.85,2.47L5.69,5A4,4,0,0,1,8.41,3.93Zm0,8.15A4.08,4.08,0,0,1,4.34,8a4,4,0,0,1,.85-2.47L11.14,11A4,4,0,0,1,8.41,12.07Z"/></svg>

After

Width:  |  Height:  |  Size: 852 B

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#212121;}</style></defs><title>documentation</title><path class="cls-1" d="M14,4.29V16H1V0H9.72ZM13,5H9V1H2V15H13ZM3,4H8V5H3ZM3,8H8V9H3Zm0,4H8v1H3ZM9,7h3v3H9Zm0,4h3v3H9Zm1-7H12.3L10,1.71Zm0,5h1V8H10Zm0,4h1V12H10Z"/></svg>

After

Width:  |  Height:  |  Size: 339 B

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#fff;}</style></defs><title>documentation_1</title><path class="cls-1" d="M14,4.29V16H1V0H9.72ZM13,5H9V1H2V15H13ZM3,4H8V5H3ZM3,8H8V9H3Zm0,4H8v1H3ZM9,7h3v3H9Zm0,4h3v3H9Zm1-7H12.3L10,1.71Zm0,5h1V8H10Zm0,4h1V12H10Z"/></svg>

After

Width:  |  Height:  |  Size: 338 B

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#212121;}</style></defs><title>insights</title><path class="cls-1" d="M15,4V8H14V5.71L9.49,10.2l-2-2L2,13.71V14H15v1H1V1H2V12.29L7.49,6.8l2,2L13.28,5H11V4Z"/></svg>

After

Width:  |  Height:  |  Size: 282 B

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#fff;}</style></defs><title>insights_inverse</title><path class="cls-1" d="M15,4V8H14V5.71L9.49,10.2l-2-2L2,13.71V14H15v1H1V1H2V12.29L7.49,6.8l2,2L13.28,5H11V4Z"/></svg>

After

Width:  |  Height:  |  Size: 287 B

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><title>launch</title><path d="M12.8,8.4l1.07-1.07v6.4H0V4.13H10.67L9.6,5.2H1.07v7.47H12.8Zm0-6.4H16V5.2H14.93V3.82l-3.8,3.8-.76-.76,3.8-3.8H12.8Z"/></svg>

After

Width:  |  Height:  |  Size: 247 B

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#fff;}</style></defs><title>launch_inverse</title><path class="cls-1" d="M12.8,8.4l1.07-1.07v6.4H0V4.13H10.67L9.6,5.2H1.07v7.47H12.8Zm0-6.4H16V5.2H14.93V3.82l-3.8,3.8-.76-.76,3.8-3.8H12.8Z"/></svg>

After

Width:  |  Height:  |  Size: 315 B

View File

@@ -0,0 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<title>TemporaryDatabase</title>
<g>
<path d="M3.086,3.758a1.565,1.565,0,0,0,.492.445,3.788,3.788,0,0,0,.719.336,6.428,6.428,0,0,0,.848.234q.449.095.878.153t.817.082c.258.015.477.023.66.023s.4-.008.66-.023.53-.043.817-.082.579-.09.878-.153a6.428,6.428,0,0,0,.848-.234,3.842,3.842,0,0,0,.715-.332,1.6,1.6,0,0,0,.5-.449.555.555,0,0,0,.063-.11A.3.3,0,0,0,12,3.531a.462.462,0,0,0-.133-.312,1.7,1.7,0,0,0-.32-.274,3.616,3.616,0,0,0-.383-.222c-.13-.065-.234-.114-.312-.145a6.1,6.1,0,0,0-.79-.254q-.421-.1-.859-.172t-.871-.093c-.289-.018-.566-.028-.832-.028s-.543.01-.832.028-.58.049-.871.093-.578.1-.859.172a6.047,6.047,0,0,0-.79.254c-.078.031-.182.08-.312.145a3.616,3.616,0,0,0-.383.222,1.673,1.673,0,0,0-.32.274A.462.462,0,0,0,3,3.531a.3.3,0,0,0,.023.117A.555.555,0,0,0,3.086,3.758Z" fill="none"/>
<path d="M7.435,14.03c-.282,0-.6-.017-.951-.046a10.214,10.214,0,0,1-1.148-.16,7.424,7.424,0,0,1-1.106-.3,3.184,3.184,0,0,1-.894-.481,1.519,1.519,0,0,1-.223-.219A.436.436,0,0,1,3,12.531V5.016A5.236,5.236,0,0,0,4.023,5.5a8.163,8.163,0,0,0,1.149.312,10.5,10.5,0,0,0,1.191.168c.4.035.778.051,1.137.051s.738-.016,1.137-.051a10.368,10.368,0,0,0,1.187-.168A8.39,8.39,0,0,0,10.973,5.5,5.218,5.218,0,0,0,12,5.016V7a5.092,5.092,0,0,1,1,.1V3.531a1.333,1.333,0,0,0-.152-.625,1.97,1.97,0,0,0-.4-.515,3.518,3.518,0,0,0-1.039-.664,7.269,7.269,0,0,0-1.3-.418,10.78,10.78,0,0,0-1.367-.215c-.456-.041-.871-.063-1.246-.063-.245,0-.511.01-.8.028s-.583.048-.891.089-.617.1-.929.164a7.689,7.689,0,0,0-.9.254,5.562,5.562,0,0,0-.8.356,2.788,2.788,0,0,0-.637.469,2.164,2.164,0,0,0-.395.511A1.3,1.3,0,0,0,2,3.531v9a1.316,1.316,0,0,0,.176.676,2.049,2.049,0,0,0,.465.543,3.278,3.278,0,0,0,.656.426,6.65,6.65,0,0,0,.75.32,7.384,7.384,0,0,0,.75.227c.245.06.463.1.656.136a11.923,11.923,0,0,0,2.047.172c.177,0,.354-.005.532-.013A5.006,5.006,0,0,1,7.435,14.03ZM3.133,3.219a1.673,1.673,0,0,1,.32-.274,3.616,3.616,0,0,1,.383-.222c.13-.065.234-.114.312-.145a6.047,6.047,0,0,1,.79-.254q.422-.1.859-.172c.291-.044.582-.075.871-.093s.566-.028.832-.028.543.01.832.028.579.049.871.093.578.1.859.172a6.1,6.1,0,0,1,.79.254c.078.031.182.08.312.145a3.616,3.616,0,0,1,.383.222,1.7,1.7,0,0,1,.32.274A.462.462,0,0,1,12,3.531a.3.3,0,0,1-.023.117.555.555,0,0,1-.063.11,1.6,1.6,0,0,1-.5.449,3.842,3.842,0,0,1-.715.332,6.428,6.428,0,0,1-.848.234q-.449.095-.878.153t-.817.082q-.387.022-.66.023c-.183,0-.4-.008-.66-.023s-.53-.043-.817-.082-.579-.09-.878-.153A6.428,6.428,0,0,1,4.3,4.539,3.788,3.788,0,0,1,3.578,4.2a1.565,1.565,0,0,1-.492-.445.555.555,0,0,1-.063-.11A.3.3,0,0,1,3,3.531.462.462,0,0,1,3.133,3.219Z"/>
<polygon points="12 10 11 10 11 13 13.5 13 13.5 12 12 12 12 10"/>
<path d="M15.688,10.441a3.99,3.99,0,0,0-2.129-2.129,4.051,4.051,0,0,0-3.118,0,3.99,3.99,0,0,0-2.129,2.129,4.051,4.051,0,0,0,0,3.118,3.99,3.99,0,0,0,2.129,2.129,4.051,4.051,0,0,0,3.118,0,3.99,3.99,0,0,0,2.129-2.129,4.051,4.051,0,0,0,0-3.118Zm-.922,2.727a3.029,3.029,0,0,1-1.6,1.6,3.022,3.022,0,0,1-2.332,0,3.04,3.04,0,0,1-1.6-1.6,3.022,3.022,0,0,1,0-2.332,3.029,3.029,0,0,1,1.6-1.6,3.022,3.022,0,0,1,2.332,0,3.019,3.019,0,0,1,1.6,1.6,3.022,3.022,0,0,1,0,2.332Z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@@ -0,0 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<title>TemporaryDatabase_Inverse</title>
<g>
<path d="M3.086,3.758a1.565,1.565,0,0,0,.492.445,3.788,3.788,0,0,0,.719.336,6.428,6.428,0,0,0,.848.234q.449.095.878.153t.817.082c.258.015.477.023.66.023s.4-.008.66-.023.53-.043.817-.082.579-.09.878-.153a6.428,6.428,0,0,0,.848-.234,3.842,3.842,0,0,0,.715-.332,1.6,1.6,0,0,0,.5-.449.555.555,0,0,0,.063-.11A.3.3,0,0,0,12,3.531a.462.462,0,0,0-.133-.312,1.7,1.7,0,0,0-.32-.274,3.616,3.616,0,0,0-.383-.222c-.13-.065-.234-.114-.312-.145a6.1,6.1,0,0,0-.79-.254q-.421-.1-.859-.172t-.871-.093c-.289-.018-.566-.028-.832-.028s-.543.01-.832.028-.58.049-.871.093-.578.1-.859.172a6.047,6.047,0,0,0-.79.254c-.078.031-.182.08-.312.145a3.616,3.616,0,0,0-.383.222,1.673,1.673,0,0,0-.32.274A.462.462,0,0,0,3,3.531a.3.3,0,0,0,.023.117A.555.555,0,0,0,3.086,3.758Z" fill="#fff"/>
<path d="M7.435,14.03c-.282,0-.6-.017-.951-.046a10.214,10.214,0,0,1-1.148-.16,7.424,7.424,0,0,1-1.106-.3,3.184,3.184,0,0,1-.894-.481,1.519,1.519,0,0,1-.223-.219A.436.436,0,0,1,3,12.531V5.016A5.236,5.236,0,0,0,4.023,5.5a8.163,8.163,0,0,0,1.149.312,10.5,10.5,0,0,0,1.191.168c.4.035.778.051,1.137.051s.738-.016,1.137-.051a10.368,10.368,0,0,0,1.187-.168A8.39,8.39,0,0,0,10.973,5.5,5.218,5.218,0,0,0,12,5.016V7a5.092,5.092,0,0,1,1,.1V3.531a1.333,1.333,0,0,0-.152-.625,1.97,1.97,0,0,0-.4-.515,3.518,3.518,0,0,0-1.039-.664,7.269,7.269,0,0,0-1.3-.418,10.78,10.78,0,0,0-1.367-.215c-.456-.041-.871-.063-1.246-.063-.245,0-.511.01-.8.028s-.583.048-.891.089-.617.1-.929.164a7.689,7.689,0,0,0-.9.254,5.562,5.562,0,0,0-.8.356,2.788,2.788,0,0,0-.637.469,2.164,2.164,0,0,0-.395.511A1.3,1.3,0,0,0,2,3.531v9a1.316,1.316,0,0,0,.176.676,2.049,2.049,0,0,0,.465.543,3.278,3.278,0,0,0,.656.426,6.65,6.65,0,0,0,.75.32,7.384,7.384,0,0,0,.75.227c.245.06.463.1.656.136a11.923,11.923,0,0,0,2.047.172c.177,0,.354-.005.532-.013A5.006,5.006,0,0,1,7.435,14.03ZM3.133,3.219a1.673,1.673,0,0,1,.32-.274,3.616,3.616,0,0,1,.383-.222c.13-.065.234-.114.312-.145a6.047,6.047,0,0,1,.79-.254q.422-.1.859-.172c.291-.044.582-.075.871-.093s.566-.028.832-.028.543.01.832.028.579.049.871.093.578.1.859.172a6.1,6.1,0,0,1,.79.254c.078.031.182.08.312.145a3.616,3.616,0,0,1,.383.222,1.7,1.7,0,0,1,.32.274A.462.462,0,0,1,12,3.531a.3.3,0,0,1-.023.117.555.555,0,0,1-.063.11,1.6,1.6,0,0,1-.5.449,3.842,3.842,0,0,1-.715.332,6.428,6.428,0,0,1-.848.234q-.449.095-.878.153t-.817.082q-.387.022-.66.023c-.183,0-.4-.008-.66-.023s-.53-.043-.817-.082-.579-.09-.878-.153A6.428,6.428,0,0,1,4.3,4.539,3.788,3.788,0,0,1,3.578,4.2a1.565,1.565,0,0,1-.492-.445.555.555,0,0,1-.063-.11A.3.3,0,0,1,3,3.531.462.462,0,0,1,3.133,3.219Z" fill="#fff"/>
<polygon points="12 10 11 10 11 13 13.5 13 13.5 12 12 12 12 10" fill="#fff"/>
<path d="M15.688,10.441a3.99,3.99,0,0,0-2.129-2.129,4.051,4.051,0,0,0-3.118,0,3.99,3.99,0,0,0-2.129,2.129,4.051,4.051,0,0,0,0,3.118,3.99,3.99,0,0,0,2.129,2.129,4.051,4.051,0,0,0,3.118,0,3.99,3.99,0,0,0,2.129-2.129,4.051,4.051,0,0,0,0-3.118Zm-.922,2.727a3.029,3.029,0,0,1-1.6,1.6,3.022,3.022,0,0,1-2.332,0,3.04,3.04,0,0,1-1.6-1.6,3.022,3.022,0,0,1,0-2.332,3.029,3.029,0,0,1,1.6-1.6,3.022,3.022,0,0,1,2.332,0,3.019,3.019,0,0,1,1.6,1.6,3.022,3.022,0,0,1,0,2.332Z" fill="#fff"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@@ -0,0 +1,34 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<script type="text/javascript">
window.onload = function () {
document.getElementById('frame').src = "{{{url}}}";
};
</script>
<style>
body {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
overflow: hidden;
}
#frame {
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
}
</style>
</head>
<body>
<iframe id="frame" width="100%" height="100%" frameborder="0"></iframe>
</body>
</html>

View File

@@ -0,0 +1,19 @@
--SQL script to grab allocation contention from histogram
Use tempdb
DECLARE @target_data XML;
SELECT @target_data = CAST(t.target_data AS XML)
FROM sys.dm_xe_sessions AS s
JOIN sys.dm_xe_session_targets AS t
ON t.event_session_address = s.address
WHERE s.name = N'PageContention' and t.target_name = N'histogram';
with wait_stats as
(
SELECT
n.value('(value)[1]','bigint') AS id,
n.value('(@count)[1]', 'bigint') AS [Count]
FROM @target_data.nodes('//HistogramTarget/Slot') AS q(n)
)
SELECT [dbo].[mapPageType](wait_stats.id), wait_stats.Count
FROM wait_stats

View File

@@ -0,0 +1,22 @@
--SQL script to grab metadata contention from histogram
Use tempdb
DECLARE @target_data XML;
SELECT @target_data = CAST(t.target_data AS XML)
FROM sys.dm_xe_sessions AS s
JOIN sys.dm_xe_session_targets AS t
ON t.event_session_address = s.address
WHERE s.name = N'ObjectContention' and t.target_name = N'histogram';
with wait_stats as
(
SELECT
n.value('(value)[1]','bigint') AS alloc_unit_id,
n.value('(@count)[1]', 'bigint') AS [Count]
FROM @target_data.nodes('//HistogramTarget/Slot') AS q(n)
)
SELECT objects.id, SUM(objects.count) as [Count] FROM
(SELECT [dbo].[isSystemTable](wait_stats.alloc_unit_id) AS id, wait_stats.Count AS [count]
FROM wait_stats
WHERE [dbo].[isSystemTable](wait_stats.alloc_unit_id) not in (0, 99)) AS objects
GROUP BY objects.id

View File

@@ -0,0 +1,80 @@
--Starts the XEvents sessions and creates the functions needed to find object id and give name to the page types
use tempdb
BEGIN TRY
IF NOT EXISTS (SELECT * FROM sys.dm_xe_sessions WHERE name = 'PageContention')
BEGIN
CREATE EVENT SESSION [PageContention] ON SERVER
ADD EVENT latch_suspend_end(
WHERE class = 28
AND (page_type_id = 8
OR page_type_id = 9
OR page_type_id = 11))
ADD TARGET package0.histogram(SET slots=16, filtering_event_name=N'latch_suspend_end', source=N'page_type_id', source_type=(0))
ALTER EVENT SESSION [PageContention] ON SERVER
STATE = START
END
IF NOT EXISTS (SELECT * FROM sys.dm_xe_sessions WHERE name = 'ObjectContention')
BEGIN
CREATE EVENT SESSION [ObjectContention] ON SERVER
ADD EVENT latch_suspend_end(
WHERE class = 28
AND database_id = 2)
ADD TARGET package0.histogram(SET slots=256, filtering_event_name=N'latch_suspend_end', source=N'page_alloc_unit_id', source_type=(0))
ALTER EVENT SESSION [ObjectContention] ON SERVER
STATE = START
END
END TRY
BEGIN CATCH
PRINT 'XEvent fields not supported'
END CATCH
GO
IF OBJECT_ID(N'[dbo].[isSystemTable]', N'FN') IS NOT NULL
DROP FUNCTION [dbo].[isSystemTable]
GO
CREATE FUNCTION [dbo].[isSystemTable] (@alloc bigint)
RETURNS bigint
AS BEGIN
DECLARE @index BIGINT;
DECLARE @objId BIGINT;
SELECT @index =
CONVERT (BIGINT,
CONVERT (FLOAT, @alloc)
* (1 / POWER (2.0, 48))
);
SELECT @objId =
CONVERT (BIGINT,
CONVERT (FLOAT, @alloc - (@index * CONVERT (BIGINT, POWER (2.0, 48))))
* (1 / POWER (2.0, 16))
);
IF (@objId > 0 AND @objId <= 100 AND @index <= 255)
return @objId
return 0
END
GO
IF OBJECT_ID(N'[dbo].[mapPageType]', N'FN') IS NOT NULL
DROP FUNCTION [dbo].[mapPageType]
GO
CREATE FUNCTION [dbo].[mapPageType] (@pageTypeId bigint)
RETURNS varchar(20)
AS BEGIN
IF @pageTypeId = 8
return 'GAM_PAGE'
ELSE IF @pageTypeId = 9
return 'SGAM_PAGE'
ELSE IF @pageTypeId = 11
return 'PFS_PAGE'
return ''
END
GO

View File

@@ -0,0 +1,3 @@
--Stops the XEvent Sessions
DROP EVENT SESSION [PageContention] ON SERVER
DROP EVENT SESSION [ObjectContention] ON SERVER

View File

@@ -0,0 +1,33 @@
--SQL script to grab all contention
Use tempdb
DECLARE @pc XML;
DECLARE @obj XML;
SELECT @pc = CAST(t.target_data AS XML)
FROM sys.dm_xe_sessions AS s
JOIN sys.dm_xe_session_targets AS t
ON t.event_session_address = s.address
WHERE s.name = N'PageContention' and t.target_name = N'histogram';
SELECT @obj = CAST(t.target_data AS XML)
FROM sys.dm_xe_sessions AS s
JOIN sys.dm_xe_session_targets AS t
ON t.event_session_address = s.address
WHERE s.name = N'ObjectContention' and t.target_name = N'histogram';
SELECT 'Metadata Contention' AS wait_type, SUM(obj.count) AS [Count]
FROM (
SELECT
n.value('(value)[1]','bigint') AS alloc_unit_id,
n.value('(@count)[1]', 'bigint') AS [count]
FROM @obj.nodes('//HistogramTarget/Slot') AS q(n)
) obj
WHERE [dbo].[isSystemTable](obj.alloc_unit_id) not in (0, 99)
UNION
SELECT 'Allocation Contention' AS wait_type, SUM(pc.count) AS [Count]
FROM
(
SELECT
n.value('(@count)[1]', 'bigint') AS [count]
FROM @pc.nodes('//HistogramTarget/Slot') AS q(n)
) pc

View File

@@ -0,0 +1,95 @@
WITH [Waits] AS
(
SELECT wait_type, resource_description, count(resource_description) AS RESOURCE_USE, COUNT(*) AS TOTAL FROM sys.dm_os_waiting_tasks
WHERE [wait_type] NOT IN (
-- These wait types are almost 100% never a problem and so they are
-- filtered out to avoid them skewing the results. Click on the URL
-- for more information.
N'BROKER_EVENTHANDLER', -- https://www.sqlskills.com/help/waits/BROKER_EVENTHANDLER
N'BROKER_RECEIVE_WAITFOR', -- https://www.sqlskills.com/help/waits/BROKER_RECEIVE_WAITFOR
N'BROKER_TASK_STOP', -- https://www.sqlskills.com/help/waits/BROKER_TASK_STOP
N'BROKER_TO_FLUSH', -- https://www.sqlskills.com/help/waits/BROKER_TO_FLUSH
N'BROKER_TRANSMITTER', -- https://www.sqlskills.com/help/waits/BROKER_TRANSMITTER
N'CHECKPOINT_QUEUE', -- https://www.sqlskills.com/help/waits/CHECKPOINT_QUEUE
N'CHKPT', -- https://www.sqlskills.com/help/waits/CHKPT
N'CLR_AUTO_EVENT', -- https://www.sqlskills.com/help/waits/CLR_AUTO_EVENT
N'CLR_MANUAL_EVENT', -- https://www.sqlskills.com/help/waits/CLR_MANUAL_EVENT
N'CLR_SEMAPHORE', -- https://www.sqlskills.com/help/waits/CLR_SEMAPHORE
N'CXCONSUMER', -- https://www.sqlskills.com/help/waits/CXCONSUMER
-- Maybe comment these four out if you have mirroring issues
N'DBMIRROR_DBM_EVENT', -- https://www.sqlskills.com/help/waits/DBMIRROR_DBM_EVENT
N'DBMIRROR_EVENTS_QUEUE', -- https://www.sqlskills.com/help/waits/DBMIRROR_EVENTS_QUEUE
N'DBMIRROR_WORKER_QUEUE', -- https://www.sqlskills.com/help/waits/DBMIRROR_WORKER_QUEUE
N'DBMIRRORING_CMD', -- https://www.sqlskills.com/help/waits/DBMIRRORING_CMD
N'DIRTY_PAGE_POLL', -- https://www.sqlskills.com/help/waits/DIRTY_PAGE_POLL
N'DISPATCHER_QUEUE_SEMAPHORE', -- https://www.sqlskills.com/help/waits/DISPATCHER_QUEUE_SEMAPHORE
N'EXECSYNC', -- https://www.sqlskills.com/help/waits/EXECSYNC
N'FSAGENT', -- https://www.sqlskills.com/help/waits/FSAGENT
N'FT_IFTS_SCHEDULER_IDLE_WAIT', -- https://www.sqlskills.com/help/waits/FT_IFTS_SCHEDULER_IDLE_WAIT
N'FT_IFTSHC_MUTEX', -- https://www.sqlskills.com/help/waits/FT_IFTSHC_MUTEX
-- Maybe comment these six out if you have AG issues
N'HADR_CLUSAPI_CALL', -- https://www.sqlskills.com/help/waits/HADR_CLUSAPI_CALL
N'HADR_FILESTREAM_IOMGR_IOCOMPLETION', -- https://www.sqlskills.com/help/waits/HADR_FILESTREAM_IOMGR_IOCOMPLETION
N'HADR_LOGCAPTURE_WAIT', -- https://www.sqlskills.com/help/waits/HADR_LOGCAPTURE_WAIT
N'HADR_NOTIFICATION_DEQUEUE', -- https://www.sqlskills.com/help/waits/HADR_NOTIFICATION_DEQUEUE
N'HADR_TIMER_TASK', -- https://www.sqlskills.com/help/waits/HADR_TIMER_TASK
N'HADR_WORK_QUEUE', -- https://www.sqlskills.com/help/waits/HADR_WORK_QUEUE
N'KSOURCE_WAKEUP', -- https://www.sqlskills.com/help/waits/KSOURCE_WAKEUP
N'LAZYWRITER_SLEEP', -- https://www.sqlskills.com/help/waits/LAZYWRITER_SLEEP
N'LOGMGR_QUEUE', -- https://www.sqlskills.com/help/waits/LOGMGR_QUEUE
N'MEMORY_ALLOCATION_EXT', -- https://www.sqlskills.com/help/waits/MEMORY_ALLOCATION_EXT
N'ONDEMAND_TASK_QUEUE', -- https://www.sqlskills.com/help/waits/ONDEMAND_TASK_QUEUE
N'PARALLEL_REDO_DRAIN_WORKER', -- https://www.sqlskills.com/help/waits/PARALLEL_REDO_DRAIN_WORKER
N'PARALLEL_REDO_LOG_CACHE', -- https://www.sqlskills.com/help/waits/PARALLEL_REDO_LOG_CACHE
N'PARALLEL_REDO_TRAN_LIST', -- https://www.sqlskills.com/help/waits/PARALLEL_REDO_TRAN_LIST
N'PARALLEL_REDO_WORKER_SYNC', -- https://www.sqlskills.com/help/waits/PARALLEL_REDO_WORKER_SYNC
N'PARALLEL_REDO_WORKER_WAIT_WORK', -- https://www.sqlskills.com/help/waits/PARALLEL_REDO_WORKER_WAIT_WORK
N'PREEMPTIVE_OS_FLUSHFILEBUFFERS', -- https://www.sqlskills.com/help/waits/PREEMPTIVE_OS_FLUSHFILEBUFFERS
N'PREEMPTIVE_XE_GETTARGETSTATE', -- https://www.sqlskills.com/help/waits/PREEMPTIVE_XE_GETTARGETSTATE
N'PWAIT_ALL_COMPONENTS_INITIALIZED', -- https://www.sqlskills.com/help/waits/PWAIT_ALL_COMPONENTS_INITIALIZED
N'PWAIT_DIRECTLOGCONSUMER_GETNEXT', -- https://www.sqlskills.com/help/waits/PWAIT_DIRECTLOGCONSUMER_GETNEXT
N'QDS_PERSIST_TASK_MAIN_LOOP_SLEEP', -- https://www.sqlskills.com/help/waits/QDS_PERSIST_TASK_MAIN_LOOP_SLEEP
N'QDS_ASYNC_QUEUE', -- https://www.sqlskills.com/help/waits/QDS_ASYNC_QUEUE
N'QDS_CLEANUP_STALE_QUERIES_TASK_MAIN_LOOP_SLEEP',
-- https://www.sqlskills.com/help/waits/QDS_CLEANUP_STALE_QUERIES_TASK_MAIN_LOOP_SLEEP
N'QDS_SHUTDOWN_QUEUE', -- https://www.sqlskills.com/help/waits/QDS_SHUTDOWN_QUEUE
N'REDO_THREAD_PENDING_WORK', -- https://www.sqlskills.com/help/waits/REDO_THREAD_PENDING_WORK
N'REQUEST_FOR_DEADLOCK_SEARCH', -- https://www.sqlskills.com/help/waits/REQUEST_FOR_DEADLOCK_SEARCH
N'RESOURCE_QUEUE', -- https://www.sqlskills.com/help/waits/RESOURCE_QUEUE
N'SERVER_IDLE_CHECK', -- https://www.sqlskills.com/help/waits/SERVER_IDLE_CHECK
N'SLEEP_BPOOL_FLUSH', -- https://www.sqlskills.com/help/waits/SLEEP_BPOOL_FLUSH
N'SLEEP_DBSTARTUP', -- https://www.sqlskills.com/help/waits/SLEEP_DBSTARTUP
N'SLEEP_DCOMSTARTUP', -- https://www.sqlskills.com/help/waits/SLEEP_DCOMSTARTUP
N'SLEEP_MASTERDBREADY', -- https://www.sqlskills.com/help/waits/SLEEP_MASTERDBREADY
N'SLEEP_MASTERMDREADY', -- https://www.sqlskills.com/help/waits/SLEEP_MASTERMDREADY
N'SLEEP_MASTERUPGRADED', -- https://www.sqlskills.com/help/waits/SLEEP_MASTERUPGRADED
N'SLEEP_MSDBSTARTUP', -- https://www.sqlskills.com/help/waits/SLEEP_MSDBSTARTUP
N'SLEEP_SYSTEMTASK', -- https://www.sqlskills.com/help/waits/SLEEP_SYSTEMTASK
N'SLEEP_TASK', -- https://www.sqlskills.com/help/waits/SLEEP_TASK
N'SLEEP_TEMPDBSTARTUP', -- https://www.sqlskills.com/help/waits/SLEEP_TEMPDBSTARTUP
N'SNI_HTTP_ACCEPT', -- https://www.sqlskills.com/help/waits/SNI_HTTP_ACCEPT
N'SOS_WORK_DISPATCHER', -- https://www.sqlskills.com/help/waits/SOS_WORK_DISPATCHER
N'SP_SERVER_DIAGNOSTICS_SLEEP', -- https://www.sqlskills.com/help/waits/SP_SERVER_DIAGNOSTICS_SLEEP
N'SQLTRACE_BUFFER_FLUSH', -- https://www.sqlskills.com/help/waits/SQLTRACE_BUFFER_FLUSH
N'SQLTRACE_INCREMENTAL_FLUSH_SLEEP', -- https://www.sqlskills.com/help/waits/SQLTRACE_INCREMENTAL_FLUSH_SLEEP
N'SQLTRACE_WAIT_ENTRIES', -- https://www.sqlskills.com/help/waits/SQLTRACE_WAIT_ENTRIES
N'VDI_CLIENT_OTHER', -- https://www.sqlskills.com/help/waits/VDI_CLIENT_OTHER
N'WAIT_FOR_RESULTS', -- https://www.sqlskills.com/help/waits/WAIT_FOR_RESULTS
N'WAITFOR', -- https://www.sqlskills.com/help/waits/WAITFOR
N'WAITFOR_TASKSHUTDOWN', -- https://www.sqlskills.com/help/waits/WAITFOR_TASKSHUTDOWN
N'WAIT_XTP_RECOVERY', -- https://www.sqlskills.com/help/waits/WAIT_XTP_RECOVERY
N'WAIT_XTP_HOST_WAIT', -- https://www.sqlskills.com/help/waits/WAIT_XTP_HOST_WAIT
N'WAIT_XTP_OFFLINE_CKPT_NEW_LOG', -- https://www.sqlskills.com/help/waits/WAIT_XTP_OFFLINE_CKPT_NEW_LOG
N'WAIT_XTP_CKPT_CLOSE', -- https://www.sqlskills.com/help/waits/WAIT_XTP_CKPT_CLOSE
N'XE_DISPATCHER_JOIN', -- https://www.sqlskills.com/help/waits/XE_DISPATCHER_JOIN
N'XE_DISPATCHER_WAIT', -- https://www.sqlskills.com/help/waits/XE_DISPATCHER_WAIT
N'XE_TIMER_EVENT' -- https://www.sqlskills.com/help/waits/XE_TIMER_EVENT
)
GROUP BY sys.dm_os_waiting_tasks.resource_description, wait_type
)
SELECT [TASKS].[wait_type] AS [WaitType], [TASKS].[resource_description] as [Resource], 100.0 * [TASKS].[TOTAL] / SUM ([TASKS].[TOTAL]) OVER () AS [Percentage] FROM Waits AS TASKS GROUP BY [TASKS].[wait_type], [TASKS].[resource_description], [TASKS].[TOTAL]

View File

@@ -5,21 +5,42 @@
'use strict';
import * as vscode from 'vscode';
import * as path from 'path';
import * as fs from 'fs-extra';
import * as handlebars from 'handlebars';
import * as Constants from './constants';
import * as LocalizedConstants from './localizedConstants';
/**
* Helper to log messages to the developer console if enabled
* @param msg Message to log to the console
*/
export function logDebug(msg: any): void {
let config = vscode.workspace.getConfiguration(Constants.extensionConfigSectionName);
let logDebugInfo = config[Constants.configLogDebugInfo];
if (logDebugInfo === true) {
let currentTime = new Date().toLocaleTimeString();
let outputMsg = '[' + currentTime + ']: ' + msg ? msg.toString() : '';
console.log(outputMsg);
}
let config = vscode.workspace.getConfiguration(Constants.extensionConfigSectionName);
let logDebugInfo = config[Constants.configLogDebugInfo];
if (logDebugInfo === true) {
let currentTime = new Date().toLocaleTimeString();
let outputMsg = '[' + currentTime + ']: ' + msg ? msg.toString() : '';
console.log(outputMsg);
}
}
export function renderTemplateHtml(extensionPath: string, templateName: string, templateValues: object): Thenable<string> {
let templatePath = path.join(extensionPath, 'resources', templateName);
// 1) Read the template from the disk
// 2) Compile it as a handlebars template and render the HTML
// 3) On failure, return a simple string as an error
return fs.readFile(templatePath, 'utf-8')
.then(templateText => {
let template = handlebars.compile(templateText);
return template(templateValues);
})
.then(
undefined,
error => {
logDebug(error);
return LocalizedConstants.msgErrorLoadingTab;
}
);
}

View File

@@ -18,67 +18,67 @@ let tsProject = ts.createProject('tsconfig.json');
// GULP TASKS //////////////////////////////////////////////////////////////
gulp.task('clean', function(done) {
return del('out', done);
gulp.task('clean', function (done) {
return del('out', done);
});
gulp.task('lint', () => {
return gulp.src([
config.paths.project.root + '/src/**/*.ts',
config.paths.project.root + '/test/**/*.ts'
])
.pipe((tslint({
formatter: "verbose"
})))
.pipe(tslint.report());
return gulp.src([
config.paths.project.root + '/src/**/*.ts',
config.paths.project.root + '/test/**/*.ts'
])
.pipe((tslint({
formatter: "verbose"
})))
.pipe(tslint.report());
});
gulp.task('compile:src', function(done) {
gulp.src([
config.paths.project.root + '/src/**/*.sql',
config.paths.project.root + '/src/**/*.svg',
config.paths.project.root + '/src/**/*.html'
]).pipe(gulp.dest('out/src/'));
gulp.task('compile:src', function (done) {
gulp.src([
config.paths.project.root + '/src/**/*.sql',
config.paths.project.root + '/src/**/*.svg',
config.paths.project.root + '/src/**/*.html'
]).pipe(gulp.dest('out/src/'));
let srcFiles = [
config.paths.project.root + '/src/**/*.ts',
config.paths.project.root + '/src/**/*.js',
config.paths.project.root + '/typings/**/*.ts'
];
let srcFiles = [
config.paths.project.root + '/src/**/*.ts',
config.paths.project.root + '/src/**/*.js',
config.paths.project.root + '/typings/**/*.ts'
];
return gulp.src(srcFiles)
.pipe(srcmap.init())
.pipe(tsProject())
.on('error', function() {
if(process.env.BUILDMACHINE) {
done('Failed to compile extension source, see above.');
process.exit(1);
}
})
// TODO: Reinstate localization code
// .pipe(nls.rewriteLocalizeCalls())
// .pipe(nls.createAdditionalLanguageFiles(nls.coreLanguages, config.paths.project.root + '/localization/i18n', undefined, false))
.pipe(srcmap.write('.', { sourceRoot: function(file) { return file.cwd + '/src'; }}))
.pipe(gulp.dest('out/src/'));
return gulp.src(srcFiles)
.pipe(srcmap.init())
.pipe(tsProject())
.on('error', function () {
if (process.env.BUILDMACHINE) {
done('Failed to compile extension source, see above.');
process.exit(1);
}
})
// TODO: Reinstate localization code
// .pipe(nls.rewriteLocalizeCalls())
// .pipe(nls.createAdditionalLanguageFiles(nls.coreLanguages, config.paths.project.root + '/localization/i18n', undefined, false))
.pipe(srcmap.write('.', { sourceRoot: function (file) { return file.cwd + '/src'; } }))
.pipe(gulp.dest('out/src/'));
});
gulp.task('compile:test', function(done) {
let srcFiles = [
config.paths.project.root + '/test/**/*.ts',
config.paths.project.root + '/typings/**/*.ts'
];
gulp.task('compile:test', function (done) {
let srcFiles = [
config.paths.project.root + '/test/**/*.ts',
config.paths.project.root + '/typings/**/*.ts'
];
return gulp.src(srcFiles)
.pipe(srcmap.init())
.pipe(tsProject())
.on('error', function() {
if(process.env.BUILDMACHINE) {
done('Failed to compile test source, see above.');
process.exit(1);
}
})
.pipe(srcmap.write('.', {sourceRoot: function(file) { return file.cwd + '/test'; }}))
.pipe(gulp.dest('out/test/'));
return gulp.src(srcFiles)
.pipe(srcmap.init())
.pipe(tsProject())
.on('error', function () {
if (process.env.BUILDMACHINE) {
done('Failed to compile test source, see above.');
process.exit(1);
}
})
.pipe(srcmap.write('.', { sourceRoot: function (file) { return file.cwd + '/test'; } }))
.pipe(gulp.dest('out/test/'));
});
// COMPOSED GULP TASKS /////////////////////////////////////////////////////
@@ -86,33 +86,38 @@ gulp.task("compile", gulp.series("compile:src", "compile:test"));
gulp.task("build", gulp.series("clean", "lint", "compile"));
gulp.task("watch", function() {
gulp.watch([config.paths.project.root + '/src/**/*',
config.paths.project.root + '/test/**/*.ts'],
gulp.series('build'))
gulp.task("watch", function () {
gulp.watch([config.paths.project.root + '/src/**/*',
config.paths.project.root + '/test/**/*.ts'],
gulp.series('build'));
});
gulp.task('test', (done) => {
let workspace = process.env['WORKSPACE'];
if (!workspace) {
workspace = process.cwd();
}
process.env.JUNIT_REPORT_PATH = workspace + '/test-reports/ext_xunit.xml';
let workspace = process.env['WORKSPACE'];
if (!workspace) {
workspace = process.cwd();
}
process.env.JUNIT_REPORT_PATH = workspace + '/test-reports/ext_xunit.xml';
let azuredatastudioPath = 'azuredatastudio';
if (process.env['SQLOPS_DEV']) {
let suffix = os.platform === 'win32' ? 'bat' : 'sh';
azuredatastudioPath = `${process.env['SQLOPS_DEV']}/scripts/sql-cli.${suffix}`;
}
console.log(`Using SQLOPS Path of ${azuredatastudioPath}`);
let azuredatastudioPath = 'azuredatastudio';
if (process.env['SQLOPS_DEV']) {
let suffix = os.platform === 'win32' ? 'bat' : 'sh';
azuredatastudioPath = `${process.env['SQLOPS_DEV']}/scripts/sql-cli.${suffix}`;
}
console.log(`Using SQLOPS Path of ${azuredatastudioPath}`);
cproc.exec(`${azuredatastudioPath} --extensionDevelopmentPath="${workspace}" --extensionTestsPath="${workspace}/out/test" --verbose`, (error, stdout, stderr) => {
if (error) {
console.error(`exec error: ${error}`);
process.exit(1);
}
console.log(`stdout: ${stdout}`);
console.log(`stderr: ${stderr}`);
done();
});
cproc.exec(`${azuredatastudioPath} --extensionDevelopmentPath="${workspace}" --extensionTestsPath="${workspace}/out/test" --verbose`, (error, stdout, stderr) => {
if (error) {
console.error(`exec error: ${error}`);
process.exit(1);
}
console.log(`stdout: ${stdout}`);
console.log(`stderr: ${stderr}`);
done();
});
});
gulp.task('copytypings', function () {
return gulp.src(config.paths.project.root + '/../../src/sql/sqlops.proposed.d.ts')
.pipe(gulp.dest('typings/'));
});

View File

@@ -6,4 +6,4 @@ node_modules
.DS_Store
.idea
test-reports/**
typings/sqlops.proposed.d.ts
typings/azdata.d.ts

View File

@@ -9,7 +9,7 @@
// - open Azure Data Studio
// - run the command "Install 'azuredatastudio' command in PATH"
{
"version": "0.2.0",
"version": "0.2.0",
"configurations": [
{
@@ -21,37 +21,37 @@
"--extensionDevelopmentPath=${workspaceFolder}"
]
},
{
"type": "node",
"request": "attach",
"name": "Attach to Ops Studio",
"protocol": "inspector",
"port": 5870,
"restart": true,
{
"type": "node",
"request": "attach",
"name": "Attach to Azure Data Studio",
"protocol": "inspector",
"port": 5870,
"restart": true,
"sourceMaps": true,
"outFiles": [
"${workspaceRoot}/out/**/*.js"
],
"outFiles": [
"${workspaceRoot}/out/**/*.js"
],
"preLaunchTask": "",
"timeout": 25000
},
},
{
"name": "Debug in enlistment",
"type": "sqlopsExtensionHost",
"type": "azuredatastudioExtensionHost",
"request": "launch",
"windows": {
"runtimeExecutable": "${workspaceFolder}/../../scripts/sql.bat"
},
"osx": {
"runtimeExecutable": "${workspaceFolder}/../../scripts/sql.sh"
},
"linux": {
"runtimeExecutable": "${workspaceFolder}/../../scripts/sql.sh"
},
"windows": {
"runtimeExecutable": "${workspaceFolder}/../../scripts/sql.bat"
},
"osx": {
"runtimeExecutable": "${workspaceFolder}/../../scripts/sql.sh"
},
"linux": {
"runtimeExecutable": "${workspaceFolder}/../../scripts/sql.sh"
},
"args": [
"--extensionDevelopmentPath=${workspaceFolder}"
],
"timeout": 20000
}
]
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -2,12 +2,12 @@
"name": "whoisactive",
"displayName": "whoisactive",
"description": "sp_whoisactive for Azure Data Studio",
"version": "0.1.1",
"version": "0.1.2",
"publisher": "Microsoft",
"preview": true,
"engines": {
"vscode": "^1.26.0",
"azdata": "*"
"azdata": "^1.11.0"
},
"icon": "images/sqlserver.png",
"license": "SEE LICENSE IN LICENSE.txt",
@@ -241,17 +241,17 @@
"compile": "gulp compile",
"watch": "gulp watch",
"typings": "gulp copytypings",
"postinstall": "node ./node_modules/vscode/bin/install && node ./node_modules/sqlops/bin/install && gulp copytypings"
"postinstall": "node ./node_modules/vscode/bin/install && node ./node_modules/azdata/bin/install && gulp copytypings"
},
"dependencies": {
"fs-extra": "^5.0.0",
"handlebars": "^4.0.11",
"openurl": "^1.1.1",
"sqlops": "github:anthonydresser/sqlops-extension-sqlops"
"openurl": "^1.1.1"
},
"devDependencies": {
"@types/mocha": "^2.2.42",
"@types/node": "^7.0.43",
"azdata": "github:microsoft/azdata-extension-azdata",
"child-process-promise": "^2.2.1",
"del": "^3.0.0",
"gulp": "^4.0.0",

View File

@@ -1,4 +1,3 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
@@ -9,18 +8,18 @@
import * as vscode from 'vscode';
export default abstract class ControllerBase implements vscode.Disposable {
protected _context: vscode.ExtensionContext;
protected _context: vscode.ExtensionContext;
public constructor(context: vscode.ExtensionContext) {
this._context = context;
}
public constructor(context: vscode.ExtensionContext) {
this._context = context;
}
abstract activate(): Promise<boolean>;
abstract activate(): Promise<boolean>;
abstract deactivate(): void;
abstract deactivate(): void;
public dispose(): void {
this.deactivate();
}
public dispose(): void {
this.deactivate();
}
}

View File

@@ -5,7 +5,7 @@
'use strict';
import * as sqlops from 'sqlops';
import * as azdata from 'azdata';
import * as Utils from '../utils';
import ControllerBase from './controllerBase';
import * as fs from 'fs';
@@ -19,35 +19,35 @@ import * as openurl from 'openurl';
*/
export default class MainController extends ControllerBase {
public apiWrapper;
// PUBLIC METHODS //////////////////////////////////////////////////////
/**
* Deactivates the extension
*/
public deactivate(): void {
Utils.logDebug('Main controller deactivated');
}
public apiWrapper;
// PUBLIC METHODS //////////////////////////////////////////////////////
/**
* Deactivates the extension
*/
public deactivate(): void {
Utils.logDebug('Main controller deactivated');
}
public activate(): Promise<boolean> {
sqlops.tasks.registerTask('sp_whoisactive.install', e => this.openurl('http://whoisactive.com/downloads/'));
sqlops.tasks.registerTask('sp_whoisactive.documentation', e => this.openurl('http://whoisactive.com/docs/'));
sqlops.tasks.registerTask('sp_whoisactive.findBlockLeaders', e => this.onExecute(e, 'findBlockLeaders.sql'));
sqlops.tasks.registerTask('sp_whoisactive.getPlans', e => this.onExecute(e, 'getPlans.sql'));
public activate(): Promise<boolean> {
azdata.tasks.registerTask('sp_whoisactive.install', e => this.openurl('http://whoisactive.com/downloads/'));
azdata.tasks.registerTask('sp_whoisactive.documentation', e => this.openurl('http://whoisactive.com/docs/'));
azdata.tasks.registerTask('sp_whoisactive.findBlockLeaders', e => this.onExecute(e, 'findBlockLeaders.sql'));
azdata.tasks.registerTask('sp_whoisactive.getPlans', e => this.onExecute(e, 'getPlans.sql'));
return Promise.resolve(true);
}
return Promise.resolve(true);
}
private openurl(link: string): void {
openurl.open(link);
}
private openurl(link: string): void {
openurl.open(link);
}
private onExecute(connection: sqlops.IConnectionProfile, fileName: string): void {
let sqlContent = fs.readFileSync(path.join(__dirname, '..', 'sql', fileName)).toString();
vscode.workspace.openTextDocument({language: 'sql', content: sqlContent}).then(doc => {
vscode.window.showTextDocument(doc, vscode.ViewColumn.Active, false).then(() => {
let filePath = doc.uri.toString();
sqlops.queryeditor.connect(filePath, connection.id).then(() => sqlops.queryeditor.runQuery(filePath));
});
});
}
private onExecute(connection: azdata.IConnectionProfile, fileName: string): void {
let sqlContent = fs.readFileSync(path.join(__dirname, '..', 'sql', fileName)).toString();
vscode.workspace.openTextDocument({language: 'sql', content: sqlContent}).then(doc => {
vscode.window.showTextDocument(doc, vscode.ViewColumn.Active, false).then(() => {
let filePath = doc.uri.toString();
azdata.queryeditor.connect(filePath, connection.id).then(() => azdata.queryeditor.runQuery(filePath));
});
});
}
}

View File

@@ -12,6 +12,7 @@ let tslint = require('gulp-tslint');
let ts = require('gulp-typescript');
let cproc = require('child_process');
let os = require('os');
let path = require('path');
let config = require('./config');
let tsProject = ts.createProject('tsconfig.json');
@@ -19,66 +20,66 @@ let tsProject = ts.createProject('tsconfig.json');
// GULP TASKS //////////////////////////////////////////////////////////////
gulp.task('clean', function(done) {
return del('out', done);
return del('out', done);
});
gulp.task('lint', () => {
return gulp.src([
config.paths.project.root + '/src/**/*.ts',
config.paths.project.root + '/test/**/*.ts'
])
.pipe((tslint({
formatter: "verbose"
})))
.pipe(tslint.report());
return gulp.src([
config.paths.project.root + '/src/**/*.ts',
config.paths.project.root + '/test/**/*.ts'
])
.pipe((tslint({
formatter: "verbose"
})))
.pipe(tslint.report());
});
gulp.task('compile:src', function(done) {
gulp.src([
config.paths.project.root + '/src/**/*.sql',
config.paths.project.root + '/src/**/*.svg',
config.paths.project.root + '/src/**/*.html'
]).pipe(gulp.dest('out/src/'));
gulp.src([
config.paths.project.root + '/src/**/*.sql',
config.paths.project.root + '/src/**/*.svg',
config.paths.project.root + '/src/**/*.html'
]).pipe(gulp.dest('out/src/'));
let srcFiles = [
config.paths.project.root + '/src/**/*.ts',
config.paths.project.root + '/src/**/*.js',
config.paths.project.root + '/typings/**/*.ts'
];
let srcFiles = [
config.paths.project.root + '/src/**/*.ts',
config.paths.project.root + '/src/**/*.js',
config.paths.project.root + '/typings/**/*.ts'
];
return gulp.src(srcFiles)
.pipe(srcmap.init())
.pipe(tsProject())
.on('error', function() {
if(process.env.BUILDMACHINE) {
done('Failed to compile extension source, see above.');
process.exit(1);
}
})
// TODO: Reinstate localization code
// .pipe(nls.rewriteLocalizeCalls())
// .pipe(nls.createAdditionalLanguageFiles(nls.coreLanguages, config.paths.project.root + '/localization/i18n', undefined, false))
.pipe(srcmap.write('.', { sourceRoot: function(file) { return file.cwd + '/src'; }}))
.pipe(gulp.dest('out/src/'));
return gulp.src(srcFiles)
.pipe(srcmap.init())
.pipe(tsProject())
.on('error', function() {
if(process.env.BUILDMACHINE) {
done('Failed to compile extension source, see above.');
process.exit(1);
}
})
// TODO: Reinstate localization code
// .pipe(nls.rewriteLocalizeCalls())
// .pipe(nls.createAdditionalLanguageFiles(nls.coreLanguages, config.paths.project.root + '/localization/i18n', undefined, false))
.pipe(srcmap.write('.', { sourceRoot: function(file) { return file.cwd + '/src'; }}))
.pipe(gulp.dest('out/src/'));
});
gulp.task('compile:test', function(done) {
let srcFiles = [
config.paths.project.root + '/test/**/*.ts',
config.paths.project.root + '/typings/**/*.ts'
];
let srcFiles = [
config.paths.project.root + '/test/**/*.ts',
config.paths.project.root + '/typings/**/*.ts'
];
return gulp.src(srcFiles)
.pipe(srcmap.init())
.pipe(tsProject())
.on('error', function() {
if(process.env.BUILDMACHINE) {
done('Failed to compile test source, see above.');
process.exit(1);
}
})
.pipe(srcmap.write('.', {sourceRoot: function(file) { return file.cwd + '/test'; }}))
.pipe(gulp.dest('out/test/'));
return gulp.src(srcFiles)
.pipe(srcmap.init())
.pipe(tsProject())
.on('error', function() {
if(process.env.BUILDMACHINE) {
done('Failed to compile test source, see above.');
process.exit(1);
}
})
.pipe(srcmap.write('.', {sourceRoot: function(file) { return file.cwd + '/test'; }}))
.pipe(gulp.dest('out/test/'));
});
// COMPOSED GULP TASKS /////////////////////////////////////////////////////
@@ -87,37 +88,37 @@ gulp.task("compile", gulp.series("compile:src", "compile:test"));
gulp.task("build", gulp.series("clean", "lint", "compile"));
gulp.task("watch", function() {
gulp.watch([config.paths.project.root + '/src/**/*',
config.paths.project.root + '/test/**/*.ts'],
gulp.series('build'))
gulp.watch([config.paths.project.root + '/src/**/*',
config.paths.project.root + '/test/**/*.ts'],
gulp.series('build'));
});
gulp.task('test', (done) => {
let workspace = process.env['WORKSPACE'];
if (!workspace) {
workspace = process.cwd();
}
process.env.JUNIT_REPORT_PATH = workspace + '/test-reports/ext_xunit.xml';
let workspace = process.env['WORKSPACE'];
if (!workspace) {
workspace = process.cwd();
}
process.env.JUNIT_REPORT_PATH = workspace + '/test-reports/ext_xunit.xml';
let azuredatastudioPath = 'azuredatastudio';
if (process.env['SQLOPS_DEV']) {
let suffix = os.platform === 'win32' ? 'bat' : 'sh';
azuredatastudioPath = `${process.env['SQLOPS_DEV']}/scripts/sql-cli.${suffix}`;
}
console.log(`Using SQLOPS Path of ${azuredatastudioPath}`);
let azuredatastudioPath = 'azuredatastudio';
if (process.env['VSCODE_DEV']) {
let suffix = os.platform === 'win32' ? 'bat' : 'sh';
azuredatastudioPath = `${process.env['VSCODE_DEV']}/scripts/sql-cli.${suffix}`;
}
console.log(`Using ADS Path of ${azuredatastudioPath}`);
cproc.exec(`${azuredatastudioPath} --extensionDevelopmentPath="${workspace}" --extensionTestsPath="${workspace}/out/test" --verbose`, (error, stdout, stderr) => {
if (error) {
console.error(`exec error: ${error}`);
process.exit(1);
}
console.log(`stdout: ${stdout}`);
console.log(`stderr: ${stderr}`);
done();
});
cproc.exec(`${azuredatastudioPath} --extensionDevelopmentPath="${workspace}" --extensionTestsPath="${workspace}/out/test" --verbose`, (error, stdout, stderr) => {
if (error) {
console.error(`exec error: ${error}`);
process.exit(1);
}
console.log(`stdout: ${stdout}`);
console.log(`stderr: ${stderr}`);
done();
});
});
gulp.task('copytypings', function() {
return gulp.src(config.paths.project.root + '/../../src/sql/sqlops.proposed.d.ts')
.pipe(gulp.dest('typings/'));
return gulp.src(config.paths.project.root + '/../../src/sql/azdata.d.ts')
.pipe(gulp.dest('typings/'));
});

View File

@@ -1,123 +1,123 @@
{
"rules": {
"align": [
true,
"parameters",
"statements"
],
"ban": false,
"class-name": true,
"comment-format": [
true,
"check-space"
],
"curly": true,
"eofline": true,
"forin": true,
"indent": [
true,
"spaces"
],
"interface-name": true,
"jsdoc-format": true,
"label-position": true,
"label-undefined": true,
"max-line-length": [
true,
160
],
"member-access": false,
"member-ordering": false,
"no-any": false,
"no-arg": true,
"no-bitwise": true,
"no-conditional-assignment": true,
"no-consecutive-blank-lines": false,
"no-console": [
true,
"debug",
"info",
"time",
"timeEnd",
"trace"
],
"no-construct": true,
"no-constructor-vars": false,
"no-debugger": true,
"no-duplicate-key": true,
"no-duplicate-variable": true,
"no-empty": true,
"no-eval": true,
"no-inferrable-types": false,
"no-internal-module": true,
"no-null-keyword": true,
"no-require-imports": false,
"no-shadowed-variable": true,
"no-string-literal": false,
"no-switch-case-fall-through": false,
"no-trailing-whitespace": true,
"no-unreachable": true,
"no-unused-expression": false,
"no-unused-variable": true,
"no-use-before-declare": true,
"no-var-keyword": true,
"no-var-requires": false,
"object-literal-sort-keys": false,
"one-line": [
true,
"check-open-brace",
"check-catch",
"check-else",
"check-finally",
"check-whitespace"
],
"quotemark": [
true,
"single",
"avoid-escape"
],
"radix": true,
"semicolon": true,
"switch-default": true,
"trailing-comma": [
true,
{
"multiline": "never",
"singleline": "never"
}
],
"triple-equals": [
true,
"allow-null-check"
],
"typedef": [
true,
"call-signature",
"property-declaration"
],
"typedef-whitespace": [
true,
{
"call-signature": "nospace",
"index-signature": "nospace",
"parameter": "nospace",
"property-declaration": "nospace",
"variable-declaration": "nospace"
}
],
"use-strict": false,
"variable-name": [
true,
"allow-leading-underscore",
"ban-keywords"
],
"whitespace": [
true,
"check-branch",
"check-decl",
"check-operator",
"check-separator",
"check-type"
]
}
}
"rules": {
"align": [
true,
"parameters",
"statements"
],
"ban": false,
"class-name": true,
"comment-format": [
true,
"check-space"
],
"curly": true,
"eofline": true,
"forin": true,
"indent": [
true,
"tabs"
],
"interface-name": true,
"jsdoc-format": true,
"label-position": true,
"label-undefined": true,
"max-line-length": [
true,
160
],
"member-access": false,
"member-ordering": false,
"no-any": false,
"no-arg": true,
"no-bitwise": true,
"no-conditional-assignment": true,
"no-consecutive-blank-lines": false,
"no-console": [
true,
"debug",
"info",
"time",
"timeEnd",
"trace"
],
"no-construct": true,
"no-constructor-vars": false,
"no-debugger": true,
"no-duplicate-key": true,
"no-duplicate-variable": true,
"no-empty": true,
"no-eval": true,
"no-inferrable-types": false,
"no-internal-module": true,
"no-null-keyword": true,
"no-require-imports": false,
"no-shadowed-variable": true,
"no-string-literal": false,
"no-switch-case-fall-through": false,
"no-trailing-whitespace": true,
"no-unreachable": true,
"no-unused-expression": false,
"no-unused-variable": true,
"no-use-before-declare": true,
"no-var-keyword": true,
"no-var-requires": false,
"object-literal-sort-keys": false,
"one-line": [
true,
"check-open-brace",
"check-catch",
"check-else",
"check-finally",
"check-whitespace"
],
"quotemark": [
true,
"single",
"avoid-escape"
],
"radix": true,
"semicolon": true,
"switch-default": true,
"trailing-comma": [
true,
{
"multiline": "never",
"singleline": "never"
}
],
"triple-equals": [
true,
"allow-null-check"
],
"typedef": [
true,
"call-signature",
"property-declaration"
],
"typedef-whitespace": [
true,
{
"call-signature": "nospace",
"index-signature": "nospace",
"parameter": "nospace",
"property-declaration": "nospace",
"variable-declaration": "nospace"
}
],
"use-strict": false,
"variable-name": [
true,
"allow-leading-underscore",
"ban-keywords"
],
"whitespace": [
true,
"check-branch",
"check-decl",
"check-operator",
"check-separator",
"check-type"
]
}
}

21
src/sql/azdata.d.ts vendored
View File

@@ -3051,10 +3051,9 @@ declare module 'azdata' {
focused?: boolean;
}
export interface TextComponentProperties {
export interface TextComponentProperties extends ComponentProperties, TitledComponentProperties {
value?: string;
links?: LinkArea[];
CSSStyles?: { [key: string]: string };
}
export interface LinkArea {
@@ -3062,7 +3061,7 @@ declare module 'azdata' {
url: string;
}
export interface HyperlinkComponentProperties extends ComponentProperties {
export interface HyperlinkComponentProperties extends ComponentProperties, TitledComponentProperties {
label: string;
url: string;
}
@@ -3165,6 +3164,13 @@ declare module 'azdata' {
clickable?: boolean;
}
export interface TitledComponentProperties {
/**
* The title for the component. This title will show when hovered over
*/
title?: string;
}
export interface CardComponent extends Component, CardProperties {
onDidActionClick: vscode.Event<ActionDescriptor>;
onCardSelectedChanged: vscode.Event<any>;
@@ -3174,8 +3180,7 @@ declare module 'azdata' {
}
export interface TextComponent extends Component, ComponentProperties {
value: string;
export interface TextComponent extends Component, TextComponentProperties {
/**
* An event called when the text is clicked
*/
@@ -3183,8 +3188,6 @@ declare module 'azdata' {
}
export interface HyperlinkComponent extends Component, HyperlinkComponentProperties {
label: string;
url: string;
}
export interface InputBoxComponent extends Component, InputBoxProperties {
@@ -3785,7 +3788,7 @@ declare module 'azdata' {
* Namespace for interacting with query editor
*/
export namespace queryeditor {
export type QueryEvent =
export type QueryEventType =
| 'queryStart'
| 'queryUpdate'
| 'queryStop'
@@ -3800,7 +3803,7 @@ declare module 'azdata' {
* visualize: ResultSetSummary
*/
export interface QueryEventListener {
onQueryEvent(type: QueryEvent, document: queryeditor.QueryDocument, args: ResultSetSummary | string | undefined): void;
onQueryEvent(type: QueryEventType, document: queryeditor.QueryDocument, args: ResultSetSummary | string | undefined): void;
}
// new extensibility interfaces

View File

@@ -28,6 +28,7 @@ export interface ICellRangeSelectorOptions {
export interface ICellRangeSelector<T> extends Slick.Plugin<T> {
onCellRangeSelected: Slick.Event<Slick.Range>;
onBeforeCellRangeSelected: Slick.Event<Slick.Cell>;
onAppendCellRangeSelected: Slick.Event<Slick.Range>;
}
export interface ICellRangeDecorator {
@@ -45,6 +46,7 @@ export class CellRangeSelector<T> implements ICellRangeSelector<T> {
public onBeforeCellRangeSelected = new Slick.Event<Slick.Cell>();
public onCellRangeSelected = new Slick.Event<Slick.Range>();
public onAppendCellRangeSelected = new Slick.Event<Slick.Range>();
constructor(private options: ICellRangeSelectorOptions) {
require.__$__nodeRequire('slickgrid/plugins/slick.cellrangedecorator');
@@ -138,11 +140,18 @@ export class CellRangeSelector<T> implements ICellRangeSelector<T> {
if (!dd || !dd.range || !dd.range.start || !dd.range.end) {
return;
}
this.onCellRangeSelected.notify(new Slick.Range(
let newRange = new Slick.Range(
dd.range.start.row,
dd.range.start.cell,
dd.range.end.row,
dd.range.end.cell
));
);
if (e.ctrlKey) {
this.onAppendCellRangeSelected.notify(newRange);
} else {
this.onCellRangeSelected.notify(newRange);
}
}
}

View File

@@ -36,11 +36,14 @@ export class CellSelectionModel<T> implements Slick.SelectionModel<T, Array<Slic
public init(grid: Slick.Grid<T>) {
this.grid = grid;
this._handler.subscribe(this.grid.onActiveCellChanged, (e: Event, args: Slick.OnActiveCellChangedEventArgs<T>) => this.handleActiveCellChange(e, args));
this._handler.subscribe(this.grid.onClick, (e: MouseEvent, args: Slick.OnActiveCellChangedEventArgs<T>) => this.handleActiveCellChange(e, args));
this._handler.subscribe(this.grid.onKeyDown, (e: KeyboardEvent) => this.handleKeyDown(e));
this._handler.subscribe(this.grid.onClick, (e: MouseEvent, args: Slick.OnClickEventArgs<T>) => this.handleIndividualCellSelection(e, args));
this._handler.subscribe(this.grid.onHeaderClick, (e: MouseEvent, args: Slick.OnHeaderClickEventArgs<T>) => this.handleHeaderClick(e, args));
this.grid.registerPlugin(this.selector);
this._handler.subscribe(this.selector.onCellRangeSelected, (e: Event, range: Slick.Range) => this.handleCellRangeSelected(e, range));
this._handler.subscribe(this.selector.onCellRangeSelected, (e: Event, range: Slick.Range) => this.handleCellRangeSelected(e, range, false));
this._handler.subscribe(this.selector.onAppendCellRangeSelected, (e: Event, range: Slick.Range) => this.handleCellRangeSelected(e, range, true));
this._handler.subscribe(this.selector.onBeforeCellRangeSelected, (e: Event, cell: Slick.Cell) => this.handleBeforeCellRangeSelected(e, cell));
}
@@ -87,13 +90,18 @@ export class CellSelectionModel<T> implements Slick.SelectionModel<T, Array<Slic
return true;
}
private handleCellRangeSelected(e: Event, range: Slick.Range) {
private handleCellRangeSelected(e: Event, range: Slick.Range, append: boolean) {
this.grid.setActiveCell(range.fromRow, range.fromCell, false, false, true);
this.setSelectedRanges([range]);
if (append) {
this.setSelectedRanges(this.insertIntoSelections(this.getSelectedRanges(), range));
} else {
this.setSelectedRanges([range]);
}
}
private handleActiveCellChange(e: Event, args: Slick.OnActiveCellChangedEventArgs<T>) {
if (this.options.selectActiveCell && !isUndefinedOrNull(args.row) && !isUndefinedOrNull(args.cell)) {
private handleActiveCellChange(e: MouseEvent, args: Slick.OnActiveCellChangedEventArgs<T>) {
if (this.options.selectActiveCell && !isUndefinedOrNull(args.row) && !isUndefinedOrNull(args.cell) && !e.ctrlKey) {
this.setSelectedRanges([new Slick.Range(args.row, args.cell)]);
} else if (!this.options.selectActiveCell) {
// clear the previous selection once the cell changes
@@ -118,6 +126,119 @@ export class CellSelectionModel<T> implements Slick.SelectionModel<T, Array<Slic
}
}
/**
* DO NOT CALL THIS DIRECTLY - GO THROUGH INSERT INTO SELECTIONS
*
*/
private mergeSelections(ranges: Array<Slick.Range>, range: Slick.Range) {
// New ranges selection
let newRanges: Array<Slick.Range> = [];
// Have we handled this value
let handled = false;
for (let current of ranges) {
// We've already processed everything. Add everything left back to the list.
if (handled) {
newRanges.push(current);
continue;
}
let newRange: Slick.Range | undefined = undefined;
// if the ranges are the same.
if (current.fromRow === range.fromRow &&
current.fromCell === range.fromCell &&
current.toRow === range.toRow &&
current.toCell === range.toCell) {
// If we're actually not going to handle it during this loop
// this region will be added with the handled boolean check
continue;
}
// Rows are the same - horizontal merging of the selection area
if (current.fromRow === range.fromRow && current.toRow === range.toRow) {
// Check if the new region is adjacent to the old selection group
if (range.toCell + 1 === current.fromCell || range.fromCell - 1 === current.toCell) {
handled = true;
let fromCell = Math.min(range.fromCell, current.fromCell, range.toCell, current.toCell);
let toCell = Math.max(range.fromCell, current.fromCell, range.toCell, current.toCell);
newRange = new Slick.Range(range.fromRow, fromCell, range.toRow, toCell);
}
// Cells are the same - vertical merging of the selection area
} else if (current.fromCell === range.fromCell && current.toCell === range.toCell) {
// Check if the new region is adjacent to the old selection group
if (range.toRow + 1 === current.fromRow || range.fromRow - 1 === current.toRow) {
handled = true;
let fromRow = Math.min(range.fromRow, current.fromRow, range.fromRow, current.fromRow);
let toRow = Math.max(range.toRow, current.toRow, range.toRow, current.toRow);
newRange = new Slick.Range(fromRow, range.fromCell, toRow, range.toCell);
}
}
if (newRange) {
newRanges.push(newRange);
} else {
newRanges.push(current);
}
}
if (!handled) {
newRanges.push(range);
}
return {
newRanges,
handled
};
}
private insertIntoSelections(ranges: Array<Slick.Range>, range: Slick.Range): Array<Slick.Range> {
let result = this.mergeSelections(ranges, range);
let newRanges = result.newRanges;
// Keep merging the rows until we stop having changes
let i = 0;
while (true) {
if (i++ > 10000) {
console.error('InsertIntoSelection infinite loop: Report this error on github');
break;
}
let shouldContinue = false;
for (let current of newRanges) {
result = this.mergeSelections(newRanges, current);
if (result.handled) {
shouldContinue = true;
newRanges = result.newRanges;
break;
}
}
if (shouldContinue) {
continue;
}
break;
}
return newRanges;
}
private handleIndividualCellSelection(e: MouseEvent, args: Slick.OnClickEventArgs<T>) {
if (!e.ctrlKey) {
return;
}
let ranges: Array<Slick.Range>;
ranges = this.getSelectedRanges();
ranges = this.insertIntoSelections(ranges, new Slick.Range(args.row, args.cell));
this.grid.setActiveCell(args.row, args.cell);
this.setSelectedRanges(ranges);
e.preventDefault();
e.stopImmediatePropagation();
}
private handleKeyDown(e: KeyboardEvent) {
/***
* Кey codes

View File

@@ -589,7 +589,7 @@ export class NewNotebookJobAction extends Action {
export class EditNotebookJobAction extends Action {
public static ID = 'notebookaction.editNotebook';
public static LABEL = nls.localize('notebookaction.editJob', "Edit Notebook Job");
public static LABEL = nls.localize('notebookaction.editJob', "Edit");
constructor(
@ICommandService private _commandService: ICommandService
@@ -624,7 +624,7 @@ export class OpenTemplateNotebookAction extends Action {
export class DeleteNotebookAction extends Action {
public static ID = 'notebookaction.deleteNotebook';
public static LABEL = nls.localize('notebookaction.deleteNotebook', "Delete Notebook");
public static LABEL = nls.localize('notebookaction.deleteNotebook', "Delete");
constructor(
@INotificationService private _notificationService: INotificationService,
@@ -732,3 +732,19 @@ export class RenameNotebookMaterializedAction extends Action {
return Promise.resolve(true);
}
}
export class OpenLatestRunMaterializedNotebook extends Action {
public static ID = 'notebookaction.openLatestRun';
public static LABEL = nls.localize('notebookaction.openLatestRun', "Open Latest Run");
constructor(
@ICommandService private _commandService: ICommandService,
) {
super(OpenLatestRunMaterializedNotebook.ID, OpenLatestRunMaterializedNotebook.LABEL);
}
public run(actionInfo: IJobActionInfo): Promise<boolean> {
actionInfo.component.openLastNRun(actionInfo.targetObject.job, 0, 1);
return Promise.resolve(true);
}
}

View File

@@ -43,22 +43,27 @@ export interface IGridDataProvider {
}
export async function getResultsString(provider: IGridDataProvider, selection: Slick.Range[], includeHeaders?: boolean): Promise<string> {
let headers: Map<Number, string> = new Map();
let rows: Map<Number, Map<Number, string>> = new Map();
let copyTable: string[][] = [];
const eol = provider.getEolString();
// create a mapping of the ranges to get promises
let tasks = selection.map((range, i) => {
return async () => {
let selectionsCopy = selection;
let startCol = range.fromCell;
let startRow = range.fromRow;
const result = await provider.getRowData(range.fromRow, range.toRow - range.fromRow + 1);
// If there was a previous selection separate it with a line break. Currently
// when there are multiple selections they are never on the same line
if (provider.shouldIncludeHeaders(includeHeaders)) {
let columnHeaders = provider.getColumnHeaders(range);
if (columnHeaders !== undefined) {
if (copyTable[0] === undefined) {
copyTable[0] = [];
}
copyTable[0].push(...columnHeaders);
let columnHeaders = provider.getColumnHeaders(range);
if (columnHeaders !== undefined) {
let idx = 0;
for (let header of columnHeaders) {
headers.set(startCol + idx, header);
idx++;
}
}
// Iterate over the rows to paste into the copy string
@@ -70,11 +75,17 @@ export async function getResultsString(provider: IGridDataProvider, selection: S
? cellObjects.map(x => removeNewLines(x.displayValue))
: cellObjects.map(x => x.displayValue);
let idx = rowIndex + 1;
if (copyTable[idx] === undefined) {
copyTable[idx] = [];
let idx = 0;
for (let cell of cells) {
let map = rows.get(rowIndex + startRow);
if (!map) {
map = new Map();
rows.set(rowIndex + startRow, map);
}
map.set(startCol + idx, cell);
idx++;
}
copyTable[idx].push(...cells);
}
};
});
@@ -88,12 +99,25 @@ export async function getResultsString(provider: IGridDataProvider, selection: S
}
let copyString = '';
copyTable.forEach((row) => {
if (row === undefined) {
return;
if (includeHeaders) {
copyString = [...headers.values()].join('\t').concat(eol);
}
const rowKeys = [...headers.keys()];
for (let rowEntry of rows) {
let rowMap = rowEntry[1];
for (let rowIdx of rowKeys) {
let value = rowMap.get(rowIdx);
if (value) {
copyString = copyString.concat(value);
}
copyString = copyString.concat('\t');
}
copyString = copyString.concat(row.join('\t').concat(eol));
});
copyString = copyString.concat(eol);
}
// Removes EoL from the end of the string
copyString = copyString.slice(0, -1 * eol.length);
return copyString;

View File

@@ -3,7 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import QueryRunner from 'sql/platform/query/common/queryRunner';
import QueryRunner, { IQueryMessage } from 'sql/platform/query/common/queryRunner';
import { DataService } from 'sql/workbench/parts/grid/common/dataService';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { Event } from 'vs/base/common/event';
@@ -31,9 +31,15 @@ export interface IQueryPlanInfo {
planXml: string;
}
export interface IQueryInfo {
selection: ISelectionData[];
messages: IQueryMessage[];
}
export interface IQueryEvent {
type: queryeditor.QueryEvent;
type: queryeditor.QueryEventType;
uri: string;
queryInfo: IQueryInfo;
params?: any;
}

View File

@@ -283,7 +283,12 @@ export class QueryModelService implements IQueryModelService {
// fire extensibility API event
let event: IQueryEvent = {
type: 'queryStop',
uri: uri
uri: uri,
queryInfo:
{
selection: info.selection,
messages: info.queryRunner.messages
}
};
this._onQueryEvent.fire(event);
@@ -296,7 +301,12 @@ export class QueryModelService implements IQueryModelService {
// fire extensibility API event
let event: IQueryEvent = {
type: 'queryStart',
uri: uri
uri: uri,
queryInfo:
{
selection: info.selection,
messages: info.queryRunner.messages
}
};
this._onQueryEvent.fire(event);
@@ -307,7 +317,12 @@ export class QueryModelService implements IQueryModelService {
let event: IQueryEvent = {
type: 'queryUpdate',
uri: uri
uri: uri,
queryInfo:
{
selection: info.selection,
messages: info.queryRunner.messages
}
};
this._onQueryEvent.fire(event);
@@ -319,6 +334,11 @@ export class QueryModelService implements IQueryModelService {
let event: IQueryEvent = {
type: 'executionPlan',
uri: planInfo.fileUri,
queryInfo:
{
selection: info.selection,
messages: info.queryRunner.messages
},
params: planInfo
};
this._onQueryEvent.fire(event);
@@ -328,6 +348,11 @@ export class QueryModelService implements IQueryModelService {
let event: IQueryEvent = {
type: 'visualize',
uri: uri,
queryInfo:
{
selection: info.selection,
messages: info.queryRunner.messages
},
params: resultSetInfo
};
this._onQueryEvent.fire(event);
@@ -443,7 +468,12 @@ export class QueryModelService implements IQueryModelService {
// fire extensibility API event
let event: IQueryEvent = {
type: 'queryStop',
uri: ownerUri
uri: ownerUri,
queryInfo:
{
selection: info.selection,
messages: info.queryRunner.messages
},
};
this._onQueryEvent.fire(event);
@@ -455,7 +485,12 @@ export class QueryModelService implements IQueryModelService {
// fire extensibility API event
let event: IQueryEvent = {
type: 'queryStart',
uri: ownerUri
uri: ownerUri,
queryInfo:
{
selection: info.selection,
messages: info.queryRunner.messages
},
};
this._onQueryEvent.fire(event);

View File

@@ -44,6 +44,7 @@ export interface IQueryMessage extends azdata.IResultMessage {
export default class QueryRunner extends Disposable {
// MEMBER VARIABLES ////////////////////////////////////////////////////
private _resultLineOffset: number;
private _resultColumnOffset: number;
private _totalElapsedMilliseconds: number = 0;
private _isExecuting: boolean = false;
private _hasCompleted: boolean = false;
@@ -179,6 +180,7 @@ export default class QueryRunner extends Disposable {
if (types.isObject(input) || types.isUndefinedOrNull(input)) {
// Update internal state to show that we're executing the query
this._resultLineOffset = input ? input.startLine : 0;
this._resultColumnOffset = input ? input.startColumn : 0;
this._isExecuting = true;
this._totalElapsedMilliseconds = 0;
// TODO issue #228 add statusview callbacks here
@@ -243,8 +245,10 @@ export default class QueryRunner extends Disposable {
this._batchSets.map(batch => {
if (batch.selection) {
batch.selection.startLine = batch.selection.startLine + this._resultLineOffset;
batch.selection.endLine = batch.selection.endLine + this._resultLineOffset;
batch.selection.startLine += this._resultLineOffset;
batch.selection.startColumn += this._resultColumnOffset;
batch.selection.endLine += this._resultLineOffset;
batch.selection.endColumn += this._resultColumnOffset;
}
});
@@ -271,7 +275,9 @@ export default class QueryRunner extends Disposable {
// Recalculate the start and end lines, relative to the result line offset
if (batch.selection) {
batch.selection.startLine += this._resultLineOffset;
batch.selection.startColumn += this._resultColumnOffset;
batch.selection.endLine += this._resultLineOffset;
batch.selection.endColumn += this._resultColumnOffset;
}
// Set the result sets as an empty array so that as result sets complete we can add to the list

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.
*--------------------------------------------------------------------------------------------*/
import { generateUuid } from 'vs/base/common/uuid';
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
export enum QueryStatus {
Succeeded = 0,
Failed = 1,
Nothing = 2
}
/**
* Contains information about a query that was ran
*/
export class QueryHistoryInfo {
public database: string;
public status: QueryStatus;
public readonly id = generateUuid();
constructor(
public queryText: string,
public connectionProfile: IConnectionProfile,
public startTime: Date,
status?: QueryStatus) {
this.database = connectionProfile ? connectionProfile.databaseName : '';
this.status = status;
}
}

View File

@@ -0,0 +1,55 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { QueryHistoryInfo } from 'sql/platform/queryHistory/common/queryHistoryInfo';
import { Event } from 'vs/base/common/event';
export const SERVICE_ID = 'queryHistoryService';
export const IQueryHistoryService = createDecorator<IQueryHistoryService>(SERVICE_ID);
/**
* Service that collects the results of executed queries
*/
export interface IQueryHistoryService {
_serviceBrand: any;
/**
* Event fired whenever the collection of stored QueryHistoryInfo's is updated
*/
onInfosUpdated: Event<QueryHistoryInfo[]>;
/**
* Event fired whenever the Query History capture state has changed
*/
onQueryHistoryCaptureChanged: Event<boolean>;
/**
* Whether Query History capture is currently enabled
*/
readonly captureEnabled: boolean;
/**
* Gets the current list of Query History Info objects that have been collected
*/
getQueryHistoryInfos(): QueryHistoryInfo[];
/**
* Deletes all QueryHistoryInfo's from the collection that have the same id as the specified one
* @param info The QueryHistoryInfo to delete
*/
deleteQueryHistoryInfo(info: QueryHistoryInfo): void;
/**
* Clears all Query History - removing all collected items
*/
clearQueryHistory(): void;
/**
* Toggles whether Query History capture is enabled
*/
toggleCaptureEnabled(): Promise<void>;
/**
* Starts the Query History Service
*/
start(): void;
}

View File

@@ -0,0 +1,127 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IQueryHistoryService } from 'sql/platform/queryHistory/common/queryHistoryService.ts';
import { IQueryModelService, IQueryEvent } from 'sql/platform/query/common/queryModel';
import { IModelService } from 'vs/editor/common/services/modelService';
import { URI } from 'vs/base/common/uri';
import { Range } from 'vs/editor/common/core/range';
import { QueryHistoryInfo, QueryStatus } from 'sql/platform/queryHistory/common/queryHistoryInfo';
import { IConnectionManagementService } from 'sql/platform/connection/common/connectionManagement';
import { Event, Emitter } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle';
import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration';
import { IConfigurationChangedEvent } from 'sql/workbench/parts/profiler/browser/profilerFindWidget';
/**
* Service that collects the results of executed queries
*/
export class QueryHistoryService extends Disposable implements IQueryHistoryService {
_serviceBrand: any;
// MEMBER VARIABLES ////////////////////////////////////////////////////
private _infos: QueryHistoryInfo[] = [];
private _onInfosUpdated: Emitter<QueryHistoryInfo[]> = new Emitter<QueryHistoryInfo[]>();
private _onQueryHistoryCaptureChanged: Emitter<boolean> = new Emitter<boolean>();
private _captureEnabled;
// EVENTS //////////////////////////////////////////////////////////////
public get onInfosUpdated(): Event<QueryHistoryInfo[]> { return this._onInfosUpdated.event; }
public get onQueryHistoryCaptureChanged(): Event<boolean> { return this._onQueryHistoryCaptureChanged.event; }
// CONSTRUCTOR /////////////////////////////////////////////////////////
constructor(
@IQueryModelService _queryModelService: IQueryModelService,
@IModelService _modelService: IModelService,
@IConnectionManagementService _connectionManagementService: IConnectionManagementService,
@IConfigurationService private _configurationService: IConfigurationService
) {
super();
this._captureEnabled = !!this._configurationService.getValue<boolean>('queryHistory.captureEnabled');
this._register(this._configurationService.onDidChangeConfiguration((e: IConfigurationChangeEvent) => {
if (e.affectedKeys.includes('queryHistory.captureEnabled')) {
this.updateCaptureEnabled();
}
}));
this._register(_queryModelService.onQueryEvent((e: IQueryEvent) => {
if (this._captureEnabled && e.type === 'queryStop') {
const uri: URI = URI.parse(e.uri);
// VS Range is 1 based so offset values by 1. The endLine we get back from SqlToolsService is incremented
// by 1 from the original input range sent in as well so take that into account and don't modify
const text: string = _modelService.getModel(uri).getValueInRange(new Range(
e.queryInfo.selection[0].startLine + 1,
e.queryInfo.selection[0].startColumn + 1,
e.queryInfo.selection[0].endLine,
e.queryInfo.selection[0].endColumn + 1));
const newInfo = new QueryHistoryInfo(text, _connectionManagementService.getConnectionProfile(e.uri), new Date(), QueryStatus.Succeeded);
// icon as required (for now logic is if any message has error query has error)
let error: boolean = false;
e.queryInfo.messages.forEach(x => error = error || x.isError);
if (error) {
newInfo.status = QueryStatus.Failed;
}
// Append new node to beginning of array so the newest ones are at the top
this._infos.unshift(newInfo);
this._onInfosUpdated.fire(this._infos);
}
}));
}
/**
* Whether Query History capture is currently enabled
*/
public get captureEnabled(): boolean {
return this._captureEnabled;
}
/**
* Gets all the current query history infos
*/
public getQueryHistoryInfos(): QueryHistoryInfo[] {
return this._infos;
}
/**
* Deletes infos from the cache with the same ID as the given QueryHistoryInfo
* @param info The QueryHistoryInfo to delete
*/
public deleteQueryHistoryInfo(info: QueryHistoryInfo): void {
this._infos = this._infos.filter(i => i.id !== info.id);
this._onInfosUpdated.fire(this._infos);
}
/**
* Clears all infos from the cache
*/
public clearQueryHistory(): void {
this._infos = [];
this._onInfosUpdated.fire(this._infos);
}
public async toggleCaptureEnabled(): Promise<void> {
const captureEnabled = !!this._configurationService.getValue<boolean>('queryHistory.captureEnabled');
await this._configurationService.updateValue('queryHistory.captureEnabled', !captureEnabled);
}
private updateCaptureEnabled(): void {
const currentCaptureEnabled = this._captureEnabled;
this._captureEnabled = !!this._configurationService.getValue<boolean>('queryHistory.captureEnabled');
if (currentCaptureEnabled !== this._captureEnabled) {
this._onQueryHistoryCaptureChanged.fire(this._captureEnabled);
}
}
/**
* Method to force initialization of the service so that it can start tracking query events
*/
public start(): void {
}
}

View File

@@ -545,7 +545,7 @@ declare module 'sqlops' {
checked?: boolean;
}
export interface TextComponentProperties {
export interface TextComponentProperties extends ComponentProperties, TitledComponentProperties {
value?: string;
links?: LinkArea[];
}
@@ -555,7 +555,7 @@ declare module 'sqlops' {
url: string;
}
export interface HyperlinkComponentProperties extends ComponentProperties {
export interface HyperlinkComponentProperties extends ComponentProperties, TitledComponentProperties {
label: string;
url: string;
}
@@ -651,6 +651,13 @@ declare module 'sqlops' {
yOffsetChange?: number;
}
export interface TitledComponentProperties {
/**
* The title for the component. This title will show when hovered over
*/
title?: string;
}
export interface CardComponent extends Component, CardProperties {
onDidActionClick: vscode.Event<ActionDescriptor>;
onCardSelectedChanged: vscode.Event<any>;
@@ -660,13 +667,14 @@ declare module 'sqlops' {
}
export interface TextComponent extends Component, ComponentProperties {
value: string;
export interface TextComponent extends Component, TextComponentProperties {
/**
* An event called when the text is clicked
*/
onDidClick: vscode.Event<any>;
}
export interface HyperlinkComponent extends Component, HyperlinkComponentProperties {
label: string;
url: string;
}
export interface InputBoxComponent extends Component, InputBoxProperties {

View File

@@ -1112,6 +1112,13 @@ class TextComponentWrapper extends ComponentWrapper implements azdata.TextCompon
this.setProperty('value', v);
}
public get title(): string {
return this.properties['title'];
}
public set title(title: string) {
this.setProperty('title', title);
}
public get onDidClick(): vscode.Event<any> {
let emitter = this._emitterMap.get(ComponentEventType.onDidClick);
return emitter && emitter.event;

View File

@@ -326,8 +326,8 @@ export enum DataProviderType {
AgentServicesProvider = 'AgentServicesProvider',
CapabilitiesProvider = 'CapabilitiesProvider',
ObjectExplorerNodeProvider = 'ObjectExplorerNodeProvider',
IconProvider = 'IconProvider',
SerializationProvider = 'SerializationProvider'
SerializationProvider = 'SerializationProvider',
IconProvider = 'IconProvider'
}
export enum DeclarativeDataType {
@@ -579,10 +579,96 @@ export class ConnectionProfile {
options: { [name: string]: any };
static createFrom(options: any[]): ConnectionProfile {
throw new Error('Method not implemented');
// create from options
return undefined;
}
}
export enum SchemaUpdateAction {
Delete = 0,
Change = 1,
Add = 2
}
export enum SchemaDifferenceType {
Object = 0,
Property = 1
}
export enum SchemaCompareEndpointType {
Database = 0,
Dacpac = 1
}
export enum SchemaObjectType {
Aggregates = 0,
ApplicationRoles = 1,
Assemblies = 2,
AssemblyFiles = 3,
AsymmetricKeys = 4,
BrokerPriorities = 5,
Certificates = 6,
ColumnEncryptionKeys = 7,
ColumnMasterKeys = 8,
Contracts = 9,
DatabaseOptions = 10,
DatabaseRoles = 11,
DatabaseTriggers = 12,
Defaults = 13,
ExtendedProperties = 14,
ExternalDataSources = 15,
ExternalFileFormats = 16,
ExternalTables = 17,
Filegroups = 18,
FileTables = 19,
FullTextCatalogs = 20,
FullTextStoplists = 21,
MessageTypes = 22,
PartitionFunctions = 23,
PartitionSchemes = 24,
Permissions = 25,
Queues = 26,
RemoteServiceBindings = 27,
RoleMembership = 28,
Rules = 29,
ScalarValuedFunctions = 30,
SearchPropertyLists = 31,
SecurityPolicies = 32,
Sequences = 33,
Services = 34,
Signatures = 35,
StoredProcedures = 36,
SymmetricKeys = 37,
Synonyms = 38,
Tables = 39,
TableValuedFunctions = 40,
UserDefinedDataTypes = 41,
UserDefinedTableTypes = 42,
ClrUserDefinedTypes = 43,
Users = 44,
Views = 45,
XmlSchemaCollections = 46,
Audits = 47,
Credentials = 48,
CryptographicProviders = 49,
DatabaseAuditSpecifications = 50,
DatabaseEncryptionKeys = 51,
DatabaseScopedCredentials = 52,
Endpoints = 53,
ErrorMessages = 54,
EventNotifications = 55,
EventSessions = 56,
LinkedServerLogins = 57,
LinkedServers = 58,
Logins = 59,
MasterKeys = 60,
Routes = 61,
ServerAuditSpecifications = 62,
ServerRoleMembership = 63,
ServerRoles = 64,
ServerTriggers = 65
}
export enum ColumnType {
text = 0,
checkBox = 1,
@@ -600,3 +686,9 @@ export enum NotebookChangeKind {
Save = 2,
CellExecuted = 3
}
export type QueryEventType =
| 'queryStart'
| 'queryStop'
| 'executionPlan'
| 'visualize';

View File

@@ -10,14 +10,14 @@ import {
import * as azdata from 'azdata';
import { ComponentBase } from 'sql/workbench/browser/modelComponents/componentBase';
import { IComponent, IComponentDescriptor, IModelStore } from 'sql/workbench/browser/modelComponents/interfaces';
import { TitledComponent } from 'sql/workbench/browser/modelComponents/titledComponent';
@Component({
selector: 'modelview-hyperlink',
template: `<a [href]="getUrl()" target="blank">{{getLabel()}}</a>`
template: `<a [href]="getUrl()" [title]="title" target="blank">{{getLabel()}}</a>`
})
export default class HyperlinkComponent extends ComponentBase implements IComponent, OnDestroy, AfterViewInit {
export default class HyperlinkComponent extends TitledComponent implements IComponent, OnDestroy, AfterViewInit {
@Input() descriptor: IComponentDescriptor;
@Input() modelStore: IModelStore;

View File

@@ -101,3 +101,7 @@ export interface IModelStore {
*/
validate(component: IComponent): Thenable<boolean>;
}
export interface ITitledComponent {
title?: string;
}

View File

@@ -11,16 +11,16 @@ import {
import * as azdata from 'azdata';
import { ComponentBase } from 'sql/workbench/browser/modelComponents/componentBase';
import { IComponent, IComponentDescriptor, IModelStore, ComponentEventType } from 'sql/workbench/browser/modelComponents/interfaces';
import { SafeHtml, DomSanitizer } from '@angular/platform-browser';
import { TitledComponent } from 'sql/workbench/browser/modelComponents/titledComponent';
@Component({
selector: 'modelview-text',
template: `
<p [style.width]="getWidth()" [innerHTML]="getValue()" [ngStyle]="this.CSSStyles" (click)="onClick()"></p>`
<p [style.width]="getWidth()" [innerHTML]="getValue()" [title]="title" [ngStyle]="this.CSSStyles" (click)="onClick()"></p>`
})
export default class TextComponent extends ComponentBase implements IComponent, OnDestroy, AfterViewInit {
export default class TextComponent extends TitledComponent implements IComponent, OnDestroy, AfterViewInit {
@Input() descriptor: IComponentDescriptor;
@Input() modelStore: IModelStore;

View File

@@ -0,0 +1,29 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import {
ChangeDetectorRef, ElementRef
} from '@angular/core';
import { ITitledComponent } from 'sql/workbench/browser/modelComponents/interfaces';
import * as azdata from 'azdata';
import { ComponentBase } from 'sql/workbench/browser/modelComponents/componentBase';
export abstract class TitledComponent extends ComponentBase implements ITitledComponent {
constructor(
protected _changeRef: ChangeDetectorRef,
protected _el: ElementRef) {
super(_changeRef, _el);
}
public get title(): string {
return this.getPropertyOrDefault<azdata.HyperlinkComponentProperties, string>((props) => props.title, '');
}
public set title(newTitle: string) {
this.setPropertyFromUI<azdata.HyperlinkComponentProperties, string>((properties, title) => { properties.title = title; }, newTitle);
}
}

View File

@@ -10,13 +10,11 @@ import {
RunQueryOnConnectionMode, IConnectionResult
} from 'sql/platform/connection/common/connectionManagement';
import { EditDataInput } from 'sql/workbench/parts/editData/browser/editDataInput';
import { IRestoreDialogController } from 'sql/platform/restore/common/restoreService';
import { IInsightsDialogService } from 'sql/workbench/services/insights/browser/insightsDialogService';
import { IObjectExplorerService } from 'sql/workbench/services/objectExplorer/common/objectExplorerService';
import { QueryInput } from 'sql/workbench/parts/query/common/queryInput';
import { DashboardInput } from 'sql/workbench/parts/dashboard/browser/dashboardInput';
import { ProfilerInput } from 'sql/workbench/parts/profiler/browser/profilerInput';
import { IBackupUiService } from 'sql/workbench/services/backup/common/backupUiService';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IInsightsConfig } from 'sql/platform/dashboard/browser/insightRegistry';

View File

@@ -9,7 +9,7 @@ import { IClipboardService } from 'sql/platform/clipboard/common/clipboardServic
import { localize } from 'vs/nls';
import { Action } from 'vs/base/common/actions';
import { IWindowsService } from 'vs/platform/windows/common/windows';
import { IWindowsService, FileFilter } from 'vs/platform/windows/common/windows';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { URI } from 'vs/base/common/uri';
import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService';
@@ -152,7 +152,9 @@ export class SaveImageAction extends Action {
public async run(context: IChartActionContext): Promise<boolean> {
if (context.insight instanceof Graph) {
const filePath = await this.fileDialogService.pickFileToSave({});
let fileFilters = new Array<FileFilter>({ extensions: ['png'], name: localize('resultsSerializer.saveAsFileExtensionPNGTitle', "PNG") });
const filePath = await this.fileDialogService.pickFileToSave({ filters: fileFilters });
const data = (<Graph>context.insight).getCanvasData();
if (!data) {
this.notificationService.error(localize('chartNotFound', "Could not find chart to save"));

View File

@@ -124,6 +124,9 @@ export abstract class JobManagementView extends TabChild implements AfterContent
public refreshJobs() {
this._agentViewComponent.refresh = true;
}
public openLastNRun(notebook: azdata.AgentNotebookInfo, n: number, maxVisibleElements: number) {
}
}
export interface JobActionContext {

View File

@@ -441,6 +441,7 @@ notebookhistory-component
height: 40px;
background-size: 40px 40px;
background-repeat: no-repeat;
cursor: pointer;
}
.vs-dark .notebook-grid-item > .img-error,

View File

@@ -5,18 +5,17 @@
agentview-component {
height: 100%;
width : 100%;
width: 100%;
display: block;
}
jobsview-component,
notebooksview-component {
height: 100%;
width : 100%;
width: 100%;
display: block;
}
.job-heading-container {
height: 50px;
border-bottom: 3px solid #f4f4f4;
@@ -33,13 +32,13 @@ notebooksview-component {
.jobview-grid {
height: calc(100% - 75px);
width : 100%;
width: 100%;
display: block;
}
.jobnotebooksview-grid {
height: calc(100% - 75px);
width : 100%;
width: 100%;
display: block;
}
@@ -53,13 +52,35 @@ notebooksview-component {
font-weight: bold;
}
.hc-black #jobsDiv jobsview-component .jobview-grid .grid-canvas .ui-widget-content.slick-row .slick-cell,
.hc-black #notebooksDiv notebooks-component .jobnotebooksview-grid .grid-canvas .ui-widget-content.slick-row .slick-cell {
.hc-black
#jobsDiv
jobsview-component
.jobview-grid
.grid-canvas
.ui-widget-content.slick-row
.slick-cell,
.hc-black
#notebooksDiv
notebooks-component
.jobnotebooksview-grid
.grid-canvas
.ui-widget-content.slick-row
.slick-cell {
border: 1px solid #2b56f2;
}
#jobsDiv jobsview-component .jobview-grid .grid-canvas .ui-widget-content.slick-row .slick-cell,
#notebooksDiv notebooksview-component .jobnotebooksview-grid .grid-canvas .ui-widget-content.slick-row .slick-cell {
#jobsDiv
jobsview-component
.jobview-grid
.grid-canvas
.ui-widget-content.slick-row
.slick-cell,
#notebooksDiv
notebooksview-component
.jobnotebooksview-grid
.grid-canvas
.ui-widget-content.slick-row
.slick-cell {
border-right: transparent !important;
border-left: transparent !important;
line-height: 33px !important;
@@ -103,8 +124,14 @@ notebooksview-component {
}
#jobsDiv .jobview-grid .slick-cell.l1.r1.error-row .jobview-jobnametext,
#notebooksDiv .jobnotebooksview-grid .slick-cell.l1.r1.error-row .jobview-jobnametext,
#notebooksDiv .jobnotebooksview-grid .slick-cell.l1.r1.notebook-error-row .jobview-jobnametext {
#notebooksDiv
.jobnotebooksview-grid
.slick-cell.l1.r1.error-row
.jobview-jobnametext,
#notebooksDiv
.jobnotebooksview-grid
.slick-cell.l1.r1.notebook-error-row
.jobview-jobnametext {
width: 100%;
}
@@ -117,7 +144,10 @@ notebooksview-component {
display: inline-block;
}
#operatorsDiv .joboperatorsview-grid .slick-cell.l1.r1 .operatorview-operatornametext,
#operatorsDiv
.joboperatorsview-grid
.slick-cell.l1.r1
.operatorview-operatornametext,
#alertsDiv .jobalertsview-grid .slick-cell.l1.r1 .alertview-alertnametext,
#proxiesDiv .jobproxiesview-grid .slick-cell.l1.r1 .proxyview-proxynametext {
text-overflow: ellipsis;
@@ -132,8 +162,18 @@ notebooksview-component {
border-bottom: none;
}
.jobview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row .slick-cell.l1.r1.error-row,
.jobnotebooksview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row .slick-cell.l1.r1.error-row {
.jobview-grid
> .monaco-table
.slick-viewport
> .grid-canvas
> .ui-widget-content.slick-row
.slick-cell.l1.r1.error-row,
.jobnotebooksview-grid
> .monaco-table
.slick-viewport
> .grid-canvas
> .ui-widget-content.slick-row
.slick-cell.l1.r1.error-row {
width: 100%;
opacity: 1;
font-weight: 700;
@@ -141,7 +181,12 @@ notebooksview-component {
color: orangered;
}
.jobnotebooksview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row .slick-cell.l1.r1.notebook-error-row {
.jobnotebooksview-grid
> .monaco-table
.slick-viewport
> .grid-canvas
> .ui-widget-content.slick-row
.slick-cell.l1.r1.notebook-error-row {
width: 100%;
opacity: 1;
font-weight: 700;
@@ -150,9 +195,24 @@ notebooksview-component {
color: orange;
}
.jobview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row .slick-cell._detail_selector.error-row,
.jobnotebooksview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row .slick-cell._detail_selector.error-row,
.jobnotebooksview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row .slick-cell._detail_selector.notebook-error-row {
.jobview-grid
> .monaco-table
.slick-viewport
> .grid-canvas
> .ui-widget-content.slick-row
.slick-cell._detail_selector.error-row,
.jobnotebooksview-grid
> .monaco-table
.slick-viewport
> .grid-canvas
> .ui-widget-content.slick-row
.slick-cell._detail_selector.error-row,
.jobnotebooksview-grid
> .monaco-table
.slick-viewport
> .grid-canvas
> .ui-widget-content.slick-row
.slick-cell._detail_selector.notebook-error-row {
opacity: 1;
}
@@ -192,7 +252,7 @@ notebooksview-component {
#jobsDiv .detail,
#notebooksDiv .detail {
padding: 5px
padding: 5px;
}
#jobsDiv .preload,
@@ -202,69 +262,83 @@ notebooksview-component {
#jobsDiv .dynamic-cell-detail > :first-child,
#notebooksDiv .dynamic-cell-detail > :first-child {
vertical-align: middle;
line-height: 13px;
padding: 10px;
margin-left: 20px;
vertical-align: middle;
line-height: 13px;
padding: 10px;
margin-left: 20px;
}
.jobsview-icon {
background-image: url('./job.svg');
background-image: url("./job.svg");
}
.vs-dark .jobsview-icon,
.hc-black .jobsview-icon {
background-image: url('./job_inverse.svg');
background-image: url("./job_inverse.svg");
}
.notebooksview-icon {
background-image: url('./notebook.svg');
background-image: url("./notebook.svg");
}
.vs-dark .notebooksview-icon,
.hc-black .notebooksview-icon {
background-image: url('./notebook_inverse.svg');
background-image: url("./notebook_inverse.svg");
}
.alertsview-icon {
background-image: url('./alert.svg');
background-image: url("./alert.svg");
}
.vs-dark .alertsview-icon,
.hc-black .alertsview-icon {
background-image: url('./alert_inverse.svg');
background-image: url("./alert_inverse.svg");
}
.proxiesview-icon {
background-image: url('./proxy.svg');
background-image: url("./proxy.svg");
}
.vs-dark .proxiesview-icon,
.hc-black .proxiesview-icon {
background-image: url('./proxy_inverse.svg');
background-image: url("./proxy_inverse.svg");
}
.operatorsview-icon {
background-image: url('./operator.svg');
background-image: url("./operator.svg");
}
.vs-dark .operatorsview-icon,
.hc-black .operatorsview-icon {
background-image: url('./operator_inverse.svg');
background-image: url("./operator_inverse.svg");
}
agentview-component .jobview-grid .grid-canvas > .ui-widget-content.slick-row.even > .slick-cell,
agentview-component .jobnotebooksview-grid .grid-canvas > .ui-widget-content.slick-row.odd > .slick-cell {
agentview-component
.jobview-grid
.grid-canvas
> .ui-widget-content.slick-row.even
> .slick-cell,
agentview-component
.jobnotebooksview-grid
.grid-canvas
> .ui-widget-content.slick-row.odd
> .slick-cell {
cursor: pointer;
}
.vs-dark .jobview-grid > .monaco-table .slick-header-columns .slick-resizable-handle {
.vs-dark
.jobview-grid
> .monaco-table
.slick-header-columns
.slick-resizable-handle {
border-left: 1px dotted white;
}
.hc-black .jobview-grid > .monaco-table .slick-header-columns .slick-resizable-handle {
.hc-black
.jobview-grid
> .monaco-table
.slick-header-columns
.slick-resizable-handle {
border-left: 1px dotted #2b56f2;
}
@@ -279,47 +353,188 @@ agentview-component .jobnotebooksview-grid .grid-canvas > .ui-widget-content.sli
padding-left: 15px;
}
#jobsDiv jobsview-component .jobview-grid .slick-cell.l1.r1.error-row td.jobview-jobnameindicatorfailure,
#notebooksDiv notebooksview-component .jobnotebooksview-grid .slick-cell.l1.r1.error-row td.jobview-jobnameindicatorfailure,
#notebooksDiv notebooksview-component .jobnotebooksview-grid .slick-cell.l1.r1.notebook-error-row td.jobview-jobnameindicatorfailure {
#jobsDiv
jobsview-component
.jobview-grid
.slick-cell.l1.r1.error-row
td.jobview-jobnameindicatorfailure,
#notebooksDiv
notebooksview-component
.jobnotebooksview-grid
.slick-cell.l1.r1.error-row
td.jobview-jobnameindicatorfailure,
#notebooksDiv
notebooksview-component
.jobnotebooksview-grid
.slick-cell.l1.r1.notebook-error-row
td.jobview-jobnameindicatorfailure {
width: 0;
background: none;
}
#jobsDiv .jobview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row:hover > .slick-cell,
#jobsDiv .jobview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row.hovered > .slick-cell,
#jobsDiv .jobview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row> .slick-cell.hovered,
#notebooksDiv .jobnotebooksview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row:hover > .slick-cell,
#notebooksDiv .jobnotebooksview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row.hovered > .slick-cell,
#notebooksDiv .jobnotebooksview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row> .slick-cell.hovered {
#jobsDiv
.jobview-grid
> .monaco-table
.slick-viewport
> .grid-canvas
> .ui-widget-content.slick-row:hover
> .slick-cell,
#jobsDiv
.jobview-grid
> .monaco-table
.slick-viewport
> .grid-canvas
> .ui-widget-content.slick-row.hovered
> .slick-cell,
#jobsDiv
.jobview-grid
> .monaco-table
.slick-viewport
> .grid-canvas
> .ui-widget-content.slick-row
> .slick-cell.hovered,
#notebooksDiv
.jobnotebooksview-grid
> .monaco-table
.slick-viewport
> .grid-canvas
> .ui-widget-content.slick-row:hover
> .slick-cell,
#notebooksDiv
.jobnotebooksview-grid
> .monaco-table
.slick-viewport
> .grid-canvas
> .ui-widget-content.slick-row.hovered
> .slick-cell,
#notebooksDiv
.jobnotebooksview-grid
> .monaco-table
.slick-viewport
> .grid-canvas
> .ui-widget-content.slick-row
> .slick-cell.hovered {
background: #dcdcdc !important;
}
.vs-dark #jobsDiv .jobview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row:hover > .slick-cell,
.vs-dark #jobsDiv .jobview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row > .slick-cell.hovered,
.vs-dark #jobsDiv .jobview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row.hovered > .slick-cell,
.vs-dark #notebooksDiv .jobnotebooksview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row:hover > .slick-cell,
.vs-dark #notebooksDiv .jobnotebooksview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row > .slick-cell.hovered,
.vs-dark #notebooksDiv .jobnotebooksview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row.hovered > .slick-cell {
.vs-dark
#jobsDiv
.jobview-grid
> .monaco-table
.slick-viewport
> .grid-canvas
> .ui-widget-content.slick-row:hover
> .slick-cell,
.vs-dark
#jobsDiv
.jobview-grid
> .monaco-table
.slick-viewport
> .grid-canvas
> .ui-widget-content.slick-row
> .slick-cell.hovered,
.vs-dark
#jobsDiv
.jobview-grid
> .monaco-table
.slick-viewport
> .grid-canvas
> .ui-widget-content.slick-row.hovered
> .slick-cell,
.vs-dark
#notebooksDiv
.jobnotebooksview-grid
> .monaco-table
.slick-viewport
> .grid-canvas
> .ui-widget-content.slick-row:hover
> .slick-cell,
.vs-dark
#notebooksDiv
.jobnotebooksview-grid
> .monaco-table
.slick-viewport
> .grid-canvas
> .ui-widget-content.slick-row
> .slick-cell.hovered,
.vs-dark
#notebooksDiv
.jobnotebooksview-grid
> .monaco-table
.slick-viewport
> .grid-canvas
> .ui-widget-content.slick-row.hovered
> .slick-cell {
background: #444444 !important;
}
.hc-black #jobsDiv .jobview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row:hover > .slick-cell,
.hc-black #jobsDiv .jobview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row > .slick-cell.hovered,
.hc-black #jobsDiv .jobview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row.hovered > .slick-cell,
.hc-black #notebooksDiv .jobnotebooksview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row:hover > .slick-cell,
.hc-black #notebooksDiv .jobnotebooksview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row > .slick-cell.hovered,
.hc-black #notebooksDiv .jobnotebooksview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row.hovered > .slick-cell {
.hc-black
#jobsDiv
.jobview-grid
> .monaco-table
.slick-viewport
> .grid-canvas
> .ui-widget-content.slick-row:hover
> .slick-cell,
.hc-black
#jobsDiv
.jobview-grid
> .monaco-table
.slick-viewport
> .grid-canvas
> .ui-widget-content.slick-row
> .slick-cell.hovered,
.hc-black
#jobsDiv
.jobview-grid
> .monaco-table
.slick-viewport
> .grid-canvas
> .ui-widget-content.slick-row.hovered
> .slick-cell,
.hc-black
#notebooksDiv
.jobnotebooksview-grid
> .monaco-table
.slick-viewport
> .grid-canvas
> .ui-widget-content.slick-row:hover
> .slick-cell,
.hc-black
#notebooksDiv
.jobnotebooksview-grid
> .monaco-table
.slick-viewport
> .grid-canvas
> .ui-widget-content.slick-row
> .slick-cell.hovered,
.hc-black
#notebooksDiv
.jobnotebooksview-grid
> .monaco-table
.slick-viewport
> .grid-canvas
> .ui-widget-content.slick-row.hovered
> .slick-cell {
background: none !important;
}
table.jobprevruns div.bar0, table.jobprevruns div.bar1, table.jobprevruns div.bar2,
table.jobprevruns div.bar3, table.jobprevruns div.bar4, table.jobprevruns div.bar5 {
table.jobprevruns div.bar,
table.jobprevruns div.bar0,
table.jobprevruns div.bar1,
table.jobprevruns div.bar2,
table.jobprevruns div.bar3,
table.jobprevruns div.bar4,
table.jobprevruns div.bar5 {
padding-top: 3px;
padding-left: 5px;
width: 10px;
}
table.jobprevruns div.bar {
cursor: pointer;
}
.jobview-grid .slick-cell.l10.r10,
.jobnotebooksview-grid .slick-cell.l10.r10 {
text-align: center;
@@ -336,51 +551,51 @@ table.jobprevruns > tbody {
#notebooksDiv .jobnotebooksview-grid {
height: calc(100% - 75px);
width : 100%;
width: 100%;
display: block;
}
#alertsDiv .jobalertsview-grid {
height: calc(100% - 75px);
width : 100%;
width: 100%;
display: block;
}
#operatorsDiv .joboperatorsview-grid {
height: calc(100% - 75px);
width : 100%;
width: 100%;
display: block;
overflow: scroll;
}
#proxiesDiv .jobproxiesview-grid {
height: calc(100% - 75px);
width : 100%;
width: 100%;
display: block;
}
.vs .action-label.icon.refreshIcon {
background-image: url('refresh.svg');
background-image: url("refresh.svg");
}
.vs-dark .action-label.icon.refreshIcon,
.hc-black .action-label.icon.refreshIcon {
background-image: url('refresh_inverse.svg');
.hc-black .action-label.icon.refreshIcon {
background-image: url("refresh_inverse.svg");
}
.vs .action-label.icon.openNotebook {
background-image: url('open_notebook.svg');
background-image: url("open_notebook.svg");
}
.vs-dark .action-label.icon.openNotebook,
.hc-black .action-label.icon.openNotebook {
background-image: url('open_notebook_inverse.svg');
.hc-black .action-label.icon.openNotebook {
background-image: url("open_notebook_inverse.svg");
}
.agent-actionbar-container .monaco-action-bar > ul.actions-container > li.action-item {
.agent-actionbar-container
.monaco-action-bar
> ul.actions-container
> li.action-item {
padding-left: 20px;
}
@@ -390,107 +605,311 @@ notebooksview-component .jobnotebooksview-grid .slick-cell.notebook-error-row {
opacity: 0;
}
#notebooksDiv notebooksview-component .jobnotebooksview-grid .grid-canvas .ui-widget-content.slick-row .slick-cell {
#notebooksDiv
notebooksview-component
.jobnotebooksview-grid
.grid-canvas
.ui-widget-content.slick-row
.slick-cell {
border-right: transparent !important;
border-left: transparent !important;
line-height: 33px !important;
}
#notebooksDiv .jobnotebooksview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row:hover > .slick-cell,
#notebooksDiv .jobnotebooksview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row.hovered > .slick-cell,
#notebooksDiv .jobnotebooksview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row> .slick-cell.hovered {
#notebooksDiv
.jobnotebooksview-grid
> .monaco-table
.slick-viewport
> .grid-canvas
> .ui-widget-content.slick-row:hover
> .slick-cell,
#notebooksDiv
.jobnotebooksview-grid
> .monaco-table
.slick-viewport
> .grid-canvas
> .ui-widget-content.slick-row.hovered
> .slick-cell,
#notebooksDiv
.jobnotebooksview-grid
> .monaco-table
.slick-viewport
> .grid-canvas
> .ui-widget-content.slick-row
> .slick-cell.hovered {
background: #dcdcdc !important;
}
.vs-dark #notebooksDiv .jobnotebooksview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row:hover > .slick-cell,
.vs-dark #notebooksDiv .jobnotebooksview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row > .slick-cell.hovered,
.vs-dark #notebooksDiv .jobnotebooksview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row.hovered > .slick-cell {
.vs-dark
#notebooksDiv
.jobnotebooksview-grid
> .monaco-table
.slick-viewport
> .grid-canvas
> .ui-widget-content.slick-row:hover
> .slick-cell,
.vs-dark
#notebooksDiv
.jobnotebooksview-grid
> .monaco-table
.slick-viewport
> .grid-canvas
> .ui-widget-content.slick-row
> .slick-cell.hovered,
.vs-dark
#notebooksDiv
.jobnotebooksview-grid
> .monaco-table
.slick-viewport
> .grid-canvas
> .ui-widget-content.slick-row.hovered
> .slick-cell {
background: #444444 !important;
}
.vs-dark .jobnotebooksview-grid > .monaco-table .slick-header-columns .slick-resizable-handle {
.vs-dark
.jobnotebooksview-grid
> .monaco-table
.slick-header-columns
.slick-resizable-handle {
border-left: 1px dotted white;
}
.jobnotebooksview-grid > .monaco-table .slick-header-columns .slick-resizable-handle {
.jobnotebooksview-grid
> .monaco-table
.slick-header-columns
.slick-resizable-handle {
border-left: 1px dotted #444444;
}
#alertsDiv jobalertsview-component .jobalertsview-grid .grid-canvas .ui-widget-content.slick-row .slick-cell {
#alertsDiv
jobalertsview-component
.jobalertsview-grid
.grid-canvas
.ui-widget-content.slick-row
.slick-cell {
border-right: transparent !important;
border-left: transparent !important;
line-height: 33px !important;
}
#alertsDiv .jobalertsview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row:hover > .slick-cell,
#alertsDiv .jobalertsview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row.hovered > .slick-cell,
#alertsDiv .jobalertsview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row> .slick-cell.hovered {
#alertsDiv
.jobalertsview-grid
> .monaco-table
.slick-viewport
> .grid-canvas
> .ui-widget-content.slick-row:hover
> .slick-cell,
#alertsDiv
.jobalertsview-grid
> .monaco-table
.slick-viewport
> .grid-canvas
> .ui-widget-content.slick-row.hovered
> .slick-cell,
#alertsDiv
.jobalertsview-grid
> .monaco-table
.slick-viewport
> .grid-canvas
> .ui-widget-content.slick-row
> .slick-cell.hovered {
background: #dcdcdc !important;
}
.vs-dark #alertsDiv .jobalertsview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row:hover > .slick-cell,
.vs-dark #alertsDiv .jobalertsview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row > .slick-cell.hovered,
.vs-dark #alertsDiv .jobalertsview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row.hovered > .slick-cell {
.vs-dark
#alertsDiv
.jobalertsview-grid
> .monaco-table
.slick-viewport
> .grid-canvas
> .ui-widget-content.slick-row:hover
> .slick-cell,
.vs-dark
#alertsDiv
.jobalertsview-grid
> .monaco-table
.slick-viewport
> .grid-canvas
> .ui-widget-content.slick-row
> .slick-cell.hovered,
.vs-dark
#alertsDiv
.jobalertsview-grid
> .monaco-table
.slick-viewport
> .grid-canvas
> .ui-widget-content.slick-row.hovered
> .slick-cell {
background: #444444 !important;
}
.vs-dark .jobalertsview-grid > .monaco-table .slick-header-columns .slick-resizable-handle {
.vs-dark
.jobalertsview-grid
> .monaco-table
.slick-header-columns
.slick-resizable-handle {
border-left: 1px dotted white;
}
.jobalertsview-grid > .monaco-table .slick-header-columns .slick-resizable-handle {
.jobalertsview-grid
> .monaco-table
.slick-header-columns
.slick-resizable-handle {
border-left: 1px dotted #444444;
}
#operatorsDiv joboperatorsview-component .joboperatorsview-grid .grid-canvas .ui-widget-content.slick-row .slick-cell {
#operatorsDiv
joboperatorsview-component
.joboperatorsview-grid
.grid-canvas
.ui-widget-content.slick-row
.slick-cell {
border-right: transparent !important;
border-left: transparent !important;
line-height: 33px !important;
}
#operatorsDiv .joboperatorsview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row:hover > .slick-cell,
#operatorsDiv .joboperatorsview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row.hovered > .slick-cell,
#operatorsDiv .joboperatorsview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row> .slick-cell.hovered {
#operatorsDiv
.joboperatorsview-grid
> .monaco-table
.slick-viewport
> .grid-canvas
> .ui-widget-content.slick-row:hover
> .slick-cell,
#operatorsDiv
.joboperatorsview-grid
> .monaco-table
.slick-viewport
> .grid-canvas
> .ui-widget-content.slick-row.hovered
> .slick-cell,
#operatorsDiv
.joboperatorsview-grid
> .monaco-table
.slick-viewport
> .grid-canvas
> .ui-widget-content.slick-row
> .slick-cell.hovered {
background: #dcdcdc !important;
}
.vs-dark #operatorsDiv .joboperatorsview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row:hover > .slick-cell,
.vs-dark #operatorsDiv .joboperatorsview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row > .slick-cell.hovered,
.vs-dark #operatorsDiv .joboperatorsview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row.hovered > .slick-cell {
.vs-dark
#operatorsDiv
.joboperatorsview-grid
> .monaco-table
.slick-viewport
> .grid-canvas
> .ui-widget-content.slick-row:hover
> .slick-cell,
.vs-dark
#operatorsDiv
.joboperatorsview-grid
> .monaco-table
.slick-viewport
> .grid-canvas
> .ui-widget-content.slick-row
> .slick-cell.hovered,
.vs-dark
#operatorsDiv
.joboperatorsview-grid
> .monaco-table
.slick-viewport
> .grid-canvas
> .ui-widget-content.slick-row.hovered
> .slick-cell {
background: #444444 !important;
}
.vs-dark .joboperatorsview-grid > .monaco-table .slick-header-columns .slick-resizable-handle {
.vs-dark
.joboperatorsview-grid
> .monaco-table
.slick-header-columns
.slick-resizable-handle {
border-left: 1px dotted white;
}
.joboperatorsview-grid > .monaco-table .slick-header-columns .slick-resizable-handle {
.joboperatorsview-grid
> .monaco-table
.slick-header-columns
.slick-resizable-handle {
border-left: 1px dotted #444444;
}
#proxiesDiv jobproxiesview-component .jobproxiesview-grid .grid-canvas .ui-widget-content.slick-row .slick-cell {
#proxiesDiv
jobproxiesview-component
.jobproxiesview-grid
.grid-canvas
.ui-widget-content.slick-row
.slick-cell {
border-right: transparent !important;
border-left: transparent !important;
line-height: 33px !important;
}
#proxiesDiv .jobproxiesview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row:hover > .slick-cell,
#proxiesDiv .jobproxiesview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row.hovered > .slick-cell,
#proxiesDiv .jobproxiesview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row> .slick-cell.hovered {
#proxiesDiv
.jobproxiesview-grid
> .monaco-table
.slick-viewport
> .grid-canvas
> .ui-widget-content.slick-row:hover
> .slick-cell,
#proxiesDiv
.jobproxiesview-grid
> .monaco-table
.slick-viewport
> .grid-canvas
> .ui-widget-content.slick-row.hovered
> .slick-cell,
#proxiesDiv
.jobproxiesview-grid
> .monaco-table
.slick-viewport
> .grid-canvas
> .ui-widget-content.slick-row
> .slick-cell.hovered {
background: #dcdcdc !important;
}
.vs-dark #proxiesDiv .jobproxiesview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row:hover > .slick-cell,
.vs-dark #proxiesDiv .jobproxiesview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row > .slick-cell.hovered,
.vs-dark #proxiesDiv .jobproxiesview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row.hovered > .slick-cell {
.vs-dark
#proxiesDiv
.jobproxiesview-grid
> .monaco-table
.slick-viewport
> .grid-canvas
> .ui-widget-content.slick-row:hover
> .slick-cell,
.vs-dark
#proxiesDiv
.jobproxiesview-grid
> .monaco-table
.slick-viewport
> .grid-canvas
> .ui-widget-content.slick-row
> .slick-cell.hovered,
.vs-dark
#proxiesDiv
.jobproxiesview-grid
> .monaco-table
.slick-viewport
> .grid-canvas
> .ui-widget-content.slick-row.hovered
> .slick-cell {
background: #444444 !important;
}
.vs-dark .jobproxiesview-grid > .monaco-table .slick-header-columns .slick-resizable-handle {
.vs-dark
.jobproxiesview-grid
> .monaco-table
.slick-header-columns
.slick-resizable-handle {
border-left: 1px dotted white;
}
.jobproxiesview-grid > .monaco-table .slick-header-columns .slick-resizable-handle {
.jobproxiesview-grid
> .monaco-table
.slick-header-columns
.slick-resizable-handle {
border-left: 1px dotted #444444;
}

View File

@@ -5,7 +5,7 @@
*--------------------------------------------------------------------------------------------*/
-->
<div class="jobhistory-heading-container">
<h1 class="job-heading">Notebook | {{ this._agentNotebookInfo?.name }}</h1>
<h1 class="job-heading">Notebook Jobs| {{ this._agentNotebookInfo?.name }}</h1>
<div class="icon in-progress" *ngIf="showProgressWheel()"></div>
</div>
@@ -29,9 +29,9 @@
<!-- Overview -->
<div class="overview-container">
<div class="overview-tab" (click)="toggleCollapse()" tabindex="0">
<div class="overview-tab" (click)="toggleCollapse()">
<input id="accordion" type="checkbox" />
<label for="accordion">
<label for="accordion" tabindex="0">
<div
class="resultsViewCollapsible collapsed"
(click)="toggleCollapse()"
@@ -48,10 +48,10 @@
{{ this._agentNotebookInfo?.targetDatabase }}
</td>
<td id="col3">
Enabled:
Has Schedule:
</td>
<td id="col4">
{{ this._agentNotebookInfo?.enabled }}
{{ this._agentNotebookInfo?.hasSchedule }}
</td>
</tr>
<tr>
@@ -81,11 +81,10 @@
<div
class="overview-tab"
(click)="toggleGridCollapse(i)"
tabindex="{{i}}"
*ngIf="grid.histories?.length"
>
<input id="accordion{{ i }}" type="checkbox" class="grid-arrow" />
<label for="accordion{{ i }}">
<input id="accordion{{ i }}" type="checkbox" class="grid-arrow"/>
<label for="accordion{{ i }}" tabindex="0">
<div id= "history-grid-icon{{i}}" (click)="toggleGridCollapse(i)"
class="resultsViewCollapsible"></div>
{{ grid.title }}
@@ -94,11 +93,13 @@
<div
*ngFor="let history of grid.histories"
class="notebook-grid-item"
tabindex="0"
(dblclick)="openNotebook(history)"
title="{{ createdTooltip(history) }}"
(contextmenu)="
openHistoryContextMenu($event, history, grid.contextMenuType)
"
(keydown.enter)="openNotebook(history)"
>
<div
*ngIf="history.materializedNotebookErrorInfo"

View File

@@ -14,7 +14,7 @@ import { Table } from 'sql/base/browser/ui/table/table';
import { AgentViewComponent } from 'sql/workbench/parts/jobManagement/browser/agentView.component';
import { RowDetailView } from 'sql/base/browser/ui/table/plugins/rowDetailView';
import { NotebookCacheObject } from 'sql/platform/jobManagement/common/jobManagementService';
import { EditJobAction, NewNotebookJobAction, RunJobAction, EditNotebookJobAction, JobsRefreshAction, IJobActionInfo, DeleteNotebookAction } from 'sql/platform/jobManagement/browser/jobActions';
import { EditJobAction, NewNotebookJobAction, RunJobAction, EditNotebookJobAction, JobsRefreshAction, IJobActionInfo, DeleteNotebookAction, OpenLatestRunMaterializedNotebook } from 'sql/platform/jobManagement/browser/jobActions';
import { JobManagementUtilities } from 'sql/platform/jobManagement/browser/jobManagementUtilities';
import { HeaderFilter } from 'sql/base/browser/ui/table/plugins/headerFilter.plugin';
import { IJobManagementService } from 'sql/platform/jobManagement/common/interfaces';
@@ -395,6 +395,22 @@ export class NotebooksViewComponent extends JobManagementView implements OnInit,
this.highlightErrorRows(e1), (e2) => this.hightlightNonErrorRows(e2));
});
jQuery('.bar').click((e) => {
let clickEventTarget = e.target;
let barId = Number(clickEventTarget.id.replace('bar', ''));
let jobId = clickEventTarget.parentElement.offsetParent.id.replace('notebook', '');
let notebooks = this._notebookCacheObject.notebooks;
let targetNotebook: azdata.AgentNotebookInfo;
for (let i = 0; i < notebooks.length; i++) {
if (jobId === notebooks[i].jobId) {
targetNotebook = notebooks[i];
break;
}
}
this.openLastNRun(targetNotebook, barId, 5);
e.stopPropagation();
});
// cache the dataview for future use
this._notebookCacheObject.dataView = this.dataView;
this.filterValueMap['start'] = [[], this.dataView.getItems()];
@@ -540,21 +556,21 @@ export class NotebooksViewComponent extends JobManagementView implements OnInit,
if (runChart && runChart.length > 0) {
return `<table class="jobprevruns" id="${dataContext.id}">
<tr>
<td>${runChart[0] ? runChart[0] : '<div class="bar0"></div>'}</td>
<td>${runChart[1] ? runChart[1] : '<div class="bar1"></div>'}</td>
<td>${runChart[2] ? runChart[2] : '<div class="bar2"></div>'}</td>
<td>${runChart[3] ? runChart[3] : '<div class="bar3"></div>'}</td>
<td>${runChart[4] ? runChart[4] : '<div class="bar4"></div>'}</td>
<td>${runChart[0] ? runChart[0] : '<div class="bar" id="bar0"></div>'}</td>
<td>${runChart[1] ? runChart[1] : '<div class="bar" id="bar1"></div>'}</td>
<td>${runChart[2] ? runChart[2] : '<div class="bar" id="bar2"></div>'}</td>
<td>${runChart[3] ? runChart[3] : '<div class="bar" id="bar3"></div>'}</td>
<td>${runChart[4] ? runChart[4] : '<div class="bar" id="bar4"></div>'}</td>
</tr>
</table>`;
} else {
return `<table class="jobprevruns" id="${dataContext.id}">
<tr>
<td><div class="bar0"></div></td>
<td><div class="bar1"></div></td>
<td><div class="bar2"></div></td>
<td><div class="bar3"></div></td>
<td><div class="bar4"></div></td>
<td><div class="bar" id="bar0"></div></td>
<td><div class="bar" id="bar1"></div></td>
<td><div class="bar" id="bar2"></div></td>
<td><div class="bar" id="bar3"></div></td>
<td><div class="bar" id="bar4"></div></td>
</tr>
</table>`;
}
@@ -667,7 +683,7 @@ export class NotebooksViewComponent extends JobManagementView implements OnInit,
if (jobHistories[i].materializedNotebookErrorInfo !== null && jobHistories[i].materializedNotebookErrorInfo.length > 0) {
bgColor = 'orange';
}
let runGraph = jQuery(`table.jobprevruns#${jobId} > tbody > tr > td > div.bar${i}`);
let runGraph = jQuery(`table.jobprevruns#${jobId} > tbody > tr > td > #bar${i}`);
if (runGraph.length > 0) {
runGraph.css('height', chartHeights[i]);
@@ -884,10 +900,11 @@ export class NotebooksViewComponent extends JobManagementView implements OnInit,
const editAction = this._instantiationService.createInstance(EditJobAction);
const editNotebookAction = this._instantiationService.createInstance(EditNotebookJobAction);
const runJobAction = this._instantiationService.createInstance(RunJobAction);
const openLatestRunAction = this._instantiationService.createInstance(OpenLatestRunMaterializedNotebook);
return [
runJobAction,
openLatestRunAction,
editNotebookAction,
editAction,
this._instantiationService.createInstance(DeleteNotebookAction)
];
}
@@ -981,4 +998,31 @@ export class NotebooksViewComponent extends JobManagementView implements OnInit,
let ownerUri: string = this._commonService.connectionManagementService.connectionInfo.ownerUri;
await this._commandService.executeCommand('agent.openNotebookDialog', ownerUri);
}
public async openLastNRun(notebook: azdata.AgentNotebookInfo, n: number, maxVisibleElements: number) {
let notebookHistories = this._notebookCacheObject.getNotebookHistory(notebook.jobId);
if (notebookHistories && n < notebookHistories.length) {
notebookHistories = notebookHistories.sort((h1, h2) => {
return new Date(h2.runDate).getTime() - new Date(h1.runDate).getTime();
});
if (notebookHistories.length > maxVisibleElements) {
n = notebookHistories.length - (maxVisibleElements - n);
}
n = notebookHistories.length - 1 - n;
let history: azdata.AgentNotebookHistoryInfo = notebookHistories[n];
// Did Job Fail? if yes, then notebook to return
if (history.runStatus === 0) {
return;
}
let ownerUri: string = this._commonService.connectionManagementService.connectionInfo.ownerUri;
let targetDatabase = notebook.targetDatabase;
const result = await this._jobManagementService.getMaterialziedNotebook(ownerUri, targetDatabase, history.materializedNotebookId);
if (result) {
let regex = /:|-/gi;
let readableDataTimeString = history.runDate.replace(regex, '').replace(' ', '');
let tempNotebookFileName = notebook.name + '_' + readableDataTimeString;
await this._commandService.executeCommand('agent.openNotebookEditorFromJsonString', tempNotebookFileName, result.notebookMaterialized);
}
}
}
}

View File

@@ -341,10 +341,17 @@ export class NotebookInput extends EditorInput {
} else {
let textOrUntitledEditorModel: UntitledEditorModel | IEditorModel;
if (this.resource.scheme === Schemas.untitled) {
textOrUntitledEditorModel = this._untitledEditorModel ? this._untitledEditorModel : await this._textInput.resolve();
}
else {
if (this._untitledEditorModel) {
this._untitledEditorModel.textEditorModel.onBeforeAttached();
textOrUntitledEditorModel = this._untitledEditorModel;
} else {
let resolvedInput = await this._textInput.resolve();
resolvedInput.textEditorModel.onBeforeAttached();
textOrUntitledEditorModel = resolvedInput;
}
} else {
const textEditorModelReference = await this.textModelService.createModelReference(this.resource);
textEditorModelReference.object.textEditorModel.onBeforeAttached();
textOrUntitledEditorModel = await textEditorModelReference.object.load();
}
this._model = this.instantiationService.createInstance(NotebookEditorModel, this.resource, textOrUntitledEditorModel);
@@ -385,6 +392,9 @@ export class NotebookInput extends EditorInput {
}
public dispose(): void {
if (this._model) {
this._model.editorModel.textEditorModel.onBeforeDetached();
}
this._disposeContainer();
super.dispose();
}

View File

@@ -39,11 +39,60 @@ export class NotebookTextFileModel {
// convert the range to leverage offsets in the json
if (contentChange && contentChange.modelContentChangedEvent && areRangePropertiesPopulated(cellGuidRange)) {
contentChange.modelContentChangedEvent.changes.forEach(change => {
// When writing to JSON we need to escape double quotes and backslashes
let textEscapedQuotesAndBackslashes = change.text.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
let startLineNumber = change.range.startLineNumber + cellGuidRange.startLineNumber - 1;
let endLineNumber = change.range.endLineNumber + cellGuidRange.startLineNumber - 1;
let startLineText = textEditorModel.textEditorModel.getLineContent(startLineNumber);
let endLineText = textEditorModel.textEditorModel.getLineContent(endLineNumber);
/* This gets the text on the start and end lines of the range where we'll be inserting text. We need to convert the escaped strings to unescaped strings.
Example:
Previous state
EDITOR:
""""
TEXTEDITORMODEL:
' "\"\"\"\""'
Now, user wants to insert text after the 4 double quotes, like so:
EDITOR:
""""sample text
TEXTEDITORMODEL (result):
' "\"\"\"\"sample text"'
Notice that we don't have a 1:1 mapping for characters from the editor to the text editor model, because the double quotes need to be escaped
(the same is true for backslashes).
Therefore, we need to determine (at both the start and end lines) the "real" start and end columns in the text editor model by counting escaped characters.
We do this by doing the following:
- Start with (escaped) text in the text editor model
- Unescape this text
- Get a substring of that text from the column in JSON until the change's startColumn (starting from the first " in the text editor model)
- Escape this substring
- Leverage the substring's length to calculate the "real" start/end columns
*/
let unescapedStartSubstring = startLineText.replace(/\\"/g, '"').replace(/\\\\/g, '\\').substr(cellGuidRange.startColumn, change.range.startColumn - 1);
let unescapedEndSubstring = endLineText.replace(/\\"/g, '"').replace(/\\\\/g, '\\').substr(cellGuidRange.startColumn, change.range.endColumn - 1);
// now we have the portion before the text to be inserted for both the start and end lines
// so next step is to escape " and \
let escapedStartSubstring = unescapedStartSubstring.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
let escapedEndSubstring = unescapedEndSubstring.replace(/\\/g, '\\\\').replace(/"/g, '\\"');
let computedStartColumn = escapedStartSubstring.length + cellGuidRange.startColumn + 1;
let computedEndColumn = escapedEndSubstring.length + cellGuidRange.startColumn + 1;
let convertedRange: IRange = {
startLineNumber: change.range.startLineNumber + cellGuidRange.startLineNumber - 1,
endLineNumber: change.range.endLineNumber + cellGuidRange.startLineNumber - 1,
startColumn: change.range.startColumn + cellGuidRange.startColumn,
endColumn: change.range.endColumn + cellGuidRange.startColumn
startLineNumber: startLineNumber,
endLineNumber: endLineNumber,
startColumn: computedStartColumn,
endColumn: computedEndColumn
};
// Need to subtract one because we're going from 1-based to 0-based
let startSpaces: string = ' '.repeat(cellGuidRange.startColumn - 1);
@@ -52,13 +101,12 @@ export class NotebookTextFileModel {
// this is another string
textEditorModel.textEditorModel.applyEdits([{
range: new Range(convertedRange.startLineNumber, convertedRange.startColumn, convertedRange.endLineNumber, convertedRange.endColumn),
text: change.text.split(this._eol).join('\\n\",'.concat(this._eol).concat(startSpaces).concat('\"'))
text: textEscapedQuotesAndBackslashes.split(/[\r\n]+/gm).join('\\n\",'.concat(this._eol).concat(startSpaces).concat('\"'))
}]);
});
} else {
return false;
return true;
}
return true;
return false;
}
public transformAndApplyEditForOutputUpdate(contentChange: NotebookContentChange, textEditorModel: TextFileEditorModel | UntitledEditorModel): boolean {
@@ -69,17 +117,23 @@ export class NotebookTextFileModel {
} else {
newOutput = '\n'.concat(newOutput).concat('\n');
}
let range = this.getEndOfOutputs(textEditorModel, contentChange.cells[0].cellGuid);
if (range) {
// Execution count will always be after the end of the outputs in JSON. This is a sanity mechanism.
let executionCountMatch = this.getExecutionCountRange(textEditorModel, contentChange.cells[0].cellGuid);
if (!executionCountMatch || !executionCountMatch.range) {
return false;
}
let endOutputsRange = this.getEndOfOutputs(textEditorModel, contentChange.cells[0].cellGuid);
if (endOutputsRange && endOutputsRange.startLineNumber < executionCountMatch.range.startLineNumber) {
textEditorModel.textEditorModel.applyEdits([{
range: new Range(range.startLineNumber, range.startColumn, range.startLineNumber, range.startColumn),
range: new Range(endOutputsRange.startLineNumber, endOutputsRange.startColumn, endOutputsRange.startLineNumber, endOutputsRange.startColumn),
text: newOutput
}]);
return true;
}
} else {
return false;
}
return true;
return false;
}
public transformAndApplyEditForCellUpdated(contentChange: NotebookContentChange, textEditorModel: TextFileEditorModel | UntitledEditorModel): boolean {
@@ -111,9 +165,7 @@ export class NotebookTextFileModel {
if (!textEditorModel || !contentChange || !contentChange.cells || !contentChange.cells[0] || !contentChange.cells[0].cellGuid) {
return false;
}
if (!this.getOutputNodeByGuid(textEditorModel, contentChange.cells[0].cellGuid)) {
this.updateOutputBeginRange(textEditorModel, contentChange.cells[0].cellGuid);
}
this.updateOutputBeginRange(textEditorModel, contentChange.cells[0].cellGuid);
let outputEndRange = this.getEndOfOutputs(textEditorModel, contentChange.cells[0].cellGuid);
let outputStartRange = this.getOutputNodeByGuid(textEditorModel, contentChange.cells[0].cellGuid);
if (outputStartRange && outputEndRange) {
@@ -247,7 +299,7 @@ export class NotebookTextFileModel {
}
function areRangePropertiesPopulated(range: Range) {
return range && range.startLineNumber && range.startColumn && range.endLineNumber && range.endColumn;
return range && range.startLineNumber !== 0 && range.startColumn !== 0 && range.endLineNumber !== 0 && range.endColumn !== 0;
}
function findOrSetCellGuidMatch(textEditorModel: TextFileEditorModel | UntitledEditorModel, cellGuid: string): FindMatch[] {

View File

@@ -12,7 +12,7 @@ import { ConnectionManagementService } from 'sql/platform/connection/common/conn
import { CellModel } from 'sql/workbench/parts/notebook/common/models/cell';
import { CellTypes, NotebookChangeType } from 'sql/workbench/parts/notebook/common/models/contracts';
import { ModelFactory } from 'sql/workbench/parts/notebook/common/models/modelFactory';
import { INotebookModelOptions, NotebookContentChange } from 'sql/workbench/parts/notebook/common/models/modelInterfaces';
import { INotebookModelOptions, NotebookContentChange, ICellModel } from 'sql/workbench/parts/notebook/common/models/modelInterfaces';
import { NotebookEditorModel } from 'sql/workbench/parts/notebook/browser/models/notebookInput';
import { NotebookModel } from 'sql/workbench/parts/notebook/common/models/notebookModel';
import { NotebookService } from 'sql/workbench/services/notebook/common/notebookServiceImpl';
@@ -589,6 +589,66 @@ suite('Notebook Editor Model', function (): void {
should(notebookEditorModel.lastEditFullReplacement).equal(false);
});
test('should not insert update at incorrect location', async function (): Promise<void> {
await createNewNotebookModel();
let notebookEditorModel = await createTextEditorModel(this);
notebookEditorModel.replaceEntireTextEditorModel(notebookModel, undefined);
let newCell = notebookModel.addCell(CellTypes.Code);
let contentChange: NotebookContentChange = {
changeType: NotebookChangeType.CellsModified,
cells: [newCell],
cellIndex: 0
};
notebookEditorModel.updateModel(contentChange, NotebookChangeType.CellsModified);
should(notebookEditorModel.lastEditFullReplacement).equal(true);
should(notebookEditorModel.editorModel.textEditorModel.getLineContent(14)).equal(' "outputs": [');
// First update the model with unmatched brackets
let newUnmatchedBracketOutput: nb.IStreamResult = { output_type: 'stream', name: 'stdout', text: '[0em' };
newCell[<any>'_outputs'] = newCell.outputs.concat(newUnmatchedBracketOutput);
contentChange = {
changeType: NotebookChangeType.CellOutputUpdated,
cells: [newCell]
};
notebookEditorModel.updateModel(contentChange, NotebookChangeType.CellOutputUpdated);
should(notebookEditorModel.editorModel.textEditorModel.getLineContent(8)).equal(' "source": [');
should(notebookEditorModel.editorModel.textEditorModel.getLineContent(12)).equal(' "azdata_cell_guid": "' + newCell.cellGuid + '"');
should(notebookEditorModel.editorModel.textEditorModel.getLineContent(14)).equal(' "outputs": [');
should(notebookEditorModel.editorModel.textEditorModel.getLineContent(26)).equal(' "text": "[0em"');
should(notebookEditorModel.editorModel.textEditorModel.getLineContent(27)).equal('}');
should(notebookEditorModel.editorModel.textEditorModel.getLineContent(28)).equal(' ],');
should(notebookEditorModel.editorModel.textEditorModel.getLineContent(29)).equal(' "execution_count": 0');
should(notebookEditorModel.editorModel.textEditorModel.getLineContent(30)).equal(' }');
should(notebookEditorModel.lastEditFullReplacement).equal(false);
// Now test updating the model after an unmatched bracket was previously output
let newBracketlessOutput: nb.IStreamResult = { output_type: 'stream', name: 'stdout', text: 'test test test' };
newCell[<any>'_outputs'] = newCell[<any>'_outputs'].concat(newBracketlessOutput);
contentChange = {
changeType: NotebookChangeType.CellOutputUpdated,
cells: [newCell]
};
notebookEditorModel.updateModel(contentChange, NotebookChangeType.CellOutputUpdated);
should(notebookEditorModel.editorModel.textEditorModel.getLineContent(32)).equal(' "text": "test test test"');
should(notebookEditorModel.editorModel.textEditorModel.getLineContent(33)).equal(' }');
should(notebookEditorModel.editorModel.textEditorModel.getLineContent(34)).equal(' ],');
should(notebookEditorModel.editorModel.textEditorModel.getLineContent(35)).equal(' "execution_count": 0');
should(notebookEditorModel.editorModel.textEditorModel.getLineContent(36)).equal(' }');
should(notebookEditorModel.editorModel.textEditorModel.getLineContent(37)).equal(' ]');
should(notebookEditorModel.editorModel.textEditorModel.getLineContent(38)).equal('}');
should(notebookEditorModel.lastEditFullReplacement).equal(true);
});
test('should not replace entire text model for output changes (1st update)', async function (): Promise<void> {
await createNewNotebookModel();
@@ -631,6 +691,170 @@ suite('Notebook Editor Model', function (): void {
should(notebookEditorModel.lastEditFullReplacement).equal(false);
});
test('should not replace entire text model for content change with double quotes', async function (): Promise<void> {
await createNewNotebookModel();
let notebookEditorModel = await createTextEditorModel(this);
notebookEditorModel.replaceEntireTextEditorModel(notebookModel, undefined);
let newCell = notebookModel.addCell(CellTypes.Code);
setupTextEditorModelWithEmptyOutputs(notebookEditorModel, newCell);
addTextToBeginningOfTextEditorModel(notebookEditorModel, newCell, '"This text is in quotes"');
should(notebookEditorModel.editorModel.textEditorModel.getLineContent(9)).equal(' "\\"This text is in quotes\\""');
ensureStaticContentInOneLineCellIsCorrect(notebookEditorModel, newCell);
should(notebookEditorModel.lastEditFullReplacement).equal(false);
});
test('should not replace entire text model for content change with many double quotes', async function (): Promise<void> {
await createNewNotebookModel();
let notebookEditorModel = await createTextEditorModel(this);
notebookEditorModel.replaceEntireTextEditorModel(notebookModel, undefined);
let newCell = notebookModel.addCell(CellTypes.Code);
setupTextEditorModelWithEmptyOutputs(notebookEditorModel, newCell);
addTextToBeginningOfTextEditorModel(notebookEditorModel, newCell, '""""""""""');
should(notebookEditorModel.editorModel.textEditorModel.getLineContent(9)).equal(' "\\"\\"\\"\\"\\"\\"\\"\\"\\"\\""');
ensureStaticContentInOneLineCellIsCorrect(notebookEditorModel, newCell);
should(notebookEditorModel.lastEditFullReplacement).equal(false);
});
test('should not replace entire text model for content change with many backslashes', async function (): Promise<void> {
await createNewNotebookModel();
let notebookEditorModel = await createTextEditorModel(this);
notebookEditorModel.replaceEntireTextEditorModel(notebookModel, undefined);
let newCell = notebookModel.addCell(CellTypes.Code);
setupTextEditorModelWithEmptyOutputs(notebookEditorModel, newCell);
addTextToBeginningOfTextEditorModel(notebookEditorModel, newCell, '\\\\\\\\\\');
should(notebookEditorModel.editorModel.textEditorModel.getLineContent(9)).equal(' "\\\\\\\\\\\\\\\\\\\\\"');
ensureStaticContentInOneLineCellIsCorrect(notebookEditorModel, newCell);
should(notebookEditorModel.lastEditFullReplacement).equal(false);
});
test('should not replace entire text model for content change with many backslashes and double quotes', async function (): Promise<void> {
await createNewNotebookModel();
let notebookEditorModel = await createTextEditorModel(this);
notebookEditorModel.replaceEntireTextEditorModel(notebookModel, undefined);
let newCell = notebookModel.addCell(CellTypes.Code);
setupTextEditorModelWithEmptyOutputs(notebookEditorModel, newCell);
addTextToBeginningOfTextEditorModel(notebookEditorModel, newCell, '\"\"\"\"\"\"\"\"\"\"');
should(notebookEditorModel.editorModel.textEditorModel.getLineContent(9)).equal(' "\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\"\\\""');
ensureStaticContentInOneLineCellIsCorrect(notebookEditorModel, newCell);
should(notebookEditorModel.lastEditFullReplacement).equal(false);
});
test('should not replace entire text model for content change with no special characters', async function (): Promise<void> {
await createNewNotebookModel();
let notebookEditorModel = await createTextEditorModel(this);
notebookEditorModel.replaceEntireTextEditorModel(notebookModel, undefined);
let newCell = notebookModel.addCell(CellTypes.Code);
setupTextEditorModelWithEmptyOutputs(notebookEditorModel, newCell);
addTextToBeginningOfTextEditorModel(notebookEditorModel, newCell, 'this is a long line in a cell test. Everything should serialize correctly! # Comments here: adding more tests is fun?');
should(notebookEditorModel.editorModel.textEditorModel.getLineContent(9)).equal(' "this is a long line in a cell test. Everything should serialize correctly! # Comments here: adding more tests is fun?"');
ensureStaticContentInOneLineCellIsCorrect(notebookEditorModel, newCell);
should(notebookEditorModel.lastEditFullReplacement).equal(false);
});
test('should not replace entire text model for content change with variety of characters', async function (): Promise<void> {
await createNewNotebookModel();
let notebookEditorModel = await createTextEditorModel(this);
notebookEditorModel.replaceEntireTextEditorModel(notebookModel, undefined);
let newCell = notebookModel.addCell(CellTypes.Code);
setupTextEditorModelWithEmptyOutputs(notebookEditorModel, newCell);
addTextToBeginningOfTextEditorModel(notebookEditorModel, newCell, '`~1!2@3#4$5%6^7&8*9(0)-_=+[{]}\\|;:",<.>/?\'');
should(notebookEditorModel.editorModel.textEditorModel.getLineContent(9)).equal(' "`~1!2@3#4$5%6^7&8*9(0)-_=+[{]}\\\\|;:\\",<.>/?\'"');
ensureStaticContentInOneLineCellIsCorrect(notebookEditorModel, newCell);
should(notebookEditorModel.lastEditFullReplacement).equal(false);
});
test('should not replace entire text model for content change with single quotes', async function (): Promise<void> {
await createNewNotebookModel();
let notebookEditorModel = await createTextEditorModel(this);
notebookEditorModel.replaceEntireTextEditorModel(notebookModel, undefined);
let newCell = notebookModel.addCell(CellTypes.Code);
setupTextEditorModelWithEmptyOutputs(notebookEditorModel, newCell);
addTextToBeginningOfTextEditorModel(notebookEditorModel, newCell, '\'\'\'\'');
should(notebookEditorModel.editorModel.textEditorModel.getLineContent(9)).equal(' "\'\'\'\'"');
ensureStaticContentInOneLineCellIsCorrect(notebookEditorModel, newCell);
should(notebookEditorModel.lastEditFullReplacement).equal(false);
});
test('should not replace entire text model for content change with empty content', async function (): Promise<void> {
await createNewNotebookModel();
let notebookEditorModel = await createTextEditorModel(this);
notebookEditorModel.replaceEntireTextEditorModel(notebookModel, undefined);
let newCell = notebookModel.addCell(CellTypes.Code);
setupTextEditorModelWithEmptyOutputs(notebookEditorModel, newCell);
addTextToBeginningOfTextEditorModel(notebookEditorModel, newCell, '');
should(notebookEditorModel.editorModel.textEditorModel.getLineContent(9)).equal(' ""');
ensureStaticContentInOneLineCellIsCorrect(notebookEditorModel, newCell);
should(notebookEditorModel.lastEditFullReplacement).equal(false);
});
test('should not replace entire text model for content change with multiline content', async function (): Promise<void> {
await createNewNotebookModel();
let notebookEditorModel = await createTextEditorModel(this);
notebookEditorModel.replaceEntireTextEditorModel(notebookModel, undefined);
let newCell = notebookModel.addCell(CellTypes.Code);
setupTextEditorModelWithEmptyOutputs(notebookEditorModel, newCell);
addTextToBeginningOfTextEditorModel(notebookEditorModel, newCell, '"test"' + os.EOL + 'test""');
should(notebookEditorModel.editorModel.textEditorModel.getLineContent(9)).equal(' "\\"test\\"\\n",');
should(notebookEditorModel.editorModel.textEditorModel.getLineContent(8)).equal(' "source": [');
should(notebookEditorModel.editorModel.textEditorModel.getLineContent(10)).equal(' "test\\"\\""');
should(notebookEditorModel.editorModel.textEditorModel.getLineContent(11)).equal(' ],');
should(notebookEditorModel.editorModel.textEditorModel.getLineContent(13)).equal(' "azdata_cell_guid": "' + newCell.cellGuid + '"');
should(notebookEditorModel.editorModel.textEditorModel.getLineContent(15)).equal(' "outputs": [],');
should(notebookEditorModel.editorModel.textEditorModel.getLineContent(16)).equal(' "execution_count": 0');
should(notebookEditorModel.editorModel.textEditorModel.getLineContent(17)).equal(' }');
should(notebookEditorModel.lastEditFullReplacement).equal(false);
});
test('should not replace entire text model for content change with multiline content different escaped characters', async function (): Promise<void> {
await createNewNotebookModel();
let notebookEditorModel = await createTextEditorModel(this);
notebookEditorModel.replaceEntireTextEditorModel(notebookModel, undefined);
let newCell = notebookModel.addCell(CellTypes.Code);
setupTextEditorModelWithEmptyOutputs(notebookEditorModel, newCell);
addTextToBeginningOfTextEditorModel(notebookEditorModel, newCell, '"""""test"' + os.EOL + '"""""""test\\""');
should(notebookEditorModel.editorModel.textEditorModel.getLineContent(9)).equal(' "\\"\\"\\"\\"\\"test\\"\\n",');
should(notebookEditorModel.editorModel.textEditorModel.getLineContent(8)).equal(' "source": [');
should(notebookEditorModel.editorModel.textEditorModel.getLineContent(10)).equal(' "\\"\\"\\"\\"\\"\\"\\"test\\\\\\"\\""');
should(notebookEditorModel.editorModel.textEditorModel.getLineContent(11)).equal(' ],');
should(notebookEditorModel.editorModel.textEditorModel.getLineContent(13)).equal(' "azdata_cell_guid": "' + newCell.cellGuid + '"');
should(notebookEditorModel.editorModel.textEditorModel.getLineContent(15)).equal(' "outputs": [],');
should(notebookEditorModel.editorModel.textEditorModel.getLineContent(16)).equal(' "execution_count": 0');
should(notebookEditorModel.editorModel.textEditorModel.getLineContent(17)).equal(' }');
should(notebookEditorModel.lastEditFullReplacement).equal(false);
});
async function createNewNotebookModel() {
let options: INotebookModelOptions = Object.assign({}, defaultModelOptions, <Partial<INotebookModelOptions>><unknown>{
factory: mockModelFactory.object
@@ -645,4 +869,47 @@ suite('Notebook Editor Model', function (): void {
await textFileEditorModel.load();
return new NotebookEditorModel(defaultUri, textFileEditorModel, mockNotebookService.object, accessor.textFileService, testResourcePropertiesService);
}
function setupTextEditorModelWithEmptyOutputs(notebookEditorModel: NotebookEditorModel, newCell: ICellModel) {
// clear outputs
newCell[<any>'_outputs'] = [];
let contentChange: NotebookContentChange = {
changeType: NotebookChangeType.CellsModified,
cells: [newCell],
cellIndex: 0
};
notebookEditorModel.updateModel(contentChange, NotebookChangeType.CellsModified);
should(notebookEditorModel.lastEditFullReplacement).equal(true);
should(notebookEditorModel.editorModel.textEditorModel.getLineContent(14)).equal(' "outputs": [],');
}
function addTextToBeginningOfTextEditorModel(notebookEditorModel: NotebookEditorModel, newCell: ICellModel, textToAdd: string) {
let contentChange: NotebookContentChange = {
changeType: NotebookChangeType.CellSourceUpdated,
cells: [newCell],
cellIndex: 0,
modelContentChangedEvent: {
changes: [{ range: new Range(1, 1, 1, 1), rangeLength: 0, rangeOffset: 0, text: textToAdd }],
eol: '\n',
isFlush: false,
isRedoing: false,
isUndoing: false,
versionId: 2
}
};
notebookEditorModel.updateModel(contentChange, NotebookChangeType.CellSourceUpdated);
}
function ensureStaticContentInOneLineCellIsCorrect(notebookEditorModel: NotebookEditorModel, newCell: ICellModel) {
should(notebookEditorModel.editorModel.textEditorModel.getLineContent(8)).equal(' "source": [');
should(notebookEditorModel.editorModel.textEditorModel.getLineContent(10)).equal(' ],');
should(notebookEditorModel.editorModel.textEditorModel.getLineContent(12)).equal(' "azdata_cell_guid": "' + newCell.cellGuid + '"');
should(notebookEditorModel.editorModel.textEditorModel.getLineContent(14)).equal(' "outputs": [],');
should(notebookEditorModel.editorModel.textEditorModel.getLineContent(15)).equal(' "execution_count": 0');
should(notebookEditorModel.editorModel.textEditorModel.getLineContent(16)).equal(' }');
}
});

View File

@@ -302,8 +302,12 @@ export class QueryInput extends EditorInput implements IEncodingSupport, IConnec
}
public onConnectCanceled(): void {
// If we're currently connecting and then cancel, set connected state to false
// Otherwise, keep connected state as it was
if (this.state.connecting) {
this.state.connected = false;
}
this.state.connecting = false;
this.state.connected = false;
}
public onConnectSuccess(params?: INewConnectionParams): void {

View File

@@ -0,0 +1,7 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10 12.6L10.7 13.3L12.3 11.7L13.9 13.3L14.7 12.6L13 11L14.7 9.40005L13.9 8.60005L12.3 10.3L10.7 8.60005L10 9.40005L11.6 11L10 12.6Z" fill="#C5C5C5"/>
<path d="M1 4L15 4L15 3L1 3L1 4Z" fill="#C5C5C5"/>
<path d="M1 7L15 7L15 6L1 6L1 7Z" fill="#C5C5C5"/>
<path d="M9 9.5L9 9L1 9L1 10L9 10L9 9.5Z" fill="#C5C5C5"/>
<path d="M9 13L9 12.5L9 12L1 12L1 13L9 13Z" fill="#C5C5C5"/>
</svg>

After

Width:  |  Height:  |  Size: 484 B

View File

@@ -0,0 +1,7 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10 12.6L10.7 13.3L12.3 11.7L13.9 13.3L14.7 12.6L13 11L14.7 9.40005L13.9 8.60005L12.3 10.3L10.7 8.60005L10 9.40005L11.6 11L10 12.6Z" fill="white"/>
<path d="M1 4L15 4L15 3L1 3L1 4Z" fill="white"/>
<path d="M1 7L15 7L15 6L1 6L1 7Z" fill="white"/>
<path d="M9 9.5L9 9L1 9L1 10L9 10L9 9.5Z" fill="white"/>
<path d="M9 13L9 12.5L9 12L1 12L1 13L9 13Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 474 B

View File

@@ -0,0 +1,7 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10 12.6L10.7 13.3L12.3 11.7L13.9 13.3L14.7 12.6L13 11L14.7 9.40005L13.9 8.60005L12.3 10.3L10.7 8.60005L10 9.40005L11.6 11L10 12.6Z" fill="#424242"/>
<path d="M1 4L15 4L15 3L1 3L1 4Z" fill="#424242"/>
<path d="M1 7L15 7L15 6L1 6L1 7Z" fill="#424242"/>
<path d="M9 9.5L9 9L1 9L1 10L9 10L9 9.5Z" fill="#424242"/>
<path d="M9 13L9 12.5L9 12L1 12L1 13L9 13Z" fill="#424242"/>
</svg>

After

Width:  |  Height:  |  Size: 484 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#f6f6f6;}.icon-canvas-transparent{opacity:0;}.icon-vs-action-blue{fill:#00539c;}</style></defs><title>pause</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M13,2V14H8.5V2ZM3,14H7.5V2H3Z"/></g><g id="iconBg"><path class="icon-vs-action-blue" d="M4,3H6.5V13H4ZM9.5,3V13H12V3Z"/></g></svg>

After

Width:  |  Height:  |  Size: 505 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#252526;}.icon-canvas-transparent{opacity:0;}.icon-vs-action-blue{fill:#75beff;}</style></defs><title>pause</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M13,2V14H8.5V2ZM3,14H7.5V2H3Z"/></g><g id="iconBg"><path class="icon-vs-action-blue" d="M4,3H6.5V13H4ZM9.5,3V13H12V3Z"/></g></svg>

After

Width:  |  Height:  |  Size: 505 B

Some files were not shown because too many files have changed in this diff Show More