diff --git a/extensions/agent/src/agentUtils.ts b/extensions/agent/src/agentUtils.ts index d3187ef5bb..f032ef545d 100644 --- a/extensions/agent/src/agentUtils.ts +++ b/extensions/agent/src/agentUtils.ts @@ -35,4 +35,5 @@ export class AgentUtils { } return this._queryProvider; } + } \ No newline at end of file diff --git a/extensions/agent/src/data/jobStepData.ts b/extensions/agent/src/data/jobStepData.ts index 79efe4bd39..b84c73f566 100644 --- a/extensions/agent/src/data/jobStepData.ts +++ b/extensions/agent/src/data/jobStepData.ts @@ -4,20 +4,22 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; +import * as sqlops from 'sqlops'; +import * as nls from 'vscode-nls'; +import * as vscode from 'vscode'; import { AgentUtils } from '../agentUtils'; import { IAgentDialogData, AgentDialogMode } from '../interfaces'; import { JobData } from './jobData'; -import * as nls from 'vscode-nls'; const localize = nls.loadMessageBundle(); export class JobStepData implements IAgentDialogData { // Error Messages - private 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_JobNameIsEmpty = localize('stepData.jobNameRequired', 'Job 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 jobId: string; public jobName: string; @@ -28,8 +30,8 @@ export class JobStepData implements IAgentDialogData { public id: number; public failureAction: string; public successAction: string; - public failStepId: number; public successStepId: number; + public failStepId: number; public command: string; public commandExecutionSuccessCode: number; public databaseName: string; @@ -43,10 +45,12 @@ export class JobStepData implements IAgentDialogData { public retryAttempts: number; public retryInterval: number; public proxyName: string; + private jobModel: JobData; constructor(ownerUri:string, jobModel?: JobData) { this.ownerUri = ownerUri; this.jobName = jobModel.name; + this.jobModel = jobModel; } public async initialize() { @@ -54,47 +58,35 @@ export class JobStepData implements IAgentDialogData { public async save() { let agentService = await AgentUtils.getAgentService(); - agentService.createJobStep(this.ownerUri, { - jobId: this.jobId, - jobName: this.jobName, - script: this.script, - scriptName: this.scriptName, - stepName: this.stepName, - subSystem: this.subSystem, - id: this.id, - failureAction: this.failureAction, - 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); + let result: any; + if (this.dialogMode === AgentDialogMode.CREATE) { + if (this.jobModel && this.jobModel.dialogMode === AgentDialogMode.CREATE) { + // create job -> create step + Promise.resolve(this); + return; + } else { + // edit job -> create step + result = await agentService.createJobStep(this.ownerUri, JobStepData.convertToAgentJobStepInfo(this)); } - }); + } 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[] } { let validationErrors: string[] = []; if (!(this.stepName && this.stepName.trim())) { - validationErrors.push(this.CreateStepErrorMessage_StepNameIsEmpty); + validationErrors.push(JobStepData.CreateStepErrorMessage_StepNameIsEmpty); } if (!(this.jobName && this.jobName.trim())) { - validationErrors.push(this.CreateStepErrorMessage_JobNameIsEmpty); + validationErrors.push(JobStepData.CreateStepErrorMessage_JobNameIsEmpty); } return { @@ -102,4 +94,66 @@ export class JobStepData implements IAgentDialogData { 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; + } + } \ No newline at end of file diff --git a/extensions/agent/src/dialogs/jobDialog.ts b/extensions/agent/src/dialogs/jobDialog.ts index 8bd76509ee..e77baa2261 100644 --- a/extensions/agent/src/dialogs/jobDialog.ts +++ b/extensions/agent/src/dialogs/jobDialog.ts @@ -235,13 +235,17 @@ export class JobDialog extends AgentDialog { width: 80 }).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) => { + if (!this.model.jobSteps) { + this.model.jobSteps = []; + } this.model.jobSteps.push(step); this.stepsTable.data = this.convertStepsToData(this.model.jobSteps); }); this.newStepButton.onDidClick((e)=>{ if (this.nameTextBox.value && this.nameTextBox.value.length > 0) { + stepDialog.jobName = this.nameTextBox.value; stepDialog.openDialog(); } else { this.dialog.message = { text: this.BlankJobNameErrorText }; @@ -267,17 +271,15 @@ export class JobDialog extends AgentDialog { // one step selection if (this.stepsTable.selectedRows.length === 1) { let rowNumber = this.stepsTable.selectedRows[0]; - let stepData = steps[rowNumber]; + let stepData = this.model.jobSteps[rowNumber]; this.deleteStepButton.enabled = true; this.editStepButton.enabled = true; - this.editStepButton.onDidClick((e) => { - // implement edit steps - - // let stepDialog = new JobStepDialog(this.model.ownerUri, this.nameTextBox.value, '' , 1, this.model); - // stepDialog.openNewStepDialog(); + this.editStepButton.onDidClick(() => { + let stepDialog = new JobStepDialog(this.model.ownerUri, '' , this.model, stepData); + stepDialog.openDialog(); }); - this.deleteStepButton.onDidClick((e) => { + this.deleteStepButton.onDidClick(() => { AgentUtils.getAgentService().then((agentService) => { let steps = this.model.jobSteps ? this.model.jobSteps : []; agentService.deleteJobStep(this.ownerUri, stepData).then((result) => { diff --git a/extensions/agent/src/dialogs/jobStepDialog.ts b/extensions/agent/src/dialogs/jobStepDialog.ts index 737851d448..da0ec4028b 100644 --- a/extensions/agent/src/dialogs/jobStepDialog.ts +++ b/extensions/agent/src/dialogs/jobStepDialog.ts @@ -10,6 +10,7 @@ import { JobStepData } from '../data/jobStepData'; import { AgentUtils } from '../agentUtils'; import { JobData } from '../data/jobData'; import { AgentDialog } from './agentDialog'; +import { AgentDialogMode } from '../interfaces'; const path = require('path'); const localize = nls.loadMessageBundle(); @@ -19,7 +20,8 @@ export class JobStepDialog extends AgentDialog { // TODO: localize // 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 OkButtonText: string = localize('jobStepDialog.ok', 'OK'); private readonly CancelButtonText: string = localize('jobStepDialog.cancel', 'Cancel'); @@ -102,24 +104,32 @@ export class JobStepDialog extends AgentDialog { // Checkbox private appendToExistingFileCheckbox: sqlops.CheckBoxComponent; private logToTableCheckbox: sqlops.CheckBoxComponent; + private logStepOutputHistoryCheckbox: sqlops.CheckBoxComponent; private fileBrowserTree: sqlops.FileBrowserTreeComponent; private jobModel: JobData; - private jobName: string; + public jobName: string; private server: string; private stepId: number; + private isEdit: boolean; constructor( ownerUri: string, server: string, - stepId: number, - jobModel?: JobData + jobModel: JobData, + jobStepInfo?: sqlops.AgentJobStepInfo, ) { - super(ownerUri, new JobStepData(ownerUri, jobModel), 'New Step'); - this.stepId = stepId; - this.jobName = jobModel.name; - this.server = server; + super(ownerUri, + jobStepInfo ? JobStepData.convertToJobStepData(jobStepInfo, jobModel) : new JobStepData(ownerUri, jobModel), + jobStepInfo ? JobStepDialog.EditDialogTitle : JobStepDialog.NewDialogTitle); + 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.jobName = this.jobName ? this.jobName : this.jobModel.name; + this.server = server; } private initializeUIComponents() { @@ -254,6 +264,14 @@ export class JobStepDialog extends AgentDialog { let formWrapper = view.modelBuilder.loadingComponent().withItem(formModel).component(); formWrapper.loading = false; 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 { let logToTableContainer = view.modelBuilder.flexContainer() .withLayout({ flexFlow: 'row', justifyContent: 'space-between', width: 300 }) .withItems([this.logToTableCheckbox]).component(); - let logStepOutputHistoryCheckbox = view.modelBuilder.checkBox() + this.logStepOutputHistoryCheckbox = view.modelBuilder.checkBox() .withProperties({ label: this.IncludeStepOutputHistoryLabel }).component(); this.userInputBox = view.modelBuilder.inputBox() .withProperties({ inputType: 'text', width: '100%' }).component(); @@ -315,7 +333,7 @@ export class JobStepDialog extends AgentDialog { component: appendCheckboxContainer, title: ' ' }, { - component: logStepOutputHistoryCheckbox, + component: this.logStepOutputHistoryCheckbox, title: '' }, { component: this.userInputBox, @@ -326,7 +344,19 @@ export class JobStepDialog extends AgentDialog { let formWrapper = view.modelBuilder.loadingComponent().withItem(formModel).component(); 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 { this.model.jobName = this.jobName; this.model.id = this.stepId; this.model.server = this.server; - this.model.stepName = this.nameTextBox.value; this.model.subSystem = this.typeDropdown.value as string; this.model.databaseName = this.databaseDropdown.value as string; this.model.script = this.commandTextBox.value; diff --git a/extensions/agent/src/mainController.ts b/extensions/agent/src/mainController.ts index 2797e4512c..d7f6b3fde4 100644 --- a/extensions/agent/src/mainController.ts +++ b/extensions/agent/src/mainController.ts @@ -13,6 +13,7 @@ import { OperatorDialog } from './dialogs/operatorDialog'; import { ProxyDialog } from './dialogs/proxyDialog'; import { JobStepDialog } from './dialogs/jobStepDialog'; import { PickScheduleDialog } from './dialogs/pickScheduleDialog'; +import { JobData } from './data/jobData'; const localize = nls.loadMessageBundle(); @@ -40,8 +41,8 @@ export class MainController { let dialog = new JobDialog(ownerUri, jobInfo); dialog.openDialog(); }); - vscode.commands.registerCommand('agent.openNewStepDialog', (ownerUri: string, server: string, stepId: number) => { - let dialog = new JobStepDialog(ownerUri, server, stepId); + vscode.commands.registerCommand('agent.openNewStepDialog', (ownerUri: string, server: string, jobData: JobData) => { + let dialog = new JobStepDialog(ownerUri, server, jobData); dialog.openDialog(); }); vscode.commands.registerCommand('agent.openPickScheduleDialog', (ownerUri: string) => { diff --git a/extensions/agent/src/test/testAgentService.ts b/extensions/agent/src/test/testAgentService.ts index 443fff76ce..ae68cf12bf 100644 --- a/extensions/agent/src/test/testAgentService.ts +++ b/extensions/agent/src/test/testAgentService.ts @@ -14,7 +14,7 @@ export class TestAgentService implements sqlops.AgentServicesProvider { getJobs(ownerUri: string): Thenable { return undefined; } - getJobHistory(ownerUri: string, jobId: string): Thenable { + getJobHistory(ownerUri: string, jobId: string, jobName: string): Thenable { return undefined; } jobAction(ownerUri: string, jobName: string, action: string): Thenable { diff --git a/extensions/mssql/src/contracts.ts b/extensions/mssql/src/contracts.ts index eab6339bc3..4b2b7bba86 100644 --- a/extensions/mssql/src/contracts.ts +++ b/extensions/mssql/src/contracts.ts @@ -40,6 +40,7 @@ export interface AgentJobsParams { export interface AgentJobHistoryParams { ownerUri: string; jobId: string; + jobName: string; } export interface AgentJobActionParams { diff --git a/extensions/mssql/src/features.ts b/extensions/mssql/src/features.ts index 57db9f1f33..a108d16cc0 100644 --- a/extensions/mssql/src/features.ts +++ b/extensions/mssql/src/features.ts @@ -80,8 +80,8 @@ export class AgentServicesFeature extends SqlOpsFeature { ); }; - let getJobHistory = (ownerUri: string, jobID: string): Thenable => { - let params: contracts.AgentJobHistoryParams = { ownerUri: ownerUri, jobId: jobID }; + let getJobHistory = (ownerUri: string, jobID: string, jobName: string): Thenable => { + let params: contracts.AgentJobHistoryParams = { ownerUri: ownerUri, jobId: jobID, jobName: jobName }; return client.sendRequest(contracts.AgentJobHistoryRequest.type, params).then( r => r, diff --git a/src/sql/parts/jobManagement/common/interfaces.ts b/src/sql/parts/jobManagement/common/interfaces.ts index 2074d9454a..19fbbe2088 100644 --- a/src/sql/parts/jobManagement/common/interfaces.ts +++ b/src/sql/parts/jobManagement/common/interfaces.ts @@ -22,7 +22,7 @@ export interface IJobManagementService { fireOnDidChange(): void; getJobs(connectionUri: string): Thenable; - getJobHistory(connectionUri: string, jobID: string): Thenable; + getJobHistory(connectionUri: string, jobID: string, jobName: string): Thenable; deleteJob(connectionUri: string, job: sqlops.AgentJobInfo): Thenable; deleteJobStep(connectionUri: string, step: sqlops.AgentJobStepInfo): Thenable; diff --git a/src/sql/parts/jobManagement/common/jobManagementService.ts b/src/sql/parts/jobManagement/common/jobManagementService.ts index eaa4ceea2e..cb8de9e61c 100644 --- a/src/sql/parts/jobManagement/common/jobManagementService.ts +++ b/src/sql/parts/jobManagement/common/jobManagementService.ts @@ -43,9 +43,9 @@ export class JobManagementService implements IJobManagementService { }); } - public getJobHistory(connectionUri: string, jobID: string): Thenable { + public getJobHistory(connectionUri: string, jobID: string, jobName: string): Thenable { 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; private _jobs: sqlops.AgentJobInfo[] = []; 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 _prevJobID: string; private _serverName: string; @@ -176,6 +179,18 @@ export class JobCacheObject { 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 */ public set jobs(value: sqlops.AgentJobInfo[]) { this._jobs = value; @@ -204,4 +219,16 @@ export class JobCacheObject { public set dataView(value: Slick.Data.DataView) { 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; + } } \ No newline at end of file diff --git a/src/sql/parts/jobManagement/views/jobHistory.component.ts b/src/sql/parts/jobManagement/views/jobHistory.component.ts index 7fe8d1e5ab..57662d387e 100644 --- a/src/sql/parts/jobManagement/views/jobHistory.component.ts +++ b/src/sql/parts/jobManagement/views/jobHistory.component.ts @@ -150,16 +150,17 @@ export class JobHistoryComponent extends JobManagementView implements OnInit { private loadHistory() { const self = this; let ownerUri: string = this._commonService.connectionManagementService.connectionInfo.ownerUri; - this._jobManagementService.getJobHistory(ownerUri, this._agentViewComponent.jobId).then((result) => { - if (result && result.jobs) { - if (result.jobs.length > 0) { + let jobName = this._agentViewComponent.agentJobInfo.name; + this._jobManagementService.getJobHistory(ownerUri, this._agentViewComponent.jobId, jobName).then((result) => { + if (result && result.histories) { + if (result.histories.length > 0) { self._showPreviousRuns = true; - self.buildHistoryTree(self, result.jobs); + self.buildHistoryTree(self, result.histories); if (self._agentViewComponent.showHistory) { self._cd.detectChanges(); } } else { - self._jobCacheObject.setJobHistory(self._agentViewComponent.jobId, result.jobs); + self._jobCacheObject.setJobHistory(self._agentViewComponent.jobId, result.histories); self._showPreviousRuns = false; } } else { diff --git a/src/sql/parts/jobManagement/views/jobManagementView.ts b/src/sql/parts/jobManagement/views/jobManagementView.ts index 8277ac3b50..e1b04e2b08 100644 --- a/src/sql/parts/jobManagement/views/jobManagementView.ts +++ b/src/sql/parts/jobManagement/views/jobManagementView.ts @@ -53,6 +53,7 @@ export abstract class JobManagementView extends TabChild implements AfterContent if (!this.isInitialized) { this._showProgressWheel = true; this.onFirstVisible(); + this.layout(); this.isInitialized = true; } } else if (this.isVisible === true && this._parentComponent.refresh === true) { diff --git a/src/sql/parts/jobManagement/views/jobsView.component.ts b/src/sql/parts/jobManagement/views/jobsView.component.ts index a6646460ca..eb96c1830e 100644 --- a/src/sql/parts/jobManagement/views/jobsView.component.ts +++ b/src/sql/parts/jobManagement/views/jobsView.component.ts @@ -36,8 +36,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { IDashboardService } from 'sql/services/dashboard/common/dashboardService'; import { escape } from 'sql/base/common/strings'; import { IWorkbenchThemeService, IColorTheme } from 'vs/workbench/services/themes/common/workbenchThemeService'; -import { tableBackground, cellBackground, tableHoverBackground, jobsHeadingBackground, cellBorderColor } from 'sql/common/theme/colors'; -import { JobStepsViewRow } from 'sql/parts/jobManagement/views/jobStepsViewTree'; +import { tableBackground, cellBackground, cellBorderColor } from 'sql/common/theme/colors'; export const JOBSVIEW_SELECTOR: string = 'jobsview-component'; export const ROW_HEIGHT: number = 45; @@ -87,7 +86,10 @@ export class JobsViewComponent extends JobManagementView implements OnInit { private sortingStylingMap: { [columnName: string]: any; } = {}; 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; @ViewChild('jobsgrid') _gridEl: ElementRef; @@ -579,10 +581,14 @@ export class JobsViewComponent extends JobManagementView implements OnInit { private async curateJobHistory(jobs: sqlops.AgentJobInfo[], ownerUri: string) { const self = this; jobs.forEach(async (job) => { - await this._jobManagementService.getJobHistory(ownerUri, job.jobId).then((result) => { - if (result && result.jobs) { - self.jobHistories[job.jobId] = result.jobs; - self._jobCacheObject.setJobHistory(job.jobId, result.jobs); + await this._jobManagementService.getJobHistory(ownerUri, job.jobId, job.name).then((result) => { + if (result) { + self.jobSteps[job.jobId] = result.steps ? result.steps : []; + 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 previousRuns: sqlops.AgentJobHistoryInfo[]; if (jobHistories.length >= 5) { @@ -592,7 +598,7 @@ export class JobsViewComponent extends JobManagementView implements OnInit { } self.createJobChart(job.jobId, previousRuns); 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 noStepsMessage = nls.localize('jobsView.noSteps', 'No Steps available for this job.'); let errorMessage = lastJobHistory ? lastJobHistory.message : noStepsMessage; @@ -909,29 +915,22 @@ export class JobsViewComponent extends JobManagementView implements OnInit { jobId = data.getItem(rowIndex - 1).jobId; } } + let job: sqlops.AgentJobInfo[] = this.jobs.filter(job => { return job.jobId === jobId; }); - let jobHistories = this.jobHistories[jobId]; - let steps: sqlops.AgentJobStep[] = undefined; - let schedules: sqlops.AgentJobScheduleInfo[] = undefined; - let alerts: sqlops.AgentAlertInfo[] = undefined; + + // add steps + if (this.jobSteps && this.jobSteps[jobId]) { + 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]) { - // 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 - schedules = jobHistories[jobHistories.length-1].schedules; if (schedules && schedules.length > 0) { if (!job[0].JobSchedules) { job[0].JobSchedules = []; @@ -944,7 +943,6 @@ export class JobsViewComponent extends JobManagementView implements OnInit { } } // add alerts - alerts = jobHistories[jobHistories.length-1].alerts; if (!job[0].Alerts) { job[0].Alerts = []; } diff --git a/src/sql/sqlops.d.ts b/src/sql/sqlops.d.ts index a34e97b8dc..1726ac7bad 100644 --- a/src/sql/sqlops.d.ts +++ b/src/sql/sqlops.d.ts @@ -1266,6 +1266,16 @@ declare module 'sqlops' { Last = 16 } + export enum JobExecutionStatus { + Executing = 1, + WaitingForWorkerThread = 2, + BetweenRetries = 3, + Idle = 4, + Suspended = 5, + WaitingForStepToFinish = 6, + PerformingCompletionAction = 7 + } + export interface AgentJobInfo { name: string; owner: string; @@ -1371,8 +1381,6 @@ declare module 'sqlops' { retriesAttempted: string; server: string; steps: AgentJobStep[]; - schedules: AgentJobScheduleInfo[]; - alerts: AgentAlertInfo[]; } export interface AgentProxyInfo { @@ -1441,7 +1449,10 @@ declare module 'sqlops' { } export interface AgentJobHistoryResult extends ResultStatus { - jobs: AgentJobHistoryInfo[]; + histories: AgentJobHistoryInfo[]; + steps: AgentJobStepInfo[]; + schedules: AgentJobScheduleInfo[]; + alerts: AgentAlertInfo[]; } export interface CreateAgentJobResult extends ResultStatus { @@ -1529,7 +1540,7 @@ declare module 'sqlops' { export interface AgentServicesProvider extends DataProvider { // Job management methods getJobs(ownerUri: string): Thenable; - getJobHistory(ownerUri: string, jobId: string): Thenable; + getJobHistory(ownerUri: string, jobId: string, jobName: string): Thenable; jobAction(ownerUri: string, jobName: string, action: string): Thenable; createJob(ownerUri: string, jobInfo: AgentJobInfo): Thenable; updateJob(ownerUri: string, originalJobName: string, jobInfo: AgentJobInfo): Thenable; diff --git a/src/sql/workbench/api/common/sqlExtHostTypes.ts b/src/sql/workbench/api/common/sqlExtHostTypes.ts index 4fc3230da0..a02bbe84b4 100644 --- a/src/sql/workbench/api/common/sqlExtHostTypes.ts +++ b/src/sql/workbench/api/common/sqlExtHostTypes.ts @@ -95,6 +95,16 @@ export enum JobCompletionActionCondition { Always = 3 } +export enum JobExecutionStatus { + Executing = 1, + WaitingForWorkerThread = 2, + BetweenRetries = 3, + Idle = 4, + Suspended = 5, + WaitingForStepToFinish = 6, + PerformingCompletionAction = 7 +} + export enum AlertType { sqlServerEvent = 1, sqlServerPerformanceCondition = 2, diff --git a/src/sql/workbench/api/node/extHostDataProtocol.ts b/src/sql/workbench/api/node/extHostDataProtocol.ts index 0d430c383e..2e83aad1f3 100644 --- a/src/sql/workbench/api/node/extHostDataProtocol.ts +++ b/src/sql/workbench/api/node/extHostDataProtocol.ts @@ -578,8 +578,8 @@ export class ExtHostDataProtocol extends ExtHostDataProtocolShape { /** * Get a Agent Job's history */ - public $getJobHistory(handle: number, ownerUri: string, jobID: string): Thenable { - return this._resolveProvider(handle).getJobHistory(ownerUri, jobID); + public $getJobHistory(handle: number, ownerUri: string, jobID: string, jobName: string): Thenable { + return this._resolveProvider(handle).getJobHistory(ownerUri, jobID, jobName); } /** diff --git a/src/sql/workbench/api/node/mainThreadDataProtocol.ts b/src/sql/workbench/api/node/mainThreadDataProtocol.ts index bed01823a2..1c76fc25a6 100644 --- a/src/sql/workbench/api/node/mainThreadDataProtocol.ts +++ b/src/sql/workbench/api/node/mainThreadDataProtocol.ts @@ -350,8 +350,8 @@ export class MainThreadDataProtocol implements MainThreadDataProtocolShape { getJobs(connectionUri: string): Thenable { return self._proxy.$getJobs(handle, connectionUri); }, - getJobHistory(connectionUri: string, jobID: string): Thenable { - return self._proxy.$getJobHistory(handle, connectionUri, jobID); + getJobHistory(connectionUri: string, jobID: string, jobName: string): Thenable { + return self._proxy.$getJobHistory(handle, connectionUri, jobID, jobName); }, jobAction(connectionUri: string, jobName: string, action: string): Thenable { return self._proxy.$jobAction(handle, connectionUri, jobName, action); diff --git a/src/sql/workbench/api/node/sqlExtHost.api.impl.ts b/src/sql/workbench/api/node/sqlExtHost.api.impl.ts index efdd7e06a0..3c99d11f94 100644 --- a/src/sql/workbench/api/node/sqlExtHost.api.impl.ts +++ b/src/sql/workbench/api/node/sqlExtHost.api.impl.ts @@ -422,6 +422,7 @@ export function createApiFactory( WeekDays: sqlExtHostTypes.WeekDays, NotifyMethods: sqlExtHostTypes.NotifyMethods, JobCompletionActionCondition: sqlExtHostTypes.JobCompletionActionCondition, + JobExecutionStatus: sqlExtHostTypes.JobExecutionStatus, AlertType: sqlExtHostTypes.AlertType, FrequencyTypes: sqlExtHostTypes.FrequencyTypes, FrequencySubDayTypes: sqlExtHostTypes.FrequencySubDayTypes, diff --git a/src/sql/workbench/api/node/sqlExtHost.protocol.ts b/src/sql/workbench/api/node/sqlExtHost.protocol.ts index 7b7d94bda3..6694a5e502 100644 --- a/src/sql/workbench/api/node/sqlExtHost.protocol.ts +++ b/src/sql/workbench/api/node/sqlExtHost.protocol.ts @@ -358,7 +358,7 @@ export abstract class ExtHostDataProtocolShape { /** * Get a Agent Job's history */ - $getJobHistory(handle: number, ownerUri: string, jobID: string): Thenable { throw ni(); } + $getJobHistory(handle: number, ownerUri: string, jobID: string, jobName: string): Thenable { throw ni(); } /** * Run an action on a Job