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
This commit is contained in:
Aditya Bist
2018-10-16 13:24:43 -07:00
committed by GitHub
parent ac1f7542a9
commit 425eecf692
19 changed files with 242 additions and 105 deletions

View File

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

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, } else if (this.jobModel && this.jobModel.dialogMode === AgentDialogMode.EDIT) {
successStepId: this.successStepId, // edit job -> edit step
command: this.command, result = await agentService.updateJobStep(this.ownerUri, this.stepName, JobStepData.convertToAgentJobStepInfo(this));
commandExecutionSuccessCode: this.commandExecutionSuccessCode, }
databaseName: this.databaseName, if (!result || !result.success) {
databaseUserName: this.databaseUserName, vscode.window.showErrorMessage(
server: this.server, localize('jobStepData.saveErrorMessage', "Step update failed '{0}'", result.errorMessage ? result.errorMessage : 'Unknown'));
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);
} }
});
} }
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

@@ -235,13 +235,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 +271,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) => {

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

@@ -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,8 +41,8 @@ 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) => {
let dialog = new JobStepDialog(ownerUri, server, stepId); let dialog = new JobStepDialog(ownerUri, server, jobData);
dialog.openDialog(); dialog.openDialog();
}); });
vscode.commands.registerCommand('agent.openPickScheduleDialog', (ownerUri: string) => { vscode.commands.registerCommand('agent.openPickScheduleDialog', (ownerUri: 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

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

@@ -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;
let schedules: sqlops.AgentJobScheduleInfo[] = undefined;
let alerts: sqlops.AgentAlertInfo[] = undefined;
if (jobHistories && jobHistories[jobHistories.length-1]) {
// add steps // add steps
steps = jobHistories[jobHistories.length-1].steps; if (this.jobSteps && this.jobSteps[jobId]) {
if (steps && steps.length > 0) { let steps = this.jobSteps[jobId];
if (!job[0].JobSteps) { job[0].JobSteps = steps;
job[0].JobSteps = [];
}
if (job[0].JobSteps.length !== steps.length) {
job[0].JobSteps = [];
steps.forEach(step => {
job[0].JobSteps.push(step.stepDetails);
});
}
} }
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]) {
// 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 = [];
} }

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

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

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