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
## 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
* Release date: June 20, 2018
* 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
-- | --
Windows Setup Installer | https://go.microsoft.com/fwlink/?linkid=875602
Windows ZIP | https://go.microsoft.com/fwlink/?linkid=875603
macOS ZIP | https://go.microsoft.com/fwlink/?linkid=875604
Linux TAR.GZ | https://go.microsoft.com/fwlink/?linkid=875605
Linux RPM | https://go.microsoft.com/fwlink/?linkid=875606
Linux DEB | https://go.microsoft.com/fwlink/?linkid=875607
Windows Setup Installer | https://go.microsoft.com/fwlink/?linkid=2005949
Windows ZIP | https://go.microsoft.com/fwlink/?linkid=2005950
macOS ZIP | https://go.microsoft.com/fwlink/?linkid=2005959
Linux TAR.GZ | https://go.microsoft.com/fwlink/?linkid=2005960
Linux RPM | https://go.microsoft.com/fwlink/?linkid=2006083
Linux DEB | https://go.microsoft.com/fwlink/?linkid=2006084
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, 謝政廷
* 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
* 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)

View File

@@ -2,7 +2,7 @@
"name": "agent",
"displayName": "SQL Server Agent",
"description": "Manage and troubleshoot SQL Server Agent jobs",
"version": "0.31.1",
"version": "0.31.4",
"publisher": "Microsoft",
"preview": true,
"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();
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;
dialogMode: AgentDialogMode = AgentDialogMode.CREATE;
id: number;
@@ -23,7 +28,7 @@ export class AlertData implements IAgentDialogData {
eventSource: string;
hasNotification: number;
includeEventDescription: string;
isEnabled: boolean;
isEnabled: boolean = true;
jobId: string;
jobName: string;
lastOccurrenceDate: string;
@@ -36,7 +41,7 @@ export class AlertData implements IAgentDialogData {
databaseName: string;
countResetDate: string;
categoryName: string;
alertType: string;
alertType: string = AlertData.DefaultAlertTypeString;
wmiEventNamespace: string;
wmiEventQuery: string;
@@ -109,9 +114,19 @@ export class AlertData implements IAgentDialogData {
databaseName: this.databaseName,
countResetDate: this.countResetDate,
categoryName: this.categoryName,
alertType: sqlops.AlertType.sqlServerEvent, //this.alertType,
alertType: AlertData.getAlertTypeFromString(this.alertType),
wmiEventNamespace: this.wmiEventNamespace,
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';
import * as nls from 'vscode-nls';
import * as sqlops from 'sqlops';
import * as vscode from 'vscode';
import { AgentUtils } from '../agentUtils';
import { IAgentDialogData, AgentDialogMode } from '../interfaces';
const localize = nls.loadMessageBundle();
export class JobData implements IAgentDialogData {
private readonly JobCompletionActionCondition_Always: string = 'When the job completes';
private readonly JobCompletionActionCondition_OnFailure: string = 'When the job fails';
private readonly JobCompletionActionCondition_OnSuccess: string = 'When the job succeeds';
private readonly JobCompletionActionCondition_Always: string = localize('jobData.whenJobCompletes', 'When the job completes');
private readonly JobCompletionActionCondition_OnFailure: string = localize('jobData.whenJobFails', 'When the job fails');
private readonly JobCompletionActionCondition_OnSuccess: string = localize('jobData.whenJobSucceeds', 'When the job succeeds');
// 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 _jobCategories: string[];
@@ -25,6 +29,7 @@ export class JobData implements IAgentDialogData {
public dialogMode: AgentDialogMode = AgentDialogMode.CREATE;
public name: string;
public originalName: string;
public enabled: boolean = true;
public description: string;
public category: string;
@@ -40,8 +45,21 @@ export class JobData implements IAgentDialogData {
public jobSchedules: sqlops.AgentJobScheduleInfo[];
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;
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[] {
@@ -92,7 +110,39 @@ export class JobData implements IAgentDialogData {
}
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,
owner: this.owner,
description: this.description,
@@ -122,30 +172,6 @@ export class JobData implements IAgentDialogData {
lastRun: '',
nextRun: '',
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;
weekdayPagerEndTime: string;
constructor(ownerUri:string) {
constructor(ownerUri:string, operatorInfo: sqlops.AgentOperatorInfo) {
this.ownerUri = ownerUri;
if (operatorInfo) {
this.dialogMode = AgentDialogMode.EDIT;
this.name = operatorInfo.name;
this.enabled = operatorInfo.enabled;
}
}
public async initialize() {

View File

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

View File

@@ -10,6 +10,8 @@ import * as sqlops from 'sqlops';
import { AgentDialog } from './agentDialog';
import { AgentUtils } from '../agentUtils';
import { AlertData } from '../data/alertData';
import { OperatorDialog } from './operatorDialog';
import { JobDialog } from './jobDialog';
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 ResponseTabText: string = localize('alertDialog.Response', 'Response');
private static readonly OptionsTabText: string = localize('alertDialog.Options', 'Options');
private static readonly EventAlertText: string = localize('alertDialog.eventAlert', 'Event alert definition');
// General tab strings
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 RaiseIfMessageContainsLabel: string = localize('alertDialog.RaiseAlertContains', 'Raise alert when message contains');
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 AlertSeverity002Label: string = localize('alertDialog.Severity002', '002 - 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 AlertSeverity024Label: string = localize('alertDialog.Severity024', '024 - Fatal Error: Hardware 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[] = [
AlertDialog.AlertTypeSqlServerEventString,
AlertDialog.AlertTypePerformanceConditionString,
AlertDialog.AlertTypeWmiEventString
AlertData.AlertTypeSqlServerEventString,
// Disabled until next release
// AlertData.AlertTypePerformanceConditionString,
// AlertData.AlertTypeWmiEventString
];
private static readonly AlertSeverities: string[] = [
@@ -124,6 +126,10 @@ export class AlertDialog extends AgentDialog<AlertData> {
private severityDropDown: sqlops.DropDownComponent;
private databaseDropDown: sqlops.DropDownComponent;
private enabledCheckBox: sqlops.CheckBoxComponent;
private errorNumberRadioButton: sqlops.RadioButtonComponent;
private severityRadioButton: sqlops.RadioButtonComponent;
private errorNumberTextBox: sqlops.InputBoxComponent;
private raiseAlertMessageCheckBox: sqlops.CheckBoxComponent;
private raiseAlertMessageTextBox: sqlops.InputBoxComponent;
@@ -142,58 +148,115 @@ export class AlertDialog extends AgentDialog<AlertData> {
private delayMinutesTextBox: 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,
new AlertData(ownerUri, alertInfo),
alertInfo ? AlertDialog.EditDialogTitle : AlertDialog.CreateDialogTitle);
this.jobs = jobs;
}
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.responseTab = sqlops.window.modelviewdialog.createTab(AlertDialog.ResponseTabText);
this.optionsTab = sqlops.window.modelviewdialog.createTab(AlertDialog.OptionsTabText);
this.initializeGeneralTab(databases);
this.initializeGeneralTab(this.databases, dialog);
this.initializeResponseTab();
this.initializeOptionsTab();
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 => {
// create controls
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()
.withProperties({
label: AlertDialog.EnabledCheckboxLabel
}).component();
this.enabledCheckBox.checked = true;
this.databaseDropDown = view.modelBuilder.dropDown()
.withProperties({
value: databases[0],
values: databases
values: databases,
width: '100%'
}).component();
this.typeDropDown = view.modelBuilder.dropDown()
.withProperties({
value: AlertDialog.AlertTypes[0],
values: AlertDialog.AlertTypes
value: '',
values: AlertDialog.AlertTypes,
width: '100%'
}).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()
.withProperties({
value: AlertDialog.AlertSeverities[0],
values: AlertDialog.AlertSeverities
values: AlertDialog.AlertSeverities,
width: '100%'
}).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()
.withProperties({
label: AlertDialog.RaiseIfMessageContainsLabel
}).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()
.withFormItems([{
@@ -206,24 +269,61 @@ export class AlertDialog extends AgentDialog<AlertData> {
component: this.typeDropDown,
title: AlertDialog.TypeLabel
}, {
component: this.databaseDropDown,
title: AlertDialog.DatabaseLabel
}, {
component: this.severityDropDown,
title: AlertDialog.SeverityLabel
}, {
component: this.raiseAlertMessageCheckBox,
title: ''
}, {
component: this.raiseAlertMessageTextBox,
title: AlertDialog.MessageTextLabel
components: [{
component: this.databaseDropDown,
title: AlertDialog.DatabaseLabel
},
{
component: this.severityRadioButton,
title: ''
},
{
component: this.severityDropDown,
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();
await view.initializeModel(formModel);
// initialize control values
this.nameTextBox.value = this.model.name;
this.raiseAlertMessageTextBox.value = this.model.eventDescriptionKeyword;
this.typeDropDown.value = this.model.alertType;
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
}).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({
label: AlertDialog.NewJobButtonLabel,
width: 80
}).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()
.withProperties({
@@ -254,32 +380,57 @@ export class AlertDialog extends AgentDialog<AlertData> {
AlertDialog.OperatorPagerColumnLabel
],
data: [],
height: 500
height: 500,
width: 375
}).component();
this.newOperatorButton = view.modelBuilder.button().withProperties({
label: this.newOperatorButton,
label: AlertDialog.NewOperatorButtonLabel,
width: 80
}).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()
.withFormItems([{
component: this.executeJobCheckBox,
title: ''
}, {
component: this.executeJobTextBox,
title: AlertDialog.ExecuteJobTextBoxLabel
}, {
component: this.newJobButton,
title: AlertDialog.NewJobButtonLabel
component: executeJobContainer,
title: ''
}, {
component: this.notifyOperatorsCheckBox,
title: ''
}, {
component: this.operatorsTable,
title: AlertDialog.OperatorListLabel,
actions: [this.newOperatorButton]
}]).withLayout({ width: '100%' }).component();
component: notifyOperatorContainer,
title: ''
}])
.withLayout({ width: '100%' }).component();
await view.initializeModel(formModel);
});
@@ -300,9 +451,19 @@ export class AlertDialog extends AgentDialog<AlertData> {
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()
.withFormItems([{
@@ -343,13 +504,25 @@ export class AlertDialog extends AgentDialog<AlertData> {
this.model.isEnabled = this.enabledCheckBox.checked;
this.model.alertType = this.getDropdownValue(this.typeDropDown);
this.model.databaseName = this.getDropdownValue(this.databaseDropDown);
this.model.severity = this.getSeverityNumber();
this.model.messageId = undefined;
let databaseName = this.getDropdownValue(this.databaseDropDown);
this.model.databaseName = (databaseName !== AlertDialog.AllDatabases) ? databaseName : undefined;
let raiseIfError = this.raiseAlertMessageCheckBox.checked;
if (raiseIfError) {
let messageText = this.raiseAlertMessageTextBox.value;
if (this.severityRadioButton.checked) {
this.model.severity = this.getSeverityNumber();
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.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as nls from 'vscode-nls';
import * as sqlops from 'sqlops';
import { JobData } from '../data/jobData';
import { JobStepDialog } from './jobStepDialog';
@@ -10,50 +11,57 @@ import { PickScheduleDialog } from './pickScheduleDialog';
import { AlertDialog } from './alertDialog';
import { AgentDialog } from './agentDialog';
const localize = nls.loadMessageBundle();
export class JobDialog extends AgentDialog<JobData> {
// TODO: localize
// Top level
private static readonly DialogTitle: string = 'New Job';
private readonly GeneralTabText: string = 'General';
private readonly StepsTabText: string = 'Steps';
private readonly SchedulesTabText: string = 'Schedules';
private readonly AlertsTabText: string = 'Alerts';
private readonly NotificationsTabText: string = 'Notifications';
private static readonly CreateDialogTitle: string = localize('jobDialog.newJob', 'New Job');
private static readonly EditDialogTitle: string = localize('jobDialog.editJob', 'Edit Job');
private readonly GeneralTabText: string = localize('jobDialog.general', 'General');
private readonly StepsTabText: string = localize('jobDialog.steps', 'Steps');
private readonly SchedulesTabText: string = localize('jobDialog.schedules', 'Schedules');
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
private readonly NameTextBoxLabel: string = 'Name';
private readonly OwnerTextBoxLabel: string = 'Owner';
private readonly CategoryDropdownLabel: string = 'Category';
private readonly DescriptionTextBoxLabel: string = 'Description';
private readonly EnabledCheckboxLabel: string = 'Enabled';
private readonly NameTextBoxLabel: string = localize('jobDialog.name', 'Name');
private readonly OwnerTextBoxLabel: string = localize('jobDialog.owner', 'Owner');
private readonly CategoryDropdownLabel: string = localize('jobDialog.category', 'Category');
private readonly DescriptionTextBoxLabel: string = localize('jobDialog.description', 'Description');
private readonly EnabledCheckboxLabel: string = localize('jobDialog.enabled', 'Enabled');
// Steps tab strings
private readonly JobStepsTopLabelString: string = 'Job step list';
private readonly StepsTable_StepColumnString: string = 'Step';
private readonly StepsTable_NameColumnString: string = 'Name';
private readonly StepsTable_TypeColumnString: string = 'Type';
private readonly StepsTable_SuccessColumnString: string = 'On Success';
private readonly StepsTable_FailureColumnString: string = 'On Failure';
private readonly NewStepButtonString: string = 'New...';
private readonly InsertStepButtonString: string = 'Insert...';
private readonly EditStepButtonString: string = 'Edit';
private readonly DeleteStepButtonString: string = 'Delete';
private readonly JobStepsTopLabelString: string = localize('jobDialog.jobStepList', 'Job step list');
private readonly StepsTable_StepColumnString: string = localize('jobDialog.step', 'Step');
private readonly StepsTable_NameColumnString: string = localize('jobDialog.name', 'Name');
private readonly StepsTable_TypeColumnString: string = localize('jobDialog.type', 'Type');
private readonly StepsTable_SuccessColumnString: string = localize('jobDialog.onSuccess', 'On Success');
private readonly StepsTable_FailureColumnString: string = localize('jobDialog.onFailure', 'On Failure');
private readonly NewStepButtonString: string = localize('jobDialog.new', 'New...');
private readonly EditStepButtonString: string = localize('jobDialog.edit', 'Edit');
private readonly DeleteStepButtonString: string = localize('jobDialog.delete', 'Delete');
private readonly MoveStepUpButtonString: string = localize('jobDialog.moveUp', 'Move Step Up');
private readonly MoveStepDownButtonString: string = localize('jobDialog.moveDown', 'Move Step Up');
// Notifications tab strings
private readonly NotificationsTabTopLabelString: string = 'Actions to perform when the job completes';
private readonly EmailCheckBoxString: string = 'Email';
private readonly PagerCheckBoxString: string = 'Page';
private readonly EventLogCheckBoxString: string = 'Write to the Windows Application event log';
private readonly DeleteJobCheckBoxString: string = 'Automatically delete job';
private readonly NotificationsTabTopLabelString: string = localize('jobDialog.notificationsTabTop', 'Actions to perform when the job completes');
private readonly EmailCheckBoxString: string = localize('jobDialog.email', 'Email');
private readonly PagerCheckBoxString: string = localize('jobDialog.page', 'Page');
private readonly EventLogCheckBoxString: string = localize('jobDialog.eventLogCheckBoxLabel', 'Write to the Windows Application event log');
private readonly DeleteJobCheckBoxString: string = localize('jobDialog.deleteJobLabel', 'Automatically delete job');
// Schedules tab strings
private readonly SchedulesTopLabelString: string = 'Schedules list';
private readonly PickScheduleButtonString: string = 'Pick Schedule';
private readonly SchedulesTopLabelString: string = localize('jobDialog.schedulesaLabel', 'Schedules list');
private readonly PickScheduleButtonString: string = localize('jobDialog.pickSchedule', 'Pick Schedule');
private readonly ScheduleNameLabelString: string = localize('jobDialog.scheduleNameLabel', 'Schedule Name');
// Alerts tab strings
private readonly AlertsTopLabelString: string = 'Alerts list';
private readonly NewAlertButtonString: string = 'New Alert';
private readonly AlertsTopLabelString: string = localize('jobDialog.alertsList', 'Alerts list');
private readonly NewAlertButtonString: string = localize('jobDialog.newAlert', 'New Alert');
private readonly AlertNameLabelString: string = localize('jobDialog.alertNameLabel', 'Alert Name');
// UI Components
private generalTab: sqlops.window.modelviewdialog.DialogTab;
@@ -72,7 +80,8 @@ export class JobDialog extends AgentDialog<JobData> {
// Steps tab controls
private stepsTable: sqlops.TableComponent;
private newStepButton: sqlops.ButtonComponent;
private insertStepButton: sqlops.ButtonComponent;
private moveStepUpButton: sqlops.ButtonComponent;
private moveStepDownButton: sqlops.ButtonComponent;
private editStepButton: sqlops.ButtonComponent;
private deleteStepButton: sqlops.ButtonComponent;
@@ -97,8 +106,11 @@ export class JobDialog extends AgentDialog<JobData> {
private alertsTable: sqlops.TableComponent;
private newAlertButton: sqlops.ButtonComponent;
constructor(ownerUri: string) {
super(ownerUri, new JobData(ownerUri), JobDialog.DialogTitle);
constructor(ownerUri: string, jobInfo: sqlops.AgentJobInfo = undefined) {
super(
ownerUri,
new JobData(ownerUri, jobInfo),
jobInfo ? JobDialog.EditDialogTitle : JobDialog.CreateDialogTitle);
}
protected async initializeDialog() {
@@ -129,6 +141,12 @@ export class JobDialog extends AgentDialog<JobData> {
private initializeGeneralTab() {
this.generalTab.registerContent(async view => {
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.categoryDropdown = view.modelBuilder.dropDown().component();
this.descriptionTextBox = view.modelBuilder.inputBox().withProperties({
@@ -159,11 +177,18 @@ export class JobDialog extends AgentDialog<JobData> {
await view.initializeModel(formModel);
this.nameTextBox.value = this.model.name;
this.ownerTextBox.value = this.model.defaultOwner;
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.descriptionTextBox.value = '';
this.descriptionTextBox.value = this.model.description;
});
}
@@ -179,24 +204,38 @@ export class JobDialog extends AgentDialog<JobData> {
this.StepsTable_FailureColumnString
],
data: [],
height: 800
height: 430
}).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({
label: this.NewStepButtonString,
width: 80
}).component();
this.newStepButton.onDidClick((e)=>{
let stepDialog = new JobStepDialog(this.model.ownerUri, '', '', 1, this.model);
stepDialog.openNewStepDialog();
if (this.nameTextBox.value && this.nameTextBox.value.length > 0) {
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({
label: this.EditStepButtonString,
width: 80
@@ -211,7 +250,7 @@ export class JobDialog extends AgentDialog<JobData> {
.withFormItems([{
component: this.stepsTable,
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();
await view.initializeModel(formModel);
});
@@ -222,10 +261,10 @@ export class JobDialog extends AgentDialog<JobData> {
this.alertsTable = view.modelBuilder.table()
.withProperties({
columns: [
'Alert Name'
this.AlertNameLabelString
],
data: [],
height: 600,
height: 430,
width: 400
}).component();
@@ -235,7 +274,7 @@ export class JobDialog extends AgentDialog<JobData> {
}).component();
this.newAlertButton.onDidClick((e)=>{
let alertDialog = new AlertDialog(this.model.ownerUri);
let alertDialog = new AlertDialog(this.model.ownerUri, null, []);
alertDialog.onSuccess((dialogModel) => {
});
alertDialog.openDialog();
@@ -257,11 +296,11 @@ export class JobDialog extends AgentDialog<JobData> {
this.schedulesTable = view.modelBuilder.table()
.withProperties({
columns: [
'Schedule Name'
this.ScheduleNameLabelString
],
data: [],
height: 600,
width: 400
height: 430,
width: 420
}).component();
this.pickScheduleButton = view.modelBuilder.button().withProperties({
@@ -361,21 +400,23 @@ export class JobDialog extends AgentDialog<JobData> {
let formModel = view.modelBuilder.formContainer().withFormItems([
{
component: this.notificationsTabTopLabel,
title: ''
}, {
component: emailContainer,
title: ''
}, {
component: pagerContainer,
title: ''
}, {
component: eventLogContainer,
title: ''
}, {
component: deleteJobContainer,
title: ''
}]).withLayout({ width: '100%' }).component();
components:
[{
component: emailContainer,
title: ''
},
{
component: pagerContainer,
title: ''
},
{
component: eventLogContainer,
title: ''
},
{
component: deleteJobContainer,
title: ''
}], title: this.NotificationsTabTopLabelString}]).withLayout({ width: '100%' }).component();
await view.initializeModel(formModel);
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.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as nls from 'vscode-nls';
import * as sqlops from 'sqlops';
import * as vscode from 'vscode';
import { JobStepData } from '../data/jobStepData';
@@ -10,31 +11,58 @@ import { AgentUtils } from '../agentUtils';
import { JobData } from '../data/jobData';
const path = require('path');
const localize = nls.loadMessageBundle();
export class JobStepDialog {
// TODO: localize
// Top level
//
private static readonly DialogTitle: string = 'New Job Step';
private static readonly FileBrowserDialogTitle: string = 'Locate Database Files - ';
private static readonly OkButtonText: string = 'OK';
private static readonly CancelButtonText: string = 'Cancel';
private static readonly GeneralTabText: string = 'General';
private static readonly AdvancedTabText: string = 'Advanced';
private static readonly OpenCommandText: string = 'Open...';
private static readonly ParseCommandText: string = 'Parse';
private static readonly NextButtonText: string = 'Next';
private static readonly PreviousButtonText: string = 'Previous';
private static readonly SuccessAction: string = 'On success action';
private static readonly FailureAction: string = 'On failure action';
private readonly DialogTitle: string = localize('jobStepDialog.newJobStep', 'New Job Step');
private readonly FileBrowserDialogTitle: string = localize('jobStepDialog.fileBrowserTitle', 'Locate Database Files - ');
private readonly OkButtonText: string = localize('jobStepDialog.ok', 'OK');
private readonly CancelButtonText: string = localize('jobStepDialog.cancel', 'Cancel');
private readonly GeneralTabText: string = localize('jobStepDialog.general', 'General');
private readonly AdvancedTabText: string = localize('jobStepDialog.advanced', 'Advanced');
private readonly OpenCommandText: string = localize('jobStepDialog.open', 'Open...');
private readonly ParseCommandText: string = localize('jobStepDialog.parse','Parse');
private readonly NextButtonText: string = localize('jobStepDialog.next', 'Next');
private readonly PreviousButtonText: string = localize('jobStepDialog.previous','Previous');
private readonly SuccessfulParseText: string = localize('jobStepDialog.successParse', 'The command was successfully parsed.');
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
private static readonly TSQLScript: string = 'Transact-SQL script (T-SQL)';
private static readonly AgentServiceAccount: string = 'SQL Server Agent Service Account';
private static readonly NextStep: string = 'Go to the next step';
private static readonly QuitJobReportingSuccess: string = 'Quit the job reporting success';
private static readonly QuitJobReportingFailure: string = 'Quit the job reporting failure';
private readonly TSQLScript: string = localize('jobStepDialog.TSQL', 'Transact-SQL script (T-SQL)');
private readonly AgentServiceAccount: string = localize('jobStepDialog.agentServiceAccount', 'SQL Server Agent Service Account');
private readonly NextStep: string = localize('jobStepDialog.nextStep', 'Go to the next step');
private readonly QuitJobReportingSuccess: string = localize('jobStepDialog.quitJobSuccess', 'Quit the job reporting success');
private readonly QuitJobReportingFailure: string = localize('jobStepDialog.quitJobFailure', 'Quit the job reporting failure');
// UI Components
@@ -54,6 +82,7 @@ export class JobStepDialog {
private retryIntervalBox: sqlops.InputBoxComponent;
private outputFileNameBox: sqlops.InputBoxComponent;
private fileBrowserNameBox: sqlops.InputBoxComponent;
private userInputBox: sqlops.InputBoxComponent;
// Dropdowns
private typeDropdown: sqlops.DropDownComponent;
@@ -98,33 +127,36 @@ export class JobStepDialog {
}
private initializeUIComponents() {
this.dialog = sqlops.window.modelviewdialog.createDialog(JobStepDialog.DialogTitle);
this.generalTab = sqlops.window.modelviewdialog.createTab(JobStepDialog.GeneralTabText);
this.advancedTab = sqlops.window.modelviewdialog.createTab(JobStepDialog.AdvancedTabText);
this.dialog = sqlops.window.modelviewdialog.createDialog(this.DialogTitle);
this.generalTab = sqlops.window.modelviewdialog.createTab(this.GeneralTabText);
this.advancedTab = sqlops.window.modelviewdialog.createTab(this.AdvancedTabText);
this.dialog.content = [this.generalTab, this.advancedTab];
this.dialog.okButton.onClick(async () => await this.execute());
this.dialog.okButton.label = JobStepDialog.OkButtonText;
this.dialog.cancelButton.label = JobStepDialog.CancelButtonText;
this.dialog.okButton.label = this.OkButtonText;
this.dialog.cancelButton.label = this.CancelButtonText;
}
private createCommands(view, queryProvider: sqlops.QueryProvider) {
this.openButton = view.modelBuilder.button()
.withProperties({
label: JobStepDialog.OpenCommandText,
width: '80px'
label: this.OpenCommandText,
width: '80px',
isFile: true
}).component();
this.openButton.enabled = false;
this.parseButton = view.modelBuilder.button()
.withProperties({
label: JobStepDialog.ParseCommandText,
width: '80px'
label: this.ParseCommandText,
width: '80px',
isFile: false
}).component();
this.parseButton.onDidClick(e => {
if (this.commandTextBox.value) {
queryProvider.parseSyntax(this.ownerUri, this.commandTextBox.value).then(result => {
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) {
this.dialog.message = { text: 'The command failed' };
this.dialog.message = { text: this.FailureParseText };
}
});
}
@@ -139,13 +171,13 @@ export class JobStepDialog {
.component();
this.nextButton = view.modelBuilder.button()
.withProperties({
label: JobStepDialog.NextButtonText,
label: this.NextButtonText,
enabled: false,
width: '80px'
}).component();
this.previousButton = view.modelBuilder.button()
.withProperties({
label: JobStepDialog.PreviousButtonText,
label: this.PreviousButtonText,
enabled: false,
width: '80px'
}).component();
@@ -157,10 +189,16 @@ export class JobStepDialog {
.withProperties({
}).component();
this.nameTextBox.required = true;
this.nameTextBox.onTextChanged(() => {
if (this.nameTextBox.value.length > 0) {
this.dialog.message = null;
}
});
this.typeDropdown = view.modelBuilder.dropDown()
.withProperties({
value: JobStepDialog.TSQLScript,
values: [JobStepDialog.TSQLScript]
value: this.TSQLScript,
values: [this.TSQLScript]
})
.component();
this.runAsDropdown = view.modelBuilder.dropDown()
@@ -171,8 +209,8 @@ export class JobStepDialog {
.component();
this.runAsDropdown.enabled = false;
this.typeDropdown.onValueChanged((type) => {
if (type.selected !== JobStepDialog.TSQLScript) {
this.runAsDropdown.value = JobStepDialog.AgentServiceAccount;
if (type.selected !== this.TSQLScript) {
this.runAsDropdown.value = this.AgentServiceAccount;
this.runAsDropdown.values = [this.runAsDropdown.value];
} else {
this.runAsDropdown.value = '';
@@ -200,19 +238,19 @@ export class JobStepDialog {
let formModel = view.modelBuilder.formContainer()
.withFormItems([{
component: this.nameTextBox,
title: 'Step name'
title: this.StepNameLabelString
}, {
component: this.typeDropdown,
title: 'Type'
title: this.TypeLabelString
}, {
component: this.runAsDropdown,
title: 'Run as'
title: this.RunAsLabelString
}, {
component: this.databaseDropdown,
title: 'Database'
title: this.DatabaseLabelString
}, {
component: this.commandTextBox,
title: 'Command',
title: this.CommandLabelString,
actions: [buttonContainer]
}], {
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() {
this.advancedTab.registerContent(async (view) => {
this.successActionDropdown = view.modelBuilder.dropDown()
.withProperties({
value: JobStepDialog.NextStep,
values: [JobStepDialog.NextStep, JobStepDialog.QuitJobReportingSuccess, JobStepDialog.QuitJobReportingFailure]
width: '100%',
value: this.NextStep,
values: [this.NextStep, this.QuitJobReportingSuccess, this.QuitJobReportingFailure]
})
.component();
let retryFlexContainer = this.createRetryCounters(view);
this.failureActionDropdown = view.modelBuilder.dropDown()
.withProperties({
value: JobStepDialog.QuitJobReportingFailure,
values: [JobStepDialog.QuitJobReportingFailure, JobStepDialog.NextStep, JobStepDialog.QuitJobReportingSuccess]
value: this.QuitJobReportingFailure,
values: [this.QuitJobReportingFailure, this.NextStep, this.QuitJobReportingSuccess]
})
.component();
let optionsGroup = this.createTSQLOptions(view);
let viewButton = view.modelBuilder.button()
.withProperties({ label: 'View', width: '50px' }).component();
viewButton.enabled = false;
this.logToTableCheckbox = view.modelBuilder.checkBox()
.withProperties({
label: 'Log to table'
label: this.LogToTableLabel
}).component();
let appendToExistingEntryInTableCheckbox = view.modelBuilder.checkBox()
.withProperties({ label: 'Append output to existing entry in table' }).component();
.withProperties({ label: this.AppendExistingTableEntryLabel }).component();
appendToExistingEntryInTableCheckbox.enabled = false;
this.logToTableCheckbox.onChanged(e => {
viewButton.enabled = e;
appendToExistingEntryInTableCheckbox.enabled = e;
});
let appendCheckboxContainer = view.modelBuilder.groupContainer()
.withItems([appendToExistingEntryInTableCheckbox]).component();
let logToTableContainer = view.modelBuilder.flexContainer()
.withLayout({ flexFlow: 'row', justifyContent: 'space-between', width: 300 })
.withItems([this.logToTableCheckbox, viewButton]).component();
.withItems([this.logToTableCheckbox]).component();
let logStepOutputHistoryCheckbox = view.modelBuilder.checkBox()
.withProperties({ label: 'Include step output in history' }).component();
let runAsUserOptions = this.createRunAsUserOptions(view);
.withProperties({ label: this.IncludeStepOutputHistoryLabel }).component();
this.userInputBox = view.modelBuilder.inputBox()
.withProperties({ inputType: 'text', width: '100%' }).component();
let formModel = view.modelBuilder.formContainer()
.withFormItems(
[{
component: this.successActionDropdown,
title: JobStepDialog.SuccessAction
title: this.SuccessActionLabel
}, {
component: retryFlexContainer,
title: ''
}, {
component: this.failureActionDropdown,
title: JobStepDialog.FailureAction
title: this.FailureActionLabel
}, {
component: optionsGroup,
title: 'Transact-SQL script (T-SQL)'
title: this.TSQLScript
}, {
component: logToTableContainer,
title: ''
@@ -309,8 +323,8 @@ export class JobStepDialog {
component: logStepOutputHistoryCheckbox,
title: ''
}, {
component: runAsUserOptions,
title: ''
component: this.userInputBox,
title: this.RunAsUserLabel
}], {
componentWidth: 400
}).component();
@@ -323,32 +337,37 @@ export class JobStepDialog {
private createRetryCounters(view) {
this.retryAttemptsBox = view.modelBuilder.inputBox()
.withValidation(component => component.value >= 0)
.withProperties({
inputType: 'number'
})
.component();
.withValidation(component => component.value >= 0)
.withProperties({
inputType: 'number',
width: '100%',
placeHolder: '0'
})
.component();
this.retryIntervalBox = view.modelBuilder.inputBox()
.withValidation(component => component.value >= 0)
.withProperties({
inputType: 'number'
inputType: 'number',
width: '100%',
placeHolder: '0'
}).component();
let retryAttemptsContainer = view.modelBuilder.formContainer()
.withFormItems(
[{
component: this.retryAttemptsBox,
title: 'Retry Attempts'
}], {
horizontal: false
})
[{
component: this.retryAttemptsBox,
title: this.RetryAttemptsLabel
}], {
horizontal: false,
componentWidth: '100%'
})
.component();
let retryIntervalContainer = view.modelBuilder.formContainer()
.withFormItems(
[{
component: this.retryIntervalBox,
title: 'Retry Interval (minutes)'
title: this.RetryIntervalLabel
}], {
horizontal: false
})
@@ -362,7 +381,7 @@ export class JobStepDialog {
}
private openFileBrowserDialog() {
let fileBrowserTitle = JobStepDialog.FileBrowserDialogTitle + `${this.server}`;
let fileBrowserTitle = this.FileBrowserDialogTitle + `${this.server}`;
this.fileBrowserDialog = sqlops.window.modelviewdialog.createDialog(fileBrowserTitle);
let fileBrowserTab = sqlops.window.modelviewdialog.createTab('File Browser');
this.fileBrowserDialog.content = [fileBrowserTab];
@@ -379,8 +398,8 @@ export class JobStepDialog {
});
this.fileTypeDropdown = view.modelBuilder.dropDown()
.withProperties({
value: 'All Files (*)',
values: ['All Files (*)']
value: this.AllFilesLabelString,
values: [this.AllFilesLabelString]
})
.component();
this.fileBrowserNameBox = view.modelBuilder.inputBox()
@@ -392,13 +411,13 @@ export class JobStepDialog {
title: ''
}, {
component: this.selectedPathTextBox,
title: 'Selected path:'
title: this.SelectedPathLabelString
}, {
component: this.fileTypeDropdown,
title: 'Files of type:'
title: this.FilesOfTypeLabelString
}, {
component: this.fileBrowserNameBox,
title: 'File name:'
title: this.FileNameLabelString
}
]).component();
view.initializeModel(fileBrowserContainer);
@@ -406,8 +425,8 @@ export class JobStepDialog {
this.fileBrowserDialog.okButton.onClick(() => {
this.outputFileNameBox.value = path.join(path.dirname(this.selectedPathTextBox.value), this.fileBrowserNameBox.value);
});
this.fileBrowserDialog.okButton.label = JobStepDialog.OkButtonText;
this.fileBrowserDialog.cancelButton.label = JobStepDialog.CancelButtonText;
this.fileBrowserDialog.okButton.label = this.OkButtonText;
this.fileBrowserDialog.cancelButton.label = this.CancelButtonText;
sqlops.window.modelviewdialog.openDialog(this.fileBrowserDialog);
}
@@ -417,21 +436,15 @@ export class JobStepDialog {
this.outputFileBrowserButton.onDidClick(() => this.openFileBrowserDialog());
this.outputFileNameBox = view.modelBuilder.inputBox()
.withProperties({
width: '150px',
width: 250,
inputType: 'text'
}).component();
let outputViewButton = view.modelBuilder.button()
.withProperties({
width: '50px',
label: 'View'
}).component();
outputViewButton.enabled = false;
let outputButtonContainer = view.modelBuilder.flexContainer()
.withLayout({
flexFlow: 'row',
textAlign: 'right',
width: 120
}).withItems([this.outputFileBrowserButton, outputViewButton], { flex: '1 1 50%' }).component();
width: '100%'
}).withItems([this.outputFileBrowserButton], { flex: '1 1 50%' }).component();
let outputFlexBox = view.modelBuilder.flexContainer()
.withLayout({
flexFlow: 'row',
@@ -441,7 +454,7 @@ export class JobStepDialog {
}).component();
this.appendToExistingFileCheckbox = view.modelBuilder.checkBox()
.withProperties({
label: 'Append output to existing file'
label: this.AppendOutputToFileLabel
}).component();
this.appendToExistingFileCheckbox.enabled = false;
this.outputFileNameBox.onTextChanged((input) => {
@@ -454,15 +467,20 @@ export class JobStepDialog {
let outputFileForm = view.modelBuilder.formContainer()
.withFormItems([{
component: outputFlexBox,
title: 'Output file'
title: this.OutputFileNameLabel
}, {
component: this.appendToExistingFileCheckbox,
title: ''
}], { horizontal: true, componentWidth: 200 }).component();
}], { horizontal: false, componentWidth: 200 }).component();
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.id = this.stepId;
this.model.server = this.server;
@@ -471,12 +489,11 @@ export class JobStepDialog {
this.model.databaseName = this.databaseDropdown.value as string;
this.model.script = this.commandTextBox.value;
this.model.successAction = this.successActionDropdown.value as string;
this.model.retryAttempts = +this.retryAttemptsBox.value;
this.model.retryInterval = +this.retryIntervalBox.value;
this.model.retryAttempts = this.retryAttemptsBox.value ? +this.retryAttemptsBox.value : 0;
this.model.retryInterval = +this.retryIntervalBox.value ? +this.retryIntervalBox.value : 0;
this.model.failureAction = this.failureActionDropdown.value as string;
this.model.outputFileName = this.outputFileNameBox.value;
this.model.appendToLogFile = this.appendToExistingFileCheckbox.checked;
await this.model.save();
}
public async openNewStepDialog() {

View File

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

View File

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

View File

@@ -15,32 +15,60 @@ const localize = nls.loadMessageBundle();
export class ProxyDialog extends AgentDialog<ProxyData> {
// 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');
// General tab strings
private static readonly ProxyNameTextBoxLabel: string = localize('createProxy.ProxyName', 'Proxy name');
private static readonly CredentialNameTextBoxLabel: string = localize('createProxy.CredentialName', 'Credential name');
private static readonly DescriptionTextBoxLabel: string = localize('createProxy.Description', 'Description');
private static readonly SubsystemsTableLabel: string = localize('createProxy.Subsystems', 'Subsystems');
private static readonly SubsystemNameColumnLabel: string = localize('createProxy.SubsystemName', 'Subsystem');
private static readonly SubsystemLabel: 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
private generalTab: sqlops.window.modelviewdialog.DialogTab;
// General tab controls
private proxyNameTextBox: sqlops.InputBoxComponent;
private credentialNameTextBox: sqlops.InputBoxComponent;
private credentialNameDropDown: sqlops.DropDownComponent;
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) {
super(ownerUri, new ProxyData(ownerUri), ProxyDialog.DialogTitle);
private credentials: sqlops.CredentialInfo[];
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) {
this.generalTab = sqlops.window.modelviewdialog.createTab(ProxyDialog.GeneralTabText);
this.initializeGeneralTab();
this.dialog.content = [this.generalTab];
@@ -49,43 +77,143 @@ export class ProxyDialog extends AgentDialog<ProxyData> {
private initializeGeneralTab() {
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.descriptionTextBox = view.modelBuilder.inputBox().component();
this.subsystemsTable = view.modelBuilder.table()
this.credentialNameDropDown = view.modelBuilder.dropDown()
.withProperties({
columns: [
ProxyDialog.SubsystemNameColumnLabel
],
data: [],
height: 500
width: 432,
value: '',
editable: true,
values: this.credentials.length > 0 ? this.credentials.map(c => c.name) : ['']
})
.component();
this.descriptionTextBox = view.modelBuilder.inputBox()
.withProperties({
width: 420,
multiline: true,
height: 300
})
.component();
this.subsystemCheckBox = view.modelBuilder.checkBox()
.withProperties({
label: ProxyDialog.SubsystemLabel
}).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()
.withFormItems([{
component: this.proxyNameTextBox,
title: ProxyDialog.ProxyNameTextBoxLabel
}, {
component: this.credentialNameTextBox,
component: this.credentialNameDropDown,
title: ProxyDialog.CredentialNameTextBoxLabel
}, {
component: this.descriptionTextBox,
title: ProxyDialog.DescriptionTextBoxLabel
}, {
component: this.subsystemsTable,
title: ProxyDialog.SubsystemsTableLabel
}]).withLayout({ width: '100%' }).component();
}]).withLayout({ width: 420 }).component();
await view.initializeModel(formModel);
this.proxyNameTextBox.value = this.model.accountName;
this.credentialNameDropDown.value = this.model.credentialName;
this.descriptionTextBox.value = this.model.description;
});
}
protected updateModel() {
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;
}
}

View File

@@ -4,17 +4,21 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as nls from 'vscode-nls';
import * as sqlops from 'sqlops';
import * as vscode from 'vscode';
import { ScheduleData } from '../data/scheduleData';
const localize = nls.loadMessageBundle();
export class ScheduleDialog {
// Top level
private readonly DialogTitle: string = 'New Schedule';
private readonly OkButtonText: string = 'OK';
private readonly CancelButtonText: string = 'Cancel';
private readonly DialogTitle: string = localize('scheduleDialog.newSchedule', 'New Schedule');
private readonly OkButtonText: string = localize('scheduleDialog.ok', 'OK');
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
private dialog: sqlops.window.modelviewdialog.Dialog;
@@ -46,7 +50,7 @@ export class ScheduleDialog {
this.schedulesTable = view.modelBuilder.table()
.withProperties({
columns: [
'Schedule Name'
this.ScheduleNameText
],
data: [],
height: 600,
@@ -56,7 +60,7 @@ export class ScheduleDialog {
let formModel = view.modelBuilder.formContainer()
.withFormItems([{
component: this.schedulesTable,
title: 'Schedules'
title: this.SchedulesLabelText
}]).withLayout({ width: '100%' }).component();
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.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as nls from 'vscode-nls';
import * as sqlops from 'sqlops';
import * as vscode from 'vscode';
import { AlertDialog } from './dialogs/alertDialog';
@@ -12,6 +14,8 @@ import { ProxyDialog } from './dialogs/proxyDialog';
import { JobStepDialog } from './dialogs/jobStepDialog';
import { PickScheduleDialog } from './dialogs/pickScheduleDialog';
const localize = nls.loadMessageBundle();
/**
* The main controller class that initializes the extension
*/
@@ -23,12 +27,17 @@ export class MainController {
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
*/
public activate(): void {
vscode.commands.registerCommand('agent.openCreateJobDialog', (ownerUri: string) => {
let dialog = new JobDialog(ownerUri);
vscode.commands.registerCommand('agent.openJobDialog', (ownerUri: string, jobInfo: sqlops.AgentJobInfo) => {
let dialog = new JobDialog(ownerUri, jobInfo);
dialog.openDialog();
});
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);
dialog.showDialog();
});
vscode.commands.registerCommand('agent.openAlertDialog', (ownerUri: string, alertInfo: sqlops.AgentAlertInfo) => {
let dialog = new AlertDialog(ownerUri, alertInfo);
vscode.commands.registerCommand('agent.openAlertDialog', (ownerUri: string, alertInfo: sqlops.AgentAlertInfo, jobs: string[]) => {
let dialog = new AlertDialog(ownerUri, alertInfo, jobs);
dialog.openDialog();
});
vscode.commands.registerCommand('agent.openCreateOperatorDialog', (ownerUri: string) => {
let dialog = new OperatorDialog(ownerUri);
vscode.commands.registerCommand('agent.openOperatorDialog', (ownerUri: string, operatorInfo: sqlops.AgentOperatorInfo) => {
let dialog = new OperatorDialog(ownerUri, operatorInfo);
dialog.openDialog();
});
vscode.commands.registerCommand('agent.openCreateProxyDialog', (ownerUri: string) => {
let dialog = new ProxyDialog(ownerUri);
dialog.openDialog();
vscode.commands.registerCommand('agent.openProxyDialog', (ownerUri: string, proxyInfo: sqlops.AgentProxyInfo, credentials: sqlops.CredentialInfo[]) => {
//@TODO: reenable create proxy after snapping July release (7/14/18)
// let dialog = new ProxyDialog(ownerUri, proxyInfo, credentials);
// dialog.openDialog();
MainController.showNotYetImplemented();
});
}

View File

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

View File

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

View File

@@ -658,7 +658,7 @@
}
},
"dependencies": {
"dataprotocol-client": "github:Microsoft/sqlops-dataprotocolclient#0.1.9",
"dataprotocol-client": "github:Microsoft/sqlops-dataprotocolclient#0.2.0",
"opener": "^1.4.3",
"service-downloader": "github:anthonydresser/service-downloader#0.1.2",
"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#}",
"version": "1.5.0-alpha.4",
"version": "1.5.0-alpha.12",
"downloadFileNames": {
"Windows_86": "win-x86-netcoreapp2.1.zip",
"Windows_64": "win-x64-netcoreapp2.1.zip",

View File

@@ -148,6 +148,11 @@ export interface DeleteAgentProxyParams {
proxy: sqlops.AgentProxyInfo;
}
// Agent Credentials parameters
export interface GetCredentialsParams {
ownerUri: string;
}
// Job Schedule management parameters
export interface AgentJobScheduleParams {
ownerUri: string;
@@ -262,6 +267,11 @@ export namespace DeleteAgentProxyRequest {
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
export namespace AgentJobSchedulesRequest {
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
let getJobSchedules = (ownerUri: string): Thenable<sqlops.AgentJobSchedulesResult> => {
let params: contracts.AgentJobScheduleParams = {
@@ -532,6 +548,7 @@ export class AgentServicesFeature extends SqlOpsFeature<undefined> {
createProxy,
updateProxy,
deleteProxy,
getCredentials,
getJobSchedules,
createJobSchedule,
updateJobSchedule,

View File

@@ -44,22 +44,6 @@ export class Telemetry {
private static platformInformation: PlatformInformation;
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> {
if (this.platformInformation) {
return Promise.resolve(this.platformInformation);
@@ -143,8 +127,7 @@ export class Telemetry {
}
// Augment the properties structure with additional common properties before sending
Promise.all([this.getUserId(), this.getPlatformInformation()]).then(() => {
properties['userId'] = this.userId;
Promise.all([this.getPlatformInformation()]).then(() => {
properties['distribution'] = (this.platformInformation && this.platformInformation.distribution) ?
`${this.platformInformation.distribution.name}, ${this.platformInformation.distribution.version}` : '';

View File

@@ -49,9 +49,9 @@ core-util-is@~1.0.0:
version "1.0.2"
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":
version "0.1.9"
resolved "https://codeload.github.com/Microsoft/sqlops-dataprotocolclient/tar.gz/a1a79895cb79658b75d78aa5cfd745855019148d"
"dataprotocol-client@github:Microsoft/sqlops-dataprotocolclient#0.2.0":
version "0.2.0"
resolved "https://codeload.github.com/Microsoft/sqlops-dataprotocolclient/tar.gz/c7a691c7fc5ad54a147090784a7f11efda301f4b"
dependencies:
vscode-languageclient "3.5.0"

View File

@@ -1,6 +1,6 @@
{
"name": "sqlops",
"version": "0.31.2",
"version": "0.32.1",
"distro": "8c3e97e3425cc9814496472ab73e076de2ba99ee",
"author": {
"name": "Microsoft Corporation"
@@ -60,7 +60,7 @@
"reflect-metadata": "^0.1.8",
"rxjs": "5.4.0",
"semver": "4.3.6",
"slickgrid": "github:anthonydresser/SlickGrid#2.3.22",
"slickgrid": "github:anthonydresser/SlickGrid#2.3.23",
"spdlog": "0.6.0",
"sudo-prompt": "^8.0.0",
"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> {
let inputBox = view.modelBuilder.inputBox()
.withProperties({
multiline: true,
height: 100
}).component();
.withProperties({
multiline: true,
height: 100
}).component();
let inputBoxWrapper = view.modelBuilder.loadingComponent().withItem(inputBox).component();
inputBoxWrapper.loading = false;
customButton1.onClick(() => {
@@ -145,8 +145,8 @@ export default class MainController implements vscode.Disposable {
component: inputBox4,
title: 'inputBox4'
}], {
horizontal: true
}).component();
horizontal: true
}).component();
let groupModel1 = view.modelBuilder.groupContainer()
.withLayout({
}).withItems([
@@ -178,8 +178,8 @@ export default class MainController implements vscode.Disposable {
}).component();
let declarativeTable = view.modelBuilder.declarativeTable()
.withProperties({
columns: [{
.withProperties({
columns: [{
displayName: 'Column 1',
valueType: sqlops.DeclarativeDataType.string,
width: '20px',
@@ -204,12 +204,12 @@ export default class MainController implements vscode.Disposable {
{ name: 'options2', displayName: 'option 2' }
]
}
],
data: [
['Data00', 'Data01', false, 'options2'],
['Data10', 'Data11', true, 'options1']
]
}).component();
],
data: [
['Data00', 'Data01', false, 'options2'],
['Data10', 'Data11', true, 'options1']
]
}).component();
declarativeTable.onDataChanged(e => {
inputBox2.value = e.row.toString() + ' ' + e.column.toString() + ' ' + e.value.toString();
@@ -223,7 +223,7 @@ export default class MainController implements vscode.Disposable {
height: 150
}).withItems([
radioButton, groupModel1, radioButton2]
, { flex: '1 1 50%' }).component();
, { flex: '1 1 50%' }).component();
let formModel = view.modelBuilder.formContainer()
.withFormItems([{
component: inputBoxWrapper,
@@ -254,9 +254,9 @@ export default class MainController implements vscode.Disposable {
component: listBox,
title: 'List Box'
}], {
horizontal: false,
componentWidth: componentWidth
}).component();
horizontal: false,
componentWidth: componentWidth
}).component();
let formWrapper = view.modelBuilder.loadingComponent().withItem(formModel).component();
formWrapper.loading = false;
customButton2.onClick(() => {
@@ -285,7 +285,6 @@ export default class MainController implements vscode.Disposable {
tab1.registerContent(async (view) => {
await this.getTabContent(view, customButton1, customButton2, 400);
});
sqlops.window.modelviewdialog.openDialog(dialog);
}
@@ -302,6 +301,17 @@ export default class MainController implements vscode.Disposable {
page1.registerContent(async (view) => {
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.open();
}
@@ -379,13 +389,14 @@ export default class MainController implements vscode.Disposable {
let monitorLightPath = vscode.Uri.file(path.join(__dirname, '..', 'media', 'monitor.svg'));
let monitorIcon = {
light: monitorLightPath,
dark: path.join(__dirname, '..', 'media', 'monitor_inverse.svg') };
dark: path.join(__dirname, '..', 'media', 'monitor_inverse.svg')
};
let monitorButton = view.modelBuilder.button()
.withProperties({
label: 'Monitor',
iconPath: monitorIcon
}).component();
.withProperties({
label: 'Monitor',
iconPath: monitorIcon
}).component();
let toolbarModel = view.modelBuilder.toolbarContainer()
.withToolbarItems([{
component: inputBox,

View File

@@ -63,13 +63,13 @@ export class InputBox extends vsInputBox {
this.enabledInputBorder = this.inputBorder;
this.disabledInputBackground = styles.disabledInputBackground;
this.disabledInputForeground = styles.disabledInputForeground;
this.updateInputEnabledDisabledColors();
this.applyStyles();
}
public enable(): void {
super.enable();
this.inputBackground = this.enabledInputBackground;
this.inputForeground = this.enabledInputForeground;
this.inputBorder = this.enabledInputBorder;
this.updateInputEnabledDisabledColors();
this.applyStyles();
}
@@ -89,9 +89,7 @@ export class InputBox extends vsInputBox {
public disable(): void {
super.disable();
this.inputBackground = this.disabledInputBackground;
this.inputForeground = this.disabledInputForeground;
this.inputBorder = this.disabledInputBorder;
this.updateInputEnabledDisabledColors();
this.applyStyles();
}
@@ -121,4 +119,11 @@ export class InputBox extends vsInputBox {
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) => {
rowContainer.element('td', { class: labelClass }, (labelCellContainer) => {
labelCellContainer.div({}, (labelContainer) => {
labelContainer.innerHtml(label);
labelContainer.text(label);
});
});
rowContainer.element('td', { class: cellContainerClass }, (inputCellContainer) => {
@@ -33,7 +33,7 @@ export function appendRowLink(container: Builder, label: string, labelClass: str
container.element('tr', {}, (rowContainer) => {
rowContainer.element('td', { class: labelClass }, (labelCellContainer) => {
labelCellContainer.div({}, (labelContainer) => {
labelContainer.innerHtml(label);
labelContainer.text(label);
});
});
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) => {
this._modalTitle = modalTitle;
modalTitle.innerHtml(this._title);
modalTitle.text(this._title);
});
});
parts.push(this._modalHeaderSection.getHTMLElement());
@@ -433,7 +433,7 @@ export abstract class Modal extends Disposable implements IThemable {
*/
protected set title(title: string) {
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 {
var option = this._optionElements[optionName].option;
this._optionTitle.innerHtml(option.displayName);
this._optionDescription.innerHtml(option.description);
this._optionTitle.text(option.displayName);
this._optionDescription.text(option.description);
}
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 { 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;
}
@@ -19,7 +20,7 @@ export abstract class TabChild {
`
})
export class TabComponent implements OnDestroy {
@ContentChild(TabChild) private _child: TabChild;
private _child: TabChild;
@ContentChild(TemplateRef) templateRef;
@Input() public title: string;
@Input() public canClose: boolean;
@@ -29,19 +30,30 @@ export class TabComponent implements OnDestroy {
@Input() public identifier: string;
@Input() private visibilityType: 'if' | 'visibility' = 'if';
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(
@Inject(forwardRef(() => ChangeDetectorRef)) private _cd: ChangeDetectorRef
) { }
public set active(val: boolean) {
this._active = val;
if (this.active) {
this.rendered = true;
}
this._cd.detectChanges();
if (this.active && this._child) {
this._child.layout();
if (!this.destroyed) {
this._active = val;
if (this.active) {
this.rendered = true;
}
this._cd.detectChanges();
if (this.active && this._child) {
this._child.layout();
}
}
}
@@ -50,6 +62,7 @@ export class TabComponent implements OnDestroy {
}
ngOnDestroy() {
this.destroyed = true;
if (this.actions && this.actions.length > 0) {
this.actions.forEach((action) => action.dispose());
}
@@ -74,6 +87,8 @@ export class TabComponent implements OnDestroy {
}
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);
} else {
tabLabelcontainer.className = 'tabLabel';
tabLabelcontainer.innerHTML = this.tab.title;
tabLabelcontainer.textContent = this.tab.title;
}
tabLabelcontainer.title = this.tab.title;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -81,6 +81,15 @@
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-dark .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');
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.');
noAccountTitle.innerHTML = noAccountLabel;
noAccountTitle.innerText = noAccountLabel;
// 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

View File

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

View File

@@ -145,7 +145,7 @@ export class FirewallRuleDialog extends Modal {
subnetIPRangeSection = subnetIPRangeContainer.getHTMLElement();
subnetIPRangeContainer.div({ 'class': 'dialog-input-section' }, (inputContainer) => {
inputContainer.div({ 'class': 'dialog-label' }, (labelContainer) => {
labelContainer.innerHtml(LocalizedStrings.FROM);
labelContainer.text(LocalizedStrings.FROM);
});
inputContainer.div({ 'class': 'dialog-input' }, (inputCellContainer) => {
@@ -155,7 +155,7 @@ export class FirewallRuleDialog extends Modal {
});
inputContainer.div({ 'class': 'dialog-label' }, (labelContainer) => {
labelContainer.innerHtml(LocalizedStrings.TO);
labelContainer.text(LocalizedStrings.TO);
});
inputContainer.div({ 'class': 'dialog-input' }, (inputCellContainer) => {
@@ -234,7 +234,7 @@ export class FirewallRuleDialog extends Modal {
className += ' header';
}
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 { IConnectionProfile } from 'sqlops';
export class ConnectionContextkey implements IContextKey<IConnectionProfile> {
export class ConnectionContextKey implements IContextKey<IConnectionProfile> {
static Provider = new RawContextKey<string>('connectionProvider', undefined);
static Server = new RawContextKey<string>('serverName', undefined);
@@ -23,10 +23,10 @@ export class ConnectionContextkey implements IContextKey<IConnectionProfile> {
constructor(
@IContextKeyService contextKeyService: IContextKeyService
) {
this._providerKey = ConnectionContextkey.Provider.bindTo(contextKeyService);
this._serverKey = ConnectionContextkey.Server.bindTo(contextKeyService);
this._databaseKey = ConnectionContextkey.Database.bindTo(contextKeyService);
this._connectionKey = ConnectionContextkey.Connection.bindTo(contextKeyService);
this._providerKey = ConnectionContextKey.Provider.bindTo(contextKeyService);
this._serverKey = ConnectionContextKey.Server.bindTo(contextKeyService);
this._databaseKey = ConnectionContextKey.Database.bindTo(contextKeyService);
this._connectionKey = ConnectionContextKey.Connection.bindTo(contextKeyService);
}
set(value: IConnectionProfile) {

View File

@@ -174,7 +174,7 @@ export interface IConnectionManagementService {
disconnectEditor(owner: IConnectableInput, force?: boolean): Promise<boolean>;
disconnect(connection: ConnectionProfile): Promise<void>;
disconnect(connection: IConnectionProfile): Promise<void>;
disconnect(ownerUri: string): Promise<void>;
@@ -208,7 +208,7 @@ export interface IConnectionManagementService {
*/
cancelEditorConnection(owner: IConnectableInput): Thenable<boolean>;
showDashboard(connection: ConnectionProfile): Thenable<boolean>;
showDashboard(connection: IConnectionProfile): Thenable<boolean>;
closeDashboard(uri: string): void;
@@ -216,7 +216,7 @@ export interface IConnectionManagementService {
hasRegisteredServers(): boolean;
canChangeConnectionConfig(profile: ConnectionProfile, newGroupID: string): boolean;
canChangeConnectionConfig(profile: IConnectionProfile, newGroupID: string): boolean;
getTabColorForUri(uri: string): string;
@@ -320,12 +320,13 @@ export enum MetadataType {
}
export enum TaskStatus {
notStarted = 0,
inProgress = 1,
succeeded = 2,
succeededWithWarning = 3,
failed = 4,
canceled = 5
NotStarted = 0,
InProgress = 1,
Succeeded = 2,
SucceededWithWarning = 3,
Failed = 4,
Canceled = 5,
Canceling = 6
}
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);
}

View File

@@ -240,7 +240,7 @@ export class ConnectionStore {
return connectionProfile;
} else {
return undefined;
};
}
});
}
@@ -352,7 +352,7 @@ export class ConnectionStore {
list.unshift(savedProfile);
let newList = list.map(c => {
let connectionProfile = c ? c.toIConnectionProfile() : undefined;;
let connectionProfile = c ? c.toIConnectionProfile() : undefined;
return connectionProfile;
});
return newList.filter(n => n !== undefined);
@@ -372,7 +372,7 @@ export class ConnectionStore {
});
let newList = list.map(c => {
let connectionProfile = c ? c.toIConnectionProfile() : undefined;;
let connectionProfile = c ? c.toIConnectionProfile() : undefined;
return connectionProfile;
});
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')) {
message = [
localize('kerberosErrorStart', "Connection failed due to Kerberos error."),
localize('kerberosHelpLink', "&nbsp;Help configuring Kerberos is available at ") + helpLink,
localize('kerberosKinit', "&nbsp;If you have previously connected you may need to re-run kinit.")
].join('<br/>');
localize('kerberosHelpLink', "Help configuring Kerberos is available at {0}", helpLink),
localize('kerberosKinit', "If you have previously connected you may need to re-run kinit.")
].join('\r\n');
actions.push(new Action('Kinit', 'Run kinit', null, true, () => {
this._connectionDialog.close();
this._clipboardService.writeText('kinit\r');

View File

@@ -262,7 +262,7 @@ export class ConnectionDialogWidget extends Modal {
let recentHistoryLabel = localize('recentHistory', 'Recent history');
recentConnectionContainer.div({ class: 'recent-titles-container' }, (container) => {
container.div({ class: 'connection-history-label' }, (recentTitle) => {
recentTitle.innerHtml(recentHistoryLabel);
recentTitle.text(recentHistoryLabel);
});
container.div({ class: 'connection-history-actions' }, (actionsContainer) => {
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) => {
let noRecentHistoryLabel = localize('noRecentConnections', 'No recent connection');
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) => {
let noSavedConnectionLabel = localize('noSavedConnections', 'No saved connection');
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 {
// no op
}
private _toDispose: IDisposable[] = [];
constructor() {
super();
}
public dispose(): void {
this._toDispose = dispose(this._toDispose);
}
protected _register<T extends IDisposable>(t: T): T {
this._toDispose.push(t);
return t;
}
ngOnDestroy() {
this.dispose();
}

View File

@@ -40,7 +40,7 @@ export class DashboardErrorContainer extends DashboardTab implements AfterViewIn
ngAfterViewInit() {
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 {

View File

@@ -18,7 +18,7 @@ import { DashboardModule } from './dashboard.module';
import { bootstrapAngular } from 'sql/services/bootstrap/bootstrapService';
import { IDashboardComponentParams } from 'sql/services/bootstrap/bootstrapParams';
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 { ConnectionProfile } from 'sql/parts/connection/common/connectionProfile';
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.
*/
public layout(dimension: DOM.Dimension): void {
this._dashboardService.layout(dimension);
}
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;
this._dashboardService.changeToDashboard({ profile, serverInfo });
let scopedContextService = this._contextKeyService.createScoped(input.container);
let connectionContextKey = new ConnectionContextkey(scopedContextService);
let connectionContextKey = new ConnectionContextKey(scopedContextService);
connectionContextKey.set(input.connectionProfile);
let params: IDashboardComponentParams = {

View File

@@ -163,7 +163,7 @@ export class NewDashboardTabDialog extends Modal {
this._noExtensionViewContainer = DOM.$('.no-extension-view');
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.');
noExtensionTitle.innerHTML = noExtensionLabel;
noExtensionTitle.textContent = noExtensionLabel;
DOM.append(container, this._noExtensionViewContainer);
}

View File

@@ -15,7 +15,6 @@ import {
import { ICapabilitiesService } from 'sql/services/capabilities/capabilitiesService';
import { ConnectionManagementInfo } from 'sql/parts/connection/common/connectionManagementInfo';
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 { IScriptingService } from 'sql/services/scripting/scriptingService';

View File

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

View File

@@ -226,7 +226,7 @@ export class RestoreDialog extends Modal {
destinationContainer.div({ class: 'dialog-input-section' }, (inputContainer) => {
inputContainer.div({ class: 'dialog-label' }, (labelContainer) => {
labelContainer.innerHtml(LocalizedStrings.TARGETDATABASE);
labelContainer.text(LocalizedStrings.TARGETDATABASE);
});
inputContainer.div({ class: 'dialog-input' }, (inputCellContainer) => {
@@ -471,7 +471,7 @@ export class RestoreDialog extends Modal {
className += ' header';
}
container.div({ class: className }, (labelContainer) => {
labelContainer.innerHtml(content);
labelContainer.text(content);
});
}
@@ -535,7 +535,7 @@ export class RestoreDialog extends Modal {
let selectBox: SelectBox;
container.div({ class: 'dialog-input-section' }, (inputContainer) => {
inputContainer.div({ class: 'dialog-label' }, (labelContainer) => {
labelContainer.innerHtml(label);
labelContainer.text(label);
});
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 { IQueryEditorService } from 'sql/parts/query/common/queryEditorService';
import { IBootstrapParams } from 'sql/services/bootstrap/bootstrapService';
import { escape } from 'sql/base/common/strings';
import { INotificationService } from 'vs/platform/notification/common/notification';
import Severity from 'vs/base/common/severity';
@@ -197,7 +198,7 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
rowIndex++;
return {
values: row.cells.map(c => {
return mixin({ ariaLabel: c.displayValue }, c);
return mixin({ ariaLabel: escape(c.displayValue) }, c);
}), row: row.id
};
});
@@ -367,11 +368,12 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
columnDefinitions: resultSet.columnInfo.map((c, i) => {
let isLinked = c.isXml || c.isJson;
let linkType = c.isXml ? 'xml' : 'json';
return {
id: i.toString(),
name: c.columnName === 'Microsoft SQL Server 2005 XML Showplan'
? 'XML Showplan'
: c.columnName,
: escape(c.columnName),
type: self.stringToFieldType('string'),
formatter: isLinked ? Services.hyperLinkFormatter : Services.textFormatter,
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) => {
let isLinked = c.isXml || c.isJson;
let linkType = c.isXml ? 'xml' : 'json';
return {
id: i.toString(),
name: c.columnName === 'Microsoft SQL Server 2005 XML Showplan'
? 'XML Showplan'
: c.columnName,
: escape(c.columnName),
type: self.stringToFieldType('string'),
formatter: isLinked ? Services.hyperLinkFormatter : Services.textFormatter,
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 * as nls from 'vs/nls';
import { Component, Inject, forwardRef, ElementRef, ChangeDetectorRef, ViewChild, Injectable } from '@angular/core';
import * as Utils from 'sql/parts/connection/common/utils';
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 { Component, Inject, forwardRef, ChangeDetectorRef, ViewChild, Injectable } from '@angular/core';
import { AgentJobInfo } from 'sqlops';
import { PanelComponent, IPanelOptions, NavigationBarLayout } from 'sql/base/browser/ui/panel/panel.component';
import { IJobManagementService } from 'sql/parts/jobManagement/common/interfaces';
import { IDashboardService } from 'sql/services/dashboard/common/dashboardService';
export const DASHBOARD_SELECTOR: string = 'agentview-component';
@@ -32,12 +25,6 @@ export class AgentViewComponent {
@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 _jobId: string = null;
private _agentJobInfo: AgentJobInfo = null;
@@ -49,6 +36,11 @@ export class AgentViewComponent {
public proxiesIconClass: string = 'proxiesview-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
private readonly panelOpt: IPanelOptions = {
showTabsWhenOne: true,
@@ -58,7 +50,8 @@ export class AgentViewComponent {
constructor(
@Inject(forwardRef(() => ChangeDetectorRef)) private _cd: ChangeDetectorRef,
@Inject(IJobManagementService) jobManagementService: IJobManagementService) {
@Inject(IJobManagementService) jobManagementService: IJobManagementService,
@Inject(IDashboardService) dashboardService: IDashboardService,) {
this._expanded = new Map<string, string>();
let self = this;

View File

@@ -34,6 +34,8 @@ export interface IJobManagementService {
getProxies(connectionUri: string): Thenable<sqlops.AgentProxiesResult>;
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>;
addToCache(server: string, cache: JobCacheObject);
jobCacheObjectMap: { [server: string]: JobCacheObject; };

View File

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

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> {
return this._runAction(connectionUri, (runner) => {
return runner.getJobHistory(connectionUri, jobID);

View File

@@ -32,23 +32,11 @@ jobhistory-component {
}
.jobview-grid {
height: 94.7%;
height: calc(100% - 75px);
width : 100%;
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 {
background: #333333 !important;
}
@@ -58,7 +46,6 @@ jobhistory-component {
background: white !important;
border: 0px !important;
font-weight: bold;
font-size: larger;
}
.vs-dark #jobsDiv jobsview-component .jobview-grid .grid-canvas .ui-widget-content.slick-row .slick-cell {
@@ -164,7 +151,7 @@ jobhistory-component {
}
#jobsDiv .preload {
font-size: 18px;
font-size: 13px;
}
#jobsDiv .dynamic-cell-detail > :first-child {
@@ -299,19 +286,20 @@ table.jobprevruns > tbody {
#alertsDiv .jobalertsview-grid {
height: 94.7%;
height: calc(100% - 75px);
width : 100%;
display: block;
}
#operatorsDiv .joboperatorsview-grid {
height: 94.7%;
height: calc(100% - 75px);
width : 100%;
display: block;
overflow: scroll;
}
#proxiesDiv .jobproxiesview-grid {
height: 94.7%;
height: calc(100% - 75px);
width : 100%;
display: block;
}
@@ -339,4 +327,97 @@ jobsview-component .actionbar-container {
jobsview-component .jobview-grid .slick-cell.error-row {
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 #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 { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
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 ROW_HEIGHT: number = 45;
export const ROW_HEIGHT: number = 30;
@Component({
selector: VIEW_SELECTOR,
@@ -73,8 +74,9 @@ export class AlertsViewComponent extends JobManagementView implements OnInit {
@Inject(IInstantiationService) instantiationService: IInstantiationService,
@Inject(forwardRef(() => CommonServiceInterface)) commonService: CommonServiceInterface,
@Inject(IContextMenuService) contextMenuService: IContextMenuService,
@Inject(IKeybindingService) keybindingService: IKeybindingService) {
super(commonService, contextMenuService, keybindingService, instantiationService);
@Inject(IKeybindingService) keybindingService: IKeybindingService,
@Inject(IDashboardService) _dashboardService: IDashboardService) {
super(commonService, _dashboardService, contextMenuService, keybindingService, instantiationService);
this._isCloud = commonService.connectionManagementService.connectionInfo.serverInfo.isCloud;
}
@@ -85,7 +87,14 @@ export class AlertsViewComponent extends JobManagementView implements OnInit {
}
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() {
@@ -94,13 +103,6 @@ export class AlertsViewComponent extends JobManagementView implements OnInit {
column.rerenderOnResize = true;
return column;
});
let options = <Slick.GridOptions<any>>{
syncColumnCellResize: true,
enableColumnReorder: false,
rowHeight: ROW_HEIGHT,
enableCellNavigation: true,
forceFitColumns: true
};
this.dataView = new Slick.Data.DataView();
@@ -164,7 +166,17 @@ export class AlertsViewComponent extends JobManagementView implements OnInit {
public openCreateAlertDialog() {
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() {

View File

@@ -154,8 +154,9 @@
</td>
</tr>
</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>
</div>
</div>

View File

@@ -7,6 +7,7 @@ import 'vs/css!./jobHistory';
import 'vs/css!sql/media/icons/common-icons';
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 { Taskbar } from 'sql/base/browser/ui/taskbar/taskbar';
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 { Tree } from 'vs/base/parts/tree/browser/treeImpl';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { Disposable } from 'vs/base/common/lifecycle';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
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';
@Component({
selector: DASHBOARD_SELECTOR,
templateUrl: decodeURI(require.toUrl('./jobHistory.component.html')),
providers: [{ provide: TabChild, useExisting: forwardRef(() => JobHistoryComponent) }],
changeDetection: ChangeDetectionStrategy.OnPush
})
@Injectable()
export class JobHistoryComponent extends Disposable implements OnInit {
export class JobHistoryComponent extends JobManagementView implements OnInit {
private _tree: Tree;
private _treeController: JobHistoryController;
private _treeDataSource: JobHistoryDataSource;
private _treeRenderer: JobHistoryRenderer;
private _treeFilter: JobHistoryFilter;
private _actionBar: Taskbar;
@ViewChild('table') private _tableContainer: ElementRef;
@ViewChild('actionbarContainer') private _actionbarContainer: ElementRef;
@ViewChild('jobsteps') private _jobStepsView: ElementRef;
@Input() public agentJobInfo: sqlops.AgentJobInfo = undefined;
@Input() public agentJobHistories: sqlops.AgentJobHistoryInfo[] = undefined;
@@ -67,21 +71,23 @@ export class JobHistoryComponent extends Disposable implements OnInit {
constructor(
@Inject(forwardRef(() => ElementRef)) el: ElementRef,
@Inject(forwardRef(() => ChangeDetectorRef)) private _cd: ChangeDetectorRef,
@Inject(forwardRef(() => CommonServiceInterface)) private _dashboardService: CommonServiceInterface,
@Inject(forwardRef(() => CommonServiceInterface)) commonService: CommonServiceInterface,
@Inject(forwardRef(() => AgentViewComponent)) private _agentViewComponent: AgentViewComponent,
@Inject(IWorkbenchThemeService) private themeService: IWorkbenchThemeService,
@Inject(INotificationService) private _notificationService: INotificationService,
@Inject(IInstantiationService) private instantiationService: IInstantiationService,
@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._treeDataSource = new JobHistoryDataSource();
this._treeRenderer = new JobHistoryRenderer();
this._treeFilter = new JobHistoryFilter();
let jobCacheObjectMap = this._jobManagementService.jobCacheObjectMap;
this._serverName = _dashboardService.connectionManagementService.connectionInfo.connectionProfile.serverName;
this._serverName = commonService.connectionManagementService.connectionInfo.connectionProfile.serverName;
let jobCache = jobCacheObjectMap[this._serverName];
if (jobCache) {
this._jobCacheObject = jobCache;
@@ -93,7 +99,11 @@ export class JobHistoryComponent extends Disposable implements OnInit {
}
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;
this._treeController.onClick = (tree, element, event, origin = 'mouse') => {
const payload = { origin: origin };
@@ -130,56 +140,13 @@ export class JobHistoryComponent extends Disposable implements OnInit {
}, {verticalScrollMode: ScrollbarVisibility.Visible});
this._register(attachListStyler(this._tree, this.themeService));
this._tree.layout(JobHistoryComponent.INITIAL_TREE_HEIGHT);
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);
}
});
}
this.initActionBar();
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() {
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) => {
if (result && result.jobs) {
if (result.jobs.length > 0) {
@@ -207,10 +174,12 @@ export class JobHistoryComponent extends Disposable implements OnInit {
if (self.agentJobHistoryInfo) {
self.agentJobHistoryInfo.runDate = self.formatTime(self.agentJobHistoryInfo.runDate);
if (self.agentJobHistoryInfo.steps) {
let jobStepStatus = this.didJobFail(self.agentJobHistoryInfo);
self._stepRows = self.agentJobHistoryInfo.steps.map(step => {
let stepViewRow = new JobStepsViewRow();
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);
stepViewRow.stepName = step.stepName;
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[]) {
self._treeController.jobHistories = 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);
}
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 stopJobAction = this.instantiationService.createInstance(StopJobAction);
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.context = this;
this._actionBar.setContent([
@@ -299,7 +336,7 @@ export class JobHistoryComponent extends Disposable implements OnInit {
}
public get ownerUri(): string {
return this._dashboardService.connectionManagementService.connectionInfo.ownerUri;
return this._commonService.connectionManagementService.connectionInfo.ownerUri;
}
public get serverName(): string {

View File

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

View File

@@ -104,7 +104,6 @@ export interface IListTemplate {
}
export class JobHistoryRenderer implements tree.IRenderer {
private _statusIcon: HTMLElement;
public getHeight(tree: tree.ITree, element: JobHistoryRow): number {
return 30;
@@ -121,11 +120,10 @@ export class JobHistoryRenderer implements tree.IRenderer {
public renderTemplate(tree: tree.ITree, templateId: string, container: HTMLElement): IListTemplate {
let row = DOM.$('.list-row');
let label = DOM.$('.label');
this._statusIcon = this.createStatusIcon();
row.appendChild(this._statusIcon);
let statusIcon = this.createStatusIcon();
row.appendChild(statusIcon);
row.appendChild(label);
container.appendChild(row);
let statusIcon = this._statusIcon;
return { statusIcon, label };
}
@@ -133,13 +131,13 @@ export class JobHistoryRenderer implements tree.IRenderer {
templateData.label.innerHTML = element.runDate + '&nbsp;&nbsp;' + element.runStatus;
let statusClass: string;
if (element.runStatus === 'Succeeded') {
statusClass = ' job-passed';
statusClass = 'status-icon job-passed';
} else if (element.runStatus === 'Failed') {
statusClass = ' job-failed';
statusClass = 'status-icon job-failed';
} 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 {
@@ -148,7 +146,6 @@ export class JobHistoryRenderer implements tree.IRenderer {
private createStatusIcon(): HTMLElement {
let statusIcon: HTMLElement = DOM.$('div');
statusIcon.className += ' status-icon';
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 { Taskbar } from '../../../base/browser/ui/taskbar/taskbar';
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 isInitialized: boolean = false;
protected isRefreshing: boolean = false;
@@ -32,10 +34,16 @@ export abstract class JobManagementView extends Disposable implements AfterConte
constructor(
protected _commonService: CommonServiceInterface,
protected _dashboardService: IDashboardService,
protected _contextMenuService: IContextMenuService,
protected _keybindingService: IKeybindingService,
protected _instantiationService: IInstantiationService) {
super();
let self = this;
this._dashboardService.onLayout((d) => {
self.layout();
});
}
ngAfterContentChecked() {
@@ -49,8 +57,9 @@ export abstract class JobManagementView extends Disposable implements AfterConte
}
} else if (this.isVisible === true && this._parentComponent.refresh === true) {
this._showProgressWheel = true;
this.onFirstVisible();
this.isRefreshing = true;
this.onFirstVisible();
this.layout();
this._parentComponent.refresh = false;
} else if (this.isVisible === true && this._visibilityElement.nativeElement.offsetParent === null) {
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 { attachListStyler } from 'vs/platform/theme/common/styler';
import { Tree } from 'vs/base/parts/tree/browser/treeImpl';
import { Disposable } from 'vs/base/common/lifecycle';
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service';
import { JobStepsViewController, JobStepsViewDataSource, JobStepsViewFilter,
JobStepsViewRenderer, JobStepsViewModel} from 'sql/parts/jobManagement/views/jobStepsViewTree';
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';
@Component({
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 _treeController = new JobStepsViewController();
@@ -33,15 +40,18 @@ export class JobStepsViewComponent extends Disposable implements OnInit, AfterCo
@ViewChild('table') private _tableContainer: ElementRef;
constructor(
@Inject(forwardRef(() => ElementRef)) el: ElementRef,
@Inject(forwardRef(() => ChangeDetectorRef)) private _cd: ChangeDetectorRef,
@Inject(forwardRef(() => CommonServiceInterface)) private _dashboardService: CommonServiceInterface,
@Inject(forwardRef(() => CommonServiceInterface)) commonService: CommonServiceInterface,
@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() {
@@ -64,7 +74,7 @@ export class JobStepsViewComponent extends Disposable implements OnInit, AfterCo
}
ngOnInit() {
let ownerUri: string = this._dashboardService.connectionManagementService.connectionInfo.ownerUri;
let ownerUri: string = this._commonService.connectionManagementService.connectionInfo.ownerUri;
this._tree = new Tree(this._tableContainer.nativeElement, {
controller: this._treeController,
dataSource: this._treeDataSource,
@@ -73,5 +83,11 @@ export class JobStepsViewComponent extends Disposable implements OnInit, AfterCo
}, {verticalScrollMode: ScrollbarVisibility.Visible});
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 { IAction } from 'vs/base/common/actions';
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 ROW_HEIGHT: number = 45;
@@ -99,8 +101,9 @@ export class JobsViewComponent extends JobManagementView implements OnInit {
@Inject(IInstantiationService) instantiationService: IInstantiationService,
@Inject(IContextMenuService) contextMenuService: IContextMenuService,
@Inject(IKeybindingService) keybindingService: IKeybindingService,
@Inject(IDashboardService) _dashboardService: IDashboardService
) {
super(commonService, contextMenuService, keybindingService, instantiationService);
super(commonService, _dashboardService, contextMenuService, keybindingService, instantiationService);
let jobCacheObjectMap = this._jobManagementService.jobCacheObjectMap;
this._serverName = commonService.connectionManagementService.connectionInfo.connectionProfile.serverName;
let jobCache = jobCacheObjectMap[this._serverName];
@@ -121,7 +124,15 @@ export class JobsViewComponent extends JobManagementView implements OnInit {
}
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() {
@@ -141,7 +152,7 @@ export class JobsViewComponent extends JobManagementView implements OnInit {
enableColumnReorder: false,
rowHeight: ROW_HEIGHT,
enableCellNavigation: true,
forceFitColumns: true
forceFitColumns: 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.setItems(jobViews);
this.dataView.setFilter((item) => this.filter(item));
this.dataView.endUpdate();
this._table.autosizeColumns();
this._table.resizeCanvas();
@@ -341,17 +351,6 @@ export class JobsViewComponent extends JobManagementView implements OnInit {
});
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) => {
let nameWidth: number = data.grid.getColumnWidths()[1];
// 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">' +
'<td nowrap class=' + resultIndicatorClass + '></td>' +
'<td nowrap class="jobview-jobnametext">' + dataContext.name + '</td>' +
'<td nowrap class="jobview-jobnametext">' + escape(dataContext.name) + '</td>' +
'</tr></table>';
}
@@ -844,7 +843,7 @@ export class JobsViewComponent extends JobManagementView implements OnInit {
public openCreateJobDialog() {
let ownerUri: string = this._commonService.connectionManagementService.connectionInfo.ownerUri;
this._commandService.executeCommand('agent.openCreateJobDialog', ownerUri);
this._commandService.executeCommand('agent.openJobDialog', ownerUri);
}
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 { IAction } from 'vs/base/common/actions';
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 ROW_HEIGHT: number = 45;
export const ROW_HEIGHT: number = 30;
@Component({
selector: VIEW_SELECTOR,
@@ -73,9 +74,10 @@ export class OperatorsViewComponent extends JobManagementView implements OnInit
@Inject(IInstantiationService) instantiationService: IInstantiationService,
@Inject(forwardRef(() => CommonServiceInterface)) commonService: CommonServiceInterface,
@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;
}
@@ -86,7 +88,14 @@ export class OperatorsViewComponent extends JobManagementView implements OnInit
}
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() {
@@ -95,13 +104,6 @@ export class OperatorsViewComponent extends JobManagementView implements OnInit
column.rerenderOnResize = true;
return column;
});
let options = <Slick.GridOptions<any>>{
syncColumnCellResize: true,
enableColumnReorder: false,
rowHeight: ROW_HEIGHT,
enableCellNavigation: true,
forceFitColumns: true
};
this.dataView = new Slick.Data.DataView();
@@ -163,7 +165,7 @@ export class OperatorsViewComponent extends JobManagementView implements OnInit
public openCreateOperatorDialog() {
let ownerUri: string = this._commonService.connectionManagementService.connectionInfo.ownerUri;
this._commandService.executeCommand('agent.openCreateOperatorDialog', ownerUri);
this._commandService.executeCommand('agent.openOperatorDialog', ownerUri);
}
private refreshJobs() {

View File

@@ -29,9 +29,10 @@ import { TPromise } from 'vs/base/common/winjs.base';
import { IAction } from 'vs/base/common/actions';
import { ICommandService } from 'vs/platform/commands/common/commands';
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 ROW_HEIGHT: number = 45;
export const ROW_HEIGHT: number = 30;
@Component({
selector: VIEW_SELECTOR,
@@ -75,9 +76,10 @@ export class ProxiesViewComponent extends JobManagementView implements OnInit {
@Inject(IInstantiationService) instantiationService: IInstantiationService,
@Inject(forwardRef(() => CommonServiceInterface)) commonService: CommonServiceInterface,
@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;
}
@@ -88,7 +90,14 @@ export class ProxiesViewComponent extends JobManagementView implements OnInit {
}
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() {
@@ -97,13 +106,6 @@ export class ProxiesViewComponent extends JobManagementView implements OnInit {
column.rerenderOnResize = true;
return column;
});
let options = <Slick.GridOptions<any>>{
syncColumnCellResize: true,
enableColumnReorder: false,
rowHeight: ROW_HEIGHT,
enableCellNavigation: true,
forceFitColumns: true
};
this.dataView = new Slick.Data.DataView();
@@ -164,7 +166,11 @@ export class ProxiesViewComponent extends JobManagementView implements OnInit {
public openCreateProxyDialog() {
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() {

View File

@@ -4,8 +4,8 @@
*--------------------------------------------------------------------------------------------*/
import 'vs/css!./button';
import {
Component, Input, Inject, ChangeDetectorRef, forwardRef, ComponentFactoryResolver,
ViewChild, ViewChildren, ElementRef, Injector, OnDestroy, QueryList, AfterViewInit
Component, Input, Inject, ChangeDetectorRef, forwardRef,
ViewChild, ElementRef, OnDestroy, AfterViewInit
} from '@angular/core';
import * as sqlops from 'sqlops';
@@ -24,7 +24,13 @@ import { Color } from 'vs/base/common/color';
@Component({
selector: 'modelview-button',
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 {
@@ -119,9 +125,19 @@ export default class ButtonComponent extends ComponentWithIconBase implements IC
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 {
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);
}
protected convertSizeToNumber(size: number | string): number {
public convertSizeToNumber(size: number | string): number {
if (size && typeof (size) === 'string') {
if (size.toLowerCase().endsWith('px')) {
return +size.replace('px', '');
} else if (size.toLowerCase().endsWith('em')) {
return +size.replace('em', '') * 11;
}
} else if (!size) {
return 0;
}
return +size;
@@ -148,7 +151,7 @@ export abstract class ComponentBase extends Disposable implements IComponent, On
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 || '';
if (types.isUndefinedOrNull(size)) {
return defaultValue;

View File

@@ -25,7 +25,7 @@ import { IContextViewService } from 'vs/platform/contextview/browser/contextView
template: `
<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>
`
@@ -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.render(this._dropDownContainer.nativeElement);
this._register(this._selectBox);
this._register(attachSelectBoxStyler(this._selectBox, this.themeService));
this._register(this._selectBox.onDidSelect(e => {
if (!this.editable) {
@@ -187,7 +186,7 @@ export default class DropDownComponent extends ComponentBase implements ICompone
}
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[]) {

View File

@@ -45,7 +45,7 @@ class FormItem {
template: `
<div #container *ngIf="items" class="form-table" [style.padding]="getFormPadding()" [style.width]="getFormWidth()" [style.height]="getFormHeight()">
<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">
<model-component-wrapper [descriptor]="item.descriptor" [modelStore]="modelStore">
</model-component-wrapper>
@@ -56,7 +56,7 @@ class FormItem {
<ng-container *ngIf="isHorizontal(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>
<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 class="form-cell">
<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-item-row" [style.font-size]="getItemTitleFontSize(item)">
{{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 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)">
@@ -179,8 +179,12 @@ export default class FormContainer extends ContainerBase<FormItemLayout> impleme
}
private getItemTitleFontSize(item: FormItem): string {
let defaultFontSize = '14px';
if (this.isInGroup(item)) {
defaultFontSize = '12px';
}
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[] {

View File

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

View File

@@ -4,22 +4,25 @@
*--------------------------------------------------------------------------------------------*/
import {
Component, Input, Inject, ChangeDetectorRef, forwardRef, ComponentFactoryResolver,
ViewChild, ViewChildren, ElementRef, Injector, OnDestroy, QueryList, AfterViewInit
Component, Input, Inject, ChangeDetectorRef, forwardRef,
ViewChild, ElementRef, OnDestroy, AfterViewInit
} from '@angular/core';
import * as sqlops from 'sqlops';
import { ComponentBase } from 'sql/parts/modelComponents/componentBase';
import { IComponent, IComponentDescriptor, IModelStore, ComponentEventType } from 'sql/parts/modelComponents/interfaces';
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 { attachInputBoxStyler, attachListStyler } from 'vs/platform/theme/common/styler';
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
import * as nls from 'vs/nls';
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({
selector: 'modelview-inputBox',
@@ -73,11 +76,31 @@ export default class InputBoxComponent extends ComponentBase implements ICompone
if (this._textareaContainer) {
let textAreaInputOptions = Object.assign({}, inputOptions, { flexibleHeight: true, type: 'textarea' });
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.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 {
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 { attachTableStyler } from 'sql/common/theme/styler';
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 { 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 }[] {
return rows.map(row => {
let object: { [key: string]: string } = {};
row.forEach((val, index) => {
let columnName: string = (columns[index].value) ? columns[index].value : <string>columns[index];
object[columnName] = val;
public static transformData(rows: string[][], columns: any[]): { [key: string]: string }[] {
if (rows && columns) {
return rows.map(row => {
let object: { [key: string]: string } = {};
if (row.forEach) {
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 {
@@ -147,7 +152,7 @@ export default class TableComponent extends ComponentBase implements IComponent,
public setProperties(properties: { [key: string]: any; }): void {
super.setProperties(properties);
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._table.columns = this._tableColumns;
this._table.setData(this._tableData);

View File

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

View File

@@ -5,7 +5,7 @@
'use strict';
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 { IDisposable, dispose } from 'vs/base/common/lifecycle';
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 { ServerTreeView } from 'sql/parts/objectExplorer/viewlet/serverTreeView';
import { ICapabilitiesService } from 'sql/services/capabilities/capabilitiesService';
import * as vscode from 'vscode';
export const SERVICE_ID = 'ObjectExplorerService';
@@ -162,10 +161,16 @@ export class ObjectExplorerService implements IObjectExplorerService {
error(expandResponse.errorMessage);
}
let nodeStatus = this._sessions[expandResponse.sessionId].nodes[expandResponse.nodePath];
if (nodeStatus && nodeStatus.expandEmitter) {
nodeStatus.expandEmitter.fire(expandResponse);
} else {
let sessionStatus = this._sessions[expandResponse.sessionId];
let foundSession = false;
if (sessionStatus) {
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}`);
}
}
@@ -396,7 +401,7 @@ export class ObjectExplorerService implements IObjectExplorerService {
}
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),
isExpanded: treeNode => this.isExpanded(treeNode),
setNodeExpandedState: (treeNode, expandedState) => this.setNodeExpandedState(treeNode, expandedState),

View File

@@ -6,7 +6,7 @@
'use strict';
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 UUID from 'vs/base/common/uuid';
@@ -83,6 +83,23 @@ export class TreeNode {
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 {
var currentNode: TreeNode = this;
while (!currentNode.connection && currentNode.parent) {
@@ -149,18 +166,4 @@ export class TreeNode {
public setSelected(selected: boolean, clearOtherSelections?: boolean): Thenable<void> {
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
let serverGroupNameLabel = localize('connectionGroupName', 'Server group name');
this._bodyBuilder.div({ class: 'dialog-label' }, (labelContainer) => {
labelContainer.innerHtml(serverGroupNameLabel);
labelContainer.text(serverGroupNameLabel);
});
this._bodyBuilder.div({ class: 'input-divider' }, (inputCellContainer) => {
let errorMessage = localize('MissingGroupNameError', 'Group name is required.');
@@ -94,7 +94,7 @@ export class ServerGroupDialog extends Modal {
// Connection Group Description
let groupDescriptionLabel = localize('groupDescription', 'Group description');
this._bodyBuilder.div({ class: 'dialog-label' }, (labelContainer) => {
labelContainer.innerHtml(groupDescriptionLabel);
labelContainer.text(groupDescriptionLabel);
});
this._bodyBuilder.div({ class: 'input-divider' }, (inputCellContainer) => {
this._groupDescriptionInputBox = new InputBox(inputCellContainer.getHTMLElement(), this._contextViewService, {
@@ -105,7 +105,7 @@ export class ServerGroupDialog extends Modal {
// Connection Group Color
this._bodyBuilder.div({ class: 'dialog-label' }, (labelContainer) => {
let groupColorLabel = localize('groupColor', 'Group color');
labelContainer.innerHtml(groupColorLabel);
labelContainer.text(groupColorLabel);
});
this._bodyBuilder.div({ class: 'group-color-options' }, (groupColorContainer) => {

View File

@@ -33,7 +33,7 @@ export class RefreshAction extends Action {
id: string,
label: string,
tree: ITree,
private element: ConnectionProfile | TreeNode,
private element: IConnectionProfile | TreeNode,
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
@IObjectExplorerService private _objectExplorerService: IObjectExplorerService,
@IErrorMessageService private _errorMessageService: IErrorMessageService
@@ -88,80 +88,31 @@ export class DisconnectConnectionAction extends Action {
public static ID = 'objectExplorer.disconnect';
public static LABEL = localize('DisconnectAction', 'Disconnect');
private _disposables: IDisposable[] = [];
private _connectionProfile: ConnectionProfile;
private _container: HTMLElement;
constructor(
id: string,
label: string,
private _connectionProfile: ConnectionProfile,
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
@IObjectExplorerService private _objectExplorerService: IObjectExplorerService,
@IErrorMessageService private _errorMessageService: IErrorMessageService
) {
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> {
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) {
resolve(true);
}
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) => {
if (profileImpl) {
profileImpl.isDisconnecting = false;
}
resolve(true);
}
).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 {
public static ID = 'registeredServers.newQuery';
public static LABEL = localize('registeredServers.newQuery', 'New Query');
private _connectionProfile: ConnectionProfile;
get connectionProfile(): ConnectionProfile {
private _connectionProfile: IConnectionProfile;
get connectionProfile(): IConnectionProfile {
return this._connectionProfile;
}
set connectionProfile(profile: ConnectionProfile) {
set connectionProfile(profile: IConnectionProfile) {
this._connectionProfile = profile;
}
@@ -403,7 +349,7 @@ export class DeleteConnectionAction extends Action {
constructor(
id: string,
label: string,
private element: ConnectionProfile | ConnectionProfileGroup,
private element: IConnectionProfile | ConnectionProfileGroup,
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService
) {
super(id, label);
@@ -413,7 +359,6 @@ export class DeleteConnectionAction extends Action {
}
if (element instanceof ConnectionProfile) {
element = <ConnectionProfile>element;
let parent: ConnectionProfileGroup = element.parent;
if (parent && parent.id === Constants.unsavedGroupId) {
this.enabled = false;

View File

@@ -8,11 +8,14 @@ import { localize } from 'vs/nls';
import { TPromise } from 'vs/base/common/winjs.base';
import { Action } from 'vs/base/common/actions';
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 { TreeNode } from 'sql/parts/objectExplorer/common/treeNode';
import { ConnectionProfile } from 'sql/parts/connection/common/connectionProfile';
import {
NewQueryAction, ScriptSelectAction, EditDataAction, ScriptCreateAction,
ScriptSelectAction, EditDataAction, ScriptCreateAction,
ScriptExecuteAction, ScriptDeleteAction, ScriptAlterAction
} from 'sql/workbench/common/actions';
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 { IObjectExplorerService } from 'sql/parts/objectExplorer/common/objectExplorerService';
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 { ICapabilitiesService } from 'sql/services/capabilities/capabilitiesService';
import { ConnectionProfile } from 'sql/parts/connection/common/connectionProfile';
export class ObjectExplorerActionsContext {
public treeNode: TreeNode;
public connectionProfile: ConnectionProfile;
public container: HTMLElement;
public tree: ITree;
export class ObjectExplorerActionsContext implements sqlops.ObjectExplorerContext {
public connectionProfile: IConnectionProfile;
public nodeInfo: sqlops.NodeInfo;
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 {
private _objectExplorerTreeNode: TreeNode;
private _container: HTMLElement;
private _treeSelectionHandler: TreeSelectionHandler;
constructor(
id: string, label: string,
@IInstantiationService private _instantiationService: IInstantiationService,
@ICommandService commandService: ICommandService,
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
@IObjectExplorerService private _objectExplorerService: IObjectExplorerService
) {
super(id, label, commandService);
}
public run(actionContext: any): TPromise<boolean> {
public async run(actionContext: any): TPromise<boolean> {
this._treeSelectionHandler = this._instantiationService.createInstance(TreeSelectionHandler);
let profile: IConnectionProfile;
if (actionContext.connectionProfile) {
profile = actionContext.connectionProfile;
} else {
profile = TreeUpdateUtils.getConnectionProfile(<TreeNode>actionContext.treeNode);
if (actionContext instanceof ObjectExplorerActionsContext) {
if (actionContext.isConnectionNode) {
profile = actionContext.connectionProfile;
} else {
// Get the "correct" version from the tree
let treeNode = await getTreeNode(actionContext, this._objectExplorerService);
profile = TreeUpdateUtils.getConnectionProfile(treeNode);
}
}
this._treeSelectionHandler.onTreeActionStateChange(true);
@@ -72,18 +85,16 @@ export class ManageConnectionAction extends Action {
public static ID = 'objectExplorer.manage';
public static LABEL = localize('ManageAction', 'Manage');
private _connectionProfile: ConnectionProfile;
private _objectExplorerTreeNode: TreeNode;
private _treeSelectionHandler: TreeSelectionHandler;
protected _container: HTMLElement;
constructor(
id: string,
label: string,
private _tree: ITree,
@IConnectionManagementService protected _connectionManagementService: IConnectionManagementService,
@ICapabilitiesService protected _capabilitiesService: ICapabilitiesService,
@IInstantiationService private _instantiationService: IInstantiationService,
@IObjectExplorerService private _objectExplorerService?: IObjectExplorerService,
@IObjectExplorerService private _objectExplorerService: IObjectExplorerService
) {
super(id, label);
}
@@ -91,33 +102,37 @@ export class ManageConnectionAction extends Action {
run(actionContext: ObjectExplorerActionsContext): TPromise<any> {
this._treeSelectionHandler = this._instantiationService.createInstance(TreeSelectionHandler);
this._treeSelectionHandler.onTreeActionStateChange(true);
let self = this;
let promise = new TPromise<boolean>((resolve, reject) => {
this.doManage(actionContext).then((success) => {
this.done();
self.doManage(actionContext).then((success) => {
self.done();
resolve(success);
}, error => {
this.done();
self.done();
reject(error);
});
});
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) {
//set objectExplorerTreeNode for context menu clicks
this._connectionProfile = actionContext.connectionProfile;
this._objectExplorerTreeNode = actionContext.treeNode;
if (this._connectionProfile === undefined && TreeUpdateUtils.isDatabaseNode(this._objectExplorerTreeNode)) {
this._connectionProfile = TreeUpdateUtils.getConnectionProfile(<TreeNode>this._objectExplorerTreeNode);
// Must use a real connection profile for this action due to lookup
connectionProfile = ConnectionProfile.fromIConnectionProfile(this._capabilitiesService, actionContext.connectionProfile);
if (!actionContext.isConnectionNode) {
treeNode = await getTreeNode(actionContext, this._objectExplorerService);
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
// an OE node or a database node
return TPromise.wrap(true);
return true;
}
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,
// the node is already from an open OE session we don't need to create new session
if (TreeUpdateUtils.isAvailableDatabaseNode(this._objectExplorerTreeNode)) {
return this._connectionManagementService.showDashboard(this._connectionProfile);
if (TreeUpdateUtils.isAvailableDatabaseNode(treeNode)) {
return this._connectionManagementService.showDashboard(connectionProfile);
} 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 {
public static ID = 'objectExplorer.' + ScriptSelectAction.ID;
private _objectExplorerTreeNode: TreeNode;
private _container: HTMLElement;
private _treeSelectionHandler: TreeSelectionHandler;
constructor(
@@ -157,23 +171,23 @@ export class OEScriptSelectAction extends ScriptSelectAction {
@IQueryEditorService protected _queryEditorService: IQueryEditorService,
@IConnectionManagementService protected _connectionManagementService: IConnectionManagementService,
@IScriptingService protected _scriptingService: IScriptingService,
@IObjectExplorerService private _objectExplorerService: IObjectExplorerService,
@IInstantiationService private _instantiationService: IInstantiationService
) {
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);
if (actionContext instanceof ObjectExplorerActionsContext) {
//set objectExplorerTreeNode for context menu clicks
this._objectExplorerTreeNode = actionContext.treeNode;
this._container = actionContext.container;
this._objectExplorerTreeNode = await getTreeNode(actionContext, this._objectExplorerService);
}
this._treeSelectionHandler.onTreeActionStateChange(true);
var connectionProfile = TreeUpdateUtils.getConnectionProfile(<TreeNode>this._objectExplorerTreeNode);
var connectionProfile = TreeUpdateUtils.getConnectionProfile(this._objectExplorerTreeNode);
var ownerUri = this._connectionManagementService.getConnectionId(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) => {
this._treeSelectionHandler.onTreeActionStateChange(false);
@@ -185,7 +199,6 @@ export class OEScriptSelectAction extends ScriptSelectAction {
export class OEEditDataAction extends EditDataAction {
public static ID = 'objectExplorer.' + EditDataAction.ID;
private _objectExplorerTreeNode: TreeNode;
private _container: HTMLElement;
private _treeSelectionHandler: TreeSelectionHandler;
constructor(
@@ -193,17 +206,17 @@ export class OEEditDataAction extends EditDataAction {
@IQueryEditorService protected _queryEditorService: IQueryEditorService,
@IConnectionManagementService protected _connectionManagementService: IConnectionManagementService,
@IScriptingService protected _scriptingService: IScriptingService,
@IObjectExplorerService private _objectExplorerService: IObjectExplorerService,
@IInstantiationService private _instantiationService: IInstantiationService
) {
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);
if (actionContext instanceof ObjectExplorerActionsContext) {
//set objectExplorerTreeNode for context menu clicks
this._objectExplorerTreeNode = actionContext.treeNode;
this._container = actionContext.container;
this._objectExplorerTreeNode = await getTreeNode(actionContext, this._objectExplorerService);
}
this._treeSelectionHandler.onTreeActionStateChange(true);
var connectionProfile = TreeUpdateUtils.getConnectionProfile(<TreeNode>this._objectExplorerTreeNode);
@@ -219,7 +232,6 @@ export class OEEditDataAction extends EditDataAction {
export class OEScriptCreateAction extends ScriptCreateAction {
public static ID = 'objectExplorer.' + ScriptCreateAction.ID;
private _objectExplorerTreeNode: TreeNode;
private _container: HTMLElement;
private _treeSelectionHandler: TreeSelectionHandler;
constructor(
@@ -227,18 +239,18 @@ export class OEScriptCreateAction extends ScriptCreateAction {
@IQueryEditorService protected _queryEditorService: IQueryEditorService,
@IConnectionManagementService protected _connectionManagementService: IConnectionManagementService,
@IScriptingService protected _scriptingService: IScriptingService,
@IObjectExplorerService private _objectExplorerService: IObjectExplorerService,
@IInstantiationService private _instantiationService: IInstantiationService,
@IErrorMessageService protected _errorMessageService: IErrorMessageService
) {
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);
if (actionContext instanceof ObjectExplorerActionsContext) {
//set objectExplorerTreeNode for context menu clicks
this._objectExplorerTreeNode = actionContext.treeNode;
this._container = actionContext.container;
this._objectExplorerTreeNode = await getTreeNode(actionContext, this._objectExplorerService);
}
this._treeSelectionHandler.onTreeActionStateChange(true);
var connectionProfile = TreeUpdateUtils.getConnectionProfile(<TreeNode>this._objectExplorerTreeNode);
@@ -256,7 +268,6 @@ export class OEScriptCreateAction extends ScriptCreateAction {
export class OEScriptExecuteAction extends ScriptExecuteAction {
public static ID = 'objectExplorer.' + ScriptExecuteAction.ID;
private _objectExplorerTreeNode: TreeNode;
private _container: HTMLElement;
private _treeSelectionHandler: TreeSelectionHandler;
constructor(
@@ -264,18 +275,18 @@ export class OEScriptExecuteAction extends ScriptExecuteAction {
@IQueryEditorService protected _queryEditorService: IQueryEditorService,
@IConnectionManagementService protected _connectionManagementService: IConnectionManagementService,
@IScriptingService protected _scriptingService: IScriptingService,
@IObjectExplorerService private _objectExplorerService: IObjectExplorerService,
@IInstantiationService private _instantiationService: IInstantiationService,
@IErrorMessageService protected _errorMessageService: IErrorMessageService
) {
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);
if (actionContext instanceof ObjectExplorerActionsContext) {
//set objectExplorerTreeNode for context menu clicks
this._objectExplorerTreeNode = actionContext.treeNode;
this._container = actionContext.container;
this._objectExplorerTreeNode = await getTreeNode(actionContext, this._objectExplorerService);
}
this._treeSelectionHandler.onTreeActionStateChange(true);
var connectionProfile = TreeUpdateUtils.getConnectionProfile(<TreeNode>this._objectExplorerTreeNode);
@@ -293,7 +304,6 @@ export class OEScriptExecuteAction extends ScriptExecuteAction {
export class OEScriptAlterAction extends ScriptAlterAction {
public static ID = 'objectExplorer.' + ScriptAlterAction.ID;
private _objectExplorerTreeNode: TreeNode;
private _container: HTMLElement;
private _treeSelectionHandler: TreeSelectionHandler;
constructor(
@@ -301,18 +311,18 @@ export class OEScriptAlterAction extends ScriptAlterAction {
@IQueryEditorService protected _queryEditorService: IQueryEditorService,
@IConnectionManagementService protected _connectionManagementService: IConnectionManagementService,
@IScriptingService protected _scriptingService: IScriptingService,
@IObjectExplorerService private _objectExplorerService: IObjectExplorerService,
@IInstantiationService private _instantiationService: IInstantiationService,
@IErrorMessageService protected _errorMessageService: IErrorMessageService
) {
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);
if (actionContext instanceof ObjectExplorerActionsContext) {
//set objectExplorerTreeNode for context menu clicks
this._objectExplorerTreeNode = actionContext.treeNode;
this._container = actionContext.container;
this._objectExplorerTreeNode = await getTreeNode(actionContext, this._objectExplorerService);
}
this._treeSelectionHandler.onTreeActionStateChange(true);
var connectionProfile = TreeUpdateUtils.getConnectionProfile(<TreeNode>this._objectExplorerTreeNode);
@@ -330,7 +340,6 @@ export class OEScriptAlterAction extends ScriptAlterAction {
export class OEScriptDeleteAction extends ScriptDeleteAction {
public static ID = 'objectExplorer.' + ScriptDeleteAction.ID;
private _objectExplorerTreeNode: TreeNode;
private _container: HTMLElement;
private _treeSelectionHandler: TreeSelectionHandler;
constructor(
@@ -338,18 +347,18 @@ export class OEScriptDeleteAction extends ScriptDeleteAction {
@IQueryEditorService protected _queryEditorService: IQueryEditorService,
@IConnectionManagementService protected _connectionManagementService: IConnectionManagementService,
@IScriptingService protected _scriptingService: IScriptingService,
@IObjectExplorerService private _objectExplorerService: IObjectExplorerService,
@IInstantiationService private _instantiationService: IInstantiationService,
@IErrorMessageService protected _errorMessageService: IErrorMessageService
) {
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);
if (actionContext instanceof ObjectExplorerActionsContext) {
//set objectExplorerTreeNode for context menu clicks
this._objectExplorerTreeNode = actionContext.treeNode;
this._container = actionContext.container;
this._objectExplorerTreeNode = await getTreeNode(actionContext, this._objectExplorerService);
}
this._treeSelectionHandler.onTreeActionStateChange(true);
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 {
public static readonly objectExplorerElementClass = 'object-element-group';
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[]> {
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 { IAction } from 'vs/base/common/actions';
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 {
DisconnectConnectionAction, AddServerAction,
@@ -16,9 +18,7 @@ import {
}
from 'sql/parts/objectExplorer/viewlet/connectionTreeAction';
import {
DisconnectAction, ObjectExplorerActionUtilities,
ManageConnectionAction,
OEAction
ObjectExplorerActionUtilities, ManageConnectionAction, OEAction
} from 'sql/parts/objectExplorer/viewlet/objectExplorerActions';
import { TreeNode } from 'sql/parts/objectExplorer/common/treeNode';
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 { TreeUpdateUtils } from 'sql/parts/objectExplorer/viewlet/treeUpdateUtils';
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 { 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
@@ -37,7 +43,12 @@ export class ServerTreeActionProvider extends ContributableActionProvider {
constructor(
@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();
}
@@ -57,8 +68,11 @@ export class ServerTreeActionProvider extends ContributableActionProvider {
return TPromise.as(this.getConnectionProfileGroupActions(tree, element));
}
if (element instanceof TreeNode) {
var treeNode = <TreeNode>element;
return TPromise.as(this.getObjectExplorerNodeActions(tree, treeNode));
return TPromise.as(this.getObjectExplorerNodeActions({
tree: tree,
profile: element.getConnectionProfile(),
treeNode: element
}));
}
return TPromise.as([]);
@@ -75,23 +89,58 @@ export class ServerTreeActionProvider extends ContributableActionProvider {
/**
* Return actions for connection elements
*/
public getConnectionActions(tree: ITree, element: ConnectionProfile): IAction[] {
let actions: IAction[] = [];
actions.push(this._instantiationService.createInstance(ManageConnectionAction, ManageConnectionAction.ID, ManageConnectionAction.LABEL));
actions.push(this._instantiationService.createInstance(OEAction, NewQueryAction.ID, NewQueryAction.LABEL));
if (this._connectionManagementService.isProfileConnected(element)) {
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));
public getConnectionActions(tree: ITree, profile: ConnectionProfile): IAction[] {
return this.getAllActions({
tree: tree,
profile: profile
}, (context) => this.getBuiltinConnectionActions(context));
}
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));
}
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
*/
@@ -106,32 +155,50 @@ export class ServerTreeActionProvider extends ContributableActionProvider {
/**
* Return actions for OE elements
*/
public getObjectExplorerNodeActions(tree: ITree, treeNode: TreeNode): IAction[] {
let actions = [];
private getObjectExplorerNodeActions(context: ObjectExplorerContext): IAction[] {
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.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 {
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) {
supportedActions.forEach(action => {
actions.push(self._instantiationService.createInstance(action, action.ID, action.LABEL));
});
}
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));
}
this.addNewQueryAction(context, actions);
this.addScriptingActions(context, actions);
actions.push(this._instantiationService.createInstance(RefreshAction, RefreshAction.ID, RefreshAction.LABEL, context.tree, treeNode));
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 { TreeNode } from 'sql/parts/objectExplorer/common/treeNode';
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
@@ -73,26 +74,24 @@ export class ServerTreeController extends treedefaults.DefaultController {
event.stopPropagation();
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;
if (element instanceof TreeNode) {
actionContext = new ObjectExplorerActionsContext();
actionContext.container = event.target;
actionContext.treeNode = <TreeNode>element;
actionContext.tree = tree;
let context = new ObjectExplorerActionsContext();
context.nodeInfo = element.toNodeInfo();
// Note: getting DB name before, but intentionally not using treeUpdateUtils.getConnectionProfile as it replaces
// 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) {
actionContext = new ObjectExplorerActionsContext();
actionContext.container = event.target;
actionContext.connectionProfile = <ConnectionProfile>element;
actionContext.tree = tree;
let context = new ObjectExplorerActionsContext();
context.connectionProfile = element.toIConnectionProfile();
context.isConnectionNode = true;
actionContext = context;
} 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;
}

View File

@@ -31,12 +31,10 @@ export class ServerTreeDataSource implements IDataSource {
* No more than one element may use a given identifier.
*/
public getId(tree: ITree, element: any): string {
if (element instanceof ConnectionProfile) {
return (<ConnectionProfile>element).id;
} else if (element instanceof ConnectionProfileGroup) {
return (<ConnectionProfileGroup>element).id;
} else if (element instanceof TreeNode) {
return (<TreeNode>element).id;
if (element instanceof ConnectionProfile
|| element instanceof ConnectionProfileGroup
|| element instanceof TreeNode) {
return element.id;
} else {
return undefined;
}
@@ -49,9 +47,9 @@ export class ServerTreeDataSource implements IDataSource {
if (element instanceof ConnectionProfile) {
return true;
} else if (element instanceof ConnectionProfileGroup) {
return (<ConnectionProfileGroup>element).hasChildren();
return element.hasChildren();
} else if (element instanceof TreeNode) {
return !(<TreeNode>element).isAlwaysLeaf;
return !element.isAlwaysLeaf;
}
return false;
}
@@ -70,7 +68,7 @@ export class ServerTreeDataSource implements IDataSource {
} else if (element instanceof ConnectionProfileGroup) {
resolve((<ConnectionProfileGroup>element).getChildren());
} else if (element instanceof TreeNode) {
var node = <TreeNode>element;
var node = element;
if (node.children) {
resolve(node.children);
} else {
@@ -92,11 +90,11 @@ export class ServerTreeDataSource implements IDataSource {
*/
public getParent(tree: ITree, element: any): TPromise<any> {
if (element instanceof ConnectionProfile) {
return TPromise.as((<ConnectionProfile>element).getParent());
return TPromise.as(element.getParent());
} else if (element instanceof ConnectionProfileGroup) {
return TPromise.as((<ConnectionProfileGroup>element).getParent());
return TPromise.as(element.getParent());
} else if (element instanceof TreeNode) {
return TPromise.as(TreeUpdateUtils.getObjectExplorerParent(<TreeNode>element, this._connectionManagementService));
return TPromise.as(TreeUpdateUtils.getObjectExplorerParent(element, this._connectionManagementService));
} else {
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 {
if (templateId === ServerTreeRenderer.CONNECTION_TEMPLATE_ID) {
this.renderConnection(tree, element, templateData);
this.renderConnection(element, templateData);
} else if (templateId === ServerTreeRenderer.CONNECTION_GROUP_TEMPLATE_ID) {
this.renderConnectionProfileGroup(tree, element, templateData);
this.renderConnectionProfileGroup(element, templateData);
} else {
this.renderObjectExplorer(tree, element, templateData);
this.renderObjectExplorer(element, templateData);
}
}
private renderObjectExplorer(tree: ITree, treeNode: TreeNode, templateData: IObjectExplorerTemplateData): void {
var iconName = treeNode.nodeTypeId;
if (treeNode.nodeStatus) {
iconName = treeNode.nodeTypeId + '_' + treeNode.nodeStatus;
}
if (treeNode.nodeSubType) {
iconName = treeNode.nodeTypeId + '_' + treeNode.nodeSubType;
private renderObjectExplorer(treeNode: TreeNode, templateData: IObjectExplorerTemplateData): void {
// Use an explicitly defined iconType first. If not defined, fall back to using nodeType and
// other compount indicators instead.
let iconName: string = undefined;
if (treeNode.iconType) {
iconName = (typeof treeNode.iconType === 'string') ? treeNode.iconType : treeNode.iconType.id;
} else {
iconName = treeNode.nodeTypeId;
if (treeNode.nodeStatus) {
iconName = treeNode.nodeTypeId + '_' + treeNode.nodeStatus;
}
if (treeNode.nodeSubType) {
iconName = treeNode.nodeTypeId + '_' + treeNode.nodeSubType;
}
}
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));
}
templateData.icon.classList.remove(...tokens);
templateData.icon.classList.add('icon');
let iconLoweCaseName = iconName.toLocaleLowerCase();
templateData.icon.classList.add(iconLoweCaseName);
let iconLowerCaseName = iconName.toLocaleLowerCase();
templateData.icon.classList.add(iconLowerCaseName);
templateData.label.textContent = 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._connectionManagementService.isConnected(undefined, connection)) {
templateData.icon.classList.remove('disconnected');
@@ -157,9 +164,9 @@ export class ServerTreeRenderer implements IRenderer {
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 (connectionProfileGroup.color) {
rowElement.style.background = connectionProfileGroup.color;
@@ -179,7 +186,7 @@ export class ServerTreeRenderer implements IRenderer {
* Returns the first parent which contains the className
*/
private findParentElement(container: HTMLElement, className: string): HTMLElement {
var currentElement = container;
let currentElement = container;
while (currentElement) {
if (currentElement.className.includes(className)) {
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)) {
let treeNode = <TreeNode>selection[0];
let treeNode = selection[0];
if (TreeUpdateUtils.isAvailableDatabaseNode(treeNode)) {
connectionProfile = TreeUpdateUtils.getConnectionProfile(treeNode);
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 { TreeNode } from 'sql/parts/objectExplorer/common/treeNode';
import errors = require('vs/base/common/errors');
import { IConnectionProfile } from 'sql/parts/connection/common/interfaces';
export class TreeUpdateUtils {
@@ -113,7 +114,7 @@ export class TreeUpdateUtils {
}
public static connectIfNotConnected(
connection: ConnectionProfile,
connection: IConnectionProfile,
options: IConnectionCompletionOptions,
connectionManagementService: IConnectionManagementService,
tree: ITree): TPromise<ConnectionProfile> {
@@ -172,7 +173,7 @@ export class TreeUpdateUtils {
* @param connectionManagementService Connection management 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> {
return new TPromise<boolean>((resolve, reject) => {
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 { 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(
ProfilerEditor,
@@ -24,84 +24,213 @@ const profilerDescriptor = new EditorDescriptor(
Registry.as<IEditorRegistry>(EditorExtensions.Editors)
.registerEditor(profilerDescriptor, [new SyncDescriptor(ProfilerInput)]);
const profilerSessionTemplateSchema: IJSONSchema = {
description: nls.localize('profiler.settings.sessionTemplates', "Specifies session templates"),
type: 'array',
items: <IJSONSchema>{
type: 'object',
properties: {
name: {
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']
const profilerViewTemplateSchema: IJSONSchema = {
description: nls.localize('profiler.settings.viewTemplates', "Specifies view templates"),
type: 'array',
items: <IJSONSchema>{
type: 'object',
properties: {
name: {
type: 'string'
}
],
view: {
events: [
}
},
default: <Array<IProfilerViewTemplate>>[
{
name: 'Standard View',
columns: [
{
name: 'Audit Login',
columns: ['TextData', 'ApplicationName', 'NTUserName', 'LoginName', 'ClientProcessID', 'SPID', 'StartTime']
name: 'EventClass',
eventsMapped: ['name']
},
{
name: 'Audit Logout',
columns: ['ApplicationName', 'NTUserName', 'LoginName', 'CPU', 'Reads', 'Writes', 'Duration', 'ClientProcessID', 'SPID', 'StartTime', 'EndTime']
name: 'TextData',
eventsMapped: ['options_text', 'batch_text']
},
{
name: 'ExistingConnection',
columns: ['TextData', 'ApplicationName', 'NTUserName', 'LoginName', 'ClientProcessID', 'SPID', 'StartTime']
name: 'ApplicationName',
width: '1',
eventsMapped: ['client_app_name']
},
{
name: 'RPC:Completed',
columns: ['ApplicationName', 'NTUserName', 'LoginName', 'CPU', 'Reads', 'Writes', 'Duration', 'ClientProcessID', 'SPID', 'StartTime', 'EndTime', 'BinaryData']
name: 'NTUserName',
eventsMapped: ['nt_username']
},
{
name: 'SQL:BatchCompleted',
columns: ['TextData', 'ApplicationName', 'NTUserName', 'LoginName', 'CPU', 'Reads', 'Writes', 'Duration', 'ClientProcessID', 'SPID', 'StartTime', 'EndTime', 'BinaryData']
name: 'LoginName',
eventsMapped: ['server_principal_name']
},
{
name: 'SQL:BatchStarting',
columns: ['TextData', 'ApplicationName', 'NTUserName', 'LoginName', 'ClientProcessID', 'SPID', 'StartTime']
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 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 dashboardConfig: IConfigurationNode = {
id: 'Profiler',
type: 'object',
properties: {
[PROFILER_SESSION_TEMPLATE_SETTINGS]: profilerSessionTemplateSchema
[PROFILER_VIEW_TEMPLATE_SETTINGS]: profilerViewTemplateSchema
}
};

View File

@@ -55,13 +55,17 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'profiler.newProfiler',
weight: KeybindingsRegistry.WEIGHT.builtinExtension(),
when: undefined,
primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_P,
mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_P },
primary: KeyMod.Alt | KeyCode.KEY_P,
mac: { primary: KeyMod.WinCtrl | KeyMod.Alt | KeyCode.KEY_P },
handler: CommandsRegistry.getCommand('profiler.newProfiler').handler
});
CommandsRegistry.registerCommand({
id: 'profiler.start',
KeybindingsRegistry.registerCommandAndKeybindingRule({
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) => {
let profilerService: IProfilerService = accessor.get(IProfilerService);
let editorService: IWorkbenchEditorService = accessor.get(IWorkbenchEditorService);
@@ -69,23 +73,14 @@ CommandsRegistry.registerCommand({
let activeEditor = editorService.getActiveEditor();
if (activeEditor instanceof ProfilerEditor) {
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);
}
});
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> {
this.enabled = false;
input.data.clear();
return TPromise.wrap(this._profilerService.startSession(input.id));
}
@@ -127,7 +126,6 @@ export class ProfilerStop extends Action {
}
public run(input: ProfilerInput): TPromise<boolean> {
this.enabled = false;
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");
constructor(id: string, label: string) {
super(id, label, 'stop');
super(id, label);
}
run(input: ProfilerInput): TPromise<void> {
@@ -148,14 +146,15 @@ export class ProfilerClear extends Action {
export class ProfilerAutoScroll extends Action {
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) {
super(id, label, 'stop');
super(id, label);
}
run(input: ProfilerInput): TPromise<boolean> {
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 });
return TPromise.as(true);
}

View File

@@ -360,7 +360,10 @@ export class ProfilerColumnEditorDialog extends Modal {
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 {
/*
this._element.getUnsortedChildren().forEach(e => {
let origEvent = this._input.sessionTemplate.view.events.find(i => i.name === e.id);
if (e.indeterminate) {
@@ -387,9 +390,13 @@ export class ProfilerColumnEditorDialog extends Modal {
}, []);
newColumns.unshift('EventClass');
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 {
/*
this._element = new SessionItem(this._input.sessionTemplate.name, this._selectedValue === 0 ? 'event' : 'column');
this._input.sessionTemplate.events.forEach(item => {
let event = new EventItem(item.name, this._element);
@@ -402,6 +409,7 @@ export class ProfilerColumnEditorDialog extends Modal {
});
this._tree.setInput(this._element);
this._tree.layout(DOM.getTotalHeight(this._treeContainer));
*/
}
protected layout(height?: number): void {

View File

@@ -166,6 +166,7 @@ export class ProfilerTableEditor extends BaseEditor implements IProfilerControll
public layout(dimension: Dimension): void {
this._currentDimensions = dimension;
this._profilerTable.layout(dimension);
this._profilerTable.autosizeColumns();
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 { Table } from 'sql/base/browser/ui/table/table';
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 { attachTableStyler } from 'sql/common/theme/styler';
import { IProfilerStateChangedEvent } from './profilerState';
@@ -50,6 +50,7 @@ class BasicView extends View {
private _previousSize: number;
private _collapsed: boolean;
public headerSize: number;
constructor(
initialSize: number,
private _element: HTMLElement,
@@ -58,6 +59,7 @@ class BasicView extends View {
opts: IViewOptions
) {
super(initialSize, opts);
this._previousSize = initialSize;
}
render(container: HTMLElement, orientation: Orientation): void {
@@ -114,8 +116,9 @@ export class ProfilerEditor extends BaseEditor {
private _profilerEditorContextKey: IContextKey<boolean>;
private _sessionTemplateSelector: SelectBox;
private _sessionTemplates: Array<IProfilerSessionTemplate>;
private _viewTemplateSelector: SelectBox;
private _viewTemplates: Array<IProfilerViewTemplate>;
private _connectionInfoText: HTMLElement;
// Actions
private _connectAction: Actions.ProfilerConnect;
@@ -125,6 +128,7 @@ export class ProfilerEditor extends BaseEditor {
private _autoscrollAction: Actions.ProfilerAutoScroll;
private _collapsedPanelAction: Actions.ProfilerCollapsablePanelAction;
constructor(
@ITelemetryService telemetryService: ITelemetryService,
@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._autoscrollAction = this._instantiationService.createInstance(Actions.ProfilerAutoScroll, Actions.ProfilerAutoScroll.ID, Actions.ProfilerAutoScroll.LABEL);
this._sessionTemplates = this._profilerService.getSessionTemplates();
this._sessionTemplateSelector = new SelectBox(this._sessionTemplates.map(i => i.name), 'Standard', this._contextViewService);
this._register(this._sessionTemplateSelector.onDidSelect(e => {
this._viewTemplates = this._profilerService.getViewTemplates();
this._viewTemplateSelector = new SelectBox(this._viewTemplates.map(i => i.name), 'Standard View', this._contextViewService);
this._register(this._viewTemplateSelector.onDidSelect(e => {
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');
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([
{ action: this._startAction },
{ action: this._stopAction },
{ element: dropdownContainer },
{ element: Taskbar.createTaskbarSeparator() },
{ action: this._pauseAction },
{ 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(() => {
this._profilerTableEditor.setInput(input);
if (input.sessionTemplate) {
this._sessionTemplateSelector.selectWithOptionName(input.sessionTemplate.name);
if (input.viewTemplate) {
this._viewTemplateSelector.selectWithOptionName(input.viewTemplate.name);
} else {
input.sessionTemplate = this._sessionTemplates.find(i => i.name === 'Standard');
input.viewTemplate = this._viewTemplates.find(i => i.name === 'Standard View');
}
this._actionBar.context = input;
@@ -360,6 +374,7 @@ export class ProfilerEditor extends BaseEditor {
autoscroll: true,
isPanelCollapsed: true
});
this._connectionInfoText.innerText = input.connectionName;
this._profilerTableEditor.updateState();
this._splitView.layout();
this._profilerTableEditor.focus();
@@ -401,10 +416,7 @@ export class ProfilerEditor extends BaseEditor {
if (e.isConnected) {
this._connectAction.connected = this.input.state.isConnected;
if (this.input.state.isConnected) {
this._sessionTemplateSelector.disable();
} else {
this._sessionTemplateSelector.enable();
if (!this.input.state.isConnected) {
this._startAction.enabled = this.input.state.isConnected;
this._stopAction.enabled = false;
this._pauseAction.enabled = false;

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