From ce7893c2e52e67bff2927891fd77cd9732d44f45 Mon Sep 17 00:00:00 2001 From: Aditya Bist Date: Wed, 31 Oct 2018 16:40:36 -0700 Subject: [PATCH] Agent/edit job logic (#3023) * lumped stepdata with jobdata in job dialog * fix bug with empty steps * added clumped and update steps and schedules from job dialog * edit data sends one call instead of multiple * cleaned code --- extensions/agent/src/data/alertData.ts | 31 +++++-- extensions/agent/src/data/jobData.ts | 17 +--- extensions/agent/src/data/jobStepData.ts | 20 ++--- extensions/agent/src/data/pickScheduleData.ts | 2 - extensions/agent/src/dialogs/alertDialog.ts | 21 +++-- extensions/agent/src/dialogs/jobDialog.ts | 83 +++++++++++-------- extensions/agent/src/dialogs/jobStepDialog.ts | 3 +- extensions/agent/src/mainController.ts | 19 +++-- .../parts/jobManagement/common/jobActions.ts | 8 +- .../jobManagement/views/jobsView.component.ts | 36 +++----- 10 files changed, 129 insertions(+), 111 deletions(-) diff --git a/extensions/agent/src/data/alertData.ts b/extensions/agent/src/data/alertData.ts index bcef62b4ea..295661b2cc 100644 --- a/extensions/agent/src/data/alertData.ts +++ b/extensions/agent/src/data/alertData.ts @@ -9,6 +9,7 @@ import * as vscode from 'vscode'; import * as sqlops from 'sqlops'; import { AgentUtils } from '../agentUtils'; import { IAgentDialogData, AgentDialogMode } from '../interfaces'; +import { JobData } from './jobData'; const localize = nls.loadMessageBundle(); @@ -45,8 +46,19 @@ export class AlertData implements IAgentDialogData { wmiEventNamespace: string; wmiEventQuery: string; - constructor(ownerUri:string, alertInfo: sqlops.AgentAlertInfo) { + private viaJobDialog: boolean; + private jobModel: JobData; + + constructor( + ownerUri:string, + alertInfo: sqlops.AgentAlertInfo, + jobModel?: JobData, + viaJobDialog: boolean = false + ) { this.ownerUri = ownerUri; + this.viaJobDialog = viaJobDialog; + this.jobModel = jobModel; + this.jobName = this.jobName ? this.jobName : this.jobModel.name; if (alertInfo) { this.dialogMode = AgentDialogMode.EDIT; @@ -60,7 +72,6 @@ export class AlertData implements IAgentDialogData { this.includeEventDescription = alertInfo.includeEventDescription.toString(); this.isEnabled = alertInfo.isEnabled; this.jobId = alertInfo.jobId; - this.jobName = alertInfo.jobName; this.lastOccurrenceDate = alertInfo.lastOccurrenceDate; this.lastResponseDate = alertInfo.lastResponseDate; this.messageId = alertInfo.messageId; @@ -82,10 +93,18 @@ export class AlertData implements IAgentDialogData { public async save() { let agentService = await AgentUtils.getAgentService(); - let result = this.dialogMode === AgentDialogMode.CREATE - ? await agentService.createAlert(this.ownerUri, this.toAgentAlertInfo()) - : await agentService.updateAlert(this.ownerUri, this.originalName, this.toAgentAlertInfo()); - + let result: any; + // if it's called via the job dialog, add it to the + // job model + if (this.viaJobDialog) { + if (this.jobModel) { + Promise.resolve(this); + return; + } + } else { + // has to be a create alert + result = await agentService.createAlert(this.ownerUri, this.toAgentAlertInfo()); + } if (!result || !result.success) { vscode.window.showErrorMessage( localize('alertData.saveErrorMessage', "Alert update failed '{0}'", result.errorMessage ? result.errorMessage : 'Unknown')); diff --git a/extensions/agent/src/data/jobData.ts b/extensions/agent/src/data/jobData.ts index 9f6f24cc87..1d512edd62 100644 --- a/extensions/agent/src/data/jobData.ts +++ b/extensions/agent/src/data/jobData.ts @@ -44,6 +44,7 @@ export class JobData implements IAgentDialogData { public jobSteps: sqlops.AgentJobStepInfo[]; public jobSchedules: sqlops.AgentJobScheduleInfo[]; public alerts: sqlops.AgentAlertInfo[]; + public jobId: string; constructor( ownerUri: string, @@ -62,6 +63,7 @@ export class JobData implements IAgentDialogData { this.jobSteps = jobInfo.JobSteps; this.jobSchedules = jobInfo.JobSchedules; this.alerts = jobInfo.Alerts; + this.jobId = jobInfo.jobId; } } @@ -115,7 +117,6 @@ export class JobData implements IAgentDialogData { let result = this.dialogMode === AgentDialogMode.CREATE ? await this._agentService.createJob(this.ownerUri, jobInfo) : await this._agentService.updateJob(this.ownerUri, this.originalName, jobInfo); - if (!result || !result.success) { vscode.window.showErrorMessage( localize('jobData.saveErrorMessage', "Job update failed '{0}'", result.errorMessage ? result.errorMessage : 'Unknown')); @@ -135,18 +136,6 @@ export class JobData implements IAgentDialogData { }; } - public addJobSchedule(schedule: sqlops.AgentJobScheduleInfo) { - if (this.jobSchedules) { - let existingSchedule = this.jobSchedules.find(item => item.name === schedule.name); - if (!existingSchedule) { - this.jobSchedules.push(schedule); - } - } else { - this.jobSchedules = []; - this.jobSchedules.push(schedule); - } - } - public toAgentJobInfo(): sqlops.AgentJobInfo { return { name: this.name, @@ -177,7 +166,7 @@ export class JobData implements IAgentDialogData { categoryType: 1, // LocalJob, hard-coding the value, corresponds to the target tab in SSMS lastRun: '', nextRun: '', - jobId: '' + jobId: this.jobId }; } } \ No newline at end of file diff --git a/extensions/agent/src/data/jobStepData.ts b/extensions/agent/src/data/jobStepData.ts index b84c73f566..a9624442f7 100644 --- a/extensions/agent/src/data/jobStepData.ts +++ b/extensions/agent/src/data/jobStepData.ts @@ -46,11 +46,13 @@ export class JobStepData implements IAgentDialogData { public retryInterval: number; public proxyName: string; private jobModel: JobData; + private viaJobDialog: boolean; - constructor(ownerUri:string, jobModel?: JobData) { + constructor(ownerUri:string, jobModel?: JobData, viaJobDialog: boolean = false) { this.ownerUri = ownerUri; this.jobName = jobModel.name; this.jobModel = jobModel; + this.viaJobDialog = viaJobDialog; } public async initialize() { @@ -59,18 +61,16 @@ export class JobStepData implements IAgentDialogData { public async save() { let agentService = await AgentUtils.getAgentService(); let result: any; - if (this.dialogMode === AgentDialogMode.CREATE) { - if (this.jobModel && this.jobModel.dialogMode === AgentDialogMode.CREATE) { - // create job -> create step + // if it's called via the job dialog, add it to the + // job model + if (this.viaJobDialog) { + if (this.jobModel) { 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)); + } else { + // has to be a create step + result = await agentService.createJobStep(this.ownerUri, JobStepData.convertToAgentJobStepInfo(this)); } if (!result || !result.success) { vscode.window.showErrorMessage( diff --git a/extensions/agent/src/data/pickScheduleData.ts b/extensions/agent/src/data/pickScheduleData.ts index 15ad4c0f34..d2a46bf729 100644 --- a/extensions/agent/src/data/pickScheduleData.ts +++ b/extensions/agent/src/data/pickScheduleData.ts @@ -29,8 +29,6 @@ export class PickScheduleData implements IAgentDialogData { } public async save() { - let agentService = await AgentUtils.getAgentService(); this.selectedSchedule.jobName = this.jobName; - let result = await agentService.createJobSchedule(this.ownerUri, this.selectedSchedule); } } diff --git a/extensions/agent/src/dialogs/alertDialog.ts b/extensions/agent/src/dialogs/alertDialog.ts index 1e7bfe8b5e..b10ba8e40e 100644 --- a/extensions/agent/src/dialogs/alertDialog.ts +++ b/extensions/agent/src/dialogs/alertDialog.ts @@ -12,6 +12,7 @@ import { AgentUtils } from '../agentUtils'; import { AlertData } from '../data/alertData'; import { OperatorDialog } from './operatorDialog'; import { JobDialog } from './jobDialog'; +import { JobData } from '../data/jobData'; const localize = nls.loadMessageBundle(); @@ -148,14 +149,23 @@ export class AlertDialog extends AgentDialog { private delayMinutesTextBox: sqlops.InputBoxComponent; private delaySecondsTextBox: sqlops.InputBoxComponent; - private jobs: string[]; private databases: string[]; + private jobModel: JobData; + public jobId: string; + public jobName: string; - constructor(ownerUri: string, alertInfo: sqlops.AgentAlertInfo = undefined, jobs: string[]) { + constructor( + ownerUri: string, + jobModel: JobData, + alertInfo: sqlops.AgentAlertInfo = undefined, + viaJobDialog: boolean = false + ) { super(ownerUri, - new AlertData(ownerUri, alertInfo), + new AlertData(ownerUri, alertInfo, jobModel, viaJobDialog), alertInfo ? AlertDialog.EditDialogTitle : AlertDialog.CreateDialogTitle); - this.jobs = jobs; + this.jobModel = jobModel; + this.jobId = this.jobId ? this.jobId : this.jobModel.jobId; + this.jobName = this.jobName ? this.jobName : this.jobModel.name; } protected async initializeDialog(dialog: sqlops.window.modelviewdialog.Dialog) { @@ -512,7 +522,8 @@ export class AlertDialog extends AgentDialog { protected updateModel() { this.model.name = this.nameTextBox.value; this.model.isEnabled = this.enabledCheckBox.checked; - + this.model.jobId = this.jobId; + this.model.jobName = this.jobName; this.model.alertType = this.getDropdownValue(this.typeDropDown); let databaseName = this.getDropdownValue(this.databaseDropDown); this.model.databaseName = (databaseName !== AlertDialog.AllDatabases) ? databaseName : undefined; diff --git a/extensions/agent/src/dialogs/jobDialog.ts b/extensions/agent/src/dialogs/jobDialog.ts index bf3523daf1..bbbce0f4a8 100644 --- a/extensions/agent/src/dialogs/jobDialog.ts +++ b/extensions/agent/src/dialogs/jobDialog.ts @@ -11,6 +11,7 @@ import { PickScheduleDialog } from './pickScheduleDialog'; import { AlertDialog } from './alertDialog'; import { AgentDialog } from './agentDialog'; import { AgentUtils } from '../agentUtils'; +import { JobStepData } from '../data/jobStepData'; const localize = nls.loadMessageBundle(); @@ -110,11 +111,19 @@ export class JobDialog extends AgentDialog { private newAlertButton: sqlops.ButtonComponent; private isEdit: boolean = false; + // Job objects + private steps: sqlops.AgentJobStepInfo[]; + private schedules: sqlops.AgentJobScheduleInfo[]; + private alerts: sqlops.AgentAlertInfo[] = []; + constructor(ownerUri: string, jobInfo: sqlops.AgentJobInfo = undefined) { super( ownerUri, new JobData(ownerUri, jobInfo), jobInfo ? JobDialog.EditDialogTitle : JobDialog.CreateDialogTitle); + this.steps = this.model.jobSteps ? this.model.jobSteps : []; + this.schedules = this.model.jobSchedules ? this.model.jobSchedules : []; + this.alerts = this.model.alerts ? this.model.alerts : []; this.isEdit = jobInfo ? true : false; } @@ -198,12 +207,7 @@ export class JobDialog extends AgentDialog { private initializeStepsTab() { this.stepsTab.registerContent(async view => { - let previewTag = view.modelBuilder.text() - .withProperties({ - value: 'Feature Preview' - }).component(); - let steps = this.model.jobSteps ? this.model.jobSteps : []; - let data = this.convertStepsToData(steps); + let data = this.steps ? this.convertStepsToData(this.steps) : []; this.stepsTable = view.modelBuilder.table() .withProperties({ columns: [ @@ -237,13 +241,11 @@ export class JobDialog extends AgentDialog { width: 80 }).component(); - let stepDialog = new JobStepDialog(this.model.ownerUri, '' , this.model); + let stepDialog = new JobStepDialog(this.model.ownerUri, '' , this.model, null, true); stepDialog.onSuccess((step) => { - if (!this.model.jobSteps) { - this.model.jobSteps = []; - } - this.model.jobSteps.push(step); - this.stepsTable.data = this.convertStepsToData(this.model.jobSteps); + let stepInfo = JobStepData.convertToAgentJobStepInfo(step); + this.steps.push(stepInfo); + this.stepsTable.data = this.convertStepsToData(this.steps); }); this.newStepButton.onDidClick((e)=>{ if (this.nameTextBox.value && this.nameTextBox.value.length > 0) { @@ -277,7 +279,7 @@ export class JobDialog extends AgentDialog { this.deleteStepButton.enabled = true; this.editStepButton.enabled = true; this.editStepButton.onDidClick(() => { - let stepDialog = new JobStepDialog(this.model.ownerUri, '' , this.model, stepData); + let stepDialog = new JobStepDialog(this.model.ownerUri, '' , this.model, stepData, true); stepDialog.openDialog(); }); @@ -287,7 +289,6 @@ export class JobDialog extends AgentDialog { agentService.deleteJobStep(this.ownerUri, stepData).then((result) => { if (result && result.success) { delete steps[rowNumber]; - this.model.jobSteps = steps; let data = this.convertStepsToData(steps); this.stepsTable.data = data; } @@ -299,10 +300,6 @@ export class JobDialog extends AgentDialog { let formModel = view.modelBuilder.formContainer() .withFormItems([{ - component: previewTag, - title: '' - }, - { component: this.stepsTable, title: this.JobStepsTopLabelString, actions: [this.moveStepUpButton, this.moveStepDownButton, this.newStepButton, this.editStepButton, this.deleteStepButton] @@ -313,10 +310,6 @@ export class JobDialog extends AgentDialog { private initializeAlertsTab() { this.alertsTab.registerContent(async view => { - let previewTag = view.modelBuilder.text() - .withProperties({ - value: 'Feature Preview' - }).component(); let alerts = this.model.alerts ? this.model.alerts : []; let data = this.convertAlertsToData(alerts); this.alertsTable = view.modelBuilder.table() @@ -327,7 +320,7 @@ export class JobDialog extends AgentDialog { this.AlertTypeLabelString ], data: data, - height: 430, + height: 750, width: 400 }).component(); @@ -336,18 +329,24 @@ export class JobDialog extends AgentDialog { width: 80 }).component(); - this.newAlertButton.onDidClick((e)=>{ - let alertDialog = new AlertDialog(this.model.ownerUri, null, []); - alertDialog.onSuccess((dialogModel) => { - }); - alertDialog.openDialog(); + let alertDialog = new AlertDialog(this.model.ownerUri, this.model, null, true); + alertDialog.onSuccess((alert) => { + let alertInfo = alert.toAgentAlertInfo(); + this.alerts.push(alertInfo); + this.alertsTable.data = this.convertAlertsToData(this.alerts); + }); + this.newAlertButton.onDidClick(()=>{ + if (this.nameTextBox.value && this.nameTextBox.value.length > 0) { + alertDialog.jobId = this.model.jobId; + alertDialog.jobName = this.model.name ? this.model.name : this.nameTextBox.value; + alertDialog.openDialog(); + } else { + this.dialog.message = { text: this.BlankJobNameErrorText }; + } }); let formModel = view.modelBuilder.formContainer() .withFormItems([{ - component: previewTag, - title: '' - }, { component: this.alertsTable, title: this.AlertsTopLabelString, actions: [this.newAlertButton] @@ -380,8 +379,11 @@ export class JobDialog extends AgentDialog { pickScheduleDialog.onSuccess((dialogModel) => { let selectedSchedule = dialogModel.selectedSchedule; if (selectedSchedule) { - selectedSchedule.jobName = this.model.name; - this.model.addJobSchedule(selectedSchedule); + let existingSchedule = this.schedules.find(item => item.name === selectedSchedule.name); + if (!existingSchedule) { + selectedSchedule.jobName = this.model.name ? this.model.name : this.nameTextBox.value; + this.schedules.push(selectedSchedule); + } this.populateScheduleTable(); } }); @@ -402,8 +404,7 @@ export class JobDialog extends AgentDialog { } private populateScheduleTable() { - let schedules = this.model.jobSchedules ? this.model.jobSchedules : []; - let data = this.convertSchedulesToData(schedules); + let data = this.convertSchedulesToData(this.schedules); if (data.length > 0) { this.schedulesTable.data = data; this.schedulesTable.height = 750; @@ -566,5 +567,17 @@ export class JobDialog extends AgentDialog { this.model.pageLevel = this.getActualConditionValue(this.pagerCheckBox, this.pagerConditionDropdown); this.model.eventLogLevel = this.getActualConditionValue(this.eventLogCheckBox, this.eventLogConditionDropdown); this.model.deleteLevel = this.getActualConditionValue(this.deleteJobCheckBox, this.deleteJobConditionDropdown); + if (!this.model.jobSteps) { + this.model.jobSteps = []; + } + this.model.jobSteps = this.steps; + if (!this.model.jobSchedules) { + this.model.jobSchedules = []; + } + this.model.jobSchedules = this.schedules; + if (!this.model.alerts) { + this.model.alerts = []; + } + this.model.alerts = this.alerts; } } \ No newline at end of file diff --git a/extensions/agent/src/dialogs/jobStepDialog.ts b/extensions/agent/src/dialogs/jobStepDialog.ts index da0ec4028b..614f49794c 100644 --- a/extensions/agent/src/dialogs/jobStepDialog.ts +++ b/extensions/agent/src/dialogs/jobStepDialog.ts @@ -118,9 +118,10 @@ export class JobStepDialog extends AgentDialog { server: string, jobModel: JobData, jobStepInfo?: sqlops.AgentJobStepInfo, + viaJobDialog: boolean = false ) { super(ownerUri, - jobStepInfo ? JobStepData.convertToJobStepData(jobStepInfo, jobModel) : new JobStepData(ownerUri, jobModel), + jobStepInfo ? JobStepData.convertToJobStepData(jobStepInfo, jobModel) : new JobStepData(ownerUri, jobModel, viaJobDialog), jobStepInfo ? JobStepDialog.EditDialogTitle : JobStepDialog.NewDialogTitle); this.stepId = jobStepInfo ? jobStepInfo.id : jobModel.jobSteps ? diff --git a/extensions/agent/src/mainController.ts b/extensions/agent/src/mainController.ts index af10fa01be..ce1ff93608 100644 --- a/extensions/agent/src/mainController.ts +++ b/extensions/agent/src/mainController.ts @@ -14,6 +14,7 @@ import { ProxyDialog } from './dialogs/proxyDialog'; import { JobStepDialog } from './dialogs/jobStepDialog'; import { PickScheduleDialog } from './dialogs/pickScheduleDialog'; import { JobData } from './data/jobData'; +import { AgentUtils } from './agentUtils'; const localize = nls.loadMessageBundle(); @@ -41,17 +42,23 @@ export class MainController { let dialog = new JobDialog(ownerUri, jobInfo); dialog.openDialog(); }); - vscode.commands.registerCommand('agent.openNewStepDialog', (ownerUri: string, server: string, jobData: JobData, jobStepInfo: sqlops.AgentJobStepInfo) => { - let dialog = new JobStepDialog(ownerUri, server, jobData, jobStepInfo); - dialog.openDialog(); + vscode.commands.registerCommand('agent.openNewStepDialog', (ownerUri: string, server: string, jobInfo: sqlops.AgentJobInfo, jobStepInfo: sqlops.AgentJobStepInfo) => { + AgentUtils.getAgentService().then((agentService) => { + let jobData: JobData = new JobData(ownerUri, jobInfo, agentService); + let dialog = new JobStepDialog(ownerUri, server, jobData, jobStepInfo, false); + dialog.openDialog(); + }); }); vscode.commands.registerCommand('agent.openPickScheduleDialog', (ownerUri: string, jobName: string) => { let dialog = new PickScheduleDialog(ownerUri, jobName); dialog.showDialog(); }); - vscode.commands.registerCommand('agent.openAlertDialog', (ownerUri: string, alertInfo: sqlops.AgentAlertInfo, jobs: string[]) => { - let dialog = new AlertDialog(ownerUri, alertInfo, jobs); - dialog.openDialog(); + vscode.commands.registerCommand('agent.openAlertDialog', (ownerUri: string, jobInfo: sqlops.AgentJobInfo, alertInfo: sqlops.AgentAlertInfo) => { + AgentUtils.getAgentService().then((agentService) => { + let jobData: JobData = new JobData(ownerUri, jobInfo, agentService); + let dialog = new AlertDialog(ownerUri, jobData, alertInfo, false); + dialog.openDialog(); + }); }); vscode.commands.registerCommand('agent.openOperatorDialog', (ownerUri: string, operatorInfo: sqlops.AgentOperatorInfo) => { let dialog = new OperatorDialog(ownerUri, operatorInfo); diff --git a/src/sql/parts/jobManagement/common/jobActions.ts b/src/sql/parts/jobManagement/common/jobActions.ts index ef48595e69..1ec5c54fd9 100644 --- a/src/sql/parts/jobManagement/common/jobActions.ts +++ b/src/sql/parts/jobManagement/common/jobActions.ts @@ -219,14 +219,10 @@ export class NewStepAction extends Action { public run(context: JobHistoryComponent): TPromise { let ownerUri = context.ownerUri; - let jobName = context.agentJobInfo.name; let server = context.serverName; - let stepId = 0; - if (context.agentJobHistoryInfo && context.agentJobHistoryInfo.steps) { - stepId = context.agentJobHistoryInfo.steps.length + 1; - } + let jobInfo = context.agentJobInfo; return new TPromise((resolve, reject) => { - resolve(this._commandService.executeCommand('agent.openNewStepDialog', ownerUri, jobName, server, stepId)); + resolve(this._commandService.executeCommand('agent.openNewStepDialog', ownerUri, server, jobInfo , null)); }); } } diff --git a/src/sql/parts/jobManagement/views/jobsView.component.ts b/src/sql/parts/jobManagement/views/jobsView.component.ts index e6e61ba465..ebaa2bb3bc 100644 --- a/src/sql/parts/jobManagement/views/jobsView.component.ts +++ b/src/sql/parts/jobManagement/views/jobsView.component.ts @@ -923,33 +923,17 @@ export class JobsViewComponent extends JobManagementView implements OnInit { 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 schedules - if (schedules && schedules.length > 0) { - if (!job[0].JobSchedules) { - job[0].JobSchedules = []; - } - if (job[0].JobSchedules.length !== schedules.length) { - job[0].JobSchedules = []; - schedules.forEach(schedule => { - job[0].JobSchedules.push(schedule); - }); - } - } - // add alerts - if (!job[0].Alerts) { - job[0].Alerts = []; - } - if (job[0].Alerts.length !== alerts.length) { - job[0].Alerts = []; - alerts.forEach(alert => { - job[0].Alerts.push(alert); - }); - } + // add schedules + if (this.jobSchedules && this.jobSchedules[jobId]) { + let schedules = this.jobSchedules[jobId]; + job[0].JobSchedules = schedules; + } + + // add alerts + if (this.jobAlerts && this.jobAlerts[jobId]) { + let alerts = this.jobAlerts[jobId]; + job[0].Alerts = alerts; } return job && job.length > 0 ? job[0] : undefined; }