Compare commits

..

20 Commits
1.1.1 ... 1.1.4

Author SHA1 Message Date
Anthony Dresser
5d63905056 Rework timeSeries in chart viewer (#2987)
* rework timeSeries in chart viewer

* rework important to fix tests
2018-10-29 13:12:49 -07:00
Karl Burtram
cb224fbc74 Update Azure Data Studio to 1.1.4 2018-10-29 12:05:16 -07:00
Karl Burtram
067846099b Format JSON and XML output when clicking resultgrid link (#3024) 2018-10-29 12:04:00 -07:00
Karl Burtram
0e14908360 Merge 98d06b2892 2018-10-29 12:00:03 -07:00
Anthony Dresser
27735dd68b Clean up result tab better (#3015)
* do a better job cleaning up results tab

* formatting
2018-10-29 11:54:42 -07:00
Anthony Dresser
a8eaf28884 Fix time series (#2985)
* fix time series type string

* remove unused code
2018-10-29 11:54:32 -07:00
Karl Burtram
2c007115f7 Fix query plan scrollbars (#2927)
* Fix query plan scrollbars

* Remove toString
2018-10-17 12:04:42 -07:00
Chris LaFreniere
ac47fb84a8 Fix Default Height for Editor Component (#2920)
Editor component didn't have a minimum height set, so fixing this by passing through a minimum height to EditorComponent. Now, if the scrollable height of the editor is less than the minimum height, we use the minimum height as the height of the component.

Also fixed an issue where the markdown code editor's height was far too high. Now we're calculating the height on the layout() call, which gets called every time we display the markdown editor.
2018-10-17 10:18:04 -07:00
Aditya Bist
7ba14a3925 schedules now get added in edit job (#2915) 2018-10-16 22:51:54 -07:00
Anthony Dresser
67514ccc5f change scroll container to fix ui glitch (#2924) 2018-10-16 21:55:21 -07:00
Anthony Dresser
6ee3886ecf clear out plan xml on executes (#2921) 2018-10-16 18:31:19 -07:00
Anthony Dresser
76282ed1ef Look for showplan colum name (#2919)
* change method for finding show plan through column name

* formatting
2018-10-16 18:12:01 -07:00
Anthony Dresser
bfa9e8c495 Handle query plan flow problems (#2918)
* modify the query plan work flow to account for some errors

* formatting
2018-10-16 16:15:47 -07:00
Aditya Bist
425eecf692 Agent/edit steps (#2846)
* steps can be now edited

* edit jobs now works with steps, alerts, schedules etc

* fixed bug when new step in new dialog would fail
2018-10-16 13:24:43 -07:00
Anthony Dresser
ac1f7542a9 change way we show query plan (#2866) 2018-10-15 17:18:14 -07:00
Karl Burtram
225d168fdd Add SQL vNext to recommended extensions list (#2858) 2018-10-15 16:10:00 -07:00
Karl Burtram
e073b2cf42 Change 'Clear All' to 'Show All Connections' (#2865) 2018-10-15 16:04:36 -07:00
Karl Burtram
1cb366d822 Update product name in Register Files setup checkbox (#2857) 2018-10-15 13:55:02 -07:00
Kevin Cunnane
c9c8e30ced Remove os.tmpDir deprecation warning (#2855)
- Import extension used very old version of telemetry. Updating avoids sending deprecation warning to console which CSS requested we fix.
2018-10-15 13:41:54 -07:00
Karl Burtram
c50941ac7d Update Azure Data Studio to 1.1.3 2018-10-15 13:15:36 -07:00
50 changed files with 693 additions and 332 deletions

View File

@@ -68,7 +68,7 @@ Type: filesandordirs; Name: "{app}\_"
[Tasks] [Tasks]
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: checkedonce; Check: IsNotUpdate Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: checkedonce; Check: IsNotUpdate
Name: "quicklaunchicon"; Description: "{cm:CreateQuickLaunchIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked; OnlyBelowVersion: 0,6.1 Name: "quicklaunchicon"; Description: "{cm:CreateQuickLaunchIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked; OnlyBelowVersion: 0,6.1
Name: "associatewithfiles"; Description: "{cm:AssociateWithFiles,{#NameShort}}"; GroupDescription: "{cm:Other}"; Flags: unchecked Name: "associatewithfiles"; Description: "{cm:AssociateWithFiles,{#NameLong}}"; GroupDescription: "{cm:Other}"; Flags: unchecked
Name: "addtopath"; Description: "{cm:AddToPath}"; GroupDescription: "{cm:Other}" Name: "addtopath"; Description: "{cm:AddToPath}"; GroupDescription: "{cm:Other}"
Name: "runcode"; Description: "{cm:RunAfter,{#NameShort}}"; GroupDescription: "{cm:Other}"; Check: WizardSilent Name: "runcode"; Description: "{cm:RunAfter,{#NameShort}}"; GroupDescription: "{cm:Other}"; Check: WizardSilent

View File

@@ -26,12 +26,6 @@
"outputChannels": [ "outputChannels": [
"sqlagent" "sqlagent"
], ],
"commands": [
{
"command": "agent.openNewStepDialog",
"title": "agent.openNewStepDialog"
}
],
"dashboard.tabs": [ "dashboard.tabs": [
{ {
"id": "data-management-agent", "id": "data-management-agent",

View File

@@ -35,4 +35,5 @@ export class AgentUtils {
} }
return this._queryProvider; return this._queryProvider;
} }
} }

View File

@@ -136,8 +136,13 @@ export class JobData implements IAgentDialogData {
} }
public addJobSchedule(schedule: sqlops.AgentJobScheduleInfo) { public addJobSchedule(schedule: sqlops.AgentJobScheduleInfo) {
let existingSchedule = this.jobSchedules.find(item => item.name === schedule.name); if (this.jobSchedules) {
if (!existingSchedule) { let existingSchedule = this.jobSchedules.find(item => item.name === schedule.name);
if (!existingSchedule) {
this.jobSchedules.push(schedule);
}
} else {
this.jobSchedules = [];
this.jobSchedules.push(schedule); this.jobSchedules.push(schedule);
} }
} }

View File

@@ -4,20 +4,22 @@
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
'use strict'; 'use strict';
import * as sqlops from 'sqlops';
import * as nls from 'vscode-nls';
import * as vscode from 'vscode';
import { AgentUtils } from '../agentUtils'; import { AgentUtils } from '../agentUtils';
import { IAgentDialogData, AgentDialogMode } from '../interfaces'; import { IAgentDialogData, AgentDialogMode } from '../interfaces';
import { JobData } from './jobData'; import { JobData } from './jobData';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle(); const localize = nls.loadMessageBundle();
export class JobStepData implements IAgentDialogData { export class JobStepData implements IAgentDialogData {
// Error Messages // Error Messages
private readonly CreateStepErrorMessage_JobNameIsEmpty = localize('stepData.jobNameRequired', 'Job name must be provided'); private static readonly CreateStepErrorMessage_JobNameIsEmpty = localize('stepData.jobNameRequired', 'Job name must be provided');
private readonly CreateStepErrorMessage_StepNameIsEmpty = localize('stepData.stepNameRequired', 'Step name must be provided'); private static readonly CreateStepErrorMessage_StepNameIsEmpty = localize('stepData.stepNameRequired', 'Step name must be provided');
public dialogMode: AgentDialogMode = AgentDialogMode.CREATE; public dialogMode: AgentDialogMode;
public ownerUri: string; public ownerUri: string;
public jobId: string; public jobId: string;
public jobName: string; public jobName: string;
@@ -28,8 +30,8 @@ export class JobStepData implements IAgentDialogData {
public id: number; public id: number;
public failureAction: string; public failureAction: string;
public successAction: string; public successAction: string;
public failStepId: number;
public successStepId: number; public successStepId: number;
public failStepId: number;
public command: string; public command: string;
public commandExecutionSuccessCode: number; public commandExecutionSuccessCode: number;
public databaseName: string; public databaseName: string;
@@ -43,10 +45,12 @@ export class JobStepData implements IAgentDialogData {
public retryAttempts: number; public retryAttempts: number;
public retryInterval: number; public retryInterval: number;
public proxyName: string; public proxyName: string;
private jobModel: JobData;
constructor(ownerUri:string, jobModel?: JobData) { constructor(ownerUri:string, jobModel?: JobData) {
this.ownerUri = ownerUri; this.ownerUri = ownerUri;
this.jobName = jobModel.name; this.jobName = jobModel.name;
this.jobModel = jobModel;
} }
public async initialize() { public async initialize() {
@@ -54,47 +58,35 @@ export class JobStepData implements IAgentDialogData {
public async save() { public async save() {
let agentService = await AgentUtils.getAgentService(); let agentService = await AgentUtils.getAgentService();
agentService.createJobStep(this.ownerUri, { let result: any;
jobId: this.jobId, if (this.dialogMode === AgentDialogMode.CREATE) {
jobName: this.jobName, if (this.jobModel && this.jobModel.dialogMode === AgentDialogMode.CREATE) {
script: this.script, // create job -> create step
scriptName: this.scriptName, Promise.resolve(this);
stepName: this.stepName, return;
subSystem: this.subSystem, } else {
id: this.id, // edit job -> create step
failureAction: this.failureAction, result = await agentService.createJobStep(this.ownerUri, JobStepData.convertToAgentJobStepInfo(this));
successAction: this.successAction,
failStepId: this.failStepId,
successStepId: this.successStepId,
command: this.command,
commandExecutionSuccessCode: this.commandExecutionSuccessCode,
databaseName: this.databaseName,
databaseUserName: this.databaseUserName,
server: this.server,
outputFileName: this.outputFileName,
appendToLogFile: this.appendToLogFile,
appendToStepHist: this.appendToStepHist,
writeLogToTable: this.writeLogToTable,
appendLogToTable: this.appendLogToTable,
retryAttempts: this.retryAttempts,
retryInterval: this.retryInterval,
proxyName: this.proxyName
}).then(result => {
if (result && result.success) {
console.info(result);
} }
}); } else if (this.jobModel && this.jobModel.dialogMode === AgentDialogMode.EDIT) {
// edit job -> edit step
result = await agentService.updateJobStep(this.ownerUri, this.stepName, JobStepData.convertToAgentJobStepInfo(this));
}
if (!result || !result.success) {
vscode.window.showErrorMessage(
localize('jobStepData.saveErrorMessage', "Step update failed '{0}'", result.errorMessage ? result.errorMessage : 'Unknown'));
}
} }
public validate(): { valid: boolean, errorMessages: string[] } { public validate(): { valid: boolean, errorMessages: string[] } {
let validationErrors: string[] = []; let validationErrors: string[] = [];
if (!(this.stepName && this.stepName.trim())) { if (!(this.stepName && this.stepName.trim())) {
validationErrors.push(this.CreateStepErrorMessage_StepNameIsEmpty); validationErrors.push(JobStepData.CreateStepErrorMessage_StepNameIsEmpty);
} }
if (!(this.jobName && this.jobName.trim())) { if (!(this.jobName && this.jobName.trim())) {
validationErrors.push(this.CreateStepErrorMessage_JobNameIsEmpty); validationErrors.push(JobStepData.CreateStepErrorMessage_JobNameIsEmpty);
} }
return { return {
@@ -102,4 +94,66 @@ export class JobStepData implements IAgentDialogData {
errorMessages: validationErrors errorMessages: validationErrors
}; };
} }
public static convertToJobStepData(jobStepInfo: sqlops.AgentJobStepInfo, jobData: JobData) {
let stepData = new JobStepData(jobData.ownerUri, jobData);
stepData.ownerUri = jobData.ownerUri;
stepData.jobId = jobStepInfo.jobId;
stepData.jobName = jobStepInfo.jobName;
stepData.script = jobStepInfo.script;
stepData.scriptName = jobStepInfo.scriptName,
stepData.stepName = jobStepInfo.stepName,
stepData.subSystem = jobStepInfo.subSystem,
stepData.id = jobStepInfo.id,
stepData.failureAction = jobStepInfo.failureAction,
stepData.successAction = jobStepInfo.successAction,
stepData.failStepId = jobStepInfo.failStepId,
stepData.successStepId = jobStepInfo.successStepId,
stepData.command = jobStepInfo.command,
stepData.commandExecutionSuccessCode = jobStepInfo.commandExecutionSuccessCode,
stepData.databaseName = jobStepInfo.databaseName,
stepData.databaseUserName = jobStepInfo.databaseUserName,
stepData.server = jobStepInfo.server,
stepData.outputFileName = jobStepInfo.outputFileName,
stepData.appendToLogFile = jobStepInfo.appendToLogFile,
stepData.appendToStepHist = jobStepInfo.appendToStepHist,
stepData.writeLogToTable = jobStepInfo.writeLogToTable,
stepData.appendLogToTable = jobStepInfo.appendLogToTable,
stepData.retryAttempts = jobStepInfo.retryAttempts,
stepData.retryInterval = jobStepInfo.retryInterval,
stepData.proxyName = jobStepInfo.proxyName;
stepData.dialogMode = AgentDialogMode.EDIT;
return stepData;
}
public static convertToAgentJobStepInfo(jobStepData: JobStepData): sqlops.AgentJobStepInfo {
let result: sqlops.AgentJobStepInfo = {
jobId: jobStepData.jobId,
jobName: jobStepData.jobName,
script: jobStepData.script,
scriptName: jobStepData.scriptName,
stepName: jobStepData.stepName,
subSystem: jobStepData.subSystem,
id: jobStepData.id,
failureAction: jobStepData.failureAction,
successAction: jobStepData.successAction,
failStepId: jobStepData.failStepId,
successStepId: jobStepData.successStepId,
command: jobStepData.command,
commandExecutionSuccessCode: jobStepData.commandExecutionSuccessCode,
databaseName: jobStepData.databaseName,
databaseUserName: jobStepData.databaseUserName,
server: jobStepData.server,
outputFileName: jobStepData.outputFileName,
appendToLogFile: jobStepData.appendToLogFile,
appendToStepHist: jobStepData.appendToStepHist,
writeLogToTable: jobStepData.writeLogToTable,
appendLogToTable: jobStepData.appendLogToTable,
retryAttempts: jobStepData.retryAttempts,
retryInterval: jobStepData.retryInterval,
proxyName: jobStepData.proxyName
};
return result;
}
} }

View File

@@ -13,9 +13,11 @@ export class PickScheduleData implements IAgentDialogData {
public ownerUri: string; public ownerUri: string;
public schedules: sqlops.AgentJobScheduleInfo[]; public schedules: sqlops.AgentJobScheduleInfo[];
public selectedSchedule: sqlops.AgentJobScheduleInfo; public selectedSchedule: sqlops.AgentJobScheduleInfo;
private jobName: string;
constructor(ownerUri:string) { constructor(ownerUri:string, jobName: string) {
this.ownerUri = ownerUri; this.ownerUri = ownerUri;
this.jobName = jobName;
} }
public async initialize() { public async initialize() {
@@ -27,5 +29,8 @@ export class PickScheduleData implements IAgentDialogData {
} }
public async save() { public async save() {
let agentService = await AgentUtils.getAgentService();
this.selectedSchedule.jobName = this.jobName;
let result = await agentService.createJobSchedule(this.ownerUri, this.selectedSchedule);
} }
} }

View File

@@ -108,12 +108,14 @@ export class JobDialog extends AgentDialog<JobData> {
// Alert tab controls // Alert tab controls
private alertsTable: sqlops.TableComponent; private alertsTable: sqlops.TableComponent;
private newAlertButton: sqlops.ButtonComponent; private newAlertButton: sqlops.ButtonComponent;
private isEdit: boolean = false;
constructor(ownerUri: string, jobInfo: sqlops.AgentJobInfo = undefined) { constructor(ownerUri: string, jobInfo: sqlops.AgentJobInfo = undefined) {
super( super(
ownerUri, ownerUri,
new JobData(ownerUri, jobInfo), new JobData(ownerUri, jobInfo),
jobInfo ? JobDialog.EditDialogTitle : JobDialog.CreateDialogTitle); jobInfo ? JobDialog.EditDialogTitle : JobDialog.CreateDialogTitle);
this.isEdit = jobInfo ? true : false;
} }
protected async initializeDialog() { protected async initializeDialog() {
@@ -235,13 +237,17 @@ export class JobDialog extends AgentDialog<JobData> {
width: 80 width: 80
}).component(); }).component();
let stepDialog = new JobStepDialog(this.model.ownerUri, '' , data.length + 1, this.model); let stepDialog = new JobStepDialog(this.model.ownerUri, '' , this.model);
stepDialog.onSuccess((step) => { stepDialog.onSuccess((step) => {
if (!this.model.jobSteps) {
this.model.jobSteps = [];
}
this.model.jobSteps.push(step); this.model.jobSteps.push(step);
this.stepsTable.data = this.convertStepsToData(this.model.jobSteps); this.stepsTable.data = this.convertStepsToData(this.model.jobSteps);
}); });
this.newStepButton.onDidClick((e)=>{ this.newStepButton.onDidClick((e)=>{
if (this.nameTextBox.value && this.nameTextBox.value.length > 0) { if (this.nameTextBox.value && this.nameTextBox.value.length > 0) {
stepDialog.jobName = this.nameTextBox.value;
stepDialog.openDialog(); stepDialog.openDialog();
} else { } else {
this.dialog.message = { text: this.BlankJobNameErrorText }; this.dialog.message = { text: this.BlankJobNameErrorText };
@@ -267,17 +273,15 @@ export class JobDialog extends AgentDialog<JobData> {
// one step selection // one step selection
if (this.stepsTable.selectedRows.length === 1) { if (this.stepsTable.selectedRows.length === 1) {
let rowNumber = this.stepsTable.selectedRows[0]; let rowNumber = this.stepsTable.selectedRows[0];
let stepData = steps[rowNumber]; let stepData = this.model.jobSteps[rowNumber];
this.deleteStepButton.enabled = true; this.deleteStepButton.enabled = true;
this.editStepButton.enabled = true; this.editStepButton.enabled = true;
this.editStepButton.onDidClick((e) => { this.editStepButton.onDidClick(() => {
// implement edit steps let stepDialog = new JobStepDialog(this.model.ownerUri, '' , this.model, stepData);
stepDialog.openDialog();
// let stepDialog = new JobStepDialog(this.model.ownerUri, this.nameTextBox.value, '' , 1, this.model);
// stepDialog.openNewStepDialog();
}); });
this.deleteStepButton.onDidClick((e) => { this.deleteStepButton.onDidClick(() => {
AgentUtils.getAgentService().then((agentService) => { AgentUtils.getAgentService().then((agentService) => {
let steps = this.model.jobSteps ? this.model.jobSteps : []; let steps = this.model.jobSteps ? this.model.jobSteps : [];
agentService.deleteJobStep(this.ownerUri, stepData).then((result) => { agentService.deleteJobStep(this.ownerUri, stepData).then((result) => {
@@ -371,12 +375,12 @@ export class JobDialog extends AgentDialog<JobData> {
label: this.PickScheduleButtonString, label: this.PickScheduleButtonString,
width: 80 width: 80
}).component(); }).component();
this.pickScheduleButton.onDidClick((e)=>{ this.pickScheduleButton.onDidClick((e)=>{
let pickScheduleDialog = new PickScheduleDialog(this.model.ownerUri); let pickScheduleDialog = new PickScheduleDialog(this.model.ownerUri, this.model.name);
pickScheduleDialog.onSuccess((dialogModel) => { pickScheduleDialog.onSuccess((dialogModel) => {
let selectedSchedule = dialogModel.selectedSchedule; let selectedSchedule = dialogModel.selectedSchedule;
if (selectedSchedule) { if (selectedSchedule) {
selectedSchedule.jobName = this.model.name;
this.model.addJobSchedule(selectedSchedule); this.model.addJobSchedule(selectedSchedule);
this.populateScheduleTable(); this.populateScheduleTable();
} }

View File

@@ -10,6 +10,7 @@ import { JobStepData } from '../data/jobStepData';
import { AgentUtils } from '../agentUtils'; import { AgentUtils } from '../agentUtils';
import { JobData } from '../data/jobData'; import { JobData } from '../data/jobData';
import { AgentDialog } from './agentDialog'; import { AgentDialog } from './agentDialog';
import { AgentDialogMode } from '../interfaces';
const path = require('path'); const path = require('path');
const localize = nls.loadMessageBundle(); const localize = nls.loadMessageBundle();
@@ -19,7 +20,8 @@ export class JobStepDialog extends AgentDialog<JobStepData> {
// TODO: localize // TODO: localize
// Top level // Top level
// //
private readonly DialogTitle: string = localize('jobStepDialog.newJobStep', 'New Job Step'); private static readonly NewDialogTitle: string = localize('jobStepDialog.newJobStep', 'New Job Step');
private static readonly EditDialogTitle: string = localize('jobStepDialog.editJobStep', 'Edit Job Step');
private readonly FileBrowserDialogTitle: string = localize('jobStepDialog.fileBrowserTitle', 'Locate Database Files - '); private readonly FileBrowserDialogTitle: string = localize('jobStepDialog.fileBrowserTitle', 'Locate Database Files - ');
private readonly OkButtonText: string = localize('jobStepDialog.ok', 'OK'); private readonly OkButtonText: string = localize('jobStepDialog.ok', 'OK');
private readonly CancelButtonText: string = localize('jobStepDialog.cancel', 'Cancel'); private readonly CancelButtonText: string = localize('jobStepDialog.cancel', 'Cancel');
@@ -102,24 +104,32 @@ export class JobStepDialog extends AgentDialog<JobStepData> {
// Checkbox // Checkbox
private appendToExistingFileCheckbox: sqlops.CheckBoxComponent; private appendToExistingFileCheckbox: sqlops.CheckBoxComponent;
private logToTableCheckbox: sqlops.CheckBoxComponent; private logToTableCheckbox: sqlops.CheckBoxComponent;
private logStepOutputHistoryCheckbox: sqlops.CheckBoxComponent;
private fileBrowserTree: sqlops.FileBrowserTreeComponent; private fileBrowserTree: sqlops.FileBrowserTreeComponent;
private jobModel: JobData; private jobModel: JobData;
private jobName: string; public jobName: string;
private server: string; private server: string;
private stepId: number; private stepId: number;
private isEdit: boolean;
constructor( constructor(
ownerUri: string, ownerUri: string,
server: string, server: string,
stepId: number, jobModel: JobData,
jobModel?: JobData jobStepInfo?: sqlops.AgentJobStepInfo,
) { ) {
super(ownerUri, new JobStepData(ownerUri, jobModel), 'New Step'); super(ownerUri,
this.stepId = stepId; jobStepInfo ? JobStepData.convertToJobStepData(jobStepInfo, jobModel) : new JobStepData(ownerUri, jobModel),
this.jobName = jobModel.name; jobStepInfo ? JobStepDialog.EditDialogTitle : JobStepDialog.NewDialogTitle);
this.server = server; this.stepId = jobStepInfo ?
jobStepInfo.id : jobModel.jobSteps ?
jobModel.jobSteps.length + 1 : 1;
this.isEdit = jobStepInfo ? true : false;
this.model.dialogMode = this.isEdit ? AgentDialogMode.EDIT : AgentDialogMode.CREATE;
this.jobModel = jobModel; this.jobModel = jobModel;
this.jobName = this.jobName ? this.jobName : this.jobModel.name;
this.server = server;
} }
private initializeUIComponents() { private initializeUIComponents() {
@@ -254,6 +264,14 @@ export class JobStepDialog extends AgentDialog<JobStepData> {
let formWrapper = view.modelBuilder.loadingComponent().withItem(formModel).component(); let formWrapper = view.modelBuilder.loadingComponent().withItem(formModel).component();
formWrapper.loading = false; formWrapper.loading = false;
await view.initializeModel(formWrapper); await view.initializeModel(formWrapper);
// Load values for edit scenario
if (this.isEdit) {
this.nameTextBox.value = this.model.stepName;
this.typeDropdown.value = this.model.subSystem;
this.databaseDropdown.value = this.model.databaseName;
this.commandTextBox.value = this.model.command;
}
}); });
} }
@@ -290,7 +308,7 @@ export class JobStepDialog extends AgentDialog<JobStepData> {
let logToTableContainer = view.modelBuilder.flexContainer() let logToTableContainer = view.modelBuilder.flexContainer()
.withLayout({ flexFlow: 'row', justifyContent: 'space-between', width: 300 }) .withLayout({ flexFlow: 'row', justifyContent: 'space-between', width: 300 })
.withItems([this.logToTableCheckbox]).component(); .withItems([this.logToTableCheckbox]).component();
let logStepOutputHistoryCheckbox = view.modelBuilder.checkBox() this.logStepOutputHistoryCheckbox = view.modelBuilder.checkBox()
.withProperties({ label: this.IncludeStepOutputHistoryLabel }).component(); .withProperties({ label: this.IncludeStepOutputHistoryLabel }).component();
this.userInputBox = view.modelBuilder.inputBox() this.userInputBox = view.modelBuilder.inputBox()
.withProperties({ inputType: 'text', width: '100%' }).component(); .withProperties({ inputType: 'text', width: '100%' }).component();
@@ -315,7 +333,7 @@ export class JobStepDialog extends AgentDialog<JobStepData> {
component: appendCheckboxContainer, component: appendCheckboxContainer,
title: ' ' title: ' '
}, { }, {
component: logStepOutputHistoryCheckbox, component: this.logStepOutputHistoryCheckbox,
title: '' title: ''
}, { }, {
component: this.userInputBox, component: this.userInputBox,
@@ -326,7 +344,19 @@ export class JobStepDialog extends AgentDialog<JobStepData> {
let formWrapper = view.modelBuilder.loadingComponent().withItem(formModel).component(); let formWrapper = view.modelBuilder.loadingComponent().withItem(formModel).component();
formWrapper.loading = false; formWrapper.loading = false;
view.initializeModel(formWrapper); await view.initializeModel(formWrapper);
if (this.isEdit) {
this.successActionDropdown.value = this.model.successAction;
this.retryAttemptsBox.value = this.model.retryAttempts.toString();
this.retryIntervalBox.value = this.model.retryInterval.toString();
this.failureActionDropdown.value = this.model.failureAction;
this.outputFileNameBox.value = this.model.outputFileName;
this.appendToExistingFileCheckbox.checked = this.model.appendToLogFile;
this.logToTableCheckbox.checked = this.model.appendLogToTable;
this.logStepOutputHistoryCheckbox.checked = this.model.appendToStepHist;
this.userInputBox.value = this.model.databaseUserName;
}
}); });
} }
@@ -479,7 +509,6 @@ export class JobStepDialog extends AgentDialog<JobStepData> {
this.model.jobName = this.jobName; this.model.jobName = this.jobName;
this.model.id = this.stepId; this.model.id = this.stepId;
this.model.server = this.server; this.model.server = this.server;
this.model.stepName = this.nameTextBox.value;
this.model.subSystem = this.typeDropdown.value as string; this.model.subSystem = this.typeDropdown.value as string;
this.model.databaseName = this.databaseDropdown.value as string; this.model.databaseName = this.databaseDropdown.value as string;
this.model.script = this.commandTextBox.value; this.model.script = this.commandTextBox.value;

View File

@@ -33,8 +33,8 @@ export class PickScheduleDialog {
private _onSuccess: vscode.EventEmitter<PickScheduleData> = new vscode.EventEmitter<PickScheduleData>(); private _onSuccess: vscode.EventEmitter<PickScheduleData> = new vscode.EventEmitter<PickScheduleData>();
public readonly onSuccess: vscode.Event<PickScheduleData> = this._onSuccess.event; public readonly onSuccess: vscode.Event<PickScheduleData> = this._onSuccess.event;
constructor(ownerUri: string) { constructor(ownerUri: string, jobName: string) {
this.model = new PickScheduleData(ownerUri); this.model = new PickScheduleData(ownerUri, jobName);
} }
public async showDialog() { public async showDialog() {

View File

@@ -13,6 +13,7 @@ import { OperatorDialog } from './dialogs/operatorDialog';
import { ProxyDialog } from './dialogs/proxyDialog'; import { ProxyDialog } from './dialogs/proxyDialog';
import { JobStepDialog } from './dialogs/jobStepDialog'; import { JobStepDialog } from './dialogs/jobStepDialog';
import { PickScheduleDialog } from './dialogs/pickScheduleDialog'; import { PickScheduleDialog } from './dialogs/pickScheduleDialog';
import { JobData } from './data/jobData';
const localize = nls.loadMessageBundle(); const localize = nls.loadMessageBundle();
@@ -40,12 +41,12 @@ export class MainController {
let dialog = new JobDialog(ownerUri, jobInfo); let dialog = new JobDialog(ownerUri, jobInfo);
dialog.openDialog(); dialog.openDialog();
}); });
vscode.commands.registerCommand('agent.openNewStepDialog', (ownerUri: string, server: string, stepId: number) => { vscode.commands.registerCommand('agent.openNewStepDialog', (ownerUri: string, server: string, jobData: JobData, jobStepInfo: sqlops.AgentJobStepInfo) => {
let dialog = new JobStepDialog(ownerUri, server, stepId); let dialog = new JobStepDialog(ownerUri, server, jobData, jobStepInfo);
dialog.openDialog(); dialog.openDialog();
}); });
vscode.commands.registerCommand('agent.openPickScheduleDialog', (ownerUri: string) => { vscode.commands.registerCommand('agent.openPickScheduleDialog', (ownerUri: string, jobName: string) => {
let dialog = new PickScheduleDialog(ownerUri); let dialog = new PickScheduleDialog(ownerUri, jobName);
dialog.showDialog(); dialog.showDialog();
}); });
vscode.commands.registerCommand('agent.openAlertDialog', (ownerUri: string, alertInfo: sqlops.AgentAlertInfo, jobs: string[]) => { vscode.commands.registerCommand('agent.openAlertDialog', (ownerUri: string, alertInfo: sqlops.AgentAlertInfo, jobs: string[]) => {

View File

@@ -14,7 +14,7 @@ export class TestAgentService implements sqlops.AgentServicesProvider {
getJobs(ownerUri: string): Thenable<sqlops.AgentJobsResult> { getJobs(ownerUri: string): Thenable<sqlops.AgentJobsResult> {
return undefined; return undefined;
} }
getJobHistory(ownerUri: string, jobId: string): Thenable<sqlops.AgentJobHistoryResult> { getJobHistory(ownerUri: string, jobId: string, jobName: string): Thenable<sqlops.AgentJobHistoryResult> {
return undefined; return undefined;
} }
jobAction(ownerUri: string, jobName: string, action: string): Thenable<sqlops.ResultStatus> { jobAction(ownerUri: string, jobName: string, action: string): Thenable<sqlops.ResultStatus> {

View File

@@ -83,9 +83,8 @@
"dataprotocol-client": "github:Microsoft/sqlops-dataprotocolclient#0.2.7", "dataprotocol-client": "github:Microsoft/sqlops-dataprotocolclient#0.2.7",
"opener": "^1.4.3", "opener": "^1.4.3",
"service-downloader": "github:anthonydresser/service-downloader#0.1.5", "service-downloader": "github:anthonydresser/service-downloader#0.1.5",
"vscode-extension-telemetry": "^0.0.5", "vscode-extension-telemetry": "0.0.18",
"vscode-nls": "^3.2.1" "vscode-nls": "^3.2.1"
}, },
"devDependencies": { "devDependencies": {}
}
} }

View File

@@ -8,9 +8,13 @@ agent-base@4, agent-base@^4.1.0:
dependencies: dependencies:
es6-promisify "^5.0.0" es6-promisify "^5.0.0"
applicationinsights@0.15.6: applicationinsights@1.0.1:
version "0.15.6" version "1.0.1"
resolved "https://registry.yarnpkg.com/applicationinsights/-/applicationinsights-0.15.6.tgz#201a0682c0704fe4bdd9a92d0b2cbe34d2ae5972" resolved "https://registry.npmjs.org/applicationinsights/-/applicationinsights-1.0.1.tgz#53446b830fe8d5d619eee2a278b31d3d25030927"
dependencies:
diagnostic-channel "0.2.0"
diagnostic-channel-publishers "0.2.1"
zone.js "0.7.6"
base64-js@0.0.8: base64-js@0.0.8:
version "0.0.8" version "0.0.8"
@@ -120,6 +124,16 @@ decompress@^4.2.0:
pify "^2.3.0" pify "^2.3.0"
strip-dirs "^2.0.0" strip-dirs "^2.0.0"
diagnostic-channel-publishers@0.2.1:
version "0.2.1"
resolved "https://registry.npmjs.org/diagnostic-channel-publishers/-/diagnostic-channel-publishers-0.2.1.tgz#8e2d607a8b6d79fe880b548bc58cc6beb288c4f3"
diagnostic-channel@0.2.0:
version "0.2.0"
resolved "https://registry.npmjs.org/diagnostic-channel/-/diagnostic-channel-0.2.0.tgz#cc99af9612c23fb1fff13612c72f2cbfaa8d5a17"
dependencies:
semver "^5.3.0"
end-of-stream@^1.0.0: end-of-stream@^1.0.0:
version "1.4.1" version "1.4.1"
resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.1.tgz#ed29634d19baba463b6ce6b80a37213eab71ec43" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.1.tgz#ed29634d19baba463b6ce6b80a37213eab71ec43"
@@ -297,6 +311,10 @@ seek-bzip@^1.0.5:
dependencies: dependencies:
commander "~2.8.1" commander "~2.8.1"
semver@^5.3.0:
version "5.6.0"
resolved "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004"
"service-downloader@github:anthonydresser/service-downloader#0.1.5": "service-downloader@github:anthonydresser/service-downloader#0.1.5":
version "0.1.5" version "0.1.5"
resolved "https://codeload.github.com/anthonydresser/service-downloader/tar.gz/6ebb0465573cc140e461a22f334260f55ef45546" resolved "https://codeload.github.com/anthonydresser/service-downloader/tar.gz/6ebb0465573cc140e461a22f334260f55ef45546"
@@ -357,12 +375,11 @@ util-deprecate@~1.0.1:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
vscode-extension-telemetry@^0.0.5: vscode-extension-telemetry@0.0.18:
version "0.0.5" version "0.0.18"
resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.0.5.tgz#21e2abb4cbce3326e469ddbb322123b3702f3f85" resolved "https://registry.npmjs.org/vscode-extension-telemetry/-/vscode-extension-telemetry-0.0.18.tgz#602ba20d8c71453aa34533a291e7638f6e5c0327"
dependencies: dependencies:
applicationinsights "0.15.6" applicationinsights "1.0.1"
winreg "0.0.13"
vscode-jsonrpc@3.5.0: vscode-jsonrpc@3.5.0:
version "3.5.0" version "3.5.0"
@@ -389,10 +406,6 @@ vscode-nls@^3.2.1:
version "3.2.4" version "3.2.4"
resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-3.2.4.tgz#2166b4183c8aea884d20727f5449e62be69fd398" resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-3.2.4.tgz#2166b4183c8aea884d20727f5449e62be69fd398"
winreg@0.0.13:
version "0.0.13"
resolved "https://registry.yarnpkg.com/winreg/-/winreg-0.0.13.tgz#76bfe02e1dd0c9c8275fb9fdf17a9f36846e3483"
wrappy@1: wrappy@1:
version "1.0.2" version "1.0.2"
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
@@ -407,3 +420,7 @@ yauzl@^2.4.2:
dependencies: dependencies:
buffer-crc32 "~0.2.3" buffer-crc32 "~0.2.3"
fd-slicer "~1.1.0" fd-slicer "~1.1.0"
zone.js@0.7.6:
version "0.7.6"
resolved "https://registry.npmjs.org/zone.js/-/zone.js-0.7.6.tgz#fbbc39d3e0261d0986f1ba06306eb3aeb0d22009"

View File

@@ -1,18 +1,18 @@
{ {
"downloadUrl": "https://github.com/Microsoft/sqltoolsservice/releases/download/v{#version#}/microsoft.sqltools.servicelayer-{#fileName#}", "downloadUrl": "https://github.com/Microsoft/sqltoolsservice/releases/download/v{#version#}/microsoft.sqltools.servicelayer-{#fileName#}",
"version": "1.5.0-alpha.43", "version": "1.5.0-alpha.48",
"downloadFileNames": { "downloadFileNames": {
"Windows_86": "win-x86-netcoreapp2.1.zip", "Windows_86": "win-x86-netcoreapp2.2.zip",
"Windows_64": "win-x64-netcoreapp2.1.zip", "Windows_64": "win-x64-netcoreapp2.2.zip",
"OSX": "osx-x64-netcoreapp2.1.tar.gz", "OSX": "osx-x64-netcoreapp2.2.tar.gz",
"CentOS_7": "rhel-x64-netcoreapp2.1.tar.gz", "CentOS_7": "rhel-x64-netcoreapp2.2.tar.gz",
"Debian_8": "rhel-x64-netcoreapp2.1.tar.gz", "Debian_8": "rhel-x64-netcoreapp2.2.tar.gz",
"Fedora_23": "rhel-x64-netcoreapp2.1.tar.gz", "Fedora_23": "rhel-x64-netcoreapp2.2.tar.gz",
"OpenSUSE_13_2": "rhel-x64-netcoreapp2.1.tar.gz", "OpenSUSE_13_2": "rhel-x64-netcoreapp2.2.tar.gz",
"RHEL_7": "rhel-x64-netcoreapp2.1.tar.gz", "RHEL_7": "rhel-x64-netcoreapp2.2.tar.gz",
"SLES_12_2": "rhel-x64-netcoreapp2.1.tar.gz", "SLES_12_2": "rhel-x64-netcoreapp2.2.tar.gz",
"Ubuntu_14": "rhel-x64-netcoreapp2.1.tar.gz", "Ubuntu_14": "rhel-x64-netcoreapp2.2.tar.gz",
"Ubuntu_16": "rhel-x64-netcoreapp2.1.tar.gz" "Ubuntu_16": "rhel-x64-netcoreapp2.2.tar.gz"
}, },
"installDirectory": "../sqltoolsservice/{#platform#}/{#version#}", "installDirectory": "../sqltoolsservice/{#platform#}/{#version#}",
"executableFiles": ["MicrosoftSqlToolsServiceLayer.exe", "MicrosoftSqlToolsServiceLayer"] "executableFiles": ["MicrosoftSqlToolsServiceLayer.exe", "MicrosoftSqlToolsServiceLayer"]

View File

@@ -40,6 +40,7 @@ export interface AgentJobsParams {
export interface AgentJobHistoryParams { export interface AgentJobHistoryParams {
ownerUri: string; ownerUri: string;
jobId: string; jobId: string;
jobName: string;
} }
export interface AgentJobActionParams { export interface AgentJobActionParams {

View File

@@ -80,8 +80,8 @@ export class AgentServicesFeature extends SqlOpsFeature<undefined> {
); );
}; };
let getJobHistory = (ownerUri: string, jobID: string): Thenable<sqlops.AgentJobHistoryResult> => { let getJobHistory = (ownerUri: string, jobID: string, jobName: string): Thenable<sqlops.AgentJobHistoryResult> => {
let params: contracts.AgentJobHistoryParams = { ownerUri: ownerUri, jobId: jobID }; let params: contracts.AgentJobHistoryParams = { ownerUri: ownerUri, jobId: jobID, jobName: jobName };
return client.sendRequest(contracts.AgentJobHistoryRequest.type, params).then( return client.sendRequest(contracts.AgentJobHistoryRequest.type, params).then(
r => r, r => r,

View File

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

View File

@@ -40,6 +40,7 @@
"Microsoft.import", "Microsoft.import",
"Microsoft.profiler", "Microsoft.profiler",
"Microsoft.server-report", "Microsoft.server-report",
"Microsoft.sql-vnext",
"Microsoft.whoisactive", "Microsoft.whoisactive",
"Redgate.sql-search" "Redgate.sql-search"
], ],

View File

@@ -5,8 +5,8 @@
import { IThemable } from 'vs/platform/theme/common/styler'; import { IThemable } from 'vs/platform/theme/common/styler';
import { Event, Emitter } from 'vs/base/common/event'; import { Event, Emitter } from 'vs/base/common/event';
import { Dimension, EventType } from 'vs/base/browser/dom'; import { Dimension, EventType, $, addDisposableListener } from 'vs/base/browser/dom';
import { $, Builder } from 'vs/base/browser/builder'; import { $ as quickBuilder } from 'vs/base/browser/builder';
import { IAction } from 'vs/base/common/actions'; import { IAction } from 'vs/base/common/actions';
import { IActionOptions, ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { IActionOptions, ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
@@ -34,8 +34,8 @@ export interface IPanelTab {
} }
interface IInternalPanelTab extends IPanelTab { interface IInternalPanelTab extends IPanelTab {
header: Builder; header: HTMLElement;
label: Builder; label: HTMLElement;
dispose(): void; dispose(): void;
} }
@@ -49,10 +49,10 @@ export class TabbedPanel extends Disposable implements IThemable {
private _tabMap = new Map<PanelTabIdentifier, IInternalPanelTab>(); private _tabMap = new Map<PanelTabIdentifier, IInternalPanelTab>();
private _shownTab: PanelTabIdentifier; private _shownTab: PanelTabIdentifier;
public readonly headersize = 35; public readonly headersize = 35;
private $header: Builder; private header: HTMLElement;
private $tabList: Builder; private tabList: HTMLElement;
private $body: Builder; private body: HTMLElement;
private $parent: Builder; private parent: HTMLElement;
private _actionbar: ActionBar; private _actionbar: ActionBar;
private _currentDimensions: Dimension; private _currentDimensions: Dimension;
private _collapsed = false; private _collapsed = false;
@@ -63,28 +63,35 @@ export class TabbedPanel extends Disposable implements IThemable {
private tabHistory: string[] = []; private tabHistory: string[] = [];
constructor(private container: HTMLElement, private options: IPanelOptions = defaultOptions) { constructor(container: HTMLElement, private options: IPanelOptions = defaultOptions) {
super(); super();
this.$parent = this._register($('.tabbedPanel')); this.parent = $('.tabbedPanel');
this.$parent.appendTo(container); container.appendChild(this.parent);
this.$header = $('.composite.title'); this.header = $('.composite.title');
this.$tabList = $('.tabList'); this.tabList = $('.tabList');
this.$tabList.attr('role', 'tablist'); this.tabList.setAttribute('role', 'tablist');
this.$tabList.style('height', this.headersize + 'px'); this.tabList.style.height = this.headersize + 'px';
this.$header.append(this.$tabList); this.header.appendChild(this.tabList);
let actionbarcontainer = $('.title-actions'); let actionbarcontainer = $('.title-actions');
this._actionbar = new ActionBar(actionbarcontainer.getHTMLElement()); this._actionbar = new ActionBar(actionbarcontainer);
this.$header.append(actionbarcontainer); this.header.appendChild(actionbarcontainer);
if (options.showHeaderWhenSingleView) { if (options.showHeaderWhenSingleView) {
this._headerVisible = true; this._headerVisible = true;
this.$parent.append(this.$header); this.parent.appendChild(this.header);
} else { } else {
this._headerVisible = false; this._headerVisible = false;
} }
this.$body = $('.tabBody'); this.body = $('.tabBody');
this.$body.attr('role', 'tabpanel'); this.body.setAttribute('role', 'tabpanel');
this.$body.attr('tabindex', '0'); this.body.setAttribute('tabindex', '0');
this.$parent.append(this.$body); this.parent.appendChild(this.body);
}
public dispose() {
this.header.remove();
this.tabList.remove();
this.body.remove();
this.parent.remove();
} }
public contains(tab: IPanelTab): boolean { public contains(tab: IPanelTab): boolean {
@@ -99,7 +106,7 @@ export class TabbedPanel extends Disposable implements IThemable {
this.showTab(tab.identifier); this.showTab(tab.identifier);
} }
if (this._tabMap.size > 1 && !this._headerVisible) { if (this._tabMap.size > 1 && !this._headerVisible) {
this.$parent.append(this.$header, 0); this.parent.insertBefore(this.header, this.parent.firstChild);
this._headerVisible = true; this._headerVisible = true;
this.layout(this._currentDimensions); this.layout(this._currentDimensions);
} }
@@ -116,30 +123,27 @@ export class TabbedPanel extends Disposable implements IThemable {
private _createTab(tab: IInternalPanelTab): void { private _createTab(tab: IInternalPanelTab): void {
let tabHeaderElement = $('.tab-header'); let tabHeaderElement = $('.tab-header');
tabHeaderElement.attr('tabindex', '0'); tabHeaderElement.setAttribute('tabindex', '0');
tabHeaderElement.attr('role', 'tab'); tabHeaderElement.setAttribute('role', 'tab');
tabHeaderElement.attr('aria-selected', 'false'); tabHeaderElement.setAttribute('aria-selected', 'false');
tabHeaderElement.attr('aria-controls', tab.identifier); tabHeaderElement.setAttribute('aria-controls', tab.identifier);
let tabElement = $('.tab'); let tabElement = $('.tab');
tabHeaderElement.append(tabElement); tabHeaderElement.appendChild(tabElement);
let tabLabel = $('a.tabLabel'); let tabLabel = $('a.tabLabel');
tabLabel.safeInnerHtml(tab.title); tabLabel.innerText = tab.title;
tabElement.append(tabLabel); tabElement.appendChild(tabLabel);
tabHeaderElement.on(EventType.CLICK, e => this.showTab(tab.identifier)); addDisposableListener(tabHeaderElement, EventType.CLICK, e => this.showTab(tab.identifier));
tabHeaderElement.on(EventType.KEY_DOWN, (e: KeyboardEvent) => { addDisposableListener(tabHeaderElement, EventType.KEY_DOWN, (e: KeyboardEvent) => {
let event = new StandardKeyboardEvent(e); let event = new StandardKeyboardEvent(e);
if (event.equals(KeyCode.Enter)) { if (event.equals(KeyCode.Enter)) {
this.showTab(tab.identifier); this.showTab(tab.identifier);
e.stopImmediatePropagation(); e.stopImmediatePropagation();
} }
}); });
this.$tabList.append(tabHeaderElement); this.tabList.appendChild(tabHeaderElement);
tab.header = tabHeaderElement; tab.header = tabHeaderElement;
tab.label = tabLabel; tab.label = tabLabel;
tab.dispose = () => { tab.dispose = () => { };
tab.header.dispose();
tab.label.dispose();
};
this._register(tab); this._register(tab);
} }
@@ -149,19 +153,20 @@ export class TabbedPanel extends Disposable implements IThemable {
} }
if (this._shownTab) { if (this._shownTab) {
this._tabMap.get(this._shownTab).label.removeClass('active'); this._tabMap.get(this._shownTab).label.classList.remove('active');
this._tabMap.get(this._shownTab).header.removeClass('active').attr('aria-selected', 'false'); this._tabMap.get(this._shownTab).header.classList.remove('active');
this._tabMap.get(this._shownTab).header.setAttribute('aria-selected', 'false');
} }
this._shownTab = id; this._shownTab = id;
this.tabHistory.push(id); this.tabHistory.push(id);
this.$body.clearChildren(); quickBuilder(this.body).empty();
let tab = this._tabMap.get(this._shownTab); let tab = this._tabMap.get(this._shownTab);
this.$body.attr('aria-labelledby', tab.identifier); this.body.setAttribute('aria-labelledby', tab.identifier);
tab.label.addClass('active'); tab.label.classList.add('active');
tab.header.addClass('active'); tab.header.classList.add('active');
tab.header.attr('aria-selected', 'true'); tab.header.setAttribute('aria-selected', 'true');
tab.view.render(this.$body.getHTMLElement()); tab.view.render(this.body);
this._onTabChange.fire(id); this._onTabChange.fire(id);
if (this._currentDimensions) { if (this._currentDimensions) {
this._layoutCurrentTab(new Dimension(this._currentDimensions.width, this._currentDimensions.height - this.headersize)); this._layoutCurrentTab(new Dimension(this._currentDimensions.width, this._currentDimensions.height - this.headersize));
@@ -170,11 +175,11 @@ export class TabbedPanel extends Disposable implements IThemable {
public removeTab(tab: PanelTabIdentifier) { public removeTab(tab: PanelTabIdentifier) {
let actualTab = this._tabMap.get(tab); let actualTab = this._tabMap.get(tab);
actualTab.header.destroy(); quickBuilder(actualTab.header).destroy();
if (actualTab.view.remove) { if (actualTab.view.remove) {
actualTab.view.remove(); actualTab.view.remove();
} }
this._tabMap.get(tab).header.destroy(); quickBuilder(this._tabMap.get(tab).header).destroy();
this._tabMap.delete(tab); this._tabMap.delete(tab);
if (this._shownTab === tab) { if (this._shownTab === tab) {
this._shownTab = undefined; this._shownTab = undefined;
@@ -192,7 +197,7 @@ export class TabbedPanel extends Disposable implements IThemable {
} }
if (!this.options.showHeaderWhenSingleView && this._tabMap.size === 1 && this._headerVisible) { if (!this.options.showHeaderWhenSingleView && this._tabMap.size === 1 && this._headerVisible) {
this.$header.offDOM(); this.header.remove();
this._headerVisible = false; this._headerVisible = false;
this.layout(this._currentDimensions); this.layout(this._currentDimensions);
} }
@@ -205,12 +210,12 @@ export class TabbedPanel extends Disposable implements IThemable {
public layout(dimension: Dimension): void { public layout(dimension: Dimension): void {
if (dimension) { if (dimension) {
this._currentDimensions = dimension; this._currentDimensions = dimension;
this.$parent.style('height', dimension.height + 'px'); this.parent.style.height = dimension.height + 'px';
this.$parent.style('width', dimension.width + 'px'); this.parent.style.height = dimension.width + 'px';
this.$header.style('width', dimension.width + 'px'); this.header.style.width = dimension.width + 'px';
this.$body.style('width', dimension.width + 'px'); this.body.style.width = dimension.width + 'px';
const bodyHeight = dimension.height - (this._headerVisible ? this.headersize : 0); const bodyHeight = dimension.height - (this._headerVisible ? this.headersize : 0);
this.$body.style('height', bodyHeight + 'px'); this.body.style.height = bodyHeight + 'px';
this._layoutCurrentTab(new Dimension(dimension.width, bodyHeight)); this._layoutCurrentTab(new Dimension(dimension.width, bodyHeight));
} }
} }
@@ -232,9 +237,9 @@ export class TabbedPanel extends Disposable implements IThemable {
this._collapsed = val === false ? false : true; this._collapsed = val === false ? false : true;
if (this.collapsed) { if (this.collapsed) {
this.$body.offDOM(); this.body.remove();
} else { } else {
this.$parent.append(this.$body); this.parent.appendChild(this.body);
} }
} }

View File

@@ -10,10 +10,9 @@ import * as TelemetryUtils from 'sql/common/telemetryUtilities';
import { IInsightsView, IInsightData } from 'sql/parts/dashboard/widgets/insights/interfaces'; import { IInsightsView, IInsightData } from 'sql/parts/dashboard/widgets/insights/interfaces';
import { memoize, unmemoize } from 'sql/base/common/decorators'; import { memoize, unmemoize } from 'sql/base/common/decorators';
import { mixin } from 'sql/base/common/objects'; import { mixin } from 'sql/base/common/objects';
import { LegendPosition, DataDirection, ChartType } from 'sql/parts/dashboard/widgets/insights/views/charts/interfaces'; import { LegendPosition, ChartType, defaultChartConfig, IChartConfig, IDataSet, IPointDataSet } from 'sql/parts/dashboard/widgets/insights/views/charts/interfaces';
import * as colors from 'vs/platform/theme/common/colorRegistry'; import * as colors from 'vs/platform/theme/common/colorRegistry';
import { Color } from 'vs/base/common/color';
import * as types from 'vs/base/common/types'; import * as types from 'vs/base/common/types';
import { Disposable } from 'vs/base/common/lifecycle'; import { Disposable } from 'vs/base/common/lifecycle';
import { IColorTheme, IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { IColorTheme, IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
@@ -22,51 +21,6 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
declare var Chart: any; declare var Chart: any;
export function customMixin(destination: any, source: any, overwrite?: boolean): any {
if (types.isObject(source)) {
mixin(destination, source, overwrite, customMixin);
} else if (types.isArray(source)) {
for (let i = 0; i < source.length; i++) {
if (destination[i]) {
mixin(destination[i], source[i], overwrite, customMixin);
} else {
destination[i] = source[i];
}
}
} else {
destination = source;
}
return destination;
}
export interface IDataSet {
data: Array<number>;
label?: string;
}
export interface IPointDataSet {
data: Array<{ x: number | string, y: number }>;
label?: string;
fill: boolean;
backgroundColor?: Color;
}
export interface IChartConfig {
colorMap?: { [column: string]: string };
labelFirstColumn?: boolean;
legendPosition?: LegendPosition;
dataDirection?: DataDirection;
columnsAsLabels?: boolean;
showTopNData?: number;
}
export const defaultChartConfig: IChartConfig = {
labelFirstColumn: true,
columnsAsLabels: true,
legendPosition: LegendPosition.Top,
dataDirection: DataDirection.Vertical
};
@Component({ @Component({
template: ` <div style="display: block; width: 100%; height: 100%; position: relative"> template: ` <div style="display: block; width: 100%; height: 100%; position: relative">
<canvas #canvas *ngIf="_isDataAvailable && _hasInit" <canvas #canvas *ngIf="_isDataAvailable && _hasInit"

View File

@@ -3,6 +3,11 @@
* Licensed under the Source EULA. See License.txt in the project root for license information. * Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import { Color } from 'vs/base/common/color';
import * as types from 'vs/base/common/types';
import { mixin } from 'sql/base/common/objects';
export enum ChartType { export enum ChartType {
Bar = 'bar', Bar = 'bar',
Doughnut = 'doughnut', Doughnut = 'doughnut',
@@ -29,4 +34,49 @@ export enum LegendPosition {
export enum DataType { export enum DataType {
Number = 'number', Number = 'number',
Point = 'point' Point = 'point'
} }
export function customMixin(destination: any, source: any, overwrite?: boolean): any {
if (types.isObject(source)) {
mixin(destination, source, overwrite, customMixin);
} else if (types.isArray(source)) {
for (let i = 0; i < source.length; i++) {
if (destination[i]) {
mixin(destination[i], source[i], overwrite, customMixin);
} else {
destination[i] = source[i];
}
}
} else {
destination = source;
}
return destination;
}
export interface IDataSet {
data: Array<number>;
label?: string;
}
export interface IPointDataSet {
data: Array<{ x: number | string, y: number }>;
label?: string;
fill: boolean;
backgroundColor?: Color;
}
export interface IChartConfig {
colorMap?: { [column: string]: string };
labelFirstColumn?: boolean;
legendPosition?: LegendPosition;
dataDirection?: DataDirection;
columnsAsLabels?: boolean;
showTopNData?: number;
}
export const defaultChartConfig: IChartConfig = {
labelFirstColumn: true,
columnsAsLabels: true,
legendPosition: LegendPosition.Top,
dataDirection: DataDirection.Vertical
};

View File

@@ -3,9 +3,9 @@
* Licensed under the Source EULA. See License.txt in the project root for license information. * Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import { ChartInsight, customMixin, IChartConfig } from 'sql/parts/dashboard/widgets/insights/views/charts/chartInsight.component'; import { ChartInsight } from 'sql/parts/dashboard/widgets/insights/views/charts/chartInsight.component';
import { mixin } from 'sql/base/common/objects'; import { mixin } from 'sql/base/common/objects';
import { ChartType } from 'sql/parts/dashboard/widgets/insights/views/charts/interfaces'; import { ChartType, IChartConfig, customMixin } from 'sql/parts/dashboard/widgets/insights/views/charts/interfaces';
import { IColorTheme } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { IColorTheme } from 'vs/workbench/services/themes/common/workbenchThemeService';
import * as colors from 'vs/platform/theme/common/colorRegistry'; import * as colors from 'vs/platform/theme/common/colorRegistry';

View File

@@ -5,11 +5,10 @@
import { mixin } from 'vs/base/common/objects'; import { mixin } from 'vs/base/common/objects';
import { defaultChartConfig, IDataSet, IPointDataSet } from 'sql/parts/dashboard/widgets/insights/views/charts/chartInsight.component';
import BarChart, { IBarChartConfig } from './barChart.component'; import BarChart, { IBarChartConfig } from './barChart.component';
import { memoize, unmemoize } from 'sql/base/common/decorators'; import { memoize, unmemoize } from 'sql/base/common/decorators';
import { clone } from 'sql/base/common/objects'; import { clone } from 'sql/base/common/objects';
import { ChartType, DataType } from 'sql/parts/dashboard/widgets/insights/views/charts/interfaces'; import { ChartType, DataType, defaultChartConfig, IDataSet, IPointDataSet } from 'sql/parts/dashboard/widgets/insights/views/charts/interfaces';
export interface ILineConfig extends IBarChartConfig { export interface ILineConfig extends IBarChartConfig {
dataType?: DataType; dataType?: DataType;

View File

@@ -3,10 +3,9 @@
* Licensed under the Source EULA. See License.txt in the project root for license information. * Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import { defaultChartConfig } from 'sql/parts/dashboard/widgets/insights/views/charts/chartInsight.component';
import LineChart, { ILineConfig } from './lineChart.component'; import LineChart, { ILineConfig } from './lineChart.component';
import { clone } from 'sql/base/common/objects'; import { clone } from 'sql/base/common/objects';
import { ChartType } from 'sql/parts/dashboard/widgets/insights/views/charts/interfaces'; import { ChartType, defaultChartConfig } from 'sql/parts/dashboard/widgets/insights/views/charts/interfaces';
import { mixin } from 'vs/base/common/objects'; import { mixin } from 'vs/base/common/objects';

View File

@@ -3,10 +3,9 @@
* Licensed under the Source EULA. See License.txt in the project root for license information. * Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import { defaultChartConfig, IPointDataSet } from 'sql/parts/dashboard/widgets/insights/views/charts/chartInsight.component';
import LineChart, { ILineConfig } from './lineChart.component'; import LineChart, { ILineConfig } from './lineChart.component';
import { clone } from 'sql/base/common/objects'; import { clone } from 'sql/base/common/objects';
import { ChartType } from 'sql/parts/dashboard/widgets/insights/views/charts/interfaces'; import { ChartType, defaultChartConfig, IPointDataSet } from 'sql/parts/dashboard/widgets/insights/views/charts/interfaces';
import { mixin } from 'vs/base/common/objects'; import { mixin } from 'vs/base/common/objects';
import { Color } from 'vs/base/common/color'; import { Color } from 'vs/base/common/color';

View File

@@ -22,7 +22,7 @@ export interface IJobManagementService {
fireOnDidChange(): void; fireOnDidChange(): void;
getJobs(connectionUri: string): Thenable<sqlops.AgentJobsResult>; getJobs(connectionUri: string): Thenable<sqlops.AgentJobsResult>;
getJobHistory(connectionUri: string, jobID: string): Thenable<sqlops.AgentJobHistoryResult>; getJobHistory(connectionUri: string, jobID: string, jobName: string): Thenable<sqlops.AgentJobHistoryResult>;
deleteJob(connectionUri: string, job: sqlops.AgentJobInfo): Thenable<sqlops.ResultStatus>; deleteJob(connectionUri: string, job: sqlops.AgentJobInfo): Thenable<sqlops.ResultStatus>;
deleteJobStep(connectionUri: string, step: sqlops.AgentJobStepInfo): Thenable<sqlops.ResultStatus>; deleteJobStep(connectionUri: string, step: sqlops.AgentJobStepInfo): Thenable<sqlops.ResultStatus>;

View File

@@ -43,9 +43,9 @@ export class JobManagementService implements IJobManagementService {
}); });
} }
public getJobHistory(connectionUri: string, jobID: string): Thenable<sqlops.AgentJobHistoryResult> { public getJobHistory(connectionUri: string, jobID: string, jobName: string): Thenable<sqlops.AgentJobHistoryResult> {
return this._runAction(connectionUri, (runner) => { return this._runAction(connectionUri, (runner) => {
return runner.getJobHistory(connectionUri, jobID); return runner.getJobHistory(connectionUri, jobID, jobName);
}); });
} }
@@ -142,6 +142,9 @@ export class JobCacheObject {
_serviceBrand: any; _serviceBrand: any;
private _jobs: sqlops.AgentJobInfo[] = []; private _jobs: sqlops.AgentJobInfo[] = [];
private _jobHistories: { [jobID: string]: sqlops.AgentJobHistoryInfo[]; } = {}; private _jobHistories: { [jobID: string]: sqlops.AgentJobHistoryInfo[]; } = {};
private _jobSteps: { [jobID: string]: sqlops.AgentJobStepInfo[]; } = {};
private _jobAlerts: { [jobID: string]: sqlops.AgentAlertInfo[]; } = {};
private _jobSchedules: { [jobID: string]: sqlops.AgentJobScheduleInfo[]; } = {};
private _runCharts: { [jobID: string]: string[]; } = {}; private _runCharts: { [jobID: string]: string[]; } = {};
private _prevJobID: string; private _prevJobID: string;
private _serverName: string; private _serverName: string;
@@ -176,6 +179,18 @@ export class JobCacheObject {
return this._runCharts[jobID]; return this._runCharts[jobID];
} }
public getJobSteps(jobID: string): sqlops.AgentJobStepInfo[] {
return this._jobSteps[jobID];
}
public getJobAlerts(jobID: string): sqlops.AgentAlertInfo[] {
return this._jobAlerts[jobID];
}
public getJobSchedules(jobID: string): sqlops.AgentJobScheduleInfo[] {
return this._jobSchedules[jobID];
}
/* Setters */ /* Setters */
public set jobs(value: sqlops.AgentJobInfo[]) { public set jobs(value: sqlops.AgentJobInfo[]) {
this._jobs = value; this._jobs = value;
@@ -204,4 +219,16 @@ export class JobCacheObject {
public set dataView(value: Slick.Data.DataView<any>) { public set dataView(value: Slick.Data.DataView<any>) {
this._dataView = value; this._dataView = value;
} }
public setJobSteps(jobID: string, value: sqlops.AgentJobStepInfo[]) {
this._jobSteps[jobID] = value;
}
public setJobAlerts(jobID: string, value: sqlops.AgentAlertInfo[]) {
this._jobAlerts[jobID] = value;
}
public setJobSchedules(jobID: string, value: sqlops.AgentJobScheduleInfo[]) {
this._jobSchedules[jobID] = value;
}
} }

View File

@@ -150,16 +150,17 @@ export class JobHistoryComponent extends JobManagementView implements OnInit {
private loadHistory() { private loadHistory() {
const self = this; const self = this;
let ownerUri: string = this._commonService.connectionManagementService.connectionInfo.ownerUri; let ownerUri: string = this._commonService.connectionManagementService.connectionInfo.ownerUri;
this._jobManagementService.getJobHistory(ownerUri, this._agentViewComponent.jobId).then((result) => { let jobName = this._agentViewComponent.agentJobInfo.name;
if (result && result.jobs) { this._jobManagementService.getJobHistory(ownerUri, this._agentViewComponent.jobId, jobName).then((result) => {
if (result.jobs.length > 0) { if (result && result.histories) {
if (result.histories.length > 0) {
self._showPreviousRuns = true; self._showPreviousRuns = true;
self.buildHistoryTree(self, result.jobs); self.buildHistoryTree(self, result.histories);
if (self._agentViewComponent.showHistory) { if (self._agentViewComponent.showHistory) {
self._cd.detectChanges(); self._cd.detectChanges();
} }
} else { } else {
self._jobCacheObject.setJobHistory(self._agentViewComponent.jobId, result.jobs); self._jobCacheObject.setJobHistory(self._agentViewComponent.jobId, result.histories);
self._showPreviousRuns = false; self._showPreviousRuns = false;
} }
} else { } else {

View File

@@ -53,6 +53,7 @@ export abstract class JobManagementView extends TabChild implements AfterContent
if (!this.isInitialized) { if (!this.isInitialized) {
this._showProgressWheel = true; this._showProgressWheel = true;
this.onFirstVisible(); this.onFirstVisible();
this.layout();
this.isInitialized = true; this.isInitialized = true;
} }
} else if (this.isVisible === true && this._parentComponent.refresh === true) { } else if (this.isVisible === true && this._parentComponent.refresh === true) {

View File

@@ -36,8 +36,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
import { IDashboardService } from 'sql/services/dashboard/common/dashboardService'; import { IDashboardService } from 'sql/services/dashboard/common/dashboardService';
import { escape } from 'sql/base/common/strings'; import { escape } from 'sql/base/common/strings';
import { IWorkbenchThemeService, IColorTheme } from 'vs/workbench/services/themes/common/workbenchThemeService'; import { IWorkbenchThemeService, IColorTheme } from 'vs/workbench/services/themes/common/workbenchThemeService';
import { tableBackground, cellBackground, tableHoverBackground, jobsHeadingBackground, cellBorderColor } from 'sql/common/theme/colors'; import { tableBackground, cellBackground, cellBorderColor } from 'sql/common/theme/colors';
import { JobStepsViewRow } from 'sql/parts/jobManagement/views/jobStepsViewTree';
export const JOBSVIEW_SELECTOR: string = 'jobsview-component'; export const JOBSVIEW_SELECTOR: string = 'jobsview-component';
export const ROW_HEIGHT: number = 45; export const ROW_HEIGHT: number = 45;
@@ -87,7 +86,10 @@ export class JobsViewComponent extends JobManagementView implements OnInit {
private sortingStylingMap: { [columnName: string]: any; } = {}; private sortingStylingMap: { [columnName: string]: any; } = {};
public jobs: sqlops.AgentJobInfo[]; public jobs: sqlops.AgentJobInfo[];
public jobHistories: { [jobId: string]: sqlops.AgentJobHistoryInfo[]; } = Object.create(null); private jobHistories: { [jobId: string]: sqlops.AgentJobHistoryInfo[]; } = Object.create(null);
private jobSteps: { [jobId: string]: sqlops.AgentJobStepInfo[]; } = Object.create(null);
private jobAlerts: { [jobId: string]: sqlops.AgentAlertInfo[]; } = Object.create(null);
private jobSchedules: { [jobId: string]: sqlops.AgentJobScheduleInfo[]; } = Object.create(null);
public contextAction = NewJobAction; public contextAction = NewJobAction;
@ViewChild('jobsgrid') _gridEl: ElementRef; @ViewChild('jobsgrid') _gridEl: ElementRef;
@@ -579,10 +581,14 @@ export class JobsViewComponent extends JobManagementView implements OnInit {
private async curateJobHistory(jobs: sqlops.AgentJobInfo[], ownerUri: string) { private async curateJobHistory(jobs: sqlops.AgentJobInfo[], ownerUri: string) {
const self = this; const self = this;
jobs.forEach(async (job) => { jobs.forEach(async (job) => {
await this._jobManagementService.getJobHistory(ownerUri, job.jobId).then((result) => { await this._jobManagementService.getJobHistory(ownerUri, job.jobId, job.name).then((result) => {
if (result && result.jobs) { if (result) {
self.jobHistories[job.jobId] = result.jobs; self.jobSteps[job.jobId] = result.steps ? result.steps : [];
self._jobCacheObject.setJobHistory(job.jobId, result.jobs); self.jobAlerts[job.jobId] = result.alerts ? result.alerts : [];
self.jobSchedules[job.jobId] = result.schedules ? result.schedules : [];
self.jobHistories[job.jobId] = result.histories ? result.histories : [];
self._jobCacheObject.setJobSteps(job.jobId, self.jobSteps[job.jobId]);
self._jobCacheObject.setJobHistory(job.jobId, self.jobHistories[job.jobId]);
let jobHistories = self._jobCacheObject.getJobHistory(job.jobId); let jobHistories = self._jobCacheObject.getJobHistory(job.jobId);
let previousRuns: sqlops.AgentJobHistoryInfo[]; let previousRuns: sqlops.AgentJobHistoryInfo[];
if (jobHistories.length >= 5) { if (jobHistories.length >= 5) {
@@ -592,7 +598,7 @@ export class JobsViewComponent extends JobManagementView implements OnInit {
} }
self.createJobChart(job.jobId, previousRuns); self.createJobChart(job.jobId, previousRuns);
if (self._agentViewComponent.expanded.has(job.jobId)) { if (self._agentViewComponent.expanded.has(job.jobId)) {
let lastJobHistory = jobHistories[result.jobs.length - 1]; let lastJobHistory = jobHistories[jobHistories.length - 1];
let item = self.dataView.getItemById(job.jobId + '.error'); let item = self.dataView.getItemById(job.jobId + '.error');
let noStepsMessage = nls.localize('jobsView.noSteps', 'No Steps available for this job.'); let noStepsMessage = nls.localize('jobsView.noSteps', 'No Steps available for this job.');
let errorMessage = lastJobHistory ? lastJobHistory.message : noStepsMessage; let errorMessage = lastJobHistory ? lastJobHistory.message : noStepsMessage;
@@ -909,29 +915,22 @@ export class JobsViewComponent extends JobManagementView implements OnInit {
jobId = data.getItem(rowIndex - 1).jobId; jobId = data.getItem(rowIndex - 1).jobId;
} }
} }
let job: sqlops.AgentJobInfo[] = this.jobs.filter(job => { let job: sqlops.AgentJobInfo[] = this.jobs.filter(job => {
return job.jobId === jobId; return job.jobId === jobId;
}); });
let jobHistories = this.jobHistories[jobId];
let steps: sqlops.AgentJobStep[] = undefined; // add steps
let schedules: sqlops.AgentJobScheduleInfo[] = undefined; if (this.jobSteps && this.jobSteps[jobId]) {
let alerts: sqlops.AgentAlertInfo[] = undefined; let steps = this.jobSteps[jobId];
job[0].JobSteps = steps;
}
let jobHistories = this.jobHistories[job[0].jobId];
let schedules: sqlops.AgentJobScheduleInfo[] = this.jobSchedules[job[0].jobId];
let alerts: sqlops.AgentAlertInfo[] = this.jobAlerts[job[0].jobId];
if (jobHistories && jobHistories[jobHistories.length-1]) { if (jobHistories && jobHistories[jobHistories.length-1]) {
// add steps
steps = jobHistories[jobHistories.length-1].steps;
if (steps && steps.length > 0) {
if (!job[0].JobSteps) {
job[0].JobSteps = [];
}
if (job[0].JobSteps.length !== steps.length) {
job[0].JobSteps = [];
steps.forEach(step => {
job[0].JobSteps.push(step.stepDetails);
});
}
}
// add schedules // add schedules
schedules = jobHistories[jobHistories.length-1].schedules;
if (schedules && schedules.length > 0) { if (schedules && schedules.length > 0) {
if (!job[0].JobSchedules) { if (!job[0].JobSchedules) {
job[0].JobSchedules = []; job[0].JobSchedules = [];
@@ -944,7 +943,6 @@ export class JobsViewComponent extends JobManagementView implements OnInit {
} }
} }
// add alerts // add alerts
alerts = jobHistories[jobHistories.length-1].alerts;
if (!job[0].Alerts) { if (!job[0].Alerts) {
job[0].Alerts = []; job[0].Alerts = [];
} }

View File

@@ -39,6 +39,7 @@ export default class EditorComponent extends ComponentBase implements IComponent
private _languageMode: string; private _languageMode: string;
private _uri: string; private _uri: string;
private _isAutoResizable: boolean; private _isAutoResizable: boolean;
private _minimumHeight: number;
constructor( constructor(
@Inject(forwardRef(() => ChangeDetectorRef)) changeRef: ChangeDetectorRef, @Inject(forwardRef(() => ChangeDetectorRef)) changeRef: ChangeDetectorRef,
@@ -79,6 +80,9 @@ export default class EditorComponent extends ComponentBase implements IComponent
this._register(this._editorModel.onDidChangeContent(e => { this._register(this._editorModel.onDidChangeContent(e => {
this.content = this._editorModel.getValue(); this.content = this._editorModel.getValue();
if (this._isAutoResizable) { if (this._isAutoResizable) {
if (this._minimumHeight) {
this._editor.setMinimumHeight(this._minimumHeight);
}
this._editor.setHeightToScrollHeight(); this._editor.setHeightToScrollHeight();
} }
@@ -109,7 +113,8 @@ export default class EditorComponent extends ComponentBase implements IComponent
let height: number = this.convertSizeToNumber(this.height); let height: number = this.convertSizeToNumber(this.height);
if (this._isAutoResizable) { if (this._isAutoResizable) {
height = this._editor.scrollHeight; this._editor.setHeightToScrollHeight();
height = Math.max(this._editor.scrollHeight, this._minimumHeight ? this._minimumHeight : 0);
} }
this._editor.layout(new DOM.Dimension( this._editor.layout(new DOM.Dimension(
width && width > 0 ? width : DOM.getContentWidth(this._el.nativeElement), width && width > 0 ? width : DOM.getContentWidth(this._el.nativeElement),
@@ -152,6 +157,7 @@ export default class EditorComponent extends ComponentBase implements IComponent
// Intentionally always updating editorUri as it's wiped out by parent setProperties call. // Intentionally always updating editorUri as it's wiped out by parent setProperties call.
this.editorUri = this._uri; this.editorUri = this._uri;
this._isAutoResizable = this.isAutoResizable; this._isAutoResizable = this.isAutoResizable;
this._minimumHeight = this.minimumHeight;
} }
// CSS-bound properties // CSS-bound properties
@@ -179,6 +185,14 @@ export default class EditorComponent extends ComponentBase implements IComponent
this.setPropertyFromUI<sqlops.EditorProperties, boolean>((properties, isAutoResizable) => { properties.isAutoResizable = isAutoResizable; }, newValue); this.setPropertyFromUI<sqlops.EditorProperties, boolean>((properties, isAutoResizable) => { properties.isAutoResizable = isAutoResizable; }, newValue);
} }
public get minimumHeight(): number {
return this.getPropertyOrDefault<sqlops.EditorProperties, number>((props) => props.minimumHeight, this._editor.minimumHeight);
}
public set minimumHeight(newValue: number) {
this.setPropertyFromUI<sqlops.EditorProperties, number>((properties, minimumHeight) => { properties.minimumHeight = minimumHeight; }, newValue);
}
public get editorUri(): string { public get editorUri(): string {
return this.getPropertyOrDefault<sqlops.EditorProperties, string>((props) => props.editorUri, ''); return this.getPropertyOrDefault<sqlops.EditorProperties, string>((props) => props.editorUri, '');
} }

View File

@@ -34,6 +34,7 @@ export class QueryTextEditor extends BaseTextEditor {
public static ID = 'modelview.editors.textEditor'; public static ID = 'modelview.editors.textEditor';
private _dimension: DOM.Dimension; private _dimension: DOM.Dimension;
private _config: editorCommon.IConfiguration; private _config: editorCommon.IConfiguration;
private _minHeight: number;
constructor( constructor(
@ITelemetryService telemetryService: ITelemetryService, @ITelemetryService telemetryService: ITelemetryService,
@IInstantiationService instantiationService: IInstantiationService, @IInstantiationService instantiationService: IInstantiationService,
@@ -116,6 +117,11 @@ export class QueryTextEditor extends BaseTextEditor {
this._config = new Configuration(undefined, editorWidget.getDomNode()); this._config = new Configuration(undefined, editorWidget.getDomNode());
} }
let editorHeightUsingLines = this._config.editor.lineHeight * editorWidget.getModel().getLineCount(); let editorHeightUsingLines = this._config.editor.lineHeight * editorWidget.getModel().getLineCount();
this.setHeight(editorHeightUsingLines); let editorHeightUsingMinHeight = Math.max(editorHeightUsingLines, this._minHeight);
this.setHeight(editorHeightUsingMinHeight);
}
public setMinimumHeight(height: number) : void {
this._minHeight = height;
} }
} }

View File

@@ -218,7 +218,7 @@ export class ActiveConnectionsFilterAction extends Action {
public static LABEL = localize('activeConnections', 'Show Active Connections'); public static LABEL = localize('activeConnections', 'Show Active Connections');
private static enabledClass = 'active-connections-action'; private static enabledClass = 'active-connections-action';
private static disabledClass = 'icon server-page'; private static disabledClass = 'icon server-page';
private static clearAllLabel = localize('clearAll', 'Clear All'); private static showAllConnectionsLabel = localize('showAllConnections', 'Show All Connections');
private _isSet: boolean; private _isSet: boolean;
public static readonly ACTIVE = 'active'; public static readonly ACTIVE = 'active';
public get isSet(): boolean { public get isSet(): boolean {
@@ -249,7 +249,7 @@ export class ActiveConnectionsFilterAction extends Action {
// show active connections in the tree // show active connections in the tree
this.view.showFilteredTree(ActiveConnectionsFilterAction.ACTIVE); this.view.showFilteredTree(ActiveConnectionsFilterAction.ACTIVE);
this.isSet = true; this.isSet = true;
this.label = ActiveConnectionsFilterAction.clearAllLabel; this.label = ActiveConnectionsFilterAction.showAllConnectionsLabel;
} else { } else {
// show full tree // show full tree
this.view.refreshTree(); this.view.refreshTree();

View File

@@ -18,7 +18,8 @@ export enum ControlType {
combo, combo,
numberInput, numberInput,
input, input,
checkbox checkbox,
dateInput
} }
export interface IChartOption { export interface IChartOption {
@@ -115,6 +116,20 @@ const xAxisMaxInput: IChartOption = {
default: undefined default: undefined
}; };
const xAxisMinDateInput: IChartOption = {
label: localize('xAxisMinDate', 'X Axis Minimum Date'),
type: ControlType.dateInput,
configEntry: 'xAxisMin',
default: undefined
};
const xAxisMaxDateInput: IChartOption = {
label: localize('xAxisMaxDate', 'X Axis Maximum Date'),
type: ControlType.dateInput,
configEntry: 'xAxisMax',
default: undefined
};
const dataTypeInput: IChartOption = { const dataTypeInput: IChartOption = {
label: localize('dataTypeLabel', 'Data Type'), label: localize('dataTypeLabel', 'Data Type'),
type: ControlType.combo, type: ControlType.combo,
@@ -150,7 +165,11 @@ export const ChartOptions: IChartOptions = {
[ChartType.TimeSeries]: [ [ChartType.TimeSeries]: [
legendInput, legendInput,
yAxisLabelInput, yAxisLabelInput,
xAxisLabelInput yAxisMinInput,
yAxisMaxInput,
xAxisLabelInput,
xAxisMinDateInput,
xAxisMaxDateInput,
], ],
[ChartType.Bar]: [ [ChartType.Bar]: [
dataDirectionOption, dataDirectionOption,

View File

@@ -18,7 +18,7 @@ export class ChartTab implements IPanelTab {
public readonly identifier = 'ChartTab'; public readonly identifier = 'ChartTab';
public readonly view: ChartView; public readonly view: ChartView;
constructor(@IInstantiationService instantiationService: IInstantiationService) { constructor( @IInstantiationService instantiationService: IInstantiationService) {
this.view = instantiationService.createInstance(ChartView); this.view = instantiationService.createInstance(ChartView);
} }
@@ -26,7 +26,11 @@ export class ChartTab implements IPanelTab {
this.view.queryRunner = runner; this.view.queryRunner = runner;
} }
public chart(dataId: { batchId: number, resultId: number}): void { public chart(dataId: { batchId: number, resultId: number }): void {
this.view.chart(dataId); this.view.chart(dataId);
} }
public dispose() {
this.view.dispose();
}
} }

View File

@@ -24,7 +24,7 @@ import { SelectBox } from 'vs/base/browser/ui/selectBox/selectBox';
import { IContextViewService, IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IContextViewService, IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { InputBox } from 'vs/base/browser/ui/inputbox/inputBox'; import { InputBox } from 'vs/base/browser/ui/inputbox/inputBox';
import { Builder } from 'vs/base/browser/builder'; import { Builder } from 'vs/base/browser/builder';
import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle';
import { attachSelectBoxStyler, attachInputBoxStyler } from 'vs/platform/theme/common/styler'; import { attachSelectBoxStyler, attachInputBoxStyler } from 'vs/platform/theme/common/styler';
import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
@@ -43,7 +43,7 @@ declare class Proxy {
const insightRegistry = Registry.as<IInsightRegistry>(Extensions.InsightContribution); const insightRegistry = Registry.as<IInsightRegistry>(Extensions.InsightContribution);
export class ChartView implements IPanelView { export class ChartView extends Disposable implements IPanelView {
private insight: Insight; private insight: Insight;
private _queryRunner: QueryRunner; private _queryRunner: QueryRunner;
private _data: IInsightData; private _data: IInsightData;
@@ -82,6 +82,7 @@ export class ChartView implements IPanelView {
@IInstantiationService private _instantiationService: IInstantiationService, @IInstantiationService private _instantiationService: IInstantiationService,
@IContextMenuService contextMenuService: IContextMenuService @IContextMenuService contextMenuService: IContextMenuService
) { ) {
super();
this.taskbarContainer = $('div.taskbar-container'); this.taskbarContainer = $('div.taskbar-container');
this.taskbar = new Taskbar(this.taskbarContainer, contextMenuService); this.taskbar = new Taskbar(this.taskbarContainer, contextMenuService);
this.optionsControl = $('div.options-container'); this.optionsControl = $('div.options-container');
@@ -324,6 +325,24 @@ export class ChartView implements IPanelView {
}; };
this.optionDisposables.push(attachInputBoxStyler(numberInput, this._themeService)); this.optionDisposables.push(attachInputBoxStyler(numberInput, this._themeService));
break; break;
case ControlType.dateInput:
let dateInput = new InputBox(optionContainer, this._contextViewService, { type: 'date' });
dateInput.value = value || '';
dateInput.onDidChange(e => {
if (this.options[option.configEntry] !== e) {
this.options[option.configEntry] = e;
if (this.insight) {
this.insight.options = this.options;
}
}
});
setFunc = (val: string) => {
if (!isUndefinedOrNull(val)) {
dateInput.value = val;
}
};
this.optionDisposables.push(attachInputBoxStyler(dateInput, this._themeService));
break;
} }
this.optionMap[option.configEntry] = { element: optionContainer, set: setFunc }; this.optionMap[option.configEntry] = { element: optionContainer, set: setFunc };
container.appendChild(optionContainer); container.appendChild(optionContainer);

View File

@@ -7,7 +7,7 @@
import { Chart as ChartJs } from 'chart.js'; import { Chart as ChartJs } from 'chart.js';
import { mixin } from 'vs/base/common/objects'; import { mixin } from 'sql/base/common/objects';
import { localize } from 'vs/nls'; import { localize } from 'vs/nls';
import * as colors from 'vs/platform/theme/common/colorRegistry'; import * as colors from 'vs/platform/theme/common/colorRegistry';
import { editorLineNumbers } from 'vs/editor/common/view/editorColorRegistry'; import { editorLineNumbers } from 'vs/editor/common/view/editorColorRegistry';
@@ -15,10 +15,28 @@ import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService';
import { IInsightData } from 'sql/parts/dashboard/widgets/insights/interfaces'; import { IInsightData } from 'sql/parts/dashboard/widgets/insights/interfaces';
import { IInsightOptions, IInsight } from './interfaces'; import { IInsightOptions, IInsight } from './interfaces';
import { ChartType, DataDirection, LegendPosition } from 'sql/parts/dashboard/widgets/insights/views/charts/interfaces'; import { ChartType, DataDirection, LegendPosition, DataType, IPointDataSet, customMixin } from 'sql/parts/dashboard/widgets/insights/views/charts/interfaces';
const noneLineGraphs = [ChartType.Doughnut, ChartType.Pie]; const noneLineGraphs = [ChartType.Doughnut, ChartType.Pie];
const timeSeriesScales = {
scales: {
xAxes: [{
type: 'time',
display: true,
ticks: {
autoSkip: false,
maxRotation: 45,
minRotation: 45
}
}],
yAxes: [{
display: true,
}]
}
};
const defaultOptions: IInsightOptions = { const defaultOptions: IInsightOptions = {
type: ChartType.Bar, type: ChartType.Bar,
dataDirection: DataDirection.Horizontal dataDirection: DataDirection.Horizontal
@@ -30,6 +48,8 @@ export class Graph implements IInsight {
private chartjs: ChartJs; private chartjs: ChartJs;
private _data: IInsightData; private _data: IInsightData;
private originalType: ChartType;
public static readonly types = [ChartType.Bar, ChartType.Doughnut, ChartType.HorizontalBar, ChartType.Line, ChartType.Pie, ChartType.Scatter, ChartType.TimeSeries]; public static readonly types = [ChartType.Bar, ChartType.Doughnut, ChartType.HorizontalBar, ChartType.Line, ChartType.Pie, ChartType.Scatter, ChartType.TimeSeries];
public readonly types = Graph.types; public readonly types = Graph.types;
@@ -83,37 +103,51 @@ export class Graph implements IInsight {
labels = data.rows.map(row => row[0]); labels = data.rows.map(row => row[0]);
} }
if (this.options.dataDirection === DataDirection.Horizontal) { if (this.originalType === ChartType.TimeSeries) {
if (this.options.labelFirstColumn) { let dataSetMap: { [label: string]: IPointDataSet } = {};
chartData = data.rows.map((row) => { this._data.rows.map(row => {
return { if (row && row.length >= 3) {
data: row.map(item => Number(item)).slice(1), let legend = row[0];
label: row[0] if (!dataSetMap[legend]) {
}; dataSetMap[legend] = { label: legend, data: [], fill: false };
}); }
} else { dataSetMap[legend].data.push({ x: row[1], y: Number(row[2]) });
chartData = data.rows.map((row, i) => { }
return { });
data: row.map(item => Number(item)), chartData = Object.values(dataSetMap);
label: localize('series', 'Series {0}', i)
};
});
}
} else { } else {
if (this.options.columnsAsLabels) { if (this.options.dataDirection === DataDirection.Horizontal) {
chartData = data.rows[0].slice(1).map((row, i) => { if (this.options.labelFirstColumn) {
return { chartData = data.rows.map((row) => {
data: data.rows.map(row => Number(row[i + 1])), return {
label: data.columns[i + 1] data: row.map(item => Number(item)).slice(1),
}; label: row[0]
}); };
});
} else {
chartData = data.rows.map((row, i) => {
return {
data: row.map(item => Number(item)),
label: localize('series', 'Series {0}', i)
};
});
}
} else { } else {
chartData = data.rows[0].slice(1).map((row, i) => { if (this.options.columnsAsLabels) {
return { chartData = data.rows[0].slice(1).map((row, i) => {
data: data.rows.map(row => Number(row[i + 1])), return {
label: localize('series', 'Series {0}', i + 1) data: data.rows.map(row => Number(row[i + 1])),
}; label: data.columns[i + 1]
}); };
});
} else {
chartData = data.rows[0].slice(1).map((row, i) => {
return {
data: data.rows.map(row => Number(row[i + 1])),
label: localize('series', 'Series {0}', i + 1)
};
});
}
} }
} }
@@ -187,6 +221,35 @@ export class Graph implements IInsight {
color: gridLines color: gridLines
} }
}]; }];
if (this.originalType === ChartType.TimeSeries) {
retval = mixin(retval, timeSeriesScales, true, customMixin);
if (options.xAxisMax) {
retval = mixin(retval, {
scales: {
xAxes: [{
type: 'time',
time: {
max: options.xAxisMax
}
}],
}
}, true, customMixin);
}
if (options.xAxisMin) {
retval = mixin(retval, {
scales: {
xAxes: [{
type: 'time',
time: {
min: options.xAxisMin
}
}],
}
}, true, customMixin);
}
}
} }
retval.legend = <ChartJs.ChartLegendOptions>{ retval.legend = <ChartJs.ChartLegendOptions>{
@@ -208,6 +271,12 @@ export class Graph implements IInsight {
public set options(options: IInsightOptions) { public set options(options: IInsightOptions) {
this._options = options; this._options = options;
this.originalType = options.type as ChartType;
if (this.options.type === ChartType.TimeSeries) {
this.options.type = ChartType.Line;
this.options.dataType = DataType.Point;
this.options.dataDirection = DataDirection.Horizontal;
}
this.data = this._data; this.data = this._data;
} }

View File

@@ -7,7 +7,7 @@
import { Graph } from './graphInsight'; import { Graph } from './graphInsight';
import { IInsightData } from 'sql/parts/dashboard/widgets/insights/interfaces'; import { IInsightData } from 'sql/parts/dashboard/widgets/insights/interfaces';
import { DataDirection, ChartType } from 'sql/parts/dashboard/widgets/insights/views/charts/interfaces'; import { DataDirection, ChartType, DataType } from 'sql/parts/dashboard/widgets/insights/views/charts/interfaces';
import { ImageInsight } from './imageInsight'; import { ImageInsight } from './imageInsight';
import { TableInsight } from './tableInsight'; import { TableInsight } from './tableInsight';
import { IInsightOptions, IInsight, InsightType, IInsightCtor } from './interfaces'; import { IInsightOptions, IInsight, InsightType, IInsightCtor } from './interfaces';
@@ -16,6 +16,7 @@ import { CountInsight } from './countInsight';
import { Builder } from 'vs/base/browser/builder'; import { Builder } from 'vs/base/browser/builder';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { Dimension } from 'vs/base/browser/dom'; import { Dimension } from 'vs/base/browser/dom';
import { deepClone } from 'vs/base/common/objects';
const defaultOptions: IInsightOptions = { const defaultOptions: IInsightOptions = {
type: ChartType.Bar, type: ChartType.Bar,
@@ -47,13 +48,13 @@ export class Insight {
} }
public set options(val: IInsightOptions) { public set options(val: IInsightOptions) {
this._options = val; this._options = deepClone(val);
if (this.insight) { if (this.insight) {
// check to see if we need to change the insight type // check to see if we need to change the insight type
if (!this.insight.types.includes(val.type)) { if (!this.insight.types.includes(this.options.type)) {
this.buildInsight(); this.buildInsight();
} else { } else {
this.insight.options = val; this.insight.options = this.options;
} }
} }
} }

View File

@@ -21,6 +21,7 @@ import { CopyKeybind } from 'sql/base/browser/ui/table/plugins/copyKeybind.plugi
import { AdditionalKeyBindings } from 'sql/base/browser/ui/table/plugins/additionalKeyBindings.plugin'; import { AdditionalKeyBindings } from 'sql/base/browser/ui/table/plugins/additionalKeyBindings.plugin';
import * as sqlops from 'sqlops'; import * as sqlops from 'sqlops';
import * as pretty from 'pretty-data';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
@@ -477,7 +478,27 @@ class GridTable<T> extends Disposable implements IView {
if (column && (column.isXml || column.isJson)) { if (column && (column.isXml || column.isJson)) {
this.runner.getQueryRows(event.cell.row, 1, this.resultSet.batchId, this.resultSet.id).then(d => { this.runner.getQueryRows(event.cell.row, 1, this.resultSet.batchId, this.resultSet.id).then(d => {
let value = d.resultSubset.rows[0][event.cell.cell - 1]; let value = d.resultSubset.rows[0][event.cell.cell - 1];
let input = this.untitledEditorService.createOrGet(undefined, column.isXml ? 'xml' : 'json', value.displayValue); let content = value.displayValue;
if (column.isXml) {
try {
content = pretty.pd.xml(content);
} catch (e) {
// If Xml fails to parse, fall back on original Xml content
}
} else {
let jsonContent: string = undefined;
try {
jsonContent = JSON.parse(content);
} catch (e) {
// If Json fails to parse, fall back on original Json content
}
if (jsonContent) {
// If Json content was valid and parsed, pretty print content to a string
content = JSON.stringify(jsonContent, undefined, 4);
}
}
let input = this.untitledEditorService.createOrGet(undefined, column.isXml ? 'xml' : 'json', content);
this.editorService.openEditor(input); this.editorService.openEditor(input);
}); });
} }

View File

@@ -18,9 +18,9 @@ import { PanelViewlet } from 'vs/workbench/browser/parts/views/panelViewlet';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import * as DOM from 'vs/base/browser/dom'; import * as DOM from 'vs/base/browser/dom';
import { once, anyEvent } from 'vs/base/common/event'; import { once, anyEvent } from 'vs/base/common/event';
import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle';
class ResultsView implements IPanelView { class ResultsView extends Disposable implements IPanelView {
private panelViewlet: PanelViewlet; private panelViewlet: PanelViewlet;
private gridPanel: GridPanel; private gridPanel: GridPanel;
private messagePanel: MessagePanel; private messagePanel: MessagePanel;
@@ -30,10 +30,10 @@ class ResultsView implements IPanelView {
private _state: ResultsViewState; private _state: ResultsViewState;
constructor(private instantiationService: IInstantiationService) { constructor(private instantiationService: IInstantiationService) {
super();
this.panelViewlet = this.instantiationService.createInstance(PanelViewlet, 'resultsView', { showHeaderInTitleWhenSingleView: false }); this.panelViewlet = this._register(this.instantiationService.createInstance(PanelViewlet, 'resultsView', { showHeaderInTitleWhenSingleView: false }));
this.gridPanel = this.instantiationService.createInstance(GridPanel, { title: nls.localize('gridPanel', 'Results'), id: 'gridPanel' }); this.gridPanel = this._register(this.instantiationService.createInstance(GridPanel, { title: nls.localize('gridPanel', 'Results'), id: 'gridPanel' }));
this.messagePanel = this.instantiationService.createInstance(MessagePanel, { title: nls.localize('messagePanel', 'Messages'), minimumBodySize: 0, id: 'messagePanel' }); this.messagePanel = this._register(this.instantiationService.createInstance(MessagePanel, { title: nls.localize('messagePanel', 'Messages'), minimumBodySize: 0, id: 'messagePanel' }));
this.gridPanel.render(); this.gridPanel.render();
this.messagePanel.render(); this.messagePanel.render();
this.panelViewlet.create(this.container).then(() => { this.panelViewlet.create(this.container).then(() => {
@@ -76,7 +76,7 @@ class ResultsView implements IPanelView {
this.panelViewlet.resizePanel(this.gridPanel, this.state.messagePanelSize); this.panelViewlet.resizePanel(this.gridPanel, this.state.messagePanelSize);
} }
this.panelViewlet.resizePanel(this.gridPanel, panelSize); this.panelViewlet.resizePanel(this.gridPanel, panelSize);
}) });
// once the user changes the sash we should stop trying to resize the grid // once the user changes the sash we should stop trying to resize the grid
once(this.panelViewlet.onDidSashChange)(e => { once(this.panelViewlet.onDidSashChange)(e => {
this.needsGridResize = false; this.needsGridResize = false;
@@ -147,9 +147,13 @@ class ResultsTab implements IPanelTab {
public set queryRunner(runner: QueryRunner) { public set queryRunner(runner: QueryRunner) {
this.view.queryRunner = runner; this.view.queryRunner = runner;
} }
public dispose() {
dispose(this.view);
}
} }
export class QueryResultsView { export class QueryResultsView extends Disposable {
private _panelView: TabbedPanel; private _panelView: TabbedPanel;
private _input: QueryResultsInput; private _input: QueryResultsInput;
private resultsTab: ResultsTab; private resultsTab: ResultsTab;
@@ -163,16 +167,17 @@ export class QueryResultsView {
@IInstantiationService instantiationService: IInstantiationService, @IInstantiationService instantiationService: IInstantiationService,
@IQueryModelService private queryModelService: IQueryModelService @IQueryModelService private queryModelService: IQueryModelService
) { ) {
this.resultsTab = new ResultsTab(instantiationService); super();
this.chartTab = new ChartTab(instantiationService); this.resultsTab = this._register(new ResultsTab(instantiationService));
this._panelView = new TabbedPanel(container, { showHeaderWhenSingleView: false }); this.chartTab = this._register(new ChartTab(instantiationService));
this.qpTab = new QueryPlanTab(); this._panelView = this._register(new TabbedPanel(container, { showHeaderWhenSingleView: false }));
this.qpTab = this._register(new QueryPlanTab());
this._panelView.pushTab(this.resultsTab); this._panelView.pushTab(this.resultsTab);
this._panelView.onTabChange(e => { this._register(this._panelView.onTabChange(e => {
if (this.input) { if (this.input) {
this.input.state.activeTab = e; this.input.state.activeTab = e;
} }
}); }));
} }
public style() { public style() {
@@ -203,12 +208,14 @@ export class QueryResultsView {
if (!this._panelView.contains(this.qpTab)) { if (!this._panelView.contains(this.qpTab)) {
this._panelView.pushTab(this.qpTab); this._panelView.pushTab(this.qpTab);
} }
} else if (queryRunner.isQueryPlan) {
let disp = queryRunner.onResultSet(() => {
this.showPlan(queryRunner.planXml);
disp.dispose();
});
} }
this.runnerDisposables.push(queryRunner.onQueryEnd(() => {
if (queryRunner.isQueryPlan) {
queryRunner.planXml.then(e => {
this.showPlan(e);
});
}
}));
if (this.input.state.activeTab) { if (this.input.state.activeTab) {
this._panelView.showTab(this.input.state.activeTab); this._panelView.showTab(this.input.state.activeTab);
} }

View File

@@ -26,6 +26,7 @@ import { Emitter, Event } from 'vs/base/common/event';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ResultSerializer } from 'sql/parts/query/common/resultSerializer'; import { ResultSerializer } from 'sql/parts/query/common/resultSerializer';
import { TPromise } from 'vs/base/common/winjs.base'; import { TPromise } from 'vs/base/common/winjs.base';
import { Deferred } from 'sql/base/common/promise';
export interface IEditSessionReadyEvent { export interface IEditSessionReadyEvent {
ownerUri: string; ownerUri: string;
@@ -69,11 +70,11 @@ export default class QueryRunner {
private _hasCompleted: boolean = false; private _hasCompleted: boolean = false;
private _batchSets: sqlops.BatchSummary[] = []; private _batchSets: sqlops.BatchSummary[] = [];
private _eventEmitter = new EventEmitter(); private _eventEmitter = new EventEmitter();
private _isQueryPlan: boolean;
private _isQueryPlan: boolean;
public get isQueryPlan(): boolean { return this._isQueryPlan; } public get isQueryPlan(): boolean { return this._isQueryPlan; }
private _planXml: string; private _planXml = new Deferred<string>();
public get planXml(): string { return this._planXml; } public get planXml(): Thenable<string> { return this._planXml.promise; }
private _onMessage = new Emitter<sqlops.IResultMessage>(); private _onMessage = new Emitter<sqlops.IResultMessage>();
private _debouncedMessage = debounceEvent<sqlops.IResultMessage, sqlops.IResultMessage[]>(this._onMessage.event, (l, e) => { private _debouncedMessage = debounceEvent<sqlops.IResultMessage, sqlops.IResultMessage[]>(this._onMessage.event, (l, e) => {
@@ -183,6 +184,7 @@ export default class QueryRunner {
this._echoedResultSet.clear(); this._echoedResultSet.clear();
this._debouncedMessage.clear(); this._debouncedMessage.clear();
this._debouncedResultSet.clear(); this._debouncedResultSet.clear();
this._planXml = new Deferred<string>();
let ownerUri = this.uri; let ownerUri = this.uri;
this._batchSets = []; this._batchSets = [];
this._hasCompleted = false; this._hasCompleted = false;
@@ -342,7 +344,11 @@ export default class QueryRunner {
} }
// handle getting queryPlanxml if we need too // handle getting queryPlanxml if we need too
if (this.isQueryPlan) { if (this.isQueryPlan) {
this.getQueryRows(0, 1, 0, 0).then(e => this._planXml = e.resultSubset.rows[0][0].displayValue); // check if this result has show plan, this needs work, it won't work for any other provider
let hasShowPlan = !!result.resultSetSummary.columnInfo.find(e => e.columnName === 'Microsoft SQL Server 2005 XML Showplan');
if (hasShowPlan) {
this.getQueryRows(0, 1, result.resultSetSummary.batchId, result.resultSetSummary.id).then(e => this._planXml.resolve(e.resultSubset.rows[0][0].displayValue));
}
} }
if (batchSet) { if (batchSet) {
// Store the result set in the batch and emit that a result set has completed // Store the result set in the batch and emit that a result set has completed

View File

@@ -10,8 +10,8 @@ import { IPanelView, IPanelTab } from 'sql/base/browser/ui/panel/panel';
import { Dimension } from 'vs/base/browser/dom'; import { Dimension } from 'vs/base/browser/dom';
import { localize } from 'vs/nls'; import { localize } from 'vs/nls';
import * as UUID from 'vs/base/common/uuid';
import { Builder } from 'vs/base/browser/builder'; import { Builder } from 'vs/base/browser/builder';
import { dispose, Disposable } from 'vs/base/common/lifecycle';
export class QueryPlanState { export class QueryPlanState {
xml: string; xml: string;
@@ -25,6 +25,10 @@ export class QueryPlanTab implements IPanelTab {
constructor() { constructor() {
this.view = new QueryPlanView(); this.view = new QueryPlanView();
} }
public dispose() {
dispose(this.view);
}
} }
export class QueryPlanView implements IPanelView { export class QueryPlanView implements IPanelView {
@@ -41,10 +45,18 @@ export class QueryPlanView implements IPanelView {
} }
} }
container.appendChild(this.container); container.appendChild(this.container);
container.style.overflow = 'scroll'; this.container.style.overflow = 'scroll';
}
dispose() {
this.container.remove();
this.qp = undefined;
this.container = undefined;
} }
public layout(dimension: Dimension): void { public layout(dimension: Dimension): void {
this.container.style.width = dimension.width + 'px';
this.container.style.height = dimension.height + 'px';
} }
public showPlan(xml: string) { public showPlan(xml: string) {

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

@@ -1266,6 +1266,16 @@ declare module 'sqlops' {
Last = 16 Last = 16
} }
export enum JobExecutionStatus {
Executing = 1,
WaitingForWorkerThread = 2,
BetweenRetries = 3,
Idle = 4,
Suspended = 5,
WaitingForStepToFinish = 6,
PerformingCompletionAction = 7
}
export interface AgentJobInfo { export interface AgentJobInfo {
name: string; name: string;
owner: string; owner: string;
@@ -1371,8 +1381,6 @@ declare module 'sqlops' {
retriesAttempted: string; retriesAttempted: string;
server: string; server: string;
steps: AgentJobStep[]; steps: AgentJobStep[];
schedules: AgentJobScheduleInfo[];
alerts: AgentAlertInfo[];
} }
export interface AgentProxyInfo { export interface AgentProxyInfo {
@@ -1441,7 +1449,10 @@ declare module 'sqlops' {
} }
export interface AgentJobHistoryResult extends ResultStatus { export interface AgentJobHistoryResult extends ResultStatus {
jobs: AgentJobHistoryInfo[]; histories: AgentJobHistoryInfo[];
steps: AgentJobStepInfo[];
schedules: AgentJobScheduleInfo[];
alerts: AgentAlertInfo[];
} }
export interface CreateAgentJobResult extends ResultStatus { export interface CreateAgentJobResult extends ResultStatus {
@@ -1529,7 +1540,7 @@ declare module 'sqlops' {
export interface AgentServicesProvider extends DataProvider { export interface AgentServicesProvider extends DataProvider {
// Job management methods // Job management methods
getJobs(ownerUri: string): Thenable<AgentJobsResult>; getJobs(ownerUri: string): Thenable<AgentJobsResult>;
getJobHistory(ownerUri: string, jobId: string): Thenable<AgentJobHistoryResult>; getJobHistory(ownerUri: string, jobId: string, jobName: string): Thenable<AgentJobHistoryResult>;
jobAction(ownerUri: string, jobName: string, action: string): Thenable<ResultStatus>; jobAction(ownerUri: string, jobName: string, action: string): Thenable<ResultStatus>;
createJob(ownerUri: string, jobInfo: AgentJobInfo): Thenable<CreateAgentJobResult>; createJob(ownerUri: string, jobInfo: AgentJobInfo): Thenable<CreateAgentJobResult>;
updateJob(ownerUri: string, originalJobName: string, jobInfo: AgentJobInfo): Thenable<UpdateAgentJobResult>; updateJob(ownerUri: string, originalJobName: string, jobInfo: AgentJobInfo): Thenable<UpdateAgentJobResult>;

View File

@@ -595,6 +595,10 @@ declare module 'sqlops' {
* The languge mode for this text editor. The language mode is SQL by default. * The languge mode for this text editor. The language mode is SQL by default.
*/ */
languageMode?: string; languageMode?: string;
/**
* Minimum height for editor component
*/
minimumHeight?: number;
} }
export interface ButtonProperties extends ComponentProperties, ComponentWithIcon { export interface ButtonProperties extends ComponentProperties, ComponentWithIcon {
@@ -722,6 +726,11 @@ declare module 'sqlops' {
*/ */
isAutoResizable: boolean; isAutoResizable: boolean;
/**
* Minimum height for editor component
*/
minimumHeight: number;
} }
export interface ButtonComponent extends Component, ButtonProperties { export interface ButtonComponent extends Component, ButtonProperties {

View File

@@ -95,6 +95,16 @@ export enum JobCompletionActionCondition {
Always = 3 Always = 3
} }
export enum JobExecutionStatus {
Executing = 1,
WaitingForWorkerThread = 2,
BetweenRetries = 3,
Idle = 4,
Suspended = 5,
WaitingForStepToFinish = 6,
PerformingCompletionAction = 7
}
export enum AlertType { export enum AlertType {
sqlServerEvent = 1, sqlServerEvent = 1,
sqlServerPerformanceCondition = 2, sqlServerPerformanceCondition = 2,

View File

@@ -578,8 +578,8 @@ export class ExtHostDataProtocol extends ExtHostDataProtocolShape {
/** /**
* Get a Agent Job's history * Get a Agent Job's history
*/ */
public $getJobHistory(handle: number, ownerUri: string, jobID: string): Thenable<sqlops.AgentJobHistoryResult> { public $getJobHistory(handle: number, ownerUri: string, jobID: string, jobName: string): Thenable<sqlops.AgentJobHistoryResult> {
return this._resolveProvider<sqlops.AgentServicesProvider>(handle).getJobHistory(ownerUri, jobID); return this._resolveProvider<sqlops.AgentServicesProvider>(handle).getJobHistory(ownerUri, jobID, jobName);
} }
/** /**

View File

@@ -913,6 +913,14 @@ class EditorWrapper extends ComponentWrapper implements sqlops.EditorComponent {
this.setProperty('isAutoResizable', v); this.setProperty('isAutoResizable', v);
} }
public get minimumHeight(): number {
return this.properties['minimumHeight'];
}
public set minimumHeight(v: number) {
this.setProperty('minimumHeight', v);
}
public get onContentChanged(): vscode.Event<any> { public get onContentChanged(): vscode.Event<any> {
let emitter = this._emitterMap.get(ComponentEventType.onDidChange); let emitter = this._emitterMap.get(ComponentEventType.onDidChange);
return emitter && emitter.event; return emitter && emitter.event;

View File

@@ -350,8 +350,8 @@ export class MainThreadDataProtocol implements MainThreadDataProtocolShape {
getJobs(connectionUri: string): Thenable<sqlops.AgentJobsResult> { getJobs(connectionUri: string): Thenable<sqlops.AgentJobsResult> {
return self._proxy.$getJobs(handle, connectionUri); return self._proxy.$getJobs(handle, connectionUri);
}, },
getJobHistory(connectionUri: string, jobID: string): Thenable<sqlops.AgentJobHistoryResult> { getJobHistory(connectionUri: string, jobID: string, jobName: string): Thenable<sqlops.AgentJobHistoryResult> {
return self._proxy.$getJobHistory(handle, connectionUri, jobID); return self._proxy.$getJobHistory(handle, connectionUri, jobID, jobName);
}, },
jobAction(connectionUri: string, jobName: string, action: string): Thenable<sqlops.ResultStatus> { jobAction(connectionUri: string, jobName: string, action: string): Thenable<sqlops.ResultStatus> {
return self._proxy.$jobAction(handle, connectionUri, jobName, action); return self._proxy.$jobAction(handle, connectionUri, jobName, action);

View File

@@ -422,6 +422,7 @@ export function createApiFactory(
WeekDays: sqlExtHostTypes.WeekDays, WeekDays: sqlExtHostTypes.WeekDays,
NotifyMethods: sqlExtHostTypes.NotifyMethods, NotifyMethods: sqlExtHostTypes.NotifyMethods,
JobCompletionActionCondition: sqlExtHostTypes.JobCompletionActionCondition, JobCompletionActionCondition: sqlExtHostTypes.JobCompletionActionCondition,
JobExecutionStatus: sqlExtHostTypes.JobExecutionStatus,
AlertType: sqlExtHostTypes.AlertType, AlertType: sqlExtHostTypes.AlertType,
FrequencyTypes: sqlExtHostTypes.FrequencyTypes, FrequencyTypes: sqlExtHostTypes.FrequencyTypes,
FrequencySubDayTypes: sqlExtHostTypes.FrequencySubDayTypes, FrequencySubDayTypes: sqlExtHostTypes.FrequencySubDayTypes,

View File

@@ -358,7 +358,7 @@ export abstract class ExtHostDataProtocolShape {
/** /**
* Get a Agent Job's history * Get a Agent Job's history
*/ */
$getJobHistory(handle: number, ownerUri: string, jobID: string): Thenable<sqlops.AgentJobHistoryResult> { throw ni(); } $getJobHistory(handle: number, ownerUri: string, jobID: string, jobName: string): Thenable<sqlops.AgentJobHistoryResult> { throw ni(); }
/** /**
* Run an action on a Job * Run an action on a Job