Compare commits

..

24 Commits

Author SHA1 Message Date
Matt Irvine
4bfa6b3a5d Save editor cursor/scroll position when switching sql files (#1978)
* Save editor layouts when switching sql files

* Move import

* Restore the view state after laying out the editor
2018-07-20 10:48:12 -04:00
Anthony Dresser
d20f24be18 Bump slickgrid to fix html content issue (#1980)
* add ' to escape strings for html

* update slickgrid to fix injection
2018-07-20 10:47:16 -04:00
Matt Irvine
8a17bae7a6 Loading spinner while validating next/done (#1975) 2018-07-19 17:31:30 -07:00
Anthony Dresser
d14c73fad5 vbump service-downloader (#1965) 2018-07-19 14:06:41 -07:00
Anthony Dresser
ce878e1def Rework slickgrid keyboard navigation (#1930)
* rewrite keybind nav to handle ctrl + home and end

* testing different options

* working on removed slickgrid changes we don't need

* formatting

* handle click handler to rowNumber

* fixing various bugs

* formatting

* readd click column to select

* add shift key to column select

* added logic for additional keybindings on grid

* add down and up arrow into keyboard navigation

* update styling and update slickgrid

* formatting

* update angular-slickgrid version

* remove index.js changes
2018-07-19 14:05:54 -07:00
Karl Burtram
a64a0d1db6 Bump SQL Ops to 0.32.1 for August iteration 2018-07-19 11:43:32 -07:00
Anthony Dresser
feab43f16d add ' to escape strings for html (#1974) 2018-07-19 14:42:29 -04:00
Karl Burtram
0d60fe775f Update Readme and Changelog (#1968) 2018-07-19 01:21:06 -04:00
Leila Lali
6680be6a73 adding task integration with wizard and dialog framework (#1929)
* adding task integration with wizard and dialog framework
2018-07-18 16:28:36 -07:00
Karl Burtram
e026ab85a7 Escape the aria string for Edit Data grid (#1958) 2018-07-17 18:45:10 -07:00
Karl Burtram
e53c903205 Update tools service to 1.5.0-alpha.12 2018-07-17 18:44:45 -07:00
Matt Irvine
03dbe8565f Set element text instead of HTML where possible (#1956) 2018-07-17 16:48:38 -07:00
Karl Burtram
708793cb23 Update tools service to 1.5.0-alpha.11 2018-07-17 16:47:31 -07:00
Aditya Bist
43ae4fb0aa Fixed 2 bugs in Agent Steps page. (#1953)
* fixed bug where steps werent being shown

* fixed customer reported issue
2018-07-17 15:49:38 -07:00
Aditya Bist
6b1d552277 Agent/step finishes (#1948)
* misc fixes in dialogs

* removed unused import

* disabled more advanced options
2018-07-17 10:51:31 -07:00
Madeline MacDonald
f24f576b72 Profiler display fixes (#1949)
* Fixing details tab, window resizing, and having profiler options in object explorer

* Fixing displaying connection names

* spacing

* Removing unnecessary code
2018-07-16 17:20:13 -07:00
Matt Irvine
c23328564f Hide correct element when hiding buttons (#1945) 2018-07-16 17:09:44 -07:00
Karl Burtram
cd6dd3dafa Bump Tools Service to 1.5.0-alpha.10 (#1947) 2018-07-16 15:40:41 -07:00
Aditya Bist
4081e15bef misc fixes in dialogs (#1942)
* misc fixes in dialogs

* removed unused import
2018-07-16 15:25:11 -07:00
Karl Burtram
b05e3813d1 Bump product version to 0.31.4 from July Public Preview (#1944) 2018-07-16 15:13:10 -07:00
Karl Burtram
3048311f40 Bump agent extension version to 0.31.4 (#1943) 2018-07-16 15:12:54 -07:00
Madeline MacDonald
1045392d91 Escaping profiler text (#1940) 2018-07-16 13:20:56 -07:00
Madeline MacDonald
7b23ca8ee7 Improving profiler controls and toolbar (#1931)
* Profiler toolbar improvements

* Fixing formatting issues
2018-07-16 11:44:14 -07:00
Karl Burtram
4d67eca8bb Dashboard agent tab style updates (#1934) 2018-07-15 10:51:20 -07:00
84 changed files with 1400 additions and 637 deletions

View File

@@ -1,5 +1,26 @@
# Change Log # Change Log
## Version 0.31.4
* Release date: July 19, 2018
* Release status: Public Preview
## What's new in this version
* SQL Server Agent for SQL Operations Studio extension improvements
* Added view of Alerts, Operators, and Proxies and icons on left pane
* Added dialogs for New Job, New Job Step, New Alert, and New Operator
* Added Delete Job, Delete Alert, and Delete Operator (right-click)
* Added Previous Runs visualization
* Added Filters for each column name
* SQL Server Profiler for SQL Operations Studio extension improvements
* Added Hotkeys to quickly launch and start/stop Profiler
* Added 5 Default Templates to view Extended Events
* Added Server/Database connection name
* Added support for Azure SQL Database instances
* Added suggestion to exit Profiler when tab is closed when Profiler is still running
* Release of Combine Scripts Extension
* Wizard and Dialog Extensibility
* Fix GitHub Issues
## Version 0.30.6 ## Version 0.30.6
* Release date: June 20, 2018 * Release date: June 20, 2018
* Release status: Public Preview * Release status: Public Preview

View File

@@ -8,12 +8,12 @@ SQL Operations Studio is a data management tool that enables you to work with SQ
Platform | Link Platform | Link
-- | -- -- | --
Windows Setup Installer | https://go.microsoft.com/fwlink/?linkid=875602 Windows Setup Installer | https://go.microsoft.com/fwlink/?linkid=2005949
Windows ZIP | https://go.microsoft.com/fwlink/?linkid=875603 Windows ZIP | https://go.microsoft.com/fwlink/?linkid=2005950
macOS ZIP | https://go.microsoft.com/fwlink/?linkid=875604 macOS ZIP | https://go.microsoft.com/fwlink/?linkid=2005959
Linux TAR.GZ | https://go.microsoft.com/fwlink/?linkid=875605 Linux TAR.GZ | https://go.microsoft.com/fwlink/?linkid=2005960
Linux RPM | https://go.microsoft.com/fwlink/?linkid=875606 Linux RPM | https://go.microsoft.com/fwlink/?linkid=2006083
Linux DEB | https://go.microsoft.com/fwlink/?linkid=875607 Linux DEB | https://go.microsoft.com/fwlink/?linkid=2006084
Go to our [download page](https://aka.ms/sqlopsstudio) for more specific instructions. Go to our [download page](https://aka.ms/sqlopsstudio) for more specific instructions.
@@ -85,7 +85,7 @@ We would like to thank all our users who raised issues, and in particular the fo
* Chinese (Traditional): Bruce Chen, Chiayi Yen, Kevin Yang, Winnie Lin, 保哥 Will, 謝政廷 * Chinese (Traditional): Bruce Chen, Chiayi Yen, Kevin Yang, Winnie Lin, 保哥 Will, 謝政廷
* Korean: Do-Kyun Kim, Evelyn Kim, Helen Jung, Hong Jmee, jeongwoo choi, Jun Hyoung Lee, Jungsun Kim정선, Justin Yoo, Kavrith mucha, Kiwoong Youm, MinGyu Ju, MVP_JUNO BEA, Sejun Kim, SOONMAN KWON, sung man ko, Yeongrak Choi, younggun kim, Youngjae Kim, 소영 이 * Korean: Do-Kyun Kim, Evelyn Kim, Helen Jung, Hong Jmee, jeongwoo choi, Jun Hyoung Lee, Jungsun Kim정선, Justin Yoo, Kavrith mucha, Kiwoong Youm, MinGyu Ju, MVP_JUNO BEA, Sejun Kim, SOONMAN KWON, sung man ko, Yeongrak Choi, younggun kim, Youngjae Kim, 소영 이
* Russian: Andrey Veselov, Anton Fontanov, Anton Savin, Elena Ostrovskaia, Igor Babichev, Maxim Zelensky, Rodion Fedechkin, Tasha T, Vladimir Zyryanov * Russian: Andrey Veselov, Anton Fontanov, Anton Savin, Elena Ostrovskaia, Igor Babichev, Maxim Zelensky, Rodion Fedechkin, Tasha T, Vladimir Zyryanov
* Portuguese Brazil: Daniel de Sousa, Diogo Duarte, Douglas Correa, Douglas Eccker, José Emanuel Mendes, Marcelo Fernandes, Marcondes Alexandre, Roberto Fonseca, Rodrigo Crespi * Portuguese Brazil: Daniel de Sousa, Diogo Duarte, Douglas Correa, Douglas Eccker, José Emanuel Mendes, Marcelo Fernandes, Marcondes Alexandre, Roberto Fonseca, Rodrigo Crespi
And of course we'd like to thank the authors of all upstream dependencies. Please see a full list in the [ThirdPartyNotices.txt](https://raw.githubusercontent.com/Microsoft/sqlopsstudio/master/ThirdPartyNotices.txt) And of course we'd like to thank the authors of all upstream dependencies. Please see a full list in the [ThirdPartyNotices.txt](https://raw.githubusercontent.com/Microsoft/sqlopsstudio/master/ThirdPartyNotices.txt)

View File

@@ -2,7 +2,7 @@
"name": "agent", "name": "agent",
"displayName": "SQL Server Agent", "displayName": "SQL Server Agent",
"description": "Manage and troubleshoot SQL Server Agent jobs", "description": "Manage and troubleshoot SQL Server Agent jobs",
"version": "0.31.1", "version": "0.31.4",
"publisher": "Microsoft", "publisher": "Microsoft",
"preview": true, "preview": true,
"license": "https://raw.githubusercontent.com/Microsoft/sqlopsstudio/master/LICENSE.txt", "license": "https://raw.githubusercontent.com/Microsoft/sqlopsstudio/master/LICENSE.txt",

View File

@@ -117,7 +117,7 @@ export class JobData implements IAgentDialogData {
if (!result || !result.success) { if (!result || !result.success) {
vscode.window.showErrorMessage( vscode.window.showErrorMessage(
localize('alertData.saveErrorMessage', "Alert update failed '{0}'", result.errorMessage ? result.errorMessage : 'Unknown')); localize('jobData.saveErrorMessage', "Job update failed '{0}'", result.errorMessage ? result.errorMessage : 'Unknown'));
} }
} }

View File

@@ -35,7 +35,6 @@ export class ProxyData implements IAgentDialogData {
public async save() { public async save() {
let agentService = await AgentUtils.getAgentService(); let agentService = await AgentUtils.getAgentService();
let result = await agentService.createProxy(this.ownerUri, this.toAgentProxyInfo()); let result = await agentService.createProxy(this.ownerUri, this.toAgentProxyInfo());
console.log(result);
if (!result || !result.success) { if (!result || !result.success) {
// TODO handle error here // TODO handle error here
} }

View File

@@ -63,8 +63,9 @@ export class AlertDialog extends AgentDialog<AlertData> {
private static readonly AlertTypes: string[] = [ private static readonly AlertTypes: string[] = [
AlertData.AlertTypeSqlServerEventString, AlertData.AlertTypeSqlServerEventString,
AlertData.AlertTypePerformanceConditionString, // Disabled until next release
AlertData.AlertTypeWmiEventString // AlertData.AlertTypePerformanceConditionString,
// AlertData.AlertTypeWmiEventString
]; ];
private static readonly AlertSeverities: string[] = [ private static readonly AlertSeverities: string[] = [

View File

@@ -204,7 +204,7 @@ export class JobDialog extends AgentDialog<JobData> {
this.StepsTable_FailureColumnString this.StepsTable_FailureColumnString
], ],
data: [], data: [],
height: 300 height: 430
}).component(); }).component();
this.moveStepUpButton = view.modelBuilder.button() this.moveStepUpButton = view.modelBuilder.button()
@@ -219,6 +219,9 @@ export class JobDialog extends AgentDialog<JobData> {
width: 80 width: 80
}).component(); }).component();
this.moveStepUpButton.enabled = false;
this.moveStepDownButton.enabled = false;
this.newStepButton = view.modelBuilder.button().withProperties({ this.newStepButton = view.modelBuilder.button().withProperties({
label: this.NewStepButtonString, label: this.NewStepButtonString,
width: 80 width: 80
@@ -261,7 +264,7 @@ export class JobDialog extends AgentDialog<JobData> {
this.AlertNameLabelString this.AlertNameLabelString
], ],
data: [], data: [],
height: 300, height: 430,
width: 400 width: 400
}).component(); }).component();
@@ -296,7 +299,7 @@ export class JobDialog extends AgentDialog<JobData> {
this.ScheduleNameLabelString this.ScheduleNameLabelString
], ],
data: [], data: [],
height: 300, height: 430,
width: 420 width: 420
}).component(); }).component();

View File

@@ -30,6 +30,7 @@ export class JobStepDialog {
private readonly PreviousButtonText: string = localize('jobStepDialog.previous','Previous'); private readonly PreviousButtonText: string = localize('jobStepDialog.previous','Previous');
private readonly SuccessfulParseText: string = localize('jobStepDialog.successParse', 'The command was successfully parsed.'); private readonly SuccessfulParseText: string = localize('jobStepDialog.successParse', 'The command was successfully parsed.');
private readonly FailureParseText: string = localize('jobStepDialog.failParse', 'The command failed.'); private readonly FailureParseText: string = localize('jobStepDialog.failParse', 'The command failed.');
private readonly BlankStepNameErrorText: string = localize('jobStepDialog.blankStepName', 'The step name cannot be left blank');
// General Control Titles // General Control Titles
private readonly StepNameLabelString: string = localize('jobStepDialog.stepNameLabel', 'Step Name'); private readonly StepNameLabelString: string = localize('jobStepDialog.stepNameLabel', 'Step Name');
@@ -139,12 +140,15 @@ export class JobStepDialog {
this.openButton = view.modelBuilder.button() this.openButton = view.modelBuilder.button()
.withProperties({ .withProperties({
label: this.OpenCommandText, label: this.OpenCommandText,
width: '80px' width: '80px',
isFile: true
}).component(); }).component();
this.openButton.enabled = false;
this.parseButton = view.modelBuilder.button() this.parseButton = view.modelBuilder.button()
.withProperties({ .withProperties({
label: this.ParseCommandText, label: this.ParseCommandText,
width: '80px' width: '80px',
isFile: false
}).component(); }).component();
this.parseButton.onDidClick(e => { this.parseButton.onDidClick(e => {
if (this.commandTextBox.value) { if (this.commandTextBox.value) {
@@ -185,6 +189,12 @@ export class JobStepDialog {
.withProperties({ .withProperties({
}).component(); }).component();
this.nameTextBox.required = true; this.nameTextBox.required = true;
this.nameTextBox.onTextChanged(() => {
if (this.nameTextBox.value.length > 0) {
this.dialog.message = null;
}
});
this.typeDropdown = view.modelBuilder.dropDown() this.typeDropdown = view.modelBuilder.dropDown()
.withProperties({ .withProperties({
value: this.TSQLScript, value: this.TSQLScript,
@@ -465,7 +475,12 @@ export class JobStepDialog {
return outputFileForm; return outputFileForm;
} }
private async execute() { protected execute() {
this.model.stepName = this.nameTextBox.value;
if (!this.model.stepName || this.model.stepName.length === 0) {
this.dialog.message = this.dialog.message = { text: this.BlankStepNameErrorText };
return;
}
this.model.jobName = this.jobName; this.model.jobName = this.jobName;
this.model.id = this.stepId; this.model.id = this.stepId;
this.model.server = this.server; this.model.server = this.server;
@@ -479,7 +494,6 @@ export class JobStepDialog {
this.model.failureAction = this.failureActionDropdown.value as string; this.model.failureAction = this.failureActionDropdown.value as string;
this.model.outputFileName = this.outputFileNameBox.value; this.model.outputFileName = this.outputFileNameBox.value;
this.model.appendToLogFile = this.appendToExistingFileCheckbox.checked; this.model.appendToLogFile = this.appendToExistingFileCheckbox.checked;
await this.model.save();
} }
public async openNewStepDialog() { public async openNewStepDialog() {

View File

@@ -3,6 +3,8 @@
* Licensed under the Source EULA. See License.txt in the project root for license information. * Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
'use strict'; 'use strict';
import * as nls from 'vscode-nls';
import * as sqlops from 'sqlops'; import * as sqlops from 'sqlops';
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import { AlertDialog } from './dialogs/alertDialog'; import { AlertDialog } from './dialogs/alertDialog';
@@ -12,6 +14,8 @@ import { ProxyDialog } from './dialogs/proxyDialog';
import { JobStepDialog } from './dialogs/jobStepDialog'; import { JobStepDialog } from './dialogs/jobStepDialog';
import { PickScheduleDialog } from './dialogs/pickScheduleDialog'; import { PickScheduleDialog } from './dialogs/pickScheduleDialog';
const localize = nls.loadMessageBundle();
/** /**
* The main controller class that initializes the extension * The main controller class that initializes the extension
*/ */
@@ -23,6 +27,11 @@ export class MainController {
this._context = context; this._context = context;
} }
public static showNotYetImplemented(): void {
vscode.window.showInformationMessage(
localize('mainController.notImplemented', "This feature is under development. Check-out the latest insiders build if you'd like to try out the most recent changes!"));
}
/** /**
* Activates the extension * Activates the extension
*/ */
@@ -48,8 +57,10 @@ export class MainController {
dialog.openDialog(); dialog.openDialog();
}); });
vscode.commands.registerCommand('agent.openProxyDialog', (ownerUri: string, proxyInfo: sqlops.AgentProxyInfo, credentials: sqlops.CredentialInfo[]) => { vscode.commands.registerCommand('agent.openProxyDialog', (ownerUri: string, proxyInfo: sqlops.AgentProxyInfo, credentials: sqlops.CredentialInfo[]) => {
let dialog = new ProxyDialog(ownerUri, proxyInfo, credentials); //@TODO: reenable create proxy after snapping July release (7/14/18)
dialog.openDialog(); // let dialog = new ProxyDialog(ownerUri, proxyInfo, credentials);
// dialog.openDialog();
MainController.showNotYetImplemented();
}); });
} }

View File

@@ -658,9 +658,9 @@
} }
}, },
"dependencies": { "dependencies": {
"dataprotocol-client": "github:Microsoft/sqlops-dataprotocolclient#0.1.9", "dataprotocol-client": "github:Microsoft/sqlops-dataprotocolclient#0.2.0",
"opener": "^1.4.3", "opener": "^1.4.3",
"service-downloader": "github:anthonydresser/service-downloader#0.1.2", "service-downloader": "github:anthonydresser/service-downloader#0.1.4",
"vscode-extension-telemetry": "^0.0.15" "vscode-extension-telemetry": "^0.0.15"
}, },
"devDependencies": { "devDependencies": {

View File

@@ -1,6 +1,6 @@
{ {
"downloadUrl": "https://github.com/Microsoft/sqltoolsservice/releases/download/v{#version#}/microsoft.sqltools.servicelayer-{#fileName#}", "downloadUrl": "https://github.com/Microsoft/sqltoolsservice/releases/download/v{#version#}/microsoft.sqltools.servicelayer-{#fileName#}",
"version": "1.5.0-alpha.9", "version": "1.5.0-alpha.12",
"downloadFileNames": { "downloadFileNames": {
"Windows_86": "win-x86-netcoreapp2.1.zip", "Windows_86": "win-x86-netcoreapp2.1.zip",
"Windows_64": "win-x64-netcoreapp2.1.zip", "Windows_64": "win-x64-netcoreapp2.1.zip",

View File

@@ -49,9 +49,9 @@ core-util-is@~1.0.0:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
"dataprotocol-client@github:Microsoft/sqlops-dataprotocolclient#0.1.9": "dataprotocol-client@github:Microsoft/sqlops-dataprotocolclient#0.2.0":
version "0.1.9" version "0.2.0"
resolved "https://codeload.github.com/Microsoft/sqlops-dataprotocolclient/tar.gz/a1a79895cb79658b75d78aa5cfd745855019148d" resolved "https://codeload.github.com/Microsoft/sqlops-dataprotocolclient/tar.gz/c7a691c7fc5ad54a147090784a7f11efda301f4b"
dependencies: dependencies:
vscode-languageclient "3.5.0" vscode-languageclient "3.5.0"
@@ -296,9 +296,9 @@ semver@^5.3.0:
version "5.5.0" version "5.5.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab" resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab"
"service-downloader@github:anthonydresser/service-downloader#0.1.2": "service-downloader@github:anthonydresser/service-downloader#0.1.4":
version "0.1.2" version "0.1.4"
resolved "https://codeload.github.com/anthonydresser/service-downloader/tar.gz/2aa9b336b6442e17e24693ddc907030575539798" resolved "https://codeload.github.com/anthonydresser/service-downloader/tar.gz/3c0abdf8603aca85d2eacfac3c547173e41bf0c7"
dependencies: dependencies:
decompress "^4.2.0" decompress "^4.2.0"
eventemitter2 "^5.0.1" eventemitter2 "^5.0.1"

View File

@@ -1,6 +1,6 @@
{ {
"name": "sqlops", "name": "sqlops",
"version": "0.31.3", "version": "0.32.1",
"distro": "8c3e97e3425cc9814496472ab73e076de2ba99ee", "distro": "8c3e97e3425cc9814496472ab73e076de2ba99ee",
"author": { "author": {
"name": "Microsoft Corporation" "name": "Microsoft Corporation"
@@ -34,7 +34,7 @@
"@angular/router": "~4.1.3", "@angular/router": "~4.1.3",
"@angular/upgrade": "~4.1.3", "@angular/upgrade": "~4.1.3",
"angular2-grid": "2.0.6", "angular2-grid": "2.0.6",
"angular2-slickgrid": "git://github.com/Microsoft/angular2-slickgrid.git#1.3.11", "angular2-slickgrid": "github:Microsoft/angular2-slickgrid#1.3.12",
"applicationinsights": "0.18.0", "applicationinsights": "0.18.0",
"chart.js": "^2.6.0", "chart.js": "^2.6.0",
"fast-plist": "0.1.2", "fast-plist": "0.1.2",
@@ -60,7 +60,7 @@
"reflect-metadata": "^0.1.8", "reflect-metadata": "^0.1.8",
"rxjs": "5.4.0", "rxjs": "5.4.0",
"semver": "4.3.6", "semver": "4.3.6",
"slickgrid": "github:anthonydresser/SlickGrid#2.3.23", "slickgrid": "github:anthonydresser/SlickGrid#2.3.25",
"spdlog": "0.6.0", "spdlog": "0.6.0",
"sudo-prompt": "^8.0.0", "sudo-prompt": "^8.0.0",
"svg.js": "^2.2.5", "svg.js": "^2.2.5",

View File

@@ -69,10 +69,10 @@ export default class MainController implements vscode.Disposable {
private async getTabContent(view: sqlops.ModelView, customButton1: sqlops.window.modelviewdialog.Button, customButton2: sqlops.window.modelviewdialog.Button, componentWidth: number | string): Promise<void> { private async getTabContent(view: sqlops.ModelView, customButton1: sqlops.window.modelviewdialog.Button, customButton2: sqlops.window.modelviewdialog.Button, componentWidth: number | string): Promise<void> {
let inputBox = view.modelBuilder.inputBox() let inputBox = view.modelBuilder.inputBox()
.withProperties({ .withProperties({
multiline: true, multiline: true,
height: 100 height: 100
}).component(); }).component();
let inputBoxWrapper = view.modelBuilder.loadingComponent().withItem(inputBox).component(); let inputBoxWrapper = view.modelBuilder.loadingComponent().withItem(inputBox).component();
inputBoxWrapper.loading = false; inputBoxWrapper.loading = false;
customButton1.onClick(() => { customButton1.onClick(() => {
@@ -145,8 +145,8 @@ export default class MainController implements vscode.Disposable {
component: inputBox4, component: inputBox4,
title: 'inputBox4' title: 'inputBox4'
}], { }], {
horizontal: true horizontal: true
}).component(); }).component();
let groupModel1 = view.modelBuilder.groupContainer() let groupModel1 = view.modelBuilder.groupContainer()
.withLayout({ .withLayout({
}).withItems([ }).withItems([
@@ -178,8 +178,8 @@ export default class MainController implements vscode.Disposable {
}).component(); }).component();
let declarativeTable = view.modelBuilder.declarativeTable() let declarativeTable = view.modelBuilder.declarativeTable()
.withProperties({ .withProperties({
columns: [{ columns: [{
displayName: 'Column 1', displayName: 'Column 1',
valueType: sqlops.DeclarativeDataType.string, valueType: sqlops.DeclarativeDataType.string,
width: '20px', width: '20px',
@@ -204,12 +204,12 @@ export default class MainController implements vscode.Disposable {
{ name: 'options2', displayName: 'option 2' } { name: 'options2', displayName: 'option 2' }
] ]
} }
], ],
data: [ data: [
['Data00', 'Data01', false, 'options2'], ['Data00', 'Data01', false, 'options2'],
['Data10', 'Data11', true, 'options1'] ['Data10', 'Data11', true, 'options1']
] ]
}).component(); }).component();
declarativeTable.onDataChanged(e => { declarativeTable.onDataChanged(e => {
inputBox2.value = e.row.toString() + ' ' + e.column.toString() + ' ' + e.value.toString(); inputBox2.value = e.row.toString() + ' ' + e.column.toString() + ' ' + e.value.toString();
@@ -223,7 +223,7 @@ export default class MainController implements vscode.Disposable {
height: 150 height: 150
}).withItems([ }).withItems([
radioButton, groupModel1, radioButton2] radioButton, groupModel1, radioButton2]
, { flex: '1 1 50%' }).component(); , { flex: '1 1 50%' }).component();
let formModel = view.modelBuilder.formContainer() let formModel = view.modelBuilder.formContainer()
.withFormItems([{ .withFormItems([{
component: inputBoxWrapper, component: inputBoxWrapper,
@@ -254,9 +254,9 @@ export default class MainController implements vscode.Disposable {
component: listBox, component: listBox,
title: 'List Box' title: 'List Box'
}], { }], {
horizontal: false, horizontal: false,
componentWidth: componentWidth componentWidth: componentWidth
}).component(); }).component();
let formWrapper = view.modelBuilder.loadingComponent().withItem(formModel).component(); let formWrapper = view.modelBuilder.loadingComponent().withItem(formModel).component();
formWrapper.loading = false; formWrapper.loading = false;
customButton2.onClick(() => { customButton2.onClick(() => {
@@ -301,6 +301,17 @@ export default class MainController implements vscode.Disposable {
page1.registerContent(async (view) => { page1.registerContent(async (view) => {
await this.getTabContent(view, customButton1, customButton2, 800); await this.getTabContent(view, customButton1, customButton2, 800);
}); });
wizard.registerOperation({
displayName: 'test task',
description: 'task description',
isCancelable: true
}, op => {
op.updateStatus(sqlops.TaskStatus.InProgress);
op.updateStatus(sqlops.TaskStatus.InProgress, 'Task is running');
setTimeout(() => {
op.updateStatus(sqlops.TaskStatus.Succeeded);
}, 5000);
});
wizard.pages = [page1, page2]; wizard.pages = [page1, page2];
wizard.open(); wizard.open();
} }
@@ -378,13 +389,14 @@ export default class MainController implements vscode.Disposable {
let monitorLightPath = vscode.Uri.file(path.join(__dirname, '..', 'media', 'monitor.svg')); let monitorLightPath = vscode.Uri.file(path.join(__dirname, '..', 'media', 'monitor.svg'));
let monitorIcon = { let monitorIcon = {
light: monitorLightPath, light: monitorLightPath,
dark: path.join(__dirname, '..', 'media', 'monitor_inverse.svg') }; dark: path.join(__dirname, '..', 'media', 'monitor_inverse.svg')
};
let monitorButton = view.modelBuilder.button() let monitorButton = view.modelBuilder.button()
.withProperties({ .withProperties({
label: 'Monitor', label: 'Monitor',
iconPath: monitorIcon iconPath: monitorIcon
}).component(); }).component();
let toolbarModel = view.modelBuilder.toolbarContainer() let toolbarModel = view.modelBuilder.toolbarContainer()
.withToolbarItems([{ .withToolbarItems([{
component: inputBox, component: inputBox,

View File

@@ -33,7 +33,7 @@ export function appendRowLink(container: Builder, label: string, labelClass: str
container.element('tr', {}, (rowContainer) => { container.element('tr', {}, (rowContainer) => {
rowContainer.element('td', { class: labelClass }, (labelCellContainer) => { rowContainer.element('td', { class: labelClass }, (labelCellContainer) => {
labelCellContainer.div({}, (labelContainer) => { labelCellContainer.div({}, (labelContainer) => {
labelContainer.innerHtml(label); labelContainer.text(label);
}); });
}); });
rowContainer.element('td', { class: cellContainerClass }, (inputCellContainer) => { rowContainer.element('td', { class: cellContainerClass }, (inputCellContainer) => {

View File

@@ -150,8 +150,8 @@ export class OptionsDialog extends Modal {
private onOptionLinkClicked(optionName: string): void { private onOptionLinkClicked(optionName: string): void {
var option = this._optionElements[optionName].option; var option = this._optionElements[optionName].option;
this._optionTitle.innerHtml(option.displayName); this._optionTitle.text(option.displayName);
this._optionDescription.innerHtml(option.description); this._optionDescription.text(option.description);
} }
private fillInOptions(container: Builder, options: sqlops.ServiceOption[]): void { private fillInOptions(container: Builder, options: sqlops.ServiceOption[]): void {

View File

@@ -62,7 +62,7 @@ export class TabHeaderComponent extends Disposable implements AfterContentInit,
tabLabelcontainer.classList.add(this.tab.iconClass); tabLabelcontainer.classList.add(this.tab.iconClass);
} else { } else {
tabLabelcontainer.className = 'tabLabel'; tabLabelcontainer.className = 'tabLabel';
tabLabelcontainer.innerHTML = this.tab.title; tabLabelcontainer.textContent = this.tab.title;
} }
tabLabelcontainer.title = this.tab.title; tabLabelcontainer.title = this.tab.title;
} }

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 { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
/**
* Implements the various additional navigation keybindings we want out of slickgrid
*/
export class AdditionalKeyBindings<T> implements Slick.Plugin<T> {
private grid: Slick.Grid<T>;
private handler = new Slick.EventHandler();
public init(grid: Slick.Grid<T>) {
this.grid = grid;
this.handler.subscribe(this.grid.onKeyDown, (e, args) => this.handleKeyDown(e, args));
}
public destroy() {
this.handler.unsubscribeAll();
}
private handleKeyDown(e: KeyboardEvent, args: Slick.OnKeyDownEventArgs<T>): void {
let event = new StandardKeyboardEvent(e);
let handled = true;
if (event.equals(KeyCode.RightArrow | KeyMod.CtrlCmd)) {
this.grid.setActiveCell(args.row, this.grid.getColumns().length - 1);
} else if (event.equals(KeyCode.LeftArrow | KeyMod.CtrlCmd)) {
// account for row column
if (this.grid.canCellBeActive(args.row, 0)) {
this.grid.setActiveCell(args.row, 0);
} else {
this.grid.setActiveCell(args.row, 1);
}
} else if (event.equals(KeyCode.UpArrow | KeyMod.CtrlCmd)) {
this.grid.setActiveCell(0, args.cell);
} else if (event.equals(KeyCode.DownArrow | KeyMod.CtrlCmd)) {
this.grid.setActiveCell(this.grid.getDataLength() - 1, args.cell);
} else if (event.equals(KeyCode.Home | KeyMod.CtrlCmd)) {
// account for row column
if (this.grid.canCellBeActive(0, 0)) {
this.grid.setActiveCell(0, 0);
} else {
this.grid.setActiveCell(0, 1);
}
} else if (event.equals(KeyCode.End | KeyMod.CtrlCmd)) {
this.grid.setActiveCell(this.grid.getDataLength() - 1, this.grid.getColumns().length - 1);
} else {
handled = false;
}
if (handled) {
e.preventDefault();
e.stopPropagation();
e.stopImmediatePropagation();
}
}
}

View File

@@ -16,7 +16,7 @@ export class AutoColumnSize<T> implements Slick.Plugin<T> {
private _context: CanvasRenderingContext2D; private _context: CanvasRenderingContext2D;
private _options: IAutoColumnSizeOptions; private _options: IAutoColumnSizeOptions;
constructor(options: IAutoColumnSizeOptions) { constructor(options: IAutoColumnSizeOptions = defaultOptions) {
this._options = mixin(options, defaultOptions, false); this._options = mixin(options, defaultOptions, false);
} }

View File

@@ -0,0 +1,192 @@
// Drag select selection model gist taken from https://gist.github.com/skoon/5312536
// heavily modified
import { mixin } from 'vs/base/common/objects';
import { isUndefinedOrNull } from 'vs/base/common/types';
import { IThemeService } from 'vs/platform/theme/common/themeService';
require.__$__nodeRequire('slickgrid/plugins/slick.cellrangedecorator');
require.__$__nodeRequire('slickgrid/plugins/slick.cellrangeselector');
export interface ICellRangeSelector<T> extends Slick.Plugin<T> {
onCellRangeSelected: Slick.Event<{ range: Slick.Range }>;
onBeforeCellRangeSelected: Slick.Event<Slick.Cell>;
}
export interface ICellSelectionModelOptions {
cellRangeSelector?: any;
selectActiveCell?: boolean;
}
const defaults: ICellSelectionModelOptions = {
selectActiveCell: true
};
export class CellSelectionModel<T> implements Slick.SelectionModel<T, Array<Slick.Range>> {
private grid: Slick.Grid<T>;
private selector: ICellRangeSelector<T>;
private ranges: Array<Slick.Range> = [];
public onSelectedRangesChanged = new Slick.Event<Array<Slick.Range>>();
constructor(private options: ICellSelectionModelOptions = defaults) {
this.options = mixin(this.options, defaults, false);
if (this.options.cellRangeSelector) {
this.selector = this.options.cellRangeSelector;
} else {
// this is added by the noderequires above
this.selector = new (<any>Slick).CellRangeSelector({ selectionCss: { 'border': '2px dashed grey' } });
}
}
public init(grid: Slick.Grid<T>) {
this.grid = grid;
this.grid.onActiveCellChanged.subscribe((e, args) => this.handleActiveCellChange(e, args));
this.grid.onKeyDown.subscribe(e => this.handleKeyDown(e));
this.grid.onHeaderClick.subscribe((e: MouseEvent, args) => this.handleHeaderClick(e, args));
this.grid.registerPlugin(this.selector);
this.selector.onCellRangeSelected.subscribe((e, args) => this.handleCellRangeSelected(e, args));
this.selector.onBeforeCellRangeSelected.subscribe((e, args) => this.handleBeforeCellRangeSelected(e, args));
}
public destroy() {
this.grid.onActiveCellChanged.unsubscribe((e, args) => this.handleActiveCellChange(e, args));
this.grid.onKeyDown.unsubscribe(e => this.handleKeyDown(e));
this.selector.onCellRangeSelected.unsubscribe((e, args) => this.handleCellRangeSelected(e, args));
this.selector.onBeforeCellRangeSelected.unsubscribe((e, args) => this.handleBeforeCellRangeSelected(e, args));
this.grid.unregisterPlugin(this.selector);
}
private removeInvalidRanges(ranges: Array<Slick.Range>): Array<Slick.Range> {
let result: Array<Slick.Range> = [];
for (let i = 0; i < ranges.length; i++) {
let r = ranges[i];
if (this.grid.canCellBeSelected(r.fromRow, r.fromCell) && this.grid.canCellBeSelected(r.toRow, r.toCell)) {
result.push(r);
} else if (this.grid.canCellBeSelected(r.fromRow, r.fromCell + 1) && this.grid.canCellBeSelected(r.toRow, r.toCell)) {
// account for number row
result.push(new Slick.Range(r.fromRow, r.fromCell + 1, r.toRow, r.toCell));
}
}
return result;
}
public setSelectedRanges(ranges: Array<Slick.Range>): void {
// simple check for: empty selection didn't change, prevent firing onSelectedRangesChanged
if ((!this.ranges || this.ranges.length === 0) && (!ranges || ranges.length === 0)) {
return;
}
this.ranges = this.removeInvalidRanges(ranges);
this.onSelectedRangesChanged.notify(this.ranges);
}
public getSelectedRanges() {
return this.ranges;
}
private handleBeforeCellRangeSelected(e, args: Slick.Cell) {
if (this.grid.getEditorLock().isActive()) {
e.stopPropagation();
return false;
}
return true;
}
private handleCellRangeSelected(e, args: { range: Slick.Range }) {
this.grid.setActiveCell(args.range.fromRow, args.range.fromCell, false, false, true);
this.setSelectedRanges([args.range]);
}
private handleActiveCellChange(e, args) {
if (this.options.selectActiveCell && !isUndefinedOrNull(args.row) && !isUndefinedOrNull(args.cell)) {
this.setSelectedRanges([new Slick.Range(args.row, args.cell)]);
} else if (!this.options.selectActiveCell) {
// clear the previous selection once the cell changes
this.setSelectedRanges([]);
}
}
private handleHeaderClick(e: MouseEvent, args: Slick.OnHeaderClickEventArgs<T>) {
if (!isUndefinedOrNull(args.column)) {
let columnIndex = this.grid.getColumnIndex(args.column.id);
if (this.grid.canCellBeSelected(0, columnIndex)) {
let ranges: Array<Slick.Range>;
if (e.shiftKey) {
ranges = this.getSelectedRanges();
ranges.push(new Slick.Range(0, columnIndex, this.grid.getDataLength() - 1, columnIndex));
} else {
ranges = [new Slick.Range(0, columnIndex, this.grid.getDataLength() - 1, columnIndex)];
}
this.grid.setActiveCell(0, columnIndex);
this.setSelectedRanges(ranges);
}
}
}
private handleKeyDown(e) {
/***
* Кey codes
* 37 left
* 38 up
* 39 right
* 40 down
*/
let ranges, last;
let active = this.grid.getActiveCell();
let metaKey = e.ctrlKey || e.metaKey;
if (active && e.shiftKey && !metaKey && !e.altKey &&
(e.which === 37 || e.which === 39 || e.which === 38 || e.which === 40)) {
ranges = this.getSelectedRanges();
if (!ranges.length) {
ranges.push(new Slick.Range(active.row, active.cell));
}
// keyboard can work with last range only
last = ranges.pop();
// can't handle selection out of active cell
if (!last.contains(active.row, active.cell)) {
last = new Slick.Range(active.row, active.cell);
}
let dRow = last.toRow - last.fromRow,
dCell = last.toCell - last.fromCell,
// walking direction
dirRow = active.row === last.fromRow ? 1 : -1,
dirCell = active.cell === last.fromCell ? 1 : -1;
if (e.which === 37) {
dCell -= dirCell;
} else if (e.which === 39) {
dCell += dirCell;
} else if (e.which === 38) {
dRow -= dirRow;
} else if (e.which === 40) {
dRow += dirRow;
}
// define new selection range
let new_last = new Slick.Range(active.row, active.cell, active.row + dirRow * dRow, active.cell + dirCell * dCell);
if (this.removeInvalidRanges([new_last]).length) {
ranges.push(new_last);
let viewRow = dirRow > 0 ? new_last.toRow : new_last.fromRow;
let viewCell = dirCell > 0 ? new_last.toCell : new_last.fromCell;
this.grid.scrollRowIntoView(viewRow, false);
this.grid.scrollCellIntoView(viewRow, viewCell, false);
} else {
ranges.push(last);
}
this.setSelectedRanges(ranges);
e.preventDefault();
e.stopPropagation();
}
}
}

View File

@@ -1,364 +0,0 @@
// Drag select selection model gist taken from https://gist.github.com/skoon/5312536
// heavily modified
import { clone } from 'sql/base/common/objects';
export class DragCellSelectionModel<T> implements Slick.SelectionModel<T, Array<Slick.Range>> {
private readonly keyColResizeIncr = 5;
private _grid: Slick.Grid<T>;
private _ranges: Array<Slick.Range> = [];
private _dragging = false;
private _handler = new Slick.EventHandler();
public onSelectedRangesChanged = new Slick.Event<Slick.Range[]>();
public init(grid: Slick.Grid<T>): void {
this._grid = grid;
this._handler.subscribe(this._grid.onActiveCellChanged, (e: Event, data: Slick.OnActiveCellChangedEventArgs<T>) => this.handleActiveCellChange(e, data));
this._handler.subscribe(this._grid.onKeyDown, (e: JQueryInputEventObject) => this.handleKeyDown(e));
this._handler.subscribe(this._grid.onClick, (e: MouseEvent) => this.handleClick(e));
this._handler.subscribe(this._grid.onDrag, (e: MouseEvent) => this.handleDrag(e));
this._handler.subscribe(this._grid.onDragInit, (e: MouseEvent) => this.handleDragInit(e));
this._handler.subscribe(this._grid.onDragStart, (e: MouseEvent) => this.handleDragStart(e));
this._handler.subscribe(this._grid.onDragEnd, (e: MouseEvent) => this.handleDragEnd(e));
this._handler.subscribe(this._grid.onHeaderClick, (e: MouseEvent, args: Slick.OnHeaderClickEventArgs<T>) => this.handleHeaderClick(e, args));
}
public destroy(): void {
this._handler.unsubscribeAll();
}
private rangesToRows(ranges: Array<Slick.Range>): Array<number> {
let rows = [];
for (let i = 0; i < ranges.length; i++) {
for (let j = ranges[i].fromRow; j <= ranges[i].toRow; j++) {
rows.push(j);
}
}
return rows;
}
private rowsToRanges(rows: Array<number>): Array<Slick.Range> {
let ranges = [];
let lastCell = this._grid.getColumns().length - 1;
for (let i = 0; i < rows.length; i++) {
ranges.push(new Slick.Range(rows[i], 0, rows[i], lastCell));
}
return ranges;
}
public getSelectedRows(): Array<number> {
return this.rangesToRows(this._ranges);
}
public setSelectedRows(rows: Array<number>) {
this.setSelectedRanges(this.rowsToRanges(rows));
}
public setSelectedRanges(ranges: Array<Slick.Range>) {
this._ranges = ranges;
this.onSelectedRangesChanged.notify(this._ranges);
}
public getSelectedRanges(): Array<Slick.Range> {
return this._ranges;
}
private handleActiveCellChange(e: Event, data: Slick.OnActiveCellChangedEventArgs<T>) { }
private isNavigationKey(e: BaseJQueryEventObject) {
// Nave keys (home, end, arrows) are all in sequential order so use a
switch (e.which) {
case $.ui.keyCode.HOME:
case $.ui.keyCode.END:
case $.ui.keyCode.LEFT:
case $.ui.keyCode.UP:
case $.ui.keyCode.RIGHT:
case $.ui.keyCode.DOWN:
return true;
default:
return false;
}
}
private navigateLeft(e: JQueryInputEventObject, activeCell: Slick.Cell) {
if (activeCell.cell > 1) {
let isHome = e.which === $.ui.keyCode.HOME;
let newActiveCellColumn = isHome ? 1 : activeCell.cell - 1;
// Unsure why but for range, must record 1 index less than expected
let newRangeColumn = newActiveCellColumn - 1;
if (e.shiftKey) {
let last = this._ranges.pop();
// If we are on the rightmost edge of the range and we navigate left,
// we want to deselect the rightmost cell
if (last.fromCell <= newRangeColumn) { last.toCell -= 1; }
let fromRow = Math.min(activeCell.row, last.fromRow);
let fromCell = Math.min(newRangeColumn, last.fromCell);
let toRow = Math.max(activeCell.row, last.toRow);
let toCell = Math.max(newRangeColumn, last.toCell);
this._ranges = [new Slick.Range(fromRow, fromCell, toRow, toCell)];
} else {
this._ranges = [new Slick.Range(activeCell.row, newRangeColumn, activeCell.row, newRangeColumn)];
}
this._grid.setActiveCell(activeCell.row, newActiveCellColumn);
this.setSelectedRanges(this._ranges);
}
}
private navigateRight(e: JQueryInputEventObject, activeCell: Slick.Cell) {
let columnLength = this._grid.getColumns().length;
if (activeCell.cell < columnLength) {
let isEnd = e.which === $.ui.keyCode.END;
let newActiveCellColumn = isEnd ? columnLength : activeCell.cell + 1;
// Unsure why but for range, must record 1 index less than expected
let newRangeColumn = newActiveCellColumn - 1;
if (e.shiftKey) {
let last = this._ranges.pop();
// If we are on the leftmost edge of the range and we navigate right,
// we want to deselect the leftmost cell
if (newRangeColumn <= last.toCell) { last.fromCell += 1; }
let fromRow = Math.min(activeCell.row, last.fromRow);
let fromCell = Math.min(newRangeColumn, last.fromCell);
let toRow = Math.max(activeCell.row, last.toRow);
let toCell = Math.max(newRangeColumn, last.toCell);
this._ranges = [new Slick.Range(fromRow, fromCell, toRow, toCell)];
} else {
this._ranges = [new Slick.Range(activeCell.row, newRangeColumn, activeCell.row, newRangeColumn)];
}
this._grid.setActiveCell(activeCell.row, newActiveCellColumn);
this.setSelectedRanges(this._ranges);
}
}
private handleKeyDown(e: JQueryInputEventObject) {
let activeCell = this._grid.getActiveCell();
if (activeCell) {
// navigation keys
if (this.isNavigationKey(e)) {
e.stopImmediatePropagation();
if (e.ctrlKey || e.metaKey) {
let event = new CustomEvent('gridnav', {
detail: {
which: e.which,
ctrlKey: e.ctrlKey,
metaKey: e.metaKey,
shiftKey: e.shiftKey,
altKey: e.altKey
}
});
window.dispatchEvent(event);
return;
}
// end key
if (e.which === $.ui.keyCode.END) {
this.navigateRight(e, activeCell);
}
// home key
if (e.which === $.ui.keyCode.HOME) {
this.navigateLeft(e, activeCell);
}
// left arrow
if (e.which === $.ui.keyCode.LEFT) {
// column resize
if ((e.ctrlKey || e.metaKey) && e.shiftKey) {
let allColumns = clone(this._grid.getColumns());
allColumns[activeCell.cell - 1].width = allColumns[activeCell.cell - 1].width - this.keyColResizeIncr;
this._grid.setColumnWidths(allColumns);
} else {
this.navigateLeft(e, activeCell);
}
// up arrow
} else if (e.which === $.ui.keyCode.UP && activeCell.row > 0) {
if (e.shiftKey) {
let last = this._ranges.pop();
// If we are on the bottommost edge of the range and we navigate up,
// we want to deselect the bottommost row
let newRangeRow = activeCell.row - 1;
if (last.fromRow <= newRangeRow) { last.toRow -= 1; }
let fromRow = Math.min(activeCell.row - 1, last.fromRow);
let fromCell = Math.min(activeCell.cell - 1, last.fromCell);
let toRow = Math.max(newRangeRow, last.toRow);
let toCell = Math.max(activeCell.cell - 1, last.toCell);
this._ranges = [new Slick.Range(fromRow, fromCell, toRow, toCell)];
} else {
this._ranges = [new Slick.Range(activeCell.row - 1, activeCell.cell - 1, activeCell.row - 1, activeCell.cell - 1)];
}
this._grid.setActiveCell(activeCell.row - 1, activeCell.cell);
this.setSelectedRanges(this._ranges);
// right arrow
} else if (e.which === $.ui.keyCode.RIGHT) {
// column resize
if ((e.ctrlKey || e.metaKey) && e.shiftKey) {
let allColumns = clone(this._grid.getColumns());
allColumns[activeCell.cell - 1].width = allColumns[activeCell.cell - 1].width + this.keyColResizeIncr;
this._grid.setColumnWidths(allColumns);
} else {
this.navigateRight(e, activeCell);
}
// down arrow
} else if (e.which === $.ui.keyCode.DOWN && activeCell.row < this._grid.getDataLength() - 1) {
if (e.shiftKey) {
let last = this._ranges.pop();
// If we are on the topmost edge of the range and we navigate down,
// we want to deselect the topmost row
let newRangeRow = activeCell.row + 1;
if (newRangeRow <= last.toRow) { last.fromRow += 1; }
let fromRow = Math.min(activeCell.row + 1, last.fromRow);
let fromCell = Math.min(activeCell.cell - 1, last.fromCell);
let toRow = Math.max(activeCell.row + 1, last.toRow);
let toCell = Math.max(activeCell.cell - 1, last.toCell);
this._ranges = [new Slick.Range(fromRow, fromCell, toRow, toCell)];
} else {
this._ranges = [new Slick.Range(activeCell.row + 1, activeCell.cell - 1, activeCell.row + 1, activeCell.cell - 1)];
}
this._grid.setActiveCell(activeCell.row + 1, activeCell.cell);
this.setSelectedRanges(this._ranges);
}
}
}
}
private handleHeaderClick(e: MouseEvent, args: Slick.OnHeaderClickEventArgs<T>) {
let columnIndex = this._grid.getColumnIndex(args.column.id);
if (e.ctrlKey || e.metaKey) {
this._ranges.push(new Slick.Range(0, columnIndex, this._grid.getDataLength() - 1, columnIndex));
this._grid.setActiveCell(0, columnIndex + 1);
} else if (e.shiftKey && this._ranges.length) {
let last = this._ranges.pop().fromCell;
let from = Math.min(columnIndex, last);
let to = Math.max(columnIndex, last);
this._ranges = [];
for (let i = from; i <= to; i++) {
if (i !== last) {
this._ranges.push(new Slick.Range(0, i, this._grid.getDataLength() - 1, i));
}
}
this._ranges.push(new Slick.Range(0, last, this._grid.getDataLength() - 1, last));
} else {
this._ranges = [new Slick.Range(0, columnIndex, this._grid.getDataLength() - 1, columnIndex)];
this._grid.resetActiveCell();
}
this.setSelectedRanges(this._ranges);
e.stopImmediatePropagation();
return true;
}
private handleClick(e: MouseEvent) {
let cell = this._grid.getCellFromEvent(e);
if (!cell || !this._grid.canCellBeActive(cell.row, cell.cell)) {
return false;
}
if (!e.ctrlKey && !e.shiftKey && !e.metaKey) {
if (cell.cell !== 0) {
this._ranges = [new Slick.Range(cell.row, cell.cell - 1, cell.row, cell.cell - 1)];
this.setSelectedRanges(this._ranges);
this._grid.setActiveCell(cell.row, cell.cell);
return true;
} else {
this._ranges = [new Slick.Range(cell.row, 0, cell.row, this._grid.getColumns().length - 1)];
this.setSelectedRanges(this._ranges);
this._grid.setActiveCell(cell.row, 1);
return true;
}
}
else if (this._grid.getOptions().multiSelect) {
if (e.ctrlKey || e.metaKey) {
if (cell.cell === 0) {
this._ranges.push(new Slick.Range(cell.row, 0, cell.row, this._grid.getColumns().length - 1));
this._grid.setActiveCell(cell.row, 1);
} else {
this._ranges.push(new Slick.Range(cell.row, cell.cell - 1, cell.row, cell.cell - 1));
this._grid.setActiveCell(cell.row, cell.cell);
}
} else if (this._ranges.length && e.shiftKey) {
let last = this._ranges.pop();
if (cell.cell === 0) {
let fromRow = Math.min(cell.row, last.fromRow);
let toRow = Math.max(cell.row, last.fromRow);
this._ranges = [new Slick.Range(fromRow, 0, toRow, this._grid.getColumns().length - 1)];
} else {
let fromRow = Math.min(cell.row, last.fromRow);
let fromCell = Math.min(cell.cell - 1, last.fromCell);
let toRow = Math.max(cell.row, last.toRow);
let toCell = Math.max(cell.cell - 1, last.toCell);
this._ranges = [new Slick.Range(fromRow, fromCell, toRow, toCell)];
}
}
}
this.setSelectedRanges(this._ranges);
return true;
}
private handleDragInit(e: MouseEvent) {
e.stopImmediatePropagation();
}
private handleDragStart(e: MouseEvent) {
let cell = this._grid.getCellFromEvent(e);
e.stopImmediatePropagation();
this._dragging = true;
if (e.ctrlKey || e.metaKey) {
this._ranges.push(new Slick.Range(cell.row, cell.cell));
this._grid.setActiveCell(cell.row, cell.cell);
} else if (this._ranges.length && e.shiftKey) {
let last = this._ranges.pop();
let fromRow = Math.min(cell.row, last.fromRow);
let fromCell = Math.min(cell.cell - 1, last.fromCell);
let toRow = Math.max(cell.row, last.toRow);
let toCell = Math.max(cell.cell - 1, last.toCell);
this._ranges = [new Slick.Range(fromRow, fromCell, toRow, toCell)];
} else {
this._ranges = [new Slick.Range(cell.row, cell.cell)];
this._grid.setActiveCell(cell.row, cell.cell);
}
this.setSelectedRanges(this._ranges);
}
private handleDrag(e: MouseEvent) {
if (this._dragging) {
let cell = this._grid.getCellFromEvent(e);
let activeCell = this._grid.getActiveCell();
if (!cell || !this._grid.canCellBeActive(cell.row, cell.cell)) {
return false;
}
this._ranges.pop();
if (activeCell.cell === 0) {
let lastCell = this._grid.getColumns().length - 1;
let firstRow = Math.min(cell.row, activeCell.row);
let lastRow = Math.max(cell.row, activeCell.row);
this._ranges.push(new Slick.Range(firstRow, 0, lastRow, lastCell));
} else {
let firstRow = Math.min(cell.row, activeCell.row);
let lastRow = Math.max(cell.row, activeCell.row);
let firstColumn = Math.min(cell.cell - 1, activeCell.cell - 1);
let lastColumn = Math.max(cell.cell - 1, activeCell.cell - 1);
this._ranges.push(new Slick.Range(firstRow, firstColumn, lastRow, lastColumn));
}
this.setSelectedRanges(this._ranges);
return true;
}
return false;
}
private handleDragEnd(e: MouseEvent) {
this._dragging = false;
}
}

View File

@@ -6,6 +6,7 @@ import { mixin } from 'vs/base/common/objects';
import { SlickGrid } from 'angular2-slickgrid'; import { SlickGrid } from 'angular2-slickgrid';
import { Button } from '../../button/button'; import { Button } from '../../button/button';
import { attachButtonStyler } from 'sql/common/theme/styler'; import { attachButtonStyler } from 'sql/common/theme/styler';
import { escape } from 'sql/base/common/strings';
import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IThemeService } from 'vs/platform/theme/common/themeService';
export class HeaderFilter { export class HeaderFilter {
@@ -174,7 +175,7 @@ export class HeaderFilter {
if (filterItems[i] && filterItems[i].indexOf('Error:') < 0) { if (filterItems[i] && filterItems[i].indexOf('Error:') < 0) {
filterOptions += '<label><input type="checkbox" value="' + i + '"' filterOptions += '<label><input type="checkbox" value="' + i + '"'
+ (filtered ? ' checked="checked"' : '') + (filtered ? ' checked="checked"' : '')
+ '/>' + filterItems[i] + '</label>'; + '/>' + escape(filterItems[i]) + '</label>';
} }
} }
let $filter = $('<div class="filter">') let $filter = $('<div class="filter">')

View File

@@ -1,5 +1,6 @@
// Adopted and converted to typescript from https://github.com/6pac/SlickGrid/blob/master/plugins/slick.rowdetailview.js // Adopted and converted to typescript from https://github.com/6pac/SlickGrid/blob/master/plugins/slick.rowdetailview.js
// heavily modified // heavily modified
import { escape } from 'sql/base/common/strings';
import { mixin } from 'vs/base/common/objects'; import { mixin } from 'vs/base/common/objects';
import * as nls from 'vs/nls'; import * as nls from 'vs/nls';
@@ -354,7 +355,7 @@ export class RowDetailView {
html.push("style='height:", dataContext._height, "px;"); //set total height of padding html.push("style='height:", dataContext._height, "px;"); //set total height of padding
html.push("top:", rowHeight, "px'>"); //shift detail below 1st row html.push("top:", rowHeight, "px'>"); //shift detail below 1st row
html.push("<div id='detailViewContainer_", dataContext.id, "' class='detail-container' style='max-height:" + (dataContext._height - rowHeight + bottomMargin) + "px'>"); //sub ctr for custom styling html.push("<div id='detailViewContainer_", dataContext.id, "' class='detail-container' style='max-height:" + (dataContext._height - rowHeight + bottomMargin) + "px'>"); //sub ctr for custom styling
html.push("<div id='innerDetailView_", dataContext.id, "'>", dataContext._detailContent, "</div></div>"); html.push("<div id='innerDetailView_", dataContext.id, "'>", escape(dataContext._detailContent), "</div></div>");
//&omit a final closing detail container </div> that would come next //&omit a final closing detail container </div> that would come next
return html.join(''); return html.join('');

View File

@@ -0,0 +1,70 @@
/*---------------------------------------------------------------------------------------------
* 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 { range } from 'vs/base/common/arrays';
export interface IRowNumberColumnOptions {
numberOfRows: number;
cssClass?: string;
}
const sizePerDigit = 15;
export class RowNumberColumn<T> implements Slick.Plugin<T> {
private handler = new Slick.EventHandler();
private grid: Slick.Grid<T>;
constructor(private options: IRowNumberColumnOptions) {
}
public init(grid: Slick.Grid<T>) {
this.grid = grid;
this.handler
.subscribe(this.grid.onClick, (e, args) => this.handleClick(e, args))
.subscribe(this.grid.onHeaderClick, (e, args) => this.handleHeaderClick(e, args));
}
public destroy() {
this.handler.unsubscribeAll();
}
private handleClick(e: MouseEvent, args: Slick.OnClickEventArgs<T>): void {
if (this.grid.getColumns()[args.cell].id === 'rowNumber') {
this.grid.setActiveCell(args.row, 1);
this.grid.setSelectedRows([args.row]);
}
}
private handleHeaderClick(e: MouseEvent, args: Slick.OnHeaderClickEventArgs<T>): void {
if (args.column.id === 'rowNumber') {
this.grid.setActiveCell(0, 1);
this.grid.setSelectedRows(range(this.grid.getDataLength()));
}
}
public getColumnDefinition(): Slick.Column<T> {
return {
id: 'rowNumber',
name: '',
field: 'rowNumber',
width: this.options.numberOfRows.toString().length * sizePerDigit,
resizable: false,
cssClass: this.options.cssClass,
focusable: false,
selectable: false,
formatter: (r, c, v, cd, dc) => this.formatter(r, c, v, cd, dc)
};
}
private formatter(row, cell, value, columnDef: Slick.Column<T>, dataContext): string {
if (dataContext) {
return `<span>${row}</span>`;
}
return null;
}
}

View File

@@ -82,7 +82,7 @@ export class Taskbar {
public static createTaskbarText(inputText: string): HTMLElement { public static createTaskbarText(inputText: string): HTMLElement {
let element = document.createElement('div'); let element = document.createElement('div');
element.className = 'taskbarTextSeparator'; element.className = 'taskbarTextSeparator';
element.innerHTML = inputText; element.textContent = inputText;
return element; return element;
} }

View File

@@ -15,6 +15,7 @@ export function escape(html: string): string {
case '>': return '&gt;'; case '>': return '&gt;';
case '&': return '&amp;'; case '&': return '&amp;';
case '"': return '&quot;'; case '"': return '&quot;';
case '\'': return '&#39';
default: return match; default: return match;
} }
}); });

View File

@@ -100,7 +100,7 @@ export class AutoOAuthDialog extends Modal {
let inputBox: InputBox; let inputBox: InputBox;
container.div({ class: 'dialog-input-section' }, (inputContainer) => { container.div({ class: 'dialog-input-section' }, (inputContainer) => {
inputContainer.div({ class: 'dialog-label' }, (labelContainer) => { inputContainer.div({ class: 'dialog-label' }, (labelContainer) => {
labelContainer.innerHtml(label); labelContainer.text(label);
}); });
inputContainer.div({ class: 'dialog-input' }, (inputCellContainer) => { inputContainer.div({ class: 'dialog-input' }, (inputCellContainer) => {

View File

@@ -145,7 +145,7 @@ export class FirewallRuleDialog extends Modal {
subnetIPRangeSection = subnetIPRangeContainer.getHTMLElement(); subnetIPRangeSection = subnetIPRangeContainer.getHTMLElement();
subnetIPRangeContainer.div({ 'class': 'dialog-input-section' }, (inputContainer) => { subnetIPRangeContainer.div({ 'class': 'dialog-input-section' }, (inputContainer) => {
inputContainer.div({ 'class': 'dialog-label' }, (labelContainer) => { inputContainer.div({ 'class': 'dialog-label' }, (labelContainer) => {
labelContainer.innerHtml(LocalizedStrings.FROM); labelContainer.text(LocalizedStrings.FROM);
}); });
inputContainer.div({ 'class': 'dialog-input' }, (inputCellContainer) => { inputContainer.div({ 'class': 'dialog-input' }, (inputCellContainer) => {
@@ -155,7 +155,7 @@ export class FirewallRuleDialog extends Modal {
}); });
inputContainer.div({ 'class': 'dialog-label' }, (labelContainer) => { inputContainer.div({ 'class': 'dialog-label' }, (labelContainer) => {
labelContainer.innerHtml(LocalizedStrings.TO); labelContainer.text(LocalizedStrings.TO);
}); });
inputContainer.div({ 'class': 'dialog-input' }, (inputCellContainer) => { inputContainer.div({ 'class': 'dialog-input' }, (inputCellContainer) => {
@@ -234,7 +234,7 @@ export class FirewallRuleDialog extends Modal {
className += ' header'; className += ' header';
} }
container.div({ 'class': className }, (labelContainer) => { container.div({ 'class': className }, (labelContainer) => {
labelContainer.innerHtml(content); labelContainer.text(content);
}); });
} }

View File

@@ -320,12 +320,13 @@ export enum MetadataType {
} }
export enum TaskStatus { export enum TaskStatus {
notStarted = 0, NotStarted = 0,
inProgress = 1, InProgress = 1,
succeeded = 2, Succeeded = 2,
succeededWithWarning = 3, SucceededWithWarning = 3,
failed = 4, Failed = 4,
canceled = 5 Canceled = 5,
Canceling = 6
} }
export interface IConnectionParams { export interface IConnectionParams {

View File

@@ -262,7 +262,7 @@ export class ConnectionDialogWidget extends Modal {
let recentHistoryLabel = localize('recentHistory', 'Recent history'); let recentHistoryLabel = localize('recentHistory', 'Recent history');
recentConnectionContainer.div({ class: 'recent-titles-container' }, (container) => { recentConnectionContainer.div({ class: 'recent-titles-container' }, (container) => {
container.div({ class: 'connection-history-label' }, (recentTitle) => { container.div({ class: 'connection-history-label' }, (recentTitle) => {
recentTitle.innerHtml(recentHistoryLabel); recentTitle.text(recentHistoryLabel);
}); });
container.div({ class: 'connection-history-actions' }, (actionsContainer) => { container.div({ class: 'connection-history-actions' }, (actionsContainer) => {
this._actionbar = this._register(new ActionBar(actionsContainer.getHTMLElement(), { animated: false })); this._actionbar = this._register(new ActionBar(actionsContainer.getHTMLElement(), { animated: false }));
@@ -303,7 +303,7 @@ export class ConnectionDialogWidget extends Modal {
this._noRecentConnectionBuilder.div({ class: 'connection-recent-content' }, (noRecentConnectionContainer) => { this._noRecentConnectionBuilder.div({ class: 'connection-recent-content' }, (noRecentConnectionContainer) => {
let noRecentHistoryLabel = localize('noRecentConnections', 'No recent connection'); let noRecentHistoryLabel = localize('noRecentConnections', 'No recent connection');
noRecentConnectionContainer.div({ class: 'no-recent-connections' }, (noRecentTitle) => { noRecentConnectionContainer.div({ class: 'no-recent-connections' }, (noRecentTitle) => {
noRecentTitle.innerHtml(noRecentHistoryLabel); noRecentTitle.text(noRecentHistoryLabel);
}); });
}); });
} }
@@ -335,7 +335,7 @@ export class ConnectionDialogWidget extends Modal {
this._noSavedConnectionBuilder.div({ class: 'connection-saved-content' }, (noSavedConnectionContainer) => { this._noSavedConnectionBuilder.div({ class: 'connection-saved-content' }, (noSavedConnectionContainer) => {
let noSavedConnectionLabel = localize('noSavedConnections', 'No saved connection'); let noSavedConnectionLabel = localize('noSavedConnections', 'No saved connection');
noSavedConnectionContainer.div({ class: 'no-saved-connections' }, (titleContainer) => { noSavedConnectionContainer.div({ class: 'no-saved-connections' }, (titleContainer) => {
titleContainer.innerHtml(noSavedConnectionLabel); titleContainer.text(noSavedConnectionLabel);
}); });
}); });
} }

View File

@@ -163,7 +163,7 @@ export class NewDashboardTabDialog extends Modal {
this._noExtensionViewContainer = DOM.$('.no-extension-view'); this._noExtensionViewContainer = DOM.$('.no-extension-view');
let noExtensionTitle = DOM.append(this._noExtensionViewContainer, DOM.$('.no-extensionTab-label')); let noExtensionTitle = DOM.append(this._noExtensionViewContainer, DOM.$('.no-extensionTab-label'));
let noExtensionLabel = localize('newdashboardTabDialog.noExtensionLabel', 'No dashboard extensions are installed at this time. Go to Extension Manager to explore recommended extensions.'); let noExtensionLabel = localize('newdashboardTabDialog.noExtensionLabel', 'No dashboard extensions are installed at this time. Go to Extension Manager to explore recommended extensions.');
noExtensionTitle.innerHTML = noExtensionLabel; noExtensionTitle.textContent = noExtensionLabel;
DOM.append(container, this._noExtensionViewContainer); DOM.append(container, this._noExtensionViewContainer);
} }

View File

@@ -11,8 +11,8 @@ import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/work
import { IInsightsView, IInsightData } from 'sql/parts/dashboard/widgets/insights/interfaces'; import { IInsightsView, IInsightData } from 'sql/parts/dashboard/widgets/insights/interfaces';
import { Table } from 'sql/base/browser/ui/table/table'; import { Table } from 'sql/base/browser/ui/table/table';
import { TableDataView } from 'sql/base/browser/ui/table/tableDataView'; import { TableDataView } from 'sql/base/browser/ui/table/tableDataView';
import { DragCellSelectionModel } from 'sql/base/browser/ui/table/plugins/dragCellSelectionModel.plugin'; import { attachTableStyler } from 'sql/common/theme/styler';
import { attachTableStyler} from 'sql/common/theme/styler'; import { CellSelectionModel } from 'sql/base/browser/ui/table/plugins/cellSelectionModel.plugin';
@Component({ @Component({
template: '' template: ''
@@ -63,7 +63,7 @@ export default class TableInsight extends Disposable implements IInsightsView, O
private createTable() { private createTable() {
if (!this.table) { if (!this.table) {
this.table = new Table(this._elementRef.nativeElement, this.dataView, this.columns, { showRowNumber: true }); this.table = new Table(this._elementRef.nativeElement, this.dataView, this.columns, { showRowNumber: true });
this.table.setSelectionModel(new DragCellSelectionModel()); this.table.setSelectionModel(new CellSelectionModel());
this._register(attachTableStyler(this.table, this.themeService)); this._register(attachTableStyler(this.table, this.themeService));
} }
} }

View File

@@ -179,8 +179,8 @@ export class RestoreDialogController implements IRestoreDialogController {
private isSuccessfulRestore(response: TaskNode): boolean { private isSuccessfulRestore(response: TaskNode): boolean {
return (response.taskName === this._restoreTaskName && return (response.taskName === this._restoreTaskName &&
response.message === this._restoreCompleted && response.message === this._restoreCompleted &&
(response.status === TaskStatus.succeeded || (response.status === TaskStatus.Succeeded ||
response.status === TaskStatus.succeededWithWarning) && response.status === TaskStatus.SucceededWithWarning) &&
(response.taskExecutionMode === TaskExecutionMode.execute || (response.taskExecutionMode === TaskExecutionMode.execute ||
response.taskExecutionMode === TaskExecutionMode.executeAndScript)); response.taskExecutionMode === TaskExecutionMode.executeAndScript));
} }

View File

@@ -226,7 +226,7 @@ export class RestoreDialog extends Modal {
destinationContainer.div({ class: 'dialog-input-section' }, (inputContainer) => { destinationContainer.div({ class: 'dialog-input-section' }, (inputContainer) => {
inputContainer.div({ class: 'dialog-label' }, (labelContainer) => { inputContainer.div({ class: 'dialog-label' }, (labelContainer) => {
labelContainer.innerHtml(LocalizedStrings.TARGETDATABASE); labelContainer.text(LocalizedStrings.TARGETDATABASE);
}); });
inputContainer.div({ class: 'dialog-input' }, (inputCellContainer) => { inputContainer.div({ class: 'dialog-input' }, (inputCellContainer) => {
@@ -471,7 +471,7 @@ export class RestoreDialog extends Modal {
className += ' header'; className += ' header';
} }
container.div({ class: className }, (labelContainer) => { container.div({ class: className }, (labelContainer) => {
labelContainer.innerHtml(content); labelContainer.text(content);
}); });
} }
@@ -535,7 +535,7 @@ export class RestoreDialog extends Modal {
let selectBox: SelectBox; let selectBox: SelectBox;
container.div({ class: 'dialog-input-section' }, (inputContainer) => { container.div({ class: 'dialog-input-section' }, (inputContainer) => {
inputContainer.div({ class: 'dialog-label' }, (labelContainer) => { inputContainer.div({ class: 'dialog-label' }, (labelContainer) => {
labelContainer.innerHtml(label); labelContainer.text(label);
}); });
inputContainer.div({ class: 'dialog-input' }, (inputCellContainer) => { inputContainer.div({ class: 'dialog-input' }, (inputCellContainer) => {

View File

@@ -4,7 +4,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information. * Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import { IColumnDefinition, IObservableCollection, IGridDataRow } from 'angular2-slickgrid'; import { ISlickColumn, IObservableCollection, IGridDataRow } from 'angular2-slickgrid';
export interface ISlickRange { export interface ISlickRange {
fromCell: number; fromCell: number;
@@ -42,7 +42,7 @@ export interface IGridIcon {
export interface IGridDataSet { export interface IGridDataSet {
dataRows: IObservableCollection<IGridDataRow>; dataRows: IObservableCollection<IGridDataRow>;
columnDefinitions: IColumnDefinition[]; columnDefinitions: ISlickColumn<any>[];
resized: any; // EventEmitter<any>; resized: any; // EventEmitter<any>;
totalRows: number; totalRows: number;
batchId: number; batchId: number;

View File

@@ -17,14 +17,13 @@
showDataTypeIcon="false" showDataTypeIcon="false"
showHeader="true" showHeader="true"
[resized]="dataSet.resized" [resized]="dataSet.resized"
[plugins]="slickgridPlugins" [plugins]="plugins[i]"
(activeCellChanged)="onActiveCellChanged($event)" (activeCellChanged)="onActiveCellChanged($event)"
(cellEditBegin)="onCellEditBegin($event)" (cellEditBegin)="onCellEditBegin($event)"
(cellEditExit)="onCellEditEnd($event)" (cellEditExit)="onCellEditEnd($event)"
(rowEditBegin)="onRowEditBegin($event)" (rowEditBegin)="onRowEditBegin($event)"
(rowEditExit)="onRowEditEnd($event)" (rowEditExit)="onRowEditEnd($event)"
(contextMenu)="openContextMenu($event, dataSet.batchId, dataSet.resultId, i)" (contextMenu)="openContextMenu($event, dataSet.batchId, dataSet.resultId, i)"
[isColumnEditable]="onIsColumnEditable"
[isCellEditValid]="onIsCellEditValid" [isCellEditValid]="onIsCellEditValid"
[overrideCellFn]="overrideCellFn" [overrideCellFn]="overrideCellFn"
enableEditing="true" enableEditing="true"

View File

@@ -24,6 +24,9 @@ import { error } from 'sql/base/common/log';
import { clone, mixin } from 'sql/base/common/objects'; import { clone, mixin } from 'sql/base/common/objects';
import { IQueryEditorService } from 'sql/parts/query/common/queryEditorService'; import { IQueryEditorService } from 'sql/parts/query/common/queryEditorService';
import { IBootstrapParams } from 'sql/services/bootstrap/bootstrapService'; import { IBootstrapParams } from 'sql/services/bootstrap/bootstrapService';
import { RowNumberColumn } from 'sql/base/browser/ui/table/plugins/rowNumberColumn.plugin';
import { AutoColumnSize } from 'sql/base/browser/ui/table/plugins/autoSizeColumns.plugin';
import { AdditionalKeyBindings } from 'sql/base/browser/ui/table/plugins/additionalKeyBindings.plugin';
import { escape } from 'sql/base/common/strings'; import { escape } from 'sql/base/common/strings';
import { INotificationService } from 'vs/platform/notification/common/notification'; import { INotificationService } from 'vs/platform/notification/common/notification';
@@ -36,7 +39,6 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { KeyCode } from 'vs/base/common/keyCodes'; import { KeyCode } from 'vs/base/common/keyCodes';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
export const EDITDATA_SELECTOR: string = 'editdata-component'; export const EDITDATA_SELECTOR: string = 'editdata-component';
@Component({ @Component({
@@ -66,6 +68,7 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
private newRowVisible: boolean; private newRowVisible: boolean;
private removingNewRow: boolean; private removingNewRow: boolean;
private rowIdMappings: { [gridRowId: number]: number } = {}; private rowIdMappings: { [gridRowId: number]: number } = {};
protected plugins = new Array<Array<Slick.Plugin<any>>>();
// Edit Data functions // Edit Data functions
public onActiveCellChanged: (event: { row: number, column: number }) => void; public onActiveCellChanged: (event: { row: number, column: number }) => void;
@@ -167,17 +170,6 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
this.onRowEditEnd = (event: { row: number }): void => { }; this.onRowEditEnd = (event: { row: number }): void => { };
this.onIsColumnEditable = (column: number): boolean => {
let result = false;
// Check that our variables exist
if (column !== undefined && !!this.dataSet && !!this.dataSet.columnDefinitions[column]) {
result = this.dataSet.columnDefinitions[column].isEditable;
}
// If no column definition exists then the row is not editable
return result;
};
this.overrideCellFn = (rowNumber, columnId, value?, data?): string => { this.overrideCellFn = (rowNumber, columnId, value?, data?): string => {
let returnVal = ''; let returnVal = '';
if (Services.DBCellValue.isDBCellValue(value)) { if (Services.DBCellValue.isDBCellValue(value)) {
@@ -197,9 +189,9 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
self.idMapping[rowIndex] = row.id; self.idMapping[rowIndex] = row.id;
rowIndex++; rowIndex++;
return { return {
values: row.cells.map(c => { values: [{}].concat(row.cells.map(c => {
return mixin({ ariaLabel: c.displayValue }, c); return mixin({ ariaLabel: escape(c.displayValue) }, c);
}), row: row.id })), row: row.id
}; };
}); });
@@ -351,6 +343,8 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
let maxHeight = this.getMaxHeight(resultSet.rowCount); let maxHeight = this.getMaxHeight(resultSet.rowCount);
let minHeight = this.getMinHeight(resultSet.rowCount); let minHeight = this.getMinHeight(resultSet.rowCount);
let rowNumberColumn = new RowNumberColumn({ numberOfRows: resultSet.rowCount });
// Store the result set from the event // Store the result set from the event
let dataSet: IGridDataSet = { let dataSet: IGridDataSet = {
resized: undefined, resized: undefined,
@@ -365,7 +359,7 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
this.loadDataFunction, this.loadDataFunction,
index => { return { values: [] }; } index => { return { values: [] }; }
), ),
columnDefinitions: resultSet.columnInfo.map((c, i) => { columnDefinitions: [rowNumberColumn.getColumnDefinition()].concat(resultSet.columnInfo.map((c, i) => {
let isLinked = c.isXml || c.isJson; let isLinked = c.isXml || c.isJson;
let linkType = c.isXml ? 'xml' : 'json'; let linkType = c.isXml ? 'xml' : 'json';
@@ -374,13 +368,14 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
name: c.columnName === 'Microsoft SQL Server 2005 XML Showplan' name: c.columnName === 'Microsoft SQL Server 2005 XML Showplan'
? 'XML Showplan' ? 'XML Showplan'
: escape(c.columnName), : escape(c.columnName),
type: self.stringToFieldType('string'), field: i.toString(),
formatter: isLinked ? Services.hyperLinkFormatter : Services.textFormatter, formatter: isLinked ? Services.hyperLinkFormatter : Services.textFormatter,
asyncPostRender: isLinked ? self.linkHandler(linkType) : undefined, asyncPostRender: isLinked ? self.linkHandler(linkType) : undefined,
isEditable: c.isUpdatable isEditable: c.isUpdatable
}; };
}) }))
}; };
self.plugins.push([rowNumberColumn, new AutoColumnSize(), new AdditionalKeyBindings()]);
self.dataSet = dataSet; self.dataSet = dataSet;
// Create a dataSet to render without rows to reduce DOM size // Create a dataSet to render without rows to reduce DOM size

View File

@@ -26,6 +26,7 @@ import * as GridContentEvents from 'sql/parts/grid/common/gridContentEvents';
import { ResultsVisibleContext, ResultsGridFocussedContext, ResultsMessagesFocussedContext, QueryEditorVisibleContext } from 'sql/parts/query/common/queryContext'; import { ResultsVisibleContext, ResultsGridFocussedContext, ResultsMessagesFocussedContext, QueryEditorVisibleContext } from 'sql/parts/query/common/queryContext';
import { error } from 'sql/base/common/log'; import { error } from 'sql/base/common/log';
import { IQueryEditorService } from 'sql/parts/query/common/queryEditorService'; import { IQueryEditorService } from 'sql/parts/query/common/queryEditorService';
import { CellSelectionModel } from 'sql/base/browser/ui/table/plugins/cellSelectionModel.plugin';
import { IAction } from 'vs/base/common/actions'; import { IAction } from 'vs/base/common/actions';
import { ResolvedKeybinding } from 'vs/base/common/keyCodes'; import { ResolvedKeybinding } from 'vs/base/common/keyCodes';
@@ -33,8 +34,6 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { AutoColumnSize } from 'sql/base/browser/ui/table/plugins/autoSizeColumns.plugin';
import { DragCellSelectionModel } from 'sql/base/browser/ui/table/plugins/dragCellSelectionModel.plugin';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
@@ -42,14 +41,8 @@ import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
export abstract class GridParentComponent { export abstract class GridParentComponent {
// CONSTANTS // CONSTANTS
// tslint:disable:no-unused-variable // tslint:disable:no-unused-variable
protected get selectionModel(): DragCellSelectionModel<any> {
return new DragCellSelectionModel<any>(); protected get selectionModel() { return new CellSelectionModel(); }
}
protected get slickgridPlugins(): Array<any> {
return [
new AutoColumnSize<any>({})
];
}
protected _rowHeight = 29; protected _rowHeight = 29;
protected _defaultNumShowingRows = 8; protected _defaultNumShowingRows = 8;
protected Constants = Constants; protected Constants = Constants;
@@ -94,7 +87,6 @@ export abstract class GridParentComponent {
public onRowEditBegin: (event: { row: number }) => void; public onRowEditBegin: (event: { row: number }) => void;
public onRowEditEnd: (event: { row: number }) => void; public onRowEditEnd: (event: { row: number }) => void;
public onIsCellEditValid: (row: number, column: number, newValue: any) => boolean; public onIsCellEditValid: (row: number, column: number, newValue: any) => boolean;
public onIsColumnEditable: (column: number) => boolean;
public overrideCellFn: (rowNumber, columnId, value?, data?) => string; public overrideCellFn: (rowNumber, columnId, value?, data?) => string;
public loadDataFunction: (offset: number, count: number) => Promise<IGridDataRow[]>; public loadDataFunction: (offset: number, count: number) => Promise<IGridDataRow[]>;
@@ -423,7 +415,9 @@ export abstract class GridParentComponent {
let self = this; let self = this;
return (gridIndex: number) => { return (gridIndex: number) => {
self.activeGrid = gridIndex; self.activeGrid = gridIndex;
self.slickgrids.toArray()[this.activeGrid].selection = true; let grid = self.slickgrids.toArray()[self.activeGrid];
grid.setActive();
grid.selection = true;
}; };
} }
@@ -433,22 +427,6 @@ export abstract class GridParentComponent {
} }
} }
/**
* Used to convert the string to a enum compatible with SlickGrid
*/
protected stringToFieldType(input: string): FieldType {
let fieldtype: FieldType;
switch (input) {
case 'string':
fieldtype = FieldType.String;
break;
default:
fieldtype = FieldType.String;
break;
}
return fieldtype;
}
/** /**
* Makes a resultset take up the full result height if this is not already true * Makes a resultset take up the full result height if this is not already true
* Otherwise rerenders the result sets from default * Otherwise rerenders the result sets from default

View File

@@ -20,12 +20,11 @@
[dataRows]="dataSet.dataRows" [dataRows]="dataSet.dataRows"
(contextMenu)="openContextMenu($event, dataSet.batchId, dataSet.resultId, i)" (contextMenu)="openContextMenu($event, dataSet.batchId, dataSet.resultId, i)"
enableAsyncPostRender="true" enableAsyncPostRender="true"
showDataTypeIcon="false"
showHeader="true" showHeader="true"
[resized]="dataSet.resized" [resized]="dataSet.resized"
(mousedown)="navigateToGrid(i)" (mousedown)="navigateToGrid(i)"
[selectionModel]="selectionModel" [selectionModel]="selectionModel"
[plugins]="slickgridPlugins" [plugins]="plugins[i]"
class="boxCol content vertBox slickgrid" class="boxCol content vertBox slickgrid"
[rowHeight]="rowHeight"> [rowHeight]="rowHeight">
</slick-grid> </slick-grid>

View File

@@ -28,6 +28,9 @@ import { TabChild } from 'sql/base/browser/ui/panel/tab.component';
import { clone, mixin } from 'sql/base/common/objects'; import { clone, mixin } from 'sql/base/common/objects';
import { IQueryEditorService } from 'sql/parts/query/common/queryEditorService'; import { IQueryEditorService } from 'sql/parts/query/common/queryEditorService';
import { escape } from 'sql/base/common/strings'; import { escape } from 'sql/base/common/strings';
import { RowNumberColumn } from 'sql/base/browser/ui/table/plugins/rowNumberColumn.plugin';
import { AutoColumnSize } from 'sql/base/browser/ui/table/plugins/autoSizeColumns.plugin';
import { AdditionalKeyBindings } from 'sql/base/browser/ui/table/plugins/additionalKeyBindings.plugin';
import { format } from 'vs/base/common/strings'; import { format } from 'vs/base/common/strings';
import * as DOM from 'vs/base/browser/dom'; import * as DOM from 'vs/base/browser/dom';
@@ -61,7 +64,9 @@ export class QueryComponent extends GridParentComponent implements OnInit, OnDes
// create a function alias to use inside query.component // create a function alias to use inside query.component
// tslint:disable-next-line:no-unused-variable // tslint:disable-next-line:no-unused-variable
private stringsFormat: any = format; protected stringsFormat: any = format;
protected plugins = new Array<Array<Slick.Plugin<any>>>();
// tslint:disable-next-line:no-unused-variable // tslint:disable-next-line:no-unused-variable
private dataIcons: IGridIcon[] = [ private dataIcons: IGridIcon[] = [
@@ -302,9 +307,9 @@ export class QueryComponent extends GridParentComponent implements OnInit, OnDes
for (let row = 0; row < rows.rows.length; row++) { for (let row = 0; row < rows.rows.length; row++) {
// Push row values onto end of gridData for slickgrid // Push row values onto end of gridData for slickgrid
gridData.push({ gridData.push({
values: rows.rows[row].map(c => { values: [{}].concat(rows.rows[row].map(c => {
return mixin({ ariaLabel: escape(c.displayValue) }, c); return mixin({ ariaLabel: escape(c.displayValue) }, c);
}) }))
}); });
} }
@@ -331,6 +336,8 @@ export class QueryComponent extends GridParentComponent implements OnInit, OnDes
minHeight = minHeightNumber.toString() + 'px'; minHeight = minHeightNumber.toString() + 'px';
} }
let rowNumberColumn = new RowNumberColumn({ numberOfRows: resultSet.rowCount });
// Store the result set from the event // Store the result set from the event
let dataSet: IGridDataSet = { let dataSet: IGridDataSet = {
resized: undefined, resized: undefined,
@@ -345,7 +352,7 @@ export class QueryComponent extends GridParentComponent implements OnInit, OnDes
loadDataFunction, loadDataFunction,
index => { return { values: [] }; } index => { return { values: [] }; }
), ),
columnDefinitions: resultSet.columnInfo.map((c, i) => { columnDefinitions: [rowNumberColumn.getColumnDefinition()].concat(resultSet.columnInfo.map((c, i) => {
let isLinked = c.isXml || c.isJson; let isLinked = c.isXml || c.isJson;
let linkType = c.isXml ? 'xml' : 'json'; let linkType = c.isXml ? 'xml' : 'json';
@@ -354,12 +361,13 @@ export class QueryComponent extends GridParentComponent implements OnInit, OnDes
name: c.columnName === 'Microsoft SQL Server 2005 XML Showplan' name: c.columnName === 'Microsoft SQL Server 2005 XML Showplan'
? 'XML Showplan' ? 'XML Showplan'
: escape(c.columnName), : escape(c.columnName),
type: self.stringToFieldType('string'), field: i.toString(),
formatter: isLinked ? Services.hyperLinkFormatter : Services.textFormatter, formatter: isLinked ? Services.hyperLinkFormatter : Services.textFormatter,
asyncPostRender: isLinked ? self.linkHandler(linkType) : undefined asyncPostRender: isLinked ? self.linkHandler(linkType) : undefined
}; };
}) }))
}; };
self.plugins.push([rowNumberColumn, new AutoColumnSize(), new AdditionalKeyBindings()]);
self.dataSets.push(dataSet); self.dataSets.push(dataSet);
// check if the resultset is for a query plan // check if the resultset is for a query plan

View File

@@ -352,6 +352,14 @@ jobsview-component .jobview-grid .slick-cell.error-row {
background: #444444 !important; background: #444444 !important;
} }
.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 {
border-left: 1px dotted #444444;
}
.vs-dark #operatorsDiv joboperatorsview-component .joboperatorsview-grid .grid-canvas .ui-widget-content.slick-row .slick-cell { .vs-dark #operatorsDiv joboperatorsview-component .joboperatorsview-grid .grid-canvas .ui-widget-content.slick-row .slick-cell {
background:#333333; background:#333333;
} }
@@ -375,6 +383,14 @@ jobsview-component .jobview-grid .slick-cell.error-row {
background: #444444 !important; background: #444444 !important;
} }
.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 {
border-left: 1px dotted #444444;
}
.vs-dark #proxiesDiv jobproxiesview-component .jobproxiesview-grid .grid-canvas .ui-widget-content.slick-row .slick-cell { .vs-dark #proxiesDiv jobproxiesview-component .jobproxiesview-grid .grid-canvas .ui-widget-content.slick-row .slick-cell {
background:#333333; background:#333333;
} }
@@ -397,3 +413,11 @@ jobsview-component .jobview-grid .slick-cell.error-row {
.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.hovered > .slick-cell {
background: #444444 !important; background: #444444 !important;
} }
.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 {
border-left: 1px dotted #444444;
}

View File

@@ -154,7 +154,7 @@
</td> </td>
</tr> </tr>
</table> </table>
<div #jobsteps> <div #jobsteps style="height: 100%">
<jobstepsview-component *ngIf="showSteps === true"></jobstepsview-component> <jobstepsview-component *ngIf="showSteps === true"></jobstepsview-component>
</div> </div>
<h3 *ngIf="showSteps === false">No Steps Available</h3> <h3 *ngIf="showSteps === false">No Steps Available</h3>

View File

@@ -174,10 +174,12 @@ export class JobHistoryComponent extends JobManagementView implements OnInit {
if (self.agentJobHistoryInfo) { if (self.agentJobHistoryInfo) {
self.agentJobHistoryInfo.runDate = self.formatTime(self.agentJobHistoryInfo.runDate); self.agentJobHistoryInfo.runDate = self.formatTime(self.agentJobHistoryInfo.runDate);
if (self.agentJobHistoryInfo.steps) { if (self.agentJobHistoryInfo.steps) {
let jobStepStatus = this.didJobFail(self.agentJobHistoryInfo);
self._stepRows = self.agentJobHistoryInfo.steps.map(step => { self._stepRows = self.agentJobHistoryInfo.steps.map(step => {
let stepViewRow = new JobStepsViewRow(); let stepViewRow = new JobStepsViewRow();
stepViewRow.message = step.message; stepViewRow.message = step.message;
stepViewRow.runStatus = JobManagementUtilities.convertToStatusString(step.runStatus); stepViewRow.runStatus = jobStepStatus ? JobManagementUtilities.convertToStatusString(0) :
JobManagementUtilities.convertToStatusString(step.runStatus);
self._runStatus = JobManagementUtilities.convertToStatusString(self.agentJobHistoryInfo.runStatus); self._runStatus = JobManagementUtilities.convertToStatusString(self.agentJobHistoryInfo.runStatus);
stepViewRow.stepName = step.stepName; stepViewRow.stepName = step.stepName;
stepViewRow.stepID = step.stepId.toString(); stepViewRow.stepID = step.stepId.toString();
@@ -191,6 +193,15 @@ export class JobHistoryComponent extends JobManagementView implements OnInit {
} }
} }
private didJobFail(job: sqlops.AgentJobHistoryInfo): boolean {
for (let i = 0; i < job.steps.length; i++) {
if (job.steps[i].runStatus === 0) {
return true;
}
}
return false;
}
private buildHistoryTree(self: any, jobHistories: sqlops.AgentJobHistoryInfo[]) { private buildHistoryTree(self: any, jobHistories: sqlops.AgentJobHistoryInfo[]) {
self._treeController.jobHistories = jobHistories; self._treeController.jobHistories = jobHistories;
self._jobCacheObject.setJobHistory(self._agentViewComponent.jobId, jobHistories); self._jobCacheObject.setJobHistory(self._agentViewComponent.jobId, jobHistories);

View File

@@ -104,7 +104,6 @@ export interface IListTemplate {
} }
export class JobHistoryRenderer implements tree.IRenderer { export class JobHistoryRenderer implements tree.IRenderer {
private _statusIcon: HTMLElement;
public getHeight(tree: tree.ITree, element: JobHistoryRow): number { public getHeight(tree: tree.ITree, element: JobHistoryRow): number {
return 30; return 30;
@@ -121,11 +120,10 @@ export class JobHistoryRenderer implements tree.IRenderer {
public renderTemplate(tree: tree.ITree, templateId: string, container: HTMLElement): IListTemplate { public renderTemplate(tree: tree.ITree, templateId: string, container: HTMLElement): IListTemplate {
let row = DOM.$('.list-row'); let row = DOM.$('.list-row');
let label = DOM.$('.label'); let label = DOM.$('.label');
this._statusIcon = this.createStatusIcon(); let statusIcon = this.createStatusIcon();
row.appendChild(this._statusIcon); row.appendChild(statusIcon);
row.appendChild(label); row.appendChild(label);
container.appendChild(row); container.appendChild(row);
let statusIcon = this._statusIcon;
return { statusIcon, label }; return { statusIcon, label };
} }
@@ -133,13 +131,13 @@ export class JobHistoryRenderer implements tree.IRenderer {
templateData.label.innerHTML = element.runDate + '&nbsp;&nbsp;' + element.runStatus; templateData.label.innerHTML = element.runDate + '&nbsp;&nbsp;' + element.runStatus;
let statusClass: string; let statusClass: string;
if (element.runStatus === 'Succeeded') { if (element.runStatus === 'Succeeded') {
statusClass = ' job-passed'; statusClass = 'status-icon job-passed';
} else if (element.runStatus === 'Failed') { } else if (element.runStatus === 'Failed') {
statusClass = ' job-failed'; statusClass = 'status-icon job-failed';
} else { } else {
statusClass = ' job-unknown'; statusClass = 'status-icon job-unknown';
} }
this._statusIcon.className += statusClass; templateData.statusIcon.className = statusClass;
} }
public disposeTemplate(tree: tree.ITree, templateId: string, templateData: IListTemplate): void { public disposeTemplate(tree: tree.ITree, templateId: string, templateData: IListTemplate): void {
@@ -148,7 +146,6 @@ export class JobHistoryRenderer implements tree.IRenderer {
private createStatusIcon(): HTMLElement { private createStatusIcon(): HTMLElement {
let statusIcon: HTMLElement = DOM.$('div'); let statusIcon: HTMLElement = DOM.$('div');
statusIcon.className += ' status-icon';
return statusIcon; return statusIcon;
} }
} }

View File

@@ -59,6 +59,7 @@ export abstract class JobManagementView extends TabChild implements AfterContent
this._showProgressWheel = true; this._showProgressWheel = true;
this.isRefreshing = true; this.isRefreshing = true;
this.onFirstVisible(); this.onFirstVisible();
this.layout();
this._parentComponent.refresh = false; this._parentComponent.refresh = false;
} else if (this.isVisible === true && this._visibilityElement.nativeElement.offsetParent === null) { } else if (this.isVisible === true && this._visibilityElement.nativeElement.offsetParent === null) {
this.isVisible = false; this.isVisible = false;

View File

@@ -88,15 +88,6 @@ export class JobStepsViewComponent extends JobManagementView implements OnInit,
} }
public layout() { public layout() {
let jobsViewToolbar = $('jobhistory-component .actionbar-container').get(0);
let statusBar = $('.part.statusbar').get(0);
if (jobsViewToolbar && statusBar) {
let toolbarBottom = jobsViewToolbar.getBoundingClientRect().bottom;
let statusTop = statusBar.getBoundingClientRect().top;
this._table.layout(new dom.Dimension(
dom.getContentWidth(this._tableContainer.nativeElement),
statusTop - toolbarBottom));
}
} }
} }

View File

@@ -35,6 +35,7 @@ import { TPromise } from 'vs/base/common/winjs.base';
import { IAction } from 'vs/base/common/actions'; import { IAction } from 'vs/base/common/actions';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IDashboardService } from 'sql/services/dashboard/common/dashboardService'; import { IDashboardService } from 'sql/services/dashboard/common/dashboardService';
import { escape } from 'sql/base/common/strings';
export const JOBSVIEW_SELECTOR: string = 'jobsview-component'; export const JOBSVIEW_SELECTOR: string = 'jobsview-component';
export const ROW_HEIGHT: number = 45; export const ROW_HEIGHT: number = 45;
@@ -485,7 +486,7 @@ export class JobsViewComponent extends JobManagementView implements OnInit {
return '<table class="jobview-jobnametable"><tr class="jobview-jobnamerow">' + return '<table class="jobview-jobnametable"><tr class="jobview-jobnamerow">' +
'<td nowrap class=' + resultIndicatorClass + '></td>' + '<td nowrap class=' + resultIndicatorClass + '></td>' +
'<td nowrap class="jobview-jobnametext">' + dataContext.name + '</td>' + '<td nowrap class="jobview-jobnametext">' + escape(dataContext.name) + '</td>' +
'</tr></table>'; '</tr></table>';
} }

View File

@@ -4,8 +4,8 @@
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import 'vs/css!./button'; import 'vs/css!./button';
import { import {
Component, Input, Inject, ChangeDetectorRef, forwardRef, ComponentFactoryResolver, Component, Input, Inject, ChangeDetectorRef, forwardRef,
ViewChild, ViewChildren, ElementRef, Injector, OnDestroy, QueryList, AfterViewInit ViewChild, ElementRef, OnDestroy, AfterViewInit
} from '@angular/core'; } from '@angular/core';
import * as sqlops from 'sqlops'; import * as sqlops from 'sqlops';
@@ -24,7 +24,13 @@ import { Color } from 'vs/base/common/color';
@Component({ @Component({
selector: 'modelview-button', selector: 'modelview-button',
template: ` template: `
<div #input style="width: 100%"></div> <div>
<label for={{this.label}}>
<div #input style="width: 100%">
<input *ngIf="this.isFile === true" id={{this.label}} type="file" style="display: none">
</div>
</label>
</div>
` `
}) })
export default class ButtonComponent extends ComponentWithIconBase implements IComponent, OnDestroy, AfterViewInit { export default class ButtonComponent extends ComponentWithIconBase implements IComponent, OnDestroy, AfterViewInit {
@@ -119,9 +125,19 @@ export default class ButtonComponent extends ComponentWithIconBase implements IC
this.setPropertyFromUI<sqlops.ButtonProperties, string>(this.setValueProperties, newValue); this.setPropertyFromUI<sqlops.ButtonProperties, string>(this.setValueProperties, newValue);
} }
private get isFile(): boolean {
return this.getPropertyOrDefault<sqlops.ButtonProperties, boolean>((props) => props.isFile, false);
}
private set isFile(newValue: boolean) {
this.setPropertyFromUI<sqlops.ButtonProperties, boolean>(this.setFileProperties, newValue);
}
private setValueProperties(properties: sqlops.ButtonProperties, label: string): void { private setValueProperties(properties: sqlops.ButtonProperties, label: string): void {
properties.label = label; properties.label = label;
} }
private setFileProperties(properties: sqlops.ButtonProperties, isFile: boolean): void {
properties.isFile = isFile;
}
} }

View File

@@ -25,7 +25,7 @@ import { IContextViewService } from 'vs/platform/contextview/browser/contextView
template: ` template: `
<div [style.width]="getWidth()"> <div [style.width]="getWidth()">
<div [style.display]="getEditableDisplay()" #editableDropDown style="width: 100%;"></div> <div [style.display]="getEditableDisplay()" #editableDropDown style="width: 100%;"></div>
<div [style.display]="getNotEditableDisplay()" #dropDown style="width: 100%;"></div> <div [style.display]="getNotEditableDisplay()" #dropDown style="width: 100%;"></div>
</div> </div>
` `
@@ -79,7 +79,6 @@ export default class DropDownComponent extends ComponentBase implements ICompone
this._selectBox = new SelectBox(this.getValues(), this.getSelectedValue(), this.contextViewService, this._dropDownContainer.nativeElement); this._selectBox = new SelectBox(this.getValues(), this.getSelectedValue(), this.contextViewService, this._dropDownContainer.nativeElement);
this._selectBox.render(this._dropDownContainer.nativeElement); this._selectBox.render(this._dropDownContainer.nativeElement);
this._register(this._selectBox); this._register(this._selectBox);
this._register(attachSelectBoxStyler(this._selectBox, this.themeService)); this._register(attachSelectBoxStyler(this._selectBox, this.themeService));
this._register(this._selectBox.onDidSelect(e => { this._register(this._selectBox.onDidSelect(e => {
if (!this.editable) { if (!this.editable) {

View File

@@ -4,8 +4,8 @@
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import { import {
Component, Input, Inject, ChangeDetectorRef, forwardRef, ComponentFactoryResolver, Component, Input, Inject, ChangeDetectorRef, forwardRef,
ViewChild, ViewChildren, ElementRef, Injector, OnDestroy, QueryList, AfterViewInit ViewChild, ElementRef, OnDestroy, AfterViewInit
} from '@angular/core'; } from '@angular/core';
import * as sqlops from 'sqlops'; import * as sqlops from 'sqlops';

View File

@@ -16,7 +16,6 @@ import { Table } from 'sql/base/browser/ui/table/table';
import { TableDataView } from 'sql/base/browser/ui/table/tableDataView'; import { TableDataView } from 'sql/base/browser/ui/table/tableDataView';
import { attachTableStyler } from 'sql/common/theme/styler'; import { attachTableStyler } from 'sql/common/theme/styler';
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { getContentHeight, getContentWidth, Dimension } from 'vs/base/browser/dom'; import { getContentHeight, getContentWidth, Dimension } from 'vs/base/browser/dom';
import { RowSelectionModel } from 'sql/base/browser/ui/table/plugins/rowSelectionModel.plugin'; import { RowSelectionModel } from 'sql/base/browser/ui/table/plugins/rowSelectionModel.plugin';

View File

@@ -79,7 +79,7 @@ export class ServerGroupDialog extends Modal {
// Connection Group Name // Connection Group Name
let serverGroupNameLabel = localize('connectionGroupName', 'Server group name'); let serverGroupNameLabel = localize('connectionGroupName', 'Server group name');
this._bodyBuilder.div({ class: 'dialog-label' }, (labelContainer) => { this._bodyBuilder.div({ class: 'dialog-label' }, (labelContainer) => {
labelContainer.innerHtml(serverGroupNameLabel); labelContainer.text(serverGroupNameLabel);
}); });
this._bodyBuilder.div({ class: 'input-divider' }, (inputCellContainer) => { this._bodyBuilder.div({ class: 'input-divider' }, (inputCellContainer) => {
let errorMessage = localize('MissingGroupNameError', 'Group name is required.'); let errorMessage = localize('MissingGroupNameError', 'Group name is required.');
@@ -94,7 +94,7 @@ export class ServerGroupDialog extends Modal {
// Connection Group Description // Connection Group Description
let groupDescriptionLabel = localize('groupDescription', 'Group description'); let groupDescriptionLabel = localize('groupDescription', 'Group description');
this._bodyBuilder.div({ class: 'dialog-label' }, (labelContainer) => { this._bodyBuilder.div({ class: 'dialog-label' }, (labelContainer) => {
labelContainer.innerHtml(groupDescriptionLabel); labelContainer.text(groupDescriptionLabel);
}); });
this._bodyBuilder.div({ class: 'input-divider' }, (inputCellContainer) => { this._bodyBuilder.div({ class: 'input-divider' }, (inputCellContainer) => {
this._groupDescriptionInputBox = new InputBox(inputCellContainer.getHTMLElement(), this._contextViewService, { this._groupDescriptionInputBox = new InputBox(inputCellContainer.getHTMLElement(), this._contextViewService, {
@@ -105,7 +105,7 @@ export class ServerGroupDialog extends Modal {
// Connection Group Color // Connection Group Color
this._bodyBuilder.div({ class: 'dialog-label' }, (labelContainer) => { this._bodyBuilder.div({ class: 'dialog-label' }, (labelContainer) => {
let groupColorLabel = localize('groupColor', 'Group color'); let groupColorLabel = localize('groupColor', 'Group color');
labelContainer.innerHtml(groupColorLabel); labelContainer.text(groupColorLabel);
}); });
this._bodyBuilder.div({ class: 'group-color-options' }, (groupColorContainer) => { this._bodyBuilder.div({ class: 'group-color-options' }, (groupColorContainer) => {

View File

@@ -77,7 +77,6 @@ export class ProfilerStart extends Action {
} }
public run(input: ProfilerInput): TPromise<boolean> { public run(input: ProfilerInput): TPromise<boolean> {
this.enabled = false;
input.data.clear(); input.data.clear();
return TPromise.wrap(this._profilerService.startSession(input.id)); return TPromise.wrap(this._profilerService.startSession(input.id));
} }
@@ -127,7 +126,6 @@ export class ProfilerStop extends Action {
} }
public run(input: ProfilerInput): TPromise<boolean> { public run(input: ProfilerInput): TPromise<boolean> {
this.enabled = false;
return TPromise.wrap(this._profilerService.stopSession(input.id)); return TPromise.wrap(this._profilerService.stopSession(input.id));
} }
} }
@@ -137,7 +135,7 @@ export class ProfilerClear extends Action {
public static LABEL = nls.localize('profiler.clear', "Clear Data"); public static LABEL = nls.localize('profiler.clear', "Clear Data");
constructor(id: string, label: string) { constructor(id: string, label: string) {
super(id, label, 'stop'); super(id, label);
} }
run(input: ProfilerInput): TPromise<void> { run(input: ProfilerInput): TPromise<void> {
@@ -148,14 +146,15 @@ export class ProfilerClear extends Action {
export class ProfilerAutoScroll extends Action { export class ProfilerAutoScroll extends Action {
public static ID = 'profiler.autoscroll'; public static ID = 'profiler.autoscroll';
public static LABEL = nls.localize('profiler.toggleAutoscroll', "Toggle Auto Scroll"); public static LABEL = nls.localize('profiler.autoscrollOn', "Auto Scroll: On");
constructor(id: string, label: string) { constructor(id: string, label: string) {
super(id, label, 'stop'); super(id, label);
} }
run(input: ProfilerInput): TPromise<boolean> { run(input: ProfilerInput): TPromise<boolean> {
this.checked = !this.checked; this.checked = !this.checked;
this._setLabel(this.checked ? nls.localize('profilerAction.autoscrollOn', "Auto Scroll: On") : nls.localize('profilerAction.autoscrollOff', "Auto Scroll: Off"));
input.state.change({ autoscroll: this.checked }); input.state.change({ autoscroll: this.checked });
return TPromise.as(true); return TPromise.as(true);
} }

View File

@@ -166,6 +166,7 @@ export class ProfilerTableEditor extends BaseEditor implements IProfilerControll
public layout(dimension: Dimension): void { public layout(dimension: Dimension): void {
this._currentDimensions = dimension; this._currentDimensions = dimension;
this._profilerTable.layout(dimension); this._profilerTable.layout(dimension);
this._profilerTable.autosizeColumns();
this._onDidChangeConfiguration.fire({ layoutInfo: true }); this._onDidChangeConfiguration.fire({ layoutInfo: true });
} }

View File

@@ -50,6 +50,7 @@ class BasicView extends View {
private _previousSize: number; private _previousSize: number;
private _collapsed: boolean; private _collapsed: boolean;
public headerSize: number; public headerSize: number;
constructor( constructor(
initialSize: number, initialSize: number,
private _element: HTMLElement, private _element: HTMLElement,
@@ -58,6 +59,7 @@ class BasicView extends View {
opts: IViewOptions opts: IViewOptions
) { ) {
super(initialSize, opts); super(initialSize, opts);
this._previousSize = initialSize;
} }
render(container: HTMLElement, orientation: Orientation): void { render(container: HTMLElement, orientation: Orientation): void {
@@ -116,6 +118,7 @@ export class ProfilerEditor extends BaseEditor {
private _viewTemplateSelector: SelectBox; private _viewTemplateSelector: SelectBox;
private _viewTemplates: Array<IProfilerViewTemplate>; private _viewTemplates: Array<IProfilerViewTemplate>;
private _connectionInfoText: HTMLElement;
// Actions // Actions
private _connectAction: Actions.ProfilerConnect; private _connectAction: Actions.ProfilerConnect;
@@ -199,8 +202,16 @@ export class ProfilerEditor extends BaseEditor {
})); }));
let dropdownContainer = document.createElement('div'); let dropdownContainer = document.createElement('div');
dropdownContainer.style.width = '150px'; dropdownContainer.style.width = '150px';
dropdownContainer.style.paddingRight = '5px';
this._viewTemplateSelector.render(dropdownContainer); this._viewTemplateSelector.render(dropdownContainer);
this._connectionInfoText = document.createElement('div');
this._connectionInfoText.style.paddingRight = '5px';
this._connectionInfoText.innerText = '';
this._connectionInfoText.style.textAlign = 'center';
this._connectionInfoText.style.display = 'flex';
this._connectionInfoText.style.alignItems = 'center';
this._register(attachSelectBoxStyler(this._viewTemplateSelector, this.themeService)); this._register(attachSelectBoxStyler(this._viewTemplateSelector, this.themeService));
this._actionBar.setContent([ this._actionBar.setContent([
@@ -211,6 +222,8 @@ export class ProfilerEditor extends BaseEditor {
{ action: this._autoscrollAction }, { action: this._autoscrollAction },
{ action: this._instantiationService.createInstance(Actions.ProfilerClear, Actions.ProfilerClear.ID, Actions.ProfilerClear.LABEL) }, { action: this._instantiationService.createInstance(Actions.ProfilerClear, Actions.ProfilerClear.ID, Actions.ProfilerClear.LABEL) },
{ element: dropdownContainer }, { element: dropdownContainer },
{ element: Taskbar.createTaskbarSeparator() },
{ element: this._connectionInfoText }
]); ]);
} }
@@ -361,6 +374,7 @@ export class ProfilerEditor extends BaseEditor {
autoscroll: true, autoscroll: true,
isPanelCollapsed: true isPanelCollapsed: true
}); });
this._connectionInfoText.innerText = input.connectionName;
this._profilerTableEditor.updateState(); this._profilerTableEditor.updateState();
this._splitView.layout(); this._splitView.layout();
this._profilerTableEditor.focus(); this._profilerTableEditor.focus();

View File

@@ -9,6 +9,7 @@ import { ProfilerState } from './profilerState';
import { IConnectionProfile } from 'sql/parts/connection/common/interfaces'; import { IConnectionProfile } from 'sql/parts/connection/common/interfaces';
import * as sqlops from 'sqlops'; import * as sqlops from 'sqlops';
import * as nls from 'vs/nls';
import { TPromise } from 'vs/base/common/winjs.base'; import { TPromise } from 'vs/base/common/winjs.base';
import { EditorInput } from 'vs/workbench/common/editor'; import { EditorInput } from 'vs/workbench/common/editor';
@@ -17,8 +18,8 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
import { INotificationService } from 'vs/platform/notification/common/notification'; import { INotificationService } from 'vs/platform/notification/common/notification';
import { Event, Emitter } from 'vs/base/common/event'; import { Event, Emitter } from 'vs/base/common/event';
import { generateUuid } from 'vs/base/common/uuid'; import { generateUuid } from 'vs/base/common/uuid';
import { IDialogService, IConfirmation, IConfirmationResult } from 'vs/platform/dialogs/common/dialogs';
import * as nls from 'vs/nls'; import { escape } from 'sql/base/common/strings';
export class ProfilerInput extends EditorInput implements IProfilerSession { export class ProfilerInput extends EditorInput implements IProfilerSession {
@@ -40,7 +41,8 @@ export class ProfilerInput extends EditorInput implements IProfilerSession {
private _connection: IConnectionProfile, private _connection: IConnectionProfile,
@IInstantiationService private _instantiationService: IInstantiationService, @IInstantiationService private _instantiationService: IInstantiationService,
@IProfilerService private _profilerService: IProfilerService, @IProfilerService private _profilerService: IProfilerService,
@INotificationService private _notificationService: INotificationService @INotificationService private _notificationService: INotificationService,
@IDialogService private _dialogService: IDialogService
) { ) {
super(); super();
this._state = new ProfilerState(); this._state = new ProfilerState();
@@ -64,6 +66,23 @@ export class ProfilerInput extends EditorInput implements IProfilerSession {
return ret; return ret;
}; };
this._data = new TableDataView<Slick.SlickData>(undefined, searchFn); this._data = new TableDataView<Slick.SlickData>(undefined, searchFn);
this.onDispose(() => {
if (this._state.isRunning || this.state.isPaused) {
let confirm: IConfirmation = {
message: nls.localize('confirmStopProfilerSession', "Would you like to stop the running XEvent session?"),
primaryButton: nls.localize('profilerClosingActions.yes', 'Yes'),
secondaryButton: nls.localize('profilerClosingActions.no', 'No'),
type: 'question'
};
this._dialogService.confirm(confirm).then(result => {
if (result.confirmed) {
this._profilerService.stopSession(this.id);
}
});
}
});
} }
public set viewTemplate(template: IProfilerViewTemplate) { public set viewTemplate(template: IProfilerViewTemplate) {
@@ -130,6 +149,19 @@ export class ProfilerInput extends EditorInput implements IProfilerSession {
this._onColumnsChanged.fire(this.columns); this._onColumnsChanged.fire(this.columns);
} }
public get connectionName(): string {
if (this._connection !== null) {
if (this._connection.databaseName) {
return `${ this._connection.serverName } ${ this._connection.databaseName }`;
} else {
return `${ this._connection.serverName }`;
}
}
else {
return nls.localize('profilerInput.notConnected', "Not connected");
}
}
public get id(): ProfilerSessionID { public get id(): ProfilerSessionID {
return this._id; return this._id;
} }
@@ -171,7 +203,7 @@ export class ProfilerInput extends EditorInput implements IProfilerSession {
let columnName = this._columnMapping[key]; let columnName = this._columnMapping[key];
if (columnName) { if (columnName) {
let value = e.values[key]; let value = e.values[key];
data[columnName] = value; data[columnName] = escape(value);
} }
} }
this._data.push(data); this._data.push(data);

View File

@@ -98,7 +98,6 @@ export interface IProfilerSettings {
export interface IColumnViewTemplate { export interface IColumnViewTemplate {
name: string; name: string;
width: string;
eventsMapped: Array<string>; eventsMapped: Array<string>;
} }

View File

@@ -17,6 +17,7 @@ import * as sqlops from 'sqlops';
import { TPromise } from 'vs/base/common/winjs.base'; import { TPromise } from 'vs/base/common/winjs.base';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { INotificationService } from 'vs/platform/notification/common/notification';
class TwoWayMap<T, K> { class TwoWayMap<T, K> {
private forwardMap: Map<T, K>; private forwardMap: Map<T, K>;
@@ -52,7 +53,8 @@ export class ProfilerService implements IProfilerService {
constructor( constructor(
@IConnectionManagementService private _connectionService: IConnectionManagementService, @IConnectionManagementService private _connectionService: IConnectionManagementService,
@IConfigurationService public _configurationService: IConfigurationService, @IConfigurationService public _configurationService: IConfigurationService,
@IInstantiationService private _instantiationService: IInstantiationService @IInstantiationService private _instantiationService: IInstantiationService,
@INotificationService private _notificationService: INotificationService
) { } ) { }
public registerProvider(providerId: string, provider: sqlops.ProfilerProvider): void { public registerProvider(providerId: string, provider: sqlops.ProfilerProvider): void {
@@ -99,6 +101,8 @@ export class ProfilerService implements IProfilerService {
return this._runAction(id, provider => provider.startSession(this._idMap.get(id))).then(() => { return this._runAction(id, provider => provider.startSession(this._idMap.get(id))).then(() => {
this._sessionMap.get(this._idMap.reverseGet(id)).onSessionStateChanged({ isRunning: true, isStopped: false, isPaused: false }); this._sessionMap.get(this._idMap.reverseGet(id)).onSessionStateChanged({ isRunning: true, isStopped: false, isPaused: false });
return true; return true;
}, (reason) => {
this._notificationService.error(reason.message);
}); });
} }
@@ -110,6 +114,8 @@ export class ProfilerService implements IProfilerService {
return this._runAction(id, provider => provider.stopSession(this._idMap.get(id))).then(() => { return this._runAction(id, provider => provider.stopSession(this._idMap.get(id))).then(() => {
this._sessionMap.get(this._idMap.reverseGet(id)).onSessionStateChanged({ isStopped: true, isPaused: false, isRunning: false }); this._sessionMap.get(this._idMap.reverseGet(id)).onSessionStateChanged({ isStopped: true, isPaused: false, isRunning: false });
return true; return true;
}, (reason) => {
this._notificationService.error(reason.message);
}); });
} }

View File

@@ -10,7 +10,7 @@ import * as DOM from 'vs/base/browser/dom';
import { EditorInput, EditorOptions } from 'vs/workbench/common/editor'; import { EditorInput, EditorOptions } from 'vs/workbench/common/editor';
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
import { IEditorControl, Position, IEditor } from 'vs/platform/editor/common/editor'; import { IEditorControl, Position, IEditor, IEditorInput } from 'vs/platform/editor/common/editor';
import { VerticalFlexibleSash, HorizontalFlexibleSash, IFlexibleSash } from 'sql/parts/query/views/flexibleSash'; import { VerticalFlexibleSash, HorizontalFlexibleSash, IFlexibleSash } from 'sql/parts/query/views/flexibleSash';
import { Orientation } from 'vs/base/browser/ui/sash/sash'; import { Orientation } from 'vs/base/browser/ui/sash/sash';
@@ -31,6 +31,7 @@ import { IEditorGroupService } from 'vs/workbench/services/group/common/groupSer
import { CodeEditor } from 'vs/editor/browser/codeEditor'; import { CodeEditor } from 'vs/editor/browser/codeEditor';
import { IDisposable } from 'vs/base/common/lifecycle'; import { IDisposable } from 'vs/base/common/lifecycle';
import { IRange } from 'vs/editor/common/core/range'; import { IRange } from 'vs/editor/common/core/range';
import { IEditorViewState } from 'vs/editor/common/editorCommon';
import { QueryResultsInput } from 'sql/parts/query/common/queryResultsInput'; import { QueryResultsInput } from 'sql/parts/query/common/queryResultsInput';
import { QueryInput } from 'sql/parts/query/common/queryInput'; import { QueryInput } from 'sql/parts/query/common/queryInput';
@@ -87,6 +88,8 @@ export class QueryEditor extends BaseEditor {
private _estimatedQueryPlanAction: EstimatedQueryPlanAction; private _estimatedQueryPlanAction: EstimatedQueryPlanAction;
private _actualQueryPlanAction: ActualQueryPlanAction; private _actualQueryPlanAction: ActualQueryPlanAction;
private _savedViewStates = new Map<IEditorInput, IEditorViewState>();
constructor( constructor(
@ITelemetryService _telemetryService: ITelemetryService, @ITelemetryService _telemetryService: ITelemetryService,
@IThemeService themeService: IThemeService, @IThemeService themeService: IThemeService,
@@ -481,6 +484,8 @@ export class QueryEditor extends BaseEditor {
private _updateInput(oldInput: QueryInput, newInput: QueryInput, options?: EditorOptions): TPromise<void> { private _updateInput(oldInput: QueryInput, newInput: QueryInput, options?: EditorOptions): TPromise<void> {
if (this._sqlEditor) { if (this._sqlEditor) {
let sqlEditorViewState = this._sqlEditor.getControl().saveViewState();
this._savedViewStates.set(this._sqlEditor.input, sqlEditorViewState);
this._sqlEditor.clearInput(); this._sqlEditor.clearInput();
} }
@@ -557,7 +562,12 @@ export class QueryEditor extends BaseEditor {
// Run all three steps synchronously // Run all three steps synchronously
return createEditors() return createEditors()
.then(onEditorsCreated) .then(onEditorsCreated)
.then(doLayout); .then(doLayout)
.then(() => {
if (this._savedViewStates.has(newInput.sql)) {
this._sqlEditor.getControl().restoreViewState(this._savedViewStates.get(newInput.sql));
}
});
} }
/** /**

View File

@@ -7,13 +7,13 @@ import { StopWatch } from 'vs/base/common/stopwatch';
import { generateUuid } from 'vs/base/common/uuid'; import { generateUuid } from 'vs/base/common/uuid';
export enum TaskStatus { export enum TaskStatus {
notStarted = 0, NotStarted = 0,
inProgress = 1, InProgress = 1,
succeeded = 2, Succeeded = 2,
succeededWithWarning = 3, SucceededWithWarning = 3,
failed = 4, Failed = 4,
canceled = 5, Canceled = 5,
canceling = 6 Canceling = 6
} }
export enum TaskExecutionMode { export enum TaskExecutionMode {
@@ -107,7 +107,7 @@ export class TaskNode {
this.databaseName = databaseName; this.databaseName = databaseName;
this.timer = StopWatch.create(); this.timer = StopWatch.create();
this.startTime = new Date().toLocaleTimeString(); this.startTime = new Date().toLocaleTimeString();
this.status = TaskStatus.inProgress; this.status = TaskStatus.InProgress;
this.hasChildren = false; this.hasChildren = false;
this.taskExecutionMode = taskExecutionMode; this.taskExecutionMode = taskExecutionMode;
this.isCancelable = isCancelable; this.isCancelable = isCancelable;

View File

@@ -14,6 +14,7 @@ import { localize } from 'vs/nls';
import Severity from 'vs/base/common/severity'; import Severity from 'vs/base/common/severity';
import { TPromise } from 'vs/base/common/winjs.base'; import { TPromise } from 'vs/base/common/winjs.base';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { IConnectionManagementService } from 'sql/parts/connection/common/connectionManagement';
export const SERVICE_ID = 'taskHistoryService'; export const SERVICE_ID = 'taskHistoryService';
export const ITaskService = createDecorator<ITaskService>(SERVICE_ID); export const ITaskService = createDecorator<ITaskService>(SERVICE_ID);
@@ -27,6 +28,8 @@ export interface ITaskService {
getAllTasks(): TaskNode; getAllTasks(): TaskNode;
getNumberOfInProgressTasks(): number; getNumberOfInProgressTasks(): number;
onNewTaskCreated(handle: number, taskInfo: sqlops.TaskInfo); onNewTaskCreated(handle: number, taskInfo: sqlops.TaskInfo);
createNewTask(taskInfo: sqlops.TaskInfo);
updateTask(taskProgressInfo: sqlops.TaskProgressInfo);
onTaskStatusChanged(handle: number, taskProgressInfo: sqlops.TaskProgressInfo); onTaskStatusChanged(handle: number, taskProgressInfo: sqlops.TaskProgressInfo);
cancelTask(providerId: string, taskId: string): Thenable<boolean>; cancelTask(providerId: string, taskId: string): Thenable<boolean>;
/** /**
@@ -52,7 +55,8 @@ export class TaskService implements ITaskService {
constructor( constructor(
@ILifecycleService lifecycleService: ILifecycleService, @ILifecycleService lifecycleService: ILifecycleService,
@IDialogService private dialogService: IDialogService, @IDialogService private dialogService: IDialogService,
@IQueryEditorService private queryEditorService: IQueryEditorService @IQueryEditorService private queryEditorService: IQueryEditorService,
@IConnectionManagementService private connectionManagementService: IConnectionManagementService
) { ) {
this._taskQueue = new TaskNode('Root', undefined, undefined); this._taskQueue = new TaskNode('Root', undefined, undefined);
this._onTaskComplete = new Emitter<TaskNode>(); this._onTaskComplete = new Emitter<TaskNode>();
@@ -70,12 +74,27 @@ export class TaskService implements ITaskService {
} }
public onNewTaskCreated(handle: number, taskInfo: sqlops.TaskInfo) { public onNewTaskCreated(handle: number, taskInfo: sqlops.TaskInfo) {
let node: TaskNode = new TaskNode(taskInfo.name, taskInfo.serverName, taskInfo.databaseName, taskInfo.taskId, taskInfo.taskExecutionMode, taskInfo.isCancelable); this.createNewTask(taskInfo);
}
public createNewTask(taskInfo: sqlops.TaskInfo) {
let databaseName: string = taskInfo.databaseName;
let serverName: string = taskInfo.serverName;
if (taskInfo && taskInfo.connection) {
let connectionProfile = this.connectionManagementService.getConnectionProfile(taskInfo.connection.connectionId);
if (connectionProfile && !!databaseName) {
databaseName = connectionProfile.databaseName;
}
if (connectionProfile && !!serverName) {
serverName = connectionProfile.serverName;
}
}
let node: TaskNode = new TaskNode(taskInfo.name, serverName, databaseName, taskInfo.taskId, taskInfo.taskExecutionMode, taskInfo.isCancelable);
node.providerName = taskInfo.providerName; node.providerName = taskInfo.providerName;
this.handleNewTask(node); this.handleNewTask(node);
} }
public onTaskStatusChanged(handle: number, taskProgressInfo: sqlops.TaskProgressInfo) { public updateTask(taskProgressInfo: sqlops.TaskProgressInfo) {
this.handleTaskComplete({ this.handleTaskComplete({
taskId: taskProgressInfo.taskId, taskId: taskProgressInfo.taskId,
status: taskProgressInfo.status, status: taskProgressInfo.status,
@@ -84,15 +103,23 @@ export class TaskService implements ITaskService {
}); });
} }
public onTaskStatusChanged(handle: number, taskProgressInfo: sqlops.TaskProgressInfo) {
this.updateTask(taskProgressInfo);
}
public cancelTask(providerId: string, taskId: string): Thenable<boolean> { public cancelTask(providerId: string, taskId: string): Thenable<boolean> {
let task = this.getTaskInQueue(taskId); let task = this.getTaskInQueue(taskId);
task.status = TaskStatus.canceling; task.status = TaskStatus.Canceling;
this._onTaskComplete.fire(task); this._onTaskComplete.fire(task);
let provider = this._providers[providerId]; if (providerId) {
if (provider) { let provider = this._providers[providerId];
return provider.cancelTask({ if (provider && provider.cancelTask) {
taskId: taskId return provider.cancelTask({
}); taskId: taskId
});
}
} else {
return Promise.resolve(true);
} }
return Promise.resolve(undefined); return Promise.resolve(undefined);
} }
@@ -100,7 +127,7 @@ export class TaskService implements ITaskService {
private cancelAllTasks(): Thenable<void> { private cancelAllTasks(): Thenable<void> {
return new TPromise<void>((resolve, reject) => { return new TPromise<void>((resolve, reject) => {
let promises = this._taskQueue.children.map(task => { let promises = this._taskQueue.children.map(task => {
if (task.status === TaskStatus.inProgress || task.status === TaskStatus.notStarted) { if (task.status === TaskStatus.InProgress || task.status === TaskStatus.NotStarted) {
return this.cancelTask(task.providerName, task.id); return this.cancelTask(task.providerName, task.id);
} }
return Promise.resolve(true); return Promise.resolve(true);
@@ -173,10 +200,10 @@ export class TaskService implements ITaskService {
task.message = eventArgs.message; task.message = eventArgs.message;
} }
switch (task.status) { switch (task.status) {
case TaskStatus.canceled: case TaskStatus.Canceled:
case TaskStatus.succeeded: case TaskStatus.Succeeded:
case TaskStatus.succeededWithWarning: case TaskStatus.SucceededWithWarning:
case TaskStatus.failed: case TaskStatus.Failed:
task.endTime = new Date().toLocaleTimeString(); task.endTime = new Date().toLocaleTimeString();
task.timer.stop(); task.timer.stop();
this._onTaskComplete.fire(task); this._onTaskComplete.fire(task);
@@ -185,7 +212,7 @@ export class TaskService implements ITaskService {
break; break;
} }
if ((task.status === TaskStatus.succeeded || task.status === TaskStatus.succeededWithWarning) if ((task.status === TaskStatus.Succeeded || task.status === TaskStatus.SucceededWithWarning)
&& eventArgs.script && eventArgs.script !== '') { && eventArgs.script && eventArgs.script !== '') {
if (task.taskExecutionMode === TaskExecutionMode.script) { if (task.taskExecutionMode === TaskExecutionMode.script) {
this.queryEditorService.newSqlEditor(eventArgs.script); this.queryEditorService.newSqlEditor(eventArgs.script);
@@ -214,7 +241,7 @@ export class TaskService implements ITaskService {
public getNumberOfInProgressTasks(): number { public getNumberOfInProgressTasks(): number {
if (this._taskQueue.hasChildren) { if (this._taskQueue.hasChildren) {
var inProgressTasks = this._taskQueue.children.filter(x => x.status === TaskStatus.inProgress); var inProgressTasks = this._taskQueue.children.filter(x => x.status === TaskStatus.InProgress);
return inProgressTasks ? inProgressTasks.length : 0; return inProgressTasks ? inProgressTasks.length : 0;
} }
return 0; return 0;

View File

@@ -52,12 +52,12 @@ export class TaskHistoryActionProvider extends ContributableActionProvider {
var actions = []; var actions = [];
// get actions for tasks in progress // get actions for tasks in progress
if (element.status === TaskStatus.inProgress && element.isCancelable) { if (element.status === TaskStatus.InProgress && element.isCancelable) {
actions.push(this._instantiationService.createInstance(CancelAction, CancelAction.ID, CancelAction.LABEL)); actions.push(this._instantiationService.createInstance(CancelAction, CancelAction.ID, CancelAction.LABEL));
} }
// get actions for tasks succeeded // get actions for tasks succeeded
if (element.status === TaskStatus.succeeded || element.status === TaskStatus.succeededWithWarning) { if (element.status === TaskStatus.Succeeded || element.status === TaskStatus.SucceededWithWarning) {
if (element.taskExecutionMode === TaskExecutionMode.executeAndScript) { if (element.taskExecutionMode === TaskExecutionMode.executeAndScript) {
actions.push(this._instantiationService.createInstance(ScriptAction, ScriptAction.ID, ScriptAction.LABEL)); actions.push(this._instantiationService.createInstance(ScriptAction, ScriptAction.ID, ScriptAction.LABEL));
} }

View File

@@ -67,27 +67,27 @@ export class TaskHistoryRenderer implements IRenderer {
if (taskNode) { if (taskNode) {
templateData.icon.className = TaskHistoryRenderer.ICON_CLASS; templateData.icon.className = TaskHistoryRenderer.ICON_CLASS;
switch (taskNode.status) { switch (taskNode.status) {
case TaskStatus.succeeded: case TaskStatus.Succeeded:
templateData.icon.classList.add(TaskHistoryRenderer.SUCCESS_CLASS); templateData.icon.classList.add(TaskHistoryRenderer.SUCCESS_CLASS);
taskStatus = localize('succeeded', "succeeded"); taskStatus = localize('succeeded', "succeeded");
break; break;
case TaskStatus.failed: case TaskStatus.Failed:
templateData.icon.classList.add(TaskHistoryRenderer.FAIL_CLASS); templateData.icon.classList.add(TaskHistoryRenderer.FAIL_CLASS);
taskStatus = localize('failed', "failed"); taskStatus = localize('failed', "failed");
break; break;
case TaskStatus.inProgress: case TaskStatus.InProgress:
templateData.icon.classList.add(TaskHistoryRenderer.INPROGRESS_CLASS); templateData.icon.classList.add(TaskHistoryRenderer.INPROGRESS_CLASS);
taskStatus = localize('inProgress', "in progress"); taskStatus = localize('inProgress', "in progress");
break; break;
case TaskStatus.notStarted: case TaskStatus.NotStarted:
templateData.icon.classList.add(TaskHistoryRenderer.NOTSTARTED_CLASS); templateData.icon.classList.add(TaskHistoryRenderer.NOTSTARTED_CLASS);
taskStatus = localize('notStarted', "not started"); taskStatus = localize('notStarted', "not started");
break; break;
case TaskStatus.canceled: case TaskStatus.Canceled:
templateData.icon.classList.add(TaskHistoryRenderer.CANCELED_CLASS); templateData.icon.classList.add(TaskHistoryRenderer.CANCELED_CLASS);
taskStatus = localize('canceled', "canceled"); taskStatus = localize('canceled', "canceled");
break; break;
case TaskStatus.canceling: case TaskStatus.Canceling:
templateData.icon.classList.add(TaskHistoryRenderer.INPROGRESS_CLASS); templateData.icon.classList.add(TaskHistoryRenderer.INPROGRESS_CLASS);
taskStatus = localize('canceling', "canceling"); taskStatus = localize('canceling', "canceling");
break; break;
@@ -117,7 +117,7 @@ export class TaskHistoryRenderer implements IRenderer {
public timer(taskNode: TaskNode, templateData: ITaskHistoryTemplateData) { public timer(taskNode: TaskNode, templateData: ITaskHistoryTemplateData) {
let timeLabel = ''; let timeLabel = '';
if (taskNode.status === TaskStatus.failed) { if (taskNode.status === TaskStatus.Failed) {
timeLabel += taskNode.startTime + ' Error: ' + taskNode.message; timeLabel += taskNode.startTime + ' Error: ' + taskNode.message;
} else { } else {
if (taskNode.startTime) { if (taskNode.startTime) {

View File

@@ -141,9 +141,9 @@ export class TaskHistoryView {
let isMouseOrigin = event.payload && (event.payload.origin === 'mouse'); let isMouseOrigin = event.payload && (event.payload.origin === 'mouse');
let isDoubleClick = isMouseOrigin && event.payload.originalEvent && event.payload.originalEvent.detail === 2; let isDoubleClick = isMouseOrigin && event.payload.originalEvent && event.payload.originalEvent.detail === 2;
if (isDoubleClick) { if (isDoubleClick) {
if (task.status === TaskStatus.failed) { if (task.status === TaskStatus.Failed) {
var err = task.taskName + ': ' + task.message; var err = task.taskName + ': ' + task.message;
this._errorMessageService.showDialog(Severity.Error, nls.localize('taskError','Task error'), err); this._errorMessageService.showDialog(Severity.Error, nls.localize('taskError', 'Task error'), err);
} }
} }
} }

View File

@@ -105,7 +105,7 @@ export class DialogModal extends Modal {
private updateButtonElement(buttonElement: Button, dialogButton: DialogButton, requireDialogValid: boolean = false) { private updateButtonElement(buttonElement: Button, dialogButton: DialogButton, requireDialogValid: boolean = false) {
buttonElement.label = dialogButton.label; buttonElement.label = dialogButton.label;
buttonElement.enabled = requireDialogValid ? dialogButton.enabled && this._dialog.valid : dialogButton.enabled; buttonElement.enabled = requireDialogValid ? dialogButton.enabled && this._dialog.valid : dialogButton.enabled;
dialogButton.hidden ? buttonElement.element.classList.add('dialogModal-hidden') : buttonElement.element.classList.remove('dialogModal-hidden'); dialogButton.hidden ? buttonElement.element.parentElement.classList.add('dialogModal-hidden') : buttonElement.element.parentElement.classList.remove('dialogModal-hidden');
} }
protected renderBody(container: HTMLElement): void { protected renderBody(container: HTMLElement): void {
@@ -125,11 +125,19 @@ export class DialogModal extends Modal {
public async done(): Promise<void> { public async done(): Promise<void> {
if (this._doneButton.enabled) { if (this._doneButton.enabled) {
let buttonSpinnerHandler = setTimeout(() => {
this._doneButton.enabled = false;
this._doneButton.element.innerHTML = '&nbsp';
this._doneButton.element.classList.add('validating');
}, 100);
if (await this._dialog.validateClose()) { if (await this._dialog.validateClose()) {
this._onDone.fire(); this._onDone.fire();
this.dispose(); this.dispose();
this.hide(); this.hide();
} }
clearTimeout(buttonSpinnerHandler);
this._doneButton.element.classList.remove('validating');
this.updateButtonElement(this._doneButton, this._dialog.okButton, true);
} }
} }

View File

@@ -31,6 +31,21 @@
margin: 0; margin: 0;
} }
.footer-button .validating {
background-size: 15px;
background-repeat: no-repeat;
background-position: center;
}
.vs .footer-button .validating {
background-image: url("loading.svg");
}
.vs-dark .footer-button .validating,
.hc-black .footer-button .validating {
background-image: url("loading_inverse.svg");
}
.dialogModal-wizardHeader { .dialogModal-wizardHeader {
padding: 10px 30px; padding: 10px 30px;
} }

View File

@@ -0,0 +1,31 @@
<?xml version='1.0' standalone='no' ?>
<svg xmlns='http://www.w3.org/2000/svg' version='1.1' width='10px' height='10px'>
<style>
circle {
animation: ball 0.6s linear infinite;
}
circle:nth-child(2) { animation-delay: 0.075s; }
circle:nth-child(3) { animation-delay: 0.15s; }
circle:nth-child(4) { animation-delay: 0.225s; }
circle:nth-child(5) { animation-delay: 0.3s; }
circle:nth-child(6) { animation-delay: 0.375s; }
circle:nth-child(7) { animation-delay: 0.45s; }
circle:nth-child(8) { animation-delay: 0.525s; }
@keyframes ball {
from { opacity: 1; }
to { opacity: 0.3; }
}
</style>
<g>
<circle cx='5' cy='1' r='1' style='opacity:0.3;' />
<circle cx='7.8284' cy='2.1716' r='1' style='opacity:0.3;' />
<circle cx='9' cy='5' r='1' style='opacity:0.3;' />
<circle cx='7.8284' cy='7.8284' r='1' style='opacity:0.3;' />
<circle cx='5' cy='9' r='1' style='opacity:0.3;' />
<circle cx='2.1716' cy='7.8284' r='1' style='opacity:0.3;' />
<circle cx='1' cy='5' r='1' style='opacity:0.3;' />
<circle cx='2.1716' cy='2.1716' r='1' style='opacity:0.3;' />
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1,31 @@
<?xml version='1.0' standalone='no' ?>
<svg xmlns='http://www.w3.org/2000/svg' version='1.1' width='10px' height='10px'>
<style>
circle {
animation: ball 0.6s linear infinite;
}
circle:nth-child(2) { animation-delay: 0.075s; }
circle:nth-child(3) { animation-delay: 0.15s; }
circle:nth-child(4) { animation-delay: 0.225s; }
circle:nth-child(5) { animation-delay: 0.3s; }
circle:nth-child(6) { animation-delay: 0.375s; }
circle:nth-child(7) { animation-delay: 0.45s; }
circle:nth-child(8) { animation-delay: 0.525s; }
@keyframes ball {
from { opacity: 1; }
to { opacity: 0.3; }
}
</style>
<g style="fill:white;">
<circle cx='5' cy='1' r='1' style='opacity:0.3;' />
<circle cx='7.8284' cy='2.1716' r='1' style='opacity:0.3;' />
<circle cx='9' cy='5' r='1' style='opacity:0.3;' />
<circle cx='7.8284' cy='7.8284' r='1' style='opacity:0.3;' />
<circle cx='5' cy='9' r='1' style='opacity:0.3;' />
<circle cx='2.1716' cy='7.8284' r='1' style='opacity:0.3;' />
<circle cx='1' cy='5' r='1' style='opacity:0.3;' />
<circle cx='2.1716' cy='2.1716' r='1' style='opacity:0.3;' />
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -119,7 +119,8 @@ export class WizardModal extends Modal {
private updateButtonElement(buttonElement: Button, dialogButton: DialogButton, requirePageValid: boolean = false) { private updateButtonElement(buttonElement: Button, dialogButton: DialogButton, requirePageValid: boolean = false) {
buttonElement.label = dialogButton.label; buttonElement.label = dialogButton.label;
buttonElement.enabled = requirePageValid ? dialogButton.enabled && this._wizard.pages[this._wizard.currentPage].valid : dialogButton.enabled; buttonElement.enabled = requirePageValid ? dialogButton.enabled && this._wizard.pages[this._wizard.currentPage].valid : dialogButton.enabled;
dialogButton.hidden ? buttonElement.element.classList.add('dialogModal-hidden') : buttonElement.element.classList.remove('dialogModal-hidden'); dialogButton.hidden ? buttonElement.element.parentElement.classList.add('dialogModal-hidden') : buttonElement.element.parentElement.classList.remove('dialogModal-hidden');
this.setButtonsForPage(this._wizard.currentPage);
} }
protected renderBody(container: HTMLElement): void { protected renderBody(container: HTMLElement): void {
@@ -167,7 +168,7 @@ export class WizardModal extends Modal {
this.done(validate); this.done(validate);
return; return;
} }
if (validate && !await this._wizard.validateNavigation(index)) { if (validate && !await this.validateNavigation(index)) {
return; return;
} }
this._dialogPanes.forEach((dialogPane, page) => { this._dialogPanes.forEach((dialogPane, page) => {
@@ -185,20 +186,24 @@ export class WizardModal extends Modal {
} }
private setButtonsForPage(index: number) { private setButtonsForPage(index: number) {
if (this._wizard.pages[index - 1]) { if (this._previousButton) {
this._previousButton.element.parentElement.classList.remove('dialogModal-hidden'); if (this._wizard.pages[index - 1]) {
this._previousButton.enabled = this._wizard.pages[index - 1].enabled; this._previousButton.element.parentElement.classList.remove('dialogModal-hidden');
} else { this._previousButton.enabled = this._wizard.pages[index - 1].enabled;
this._previousButton.element.parentElement.classList.add('dialogModal-hidden'); } else {
this._previousButton.element.parentElement.classList.add('dialogModal-hidden');
}
} }
if (this._wizard.pages[index + 1]) { if (this._nextButton && this._doneButton) {
this._nextButton.element.parentElement.classList.remove('dialogModal-hidden'); if (this._wizard.pages[index + 1]) {
this._nextButton.enabled = this._wizard.pages[index + 1].enabled; this._nextButton.element.parentElement.classList.remove('dialogModal-hidden');
this._doneButton.element.parentElement.classList.add('dialogModal-hidden'); this._nextButton.enabled = this._wizard.pages[index + 1].enabled;
} else { this._doneButton.element.parentElement.classList.add('dialogModal-hidden');
this._nextButton.element.parentElement.classList.add('dialogModal-hidden'); } else {
this._doneButton.element.parentElement.classList.remove('dialogModal-hidden'); this._nextButton.element.parentElement.classList.add('dialogModal-hidden');
this._doneButton.element.parentElement.classList.remove('dialogModal-hidden');
}
} }
} }
@@ -225,7 +230,7 @@ export class WizardModal extends Modal {
public async done(validate: boolean = true): Promise<void> { public async done(validate: boolean = true): Promise<void> {
if (this._doneButton.enabled) { if (this._doneButton.enabled) {
if (validate && !await this._wizard.validateNavigation(undefined)) { if (validate && !await this.validateNavigation(undefined)) {
return; return;
} }
this._onDone.fire(); this._onDone.fire();
@@ -240,6 +245,20 @@ export class WizardModal extends Modal {
this.hide(); this.hide();
} }
private async validateNavigation(newPage: number): Promise<boolean> {
let button = newPage === undefined ? this._doneButton : this._nextButton;
let buttonSpinnerHandler = setTimeout(() => {
button.enabled = false;
button.element.innerHTML = '&nbsp';
button.element.classList.add('validating');
}, 100);
let navigationValid = await this._wizard.validateNavigation(newPage);
clearTimeout(buttonSpinnerHandler);
button.element.classList.remove('validating');
this.updateButtonElement(button, newPage === undefined ? this._wizard.doneButton : this._wizard.nextButton, true);
return navigationValid;
}
protected hide(): void { protected hide(): void {
super.hide(); super.hide();
} }

17
src/sql/sqlops.d.ts vendored
View File

@@ -1531,12 +1531,13 @@ declare module 'sqlops' {
// Task service interfaces ---------------------------------------------------------------------------- // Task service interfaces ----------------------------------------------------------------------------
export enum TaskStatus { export enum TaskStatus {
notStarted = 0, NotStarted = 0,
inProgress = 1, InProgress = 1,
succeeded = 2, Succeeded = 2,
succeededWithWarning = 3, SucceededWithWarning = 3,
failed = 4, Failed = 4,
canceled = 5 Canceled = 5,
Canceling = 6
} }
export enum TaskExecutionMode { export enum TaskExecutionMode {
@@ -1550,6 +1551,7 @@ declare module 'sqlops' {
} }
export interface TaskInfo { export interface TaskInfo {
connection?: connection.Connection;
taskId: string; taskId: string;
status: TaskStatus; status: TaskStatus;
taskExecutionMode: TaskExecutionMode; taskExecutionMode: TaskExecutionMode;
@@ -1573,8 +1575,7 @@ declare module 'sqlops' {
taskId: string; taskId: string;
status: TaskStatus; status: TaskStatus;
message: string; message: string;
script: string; script?: string;
duration: number;
} }
export interface TaskServicesProvider extends DataProvider { export interface TaskServicesProvider extends DataProvider {

View File

@@ -436,6 +436,7 @@ declare module 'sqlops' {
export interface ButtonProperties extends ComponentProperties, ComponentWithIcon { export interface ButtonProperties extends ComponentProperties, ComponentWithIcon {
label?: string; label?: string;
isFile?: boolean;
} }
export interface LoadingComponentProperties { export interface LoadingComponentProperties {
@@ -711,6 +712,12 @@ declare module 'sqlops' {
* done. Return true to allow the dialog to close or false to block it from closing * done. Return true to allow the dialog to close or false to block it from closing
*/ */
registerCloseValidator(validator: () => boolean | Thenable<boolean>): void; registerCloseValidator(validator: () => boolean | Thenable<boolean>): void;
/**
* Register an operation to run in the background when the dialog is done
* @param operationInfo Operation Information
*/
registerOperation(operationInfo: BackgroundOperationInfo): void;
} }
export interface DialogTab extends ModelViewPanel { export interface DialogTab extends ModelViewPanel {
@@ -894,6 +901,12 @@ declare module 'sqlops' {
* undefined or the text is empty or undefined. The default level is error. * undefined or the text is empty or undefined. The default level is error.
*/ */
message: DialogMessage message: DialogMessage
/**
* Register an operation to run in the background when the wizard is done
* @param operationInfo Operation Information
*/
registerOperation(operationInfo: BackgroundOperationInfo): void;
} }
} }
} }
@@ -1000,4 +1013,69 @@ declare module 'sqlops' {
nodeInfo: NodeInfo; nodeInfo: NodeInfo;
} }
/**
* Background Operation
*/
export interface BackgroundOperation {
/**
* Updates the operation status or adds progress message
* @param status Operation Status
* @param message Progress message
*/
updateStatus(status: TaskStatus, message?: string): void;
/**
* Operation Id
*/
id: string;
/**
* Event raised when operation is canceled in UI
*/
onCanceled: vscode.Event<void>;
}
/**
* Operation Information
*/
export interface BackgroundOperationInfo {
/**
* The operation id. A unique id will be assigned to it If not specified a
*/
operationId?: string;
/**
* Connection information
*/
connection: connection.Connection;
/**
* Operation Display Name
*/
displayName: string;
/**
* Operation Description
*/
description: string;
/**
* True if the operation is cancelable
*/
isCancelable: boolean;
/**
* The actual operation to execute
*/
operation: (operation: BackgroundOperation) => void
}
namespace tasks {
/**
* Starts an operation to run in the background
* @param operationInfo Operation Information
*/
export function startBackgroundOperation(operationInfo: BackgroundOperationInfo): void;
}
} }

View File

@@ -39,12 +39,13 @@ export enum EditRowState {
} }
export enum TaskStatus { export enum TaskStatus {
notStarted = 0, NotStarted = 0,
inProgress = 1, InProgress = 1,
succeeded = 2, Succeeded = 2,
succeededWithWarning = 3, SucceededWithWarning = 3,
failed = 4, Failed = 4,
canceled = 5 Canceled = 5,
Canceling = 6
} }
export enum TaskExecutionMode { export enum TaskExecutionMode {
@@ -99,7 +100,7 @@ export enum AlertType {
} }
export enum FrequencyTypes { export enum FrequencyTypes {
Unknown , Unknown,
OneTime = 1 << 1, OneTime = 1 << 1,
Daily = 1 << 2, Daily = 1 << 2,
Weekly = 1 << 3, Weekly = 1 << 3,
@@ -275,7 +276,7 @@ export enum DeclarativeDataType {
} }
export enum CardType { export enum CardType {
VerticalButton = 'VerticalButton', VerticalButton = 'VerticalButton',
Details = 'Details' Details = 'Details'
} }
export class SqlThemeIcon { export class SqlThemeIcon {

View File

@@ -0,0 +1,113 @@
/*---------------------------------------------------------------------------------------------
* 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 { IMainContext } from 'vs/workbench/api/node/extHost.protocol';
import { ExtHostBackgroundTaskManagementShape, SqlMainContext, MainThreadBackgroundTaskManagementShape } from 'sql/workbench/api/node/sqlExtHost.protocol';
import * as sqlops from 'sqlops';
import * as vscode from 'vscode';
import { Emitter } from 'vs/base/common/event';
import { generateUuid } from 'vs/base/common/uuid';
export enum TaskStatus {
NotStarted = 0,
InProgress = 1,
Succeeded = 2,
SucceededWithWarning = 3,
Failed = 4,
Canceled = 5,
Canceling = 6
}
export class ExtBackgroundOperation implements sqlops.BackgroundOperation {
private readonly _proxy: MainThreadBackgroundTaskManagementShape;
private _onCanceled = new Emitter<void>();
constructor(
private _id: string,
mainContext: IMainContext
) {
this._proxy = mainContext.getProxy(SqlMainContext.MainThreadBackgroundTaskManagement);
}
public updateStatus(status: TaskStatus, message?: string): void {
this._proxy.$updateTask({
message: message,
status: status,
taskId: this.id
});
}
public get onCanceled(): vscode.Event<void> {
return this._onCanceled.event;
}
public cancel(): void {
this._onCanceled.fire();
}
public get id(): string {
return this._id;
}
}
export class ExtHostBackgroundTaskManagement implements ExtHostBackgroundTaskManagementShape {
private readonly _proxy: MainThreadBackgroundTaskManagementShape;
private readonly _handlers = new Map<string, sqlops.BackgroundOperationInfo>();
private readonly _operations = new Map<string, ExtBackgroundOperation>();
private readonly _mainContext: IMainContext;
constructor(
mainContext: IMainContext
) {
this._proxy = mainContext.getProxy(SqlMainContext.MainThreadBackgroundTaskManagement);
this._mainContext = mainContext;
}
$onTaskRegistered(operationId: string): void {
let extOperationInfo = new ExtBackgroundOperation(operationId, this._mainContext);
this._operations.set(operationId, extOperationInfo);
let operationInfo = this._handlers.get(operationId);
if (operationInfo) {
operationInfo.operation(extOperationInfo);
}
}
$onTaskCanceled(operationId: string): void {
let operation = this._operations.get(operationId);
if (operation) {
operation.cancel();
}
}
$registerTask(operationInfo: sqlops.BackgroundOperationInfo): void {
let operationId = operationInfo.operationId || `OperationId${generateUuid()}`;
if (this._handlers.has(operationId)) {
throw new Error(`operation '${operationId}' already exists`);
}
this._handlers.set(operationId, operationInfo);
let taskInfo: sqlops.TaskInfo = {
databaseName: undefined,
serverName: undefined,
description: operationInfo.description,
isCancelable: operationInfo.isCancelable,
name: operationInfo.displayName,
providerName: undefined, //setting provider name will cause the task to be processed by the provider. But this task is created in the extension and needs to be handled
//by the extension
taskExecutionMode: 0,
taskId: operationId,
status: TaskStatus.NotStarted,
connection: operationInfo.connection
};
this._proxy.$registerTask(taskInfo);
}
$removeTask(operationId: string) {
if (this._handlers.has(operationId)) {
this._handlers.delete(operationId);
}
}
}

View File

@@ -8,11 +8,12 @@ import { IMainContext } from 'vs/workbench/api/node/extHost.protocol';
import { Event, Emitter } from 'vs/base/common/event'; import { Event, Emitter } from 'vs/base/common/event';
import { deepClone } from 'vs/base/common/objects'; import { deepClone } from 'vs/base/common/objects';
import * as nls from 'vs/nls'; import * as nls from 'vs/nls';
import { generateUuid } from 'vs/base/common/uuid';
import * as vscode from 'vscode'; import * as vscode from 'vscode';
import * as sqlops from 'sqlops'; import * as sqlops from 'sqlops';
import { SqlMainContext, ExtHostModelViewDialogShape, MainThreadModelViewDialogShape, ExtHostModelViewShape } from 'sql/workbench/api/node/sqlExtHost.protocol'; import { SqlMainContext, ExtHostModelViewDialogShape, MainThreadModelViewDialogShape, ExtHostModelViewShape, ExtHostBackgroundTaskManagementShape } from 'sql/workbench/api/node/sqlExtHost.protocol';
import { IItemConfig, ModelComponentTypes, IComponentShape } from 'sql/workbench/api/common/sqlExtHostTypes'; import { IItemConfig, ModelComponentTypes, IComponentShape } from 'sql/workbench/api/common/sqlExtHostTypes';
const DONE_LABEL = nls.localize('dialogDoneLabel', 'Done'); const DONE_LABEL = nls.localize('dialogDoneLabel', 'Done');
@@ -95,12 +96,22 @@ class DialogImpl extends ModelViewPanelImpl implements sqlops.window.modelviewdi
public customButtons: sqlops.window.modelviewdialog.Button[]; public customButtons: sqlops.window.modelviewdialog.Button[];
private _message: sqlops.window.modelviewdialog.DialogMessage; private _message: sqlops.window.modelviewdialog.DialogMessage;
private _closeValidator: () => boolean | Thenable<boolean>; private _closeValidator: () => boolean | Thenable<boolean>;
private _operationHandler: BackgroundOperationHandler;
constructor(extHostModelViewDialog: ExtHostModelViewDialog, constructor(extHostModelViewDialog: ExtHostModelViewDialog,
extHostModelView: ExtHostModelViewShape) { extHostModelView: ExtHostModelViewShape,
extHostTaskManagement: ExtHostBackgroundTaskManagementShape) {
super('modelViewDialog', extHostModelViewDialog, extHostModelView); super('modelViewDialog', extHostModelViewDialog, extHostModelView);
this.okButton = this._extHostModelViewDialog.createButton(DONE_LABEL); this.okButton = this._extHostModelViewDialog.createButton(DONE_LABEL);
this.cancelButton = this._extHostModelViewDialog.createButton(CANCEL_LABEL); this.cancelButton = this._extHostModelViewDialog.createButton(CANCEL_LABEL);
this._operationHandler = new BackgroundOperationHandler('dialog', extHostTaskManagement);
this.okButton.onClick(() => {
this._operationHandler.createOperation();
});
}
public registerOperation(operationInfo: sqlops.BackgroundOperationInfo): void {
this._operationHandler.registerOperation(operationInfo);
} }
public setModelViewId(value: string) { public setModelViewId(value: string) {
@@ -192,13 +203,44 @@ class ButtonImpl implements sqlops.window.modelviewdialog.Button {
} }
} }
class BackgroundOperationHandler {
private _operationInfo: sqlops.BackgroundOperationInfo;
constructor(
private _name: string,
private _extHostTaskManagement: ExtHostBackgroundTaskManagementShape) {
}
public createOperation(): void {
if (!this._operationInfo) {
return;
}
if (!this._operationInfo.operationId) {
let uniqueId = generateUuid();
this._operationInfo.operationId = 'OperationId' + uniqueId + this._name;
}
if (this._operationInfo.operation) {
this._extHostTaskManagement.$registerTask(this._operationInfo);
}
}
public registerOperation(operationInfo: sqlops.BackgroundOperationInfo): void {
this._operationInfo = operationInfo;
}
}
class WizardPageImpl extends ModelViewPanelImpl implements sqlops.window.modelviewdialog.WizardPage { class WizardPageImpl extends ModelViewPanelImpl implements sqlops.window.modelviewdialog.WizardPage {
public customButtons: sqlops.window.modelviewdialog.Button[]; public customButtons: sqlops.window.modelviewdialog.Button[];
private _enabled: boolean = true; private _enabled: boolean = true;
private _description: string; private _description: string;
constructor(public title: string, _extHostModelViewDialog: ExtHostModelViewDialog, _extHostModelView: ExtHostModelViewShape) { constructor(public title: string,
super('modelViewWizardPage', _extHostModelViewDialog, _extHostModelView); extHostModelViewDialog: ExtHostModelViewDialog,
extHostModelView: ExtHostModelViewShape) {
super('modelViewWizardPage', extHostModelViewDialog, extHostModelView);
} }
public get enabled(): boolean { public get enabled(): boolean {
@@ -253,8 +295,9 @@ class WizardImpl implements sqlops.window.modelviewdialog.Wizard {
private _navigationValidator: (info: sqlops.window.modelviewdialog.WizardPageChangeInfo) => boolean | Thenable<boolean>; private _navigationValidator: (info: sqlops.window.modelviewdialog.WizardPageChangeInfo) => boolean | Thenable<boolean>;
private _message: sqlops.window.modelviewdialog.DialogMessage; private _message: sqlops.window.modelviewdialog.DialogMessage;
private _displayPageTitles: boolean = true; private _displayPageTitles: boolean = true;
private _operationHandler: BackgroundOperationHandler;
constructor(public title: string, private _extHostModelViewDialog: ExtHostModelViewDialog) { constructor(public title: string, private _extHostModelViewDialog: ExtHostModelViewDialog, extHostTaskManagement: ExtHostBackgroundTaskManagementShape) {
this.doneButton = this._extHostModelViewDialog.createButton(DONE_LABEL); this.doneButton = this._extHostModelViewDialog.createButton(DONE_LABEL);
this.cancelButton = this._extHostModelViewDialog.createButton(CANCEL_LABEL); this.cancelButton = this._extHostModelViewDialog.createButton(CANCEL_LABEL);
this.generateScriptButton = this._extHostModelViewDialog.createButton(GENERATE_SCRIPT_LABEL); this.generateScriptButton = this._extHostModelViewDialog.createButton(GENERATE_SCRIPT_LABEL);
@@ -263,6 +306,14 @@ class WizardImpl implements sqlops.window.modelviewdialog.Wizard {
this._extHostModelViewDialog.registerWizardPageInfoChangedCallback(this, info => this.handlePageInfoChanged(info)); this._extHostModelViewDialog.registerWizardPageInfoChangedCallback(this, info => this.handlePageInfoChanged(info));
this._currentPage = 0; this._currentPage = 0;
this.onPageChanged(info => this._currentPage = info.newPage); this.onPageChanged(info => this._currentPage = info.newPage);
this._operationHandler = new BackgroundOperationHandler('wizard' + this.title, extHostTaskManagement);
this.doneButton.onClick(() => {
this._operationHandler.createOperation();
});
}
public registerOperation(operationInfo: sqlops.BackgroundOperationInfo): void {
this._operationHandler.registerOperation(operationInfo);
} }
public get currentPage(): number { public get currentPage(): number {
@@ -344,7 +395,8 @@ export class ExtHostModelViewDialog implements ExtHostModelViewDialogShape {
constructor( constructor(
mainContext: IMainContext, mainContext: IMainContext,
private _extHostModelView: ExtHostModelViewShape private _extHostModelView: ExtHostModelViewShape,
private _extHostTaskManagement: ExtHostBackgroundTaskManagementShape
) { ) {
this._proxy = mainContext.getProxy(SqlMainContext.MainThreadModelViewDialog); this._proxy = mainContext.getProxy(SqlMainContext.MainThreadModelViewDialog);
} }
@@ -473,7 +525,7 @@ export class ExtHostModelViewDialog implements ExtHostModelViewDialogShape {
} }
public createDialog(title: string): sqlops.window.modelviewdialog.Dialog { public createDialog(title: string): sqlops.window.modelviewdialog.Dialog {
let dialog = new DialogImpl(this, this._extHostModelView); let dialog = new DialogImpl(this, this._extHostModelView, this._extHostTaskManagement);
dialog.title = title; dialog.title = title;
dialog.handle = this.getHandle(dialog); dialog.handle = this.getHandle(dialog);
return dialog; return dialog;
@@ -516,7 +568,7 @@ export class ExtHostModelViewDialog implements ExtHostModelViewDialogShape {
} }
public createWizard(title: string): sqlops.window.modelviewdialog.Wizard { public createWizard(title: string): sqlops.window.modelviewdialog.Wizard {
let wizard = new WizardImpl(title, this); let wizard = new WizardImpl(title, this, this._extHostTaskManagement);
this.getHandle(wizard); this.getHandle(wizard);
return wizard; return wizard;
} }

View File

@@ -0,0 +1,52 @@
/*---------------------------------------------------------------------------------------------
* 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 { ITaskService } from 'sql/parts/taskHistory/common/taskService';
import { MainThreadBackgroundTaskManagementShape, SqlMainContext, ExtHostBackgroundTaskManagementShape, SqlExtHostContext } from 'sql/workbench/api/node/sqlExtHost.protocol';
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
import { IExtHostContext } from 'vs/workbench/api/node/extHost.protocol';
import { Disposable } from 'vs/base/common/lifecycle';
import * as sqlops from 'sqlops';
export enum TaskStatus {
NotStarted = 0,
InProgress = 1,
Succeeded = 2,
SucceededWithWarning = 3,
Failed = 4,
Canceled = 5,
Canceling = 6
}
@extHostNamedCustomer(SqlMainContext.MainThreadBackgroundTaskManagement)
export class MainThreadBackgroundTaskManagement extends Disposable implements MainThreadBackgroundTaskManagementShape {
private readonly _proxy: ExtHostBackgroundTaskManagementShape;
constructor(
context: IExtHostContext,
@ITaskService private _taskService: ITaskService
) {
super();
this._proxy = context.getProxy(SqlExtHostContext.ExtHostBackgroundTaskManagement);
this._register(this._taskService.onTaskComplete(task => {
if (task.status === TaskStatus.Canceling) {
this._proxy.$onTaskCanceled(task.id);
}
}));
}
$registerTask(taskInfo: sqlops.TaskInfo): void {
this._taskService.createNewTask(taskInfo);
this._proxy.$onTaskRegistered(taskInfo.taskId);
}
$updateTask(taskProgressInfo: sqlops.TaskProgressInfo): void {
this._taskService.updateTask(taskProgressInfo);
}
}

View File

@@ -34,6 +34,7 @@ import { ExtHostObjectExplorer } from 'sql/workbench/api/node/extHostObjectExplo
import { ExtHostLogService } from 'vs/workbench/api/node/extHostLogService'; import { ExtHostLogService } from 'vs/workbench/api/node/extHostLogService';
import { ExtHostModelViewDialog } from 'sql/workbench/api/node/extHostModelViewDialog'; import { ExtHostModelViewDialog } from 'sql/workbench/api/node/extHostModelViewDialog';
import { ExtHostQueryEditor } from 'sql/workbench/api/node/extHostQueryEditor'; import { ExtHostQueryEditor } from 'sql/workbench/api/node/extHostQueryEditor';
import { ExtHostBackgroundTaskManagement } from './extHostBackgroundTaskManagement';
export interface ISqlExtensionApiFactory { export interface ISqlExtensionApiFactory {
vsCodeFactory(extension: IExtensionDescription): typeof vscode; vsCodeFactory(extension: IExtensionDescription): typeof vscode;
@@ -63,10 +64,11 @@ export function createApiFactory(
const extHostResourceProvider = rpcProtocol.set(SqlExtHostContext.ExtHostResourceProvider, new ExtHostResourceProvider(rpcProtocol)); const extHostResourceProvider = rpcProtocol.set(SqlExtHostContext.ExtHostResourceProvider, new ExtHostResourceProvider(rpcProtocol));
const extHostModalDialogs = rpcProtocol.set(SqlExtHostContext.ExtHostModalDialogs, new ExtHostModalDialogs(rpcProtocol)); const extHostModalDialogs = rpcProtocol.set(SqlExtHostContext.ExtHostModalDialogs, new ExtHostModalDialogs(rpcProtocol));
const extHostTasks = rpcProtocol.set(SqlExtHostContext.ExtHostTasks, new ExtHostTasks(rpcProtocol, logService)); const extHostTasks = rpcProtocol.set(SqlExtHostContext.ExtHostTasks, new ExtHostTasks(rpcProtocol, logService));
const extHostBackgroundTaskManagement = rpcProtocol.set(SqlExtHostContext.ExtHostBackgroundTaskManagement, new ExtHostBackgroundTaskManagement(rpcProtocol));
const extHostWebviewWidgets = rpcProtocol.set(SqlExtHostContext.ExtHostDashboardWebviews, new ExtHostDashboardWebviews(rpcProtocol)); const extHostWebviewWidgets = rpcProtocol.set(SqlExtHostContext.ExtHostDashboardWebviews, new ExtHostDashboardWebviews(rpcProtocol));
const extHostModelView = rpcProtocol.set(SqlExtHostContext.ExtHostModelView, new ExtHostModelView(rpcProtocol)); const extHostModelView = rpcProtocol.set(SqlExtHostContext.ExtHostModelView, new ExtHostModelView(rpcProtocol));
const extHostDashboard = rpcProtocol.set(SqlExtHostContext.ExtHostDashboard, new ExtHostDashboard(rpcProtocol)); const extHostDashboard = rpcProtocol.set(SqlExtHostContext.ExtHostDashboard, new ExtHostDashboard(rpcProtocol));
const extHostModelViewDialog = rpcProtocol.set(SqlExtHostContext.ExtHostModelViewDialog, new ExtHostModelViewDialog(rpcProtocol, extHostModelView)); const extHostModelViewDialog = rpcProtocol.set(SqlExtHostContext.ExtHostModelViewDialog, new ExtHostModelViewDialog(rpcProtocol, extHostModelView, extHostBackgroundTaskManagement));
const extHostQueryEditor = rpcProtocol.set(SqlExtHostContext.ExtHostQueryEditor, new ExtHostQueryEditor(rpcProtocol)); const extHostQueryEditor = rpcProtocol.set(SqlExtHostContext.ExtHostQueryEditor, new ExtHostQueryEditor(rpcProtocol));
@@ -334,6 +336,9 @@ export function createApiFactory(
const tasks: typeof sqlops.tasks = { const tasks: typeof sqlops.tasks = {
registerTask(id: string, task: (...args: any[]) => any, thisArgs?: any): vscode.Disposable { registerTask(id: string, task: (...args: any[]) => any, thisArgs?: any): vscode.Disposable {
return extHostTasks.registerTask(id, task, thisArgs); return extHostTasks.registerTask(id, task, thisArgs);
},
startBackgroundOperation(operationInfo: sqlops.BackgroundOperationInfo): void {
extHostBackgroundTaskManagement.$registerTask(operationInfo);
} }
}; };

View File

@@ -14,6 +14,7 @@ import 'sql/workbench/api/node/mainThreadConnectionManagement';
import 'sql/workbench/api/node/mainThreadCredentialManagement'; import 'sql/workbench/api/node/mainThreadCredentialManagement';
import 'sql/workbench/api/node/mainThreadDataProtocol'; import 'sql/workbench/api/node/mainThreadDataProtocol';
import 'sql/workbench/api/node/mainThreadObjectExplorer'; import 'sql/workbench/api/node/mainThreadObjectExplorer';
import 'sql/workbench/api/node/mainThreadBackgroundTaskManagement';
import 'sql/workbench/api/node/mainThreadSerializationProvider'; import 'sql/workbench/api/node/mainThreadSerializationProvider';
import 'sql/workbench/api/node/mainThreadResourceProvider'; import 'sql/workbench/api/node/mainThreadResourceProvider';
import 'sql/workbench/api/electron-browser/mainThreadTasks'; import 'sql/workbench/api/electron-browser/mainThreadTasks';

View File

@@ -496,6 +496,7 @@ export const SqlMainContext = {
MainThreadCredentialManagement: createMainId<MainThreadCredentialManagementShape>('MainThreadCredentialManagement'), MainThreadCredentialManagement: createMainId<MainThreadCredentialManagementShape>('MainThreadCredentialManagement'),
MainThreadDataProtocol: createMainId<MainThreadDataProtocolShape>('MainThreadDataProtocol'), MainThreadDataProtocol: createMainId<MainThreadDataProtocolShape>('MainThreadDataProtocol'),
MainThreadObjectExplorer: createMainId<MainThreadObjectExplorerShape>('MainThreadObjectExplorer'), MainThreadObjectExplorer: createMainId<MainThreadObjectExplorerShape>('MainThreadObjectExplorer'),
MainThreadBackgroundTaskManagement: createMainId<MainThreadBackgroundTaskManagementShape>('MainThreadBackgroundTaskManagement'),
MainThreadSerializationProvider: createMainId<MainThreadSerializationProviderShape>('MainThreadSerializationProvider'), MainThreadSerializationProvider: createMainId<MainThreadSerializationProviderShape>('MainThreadSerializationProvider'),
MainThreadResourceProvider: createMainId<MainThreadResourceProviderShape>('MainThreadResourceProvider'), MainThreadResourceProvider: createMainId<MainThreadResourceProviderShape>('MainThreadResourceProvider'),
MainThreadModalDialog: createMainId<MainThreadModalDialogShape>('MainThreadModalDialog'), MainThreadModalDialog: createMainId<MainThreadModalDialogShape>('MainThreadModalDialog'),
@@ -517,6 +518,7 @@ export const SqlExtHostContext = {
ExtHostResourceProvider: createExtId<ExtHostResourceProviderShape>('ExtHostResourceProvider'), ExtHostResourceProvider: createExtId<ExtHostResourceProviderShape>('ExtHostResourceProvider'),
ExtHostModalDialogs: createExtId<ExtHostModalDialogsShape>('ExtHostModalDialogs'), ExtHostModalDialogs: createExtId<ExtHostModalDialogsShape>('ExtHostModalDialogs'),
ExtHostTasks: createExtId<ExtHostTasksShape>('ExtHostTasks'), ExtHostTasks: createExtId<ExtHostTasksShape>('ExtHostTasks'),
ExtHostBackgroundTaskManagement: createExtId<ExtHostBackgroundTaskManagementShape>('ExtHostBackgroundTaskManagement'),
ExtHostDashboardWebviews: createExtId<ExtHostDashboardWebviewsShape>('ExtHostDashboardWebviews'), ExtHostDashboardWebviews: createExtId<ExtHostDashboardWebviewsShape>('ExtHostDashboardWebviews'),
ExtHostModelView: createExtId<ExtHostModelViewShape>('ExtHostModelView'), ExtHostModelView: createExtId<ExtHostModelViewShape>('ExtHostModelView'),
ExtHostDashboard: createExtId<ExtHostDashboardShape>('ExtHostDashboard'), ExtHostDashboard: createExtId<ExtHostDashboardShape>('ExtHostDashboard'),
@@ -578,6 +580,18 @@ export interface ExtHostModelViewShape {
$runCustomValidations(handle: number, id: string): Thenable<boolean>; $runCustomValidations(handle: number, id: string): Thenable<boolean>;
} }
export interface ExtHostBackgroundTaskManagementShape {
$onTaskRegistered(operationId: string): void;
$onTaskCanceled(operationId: string): void;
$registerTask(operationInfo: sqlops.BackgroundOperationInfo): void;
$removeTask(operationId: string): void;
}
export interface MainThreadBackgroundTaskManagementShape extends IDisposable {
$registerTask(taskInfo: sqlops.TaskInfo): void;
$updateTask(taskProgressInfo: sqlops.TaskProgressInfo): void;
}
export interface MainThreadModelViewShape extends IDisposable { export interface MainThreadModelViewShape extends IDisposable {
$registerProvider(id: string): void; $registerProvider(id: string): void;
$initializeModel(handle: number, rootComponent: IComponentShape): Thenable<void>; $initializeModel(handle: number, rootComponent: IComponentShape): Thenable<void>;

View File

@@ -0,0 +1,107 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as sqlops from 'sqlops';
import * as assert from 'assert';
import { Mock, It, Times } from 'typemoq';
import { ExtHostBackgroundTaskManagement, TaskStatus } from 'sql/workbench/api/node/extHostBackgroundTaskManagement';
import { MainThreadBackgroundTaskManagementShape } from 'sql/workbench/api/node/sqlExtHost.protocol';
import { IMainContext } from 'vs/workbench/api/node/extHost.protocol';
'use strict';
suite('ExtHostBackgroundTaskManagement Tests', () => {
let extHostBackgroundTaskManagement: ExtHostBackgroundTaskManagement;
let mockProxy: Mock<MainThreadBackgroundTaskManagementShape>;
let nothing: void;
let operationId = 'operation is';
setup(() => {
mockProxy = Mock.ofInstance(<MainThreadBackgroundTaskManagementShape>{
$registerTask: (taskInfo: sqlops.TaskInfo) => nothing,
$updateTask: (taskProgressInfo: sqlops.TaskProgressInfo) => nothing
});
let mainContext = <IMainContext>{
getProxy: proxyType => mockProxy.object
};
mockProxy.setup(x => x.$registerTask(It.isAny())).callback(() => {
extHostBackgroundTaskManagement.$onTaskRegistered(operationId);
});
extHostBackgroundTaskManagement = new ExtHostBackgroundTaskManagement(mainContext);
});
test('RegisterTask should successfully create background task and update status', () => {
let operationInfo: sqlops.BackgroundOperationInfo = {
connection: undefined,
description: 'description',
displayName: 'displayName',
isCancelable: true,
operation: (op: sqlops.BackgroundOperation) => { op.updateStatus(TaskStatus.Succeeded); },
operationId: operationId
};
extHostBackgroundTaskManagement.$registerTask(operationInfo);
mockProxy.verify(x => x.$registerTask(It.is(
t => t.name === operationInfo.displayName &&
t.description === operationInfo.description &&
t.taskId === operationId &&
t.isCancelable === operationInfo.isCancelable &&
t.providerName === undefined
)), Times.once());
mockProxy.verify(x => x.$updateTask(It.is(t => t.status === TaskStatus.Succeeded)), Times.once());
extHostBackgroundTaskManagement.$removeTask(operationId);
});
test('Canceling the task should notify the extension', () => {
let operationInfo: sqlops.BackgroundOperationInfo = {
connection: undefined,
description: 'description',
displayName: 'displayName',
isCancelable: true,
operation: (op: sqlops.BackgroundOperation) => {
op.onCanceled(() => {
op.updateStatus(TaskStatus.Canceled);
})
},
operationId: operationId
};
extHostBackgroundTaskManagement.$registerTask(operationInfo);
extHostBackgroundTaskManagement.$onTaskCanceled(operationId);
mockProxy.verify(x => x.$updateTask(It.is(t => t.status === TaskStatus.Canceled)), Times.once());
extHostBackgroundTaskManagement.$removeTask(operationId);
});
test('RegisterTask should assign unique id to the operation is not assigned', () => {
let operationInfo: sqlops.BackgroundOperationInfo = {
connection: undefined,
description: 'description',
displayName: 'displayName',
isCancelable: true,
operation: (op: sqlops.BackgroundOperation) => { op.updateStatus(TaskStatus.Succeeded); },
operationId: undefined
};
extHostBackgroundTaskManagement.$registerTask(operationInfo);
mockProxy.verify(x => x.$registerTask(It.is(t => t.taskId !== undefined)), Times.once());
extHostBackgroundTaskManagement.$removeTask(operationId);
});
test('RegisterTask should fail given id of an existing operation', () => {
let operationInfo: sqlops.BackgroundOperationInfo = {
connection: undefined,
description: 'description',
displayName: 'displayName',
isCancelable: true,
operation: (op: sqlops.BackgroundOperation) => { op.updateStatus(TaskStatus.Succeeded); },
operationId: operationId
};
extHostBackgroundTaskManagement.$registerTask(operationInfo);
mockProxy.verify(x => x.$registerTask(It.is(t => t.taskId === operationId)), Times.once());
assert.throws(() => extHostBackgroundTaskManagement.$registerTask(operationInfo));
extHostBackgroundTaskManagement.$removeTask(operationId);
});
});

View File

@@ -37,7 +37,7 @@ suite('ExtHostModelViewDialog Tests', () => {
extHostModelView = Mock.ofInstance(<ExtHostModelViewShape>{ extHostModelView = Mock.ofInstance(<ExtHostModelViewShape>{
$registerProvider: (widget, handler) => undefined $registerProvider: (widget, handler) => undefined
}); });
extHostModelViewDialog = new ExtHostModelViewDialog(mainContext, extHostModelView.object); extHostModelViewDialog = new ExtHostModelViewDialog(mainContext, extHostModelView.object, undefined);
}); });
test('Creating a dialog returns a dialog with initialized ok and cancel buttons and the given title', () => { test('Creating a dialog returns a dialog with initialized ok and cancel buttons and the given title', () => {

View File

@@ -0,0 +1,97 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as sqlops from 'sqlops';
import * as assert from 'assert';
import { Mock, It, Times } from 'typemoq';
import { MainThreadBackgroundTaskManagement, TaskStatus } from 'sql/workbench/api/node/mainThreadBackgroundTaskManagement';
import { ExtHostBackgroundTaskManagementShape } from 'sql/workbench/api/node/sqlExtHost.protocol';
import { ITaskService } from 'sql/parts/taskHistory/common/taskService';
import { IExtHostContext } from 'vs/workbench/api/node/extHost.protocol';
import { TaskNode } from 'sql/parts/taskHistory/common/taskNode';
import { Event, Emitter } from 'vs/base/common/event';
'use strict';
suite('MainThreadBackgroundTaskManagement Tests', () => {
let mainThreadBackgroundTaskManagement: MainThreadBackgroundTaskManagement;
let mockProxy: Mock<ExtHostBackgroundTaskManagementShape>;
let taskService: Mock<ITaskService>;
let nothing: void;
let operationId = 'operation is';
let onTaskComplete = new Emitter<TaskNode>();
setup(() => {
mockProxy = Mock.ofInstance(<ExtHostBackgroundTaskManagementShape>{
$onTaskRegistered: (operationId: string) => nothing,
$onTaskCanceled: (operationId: string) => nothing,
$registerTask: (operationInfo: sqlops.BackgroundOperationInfo) => nothing,
$removeTask: (operationId: string) => nothing,
});
taskService = Mock.ofInstance(<ITaskService>{
_serviceBrand: undefined,
onTaskComplete: undefined,
onAddNewTask: undefined,
handleNewTask: undefined,
handleTaskComplete: undefined,
getAllTasks: undefined,
getNumberOfInProgressTasks: undefined,
onNewTaskCreated: undefined,
createNewTask: (taskInfo: sqlops.TaskInfo) => nothing,
updateTask: (taskProgressInfo: sqlops.TaskProgressInfo) => nothing,
onTaskStatusChanged: undefined,
cancelTask: undefined,
registerProvider: undefined
});
let mainContext = <IExtHostContext>{
getProxy: proxyType => mockProxy.object
};
taskService.setup(x => x.onTaskComplete).returns(() => onTaskComplete.event);
mainThreadBackgroundTaskManagement = new MainThreadBackgroundTaskManagement(mainContext, taskService.object);
});
test('RegisterTask should successfully create background task', () => {
let taskInfo: sqlops.TaskInfo = {
taskId: operationId,
databaseName: undefined,
description: undefined,
isCancelable: true,
name: 'task name',
providerName: undefined,
serverName: undefined,
status: TaskStatus.NotStarted,
taskExecutionMode: 0
};
mainThreadBackgroundTaskManagement.$registerTask(taskInfo);
taskService.verify(x => x.createNewTask(It.is(t => t.status === TaskStatus.NotStarted)), Times.once());
mockProxy.verify(x => x.$onTaskRegistered(operationId), Times.once());
});
test('UpdateTask should successfully update the background task status', () => {
let taskInfo: sqlops.TaskProgressInfo = {
taskId: operationId,
status: TaskStatus.InProgress,
message: undefined,
};
mainThreadBackgroundTaskManagement.$updateTask(taskInfo);
taskService.verify(x => x.updateTask(It.is(t => t.status === TaskStatus.InProgress)), Times.once());
});
test('Canceling the task should notify the proxy', () => {
let taskInfo: sqlops.TaskProgressInfo = {
taskId: operationId,
status: TaskStatus.InProgress,
message: undefined,
};
let taskNode = new TaskNode('', '', '', operationId, undefined);
taskNode.status = TaskStatus.Canceling;
onTaskComplete.fire(taskNode);
mainThreadBackgroundTaskManagement.$updateTask(taskInfo);
mockProxy.verify(x => x.$onTaskCanceled(It.is(t => t === operationId)), Times.once());
});
});

View File

@@ -1149,7 +1149,7 @@ declare namespace Slick {
* @param row A row index. * @param row A row index.
* @param cell A column index. * @param cell A column index.
**/ **/
public setActiveCell(row: number, cell: number): void; public setActiveCell(row: number, cell: number, opt_editMode?: boolean, preClickModeOn?: boolean, suppressActiveCellChangedEvent?: boolean): void;
/** /**
* Sets CSS classes to specific grid cells by calling removeCellCssStyles(key) followed by addCellCssStyles(key, hash). key is name for this set of styles so you can reference it later - to modify it or remove it, for example. hash is a per-row-index, per-column-name nested hash of CSS classes to apply. * Sets CSS classes to specific grid cells by calling removeCellCssStyles(key) followed by addCellCssStyles(key, hash). key is name for this set of styles so you can reference it later - to modify it or remove it, for example. hash is a per-row-index, per-column-name nested hash of CSS classes to apply.

View File

@@ -81,6 +81,11 @@ export interface IColumnDefinition {
formatter?: (row: number, cell: any, value: any, columnDef: any, dataContext: any) => string; formatter?: (row: number, cell: any, value: any, columnDef: any, dataContext: any) => string;
isEditable?: boolean; isEditable?: boolean;
} }
export interface ISlickColumn<T> extends Slick.Column<T> {
isEditable?: boolean;
}
export interface IGridColumnDefinition { export interface IGridColumnDefinition {
id: string; id: string;
type: number; type: number;

View File

@@ -172,9 +172,9 @@ angular2-grid@2.0.6:
version "2.0.6" version "2.0.6"
resolved "https://registry.yarnpkg.com/angular2-grid/-/angular2-grid-2.0.6.tgz#01fe225dc13b2822370b6c61f9a6913b3a26f989" resolved "https://registry.yarnpkg.com/angular2-grid/-/angular2-grid-2.0.6.tgz#01fe225dc13b2822370b6c61f9a6913b3a26f989"
"angular2-slickgrid@git://github.com/Microsoft/angular2-slickgrid.git#1.3.11": "angular2-slickgrid@github:Microsoft/angular2-slickgrid#1.3.12":
version "1.3.10" version "1.3.12"
resolved "git://github.com/Microsoft/angular2-slickgrid.git#35f00750ef2f544b17744cc167c0ff7997c114b4" resolved "https://codeload.github.com/Microsoft/angular2-slickgrid/tar.gz/19aafe8888d2f2eb70aec858e4b86e3c1b7b3fc8"
ansi-colors@^1.0.1: ansi-colors@^1.0.1:
version "1.1.0" version "1.1.0"
@@ -5889,9 +5889,9 @@ slice-ansi@0.0.4:
version "0.0.4" version "0.0.4"
resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-0.0.4.tgz#edbf8903f66f7ce2f8eafd6ceed65e264c831b35" resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-0.0.4.tgz#edbf8903f66f7ce2f8eafd6ceed65e264c831b35"
"slickgrid@github:anthonydresser/SlickGrid#2.3.23": "slickgrid@github:anthonydresser/SlickGrid#2.3.25":
version "2.3.23" version "2.3.25"
resolved "https://codeload.github.com/anthonydresser/SlickGrid/tar.gz/a88c0c25fd6cbe01be86b3018f523987a198a6b6" resolved "https://codeload.github.com/anthonydresser/SlickGrid/tar.gz/5fa498a3df7ed671958bedeac1863c41352c7825"
dependencies: dependencies:
jquery ">=1.8.0" jquery ">=1.8.0"
jquery-ui ">=1.8.0" jquery-ui ">=1.8.0"