Jobs - New step (WIP) (#1711)

* added jobs view toolbar

* create job command and dialog stub

* add tab content and wire up the provider

* fix the steps tab error

* create job dialog 6/15 changes

* general tab done

* success action and retries completed

* added failure action dropdown

* add notification tab checkbox events

* added AgentJobStepInfo objects in sqlops

* create job dialog - 0618 update 1

* added model save function

* width for controls and initial state for notification tab controls

* refresh master and changes to work with latest code

* fixed next and prev button positions

* new step dialog ui finished

* implemented parse button

* fix package file

* add validation and sub-items collections

* hook up the step creation dialog - step 1

* merged master
This commit is contained in:
Aditya Bist
2018-06-26 11:09:41 -07:00
committed by GitHub
parent ca755365ce
commit 3e3ff163db
27 changed files with 6878 additions and 718 deletions

View File

@@ -0,0 +1,47 @@
'use strict';
import * as sqlops from 'sqlops';
export class AgentUtils {
private static _agentService: sqlops.AgentServicesProvider;
private static _connectionService: sqlops.ConnectionProvider;
private static _fileBrowserService: sqlops.FileBrowserProvider;
private static _queryProvider: sqlops.QueryProvider;
public static async getAgentService(): Promise<sqlops.AgentServicesProvider> {
if (!AgentUtils._agentService) {
let currentConnection = await sqlops.connection.getCurrentConnection();
this._agentService = sqlops.dataprotocol.getProvider<sqlops.AgentServicesProvider>(currentConnection.providerName, sqlops.DataProviderType.AgentServicesProvider);
}
return AgentUtils._agentService;
}
public static async getDatabases(ownerUri: string): Promise<string[]> {
if (!AgentUtils._connectionService) {
let currentConnection = await sqlops.connection.getCurrentConnection();
this._connectionService = sqlops.dataprotocol.getProvider<sqlops.ConnectionProvider>(currentConnection.providerName, sqlops.DataProviderType.ConnectionProvider);
}
return AgentUtils._connectionService.listDatabases(ownerUri).then(result => {
if (result && result.databaseNames && result.databaseNames.length > 0) {
return result.databaseNames;
}
});
}
public static async getFileBrowserService(ownerUri: string): Promise<sqlops.FileBrowserProvider> {
if (!AgentUtils._fileBrowserService) {
let currentConnection = await sqlops.connection.getCurrentConnection();
this._fileBrowserService = sqlops.dataprotocol.getProvider<sqlops.FileBrowserProvider>(currentConnection.providerName, sqlops.DataProviderType.FileBrowserProvider);
}
return this._fileBrowserService;
}
public static async getQueryProvider(ownerUri: string): Promise<sqlops.QueryProvider> {
if (!AgentUtils._queryProvider) {
let currentConnection = await sqlops.connection.getCurrentConnection();
this._queryProvider = sqlops.dataprotocol.getProvider<sqlops.QueryProvider>(currentConnection.providerName, sqlops.DataProviderType.QueryProvider);
}
return this._queryProvider;
}
}

View File

@@ -0,0 +1,147 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as sqlops from 'sqlops';
import { AgentUtils } from '../agentUtils';
import { runInThisContext } from 'vm';
export class CreateJobData {
private readonly JobCompletionActionCondition_Always: string = 'When the job completes';
private readonly JobCompletionActionCondition_OnFailure: string = 'When the job fails';
private readonly JobCompletionActionCondition_OnSuccess: string = 'When the job succeeds';
// Error Messages
private readonly CreateJobErrorMessage_NameIsEmpty = 'Job name must be provided';
private _ownerUri: string;
private _jobCategories: string[];
private _operators: string[];
private _agentService: sqlops.AgentServicesProvider;
private _defaultOwner: string;
private _jobCompletionActionConditions: sqlops.CategoryValue[];
public name: string;
public enabled: boolean = true;
public description: string;
public category: string;
public categoryId: number;
public owner: string;
public emailLevel: sqlops.JobCompletionActionCondition = sqlops.JobCompletionActionCondition.OnFailure;
public pageLevel: sqlops.JobCompletionActionCondition = sqlops.JobCompletionActionCondition.OnFailure;
public eventLogLevel: sqlops.JobCompletionActionCondition = sqlops.JobCompletionActionCondition.OnFailure;
public deleteLevel: sqlops.JobCompletionActionCondition = sqlops.JobCompletionActionCondition.OnSuccess;
public operatorToEmail: string;
public operatorToPage: string;
public jobSteps: sqlops.AgentJobStepInfo[];
public jobSchedules: sqlops.AgentJobScheduleInfo[];
public alerts: sqlops.AgentAlertInfo[];
constructor(ownerUri: string) {
this._ownerUri = ownerUri;
}
public get jobCategories(): string[] {
return this._jobCategories;
}
public get operators(): string[] {
return this._operators;
}
public get ownerUri(): string {
return this._ownerUri;
}
public get defaultOwner(): string {
return this._defaultOwner;
}
public get JobCompletionActionConditions(): sqlops.CategoryValue[] {
return this._jobCompletionActionConditions;
}
public async initialize() {
this._agentService = await AgentUtils.getAgentService();
// TODO: fetch real data using agent service
//
this._jobCategories = [
'[Uncategorized (Local)]',
'Jobs from MSX'
];
// await this._agentService.getOperators(this.ownerUri).then(result => {
// this._operators = result.operators.map(o => o.name);
// });
this._operators = ['', 'alanren'];
this._defaultOwner = 'REDMOND\\alanren';
this._jobCompletionActionConditions = [{
displayName: this.JobCompletionActionCondition_OnSuccess,
name: sqlops.JobCompletionActionCondition.OnSuccess.toString()
}, {
displayName: this.JobCompletionActionCondition_OnFailure,
name: sqlops.JobCompletionActionCondition.OnFailure.toString()
}, {
displayName: this.JobCompletionActionCondition_Always,
name: sqlops.JobCompletionActionCondition.Always.toString()
}];
}
public async save() {
await this._agentService.createJob(this.ownerUri, {
name: this.name,
owner: this.owner,
description: this.description,
EmailLevel: this.emailLevel,
PageLevel: this.pageLevel,
EventLogLevel: this.eventLogLevel,
DeleteLevel: this.deleteLevel,
OperatorToEmail: this.operatorToEmail,
OperatorToPage: this.operatorToPage,
enabled: this.enabled,
category: this.category,
Alerts: this.alerts,
JobSchedules: this.jobSchedules,
JobSteps: this.jobSteps,
// The properties below are not collected from UI
// We could consider using a seperate class for create job request
//
currentExecutionStatus: 0,
lastRunOutcome: 0,
currentExecutionStep: '',
hasTarget: true,
hasSchedule: false,
hasStep: false,
runnable: true,
categoryId: 0,
categoryType: 1, // LocalJob, hard-coding the value, corresponds to the target tab in SSMS
lastRun: '',
nextRun: '',
jobId: ''
}).then(result => {
if (!result.success) {
console.info(result.errorMessage);
}
});
}
public validate(): { valid: boolean, errorMessages: string[] } {
let validationErrors: string[] = [];
if (!(this.name && this.name.trim())) {
validationErrors.push(this.CreateJobErrorMessage_NameIsEmpty);
}
return {
valid: validationErrors.length > 0,
errorMessages: validationErrors
}
}
}

View File

@@ -0,0 +1,71 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { AgentUtils } from '../agentUtils';
export class CreateStepData {
public ownerUri: string;
public jobId: string; //
public jobName: string;
public script: string; //
public scriptName: string;
public stepName: string; //
public subSystem: string; //
public id: number;
public failureAction: string; //
public successAction: string; //
public failStepId: number;
public successStepId: number;
public command: string;
public commandExecutionSuccessCode: number;
public databaseName: string; //
public databaseUserName: string;
public server: string;
public outputFileName: string; //
public appendToLogFile: boolean;
public appendToStepHist: boolean;
public writeLogToTable: boolean;
public appendLogToTable: boolean;
public retryAttempts: number; //
public retryInterval: number; //
public proxyName: string;
constructor(ownerUri:string) {
this.ownerUri = ownerUri;
}
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 => {
console.info(result);
});
}
}

View File

@@ -0,0 +1,370 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as sqlops from 'sqlops';
import { CreateJobData } from '../data/createJobData';
import { CreateStepDialog } from './createStepDialog';
export class CreateJobDialog {
// TODO: localize
// Top level
//
private readonly DialogTitle: string = 'New Job';
private readonly OkButtonText: string = 'Ok';
private readonly CancelButtonText: string = 'Cancel';
private readonly GeneralTabText: string = 'General';
private readonly StepsTabText: string = 'Steps';
private readonly SchedulesTabText: string = 'Schedules';
private readonly AlertsTabText: string = 'Alerts';
private readonly NotificationsTabText: string = 'Notifications';
// General tab strings
//
private readonly NameTextBoxLabel: string = 'Name';
private readonly OwnerTextBoxLabel: string = 'Owner';
private readonly CategoryDropdownLabel: string = 'Category';
private readonly DescriptionTextBoxLabel: string = 'Description';
private readonly EnabledCheckboxLabel: string = 'Enabled';
// Steps tab strings
private readonly JobStepsTopLabelString: string = 'Job step list';
private readonly StepsTable_StepColumnString: string = 'Step';
private readonly StepsTable_NameColumnString: string = 'Name';
private readonly StepsTable_TypeColumnString: string = 'Type';
private readonly StepsTable_SuccessColumnString: string = 'On Success';
private readonly StepsTable_FailureColumnString: string = 'On Failure';
private readonly NewStepButtonString: string = 'New...';
private readonly InsertStepButtonString: string = 'Insert...';
private readonly EditStepButtonString: string = 'Edit';
private readonly DeleteStepButtonString: string = 'Delete';
// Notifications tab strings
//
private readonly NotificationsTabTopLabelString: string = 'Actions to perform when the job completes';
private readonly EmailCheckBoxString: string = 'Email';
private readonly PagerCheckBoxString: string = 'Page';
private readonly EventLogCheckBoxString: string = 'Write to the Windows Application event log';
private readonly DeleteJobCheckBoxString: string = 'Automatically delete job';
// UI Components
//
private dialog: sqlops.window.modelviewdialog.Dialog;
private generalTab: sqlops.window.modelviewdialog.DialogTab;
private stepsTab: sqlops.window.modelviewdialog.DialogTab;
private alertsTab: sqlops.window.modelviewdialog.DialogTab;
private schedulesTab: sqlops.window.modelviewdialog.DialogTab;
private notificationsTab: sqlops.window.modelviewdialog.DialogTab;
// General tab controls
//
private nameTextBox: sqlops.InputBoxComponent;
private ownerTextBox: sqlops.InputBoxComponent;
private categoryDropdown: sqlops.DropDownComponent;
private descriptionTextBox: sqlops.InputBoxComponent;
private enabledCheckBox: sqlops.CheckBoxComponent;
// Steps tab controls
private stepsTable: sqlops.TableComponent;
private newStepButton: sqlops.ButtonComponent;
private insertStepButton: sqlops.ButtonComponent;
private editStepButton: sqlops.ButtonComponent;
private deleteStepButton: sqlops.ButtonComponent;
// Notifications tab controls
//
private notificationsTabTopLabel: sqlops.TextComponent;
private emailCheckBox: sqlops.CheckBoxComponent;
private emailOperatorDropdown: sqlops.DropDownComponent;
private emailConditionDropdown: sqlops.DropDownComponent;
private pagerCheckBox: sqlops.CheckBoxComponent;
private pagerOperatorDropdown: sqlops.DropDownComponent;
private pagerConditionDropdown: sqlops.DropDownComponent;
private eventLogCheckBox: sqlops.CheckBoxComponent;
private eventLogConditionDropdown: sqlops.DropDownComponent;
private deleteJobCheckBox: sqlops.CheckBoxComponent;
private deleteJobConditionDropdown: sqlops.DropDownComponent;
private model: CreateJobData;
constructor(ownerUri: string) {
this.model = new CreateJobData(ownerUri);
}
public async showDialog() {
await this.model.initialize();
this.dialog = sqlops.window.modelviewdialog.createDialog(this.DialogTitle);
this.generalTab = sqlops.window.modelviewdialog.createTab(this.GeneralTabText);
this.stepsTab = sqlops.window.modelviewdialog.createTab(this.StepsTabText);
this.alertsTab = sqlops.window.modelviewdialog.createTab(this.AlertsTabText);
this.schedulesTab = sqlops.window.modelviewdialog.createTab(this.SchedulesTabText);
this.notificationsTab = sqlops.window.modelviewdialog.createTab(this.NotificationsTabText);
this.initializeGeneralTab();
this.initializeStepsTab();
this.initializeAlertsTab();
this.initializeSchedulesTab();
this.initializeNotificationsTab();
this.dialog.content = [this.generalTab, this.stepsTab, this.schedulesTab, this.alertsTab, this.notificationsTab];
this.dialog.okButton.onClick(async () => await this.execute());
this.dialog.cancelButton.onClick(async () => await this.cancel());
this.dialog.okButton.label = this.OkButtonText;
this.dialog.cancelButton.label = this.CancelButtonText;
this.dialog.registerCloseValidator(() => {
this.updateModel();
let validationResult = this.model.validate();
if (!validationResult.valid) {
// TODO: Show Error Messages
console.error(validationResult.errorMessages.join(','));
}
return validationResult.valid;
});
sqlops.window.modelviewdialog.openDialog(this.dialog);
}
private initializeGeneralTab() {
this.generalTab.registerContent(async view => {
this.nameTextBox = view.modelBuilder.inputBox().component();
this.ownerTextBox = view.modelBuilder.inputBox().component();
this.categoryDropdown = view.modelBuilder.dropDown().component();
this.descriptionTextBox = view.modelBuilder.inputBox().withProperties({
multiline: true,
height: 200
}).component();
this.enabledCheckBox = view.modelBuilder.checkBox()
.withProperties({
label: this.EnabledCheckboxLabel
}).component();
let formModel = view.modelBuilder.formContainer()
.withFormItems([{
component: this.nameTextBox,
title: this.NameTextBoxLabel
}, {
component: this.ownerTextBox,
title: this.OwnerTextBoxLabel
}, {
component: this.categoryDropdown,
title: this.CategoryDropdownLabel
}, {
component: this.descriptionTextBox,
title: this.DescriptionTextBoxLabel
}, {
component: this.enabledCheckBox,
title: ''
}]).withLayout({ width: '100%' }).component();
await view.initializeModel(formModel);
this.ownerTextBox.value = this.model.defaultOwner;
this.categoryDropdown.values = this.model.jobCategories;
this.categoryDropdown.value = this.model.jobCategories[0];
this.enabledCheckBox.checked = this.model.enabled;
this.descriptionTextBox.value = '';
});
}
private initializeStepsTab() {
this.stepsTab.registerContent(async view => {
this.stepsTable = view.modelBuilder.table()
.withProperties({
columns: [
this.StepsTable_StepColumnString,
this.StepsTable_NameColumnString,
this.StepsTable_TypeColumnString,
this.StepsTable_SuccessColumnString,
this.StepsTable_FailureColumnString
],
data: [],
height: 800
}).component();
this.newStepButton = view.modelBuilder.button().withProperties({
label: this.NewStepButtonString,
width: 80
}).component();
this.newStepButton.onDidClick((e)=>{
let stepDialog =new CreateStepDialog(this.model.ownerUri, '', '', this.model);
stepDialog.openNewStepDialog();
});
this.insertStepButton = view.modelBuilder.button().withProperties({
label: this.InsertStepButtonString,
width: 80
}).component();
this.editStepButton = view.modelBuilder.button().withProperties({
label: this.EditStepButtonString,
width: 80
}).component();
this.deleteStepButton = view.modelBuilder.button().withProperties({
label: this.DeleteStepButtonString,
width: 80
}).component();
let formModel = view.modelBuilder.formContainer()
.withFormItems([{
component: this.stepsTable,
title: this.JobStepsTopLabelString,
actions: [this.newStepButton, this.insertStepButton, this.editStepButton, this.deleteStepButton]
}]).withLayout({ width: '100%' }).component();
await view.initializeModel(formModel);
});
}
private initializeAlertsTab() {
}
private initializeSchedulesTab() {
}
private initializeNotificationsTab() {
this.notificationsTab.registerContent(async view => {
this.notificationsTabTopLabel = view.modelBuilder.text().withProperties({ value: this.NotificationsTabTopLabelString }).component();
this.emailCheckBox = view.modelBuilder.checkBox().withProperties({
label: this.EmailCheckBoxString,
width: 80
}).component();
this.pagerCheckBox = view.modelBuilder.checkBox().withProperties({
label: this.PagerCheckBoxString,
width: 80
}).component();
this.eventLogCheckBox = view.modelBuilder.checkBox().withProperties({
label: this.EventLogCheckBoxString,
width: 250
}).component();
this.deleteJobCheckBox = view.modelBuilder.checkBox().withProperties({
label: this.DeleteJobCheckBoxString,
width: 250
}).component();
this.emailCheckBox.onChanged(() => {
this.emailConditionDropdown.enabled = this.emailCheckBox.checked;
this.emailOperatorDropdown.enabled = this.emailCheckBox.checked;
});
this.pagerCheckBox.onChanged(() => {
this.pagerConditionDropdown.enabled = this.pagerCheckBox.checked;
this.pagerOperatorDropdown.enabled = this.pagerCheckBox.checked;
});
this.eventLogCheckBox.onChanged(() => {
this.eventLogConditionDropdown.enabled = this.eventLogCheckBox.checked;
});
this.deleteJobCheckBox.onChanged(() => {
this.deleteJobConditionDropdown.enabled = this.deleteJobCheckBox.checked;
});
this.emailOperatorDropdown = view.modelBuilder.dropDown().withProperties({ width: 150 }).component();
this.pagerOperatorDropdown = view.modelBuilder.dropDown().withProperties({ width: 150 }).component();
this.emailConditionDropdown = view.modelBuilder.dropDown().withProperties({ width: 150 }).component();
this.pagerConditionDropdown = view.modelBuilder.dropDown().withProperties({ width: 150 }).component();
this.eventLogConditionDropdown = view.modelBuilder.dropDown().withProperties({ width: 150 }).component();
this.deleteJobConditionDropdown = view.modelBuilder.dropDown().withProperties({ width: 150 }).component();
let emailContainer = this.createRowContainer(view).withItems([this.emailCheckBox, this.emailOperatorDropdown, this.emailConditionDropdown]).component();
let pagerContainer = this.createRowContainer(view).withItems([this.pagerCheckBox, this.pagerOperatorDropdown, this.pagerConditionDropdown]).component();
let eventLogContainer = this.createRowContainer(view).withItems([this.eventLogCheckBox, this.eventLogConditionDropdown]).component();
let deleteJobContainer = this.createRowContainer(view).withItems([this.deleteJobCheckBox, this.deleteJobConditionDropdown]).component();
let formModel = view.modelBuilder.formContainer().withFormItems([
{
component: this.notificationsTabTopLabel,
title: ''
}, {
component: emailContainer,
title: ''
}, {
component: pagerContainer,
title: ''
}, {
component: eventLogContainer,
title: ''
}, {
component: deleteJobContainer,
title: ''
}]).withLayout({ width: '100%' }).component();
await view.initializeModel(formModel);
this.emailConditionDropdown.values = this.model.JobCompletionActionConditions;
this.pagerConditionDropdown.values = this.model.JobCompletionActionConditions;
this.eventLogConditionDropdown.values = this.model.JobCompletionActionConditions;
this.deleteJobConditionDropdown.values = this.model.JobCompletionActionConditions;
this.setConditionDropdownSelectedValue(this.emailConditionDropdown, this.model.emailLevel);
this.setConditionDropdownSelectedValue(this.pagerConditionDropdown, this.model.pageLevel);
this.setConditionDropdownSelectedValue(this.eventLogConditionDropdown, this.model.eventLogLevel);
this.setConditionDropdownSelectedValue(this.deleteJobConditionDropdown, this.model.deleteLevel);
this.emailOperatorDropdown.values = this.model.operators;
this.pagerOperatorDropdown.values = this.model.operators;
this.emailCheckBox.checked = false;
this.pagerCheckBox.checked = false;
this.eventLogCheckBox.checked = false;
this.deleteJobCheckBox.checked = false;
this.emailOperatorDropdown.enabled = false;
this.pagerOperatorDropdown.enabled = false;
this.emailConditionDropdown.enabled = false;
this.pagerConditionDropdown.enabled = false;
this.eventLogConditionDropdown.enabled = false;
this.deleteJobConditionDropdown.enabled = false;
});
}
private createRowContainer(view: sqlops.ModelView): sqlops.FlexBuilder {
return view.modelBuilder.flexContainer().withLayout({
flexFlow: 'row',
alignItems: 'left',
justifyContent: 'space-between'
});
}
private async execute() {
this.updateModel();
await this.model.save();
}
private async cancel() {
}
private getActualConditionValue(checkbox: sqlops.CheckBoxComponent, dropdown: sqlops.DropDownComponent): sqlops.JobCompletionActionCondition {
return checkbox.checked ? Number(this.getDropdownValue(dropdown)) : sqlops.JobCompletionActionCondition.Never;
}
private getDropdownValue(dropdown: sqlops.DropDownComponent): string {
return (typeof dropdown.value === 'string') ? dropdown.value : dropdown.value.name;
}
private setConditionDropdownSelectedValue(dropdown: sqlops.DropDownComponent, selectedValue: number) {
let idx: number = 0;
for (idx = 0; idx < dropdown.values.length; idx++) {
if (Number((<sqlops.CategoryValue>dropdown.values[idx]).name) === selectedValue) {
dropdown.value = dropdown.values[idx];
break;
}
}
}
private updateModel() {
this.model.name = this.nameTextBox.value;
this.model.owner = this.ownerTextBox.value;
this.model.enabled = this.enabledCheckBox.checked;
this.model.description = this.descriptionTextBox.value;
this.model.category = this.getDropdownValue(this.categoryDropdown);
this.model.emailLevel = this.getActualConditionValue(this.emailCheckBox, this.emailConditionDropdown);
this.model.operatorToEmail = this.getDropdownValue(this.emailOperatorDropdown);
this.model.operatorToPage = this.getDropdownValue(this.pagerOperatorDropdown);
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);
}
}

View File

@@ -0,0 +1,444 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as sqlops from 'sqlops';
import * as vscode from 'vscode';
import { CreateStepData } from '../data/createStepData';
import { AgentUtils } from '../agentUtils';
import { CreateJobData } from '../data/createJobData';
export class CreateStepDialog {
// TODO: localize
// Top level
//
private static readonly DialogTitle: string = 'New Job Step';
private static readonly OkButtonText: string = 'OK';
private static readonly CancelButtonText: string = 'Cancel';
private static readonly GeneralTabText: string = 'General';
private static readonly AdvancedTabText: string = 'Advanced';
private static readonly OpenCommandText: string = 'Open...';
private static readonly ParseCommandText: string = 'Parse';
private static readonly NextButtonText: string = 'Next';
private static readonly PreviousButtonText: string = 'Previous';
private static readonly SuccessAction: string = 'On success action';
private static readonly FailureAction: string = 'On failure action';
// Dropdown options
private static readonly TSQLScript: string = 'Transact-SQL script (T-SQL)';
private static readonly AgentServiceAccount: string = 'SQL Server Agent Service Account';
private static readonly NextStep: string = 'Go to the next step';
private static readonly QuitJobReportingSuccess: string = 'Quit the job reporting success';
private static readonly QuitJobReportingFailure: string = 'Quit the job reporting failure';
// UI Components
//
private dialog: sqlops.window.modelviewdialog.Dialog;
private generalTab: sqlops.window.modelviewdialog.DialogTab;
private advancedTab: sqlops.window.modelviewdialog.DialogTab;
private nameTextBox: sqlops.InputBoxComponent;
private typeDropdown: sqlops.DropDownComponent;
private runAsDropdown: sqlops.DropDownComponent;
private databaseDropdown: sqlops.DropDownComponent;
private successActionDropdown: sqlops.DropDownComponent;
private failureActionDropdown: sqlops.DropDownComponent;
private commandTextBox: sqlops.InputBoxComponent;
private openButton: sqlops.ButtonComponent;
private parseButton: sqlops.ButtonComponent;
private nextButton: sqlops.ButtonComponent;
private previousButton: sqlops.ButtonComponent;
private retryAttemptsBox: sqlops.InputBoxComponent;
private retryIntervalBox: sqlops.InputBoxComponent;
private appendToExistingFileCheckbox: sqlops.CheckBoxComponent;
private logToTableCheckbox: sqlops.CheckBoxComponent;
private outputFileNameBox: sqlops.InputBoxComponent;
private outputFileBrowserButton: sqlops.ButtonComponent;
private model: CreateStepData;
private ownerUri: string;
private jobId: string;
private server: string;
private jobModel: CreateJobData;
constructor(
ownerUri: string,
jobId: string,
server: string,
jobModel?: CreateJobData
) {
this.model = new CreateStepData(ownerUri);
this.ownerUri = ownerUri;
this.jobId = jobId;
this.server = server;
this.jobModel = jobModel;
}
private initializeUIComponents() {
this.dialog = sqlops.window.modelviewdialog.createDialog(CreateStepDialog.DialogTitle);
this.generalTab = sqlops.window.modelviewdialog.createTab(CreateStepDialog.GeneralTabText);
this.advancedTab = sqlops.window.modelviewdialog.createTab(CreateStepDialog.AdvancedTabText);
this.dialog.content = [this.generalTab, this.advancedTab];
this.dialog.okButton.label = CreateStepDialog.OkButtonText;
this.dialog.okButton.onClick(() => this.execute());
this.dialog.cancelButton.label = CreateStepDialog.CancelButtonText;
}
private createCommands(view, queryProvider: sqlops.QueryProvider) {
this.openButton = view.modelBuilder.button()
.withProperties({
label: CreateStepDialog.OpenCommandText,
width: '80px'
}).component();
this.parseButton = view.modelBuilder.button()
.withProperties({
label: CreateStepDialog.ParseCommandText,
width: '80px'
}).component();
this.parseButton.onDidClick(e => {
if (this.commandTextBox.value) {
queryProvider.parseSyntax(this.ownerUri, this.commandTextBox.value).then(result => {
if (result && result.parseable) {
this.dialog.message = { text: 'The command was successfully parsed.', level: 2};
} else if (result && !result.parseable) {
this.dialog.message = { text: 'The command failed' };
}
});
}
});
this.commandTextBox = view.modelBuilder.inputBox()
.withProperties({
height: 300,
width: 400,
multiline: true,
inputType: 'text'
})
.component();
this.nextButton = view.modelBuilder.button()
.withProperties({
label: CreateStepDialog.NextButtonText,
enabled: false,
width: '80px'
}).component();
this.previousButton = view.modelBuilder.button()
.withProperties({
label: CreateStepDialog.PreviousButtonText,
enabled: false,
width: '80px'
}).component();
}
private createGeneralTab(databases: string[], queryProvider: sqlops.QueryProvider) {
this.generalTab.registerContent(async (view) => {
this.nameTextBox = view.modelBuilder.inputBox()
.withProperties({
}).component();
this.typeDropdown = view.modelBuilder.dropDown()
.withProperties({
value: CreateStepDialog.TSQLScript,
values: [CreateStepDialog.TSQLScript]
})
.component();
this.runAsDropdown = view.modelBuilder.dropDown()
.withProperties({
value: '',
values: ['']
})
.component();
this.runAsDropdown.enabled = false;
this.typeDropdown.onValueChanged((type) => {
if (type.selected !== CreateStepDialog.TSQLScript) {
this.runAsDropdown.value = CreateStepDialog.AgentServiceAccount;
this.runAsDropdown.values = [this.runAsDropdown.value];
} else {
this.runAsDropdown.value = '';
this.runAsDropdown.values = [''];
}
});
this.databaseDropdown = view.modelBuilder.dropDown()
.withProperties({
value: databases[0],
values: databases
}).component();
// create the commands section
this.createCommands(view, queryProvider);
let buttonContainer = view.modelBuilder.flexContainer()
.withLayout({
flexFlow: 'row',
justifyContent: 'space-between',
width: 420
}).withItems([this.openButton, this.parseButton, this.previousButton, this.nextButton], {
flex: '1 1 50%'
}).component();
let formModel = view.modelBuilder.formContainer()
.withFormItems([{
component: this.nameTextBox,
title: 'Step name'
}, {
component: this.typeDropdown,
title: 'Type'
}, {
component: this.runAsDropdown,
title: 'Run as'
}, {
component: this.databaseDropdown,
title: 'Database'
}, {
component: this.commandTextBox,
title: 'Command',
actions: [buttonContainer]
}], {
horizontal: false,
componentWidth: 420
}).component();
let formWrapper = view.modelBuilder.loadingComponent().withItem(formModel).component();
formWrapper.loading = false;
await view.initializeModel(formWrapper);
});
}
private createRunAsUserOptions(view) {
let userInputBox = view.modelBuilder.inputBox()
.withProperties({ inputType: 'text', width: '100px' }).component();
let viewButton = view.modelBuilder.button()
.withProperties({ label: '...', width: '20px' }).component();
let viewButtonContainer = view.modelBuilder.flexContainer()
.withLayout({ width: 100, textAlign: 'right' })
.withItems([viewButton], { flex: '1 1 50%' }).component();
let userInputBoxContainer = view.modelBuilder.flexContainer()
.withLayout({ width: 200, textAlign: 'left' })
.withItems([userInputBox], { flex: '1 1 50%' }).component();
let runAsUserContainer = view.modelBuilder.flexContainer()
.withLayout({ width: 200 })
.withItems([userInputBoxContainer, viewButtonContainer], { flex: '1 1 50%' })
.component();
let runAsUserForm = view.modelBuilder.formContainer()
.withFormItems([{
component: runAsUserContainer,
title: 'Run as user'
}], { horizontal: true, componentWidth: 200 }).component();
return runAsUserForm;
}
private createAdvancedTab(fileBrowserService: sqlops.FileBrowserProvider) {
this.advancedTab.registerContent(async (view) => {
this.successActionDropdown = view.modelBuilder.dropDown()
.withProperties({
value: CreateStepDialog.NextStep,
values: [CreateStepDialog.NextStep, CreateStepDialog.QuitJobReportingSuccess, CreateStepDialog.QuitJobReportingFailure]
})
.component();
let retryFlexContainer = this.createRetryCounters(view);
this.failureActionDropdown = view.modelBuilder.dropDown()
.withProperties({
value: CreateStepDialog.QuitJobReportingFailure,
values: [CreateStepDialog.QuitJobReportingFailure, CreateStepDialog.NextStep, CreateStepDialog.QuitJobReportingSuccess]
})
.component();
let optionsGroup = this.createTSQLOptions(view, fileBrowserService);
let viewButton = view.modelBuilder.button()
.withProperties({ label: 'View', width: '50px' }).component();
viewButton.enabled = false;
this.logToTableCheckbox = view.modelBuilder.checkBox()
.withProperties({
label: 'Log to table'
}).component();
let appendToExistingEntryInTableCheckbox = view.modelBuilder.checkBox()
.withProperties({ label: 'Append output to existing entry in table' }).component();
appendToExistingEntryInTableCheckbox.enabled = false;
this.logToTableCheckbox.onChanged(e => {
viewButton.enabled = e;
appendToExistingEntryInTableCheckbox.enabled = e;
});
let appendCheckboxContainer = view.modelBuilder.groupContainer()
.withItems([appendToExistingEntryInTableCheckbox]).component();
let logToTableContainer = view.modelBuilder.flexContainer()
.withLayout({ flexFlow: 'row', justifyContent: 'space-between', width: 300 })
.withItems([this.logToTableCheckbox, viewButton]).component();
let logStepOutputHistoryCheckbox = view.modelBuilder.checkBox()
.withProperties({ label: 'Include step output in history' }).component();
let runAsUserOptions = this.createRunAsUserOptions(view);
let formModel = view.modelBuilder.formContainer()
.withFormItems(
[{
component: this.successActionDropdown,
title: CreateStepDialog.SuccessAction
}, {
component: retryFlexContainer,
title: ''
}, {
component: this.failureActionDropdown,
title: CreateStepDialog.FailureAction
}, {
component: optionsGroup,
title: 'Transact-SQL script (T-SQL)'
}, {
component: logToTableContainer,
title: ''
}, {
component: appendCheckboxContainer,
title: ' '
}, {
component: logStepOutputHistoryCheckbox,
title: ''
}, {
component: runAsUserOptions,
title: ''
}], {
componentWidth: 400
}).component();
let formWrapper = view.modelBuilder.loadingComponent().withItem(formModel).component();
formWrapper.loading = false;
view.initializeModel(formWrapper);
});
}
private createRetryCounters(view) {
this.retryAttemptsBox = view.modelBuilder.inputBox()
.withValidation(component => component.value >= 0)
.withProperties({
inputType: 'number'
})
.component();
this.retryIntervalBox = view.modelBuilder.inputBox()
.withValidation(component => component.value >= 0)
.withProperties({
inputType: 'number'
}).component();
let retryAttemptsContainer = view.modelBuilder.formContainer()
.withFormItems(
[{
component: this.retryAttemptsBox,
title: 'Retry Attempts'
}], {
horizontal: false
})
.component();
let retryIntervalContainer = view.modelBuilder.formContainer()
.withFormItems(
[{
component: this.retryIntervalBox,
title: 'Retry Interval (minutes)'
}], {
horizontal: false
})
.component();
let retryFlexContainer = view.modelBuilder.flexContainer()
.withLayout({
flexFlow: 'row',
}).withItems([retryAttemptsContainer, retryIntervalContainer]).component();
return retryFlexContainer;
}
private createTSQLOptions(view, fileBrowserService: sqlops.FileBrowserProvider) {
this.outputFileBrowserButton = view.modelBuilder.button()
.withProperties({ width: '20px', label: '...' }).component();
this.outputFileBrowserButton.onDidClick(() => {
fileBrowserService.openFileBrowser(this.ownerUri,
'C:\\Program Files\\Microsoft SQL Server\\MSSQL14.MSSQLSERVER\\MSSQL\\Backup',
['*'], false).then(result => {
if (result) {
console.log(result);
Promise.resolve(result);
} else {
Promise.reject(false);
}
});
});
this.outputFileNameBox = view.modelBuilder.inputBox()
.withProperties({
width: '100px',
inputType: 'text'
}).component();
let outputViewButton = view.modelBuilder.button()
.withProperties({
width: '50px',
label: 'View'
}).component();
outputViewButton.enabled = false;
let outputButtonContainer = view.modelBuilder.flexContainer()
.withLayout({
flexFlow: 'row',
textAlign: 'right',
width: 120
}).withItems([this.outputFileBrowserButton, outputViewButton], { flex: '1 1 50%' }).component();
let outputFlexBox = view.modelBuilder.flexContainer()
.withLayout({
flexFlow: 'row',
width: 350
}).withItems([this.outputFileNameBox, outputButtonContainer], {
flex: '1 1 50%'
}).component();
this.appendToExistingFileCheckbox = view.modelBuilder.checkBox()
.withProperties({
label: 'Append output to existing file'
}).component();
this.appendToExistingFileCheckbox.enabled = false;
this.outputFileNameBox.onTextChanged((input) => {
if (input !== '') {
this.appendToExistingFileCheckbox.enabled = true;
} else {
this.appendToExistingFileCheckbox.enabled = false;
}
});
let outputFileForm = view.modelBuilder.formContainer()
.withFormItems([{
component: outputFlexBox,
title: 'Output file'
}, {
component: this.appendToExistingFileCheckbox,
title: ''
}], { horizontal: true, componentWidth: 200 }).component();
return outputFileForm;
}
private async execute() {
this.model.jobId = this.jobId;
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;
this.model.successAction = this.successActionDropdown.value as string;
this.model.retryAttempts = +this.retryAttemptsBox.value;
this.model.retryInterval = +this.retryIntervalBox.value;
this.model.failureAction = this.failureActionDropdown.value as string;
this.model.outputFileName = this.outputFileNameBox.value;
await this.model.save();
}
private openFileBrowserDialog(rootNode, selectedNode) {
}
private onFileBrowserOpened(handle: number, fileBrowserOpenedParams: sqlops.FileBrowserOpenedParams) {
if (fileBrowserOpenedParams.succeeded === true
&& fileBrowserOpenedParams.fileTree
&& fileBrowserOpenedParams.fileTree.rootNode
&& fileBrowserOpenedParams.fileTree.selectedNode) {
this.openFileBrowserDialog(fileBrowserOpenedParams.fileTree.rootNode, fileBrowserOpenedParams.fileTree.selectedNode);
}
}
public async openNewStepDialog() {
let databases = await AgentUtils.getDatabases(this.ownerUri);
let fileBrowserService = await AgentUtils.getFileBrowserService(this.ownerUri);
let queryProvider = await AgentUtils.getQueryProvider(this.ownerUri);
fileBrowserService.registerOnFileBrowserOpened((response: sqlops.FileBrowserOpenedParams) => {
this.onFileBrowserOpened(fileBrowserService.handle, response);
});
this.initializeUIComponents();
this.createGeneralTab(databases, queryProvider);
this.createAdvancedTab(fileBrowserService);
sqlops.window.modelviewdialog.openDialog(this.dialog);
}
}

View File

@@ -3,15 +3,15 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as vscode from 'vscode';
import * as data from 'sqlops';
import { ApiWrapper } from './apiWrapper';
import { CreateJobDialog } from './dialogs/createJobDialog';
import { CreateStepDialog } from './dialogs/createStepDialog';
/**
* The main controller class that initializes the extension
*/
export class MainController {
export class MainController {
protected _apiWrapper: ApiWrapper;
protected _context: vscode.ExtensionContext;
@@ -30,9 +30,21 @@ export class MainController {
}
public activate(): void {
this._apiWrapper.registerWebviewProvider('data-management-agent', webview => {
webview.html = '<div><h1>SQL Agent</h1></div>';
});
}
vscode.commands.registerCommand('agent.openCreateJobDialog', (ownerUri: string) => {
let dialog = new CreateJobDialog(ownerUri);
dialog.showDialog();
});
vscode.commands.registerCommand('agent.openNewStepDialog', (ownerUri: string, jobId: string, server: string) => {
let dialog = new CreateStepDialog(ownerUri, jobId, server);
dialog.openNewStepDialog();
});
}
private updateJobStepDialog() {
}
}

View File

@@ -5,4 +5,5 @@
/// <reference path='../../../../../src/vs/vscode.d.ts'/>
/// <reference path='../../../../../src/sql/sqlops.d.ts'/>
/// <reference path='../../../../../src/sql/sqlops.proposed.d.ts'/>
/// <reference types='@types/node'/>

3234
extensions/agent/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -9,15 +9,12 @@
"icon": "images/sqlserver.png",
"aiKey": "AIF-5574968e-856d-40d2-af67-c89a14e76412",
"engines": {
"vscode": "0.10.x"
"vscode": "0.10.x"
},
"activationEvents": [
"*"
],
"main": "./client/out/main",
"scripts": {
"compile": "gulp compile-extension:agent-client"
},
"repository": {
"type": "git",
"url": "https://github.com/Microsoft/sqlopsstudio.git"
@@ -29,22 +26,25 @@
"outputChannels": [
"sqlagent"
],
"commands": [
{
"command": "agent.openNewStepDialog",
"title": "agent.openNewStepDialog"
}
],
"dashboard.tabs": [
{
"id": "data-management-agent",
"description": "Manage and troubleshoot SQL Agent jobs",
"provider": "MSSQL",
"title": "SQL Agent",
"when": "connectionProvider == 'MSSQL' && !mssql:iscloud",
"container": {
"controlhost-container": {
"type": "agent"
}
}
{
"id": "data-management-agent",
"description": "Manage and troubleshoot SQL Agent jobs",
"provider": "MSSQL",
"title": "SQL Agent",
"when": "connectionProvider == 'MSSQL' && !mssql:iscloud",
"container": {
"controlhost-container": {
"type": "agent"
}
}
}
]
},
"devDependencies": {
"vscode": "1.0.1"
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -49,9 +49,8 @@ core-util-is@~1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
"dataprotocol-client@github:Microsoft/sqlops-dataprotocolclient#0.1.9":
version "0.1.9"
resolved "https://codeload.github.com/Microsoft/sqlops-dataprotocolclient/tar.gz/a1a79895cb79658b75d78aa5cfd745855019148d"
"dataprotocol-client@file:../../../sqlops-dataprotocolclient":
version "0.1.7"
dependencies:
vscode-languageclient "3.5.0"

View File

@@ -216,4 +216,19 @@
.vs .icon.unpin {
background: url('unpin.svg') center center no-repeat;
}
.small {
width: 16px;
height: 16px;
}
.medium {
width: 24px;
height: 24px;
}
.large {
width: 32px;
height: 32px;
}

View File

@@ -238,4 +238,31 @@ table.jobprevruns {
table.jobprevruns > tbody {
vertical-align: bottom;
}
}
.jobs-view-toolbar{
display: flex;
flex-wrap: wrap;
align-items: flex-start;
padding: 15px 10px 15px 30px;
font-size: 16px;
border-bottom: 3px solid #f4f4f4;
}
.jobs-view-toolbar .vs-dark {
border-bottom: 3px solid #444444;
}
.jobs-view-toolbar > div{
display: flex;
flex-wrap: nowrap;
align-items: flex-start;
margin-right: 25px;
cursor: pointer;
padding: 2px;
}
.jobs-view-toolbar span{
padding-left: 5px;
}

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#231f20;}.cls-2{fill:#212121;}</style></defs><title>new_16x16</title><polygon class="cls-1" points="13.59 2.21 13.58 2.22 13.58 2.2 13.59 2.21"/><path class="cls-2" d="M16,7.5v1H8.5V16h-1V8.5H0v-1H7.5V0h1V7.5Z"/></svg>

After

Width:  |  Height:  |  Size: 336 B

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#fff;}</style></defs><title>new_inverse_16x16</title><polygon class="cls-1" points="13.59 2.21 13.58 2.22 13.58 2.2 13.59 2.21"/><path class="cls-1" d="M16,7.5v1H8.5V16h-1V8.5H0v-1H7.5V0h1V7.5Z"/></svg>

After

Width:  |  Height:  |  Size: 320 B

View File

@@ -10,11 +10,15 @@ import { INotificationService } from 'vs/platform/notification/common/notificati
import Severity from 'vs/base/common/severity';
import { JobHistoryComponent } from 'sql/parts/jobManagement/views/jobHistory.component';
import { IJobManagementService } from '../common/interfaces';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IConnectionManagementService } from '../../connection/common/connectionManagement';
export enum JobHistoryActions {
export enum JobActions {
Run = 'run',
Stop = 'stop',
NewStep = 'newStep'
}
export class RunJobAction extends Action {
public static ID = 'jobaction.runJob';
public static LABEL = nls.localize('jobaction.run', "Run");
@@ -30,7 +34,7 @@ export class RunJobAction extends Action {
let jobName = context.agentJobInfo.name;
let ownerUri = context.ownerUri;
return new TPromise<boolean>((resolve, reject) => {
this.jobManagementService.jobAction(ownerUri, jobName, JobHistoryActions.Run).then(result => {
this.jobManagementService.jobAction(ownerUri, jobName, JobActions.Run).then(result => {
if (result.success) {
var startMsg = nls.localize('jobSuccessfullyStarted', ': The job was successfully started.');
this.notificationService.notify({
@@ -65,7 +69,7 @@ export class StopJobAction extends Action {
let jobName = context.agentJobInfo.name;
let ownerUri = context.ownerUri;
return new TPromise<boolean>((resolve, reject) => {
this.jobManagementService.jobAction(ownerUri, jobName, JobHistoryActions.Stop).then(result => {
this.jobManagementService.jobAction(ownerUri, jobName, JobActions.Stop).then(result => {
if (result.success) {
var stopMsg = nls.localize('jobSuccessfullyStopped', ': The job was successfully stopped.');
this.notificationService.notify({
@@ -83,4 +87,26 @@ export class StopJobAction extends Action {
});
});
}
}
export class NewStepAction extends Action {
public static ID = 'jobaction.newStep';
public static LABEL = nls.localize('jobaction.newStep', "New Step");
constructor(
@INotificationService private notificationService: INotificationService,
@ICommandService private _commandService: ICommandService,
@IConnectionManagementService private _connectionService
) {
super(NewStepAction.ID, NewStepAction.LABEL, 'newStepIcon');
}
public run(context: JobHistoryComponent): TPromise<boolean> {
let ownerUri = context.ownerUri;
let jobId = context.agentJobInfo.jobId;
let server = context.serverName;
return new TPromise<boolean>((resolve, reject) => {
resolve(this._commandService.executeCommand('agent.openNewStepDialog', ownerUri, jobId, server));
});
}
}

View File

@@ -6,9 +6,9 @@
import 'vs/css!./jobHistory';
import 'vs/css!sql/media/icons/common-icons';
import { OnInit, OnChanges, Component, Inject, Input, forwardRef, ElementRef, ChangeDetectorRef, ViewChild, ChangeDetectionStrategy, Injectable } from '@angular/core';
import { AgentJobHistoryInfo, AgentJobInfo } from 'sqlops';
import { Taskbar } from 'sql/base/browser/ui/taskbar/taskbar';
import { RunJobAction, StopJobAction } from 'sql/parts/jobManagement/views/jobHistoryActions';
import * as sqlops from 'sqlops';
import { Taskbar, ITaskbarContent } from 'sql/base/browser/ui/taskbar/taskbar';
import { RunJobAction, StopJobAction, NewStepAction } from 'sql/parts/jobManagement/views/jobActions';
import { JobCacheObject } from 'sql/parts/jobManagement/common/jobManagementService';
import { AgentJobUtilities } from '../common/agentJobUtilities';
import { IJobManagementService } from '../common/interfaces';
@@ -46,9 +46,9 @@ export class JobHistoryComponent extends Disposable implements OnInit {
@ViewChild('table') private _tableContainer: ElementRef;
@ViewChild('actionbarContainer') private _actionbarContainer: ElementRef;
@Input() public agentJobInfo: AgentJobInfo = undefined;
@Input() public agentJobHistories: AgentJobHistoryInfo[] = undefined;
public agentJobHistoryInfo: AgentJobHistoryInfo = undefined;
@Input() public agentJobInfo: sqlops.AgentJobInfo = undefined;
@Input() public agentJobHistories: sqlops.AgentJobHistoryInfo[] = undefined;
public agentJobHistoryInfo: sqlops.AgentJobHistoryInfo = undefined;
private _isVisible: boolean = false;
private _stepRows: JobStepsViewRow[] = [];
@@ -56,8 +56,9 @@ export class JobHistoryComponent extends Disposable implements OnInit {
private _showPreviousRuns: boolean = undefined;
private _runStatus: string = undefined;
private _jobCacheObject: JobCacheObject;
private _agentJobInfo: AgentJobInfo;
private _agentJobInfo: sqlops.AgentJobInfo;
private _noJobsAvailable: boolean = false;
private _serverName: string;
constructor(
@Inject(forwardRef(() => ElementRef)) el: ElementRef,
@@ -76,14 +77,14 @@ export class JobHistoryComponent extends Disposable implements OnInit {
this._treeRenderer = new JobHistoryRenderer();
this._treeFilter = new JobHistoryFilter();
let jobCacheObjectMap = this._jobManagementService.jobCacheObjectMap;
let serverName = _dashboardService.connectionManagementService.connectionInfo.connectionProfile.serverName;
let jobCache = jobCacheObjectMap[serverName];
this._serverName = _dashboardService.connectionManagementService.connectionInfo.connectionProfile.serverName;
let jobCache = jobCacheObjectMap[this._serverName];
if (jobCache) {
this._jobCacheObject = jobCache;
} else {
this._jobCacheObject = new JobCacheObject();
this._jobCacheObject.serverName = serverName;
this._jobManagementService.addToCache(serverName, this._jobCacheObject);
this._jobCacheObject.serverName = this._serverName;
this._jobManagementService.addToCache(this._serverName, this._jobCacheObject);
}
}
@@ -210,7 +211,7 @@ export class JobHistoryComponent extends Disposable implements OnInit {
}
}
private buildHistoryTree(self: any, jobHistories: AgentJobHistoryInfo[]) {
private buildHistoryTree(self: any, jobHistories: sqlops.AgentJobHistoryInfo[]) {
self._treeController.jobHistories = jobHistories;
self._jobCacheObject.setJobHistory(self._agentViewComponent.jobId, jobHistories);
let jobHistoryRows = this._treeController.jobHistories.map(job => self.convertToJobHistoryRow(job));
@@ -237,7 +238,7 @@ export class JobHistoryComponent extends Disposable implements OnInit {
this._agentViewComponent.showHistory = false;
}
private convertToJobHistoryRow(historyInfo: AgentJobHistoryInfo): JobHistoryRow {
private convertToJobHistoryRow(historyInfo: sqlops.AgentJobHistoryInfo): JobHistoryRow {
let jobHistoryRow = new JobHistoryRow();
jobHistoryRow.runDate = this.formatTime(historyInfo.runDate);
jobHistoryRow.runStatus = AgentJobUtilities.convertToStatusString(historyInfo.runStatus);
@@ -263,12 +264,14 @@ export class JobHistoryComponent extends Disposable implements OnInit {
private _initActionBar() {
let runJobAction = this.instantiationService.createInstance(RunJobAction);
let stopJobAction = this.instantiationService.createInstance(StopJobAction);
let newStepAction = this.instantiationService.createInstance(NewStepAction);
let taskbar = <HTMLElement>this._actionbarContainer.nativeElement;
this._actionBar = new Taskbar(taskbar, this.contextMenuService);
this._actionBar.context = this;
this._actionBar.setContent([
{ action: runJobAction },
{ action: stopJobAction }
{ action: stopJobAction },
{ action: newStepAction }
]);
}
@@ -286,6 +289,10 @@ export class JobHistoryComponent extends Disposable implements OnInit {
return this._dashboardService.connectionManagementService.connectionInfo.ownerUri;
}
public get serverName(): string {
return this._serverName;
}
/** SETTERS */
public set showSteps(value: boolean) {

View File

@@ -122,6 +122,15 @@ input#accordion:checked ~ .accordion-content {
background-image: url('../common/media/stop.svg');
}
.vs .action-label.icon.newStepIcon {
background-image: url('../common/media/new.svg');
}
.vs-dark .action-label.icon.newStepIcon,
.hc-black .action-label.icon.newStepIcon {
background-image: url('../common/media/new_inverse.svg');
}
a.action-label.icon.runJobIcon.non-runnable {
opacity: 0.4;
cursor: default;

View File

@@ -9,5 +9,9 @@
<h1 class="job-heading" *ngIf="_isCloud === true">No Jobs Available</h1>
<div class="icon in-progress" *ngIf="_showProgressWheel === true"></div>
</div>
<div class="jobs-view-toolbar">
<div (click)="refreshJobs()" tabindex="0"><div class="small icon refresh"></div><span>{{RefreshText}}</span></div>
<div (click)="openCreateJobDialog()" tabindex="0"><div class="small icon new"></div><span>{{NewJobText}}</span></div>
</div>
<div #jobsgrid class="jobview-grid"></div>

View File

@@ -27,7 +27,7 @@ import { IJobManagementService } from '../common/interfaces';
import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { TabChild } from 'sql/base/browser/ui/panel/tab.component';
import { ICommandService } from 'vs/platform/commands/common/commands';
export const JOBSVIEW_SELECTOR: string = 'jobsview-component';
export const ROW_HEIGHT: number = 45;
@@ -44,16 +44,16 @@ export class JobsViewComponent implements AfterContentChecked {
private _disposables = new Array<vscode.Disposable>();
private columns: Array<Slick.Column<any>> = [
{ name: nls.localize('jobColumns.name','Name'), field: 'name', formatter: (row, cell, value, columnDef, dataContext) => this.renderName(row, cell, value, columnDef, dataContext), width: 200 , id: 'name' },
{ name: nls.localize('jobColumns.lastRun','Last Run'), field: 'lastRun', width: 120, id: 'lastRun' },
{ name: nls.localize('jobColumns.nextRun','Next Run'), field: 'nextRun', width: 120, id: 'nextRun' },
{ name: nls.localize('jobColumns.enabled','Enabled'), field: 'enabled', width: 50, id: 'enabled' },
{ name: nls.localize('jobColumns.status','Status'), field: 'currentExecutionStatus', width: 60, id: 'currentExecutionStatus' },
{ name: nls.localize('jobColumns.category','Category'), field: 'category', width: 120, id: 'category' },
{ name: nls.localize('jobColumns.runnable','Runnable'), field: 'runnable', width: 70, id: 'runnable' },
{ name: nls.localize('jobColumns.schedule','Schedule'), field: 'hasSchedule', width: 60, id: 'hasSchedule' },
{ name: nls.localize('jobColumns.name', 'Name'), field: 'name', formatter: (row, cell, value, columnDef, dataContext) => this.renderName(row, cell, value, columnDef, dataContext), width: 200, id: 'name' },
{ name: nls.localize('jobColumns.lastRun', 'Last Run'), field: 'lastRun', width: 120, id: 'lastRun' },
{ name: nls.localize('jobColumns.nextRun', 'Next Run'), field: 'nextRun', width: 120, id: 'nextRun' },
{ name: nls.localize('jobColumns.enabled', 'Enabled'), field: 'enabled', width: 50, id: 'enabled' },
{ name: nls.localize('jobColumns.status', 'Status'), field: 'currentExecutionStatus', width: 60, id: 'currentExecutionStatus' },
{ name: nls.localize('jobColumns.category', 'Category'), field: 'category', width: 120, id: 'category' },
{ name: nls.localize('jobColumns.runnable', 'Runnable'), field: 'runnable', width: 70, id: 'runnable' },
{ name: nls.localize('jobColumns.schedule', 'Schedule'), field: 'hasSchedule', width: 60, id: 'hasSchedule' },
{ name: nls.localize('jobColumns.lastRunOutcome', 'Last Run Outcome'), field: 'lastRunOutcome', width: 120, id: 'lastRunOutcome' },
{ name: nls.localize('jobColumns.previousRuns', 'Previous Runs'), formatter: this.renderChartsPostHistory, field: 'previousRuns', width: 80, id: 'previousRuns'}
{ name: nls.localize('jobColumns.previousRuns', 'Previous Runs'), formatter: this.renderChartsPostHistory, field: 'previousRuns', width: 80, id: 'previousRuns' }
];
@@ -80,18 +80,22 @@ export class JobsViewComponent implements AfterContentChecked {
private _isCloud: boolean;
private _showProgressWheel: boolean;
private _tabHeight: number;
private filterStylingMap: { [columnName: string]: [any] ;} = {};
private filterStylingMap: { [columnName: string]: [any]; } = {};
private filterStack = ['start'];
private filterValueMap: { [columnName: string]: string[] ;} = {};
private filterValueMap: { [columnName: string]: string[]; } = {};
private sortingStylingMap: { [columnName: string]: any; } = {};
private NewJobText: string = nls.localize("jobsToolbar-NewJob", "New job");
private RefreshText: string = nls.localize("jobsToolbar-Refresh", "Refresh");
constructor(
@Inject(forwardRef(() => CommonServiceInterface)) private _dashboardService: CommonServiceInterface,
@Inject(forwardRef(() => ChangeDetectorRef)) private _cd: ChangeDetectorRef,
@Inject(forwardRef(() => ElementRef)) private _el: ElementRef,
@Inject(forwardRef(() => AgentViewComponent)) private _agentViewComponent: AgentViewComponent,
@Inject(IJobManagementService) private _jobManagementService: IJobManagementService,
@Inject(IThemeService) private _themeService: IThemeService
@Inject(IThemeService) private _themeService: IThemeService,
@Inject(ICommandService) private _commandService: ICommandService
) {
let jobCacheObjectMap = this._jobManagementService.jobCacheObjectMap;
this._serverName = _dashboardService.connectionManagementService.connectionInfo.connectionProfile.serverName;
@@ -241,9 +245,9 @@ export class JobsViewComponent implements AfterContentChecked {
}
// apply the previous filter styling
let currentItems = this.dataView.getFilteredItems();
let styledItems = this.filterValueMap[this.filterStack[this.filterStack.length-1]][1];
let styledItems = this.filterValueMap[this.filterStack[this.filterStack.length - 1]][1];
if (styledItems === currentItems) {
let lastColStyle = this.filterStylingMap[this.filterStack[this.filterStack.length-1]];
let lastColStyle = this.filterStylingMap[this.filterStack[this.filterStack.length - 1]];
for (let i = 0; i < lastColStyle.length; i++) {
this._table.grid.setCellCssStyles(lastColStyle[i][0], lastColStyle[i][1]);
}
@@ -251,7 +255,7 @@ export class JobsViewComponent implements AfterContentChecked {
// style it all over again
let seenJobs = 0;
for (let i = 0; i < currentItems.length; i++) {
this._table.grid.removeCellCssStyles('error-row'+i.toString());
this._table.grid.removeCellCssStyles('error-row' + i.toString());
let item = this.dataView.getFilteredItems()[i];
if (item.lastRunOutcome === 'Failed') {
this.addToStyleHash(seenJobs, false, this.filterStylingMap, args.column.name);
@@ -261,7 +265,7 @@ export class JobsViewComponent implements AfterContentChecked {
}
// one expansion for the row and one for
// the error detail
seenJobs ++;
seenJobs++;
i++;
}
seenJobs++;
@@ -277,7 +281,7 @@ export class JobsViewComponent implements AfterContentChecked {
} else {
let seenJobs = 0;
for (let i = 0; i < this.jobs.length; i++) {
this._table.grid.removeCellCssStyles('error-row'+i.toString());
this._table.grid.removeCellCssStyles('error-row' + i.toString());
let item = this.dataView.getItemByIdx(i);
// current filter
if (_.contains(filterValues, item[args.column.field])) {
@@ -291,7 +295,7 @@ export class JobsViewComponent implements AfterContentChecked {
}
// one expansion for the row and one for
// the error detail
seenJobs ++;
seenJobs++;
i++;
}
seenJobs++;
@@ -359,7 +363,7 @@ export class JobsViewComponent implements AfterContentChecked {
self.jobs.forEach(job => {
let jobId = job.jobId;
let jobHistories = self._jobCacheObject.getJobHistory(job.jobId);
let previousRuns = jobHistories.slice(jobHistories.length-5, jobHistories.length);
let previousRuns = jobHistories.slice(jobHistories.length - 5, jobHistories.length);
self.createJobChart(job.jobId, previousRuns);
});
});
@@ -427,27 +431,27 @@ export class JobsViewComponent implements AfterContentChecked {
}
private addToStyleHash(row: number, start: boolean, map: any, columnName: string) {
let hash : {
let hash: {
[index: number]: {
[id: string]: string;
}
} = {};
hash = this.setRowWithErrorClass(hash, row, 'job-with-error');
hash = this.setRowWithErrorClass(hash, row+1, 'error-row');
hash = this.setRowWithErrorClass(hash, row + 1, 'error-row');
if (start) {
if (map['start']) {
map['start'].push(['error-row'+row.toString(), hash]);
map['start'].push(['error-row' + row.toString(), hash]);
} else {
map['start'] = [['error-row'+row.toString(), hash]];
map['start'] = [['error-row' + row.toString(), hash]];
}
} else {
if (map[columnName]) {
map[columnName].push(['error-row'+row.toString(), hash]);
map[columnName].push(['error-row' + row.toString(), hash]);
} else {
map[columnName] = [['error-row'+row.toString(), hash]];
map[columnName] = [['error-row' + row.toString(), hash]];
}
}
this._table.grid.setCellCssStyles('error-row'+row.toString(), hash);
this._table.grid.setCellCssStyles('error-row' + row.toString(), hash);
}
private renderName(row, cell, value, columnDef, dataContext) {
@@ -557,13 +561,13 @@ export class JobsViewComponent implements AfterContentChecked {
self.jobHistories[job.jobId] = result.jobs;
self._jobCacheObject.setJobHistory(job.jobId, result.jobs);
let jobHistories = self._jobCacheObject.getJobHistory(job.jobId);
let previousRuns = jobHistories.slice(jobHistories.length-5, jobHistories.length);
let previousRuns = jobHistories.slice(jobHistories.length - 5, jobHistories.length);
self.createJobChart(job.jobId, previousRuns);
if (self._agentViewComponent.expanded.has(job.jobId)) {
let lastJobHistory = jobHistories[result.jobs.length-1];
let lastJobHistory = jobHistories[result.jobs.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;
let errorMessage = lastJobHistory ? lastJobHistory.message : noStepsMessage;
item['name'] = nls.localize('jobsView.error', 'Error: ') + errorMessage;
self._agentViewComponent.setExpanded(job.jobId, item['name']);
self.dataView.updateItem(job.jobId + '.error', item);
@@ -576,7 +580,7 @@ export class JobsViewComponent implements AfterContentChecked {
private createJobChart(jobId: string, jobHistories: sqlops.AgentJobHistoryInfo[]): void {
let chartHeights = this.getChartHeights(jobHistories);
for (let i = 0; i < jobHistories.length; i++) {
let runGraph = $(`table#${jobId}.jobprevruns > tbody > tr > td > div.bar${i+1}`);
let runGraph = $(`table#${jobId}.jobprevruns > tbody > tr > td > div.bar${i + 1}`);
if (jobHistories && jobHistories.length > 0) {
runGraph.css('height', chartHeights[i]);
let bgColor = jobHistories[i].runStatus === 0 ? 'red' : 'green';
@@ -599,11 +603,11 @@ export class JobsViewComponent implements AfterContentChecked {
// chart height normalization logic
private getChartHeights(jobHistories: sqlops.AgentJobHistoryInfo[]): string[] {
if (!jobHistories || jobHistories.length === 0) {
return ['5px','5px','5px','5px','5px'];
return ['5px', '5px', '5px', '5px', '5px'];
}
let maxDuration: number = 0;
jobHistories.forEach(history => {
let historyDuration = AgentJobUtilities.convertDurationToSeconds(history.runDuration) ;
let historyDuration = AgentJobUtilities.convertDurationToSeconds(history.runDuration);
if (historyDuration > maxDuration) {
maxDuration = historyDuration;
}
@@ -613,7 +617,7 @@ export class JobsViewComponent implements AfterContentChecked {
let chartHeights = [];
for (let i = 0; i < jobHistories.length; i++) {
let duration = jobHistories[i].runDuration;
let chartHeight = (maxBarHeight * AgentJobUtilities.convertDurationToSeconds(duration))/maxDuration;
let chartHeight = (maxBarHeight * AgentJobUtilities.convertDurationToSeconds(duration)) / maxDuration;
chartHeights.push(`${chartHeight}px`);
}
return chartHeights;
@@ -625,14 +629,14 @@ export class JobsViewComponent implements AfterContentChecked {
}
let expandedJobs = this._agentViewComponent.expanded;
let expansions = 0;
for (let i = 0; i < this.jobs.length; i++){
for (let i = 0; i < this.jobs.length; i++) {
let job = this.jobs[i];
if (job.lastRunOutcome === 0 && !expandedJobs.get(job.jobId)) {
this.expandJobRowDetails(i+expandedJobs.size);
this.addToStyleHash(i+expandedJobs.size, start, this.filterStylingMap, undefined);
this.expandJobRowDetails(i + expandedJobs.size);
this.addToStyleHash(i + expandedJobs.size, start, this.filterStylingMap, undefined);
this._agentViewComponent.setExpanded(job.jobId, 'Loading Error...');
} else if (job.lastRunOutcome === 0 && expandedJobs.get(job.jobId)) {
this.addToStyleHash(i+expansions, start, this.filterStylingMap, undefined);
this.addToStyleHash(i + expansions, start, this.filterStylingMap, undefined);
expansions++;
}
}
@@ -648,7 +652,7 @@ export class JobsViewComponent implements AfterContentChecked {
if (item._parent) {
value = value && _.contains(filterValues, item._parent[col.field]);
} else {
value = value && _.contains(filterValues, item[col.field]);
value = value && _.contains(filterValues, item[col.field]);
}
}
}
@@ -661,8 +665,8 @@ export class JobsViewComponent implements AfterContentChecked {
let jobItems = items.filter(x => x._parent === undefined);
let errorItems = items.filter(x => x._parent !== undefined);
this.sortingStylingMap[column] = items;
switch(column) {
case('Name'): {
switch (column) {
case ('Name'): {
this.dataView.setItems(jobItems);
// sort the actual jobs
this.dataView.sort((item1, item2) => {
@@ -670,13 +674,13 @@ export class JobsViewComponent implements AfterContentChecked {
}, isAscending);
break;
}
case('Last Run'): {
case ('Last Run'): {
this.dataView.setItems(jobItems);
// sort the actual jobs
this.dataView.sort((item1, item2) => this.dateCompare(item1, item2, true), isAscending);
break;
}
case ('Next Run') : {
case ('Next Run'): {
this.dataView.setItems(jobItems);
// sort the actual jobs
this.dataView.sort((item1, item2) => this.dateCompare(item1, item2, false), isAscending);
@@ -737,7 +741,7 @@ export class JobsViewComponent implements AfterContentChecked {
let item = jobItems[i];
if (item._child) {
let child = errorItems.find(error => error === item._child);
jobItems.splice(i+1, 0, child);
jobItems.splice(i + 1, 0, child);
jobItemsLength++;
}
}
@@ -751,12 +755,12 @@ export class JobsViewComponent implements AfterContentChecked {
}
} else {
for (let i = 0; i < this.jobs.length; i++) {
this._table.grid.removeCellCssStyles('error-row'+i.toString());
this._table.grid.removeCellCssStyles('error-row' + i.toString());
}
}
// add new style to the items back again
items = this.filterStack.length > 1 ? this.dataView.getFilteredItems() : this.dataView.getItems();
for (let i = 0; i < items.length; i ++) {
for (let i = 0; i < items.length; i++) {
let item = items[i];
if (item.lastRunOutcome === 'Failed') {
this.addToStyleHash(i, false, this.sortingStylingMap, column);
@@ -784,4 +788,13 @@ export class JobsViewComponent implements AfterContentChecked {
}
}
}
private openCreateJobDialog() {
let ownerUri: string = this._dashboardService.connectionManagementService.connectionInfo.ownerUri;
this._commandService.executeCommand("agent.openCreateJobDialog", ownerUri);
}
private refreshJobs() {
this._agentViewComponent.refresh = true;
}
}

View File

@@ -28,6 +28,7 @@ export interface IQueryManagementService {
runQueryStatement(ownerUri: string, line: number, column: number): Thenable<void>;
runQueryString(ownerUri: string, queryString: string): Thenable<void>;
runQueryAndReturn(ownerUri: string, queryString: string): Thenable<sqlops.SimpleExecuteResult>;
parseSyntax(ownerUri:string, query: string): Thenable<sqlops.SyntaxParseResult>;
getQueryRows(rowData: sqlops.QueryExecuteSubsetParams): Thenable<sqlops.QueryExecuteSubsetResult>;
disposeQuery(ownerUri: string): Thenable<void>;
saveResults(requestParams: sqlops.SaveResultsRequestParams): Thenable<sqlops.SaveResultRequestResult>;
@@ -63,6 +64,7 @@ export interface IQueryRequestHandler {
runQueryStatement(ownerUri: string, line: number, column: number): Thenable<void>;
runQueryString(ownerUri: string, queryString: string): Thenable<void>;
runQueryAndReturn(ownerUri: string, queryString: string): Thenable<sqlops.SimpleExecuteResult>;
parseSyntax(ownerUri:string, query: string): Thenable<sqlops.SyntaxParseResult>;
getQueryRows(rowData: sqlops.QueryExecuteSubsetParams): Thenable<sqlops.QueryExecuteSubsetResult>;
disposeQuery(ownerUri: string): Thenable<void>;
saveResults(requestParams: sqlops.SaveResultsRequestParams): Thenable<sqlops.SaveResultRequestResult>;
@@ -197,6 +199,11 @@ export class QueryManagementService implements IQueryManagementService {
return runner.runQueryAndReturn(ownerUri, queryString);
});
}
public parseSyntax(ownerUri: string, query: string): Thenable<sqlops.SyntaxParseResult> {
return this._runAction(ownerUri, (runner) => {
return runner.parseSyntax(ownerUri, query);
});
}
public getQueryRows(rowData: sqlops.QueryExecuteSubsetParams): Thenable<sqlops.QueryExecuteSubsetResult> {
return this._runAction(rowData.ownerUri, (runner) => {
return runner.getQueryRows(rowData);

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

@@ -635,6 +635,7 @@ declare module 'sqlops' {
runQueryStatement(ownerUri: string, line: number, column: number): Thenable<void>;
runQueryString(ownerUri: string, queryString: string): Thenable<void>;
runQueryAndReturn(ownerUri: string, queryString: string): Thenable<SimpleExecuteResult>;
parseSyntax(ownerUri: string, query: string): Thenable<SyntaxParseResult>;
getQueryRows(rowData: QueryExecuteSubsetParams): Thenable<QueryExecuteSubsetResult>;
disposeQuery(ownerUri: string): Thenable<void>;
saveResults(requestParams: SaveResultsRequestParams): Thenable<SaveResultRequestResult>;
@@ -769,6 +770,11 @@ declare module 'sqlops' {
rows: DbCellValue[][];
}
export interface SyntaxParseResult {
parseable: boolean;
errorMessages: string[];
}
// Query Batch Notification -----------------------------------------------------------------------
export interface QueryExecuteBatchNotificationParams {
batchSummary: BatchSummary;
@@ -1054,8 +1060,17 @@ declare module 'sqlops' {
wmiEvent = 4
}
export enum JobCompletionActionCondition{
Never = 0,
OnSuccess = 1,
OnFailure = 2,
Always = 3
}
export interface AgentJobInfo {
name: string;
owner: string;
description: string;
currentExecutionStatus: number;
lastRunOutcome: number;
currentExecutionStep: string;
@@ -1070,9 +1085,22 @@ declare module 'sqlops' {
lastRun: string;
nextRun: string;
jobId: string;
EmailLevel: JobCompletionActionCondition;
PageLevel: JobCompletionActionCondition;
EventLogLevel: JobCompletionActionCondition;
DeleteLevel: JobCompletionActionCondition;
OperatorToEmail: string;
OperatorToPage: string;
JobSteps: AgentJobStepInfo[];
JobSchedules: AgentJobScheduleInfo[];
Alerts: AgentAlertInfo[];
}
export interface AgentJobStepInfo {
export interface AgentJobScheduleInfo {
}
export interface AgentJobStep {
jobId: string;
stepId: string;
stepName: string;
@@ -1081,6 +1109,33 @@ declare module 'sqlops' {
runStatus: number;
}
export interface AgentJobStepInfo {
jobId: string;
jobName: string;
script: string;
scriptName: string;
stepName: string;
subSystem: string;
id: number;
failureAction: string;
successAction: string;
failStepId: number;
successStepId: number;
command: string;
commandExecutionSuccessCode: number;
databaseName: string;
databaseUserName: string;
server: string;
outputFileName: string;
appendToLogFile: boolean;
appendToStepHist: boolean;
writeLogToTable: boolean;
appendLogToTable: boolean;
retryAttempts: number;
retryInterval: number;
proxyName: string;
}
export interface AgentJobHistoryInfo {
instanceId: number;
sqlMessageId: string;
@@ -1098,7 +1153,7 @@ declare module 'sqlops' {
operatorPaged: string;
retriesAttempted: string;
server: string;
steps: AgentJobStepInfo[];
steps: AgentJobStep[];
}
export interface AgentProxyInfo {
@@ -1173,7 +1228,7 @@ declare module 'sqlops' {
job: AgentJobInfo;
}
export interface UpdateAgentJobResult extends ResultStatus {
export interface UpdateAgentJobResult extends ResultStatus {
job: AgentJobInfo;
}
@@ -1181,7 +1236,7 @@ declare module 'sqlops' {
step: AgentJobStepInfo;
}
export interface UpdateAgentJobStepResult extends ResultStatus {
export interface UpdateAgentJobStepResult extends ResultStatus {
step: AgentJobStepInfo;
}
@@ -1189,7 +1244,7 @@ declare module 'sqlops' {
step: AgentJobStepInfo;
}
export interface UpdateAgentProxyResult extends ResultStatus {
export interface UpdateAgentProxyResult extends ResultStatus {
step: AgentJobStepInfo;
}
@@ -1201,7 +1256,7 @@ declare module 'sqlops' {
alert: AgentJobStepInfo;
}
export interface UpdateAgentAlertResult extends ResultStatus {
export interface UpdateAgentAlertResult extends ResultStatus {
alert: AgentJobStepInfo;
}
@@ -1213,7 +1268,7 @@ declare module 'sqlops' {
operator: AgentOperatorInfo;
}
export interface UpdateAgentOperatorResult extends ResultStatus {
export interface UpdateAgentOperatorResult extends ResultStatus {
operator: AgentOperatorInfo;
}
@@ -1225,7 +1280,7 @@ declare module 'sqlops' {
operator: AgentOperatorInfo;
}
export interface UpdateAgentProxyResult extends ResultStatus {
export interface UpdateAgentProxyResult extends ResultStatus {
operator: AgentOperatorInfo;
}

View File

@@ -86,6 +86,13 @@ export enum NotifyMethods
notifyAll = 7
}
export enum JobCompletionActionCondition{
Never = 0,
OnSuccess = 1,
OnFailure = 2,
Always = 3
}
export enum AlertType
{
sqlServerEvent = 1,

View File

@@ -224,6 +224,10 @@ export class ExtHostDataProtocol extends ExtHostDataProtocolShape {
return this._resolveProvider<sqlops.QueryProvider>(handle).runQueryAndReturn(ownerUri, queryString);
}
$parseSyntax(handle: number, ownerUri: string, query: string): Thenable<sqlops.SyntaxParseResult> {
return this._resolveProvider<sqlops.QueryProvider>(handle).parseSyntax(ownerUri, query);
}
$getQueryRows(handle: number, rowData: sqlops.QueryExecuteSubsetParams): Thenable<sqlops.QueryExecuteSubsetResult> {
return this._resolveProvider<sqlops.QueryProvider>(handle).getQueryRows(rowData);
}

View File

@@ -114,6 +114,9 @@ export class MainThreadDataProtocol implements MainThreadDataProtocolShape {
runQueryAndReturn(ownerUri: string, queryString: string): Thenable<sqlops.SimpleExecuteResult> {
return self._proxy.$runQueryAndReturn(handle, ownerUri, queryString);
},
parseSyntax(ownerUri: string, query: string): Thenable<sqlops.SyntaxParseResult> {
return self._proxy.$parseSyntax(handle, ownerUri, query);
},
getQueryRows(rowData: sqlops.QueryExecuteSubsetParams): Thenable<sqlops.QueryExecuteSubsetResult> {
return self._proxy.$getQueryRows(handle, rowData);
},

View File

@@ -384,6 +384,7 @@ export function createApiFactory(
ScriptOperation: sqlExtHostTypes.ScriptOperation,
WeekDays: sqlExtHostTypes.WeekDays,
NotifyMethods: sqlExtHostTypes.NotifyMethods,
JobCompletionActionCondition: sqlExtHostTypes.JobCompletionActionCondition,
AlertType: sqlExtHostTypes.AlertType,
window,
tasks,

View File

@@ -140,6 +140,10 @@ export abstract class ExtHostDataProtocolShape {
* Runs a query for a provided query and returns result
*/
$runQueryAndReturn(handle: number, ownerUri: string, queryString: string): Thenable<sqlops.SimpleExecuteResult> { throw ni(); }
/**
* Parses a T-SQL string without actually executing it
*/
$parseSyntax(handle: number, ownerUri: string, query: string): Thenable<sqlops.SyntaxParseResult> { throw ni(); }
/**
* Gets a subset of rows in a result set in order to display in the UI
*/