Compare commits

...

48 Commits

Author SHA1 Message Date
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
Aditya Bist
74c4b7311e Agent/proxy ui (#1880)
* finished basic proxy ui

* finished proxy UI and logic

* made changes to accomodate toolsservice side changes
2018-07-14 10:43:31 -07:00
Karl Burtram
713c74adfd Update version to 0.31.3 2018-07-14 10:41:07 -07:00
Karl Burtram
408a8a6f19 Edit Agent Job dialog updates (#1925)
* Fill in job name and description on edit

* Hook up update job request call

* Clean up table styles
2018-07-13 22:57:09 -07:00
Karl Burtram
e1485e49d3 Pick up 1.5.0-alpha.9 2018-07-13 22:56:13 -07:00
Matt Irvine
30b66934cd Enable custom delimiters when saving as CSV (#1928)
* Support custom delimiters for csv

* Run tsfmt
2018-07-13 18:12:57 -07:00
Matt Irvine
fd49c081c2 Fix uses of innerHtml when we could just set element text (#1919) 2018-07-13 15:24:24 -07:00
Madeline MacDonald
1327120024 Profiler view templates (#1915)
* Initial view template framework

* Removing some templates, reordering drop down

* Fixing comments and formatting

* Adding issue reference for commented code
2018-07-13 14:24:49 -07:00
Matt Irvine
d2b5043972 Render column titles as text not html in query results (#1923) 2018-07-13 14:23:50 -07:00
Karl Burtram
a0e55ea3fd Update config.json 2018-07-13 12:12:08 -07:00
Matt Irvine
1f32de29c1 Style SQL input box correctly when enabled/disabled (#1920) 2018-07-13 09:03:22 -07:00
Aditya Bist
12be06d682 Agent: dialog finishes (#1913)
* fixed crashes from job dialog and new step dialog group options UI

* added placeholder for retry counters

* fixed alert general UI

* fixed misc dialog errors

* localized all strings

* fixed create operator UI
2018-07-12 19:43:59 -07:00
Kevin Cunnane
27ca9b13f8 Fix #1916 Object explorer context object doesn't include database name (#1917) 2018-07-12 17:21:57 -07:00
Matt Irvine
be45905830 Add wizard sidebar navigation (#1911) 2018-07-12 11:15:56 -07:00
Karl Burtram
05d0a89655 Fix Job History scroll and resize issues (#1912) 2018-07-12 09:32:43 -07:00
Aditya Bist
3ba575dcd0 Agent - dialog finishes (#1910)
* fixed crashes from job dialog and new step dialog group options UI

* added placeholder for retry counters

* fixed alert general UI

* fixed misc dialog errors

* localized all strings
2018-07-11 23:08:23 -07:00
Karl Burtram
3e200b7f0f Handle resize message in Agent dashboard tab (#1908)
* Resize related changes WIP

* Resize table control better on resize

* Update DashboardTab to use inherited Disposable

* Set forceFitColumns to false
2018-07-11 13:50:58 -07:00
Aditya Bist
cbce1f7008 added parse syntax params to sqlops (#1906) 2018-07-11 13:26:51 -07:00
Kevin Cunnane
e99101447e Fixes #1856 Object Explorer needs Icons field for nodes separate from… (#1901)
* Fixes #1856 Object Explorer needs Icons field for nodes separate from type/subtype
- Adds in the concept of a themeable icon path which matches VSCode's implementation. This should help support theme-based overrides in the future
2018-07-11 11:24:35 -07:00
Karl Burtram
0ddb326e44 Update SQL Tools Service to 1.5.0-alpha.6 (#1897) 2018-07-11 10:25:18 -07:00
Leila Lali
460446a15c updated the icon for form container help (#1892) 2018-07-10 15:53:01 -07:00
Aditya Bist
4eea24997f Agent: Updated Alerts dialog UI (#1874)
* finished alert dialog UI

* removed unused import
2018-07-10 14:21:58 -07:00
Matt Irvine
0b1e9c7c66 Update form layout defaults to match design (#1878) 2018-07-10 13:45:36 -07:00
Kevin Cunnane
d51a7a9eb7 Extensibility: Context menu support in Object Explorer (#1883)
- Fixes #1867 context menu should be extensible
- Added context keys to support "when" conditions on the new extensions
- Fixes issue where actions like New Query, scripting show up even if these are not valid for the provider type or object type
- Fixed node expansion bug where rapid connect / expand / disconnect could break the app (fix in ObjectExplorerService.onNodeExpanded)
- Major change to how internal actions work. These cannot assume the context has non-serializable objects. Opened up some APIs to make this easier to handle.
- Fixed a number of existing bugs in internal actions.
  - Notably, DisconnectAction was adding a listener on each right-click on an active connection and never getting it disposed. This wasn't needed at all due to design changes.
  - Another bug fix is that the Manage action now correctly navigates to the DB dashboard for database-level connections. Before this it went to the server-level dashboard.

* Define API for context info
2018-07-10 12:23:47 -07:00
Karl Burtram
0f0b959e14 Update config.json 2018-07-10 09:53:00 -07:00
Leila Lali
b2ceb09e4d fixed some issues in table component and added tests (#1873)
* fixed some issues in table component  and added tests

* fixed the enter key handling in text area component
2018-07-09 14:40:21 -07:00
Anthony Dresser
53953f5cda bump slickgrid to fix focus issue (#1875) 2018-07-09 13:30:16 -07:00
Karl Burtram
fbd5e819a2 Edit Agent Alert updates (#1872)
* Set database name and severity when editing alert

* Add Operator and Proxy edit

* Add edit job hookup

* Edit WIP

* Additional edit alert updates

* Remove unused method
2018-07-09 10:47:50 -07:00
Matt Irvine
bdc391d376 Fix enter button behavior for wizards and dialogs (#1868) 2018-07-06 17:35:28 -07:00
Madeline MacDonald
6b618fb121 Updating keybindings (#1839)
* Changing key combos, and behavior for starting/stopping

* Updating keybindings

* Fixing mac keybindings

* Clear data when starting profiler from keyboard shortcut
2018-07-06 14:53:31 -07:00
144 changed files with 3444 additions and 1333 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

@@ -13,6 +13,11 @@ import { IAgentDialogData, AgentDialogMode } from '../interfaces';
const localize = nls.loadMessageBundle(); const localize = nls.loadMessageBundle();
export class AlertData implements IAgentDialogData { export class AlertData implements IAgentDialogData {
public static readonly AlertTypeSqlServerEventString: string = localize('alertData.DefaultAlertTypString', 'SQL Server event alert');
public static readonly AlertTypePerformanceConditionString: string = localize('alertDialog.PerformanceCondition', 'SQL Server performance condition alert');
public static readonly AlertTypeWmiEventString: string = localize('alertDialog.WmiEvent', 'WMI event alert');
public static readonly DefaultAlertTypeString: string = AlertData.AlertTypeSqlServerEventString;
ownerUri: string; ownerUri: string;
dialogMode: AgentDialogMode = AgentDialogMode.CREATE; dialogMode: AgentDialogMode = AgentDialogMode.CREATE;
id: number; id: number;
@@ -23,7 +28,7 @@ export class AlertData implements IAgentDialogData {
eventSource: string; eventSource: string;
hasNotification: number; hasNotification: number;
includeEventDescription: string; includeEventDescription: string;
isEnabled: boolean; isEnabled: boolean = true;
jobId: string; jobId: string;
jobName: string; jobName: string;
lastOccurrenceDate: string; lastOccurrenceDate: string;
@@ -36,7 +41,7 @@ export class AlertData implements IAgentDialogData {
databaseName: string; databaseName: string;
countResetDate: string; countResetDate: string;
categoryName: string; categoryName: string;
alertType: string; alertType: string = AlertData.DefaultAlertTypeString;
wmiEventNamespace: string; wmiEventNamespace: string;
wmiEventQuery: string; wmiEventQuery: string;
@@ -109,9 +114,19 @@ export class AlertData implements IAgentDialogData {
databaseName: this.databaseName, databaseName: this.databaseName,
countResetDate: this.countResetDate, countResetDate: this.countResetDate,
categoryName: this.categoryName, categoryName: this.categoryName,
alertType: sqlops.AlertType.sqlServerEvent, //this.alertType, alertType: AlertData.getAlertTypeFromString(this.alertType),
wmiEventNamespace: this.wmiEventNamespace, wmiEventNamespace: this.wmiEventNamespace,
wmiEventQuery: this.wmiEventQuery wmiEventQuery: this.wmiEventQuery
}; };
} }
private static getAlertTypeFromString(alertTypeString: string): sqlops.AlertType {
if (alertTypeString === AlertData.AlertTypePerformanceConditionString) {
return sqlops.AlertType.sqlServerPerformanceCondition;
} else if (alertTypeString === AlertData.AlertTypeWmiEventString) {
return sqlops.AlertType.wmiEvent;
} else {
return sqlops.AlertType.sqlServerEvent;
}
}
} }

View File

@@ -4,18 +4,22 @@
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
'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 { AgentUtils } from '../agentUtils'; import { AgentUtils } from '../agentUtils';
import { IAgentDialogData, AgentDialogMode } from '../interfaces'; import { IAgentDialogData, AgentDialogMode } from '../interfaces';
const localize = nls.loadMessageBundle();
export class JobData implements IAgentDialogData { export class JobData implements IAgentDialogData {
private readonly JobCompletionActionCondition_Always: string = 'When the job completes'; private readonly JobCompletionActionCondition_Always: string = localize('jobData.whenJobCompletes', 'When the job completes');
private readonly JobCompletionActionCondition_OnFailure: string = 'When the job fails'; private readonly JobCompletionActionCondition_OnFailure: string = localize('jobData.whenJobFails', 'When the job fails');
private readonly JobCompletionActionCondition_OnSuccess: string = 'When the job succeeds'; private readonly JobCompletionActionCondition_OnSuccess: string = localize('jobData.whenJobSucceeds', 'When the job succeeds');
// Error Messages // Error Messages
private readonly CreateJobErrorMessage_NameIsEmpty = 'Job name must be provided'; private readonly CreateJobErrorMessage_NameIsEmpty = localize('jobData.jobNameRequired', 'Job name must be provided');
private _ownerUri: string; private _ownerUri: string;
private _jobCategories: string[]; private _jobCategories: string[];
@@ -25,6 +29,7 @@ export class JobData implements IAgentDialogData {
public dialogMode: AgentDialogMode = AgentDialogMode.CREATE; public dialogMode: AgentDialogMode = AgentDialogMode.CREATE;
public name: string; public name: string;
public originalName: string;
public enabled: boolean = true; public enabled: boolean = true;
public description: string; public description: string;
public category: string; public category: string;
@@ -40,8 +45,21 @@ export class JobData implements IAgentDialogData {
public jobSchedules: sqlops.AgentJobScheduleInfo[]; public jobSchedules: sqlops.AgentJobScheduleInfo[];
public alerts: sqlops.AgentAlertInfo[]; public alerts: sqlops.AgentAlertInfo[];
constructor(ownerUri: string, private _agentService: sqlops.AgentServicesProvider = null) { constructor(
ownerUri: string,
jobInfo: sqlops.AgentJobInfo = undefined,
private _agentService: sqlops.AgentServicesProvider = undefined) {
this._ownerUri = ownerUri; this._ownerUri = ownerUri;
if (jobInfo) {
this.dialogMode = AgentDialogMode.EDIT;
this.name = jobInfo.name;
this.originalName = jobInfo.name;
this.owner = jobInfo.owner;
this.category = jobInfo.category;
this.description = jobInfo.description;
this.enabled = jobInfo.enabled;
}
} }
public get jobCategories(): string[] { public get jobCategories(): string[] {
@@ -92,7 +110,39 @@ export class JobData implements IAgentDialogData {
} }
public async save() { public async save() {
await this._agentService.createJob(this.ownerUri, { let jobInfo: sqlops.AgentJobInfo = this.toAgentJobInfo();
let result = this.dialogMode === AgentDialogMode.CREATE
? await this._agentService.createJob(this.ownerUri, jobInfo)
: await this._agentService.updateJob(this.ownerUri, this.originalName, jobInfo);
if (!result || !result.success) {
vscode.window.showErrorMessage(
localize('jobData.saveErrorMessage', "Job update failed '{0}'", result.errorMessage ? result.errorMessage : 'Unknown'));
}
}
public validate(): { valid: boolean, errorMessages: string[] } {
let validationErrors: string[] = [];
if (!(this.name && this.name.trim())) {
validationErrors.push(this.CreateJobErrorMessage_NameIsEmpty);
}
return {
valid: validationErrors.length === 0,
errorMessages: validationErrors
};
}
public addJobSchedule(schedule: sqlops.AgentJobScheduleInfo) {
let existingSchedule = this.jobSchedules.find(item => item.name === schedule.name);
if (!existingSchedule) {
this.jobSchedules.push(schedule);
}
}
public toAgentJobInfo(): sqlops.AgentJobInfo {
return {
name: this.name, name: this.name,
owner: this.owner, owner: this.owner,
description: this.description, description: this.description,
@@ -122,30 +172,6 @@ export class JobData implements IAgentDialogData {
lastRun: '', lastRun: '',
nextRun: '', nextRun: '',
jobId: '' jobId: ''
}).then(result => {
if (!result.success) {
console.info(result.errorMessage);
}
});
}
public validate(): { valid: boolean, errorMessages: string[] } {
let validationErrors: string[] = [];
if (!(this.name && this.name.trim())) {
validationErrors.push(this.CreateJobErrorMessage_NameIsEmpty);
}
return {
valid: validationErrors.length === 0,
errorMessages: validationErrors
}; };
} }
public addJobSchedule(schedule: sqlops.AgentJobScheduleInfo) {
let existingSchedule = this.jobSchedules.find(item => item.name === schedule.name);
if (!existingSchedule) {
this.jobSchedules.push(schedule);
}
}
} }

View File

@@ -29,8 +29,14 @@ export class OperatorData implements IAgentDialogData {
weekdayPagerStartTime: string; weekdayPagerStartTime: string;
weekdayPagerEndTime: string; weekdayPagerEndTime: string;
constructor(ownerUri:string) { constructor(ownerUri:string, operatorInfo: sqlops.AgentOperatorInfo) {
this.ownerUri = ownerUri; this.ownerUri = ownerUri;
if (operatorInfo) {
this.dialogMode = AgentDialogMode.EDIT;
this.name = operatorInfo.name;
this.enabled = operatorInfo.enabled;
}
} }
public async initialize() { public async initialize() {

View File

@@ -19,8 +19,14 @@ export class ProxyData implements IAgentDialogData {
credentialId: number; credentialId: number;
isEnabled: boolean; isEnabled: boolean;
constructor(ownerUri:string) { constructor(ownerUri:string, proxyInfo: sqlops.AgentProxyInfo) {
this.ownerUri = ownerUri; this.ownerUri = ownerUri;
if (proxyInfo) {
this.accountName = proxyInfo.accountName;
this.credentialName = proxyInfo.credentialName;
this.description = proxyInfo.description;
}
} }
public async initialize() { public async initialize() {

View File

@@ -10,6 +10,8 @@ import * as sqlops from 'sqlops';
import { AgentDialog } from './agentDialog'; import { AgentDialog } from './agentDialog';
import { AgentUtils } from '../agentUtils'; import { AgentUtils } from '../agentUtils';
import { AlertData } from '../data/alertData'; import { AlertData } from '../data/alertData';
import { OperatorDialog } from './operatorDialog';
import { JobDialog } from './jobDialog';
const localize = nls.loadMessageBundle(); const localize = nls.loadMessageBundle();
@@ -21,6 +23,7 @@ export class AlertDialog extends AgentDialog<AlertData> {
private static readonly GeneralTabText: string = localize('alertDialog.General', 'General'); private static readonly GeneralTabText: string = localize('alertDialog.General', 'General');
private static readonly ResponseTabText: string = localize('alertDialog.Response', 'Response'); private static readonly ResponseTabText: string = localize('alertDialog.Response', 'Response');
private static readonly OptionsTabText: string = localize('alertDialog.Options', 'Options'); private static readonly OptionsTabText: string = localize('alertDialog.Options', 'Options');
private static readonly EventAlertText: string = localize('alertDialog.eventAlert', 'Event alert definition');
// General tab strings // General tab strings
private static readonly NameLabel: string = localize('alertDialog.Name', 'Name'); private static readonly NameLabel: string = localize('alertDialog.Name', 'Name');
@@ -31,9 +34,6 @@ export class AlertDialog extends AgentDialog<AlertData> {
private static readonly SeverityLabel: string = localize('alertDialog.Severity', 'Severity'); private static readonly SeverityLabel: string = localize('alertDialog.Severity', 'Severity');
private static readonly RaiseIfMessageContainsLabel: string = localize('alertDialog.RaiseAlertContains', 'Raise alert when message contains'); private static readonly RaiseIfMessageContainsLabel: string = localize('alertDialog.RaiseAlertContains', 'Raise alert when message contains');
private static readonly MessageTextLabel: string = localize('alertDialog.MessageText', 'Message text'); private static readonly MessageTextLabel: string = localize('alertDialog.MessageText', 'Message text');
private static readonly AlertTypeSqlServerEventString: string = localize('alertDialog.SqlServerEventAlert', 'SQL Server event alert');
private static readonly AlertTypePerformanceConditionString: string = localize('alertDialog.PerformanceCondition', 'SQL Server performance condition alert');
private static readonly AlertTypeWmiEventString: string = localize('alertDialog.WmiEvent', 'WMI event alert');
private static readonly AlertSeverity001Label: string = localize('alertDialog.Severity001', '001 - Miscellaneous System Information'); private static readonly AlertSeverity001Label: string = localize('alertDialog.Severity001', '001 - Miscellaneous System Information');
private static readonly AlertSeverity002Label: string = localize('alertDialog.Severity002', '002 - Reserved'); private static readonly AlertSeverity002Label: string = localize('alertDialog.Severity002', '002 - Reserved');
private static readonly AlertSeverity003Label: string = localize('alertDialog.Severity003', '003 - Reserved'); private static readonly AlertSeverity003Label: string = localize('alertDialog.Severity003', '003 - Reserved');
@@ -59,11 +59,13 @@ export class AlertDialog extends AgentDialog<AlertData> {
private static readonly AlertSeverity023Label: string = localize('alertDialog.Severity023', '023 - Fatal Error: Database Integrity Suspect'); private static readonly AlertSeverity023Label: string = localize('alertDialog.Severity023', '023 - Fatal Error: Database Integrity Suspect');
private static readonly AlertSeverity024Label: string = localize('alertDialog.Severity024', '024 - Fatal Error: Hardware Error'); private static readonly AlertSeverity024Label: string = localize('alertDialog.Severity024', '024 - Fatal Error: Hardware Error');
private static readonly AlertSeverity025Label: string = localize('alertDialog.Severity025', '025 - Fatal Error'); private static readonly AlertSeverity025Label: string = localize('alertDialog.Severity025', '025 - Fatal Error');
private static readonly AllDatabases: string = localize('alertDialog.AllDatabases', '<all databases>');
private static readonly AlertTypes: string[] = [ private static readonly AlertTypes: string[] = [
AlertDialog.AlertTypeSqlServerEventString, AlertData.AlertTypeSqlServerEventString,
AlertDialog.AlertTypePerformanceConditionString, // Disabled until next release
AlertDialog.AlertTypeWmiEventString // AlertData.AlertTypePerformanceConditionString,
// AlertData.AlertTypeWmiEventString
]; ];
private static readonly AlertSeverities: string[] = [ private static readonly AlertSeverities: string[] = [
@@ -124,6 +126,10 @@ export class AlertDialog extends AgentDialog<AlertData> {
private severityDropDown: sqlops.DropDownComponent; private severityDropDown: sqlops.DropDownComponent;
private databaseDropDown: sqlops.DropDownComponent; private databaseDropDown: sqlops.DropDownComponent;
private enabledCheckBox: sqlops.CheckBoxComponent; private enabledCheckBox: sqlops.CheckBoxComponent;
private errorNumberRadioButton: sqlops.RadioButtonComponent;
private severityRadioButton: sqlops.RadioButtonComponent;
private errorNumberTextBox: sqlops.InputBoxComponent;
private raiseAlertMessageCheckBox: sqlops.CheckBoxComponent; private raiseAlertMessageCheckBox: sqlops.CheckBoxComponent;
private raiseAlertMessageTextBox: sqlops.InputBoxComponent; private raiseAlertMessageTextBox: sqlops.InputBoxComponent;
@@ -142,58 +148,115 @@ export class AlertDialog extends AgentDialog<AlertData> {
private delayMinutesTextBox: sqlops.InputBoxComponent; private delayMinutesTextBox: sqlops.InputBoxComponent;
private delaySecondsTextBox: sqlops.InputBoxComponent; private delaySecondsTextBox: sqlops.InputBoxComponent;
constructor(ownerUri: string, alertInfo: sqlops.AgentAlertInfo = null) { private jobs: string[];
private databases: string[];
constructor(ownerUri: string, alertInfo: sqlops.AgentAlertInfo = undefined, jobs: string[]) {
super(ownerUri, super(ownerUri,
new AlertData(ownerUri, alertInfo), new AlertData(ownerUri, alertInfo),
alertInfo ? AlertDialog.EditDialogTitle : AlertDialog.CreateDialogTitle); alertInfo ? AlertDialog.EditDialogTitle : AlertDialog.CreateDialogTitle);
this.jobs = jobs;
} }
protected async initializeDialog(dialog: sqlops.window.modelviewdialog.Dialog) { protected async initializeDialog(dialog: sqlops.window.modelviewdialog.Dialog) {
let databases = await AgentUtils.getDatabases(this.ownerUri); this.databases = await AgentUtils.getDatabases(this.ownerUri);
this.databases.unshift(AlertDialog.AllDatabases);
this.generalTab = sqlops.window.modelviewdialog.createTab(AlertDialog.GeneralTabText); this.generalTab = sqlops.window.modelviewdialog.createTab(AlertDialog.GeneralTabText);
this.responseTab = sqlops.window.modelviewdialog.createTab(AlertDialog.ResponseTabText); this.responseTab = sqlops.window.modelviewdialog.createTab(AlertDialog.ResponseTabText);
this.optionsTab = sqlops.window.modelviewdialog.createTab(AlertDialog.OptionsTabText); this.optionsTab = sqlops.window.modelviewdialog.createTab(AlertDialog.OptionsTabText);
this.initializeGeneralTab(databases); this.initializeGeneralTab(this.databases, dialog);
this.initializeResponseTab(); this.initializeResponseTab();
this.initializeOptionsTab(); this.initializeOptionsTab();
dialog.content = [this.generalTab, this.responseTab, this.optionsTab]; dialog.content = [this.generalTab, this.responseTab, this.optionsTab];
} }
private initializeGeneralTab(databases: string[]) { private initializeGeneralTab(databases: string[], dialog: sqlops.window.modelviewdialog.Dialog) {
this.generalTab.registerContent(async view => { this.generalTab.registerContent(async view => {
// create controls
this.nameTextBox = view.modelBuilder.inputBox().component(); this.nameTextBox = view.modelBuilder.inputBox().component();
this.nameTextBox.required = true;
this.nameTextBox.onTextChanged(() => {
if (this.nameTextBox.value.length > 0) {
dialog.okButton.enabled = true;
} else {
dialog.okButton.enabled = false;
}
});
this.enabledCheckBox = view.modelBuilder.checkBox() this.enabledCheckBox = view.modelBuilder.checkBox()
.withProperties({ .withProperties({
label: AlertDialog.EnabledCheckboxLabel label: AlertDialog.EnabledCheckboxLabel
}).component(); }).component();
this.enabledCheckBox.checked = true;
this.databaseDropDown = view.modelBuilder.dropDown() this.databaseDropDown = view.modelBuilder.dropDown()
.withProperties({ .withProperties({
value: databases[0], value: databases[0],
values: databases values: databases,
width: '100%'
}).component(); }).component();
this.typeDropDown = view.modelBuilder.dropDown() this.typeDropDown = view.modelBuilder.dropDown()
.withProperties({ .withProperties({
value: AlertDialog.AlertTypes[0], value: '',
values: AlertDialog.AlertTypes values: AlertDialog.AlertTypes,
width: '100%'
}).component(); }).component();
this.severityRadioButton = view.modelBuilder.radioButton()
.withProperties({
value: 'serverity',
name: 'alertTypeOptions',
label: AlertDialog.SeverityLabel,
checked: true
}).component();
this.severityRadioButton.checked = true;
this.severityDropDown = view.modelBuilder.dropDown() this.severityDropDown = view.modelBuilder.dropDown()
.withProperties({ .withProperties({
value: AlertDialog.AlertSeverities[0], value: AlertDialog.AlertSeverities[0],
values: AlertDialog.AlertSeverities values: AlertDialog.AlertSeverities,
width: '100%'
}).component(); }).component();
this.errorNumberRadioButton = view.modelBuilder.radioButton()
.withProperties({
value: 'errorNumber',
name: 'alertTypeOptions',
label: AlertDialog.ErrorNumberLabel
}).component();
this.errorNumberTextBox = view.modelBuilder.inputBox()
.withProperties({
width: '100%'
})
.component();
this.errorNumberTextBox.enabled = false;
this.errorNumberRadioButton.onDidClick(() => {
this.errorNumberTextBox.enabled = true;
this.severityDropDown.enabled = false;
});
this.severityRadioButton.onDidClick(() => {
this.errorNumberTextBox.enabled = false;
this.severityDropDown.enabled = true;
});
this.raiseAlertMessageCheckBox = view.modelBuilder.checkBox() this.raiseAlertMessageCheckBox = view.modelBuilder.checkBox()
.withProperties({ .withProperties({
label: AlertDialog.RaiseIfMessageContainsLabel label: AlertDialog.RaiseIfMessageContainsLabel
}).component(); }).component();
this.raiseAlertMessageTextBox = view.modelBuilder.inputBox().component(); this.raiseAlertMessageTextBox = view.modelBuilder.inputBox().component();
this.raiseAlertMessageTextBox.enabled = false;
this.raiseAlertMessageCheckBox.onChanged(() => {
this.raiseAlertMessageTextBox.enabled = this.raiseAlertMessageCheckBox.checked;
});
let formModel = view.modelBuilder.formContainer() let formModel = view.modelBuilder.formContainer()
.withFormItems([{ .withFormItems([{
@@ -206,24 +269,61 @@ export class AlertDialog extends AgentDialog<AlertData> {
component: this.typeDropDown, component: this.typeDropDown,
title: AlertDialog.TypeLabel title: AlertDialog.TypeLabel
}, { }, {
component: this.databaseDropDown, components: [{
title: AlertDialog.DatabaseLabel component: this.databaseDropDown,
}, { title: AlertDialog.DatabaseLabel
component: this.severityDropDown, },
title: AlertDialog.SeverityLabel {
}, { component: this.severityRadioButton,
component: this.raiseAlertMessageCheckBox, title: ''
title: '' },
}, { {
component: this.raiseAlertMessageTextBox, component: this.severityDropDown,
title: AlertDialog.MessageTextLabel title: ''
},
{
component: this.errorNumberRadioButton,
title: ''
},
{
component: this.errorNumberTextBox,
title: ''
},
{
component: this.raiseAlertMessageCheckBox,
title: ''
}, {
component: this.raiseAlertMessageTextBox,
title: AlertDialog.MessageTextLabel
}],
title: AlertDialog.EventAlertText
} }
]).withLayout({ width: '100%' }).component(); ]).withLayout({ width: '100%' }).component();
await view.initializeModel(formModel); await view.initializeModel(formModel);
// initialize control values
this.nameTextBox.value = this.model.name; this.nameTextBox.value = this.model.name;
this.raiseAlertMessageTextBox.value = this.model.eventDescriptionKeyword;
this.typeDropDown.value = this.model.alertType;
this.enabledCheckBox.checked = this.model.isEnabled; this.enabledCheckBox.checked = this.model.isEnabled;
if (this.model.messageId > 0) {
this.errorNumberRadioButton.checked = true;
this.errorNumberTextBox.value = this.model.messageId.toString();
}
if (this.model.severity > 0) {
this.severityRadioButton.checked = true;
this.severityDropDown.value = this.severityDropDown.values[this.model.severity-1];
}
if (this.model.databaseName) {
let idx = this.databases.indexOf(this.model.databaseName);
if (idx >= 0) {
this.databaseDropDown.value = this.databases[idx];
}
}
}); });
} }
@@ -234,12 +334,38 @@ export class AlertDialog extends AgentDialog<AlertData> {
label: AlertDialog.ExecuteJobCheckBoxLabel label: AlertDialog.ExecuteJobCheckBoxLabel
}).component(); }).component();
this.executeJobTextBox = view.modelBuilder.inputBox().component(); this.executeJobTextBox = view.modelBuilder.inputBox()
.withProperties({ width: 375 })
.component();
this.executeJobTextBox.enabled = false;
this.newJobButton = view.modelBuilder.button().withProperties({ this.newJobButton = view.modelBuilder.button().withProperties({
label: AlertDialog.NewJobButtonLabel, label: AlertDialog.NewJobButtonLabel,
width: 80 width: 80
}).component(); }).component();
this.newJobButton.enabled = false;
this.newJobButton.onDidClick(() => {
let jobDialog = new JobDialog(this.ownerUri);
jobDialog.openDialog();
});
this.executeJobCheckBox.onChanged(() => {
if (this.executeJobCheckBox.checked) {
this.executeJobTextBox.enabled = true;
this.newJobButton.enabled = true;
} else {
this.executeJobTextBox.enabled = false;
this.newJobButton.enabled = false;
}
});
let executeJobContainer = view.modelBuilder.formContainer()
.withFormItems([{
component: this.executeJobTextBox,
title: AlertDialog.ExecuteJobTextBoxLabel
}, {
component: this.newJobButton,
title: AlertDialog.NewJobButtonLabel
}], { componentWidth: '100%'}).component();
this.notifyOperatorsCheckBox = view.modelBuilder.checkBox() this.notifyOperatorsCheckBox = view.modelBuilder.checkBox()
.withProperties({ .withProperties({
@@ -254,32 +380,57 @@ export class AlertDialog extends AgentDialog<AlertData> {
AlertDialog.OperatorPagerColumnLabel AlertDialog.OperatorPagerColumnLabel
], ],
data: [], data: [],
height: 500 height: 500,
width: 375
}).component(); }).component();
this.newOperatorButton = view.modelBuilder.button().withProperties({ this.newOperatorButton = view.modelBuilder.button().withProperties({
label: this.newOperatorButton, label: AlertDialog.NewOperatorButtonLabel,
width: 80 width: 80
}).component(); }).component();
this.operatorsTable.enabled = false;
this.newOperatorButton.enabled = false;
this.newOperatorButton.onDidClick(() => {
let operatorDialog = new OperatorDialog(this.ownerUri);
operatorDialog.openDialog();
});
this.notifyOperatorsCheckBox.onChanged(() => {
if (this.notifyOperatorsCheckBox.checked) {
this.operatorsTable.enabled = true;
this.newOperatorButton.enabled = true;
} else {
this.operatorsTable.enabled = false;
this.newOperatorButton.enabled = false;
}
});
let notifyOperatorContainer = view.modelBuilder.formContainer()
.withFormItems([{
component: this.operatorsTable,
title: AlertDialog.OperatorListLabel
}, {
component: this.newOperatorButton,
title: ''
}], { componentWidth: '100%'}).component();
let formModel = view.modelBuilder.formContainer() let formModel = view.modelBuilder.formContainer()
.withFormItems([{ .withFormItems([{
component: this.executeJobCheckBox, component: this.executeJobCheckBox,
title: '' title: ''
}, { }, {
component: this.executeJobTextBox, component: executeJobContainer,
title: AlertDialog.ExecuteJobTextBoxLabel title: ''
}, {
component: this.newJobButton,
title: AlertDialog.NewJobButtonLabel
}, { }, {
component: this.notifyOperatorsCheckBox, component: this.notifyOperatorsCheckBox,
title: '' title: ''
}, { }, {
component: this.operatorsTable, component: notifyOperatorContainer,
title: AlertDialog.OperatorListLabel, title: ''
actions: [this.newOperatorButton] }])
}]).withLayout({ width: '100%' }).component(); .withLayout({ width: '100%' }).component();
await view.initializeModel(formModel); await view.initializeModel(formModel);
}); });
@@ -300,9 +451,19 @@ export class AlertDialog extends AgentDialog<AlertData> {
this.additionalMessageTextBox = view.modelBuilder.inputBox().component(); this.additionalMessageTextBox = view.modelBuilder.inputBox().component();
this.delayMinutesTextBox = view.modelBuilder.inputBox().component(); this.delayMinutesTextBox = view.modelBuilder.inputBox()
.withProperties({
inputType: 'number',
placeHolder: 0
})
.component();
this.delaySecondsTextBox = view.modelBuilder.inputBox().component(); this.delaySecondsTextBox = view.modelBuilder.inputBox()
.withProperties({
inputType: 'number',
placeHolder: 0
})
.component();
let formModel = view.modelBuilder.formContainer() let formModel = view.modelBuilder.formContainer()
.withFormItems([{ .withFormItems([{
@@ -343,13 +504,25 @@ export class AlertDialog extends AgentDialog<AlertData> {
this.model.isEnabled = this.enabledCheckBox.checked; this.model.isEnabled = this.enabledCheckBox.checked;
this.model.alertType = this.getDropdownValue(this.typeDropDown); this.model.alertType = this.getDropdownValue(this.typeDropDown);
this.model.databaseName = this.getDropdownValue(this.databaseDropDown); let databaseName = this.getDropdownValue(this.databaseDropDown);
this.model.severity = this.getSeverityNumber(); this.model.databaseName = (databaseName !== AlertDialog.AllDatabases) ? databaseName : undefined;
this.model.messageId = undefined;
let raiseIfError = this.raiseAlertMessageCheckBox.checked; if (this.severityRadioButton.checked) {
if (raiseIfError) { this.model.severity = this.getSeverityNumber();
let messageText = this.raiseAlertMessageTextBox.value; this.model.messageId = 0;
} else {
this.model.severity = 0;
this.model.messageId = +this.errorNumberTextBox.value;
} }
if (this.raiseAlertMessageCheckBox.checked) {
this.model.eventDescriptionKeyword = this.raiseAlertMessageTextBox.value;
} else {
this.model.eventDescriptionKeyword = '';
}
let minutes = this.delayMinutesTextBox.value ? +this.delayMinutesTextBox.value : 0;
let seconds = this.delaySecondsTextBox.value ? +this.delaySecondsTextBox : 0;
this.model.delayBetweenResponses = minutes + seconds;
} }
} }

View File

@@ -3,6 +3,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.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
'use strict'; 'use strict';
import * as nls from 'vscode-nls';
import * as sqlops from 'sqlops'; import * as sqlops from 'sqlops';
import { JobData } from '../data/jobData'; import { JobData } from '../data/jobData';
import { JobStepDialog } from './jobStepDialog'; import { JobStepDialog } from './jobStepDialog';
@@ -10,50 +11,57 @@ import { PickScheduleDialog } from './pickScheduleDialog';
import { AlertDialog } from './alertDialog'; import { AlertDialog } from './alertDialog';
import { AgentDialog } from './agentDialog'; import { AgentDialog } from './agentDialog';
const localize = nls.loadMessageBundle();
export class JobDialog extends AgentDialog<JobData> { export class JobDialog extends AgentDialog<JobData> {
// TODO: localize // TODO: localize
// Top level // Top level
private static readonly DialogTitle: string = 'New Job'; private static readonly CreateDialogTitle: string = localize('jobDialog.newJob', 'New Job');
private readonly GeneralTabText: string = 'General'; private static readonly EditDialogTitle: string = localize('jobDialog.editJob', 'Edit Job');
private readonly StepsTabText: string = 'Steps'; private readonly GeneralTabText: string = localize('jobDialog.general', 'General');
private readonly SchedulesTabText: string = 'Schedules'; private readonly StepsTabText: string = localize('jobDialog.steps', 'Steps');
private readonly AlertsTabText: string = 'Alerts'; private readonly SchedulesTabText: string = localize('jobDialog.schedules', 'Schedules');
private readonly NotificationsTabText: string = 'Notifications'; private readonly AlertsTabText: string = localize('jobDialog.alerts', 'Alerts');
private readonly NotificationsTabText: string = localize('jobDialog.notifications', 'Notifications');
private readonly BlankJobNameErrorText: string = localize('jobDialog.blankJobNameError', 'The name of the job cannot be blank.');
// General tab strings // General tab strings
private readonly NameTextBoxLabel: string = 'Name'; private readonly NameTextBoxLabel: string = localize('jobDialog.name', 'Name');
private readonly OwnerTextBoxLabel: string = 'Owner'; private readonly OwnerTextBoxLabel: string = localize('jobDialog.owner', 'Owner');
private readonly CategoryDropdownLabel: string = 'Category'; private readonly CategoryDropdownLabel: string = localize('jobDialog.category', 'Category');
private readonly DescriptionTextBoxLabel: string = 'Description'; private readonly DescriptionTextBoxLabel: string = localize('jobDialog.description', 'Description');
private readonly EnabledCheckboxLabel: string = 'Enabled'; private readonly EnabledCheckboxLabel: string = localize('jobDialog.enabled', 'Enabled');
// Steps tab strings // Steps tab strings
private readonly JobStepsTopLabelString: string = 'Job step list'; private readonly JobStepsTopLabelString: string = localize('jobDialog.jobStepList', 'Job step list');
private readonly StepsTable_StepColumnString: string = 'Step'; private readonly StepsTable_StepColumnString: string = localize('jobDialog.step', 'Step');
private readonly StepsTable_NameColumnString: string = 'Name'; private readonly StepsTable_NameColumnString: string = localize('jobDialog.name', 'Name');
private readonly StepsTable_TypeColumnString: string = 'Type'; private readonly StepsTable_TypeColumnString: string = localize('jobDialog.type', 'Type');
private readonly StepsTable_SuccessColumnString: string = 'On Success'; private readonly StepsTable_SuccessColumnString: string = localize('jobDialog.onSuccess', 'On Success');
private readonly StepsTable_FailureColumnString: string = 'On Failure'; private readonly StepsTable_FailureColumnString: string = localize('jobDialog.onFailure', 'On Failure');
private readonly NewStepButtonString: string = 'New...'; private readonly NewStepButtonString: string = localize('jobDialog.new', 'New...');
private readonly InsertStepButtonString: string = 'Insert...'; private readonly EditStepButtonString: string = localize('jobDialog.edit', 'Edit');
private readonly EditStepButtonString: string = 'Edit'; private readonly DeleteStepButtonString: string = localize('jobDialog.delete', 'Delete');
private readonly DeleteStepButtonString: string = 'Delete'; private readonly MoveStepUpButtonString: string = localize('jobDialog.moveUp', 'Move Step Up');
private readonly MoveStepDownButtonString: string = localize('jobDialog.moveDown', 'Move Step Up');
// Notifications tab strings // Notifications tab strings
private readonly NotificationsTabTopLabelString: string = 'Actions to perform when the job completes'; private readonly NotificationsTabTopLabelString: string = localize('jobDialog.notificationsTabTop', 'Actions to perform when the job completes');
private readonly EmailCheckBoxString: string = 'Email'; private readonly EmailCheckBoxString: string = localize('jobDialog.email', 'Email');
private readonly PagerCheckBoxString: string = 'Page'; private readonly PagerCheckBoxString: string = localize('jobDialog.page', 'Page');
private readonly EventLogCheckBoxString: string = 'Write to the Windows Application event log'; private readonly EventLogCheckBoxString: string = localize('jobDialog.eventLogCheckBoxLabel', 'Write to the Windows Application event log');
private readonly DeleteJobCheckBoxString: string = 'Automatically delete job'; private readonly DeleteJobCheckBoxString: string = localize('jobDialog.deleteJobLabel', 'Automatically delete job');
// Schedules tab strings // Schedules tab strings
private readonly SchedulesTopLabelString: string = 'Schedules list'; private readonly SchedulesTopLabelString: string = localize('jobDialog.schedulesaLabel', 'Schedules list');
private readonly PickScheduleButtonString: string = 'Pick Schedule'; private readonly PickScheduleButtonString: string = localize('jobDialog.pickSchedule', 'Pick Schedule');
private readonly ScheduleNameLabelString: string = localize('jobDialog.scheduleNameLabel', 'Schedule Name');
// Alerts tab strings // Alerts tab strings
private readonly AlertsTopLabelString: string = 'Alerts list'; private readonly AlertsTopLabelString: string = localize('jobDialog.alertsList', 'Alerts list');
private readonly NewAlertButtonString: string = 'New Alert'; private readonly NewAlertButtonString: string = localize('jobDialog.newAlert', 'New Alert');
private readonly AlertNameLabelString: string = localize('jobDialog.alertNameLabel', 'Alert Name');
// UI Components // UI Components
private generalTab: sqlops.window.modelviewdialog.DialogTab; private generalTab: sqlops.window.modelviewdialog.DialogTab;
@@ -72,7 +80,8 @@ export class JobDialog extends AgentDialog<JobData> {
// Steps tab controls // Steps tab controls
private stepsTable: sqlops.TableComponent; private stepsTable: sqlops.TableComponent;
private newStepButton: sqlops.ButtonComponent; private newStepButton: sqlops.ButtonComponent;
private insertStepButton: sqlops.ButtonComponent; private moveStepUpButton: sqlops.ButtonComponent;
private moveStepDownButton: sqlops.ButtonComponent;
private editStepButton: sqlops.ButtonComponent; private editStepButton: sqlops.ButtonComponent;
private deleteStepButton: sqlops.ButtonComponent; private deleteStepButton: sqlops.ButtonComponent;
@@ -97,8 +106,11 @@ export class JobDialog extends AgentDialog<JobData> {
private alertsTable: sqlops.TableComponent; private alertsTable: sqlops.TableComponent;
private newAlertButton: sqlops.ButtonComponent; private newAlertButton: sqlops.ButtonComponent;
constructor(ownerUri: string) { constructor(ownerUri: string, jobInfo: sqlops.AgentJobInfo = undefined) {
super(ownerUri, new JobData(ownerUri), JobDialog.DialogTitle); super(
ownerUri,
new JobData(ownerUri, jobInfo),
jobInfo ? JobDialog.EditDialogTitle : JobDialog.CreateDialogTitle);
} }
protected async initializeDialog() { protected async initializeDialog() {
@@ -129,6 +141,12 @@ export class JobDialog extends AgentDialog<JobData> {
private initializeGeneralTab() { private initializeGeneralTab() {
this.generalTab.registerContent(async view => { this.generalTab.registerContent(async view => {
this.nameTextBox = view.modelBuilder.inputBox().component(); this.nameTextBox = view.modelBuilder.inputBox().component();
this.nameTextBox.required = true;
this.nameTextBox.onTextChanged(() => {
if (this.nameTextBox.value && this.nameTextBox.value.length > 0) {
this.dialog.message = null;
}
});
this.ownerTextBox = view.modelBuilder.inputBox().component(); this.ownerTextBox = view.modelBuilder.inputBox().component();
this.categoryDropdown = view.modelBuilder.dropDown().component(); this.categoryDropdown = view.modelBuilder.dropDown().component();
this.descriptionTextBox = view.modelBuilder.inputBox().withProperties({ this.descriptionTextBox = view.modelBuilder.inputBox().withProperties({
@@ -159,11 +177,18 @@ export class JobDialog extends AgentDialog<JobData> {
await view.initializeModel(formModel); await view.initializeModel(formModel);
this.nameTextBox.value = this.model.name;
this.ownerTextBox.value = this.model.defaultOwner; this.ownerTextBox.value = this.model.defaultOwner;
this.categoryDropdown.values = this.model.jobCategories; this.categoryDropdown.values = this.model.jobCategories;
this.categoryDropdown.value = this.model.jobCategories[0];
let idx: number = undefined;
if (this.model.category && this.model.category !== '') {
idx = this.model.jobCategories.indexOf(this.model.category);
}
this.categoryDropdown.value = this.model.jobCategories[idx > 0 ? idx : 0];
this.enabledCheckBox.checked = this.model.enabled; this.enabledCheckBox.checked = this.model.enabled;
this.descriptionTextBox.value = ''; this.descriptionTextBox.value = this.model.description;
}); });
} }
@@ -179,24 +204,38 @@ export class JobDialog extends AgentDialog<JobData> {
this.StepsTable_FailureColumnString this.StepsTable_FailureColumnString
], ],
data: [], data: [],
height: 800 height: 430
}).component(); }).component();
this.moveStepUpButton = view.modelBuilder.button()
.withProperties({
label: this.MoveStepUpButtonString,
width: 80
}).component();
this.moveStepDownButton = view.modelBuilder.button()
.withProperties({
label: this.MoveStepDownButtonString,
width: 80
}).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
}).component(); }).component();
this.newStepButton.onDidClick((e)=>{ this.newStepButton.onDidClick((e)=>{
let stepDialog = new JobStepDialog(this.model.ownerUri, '', '', 1, this.model); if (this.nameTextBox.value && this.nameTextBox.value.length > 0) {
stepDialog.openNewStepDialog(); let stepDialog = new JobStepDialog(this.model.ownerUri, this.nameTextBox.value, '' , 1, this.model);
stepDialog.openNewStepDialog();
} else {
this.dialog.message = { text: this.BlankJobNameErrorText };
}
}); });
this.insertStepButton = view.modelBuilder.button().withProperties({
label: this.InsertStepButtonString,
width: 80
}).component();
this.editStepButton = view.modelBuilder.button().withProperties({ this.editStepButton = view.modelBuilder.button().withProperties({
label: this.EditStepButtonString, label: this.EditStepButtonString,
width: 80 width: 80
@@ -211,7 +250,7 @@ export class JobDialog extends AgentDialog<JobData> {
.withFormItems([{ .withFormItems([{
component: this.stepsTable, component: this.stepsTable,
title: this.JobStepsTopLabelString, title: this.JobStepsTopLabelString,
actions: [this.newStepButton, this.insertStepButton, this.editStepButton, this.deleteStepButton] actions: [this.moveStepUpButton, this.moveStepDownButton, this.newStepButton, this.editStepButton, this.deleteStepButton]
}]).withLayout({ width: '100%' }).component(); }]).withLayout({ width: '100%' }).component();
await view.initializeModel(formModel); await view.initializeModel(formModel);
}); });
@@ -222,10 +261,10 @@ export class JobDialog extends AgentDialog<JobData> {
this.alertsTable = view.modelBuilder.table() this.alertsTable = view.modelBuilder.table()
.withProperties({ .withProperties({
columns: [ columns: [
'Alert Name' this.AlertNameLabelString
], ],
data: [], data: [],
height: 600, height: 430,
width: 400 width: 400
}).component(); }).component();
@@ -235,7 +274,7 @@ export class JobDialog extends AgentDialog<JobData> {
}).component(); }).component();
this.newAlertButton.onDidClick((e)=>{ this.newAlertButton.onDidClick((e)=>{
let alertDialog = new AlertDialog(this.model.ownerUri); let alertDialog = new AlertDialog(this.model.ownerUri, null, []);
alertDialog.onSuccess((dialogModel) => { alertDialog.onSuccess((dialogModel) => {
}); });
alertDialog.openDialog(); alertDialog.openDialog();
@@ -257,11 +296,11 @@ export class JobDialog extends AgentDialog<JobData> {
this.schedulesTable = view.modelBuilder.table() this.schedulesTable = view.modelBuilder.table()
.withProperties({ .withProperties({
columns: [ columns: [
'Schedule Name' this.ScheduleNameLabelString
], ],
data: [], data: [],
height: 600, height: 430,
width: 400 width: 420
}).component(); }).component();
this.pickScheduleButton = view.modelBuilder.button().withProperties({ this.pickScheduleButton = view.modelBuilder.button().withProperties({
@@ -361,21 +400,23 @@ export class JobDialog extends AgentDialog<JobData> {
let formModel = view.modelBuilder.formContainer().withFormItems([ let formModel = view.modelBuilder.formContainer().withFormItems([
{ {
component: this.notificationsTabTopLabel, components:
title: '' [{
}, { component: emailContainer,
component: emailContainer, title: ''
title: '' },
}, { {
component: pagerContainer, component: pagerContainer,
title: '' title: ''
}, { },
component: eventLogContainer, {
title: '' component: eventLogContainer,
}, { title: ''
component: deleteJobContainer, },
title: '' {
}]).withLayout({ width: '100%' }).component(); component: deleteJobContainer,
title: ''
}], title: this.NotificationsTabTopLabelString}]).withLayout({ width: '100%' }).component();
await view.initializeModel(formModel); await view.initializeModel(formModel);
this.emailConditionDropdown.values = this.model.JobCompletionActionConditions; this.emailConditionDropdown.values = this.model.JobCompletionActionConditions;

View File

@@ -3,6 +3,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.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
'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 { JobStepData } from '../data/jobStepData'; import { JobStepData } from '../data/jobStepData';
@@ -10,31 +11,58 @@ import { AgentUtils } from '../agentUtils';
import { JobData } from '../data/jobData'; import { JobData } from '../data/jobData';
const path = require('path'); const path = require('path');
const localize = nls.loadMessageBundle();
export class JobStepDialog { export class JobStepDialog {
// TODO: localize // TODO: localize
// Top level // Top level
// //
private static readonly DialogTitle: string = 'New Job Step'; private readonly DialogTitle: string = localize('jobStepDialog.newJobStep', 'New Job Step');
private static readonly FileBrowserDialogTitle: string = 'Locate Database Files - '; private readonly FileBrowserDialogTitle: string = localize('jobStepDialog.fileBrowserTitle', 'Locate Database Files - ');
private static readonly OkButtonText: string = 'OK'; private readonly OkButtonText: string = localize('jobStepDialog.ok', 'OK');
private static readonly CancelButtonText: string = 'Cancel'; private readonly CancelButtonText: string = localize('jobStepDialog.cancel', 'Cancel');
private static readonly GeneralTabText: string = 'General'; private readonly GeneralTabText: string = localize('jobStepDialog.general', 'General');
private static readonly AdvancedTabText: string = 'Advanced'; private readonly AdvancedTabText: string = localize('jobStepDialog.advanced', 'Advanced');
private static readonly OpenCommandText: string = 'Open...'; private readonly OpenCommandText: string = localize('jobStepDialog.open', 'Open...');
private static readonly ParseCommandText: string = 'Parse'; private readonly ParseCommandText: string = localize('jobStepDialog.parse','Parse');
private static readonly NextButtonText: string = 'Next'; private readonly NextButtonText: string = localize('jobStepDialog.next', 'Next');
private static readonly PreviousButtonText: string = 'Previous'; private readonly PreviousButtonText: string = localize('jobStepDialog.previous','Previous');
private static readonly SuccessAction: string = 'On success action'; private readonly SuccessfulParseText: string = localize('jobStepDialog.successParse', 'The command was successfully parsed.');
private static readonly FailureAction: string = 'On failure action'; 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
private readonly StepNameLabelString: string = localize('jobStepDialog.stepNameLabel', 'Step Name');
private readonly TypeLabelString: string = localize('jobStepDialog.typeLabel', 'Type');
private readonly RunAsLabelString: string = localize('jobStepDialog.runAsLabel', 'Run as');
private readonly DatabaseLabelString: string = localize('jobStepDialog.databaseLabel', 'Database');
private readonly CommandLabelString: string = localize('jobStepDialog.commandLabel', 'Command');
// Advanced Control Titles
private readonly SuccessActionLabel: string = localize('jobStepDialog.successAction', 'On success action');
private readonly FailureActionLabel: string = localize('jobStepDialog.failureAction', 'On failure action');
private readonly RunAsUserLabel: string = localize('jobStepDialog.runAsUser', 'Run as user');
private readonly RetryAttemptsLabel: string = localize('jobStepDialog.retryAttempts', 'Retry Attempts');
private readonly RetryIntervalLabel: string = localize('jobStepDialog.retryInterval', 'Retry Interval (minutes)');
private readonly LogToTableLabel: string = localize('jobStepDialog.logToTable', 'Log to table');
private readonly AppendExistingTableEntryLabel: string = localize('jobStepDialog.appendExistingTableEntry', 'Append output to exisiting entry in table');
private readonly IncludeStepOutputHistoryLabel: string = localize('jobStepDialog.includeStepOutputHistory', 'Include step output in history');
private readonly OutputFileNameLabel: string = localize('jobStepDialog.outputFile', 'Output File');
private readonly AppendOutputToFileLabel: string = localize('jobStepDialog.appendOutputToFile', 'Append output to existing file');
// File Browser Control Titles
private readonly SelectedPathLabelString: string = localize('jobStepDialog.selectedPath', 'Selected path');
private readonly FilesOfTypeLabelString: string = localize('jobStepDialog.filesOfType', 'Files of type');
private readonly FileNameLabelString: string = localize('jobStepDialog.fileName', 'File name');
private readonly AllFilesLabelString: string = localize('jobStepDialog.allFiles', 'All Files (*)');
// Dropdown options // Dropdown options
private static readonly TSQLScript: string = 'Transact-SQL script (T-SQL)'; private readonly TSQLScript: string = localize('jobStepDialog.TSQL', 'Transact-SQL script (T-SQL)');
private static readonly AgentServiceAccount: string = 'SQL Server Agent Service Account'; private readonly AgentServiceAccount: string = localize('jobStepDialog.agentServiceAccount', 'SQL Server Agent Service Account');
private static readonly NextStep: string = 'Go to the next step'; private readonly NextStep: string = localize('jobStepDialog.nextStep', 'Go to the next step');
private static readonly QuitJobReportingSuccess: string = 'Quit the job reporting success'; private readonly QuitJobReportingSuccess: string = localize('jobStepDialog.quitJobSuccess', 'Quit the job reporting success');
private static readonly QuitJobReportingFailure: string = 'Quit the job reporting failure'; private readonly QuitJobReportingFailure: string = localize('jobStepDialog.quitJobFailure', 'Quit the job reporting failure');
// UI Components // UI Components
@@ -54,6 +82,7 @@ export class JobStepDialog {
private retryIntervalBox: sqlops.InputBoxComponent; private retryIntervalBox: sqlops.InputBoxComponent;
private outputFileNameBox: sqlops.InputBoxComponent; private outputFileNameBox: sqlops.InputBoxComponent;
private fileBrowserNameBox: sqlops.InputBoxComponent; private fileBrowserNameBox: sqlops.InputBoxComponent;
private userInputBox: sqlops.InputBoxComponent;
// Dropdowns // Dropdowns
private typeDropdown: sqlops.DropDownComponent; private typeDropdown: sqlops.DropDownComponent;
@@ -98,33 +127,36 @@ export class JobStepDialog {
} }
private initializeUIComponents() { private initializeUIComponents() {
this.dialog = sqlops.window.modelviewdialog.createDialog(JobStepDialog.DialogTitle); this.dialog = sqlops.window.modelviewdialog.createDialog(this.DialogTitle);
this.generalTab = sqlops.window.modelviewdialog.createTab(JobStepDialog.GeneralTabText); this.generalTab = sqlops.window.modelviewdialog.createTab(this.GeneralTabText);
this.advancedTab = sqlops.window.modelviewdialog.createTab(JobStepDialog.AdvancedTabText); this.advancedTab = sqlops.window.modelviewdialog.createTab(this.AdvancedTabText);
this.dialog.content = [this.generalTab, this.advancedTab]; this.dialog.content = [this.generalTab, this.advancedTab];
this.dialog.okButton.onClick(async () => await this.execute()); this.dialog.okButton.onClick(async () => await this.execute());
this.dialog.okButton.label = JobStepDialog.OkButtonText; this.dialog.okButton.label = this.OkButtonText;
this.dialog.cancelButton.label = JobStepDialog.CancelButtonText; this.dialog.cancelButton.label = this.CancelButtonText;
} }
private createCommands(view, queryProvider: sqlops.QueryProvider) { private createCommands(view, queryProvider: sqlops.QueryProvider) {
this.openButton = view.modelBuilder.button() this.openButton = view.modelBuilder.button()
.withProperties({ .withProperties({
label: JobStepDialog.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: JobStepDialog.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) {
queryProvider.parseSyntax(this.ownerUri, this.commandTextBox.value).then(result => { queryProvider.parseSyntax(this.ownerUri, this.commandTextBox.value).then(result => {
if (result && result.parseable) { if (result && result.parseable) {
this.dialog.message = { text: 'The command was successfully parsed.', level: 2}; this.dialog.message = { text: this.SuccessfulParseText, level: 2};
} else if (result && !result.parseable) { } else if (result && !result.parseable) {
this.dialog.message = { text: 'The command failed' }; this.dialog.message = { text: this.FailureParseText };
} }
}); });
} }
@@ -139,13 +171,13 @@ export class JobStepDialog {
.component(); .component();
this.nextButton = view.modelBuilder.button() this.nextButton = view.modelBuilder.button()
.withProperties({ .withProperties({
label: JobStepDialog.NextButtonText, label: this.NextButtonText,
enabled: false, enabled: false,
width: '80px' width: '80px'
}).component(); }).component();
this.previousButton = view.modelBuilder.button() this.previousButton = view.modelBuilder.button()
.withProperties({ .withProperties({
label: JobStepDialog.PreviousButtonText, label: this.PreviousButtonText,
enabled: false, enabled: false,
width: '80px' width: '80px'
}).component(); }).component();
@@ -157,10 +189,16 @@ 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: JobStepDialog.TSQLScript, value: this.TSQLScript,
values: [JobStepDialog.TSQLScript] values: [this.TSQLScript]
}) })
.component(); .component();
this.runAsDropdown = view.modelBuilder.dropDown() this.runAsDropdown = view.modelBuilder.dropDown()
@@ -171,8 +209,8 @@ export class JobStepDialog {
.component(); .component();
this.runAsDropdown.enabled = false; this.runAsDropdown.enabled = false;
this.typeDropdown.onValueChanged((type) => { this.typeDropdown.onValueChanged((type) => {
if (type.selected !== JobStepDialog.TSQLScript) { if (type.selected !== this.TSQLScript) {
this.runAsDropdown.value = JobStepDialog.AgentServiceAccount; this.runAsDropdown.value = this.AgentServiceAccount;
this.runAsDropdown.values = [this.runAsDropdown.value]; this.runAsDropdown.values = [this.runAsDropdown.value];
} else { } else {
this.runAsDropdown.value = ''; this.runAsDropdown.value = '';
@@ -200,19 +238,19 @@ export class JobStepDialog {
let formModel = view.modelBuilder.formContainer() let formModel = view.modelBuilder.formContainer()
.withFormItems([{ .withFormItems([{
component: this.nameTextBox, component: this.nameTextBox,
title: 'Step name' title: this.StepNameLabelString
}, { }, {
component: this.typeDropdown, component: this.typeDropdown,
title: 'Type' title: this.TypeLabelString
}, { }, {
component: this.runAsDropdown, component: this.runAsDropdown,
title: 'Run as' title: this.RunAsLabelString
}, { }, {
component: this.databaseDropdown, component: this.databaseDropdown,
title: 'Database' title: this.DatabaseLabelString
}, { }, {
component: this.commandTextBox, component: this.commandTextBox,
title: 'Command', title: this.CommandLabelString,
actions: [buttonContainer] actions: [buttonContainer]
}], { }], {
horizontal: false, horizontal: false,
@@ -224,81 +262,57 @@ export class JobStepDialog {
}); });
} }
private createRunAsUserOptions(view) {
let userInputBox = view.modelBuilder.inputBox()
.withProperties({ inputType: 'text', width: '100px' }).component();
let viewButton = view.modelBuilder.button()
.withProperties({ label: '...', width: '20px' }).component();
let viewButtonContainer = view.modelBuilder.flexContainer()
.withLayout({ width: 100, textAlign: 'right' })
.withItems([viewButton], { flex: '1 1 50%' }).component();
let userInputBoxContainer = view.modelBuilder.flexContainer()
.withLayout({ width: 200, textAlign: 'left' })
.withItems([userInputBox], { flex: '1 1 50%' }).component();
let runAsUserContainer = view.modelBuilder.flexContainer()
.withLayout({ width: 200 })
.withItems([userInputBoxContainer, viewButtonContainer], { flex: '1 1 50%' })
.component();
let runAsUserForm = view.modelBuilder.formContainer()
.withFormItems([{
component: runAsUserContainer,
title: 'Run as user'
}], { horizontal: true, componentWidth: 200 }).component();
return runAsUserForm;
}
private createAdvancedTab() { private createAdvancedTab() {
this.advancedTab.registerContent(async (view) => { this.advancedTab.registerContent(async (view) => {
this.successActionDropdown = view.modelBuilder.dropDown() this.successActionDropdown = view.modelBuilder.dropDown()
.withProperties({ .withProperties({
value: JobStepDialog.NextStep, width: '100%',
values: [JobStepDialog.NextStep, JobStepDialog.QuitJobReportingSuccess, JobStepDialog.QuitJobReportingFailure] value: this.NextStep,
values: [this.NextStep, this.QuitJobReportingSuccess, this.QuitJobReportingFailure]
}) })
.component(); .component();
let retryFlexContainer = this.createRetryCounters(view); let retryFlexContainer = this.createRetryCounters(view);
this.failureActionDropdown = view.modelBuilder.dropDown() this.failureActionDropdown = view.modelBuilder.dropDown()
.withProperties({ .withProperties({
value: JobStepDialog.QuitJobReportingFailure, value: this.QuitJobReportingFailure,
values: [JobStepDialog.QuitJobReportingFailure, JobStepDialog.NextStep, JobStepDialog.QuitJobReportingSuccess] values: [this.QuitJobReportingFailure, this.NextStep, this.QuitJobReportingSuccess]
}) })
.component(); .component();
let optionsGroup = this.createTSQLOptions(view); let optionsGroup = this.createTSQLOptions(view);
let viewButton = view.modelBuilder.button()
.withProperties({ label: 'View', width: '50px' }).component();
viewButton.enabled = false;
this.logToTableCheckbox = view.modelBuilder.checkBox() this.logToTableCheckbox = view.modelBuilder.checkBox()
.withProperties({ .withProperties({
label: 'Log to table' label: this.LogToTableLabel
}).component(); }).component();
let appendToExistingEntryInTableCheckbox = view.modelBuilder.checkBox() let appendToExistingEntryInTableCheckbox = view.modelBuilder.checkBox()
.withProperties({ label: 'Append output to existing entry in table' }).component(); .withProperties({ label: this.AppendExistingTableEntryLabel }).component();
appendToExistingEntryInTableCheckbox.enabled = false; appendToExistingEntryInTableCheckbox.enabled = false;
this.logToTableCheckbox.onChanged(e => { this.logToTableCheckbox.onChanged(e => {
viewButton.enabled = e;
appendToExistingEntryInTableCheckbox.enabled = e; appendToExistingEntryInTableCheckbox.enabled = e;
}); });
let appendCheckboxContainer = view.modelBuilder.groupContainer() let appendCheckboxContainer = view.modelBuilder.groupContainer()
.withItems([appendToExistingEntryInTableCheckbox]).component(); .withItems([appendToExistingEntryInTableCheckbox]).component();
let logToTableContainer = view.modelBuilder.flexContainer() let logToTableContainer = view.modelBuilder.flexContainer()
.withLayout({ flexFlow: 'row', justifyContent: 'space-between', width: 300 }) .withLayout({ flexFlow: 'row', justifyContent: 'space-between', width: 300 })
.withItems([this.logToTableCheckbox, viewButton]).component(); .withItems([this.logToTableCheckbox]).component();
let logStepOutputHistoryCheckbox = view.modelBuilder.checkBox() let logStepOutputHistoryCheckbox = view.modelBuilder.checkBox()
.withProperties({ label: 'Include step output in history' }).component(); .withProperties({ label: this.IncludeStepOutputHistoryLabel }).component();
let runAsUserOptions = this.createRunAsUserOptions(view); this.userInputBox = view.modelBuilder.inputBox()
.withProperties({ inputType: 'text', width: '100%' }).component();
let formModel = view.modelBuilder.formContainer() let formModel = view.modelBuilder.formContainer()
.withFormItems( .withFormItems(
[{ [{
component: this.successActionDropdown, component: this.successActionDropdown,
title: JobStepDialog.SuccessAction title: this.SuccessActionLabel
}, { }, {
component: retryFlexContainer, component: retryFlexContainer,
title: '' title: ''
}, { }, {
component: this.failureActionDropdown, component: this.failureActionDropdown,
title: JobStepDialog.FailureAction title: this.FailureActionLabel
}, { }, {
component: optionsGroup, component: optionsGroup,
title: 'Transact-SQL script (T-SQL)' title: this.TSQLScript
}, { }, {
component: logToTableContainer, component: logToTableContainer,
title: '' title: ''
@@ -309,8 +323,8 @@ export class JobStepDialog {
component: logStepOutputHistoryCheckbox, component: logStepOutputHistoryCheckbox,
title: '' title: ''
}, { }, {
component: runAsUserOptions, component: this.userInputBox,
title: '' title: this.RunAsUserLabel
}], { }], {
componentWidth: 400 componentWidth: 400
}).component(); }).component();
@@ -323,32 +337,37 @@ export class JobStepDialog {
private createRetryCounters(view) { private createRetryCounters(view) {
this.retryAttemptsBox = view.modelBuilder.inputBox() this.retryAttemptsBox = view.modelBuilder.inputBox()
.withValidation(component => component.value >= 0) .withValidation(component => component.value >= 0)
.withProperties({ .withProperties({
inputType: 'number' inputType: 'number',
}) width: '100%',
.component(); placeHolder: '0'
})
.component();
this.retryIntervalBox = view.modelBuilder.inputBox() this.retryIntervalBox = view.modelBuilder.inputBox()
.withValidation(component => component.value >= 0) .withValidation(component => component.value >= 0)
.withProperties({ .withProperties({
inputType: 'number' inputType: 'number',
width: '100%',
placeHolder: '0'
}).component(); }).component();
let retryAttemptsContainer = view.modelBuilder.formContainer() let retryAttemptsContainer = view.modelBuilder.formContainer()
.withFormItems( .withFormItems(
[{ [{
component: this.retryAttemptsBox, component: this.retryAttemptsBox,
title: 'Retry Attempts' title: this.RetryAttemptsLabel
}], { }], {
horizontal: false horizontal: false,
}) componentWidth: '100%'
})
.component(); .component();
let retryIntervalContainer = view.modelBuilder.formContainer() let retryIntervalContainer = view.modelBuilder.formContainer()
.withFormItems( .withFormItems(
[{ [{
component: this.retryIntervalBox, component: this.retryIntervalBox,
title: 'Retry Interval (minutes)' title: this.RetryIntervalLabel
}], { }], {
horizontal: false horizontal: false
}) })
@@ -362,7 +381,7 @@ export class JobStepDialog {
} }
private openFileBrowserDialog() { private openFileBrowserDialog() {
let fileBrowserTitle = JobStepDialog.FileBrowserDialogTitle + `${this.server}`; let fileBrowserTitle = this.FileBrowserDialogTitle + `${this.server}`;
this.fileBrowserDialog = sqlops.window.modelviewdialog.createDialog(fileBrowserTitle); this.fileBrowserDialog = sqlops.window.modelviewdialog.createDialog(fileBrowserTitle);
let fileBrowserTab = sqlops.window.modelviewdialog.createTab('File Browser'); let fileBrowserTab = sqlops.window.modelviewdialog.createTab('File Browser');
this.fileBrowserDialog.content = [fileBrowserTab]; this.fileBrowserDialog.content = [fileBrowserTab];
@@ -379,8 +398,8 @@ export class JobStepDialog {
}); });
this.fileTypeDropdown = view.modelBuilder.dropDown() this.fileTypeDropdown = view.modelBuilder.dropDown()
.withProperties({ .withProperties({
value: 'All Files (*)', value: this.AllFilesLabelString,
values: ['All Files (*)'] values: [this.AllFilesLabelString]
}) })
.component(); .component();
this.fileBrowserNameBox = view.modelBuilder.inputBox() this.fileBrowserNameBox = view.modelBuilder.inputBox()
@@ -392,13 +411,13 @@ export class JobStepDialog {
title: '' title: ''
}, { }, {
component: this.selectedPathTextBox, component: this.selectedPathTextBox,
title: 'Selected path:' title: this.SelectedPathLabelString
}, { }, {
component: this.fileTypeDropdown, component: this.fileTypeDropdown,
title: 'Files of type:' title: this.FilesOfTypeLabelString
}, { }, {
component: this.fileBrowserNameBox, component: this.fileBrowserNameBox,
title: 'File name:' title: this.FileNameLabelString
} }
]).component(); ]).component();
view.initializeModel(fileBrowserContainer); view.initializeModel(fileBrowserContainer);
@@ -406,8 +425,8 @@ export class JobStepDialog {
this.fileBrowserDialog.okButton.onClick(() => { this.fileBrowserDialog.okButton.onClick(() => {
this.outputFileNameBox.value = path.join(path.dirname(this.selectedPathTextBox.value), this.fileBrowserNameBox.value); this.outputFileNameBox.value = path.join(path.dirname(this.selectedPathTextBox.value), this.fileBrowserNameBox.value);
}); });
this.fileBrowserDialog.okButton.label = JobStepDialog.OkButtonText; this.fileBrowserDialog.okButton.label = this.OkButtonText;
this.fileBrowserDialog.cancelButton.label = JobStepDialog.CancelButtonText; this.fileBrowserDialog.cancelButton.label = this.CancelButtonText;
sqlops.window.modelviewdialog.openDialog(this.fileBrowserDialog); sqlops.window.modelviewdialog.openDialog(this.fileBrowserDialog);
} }
@@ -417,21 +436,15 @@ export class JobStepDialog {
this.outputFileBrowserButton.onDidClick(() => this.openFileBrowserDialog()); this.outputFileBrowserButton.onDidClick(() => this.openFileBrowserDialog());
this.outputFileNameBox = view.modelBuilder.inputBox() this.outputFileNameBox = view.modelBuilder.inputBox()
.withProperties({ .withProperties({
width: '150px', width: 250,
inputType: 'text' inputType: 'text'
}).component(); }).component();
let outputViewButton = view.modelBuilder.button()
.withProperties({
width: '50px',
label: 'View'
}).component();
outputViewButton.enabled = false;
let outputButtonContainer = view.modelBuilder.flexContainer() let outputButtonContainer = view.modelBuilder.flexContainer()
.withLayout({ .withLayout({
flexFlow: 'row', flexFlow: 'row',
textAlign: 'right', textAlign: 'right',
width: 120 width: '100%'
}).withItems([this.outputFileBrowserButton, outputViewButton], { flex: '1 1 50%' }).component(); }).withItems([this.outputFileBrowserButton], { flex: '1 1 50%' }).component();
let outputFlexBox = view.modelBuilder.flexContainer() let outputFlexBox = view.modelBuilder.flexContainer()
.withLayout({ .withLayout({
flexFlow: 'row', flexFlow: 'row',
@@ -441,7 +454,7 @@ export class JobStepDialog {
}).component(); }).component();
this.appendToExistingFileCheckbox = view.modelBuilder.checkBox() this.appendToExistingFileCheckbox = view.modelBuilder.checkBox()
.withProperties({ .withProperties({
label: 'Append output to existing file' label: this.AppendOutputToFileLabel
}).component(); }).component();
this.appendToExistingFileCheckbox.enabled = false; this.appendToExistingFileCheckbox.enabled = false;
this.outputFileNameBox.onTextChanged((input) => { this.outputFileNameBox.onTextChanged((input) => {
@@ -454,15 +467,20 @@ export class JobStepDialog {
let outputFileForm = view.modelBuilder.formContainer() let outputFileForm = view.modelBuilder.formContainer()
.withFormItems([{ .withFormItems([{
component: outputFlexBox, component: outputFlexBox,
title: 'Output file' title: this.OutputFileNameLabel
}, { }, {
component: this.appendToExistingFileCheckbox, component: this.appendToExistingFileCheckbox,
title: '' title: ''
}], { horizontal: true, componentWidth: 200 }).component(); }], { horizontal: false, componentWidth: 200 }).component();
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;
@@ -471,12 +489,11 @@ export class JobStepDialog {
this.model.databaseName = this.databaseDropdown.value as string; this.model.databaseName = this.databaseDropdown.value as string;
this.model.script = this.commandTextBox.value; this.model.script = this.commandTextBox.value;
this.model.successAction = this.successActionDropdown.value as string; this.model.successAction = this.successActionDropdown.value as string;
this.model.retryAttempts = +this.retryAttemptsBox.value; this.model.retryAttempts = this.retryAttemptsBox.value ? +this.retryAttemptsBox.value : 0;
this.model.retryInterval = +this.retryIntervalBox.value; this.model.retryInterval = +this.retryIntervalBox.value ? +this.retryIntervalBox.value : 0;
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

@@ -16,7 +16,8 @@ const localize = nls.loadMessageBundle();
export class OperatorDialog extends AgentDialog<OperatorData> { export class OperatorDialog extends AgentDialog<OperatorData> {
// Top level // Top level
private static readonly DialogTitle: string = localize('createOperator.createOperator', 'Create Operator'); private static readonly CreateDialogTitle: string = localize('createOperator.createOperator', 'Create Operator');
private static readonly EditDialogTitle: string = localize('createOperator.editOperator', 'Edit Operator');
private static readonly GeneralTabText: string = localize('createOperator.General', 'General'); private static readonly GeneralTabText: string = localize('createOperator.General', 'General');
private static readonly NotificationsTabText: string = localize('createOperator.Notifications', 'Notifications'); private static readonly NotificationsTabText: string = localize('createOperator.Notifications', 'Notifications');
@@ -32,6 +33,9 @@ export class OperatorDialog extends AgentDialog<OperatorData> {
private static readonly PagerFridayCheckBoxLabel: string = localize('createOperator.PagerFridayCheckBox', 'Friday '); private static readonly PagerFridayCheckBoxLabel: string = localize('createOperator.PagerFridayCheckBox', 'Friday ');
private static readonly PagerSaturdayCheckBoxLabel: string = localize('createOperator.PagerSaturdayCheckBox', 'Saturday'); private static readonly PagerSaturdayCheckBoxLabel: string = localize('createOperator.PagerSaturdayCheckBox', 'Saturday');
private static readonly PagerSundayCheckBoxLabel: string = localize('createOperator.PagerSundayCheckBox', 'Sunday'); private static readonly PagerSundayCheckBoxLabel: string = localize('createOperator.PagerSundayCheckBox', 'Sunday');
private static readonly WorkdayBeginLabel: string = localize('createOperator.workdayBegin', 'Workday begin');
private static readonly WorkdayEndLabel: string = localize('createOperator.workdayEnd', 'Workday end');
private static readonly PagerDutyScheduleLabel: string = localize('createOperator.PagerDutySchedule', 'Pager on duty schdule');
// Notifications tab strings // Notifications tab strings
private static readonly AlertsTableLabel: string = localize('createOperator.AlertListHeading', 'Alert list'); private static readonly AlertsTableLabel: string = localize('createOperator.AlertListHeading', 'Alert list');
@@ -65,8 +69,11 @@ export class OperatorDialog extends AgentDialog<OperatorData> {
// Notification tab controls // Notification tab controls
private alertsTable: sqlops.TableComponent; private alertsTable: sqlops.TableComponent;
constructor(ownerUri: string) { constructor(ownerUri: string, operatorInfo: sqlops.AgentOperatorInfo = undefined) {
super(ownerUri, new OperatorData(ownerUri), OperatorDialog.DialogTitle); super(
ownerUri,
new OperatorData(ownerUri, operatorInfo),
operatorInfo ? OperatorDialog.EditDialogTitle : OperatorDialog.CreateDialogTitle);
} }
protected async initializeDialog(dialog: sqlops.window.modelviewdialog.Dialog) { protected async initializeDialog(dialog: sqlops.window.modelviewdialog.Dialog) {
@@ -174,13 +181,13 @@ export class OperatorDialog extends AgentDialog<OperatorData> {
this.weekdayPagerStartTimeInput = view.modelBuilder.inputBox() this.weekdayPagerStartTimeInput = view.modelBuilder.inputBox()
.withProperties({ .withProperties({
inputType: 'time', inputType: 'time',
placeHolder: '08:00:00' placeHolder: '08:00:00',
}).component(); }).component();
this.weekdayPagerStartTimeInput.enabled = false; this.weekdayPagerStartTimeInput.enabled = false;
let weekdayStartInputContainer = view.modelBuilder.formContainer() let weekdayStartInputContainer = view.modelBuilder.formContainer()
.withFormItems([{ .withFormItems([{
component: this.weekdayPagerStartTimeInput, component: this.weekdayPagerStartTimeInput,
title: 'Workday begin' title: OperatorDialog.WorkdayBeginLabel
}]).component(); }]).component();
this.weekdayPagerEndTimeInput = view.modelBuilder.inputBox() this.weekdayPagerEndTimeInput = view.modelBuilder.inputBox()
@@ -192,7 +199,7 @@ export class OperatorDialog extends AgentDialog<OperatorData> {
let weekdayEndInputContainer = view.modelBuilder.formContainer() let weekdayEndInputContainer = view.modelBuilder.formContainer()
.withFormItems([{ .withFormItems([{
component: this.weekdayPagerEndTimeInput, component: this.weekdayPagerEndTimeInput,
title: 'Workday end' title: OperatorDialog.WorkdayEndLabel
}]).component(); }]).component();
this.pagerFridayCheckBox = view.modelBuilder.checkBox() this.pagerFridayCheckBox = view.modelBuilder.checkBox()
@@ -216,7 +223,8 @@ export class OperatorDialog extends AgentDialog<OperatorData> {
let pagerFridayCheckboxContainer = view.modelBuilder.flexContainer() let pagerFridayCheckboxContainer = view.modelBuilder.flexContainer()
.withLayout({ .withLayout({
flexFlow: 'row', flexFlow: 'row',
alignItems: 'baseline' alignItems: 'baseline',
width: '100%'
}).withItems([this.pagerFridayCheckBox, weekdayStartInputContainer, weekdayEndInputContainer]) }).withItems([this.pagerFridayCheckBox, weekdayStartInputContainer, weekdayEndInputContainer])
.component(); .component();
@@ -245,7 +253,7 @@ export class OperatorDialog extends AgentDialog<OperatorData> {
let saturdayStartInputContainer = view.modelBuilder.formContainer() let saturdayStartInputContainer = view.modelBuilder.formContainer()
.withFormItems([{ .withFormItems([{
component: this.saturdayPagerStartTimeInput, component: this.saturdayPagerStartTimeInput,
title: 'Workday begin' title: OperatorDialog.WorkdayBeginLabel
}]).component(); }]).component();
this.saturdayPagerEndTimeInput = view.modelBuilder.inputBox() this.saturdayPagerEndTimeInput = view.modelBuilder.inputBox()
@@ -257,7 +265,7 @@ export class OperatorDialog extends AgentDialog<OperatorData> {
let saturdayEndInputContainer = view.modelBuilder.formContainer() let saturdayEndInputContainer = view.modelBuilder.formContainer()
.withFormItems([{ .withFormItems([{
component: this.saturdayPagerEndTimeInput, component: this.saturdayPagerEndTimeInput,
title: 'Workday end' title: OperatorDialog.WorkdayEndLabel
}]).component(); }]).component();
let pagerSaturdayCheckboxContainer = view.modelBuilder.flexContainer() let pagerSaturdayCheckboxContainer = view.modelBuilder.flexContainer()
@@ -292,7 +300,7 @@ export class OperatorDialog extends AgentDialog<OperatorData> {
let sundayStartInputContainer = view.modelBuilder.formContainer() let sundayStartInputContainer = view.modelBuilder.formContainer()
.withFormItems([{ .withFormItems([{
component: this.sundayPagerStartTimeInput, component: this.sundayPagerStartTimeInput,
title: 'Workday begin' title: OperatorDialog.WorkdayBeginLabel
}]).component(); }]).component();
this.sundayPagerEndTimeInput = view.modelBuilder.inputBox() this.sundayPagerEndTimeInput = view.modelBuilder.inputBox()
@@ -304,7 +312,7 @@ export class OperatorDialog extends AgentDialog<OperatorData> {
let sundayEndInputContainer = view.modelBuilder.formContainer() let sundayEndInputContainer = view.modelBuilder.formContainer()
.withFormItems([{ .withFormItems([{
component: this.sundayPagerEndTimeInput, component: this.sundayPagerEndTimeInput,
title: 'Workday end' title: OperatorDialog.WorkdayEndLabel
}]).component(); }]).component();
let pagerSundayCheckboxContainer = view.modelBuilder.flexContainer() let pagerSundayCheckboxContainer = view.modelBuilder.flexContainer()
@@ -314,36 +322,6 @@ export class OperatorDialog extends AgentDialog<OperatorData> {
}).withItems([this.pagerSundayCheckBox, sundayStartInputContainer, sundayEndInputContainer]) }).withItems([this.pagerSundayCheckBox, sundayStartInputContainer, sundayEndInputContainer])
.component(); .component();
let checkBoxContainer = view.modelBuilder.formContainer()
.withFormItems([{
component: this.pagerMondayCheckBox,
title: ''
}, {
component: this.pagerTuesdayCheckBox,
title: ''
}, {
component: this.pagerWednesdayCheckBox,
title: ''
}, {
component: this.pagerThursdayCheckBox,
title: ''
}, {
component: pagerFridayCheckboxContainer,
title: ''
}, {
component: pagerSaturdayCheckboxContainer,
title: ''
}, {
component: pagerSundayCheckboxContainer,
title: ''
}]).component();
let pagerContainer = view.modelBuilder.flexContainer()
.withLayout({
flexFlow: 'row'
}).withItems([checkBoxContainer])
.component();
let formModel = view.modelBuilder.formContainer() let formModel = view.modelBuilder.formContainer()
.withFormItems([{ .withFormItems([{
component: this.nameTextBox, component: this.nameTextBox,
@@ -358,8 +336,29 @@ export class OperatorDialog extends AgentDialog<OperatorData> {
component: this.pagerEmailNameTextBox, component: this.pagerEmailNameTextBox,
title: OperatorDialog.PagerEmailNameTextLabel title: OperatorDialog.PagerEmailNameTextLabel
}, { }, {
component: pagerContainer, components: [{
title: '' component: this.pagerMondayCheckBox,
title: ''
}, {
component: this.pagerTuesdayCheckBox,
title: ''
}, {
component: this.pagerWednesdayCheckBox,
title: ''
}, {
component: this.pagerThursdayCheckBox,
title: ''
}, {
component: pagerFridayCheckboxContainer,
title: ''
}, {
component: pagerSaturdayCheckboxContainer,
title: ''
}, {
component: pagerSundayCheckboxContainer,
title: ''
}] ,
title: OperatorDialog.PagerDutyScheduleLabel
}]).withLayout({ width: '100%' }).component(); }]).withLayout({ width: '100%' }).component();
await view.initializeModel(formModel); await view.initializeModel(formModel);

View File

@@ -4,18 +4,22 @@
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
'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 { PickScheduleData } from '../data/pickScheduleData'; import { PickScheduleData } from '../data/pickScheduleData';
const localize = nls.loadMessageBundle();
export class PickScheduleDialog { export class PickScheduleDialog {
// TODO: localize // TODO: localize
// Top level // Top level
private readonly DialogTitle: string = 'Job Schedules'; private readonly DialogTitle: string = localize('pickSchedule.jobSchedules', 'Job Schedules');
private readonly OkButtonText: string = 'OK'; private readonly OkButtonText: string = localize('pickSchedule.ok', 'OK');
private readonly CancelButtonText: string = 'Cancel'; private readonly CancelButtonText: string = localize('pickSchedule.cancel', 'Cancel');
private readonly SchedulesTabText: string = 'Schedules'; private readonly ScheduleNameLabelText: string = localize('pickSchedule.scheduleName', 'Schedule Name');
private readonly SchedulesLabelText: string = localize('pickSchedule.schedules', 'Schedules');
// UI Components // UI Components
private dialog: sqlops.window.modelviewdialog.Dialog; private dialog: sqlops.window.modelviewdialog.Dialog;
@@ -38,7 +42,6 @@ export class PickScheduleDialog {
this.dialog.cancelButton.onClick(async () => await this.cancel()); this.dialog.cancelButton.onClick(async () => await this.cancel());
this.dialog.okButton.label = this.OkButtonText; this.dialog.okButton.label = this.OkButtonText;
this.dialog.cancelButton.label = this.CancelButtonText; this.dialog.cancelButton.label = this.CancelButtonText;
sqlops.window.modelviewdialog.openDialog(this.dialog); sqlops.window.modelviewdialog.openDialog(this.dialog);
} }
@@ -47,17 +50,17 @@ export class PickScheduleDialog {
this.schedulesTable = view.modelBuilder.table() this.schedulesTable = view.modelBuilder.table()
.withProperties({ .withProperties({
columns: [ columns: [
'Schedule Name' this.ScheduleNameLabelText
], ],
data: [], data: [],
height: 600, height: '80em',
width: 400 width: '40em'
}).component(); }).component();
let formModel = view.modelBuilder.formContainer() let formModel = view.modelBuilder.formContainer()
.withFormItems([{ .withFormItems([{
component: this.schedulesTable, component: this.schedulesTable,
title: 'Schedules' title: this.SchedulesLabelText
}]).withLayout({ width: '100%' }).component(); }]).withLayout({ width: '100%' }).component();
await view.initializeModel(formModel); await view.initializeModel(formModel);

View File

@@ -15,32 +15,60 @@ const localize = nls.loadMessageBundle();
export class ProxyDialog extends AgentDialog<ProxyData> { export class ProxyDialog extends AgentDialog<ProxyData> {
// Top level // Top level
private static readonly DialogTitle: string = localize('createProxy.createAlert', 'Create Alert'); private static readonly CreateDialogTitle: string = localize('createProxy.createProxy', 'Create Proxy');
private static readonly EditDialogTitle: string = localize('createProxy.editProxy', 'Edit Proxy');
private static readonly GeneralTabText: string = localize('createProxy.General', 'General'); private static readonly GeneralTabText: string = localize('createProxy.General', 'General');
// General tab strings // General tab strings
private static readonly ProxyNameTextBoxLabel: string = localize('createProxy.ProxyName', 'Proxy name'); private static readonly ProxyNameTextBoxLabel: string = localize('createProxy.ProxyName', 'Proxy name');
private static readonly CredentialNameTextBoxLabel: string = localize('createProxy.CredentialName', 'Credential name'); private static readonly CredentialNameTextBoxLabel: string = localize('createProxy.CredentialName', 'Credential name');
private static readonly DescriptionTextBoxLabel: string = localize('createProxy.Description', 'Description'); private static readonly DescriptionTextBoxLabel: string = localize('createProxy.Description', 'Description');
private static readonly SubsystemsTableLabel: string = localize('createProxy.Subsystems', 'Subsystems'); private static readonly SubsystemLabel: string = localize('createProxy.SubsystemName', 'Subsystem');
private static readonly SubsystemNameColumnLabel: string = localize('createProxy.SubsystemName', 'Subsystem'); private static readonly OperatingSystemLabel: string = localize('createProxy.OperatingSystem', 'Operating system (CmdExec)');
private static readonly ReplicationSnapshotLabel: string = localize('createProxy.ReplicationSnapshot', 'Replication Snapshot');
private static readonly ReplicationTransactionLogLabel: string = localize('createProxy.ReplicationTransactionLog', 'Replication Transaction-Log Reader');
private static readonly ReplicationDistributorLabel: string = localize('createProxy.ReplicationDistributor', 'Replication Distributor');
private static readonly ReplicationMergeLabel: string = localize('createProxy.ReplicationMerge', 'Replication Merge');
private static readonly ReplicationQueueReaderLabel: string = localize('createProxy.ReplicationQueueReader', 'Replication Queue Reader');
private static readonly SSASQueryLabel: string = localize('createProxy.SSASQueryLabel', 'SQL Server Analysis Services Query');
private static readonly SSASCommandLabel: string = localize('createProxy.SSASCommandLabel', 'SQL Server Analysis Services Command');
private static readonly SSISPackageLabel: string = localize('createProxy.SSISPackage', 'SQL Server Integration Services Package');
private static readonly PowerShellLabel: string = localize('createProxy.PowerShell', 'PowerShell');
private static readonly SubSystemHeadingLabel: string = localize('createProxy.subSystemHeading', 'Active to the following subsytems');
// UI Components // UI Components
private generalTab: sqlops.window.modelviewdialog.DialogTab; private generalTab: sqlops.window.modelviewdialog.DialogTab;
// General tab controls // General tab controls
private proxyNameTextBox: sqlops.InputBoxComponent; private proxyNameTextBox: sqlops.InputBoxComponent;
private credentialNameTextBox: sqlops.InputBoxComponent; private credentialNameDropDown: sqlops.DropDownComponent;
private descriptionTextBox: sqlops.InputBoxComponent; private descriptionTextBox: sqlops.InputBoxComponent;
private subsystemsTable: sqlops.TableComponent; private subsystemCheckBox: sqlops.CheckBoxComponent;
private operatingSystemCheckBox: sqlops.CheckBoxComponent;
private replicationSnapshotCheckBox: sqlops.CheckBoxComponent;
private replicationTransactionLogCheckBox: sqlops.CheckBoxComponent;
private replicationDistributorCheckBox: sqlops.CheckBoxComponent;
private replicationMergeCheckbox: sqlops.CheckBoxComponent;
private replicationQueueReaderCheckbox: sqlops.CheckBoxComponent;
private sqlQueryCheckBox: sqlops.CheckBoxComponent;
private sqlCommandCheckBox: sqlops.CheckBoxComponent;
private sqlIntegrationServicesPackageCheckbox: sqlops.CheckBoxComponent;
private powershellCheckBox: sqlops.CheckBoxComponent;
constructor(ownerUri: string) { private credentials: sqlops.CredentialInfo[];
super(ownerUri, new ProxyData(ownerUri), ProxyDialog.DialogTitle);
constructor(ownerUri: string, proxyInfo: sqlops.AgentProxyInfo = undefined, credentials: sqlops.CredentialInfo[]) {
super(
ownerUri,
new ProxyData(ownerUri, proxyInfo),
proxyInfo ? ProxyDialog.EditDialogTitle : ProxyDialog.CreateDialogTitle);
this.credentials = credentials;
} }
protected async initializeDialog(dialog: sqlops.window.modelviewdialog.Dialog) { protected async initializeDialog(dialog: sqlops.window.modelviewdialog.Dialog) {
this.generalTab = sqlops.window.modelviewdialog.createTab(ProxyDialog.GeneralTabText); this.generalTab = sqlops.window.modelviewdialog.createTab(ProxyDialog.GeneralTabText);
this.initializeGeneralTab(); this.initializeGeneralTab();
this.dialog.content = [this.generalTab]; this.dialog.content = [this.generalTab];
@@ -49,43 +77,143 @@ export class ProxyDialog extends AgentDialog<ProxyData> {
private initializeGeneralTab() { private initializeGeneralTab() {
this.generalTab.registerContent(async view => { this.generalTab.registerContent(async view => {
this.proxyNameTextBox = view.modelBuilder.inputBox().component(); this.proxyNameTextBox = view.modelBuilder.inputBox()
.withProperties({width: 420})
.component();
this.credentialNameTextBox = view.modelBuilder.inputBox().component(); this.credentialNameDropDown = view.modelBuilder.dropDown()
this.descriptionTextBox = view.modelBuilder.inputBox().component();
this.subsystemsTable = view.modelBuilder.table()
.withProperties({ .withProperties({
columns: [ width: 432,
ProxyDialog.SubsystemNameColumnLabel value: '',
], editable: true,
data: [], values: this.credentials.length > 0 ? this.credentials.map(c => c.name) : ['']
height: 500 })
.component();
this.descriptionTextBox = view.modelBuilder.inputBox()
.withProperties({
width: 420,
multiline: true,
height: 300
})
.component();
this.subsystemCheckBox = view.modelBuilder.checkBox()
.withProperties({
label: ProxyDialog.SubsystemLabel
}).component(); }).component();
this.subsystemCheckBox.onChanged(() => {
if (this.subsystemCheckBox.checked) {
this.operatingSystemCheckBox.checked = true;
this.replicationSnapshotCheckBox.checked = true;
this.replicationTransactionLogCheckBox.checked = true;
this.replicationDistributorCheckBox.checked = true;
this.replicationMergeCheckbox.checked = true;
this.replicationQueueReaderCheckbox.checked = true;
this.sqlQueryCheckBox.checked = true;
this.sqlCommandCheckBox.checked = true;
this.sqlIntegrationServicesPackageCheckbox.checked = true;
this.powershellCheckBox.checked = true;
} else {
this.operatingSystemCheckBox.checked = false;
this.replicationSnapshotCheckBox.checked = false;
this.replicationTransactionLogCheckBox.checked = false;
this.replicationDistributorCheckBox.checked = false;
this.replicationMergeCheckbox.checked = false;
this.replicationQueueReaderCheckbox.checked = false;
this.sqlQueryCheckBox.checked = false;
this.sqlCommandCheckBox.checked = false;
this.sqlIntegrationServicesPackageCheckbox.checked = false;
this.powershellCheckBox.checked = false;
}
});
this.operatingSystemCheckBox = view.modelBuilder.checkBox()
.withProperties({
label: ProxyDialog.OperatingSystemLabel
}).component();
this.replicationSnapshotCheckBox = view.modelBuilder.checkBox()
.withProperties({
label: ProxyDialog.ReplicationSnapshotLabel
}).component();
this.replicationTransactionLogCheckBox = view.modelBuilder.checkBox()
.withProperties({
label: ProxyDialog.ReplicationTransactionLogLabel
}).component();
this.replicationDistributorCheckBox = view.modelBuilder.checkBox()
.withProperties({
label: ProxyDialog.ReplicationDistributorLabel
}).component();
this.replicationMergeCheckbox = view.modelBuilder.checkBox()
.withProperties({
label: ProxyDialog.ReplicationMergeLabel
}).component();
this.replicationQueueReaderCheckbox = view.modelBuilder.checkBox()
.withProperties({
label: ProxyDialog.ReplicationQueueReaderLabel
}).component();
this.sqlQueryCheckBox = view.modelBuilder.checkBox()
.withProperties({
label: ProxyDialog.SSASQueryLabel
}).component();
this.sqlCommandCheckBox = view.modelBuilder.checkBox()
.withProperties({
label: ProxyDialog.SSASCommandLabel
}).component();
this.sqlIntegrationServicesPackageCheckbox = view.modelBuilder.checkBox()
.withProperties({
label: ProxyDialog.SSISPackageLabel
}).component();
this.powershellCheckBox = view.modelBuilder.checkBox()
.withProperties({
label: ProxyDialog.PowerShellLabel
}).component();
let checkBoxContainer = view.modelBuilder.groupContainer()
.withItems([this.operatingSystemCheckBox, this.replicationSnapshotCheckBox,
this.replicationTransactionLogCheckBox, this.replicationDistributorCheckBox, this.replicationMergeCheckbox,
this.replicationQueueReaderCheckbox, this.sqlQueryCheckBox, this.sqlCommandCheckBox, this.sqlIntegrationServicesPackageCheckbox,
this.powershellCheckBox])
.component();
let formModel = view.modelBuilder.formContainer() let formModel = view.modelBuilder.formContainer()
.withFormItems([{ .withFormItems([{
component: this.proxyNameTextBox, component: this.proxyNameTextBox,
title: ProxyDialog.ProxyNameTextBoxLabel title: ProxyDialog.ProxyNameTextBoxLabel
}, { }, {
component: this.credentialNameTextBox, component: this.credentialNameDropDown,
title: ProxyDialog.CredentialNameTextBoxLabel title: ProxyDialog.CredentialNameTextBoxLabel
}, { }, {
component: this.descriptionTextBox, component: this.descriptionTextBox,
title: ProxyDialog.DescriptionTextBoxLabel title: ProxyDialog.DescriptionTextBoxLabel
}, { }]).withLayout({ width: 420 }).component();
component: this.subsystemsTable,
title: ProxyDialog.SubsystemsTableLabel
}]).withLayout({ width: '100%' }).component();
await view.initializeModel(formModel); await view.initializeModel(formModel);
this.proxyNameTextBox.value = this.model.accountName;
this.credentialNameDropDown.value = this.model.credentialName;
this.descriptionTextBox.value = this.model.description;
}); });
} }
protected updateModel() { protected updateModel() {
this.model.accountName = this.proxyNameTextBox.value; this.model.accountName = this.proxyNameTextBox.value;
this.model.credentialName = this.credentialNameTextBox.value; this.model.credentialName = this.credentialNameDropDown.value as string;
this.model.credentialId = this.credentials.find(
c => c.name === this.model.credentialName).id;
this.model.credentialIdentity = this.credentials.find(
c => c.name === this.model.credentialName).identity;
this.model.description = this.descriptionTextBox.value; this.model.description = this.descriptionTextBox.value;
} }
} }

View File

@@ -4,17 +4,21 @@
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
'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 { ScheduleData } from '../data/scheduleData'; import { ScheduleData } from '../data/scheduleData';
const localize = nls.loadMessageBundle();
export class ScheduleDialog { export class ScheduleDialog {
// Top level // Top level
private readonly DialogTitle: string = 'New Schedule'; private readonly DialogTitle: string = localize('scheduleDialog.newSchedule', 'New Schedule');
private readonly OkButtonText: string = 'OK'; private readonly OkButtonText: string = localize('scheduleDialog.ok', 'OK');
private readonly CancelButtonText: string = 'Cancel'; private readonly CancelButtonText: string = localize('scheduleDialog.cancel', 'Cancel');
private readonly ScheduleNameText: string = localize('scheduleDialog.scheduleName', 'Schedule Name');
private readonly SchedulesLabelText: string = localize('scheduleDialog.schedules', 'Schedules');
// UI Components // UI Components
private dialog: sqlops.window.modelviewdialog.Dialog; private dialog: sqlops.window.modelviewdialog.Dialog;
@@ -46,7 +50,7 @@ export class ScheduleDialog {
this.schedulesTable = view.modelBuilder.table() this.schedulesTable = view.modelBuilder.table()
.withProperties({ .withProperties({
columns: [ columns: [
'Schedule Name' this.ScheduleNameText
], ],
data: [], data: [],
height: 600, height: 600,
@@ -56,7 +60,7 @@ export class ScheduleDialog {
let formModel = view.modelBuilder.formContainer() let formModel = view.modelBuilder.formContainer()
.withFormItems([{ .withFormItems([{
component: this.schedulesTable, component: this.schedulesTable,
title: 'Schedules' title: this.SchedulesLabelText
}]).withLayout({ width: '100%' }).component(); }]).withLayout({ width: '100%' }).component();
await view.initializeModel(formModel); await view.initializeModel(formModel);

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,12 +27,17 @@ 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
*/ */
public activate(): void { public activate(): void {
vscode.commands.registerCommand('agent.openCreateJobDialog', (ownerUri: string) => { vscode.commands.registerCommand('agent.openJobDialog', (ownerUri: string, jobInfo: sqlops.AgentJobInfo) => {
let dialog = new JobDialog(ownerUri); let dialog = new JobDialog(ownerUri, jobInfo);
dialog.openDialog(); dialog.openDialog();
}); });
vscode.commands.registerCommand('agent.openNewStepDialog', (ownerUri: string, jobId: string, server: string, stepId: number) => { vscode.commands.registerCommand('agent.openNewStepDialog', (ownerUri: string, jobId: string, server: string, stepId: number) => {
@@ -39,17 +48,19 @@ export class MainController {
let dialog = new PickScheduleDialog(ownerUri); let dialog = new PickScheduleDialog(ownerUri);
dialog.showDialog(); dialog.showDialog();
}); });
vscode.commands.registerCommand('agent.openAlertDialog', (ownerUri: string, alertInfo: sqlops.AgentAlertInfo) => { vscode.commands.registerCommand('agent.openAlertDialog', (ownerUri: string, alertInfo: sqlops.AgentAlertInfo, jobs: string[]) => {
let dialog = new AlertDialog(ownerUri, alertInfo); let dialog = new AlertDialog(ownerUri, alertInfo, jobs);
dialog.openDialog(); dialog.openDialog();
}); });
vscode.commands.registerCommand('agent.openCreateOperatorDialog', (ownerUri: string) => { vscode.commands.registerCommand('agent.openOperatorDialog', (ownerUri: string, operatorInfo: sqlops.AgentOperatorInfo) => {
let dialog = new OperatorDialog(ownerUri); let dialog = new OperatorDialog(ownerUri, operatorInfo);
dialog.openDialog(); dialog.openDialog();
}); });
vscode.commands.registerCommand('agent.openCreateProxyDialog', (ownerUri: string) => { vscode.commands.registerCommand('agent.openProxyDialog', (ownerUri: string, proxyInfo: sqlops.AgentProxyInfo, credentials: sqlops.CredentialInfo[]) => {
let dialog = new ProxyDialog(ownerUri); //@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

@@ -15,7 +15,7 @@ const testOwnerUri = 'agent://testuri';
suite('Agent extension', () => { suite('Agent extension', () => {
test('Create Job Data', async () => { test('Create Job Data', async () => {
let testAgentService = new TestAgentService(); let testAgentService = new TestAgentService();
let data = new JobData(testOwnerUri, testAgentService); let data = new JobData(testOwnerUri, undefined, testAgentService);
data.save(); data.save();
}); });
}); });

View File

@@ -86,6 +86,11 @@ export class TestAgentService implements sqlops.AgentServicesProvider {
return undefined; return undefined;
} }
// Agent Credential method
getCredentials(ownerUri: string): Thenable<sqlops.GetCredentialsResult> {
return undefined;
}
// Job Schedule management methods // Job Schedule management methods
getJobSchedules(ownerUri: string): Thenable<sqlops.AgentJobSchedulesResult> { getJobSchedules(ownerUri: string): Thenable<sqlops.AgentJobSchedulesResult> {
return undefined; return undefined;

View File

@@ -658,7 +658,7 @@
} }
}, },
"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.2",
"vscode-extension-telemetry": "^0.0.15" "vscode-extension-telemetry": "^0.0.15"

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.4", "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

@@ -148,6 +148,11 @@ export interface DeleteAgentProxyParams {
proxy: sqlops.AgentProxyInfo; proxy: sqlops.AgentProxyInfo;
} }
// Agent Credentials parameters
export interface GetCredentialsParams {
ownerUri: string;
}
// Job Schedule management parameters // Job Schedule management parameters
export interface AgentJobScheduleParams { export interface AgentJobScheduleParams {
ownerUri: string; ownerUri: string;
@@ -262,6 +267,11 @@ export namespace DeleteAgentProxyRequest {
export const type = new RequestType<DeleteAgentProxyParams, sqlops.ResultStatus, void, void>('agent/deleteproxy'); export const type = new RequestType<DeleteAgentProxyParams, sqlops.ResultStatus, void, void>('agent/deleteproxy');
} }
// Agent Credentials request
export namespace AgentCredentialsRequest {
export const type = new RequestType<GetCredentialsParams, sqlops.GetCredentialsResult, void, void>('security/credentials');
}
// Job Schedules requests // Job Schedules requests
export namespace AgentJobSchedulesRequest { export namespace AgentJobSchedulesRequest {
export const type = new RequestType<AgentJobScheduleParams, sqlops.AgentJobSchedulesResult, void, void>('agent/schedules'); export const type = new RequestType<AgentJobScheduleParams, sqlops.AgentJobSchedulesResult, void, void>('agent/schedules');

View File

@@ -438,6 +438,22 @@ export class AgentServicesFeature extends SqlOpsFeature<undefined> {
); );
}; };
// Agent Credential Method
let getCredentials = (ownerUri: string): Thenable<sqlops.GetCredentialsResult> => {
let params: contracts.GetCredentialsParams = {
ownerUri: ownerUri
};
let requestType = contracts.AgentCredentialsRequest.type;
return client.sendRequest(requestType, params).then(
r => r,
e => {
client.logFailedRequest(requestType, e);
return Promise.resolve(undefined);
}
);
};
// Job Schedule management methods // Job Schedule management methods
let getJobSchedules = (ownerUri: string): Thenable<sqlops.AgentJobSchedulesResult> => { let getJobSchedules = (ownerUri: string): Thenable<sqlops.AgentJobSchedulesResult> => {
let params: contracts.AgentJobScheduleParams = { let params: contracts.AgentJobScheduleParams = {
@@ -532,6 +548,7 @@ export class AgentServicesFeature extends SqlOpsFeature<undefined> {
createProxy, createProxy,
updateProxy, updateProxy,
deleteProxy, deleteProxy,
getCredentials,
getJobSchedules, getJobSchedules,
createJobSchedule, createJobSchedule,
updateJobSchedule, updateJobSchedule,

View File

@@ -44,22 +44,6 @@ export class Telemetry {
private static platformInformation: PlatformInformation; private static platformInformation: PlatformInformation;
private static disabled: boolean; private static disabled: boolean;
// Get the unique ID for the current user of the extension
public static getUserId(): Promise<string> {
return new Promise<string>(resolve => {
// Generate the user id if it has not been created already
if (typeof this.userId === 'undefined') {
let id = Utils.generateUserId();
id.then(newId => {
this.userId = newId;
resolve(this.userId);
});
} else {
resolve(this.userId);
}
});
}
public static getPlatformInformation(): Promise<PlatformInformation> { public static getPlatformInformation(): Promise<PlatformInformation> {
if (this.platformInformation) { if (this.platformInformation) {
return Promise.resolve(this.platformInformation); return Promise.resolve(this.platformInformation);
@@ -143,8 +127,7 @@ export class Telemetry {
} }
// Augment the properties structure with additional common properties before sending // Augment the properties structure with additional common properties before sending
Promise.all([this.getUserId(), this.getPlatformInformation()]).then(() => { Promise.all([this.getPlatformInformation()]).then(() => {
properties['userId'] = this.userId;
properties['distribution'] = (this.platformInformation && this.platformInformation.distribution) ? properties['distribution'] = (this.platformInformation && this.platformInformation.distribution) ?
`${this.platformInformation.distribution.name}, ${this.platformInformation.distribution.version}` : ''; `${this.platformInformation.distribution.name}, ${this.platformInformation.distribution.version}` : '';

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"

View File

@@ -1,6 +1,6 @@
{ {
"name": "sqlops", "name": "sqlops",
"version": "0.31.2", "version": "0.32.1",
"distro": "8c3e97e3425cc9814496472ab73e076de2ba99ee", "distro": "8c3e97e3425cc9814496472ab73e076de2ba99ee",
"author": { "author": {
"name": "Microsoft Corporation" "name": "Microsoft Corporation"
@@ -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.22", "slickgrid": "github:anthonydresser/SlickGrid#2.3.23",
"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(() => {
@@ -285,7 +285,6 @@ export default class MainController implements vscode.Disposable {
tab1.registerContent(async (view) => { tab1.registerContent(async (view) => {
await this.getTabContent(view, customButton1, customButton2, 400); await this.getTabContent(view, customButton1, customButton2, 400);
}); });
sqlops.window.modelviewdialog.openDialog(dialog); sqlops.window.modelviewdialog.openDialog(dialog);
} }
@@ -302,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();
} }
@@ -379,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

@@ -63,13 +63,13 @@ export class InputBox extends vsInputBox {
this.enabledInputBorder = this.inputBorder; this.enabledInputBorder = this.inputBorder;
this.disabledInputBackground = styles.disabledInputBackground; this.disabledInputBackground = styles.disabledInputBackground;
this.disabledInputForeground = styles.disabledInputForeground; this.disabledInputForeground = styles.disabledInputForeground;
this.updateInputEnabledDisabledColors();
this.applyStyles();
} }
public enable(): void { public enable(): void {
super.enable(); super.enable();
this.inputBackground = this.enabledInputBackground; this.updateInputEnabledDisabledColors();
this.inputForeground = this.enabledInputForeground;
this.inputBorder = this.enabledInputBorder;
this.applyStyles(); this.applyStyles();
} }
@@ -89,9 +89,7 @@ export class InputBox extends vsInputBox {
public disable(): void { public disable(): void {
super.disable(); super.disable();
this.inputBackground = this.disabledInputBackground; this.updateInputEnabledDisabledColors();
this.inputForeground = this.disabledInputForeground;
this.inputBorder = this.disabledInputBorder;
this.applyStyles(); this.applyStyles();
} }
@@ -121,4 +119,11 @@ export class InputBox extends vsInputBox {
super.showMessage(message, force); super.showMessage(message, force);
} }
} }
private updateInputEnabledDisabledColors(): void {
let enabled = this.isEnabled();
this.inputBackground = enabled ? this.enabledInputBackground : this.disabledInputBackground;
this.inputForeground = enabled ? this.enabledInputForeground : this.disabledInputForeground;
this.inputBorder = enabled ? this.enabledInputBorder : this.disabledInputBorder;
}
} }

View File

@@ -17,7 +17,7 @@ export function appendRow(container: Builder, label: string, labelClass: string,
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) => {
@@ -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

@@ -170,7 +170,7 @@ export abstract class Modal extends Disposable implements IThemable {
} }
modalHeader.div({ class: 'modal-title' }, (modalTitle) => { modalHeader.div({ class: 'modal-title' }, (modalTitle) => {
this._modalTitle = modalTitle; this._modalTitle = modalTitle;
modalTitle.innerHtml(this._title); modalTitle.text(this._title);
}); });
}); });
parts.push(this._modalHeaderSection.getHTMLElement()); parts.push(this._modalHeaderSection.getHTMLElement());
@@ -433,7 +433,7 @@ export abstract class Modal extends Disposable implements IThemable {
*/ */
protected set title(title: string) { protected set title(title: string) {
if (this._title !== undefined) { if (this._title !== undefined) {
this._modalTitle.innerHtml(title); this._modalTitle.text(title);
} }
} }

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

@@ -5,8 +5,9 @@
import { Component, Input, ContentChild, OnDestroy, TemplateRef, ChangeDetectorRef, forwardRef, Inject } from '@angular/core'; import { Component, Input, ContentChild, OnDestroy, TemplateRef, ChangeDetectorRef, forwardRef, Inject } from '@angular/core';
import { Action } from 'vs/base/common/actions'; import { Action } from 'vs/base/common/actions';
import { Disposable } from 'vs/base/common/lifecycle';
export abstract class TabChild { export abstract class TabChild extends Disposable {
public abstract layout(): void; public abstract layout(): void;
} }
@@ -19,7 +20,7 @@ export abstract class TabChild {
` `
}) })
export class TabComponent implements OnDestroy { export class TabComponent implements OnDestroy {
@ContentChild(TabChild) private _child: TabChild; private _child: TabChild;
@ContentChild(TemplateRef) templateRef; @ContentChild(TemplateRef) templateRef;
@Input() public title: string; @Input() public title: string;
@Input() public canClose: boolean; @Input() public canClose: boolean;
@@ -29,19 +30,30 @@ export class TabComponent implements OnDestroy {
@Input() public identifier: string; @Input() public identifier: string;
@Input() private visibilityType: 'if' | 'visibility' = 'if'; @Input() private visibilityType: 'if' | 'visibility' = 'if';
private rendered = false; private rendered = false;
private destroyed: boolean = false;
@ContentChild(TabChild) private set child(tab: TabChild) {
this._child = tab;
if (this.active && this._child) {
this._child.layout();
}
}
constructor( constructor(
@Inject(forwardRef(() => ChangeDetectorRef)) private _cd: ChangeDetectorRef @Inject(forwardRef(() => ChangeDetectorRef)) private _cd: ChangeDetectorRef
) { } ) { }
public set active(val: boolean) { public set active(val: boolean) {
this._active = val; if (!this.destroyed) {
if (this.active) { this._active = val;
this.rendered = true; if (this.active) {
} this.rendered = true;
this._cd.detectChanges(); }
if (this.active && this._child) { this._cd.detectChanges();
this._child.layout(); if (this.active && this._child) {
this._child.layout();
}
} }
} }
@@ -50,6 +62,7 @@ export class TabComponent implements OnDestroy {
} }
ngOnDestroy() { ngOnDestroy() {
this.destroyed = true;
if (this.actions && this.actions.length > 0) { if (this.actions && this.actions.length > 0) {
this.actions.forEach((action) => action.dispose()); this.actions.forEach((action) => action.dispose());
} }
@@ -74,6 +87,8 @@ export class TabComponent implements OnDestroy {
} }
public layout() { public layout() {
this._child.layout(); if (this._child) {
this._child.layout();
}
} }
} }

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

@@ -84,7 +84,7 @@
{ {
border: 1px solid #BFBDBD; border: 1px solid #BFBDBD;
font-size: 8pt; font-size: 8pt;
height: 400px; height: 250px;
margin-top: 6px; margin-top: 6px;
overflow: scroll; overflow: scroll;
padding: 4px; padding: 4px;

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

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

@@ -13,3 +13,6 @@ export const SerializationDisabled = 'Saving results into different format disab
*/ */
export const RestoreFeatureName = 'restore'; export const RestoreFeatureName = 'restore';
export const BackupFeatureName = 'backup'; export const BackupFeatureName = 'backup';
export const MssqlProviderId = 'MSSQL';

View File

@@ -81,6 +81,15 @@
content: url("status_info.svg"); content: url("status_info.svg");
} }
.vs .icon.help {
content: url("help.svg");
}
.vs-dark .icon.help,
.hc-black .icon.help {
content: url("help_inverse.svg");
}
.vs .icon.success, .vs .icon.success,
.vs-dark .icon.success, .vs-dark .icon.success,
.hc-black .icon.success { .hc-black .icon.success {

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><title>help_16x16</title><path d="M10.13.45A8.14,8.14,0,0,1,12,1.23a8,8,0,0,1,1.62,1.21A7.8,7.8,0,0,1,14.91,4a7.59,7.59,0,0,1,.81,1.85,7.6,7.6,0,0,1,0,4.12,7.59,7.59,0,0,1-.81,1.85,7.8,7.8,0,0,1-1.25,1.57A8,8,0,0,1,12,14.62a8.2,8.2,0,0,1-8.08,0,8,8,0,0,1-1.62-1.21,7.8,7.8,0,0,1-1.25-1.57A7.63,7.63,0,0,1,.28,10a7.6,7.6,0,0,1,0-4.12A7.63,7.63,0,0,1,1.09,4,7.8,7.8,0,0,1,2.34,2.44,8,8,0,0,1,4,1.23,8.26,8.26,0,0,1,10.13.45Zm-.27,14a7.17,7.17,0,0,0,1.67-.69,7,7,0,0,0,1.41-1.06,6.75,6.75,0,0,0,1.8-3,6.59,6.59,0,0,0,0-3.6,6.75,6.75,0,0,0-1.8-3,7,7,0,0,0-1.41-1.06,7.17,7.17,0,0,0-1.67-.69,7.21,7.21,0,0,0-3.72,0,7.17,7.17,0,0,0-1.67.69A7,7,0,0,0,3.05,3.13,6.82,6.82,0,0,0,2,4.5a6.72,6.72,0,0,0-.71,1.62,6.59,6.59,0,0,0,0,3.6A6.72,6.72,0,0,0,2,11.35a6.82,6.82,0,0,0,1.09,1.37,7,7,0,0,0,1.41,1.06,7.17,7.17,0,0,0,1.67.69,7.21,7.21,0,0,0,3.72,0ZM9,4a2.75,2.75,0,0,1,.85.56,2.61,2.61,0,0,1,.57.82,2.44,2.44,0,0,1,.21,1,2.08,2.08,0,0,1-.16.85,2.82,2.82,0,0,1-.4.65,4.28,4.28,0,0,1-.51.53c-.18.16-.36.32-.51.48a2.55,2.55,0,0,0-.4.51,1.19,1.19,0,0,0-.16.61v.52H7.46V10a2.09,2.09,0,0,1,.16-.85A2.86,2.86,0,0,1,8,8.5,4.28,4.28,0,0,1,8.54,8c.18-.16.36-.32.51-.48A2.51,2.51,0,0,0,9.45,7a1.18,1.18,0,0,0,.16-.61,1.49,1.49,0,0,0-.13-.61,1.56,1.56,0,0,0-.34-.49A1.63,1.63,0,0,0,8,4.81a1.63,1.63,0,0,0-1.14.45,1.57,1.57,0,0,0-.34.49,1.49,1.49,0,0,0-.13.61H5.32a2.41,2.41,0,0,1,.21-1,2.68,2.68,0,0,1,.58-.82A2.77,2.77,0,0,1,7,4a2.62,2.62,0,0,1,1-.21A2.65,2.65,0,0,1,9,4ZM8.54,12.6H7.46v-1H8.54Z"/></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#fff;}</style></defs><title>help_inverse_16x16</title><path class="cls-1" d="M10.13.45A8.14,8.14,0,0,1,12,1.23a8,8,0,0,1,1.62,1.21A7.8,7.8,0,0,1,14.91,4a7.59,7.59,0,0,1,.81,1.85,7.6,7.6,0,0,1,0,4.12,7.59,7.59,0,0,1-.81,1.85,7.8,7.8,0,0,1-1.25,1.57A8,8,0,0,1,12,14.62a8.2,8.2,0,0,1-8.08,0,8,8,0,0,1-1.62-1.21,7.8,7.8,0,0,1-1.25-1.57A7.63,7.63,0,0,1,.28,10a7.6,7.6,0,0,1,0-4.12A7.63,7.63,0,0,1,1.09,4,7.8,7.8,0,0,1,2.34,2.44,8,8,0,0,1,4,1.23,8.26,8.26,0,0,1,10.13.45Zm-.27,14a7.17,7.17,0,0,0,1.67-.69,7,7,0,0,0,1.41-1.06,6.75,6.75,0,0,0,1.8-3,6.59,6.59,0,0,0,0-3.6,6.75,6.75,0,0,0-1.8-3,7,7,0,0,0-1.41-1.06,7.17,7.17,0,0,0-1.67-.69,7.21,7.21,0,0,0-3.72,0,7.17,7.17,0,0,0-1.67.69A7,7,0,0,0,3.05,3.13,6.82,6.82,0,0,0,2,4.5a6.72,6.72,0,0,0-.71,1.62,6.59,6.59,0,0,0,0,3.6A6.72,6.72,0,0,0,2,11.35a6.82,6.82,0,0,0,1.09,1.37,7,7,0,0,0,1.41,1.06,7.17,7.17,0,0,0,1.67.69,7.21,7.21,0,0,0,3.72,0ZM9,4a2.75,2.75,0,0,1,.85.56,2.61,2.61,0,0,1,.57.82,2.44,2.44,0,0,1,.21,1,2.08,2.08,0,0,1-.16.85,2.82,2.82,0,0,1-.4.65,4.28,4.28,0,0,1-.51.53c-.18.16-.36.32-.51.48a2.55,2.55,0,0,0-.4.51,1.19,1.19,0,0,0-.16.61v.52H7.46V10a2.09,2.09,0,0,1,.16-.85A2.86,2.86,0,0,1,8,8.5,4.28,4.28,0,0,1,8.54,8c.18-.16.36-.32.51-.48A2.51,2.51,0,0,0,9.45,7a1.18,1.18,0,0,0,.16-.61,1.49,1.49,0,0,0-.13-.61,1.56,1.56,0,0,0-.34-.49A1.63,1.63,0,0,0,8,4.81a1.63,1.63,0,0,0-1.14.45,1.57,1.57,0,0,0-.34.49,1.49,1.49,0,0,0-.13.61H5.32a2.41,2.41,0,0,1,.21-1,2.68,2.68,0,0,1,.58-.82A2.77,2.77,0,0,1,7,4a2.62,2.62,0,0,1,1-.21A2.65,2.65,0,0,1,9,4ZM8.54,12.6H7.46v-1H8.54Z"/></svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -131,7 +131,7 @@ export class AccountDialog extends Modal {
this._noaccountViewContainer = DOM.$('div.no-account-view'); this._noaccountViewContainer = DOM.$('div.no-account-view');
let noAccountTitle = DOM.append(this._noaccountViewContainer, DOM.$('.no-account-view-label')); let noAccountTitle = DOM.append(this._noaccountViewContainer, DOM.$('.no-account-view-label'));
let noAccountLabel = localize('accountDialog.noAccountLabel', 'There is no linked account. Please add an account.'); let noAccountLabel = localize('accountDialog.noAccountLabel', 'There is no linked account. Please add an account.');
noAccountTitle.innerHTML = noAccountLabel; noAccountTitle.innerText = noAccountLabel;
// Show the add account button for the first provider // Show the add account button for the first provider
// Todo: If we have more than 1 provider, need to show all add account buttons for all providers // Todo: If we have more than 1 provider, need to show all add account buttons for all providers

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

@@ -8,7 +8,7 @@
import { RawContextKey, IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { RawContextKey, IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { IConnectionProfile } from 'sqlops'; import { IConnectionProfile } from 'sqlops';
export class ConnectionContextkey implements IContextKey<IConnectionProfile> { export class ConnectionContextKey implements IContextKey<IConnectionProfile> {
static Provider = new RawContextKey<string>('connectionProvider', undefined); static Provider = new RawContextKey<string>('connectionProvider', undefined);
static Server = new RawContextKey<string>('serverName', undefined); static Server = new RawContextKey<string>('serverName', undefined);
@@ -23,10 +23,10 @@ export class ConnectionContextkey implements IContextKey<IConnectionProfile> {
constructor( constructor(
@IContextKeyService contextKeyService: IContextKeyService @IContextKeyService contextKeyService: IContextKeyService
) { ) {
this._providerKey = ConnectionContextkey.Provider.bindTo(contextKeyService); this._providerKey = ConnectionContextKey.Provider.bindTo(contextKeyService);
this._serverKey = ConnectionContextkey.Server.bindTo(contextKeyService); this._serverKey = ConnectionContextKey.Server.bindTo(contextKeyService);
this._databaseKey = ConnectionContextkey.Database.bindTo(contextKeyService); this._databaseKey = ConnectionContextKey.Database.bindTo(contextKeyService);
this._connectionKey = ConnectionContextkey.Connection.bindTo(contextKeyService); this._connectionKey = ConnectionContextKey.Connection.bindTo(contextKeyService);
} }
set(value: IConnectionProfile) { set(value: IConnectionProfile) {

View File

@@ -174,7 +174,7 @@ export interface IConnectionManagementService {
disconnectEditor(owner: IConnectableInput, force?: boolean): Promise<boolean>; disconnectEditor(owner: IConnectableInput, force?: boolean): Promise<boolean>;
disconnect(connection: ConnectionProfile): Promise<void>; disconnect(connection: IConnectionProfile): Promise<void>;
disconnect(ownerUri: string): Promise<void>; disconnect(ownerUri: string): Promise<void>;
@@ -208,7 +208,7 @@ export interface IConnectionManagementService {
*/ */
cancelEditorConnection(owner: IConnectableInput): Thenable<boolean>; cancelEditorConnection(owner: IConnectableInput): Thenable<boolean>;
showDashboard(connection: ConnectionProfile): Thenable<boolean>; showDashboard(connection: IConnectionProfile): Thenable<boolean>;
closeDashboard(uri: string): void; closeDashboard(uri: string): void;
@@ -216,7 +216,7 @@ export interface IConnectionManagementService {
hasRegisteredServers(): boolean; hasRegisteredServers(): boolean;
canChangeConnectionConfig(profile: ConnectionProfile, newGroupID: string): boolean; canChangeConnectionConfig(profile: IConnectionProfile, newGroupID: string): boolean;
getTabColorForUri(uri: string): string; getTabColorForUri(uri: string): string;
@@ -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

@@ -553,7 +553,7 @@ export class ConnectionManagementService extends Disposable implements IConnecti
}); });
} }
public showDashboard(connection: ConnectionProfile): Thenable<boolean> { public showDashboard(connection: IConnectionProfile): Thenable<boolean> {
return this.showDashboardForConnectionManagementInfo(connection); return this.showDashboardForConnectionManagementInfo(connection);
} }

View File

@@ -240,7 +240,7 @@ export class ConnectionStore {
return connectionProfile; return connectionProfile;
} else { } else {
return undefined; return undefined;
}; }
}); });
} }
@@ -352,7 +352,7 @@ export class ConnectionStore {
list.unshift(savedProfile); list.unshift(savedProfile);
let newList = list.map(c => { let newList = list.map(c => {
let connectionProfile = c ? c.toIConnectionProfile() : undefined;; let connectionProfile = c ? c.toIConnectionProfile() : undefined;
return connectionProfile; return connectionProfile;
}); });
return newList.filter(n => n !== undefined); return newList.filter(n => n !== undefined);
@@ -372,7 +372,7 @@ export class ConnectionStore {
}); });
let newList = list.map(c => { let newList = list.map(c => {
let connectionProfile = c ? c.toIConnectionProfile() : undefined;; let connectionProfile = c ? c.toIConnectionProfile() : undefined;
return connectionProfile; return connectionProfile;
}); });
return newList.filter(n => n !== undefined); return newList.filter(n => n !== undefined);

View File

@@ -339,9 +339,9 @@ export class ConnectionDialogService implements IConnectionDialogService {
if (!platform.isWindows && types.isString(message) && message.toLowerCase().includes('kerberos') && message.toLowerCase().includes('kinit')) { if (!platform.isWindows && types.isString(message) && message.toLowerCase().includes('kerberos') && message.toLowerCase().includes('kinit')) {
message = [ message = [
localize('kerberosErrorStart', "Connection failed due to Kerberos error."), localize('kerberosErrorStart', "Connection failed due to Kerberos error."),
localize('kerberosHelpLink', "&nbsp;Help configuring Kerberos is available at ") + helpLink, localize('kerberosHelpLink', "Help configuring Kerberos is available at {0}", helpLink),
localize('kerberosKinit', "&nbsp;If you have previously connected you may need to re-run kinit.") localize('kerberosKinit', "If you have previously connected you may need to re-run kinit.")
].join('<br/>'); ].join('\r\n');
actions.push(new Action('Kinit', 'Run kinit', null, true, () => { actions.push(new Action('Kinit', 'Run kinit', null, true, () => {
this._connectionDialog.close(); this._connectionDialog.close();
this._clipboardService.writeText('kinit\r'); this._clipboardService.writeText('kinit\r');

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

@@ -32,22 +32,10 @@ export abstract class DashboardTab extends TabChild implements OnDestroy {
public enableEdit(): void { public enableEdit(): void {
// no op // no op
} }
private _toDispose: IDisposable[] = [];
constructor() { constructor() {
super(); super();
} }
public dispose(): void {
this._toDispose = dispose(this._toDispose);
}
protected _register<T extends IDisposable>(t: T): T {
this._toDispose.push(t);
return t;
}
ngOnDestroy() { ngOnDestroy() {
this.dispose(); this.dispose();
} }

View File

@@ -40,7 +40,7 @@ export class DashboardErrorContainer extends DashboardTab implements AfterViewIn
ngAfterViewInit() { ngAfterViewInit() {
let errorMessage = this._errorMessageContainer.nativeElement as HTMLElement; let errorMessage = this._errorMessageContainer.nativeElement as HTMLElement;
errorMessage.innerHTML = nls.localize('dashboardNavSection_loadTabError', 'The "{0}" section has invalid content. Please contact extension owner.', this.tab.title); errorMessage.innerText = nls.localize('dashboardNavSection_loadTabError', 'The "{0}" section has invalid content. Please contact extension owner.', this.tab.title);
} }
public get id(): string { public get id(): string {

View File

@@ -18,7 +18,7 @@ import { DashboardModule } from './dashboard.module';
import { bootstrapAngular } from 'sql/services/bootstrap/bootstrapService'; import { bootstrapAngular } from 'sql/services/bootstrap/bootstrapService';
import { IDashboardComponentParams } from 'sql/services/bootstrap/bootstrapParams'; import { IDashboardComponentParams } from 'sql/services/bootstrap/bootstrapParams';
import { DASHBOARD_SELECTOR } from 'sql/parts/dashboard/dashboard.component'; import { DASHBOARD_SELECTOR } from 'sql/parts/dashboard/dashboard.component';
import { ConnectionContextkey } from 'sql/parts/connection/common/connectionContextKey'; import { ConnectionContextKey } from 'sql/parts/connection/common/connectionContextKey';
import { IDashboardService } from 'sql/services/dashboard/common/dashboardService'; import { IDashboardService } from 'sql/services/dashboard/common/dashboardService';
import { ConnectionProfile } from 'sql/parts/connection/common/connectionProfile'; import { ConnectionProfile } from 'sql/parts/connection/common/connectionProfile';
import { IConnectionProfile } from 'sql/parts/connection/common/interfaces'; import { IConnectionProfile } from 'sql/parts/connection/common/interfaces';
@@ -71,6 +71,7 @@ export class DashboardEditor extends BaseEditor {
* To be called when the container of this editor changes size. * To be called when the container of this editor changes size.
*/ */
public layout(dimension: DOM.Dimension): void { public layout(dimension: DOM.Dimension): void {
this._dashboardService.layout(dimension);
} }
public setInput(input: DashboardInput, options: EditorOptions): TPromise<void> { public setInput(input: DashboardInput, options: EditorOptions): TPromise<void> {
@@ -110,7 +111,7 @@ export class DashboardEditor extends BaseEditor {
let serverInfo = this._connMan.getConnectionInfo(this.input.uri).serverInfo; let serverInfo = this._connMan.getConnectionInfo(this.input.uri).serverInfo;
this._dashboardService.changeToDashboard({ profile, serverInfo }); this._dashboardService.changeToDashboard({ profile, serverInfo });
let scopedContextService = this._contextKeyService.createScoped(input.container); let scopedContextService = this._contextKeyService.createScoped(input.container);
let connectionContextKey = new ConnectionContextkey(scopedContextService); let connectionContextKey = new ConnectionContextKey(scopedContextService);
connectionContextKey.set(input.connectionProfile); connectionContextKey.set(input.connectionProfile);
let params: IDashboardComponentParams = { let params: IDashboardComponentParams = {

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

@@ -15,7 +15,6 @@ import {
import { ICapabilitiesService } from 'sql/services/capabilities/capabilitiesService'; import { ICapabilitiesService } from 'sql/services/capabilities/capabilitiesService';
import { ConnectionManagementInfo } from 'sql/parts/connection/common/connectionManagementInfo'; import { ConnectionManagementInfo } from 'sql/parts/connection/common/connectionManagementInfo';
import * as Constants from 'sql/parts/connection/common/constants'; import * as Constants from 'sql/parts/connection/common/constants';
import { OEAction } from 'sql/parts/objectExplorer/viewlet/objectExplorerActions';
import { IQueryEditorService } from 'sql/parts/query/common/queryEditorService'; import { IQueryEditorService } from 'sql/parts/query/common/queryEditorService';
import { IScriptingService } from 'sql/services/scripting/scriptingService'; import { IScriptingService } from 'sql/services/scripting/scriptingService';

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

@@ -24,6 +24,7 @@ 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 { escape } from 'sql/base/common/strings';
import { INotificationService } from 'vs/platform/notification/common/notification'; import { INotificationService } from 'vs/platform/notification/common/notification';
import Severity from 'vs/base/common/severity'; import Severity from 'vs/base/common/severity';
@@ -197,7 +198,7 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
rowIndex++; rowIndex++;
return { return {
values: row.cells.map(c => { values: row.cells.map(c => {
return mixin({ ariaLabel: c.displayValue }, c); return mixin({ ariaLabel: escape(c.displayValue) }, c);
}), row: row.id }), row: row.id
}; };
}); });
@@ -367,11 +368,12 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
columnDefinitions: resultSet.columnInfo.map((c, i) => { columnDefinitions: 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';
return { return {
id: i.toString(), id: i.toString(),
name: c.columnName === 'Microsoft SQL Server 2005 XML Showplan' name: c.columnName === 'Microsoft SQL Server 2005 XML Showplan'
? 'XML Showplan' ? 'XML Showplan'
: c.columnName, : escape(c.columnName),
type: self.stringToFieldType('string'), type: self.stringToFieldType('string'),
formatter: isLinked ? Services.hyperLinkFormatter : Services.textFormatter, formatter: isLinked ? Services.hyperLinkFormatter : Services.textFormatter,
asyncPostRender: isLinked ? self.linkHandler(linkType) : undefined, asyncPostRender: isLinked ? self.linkHandler(linkType) : undefined,

View File

@@ -348,11 +348,12 @@ export class QueryComponent extends GridParentComponent implements OnInit, OnDes
columnDefinitions: resultSet.columnInfo.map((c, i) => { columnDefinitions: 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';
return { return {
id: i.toString(), id: i.toString(),
name: c.columnName === 'Microsoft SQL Server 2005 XML Showplan' name: c.columnName === 'Microsoft SQL Server 2005 XML Showplan'
? 'XML Showplan' ? 'XML Showplan'
: c.columnName, : escape(c.columnName),
type: self.stringToFieldType('string'), type: self.stringToFieldType('string'),
formatter: isLinked ? Services.hyperLinkFormatter : Services.textFormatter, formatter: isLinked ? Services.hyperLinkFormatter : Services.textFormatter,
asyncPostRender: isLinked ? self.linkHandler(linkType) : undefined asyncPostRender: isLinked ? self.linkHandler(linkType) : undefined

View File

@@ -7,18 +7,11 @@ import 'vs/css!../common/media/jobs';
import 'sql/parts/dashboard/common/dashboardPanelStyles'; import 'sql/parts/dashboard/common/dashboardPanelStyles';
import * as nls from 'vs/nls'; import * as nls from 'vs/nls';
import { Component, Inject, forwardRef, ElementRef, ChangeDetectorRef, ViewChild, Injectable } from '@angular/core'; import { Component, Inject, forwardRef, ChangeDetectorRef, ViewChild, Injectable } from '@angular/core';
import * as Utils from 'sql/parts/connection/common/utils'; import { AgentJobInfo } from 'sqlops';
import { RefreshWidgetAction, EditDashboardAction } from 'sql/parts/dashboard/common/actions';
import { IColorTheme } from 'vs/workbench/services/themes/common/workbenchThemeService';
import { IDisposable } from 'vs/base/common/lifecycle';
import * as themeColors from 'vs/workbench/common/theme';
import { DashboardPage } from 'sql/parts/dashboard/common/dashboardPage.component';
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
import { DashboardServiceInterface } from 'sql/parts/dashboard/services/dashboardServiceInterface.service';
import { AgentJobInfo, AgentJobHistoryInfo } from 'sqlops';
import { PanelComponent, IPanelOptions, NavigationBarLayout } from 'sql/base/browser/ui/panel/panel.component'; import { PanelComponent, IPanelOptions, NavigationBarLayout } from 'sql/base/browser/ui/panel/panel.component';
import { IJobManagementService } from 'sql/parts/jobManagement/common/interfaces'; import { IJobManagementService } from 'sql/parts/jobManagement/common/interfaces';
import { IDashboardService } from 'sql/services/dashboard/common/dashboardService';
export const DASHBOARD_SELECTOR: string = 'agentview-component'; export const DASHBOARD_SELECTOR: string = 'agentview-component';
@@ -32,12 +25,6 @@ export class AgentViewComponent {
@ViewChild(PanelComponent) private _panel: PanelComponent; @ViewChild(PanelComponent) private _panel: PanelComponent;
// tslint:disable:no-unused-variable
private readonly jobsComponentTitle: string = nls.localize('jobview.Jobs', "Jobs");
private readonly alertsComponentTitle: string = nls.localize('jobview.Alerts', "Alerts");
private readonly proxiesComponentTitle: string = nls.localize('jobview.Proxies', "Proxies");
private readonly operatorsComponentTitle: string = nls.localize('jobview.Operators', "Operators");
private _showHistory: boolean = false; private _showHistory: boolean = false;
private _jobId: string = null; private _jobId: string = null;
private _agentJobInfo: AgentJobInfo = null; private _agentJobInfo: AgentJobInfo = null;
@@ -49,6 +36,11 @@ export class AgentViewComponent {
public proxiesIconClass: string = 'proxiesview-icon'; public proxiesIconClass: string = 'proxiesview-icon';
public operatorsIconClass: string = 'operatorsview-icon'; public operatorsIconClass: string = 'operatorsview-icon';
private readonly jobsComponentTitle: string = nls.localize('jobview.Jobs', "Jobs");
private readonly alertsComponentTitle: string = nls.localize('jobview.Alerts', "Alerts");
private readonly proxiesComponentTitle: string = nls.localize('jobview.Proxies', "Proxies");
private readonly operatorsComponentTitle: string = nls.localize('jobview.Operators', "Operators");
// tslint:disable-next-line:no-unused-variable // tslint:disable-next-line:no-unused-variable
private readonly panelOpt: IPanelOptions = { private readonly panelOpt: IPanelOptions = {
showTabsWhenOne: true, showTabsWhenOne: true,
@@ -58,7 +50,8 @@ export class AgentViewComponent {
constructor( constructor(
@Inject(forwardRef(() => ChangeDetectorRef)) private _cd: ChangeDetectorRef, @Inject(forwardRef(() => ChangeDetectorRef)) private _cd: ChangeDetectorRef,
@Inject(IJobManagementService) jobManagementService: IJobManagementService) { @Inject(IJobManagementService) jobManagementService: IJobManagementService,
@Inject(IDashboardService) dashboardService: IDashboardService,) {
this._expanded = new Map<string, string>(); this._expanded = new Map<string, string>();
let self = this; let self = this;

View File

@@ -34,6 +34,8 @@ export interface IJobManagementService {
getProxies(connectionUri: string): Thenable<sqlops.AgentProxiesResult>; getProxies(connectionUri: string): Thenable<sqlops.AgentProxiesResult>;
deleteProxy(connectionUri: string, proxy: sqlops.AgentProxyInfo): Thenable<sqlops.ResultStatus>; deleteProxy(connectionUri: string, proxy: sqlops.AgentProxyInfo): Thenable<sqlops.ResultStatus>;
getCredentials(connectionUri: string): Thenable<sqlops.GetCredentialsResult>;
jobAction(connectionUri: string, jobName: string, action: string): Thenable<sqlops.ResultStatus>; jobAction(connectionUri: string, jobName: string, action: string): Thenable<sqlops.ResultStatus>;
addToCache(server: string, cache: JobCacheObject); addToCache(server: string, cache: JobCacheObject);
jobCacheObjectMap: { [server: string]: JobCacheObject; }; jobCacheObjectMap: { [server: string]: JobCacheObject; };

View File

@@ -145,11 +145,17 @@ export class EditJobAction extends Action {
public static ID = 'jobaction.editJob'; public static ID = 'jobaction.editJob';
public static LABEL = nls.localize('jobaction.editJob', "Edit Job"); public static LABEL = nls.localize('jobaction.editJob', "Edit Job");
constructor() { constructor(
@ICommandService private _commandService: ICommandService
) {
super(EditJobAction.ID, EditJobAction.LABEL); super(EditJobAction.ID, EditJobAction.LABEL);
} }
public run(actionInfo: IJobActionInfo): TPromise<boolean> { public run(actionInfo: IJobActionInfo): TPromise<boolean> {
this._commandService.executeCommand(
'agent.openJobDialog',
actionInfo.ownerUri,
actionInfo.targetObject);
return TPromise.as(true); return TPromise.as(true);
} }
} }
@@ -324,11 +330,17 @@ export class EditOperatorAction extends Action {
public static ID = 'jobaction.editAlert'; public static ID = 'jobaction.editAlert';
public static LABEL = nls.localize('jobaction.editOperator', "Edit Operator"); public static LABEL = nls.localize('jobaction.editOperator', "Edit Operator");
constructor() { constructor(
@ICommandService private _commandService: ICommandService
) {
super(EditOperatorAction.ID, EditOperatorAction.LABEL); super(EditOperatorAction.ID, EditOperatorAction.LABEL);
} }
public run(info: any): TPromise<boolean> { public run(actionInfo: IJobActionInfo): TPromise<boolean> {
this._commandService.executeCommand(
'agent.openOperatorDialog',
actionInfo.ownerUri,
actionInfo.targetObject);
return TPromise.as(true); return TPromise.as(true);
} }
} }
@@ -398,11 +410,17 @@ export class EditProxyAction extends Action {
public static ID = 'jobaction.editProxy'; public static ID = 'jobaction.editProxy';
public static LABEL = nls.localize('jobaction.editProxy', "Edit Proxy"); public static LABEL = nls.localize('jobaction.editProxy', "Edit Proxy");
constructor() { constructor(
@ICommandService private _commandService: ICommandService
) {
super(EditProxyAction.ID, EditProxyAction.LABEL); super(EditProxyAction.ID, EditProxyAction.LABEL);
} }
public run(info: any): TPromise<boolean> { public run(actionInfo: IJobActionInfo): TPromise<boolean> {
this._commandService.executeCommand(
'agent.openProxyDialog',
actionInfo.ownerUri,
actionInfo.targetObject);
return TPromise.as(true); return TPromise.as(true);
} }
} }

View File

@@ -78,6 +78,12 @@ export class JobManagementService implements IJobManagementService {
}); });
} }
public getCredentials(connectionUri: string): Thenable<sqlops.GetCredentialsResult> {
return this._runAction(connectionUri, (runner) => {
return runner.getCredentials(connectionUri);
});
}
public getJobHistory(connectionUri: string, jobID: string): Thenable<sqlops.AgentJobHistoryResult> { public getJobHistory(connectionUri: string, jobID: string): Thenable<sqlops.AgentJobHistoryResult> {
return this._runAction(connectionUri, (runner) => { return this._runAction(connectionUri, (runner) => {
return runner.getJobHistory(connectionUri, jobID); return runner.getJobHistory(connectionUri, jobID);

View File

@@ -32,23 +32,11 @@ jobhistory-component {
} }
.jobview-grid { .jobview-grid {
height: 94.7%; height: calc(100% - 75px);
width : 100%; width : 100%;
display: block; display: block;
} }
.vs-dark #jobsDiv .slick-header-column {
background: #333333 !important;
}
#jobsDiv .slick-header-column {
background-color: transparent !important;
background: white !important;
border: 0px !important;
font-weight: bold;
font-size: larger;
}
.vs-dark #agentViewDiv .slick-header-column { .vs-dark #agentViewDiv .slick-header-column {
background: #333333 !important; background: #333333 !important;
} }
@@ -58,7 +46,6 @@ jobhistory-component {
background: white !important; background: white !important;
border: 0px !important; border: 0px !important;
font-weight: bold; font-weight: bold;
font-size: larger;
} }
.vs-dark #jobsDiv jobsview-component .jobview-grid .grid-canvas .ui-widget-content.slick-row .slick-cell { .vs-dark #jobsDiv jobsview-component .jobview-grid .grid-canvas .ui-widget-content.slick-row .slick-cell {
@@ -164,7 +151,7 @@ jobhistory-component {
} }
#jobsDiv .preload { #jobsDiv .preload {
font-size: 18px; font-size: 13px;
} }
#jobsDiv .dynamic-cell-detail > :first-child { #jobsDiv .dynamic-cell-detail > :first-child {
@@ -299,19 +286,20 @@ table.jobprevruns > tbody {
#alertsDiv .jobalertsview-grid { #alertsDiv .jobalertsview-grid {
height: 94.7%; height: calc(100% - 75px);
width : 100%; width : 100%;
display: block; display: block;
} }
#operatorsDiv .joboperatorsview-grid { #operatorsDiv .joboperatorsview-grid {
height: 94.7%; height: calc(100% - 75px);
width : 100%; width : 100%;
display: block; display: block;
overflow: scroll;
} }
#proxiesDiv .jobproxiesview-grid { #proxiesDiv .jobproxiesview-grid {
height: 94.7%; height: calc(100% - 75px);
width : 100%; width : 100%;
display: block; display: block;
} }
@@ -339,4 +327,97 @@ jobsview-component .actionbar-container {
jobsview-component .jobview-grid .slick-cell.error-row { jobsview-component .jobview-grid .slick-cell.error-row {
opacity: 0; opacity: 0;
} }
.vs-dark #alertsDiv jobalertsview-component .jobalertsview-grid .grid-canvas .ui-widget-content.slick-row .slick-cell {
background:#333333;
}
#alertsDiv jobalertsview-component .jobalertsview-grid .grid-canvas .ui-widget-content.slick-row .slick-cell {
background: white;
border-right: transparent !important;
border-left: transparent !important;
line-height: 33px !important;
}
#alertsDiv .jobalertsview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row:hover > .slick-cell,
#alertsDiv .jobalertsview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row.hovered > .slick-cell,
#alertsDiv .jobalertsview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row> .slick-cell.hovered {
background: #dcdcdc !important;
}
.vs-dark #alertsDiv .jobalertsview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row:hover > .slick-cell,
.vs-dark #alertsDiv .jobalertsview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row > .slick-cell.hovered,
.vs-dark #alertsDiv .jobalertsview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row.hovered > .slick-cell {
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 {
background:#333333;
}
#operatorsDiv joboperatorsview-component .joboperatorsview-grid .grid-canvas .ui-widget-content.slick-row .slick-cell {
background: white;
border-right: transparent !important;
border-left: transparent !important;
line-height: 33px !important;
}
#operatorsDiv .joboperatorsview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row:hover > .slick-cell,
#operatorsDiv .joboperatorsview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row.hovered > .slick-cell,
#operatorsDiv .joboperatorsview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row> .slick-cell.hovered {
background: #dcdcdc !important;
}
.vs-dark #operatorsDiv .joboperatorsview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row:hover > .slick-cell,
.vs-dark #operatorsDiv .joboperatorsview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row > .slick-cell.hovered,
.vs-dark #operatorsDiv .joboperatorsview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row.hovered > .slick-cell {
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 {
background:#333333;
}
#proxiesDiv jobproxiesview-component .jobproxiesview-grid .grid-canvas .ui-widget-content.slick-row .slick-cell {
background: white;
border-right: transparent !important;
border-left: transparent !important;
line-height: 33px !important;
}
#proxiesDiv .jobproxiesview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row:hover > .slick-cell,
#proxiesDiv .jobproxiesview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row.hovered > .slick-cell,
#proxiesDiv .jobproxiesview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row> .slick-cell.hovered {
background: #dcdcdc !important;
}
.vs-dark #proxiesDiv .jobproxiesview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row:hover > .slick-cell,
.vs-dark #proxiesDiv .jobproxiesview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row > .slick-cell.hovered,
.vs-dark #proxiesDiv .jobproxiesview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row.hovered > .slick-cell {
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

@@ -12,4 +12,4 @@
<div #actionbarContainer class="actionbar-container"></div> <div #actionbarContainer class="actionbar-container"></div>
<div #jobalertsgrid class="jobview-grid"></div> <div #jobalertsgrid class="jobalertsview-grid"></div>

View File

@@ -29,9 +29,10 @@ import { IAction } from 'vs/base/common/actions';
import { TPromise } from 'vs/base/common/winjs.base'; import { TPromise } from 'vs/base/common/winjs.base';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IDashboardService } from 'sql/services/dashboard/common/dashboardService';
export const VIEW_SELECTOR: string = 'jobalertsview-component'; export const VIEW_SELECTOR: string = 'jobalertsview-component';
export const ROW_HEIGHT: number = 45; export const ROW_HEIGHT: number = 30;
@Component({ @Component({
selector: VIEW_SELECTOR, selector: VIEW_SELECTOR,
@@ -73,8 +74,9 @@ export class AlertsViewComponent extends JobManagementView implements OnInit {
@Inject(IInstantiationService) instantiationService: IInstantiationService, @Inject(IInstantiationService) instantiationService: IInstantiationService,
@Inject(forwardRef(() => CommonServiceInterface)) commonService: CommonServiceInterface, @Inject(forwardRef(() => CommonServiceInterface)) commonService: CommonServiceInterface,
@Inject(IContextMenuService) contextMenuService: IContextMenuService, @Inject(IContextMenuService) contextMenuService: IContextMenuService,
@Inject(IKeybindingService) keybindingService: IKeybindingService) { @Inject(IKeybindingService) keybindingService: IKeybindingService,
super(commonService, contextMenuService, keybindingService, instantiationService); @Inject(IDashboardService) _dashboardService: IDashboardService) {
super(commonService, _dashboardService, contextMenuService, keybindingService, instantiationService);
this._isCloud = commonService.connectionManagementService.connectionInfo.serverInfo.isCloud; this._isCloud = commonService.connectionManagementService.connectionInfo.serverInfo.isCloud;
} }
@@ -85,7 +87,14 @@ export class AlertsViewComponent extends JobManagementView implements OnInit {
} }
public layout() { public layout() {
this._table.layout(new dom.Dimension(dom.getContentWidth(this._gridEl.nativeElement), dom.getContentHeight(this._gridEl.nativeElement))); let height = dom.getContentHeight(this._gridEl.nativeElement) - 10;
if (height < 0) {
height = 0;
}
this._table.layout(new dom.Dimension(
dom.getContentWidth(this._gridEl.nativeElement),
height));
} }
onFirstVisible() { onFirstVisible() {
@@ -94,13 +103,6 @@ export class AlertsViewComponent extends JobManagementView implements OnInit {
column.rerenderOnResize = true; column.rerenderOnResize = true;
return column; return column;
}); });
let options = <Slick.GridOptions<any>>{
syncColumnCellResize: true,
enableColumnReorder: false,
rowHeight: ROW_HEIGHT,
enableCellNavigation: true,
forceFitColumns: true
};
this.dataView = new Slick.Data.DataView(); this.dataView = new Slick.Data.DataView();
@@ -164,7 +166,17 @@ export class AlertsViewComponent extends JobManagementView implements OnInit {
public openCreateAlertDialog() { public openCreateAlertDialog() {
let ownerUri: string = this._commonService.connectionManagementService.connectionInfo.ownerUri; let ownerUri: string = this._commonService.connectionManagementService.connectionInfo.ownerUri;
this._commandService.executeCommand('agent.openAlertDialog', ownerUri); this._jobManagementService.getJobs(ownerUri).then((result) => {
if (result && result.jobs.length > 0) {
let jobs = [];
result.jobs.forEach(job => {
jobs.push(job.name);
});
this._commandService.executeCommand('agent.openAlertDialog', ownerUri, null, jobs);
} else {
this._commandService.executeCommand('agent.openAlertDialog', ownerUri, null, null);
}
});
} }
private refreshJobs() { private refreshJobs() {

View File

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

View File

@@ -7,6 +7,7 @@ import 'vs/css!./jobHistory';
import 'vs/css!sql/media/icons/common-icons'; import 'vs/css!sql/media/icons/common-icons';
import * as sqlops from 'sqlops'; import * as sqlops from 'sqlops';
import * as dom from 'vs/base/browser/dom';
import { OnInit, Component, Inject, Input, forwardRef, ElementRef, ChangeDetectorRef, ViewChild, ChangeDetectionStrategy, Injectable } from '@angular/core'; import { OnInit, Component, Inject, Input, forwardRef, ElementRef, ChangeDetectorRef, ViewChild, ChangeDetectionStrategy, Injectable } from '@angular/core';
import { Taskbar } from 'sql/base/browser/ui/taskbar/taskbar'; import { Taskbar } from 'sql/base/browser/ui/taskbar/taskbar';
import { AgentViewComponent } from 'sql/parts/jobManagement/agent/agentView.component'; import { AgentViewComponent } from 'sql/parts/jobManagement/agent/agentView.component';
@@ -22,30 +23,33 @@ import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/work
import { attachListStyler } from 'vs/platform/theme/common/styler'; import { attachListStyler } from 'vs/platform/theme/common/styler';
import { Tree } from 'vs/base/parts/tree/browser/treeImpl'; import { Tree } from 'vs/base/parts/tree/browser/treeImpl';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { Disposable } from 'vs/base/common/lifecycle';
import { INotificationService } from 'vs/platform/notification/common/notification'; import { INotificationService } from 'vs/platform/notification/common/notification';
import { ScrollbarVisibility } from 'vs/base/common/scrollable'; import { ScrollbarVisibility } from 'vs/base/common/scrollable';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { JobManagementView } from 'sql/parts/jobManagement/views/jobManagementView';
import { TabChild } from 'sql/base/browser/ui/panel/tab.component';
import { IDashboardService } from 'sql/services/dashboard/common/dashboardService';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
export const DASHBOARD_SELECTOR: string = 'jobhistory-component'; export const DASHBOARD_SELECTOR: string = 'jobhistory-component';
@Component({ @Component({
selector: DASHBOARD_SELECTOR, selector: DASHBOARD_SELECTOR,
templateUrl: decodeURI(require.toUrl('./jobHistory.component.html')), templateUrl: decodeURI(require.toUrl('./jobHistory.component.html')),
providers: [{ provide: TabChild, useExisting: forwardRef(() => JobHistoryComponent) }],
changeDetection: ChangeDetectionStrategy.OnPush changeDetection: ChangeDetectionStrategy.OnPush
}) })
@Injectable() @Injectable()
export class JobHistoryComponent extends Disposable implements OnInit { export class JobHistoryComponent extends JobManagementView implements OnInit {
private _tree: Tree; private _tree: Tree;
private _treeController: JobHistoryController; private _treeController: JobHistoryController;
private _treeDataSource: JobHistoryDataSource; private _treeDataSource: JobHistoryDataSource;
private _treeRenderer: JobHistoryRenderer; private _treeRenderer: JobHistoryRenderer;
private _treeFilter: JobHistoryFilter; private _treeFilter: JobHistoryFilter;
private _actionBar: Taskbar;
@ViewChild('table') private _tableContainer: ElementRef; @ViewChild('table') private _tableContainer: ElementRef;
@ViewChild('actionbarContainer') private _actionbarContainer: ElementRef; @ViewChild('jobsteps') private _jobStepsView: ElementRef;
@Input() public agentJobInfo: sqlops.AgentJobInfo = undefined; @Input() public agentJobInfo: sqlops.AgentJobInfo = undefined;
@Input() public agentJobHistories: sqlops.AgentJobHistoryInfo[] = undefined; @Input() public agentJobHistories: sqlops.AgentJobHistoryInfo[] = undefined;
@@ -67,21 +71,23 @@ export class JobHistoryComponent extends Disposable implements OnInit {
constructor( constructor(
@Inject(forwardRef(() => ElementRef)) el: ElementRef, @Inject(forwardRef(() => ElementRef)) el: ElementRef,
@Inject(forwardRef(() => ChangeDetectorRef)) private _cd: ChangeDetectorRef, @Inject(forwardRef(() => ChangeDetectorRef)) private _cd: ChangeDetectorRef,
@Inject(forwardRef(() => CommonServiceInterface)) private _dashboardService: CommonServiceInterface, @Inject(forwardRef(() => CommonServiceInterface)) commonService: CommonServiceInterface,
@Inject(forwardRef(() => AgentViewComponent)) private _agentViewComponent: AgentViewComponent, @Inject(forwardRef(() => AgentViewComponent)) private _agentViewComponent: AgentViewComponent,
@Inject(IWorkbenchThemeService) private themeService: IWorkbenchThemeService, @Inject(IWorkbenchThemeService) private themeService: IWorkbenchThemeService,
@Inject(INotificationService) private _notificationService: INotificationService, @Inject(INotificationService) private _notificationService: INotificationService,
@Inject(IInstantiationService) private instantiationService: IInstantiationService, @Inject(IInstantiationService) private instantiationService: IInstantiationService,
@Inject(IContextMenuService) private contextMenuService: IContextMenuService, @Inject(IContextMenuService) private contextMenuService: IContextMenuService,
@Inject(IJobManagementService) private _jobManagementService: IJobManagementService @Inject(IJobManagementService) private _jobManagementService: IJobManagementService,
@Inject(IKeybindingService) keybindingService: IKeybindingService,
@Inject(IDashboardService) dashboardService: IDashboardService
) { ) {
super(); super(commonService, dashboardService, contextMenuService, keybindingService, instantiationService);
this._treeController = new JobHistoryController(); this._treeController = new JobHistoryController();
this._treeDataSource = new JobHistoryDataSource(); this._treeDataSource = new JobHistoryDataSource();
this._treeRenderer = new JobHistoryRenderer(); this._treeRenderer = new JobHistoryRenderer();
this._treeFilter = new JobHistoryFilter(); this._treeFilter = new JobHistoryFilter();
let jobCacheObjectMap = this._jobManagementService.jobCacheObjectMap; let jobCacheObjectMap = this._jobManagementService.jobCacheObjectMap;
this._serverName = _dashboardService.connectionManagementService.connectionInfo.connectionProfile.serverName; this._serverName = commonService.connectionManagementService.connectionInfo.connectionProfile.serverName;
let jobCache = jobCacheObjectMap[this._serverName]; let jobCache = jobCacheObjectMap[this._serverName];
if (jobCache) { if (jobCache) {
this._jobCacheObject = jobCache; this._jobCacheObject = jobCache;
@@ -93,7 +99,11 @@ export class JobHistoryComponent extends Disposable implements OnInit {
} }
ngOnInit() { ngOnInit() {
let ownerUri: string = this._dashboardService.connectionManagementService.connectionInfo.ownerUri; // set base class elements
this._visibilityElement = this._tableContainer;
this._parentComponent = this._agentViewComponent;
let ownerUri: string = this._commonService.connectionManagementService.connectionInfo.ownerUri;
const self = this; const self = this;
this._treeController.onClick = (tree, element, event, origin = 'mouse') => { this._treeController.onClick = (tree, element, event, origin = 'mouse') => {
const payload = { origin: origin }; const payload = { origin: origin };
@@ -130,56 +140,13 @@ export class JobHistoryComponent extends Disposable implements OnInit {
}, {verticalScrollMode: ScrollbarVisibility.Visible}); }, {verticalScrollMode: ScrollbarVisibility.Visible});
this._register(attachListStyler(this._tree, this.themeService)); this._register(attachListStyler(this._tree, this.themeService));
this._tree.layout(JobHistoryComponent.INITIAL_TREE_HEIGHT); this._tree.layout(JobHistoryComponent.INITIAL_TREE_HEIGHT);
this._initActionBar(); this.initActionBar();
$(window).resize(() => {
let historyDetails = $('.overview-container').get(0);
let statusBar = $('.part.statusbar').get(0);
if (historyDetails && statusBar) {
let historyBottom = historyDetails.getBoundingClientRect().bottom;
let statusTop = statusBar.getBoundingClientRect().top;
this._tree.layout(statusTop - historyBottom - JobHistoryComponent.HEADING_HEIGHT);
}
});
}
ngAfterContentChecked() {
this._agentJobInfo = this._agentViewComponent.agentJobInfo;
if (!this.agentJobInfo) {
this.agentJobInfo = this._agentJobInfo;
this.setActions();
}
if (this._isVisible === false && this._tableContainer.nativeElement.offsetParent !== null) {
this._isVisible = true;
let jobHistories = this._jobCacheObject.jobHistories[this._agentViewComponent.jobId];
if (jobHistories && jobHistories.length > 0) {
const self = this;
if (this._jobCacheObject.prevJobID === this._agentViewComponent.jobId || jobHistories[0].jobId === this._agentViewComponent.jobId) {
this._showPreviousRuns = true;
this.buildHistoryTree(self, jobHistories);
$('jobhistory-component .history-details .prev-run-list .monaco-tree').attr('tabIndex', '-1');
$('jobhistory-component .history-details .prev-run-list .monaco-tree-row').attr('tabIndex', '0');
this._cd.detectChanges();
}
} else if (jobHistories && jobHistories.length === 0 ){
this._showPreviousRuns = false;
this._showSteps = false;
this._noJobsAvailable = true;
this._cd.detectChanges();
} else {
this.loadHistory();
}
this._jobCacheObject.prevJobID = this._agentViewComponent.jobId;
} else if (this._isVisible === true && this._agentViewComponent.refresh) {
this.loadHistory();
this._agentViewComponent.refresh = false;
} else if (this._isVisible === true && this._tableContainer.nativeElement.offsetParent === null) {
this._isVisible = false;
}
} }
private loadHistory() { private loadHistory() {
const self = this; const self = this;
let ownerUri: string = this._dashboardService.connectionManagementService.connectionInfo.ownerUri; let ownerUri: string = this._commonService.connectionManagementService.connectionInfo.ownerUri;
this._jobManagementService.getJobHistory(ownerUri, this._agentViewComponent.jobId).then((result) => { this._jobManagementService.getJobHistory(ownerUri, this._agentViewComponent.jobId).then((result) => {
if (result && result.jobs) { if (result && result.jobs) {
if (result.jobs.length > 0) { if (result.jobs.length > 0) {
@@ -207,10 +174,12 @@ export class JobHistoryComponent extends Disposable 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();
@@ -224,6 +193,15 @@ export class JobHistoryComponent extends Disposable 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);
@@ -273,12 +251,71 @@ export class JobHistoryComponent extends Disposable implements OnInit {
JobManagementUtilities.getActionIconClassName(startIcon, stopIcon, this.agentJobInfo.currentExecutionStatus); JobManagementUtilities.getActionIconClassName(startIcon, stopIcon, this.agentJobInfo.currentExecutionStatus);
} }
public onFirstVisible() {
this._agentJobInfo = this._agentViewComponent.agentJobInfo;
if (!this.agentJobInfo) {
this.agentJobInfo = this._agentJobInfo;
this.setActions();
}
private _initActionBar() { if (this.isRefreshing ) {
this.loadHistory();
return;
}
let jobHistories = this._jobCacheObject.jobHistories[this._agentViewComponent.jobId];
if (jobHistories && jobHistories.length > 0) {
const self = this;
if (this._jobCacheObject.prevJobID === this._agentViewComponent.jobId || jobHistories[0].jobId === this._agentViewComponent.jobId) {
this._showPreviousRuns = true;
this.buildHistoryTree(self, jobHistories);
$('jobhistory-component .history-details .prev-run-list .monaco-tree').attr('tabIndex', '-1');
$('jobhistory-component .history-details .prev-run-list .monaco-tree-row').attr('tabIndex', '0');
this._cd.detectChanges();
}
} else if (jobHistories && jobHistories.length === 0 ){
this._showPreviousRuns = false;
this._showSteps = false;
this._noJobsAvailable = true;
this._cd.detectChanges();
} else {
this.loadHistory();
}
this._jobCacheObject.prevJobID = this._agentViewComponent.jobId;
}
public layout() {
let historyDetails = $('.overview-container').get(0);
let statusBar = $('.part.statusbar').get(0);
if (historyDetails && statusBar) {
let historyBottom = historyDetails.getBoundingClientRect().bottom;
let statusTop = statusBar.getBoundingClientRect().top;
let height: number = statusTop - historyBottom - JobHistoryComponent.HEADING_HEIGHT;
if (this._table) {
this._table.layout(new dom.Dimension(
dom.getContentWidth(this._tableContainer.nativeElement),
height));
}
if (this._tree) {
this._tree.layout(height);
}
if (this._jobStepsView) {
let element = this._jobStepsView.nativeElement as HTMLElement;
if (element) {
element.style.height = height + 'px';
}
}
}
}
protected initActionBar() {
let runJobAction = this.instantiationService.createInstance(RunJobAction); let runJobAction = this.instantiationService.createInstance(RunJobAction);
let stopJobAction = this.instantiationService.createInstance(StopJobAction); let stopJobAction = this.instantiationService.createInstance(StopJobAction);
let newStepAction = this.instantiationService.createInstance(NewStepAction); let newStepAction = this.instantiationService.createInstance(NewStepAction);
let taskbar = <HTMLElement>this._actionbarContainer.nativeElement; let taskbar = <HTMLElement>this.actionBarContainer.nativeElement;
this._actionBar = new Taskbar(taskbar, this.contextMenuService); this._actionBar = new Taskbar(taskbar, this.contextMenuService);
this._actionBar.context = this; this._actionBar.context = this;
this._actionBar.setContent([ this._actionBar.setContent([
@@ -299,7 +336,7 @@ export class JobHistoryComponent extends Disposable implements OnInit {
} }
public get ownerUri(): string { public get ownerUri(): string {
return this._dashboardService.connectionManagementService.connectionInfo.ownerUri; return this._commonService.connectionManagementService.connectionInfo.ownerUri;
} }
public get serverName(): string { public get serverName(): string {

View File

@@ -167,7 +167,7 @@ table.step-list tr.step-row td {
} }
.vs-dark .history-details > .job-steps { .vs-dark .history-details > .job-steps {
display: inline-block; display: block;
border-left: 3px solid #444444; border-left: 3px solid #444444;
padding-left: 10px; padding-left: 10px;
height: 100%; height: 100%;
@@ -176,10 +176,12 @@ table.step-list tr.step-row td {
} }
.history-details > .job-steps { .history-details > .job-steps {
display: inline-block; display: block;
border-left: 3px solid #f4f4f4; border-left: 3px solid #f4f4f4;
padding-left: 10px; padding-left: 10px;
height: 100%; height: 100%;
width: 90%;
overflow-y: scroll;
} }
.history-details > .job-steps > .step-list { .history-details > .job-steps > .step-list {

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

@@ -16,8 +16,10 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { Taskbar } from '../../../base/browser/ui/taskbar/taskbar'; import { Taskbar } from '../../../base/browser/ui/taskbar/taskbar';
import { JobsRefreshAction } from 'sql/parts/jobManagement/common/jobActions'; import { JobsRefreshAction } from 'sql/parts/jobManagement/common/jobActions';
import { TabChild } from 'sql/base/browser/ui/panel/tab.component';
import { IDashboardService } from 'sql/services/dashboard/common/dashboardService';
export abstract class JobManagementView extends Disposable implements AfterContentChecked { export abstract class JobManagementView extends TabChild implements AfterContentChecked {
protected isVisible: boolean = false; protected isVisible: boolean = false;
protected isInitialized: boolean = false; protected isInitialized: boolean = false;
protected isRefreshing: boolean = false; protected isRefreshing: boolean = false;
@@ -32,10 +34,16 @@ export abstract class JobManagementView extends Disposable implements AfterConte
constructor( constructor(
protected _commonService: CommonServiceInterface, protected _commonService: CommonServiceInterface,
protected _dashboardService: IDashboardService,
protected _contextMenuService: IContextMenuService, protected _contextMenuService: IContextMenuService,
protected _keybindingService: IKeybindingService, protected _keybindingService: IKeybindingService,
protected _instantiationService: IInstantiationService) { protected _instantiationService: IInstantiationService) {
super(); super();
let self = this;
this._dashboardService.onLayout((d) => {
self.layout();
});
} }
ngAfterContentChecked() { ngAfterContentChecked() {
@@ -49,8 +57,9 @@ export abstract class JobManagementView extends Disposable implements AfterConte
} }
} else if (this.isVisible === true && this._parentComponent.refresh === true) { } else if (this.isVisible === true && this._parentComponent.refresh === true) {
this._showProgressWheel = true; this._showProgressWheel = true;
this.onFirstVisible();
this.isRefreshing = true; this.isRefreshing = true;
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

@@ -8,21 +8,28 @@ import 'vs/css!./jobStepsView';
import { OnInit, Component, Inject, forwardRef, ElementRef, ChangeDetectorRef, ViewChild, Injectable, AfterContentChecked } from '@angular/core'; import { OnInit, Component, Inject, forwardRef, ElementRef, ChangeDetectorRef, ViewChild, Injectable, AfterContentChecked } from '@angular/core';
import { attachListStyler } from 'vs/platform/theme/common/styler'; import { attachListStyler } from 'vs/platform/theme/common/styler';
import { Tree } from 'vs/base/parts/tree/browser/treeImpl'; import { Tree } from 'vs/base/parts/tree/browser/treeImpl';
import { Disposable } from 'vs/base/common/lifecycle';
import { ScrollbarVisibility } from 'vs/base/common/scrollable'; import { ScrollbarVisibility } from 'vs/base/common/scrollable';
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service'; import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service';
import { JobStepsViewController, JobStepsViewDataSource, JobStepsViewFilter, import { JobStepsViewController, JobStepsViewDataSource, JobStepsViewFilter,
JobStepsViewRenderer, JobStepsViewModel} from 'sql/parts/jobManagement/views/jobStepsViewTree'; JobStepsViewRenderer, JobStepsViewModel} from 'sql/parts/jobManagement/views/jobStepsViewTree';
import { JobHistoryComponent } from 'sql/parts/jobManagement/views/jobHistory.component'; import { JobHistoryComponent } from 'sql/parts/jobManagement/views/jobHistory.component';
import { JobManagementView } from 'sql/parts/jobManagement/views/jobManagementView';
import { IDashboardService } from 'sql/services/dashboard/common/dashboardService';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { TabChild } from 'sql/base/browser/ui/panel/tab.component';
import * as dom from 'vs/base/browser/dom';
export const JOBSTEPSVIEW_SELECTOR: string = 'jobstepsview-component'; export const JOBSTEPSVIEW_SELECTOR: string = 'jobstepsview-component';
@Component({ @Component({
selector: JOBSTEPSVIEW_SELECTOR, selector: JOBSTEPSVIEW_SELECTOR,
templateUrl: decodeURI(require.toUrl('./jobStepsView.component.html')) templateUrl: decodeURI(require.toUrl('./jobStepsView.component.html')),
providers: [{ provide: TabChild, useExisting: forwardRef(() => JobStepsViewComponent) }],
}) })
export class JobStepsViewComponent extends Disposable implements OnInit, AfterContentChecked { export class JobStepsViewComponent extends JobManagementView implements OnInit, AfterContentChecked {
private _tree: Tree; private _tree: Tree;
private _treeController = new JobStepsViewController(); private _treeController = new JobStepsViewController();
@@ -33,15 +40,18 @@ export class JobStepsViewComponent extends Disposable implements OnInit, AfterCo
@ViewChild('table') private _tableContainer: ElementRef; @ViewChild('table') private _tableContainer: ElementRef;
constructor( constructor(
@Inject(forwardRef(() => ElementRef)) el: ElementRef, @Inject(forwardRef(() => ElementRef)) el: ElementRef,
@Inject(forwardRef(() => ChangeDetectorRef)) private _cd: ChangeDetectorRef, @Inject(forwardRef(() => ChangeDetectorRef)) private _cd: ChangeDetectorRef,
@Inject(forwardRef(() => CommonServiceInterface)) private _dashboardService: CommonServiceInterface, @Inject(forwardRef(() => CommonServiceInterface)) commonService: CommonServiceInterface,
@Inject(forwardRef(() => JobHistoryComponent)) private _jobHistoryComponent: JobHistoryComponent, @Inject(forwardRef(() => JobHistoryComponent)) private _jobHistoryComponent: JobHistoryComponent,
@Inject(IWorkbenchThemeService) private themeService: IWorkbenchThemeService @Inject(IWorkbenchThemeService) private themeService: IWorkbenchThemeService,
@Inject(IInstantiationService) instantiationService: IInstantiationService,
@Inject(IContextMenuService) contextMenuService: IContextMenuService,
@Inject(IKeybindingService) keybindingService: IKeybindingService,
@Inject(IDashboardService) dashboardService: IDashboardService
) { ) {
super(); super(commonService, dashboardService, contextMenuService, keybindingService, instantiationService);
} }
ngAfterContentChecked() { ngAfterContentChecked() {
@@ -64,7 +74,7 @@ export class JobStepsViewComponent extends Disposable implements OnInit, AfterCo
} }
ngOnInit() { ngOnInit() {
let ownerUri: string = this._dashboardService.connectionManagementService.connectionInfo.ownerUri; let ownerUri: string = this._commonService.connectionManagementService.connectionInfo.ownerUri;
this._tree = new Tree(this._tableContainer.nativeElement, { this._tree = new Tree(this._tableContainer.nativeElement, {
controller: this._treeController, controller: this._treeController,
dataSource: this._treeDataSource, dataSource: this._treeDataSource,
@@ -73,5 +83,11 @@ export class JobStepsViewComponent extends Disposable implements OnInit, AfterCo
}, {verticalScrollMode: ScrollbarVisibility.Visible}); }, {verticalScrollMode: ScrollbarVisibility.Visible});
this._register(attachListStyler(this._tree, this.themeService)); this._register(attachListStyler(this._tree, this.themeService));
} }
public onFirstVisible() {
}
public layout() {
}
} }

View File

@@ -34,6 +34,8 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { TPromise } from 'vs/base/common/winjs.base'; 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 { 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;
@@ -99,8 +101,9 @@ export class JobsViewComponent extends JobManagementView implements OnInit {
@Inject(IInstantiationService) instantiationService: IInstantiationService, @Inject(IInstantiationService) instantiationService: IInstantiationService,
@Inject(IContextMenuService) contextMenuService: IContextMenuService, @Inject(IContextMenuService) contextMenuService: IContextMenuService,
@Inject(IKeybindingService) keybindingService: IKeybindingService, @Inject(IKeybindingService) keybindingService: IKeybindingService,
@Inject(IDashboardService) _dashboardService: IDashboardService
) { ) {
super(commonService, contextMenuService, keybindingService, instantiationService); super(commonService, _dashboardService, contextMenuService, keybindingService, instantiationService);
let jobCacheObjectMap = this._jobManagementService.jobCacheObjectMap; let jobCacheObjectMap = this._jobManagementService.jobCacheObjectMap;
this._serverName = commonService.connectionManagementService.connectionInfo.connectionProfile.serverName; this._serverName = commonService.connectionManagementService.connectionInfo.connectionProfile.serverName;
let jobCache = jobCacheObjectMap[this._serverName]; let jobCache = jobCacheObjectMap[this._serverName];
@@ -121,7 +124,15 @@ export class JobsViewComponent extends JobManagementView implements OnInit {
} }
public layout() { public layout() {
this._table.layout(new dom.Dimension(dom.getContentWidth(this._gridEl.nativeElement), dom.getContentHeight(this._gridEl.nativeElement))); let jobsViewToolbar = $('jobsview-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._gridEl.nativeElement),
statusTop - toolbarBottom));
}
} }
onFirstVisible() { onFirstVisible() {
@@ -141,7 +152,7 @@ export class JobsViewComponent extends JobManagementView implements OnInit {
enableColumnReorder: false, enableColumnReorder: false,
rowHeight: ROW_HEIGHT, rowHeight: ROW_HEIGHT,
enableCellNavigation: true, enableCellNavigation: true,
forceFitColumns: true forceFitColumns: false
}; };
this.dataView = new Slick.Data.DataView({ inlineFilters: false }); this.dataView = new Slick.Data.DataView({ inlineFilters: false });
@@ -328,7 +339,6 @@ export class JobsViewComponent extends JobManagementView implements OnInit {
this.dataView.beginUpdate(); this.dataView.beginUpdate();
this.dataView.setItems(jobViews); this.dataView.setItems(jobViews);
this.dataView.setFilter((item) => this.filter(item)); this.dataView.setFilter((item) => this.filter(item));
this.dataView.endUpdate(); this.dataView.endUpdate();
this._table.autosizeColumns(); this._table.autosizeColumns();
this._table.resizeCanvas(); this._table.resizeCanvas();
@@ -341,17 +351,6 @@ export class JobsViewComponent extends JobManagementView implements OnInit {
}); });
const self = this; const self = this;
$(window).resize(() => {
let jobsViewToolbar = $('jobsview-component .actionbar-container').get(0);
let statusBar = $('.part.statusbar').get(0);
if (jobsViewToolbar && statusBar) {
let toolbarBottom = jobsViewToolbar.getBoundingClientRect().bottom;
let statusTop = statusBar.getBoundingClientRect().top;
$('agentview-component #jobsDiv .jobview-grid').css('height', statusTop - toolbarBottom);
self._table.resizeCanvas();
}
});
this._table.grid.onColumnsResized.subscribe((e, data: any) => { this._table.grid.onColumnsResized.subscribe((e, data: any) => {
let nameWidth: number = data.grid.getColumnWidths()[1]; let nameWidth: number = data.grid.getColumnWidths()[1];
// adjust job name when resized // adjust job name when resized
@@ -487,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>';
} }
@@ -844,7 +843,7 @@ export class JobsViewComponent extends JobManagementView implements OnInit {
public openCreateJobDialog() { public openCreateJobDialog() {
let ownerUri: string = this._commonService.connectionManagementService.connectionInfo.ownerUri; let ownerUri: string = this._commonService.connectionManagementService.connectionInfo.ownerUri;
this._commandService.executeCommand('agent.openCreateJobDialog', ownerUri); this._commandService.executeCommand('agent.openJobDialog', ownerUri);
} }
public refreshJobs() { public refreshJobs() {

View File

@@ -29,9 +29,10 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { TPromise } from 'vs/base/common/winjs.base'; 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';
export const VIEW_SELECTOR: string = 'joboperatorsview-component'; export const VIEW_SELECTOR: string = 'joboperatorsview-component';
export const ROW_HEIGHT: number = 45; export const ROW_HEIGHT: number = 30;
@Component({ @Component({
selector: VIEW_SELECTOR, selector: VIEW_SELECTOR,
@@ -73,9 +74,10 @@ export class OperatorsViewComponent extends JobManagementView implements OnInit
@Inject(IInstantiationService) instantiationService: IInstantiationService, @Inject(IInstantiationService) instantiationService: IInstantiationService,
@Inject(forwardRef(() => CommonServiceInterface)) commonService: CommonServiceInterface, @Inject(forwardRef(() => CommonServiceInterface)) commonService: CommonServiceInterface,
@Inject(IContextMenuService) contextMenuService: IContextMenuService, @Inject(IContextMenuService) contextMenuService: IContextMenuService,
@Inject(IKeybindingService) keybindingService: IKeybindingService @Inject(IKeybindingService) keybindingService: IKeybindingService,
@Inject(IDashboardService) _dashboardService: IDashboardService
) { ) {
super(commonService, contextMenuService, keybindingService, instantiationService); super(commonService, _dashboardService, contextMenuService, keybindingService, instantiationService);
this._isCloud = commonService.connectionManagementService.connectionInfo.serverInfo.isCloud; this._isCloud = commonService.connectionManagementService.connectionInfo.serverInfo.isCloud;
} }
@@ -86,7 +88,14 @@ export class OperatorsViewComponent extends JobManagementView implements OnInit
} }
public layout() { public layout() {
this._table.layout(new dom.Dimension(dom.getContentWidth(this._gridEl.nativeElement), dom.getContentHeight(this._gridEl.nativeElement))); let height = dom.getContentHeight(this._gridEl.nativeElement) - 10;
if (height < 0) {
height = 0;
}
this._table.layout(new dom.Dimension(
dom.getContentWidth(this._gridEl.nativeElement),
height));
} }
onFirstVisible() { onFirstVisible() {
@@ -95,13 +104,6 @@ export class OperatorsViewComponent extends JobManagementView implements OnInit
column.rerenderOnResize = true; column.rerenderOnResize = true;
return column; return column;
}); });
let options = <Slick.GridOptions<any>>{
syncColumnCellResize: true,
enableColumnReorder: false,
rowHeight: ROW_HEIGHT,
enableCellNavigation: true,
forceFitColumns: true
};
this.dataView = new Slick.Data.DataView(); this.dataView = new Slick.Data.DataView();
@@ -163,7 +165,7 @@ export class OperatorsViewComponent extends JobManagementView implements OnInit
public openCreateOperatorDialog() { public openCreateOperatorDialog() {
let ownerUri: string = this._commonService.connectionManagementService.connectionInfo.ownerUri; let ownerUri: string = this._commonService.connectionManagementService.connectionInfo.ownerUri;
this._commandService.executeCommand('agent.openCreateOperatorDialog', ownerUri); this._commandService.executeCommand('agent.openOperatorDialog', ownerUri);
} }
private refreshJobs() { private refreshJobs() {

View File

@@ -29,9 +29,10 @@ import { TPromise } from 'vs/base/common/winjs.base';
import { IAction } from 'vs/base/common/actions'; import { IAction } from 'vs/base/common/actions';
import { ICommandService } from 'vs/platform/commands/common/commands'; import { ICommandService } from 'vs/platform/commands/common/commands';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IDashboardService } from 'sql/services/dashboard/common/dashboardService';
export const VIEW_SELECTOR: string = 'jobproxiesview-component'; export const VIEW_SELECTOR: string = 'jobproxiesview-component';
export const ROW_HEIGHT: number = 45; export const ROW_HEIGHT: number = 30;
@Component({ @Component({
selector: VIEW_SELECTOR, selector: VIEW_SELECTOR,
@@ -75,9 +76,10 @@ export class ProxiesViewComponent extends JobManagementView implements OnInit {
@Inject(IInstantiationService) instantiationService: IInstantiationService, @Inject(IInstantiationService) instantiationService: IInstantiationService,
@Inject(forwardRef(() => CommonServiceInterface)) commonService: CommonServiceInterface, @Inject(forwardRef(() => CommonServiceInterface)) commonService: CommonServiceInterface,
@Inject(IContextMenuService) contextMenuService: IContextMenuService, @Inject(IContextMenuService) contextMenuService: IContextMenuService,
@Inject(IKeybindingService) keybindingService: IKeybindingService @Inject(IKeybindingService) keybindingService: IKeybindingService,
@Inject(IDashboardService) _dashboardService: IDashboardService
) { ) {
super(commonService, contextMenuService, keybindingService, instantiationService); super(commonService, _dashboardService, contextMenuService, keybindingService, instantiationService);
this._isCloud = commonService.connectionManagementService.connectionInfo.serverInfo.isCloud; this._isCloud = commonService.connectionManagementService.connectionInfo.serverInfo.isCloud;
} }
@@ -88,7 +90,14 @@ export class ProxiesViewComponent extends JobManagementView implements OnInit {
} }
public layout() { public layout() {
this._table.layout(new dom.Dimension(dom.getContentWidth(this._gridEl.nativeElement), dom.getContentHeight(this._gridEl.nativeElement))); let height = dom.getContentHeight(this._gridEl.nativeElement) - 10;
if (height < 0) {
height = 0;
}
this._table.layout(new dom.Dimension(
dom.getContentWidth(this._gridEl.nativeElement),
height));
} }
onFirstVisible() { onFirstVisible() {
@@ -97,13 +106,6 @@ export class ProxiesViewComponent extends JobManagementView implements OnInit {
column.rerenderOnResize = true; column.rerenderOnResize = true;
return column; return column;
}); });
let options = <Slick.GridOptions<any>>{
syncColumnCellResize: true,
enableColumnReorder: false,
rowHeight: ROW_HEIGHT,
enableCellNavigation: true,
forceFitColumns: true
};
this.dataView = new Slick.Data.DataView(); this.dataView = new Slick.Data.DataView();
@@ -164,7 +166,11 @@ export class ProxiesViewComponent extends JobManagementView implements OnInit {
public openCreateProxyDialog() { public openCreateProxyDialog() {
let ownerUri: string = this._commonService.connectionManagementService.connectionInfo.ownerUri; let ownerUri: string = this._commonService.connectionManagementService.connectionInfo.ownerUri;
this._commandService.executeCommand('agent.openCreateProxyDialog', ownerUri); this._jobManagementService.getCredentials(ownerUri).then((result) => {
if (result && result.credentials) {
this._commandService.executeCommand('agent.openProxyDialog', ownerUri, undefined, result.credentials);
}
});
} }
private refreshJobs() { private refreshJobs() {

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

@@ -130,11 +130,14 @@ export abstract class ComponentBase extends Disposable implements IComponent, On
this.setPropertyFromUI<sqlops.ComponentProperties, number | string>((props, value) => props.width = value, newValue); this.setPropertyFromUI<sqlops.ComponentProperties, number | string>((props, value) => props.width = value, newValue);
} }
protected convertSizeToNumber(size: number | string): number { public convertSizeToNumber(size: number | string): number {
if (size && typeof (size) === 'string') { if (size && typeof (size) === 'string') {
if (size.toLowerCase().endsWith('px')) { if (size.toLowerCase().endsWith('px')) {
return +size.replace('px', ''); return +size.replace('px', '');
} else if (size.toLowerCase().endsWith('em')) {
return +size.replace('em', '') * 11;
} }
} else if (!size) {
return 0; return 0;
} }
return +size; return +size;
@@ -148,7 +151,7 @@ export abstract class ComponentBase extends Disposable implements IComponent, On
return this.height ? this.convertSize(this.height) : ''; return this.height ? this.convertSize(this.height) : '';
} }
protected convertSize(size: number | string, defaultValue?: string): string { public convertSize(size: number | string, defaultValue?: string): string {
defaultValue = defaultValue || ''; defaultValue = defaultValue || '';
if (types.isUndefinedOrNull(size)) { if (types.isUndefinedOrNull(size)) {
return defaultValue; return defaultValue;

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) {
@@ -187,7 +186,7 @@ export default class DropDownComponent extends ComponentBase implements ICompone
} }
private get values(): string[] | sqlops.CategoryValue[] { private get values(): string[] | sqlops.CategoryValue[] {
return this.getPropertyOrDefault<sqlops.DropDownProperties, string[] | sqlops.CategoryValue[]>((props) => props.values, undefined); return this.getPropertyOrDefault<sqlops.DropDownProperties, string[] | sqlops.CategoryValue[]>((props) => props.values, []);
} }
private set values(newValue: string[] | sqlops.CategoryValue[]) { private set values(newValue: string[] | sqlops.CategoryValue[]) {

View File

@@ -45,7 +45,7 @@ class FormItem {
template: ` template: `
<div #container *ngIf="items" class="form-table" [style.padding]="getFormPadding()" [style.width]="getFormWidth()" [style.height]="getFormHeight()"> <div #container *ngIf="items" class="form-table" [style.padding]="getFormPadding()" [style.width]="getFormWidth()" [style.height]="getFormHeight()">
<ng-container *ngFor="let item of items"> <ng-container *ngFor="let item of items">
<div class="form-row" *ngIf="isGroupLabel(item)"> <div class="form-row" *ngIf="isGroupLabel(item)" [style.font-size]="getItemTitleFontSize(item)">
<div class="form-item-row form-group-label"> <div class="form-item-row form-group-label">
<model-component-wrapper [descriptor]="item.descriptor" [modelStore]="modelStore"> <model-component-wrapper [descriptor]="item.descriptor" [modelStore]="modelStore">
</model-component-wrapper> </model-component-wrapper>
@@ -56,7 +56,7 @@ class FormItem {
<ng-container *ngIf="isHorizontal(item)"> <ng-container *ngIf="isHorizontal(item)">
<div class="form-cell" [style.font-size]="getItemTitleFontSize(item)" [ngClass]="{'form-group-item': isInGroup(item)}"> <div class="form-cell" [style.font-size]="getItemTitleFontSize(item)" [ngClass]="{'form-group-item': isInGroup(item)}">
{{getItemTitle(item)}}<span class="form-required" *ngIf="isItemRequired(item)">*</span> {{getItemTitle(item)}}<span class="form-required" *ngIf="isItemRequired(item)">*</span>
<span class="icon info form-info" *ngIf="itemHasInfo(item)" [title]="getItemInfo(item)"></span> <span class="icon help form-info" *ngIf="itemHasInfo(item)" [title]="getItemInfo(item)"></span>
</div> </div>
<div class="form-cell"> <div class="form-cell">
<div class="form-component-container"> <div class="form-component-container">
@@ -76,7 +76,7 @@ class FormItem {
<div class="form-vertical-container" *ngIf="isVertical(item)" [style.height]="getRowHeight(item)" [ngClass]="{'form-group-item': isInGroup(item)}"> <div class="form-vertical-container" *ngIf="isVertical(item)" [style.height]="getRowHeight(item)" [ngClass]="{'form-group-item': isInGroup(item)}">
<div class="form-item-row" [style.font-size]="getItemTitleFontSize(item)"> <div class="form-item-row" [style.font-size]="getItemTitleFontSize(item)">
{{getItemTitle(item)}}<span class="form-required" *ngIf="isItemRequired(item)">*</span> {{getItemTitle(item)}}<span class="form-required" *ngIf="isItemRequired(item)">*</span>
<span class="icon info form-info" *ngIf="itemHasInfo(item)" [title]="getItemInfo(item)"></span> <span class="icon help form-info" *ngIf="itemHasInfo(item)" [title]="getItemInfo(item)"></span>
</div> </div>
<div class="form-item-row" [style.width]="getComponentWidth(item)" [style.height]="getRowHeight(item)"> <div class="form-item-row" [style.width]="getComponentWidth(item)" [style.height]="getRowHeight(item)">
<model-component-wrapper [descriptor]="item.descriptor" [modelStore]="modelStore" [style.width]="getComponentWidth(item)" [style.height]="getRowHeight(item)"> <model-component-wrapper [descriptor]="item.descriptor" [modelStore]="modelStore" [style.width]="getComponentWidth(item)" [style.height]="getRowHeight(item)">
@@ -179,8 +179,12 @@ export default class FormContainer extends ContainerBase<FormItemLayout> impleme
} }
private getItemTitleFontSize(item: FormItem): string { private getItemTitleFontSize(item: FormItem): string {
let defaultFontSize = '14px';
if (this.isInGroup(item)) {
defaultFontSize = '12px';
}
let itemConfig = item.config; let itemConfig = item.config;
return itemConfig && itemConfig.titleFontSize ? this.convertSize(itemConfig.titleFontSize, '11px') : '11px'; return itemConfig && itemConfig.titleFontSize ? this.convertSize(itemConfig.titleFontSize, defaultFontSize) : defaultFontSize;
} }
private getActionComponents(item: FormItem): FormItem[] { private getActionComponents(item: FormItem): FormItem[] {

View File

@@ -18,7 +18,7 @@
} }
.form-vertical-container { .form-vertical-container {
padding-bottom: 15px; padding-bottom: 5px;
width: 100%; width: 100%;
} }
@@ -57,11 +57,8 @@
display: table-cell; display: table-cell;
} }
.form-group-item .form-item-row,
.form-group-item.form-cell {
padding-left: 30px;
}
.form-group-label { .form-group-label {
padding-top: 3px;
padding-bottom: 0px; padding-bottom: 0px;
} font-weight: bold;
}

View File

@@ -4,22 +4,25 @@
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
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';
import { ComponentBase } from 'sql/parts/modelComponents/componentBase'; import { ComponentBase } from 'sql/parts/modelComponents/componentBase';
import { IComponent, IComponentDescriptor, IModelStore, ComponentEventType } from 'sql/parts/modelComponents/interfaces'; import { IComponent, IComponentDescriptor, IModelStore, ComponentEventType } from 'sql/parts/modelComponents/interfaces';
import { InputBox } from 'sql/base/browser/ui/inputBox/inputBox'; import { InputBox } from 'sql/base/browser/ui/inputBox/inputBox';
import { attachInputBoxStyler } from 'sql/common/theme/styler';
import { IInputOptions, MessageType } from 'vs/base/browser/ui/inputbox/inputBox'; import { IInputOptions, MessageType } from 'vs/base/browser/ui/inputbox/inputBox';
import { attachInputBoxStyler, attachListStyler } from 'vs/platform/theme/common/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 { IContextViewService } from 'vs/platform/contextview/browser/contextView';
import * as nls from 'vs/nls'; import * as nls from 'vs/nls';
import { inputBackground, inputBorder } from 'vs/platform/theme/common/colorRegistry'; import { inputBackground, inputBorder } from 'vs/platform/theme/common/colorRegistry';
import * as DomUtils from 'vs/base/browser/dom';
import { StandardKeyboardEvent, IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { KeyCode } from 'vs/base/common/keyCodes';
@Component({ @Component({
selector: 'modelview-inputBox', selector: 'modelview-inputBox',
@@ -73,11 +76,31 @@ export default class InputBoxComponent extends ComponentBase implements ICompone
if (this._textareaContainer) { if (this._textareaContainer) {
let textAreaInputOptions = Object.assign({}, inputOptions, { flexibleHeight: true, type: 'textarea' }); let textAreaInputOptions = Object.assign({}, inputOptions, { flexibleHeight: true, type: 'textarea' });
this._textAreaInput = new InputBox(this._textareaContainer.nativeElement, this.contextViewService, textAreaInputOptions); this._textAreaInput = new InputBox(this._textareaContainer.nativeElement, this.contextViewService, textAreaInputOptions);
this.onkeydown(this._textAreaInput.inputElement, (e: StandardKeyboardEvent) => {
if (this.tryHandleKeyEvent(e)) {
e.stopPropagation();
}
// Else assume that keybinding service handles routing this to a command
});
this.registerInput(this._textAreaInput, () => this.multiline); this.registerInput(this._textAreaInput, () => this.multiline);
} }
this.inputElement.hideErrors = true; this.inputElement.hideErrors = true;
} }
private onkeydown(domNode: HTMLElement, listener: (e: IKeyboardEvent) => void): void {
this._register(DomUtils.addDisposableListener(domNode, DomUtils.EventType.KEY_DOWN, (e: KeyboardEvent) => listener(new StandardKeyboardEvent(e))));
}
private tryHandleKeyEvent(e: StandardKeyboardEvent): boolean {
let handled: boolean = false;
if (this.multiline && e.keyCode === KeyCode.Enter) {
handled = true;
}
return handled;
}
private get inputElement(): InputBox { private get inputElement(): InputBox {
return this.multiline ? this._textAreaInput : this._input; return this.multiline ? this._textAreaInput : this._input;
} }

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';
@@ -74,15 +73,21 @@ export default class TableComponent extends ComponentBase implements IComponent,
} }
} }
transformData(rows: string[][], columns: any[]): { [key: string]: string }[] { public static transformData(rows: string[][], columns: any[]): { [key: string]: string }[] {
return rows.map(row => { if (rows && columns) {
let object: { [key: string]: string } = {}; return rows.map(row => {
row.forEach((val, index) => { let object: { [key: string]: string } = {};
let columnName: string = (columns[index].value) ? columns[index].value : <string>columns[index]; if (row.forEach) {
object[columnName] = val; row.forEach((val, index) => {
let columnName: string = (columns[index].value) ? columns[index].value : <string>columns[index];
object[columnName] = val;
});
}
return object;
}); });
return object; } else {
}); return [];
}
} }
ngAfterViewInit(): void { ngAfterViewInit(): void {
@@ -147,7 +152,7 @@ export default class TableComponent extends ComponentBase implements IComponent,
public setProperties(properties: { [key: string]: any; }): void { public setProperties(properties: { [key: string]: any; }): void {
super.setProperties(properties); super.setProperties(properties);
this._tableData.clear(); this._tableData.clear();
this._tableData.push(this.transformData(this.data, this.columns)); this._tableData.push(TableComponent.transformData(this.data, this.columns));
this._tableColumns = this.transformColumns(this.columns); this._tableColumns = this.transformColumns(this.columns);
this._table.columns = this._tableColumns; this._table.columns = this._tableColumns;
this._table.setData(this._tableData); this._table.setData(this._tableData);

View File

@@ -95,3 +95,7 @@ export class NodeType {
public static ColumnMasterKey = 'ColumnMasterKey'; public static ColumnMasterKey = 'ColumnMasterKey';
public static ColumnEncryptionKey = 'ColumnEncryptionKey'; public static ColumnEncryptionKey = 'ColumnEncryptionKey';
} }
export interface SqlThemeIcon {
readonly id: string;
}

View File

@@ -5,7 +5,7 @@
'use strict'; 'use strict';
import { NodeType } from 'sql/parts/objectExplorer/common/nodeType'; import { NodeType } from 'sql/parts/objectExplorer/common/nodeType';
import { TreeNode, TreeItemCollapsibleState, ObjectExplorerCallbacks } from 'sql/parts/objectExplorer/common/treeNode'; import { TreeNode, TreeItemCollapsibleState } from 'sql/parts/objectExplorer/common/treeNode';
import { ConnectionProfile } from 'sql/parts/connection/common/connectionProfile'; import { ConnectionProfile } from 'sql/parts/connection/common/connectionProfile';
import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
@@ -20,7 +20,6 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { warn, error } from 'sql/base/common/log'; import { warn, error } from 'sql/base/common/log';
import { ServerTreeView } from 'sql/parts/objectExplorer/viewlet/serverTreeView'; import { ServerTreeView } from 'sql/parts/objectExplorer/viewlet/serverTreeView';
import { ICapabilitiesService } from 'sql/services/capabilities/capabilitiesService'; import { ICapabilitiesService } from 'sql/services/capabilities/capabilitiesService';
import * as vscode from 'vscode';
export const SERVICE_ID = 'ObjectExplorerService'; export const SERVICE_ID = 'ObjectExplorerService';
@@ -162,10 +161,16 @@ export class ObjectExplorerService implements IObjectExplorerService {
error(expandResponse.errorMessage); error(expandResponse.errorMessage);
} }
let nodeStatus = this._sessions[expandResponse.sessionId].nodes[expandResponse.nodePath]; let sessionStatus = this._sessions[expandResponse.sessionId];
if (nodeStatus && nodeStatus.expandEmitter) { let foundSession = false;
nodeStatus.expandEmitter.fire(expandResponse); if (sessionStatus) {
} else { let nodeStatus = this._sessions[expandResponse.sessionId].nodes[expandResponse.nodePath];
foundSession = !!nodeStatus;
if (foundSession && nodeStatus.expandEmitter) {
nodeStatus.expandEmitter.fire(expandResponse);
}
}
if (!foundSession) {
warn(`Cannot find node status for session: ${expandResponse.sessionId} and node path: ${expandResponse.nodePath}`); warn(`Cannot find node status for session: ${expandResponse.sessionId} and node path: ${expandResponse.nodePath}`);
} }
} }
@@ -396,7 +401,7 @@ export class ObjectExplorerService implements IObjectExplorerService {
} }
return new TreeNode(nodeInfo.nodeType, nodeInfo.label, isLeaf, nodeInfo.nodePath, return new TreeNode(nodeInfo.nodeType, nodeInfo.label, isLeaf, nodeInfo.nodePath,
nodeInfo.nodeSubType, nodeInfo.nodeStatus, parent, nodeInfo.metadata, { nodeInfo.nodeSubType, nodeInfo.nodeStatus, parent, nodeInfo.metadata, nodeInfo.iconType, {
getChildren: treeNode => this.getChildren(treeNode), getChildren: treeNode => this.getChildren(treeNode),
isExpanded: treeNode => this.isExpanded(treeNode), isExpanded: treeNode => this.isExpanded(treeNode),
setNodeExpandedState: (treeNode, expandedState) => this.setNodeExpandedState(treeNode, expandedState), setNodeExpandedState: (treeNode, expandedState) => this.setNodeExpandedState(treeNode, expandedState),

View File

@@ -6,7 +6,7 @@
'use strict'; 'use strict';
import { ConnectionProfile } from 'sql/parts/connection/common/connectionProfile'; import { ConnectionProfile } from 'sql/parts/connection/common/connectionProfile';
import { NodeType } from 'sql/parts/objectExplorer/common/nodeType'; import { NodeType, SqlThemeIcon } from 'sql/parts/objectExplorer/common/nodeType';
import * as sqlops from 'sqlops'; import * as sqlops from 'sqlops';
import * as UUID from 'vs/base/common/uuid'; import * as UUID from 'vs/base/common/uuid';
@@ -83,6 +83,23 @@ export class TreeNode {
public metadata: sqlops.ObjectMetadata; public metadata: sqlops.ObjectMetadata;
public iconType: string | SqlThemeIcon;
constructor(nodeTypeId: string, label: string, isAlwaysLeaf: boolean, nodePath: string,
nodeSubType: string, nodeStatus: string, parent: TreeNode, metadata: sqlops.ObjectMetadata,
iconType: string | SqlThemeIcon,
private _objectExplorerCallbacks: ObjectExplorerCallbacks) {
this.nodeTypeId = nodeTypeId;
this.label = label;
this.isAlwaysLeaf = isAlwaysLeaf;
this.nodePath = nodePath;
this.parent = parent;
this.metadata = metadata;
this.iconType = iconType;
this.id = UUID.generateUuid();
this.nodeSubType = nodeSubType;
this.nodeStatus = nodeStatus;
}
public getConnectionProfile(): ConnectionProfile { public getConnectionProfile(): ConnectionProfile {
var currentNode: TreeNode = this; var currentNode: TreeNode = this;
while (!currentNode.connection && currentNode.parent) { while (!currentNode.connection && currentNode.parent) {
@@ -149,18 +166,4 @@ export class TreeNode {
public setSelected(selected: boolean, clearOtherSelections?: boolean): Thenable<void> { public setSelected(selected: boolean, clearOtherSelections?: boolean): Thenable<void> {
return this._objectExplorerCallbacks.setNodeSelected(this, selected, clearOtherSelections); return this._objectExplorerCallbacks.setNodeSelected(this, selected, clearOtherSelections);
} }
constructor(nodeTypeId: string, label: string, isAlwaysLeaf: boolean, nodePath: string,
nodeSubType: string, nodeStatus: string, parent: TreeNode, metadata: sqlops.ObjectMetadata,
private _objectExplorerCallbacks: ObjectExplorerCallbacks) {
this.nodeTypeId = nodeTypeId;
this.label = label;
this.isAlwaysLeaf = isAlwaysLeaf;
this.nodePath = nodePath;
this.parent = parent;
this.metadata = metadata;
this.id = UUID.generateUuid();
this.nodeSubType = nodeSubType;
this.nodeStatus = nodeStatus;
}
} }

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

@@ -33,7 +33,7 @@ export class RefreshAction extends Action {
id: string, id: string,
label: string, label: string,
tree: ITree, tree: ITree,
private element: ConnectionProfile | TreeNode, private element: IConnectionProfile | TreeNode,
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService, @IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
@IObjectExplorerService private _objectExplorerService: IObjectExplorerService, @IObjectExplorerService private _objectExplorerService: IObjectExplorerService,
@IErrorMessageService private _errorMessageService: IErrorMessageService @IErrorMessageService private _errorMessageService: IErrorMessageService
@@ -88,80 +88,31 @@ export class DisconnectConnectionAction extends Action {
public static ID = 'objectExplorer.disconnect'; public static ID = 'objectExplorer.disconnect';
public static LABEL = localize('DisconnectAction', 'Disconnect'); public static LABEL = localize('DisconnectAction', 'Disconnect');
private _disposables: IDisposable[] = [];
private _connectionProfile: ConnectionProfile;
private _container: HTMLElement;
constructor( constructor(
id: string, id: string,
label: string, label: string,
private _connectionProfile: ConnectionProfile,
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService, @IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
@IObjectExplorerService private _objectExplorerService: IObjectExplorerService, @IObjectExplorerService private _objectExplorerService: IObjectExplorerService,
@IErrorMessageService private _errorMessageService: IErrorMessageService @IErrorMessageService private _errorMessageService: IErrorMessageService
) { ) {
super(id, label); super(id, label);
const self = this;
this._disposables.push(this._connectionManagementService.onConnect(() => {
self.setLabel();
})
);
this._disposables.push(this._connectionManagementService.onDisconnect((disconnectParams) => {
if (this._connectionProfile) {
this._connectionProfile.isDisconnecting = false;
}
self.setLabel();
self._connectionManagementService.closeDashboard(disconnectParams.connectionUri);
})
);
if (this._objectExplorerService && this._objectExplorerService.onUpdateObjectExplorerNodes) {
this._disposables.push(this._objectExplorerService.onUpdateObjectExplorerNodes((args) => {
self.removeSpinning(args.connection);
if (args.errorMessage !== undefined) {
self.showError(args.errorMessage);
}
})
);
}
}
private showError(errorMessage: string) {
if (this._errorMessageService) {
this._errorMessageService.showDialog(Severity.Error, '', errorMessage);
}
}
private setLabel(): void {
if (!this._connectionProfile) {
this.label = 'Connect';
return;
}
this.label = this._connectionManagementService.isProfileConnected(this._connectionProfile) ? 'Disconnect' : 'Connect';
}
private removeSpinning(connection: IConnectionProfile): void {
if (this._connectionProfile) {
if (connection.id === this._connectionProfile.id && this._container) {
ObjectExplorerActionUtilities.hideLoadingIcon(this._container, ObjectExplorerActionUtilities.connectionElementClass);
}
}
} }
run(actionContext: ObjectExplorerActionsContext): TPromise<any> { run(actionContext: ObjectExplorerActionsContext): TPromise<any> {
return new TPromise<boolean>((resolve, reject) => { return new TPromise<boolean>((resolve, reject) => {
if (actionContext instanceof ObjectExplorerActionsContext) {
//set objectExplorerTreeNode for context menu clicks
this._connectionProfile = actionContext.connectionProfile;
this._container = actionContext.container;
resolve(true);
}
if (!this._connectionProfile) { if (!this._connectionProfile) {
resolve(true); resolve(true);
} }
if (this._connectionManagementService.isProfileConnected(this._connectionProfile)) { if (this._connectionManagementService.isProfileConnected(this._connectionProfile)) {
this._connectionProfile.isDisconnecting = true; let profileImpl = this._connectionProfile as ConnectionProfile;
if (profileImpl) {
profileImpl.isDisconnecting = true;
}
this._connectionManagementService.disconnect(this._connectionProfile).then((value) => { this._connectionManagementService.disconnect(this._connectionProfile).then((value) => {
if (profileImpl) {
profileImpl.isDisconnecting = false;
}
resolve(true); resolve(true);
} }
).catch(disconnectError => { ).catch(disconnectError => {
@@ -172,11 +123,6 @@ export class DisconnectConnectionAction extends Action {
} }
}); });
} }
dispose(): void {
super.dispose();
this._disposables = dispose(this._disposables);
}
} }
@@ -362,11 +308,11 @@ export class RecentConnectionsFilterAction extends Action {
export class NewQueryAction extends Action { export class NewQueryAction extends Action {
public static ID = 'registeredServers.newQuery'; public static ID = 'registeredServers.newQuery';
public static LABEL = localize('registeredServers.newQuery', 'New Query'); public static LABEL = localize('registeredServers.newQuery', 'New Query');
private _connectionProfile: ConnectionProfile; private _connectionProfile: IConnectionProfile;
get connectionProfile(): ConnectionProfile { get connectionProfile(): IConnectionProfile {
return this._connectionProfile; return this._connectionProfile;
} }
set connectionProfile(profile: ConnectionProfile) { set connectionProfile(profile: IConnectionProfile) {
this._connectionProfile = profile; this._connectionProfile = profile;
} }
@@ -403,7 +349,7 @@ export class DeleteConnectionAction extends Action {
constructor( constructor(
id: string, id: string,
label: string, label: string,
private element: ConnectionProfile | ConnectionProfileGroup, private element: IConnectionProfile | ConnectionProfileGroup,
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService @IConnectionManagementService private _connectionManagementService: IConnectionManagementService
) { ) {
super(id, label); super(id, label);
@@ -413,7 +359,6 @@ export class DeleteConnectionAction extends Action {
} }
if (element instanceof ConnectionProfile) { if (element instanceof ConnectionProfile) {
element = <ConnectionProfile>element;
let parent: ConnectionProfileGroup = element.parent; let parent: ConnectionProfileGroup = element.parent;
if (parent && parent.id === Constants.unsavedGroupId) { if (parent && parent.id === Constants.unsavedGroupId) {
this.enabled = false; this.enabled = false;

View File

@@ -8,11 +8,14 @@ import { localize } from 'vs/nls';
import { TPromise } from 'vs/base/common/winjs.base'; import { TPromise } from 'vs/base/common/winjs.base';
import { Action } from 'vs/base/common/actions'; import { Action } from 'vs/base/common/actions';
import { ITree } from 'vs/base/parts/tree/browser/tree'; import { ITree } from 'vs/base/parts/tree/browser/tree';
import { ExecuteCommandAction } from 'vs/platform/actions/common/actions';
import { ICommandService } from 'vs/platform/commands/common/commands';
import * as sqlops from 'sqlops';
import { IConnectionManagementService, IConnectionCompletionOptions, IErrorMessageService } from 'sql/parts/connection/common/connectionManagement'; import { IConnectionManagementService, IConnectionCompletionOptions, IErrorMessageService } from 'sql/parts/connection/common/connectionManagement';
import { TreeNode } from 'sql/parts/objectExplorer/common/treeNode'; import { TreeNode } from 'sql/parts/objectExplorer/common/treeNode';
import { ConnectionProfile } from 'sql/parts/connection/common/connectionProfile';
import { import {
NewQueryAction, ScriptSelectAction, EditDataAction, ScriptCreateAction, ScriptSelectAction, EditDataAction, ScriptCreateAction,
ScriptExecuteAction, ScriptDeleteAction, ScriptAlterAction ScriptExecuteAction, ScriptDeleteAction, ScriptAlterAction
} from 'sql/workbench/common/actions'; } from 'sql/workbench/common/actions';
import { NodeType } from 'sql/parts/objectExplorer/common/nodeType'; import { NodeType } from 'sql/parts/objectExplorer/common/nodeType';
@@ -23,41 +26,51 @@ import { IScriptingService } from 'sql/services/scripting/scriptingService';
import { IQueryEditorService } from 'sql/parts/query/common/queryEditorService'; import { IQueryEditorService } from 'sql/parts/query/common/queryEditorService';
import { IObjectExplorerService } from 'sql/parts/objectExplorer/common/objectExplorerService'; import { IObjectExplorerService } from 'sql/parts/objectExplorer/common/objectExplorerService';
import * as Constants from 'sql/parts/connection/common/constants'; import * as Constants from 'sql/parts/connection/common/constants';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { ExecuteCommandAction } from 'vs/platform/actions/common/actions';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IConnectionProfile } from 'sql/parts/connection/common/interfaces'; import { IConnectionProfile } from 'sql/parts/connection/common/interfaces';
import { ICapabilitiesService } from 'sql/services/capabilities/capabilitiesService';
import { ConnectionProfile } from 'sql/parts/connection/common/connectionProfile';
export class ObjectExplorerActionsContext {
public treeNode: TreeNode; export class ObjectExplorerActionsContext implements sqlops.ObjectExplorerContext {
public connectionProfile: ConnectionProfile; public connectionProfile: IConnectionProfile;
public container: HTMLElement; public nodeInfo: sqlops.NodeInfo;
public tree: ITree; public isConnectionNode: boolean = false;
} }
async function getTreeNode(context: ObjectExplorerActionsContext, objectExplorerService: IObjectExplorerService): TPromise<TreeNode> {
if (context.isConnectionNode) {
return Promise.resolve(undefined);
}
return await objectExplorerService.getTreeNode(context.connectionProfile.id, context.nodeInfo.nodePath);
}
export class OEAction extends ExecuteCommandAction { export class OEAction extends ExecuteCommandAction {
private _objectExplorerTreeNode: TreeNode;
private _container: HTMLElement;
private _treeSelectionHandler: TreeSelectionHandler; private _treeSelectionHandler: TreeSelectionHandler;
constructor( constructor(
id: string, label: string, id: string, label: string,
@IInstantiationService private _instantiationService: IInstantiationService, @IInstantiationService private _instantiationService: IInstantiationService,
@ICommandService commandService: ICommandService, @ICommandService commandService: ICommandService,
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService @IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
@IObjectExplorerService private _objectExplorerService: IObjectExplorerService
) { ) {
super(id, label, commandService); super(id, label, commandService);
} }
public run(actionContext: any): TPromise<boolean> { public async run(actionContext: any): TPromise<boolean> {
this._treeSelectionHandler = this._instantiationService.createInstance(TreeSelectionHandler); this._treeSelectionHandler = this._instantiationService.createInstance(TreeSelectionHandler);
let profile: IConnectionProfile; let profile: IConnectionProfile;
if (actionContext.connectionProfile) { if (actionContext instanceof ObjectExplorerActionsContext) {
profile = actionContext.connectionProfile; if (actionContext.isConnectionNode) {
} else { profile = actionContext.connectionProfile;
profile = TreeUpdateUtils.getConnectionProfile(<TreeNode>actionContext.treeNode); } else {
// Get the "correct" version from the tree
let treeNode = await getTreeNode(actionContext, this._objectExplorerService);
profile = TreeUpdateUtils.getConnectionProfile(treeNode);
}
} }
this._treeSelectionHandler.onTreeActionStateChange(true); this._treeSelectionHandler.onTreeActionStateChange(true);
@@ -72,18 +85,16 @@ export class ManageConnectionAction extends Action {
public static ID = 'objectExplorer.manage'; public static ID = 'objectExplorer.manage';
public static LABEL = localize('ManageAction', 'Manage'); public static LABEL = localize('ManageAction', 'Manage');
private _connectionProfile: ConnectionProfile;
private _objectExplorerTreeNode: TreeNode;
private _treeSelectionHandler: TreeSelectionHandler; private _treeSelectionHandler: TreeSelectionHandler;
protected _container: HTMLElement;
constructor( constructor(
id: string, id: string,
label: string, label: string,
private _tree: ITree,
@IConnectionManagementService protected _connectionManagementService: IConnectionManagementService, @IConnectionManagementService protected _connectionManagementService: IConnectionManagementService,
@ICapabilitiesService protected _capabilitiesService: ICapabilitiesService,
@IInstantiationService private _instantiationService: IInstantiationService, @IInstantiationService private _instantiationService: IInstantiationService,
@IObjectExplorerService private _objectExplorerService?: IObjectExplorerService, @IObjectExplorerService private _objectExplorerService: IObjectExplorerService
) { ) {
super(id, label); super(id, label);
} }
@@ -91,33 +102,37 @@ export class ManageConnectionAction extends Action {
run(actionContext: ObjectExplorerActionsContext): TPromise<any> { run(actionContext: ObjectExplorerActionsContext): TPromise<any> {
this._treeSelectionHandler = this._instantiationService.createInstance(TreeSelectionHandler); this._treeSelectionHandler = this._instantiationService.createInstance(TreeSelectionHandler);
this._treeSelectionHandler.onTreeActionStateChange(true); this._treeSelectionHandler.onTreeActionStateChange(true);
let self = this;
let promise = new TPromise<boolean>((resolve, reject) => { let promise = new TPromise<boolean>((resolve, reject) => {
this.doManage(actionContext).then((success) => { self.doManage(actionContext).then((success) => {
this.done(); self.done();
resolve(success); resolve(success);
}, error => { }, error => {
this.done(); self.done();
reject(error); reject(error);
}); });
}); });
return promise; return promise;
} }
private doManage(actionContext: ObjectExplorerActionsContext): Thenable<boolean> { private async doManage(actionContext: ObjectExplorerActionsContext): TPromise<boolean> {
let treeNode: TreeNode = undefined;
let connectionProfile: IConnectionProfile = undefined;
if (actionContext instanceof ObjectExplorerActionsContext) { if (actionContext instanceof ObjectExplorerActionsContext) {
//set objectExplorerTreeNode for context menu clicks // Must use a real connection profile for this action due to lookup
this._connectionProfile = actionContext.connectionProfile; connectionProfile = ConnectionProfile.fromIConnectionProfile(this._capabilitiesService, actionContext.connectionProfile);
this._objectExplorerTreeNode = actionContext.treeNode; if (!actionContext.isConnectionNode) {
if (this._connectionProfile === undefined && TreeUpdateUtils.isDatabaseNode(this._objectExplorerTreeNode)) { treeNode = await getTreeNode(actionContext, this._objectExplorerService);
this._connectionProfile = TreeUpdateUtils.getConnectionProfile(<TreeNode>this._objectExplorerTreeNode); if (TreeUpdateUtils.isDatabaseNode(treeNode)) {
connectionProfile = TreeUpdateUtils.getConnectionProfile(treeNode);
}
} }
this._container = actionContext.container;
} }
if (!this._connectionProfile) { if (!connectionProfile) {
// This should never happen. There should be always a valid connection if the manage action is called for // This should never happen. There should be always a valid connection if the manage action is called for
// an OE node or a database node // an OE node or a database node
return TPromise.wrap(true); return true;
} }
let options: IConnectionCompletionOptions = { let options: IConnectionCompletionOptions = {
@@ -130,10 +145,10 @@ export class ManageConnectionAction extends Action {
// If it's a database node just open a database connection and open dashboard, // If it's a database node just open a database connection and open dashboard,
// the node is already from an open OE session we don't need to create new session // the node is already from an open OE session we don't need to create new session
if (TreeUpdateUtils.isAvailableDatabaseNode(this._objectExplorerTreeNode)) { if (TreeUpdateUtils.isAvailableDatabaseNode(treeNode)) {
return this._connectionManagementService.showDashboard(this._connectionProfile); return this._connectionManagementService.showDashboard(connectionProfile);
} else { } else {
return TreeUpdateUtils.connectAndCreateOeSession(this._connectionProfile, options, this._connectionManagementService, this._objectExplorerService, actionContext.tree); return TreeUpdateUtils.connectAndCreateOeSession(connectionProfile, options, this._connectionManagementService, this._objectExplorerService, this._tree);
} }
} }
@@ -149,7 +164,6 @@ export class ManageConnectionAction extends Action {
export class OEScriptSelectAction extends ScriptSelectAction { export class OEScriptSelectAction extends ScriptSelectAction {
public static ID = 'objectExplorer.' + ScriptSelectAction.ID; public static ID = 'objectExplorer.' + ScriptSelectAction.ID;
private _objectExplorerTreeNode: TreeNode; private _objectExplorerTreeNode: TreeNode;
private _container: HTMLElement;
private _treeSelectionHandler: TreeSelectionHandler; private _treeSelectionHandler: TreeSelectionHandler;
constructor( constructor(
@@ -157,23 +171,23 @@ export class OEScriptSelectAction extends ScriptSelectAction {
@IQueryEditorService protected _queryEditorService: IQueryEditorService, @IQueryEditorService protected _queryEditorService: IQueryEditorService,
@IConnectionManagementService protected _connectionManagementService: IConnectionManagementService, @IConnectionManagementService protected _connectionManagementService: IConnectionManagementService,
@IScriptingService protected _scriptingService: IScriptingService, @IScriptingService protected _scriptingService: IScriptingService,
@IObjectExplorerService private _objectExplorerService: IObjectExplorerService,
@IInstantiationService private _instantiationService: IInstantiationService @IInstantiationService private _instantiationService: IInstantiationService
) { ) {
super(id, label, _queryEditorService, _connectionManagementService, _scriptingService); super(id, label, _queryEditorService, _connectionManagementService, _scriptingService);
} }
public run(actionContext: any): TPromise<boolean> { public async run(actionContext: any): TPromise<boolean> {
this._treeSelectionHandler = this._instantiationService.createInstance(TreeSelectionHandler); this._treeSelectionHandler = this._instantiationService.createInstance(TreeSelectionHandler);
if (actionContext instanceof ObjectExplorerActionsContext) { if (actionContext instanceof ObjectExplorerActionsContext) {
//set objectExplorerTreeNode for context menu clicks //set objectExplorerTreeNode for context menu clicks
this._objectExplorerTreeNode = actionContext.treeNode; this._objectExplorerTreeNode = await getTreeNode(actionContext, this._objectExplorerService);
this._container = actionContext.container;
} }
this._treeSelectionHandler.onTreeActionStateChange(true); this._treeSelectionHandler.onTreeActionStateChange(true);
var connectionProfile = TreeUpdateUtils.getConnectionProfile(<TreeNode>this._objectExplorerTreeNode); var connectionProfile = TreeUpdateUtils.getConnectionProfile(this._objectExplorerTreeNode);
var ownerUri = this._connectionManagementService.getConnectionId(connectionProfile); var ownerUri = this._connectionManagementService.getConnectionId(connectionProfile);
ownerUri = this._connectionManagementService.getFormattedUri(ownerUri, connectionProfile); ownerUri = this._connectionManagementService.getFormattedUri(ownerUri, connectionProfile);
var metadata = (<TreeNode>this._objectExplorerTreeNode).metadata; var metadata = this._objectExplorerTreeNode.metadata;
return super.run({ profile: connectionProfile, object: metadata }).then((result) => { return super.run({ profile: connectionProfile, object: metadata }).then((result) => {
this._treeSelectionHandler.onTreeActionStateChange(false); this._treeSelectionHandler.onTreeActionStateChange(false);
@@ -185,7 +199,6 @@ export class OEScriptSelectAction extends ScriptSelectAction {
export class OEEditDataAction extends EditDataAction { export class OEEditDataAction extends EditDataAction {
public static ID = 'objectExplorer.' + EditDataAction.ID; public static ID = 'objectExplorer.' + EditDataAction.ID;
private _objectExplorerTreeNode: TreeNode; private _objectExplorerTreeNode: TreeNode;
private _container: HTMLElement;
private _treeSelectionHandler: TreeSelectionHandler; private _treeSelectionHandler: TreeSelectionHandler;
constructor( constructor(
@@ -193,17 +206,17 @@ export class OEEditDataAction extends EditDataAction {
@IQueryEditorService protected _queryEditorService: IQueryEditorService, @IQueryEditorService protected _queryEditorService: IQueryEditorService,
@IConnectionManagementService protected _connectionManagementService: IConnectionManagementService, @IConnectionManagementService protected _connectionManagementService: IConnectionManagementService,
@IScriptingService protected _scriptingService: IScriptingService, @IScriptingService protected _scriptingService: IScriptingService,
@IObjectExplorerService private _objectExplorerService: IObjectExplorerService,
@IInstantiationService private _instantiationService: IInstantiationService @IInstantiationService private _instantiationService: IInstantiationService
) { ) {
super(id, label, _queryEditorService, _connectionManagementService, _scriptingService); super(id, label, _queryEditorService, _connectionManagementService, _scriptingService);
} }
public run(actionContext: any): TPromise<boolean> { public async run(actionContext: any): TPromise<boolean> {
this._treeSelectionHandler = this._instantiationService.createInstance(TreeSelectionHandler); this._treeSelectionHandler = this._instantiationService.createInstance(TreeSelectionHandler);
if (actionContext instanceof ObjectExplorerActionsContext) { if (actionContext instanceof ObjectExplorerActionsContext) {
//set objectExplorerTreeNode for context menu clicks //set objectExplorerTreeNode for context menu clicks
this._objectExplorerTreeNode = actionContext.treeNode; this._objectExplorerTreeNode = await getTreeNode(actionContext, this._objectExplorerService);
this._container = actionContext.container;
} }
this._treeSelectionHandler.onTreeActionStateChange(true); this._treeSelectionHandler.onTreeActionStateChange(true);
var connectionProfile = TreeUpdateUtils.getConnectionProfile(<TreeNode>this._objectExplorerTreeNode); var connectionProfile = TreeUpdateUtils.getConnectionProfile(<TreeNode>this._objectExplorerTreeNode);
@@ -219,7 +232,6 @@ export class OEEditDataAction extends EditDataAction {
export class OEScriptCreateAction extends ScriptCreateAction { export class OEScriptCreateAction extends ScriptCreateAction {
public static ID = 'objectExplorer.' + ScriptCreateAction.ID; public static ID = 'objectExplorer.' + ScriptCreateAction.ID;
private _objectExplorerTreeNode: TreeNode; private _objectExplorerTreeNode: TreeNode;
private _container: HTMLElement;
private _treeSelectionHandler: TreeSelectionHandler; private _treeSelectionHandler: TreeSelectionHandler;
constructor( constructor(
@@ -227,18 +239,18 @@ export class OEScriptCreateAction extends ScriptCreateAction {
@IQueryEditorService protected _queryEditorService: IQueryEditorService, @IQueryEditorService protected _queryEditorService: IQueryEditorService,
@IConnectionManagementService protected _connectionManagementService: IConnectionManagementService, @IConnectionManagementService protected _connectionManagementService: IConnectionManagementService,
@IScriptingService protected _scriptingService: IScriptingService, @IScriptingService protected _scriptingService: IScriptingService,
@IObjectExplorerService private _objectExplorerService: IObjectExplorerService,
@IInstantiationService private _instantiationService: IInstantiationService, @IInstantiationService private _instantiationService: IInstantiationService,
@IErrorMessageService protected _errorMessageService: IErrorMessageService @IErrorMessageService protected _errorMessageService: IErrorMessageService
) { ) {
super(id, label, _queryEditorService, _connectionManagementService, _scriptingService, _errorMessageService); super(id, label, _queryEditorService, _connectionManagementService, _scriptingService, _errorMessageService);
} }
public run(actionContext: any): TPromise<boolean> { public async run(actionContext: any): TPromise<boolean> {
this._treeSelectionHandler = this._instantiationService.createInstance(TreeSelectionHandler); this._treeSelectionHandler = this._instantiationService.createInstance(TreeSelectionHandler);
if (actionContext instanceof ObjectExplorerActionsContext) { if (actionContext instanceof ObjectExplorerActionsContext) {
//set objectExplorerTreeNode for context menu clicks //set objectExplorerTreeNode for context menu clicks
this._objectExplorerTreeNode = actionContext.treeNode; this._objectExplorerTreeNode = await getTreeNode(actionContext, this._objectExplorerService);
this._container = actionContext.container;
} }
this._treeSelectionHandler.onTreeActionStateChange(true); this._treeSelectionHandler.onTreeActionStateChange(true);
var connectionProfile = TreeUpdateUtils.getConnectionProfile(<TreeNode>this._objectExplorerTreeNode); var connectionProfile = TreeUpdateUtils.getConnectionProfile(<TreeNode>this._objectExplorerTreeNode);
@@ -256,7 +268,6 @@ export class OEScriptCreateAction extends ScriptCreateAction {
export class OEScriptExecuteAction extends ScriptExecuteAction { export class OEScriptExecuteAction extends ScriptExecuteAction {
public static ID = 'objectExplorer.' + ScriptExecuteAction.ID; public static ID = 'objectExplorer.' + ScriptExecuteAction.ID;
private _objectExplorerTreeNode: TreeNode; private _objectExplorerTreeNode: TreeNode;
private _container: HTMLElement;
private _treeSelectionHandler: TreeSelectionHandler; private _treeSelectionHandler: TreeSelectionHandler;
constructor( constructor(
@@ -264,18 +275,18 @@ export class OEScriptExecuteAction extends ScriptExecuteAction {
@IQueryEditorService protected _queryEditorService: IQueryEditorService, @IQueryEditorService protected _queryEditorService: IQueryEditorService,
@IConnectionManagementService protected _connectionManagementService: IConnectionManagementService, @IConnectionManagementService protected _connectionManagementService: IConnectionManagementService,
@IScriptingService protected _scriptingService: IScriptingService, @IScriptingService protected _scriptingService: IScriptingService,
@IObjectExplorerService private _objectExplorerService: IObjectExplorerService,
@IInstantiationService private _instantiationService: IInstantiationService, @IInstantiationService private _instantiationService: IInstantiationService,
@IErrorMessageService protected _errorMessageService: IErrorMessageService @IErrorMessageService protected _errorMessageService: IErrorMessageService
) { ) {
super(id, label, _queryEditorService, _connectionManagementService, _scriptingService, _errorMessageService); super(id, label, _queryEditorService, _connectionManagementService, _scriptingService, _errorMessageService);
} }
public run(actionContext: any): TPromise<boolean> { public async run(actionContext: any): TPromise<boolean> {
this._treeSelectionHandler = this._instantiationService.createInstance(TreeSelectionHandler); this._treeSelectionHandler = this._instantiationService.createInstance(TreeSelectionHandler);
if (actionContext instanceof ObjectExplorerActionsContext) { if (actionContext instanceof ObjectExplorerActionsContext) {
//set objectExplorerTreeNode for context menu clicks //set objectExplorerTreeNode for context menu clicks
this._objectExplorerTreeNode = actionContext.treeNode; this._objectExplorerTreeNode = await getTreeNode(actionContext, this._objectExplorerService);
this._container = actionContext.container;
} }
this._treeSelectionHandler.onTreeActionStateChange(true); this._treeSelectionHandler.onTreeActionStateChange(true);
var connectionProfile = TreeUpdateUtils.getConnectionProfile(<TreeNode>this._objectExplorerTreeNode); var connectionProfile = TreeUpdateUtils.getConnectionProfile(<TreeNode>this._objectExplorerTreeNode);
@@ -293,7 +304,6 @@ export class OEScriptExecuteAction extends ScriptExecuteAction {
export class OEScriptAlterAction extends ScriptAlterAction { export class OEScriptAlterAction extends ScriptAlterAction {
public static ID = 'objectExplorer.' + ScriptAlterAction.ID; public static ID = 'objectExplorer.' + ScriptAlterAction.ID;
private _objectExplorerTreeNode: TreeNode; private _objectExplorerTreeNode: TreeNode;
private _container: HTMLElement;
private _treeSelectionHandler: TreeSelectionHandler; private _treeSelectionHandler: TreeSelectionHandler;
constructor( constructor(
@@ -301,18 +311,18 @@ export class OEScriptAlterAction extends ScriptAlterAction {
@IQueryEditorService protected _queryEditorService: IQueryEditorService, @IQueryEditorService protected _queryEditorService: IQueryEditorService,
@IConnectionManagementService protected _connectionManagementService: IConnectionManagementService, @IConnectionManagementService protected _connectionManagementService: IConnectionManagementService,
@IScriptingService protected _scriptingService: IScriptingService, @IScriptingService protected _scriptingService: IScriptingService,
@IObjectExplorerService private _objectExplorerService: IObjectExplorerService,
@IInstantiationService private _instantiationService: IInstantiationService, @IInstantiationService private _instantiationService: IInstantiationService,
@IErrorMessageService protected _errorMessageService: IErrorMessageService @IErrorMessageService protected _errorMessageService: IErrorMessageService
) { ) {
super(id, label, _queryEditorService, _connectionManagementService, _scriptingService, _errorMessageService); super(id, label, _queryEditorService, _connectionManagementService, _scriptingService, _errorMessageService);
} }
public run(actionContext: any): TPromise<boolean> { public async run(actionContext: any): TPromise<boolean> {
this._treeSelectionHandler = this._instantiationService.createInstance(TreeSelectionHandler); this._treeSelectionHandler = this._instantiationService.createInstance(TreeSelectionHandler);
if (actionContext instanceof ObjectExplorerActionsContext) { if (actionContext instanceof ObjectExplorerActionsContext) {
//set objectExplorerTreeNode for context menu clicks //set objectExplorerTreeNode for context menu clicks
this._objectExplorerTreeNode = actionContext.treeNode; this._objectExplorerTreeNode = await getTreeNode(actionContext, this._objectExplorerService);
this._container = actionContext.container;
} }
this._treeSelectionHandler.onTreeActionStateChange(true); this._treeSelectionHandler.onTreeActionStateChange(true);
var connectionProfile = TreeUpdateUtils.getConnectionProfile(<TreeNode>this._objectExplorerTreeNode); var connectionProfile = TreeUpdateUtils.getConnectionProfile(<TreeNode>this._objectExplorerTreeNode);
@@ -330,7 +340,6 @@ export class OEScriptAlterAction extends ScriptAlterAction {
export class OEScriptDeleteAction extends ScriptDeleteAction { export class OEScriptDeleteAction extends ScriptDeleteAction {
public static ID = 'objectExplorer.' + ScriptDeleteAction.ID; public static ID = 'objectExplorer.' + ScriptDeleteAction.ID;
private _objectExplorerTreeNode: TreeNode; private _objectExplorerTreeNode: TreeNode;
private _container: HTMLElement;
private _treeSelectionHandler: TreeSelectionHandler; private _treeSelectionHandler: TreeSelectionHandler;
constructor( constructor(
@@ -338,18 +347,18 @@ export class OEScriptDeleteAction extends ScriptDeleteAction {
@IQueryEditorService protected _queryEditorService: IQueryEditorService, @IQueryEditorService protected _queryEditorService: IQueryEditorService,
@IConnectionManagementService protected _connectionManagementService: IConnectionManagementService, @IConnectionManagementService protected _connectionManagementService: IConnectionManagementService,
@IScriptingService protected _scriptingService: IScriptingService, @IScriptingService protected _scriptingService: IScriptingService,
@IObjectExplorerService private _objectExplorerService: IObjectExplorerService,
@IInstantiationService private _instantiationService: IInstantiationService, @IInstantiationService private _instantiationService: IInstantiationService,
@IErrorMessageService protected _errorMessageService: IErrorMessageService @IErrorMessageService protected _errorMessageService: IErrorMessageService
) { ) {
super(id, label, _queryEditorService, _connectionManagementService, _scriptingService, _errorMessageService); super(id, label, _queryEditorService, _connectionManagementService, _scriptingService, _errorMessageService);
} }
public run(actionContext: any): TPromise<boolean> { public async run(actionContext: any): TPromise<boolean> {
this._treeSelectionHandler = this._instantiationService.createInstance(TreeSelectionHandler); this._treeSelectionHandler = this._instantiationService.createInstance(TreeSelectionHandler);
if (actionContext instanceof ObjectExplorerActionsContext) { if (actionContext instanceof ObjectExplorerActionsContext) {
//set objectExplorerTreeNode for context menu clicks //set objectExplorerTreeNode for context menu clicks
this._objectExplorerTreeNode = actionContext.treeNode; this._objectExplorerTreeNode = await getTreeNode(actionContext, this._objectExplorerService);
this._container = actionContext.container;
} }
this._treeSelectionHandler.onTreeActionStateChange(true); this._treeSelectionHandler.onTreeActionStateChange(true);
var connectionProfile = TreeUpdateUtils.getConnectionProfile(<TreeNode>this._objectExplorerTreeNode); var connectionProfile = TreeUpdateUtils.getConnectionProfile(<TreeNode>this._objectExplorerTreeNode);
@@ -364,77 +373,11 @@ export class OEScriptDeleteAction extends ScriptDeleteAction {
} }
} }
export class DisconnectAction extends Action {
public static ID = 'objectExplorer.disconnect';
public static LABEL = localize('objectExplorAction.disconnect', 'Disconnect');
private _objectExplorerTreeNode: TreeNode;
private _container: HTMLElement;
private _treeSelectionHandler: TreeSelectionHandler;
constructor(
id: string,
label: string,
@IConnectionManagementService private connectionManagementService: IConnectionManagementService,
@IInstantiationService private _instantiationService: IInstantiationService
) {
super(id, label);
}
public run(actionContext: any): TPromise<boolean> {
this._treeSelectionHandler = this._instantiationService.createInstance(TreeSelectionHandler);
if (actionContext instanceof ObjectExplorerActionsContext) {
//set objectExplorerTreeNode for context menu clicks
this._objectExplorerTreeNode = actionContext.treeNode;
this._container = actionContext.container;
}
var connectionProfile = (<TreeNode>this._objectExplorerTreeNode).getConnectionProfile();
if (this.connectionManagementService.isProfileConnected(connectionProfile)) {
this._treeSelectionHandler.onTreeActionStateChange(true);
this.connectionManagementService.disconnect(connectionProfile).then(() => {
this._treeSelectionHandler.onTreeActionStateChange(false);
});
}
return TPromise.as(true);
}
}
export class ObjectExplorerActionUtilities { export class ObjectExplorerActionUtilities {
public static readonly objectExplorerElementClass = 'object-element-group'; public static readonly objectExplorerElementClass = 'object-element-group';
public static readonly connectionElementClass = 'connection-tile'; public static readonly connectionElementClass = 'connection-tile';
private static getGroupContainer(container: HTMLElement, elementName: string): HTMLElement {
var element = container;
while (element && element.className !== elementName) {
element = element.parentElement;
}
return element ? element.parentElement : undefined;
}
public static showLoadingIcon(container: HTMLElement, elementName: string): void {
if (container) {
let groupContainer = this.getGroupContainer(container, elementName);
if (groupContainer) {
groupContainer.classList.add('icon');
groupContainer.classList.add('in-progress');
}
}
}
public static hideLoadingIcon(container: HTMLElement, elementName: string): void {
if (container) {
let element = this.getGroupContainer(container, elementName);
if (element && element.classList) {
element.classList.remove('icon');
element.classList.remove('in-progress');
}
}
}
public static getScriptMap(treeNode: TreeNode): Map<NodeType, any[]> { public static getScriptMap(treeNode: TreeNode): Map<NodeType, any[]> {
let scriptMap = new Map<NodeType, any[]>(); let scriptMap = new Map<NodeType, any[]>();

View File

@@ -9,6 +9,8 @@ import { ITree } from 'vs/base/parts/tree/browser/tree';
import { ContributableActionProvider } from 'vs/workbench/browser/actions'; import { ContributableActionProvider } from 'vs/workbench/browser/actions';
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 { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { fillInActions } from 'vs/platform/actions/browser/menuItemActionItem';
import { import {
DisconnectConnectionAction, AddServerAction, DisconnectConnectionAction, AddServerAction,
@@ -16,9 +18,7 @@ import {
} }
from 'sql/parts/objectExplorer/viewlet/connectionTreeAction'; from 'sql/parts/objectExplorer/viewlet/connectionTreeAction';
import { import {
DisconnectAction, ObjectExplorerActionUtilities, ObjectExplorerActionUtilities, ManageConnectionAction, OEAction
ManageConnectionAction,
OEAction
} from 'sql/parts/objectExplorer/viewlet/objectExplorerActions'; } from 'sql/parts/objectExplorer/viewlet/objectExplorerActions';
import { TreeNode } from 'sql/parts/objectExplorer/common/treeNode'; import { TreeNode } from 'sql/parts/objectExplorer/common/treeNode';
import { NodeType } from 'sql/parts/objectExplorer/common/nodeType'; import { NodeType } from 'sql/parts/objectExplorer/common/nodeType';
@@ -27,8 +27,14 @@ import { ConnectionProfile } from 'sql/parts/connection/common/connectionProfile
import { NewProfilerAction } from 'sql/parts/profiler/contrib/profilerActions'; import { NewProfilerAction } from 'sql/parts/profiler/contrib/profilerActions';
import { TreeUpdateUtils } from 'sql/parts/objectExplorer/viewlet/treeUpdateUtils'; import { TreeUpdateUtils } from 'sql/parts/objectExplorer/viewlet/treeUpdateUtils';
import { IConnectionManagementService } from 'sql/parts/connection/common/connectionManagement'; import { IConnectionManagementService } from 'sql/parts/connection/common/connectionManagement';
import { ExecuteCommandAction } from 'vs/platform/actions/common/actions'; import { MenuId, IMenuService } from 'vs/platform/actions/common/actions';
import { NewQueryAction } from 'sql/workbench/common/actions'; import { NewQueryAction } from 'sql/workbench/common/actions';
import { ConnectionContextKey } from 'sql/parts/connection/common/connectionContextKey';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { TreeNodeContextKey } from './treeNodeContextKey';
import { IQueryManagementService } from 'sql/parts/query/common/queryManagement';
import { IScriptingService } from 'sql/services/scripting/scriptingService';
import * as constants from 'sql/common/constants';
/** /**
* Provides actions for the server tree elements * Provides actions for the server tree elements
@@ -37,7 +43,12 @@ export class ServerTreeActionProvider extends ContributableActionProvider {
constructor( constructor(
@IInstantiationService private _instantiationService: IInstantiationService, @IInstantiationService private _instantiationService: IInstantiationService,
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService @IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
@IQueryManagementService private _queryManagementService: IQueryManagementService,
@IScriptingService private _scriptingService: IScriptingService,
@IContextMenuService private contextMenuService: IContextMenuService,
@IMenuService private menuService: IMenuService,
@IContextKeyService private _contextKeyService: IContextKeyService
) { ) {
super(); super();
} }
@@ -57,8 +68,11 @@ export class ServerTreeActionProvider extends ContributableActionProvider {
return TPromise.as(this.getConnectionProfileGroupActions(tree, element)); return TPromise.as(this.getConnectionProfileGroupActions(tree, element));
} }
if (element instanceof TreeNode) { if (element instanceof TreeNode) {
var treeNode = <TreeNode>element; return TPromise.as(this.getObjectExplorerNodeActions({
return TPromise.as(this.getObjectExplorerNodeActions(tree, treeNode)); tree: tree,
profile: element.getConnectionProfile(),
treeNode: element
}));
} }
return TPromise.as([]); return TPromise.as([]);
@@ -75,23 +89,58 @@ export class ServerTreeActionProvider extends ContributableActionProvider {
/** /**
* Return actions for connection elements * Return actions for connection elements
*/ */
public getConnectionActions(tree: ITree, element: ConnectionProfile): IAction[] { public getConnectionActions(tree: ITree, profile: ConnectionProfile): IAction[] {
let actions: IAction[] = []; return this.getAllActions({
actions.push(this._instantiationService.createInstance(ManageConnectionAction, ManageConnectionAction.ID, ManageConnectionAction.LABEL)); tree: tree,
actions.push(this._instantiationService.createInstance(OEAction, NewQueryAction.ID, NewQueryAction.LABEL)); profile: profile
if (this._connectionManagementService.isProfileConnected(element)) { }, (context) => this.getBuiltinConnectionActions(context));
actions.push(this._instantiationService.createInstance(DisconnectConnectionAction, DisconnectConnectionAction.ID, DisconnectConnectionAction.LABEL)); }
}
actions.push(this._instantiationService.createInstance(DeleteConnectionAction, DeleteConnectionAction.ID, DeleteConnectionAction.DELETE_CONNECTION_LABEL, element));
actions.push(this._instantiationService.createInstance(RefreshAction, RefreshAction.ID, RefreshAction.LABEL, tree, element));
if (process.env['VSCODE_DEV']) { private getAllActions(context: ObjectExplorerContext, getDefaultActions: (ObjectExplorerContext) => IAction[]) {
// Create metadata needed to get a useful set of actions
let scopedContextService = this.getContextKeyService(context);
let menu = this.menuService.createMenu(MenuId.ObjectExplorerItemContext, scopedContextService);
// Fill in all actions
let actions = getDefaultActions(context);
fillInActions(menu, { arg: undefined, shouldForwardArgs: true }, actions, this.contextMenuService);
// Cleanup
scopedContextService.dispose();
menu.dispose();
return actions;
}
private getBuiltinConnectionActions(context: ObjectExplorerContext): IAction[] {
let actions: IAction[] = [];
actions.push(this._instantiationService.createInstance(ManageConnectionAction, ManageConnectionAction.ID, ManageConnectionAction.LABEL, context.tree));
this.addNewQueryAction(context, actions);
if (this._connectionManagementService.isProfileConnected(context.profile)) {
actions.push(this._instantiationService.createInstance(DisconnectConnectionAction, DisconnectConnectionAction.ID, DisconnectConnectionAction.LABEL, context.profile));
}
actions.push(this._instantiationService.createInstance(DeleteConnectionAction, DeleteConnectionAction.ID, DeleteConnectionAction.DELETE_CONNECTION_LABEL, context.profile));
actions.push(this._instantiationService.createInstance(RefreshAction, RefreshAction.ID, RefreshAction.LABEL, context.tree, context.profile));
if (process.env['VSCODE_DEV'] && constants.MssqlProviderId === context.profile.providerName) {
actions.push(this._instantiationService.createInstance(OEAction, NewProfilerAction.ID, NewProfilerAction.LABEL)); actions.push(this._instantiationService.createInstance(OEAction, NewProfilerAction.ID, NewProfilerAction.LABEL));
} }
return actions; return actions;
} }
private getContextKeyService(context: ObjectExplorerContext): IContextKeyService {
let scopedContextService = this._contextKeyService.createScoped();
let connectionContextKey = new ConnectionContextKey(scopedContextService);
connectionContextKey.set(context.profile);
let treeNodeContextKey = new TreeNodeContextKey(scopedContextService);
if (context.treeNode) {
treeNodeContextKey.set(context.treeNode);
}
return scopedContextService;
}
/** /**
* Return actions for connection group elements * Return actions for connection group elements
*/ */
@@ -106,32 +155,50 @@ export class ServerTreeActionProvider extends ContributableActionProvider {
/** /**
* Return actions for OE elements * Return actions for OE elements
*/ */
public getObjectExplorerNodeActions(tree: ITree, treeNode: TreeNode): IAction[] { private getObjectExplorerNodeActions(context: ObjectExplorerContext): IAction[] {
let actions = []; return this.getAllActions(context, (context) => this.getBuiltInNodeActions(context));
}
private getBuiltInNodeActions(context: ObjectExplorerContext): IAction[] {
let actions: IAction[] = [];
let treeNode = context.treeNode;
if (TreeUpdateUtils.isDatabaseNode(treeNode)) { if (TreeUpdateUtils.isDatabaseNode(treeNode)) {
if (TreeUpdateUtils.isAvailableDatabaseNode(treeNode)) { if (TreeUpdateUtils.isAvailableDatabaseNode(treeNode)) {
actions.push(this._instantiationService.createInstance(ManageConnectionAction, ManageConnectionAction.ID, ManageConnectionAction.LABEL)); actions.push(this._instantiationService.createInstance(ManageConnectionAction, ManageConnectionAction.ID, ManageConnectionAction.LABEL, context.tree));
} else { } else {
return actions; return actions;
} }
} }
actions.push(this._instantiationService.createInstance(OEAction, NewQueryAction.ID, NewQueryAction.LABEL));
let scriptMap: Map<NodeType, any[]> = ObjectExplorerActionUtilities.getScriptMap(treeNode);
let supportedActions = scriptMap.get(treeNode.nodeTypeId);
let self = this;
if (supportedActions !== null && supportedActions !== undefined) { this.addNewQueryAction(context, actions);
supportedActions.forEach(action => { this.addScriptingActions(context, actions);
actions.push(self._instantiationService.createInstance(action, action.ID, action.LABEL)); actions.push(this._instantiationService.createInstance(RefreshAction, RefreshAction.ID, RefreshAction.LABEL, context.tree, treeNode));
});
}
actions.push(this._instantiationService.createInstance(RefreshAction, RefreshAction.ID, RefreshAction.LABEL, tree, treeNode));
if (treeNode.isTopLevel()) {
actions.push(this._instantiationService.createInstance(DisconnectAction, DisconnectAction.ID, DisconnectAction.LABEL));
}
return actions; return actions;
} }
private addNewQueryAction(context: ObjectExplorerContext, actions: IAction[]): void {
if (this._queryManagementService.isProviderRegistered(context.profile.providerName)) {
actions.push(this._instantiationService.createInstance(OEAction, NewQueryAction.ID, NewQueryAction.LABEL));
}
}
private addScriptingActions(context: ObjectExplorerContext, actions: IAction[]): void {
if (this._scriptingService.isProviderRegistered(context.profile.providerName)) {
let scriptMap: Map<NodeType, any[]> = ObjectExplorerActionUtilities.getScriptMap(context.treeNode);
let supportedActions = scriptMap.get(context.treeNode.nodeTypeId);
let self = this;
if (supportedActions !== null && supportedActions !== undefined) {
supportedActions.forEach(action => {
actions.push(self._instantiationService.createInstance(action, action.ID, action.LABEL));
});
}
}
}
}
interface ObjectExplorerContext {
tree: ITree;
profile: ConnectionProfile;
treeNode?: TreeNode;
} }

View File

@@ -18,6 +18,7 @@ import { ServerTreeActionProvider } from 'sql/parts/objectExplorer/viewlet/serve
import { ObjectExplorerActionsContext } from 'sql/parts/objectExplorer/viewlet/objectExplorerActions'; import { ObjectExplorerActionsContext } from 'sql/parts/objectExplorer/viewlet/objectExplorerActions';
import { TreeNode } from 'sql/parts/objectExplorer/common/treeNode'; import { TreeNode } from 'sql/parts/objectExplorer/common/treeNode';
import { OpenMode } from 'vs/base/parts/tree/browser/treeDefaults'; import { OpenMode } from 'vs/base/parts/tree/browser/treeDefaults';
import { TreeUpdateUtils } from 'sql/parts/objectExplorer/viewlet/treeUpdateUtils';
/** /**
* Extends the tree controller to handle clicks on the tree elements * Extends the tree controller to handle clicks on the tree elements
@@ -73,26 +74,24 @@ export class ServerTreeController extends treedefaults.DefaultController {
event.stopPropagation(); event.stopPropagation();
tree.setFocus(element); tree.setFocus(element);
let parent: ConnectionProfileGroup = undefined;
if (element instanceof ConnectionProfileGroup) {
parent = <ConnectionProfileGroup>element;
}
else if (element instanceof ConnectionProfile) {
parent = (<ConnectionProfile>element).parent;
}
var actionContext: any; var actionContext: any;
if (element instanceof TreeNode) { if (element instanceof TreeNode) {
actionContext = new ObjectExplorerActionsContext(); let context = new ObjectExplorerActionsContext();
actionContext.container = event.target; context.nodeInfo = element.toNodeInfo();
actionContext.treeNode = <TreeNode>element; // Note: getting DB name before, but intentionally not using treeUpdateUtils.getConnectionProfile as it replaces
actionContext.tree = tree; // the connection ID with a new one. This breaks a number of internal tasks
context.connectionProfile = element.getConnectionProfile().toIConnectionProfile();
context.connectionProfile.databaseName = element.getDatabaseName();
actionContext = context;
} else if (element instanceof ConnectionProfile) { } else if (element instanceof ConnectionProfile) {
actionContext = new ObjectExplorerActionsContext(); let context = new ObjectExplorerActionsContext();
actionContext.container = event.target; context.connectionProfile = element.toIConnectionProfile();
actionContext.connectionProfile = <ConnectionProfile>element; context.isConnectionNode = true;
actionContext.tree = tree; actionContext = context;
} else { } else {
// TODO: because the connection group is used as a context object and isn't serializable,
// the Group-level context menu is not currently extensible
actionContext = element; actionContext = element;
} }

View File

@@ -31,12 +31,10 @@ export class ServerTreeDataSource implements IDataSource {
* No more than one element may use a given identifier. * No more than one element may use a given identifier.
*/ */
public getId(tree: ITree, element: any): string { public getId(tree: ITree, element: any): string {
if (element instanceof ConnectionProfile) { if (element instanceof ConnectionProfile
return (<ConnectionProfile>element).id; || element instanceof ConnectionProfileGroup
} else if (element instanceof ConnectionProfileGroup) { || element instanceof TreeNode) {
return (<ConnectionProfileGroup>element).id; return element.id;
} else if (element instanceof TreeNode) {
return (<TreeNode>element).id;
} else { } else {
return undefined; return undefined;
} }
@@ -49,9 +47,9 @@ export class ServerTreeDataSource implements IDataSource {
if (element instanceof ConnectionProfile) { if (element instanceof ConnectionProfile) {
return true; return true;
} else if (element instanceof ConnectionProfileGroup) { } else if (element instanceof ConnectionProfileGroup) {
return (<ConnectionProfileGroup>element).hasChildren(); return element.hasChildren();
} else if (element instanceof TreeNode) { } else if (element instanceof TreeNode) {
return !(<TreeNode>element).isAlwaysLeaf; return !element.isAlwaysLeaf;
} }
return false; return false;
} }
@@ -70,7 +68,7 @@ export class ServerTreeDataSource implements IDataSource {
} else if (element instanceof ConnectionProfileGroup) { } else if (element instanceof ConnectionProfileGroup) {
resolve((<ConnectionProfileGroup>element).getChildren()); resolve((<ConnectionProfileGroup>element).getChildren());
} else if (element instanceof TreeNode) { } else if (element instanceof TreeNode) {
var node = <TreeNode>element; var node = element;
if (node.children) { if (node.children) {
resolve(node.children); resolve(node.children);
} else { } else {
@@ -92,11 +90,11 @@ export class ServerTreeDataSource implements IDataSource {
*/ */
public getParent(tree: ITree, element: any): TPromise<any> { public getParent(tree: ITree, element: any): TPromise<any> {
if (element instanceof ConnectionProfile) { if (element instanceof ConnectionProfile) {
return TPromise.as((<ConnectionProfile>element).getParent()); return TPromise.as(element.getParent());
} else if (element instanceof ConnectionProfileGroup) { } else if (element instanceof ConnectionProfileGroup) {
return TPromise.as((<ConnectionProfileGroup>element).getParent()); return TPromise.as(element.getParent());
} else if (element instanceof TreeNode) { } else if (element instanceof TreeNode) {
return TPromise.as(TreeUpdateUtils.getObjectExplorerParent(<TreeNode>element, this._connectionManagementService)); return TPromise.as(TreeUpdateUtils.getObjectExplorerParent(element, this._connectionManagementService));
} else { } else {
return TPromise.as(null); return TPromise.as(null);
} }

View File

@@ -105,38 +105,45 @@ export class ServerTreeRenderer implements IRenderer {
*/ */
public renderElement(tree: ITree, element: any, templateId: string, templateData: any): void { public renderElement(tree: ITree, element: any, templateId: string, templateData: any): void {
if (templateId === ServerTreeRenderer.CONNECTION_TEMPLATE_ID) { if (templateId === ServerTreeRenderer.CONNECTION_TEMPLATE_ID) {
this.renderConnection(tree, element, templateData); this.renderConnection(element, templateData);
} else if (templateId === ServerTreeRenderer.CONNECTION_GROUP_TEMPLATE_ID) { } else if (templateId === ServerTreeRenderer.CONNECTION_GROUP_TEMPLATE_ID) {
this.renderConnectionProfileGroup(tree, element, templateData); this.renderConnectionProfileGroup(element, templateData);
} else { } else {
this.renderObjectExplorer(tree, element, templateData); this.renderObjectExplorer(element, templateData);
} }
} }
private renderObjectExplorer(tree: ITree, treeNode: TreeNode, templateData: IObjectExplorerTemplateData): void { private renderObjectExplorer(treeNode: TreeNode, templateData: IObjectExplorerTemplateData): void {
var iconName = treeNode.nodeTypeId; // Use an explicitly defined iconType first. If not defined, fall back to using nodeType and
if (treeNode.nodeStatus) { // other compount indicators instead.
iconName = treeNode.nodeTypeId + '_' + treeNode.nodeStatus; let iconName: string = undefined;
} if (treeNode.iconType) {
if (treeNode.nodeSubType) { iconName = (typeof treeNode.iconType === 'string') ? treeNode.iconType : treeNode.iconType.id;
iconName = treeNode.nodeTypeId + '_' + treeNode.nodeSubType; } else {
iconName = treeNode.nodeTypeId;
if (treeNode.nodeStatus) {
iconName = treeNode.nodeTypeId + '_' + treeNode.nodeStatus;
}
if (treeNode.nodeSubType) {
iconName = treeNode.nodeTypeId + '_' + treeNode.nodeSubType;
}
} }
let tokens: string[] = []; let tokens: string[] = [];
for (var index = 1; index < templateData.icon.classList.length; index++) { for (let index = 1; index < templateData.icon.classList.length; index++) {
tokens.push(templateData.icon.classList.item(index)); tokens.push(templateData.icon.classList.item(index));
} }
templateData.icon.classList.remove(...tokens); templateData.icon.classList.remove(...tokens);
templateData.icon.classList.add('icon'); templateData.icon.classList.add('icon');
let iconLoweCaseName = iconName.toLocaleLowerCase(); let iconLowerCaseName = iconName.toLocaleLowerCase();
templateData.icon.classList.add(iconLoweCaseName); templateData.icon.classList.add(iconLowerCaseName);
templateData.label.textContent = treeNode.label; templateData.label.textContent = treeNode.label;
templateData.root.title = treeNode.label; templateData.root.title = treeNode.label;
} }
private renderConnection(tree: ITree, connection: ConnectionProfile, templateData: IConnectionTemplateData): void { private renderConnection(connection: ConnectionProfile, templateData: IConnectionTemplateData): void {
if (!this._isCompact) { if (!this._isCompact) {
if (this._connectionManagementService.isConnected(undefined, connection)) { if (this._connectionManagementService.isConnected(undefined, connection)) {
templateData.icon.classList.remove('disconnected'); templateData.icon.classList.remove('disconnected');
@@ -157,9 +164,9 @@ export class ServerTreeRenderer implements IRenderer {
templateData.connectionProfile = connection; templateData.connectionProfile = connection;
} }
private renderConnectionProfileGroup(tree: ITree, connectionProfileGroup: ConnectionProfileGroup, templateData: IConnectionProfileGroupTemplateData): void { private renderConnectionProfileGroup(connectionProfileGroup: ConnectionProfileGroup, templateData: IConnectionProfileGroupTemplateData): void {
var rowElement = this.findParentElement(templateData.root, 'monaco-tree-row'); let rowElement = this.findParentElement(templateData.root, 'monaco-tree-row');
if (rowElement) { if (rowElement) {
if (connectionProfileGroup.color) { if (connectionProfileGroup.color) {
rowElement.style.background = connectionProfileGroup.color; rowElement.style.background = connectionProfileGroup.color;
@@ -179,7 +186,7 @@ export class ServerTreeRenderer implements IRenderer {
* Returns the first parent which contains the className * Returns the first parent which contains the className
*/ */
private findParentElement(container: HTMLElement, className: string): HTMLElement { private findParentElement(container: HTMLElement, className: string): HTMLElement {
var currentElement = container; let currentElement = container;
while (currentElement) { while (currentElement) {
if (currentElement.className.includes(className)) { if (currentElement.className.includes(className)) {
break; break;

View File

@@ -0,0 +1,50 @@
/*---------------------------------------------------------------------------------------------
* 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 { RawContextKey, IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { IConnectionProfile } from 'sqlops';
import { TreeNode } from 'sql/parts/objectExplorer/common/treeNode';
export class TreeNodeContextKey implements IContextKey<TreeNode> {
static NodeType = new RawContextKey<string>('nodeType', undefined);
static SubType = new RawContextKey<string>('nodeSubType', undefined);
static Status = new RawContextKey<string>('nodeStatus', undefined);
static TreeNode = new RawContextKey<TreeNode>('treeNode', undefined);
private _nodeTypeKey: IContextKey<string>;
private _subTypeKey: IContextKey<string>;
private _statusKey: IContextKey<string>;
private _treeNodeKey: IContextKey<TreeNode>;
constructor(
@IContextKeyService contextKeyService: IContextKeyService
) {
this._nodeTypeKey = TreeNodeContextKey.NodeType.bindTo(contextKeyService);
this._subTypeKey = TreeNodeContextKey.SubType.bindTo(contextKeyService);
this._statusKey = TreeNodeContextKey.Status.bindTo(contextKeyService);
this._treeNodeKey = TreeNodeContextKey.TreeNode.bindTo(contextKeyService);
}
set(value: TreeNode) {
this._treeNodeKey.set(value);
this._nodeTypeKey.set(value && value.nodeTypeId);
this._subTypeKey.set(value && value.nodeSubType);
this._statusKey.set(value && value.nodeStatus);
}
reset(): void {
this._nodeTypeKey.reset();
this._subTypeKey.reset();
this._statusKey.reset();
this._treeNodeKey.reset();
}
public get(): TreeNode {
return this._treeNodeKey.get();
}
}

View File

@@ -104,7 +104,7 @@ export class TreeSelectionHandler {
}); });
} }
} else if (isDoubleClick && selection && selection.length > 0 && (selection[0] instanceof TreeNode)) { } else if (isDoubleClick && selection && selection.length > 0 && (selection[0] instanceof TreeNode)) {
let treeNode = <TreeNode>selection[0]; let treeNode = selection[0];
if (TreeUpdateUtils.isAvailableDatabaseNode(treeNode)) { if (TreeUpdateUtils.isAvailableDatabaseNode(treeNode)) {
connectionProfile = TreeUpdateUtils.getConnectionProfile(treeNode); connectionProfile = TreeUpdateUtils.getConnectionProfile(treeNode);
if (connectionProfile) { if (connectionProfile) {

View File

@@ -13,6 +13,7 @@ import { NodeType } from 'sql/parts/objectExplorer/common/nodeType';
import { TPromise } from 'vs/base/common/winjs.base'; import { TPromise } from 'vs/base/common/winjs.base';
import { TreeNode } from 'sql/parts/objectExplorer/common/treeNode'; import { TreeNode } from 'sql/parts/objectExplorer/common/treeNode';
import errors = require('vs/base/common/errors'); import errors = require('vs/base/common/errors');
import { IConnectionProfile } from 'sql/parts/connection/common/interfaces';
export class TreeUpdateUtils { export class TreeUpdateUtils {
@@ -113,7 +114,7 @@ export class TreeUpdateUtils {
} }
public static connectIfNotConnected( public static connectIfNotConnected(
connection: ConnectionProfile, connection: IConnectionProfile,
options: IConnectionCompletionOptions, options: IConnectionCompletionOptions,
connectionManagementService: IConnectionManagementService, connectionManagementService: IConnectionManagementService,
tree: ITree): TPromise<ConnectionProfile> { tree: ITree): TPromise<ConnectionProfile> {
@@ -172,7 +173,7 @@ export class TreeUpdateUtils {
* @param connectionManagementService Connection management service instance * @param connectionManagementService Connection management service instance
* @param objectExplorerService Object explorer service instance * @param objectExplorerService Object explorer service instance
*/ */
public static connectAndCreateOeSession(connection: ConnectionProfile, options: IConnectionCompletionOptions, public static connectAndCreateOeSession(connection: IConnectionProfile, options: IConnectionCompletionOptions,
connectionManagementService: IConnectionManagementService, objectExplorerService: IObjectExplorerService, tree: ITree): TPromise<boolean> { connectionManagementService: IConnectionManagementService, objectExplorerService: IObjectExplorerService, tree: ITree): TPromise<boolean> {
return new TPromise<boolean>((resolve, reject) => { return new TPromise<boolean>((resolve, reject) => {
TreeUpdateUtils.connectIfNotConnected(connection, options, connectionManagementService, tree).then(connectedConnection => { TreeUpdateUtils.connectIfNotConnected(connection, options, connectionManagementService, tree).then(connectedConnection => {

View File

@@ -13,7 +13,7 @@ import * as nls from 'vs/nls';
import { ProfilerInput } from 'sql/parts/profiler/editor/profilerInput'; import { ProfilerInput } from 'sql/parts/profiler/editor/profilerInput';
import { ProfilerEditor } from 'sql/parts/profiler/editor/profilerEditor'; import { ProfilerEditor } from 'sql/parts/profiler/editor/profilerEditor';
import { PROFILER_SESSION_TEMPLATE_SETTINGS, IProfilerSessionTemplate } from 'sql/parts/profiler/service/interfaces'; import { PROFILER_VIEW_TEMPLATE_SETTINGS, IProfilerViewTemplate } from 'sql/parts/profiler/service/interfaces';
const profilerDescriptor = new EditorDescriptor( const profilerDescriptor = new EditorDescriptor(
ProfilerEditor, ProfilerEditor,
@@ -24,84 +24,213 @@ const profilerDescriptor = new EditorDescriptor(
Registry.as<IEditorRegistry>(EditorExtensions.Editors) Registry.as<IEditorRegistry>(EditorExtensions.Editors)
.registerEditor(profilerDescriptor, [new SyncDescriptor(ProfilerInput)]); .registerEditor(profilerDescriptor, [new SyncDescriptor(ProfilerInput)]);
const profilerSessionTemplateSchema: IJSONSchema = { const profilerViewTemplateSchema: IJSONSchema = {
description: nls.localize('profiler.settings.sessionTemplates', "Specifies session templates"), description: nls.localize('profiler.settings.viewTemplates', "Specifies view templates"),
type: 'array', type: 'array',
items: <IJSONSchema>{ items: <IJSONSchema>{
type: 'object', type: 'object',
properties: { properties: {
name: { name: {
type: 'string' type: 'string'
}
}
},
default: <Array<IProfilerSessionTemplate>>[
{
name: 'Standard',
events: [
{
name: 'Audit Login',
optionalColumns: ['TextData', 'ApplicationName', 'NTUserName', 'LoginName', 'ClientProcessID', 'SPID', 'StartTime', 'BinaryData']
},
{
name: 'Audit Logout',
optionalColumns: ['ApplicationName', 'NTUserName', 'LoginName', 'CPU', 'Reads', 'Writes', 'Duration', 'ClientProcessID', 'SPID', 'StartTime', 'EndTime']
},
{
name: 'ExistingConnection',
optionalColumns: ['TextData', 'ApplicationName', 'NTUserName', 'LoginName', 'Duration', 'ClientProcessID', 'SPID', 'StartTime', 'EndTime', 'BinaryData']
},
{
name: 'RPC:Completed',
optionalColumns: ['TextData', 'ApplicationName', 'NTUserName', 'LoginName', 'CPU', 'Reads', 'Writes', 'Duration', 'ClientProcessID', 'SPID', 'StartTime', 'EndTime', 'BinaryData']
},
{
name: 'SQL:BatchCompleted',
optionalColumns: ['TextData', 'ApplicationName', 'NTUserName', 'LoginName', 'CPU', 'Reads', 'Writes', 'Duration', 'ClientProcessID', 'SPID', 'StartTime', 'EndTime', 'BinaryData']
},
{
name: 'SQL:BatchStarting',
optionalColumns: ['TextData', 'ApplicationName', 'NTUserName', 'LoginName', 'ClientProcessID', 'SPID', 'StartTime']
} }
], }
view: { },
events: [ default: <Array<IProfilerViewTemplate>>[
{
name: 'Standard View',
columns: [
{ {
name: 'Audit Login', name: 'EventClass',
columns: ['TextData', 'ApplicationName', 'NTUserName', 'LoginName', 'ClientProcessID', 'SPID', 'StartTime'] eventsMapped: ['name']
}, },
{ {
name: 'Audit Logout', name: 'TextData',
columns: ['ApplicationName', 'NTUserName', 'LoginName', 'CPU', 'Reads', 'Writes', 'Duration', 'ClientProcessID', 'SPID', 'StartTime', 'EndTime'] eventsMapped: ['options_text', 'batch_text']
}, },
{ {
name: 'ExistingConnection', name: 'ApplicationName',
columns: ['TextData', 'ApplicationName', 'NTUserName', 'LoginName', 'ClientProcessID', 'SPID', 'StartTime'] width: '1',
eventsMapped: ['client_app_name']
}, },
{ {
name: 'RPC:Completed', name: 'NTUserName',
columns: ['ApplicationName', 'NTUserName', 'LoginName', 'CPU', 'Reads', 'Writes', 'Duration', 'ClientProcessID', 'SPID', 'StartTime', 'EndTime', 'BinaryData'] eventsMapped: ['nt_username']
}, },
{ {
name: 'SQL:BatchCompleted', name: 'LoginName',
columns: ['TextData', 'ApplicationName', 'NTUserName', 'LoginName', 'CPU', 'Reads', 'Writes', 'Duration', 'ClientProcessID', 'SPID', 'StartTime', 'EndTime', 'BinaryData'] eventsMapped: ['server_principal_name']
}, },
{ {
name: 'SQL:BatchStarting', name: 'ClientProcessID',
columns: ['TextData', 'ApplicationName', 'NTUserName', 'LoginName', 'ClientProcessID', 'SPID', 'StartTime'] eventsMapped: ['client_pid']
},
{
name: 'SPID',
eventsMapped: ['session_id']
},
{
name: 'StartTime',
eventsMapped: ['timestamp']
},
{
name: 'CPU',
eventsMapped: ['cpu_time']
},
{
name: 'Reads',
eventsMapped: ['logical_reads']
},
{
name: 'Writes',
eventsMapped: ['writes']
},
{
name: 'Duration',
eventsMapped: ['duration']
}
]
},
{
name: 'TSQL View',
columns: [
{
name: 'EventClass',
eventsMapped: ['name']
},
{
name: 'TextData',
eventsMapped: ['options_text', 'batch_text']
},
{
name: 'SPID',
eventsMapped: ['session_id']
},
{
name: 'StartTime',
eventsMapped: ['timestamp']
}
]
},
{
name: 'Tuning View',
columns: [
{
name: 'EventClass',
eventsMapped: ['name']
},
{
name: 'TextData',
eventsMapped: ['options_text', 'batch_text']
},
{
name: 'Duration',
eventsMapped: ['duration']
},
{
name: 'SPID',
eventsMapped: ['session_id']
},
{
name: 'DatabaseID',
eventsMapped: ['database_id']
},
{
name: 'DatabaseName',
eventsMapped: ['database_name']
},
{
name: 'ObjectType',
eventsMapped: ['object_type']
},
{
name: 'LoginName',
eventsMapped: ['server_principal_name']
}
]
},
{
name: 'TSQL_Locks View',
columns: [
{
name: 'EventClass',
eventsMapped: ['name']
},
{
name: 'TextData',
eventsMapped: ['options_text', 'batch_text']
},
{
name: 'ApplicationName',
eventsMapped: ['client_app_name']
},
{
name: 'NTUserName',
eventsMapped: ['nt_username']
},
{
name: 'LoginName',
eventsMapped: ['server_principal_name']
},
{
name: 'ClientProcessID',
eventsMapped: ['client_pid']
},
{
name: 'SPID',
eventsMapped: ['session_id']
},
{
name: 'StartTime',
eventsMapped: ['timestamp']
},
{
name: 'CPU',
eventsMapped: ['cpu_time']
},
{
name: 'Reads',
eventsMapped: ['logical_reads']
},
{
name: 'Writes',
eventsMapped: ['writes']
},
{
name: 'Duration',
eventsMapped: ['duration']
}
]
},
{
name: 'TSQL_Duration View',
columns: [
{
name: 'EventClass',
eventsMapped: ['name']
},
{
name: 'Duration',
eventsMapped: ['duration']
},
{
name: 'TextData',
eventsMapped: ['options_text', 'batch_text']
},
{
name: 'SPID',
eventsMapped: ['session_id']
} }
] ]
} }
} ]
] };
};
const configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration); const configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);
const dashboardConfig: IConfigurationNode = { const dashboardConfig: IConfigurationNode = {
id: 'Profiler', id: 'Profiler',
type: 'object', type: 'object',
properties: { properties: {
[PROFILER_SESSION_TEMPLATE_SETTINGS]: profilerSessionTemplateSchema [PROFILER_VIEW_TEMPLATE_SETTINGS]: profilerViewTemplateSchema
} }
}; };

View File

@@ -55,13 +55,17 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'profiler.newProfiler', id: 'profiler.newProfiler',
weight: KeybindingsRegistry.WEIGHT.builtinExtension(), weight: KeybindingsRegistry.WEIGHT.builtinExtension(),
when: undefined, when: undefined,
primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_P, primary: KeyMod.Alt | KeyCode.KEY_P,
mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_P }, mac: { primary: KeyMod.WinCtrl | KeyMod.Alt | KeyCode.KEY_P },
handler: CommandsRegistry.getCommand('profiler.newProfiler').handler handler: CommandsRegistry.getCommand('profiler.newProfiler').handler
}); });
CommandsRegistry.registerCommand({ KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'profiler.start', id: 'profiler.toggleStartStop',
weight: KeybindingsRegistry.WEIGHT.editorContrib(),
when: undefined,
primary: KeyMod.Alt | KeyCode.KEY_S,
mac: { primary: KeyMod.WinCtrl | KeyMod.Alt | KeyCode.KEY_S },
handler: (accessor: ServicesAccessor) => { handler: (accessor: ServicesAccessor) => {
let profilerService: IProfilerService = accessor.get(IProfilerService); let profilerService: IProfilerService = accessor.get(IProfilerService);
let editorService: IWorkbenchEditorService = accessor.get(IWorkbenchEditorService); let editorService: IWorkbenchEditorService = accessor.get(IWorkbenchEditorService);
@@ -69,23 +73,14 @@ CommandsRegistry.registerCommand({
let activeEditor = editorService.getActiveEditor(); let activeEditor = editorService.getActiveEditor();
if (activeEditor instanceof ProfilerEditor) { if (activeEditor instanceof ProfilerEditor) {
let profilerInput = activeEditor.input; let profilerInput = activeEditor.input;
return profilerService.startSession(profilerInput.id); if (profilerInput.state.isRunning){
return profilerService.stopSession(profilerInput.id);
} else {
// clear data when profiler is started
profilerInput.data.clear();
return profilerService.startSession(profilerInput.id);
}
} }
return TPromise.as(false); return TPromise.as(false);
} }
}); });
CommandsRegistry.registerCommand({
id: 'profiler.stop',
handler: (accessor: ServicesAccessor) => {
let profilerService: IProfilerService = accessor.get(IProfilerService);
let editorService: IWorkbenchEditorService = accessor.get(IWorkbenchEditorService);
let activeEditor = editorService.getActiveEditor();
if (activeEditor instanceof ProfilerEditor) {
let profilerInput = activeEditor.input;
return profilerService.stopSession(profilerInput.id);
}
return TPromise.as(false);
}
});

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

@@ -360,7 +360,10 @@ export class ProfilerColumnEditorDialog extends Modal {
super.onAccept(e); super.onAccept(e);
} }
// currently not used, this dialog is a work in progress
// tracked in issue #1545 https://github.com/Microsoft/sqlopsstudio/issues/1545
private _updateInput(): void { private _updateInput(): void {
/*
this._element.getUnsortedChildren().forEach(e => { this._element.getUnsortedChildren().forEach(e => {
let origEvent = this._input.sessionTemplate.view.events.find(i => i.name === e.id); let origEvent = this._input.sessionTemplate.view.events.find(i => i.name === e.id);
if (e.indeterminate) { if (e.indeterminate) {
@@ -387,9 +390,13 @@ export class ProfilerColumnEditorDialog extends Modal {
}, []); }, []);
newColumns.unshift('EventClass'); newColumns.unshift('EventClass');
this._input.setColumns(newColumns); this._input.setColumns(newColumns);
*/
} }
// currently not used, this dialog is a work in progress
// tracked in issue #1545 https://github.com/Microsoft/sqlopsstudio/issues/1545
private _updateList(): void { private _updateList(): void {
/*
this._element = new SessionItem(this._input.sessionTemplate.name, this._selectedValue === 0 ? 'event' : 'column'); this._element = new SessionItem(this._input.sessionTemplate.name, this._selectedValue === 0 ? 'event' : 'column');
this._input.sessionTemplate.events.forEach(item => { this._input.sessionTemplate.events.forEach(item => {
let event = new EventItem(item.name, this._element); let event = new EventItem(item.name, this._element);
@@ -402,6 +409,7 @@ export class ProfilerColumnEditorDialog extends Modal {
}); });
this._tree.setInput(this._element); this._tree.setInput(this._element);
this._tree.layout(DOM.getTotalHeight(this._treeContainer)); this._tree.layout(DOM.getTotalHeight(this._treeContainer));
*/
} }
protected layout(height?: number): void { protected layout(height?: number): void {

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

@@ -8,7 +8,7 @@ import { ProfilerInput } from './profilerInput';
import { TabbedPanel } from 'sql/base/browser/ui/panel/panel'; import { TabbedPanel } from 'sql/base/browser/ui/panel/panel';
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 { IProfilerService, IProfilerSessionTemplate } from 'sql/parts/profiler/service/interfaces'; import { IProfilerService, IProfilerViewTemplate } from 'sql/parts/profiler/service/interfaces';
import { Taskbar } from 'sql/base/browser/ui/taskbar/taskbar'; import { Taskbar } from 'sql/base/browser/ui/taskbar/taskbar';
import { attachTableStyler } from 'sql/common/theme/styler'; import { attachTableStyler } from 'sql/common/theme/styler';
import { IProfilerStateChangedEvent } from './profilerState'; import { IProfilerStateChangedEvent } from './profilerState';
@@ -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 {
@@ -114,8 +116,9 @@ export class ProfilerEditor extends BaseEditor {
private _profilerEditorContextKey: IContextKey<boolean>; private _profilerEditorContextKey: IContextKey<boolean>;
private _sessionTemplateSelector: SelectBox; private _viewTemplateSelector: SelectBox;
private _sessionTemplates: Array<IProfilerSessionTemplate>; private _viewTemplates: Array<IProfilerViewTemplate>;
private _connectionInfoText: HTMLElement;
// Actions // Actions
private _connectAction: Actions.ProfilerConnect; private _connectAction: Actions.ProfilerConnect;
@@ -125,6 +128,7 @@ export class ProfilerEditor extends BaseEditor {
private _autoscrollAction: Actions.ProfilerAutoScroll; private _autoscrollAction: Actions.ProfilerAutoScroll;
private _collapsedPanelAction: Actions.ProfilerCollapsablePanelAction; private _collapsedPanelAction: Actions.ProfilerCollapsablePanelAction;
constructor( constructor(
@ITelemetryService telemetryService: ITelemetryService, @ITelemetryService telemetryService: ITelemetryService,
@IWorkbenchThemeService themeService: IWorkbenchThemeService, @IWorkbenchThemeService themeService: IWorkbenchThemeService,
@@ -189,27 +193,37 @@ export class ProfilerEditor extends BaseEditor {
this._connectAction = this._instantiationService.createInstance(Actions.ProfilerConnect, Actions.ProfilerConnect.ID, Actions.ProfilerConnect.LABEL); this._connectAction = this._instantiationService.createInstance(Actions.ProfilerConnect, Actions.ProfilerConnect.ID, Actions.ProfilerConnect.LABEL);
this._autoscrollAction = this._instantiationService.createInstance(Actions.ProfilerAutoScroll, Actions.ProfilerAutoScroll.ID, Actions.ProfilerAutoScroll.LABEL); this._autoscrollAction = this._instantiationService.createInstance(Actions.ProfilerAutoScroll, Actions.ProfilerAutoScroll.ID, Actions.ProfilerAutoScroll.LABEL);
this._sessionTemplates = this._profilerService.getSessionTemplates(); this._viewTemplates = this._profilerService.getViewTemplates();
this._sessionTemplateSelector = new SelectBox(this._sessionTemplates.map(i => i.name), 'Standard', this._contextViewService); this._viewTemplateSelector = new SelectBox(this._viewTemplates.map(i => i.name), 'Standard View', this._contextViewService);
this._register(this._sessionTemplateSelector.onDidSelect(e => { this._register(this._viewTemplateSelector.onDidSelect(e => {
if (this.input) { if (this.input) {
this.input.sessionTemplate = this._sessionTemplates.find(i => i.name === e.selected); this.input.viewTemplate = this._viewTemplates.find(i => i.name === e.selected);
} }
})); }));
let dropdownContainer = document.createElement('div'); let dropdownContainer = document.createElement('div');
dropdownContainer.style.width = '150px'; dropdownContainer.style.width = '150px';
this._sessionTemplateSelector.render(dropdownContainer); dropdownContainer.style.paddingRight = '5px';
this._viewTemplateSelector.render(dropdownContainer);
this._register(attachSelectBoxStyler(this._sessionTemplateSelector, this.themeService)); 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._actionBar.setContent([ this._actionBar.setContent([
{ action: this._startAction }, { action: this._startAction },
{ action: this._stopAction }, { action: this._stopAction },
{ element: dropdownContainer },
{ element: Taskbar.createTaskbarSeparator() }, { element: Taskbar.createTaskbarSeparator() },
{ action: this._pauseAction }, { action: this._pauseAction },
{ 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: Taskbar.createTaskbarSeparator() },
{ element: this._connectionInfoText }
]); ]);
} }
@@ -340,10 +354,10 @@ export class ProfilerEditor extends BaseEditor {
return super.setInput(input, options).then(() => { return super.setInput(input, options).then(() => {
this._profilerTableEditor.setInput(input); this._profilerTableEditor.setInput(input);
if (input.sessionTemplate) { if (input.viewTemplate) {
this._sessionTemplateSelector.selectWithOptionName(input.sessionTemplate.name); this._viewTemplateSelector.selectWithOptionName(input.viewTemplate.name);
} else { } else {
input.sessionTemplate = this._sessionTemplates.find(i => i.name === 'Standard'); input.viewTemplate = this._viewTemplates.find(i => i.name === 'Standard View');
} }
this._actionBar.context = input; this._actionBar.context = input;
@@ -360,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();
@@ -401,10 +416,7 @@ export class ProfilerEditor extends BaseEditor {
if (e.isConnected) { if (e.isConnected) {
this._connectAction.connected = this.input.state.isConnected; this._connectAction.connected = this.input.state.isConnected;
if (this.input.state.isConnected) { if (!this.input.state.isConnected) {
this._sessionTemplateSelector.disable();
} else {
this._sessionTemplateSelector.enable();
this._startAction.enabled = this.input.state.isConnected; this._startAction.enabled = this.input.state.isConnected;
this._stopAction.enabled = false; this._stopAction.enabled = false;
this._pauseAction.enabled = false; this._pauseAction.enabled = false;

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