Compare commits

..

24 Commits

Author SHA1 Message Date
Peter Schneider
9baee1c22c Changed the stored procedure call to work on case sensitive instances (#1809) 2018-06-30 15:48:15 -07:00
Karl Burtram
8cb67b4f9d Add Alert, Operator and Proxy panel tabs (#1811)
* Add Create Alert dialog

* Add Job Alerts view

* Stage WIP

* Add Proxy View component

* Hook up proxy and operator view callbacks

* Style cleanup

* Add additonal columns to views
2018-06-30 15:31:40 -07:00
Matt Irvine
0cd47bc328 Fix webview editor height issue (#1808) 2018-06-29 19:13:22 -07:00
Kevin Cunnane
07fb58d5e1 Fixes #1804 Dashboard Home tab should be overrideable for other connection providers (#1805)
- Adds new isHomeTab property. If set, this indicates a tab should override the default home tab.
2018-06-29 18:49:56 -07:00
Anthony Dresser
2d80d5e611 Add connection info to title (#1645)
* add connection info to the file label

* formatting

* first attempt at title shortening

* add user to title shortening

* add settings to control connection info in title

* formatting

* move setting
2018-06-29 17:04:09 -07:00
Anthony Dresser
4bd63b615b fix table highlighting issues (#1802) 2018-06-29 17:03:01 -07:00
Kevin Cunnane
335f667507 Fix null ref error when no database or server node are in object tree (#1790)
- Also fixed minor issue where `var` was used instead of `let` in a related file, since this is discouraged in our codebase.
2018-06-29 16:49:46 -07:00
Madeline MacDonald
1819036d7d Profiler Keybindings (#1801)
* Added default new profiler keybinding. Exposed profiler.start and profiler.stop so they can be used with keybindings

* Changing where session state gets set

* Cleaning up unnecessary code
2018-06-29 16:34:40 -07:00
Kevin Cunnane
4f76f116ac Ensure dashboard tabs only show for supported providers (#1798)
- Fixes #1764, fixes #1765
- Fixes bug where configModifiers were passed in the dashboard service in some places, and a different object with incompatible / missing fields in a different one. Now it always passes in the owner object, and this should have the required fields
 - Note: this doesn't fix the problem where the code does not fail to build, which I would have expected to be the case.
- Adds "provider" as an option for TabConfig including in the schema
- Filter by "provider" type when loading tabs. If provider is not set for a tab, will assume "MSSQL" since it's the default provider. This is a design decision - without this, we would either have to disable tabs that don't show this or have them incorrectly show for non-MSSQL provider types

Notes:

Does not override behavior for individual widgets, which still get the current provider set as their provider if not specified. This seems to be required so should be fine.
We will fix tasks/widgets that aren't supported by a provider showing up in a separate PR, since it's a different issue
2018-06-29 16:04:57 -07:00
Matt Irvine
1eba7c7d2a Fix model view input box bugs (#1797) 2018-06-29 14:20:06 -07:00
Karl Burtram
83234dd52c Update SQL Tools Service to 1.5.0-alpha.1 2018-06-29 11:09:24 -07:00
Aditya Bist
bae23b7fce Agent: UI/UX finishes and clean up (#1768)
* changed columnw widths

* fixed scrolling for prev run job history list

* fixed all resizing and scrolling issues in agent

* removed commented code

* code review comments
2018-06-28 10:12:02 -07:00
Matt Irvine
3db61eaa82 Add sqlservices wizard sample (#1769) 2018-06-28 10:06:48 -07:00
Matt Irvine
5cf85a0361 Display page number, title, and description in wizard page headers (#1766) 2018-06-27 16:26:26 -07:00
Anthony Dresser
ffe27f5bde Tab outline in dashboard (#1742)
* add border to dashboard tabs

* formatting
2018-06-27 14:33:58 -07:00
Anthony Dresser
78bcd9d54c bump slickgrid (#1758) 2018-06-27 14:29:18 -07:00
Anthony Dresser
1a9797f0ff Change default settings to remove error (#1741)
* change default settings

* formatting
2018-06-27 13:59:36 -07:00
Karl Burtram
0de94ff8a4 Add "Schedule Picker" dialog (#1759)
* Merge master

* Add pick schedule dialog 1

* Add Pick Schedule button to create job dialog

* Cleanup Pick Schedule dialog

* Hook-up onClose event for Pick Schedule

* Code review feedback
2018-06-27 13:42:08 -07:00
Kevin Cunnane
472233d9a7 Providers without metadata service or serverInfo shouldn't break dashboard (#1761)
- Fix a number of issues that arose while testing a provider without a metadata service or serverInfo object returned via DMP calls. These should be optional services/features and we should be resilient to them not existing. In most places we already have these checks
- This does not cover a number of "improvement" scenarios, such as filtering extension tabs by provider, and defaulting any tabs that don't specify a provider to be MSSQL. This and some other features to ensure things make sense will be implemented in separate PRs but this unblocked the scenario
2018-06-27 13:40:37 -07:00
Aditya Bist
f69e31b0d5 Jobs/new step (#1734)
* 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

* fixed step issue, step can me made now
2018-06-26 19:11:59 -07:00
Karl Burtram
60b696cc31 Fix casing for resourceprovider module require (#1731)
* Fix casing for resourceprovider module require

* Rename files to avoid build output recasing on macos
2018-06-26 18:37:31 -07:00
Leila Lali
549037f744 fixing model view issues (#1737)
* fixing model view issues
2018-06-26 16:40:41 -07:00
Karl Burtram
ca5e1e6133 Fix a couple references to VS Code in UI (#1730) 2018-06-26 15:30:11 -07:00
Aditya Bist
3e3ff163db 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
2018-06-26 11:09:41 -07:00
116 changed files with 9255 additions and 1264 deletions

View File

@@ -0,0 +1,38 @@
'use strict';
import * as sqlops from 'sqlops';
export class AgentUtils {
private static _agentService: sqlops.AgentServicesProvider;
private static _connectionService: sqlops.ConnectionProvider;
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 getQueryProvider(): 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,25 @@
/*---------------------------------------------------------------------------------------------
* 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';
export class CreateAlertData {
public ownerUri: string;
private _alert: sqlops.AgentAlertInfo;
constructor(ownerUri:string) {
this.ownerUri = ownerUri;
}
public async initialize() {
let agentService = await AgentUtils.getAgentService();
}
public async save() {
}
}

View File

@@ -0,0 +1,150 @@
/*---------------------------------------------------------------------------------------------
* 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';
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();
let jobDefaults = await this._agentService.getJobDefaults(this.ownerUri);
if (jobDefaults && jobDefaults.success) {
this._jobCategories = jobDefaults.categories.map((cat) => {
return cat.name;
});
this._defaultOwner = jobDefaults.owner;
this._operators = ['', this._defaultOwner];
}
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()
}];
this.jobSchedules = [];
}
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
};
}
public addJobSchedule(schedule: sqlops.AgentJobScheduleInfo) {
let existingSchedule = this.jobSchedules.find(item => item.name === schedule.name);
if (!existingSchedule) {
this.jobSchedules.push(schedule);
}
}
}

View File

@@ -0,0 +1,29 @@
/*---------------------------------------------------------------------------------------------
* 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';
export class CreateScheduleData {
public ownerUri: string;
public schedules: sqlops.AgentJobScheduleInfo[];
public selectedSchedule: sqlops.AgentJobScheduleInfo;
constructor(ownerUri:string) {
this.ownerUri = ownerUri;
}
public async initialize() {
let agentService = await AgentUtils.getAgentService();
let result = await agentService.getJobSchedules(this.ownerUri);
if (result && result.success) {
this.schedules = result.schedules;
}
}
public async save() {
}
}

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: 1,
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,29 @@
/*---------------------------------------------------------------------------------------------
* 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';
export class PickScheduleData {
public ownerUri: string;
public schedules: sqlops.AgentJobScheduleInfo[];
public selectedSchedule: sqlops.AgentJobScheduleInfo;
constructor(ownerUri:string) {
this.ownerUri = ownerUri;
}
public async initialize() {
let agentService = await AgentUtils.getAgentService();
let result = await agentService.getJobSchedules(this.ownerUri);
if (result && result.success) {
this.schedules = result.schedules;
}
}
public async save() {
}
}

View File

@@ -0,0 +1,149 @@
/*---------------------------------------------------------------------------------------------
* 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 { CreateAlertData } from '../data/createAlertData';
export class CreateAlertDialog {
// Top level
private readonly DialogTitle: string = 'Create Alert';
private readonly OkButtonText: string = 'OK';
private readonly CancelButtonText: string = 'Cancel';
private readonly GeneralTabText: string = 'Response';
private readonly ResponseTabText: string = 'Steps';
private readonly OptionsTabText: string = 'Options';
private readonly HistoryTabText: string = 'History';
// General tab strings
private readonly NameTextBoxLabel: string = 'Name';
// Response tab strings
private readonly ExecuteJobTextBoxLabel: string = 'Execute Job';
// Options tab strings
private readonly AdditionalMessageTextBoxLabel: string = 'Additional notification message to send';
// History tab strings
private readonly ResetCountTextBoxLabel: string = 'Reset Count';
// UI Components
private dialog: sqlops.window.modelviewdialog.Dialog;
private generalTab: sqlops.window.modelviewdialog.DialogTab;
private responseTab: sqlops.window.modelviewdialog.DialogTab;
private optionsTab: sqlops.window.modelviewdialog.DialogTab;
private historyTab: sqlops.window.modelviewdialog.DialogTab;
private schedulesTable: sqlops.TableComponent;
// General tab controls
private nameTextBox: sqlops.InputBoxComponent;
// Response tab controls
private executeJobTextBox: sqlops.InputBoxComponent;
// Options tab controls
private additionalMessageTextBox: sqlops.InputBoxComponent;
// History tab controls
private resetCountTextBox: sqlops.InputBoxComponent;
private model: CreateAlertData;
private _onSuccess: vscode.EventEmitter<CreateAlertData> = new vscode.EventEmitter<CreateAlertData>();
public readonly onSuccess: vscode.Event<CreateAlertData> = this._onSuccess.event;
constructor(ownerUri: string) {
this.model = new CreateAlertData(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.responseTab = sqlops.window.modelviewdialog.createTab(this.ResponseTabText);
this.optionsTab = sqlops.window.modelviewdialog.createTab(this.OptionsTabText);
this.historyTab = sqlops.window.modelviewdialog.createTab(this.HistoryTabText);
this.initializeGeneralTab();
this.initializeResponseTab();
this.initializeOptionsTab();
this.initializeHistoryTab();
this.dialog.content = [this.generalTab, this.responseTab, this.optionsTab, this.historyTab];
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;
sqlops.window.modelviewdialog.openDialog(this.dialog);
}
private initializeGeneralTab() {
this.generalTab.registerContent(async view => {
this.nameTextBox = view.modelBuilder.inputBox().component();
let formModel = view.modelBuilder.formContainer()
.withFormItems([{
component: this.nameTextBox,
title: this.NameTextBoxLabel
}]).withLayout({ width: '100%' }).component();
await view.initializeModel(formModel);
});
}
private initializeResponseTab() {
this.responseTab.registerContent(async view => {
this.executeJobTextBox = view.modelBuilder.inputBox().component();
let formModel = view.modelBuilder.formContainer()
.withFormItems([{
component: this.executeJobTextBox,
title: this.ExecuteJobTextBoxLabel
}]).withLayout({ width: '100%' }).component();
await view.initializeModel(formModel);
});
}
private initializeOptionsTab() {
this.optionsTab.registerContent(async view => {
this.additionalMessageTextBox = view.modelBuilder.inputBox().component();
let formModel = view.modelBuilder.formContainer()
.withFormItems([{
component: this.additionalMessageTextBox,
title: this.AdditionalMessageTextBoxLabel
}]).withLayout({ width: '100%' }).component();
await view.initializeModel(formModel);
});
}
private initializeHistoryTab() {
this.historyTab.registerContent(async view => {
this.resetCountTextBox = view.modelBuilder.inputBox().component();
let formModel = view.modelBuilder.formContainer()
.withFormItems([{
component: this.resetCountTextBox,
title: this.ResetCountTextBoxLabel
}]).withLayout({ width: '100%' }).component();
await view.initializeModel(formModel);
});
}
private async execute() {
this.updateModel();
await this.model.save();
this._onSuccess.fire(this.model);
}
private async cancel() {
}
private updateModel() {
}
}

View File

@@ -0,0 +1,464 @@
/*---------------------------------------------------------------------------------------------
* 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';
import { PickScheduleDialog } from './pickScheduleDialog';
import { CreateAlertDialog } from './createAlertDialog';
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';
// Schedules tab strings
private readonly SchedulesTopLabelString: string = 'Schedules list';
private readonly PickScheduleButtonString: string = 'Pick Schedule';
// Alerts tab strings
private readonly AlertsTopLabelString: string = 'Alerts list';
private readonly NewAlertButtonString: string = 'New Alert';
// 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;
// Schedule tab controls
private schedulesTable: sqlops.TableComponent;
private pickScheduleButton: sqlops.ButtonComponent;
// Alert tab controls
private alertsTable: sqlops.TableComponent;
private newAlertButton: sqlops.ButtonComponent;
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, '', '', 1, 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() {
this.alertsTab.registerContent(async view => {
this.alertsTable = view.modelBuilder.table()
.withProperties({
columns: [
'Alert Name'
],
data: [],
height: 600,
width: 400
}).component();
this.newAlertButton = view.modelBuilder.button().withProperties({
label: this.NewAlertButtonString,
width: 80
}).component();
this.newAlertButton.onDidClick((e)=>{
let alertDialog = new CreateAlertDialog(this.model.ownerUri);
alertDialog.onSuccess((dialogModel) => {
});
alertDialog.showDialog();
});
let formModel = view.modelBuilder.formContainer()
.withFormItems([{
component: this.alertsTable,
title: this.AlertsTopLabelString,
actions: [this.newAlertButton]
}]).withLayout({ width: '100%' }).component();
await view.initializeModel(formModel);
});
}
private initializeSchedulesTab() {
this.schedulesTab.registerContent(async view => {
this.schedulesTable = view.modelBuilder.table()
.withProperties({
columns: [
'Schedule Name'
],
data: [],
height: 600,
width: 400
}).component();
this.pickScheduleButton = view.modelBuilder.button().withProperties({
label: this.PickScheduleButtonString,
width: 80
}).component();
this.pickScheduleButton.onDidClick((e)=>{
let pickScheduleDialog = new PickScheduleDialog(this.model.ownerUri);
pickScheduleDialog.onSuccess((dialogModel) => {
let selectedSchedule = dialogModel.selectedSchedule;
if (selectedSchedule) {
this.model.addJobSchedule(selectedSchedule);
this.populateScheduleTable();
}
});
pickScheduleDialog.showDialog();
});
let formModel = view.modelBuilder.formContainer()
.withFormItems([{
component: this.schedulesTable,
title: this.SchedulesTopLabelString,
actions: [this.pickScheduleButton]
}]).withLayout({ width: '100%' }).component();
await view.initializeModel(formModel);
this.populateScheduleTable();
});
}
private populateScheduleTable() {
if (this.model.jobSchedules) {
let data: any[][] = [];
for (let i = 0; i < this.model.jobSchedules.length; ++i) {
let schedule = this.model.jobSchedules[i];
data[i] = [ schedule.name ];
}
this.schedulesTable.data = data;
}
}
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,91 @@
/*---------------------------------------------------------------------------------------------
* 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 { CreateScheduleData } from '../data/createScheduleData';
export class CreateScheduleDialog {
// Top level
private readonly DialogTitle: string = 'New Schedule';
private readonly OkButtonText: string = 'OK';
private readonly CancelButtonText: string = 'Cancel';
// UI Components
private dialog: sqlops.window.modelviewdialog.Dialog;
private schedulesTable: sqlops.TableComponent;
private model: CreateScheduleData;
private _onSuccess: vscode.EventEmitter<CreateScheduleData> = new vscode.EventEmitter<CreateScheduleData>();
public readonly onSuccess: vscode.Event<CreateScheduleData> = this._onSuccess.event;
constructor(ownerUri: string) {
this.model = new CreateScheduleData(ownerUri);
}
public async showDialog() {
await this.model.initialize();
this.dialog = sqlops.window.modelviewdialog.createDialog(this.DialogTitle);
this.initializeContent();
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;
sqlops.window.modelviewdialog.openDialog(this.dialog);
}
private initializeContent() {
this.dialog.registerContent(async view => {
this.schedulesTable = view.modelBuilder.table()
.withProperties({
columns: [
'Schedule Name'
],
data: [],
height: 600,
width: 400
}).component();
let formModel = view.modelBuilder.formContainer()
.withFormItems([{
component: this.schedulesTable,
title: 'Schedules'
}]).withLayout({ width: '100%' }).component();
await view.initializeModel(formModel);
if (this.model.schedules) {
let data: any[][] = [];
for (let i = 0; i < this.model.schedules.length; ++i) {
let schedule = this.model.schedules[i];
data[i] = [ schedule.name ];
}
this.schedulesTable.data = data;
}
});
}
private async execute() {
this.updateModel();
await this.model.save();
this._onSuccess.fire(this.model);
}
private async cancel() {
}
private updateModel() {
let selectedRows = this.schedulesTable.selectedRows;
if (selectedRows && selectedRows.length > 0) {
let selectedRow = selectedRows[0];
this.model.selectedSchedule = this.model.schedules[selectedRow];
}
}
}

View File

@@ -0,0 +1,421 @@
/*---------------------------------------------------------------------------------------------
* 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 jobName: string;
private server: string;
private stepId: number;
private jobModel: CreateJobData;
constructor(
ownerUri: string,
jobName: string,
server: string,
stepId: number,
jobModel?: CreateJobData
) {
this.model = new CreateStepData(ownerUri);
this.stepId = stepId;
this.ownerUri = ownerUri;
this.jobName = jobName;
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.onClick(async () => await this.execute());
this.dialog.okButton.label = CreateStepDialog.OkButtonText;
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.nameTextBox.required = true;
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() {
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);
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) {
this.outputFileBrowserButton = view.modelBuilder.button()
.withProperties({ width: '20px', label: '...' }).component();
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.jobName = this.jobName;
this.model.id = this.stepId;
this.model.server = this.server;
this.model.stepName = this.nameTextBox.value;
this.model.subSystem = this.typeDropdown.value as string;
this.model.databaseName = this.databaseDropdown.value as string;
this.model.script = this.commandTextBox.value;
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();
}
public async openNewStepDialog() {
let databases = await AgentUtils.getDatabases(this.ownerUri);
let queryProvider = await AgentUtils.getQueryProvider();
this.initializeUIComponents();
this.createGeneralTab(databases, queryProvider);
this.createAdvancedTab();
sqlops.window.modelviewdialog.openDialog(this.dialog);
}
}

View File

@@ -0,0 +1,92 @@
/*---------------------------------------------------------------------------------------------
* 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 { PickScheduleData } from '../data/pickScheduleData';
export class PickScheduleDialog {
// TODO: localize
// Top level
private readonly DialogTitle: string = 'Job Schedules';
private readonly OkButtonText: string = 'OK';
private readonly CancelButtonText: string = 'Cancel';
private readonly SchedulesTabText: string = 'Schedules';
// UI Components
private dialog: sqlops.window.modelviewdialog.Dialog;
private schedulesTable: sqlops.TableComponent;
private model: PickScheduleData;
private _onSuccess: vscode.EventEmitter<PickScheduleData> = new vscode.EventEmitter<PickScheduleData>();
public readonly onSuccess: vscode.Event<PickScheduleData> = this._onSuccess.event;
constructor(ownerUri: string) {
this.model = new PickScheduleData(ownerUri);
}
public async showDialog() {
await this.model.initialize();
this.dialog = sqlops.window.modelviewdialog.createDialog(this.DialogTitle);
this.initializeContent();
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;
sqlops.window.modelviewdialog.openDialog(this.dialog);
}
private initializeContent() {
this.dialog.registerContent(async view => {
this.schedulesTable = view.modelBuilder.table()
.withProperties({
columns: [
'Schedule Name'
],
data: [],
height: 600,
width: 400
}).component();
let formModel = view.modelBuilder.formContainer()
.withFormItems([{
component: this.schedulesTable,
title: 'Schedules'
}]).withLayout({ width: '100%' }).component();
await view.initializeModel(formModel);
if (this.model.schedules) {
let data: any[][] = [];
for (let i = 0; i < this.model.schedules.length; ++i) {
let schedule = this.model.schedules[i];
data[i] = [ schedule.name ];
}
this.schedulesTable.data = data;
}
});
}
private async execute() {
this.updateModel();
await this.model.save();
this._onSuccess.fire(this.model);
}
private async cancel() {
}
private updateModel() {
let selectedRows = this.schedulesTable.selectedRows;
if (selectedRows && selectedRows.length > 0) {
let selectedRow = selectedRows[0];
this.model.selectedSchedule = this.model.schedules[selectedRow];
}
}
}

View File

@@ -6,12 +6,10 @@
import vscode = require('vscode');
import { MainController } from './mainController';
import { ApiWrapper } from './apiWrapper';
export let controller: MainController;
export function activate(context: vscode.ExtensionContext) {
let apiWrapper = new ApiWrapper();
controller = new MainController(context, apiWrapper);
controller = new MainController(context);
controller.activate();
}

View File

@@ -3,24 +3,47 @@
* 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 { CreateAlertDialog } from './dialogs/createAlertDialog';
import { CreateJobDialog } from './dialogs/createJobDialog';
import { CreateStepDialog } from './dialogs/createStepDialog';
import { PickScheduleDialog } from './dialogs/pickScheduleDialog';
/**
* The main controller class that initializes the extension
*/
export class MainController {
protected _apiWrapper: ApiWrapper;
export class MainController {
protected _context: vscode.ExtensionContext;
// PUBLIC METHODS //////////////////////////////////////////////////////
public constructor(context: vscode.ExtensionContext, apiWrapper?: ApiWrapper) {
this._apiWrapper = apiWrapper || new ApiWrapper();
public constructor(context: vscode.ExtensionContext) {
this._context = context;
}
console.log('Got: ' + apiWrapper);
/**
* Activates the extension
*/
public activate(): void {
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, stepId: number) => {
let dialog = new CreateStepDialog(ownerUri, jobId, server, stepId);
dialog.openNewStepDialog();
});
vscode.commands.registerCommand('agent.openPickScheduleDialog', (ownerUri: string) => {
let dialog = new PickScheduleDialog(ownerUri);
dialog.showDialog();
});
vscode.commands.registerCommand('agent.openCreateAlertDialog', (ownerUri: string) => {
let dialog = new CreateAlertDialog(ownerUri);
dialog.showDialog();
});
vscode.commands.registerCommand('agent.openCreateOperatorDialog', (ownerUri: string) => {
});
vscode.commands.registerCommand('agent.openCreateProxyDialog', (ownerUri: string) => {
});
}
/**
@@ -28,11 +51,4 @@ export class MainController {
*/
public deactivate(): void {
}
public activate(): void {
this._apiWrapper.registerWebviewProvider('data-management-agent', webview => {
webview.html = '<div><h1>SQL Agent</h1></div>';
});
}
}

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

@@ -2,4 +2,4 @@
See [documentation](https://code.visualstudio.com/docs/editor/versioncontrol#_merge-conflicts).
**Notice** This is a an extension that is bundled with Visual Studio Code.
**Notice** This is a an extension that is bundled with SQL Operations Studio.

View File

@@ -1,6 +1,6 @@
{
"downloadUrl": "https://github.com/Microsoft/sqltoolsservice/releases/download/v{#version#}/microsoft.sqltools.servicelayer-{#fileName#}",
"version": "1.4.0-alpha.46",
"version": "1.5.0-alpha.2",
"downloadFileNames": {
"Windows_86": "win-x86-netcoreapp2.1.zip",
"Windows_64": "win-x64-netcoreapp2.1.zip",
@@ -16,4 +16,4 @@
},
"installDirectory": "../sqltoolsservice/{#platform#}/{#version#}",
"executableFiles": ["MicrosoftSqlToolsServiceLayer.exe", "MicrosoftSqlToolsServiceLayer"]
}
}

View File

@@ -37,7 +37,7 @@ export default class ContextProvider {
public onDashboardOpen(e: sqlops.DashboardDocument): void {
let iscloud: boolean;
let edition: number;
if (e.profile.providerName.toLowerCase() === 'mssql' && !types.isUndefinedOrNull(e.serverInfo.engineEditionId)) {
if (e.profile.providerName.toLowerCase() === 'mssql' && !types.isUndefinedOrNull(e.serverInfo) && !types.isUndefinedOrNull(e.serverInfo.engineEditionId)) {
if (isCloudEditions.some(i => i === e.serverInfo.engineEditionId)) {
iscloud = true;
} else {

View File

@@ -64,6 +64,10 @@ export interface DeleteAgentJobParams {
job: sqlops.AgentJobInfo;
}
export interface AgentJobDefaultsParams {
ownerUri: string;
}
// Job Step management parameters
export interface CreateAgentJobStepParams {
ownerUri: string;
@@ -144,6 +148,27 @@ export interface DeleteAgentProxyParams {
proxy: sqlops.AgentProxyInfo;
}
// Job Schedule management parameters
export interface AgentJobScheduleParams {
ownerUri: string;
}
export interface CreateAgentJobScheduleParams {
ownerUri: string;
schedule: sqlops.AgentJobScheduleInfo;
}
export interface UpdateAgentJobScheduleParams {
ownerUri: string;
originalScheduleName: string;
schedule: sqlops.AgentJobScheduleInfo;
}
export interface DeleteAgentJobScheduleParams {
ownerUri: string;
schedule: sqlops.AgentJobScheduleInfo;
}
// Agent Job management requests
export namespace AgentJobsRequest {
export const type = new RequestType<AgentJobsParams, sqlops.AgentJobsResult, void, void>('agent/jobs');
@@ -169,6 +194,10 @@ export namespace DeleteAgentJobRequest {
export const type = new RequestType<DeleteAgentJobParams, sqlops.ResultStatus, void, void>('agent/deletejob');
}
export namespace AgentJobDefaultsRequest {
export const type = new RequestType<AgentJobDefaultsParams, sqlops.AgentJobDefaultsResult, void, void>('agent/jobdefaults');
}
// Job Step requests
export namespace CreateAgentJobStepRequest {
export const type = new RequestType<CreateAgentJobStepParams, sqlops.CreateAgentJobStepResult, void, void>('agent/createjobstep');
@@ -233,4 +262,21 @@ export namespace DeleteAgentProxyRequest {
export const type = new RequestType<DeleteAgentProxyParams, sqlops.ResultStatus, void, void>('agent/deleteproxy');
}
// Job Schedules requests
export namespace AgentJobSchedulesRequest {
export const type = new RequestType<AgentJobScheduleParams, sqlops.AgentJobSchedulesResult, void, void>('agent/schedules');
}
export namespace CreateAgentJobScheduleRequest {
export const type = new RequestType<CreateAgentJobScheduleParams, sqlops.CreateAgentJobScheduleResult, void, void>('agent/createschedule');
}
export namespace UpdateAgentJobScheduleRequest {
export const type = new RequestType<UpdateAgentJobScheduleParams, sqlops.UpdateAgentJobScheduleResult, void, void>('agent/updateschedule');
}
export namespace DeleteAgentJobScheduleRequest {
export const type = new RequestType<DeleteAgentJobScheduleParams, sqlops.ResultStatus, void, void>('agent/deleteschedule');
}
// ------------------------------- < Agent Management > ------------------------------------

View File

@@ -135,6 +135,20 @@ export class AgentServicesFeature extends SqlOpsFeature<undefined> {
);
};
let getJobDefaults = (ownerUri: string): Thenable<sqlops.AgentJobDefaultsResult> => {
let params: contracts.AgentJobDefaultsParams = {
ownerUri: ownerUri
};
let requestType = contracts.AgentJobDefaultsRequest.type;
return client.sendRequest(requestType, params).then(
r => r,
e => {
client.logFailedRequest(requestType, e);
return Promise.resolve(undefined);
}
);
};
// Job Step management methods
let createJobStep = (ownerUri: string, stepInfo: sqlops.AgentJobStepInfo): Thenable<sqlops.CreateAgentJobStepResult> => {
let params: contracts.CreateAgentJobStepParams = {
@@ -365,6 +379,67 @@ export class AgentServicesFeature extends SqlOpsFeature<undefined> {
);
};
// Job Schedule management methods
let getJobSchedules = (ownerUri: string): Thenable<sqlops.AgentJobSchedulesResult> => {
let params: contracts.AgentJobScheduleParams = {
ownerUri: ownerUri
};
let requestType = contracts.AgentJobSchedulesRequest.type;
return client.sendRequest(requestType, params).then(
r => r,
e => {
client.logFailedRequest(requestType, e);
return Promise.resolve(undefined);
}
);
};
let createJobSchedule = (ownerUri: string, scheduleInfo: sqlops.AgentJobScheduleInfo): Thenable<sqlops.CreateAgentJobScheduleResult> => {
let params: contracts.CreateAgentJobScheduleParams = {
ownerUri: ownerUri,
schedule: scheduleInfo
};
let requestType = contracts.CreateAgentJobScheduleRequest.type;
return client.sendRequest(requestType, params).then(
r => r,
e => {
client.logFailedRequest(requestType, e);
return Promise.resolve(undefined);
}
);
};
let updateJobSchedule = (ownerUri: string, originalScheduleName: string, scheduleInfo: sqlops.AgentJobScheduleInfo): Thenable<sqlops.UpdateAgentJobScheduleResult> => {
let params: contracts.UpdateAgentJobScheduleParams = {
ownerUri: ownerUri,
originalScheduleName: originalScheduleName,
schedule: scheduleInfo
};
let requestType = contracts.UpdateAgentJobScheduleRequest.type;
return client.sendRequest(requestType, params).then(
r => r,
e => {
client.logFailedRequest(requestType, e);
return Promise.resolve(undefined);
}
);
};
let deleteJobSchedule = (ownerUri: string, scheduleInfo: sqlops.AgentJobScheduleInfo): Thenable<sqlops.ResultStatus> => {
let params: contracts.DeleteAgentJobScheduleParams = {
ownerUri: ownerUri,
schedule: scheduleInfo
};
let requestType = contracts.DeleteAgentJobScheduleRequest.type;
return client.sendRequest(requestType, params).then(
r => r,
e => {
client.logFailedRequest(requestType, e);
return Promise.resolve(undefined);
}
);
};
return sqlops.dataprotocol.registerAgentServicesProvider({
providerId: client.providerId,
getJobs,
@@ -373,6 +448,7 @@ export class AgentServicesFeature extends SqlOpsFeature<undefined> {
createJob,
updateJob,
deleteJob,
getJobDefaults,
createJobStep,
updateJobStep,
deleteJobStep,
@@ -387,7 +463,11 @@ export class AgentServicesFeature extends SqlOpsFeature<undefined> {
getProxies,
createProxy,
updateProxy,
deleteProxy
deleteProxy,
getJobSchedules,
createJobSchedule,
updateJobSchedule,
deleteJobSchedule
});
}
}

View File

@@ -32,6 +32,16 @@
"command": "profiler.newProfiler",
"title": "New Profiler",
"category": "Profiler"
},
{
"command": "profiler.start",
"title": "Start",
"category": "Profiler"
},
{
"command": "profiler.stop",
"title": "Stop",
"category": "Profiler"
}
],
"outputChannels": [

View File

@@ -60,7 +60,7 @@
"reflect-metadata": "^0.1.8",
"rxjs": "5.4.0",
"semver": "4.3.6",
"slickgrid": "github:anthonydresser/SlickGrid#2.3.20",
"slickgrid": "github:anthonydresser/SlickGrid#2.3.22",
"spdlog": "0.6.0",
"sudo-prompt": "^8.0.0",
"svg.js": "^2.2.5",

View File

@@ -9,7 +9,7 @@ declare @dbsize table
Free_Space_MB decimal(20,2) default (0))
insert into @dbsize
(Dbname,file_Size_MB,Space_Used_MB,Free_Space_MB)
exec sp_msforeachdb
exec sp_MSforeachdb
'use [?];
select DB_NAME() AS DbName,
sum(size)/128.0 AS File_Size_MB,
@@ -24,7 +24,7 @@ declare @logsize table
log_Free_Space_MB decimal(20,2)default (0))
insert into @logsize
(Dbname,Log_File_Size_MB,log_Space_Used_MB,log_Free_Space_MB)
exec sp_msforeachdb
exec sp_MSforeachdb
'use [?];
select DB_NAME() AS DbName,
sum(size)/128.0 AS Log_File_Size_MB,
@@ -38,7 +38,7 @@ declare @dbfreesize table
Freespace varchar(50)default (0.00))
insert into @dbfreesize
(name,database_size,Freespace)
exec sp_msforeachdb
exec sp_MSforeachdb
'use [?];SELECT database_name = db_name()
,database_size = ltrim(str((convert(DECIMAL(15, 2), dbsize) + convert(DECIMAL(15, 2), logsize)) * 8192 / 1048576, 15, 2) + ''MB'')
,''unallocated space'' = ltrim(str((

View File

@@ -19,7 +19,7 @@
"commands": [
{
"command": "sqlservices.openDialog",
"title": "openDialog"
"title": "sqlservices.openDialog"
},
{
"command": "sqlservices.openEditor",
@@ -32,6 +32,10 @@
{
"command": "sqlservices.openEditorWithWebView2",
"title": "sqlservices.openEditorWithWebView2"
},
{
"command": "sqlservices.openWizard",
"title": "sqlservices.openWizard"
}
],
"dashboard.tabs": [

View File

@@ -60,9 +60,212 @@ export default class MainController implements vscode.Disposable {
this.openEditorWithWebview2();
});
vscode.commands.registerCommand('sqlservices.openWizard', () => {
this.openWizard();
});
return Promise.resolve(true);
}
private async getTabContent(view: sqlops.ModelView, customButton1: sqlops.window.modelviewdialog.Button, customButton2: sqlops.window.modelviewdialog.Button, componentWidth: number | string): Promise<void> {
let inputBox = view.modelBuilder.inputBox()
.withProperties({
multiline: true,
height: 100
}).component();
let inputBoxWrapper = view.modelBuilder.loadingComponent().withItem(inputBox).component();
inputBoxWrapper.loading = false;
customButton1.onClick(() => {
inputBoxWrapper.loading = true;
setTimeout(() => inputBoxWrapper.loading = false, 5000);
});
let inputBox2 = view.modelBuilder.inputBox().component();
let backupFilesInputBox = view.modelBuilder.inputBox().component();
let checkbox = view.modelBuilder.checkBox()
.withProperties({
label: 'Copy-only backup'
})
.component();
checkbox.onChanged(e => {
console.info("inputBox.enabled " + inputBox.enabled);
inputBox.enabled = !inputBox.enabled;
});
let button = view.modelBuilder.button()
.withProperties({
label: '+'
}).component();
let button3 = view.modelBuilder.button()
.withProperties({
label: '-'
}).component();
let button2 = view.modelBuilder.button()
.component();
button.onDidClick(e => {
backupFilesInputBox.value = 'Button clicked';
});
let dropdown = view.modelBuilder.dropDown()
.withProperties({
value: 'Full',
values: ['Full', 'Differential', 'Transaction Log']
})
.component();
let f = 0;
inputBox.onTextChanged((params) => {
vscode.window.showInformationMessage(inputBox.value);
f = f + 1;
inputBox2.value = f.toString();
});
dropdown.onValueChanged((params) => {
vscode.window.showInformationMessage(inputBox2.value);
inputBox.value = dropdown.value.toString();
});
let radioButton = view.modelBuilder.radioButton()
.withProperties({
value: 'option1',
name: 'radioButtonOptions',
label: 'Option 1',
checked: true
//width: 300
}).component();
let radioButton2 = view.modelBuilder.radioButton()
.withProperties({
value: 'option2',
name: 'radioButtonOptions',
label: 'Option 2'
}).component();
let inputBox3 = view.modelBuilder.inputBox().component();
let inputBox4 = view.modelBuilder.inputBox().component();
let form2Model = view.modelBuilder.formContainer()
.withFormItems([{
component: inputBox3,
title: 'inputBox3'
}, {
component: inputBox4,
title: 'inputBox4'
}], {
horizontal: true
}).component();
let groupModel1 = view.modelBuilder.groupContainer()
.withLayout({
}).withItems([
form2Model
]).component();
radioButton.onDidClick(() => {
inputBox.value = radioButton.value;
groupModel1.enabled = true;
});
radioButton2.onDidClick(() => {
inputBox.value = radioButton.value;
groupModel1.enabled = false;
});
let table = view.modelBuilder.table().withProperties({
data: [
['1', '2', '2'],
['4', '5', '6'],
['7', '8', '9']
], columns: ['c1', 'c2', 'c3'],
height: 250,
selectedRows: [0]
}).component();
table.onRowSelected(e => {
// TODO:
});
let listBox = view.modelBuilder.listBox().withProperties({
values: ['1', '2', '3'],
selectedRow: 2
}).component();
let declarativeTable = view.modelBuilder.declarativeTable()
.withProperties({
columns: [{
displayName: 'Column 1',
valueType: sqlops.DeclarativeDataType.string,
width: '20px',
isReadOnly: true
}, {
displayName: 'Column 2',
valueType: sqlops.DeclarativeDataType.string,
width: '100px',
isReadOnly: false
}, {
displayName: 'Column 3',
valueType: sqlops.DeclarativeDataType.boolean,
width: '20px',
isReadOnly: false
}, {
displayName: 'Column 4',
valueType: sqlops.DeclarativeDataType.category,
isReadOnly: false,
width: '120px',
categoryValues: [
{ name: 'options1', displayName: 'option 1' },
{ name: 'options2', displayName: 'option 2' }
]
}
],
data: [
['Data00', 'Data01', false, 'options2'],
['Data10', 'Data11', true, 'options1']
]
}).component();
declarativeTable.onDataChanged(e => {
inputBox2.value = e.row.toString() + ' ' + e.column.toString() + ' ' + e.value.toString();
inputBox3.value = declarativeTable.data[e.row][e.column];
});
let flexRadioButtonsModel = view.modelBuilder.flexContainer()
.withLayout({
flexFlow: 'column',
alignItems: 'left',
height: 150
}).withItems([
radioButton, groupModel1, radioButton2]
, { flex: '1 1 50%' }).component();
let formModel = view.modelBuilder.formContainer()
.withFormItems([{
component: inputBoxWrapper,
title: 'Backup name'
}, {
component: inputBox2,
title: 'Recovery model'
}, {
component: dropdown,
title: 'Backup type'
}, {
component: checkbox,
title: ''
}, {
component: backupFilesInputBox,
title: 'Backup files',
actions: [button, button3]
}, {
component: flexRadioButtonsModel,
title: 'Options'
}, {
component: declarativeTable,
title: 'Declarative Table'
}, {
component: table,
title: 'Table'
}, {
component: listBox,
title: 'List Box'
}], {
horizontal: false,
componentWidth: componentWidth
}).component();
let formWrapper = view.modelBuilder.loadingComponent().withItem(formModel).component();
formWrapper.loading = false;
customButton2.onClick(() => {
formWrapper.loading = true;
setTimeout(() => formWrapper.loading = false, 5000);
});
await view.initializeModel(formWrapper);
}
private openDialog(): void {
let dialog = sqlops.window.modelviewdialog.createDialog('Test dialog');
let tab1 = sqlops.window.modelviewdialog.createTab('Test tab 1');
@@ -80,207 +283,29 @@ export default class MainController implements vscode.Disposable {
customButton2.onClick(() => console.log('button 2 clicked!'));
dialog.customButtons = [customButton1, customButton2];
tab1.registerContent(async (view) => {
let inputBox = view.modelBuilder.inputBox()
.withProperties({
multiline: true,
height: 100
}).component();
let inputBoxWrapper = view.modelBuilder.loadingComponent().withItem(inputBox).component();
inputBoxWrapper.loading = false;
customButton1.onClick(() => {
inputBoxWrapper.loading = true;
setTimeout(() => inputBoxWrapper.loading = false, 5000);
});
let inputBox2 = view.modelBuilder.inputBox().component();
let backupFilesInputBox = view.modelBuilder.inputBox().component();
let checkbox = view.modelBuilder.checkBox()
.withProperties({
label: 'Copy-only backup'
})
.component();
checkbox.onChanged(e => {
console.info("inputBox.enabled " + inputBox.enabled);
inputBox.enabled = !inputBox.enabled;
});
let button = view.modelBuilder.button()
.withProperties({
label: '+'
}).component();
let button3 = view.modelBuilder.button()
.withProperties({
label: '-'
}).component();
let button2 = view.modelBuilder.button()
.component();
button.onDidClick(e => {
backupFilesInputBox.value = 'Button clicked';
});
let dropdown = view.modelBuilder.dropDown()
.withProperties({
value: 'Full',
values: ['Full', 'Differential', 'Transaction Log']
})
.component();
let f = 0;
inputBox.onTextChanged((params) => {
vscode.window.showInformationMessage(inputBox.value);
f = f + 1;
inputBox2.value = f.toString();
});
dropdown.onValueChanged((params) => {
vscode.window.showInformationMessage(inputBox2.value);
inputBox.value = dropdown.value;
});
let radioButton = view.modelBuilder.radioButton()
.withProperties({
value: 'option1',
name: 'radioButtonOptions',
label: 'Option 1',
checked: true
//width: 300
}).component();
let radioButton2 = view.modelBuilder.radioButton()
.withProperties({
value: 'option2',
name: 'radioButtonOptions',
label: 'Option 2'
}).component();
let inputBox3 = view.modelBuilder.inputBox().component();
let inputBox4 = view.modelBuilder.inputBox().component();
let form2Model = view.modelBuilder.formContainer()
.withFormItems([{
component: inputBox3,
title: 'inputBox3'
}, {
component: inputBox4,
title: 'inputBox4'
}], {
horizontal: true
}).component();
let groupModel1 = view.modelBuilder.groupContainer()
.withLayout({
}).withItems([
form2Model
]).component();
radioButton.onDidClick(() => {
inputBox.value = radioButton.value;
groupModel1.enabled = true;
});
radioButton2.onDidClick(() => {
inputBox.value = radioButton.value;
groupModel1.enabled = false;
});
let table = view.modelBuilder.table().withProperties({
data: [
['1', '2', '2'],
['4', '5', '6'],
['7', '8', '9']
], columns: ['c1', 'c2', 'c3'],
height: 250,
selectedRows: [0]
}).component();
table.onRowSelected(e => {
// TODO:
});
let listBox = view.modelBuilder.listBox().withProperties({
values: ['1', '2', '3'],
selectedRow: 2
}).component();
let declarativeTable = view.modelBuilder.declarativeTable()
.withProperties({
columns: [{
displayName: 'Column 1',
valueType: sqlops.DeclarativeDataType.string,
width: '20px',
isReadOnly: true
}, {
displayName: 'Column 2',
valueType: sqlops.DeclarativeDataType.string,
width: '100px',
isReadOnly: false
}, {
displayName: 'Column 3',
valueType: sqlops.DeclarativeDataType.boolean,
width: '20px',
isReadOnly: false
}, {
displayName: 'Column 4',
valueType: sqlops.DeclarativeDataType.category,
isReadOnly: false,
width: '120px',
categoryValues: [
{ name: 'options1', displayName: 'option 1' },
{ name: 'options2', displayName: 'option 2' }
]
}
],
data: [
['Data00', 'Data01', false, 'options2'],
['Data10', 'Data11', true, 'options1']
]
}).component();
declarativeTable.onDataChanged(e => {
inputBox2.value = e.row.toString() + ' ' + e.column.toString() + ' ' + e.value.toString();
inputBox3.value = declarativeTable.data[e.row][e.column];
});
let flexRadioButtonsModel = view.modelBuilder.flexContainer()
.withLayout({
flexFlow: 'column',
alignItems: 'left',
height: 150
}).withItems([
radioButton, groupModel1, radioButton2]
, { flex: '1 1 50%' }).component();
let formModel = view.modelBuilder.formContainer()
.withFormItems([{
component: inputBoxWrapper,
title: 'Backup name'
}, {
component: inputBox2,
title: 'Recovery model'
}, {
component: dropdown,
title: 'Backup type'
}, {
component: checkbox,
title: ''
}, {
component: backupFilesInputBox,
title: 'Backup files',
actions: [button, button3]
}, {
component: flexRadioButtonsModel,
title: 'Options'
}, {
component: declarativeTable,
title: 'Declarative Table'
}, {
component: table,
title: 'Table'
}, {
component: listBox,
title: 'List Box'
}], {
horizontal: false,
componentWidth: 400
}).component();
let formWrapper = view.modelBuilder.loadingComponent().withItem(formModel).component();
formWrapper.loading = false;
customButton2.onClick(() => {
formWrapper.loading = true;
setTimeout(() => formWrapper.loading = false, 5000);
});
await view.initializeModel(formWrapper);
await this.getTabContent(view, customButton1, customButton2, 400);
});
sqlops.window.modelviewdialog.openDialog(dialog);
}
private openWizard(): void {
let wizard = sqlops.window.modelviewdialog.createWizard('Test wizard');
let page1 = sqlops.window.modelviewdialog.createWizardPage('First wizard page');
let page2 = sqlops.window.modelviewdialog.createWizardPage('Second wizard page');
page2.content = 'sqlservices';
let customButton1 = sqlops.window.modelviewdialog.createButton('Load name');
customButton1.onClick(() => console.log('button 1 clicked!'));
let customButton2 = sqlops.window.modelviewdialog.createButton('Load all');
customButton2.onClick(() => console.log('button 2 clicked!'));
wizard.customButtons = [customButton1, customButton2];
page1.registerContent(async (view) => {
await this.getTabContent(view, customButton1, customButton2, 800);
});
wizard.pages = [page1, page2];
wizard.open();
}
private openEditor(): void {
let editor = sqlops.workspace.createModelViewEditor('Test Model View');
editor.registerContent(async view => {

View File

@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
import { InputBox as vsInputBox, IInputOptions, IInputBoxStyles as vsIInputBoxStyles } from 'vs/base/browser/ui/inputbox/inputBox';
import { InputBox as vsInputBox, IInputOptions, IInputBoxStyles as vsIInputBoxStyles, IMessage } from 'vs/base/browser/ui/inputbox/inputBox';
import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview';
import { Color } from 'vs/base/common/color';
import { Event, Emitter } from 'vs/base/common/event';
@@ -33,6 +33,7 @@ export class InputBox extends vsInputBox {
public onLoseFocus: Event<OnLoseFocusParams> = this._onLoseFocus.event;
private _isTextAreaInput: boolean;
private _hideErrors = false;
constructor(container: HTMLElement, contextViewProvider: IContextViewProvider, options?: IInputOptions) {
super(container, contextViewProvider, options);
@@ -103,4 +104,21 @@ export class InputBox extends vsInputBox {
public isEnabled(): boolean {
return !this.inputElement.hasAttribute('disabled');
}
public get hideErrors(): boolean {
return this._hideErrors;
}
public set hideErrors(hideErrors: boolean) {
this._hideErrors = hideErrors;
if (hideErrors) {
this.hideMessage();
}
}
public showMessage(message: IMessage, force?: boolean): void {
if (!this.hideErrors) {
super.showMessage(message, force);
}
}
}

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

@@ -38,13 +38,13 @@ export function convertEditorInput(input: EditorInput, options: IQueryEditorOpti
let uri: URI = getQueryEditorFileUri(input);
if (uri) {
const queryResultsInput: QueryResultsInput = instantiationService.createInstance(QueryResultsInput, uri.toString());
let queryInput: QueryInput = instantiationService.createInstance(QueryInput, input.getName(), '', input, queryResultsInput, undefined);
let queryInput: QueryInput = instantiationService.createInstance(QueryInput, '', input, queryResultsInput, undefined);
return queryInput;
}
//QueryPlanInput
uri = getQueryPlanEditorUri(input);
if(uri) {
if (uri) {
let queryPlanXml: string = fs.readFileSync(uri.fsPath);
let queryPlanInput: QueryPlanInput = instantiationService.createInstance(QueryPlanInput, queryPlanXml, 'aaa', undefined);
return queryPlanInput;
@@ -60,14 +60,14 @@ export function convertEditorInput(input: EditorInput, options: IQueryEditorOpti
*/
export function getSupportedInputResource(input: IEditorInput): URI {
if (input instanceof UntitledEditorInput) {
let untitledCast: UntitledEditorInput = <UntitledEditorInput> input;
let untitledCast: UntitledEditorInput = <UntitledEditorInput>input;
if (untitledCast) {
return untitledCast.getResource();
}
}
if (input instanceof FileEditorInput) {
let fileCast: FileEditorInput = <FileEditorInput> input;
let fileCast: FileEditorInput = <FileEditorInput>input;
if (fileCast) {
return fileCast.getResource();
}
@@ -99,7 +99,7 @@ function getQueryEditorFileUri(input: EditorInput): URI {
if (uri) {
let isValidUri: boolean = !!uri && !!uri.toString;
if (isValidUri && (hasFileExtension(sqlFileTypes, input, true) || hasSqlFileMode(input)) ) {
if (isValidUri && (hasFileExtension(sqlFileTypes, input, true) || hasSqlFileMode(input))) {
return uri;
}
}
@@ -120,7 +120,7 @@ function getQueryPlanEditorUri(input: EditorInput): URI {
// If this editor is not already of type queryinput
if (!(input instanceof QueryPlanInput)) {
let uri: URI = getSupportedInputResource(input);
if(uri) {
if (uri) {
if (hasFileExtension(sqlPlanFileTypes, input, false)) {
return uri;
}
@@ -136,7 +136,7 @@ function getQueryPlanEditorUri(input: EditorInput): URI {
*/
function hasSqlFileMode(input: EditorInput): boolean {
if (input instanceof UntitledEditorInput) {
let untitledCast: UntitledEditorInput = <UntitledEditorInput> input;
let untitledCast: UntitledEditorInput = <UntitledEditorInput>input;
return untitledCast && (untitledCast.getModeId() === undefined || untitledCast.getModeId() === sqlModeId);
}

View File

@@ -21,6 +21,8 @@ export const capabilitiesOptions = 'OPTIONS_METADATA';
export const configMaxRecentConnections = 'maxRecentConnections';
export const mssqlProviderName = 'MSSQL';
export const anyProviderName = '*';
export const connectionProviderContextKey = 'connectionProvider';
export const applicationName = 'sqlops';

View File

@@ -19,9 +19,9 @@ import { WEBVIEW_CONTAINER } from 'sql/parts/dashboard/containers/dashboardWebvi
import { MODELVIEW_CONTAINER } from 'sql/parts/dashboard/containers/dashboardModelViewContainer.contribution';
import { CONTROLHOST_CONTAINER } from 'sql/parts/dashboard/containers/dashboardControlHostContainer.contribution';
import { NAV_SECTION } from 'sql/parts/dashboard/containers/dashboardNavSection.contribution';
import { IDashboardContainerRegistry, Extensions as DashboardContainerExtensions, IDashboardContainer, registerContainerType } from 'sql/platform/dashboard/common/dashboardContainerRegistry';
import { IDashboardTab } from 'sql/platform/dashboard/common/dashboardRegistry';
import { IDashboardContainerRegistry, Extensions as DashboardContainerExtensions } from 'sql/platform/dashboard/common/dashboardContainerRegistry';
import { SingleConnectionManagementService } from 'sql/services/common/commonServiceInterface.service';
import * as Constants from 'sql/parts/connection/common/constants';
const dashboardcontainerRegistry = Registry.as<IDashboardContainerRegistry>(DashboardContainerExtensions.dashboardContainerContributions);
const containerTypes = [
@@ -136,13 +136,17 @@ export function addProvider<T extends { connectionManagementService: SingleConne
*/
export function addEdition<T extends { connectionManagementService: SingleConnectionManagementService }>(config: WidgetConfig[], collection: DashboardServiceInterface): Array<WidgetConfig> {
let connectionInfo: ConnectionManagementInfo = collection.connectionManagementService.connectionInfo;
let edition = connectionInfo.serverInfo.engineEditionId;
return config.map((item) => {
if (item.edition === undefined) {
item.edition = edition;
}
return item;
});
if (connectionInfo.serverInfo) {
let edition = connectionInfo.serverInfo.engineEditionId;
return config.map((item) => {
if (item.edition === undefined) {
item.edition = edition;
}
return item;
});
} else {
return config;
}
}
/**
@@ -162,9 +166,11 @@ export function addContext(config: WidgetConfig[], collection: any, context: str
* Returns a filtered version of the widgets passed based on edition and provider
* @param config widgets to filter
*/
export function filterConfigs<T extends { when?: string }, K extends { contextKeyService: IContextKeyService }>(config: T[], collection: K): Array<T> {
export function filterConfigs<T extends { provider?: string | string[], when?: string }, K extends { contextKeyService: IContextKeyService }>(config: T[], collection: K): Array<T> {
return config.filter((item) => {
if (!item.when) {
if (!hasCompatibleProvider(item.provider, collection.contextKeyService)) {
return false;
} else if (!item.when) {
return true;
} else {
return collection.contextKeyService.contextMatchesRules(ContextKeyExpr.deserialize(item.when));
@@ -172,6 +178,21 @@ export function filterConfigs<T extends { when?: string }, K extends { contextKe
});
}
/**
* Check whether the listed providers contain '*' indicating any provider will do, or that they are a match
* for the currently scoped 'connectionProvider' context key.
*/
function hasCompatibleProvider(provider: string | string[], contextKeyService: IContextKeyService): boolean {
let isCompatible = true;
let connectionProvider = contextKeyService.getContextKeyValue<string>(Constants.connectionProviderContextKey);
if (connectionProvider) {
let providers = (provider instanceof Array) ? provider : [provider];
let matchingProvider = providers.find((p) => p === connectionProvider || p === Constants.anyProviderName);
isCompatible = (matchingProvider !== undefined);
} // Else there's no connection context so skip the check
return isCompatible;
}
/**
* Get registered container if it is specified as the key
* @param container dashboard container

View File

@@ -17,11 +17,12 @@ import { IDashboardRegistry, Extensions as DashboardExtensions, IDashboardTab }
import { PinUnpinTabAction, AddFeatureTabAction } from './actions';
import { TabComponent, TabChild } from 'sql/base/browser/ui/panel/tab.component';
import { AngularEventType, IAngularEventingService } from 'sql/services/angularEventing/angularEventingService';
import { DashboardTab } from 'sql/parts/dashboard/common/interfaces';
import { DashboardTab, IConfigModifierCollection } from 'sql/parts/dashboard/common/interfaces';
import * as dashboardHelper from 'sql/parts/dashboard/common/dashboardHelper';
import { WIDGETS_CONTAINER } from 'sql/parts/dashboard/containers/dashboardWidgetContainer.contribution';
import { GRID_CONTAINER } from 'sql/parts/dashboard/containers/dashboardGridContainer.contribution';
import { AngularDisposable } from 'sql/base/common/lifecycle';
import * as Constants from 'sql/parts/connection/common/constants';
import { Registry } from 'vs/platform/registry/common/platform';
import * as types from 'vs/base/common/types';
@@ -38,16 +39,11 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
const dashboardRegistry = Registry.as<IDashboardRegistry>(DashboardExtensions.DashboardContributions);
interface IConfigModifierCollection {
connectionManagementService: SingleConnectionManagementService;
contextKeyService: IContextKeyService;
}
@Component({
selector: 'dashboard-page',
templateUrl: decodeURI(require.toUrl('sql/parts/dashboard/common/dashboardPage.component.html'))
})
export abstract class DashboardPage extends AngularDisposable {
export abstract class DashboardPage extends AngularDisposable implements IConfigModifierCollection {
protected tabs: Array<TabConfig> = [];
@@ -137,22 +133,11 @@ export abstract class DashboardPage extends AngularDisposable {
this._tabsDispose.forEach(i => i.dispose());
this._tabsDispose = [];
// Create home tab
let homeTab: TabConfig = {
id: 'homeTab',
publisher: undefined,
title: this.homeTabTitle,
container: { 'widgets-container': homeWidgets },
context: this.context,
originalConfig: this._originalConfig,
editable: true,
canClose: false,
actions: []
};
this.addNewTab(homeTab);
let allTabs = dashboardHelper.filterConfigs(dashboardRegistry.tabs, this);
// Before separating tabs into pinned / shown, ensure that the home tab is always set up as expected
allTabs = this.setAndRemoveHomeTab(allTabs, homeWidgets);
// Load tab setting configs
this._tabSettingConfigs = this.dashboardService.getSettings<Array<TabSettingConfig>>([this.context, 'tabs'].join('.'));
@@ -199,6 +184,32 @@ export abstract class DashboardPage extends AngularDisposable {
}));
}
private setAndRemoveHomeTab(allTabs: IDashboardTab[], homeWidgets: WidgetConfig[]): IDashboardTab[] {
let homeTabConfig: TabConfig = {
id: 'homeTab',
provider: Constants.anyProviderName,
publisher: undefined,
title: this.homeTabTitle,
container: { 'widgets-container': homeWidgets },
context: this.context,
originalConfig: this._originalConfig,
editable: true,
canClose: false,
actions: []
};
let homeTabIndex = allTabs.findIndex((tab) => tab.isHomeTab === true);
if (homeTabIndex !== undefined && homeTabIndex > -1) {
// Have a tab: get its information and copy over to the home tab definition
let homeTab = allTabs.splice(homeTabIndex, 1)[0];
let tabConfig = this.initTabComponents(homeTab);
homeTabConfig.id = tabConfig.id;
homeTabConfig.container = tabConfig.container;
}
this.addNewTab(homeTabConfig);
return allTabs;
}
private rewriteConfig(): void {
let writeableConfig = objects.deepClone(this._tabSettingConfigs);
@@ -208,35 +219,12 @@ export abstract class DashboardPage extends AngularDisposable {
private loadNewTabs(dashboardTabs: IDashboardTab[], openLastTab: boolean = false) {
if (dashboardTabs && dashboardTabs.length > 0) {
let selectedTabs = dashboardTabs.map(v => {
let containerResult = dashboardHelper.getDashboardContainer(v.container);
if (!containerResult.result) {
return { id: v.id, title: v.title, container: { 'error-container': undefined }, alwaysShow: v.alwaysShow };
}
let key = Object.keys(containerResult.container)[0];
if (key === WIDGETS_CONTAINER || key === GRID_CONTAINER) {
let configs = <WidgetConfig[]>Object.values(containerResult.container)[0];
this._configModifiers.forEach(cb => {
configs = cb.apply(this, [configs, this.dashboardService, this.context]);
});
this._gridModifiers.forEach(cb => {
configs = cb.apply(this, [configs]);
});
if (key === WIDGETS_CONTAINER) {
return { id: v.id, title: v.title, container: { 'widgets-container': configs }, alwaysShow: v.alwaysShow };
} else {
return { id: v.id, title: v.title, container: { 'grid-container': configs }, alwaysShow: v.alwaysShow };
}
}
return { id: v.id, title: v.title, container: containerResult.container, alwaysShow: v.alwaysShow };
}).map(v => {
let selectedTabs = dashboardTabs.map(v => this.initTabComponents(v)).map(v => {
let actions = [];
let tabConfig = this._tabSettingConfigs.find(i => i.tabId === v.id);
let tabSettingConfig = this._tabSettingConfigs.find(i => i.tabId === v.id);
let isPinned = false;
if (tabConfig) {
isPinned = tabConfig.isPinned;
if (tabSettingConfig) {
isPinned = tabSettingConfig.isPinned;
} else if (v.alwaysShow) {
isPinned = true;
}
@@ -261,6 +249,30 @@ export abstract class DashboardPage extends AngularDisposable {
}
}
private initTabComponents(value: IDashboardTab): { id: string; title: string; container: object; alwaysShow: boolean; } {
let containerResult = dashboardHelper.getDashboardContainer(value.container);
if (!containerResult.result) {
return { id: value.id, title: value.title, container: { 'error-container': undefined }, alwaysShow: value.alwaysShow };
}
let key = Object.keys(containerResult.container)[0];
if (key === WIDGETS_CONTAINER || key === GRID_CONTAINER) {
let configs = <WidgetConfig[]>Object.values(containerResult.container)[0];
this._configModifiers.forEach(cb => {
configs = cb.apply(this, [configs, this, this.context]);
});
this._gridModifiers.forEach(cb => {
configs = cb.apply(this, [configs]);
});
if (key === WIDGETS_CONTAINER) {
return { id: value.id, title: value.title, container: { 'widgets-container': configs }, alwaysShow: value.alwaysShow };
}
else {
return { id: value.id, title: value.title, container: { 'grid-container': configs }, alwaysShow: value.alwaysShow };
}
}
return { id: value.id, title: value.title, container: containerResult.container, alwaysShow: value.alwaysShow };
}
private getContentType(tab: TabConfig): string {
return tab.container ? Object.keys(tab.container)[0] : '';
}

View File

@@ -5,8 +5,11 @@
import 'vs/css!./dashboardPanel';
import { registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService';
import { TAB_ACTIVE_BACKGROUND, TAB_ACTIVE_FOREGROUND, TAB_ACTIVE_BORDER, TAB_INACTIVE_BACKGROUND, TAB_INACTIVE_FOREGROUND, EDITOR_GROUP_HEADER_TABS_BACKGROUND, TAB_BORDER } from 'vs/workbench/common/theme';
import { activeContrastBorder, focusBorder } from 'vs/platform/theme/common/colorRegistry';
import {
TAB_ACTIVE_BACKGROUND, TAB_ACTIVE_FOREGROUND, TAB_ACTIVE_BORDER, TAB_INACTIVE_BACKGROUND,
TAB_INACTIVE_FOREGROUND, EDITOR_GROUP_HEADER_TABS_BACKGROUND, TAB_BORDER, EDITOR_GROUP_BORDER
} from 'vs/workbench/common/theme';
import { activeContrastBorder } from 'vs/platform/theme/common/colorRegistry';
registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
@@ -98,4 +101,14 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
}
`);
}
const divider = theme.getColor(EDITOR_GROUP_BORDER);
if (divider) {
collector.addRule(`
panel.dashboard-panel > .tabbedPanel > .title .tabList .tab-header {
border-right-width: 1px;
border-right-style: solid;
}
`);
}
});

View File

@@ -7,6 +7,7 @@ import { IJSONSchema } from 'vs/base/common/jsonSchema';
import { localize } from 'vs/nls';
import * as types from 'vs/base/common/types';
import * as Constants from 'sql/parts/connection/common/constants';
import { registerTab } from 'sql/platform/dashboard/common/dashboardRegistry';
import { generateContainerTypeSchemaProperties } from 'sql/platform/dashboard/common/dashboardContainerRegistry';
import { NAV_SECTION, validateNavSectionContributionAndRegisterIcon } from 'sql/parts/dashboard/containers/dashboardNavSection.contribution';
@@ -17,9 +18,11 @@ export interface IDashboardTabContrib {
id: string;
title: string;
container: object;
provider: string | string[];
when?: string;
description?: string;
alwaysShow?: boolean;
isHomeTab?: boolean;
}
const tabSchema: IJSONSchema = {
@@ -41,6 +44,11 @@ const tabSchema: IJSONSchema = {
description: localize('sqlops.extension.contributes.tab.when', 'Condition which must be true to show this item'),
type: 'string'
},
provider: {
description: localize('sqlops.extension.contributes.tab.provider', 'Defines the connection types this tab is compatible with. Defaults to "MSSQL" if not set'),
type: ['string', 'array']
},
container: {
description: localize('sqlops.extension.contributes.dashboard.tab.container', "The container that will be displayed in this tab."),
type: 'object',
@@ -49,6 +57,10 @@ const tabSchema: IJSONSchema = {
alwaysShow: {
description: localize('sqlops.extension.contributes.dashboard.tab.alwaysShow', "Whether or not this tab should always be shown or only when the user adds it."),
type: 'boolean'
},
isHomeTab: {
description: localize('sqlops.extension.contributes.dashboard.tab.isHomeTab', "Whether or not this tab should be used as the Home tab for a connection type."),
type: 'boolean'
}
}
};
@@ -67,7 +79,7 @@ const tabContributionSchema: IJSONSchema = {
ExtensionsRegistry.registerExtensionPoint<IDashboardTabContrib | IDashboardTabContrib[]>('dashboard.tabs', [], tabContributionSchema).setHandler(extensions => {
function handleCommand(tab: IDashboardTabContrib, extension: IExtensionPointUser<any>) {
let { description, container, title, when, id, alwaysShow } = tab;
let { description, container, provider, title, when, id, alwaysShow, isHomeTab } = tab;
// If always show is not specified, set it to true by default.
if (!types.isBoolean(alwaysShow)) {
@@ -88,6 +100,13 @@ ExtensionsRegistry.registerExtensionPoint<IDashboardTabContrib | IDashboardTabCo
return;
}
if (!provider) {
// Use a default. Consider warning extension developers about this in the future if in development mode
provider = Constants.mssqlProviderName;
// Cannot be a home tab if it did not specify a provider
isHomeTab = false;
}
if (Object.keys(container).length !== 1) {
extension.collector.error(localize('dashboardTab.contribution.moreThanOneDashboardContainersError', 'Exactly 1 dashboard container must be defined per space'));
return;
@@ -110,7 +129,7 @@ ExtensionsRegistry.registerExtensionPoint<IDashboardTabContrib | IDashboardTabCo
}
if (result) {
registerTab({ description, title, container, when, id, alwaysShow, publisher });
registerTab({ description, title, container, provider, when, id, alwaysShow, publisher, isHomeTab });
}
}

View File

@@ -7,9 +7,11 @@ import { OnDestroy } from '@angular/core';
import { Event } from 'vs/base/common/event';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { AngularDisposable } from 'sql/base/common/lifecycle';
import { TabChild } from 'sql/base/browser/ui/panel/tab.component';
import { SingleConnectionManagementService } from 'sql/services/common/commonServiceInterface.service';
export enum Conditional {
'equals',
@@ -50,3 +52,8 @@ export abstract class DashboardTab extends TabChild implements OnDestroy {
this.dispose();
}
}
export interface IConfigModifierCollection {
connectionManagementService: SingleConnectionManagementService;
contextKeyService: IContextKeyService;
}

View File

@@ -5,28 +5,27 @@
import 'vs/css!./dashboardNavSection';
import { Component, Inject, Input, forwardRef, ViewChild, ElementRef, ViewChildren, QueryList, OnDestroy, ChangeDetectorRef, EventEmitter, OnChanges, AfterContentInit } from '@angular/core';
import { Component, Inject, Input, forwardRef, ViewChild, ElementRef, ViewChildren, QueryList, OnDestroy, ChangeDetectorRef, OnChanges, AfterContentInit } from '@angular/core';
import { DashboardServiceInterface } from 'sql/parts/dashboard/services/dashboardServiceInterface.service';
import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service';
import { CommonServiceInterface, SingleConnectionManagementService } from 'sql/services/common/commonServiceInterface.service';
import { WidgetConfig, TabConfig, NavSectionConfig } from 'sql/parts/dashboard/common/dashboardWidget';
import { PanelComponent, IPanelOptions, NavigationBarLayout } from 'sql/base/browser/ui/panel/panel.component';
import { TabComponent, TabChild } from 'sql/base/browser/ui/panel/tab.component';
import { DashboardTab } from 'sql/parts/dashboard/common/interfaces';
import { TabChild } from 'sql/base/browser/ui/panel/tab.component';
import { DashboardTab, IConfigModifierCollection } from 'sql/parts/dashboard/common/interfaces';
import { WIDGETS_CONTAINER } from 'sql/parts/dashboard/containers/dashboardWidgetContainer.contribution';
import { GRID_CONTAINER } from 'sql/parts/dashboard/containers/dashboardGridContainer.contribution';
import * as dashboardHelper from 'sql/parts/dashboard/common/dashboardHelper';
import { Registry } from 'vs/platform/registry/common/platform';
import { Event, Emitter } from 'vs/base/common/event';
import * as nls from 'vs/nls';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
@Component({
selector: 'dashboard-nav-section',
providers: [{ provide: TabChild, useExisting: forwardRef(() => DashboardNavSection) }],
templateUrl: decodeURI(require.toUrl('sql/parts/dashboard/containers/dashboardNavSection.component.html'))
})
export class DashboardNavSection extends DashboardTab implements OnDestroy, OnChanges, AfterContentInit {
export class DashboardNavSection extends DashboardTab implements OnDestroy, OnChanges, AfterContentInit, IConfigModifierCollection {
@Input() private tab: TabConfig;
protected tabs: Array<TabConfig> = [];
private _onResize = new Emitter<void>();
@@ -38,7 +37,7 @@ export class DashboardNavSection extends DashboardTab implements OnDestroy, OnCh
};
// a set of config modifiers
private readonly _configModifiers: Array<(item: Array<WidgetConfig>, dashboardServer: DashboardServiceInterface, context: string) => Array<WidgetConfig>> = [
private readonly _configModifiers: Array<(item: Array<WidgetConfig>, collection: IConfigModifierCollection, context: string) => Array<WidgetConfig>> = [
dashboardHelper.removeEmpty,
dashboardHelper.initExtensionConfigs,
dashboardHelper.addProvider,
@@ -103,7 +102,7 @@ export class DashboardNavSection extends DashboardTab implements OnDestroy, OnCh
if (key === WIDGETS_CONTAINER || key === GRID_CONTAINER) {
let configs = <WidgetConfig[]>Object.values(containerResult.container)[0];
this._configModifiers.forEach(cb => {
configs = cb.apply(this, [configs, this.dashboardService, this.tab.context]);
configs = cb.apply(this, [configs, this, this.tab.context]);
});
this._gridModifiers.forEach(cb => {
configs = cb.apply(this, [configs]);
@@ -169,4 +168,12 @@ export class DashboardNavSection extends DashboardTab implements OnDestroy, OnCh
});
}
}
public get connectionManagementService(): SingleConnectionManagementService {
return this.dashboardService.connectionManagementService;
}
public get contextKeyService(): IContextKeyService {
return this.dashboardService.scopedContextKeyService;
}
}

View File

@@ -51,7 +51,10 @@ import { ControlHostContent } from 'sql/parts/dashboard/contents/controlHostCont
import { DashboardControlHostContainer } from 'sql/parts/dashboard/containers/dashboardControlHostContainer.component';
import { JobsViewComponent } from 'sql/parts/jobManagement/views/jobsView.component';
import { AgentViewComponent } from 'sql/parts/jobManagement/agent/agentView.component';
import { AlertsViewComponent } from 'sql/parts/jobManagement/views/alertsView.component';
import { JobHistoryComponent } from 'sql/parts/jobManagement/views/jobHistory.component';
import { OperatorsViewComponent } from 'sql/parts/jobManagement/views/operatorsView.component';
import { ProxiesViewComponent } from 'sql/parts/jobManagement/views/proxiesView.component';
import { Checkbox } from 'sql/base/browser/ui/checkbox/checkbox.component';
import { SelectBox } from 'sql/base/browser/ui/selectBox/selectBox.component';
import { InputBox } from 'sql/base/browser/ui/inputBox/inputBox.component';
@@ -59,9 +62,8 @@ import { InputBox } from 'sql/base/browser/ui/inputBox/inputBox.component';
let baseComponents = [DashboardHomeContainer, DashboardComponent, DashboardWidgetWrapper, DashboardWebviewContainer,
DashboardWidgetContainer, DashboardGridContainer, DashboardErrorContainer, DashboardNavSection, ModelViewContent, WebviewContent, WidgetContent,
ComponentHostDirective, BreadcrumbComponent, ControlHostContent, DashboardControlHostContainer,
JobsViewComponent, AgentViewComponent, JobHistoryComponent, JobStepsViewComponent, DashboardModelViewContainer, ModelComponentWrapper, Checkbox,
SelectBox,
InputBox,];
JobsViewComponent, AgentViewComponent, JobHistoryComponent, JobStepsViewComponent, AlertsViewComponent, ProxiesViewComponent, OperatorsViewComponent,
DashboardModelViewContainer, ModelComponentWrapper, Checkbox, SelectBox, InputBox,];
/* Panel */
import { PanelModule } from 'sql/base/browser/ui/panel/panel.module';

View File

@@ -96,16 +96,12 @@ let defaultVal = [
},
{
widget: {
'backup-history-server-insight': {
cacheId: '0c7cba8b-c87a-4bcc-ae54-2f40a5503a90'
}
'backup-history-server-insight': null
}
},
{
widget: {
'all-database-size-server-insight': {
cacheId: '1d7cba8b-c87a-4bcc-ae54-2f40a5503a90'
}
'all-database-size-server-insight': null
}
}
];
@@ -121,8 +117,7 @@ export const serverDashboardTabsSchema: IJSONSchema = {
type: ['array'],
description: nls.localize('dashboardServerTabs', 'Customizes the Server dashboard tabs'),
items: generateDashboardTabSchema('server'),
default: [
]
default: []
};
export const SERVER_DASHBOARD_SETTING = 'dashboard.server.widgets';

View File

@@ -28,6 +28,7 @@ import { IContextViewService, IContextMenuService } from 'vs/platform/contextvie
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IProgressService } from 'vs/platform/progress/common/progress';
import * as types from 'vs/base/common/types';
@Component({
selector: 'explorer-widget',
@@ -120,6 +121,8 @@ export class ExplorerWidget extends DashboardWidget implements IDashboardWidget,
let currentProfile = this._bootstrap.connectionManagementService.connectionInfo.connectionProfile;
this._register(toDisposableSubscription(this._bootstrap.metadataService.databaseNames.subscribe(
data => {
// Handle the case where there is no metadata service
data = data || [];
let profileData = data.map(d => {
let profile = new ConnectionProfile(this.capabilitiesService, currentProfile);
profile.databaseName = d;

View File

@@ -166,7 +166,6 @@ export class TasksWidget extends DashboardWidget implements IDashboardWidget, On
}
public runTask(task: ICommandAction) {
let serverInfo = this._bootstrap.connectionManagementService.connectionInfo.serverInfo;
this.commandService.executeCommand(task.id, this._profile);
}

View File

@@ -4,17 +4,42 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
-->
<panel class="dashboard-panel" [options]="panelOpt">
<tab [title]="jobsComponentTitle" class="fullsize" [identifier]="jobsTabIdentifier"
[iconClass]="jobsIconClass">
<ng-template>
<div id="jobsDiv" class="fullsize" *ngIf="showHistory === false">
<jobsview-component></jobsview-component>
</div>
<div id="historyDiv" class="fullsize" *ngIf="showHistory === true">
<jobhistory-component></jobhistory-component>
</div>
</ng-template>
</tab>
</panel>
<div id="agentViewDiv" class="fullsize">
<panel class="dashboard-panel" [options]="panelOpt">
<tab [title]="jobsComponentTitle" class="fullsize" [identifier]="jobsTabIdentifier"
[iconClass]="jobsIconClass">
<ng-template>
<div id="jobsDiv" class="fullsize" *ngIf="showHistory === false">
<jobsview-component></jobsview-component>
</div>
<div id="historyDiv" class="fullsize" *ngIf="showHistory === true">
<jobhistory-component></jobhistory-component>
</div>
</ng-template>
</tab>
<tab [title]="alertsComponentTitle" class="fullsize" [identifier]="alertsTabIdentifier"
[iconClass]="alertsIconClass">
<ng-template>
<div id="alertsDiv" class="fullsize">
<jobalertsview-component></jobalertsview-component>
</div>
</ng-template>
</tab>
<tab [title]="operatorsComponentTitle" class="fullsize" [identifier]="operatorsTabIdentifier"
[iconClass]="operatorsIconClass">
<ng-template>
<div id="operatorsDiv" class="fullsize">
<joboperatorsview-component></joboperatorsview-component>
</div>
</ng-template>
</tab>
<tab [title]="proxiesComponentTitle" class="fullsize" [identifier]="proxiesTabIdentifier"
[iconClass]="proxiesIconClass">
<ng-template>
<div id="proxiesDiv" class="fullsize">
<jobproxiesview-component></jobproxiesview-component>
</div>
</ng-template>
</tab>
</panel>
</div>

View File

@@ -33,6 +33,10 @@ export class AgentViewComponent {
// tslint:disable:no-unused-variable
private readonly jobsComponentTitle: string = nls.localize('jobview.Jobs', "Jobs");
private readonly alertsComponentTitle: string = nls.localize('jobview.Alerts', "Alerts");
private readonly proxiesComponentTitle: string = nls.localize('jobview.Proxies', "Proxies");
private readonly operatorsComponentTitle: string = nls.localize('jobview.Operators', "Operators");
private _showHistory: boolean = false;
private _jobId: string = null;
private _agentJobInfo: AgentJobInfo = null;
@@ -40,6 +44,9 @@ export class AgentViewComponent {
private _expanded: Map<string, string>;
public jobsIconClass: string = 'jobsview-icon';
public alertsIconClass: string = 'alertsview-icon';
public proxiesIconClass: string = 'proxiesview-icon';
public operatorsIconClass: string = 'operatorsview-icon';
// tslint:disable-next-line:no-unused-variable
private readonly panelOpt: IPanelOptions = {

View File

@@ -7,7 +7,6 @@
import * as sqlops from 'sqlops';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { Table } from 'sql/base/browser/ui/table/table';
import { JobCacheObject } from './jobManagementService';
export const SERVICE_ID = 'jobManagementService';
@@ -21,6 +20,12 @@ export interface IJobManagementService {
getJobs(connectionUri: string): Thenable<sqlops.AgentJobsResult>;
getAlerts(connectionUri: string): Thenable<sqlops.AgentAlertsResult>;
getOperators(connectionUri: string): Thenable<sqlops.AgentOperatorsResult>;
getProxies(connectionUri: string): Thenable<sqlops.AgentProxiesResult>;
getJobHistory(connectionUri: string, jobID: string): Thenable<sqlops.AgentJobHistoryResult>;
jobAction(connectionUri: string, jobName: string, action: string): Thenable<sqlops.ResultStatus>;

View File

@@ -7,11 +7,8 @@
import { localize } from 'vs/nls';
import * as sqlops from 'sqlops';
import * as vscode from 'vscode';
import { TPromise } from 'vs/base/common/winjs.base';
import { Injectable } from '@angular/core';
import { IJobManagementService } from 'sql/parts/jobManagement/common/interfaces';
import { ICapabilitiesService } from 'sql/services/capabilities/capabilitiesService';
import { IConnectionManagementService } from 'sql/parts/connection/common/connectionManagement';
@@ -22,8 +19,7 @@ export class JobManagementService implements IJobManagementService {
private _jobCacheObject : {[server: string]: JobCacheObject; } = {};
constructor(
@IConnectionManagementService private _connectionService: IConnectionManagementService,
@ICapabilitiesService private _capabilitiesService: ICapabilitiesService
@IConnectionManagementService private _connectionService: IConnectionManagementService
) {
}
@@ -33,6 +29,25 @@ export class JobManagementService implements IJobManagementService {
});
}
public getAlerts(connectionUri: string): Thenable<sqlops.AgentAlertsResult> {
return this._runAction(connectionUri, (runner) => {
return runner.getAlerts(connectionUri);
});
}
public getOperators(connectionUri: string): Thenable<sqlops.AgentOperatorsResult> {
return this._runAction(connectionUri, (runner) => {
return runner.getOperators(connectionUri);
});
}
public getProxies(connectionUri: string): Thenable<sqlops.AgentProxiesResult> {
return this._runAction(connectionUri, (runner) => {
return runner.getProxies(connectionUri);
});
}
public getJobHistory(connectionUri: string, jobID: string): Thenable<sqlops.AgentJobHistoryResult> {
return this._runAction(connectionUri, (runner) => {
return runner.getJobHistory(connectionUri, jobID);
@@ -79,7 +94,8 @@ export class JobManagementService implements IJobManagementService {
export class JobCacheObject {
_serviceBrand: any;
private _jobs: sqlops.AgentJobInfo[] = [];
private _jobHistories: { [jobId: string]: sqlops.AgentJobHistoryInfo[]; } = {};
private _jobHistories: { [jobID: string]: sqlops.AgentJobHistoryInfo[]; } = {};
private _runCharts: { [jobID: string]: string[]; } = {};
private _prevJobID: string;
private _serverName: string;
private _dataView: Slick.Data.DataView<any>;
@@ -89,7 +105,7 @@ export class JobCacheObject {
return this._jobs;
}
public get jobHistories(): { [jobId: string]: sqlops.AgentJobHistoryInfo[] } {
public get jobHistories(): { [jobID: string]: sqlops.AgentJobHistoryInfo[] } {
return this._jobHistories;
}
@@ -109,12 +125,16 @@ export class JobCacheObject {
return this._dataView;
}
public getRunChart(jobID: string): string[] {
return this._runCharts[jobID];
}
/* Setters */
public set jobs(value: sqlops.AgentJobInfo[]) {
this._jobs = value;
}
public set jobHistories(value: { [jobId: string]: sqlops.AgentJobHistoryInfo[]; }) {
public set jobHistories(value: { [jobID: string]: sqlops.AgentJobHistoryInfo[]; }) {
this._jobHistories = value;
}
@@ -126,6 +146,10 @@ export class JobCacheObject {
this._jobHistories[jobID] = value;
}
public setRunChart(jobID: string, value: string[]) {
this._runCharts[jobID] = value;
}
public set serverName(value: string) {
this._serverName = value;
}

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:#212121;}</style></defs><title>blocker</title><polygon class="cls-1" points="0.99 3.99 -0.01 3.99 -0.01 0.03 3.98 0.03 3.98 1.03 0.99 1.03 0.99 3.99"/><polygon class="cls-1" points="16.01 3.99 15.01 3.99 15.01 1.03 12.02 1.03 12.02 0.03 16.01 0.03 16.01 3.99"/><polygon class="cls-1" points="16.01 15.97 12.02 15.97 12.02 14.97 15.01 14.97 15.01 12.01 16.01 12.01 16.01 15.97"/><polygon class="cls-1" points="4 15.97 0.01 15.97 0.01 12.01 1.01 12.01 1.01 14.97 4 14.97 4 15.97"/><path class="cls-1" d="M8.41,3.18A4.82,4.82,0,1,0,13.23,8,4.83,4.83,0,0,0,8.41,3.18Zm0,.74A4.08,4.08,0,0,1,12.49,8a4,4,0,0,1-.85,2.47L5.69,5A4,4,0,0,1,8.41,3.93Zm0,8.15A4.08,4.08,0,0,1,4.34,8a4,4,0,0,1,.85-2.47L11.14,11A4,4,0,0,1,8.41,12.07Z"/></svg>

After

Width:  |  Height:  |  Size: 847 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>blocker_inverse</title><polygon class="cls-1" points="0.99 3.99 -0.01 3.99 -0.01 0.03 3.98 0.03 3.98 1.03 0.99 1.03 0.99 3.99"/><polygon class="cls-1" points="16.01 3.99 15.01 3.99 15.01 1.03 12.02 1.03 12.02 0.03 16.01 0.03 16.01 3.99"/><polygon class="cls-1" points="16.01 15.97 12.02 15.97 12.02 14.97 15.01 14.97 15.01 12.01 16.01 12.01 16.01 15.97"/><polygon class="cls-1" points="4 15.97 0.01 15.97 0.01 12.01 1.01 12.01 1.01 14.97 4 14.97 4 15.97"/><path class="cls-1" d="M8.41,3.18A4.82,4.82,0,1,0,13.23,8,4.83,4.83,0,0,0,8.41,3.18Zm0,.74A4.08,4.08,0,0,1,12.49,8a4,4,0,0,1-.85,2.47L5.69,5A4,4,0,0,1,8.41,3.93Zm0,8.15A4.08,4.08,0,0,1,4.34,8a4,4,0,0,1,.85-2.47L11.14,11A4,4,0,0,1,8.41,12.07Z"/></svg>

After

Width:  |  Height:  |  Size: 852 B

View File

@@ -31,7 +31,7 @@ jobhistory-component {
border-bottom: 3px solid #444444;
}
#jobsDiv .jobview-grid {
.jobview-grid {
height: 94.7%;
width : 100%;
display: block;
@@ -49,6 +49,18 @@ jobhistory-component {
font-size: larger;
}
.vs-dark #agentViewDiv .slick-header-column {
background: #333333 !important;
}
#agentViewDiv .slick-header-column {
background-color: transparent !important;
background: white !important;
border: 0px !important;
font-weight: bold;
font-size: larger;
}
.vs-dark #jobsDiv jobsview-component .jobview-grid .grid-canvas .ui-widget-content.slick-row .slick-cell {
background:#333333;
}
@@ -97,7 +109,7 @@ jobhistory-component {
#jobsDiv .jobview-grid .slick-cell.l1.r1 .jobview-jobnametext {
text-overflow: ellipsis;
width: 200px;
width: 250px;
overflow: hidden;
white-space: nowrap;
display: inline-block;
@@ -107,18 +119,14 @@ jobhistory-component {
border-bottom: none;
}
#jobsDiv .jobview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row .slick-cell.l1.r1.error-row {
.jobview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row .slick-cell.l1.r1.error-row {
width: 100%;
opacity: 1;
font-weight: 700;
color: orangered;
}
#jobsDiv .jobview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row .slick-cell.error-row {
opacity: 0;
}
#jobsDiv .jobview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row .slick-cell._detail_selector.error-row {
.jobview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row .slick-cell._detail_selector.error-row {
opacity: 1;
}
@@ -182,6 +190,30 @@ jobhistory-component {
background-image: url('./job_inverse.svg');
}
.alertsview-icon {
background-image: url('./alert.svg');
}
.vs-dark .alertsview-icon {
background-image: url('./alert_inverse.svg');
}
.proxiesview-icon {
background-image: url('./proxy.svg');
}
.vs-dark .proxiesview-icon {
background-image: url('./proxy_inverse.svg');
}
.operatorsview-icon {
background-image: url('./operator.svg');
}
.vs-dark .operatorsview-icon {
background-image: url('./operator_inverse.svg');
}
agentview-component .jobview-grid .grid-canvas > .ui-widget-content.slick-row.even > .slick-cell,
agentview-component .jobview-grid .grid-canvas > .ui-widget-content.slick-row.odd > .slick-cell {
cursor: pointer;
@@ -219,8 +251,8 @@ agentview-component .jobview-grid .grid-canvas > .ui-widget-content.slick-row.od
background: #444444 !important;
}
table.jobprevruns div.bar1, table.jobprevruns div.bar2, table.jobprevruns div.bar3,
table.jobprevruns div.bar4, table.jobprevruns div.bar5 {
table.jobprevruns div.bar0, table.jobprevruns div.bar1, table.jobprevruns div.bar2,
table.jobprevruns div.bar3, table.jobprevruns div.bar4, table.jobprevruns div.bar5 {
padding-top: 3px;
padding-left: 5px;
width: 10px;
@@ -232,10 +264,54 @@ table.jobprevruns div.bar4, table.jobprevruns div.bar5 {
}
table.jobprevruns {
margin: auto;
height: 100%;
}
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;
}
#alertsDiv .jobalertsview-grid {
height: 94.7%;
width : 100%;
display: block;
}
#operatorsDiv .joboperatorsview-grid {
height: 94.7%;
width : 100%;
display: block;
}
#proxiesDiv .jobproxiesview-grid {
height: 94.7%;
width : 100%;
display: block;
}

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

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><title>security</title><path d="M6,8a4.88,4.88,0,0,0-1.33.18,5.11,5.11,0,0,0-1.2.5,5,5,0,0,0-1.79,1.79,5.11,5.11,0,0,0-.5,1.2A4.88,4.88,0,0,0,1,13H0a5.9,5.9,0,0,1,.28-1.79,6.12,6.12,0,0,1,2-2.94,5.33,5.33,0,0,1,1.58-.88,4.18,4.18,0,0,1-.79-.65,4,4,0,0,1-.59-.8A4.05,4.05,0,0,1,2.13,5a4,4,0,0,1,.18-2.57A4,4,0,0,1,4.44.31a4,4,0,0,1,3.12,0A4,4,0,0,1,9.69,2.44,4,4,0,0,1,9.87,5a4.05,4.05,0,0,1-.37.93,4,4,0,0,1-.59.8,4.18,4.18,0,0,1-.79.65,6.14,6.14,0,0,1,1,.5,5.73,5.73,0,0,1,.91.69l-.68.74a5,5,0,0,0-1.57-1A4.93,4.93,0,0,0,6,8ZM3,4a2.92,2.92,0,0,0,.23,1.17,3,3,0,0,0,1.6,1.6,3,3,0,0,0,2.33,0,3,3,0,0,0,1.6-1.6,3,3,0,0,0,0-2.33,3,3,0,0,0-1.6-1.6,3,3,0,0,0-2.33,0,3,3,0,0,0-1.6,1.6A2.92,2.92,0,0,0,3,4Zm12,8a2.45,2.45,0,0,1,0,1l1,.4-.38.93-1-.41a2.59,2.59,0,0,1-.67.67l.41,1-.93.38L13,15a2.45,2.45,0,0,1-1,0l-.4,1-.93-.38.41-1a2.59,2.59,0,0,1-.67-.67l-1,.41-.38-.93,1-.4a2.45,2.45,0,0,1,0-1l-1-.4.38-.93,1,.41a2.59,2.59,0,0,1,.67-.67l-.41-1,.93-.38.4,1a2.45,2.45,0,0,1,1,0l.4-1,.93.38-.41,1a2.59,2.59,0,0,1,.67.67l1-.41.38.93ZM12.5,14a1.47,1.47,0,0,0,.59-.12,1.49,1.49,0,0,0,.8-.8,1.52,1.52,0,0,0,0-1.17,1.49,1.49,0,0,0-.8-.8,1.52,1.52,0,0,0-1.17,0,1.49,1.49,0,0,0-.8.8,1.52,1.52,0,0,0,0,1.17,1.49,1.49,0,0,0,.8.8A1.47,1.47,0,0,0,12.5,14Z"/></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

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>security_inverse</title><path class="cls-1" d="M6,8a4.88,4.88,0,0,0-1.33.18,5.11,5.11,0,0,0-1.2.5,5,5,0,0,0-1.79,1.79,5.11,5.11,0,0,0-.5,1.2A4.88,4.88,0,0,0,1,13H0a5.9,5.9,0,0,1,.28-1.79,6.12,6.12,0,0,1,2-2.94,5.33,5.33,0,0,1,1.58-.88,4.18,4.18,0,0,1-.79-.65,4,4,0,0,1-.59-.8A4.05,4.05,0,0,1,2.13,5a4,4,0,0,1,.18-2.57A4,4,0,0,1,4.44.31a4,4,0,0,1,3.12,0A4,4,0,0,1,9.69,2.44,4,4,0,0,1,9.87,5a4.05,4.05,0,0,1-.37.93,4,4,0,0,1-.59.8,4.18,4.18,0,0,1-.79.65,6.14,6.14,0,0,1,1,.5,5.73,5.73,0,0,1,.91.69l-.68.74a5,5,0,0,0-1.57-1A4.93,4.93,0,0,0,6,8ZM3,4a2.92,2.92,0,0,0,.23,1.17,3,3,0,0,0,1.6,1.6,3,3,0,0,0,2.33,0,3,3,0,0,0,1.6-1.6,3,3,0,0,0,0-2.33,3,3,0,0,0-1.6-1.6,3,3,0,0,0-2.33,0,3,3,0,0,0-1.6,1.6A2.92,2.92,0,0,0,3,4Zm12,8a2.45,2.45,0,0,1,0,1l1,.4-.38.93-1-.41a2.59,2.59,0,0,1-.67.67l.41,1-.93.38L13,15a2.45,2.45,0,0,1-1,0l-.4,1-.93-.38.41-1a2.59,2.59,0,0,1-.67-.67l-1,.41-.38-.93,1-.4a2.45,2.45,0,0,1,0-1l-1-.4.38-.93,1,.41a2.59,2.59,0,0,1,.67-.67l-.41-1,.93-.38.4,1a2.45,2.45,0,0,1,1,0l.4-1,.93.38-.41,1a2.59,2.59,0,0,1,.67.67l1-.41.38.93ZM12.5,14a1.47,1.47,0,0,0,.59-.12,1.49,1.49,0,0,0,.8-.8,1.52,1.52,0,0,0,0-1.17,1.49,1.49,0,0,0-.8-.8,1.52,1.52,0,0,0-1.17,0,1.49,1.49,0,0,0-.8.8,1.52,1.52,0,0,0,0,1.17,1.49,1.49,0,0,0,.8.8A1.47,1.47,0,0,0,12.5,14Z"/></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

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:#101e23;}.cls-2{fill:#4bb8d1;}.cls-3{fill:#0c1011;}</style></defs><title>health</title><path class="cls-1" d="M12.58,1.51A6.36,6.36,0,0,0,8,3.9,6.32,6.32,0,0,0,3.41,1.51,3.81,3.81,0,0,0,0,5.35,5.7,5.7,0,0,0,.64,7.88h.72l0-.08A5.18,5.18,0,0,1,.64,5.39c.07-1.25.87-3.14,2.8-3.23h.12A5.81,5.81,0,0,1,7.73,4.63L8,5.06l.27-.43a5.72,5.72,0,0,1,4.28-2.47c1.93.09,2.73,2,2.8,3.23a5.15,5.15,0,0,1-.64,2.34l0,0a2.38,2.38,0,0,1-.34.68,19.45,19.45,0,0,1-6.57,6.06,11.11,11.11,0,0,1-1.25-.81c-.34-.25-.66-.52-1-.8h0a22.83,22.83,0,0,1-2.76-3H2a18.68,18.68,0,0,0,5.76,5.29h0l0,0h0c3.49-1.63,7-5.73,7.49-7.18V8A5.85,5.85,0,0,0,16,5.35,3.81,3.81,0,0,0,12.58,1.51Z"/><path class="cls-1" d="M1.41,8l-.1-.15h0Z"/><path class="cls-1" d="M7.79,15.22v0h0Z"/><path class="cls-1" d="M7.76,15.23h0v0Z"/><path class="cls-1" d="M14.72,7.73l0,0a.13.13,0,0,0,0,0Z"/><path class="cls-2" d="M12.62,8.7v.12a.48.48,0,0,1-.48.48H8.66l0,.07L7.38,12.65h0A.72.72,0,0,1,6,12.53V9.44H6V6.6L5,9.05H5a.56.56,0,0,1-.52.37H.92V8.36H4.13l0-.07L5.41,5h0a.72.72,0,0,1,1.42.12V8.22h0v2.84L7.77,8.6h0a.56.56,0,0,1,.52-.37h3.84A.48.48,0,0,1,12.62,8.7Z"/><path class="cls-3" d="M12.62,8.7v.12a.48.48,0,0,1-.48.48H8.66l0,.07L7.38,12.65h0A.72.72,0,0,1,6,12.53V9.44H6V6.6L5,9.05H5a.56.56,0,0,1-.52.37H.92V8.36H4.13l0-.07L5.41,5h0a.72.72,0,0,1,1.42.12V8.22h0v2.84L7.77,8.6h0a.56.56,0,0,1,.52-.37h3.84A.48.48,0,0,1,12.62,8.7Z"/></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

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;}.cls-2{fill:#101e23;}.cls-3{fill:#4bb8d1;}</style></defs><title>health_inverse</title><path class="cls-1" d="M12.58,1.51A6.36,6.36,0,0,0,8,3.9,6.32,6.32,0,0,0,3.41,1.51,3.81,3.81,0,0,0,0,5.35,5.7,5.7,0,0,0,.64,7.88h.72l0-.08A5.18,5.18,0,0,1,.64,5.39c.07-1.25.87-3.14,2.8-3.23h.12A5.81,5.81,0,0,1,7.73,4.63L8,5.06l.27-.43a5.72,5.72,0,0,1,4.28-2.47c1.93.09,2.73,2,2.8,3.23a5.15,5.15,0,0,1-.64,2.34l0,0a2.38,2.38,0,0,1-.34.68,19.45,19.45,0,0,1-6.57,6.06,11.11,11.11,0,0,1-1.25-.81c-.34-.25-.66-.52-1-.8h0a22.83,22.83,0,0,1-2.76-3H2a18.68,18.68,0,0,0,5.76,5.29h0l0,0h0c3.49-1.63,7-5.73,7.49-7.18V8A5.85,5.85,0,0,0,16,5.35,3.81,3.81,0,0,0,12.58,1.51Z"/><path class="cls-2" d="M1.41,8l-.1-.15h0Z"/><path class="cls-2" d="M7.79,15.22v0h0Z"/><path class="cls-2" d="M7.76,15.23h0v0Z"/><path class="cls-2" d="M14.72,7.73l0,0a.13.13,0,0,0,0,0Z"/><path class="cls-3" d="M12.62,8.7v.12a.48.48,0,0,1-.48.48H8.66l0,.07L7.38,12.65h0A.72.72,0,0,1,6,12.53V9.44H6V6.6L5,9.05H5a.56.56,0,0,1-.52.37H.92V8.36H4.13l0-.07L5.41,5h0a.72.72,0,0,1,1.42.12V8.22h0v2.84L7.77,8.6h0a.56.56,0,0,1,.52-.37h3.84A.48.48,0,0,1,12.62,8.7Z"/><path class="cls-1" d="M12.62,8.7v.12a.48.48,0,0,1-.48.48H8.66l0,.07L7.38,12.65h0A.72.72,0,0,1,6,12.53V9.44H6V6.6L5,9.05H5a.56.56,0,0,1-.52.37H.92V8.36H4.13l0-.07L5.41,5h0a.72.72,0,0,1,1.42.12V8.22h0v2.84L7.77,8.6h0a.56.56,0,0,1,.52-.37h3.84A.48.48,0,0,1,12.62,8.7Z"/></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1,17 @@
<!--
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
-->
<div class="job-heading-container">
<h1 class="job-heading" *ngIf="_isCloud === false">Alerts</h1>
<h1 class="job-heading" *ngIf="_isCloud === true">No Alerts 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>{{NewAlertText}}</span></div>
</div>
<div #jobalertsgrid class="jobview-grid"></div>

View File

@@ -0,0 +1,160 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!sql/parts/grid/media/slickColorTheme';
import 'vs/css!sql/parts/grid/media/flexbox';
import 'vs/css!sql/parts/grid/media/styles';
import 'vs/css!sql/parts/grid/media/slick.grid';
import 'vs/css!sql/parts/grid/media/slickGrid';
import 'vs/css!../common/media/jobs';
import 'vs/css!sql/media/icons/common-icons';
import 'vs/css!sql/base/browser/ui/table/media/table';
import { Component, Inject, forwardRef, ElementRef, ChangeDetectorRef, ViewChild, AfterContentChecked } from '@angular/core';
import * as sqlops from 'sqlops';
import * as nls from 'vs/nls';
import { Table } from 'sql/base/browser/ui/table/table';
import { AgentViewComponent } from 'sql/parts/jobManagement/agent/agentView.component';
import * as dom from 'vs/base/browser/dom';
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 VIEW_SELECTOR: string = 'jobalertsview-component';
export const ROW_HEIGHT: number = 45;
@Component({
selector: VIEW_SELECTOR,
templateUrl: decodeURI(require.toUrl('./alertsView.component.html')),
providers: [{ provide: TabChild, useExisting: forwardRef(() => AlertsViewComponent) }],
})
export class AlertsViewComponent implements AfterContentChecked {
private columns: Array<Slick.Column<any>> = [
{ name: nls.localize('jobAlertColumns.name', 'Name'), field: 'name', width: 200, id: 'name' },
{ name: nls.localize('jobAlertColumns.lastOccurrenceDate', 'Last Occurrence'), field: 'lastOccurrenceDate', width: 200, id: 'lastOccurrenceDate' },
{ name: nls.localize('jobAlertColumns.enabled', 'Enabled'), field: 'enabled', width: 200, id: 'enabled' },
{ name: nls.localize('jobAlertColumns.databaseName', 'Database Name'), field: 'databaseName', width: 200, id: 'databaseName' },
{ name: nls.localize('jobAlertColumns.categoryName', 'Category Name'), field: 'categoryName', width: 200, id: 'categoryName' },
];
private options: Slick.GridOptions<any> = {
syncColumnCellResize: true,
enableColumnReorder: false,
rowHeight: 45,
enableCellNavigation: true,
editable: false
};
private dataView: any;
@ViewChild('jobalertsgrid') _gridEl: ElementRef;
private isVisible: boolean = false;
private isInitialized: boolean = false;
private isRefreshing: boolean = false;
private _table: Table<any>;
public alerts: sqlops.AgentAlertInfo[];
private _serverName: string;
private _isCloud: boolean;
private _showProgressWheel: boolean;
private NewAlertText: string = nls.localize('jobAlertToolbar-NewJob', "New Alert");
private RefreshText: string = nls.localize('jobAlertToolbar-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(ICommandService) private _commandService: ICommandService
) {
this._isCloud = this._dashboardService.connectionManagementService.connectionInfo.serverInfo.isCloud;
}
public layout() {
this._table.layout(new dom.Dimension(dom.getContentWidth(this._gridEl.nativeElement), dom.getContentHeight(this._gridEl.nativeElement)));
}
ngAfterContentChecked() {
if (this.isVisible === false && this._gridEl.nativeElement.offsetParent !== null) {
this.isVisible = true;
if (!this.isInitialized) {
this._showProgressWheel = true;
this.onFirstVisible(false);
this.isInitialized = true;
}
} else if (this.isVisible === true && this._agentViewComponent.refresh === true) {
this._showProgressWheel = true;
this.onFirstVisible(false);
this.isRefreshing = true;
this._agentViewComponent.refresh = false;
} else if (this.isVisible === true && this._gridEl.nativeElement.offsetParent === null) {
this.isVisible = false;
}
}
onFirstVisible(cached?: boolean) {
let self = this;
let columns = this.columns.map((column) => {
column.rerenderOnResize = true;
return column;
});
let options = <Slick.GridOptions<any>>{
syncColumnCellResize: true,
enableColumnReorder: false,
rowHeight: ROW_HEIGHT,
enableCellNavigation: true,
forceFitColumns: true
};
this.dataView = new Slick.Data.DataView();
$(this._gridEl.nativeElement).empty();
this._table = new Table(this._gridEl.nativeElement, undefined, columns, this.options);
this._table.grid.setData(this.dataView, true);
let ownerUri: string = this._dashboardService.connectionManagementService.connectionInfo.ownerUri;
this._jobManagementService.getAlerts(ownerUri).then((result) => {
if (result && result.alerts) {
self.alerts = result.alerts;
self.onAlertsAvailable(result.alerts);
}
});
}
private onAlertsAvailable(alerts: sqlops.AgentAlertInfo[]) {
let items: any = alerts.map((item) => {
return {
id: item.id,
name: item.name,
lastOccurrenceDate: item.lastOccurrenceDate,
enabled: item.isEnabled,
databaseName: item.databaseName,
categoryName: item.categoryName
};
});
this.dataView.beginUpdate();
this.dataView.setItems(items);
this.dataView.endUpdate();
this._table.autosizeColumns();
this._table.resizeCanvas();
this._showProgressWheel = false;
this._cd.detectChanges();
}
private openCreateJobDialog() {
let ownerUri: string = this._dashboardService.connectionManagementService.connectionInfo.ownerUri;
this._commandService.executeCommand('agent.openCreateAlertDialog', ownerUri);
}
private refreshJobs() {
this._agentViewComponent.refresh = true;
}
}

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,30 @@ 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 jobName = context.agentJobInfo.name;
let server = context.serverName;
let stepId = 0;
if (context.agentJobHistoryInfo && context.agentJobHistoryInfo.steps) {
stepId = context.agentJobHistoryInfo.steps.length + 1;
}
return new TPromise<boolean>((resolve, reject) => {
resolve(this._commandService.executeCommand('agent.openNewStepDialog', ownerUri, jobName, server, stepId));
});
}
}

View File

@@ -77,7 +77,7 @@
<!-- Job History details -->
<div class='history-details'>
<!-- Previous run list -->
<div style="min-width: 275px">
<div class="prev-run-list-container" style="min-width: 275px; height: 75vh">
<table *ngIf="_showPreviousRuns === true">
<tr>
<td class="date-column">

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,12 @@ 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;
private static readonly INITIAL_TREE_HEIGHT: number = 780;
private static readonly HEADING_HEIGHT: number = 24;
constructor(
@Inject(forwardRef(() => ElementRef)) el: ElementRef,
@@ -76,14 +80,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);
}
}
@@ -124,8 +128,17 @@ export class JobHistoryComponent extends Disposable implements OnInit {
renderer: this._treeRenderer
}, {verticalScrollMode: ScrollbarVisibility.Visible});
this._register(attachListStyler(this._tree, this.themeService));
this._tree.layout(1024);
this._tree.layout(JobHistoryComponent.INITIAL_TREE_HEIGHT);
this._initActionBar();
$(window).resize(() => {
let historyDetails = $('.overview-container').get(0);
let statusBar = $('.part.statusbar').get(0);
if (historyDetails && statusBar) {
let historyBottom = historyDetails.getBoundingClientRect().bottom;
let statusTop = statusBar.getBoundingClientRect().top;
this._tree.layout(statusTop - historyBottom - JobHistoryComponent.HEADING_HEIGHT);
}
});
}
ngAfterContentChecked() {
@@ -210,7 +223,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 +250,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 +276,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 +301,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;
@@ -221,10 +230,6 @@ table.step-list tr.step-row td {
min-height: 40px !important;
}
jobhistory-component .history-details .step-table.prev-run-list .monaco-scrollable-element {
overflow-y: scroll !important;
}
jobhistory-component .jobhistory-heading-container {
display: -webkit-box;
}

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,28 @@ 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.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.name', 'Name'),
field: 'name',
formatter: (row, cell, value, columnDef, dataContext) => this.renderName(row, cell, value, columnDef, dataContext),
width: 150,
id: 'name'
},
{ name: nls.localize('jobColumns.lastRun', 'Last Run'), field: 'lastRun', width: 80, id: 'lastRun' },
{ name: nls.localize('jobColumns.nextRun', 'Next Run'), field: 'nextRun', width: 80, id: 'nextRun' },
{ name: nls.localize('jobColumns.enabled', 'Enabled'), field: 'enabled', width: 60, id: 'enabled' },
{ name: nls.localize('jobColumns.status', 'Status'), field: 'currentExecutionStatus', width: 50, id: 'currentExecutionStatus' },
{ name: nls.localize('jobColumns.category', 'Category'), field: 'category', width: 100, 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: 100, id: 'lastRunOutcome' },
{
name: nls.localize('jobColumns.previousRuns', 'Previous Runs'),
formatter: (row, cell, value, columnDef, dataContext) => this.renderChartsPostHistory(row, cell, value, columnDef, dataContext),
field: 'previousRuns',
width: 100,
id: 'previousRuns'
}
];
@@ -79,19 +91,22 @@ export class JobsViewComponent implements AfterContentChecked {
private _serverName: string;
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;
@@ -130,13 +145,13 @@ export class JobsViewComponent implements AfterContentChecked {
this.onFirstVisible(false);
this.isRefreshing = true;
this._agentViewComponent.refresh = false;
} else if (this.isVisible === true && this._agentViewComponent.refresh === false) {
} /*else if (this.isVisible === true && this._agentViewComponent.refresh === false) {
if (!this.isRefreshing) {
this._showProgressWheel = true;
this.onFirstVisible(true);
}
this.isRefreshing = false;
} else if (this.isVisible === true && this._gridEl.nativeElement.offsetParent === null) {
}*/ else if (this.isVisible === true && this._gridEl.nativeElement.offsetParent === null) {
this.isVisible = false;
}
}
@@ -241,9 +256,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 +266,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 +276,7 @@ export class JobsViewComponent implements AfterContentChecked {
}
// one expansion for the row and one for
// the error detail
seenJobs ++;
seenJobs++;
i++;
}
seenJobs++;
@@ -277,7 +292,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 +306,7 @@ export class JobsViewComponent implements AfterContentChecked {
}
// one expansion for the row and one for
// the error detail
seenJobs ++;
seenJobs++;
i++;
}
seenJobs++;
@@ -331,21 +346,20 @@ export class JobsViewComponent implements AfterContentChecked {
currentTarget.title = currentTarget.innerText;
});
this._showProgressWheel = false;
this._cd.detectChanges();
if (this.isVisible) {
this._cd.detectChanges();
}
const self = this;
this._tabHeight = $('agentview-component #jobsDiv .jobview-grid').get(0).clientHeight;
$(window).resize(() => {
let currentTab = $('agentview-component #jobsDiv .jobview-grid').get(0);
if (currentTab) {
let currentTabHeight = currentTab.clientHeight;
if (currentTabHeight < self._tabHeight) {
$('agentview-component #jobsDiv div.ui-widget').css('height', `${currentTabHeight - 22}px`);
self._table.resizeCanvas();
} else {
$('agentview-component #jobsDiv div.ui-widget').css('height', `${currentTabHeight}px`);
self._table.resizeCanvas();
}
self._tabHeight = currentTabHeight;
let jobsViewToolbar = $('jobsview-component .jobs-view-toolbar').get(0);
let statusBar = $('.part.statusbar').get(0);
if (jobsViewToolbar && statusBar) {
let toolbarBottom = jobsViewToolbar.getBoundingClientRect().bottom;
let statusTop = statusBar.getBoundingClientRect().top;
$('agentview-component #jobsDiv .jobview-grid').css('height', statusTop - toolbarBottom);
self._table.resizeCanvas();
}
});
this._table.grid.onColumnsResized.subscribe((e, data: any) => {
@@ -357,49 +371,17 @@ export class JobsViewComponent implements AfterContentChecked {
// generate job charts again
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);
});
});
$('#jobsDiv .jobview-grid .monaco-table .slick-viewport .grid-canvas .ui-widget-content.slick-row').hover((e) => {
// highlight the error row as well if a failing job row is hovered
if (e.currentTarget.children.item(0).classList.contains('job-with-error')) {
let target = $(e.currentTarget);
let targetChildren = $(e.currentTarget.children);
let siblings = target.nextAll().toArray();
let top = parseInt(target.css('top'), 10);
for (let i = 0; i < siblings.length; i++) {
let sibling = siblings[i];
let siblingTop = parseInt($(sibling).css('top'), 10);
if (siblingTop === top + ROW_HEIGHT) {
$(sibling.children).addClass('hovered');
sibling.onmouseenter = (e) => {
targetChildren.addClass('hovered');
};
sibling.onmouseleave = (e) => {
targetChildren.removeClass('hovered');
}
break;
}
}
}
}, (e) => {
// switch back to original background
if (e.currentTarget.children.item(0).classList.contains('job-with-error')) {
let target = $(e.currentTarget);
let siblings = target.nextAll().toArray();
let top = parseInt(target.css('top'), 10);
for (let i = 0; i < siblings.length; i++) {
let sibling = siblings[i];
let siblingTop = parseInt($(sibling).css('top'), 10);
if (siblingTop === top + ROW_HEIGHT) {
$(sibling.children).removeClass('hovered');
break;
}
}
}
$('#jobsDiv .jobview-grid .monaco-table .slick-viewport .grid-canvas .ui-widget-content.slick-row').hover((e1) =>
this.highlightErrorRows(e1), (e2) => this.hightlightNonErrorRows(e2));
this._table.grid.onScroll.subscribe((e) => {
$('#jobsDiv .jobview-grid .monaco-table .slick-viewport .grid-canvas .ui-widget-content.slick-row').hover((e1) =>
this.highlightErrorRows(e1), (e2) => this.hightlightNonErrorRows(e2));
});
// cache the dataview for future use
this._jobCacheObject.dataView = this.dataView;
@@ -407,6 +389,47 @@ export class JobsViewComponent implements AfterContentChecked {
this.loadJobHistories();
}
private highlightErrorRows(e) {
// highlight the error row as well if a failing job row is hovered
if (e.currentTarget.children.item(0).classList.contains('job-with-error')) {
let target = $(e.currentTarget);
let targetChildren = $(e.currentTarget.children);
let siblings = target.nextAll().toArray();
let top = parseInt(target.css('top'), 10);
for (let i = 0; i < siblings.length; i++) {
let sibling = siblings[i];
let siblingTop = parseInt($(sibling).css('top'), 10);
if (siblingTop === top + ROW_HEIGHT) {
$(sibling.children).addClass('hovered');
sibling.onmouseenter = (e) => {
targetChildren.addClass('hovered');
};
sibling.onmouseleave = (e) => {
targetChildren.removeClass('hovered');
}
break;
}
}
}
}
private hightlightNonErrorRows(e) {
// switch back to original background
if (e.currentTarget.children.item(0).classList.contains('job-with-error')) {
let target = $(e.currentTarget);
let siblings = target.nextAll().toArray();
let top = parseInt(target.css('top'), 10);
for (let i = 0; i < siblings.length; i++) {
let sibling = siblings[i];
let siblingTop = parseInt($(sibling).css('top'), 10);
if (siblingTop === top + ROW_HEIGHT) {
$(sibling.children).removeClass('hovered');
break;
}
}
}
}
private setRowWithErrorClass(hash: { [index: number]: { [id: string]: string; } }, row: number, errorClass: string) {
hash[row] = {
'_detail_selector': errorClass,
@@ -427,27 +450,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) {
@@ -477,14 +500,28 @@ export class JobsViewComponent implements AfterContentChecked {
}
private renderChartsPostHistory(row, cell, value, columnDef, dataContext) {
return `<table class="jobprevruns" id="${dataContext.id}">
let runChart = this._jobCacheObject.getRunChart(dataContext.id);
if (runChart && runChart.length > 0) {
return `<table class="jobprevruns" id="${dataContext.id}">
<tr>
<td><div class="bar1"></div></td>
<td><div class="bar2"></div></td>
<td><div class="bar3"></div></td>
<td><div class="bar4"></div></td>
<td>${runChart[0] ? runChart[0] : '<div></div>' }</td>
<td>${runChart[1] ? runChart[1] : '<div></div>' }</td>
<td>${runChart[2] ? runChart[2] : '<div></div>' }</td>
<td>${runChart[3] ? runChart[3] : '<div></div>' }</td>
<td>${runChart[4] ? runChart[4] : '<div></div>' }</td>
</tr>
</table>`;
</table>`;
} else {
return `<table class="jobprevruns" id="${dataContext.id}">
<tr>
<td><div class="bar0"></div></td>
<td><div class="bar1"></div></td>
<td><div class="bar2"></div></td>
<td><div class="bar3"></div></td>
<td><div class="bar4"></div></td>
</tr>
</table>`;
}
}
private expandJobRowDetails(rowIdx: number, message?: string): void {
@@ -557,13 +594,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);
@@ -575,8 +612,9 @@ export class JobsViewComponent implements AfterContentChecked {
private createJobChart(jobId: string, jobHistories: sqlops.AgentJobHistoryInfo[]): void {
let chartHeights = this.getChartHeights(jobHistories);
let runCharts = [];
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}`);
if (jobHistories && jobHistories.length > 0) {
runGraph.css('height', chartHeights[i]);
let bgColor = jobHistories[i].runStatus === 0 ? 'red' : 'green';
@@ -585,6 +623,9 @@ export class JobsViewComponent implements AfterContentChecked {
let currentTarget = e.currentTarget;
currentTarget.title = jobHistories[i].runDuration;
});
if (runGraph.get(0)) {
runCharts.push(runGraph.get(0).outerHTML);
}
} else {
runGraph.css('height', '5px');
runGraph.css('background', 'red');
@@ -594,16 +635,19 @@ export class JobsViewComponent implements AfterContentChecked {
});
}
}
if (runCharts.length > 0) {
this._jobCacheObject.setRunChart(jobId, runCharts);
}
}
// 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 +657,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 +669,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 +692,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 +705,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 +714,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 +781,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 +795,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 +828,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

@@ -0,0 +1,17 @@
<!--
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
-->
<div class="job-heading-container">
<h1 class="job-heading" *ngIf="_isCloud === false">Operators</h1>
<h1 class="job-heading" *ngIf="_isCloud === true">No Operators 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)="openCreateOperatorDialog()" tabindex="0"><div class="small icon new"></div><span>{{NewOperatorText}}</span></div>
</div>
<div #operatorsgrid class="joboperatorsview-grid"></div>

View File

@@ -0,0 +1,157 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!sql/parts/grid/media/slickColorTheme';
import 'vs/css!sql/parts/grid/media/flexbox';
import 'vs/css!sql/parts/grid/media/styles';
import 'vs/css!sql/parts/grid/media/slick.grid';
import 'vs/css!sql/parts/grid/media/slickGrid';
import 'vs/css!../common/media/jobs';
import 'vs/css!sql/media/icons/common-icons';
import 'vs/css!sql/base/browser/ui/table/media/table';
import { Component, Inject, forwardRef, ElementRef, ChangeDetectorRef, ViewChild, AfterContentChecked } from '@angular/core';
import * as sqlops from 'sqlops';
import * as nls from 'vs/nls';
import { Table } from 'sql/base/browser/ui/table/table';
import { AgentViewComponent } from 'sql/parts/jobManagement/agent/agentView.component';
import * as dom from 'vs/base/browser/dom';
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 VIEW_SELECTOR: string = 'joboperatorsview-component';
export const ROW_HEIGHT: number = 45;
@Component({
selector: VIEW_SELECTOR,
templateUrl: decodeURI(require.toUrl('./operatorsView.component.html')),
providers: [{ provide: TabChild, useExisting: forwardRef(() => OperatorsViewComponent) }],
})
export class OperatorsViewComponent implements AfterContentChecked {
private columns: Array<Slick.Column<any>> = [
{ name: nls.localize('jobOperatorsView.name', 'Name'), field: 'name', width: 200, id: 'name' },
{ name: nls.localize('jobOperatorsView.emailAddress', 'Email Address'), field: 'emailAddress', width: 200, id: 'emailAddress' },
{ name: nls.localize('jobOperatorsView.enabled', 'Enabled'), field: 'enabled', width: 200, id: 'enabled' },
];
private options: Slick.GridOptions<any> = {
syncColumnCellResize: true,
enableColumnReorder: false,
rowHeight: 45,
enableCellNavigation: true,
editable: false
};
private dataView: any;
@ViewChild('operatorsgrid') _gridEl: ElementRef;
private isVisible: boolean = false;
private isInitialized: boolean = false;
private isRefreshing: boolean = false;
private _table: Table<any>;
public operators: sqlops.AgentOperatorInfo[];
private _serverName: string;
private _isCloud: boolean;
private _showProgressWheel: boolean;
private NewOperatorText: string = nls.localize('jobOperatorToolbar-NewItem', "New Operator");
private RefreshText: string = nls.localize('jobOperatorToolbar-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(ICommandService) private _commandService: ICommandService
) {
this._isCloud = this._dashboardService.connectionManagementService.connectionInfo.serverInfo.isCloud;
}
public layout() {
this._table.layout(new dom.Dimension(dom.getContentWidth(this._gridEl.nativeElement), dom.getContentHeight(this._gridEl.nativeElement)));
}
ngAfterContentChecked() {
if (this.isVisible === false && this._gridEl.nativeElement.offsetParent !== null) {
this.isVisible = true;
if (!this.isInitialized) {
this._showProgressWheel = true;
this.onFirstVisible(false);
this.isInitialized = true;
}
} else if (this.isVisible === true && this._agentViewComponent.refresh === true) {
this._showProgressWheel = true;
this.onFirstVisible(false);
this.isRefreshing = true;
this._agentViewComponent.refresh = false;
} else if (this.isVisible === true && this._gridEl.nativeElement.offsetParent === null) {
this.isVisible = false;
}
}
onFirstVisible(cached?: boolean) {
let self = this;
let columns = this.columns.map((column) => {
column.rerenderOnResize = true;
return column;
});
let options = <Slick.GridOptions<any>>{
syncColumnCellResize: true,
enableColumnReorder: false,
rowHeight: ROW_HEIGHT,
enableCellNavigation: true,
forceFitColumns: true
};
this.dataView = new Slick.Data.DataView();
$(this._gridEl.nativeElement).empty();
this._table = new Table(this._gridEl.nativeElement, undefined, columns, this.options);
this._table.grid.setData(this.dataView, true);
let ownerUri: string = this._dashboardService.connectionManagementService.connectionInfo.ownerUri;
this._jobManagementService.getOperators(ownerUri).then((result) => {
if (result && result.operators) {
self.operators = result.operators;
self.onOperatorsAvailable(result.operators);
}
});
}
private onOperatorsAvailable(operators: sqlops.AgentOperatorInfo[]) {
let items: any = operators.map((item) => {
return {
id: item.id,
name: item.name,
emailAddress: item.emailAddress,
enabled: item.enabled
};
});
this.dataView.beginUpdate();
this.dataView.setItems(items);
this.dataView.endUpdate();
this._table.autosizeColumns();
this._table.resizeCanvas();
this._showProgressWheel = false;
this._cd.detectChanges();
}
private openCreateOperatorDialog() {
let ownerUri: string = this._dashboardService.connectionManagementService.connectionInfo.ownerUri;
this._commandService.executeCommand('agent.openCreateOperatorDialog', ownerUri);
}
private refreshJobs() {
this._agentViewComponent.refresh = true;
}
}

View File

@@ -0,0 +1,17 @@
<!--
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
-->
<div class="job-heading-container">
<h1 class="job-heading" *ngIf="_isCloud === false">Proxies</h1>
<h1 class="job-heading" *ngIf="_isCloud === true">No Proxies 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)="openCreateProxyDialog()" tabindex="0"><div class="small icon new"></div><span>{{NewProxyText}}</span></div>
</div>
<div #proxiesgrid class="jobproxiesview-grid"></div>

View File

@@ -0,0 +1,155 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!sql/parts/grid/media/slickColorTheme';
import 'vs/css!sql/parts/grid/media/flexbox';
import 'vs/css!sql/parts/grid/media/styles';
import 'vs/css!sql/parts/grid/media/slick.grid';
import 'vs/css!sql/parts/grid/media/slickGrid';
import 'vs/css!../common/media/jobs';
import 'vs/css!sql/media/icons/common-icons';
import 'vs/css!sql/base/browser/ui/table/media/table';
import { Component, Inject, forwardRef, ElementRef, ChangeDetectorRef, ViewChild, AfterContentChecked } from '@angular/core';
import * as sqlops from 'sqlops';
import * as nls from 'vs/nls';
import { Table } from 'sql/base/browser/ui/table/table';
import { AgentViewComponent } from 'sql/parts/jobManagement/agent/agentView.component';
import * as dom from 'vs/base/browser/dom';
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 VIEW_SELECTOR: string = 'jobproxiesview-component';
export const ROW_HEIGHT: number = 45;
@Component({
selector: VIEW_SELECTOR,
templateUrl: decodeURI(require.toUrl('./proxiesView.component.html')),
providers: [{ provide: TabChild, useExisting: forwardRef(() => ProxiesViewComponent) }],
})
export class ProxiesViewComponent implements AfterContentChecked {
private columns: Array<Slick.Column<any>> = [
{ name: nls.localize('jobProxiesView.accountName', 'Account Name'), field: 'accountName', width: 200, id: 'accountName' },
{ name: nls.localize('jobProxiesView.credentialName', 'Credential Name'), field: 'credentialName', width: 200, id: 'credentialName' },
];
private options: Slick.GridOptions<any> = {
syncColumnCellResize: true,
enableColumnReorder: false,
rowHeight: 45,
enableCellNavigation: true,
editable: false
};
private dataView: any;
@ViewChild('proxiesgrid') _gridEl: ElementRef;
private isVisible: boolean = false;
private isInitialized: boolean = false;
private isRefreshing: boolean = false;
private _table: Table<any>;
public proxies: sqlops.AgentProxyInfo[];
private _serverName: string;
private _isCloud: boolean;
private _showProgressWheel: boolean;
private NewProxyText: string = nls.localize('jobProxyToolbar-NewItem', "New Proxy");
private RefreshText: string = nls.localize('jobProxyToolbar-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(ICommandService) private _commandService: ICommandService
) {
this._isCloud = this._dashboardService.connectionManagementService.connectionInfo.serverInfo.isCloud;
}
public layout() {
this._table.layout(new dom.Dimension(dom.getContentWidth(this._gridEl.nativeElement), dom.getContentHeight(this._gridEl.nativeElement)));
}
ngAfterContentChecked() {
if (this.isVisible === false && this._gridEl.nativeElement.offsetParent !== null) {
this.isVisible = true;
if (!this.isInitialized) {
this._showProgressWheel = true;
this.onFirstVisible(false);
this.isInitialized = true;
}
} else if (this.isVisible === true && this._agentViewComponent.refresh === true) {
this._showProgressWheel = true;
this.onFirstVisible(false);
this.isRefreshing = true;
this._agentViewComponent.refresh = false;
} else if (this.isVisible === true && this._gridEl.nativeElement.offsetParent === null) {
this.isVisible = false;
}
}
onFirstVisible(cached?: boolean) {
let self = this;
let columns = this.columns.map((column) => {
column.rerenderOnResize = true;
return column;
});
let options = <Slick.GridOptions<any>>{
syncColumnCellResize: true,
enableColumnReorder: false,
rowHeight: ROW_HEIGHT,
enableCellNavigation: true,
forceFitColumns: true
};
this.dataView = new Slick.Data.DataView();
$(this._gridEl.nativeElement).empty();
this._table = new Table(this._gridEl.nativeElement, undefined, columns, this.options);
this._table.grid.setData(this.dataView, true);
let ownerUri: string = this._dashboardService.connectionManagementService.connectionInfo.ownerUri;
this._jobManagementService.getProxies(ownerUri).then((result) => {
if (result && result.proxies) {
self.proxies = result.proxies;
self.onProxiesAvailable(result.proxies);
}
});
}
private onProxiesAvailable(proxies: sqlops.AgentProxyInfo[]) {
let items: any = proxies.map((item) => {
return {
id: item.id,
accountName: item.accountName,
credentialName: item.credentialName
};
});
this.dataView.beginUpdate();
this.dataView.setItems(items);
this.dataView.endUpdate();
this._table.autosizeColumns();
this._table.resizeCanvas();
this._showProgressWheel = false;
this._cd.detectChanges();
}
private openCreateProxyDialog() {
let ownerUri: string = this._dashboardService.connectionManagementService.connectionInfo.ownerUri;
this._commandService.executeCommand('agent.openCreateProxyDialog', ownerUri);
}
private refreshJobs() {
this._agentViewComponent.refresh = true;
}
}

View File

@@ -207,7 +207,9 @@ export abstract class ContainerBase<T> extends ComponentBase {
) {
super(_changeRef);
this.items = [];
this._validations.push(() => this.items.every(item => this.modelStore.getComponent(item.descriptor.id).valid));
this._validations.push(() => this.items.every(item => {
return this.modelStore.getComponent(item.descriptor.id).valid;
}));
}
/// IComponent container-related implementation

View File

@@ -32,9 +32,9 @@
border: 1px solid gray;
}
[role="gridcell"]:focus,
[role="gridcell"] *:focus,
[role="grid"] [tabindex="0"]:focus {
.declarative-table [role="gridcell"]:focus,
.declarative-table [role="gridcell"] *:focus,
.declarative-table [role="grid"] [tabindex="0"]:focus {
outline: #005a9c;
outline-style: dotted;
outline-width: 3px;

View File

@@ -18,9 +18,8 @@ import { IInputOptions, MessageType } from 'vs/base/browser/ui/inputbox/inputBox
import { attachInputBoxStyler, attachListStyler } from 'vs/platform/theme/common/styler';
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { Event, Emitter } from 'vs/base/common/event';
import * as nls from 'vs/nls';
import { TextAreaInput } from 'vs/editor/browser/controller/textAreaInput';
import { inputBackground, inputBorder } from 'vs/platform/theme/common/colorRegistry';
@Component({
selector: 'modelview-inputBox',
@@ -70,13 +69,13 @@ export default class InputBoxComponent extends ComponentBase implements ICompone
if (this._inputContainer) {
this._input = new InputBox(this._inputContainer.nativeElement, this.contextViewService, inputOptions);
this.registerInput(this._input, () => !this.multiline);
}
if (this._textareaContainer) {
let textAreaInputOptions = Object.assign({}, inputOptions, { flexibleHeight: true, type: 'textarea' });
this._textAreaInput = new InputBox(this._textareaContainer.nativeElement, this.contextViewService, textAreaInputOptions);
this.registerInput(this._textAreaInput, () => this.multiline);
}
this.inputElement.hideErrors = true;
}
private get inputElement(): InputBox {
@@ -88,10 +87,17 @@ export default class InputBoxComponent extends ComponentBase implements ICompone
this._validations.push(() => !input.inputElement.validationMessage);
this._register(input);
this._register(attachInputBoxStyler(input, this.themeService));
this._register(input.onDidChange(e => {
this._register(attachInputBoxStyler(input, this.themeService, {
inputValidationInfoBackground: inputBackground,
inputValidationInfoBorder: inputBorder,
}));
this._register(input.onDidChange(async e => {
if (checkOption()) {
this.value = input.value;
await this.validate();
if (input.hideErrors) {
input.hideErrors = false;
}
this._onEventEmitter.fire({
eventType: ComponentEventType.onDidChange,
args: e

View File

@@ -94,8 +94,8 @@ export class ModelComponentWrapper extends AngularDisposable implements OnInit {
}
public layout(): void {
if (this._componentInstance && this._componentInstance.layout) {
this._componentInstance.layout();
if (this.componentInstance && this.componentInstance.layout) {
this.componentInstance.layout();
}
}
@@ -110,6 +110,12 @@ export class ModelComponentWrapper extends AngularDisposable implements OnInit {
};
}
private get componentInstance(): IComponent {
if (!this._componentInstance) {
this.loadComponent();
}
return this._componentInstance;
}
private loadComponent(): void {
if (!this.descriptor || !this.descriptor.type) {

View File

@@ -73,7 +73,7 @@ export class ModelViewInput extends EditorInput {
private createDialogPane(): void {
this._dialogPaneContainer = DOM.$('div.model-view-container');
this._dialogPane = new DialogPane(this.title, this.modelViewId, () => undefined, this._instantiationService);
this._dialogPane = new DialogPane(this.title, this.modelViewId, () => undefined, this._instantiationService, false);
this._dialogPane.createBody(this._dialogPaneContainer);
}

View File

@@ -26,7 +26,7 @@ import * as sqlops from 'sqlops';
selector: 'modelview-content',
template: `
<div *ngIf="rootDescriptor" style="width: 100%; height: 100%;">
<model-component-wrapper [descriptor]="rootDescriptor" [modelStore]="modelStore">
<model-component-wrapper style="display: block; height: 100%" [descriptor]="rootDescriptor" [modelStore]="modelStore">
</model-component-wrapper>
</div>
`

View File

@@ -12,7 +12,7 @@ import nls = require('vs/nls');
import * as sqlops from 'sqlops';
import { IModelStore, IComponentDescriptor, IComponent, IComponentEventArgs } from './interfaces';
import { IItemConfig, ModelComponentTypes, IComponentShape } from 'sql/workbench/api/common/sqlExtHostTypes';
import { IModelView } from 'sql/services/model/modelViewService';
import { IModelView, IModelViewEventArgs } from 'sql/services/model/modelViewService';
import { Extensions, IComponentRegistry } from 'sql/platform/dashboard/common/modelComponentRegistry';
import { AngularDisposable } from 'sql/base/common/lifecycle';
import { ModelStore } from 'sql/parts/modelComponents/modelStore';
@@ -38,7 +38,7 @@ export abstract class ViewBase extends AngularDisposable implements IModelView {
abstract id: string;
abstract connection: sqlops.connection.Connection;
abstract serverInfo: sqlops.ServerInfo;
private _onEventEmitter = new Emitter<any>();
private _onEventEmitter = new Emitter<IModelViewEventArgs>();
initializeModel(rootComponent: IComponentShape, validationCallback: (componentId: string) => Thenable<boolean>): void {
let descriptor = this.defineComponent(rootComponent);
@@ -106,13 +106,16 @@ export abstract class ViewBase extends AngularDisposable implements IModelView {
registerEvent(componentId: string) {
this.queueAction(componentId, (component) => {
this._register(component.registerEventHandler(e => {
e.componentId = componentId;
this._onEventEmitter.fire(e);
let modelViewEvent: IModelViewEventArgs = Object.assign({
componentId: componentId,
isRootComponent: componentId === this.rootDescriptor.id
}, e);
this._onEventEmitter.fire(modelViewEvent);
}));
});
}
public get onEvent(): Event<IComponentEventArgs> {
public get onEvent(): Event<IModelViewEventArgs> {
return this._onEventEmitter.event;
}

View File

@@ -96,11 +96,11 @@ export class TreeNode {
return undefined;
}
var currentNode: TreeNode = this;
while (currentNode.nodeTypeId !== NodeType.Database && currentNode.nodeTypeId !== NodeType.Server) {
while (currentNode.nodeTypeId !== NodeType.Database && currentNode.nodeTypeId !== NodeType.Server && currentNode.parent) {
currentNode = currentNode.parent;
}
if (currentNode.nodeTypeId === NodeType.Database) {
if (currentNode && currentNode.nodeTypeId === NodeType.Database) {
return currentNode.metadata ? currentNode.metadata.name : null;
}
return undefined;

View File

@@ -181,7 +181,7 @@ export class TreeUpdateUtils {
connectedConnection.options['groupId'] = connection.groupId;
connectedConnection.options['databaseDisplayName'] = connection.databaseName;
var rootNode: TreeNode = objectExplorerService.getObjectExplorerNode(connectedConnection);
let rootNode: TreeNode = objectExplorerService.getObjectExplorerNode(connectedConnection);
if (!rootNode) {
objectExplorerService.updateObjectExplorerNodes(connectedConnection).then(() => {
rootNode = objectExplorerService.getObjectExplorerNode(connectedConnection);
@@ -207,7 +207,7 @@ export class TreeUpdateUtils {
if (connection.isDisconnecting) {
resolve([]);
} else {
var rootNode = objectExplorerService.getObjectExplorerNode(connection);
let rootNode = objectExplorerService.getObjectExplorerNode(connection);
if (rootNode) {
objectExplorerService.resolveTreeNodeChildren(rootNode.getSession(), rootNode).then(() => {
resolve(rootNode.children);
@@ -226,7 +226,7 @@ export class TreeUpdateUtils {
if (objectExplorerNode && objectExplorerNode.parent) {
// if object explorer node's parent is root, return connection profile
if (!objectExplorerNode.parent.parent) {
var connectionId = objectExplorerNode.getConnectionProfile().id;
let connectionId = objectExplorerNode.getConnectionProfile().id;
// get connection profile from connection profile groups
let root = TreeUpdateUtils.getTreeInput(connectionManagementService);
@@ -268,8 +268,8 @@ export class TreeUpdateUtils {
* Get connection profile with the current database
*/
public static getConnectionProfile(treeNode: TreeNode): ConnectionProfile {
var connectionProfile = treeNode.getConnectionProfile();
var databaseName = treeNode.getDatabaseName();
let connectionProfile = treeNode.getConnectionProfile();
let databaseName = treeNode.getDatabaseName();
if (databaseName !== undefined && connectionProfile.databaseName !== databaseName) {
connectionProfile = connectionProfile.cloneWithDatabase(databaseName);
}

View File

@@ -23,6 +23,10 @@ import { IObjectExplorerService } from '../../objectExplorer/common/objectExplor
import { ProfilerInput } from 'sql/parts/profiler/editor/profilerInput';
import { TPromise } from 'vs/base/common/winjs.base';
import * as TaskUtilities from 'sql/workbench/common/taskUtilities';
import { IProfilerService} from '../service/interfaces';
import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { KeyCode, KeyMod } from 'vs/editor/editor.api';
import { ProfilerEditor } from '../editor/profilerEditor';
// Contribute Global Actions
const category = nls.localize('profilerCategory', "Profiler");
@@ -46,3 +50,42 @@ CommandsRegistry.registerCommand({
return editorService.openEditor(profilerInput, { pinned: true }, false).then(() => TPromise.as(true));
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'profiler.newProfiler',
weight: KeybindingsRegistry.WEIGHT.builtinExtension(),
when: undefined,
primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_P,
mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_P },
handler: CommandsRegistry.getCommand('profiler.newProfiler').handler
});
CommandsRegistry.registerCommand({
id: 'profiler.start',
handler: (accessor: ServicesAccessor) => {
let profilerService: IProfilerService = accessor.get(IProfilerService);
let editorService: IWorkbenchEditorService = accessor.get(IWorkbenchEditorService);
let activeEditor = editorService.getActiveEditor();
if (activeEditor instanceof ProfilerEditor) {
let profilerInput = activeEditor.input;
return profilerService.startSession(profilerInput.id);
}
return TPromise.as(false);
}
});
CommandsRegistry.registerCommand({
id: 'profiler.stop',
handler: (accessor: ServicesAccessor) => {
let profilerService: IProfilerService = accessor.get(IProfilerService);
let editorService: IWorkbenchEditorService = accessor.get(IWorkbenchEditorService);
let activeEditor = editorService.getActiveEditor();
if (activeEditor instanceof ProfilerEditor) {
let profilerInput = activeEditor.input;
return profilerService.stopSession(profilerInput.id);
}
return TPromise.as(false);
}
});

View File

@@ -79,10 +79,7 @@ export class ProfilerStart extends Action {
public run(input: ProfilerInput): TPromise<boolean> {
this.enabled = false;
input.data.clear();
return TPromise.wrap(this._profilerService.startSession(input.id).then(() => {
input.state.change({ isRunning: true, isStopped: false, isPaused: false });
return true;
}));
return TPromise.wrap(this._profilerService.startSession(input.id));
}
}
@@ -131,10 +128,7 @@ export class ProfilerStop extends Action {
public run(input: ProfilerInput): TPromise<boolean> {
this.enabled = false;
return TPromise.wrap(this._profilerService.stopSession(input.id).then(() => {
input.state.change({ isStopped: true, isPaused: false, isRunning: false });
return true;
}));
return TPromise.wrap(this._profilerService.stopSession(input.id));
}
}

View File

@@ -135,6 +135,10 @@ export class ProfilerInput extends EditorInput implements IProfilerSession {
});
}
public onSessionStateChanged(state: ProfilerState) {
this.state.change(state);
}
public onMoreRows(eventMessage: sqlops.ProfilerSessionEvents) {
if (eventMessage.eventsLost){
this._notificationService.warn(nls.localize("profiler.eventsLost", "The XEvent Profiler session for {0} has lost events.", this._connection.serverName));

View File

@@ -8,6 +8,7 @@ import { ProfilerInput } from 'sql/parts/profiler/editor/profilerInput';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import * as sqlops from 'sqlops';
import { INewProfilerState } from '../editor/profilerState';
const PROFILER_SERVICE_ID = 'profilerService';
export const IProfilerService = createDecorator<IProfilerService>(PROFILER_SERVICE_ID);
@@ -29,6 +30,10 @@ export interface IProfilerSession {
* Called by the service when the session is closed unexpectedly
*/
onSessionStopped(events: sqlops.ProfilerSessionStoppedParams);
/**
* Called by the service when the session state is changed
*/
onSessionStateChanged(newState: INewProfilerState);
}
/**

View File

@@ -96,7 +96,10 @@ export class ProfilerService implements IProfilerService {
}
public startSession(id: ProfilerSessionID): Thenable<boolean> {
return this._runAction(id, provider => provider.startSession(this._idMap.get(id)));
return this._runAction(id, provider => provider.startSession(this._idMap.get(id))).then(() => {
this._sessionMap.get(this._idMap.reverseGet(id)).onSessionStateChanged({ isRunning: true, isStopped: false, isPaused: false });
return true;
});
}
public pauseSession(id: ProfilerSessionID): Thenable<boolean> {
@@ -104,7 +107,10 @@ export class ProfilerService implements IProfilerService {
}
public stopSession(id: ProfilerSessionID): Thenable<boolean> {
return this._runAction(id, provider => provider.stopSession(this._idMap.get(id)));
return this._runAction(id, provider => provider.stopSession(this._idMap.get(id))).then(() => {
this._sessionMap.get(this._idMap.reverseGet(id)).onSessionStateChanged({ isStopped: true, isPaused: false, isRunning: false });
return true;
});
}
private _runAction<T>(id: ProfilerSessionID, action: (handler: sqlops.ProfilerProvider) => Thenable<T>): Thenable<T> {

View File

@@ -306,6 +306,11 @@ let registryProperties = {
'default': Constants.tabColorModeOff,
'description': localize('tabColorMode', "Controls how to color tabs based on the server group of their active connection")
},
'sql.showConnectionInfoInTitle': {
'type': 'boolean',
'description': localize('showConnectionInfoInTitle', "Controls whether to show the connection info for a tab in the title."),
'default': false
},
'mssql.intelliSense.enableIntelliSense': {
'type': 'boolean',
'default': true,

View File

@@ -4,17 +4,35 @@
*--------------------------------------------------------------------------------------------*/
import { TPromise } from 'vs/base/common/winjs.base';
import { EditorInput, EditorModel, ConfirmResult, EncodingMode, IEncodingSupport } from 'vs/workbench/common/editor';
import { IConnectionManagementService, IConnectableInput, INewConnectionParams, RunQueryOnConnectionMode } from 'sql/parts/connection/common/connectionManagement';
import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
import { QueryResultsInput } from 'sql/parts/query/common/queryResultsInput';
import { IQueryModelService } from 'sql/parts/query/execution/queryModel';
import { localize } from 'vs/nls';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { Event, Emitter } from 'vs/base/common/event';
import URI from 'vs/base/common/uri';
import { ISelectionData, ExecutionPlanOptions } from 'sqlops';
import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
import { EditorInput, EditorModel, ConfirmResult, EncodingMode, IEncodingSupport } from 'vs/workbench/common/editor';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IConnectionManagementService, IConnectableInput, INewConnectionParams, RunQueryOnConnectionMode } from 'sql/parts/connection/common/connectionManagement';
import { QueryResultsInput } from 'sql/parts/query/common/queryResultsInput';
import { IQueryModelService } from 'sql/parts/query/execution/queryModel';
import { IQueryEditorService } from 'sql/parts/query/common/queryEditorService';
import { ISelectionData, ExecutionPlanOptions } from 'sqlops';
const MAX_SIZE = 13;
function trimTitle(title: string): string {
const length = title.length;
const diff = length - MAX_SIZE;
if (Math.sign(diff) <= 0) {
return title;
} else {
const start = (length / 2) - (diff / 2);
return title.slice(0, start) + '...' + title.slice(start + diff, length);
}
}
/**
* Input for the QueryEditor. This input is simply a wrapper around a QueryResultsInput for the QueryResultsEditor
* and a UntitledEditorInput for the SQL File Editor.
@@ -39,17 +57,16 @@ export class QueryInput extends EditorInput implements IEncodingSupport, IConnec
private _currentEventCallbacks: IDisposable[];
constructor(
private _name: string,
private _description: string,
private _sql: UntitledEditorInput,
private _results: QueryResultsInput,
private _connectionProviderName: string,
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
@IQueryModelService private _queryModelService: IQueryModelService,
@IQueryEditorService private _queryEditorService: IQueryEditorService
@IQueryEditorService private _queryEditorService: IQueryEditorService,
@IConfigurationService private _configurationService: IConfigurationService
) {
super();
let self = this;
this._updateTaskbar = new Emitter<void>();
this._showQueryResultsEditor = new Emitter<void>();
this._updateSelection = new Emitter<ISelectionData>();
@@ -66,36 +83,44 @@ export class QueryInput extends EditorInput implements IEncodingSupport, IConnec
// Register callbacks for the Actions
this._toDispose.push(
this._queryModelService.onRunQueryStart(uri => {
if (self.uri === uri) {
self.onRunQuery();
if (this.uri === uri) {
this.onRunQuery();
}
})
);
this._toDispose.push(
this._queryModelService.onRunQueryComplete(uri => {
if (self.uri === uri) {
self.onQueryComplete();
if (this.uri === uri) {
this.onQueryComplete();
}
})
);
}
if (this._connectionManagementService) {
this._toDispose.push(self._connectionManagementService.onDisconnect(result => {
if (result.connectionUri === self.uri) {
self.onDisconnect();
this._toDispose.push(this._connectionManagementService.onDisconnect(result => {
if (result.connectionUri === this.uri) {
this.onDisconnect();
}
}));
if (self.uri) {
if (this.uri) {
if (this._connectionProviderName) {
this._connectionManagementService.doChangeLanguageFlavor(self.uri, 'sql', this._connectionProviderName);
this._connectionManagementService.doChangeLanguageFlavor(this.uri, 'sql', this._connectionProviderName);
} else {
this._connectionManagementService.ensureDefaultLanguageFlavor(self.uri);
this._connectionManagementService.ensureDefaultLanguageFlavor(this.uri);
}
}
}
if (this._configurationService) {
this._configurationService.onDidChangeConfiguration(e => {
if (e.affectedKeys.includes('sql.showConnectionInfoInTitle')) {
this._onDidChangeLabel.fire();
}
});
}
this.onDisconnect();
this.onQueryComplete();
}
@@ -136,11 +161,31 @@ export class QueryInput extends EditorInput implements IEncodingSupport, IConnec
public resolve(refresh?: boolean): TPromise<EditorModel> { return this._sql.resolve(); }
public save(): TPromise<boolean> { return this._sql.save(); }
public isDirty(): boolean { return this._sql.isDirty(); }
public confirmSave(): TPromise<ConfirmResult> { return this._sql.confirmSave(); }
public confirmSave(): TPromise<ConfirmResult> { return this._sql.confirmSave(); }
public getResource(): URI { return this._sql.getResource(); }
public getEncoding(): string { return this._sql.getEncoding(); }
public suggestFileName(): string { return this._sql.suggestFileName(); }
public getName(): string { return this._sql.getName(); }
public getName(): string {
if (this._configurationService.getValue('sql.showConnectionInfoInTitle')) {
let profile = this._connectionManagementService.getConnectionProfile(this.uri);
let title = '';
if (profile) {
if (profile.userName) {
title = `${profile.serverName}.${profile.databaseName} (${profile.userName})`;
} else {
title = `${profile.serverName}.${profile.databaseName} (${profile.authenticationType})`;
}
} else {
title = localize('disconnected', 'disconnected');
}
return this._sql.getName() + ` - ${trimTitle(title)}`;
} else {
return this._sql.getName();
}
}
public get hasAssociatedFilePath(): boolean { return this._sql.hasAssociatedFilePath; }
public setEncoding(encoding: string, mode: EncodingMode /* ignored, we only have Encode */): void {

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);

View File

@@ -93,7 +93,7 @@ export class QueryEditorService implements IQueryEditorService {
//input.resolve().then(model => this.backupFileService.backupResource(resource, model.getValue(), model.getVersionId())).done(null, errors.onUnexpectedError);
const queryResultsInput: QueryResultsInput = this._instantiationService.createInstance(QueryResultsInput, docUri.toString());
let queryInput: QueryInput = this._instantiationService.createInstance(QueryInput, fileInput.getName(), '', fileInput, queryResultsInput, connectionProviderName);
let queryInput: QueryInput = this._instantiationService.createInstance(QueryInput, '', fileInput, queryResultsInput, connectionProviderName);
this._editorService.openEditor(queryInput, { pinned: true })
.then((editor) => {
@@ -113,7 +113,7 @@ export class QueryEditorService implements IQueryEditorService {
const self = this;
return new Promise<any>((resolve, reject) => {
let queryPlanInput: QueryPlanInput = self._instantiationService.createInstance(QueryPlanInput, xmlShowPlan, 'aaa', undefined);
self._editorService.openEditor(queryPlanInput, { pinned: true }, false);
self._editorService.openEditor(queryPlanInput, { pinned: true }, false);
resolve(true);
});
}
@@ -222,8 +222,7 @@ export class QueryEditorService implements IQueryEditorService {
}
let uri: URI = QueryEditorService._getEditorChangeUri(editor.input, changingToSql);
if(uri.scheme === Schemas.untitled && (editor.input instanceof QueryInput || editor.input instanceof EditDataInput))
{
if (uri.scheme === Schemas.untitled && (editor.input instanceof QueryInput || editor.input instanceof EditDataInput)) {
QueryEditorService.notificationService.notify({
severity: Severity.Error,
message: QueryEditorService.CHANGE_UNSUPPORTED_ERROR_MESSAGE
@@ -258,15 +257,15 @@ export class QueryEditorService implements IQueryEditorService {
}
// Close the current editor
QueryEditorService.editorService.closeEditor(position, editor.input).then(() => {
QueryEditorService.editorService.closeEditor(position, editor.input).then(() => {
// Reopen a new editor in the same position/index
QueryEditorService.editorService.openEditor(newEditorInput, options, position).then((editor) => {
QueryEditorService.editorService.openEditor(newEditorInput, options, position).then((editor) => {
resolve(QueryEditorService._onEditorOpened(editor, uri.toString(), position, options.pinned));
},
(error) => {
reject(error);
});
(error) => {
reject(error);
});
});
});
}
@@ -331,10 +330,10 @@ export class QueryEditorService implements IQueryEditorService {
let newEditorInput: IEditorInput = undefined;
if (changingToSql) {
const queryResultsInput: QueryResultsInput = QueryEditorService.instantiationService.createInstance(QueryResultsInput, uri.toString());
let queryInput: QueryInput = QueryEditorService.instantiationService.createInstance(QueryInput, input.getName(), '', input, queryResultsInput, undefined);
let queryInput: QueryInput = QueryEditorService.instantiationService.createInstance(QueryInput, '', input, queryResultsInput, undefined);
newEditorInput = queryInput;
} else {
let uriCopy: URI = URI.from( { scheme: uri.scheme, authority: uri.authority, path: uri.path, query: uri.query, fragment: uri.fragment } );
let uriCopy: URI = URI.from({ scheme: uri.scheme, authority: uri.authority, path: uri.path, query: uri.query, fragment: uri.fragment });
newEditorInput = QueryEditorService.instantiationService.createInstance(FileEditorInput, uriCopy, undefined);
}
@@ -350,7 +349,7 @@ export class QueryEditorService implements IQueryEditorService {
// It is assumed that if we got here, !changingToSql is logically equivalent to changingFromSql
let changingFromSql = !changingToSql;
if (input instanceof QueryInput && changingFromSql) {
let queryInput: QueryInput = <QueryInput> input;
let queryInput: QueryInput = <QueryInput>input;
uriSource = queryInput.sql;
}
return getSupportedInputResource(uriSource);
@@ -378,7 +377,7 @@ export class QueryEditorService implements IQueryEditorService {
// Grab and returns the IModel that will be used to resolve the sqlLanguageModeCheck promise.
let control = editor.getControl();
let codeEditor: CodeEditor = <CodeEditor> control;
let codeEditor: CodeEditor = <CodeEditor>control;
let newModel = codeEditor ? codeEditor.getModel() : undefined;
return newModel;
}

View File

@@ -7,7 +7,6 @@ import { Registry } from 'vs/platform/registry/common/platform';
import { IConfigurationRegistry, Extensions as ConfigurationExtension } from 'vs/platform/configuration/common/configurationRegistry';
import { IJSONSchema, IJSONSchemaMap } from 'vs/base/common/jsonSchema';
import * as nls from 'vs/nls';
import { deepClone } from 'vs/base/common/objects';
import { IExtensionPointUser, ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry';
import { ProviderProperties } from 'sql/parts/dashboard/widgets/properties/propertiesWidget.component';
@@ -22,11 +21,13 @@ export const Extensions = {
export interface IDashboardTab {
id: string;
title: string;
provider: string | string[];
publisher: string;
description?: string;
container?: object;
when?: string;
alwaysShow?: boolean;
isHomeTab?: boolean;
}
export interface IDashboardRegistry {

View File

@@ -26,10 +26,11 @@ import { InputBox } from 'sql/base/browser/ui/inputBox/inputBox.component';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { Registry } from 'vs/platform/registry/common/platform';
/* Model-backed components */
let extensionComponents = Registry.as<IComponentRegistry>(Extensions.ComponentContribution).getAllCtors();
export const DialogModule = (params, selector: string, instantiationService: IInstantiationService): any => {
/* Model-backed components */
let extensionComponents = Registry.as<IComponentRegistry>(Extensions.ComponentContribution).getAllCtors();
@NgModule({
declarations: [
Checkbox,

View File

@@ -6,29 +6,41 @@
'use strict';
import 'vs/css!./media/dialogModal';
import { Component, AfterContentInit, ViewChild, Input, Inject, forwardRef, ElementRef } from '@angular/core';
import { Component, ViewChild, Inject, forwardRef, ElementRef, AfterViewInit } from '@angular/core';
import { ModelViewContent } from 'sql/parts/modelComponents/modelViewContent.component';
import { IBootstrapParams } from 'sql/services/bootstrap/bootstrapService';
import { DialogPane } from 'sql/platform/dialog/dialogPane';
import { ComponentEventType } from 'sql/parts/modelComponents/interfaces';
import { Event, Emitter } from 'vs/base/common/event';
import { ComponentEventType } from '../../parts/modelComponents/interfaces';
export interface DialogComponentParams extends IBootstrapParams {
modelViewId: string;
validityChangedCallback: (valid: boolean) => void;
onLayoutRequested: Event<string>;
dialogPane: DialogPane;
}
@Component({
selector: 'dialog-modelview-container',
providers: [],
template: `
<modelview-content [modelViewId]="modelViewId">
<div class="dialogContainer" *ngIf="_dialogPane && _dialogPane.displayPageTitle">
<div class="dialogModal-wizardHeader" *ngIf="_dialogPane && _dialogPane.displayPageTitle">
<div *ngIf="_dialogPane.pageNumber" class="wizardPageNumber">Step {{_dialogPane.pageNumber}}</div>
<h1 class="wizardPageTitle">{{_dialogPane.title}}</h1>
<div *ngIf="_dialogPane.description">{{_dialogPane.description}}</div>
</div>
<modelview-content [modelViewId]="modelViewId">
</modelview-content>
</div>
<modelview-content [modelViewId]="modelViewId" *ngIf="!_dialogPane || !_dialogPane.displayPageTitle">
</modelview-content>
`
})
export class DialogContainer implements AfterContentInit {
export class DialogContainer implements AfterViewInit {
private _onResize = new Emitter<void>();
public readonly onResize: Event<void> = this._onResize.event;
private _dialogPane: DialogPane;
public modelViewId: string;
@ViewChild(ModelViewContent) private _modelViewContent: ModelViewContent;
@@ -41,11 +53,12 @@ export class DialogContainer implements AfterContentInit {
this.layout();
}
});
this._dialogPane = this._params.dialogPane;
}
ngAfterContentInit(): void {
ngAfterViewInit(): void {
this._modelViewContent.onEvent(event => {
if (event.eventType === ComponentEventType.validityChanged) {
if (event.isRootComponent && event.eventType === ComponentEventType.validityChanged) {
this._params.validityChangedCallback(event.args);
}
});

View File

@@ -115,7 +115,7 @@ export class DialogModal extends Modal {
});
this._dialogPane = new DialogPane(this._dialog.title, this._dialog.content,
valid => this._dialog.notifyValidityChanged(valid), this._instantiationService);
valid => this._dialog.notifyValidityChanged(valid), this._instantiationService, false);
this._dialogPane.createBody(body);
}

View File

@@ -10,12 +10,11 @@ import 'vs/css!./media/dialogModal';
import { NgModuleRef } from '@angular/core';
import { IModalDialogStyles } from 'sql/base/browser/ui/modal/modal';
import { Dialog, DialogTab } from 'sql/platform/dialog/dialogTypes';
import { DialogTab } from 'sql/platform/dialog/dialogTypes';
import { TabbedPanel, IPanelTab, IPanelView } from 'sql/base/browser/ui/panel/panel';
import { bootstrapAngular } from 'sql/services/bootstrap/bootstrapService';
import { DialogModule } from 'sql/platform/dialog/dialog.module';
import { DialogComponentParams } from 'sql/platform/dialog/dialogContainer.component';
import { DialogMessage } from 'sql/workbench/api/common/sqlExtHostTypes';
import * as DOM from 'vs/base/browser/dom';
import { Builder } from 'vs/base/browser/builder';
@@ -31,24 +30,21 @@ export class DialogPane extends Disposable implements IThemable {
// Validation
private _modelViewValidityMap = new Map<string, boolean>();
// HTML Elements
private _body: HTMLElement;
private _tabBar: HTMLElement;
private _tabs: HTMLElement[];
private _tabContent: HTMLElement[];
private _selectedTabIndex: number = 0; //TODO: can be an option
private _onTabChange = new Emitter<string>();
private _selectedTabContent: string;
public pageNumber?: number;
constructor(
private _title: string,
public title: string,
private _content: string | DialogTab[],
private _validityChangedCallback: (valid: boolean) => void,
private _instantiationService: IInstantiationService
private _instantiationService: IInstantiationService,
public displayPageTitle: boolean,
public description?: string,
) {
super();
this._tabs = [];
this._tabContent = [];
}
public createBody(container: HTMLElement): HTMLElement {
@@ -73,7 +69,7 @@ export class DialogPane extends Disposable implements IThemable {
});
this._tabbedPanel.pushTab({
title: tab.title,
identifier: 'dialogPane.' + this._title + '.' + tabIndex,
identifier: 'dialogPane.' + this.title + '.' + tabIndex,
view: {
render: (container) => {
if (tabContainer.parentElement === this._body) {
@@ -93,12 +89,12 @@ export class DialogPane extends Disposable implements IThemable {
}
private getTabDimension(): DOM.Dimension {
return new DOM.Dimension(DOM.getContentWidth(this._body), DOM.getContentHeight(this._body));
return new DOM.Dimension(DOM.getContentWidth(this._body) - 5, DOM.getContentHeight(this._body) - 5);
}
public layout(): void {
if (this._tabbedPanel) {
this._tabbedPanel.layout(new DOM.Dimension(DOM.getContentWidth(this._body), DOM.getContentHeight(this._body)));
this._tabbedPanel.layout(this.getTabDimension());
this._onTabChange.fire(this._selectedTabContent);
}
}
@@ -119,7 +115,8 @@ export class DialogPane extends Disposable implements IThemable {
tab.notifyValidityChanged(valid);
}
},
onLayoutRequested: this._onTabChange.event
onLayoutRequested: this._onTabChange.event,
dialogPane: this
} as DialogComponentParams,
undefined,
(moduleRef) => {

View File

@@ -135,6 +135,7 @@ export class DialogButton implements sqlops.window.modelviewdialog.Button {
export class WizardPage extends DialogTab {
public customButtons: DialogButton[];
private _enabled: boolean;
private _description: string;
private _onUpdate: Emitter<void> = new Emitter<void>();
public readonly onUpdate: Event<void> = this._onUpdate.event;
@@ -150,6 +151,15 @@ export class WizardPage extends DialogTab {
this._enabled = enabled;
this._onUpdate.fire();
}
public get description(): string {
return this._description;
}
public set description(description: string) {
this._description = description;
this._onUpdate.fire();
}
}
export class Wizard {
@@ -171,6 +181,7 @@ export class Wizard {
private _onMessageChange = new Emitter<DialogMessage>();
public readonly onMessageChange = this._onMessageChange.event;
private _message: DialogMessage;
public displayPageTitles: boolean;
constructor(public title: string) { }

View File

@@ -30,3 +30,19 @@
.footer-button.dialogModal-hidden {
margin: 0;
}
.dialogModal-wizardHeader {
padding: 10px 30px;
}
.dialogModal-wizardHeader h1 {
margin-top: 10px;
margin-bottom: 3px;
font-size: 1.5em;
font-weight: lighter;
}
.dialogContainer {
display: flex;
flex-direction: column
}

View File

@@ -133,18 +133,28 @@ export class WizardModal extends Modal {
});
this._wizard.onPageAdded(page => {
this.registerPage(page);
this.updatePageNumbers();
this.showPage(this._wizard.currentPage, false);
});
this._wizard.onPageRemoved(page => {
let dialogPane = this._dialogPanes.get(page);
this._dialogPanes.delete(page);
this.updatePageNumbers();
this.showPage(this._wizard.currentPage, false);
dialogPane.dispose();
});
this.updatePageNumbers();
}
private updatePageNumbers(): void {
this._wizard.pages.forEach((page, index) => {
let dialogPane = this._dialogPanes.get(page);
dialogPane.pageNumber = index + 1;
});
}
private registerPage(page: WizardPage): void {
let dialogPane = new DialogPane(page.title, page.content, valid => page.notifyValidityChanged(valid), this._instantiationService);
let dialogPane = new DialogPane(page.title, page.content, valid => page.notifyValidityChanged(valid), this._instantiationService, this._wizard.displayPageTitles, page.description);
dialogPane.createBody(this._body);
this._dialogPanes.set(page, dialogPane);
page.onUpdate(() => this.setButtonsForPage(this._wizard.currentPage));

View File

@@ -63,7 +63,7 @@ export class MetadataService implements IMetadataService {
}
}
return Promise.resolve(undefined);
return Promise.resolve([]);
}
public getTableInfo(connectionUri: string, metadata: sqlops.ObjectMetadata): Thenable<sqlops.ColumnMetadata[]> {

View File

@@ -6,7 +6,8 @@
'use strict';
import * as sqlops from 'sqlops';
import { IItemConfig, IComponentShape } from 'sql/workbench/api/common/sqlExtHostTypes';
import { Event, Emitter } from 'vs/base/common/event';
import { IComponentEventArgs } from 'sql/parts/modelComponents/interfaces';
import { Event } from 'vs/base/common/event';
export interface IView {
readonly id: string;
@@ -14,6 +15,10 @@ export interface IView {
readonly serverInfo: sqlops.ServerInfo;
}
export interface IModelViewEventArgs extends IComponentEventArgs {
isRootComponent: boolean;
}
export interface IModelView extends IView {
initializeModel(rootComponent: IComponentShape, validationCallback?: (componentId: string) => Thenable<boolean>): void;
clearContainer(componentId: string): void;
@@ -21,7 +26,7 @@ export interface IModelView extends IView {
setLayout(componentId: string, layout: any): void;
setProperties(componentId: string, properties: { [key: string]: any }): void;
registerEvent(componentId: string);
onEvent: Event<any>;
onEvent: Event<IModelViewEventArgs>;
validate(componentId: string): Thenable<boolean>;
readonly onDestroy: Event<void>;
}

152
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,44 @@ declare module 'sqlops' {
wmiEvent = 4
}
export enum JobCompletionActionCondition {
Never = 0,
OnSuccess = 1,
OnFailure = 2,
Always = 3
}
export enum FrequencyTypes {
Unknown ,
OneTime = 1 << 1,
Daily = 1 << 2,
Weekly = 1 << 3,
Monthly = 1 << 4,
MonthlyRelative = 1 << 5,
AutoStart = 1 << 6,
OnIdle = 1 << 7
}
export enum FrequencySubDayTypes {
Unknown = 0,
Once = 1,
Second = 2,
Minute = 4,
Hour = 8
}
export enum FrequencyRelativeIntervals {
First = 1,
Second = 2,
Third = 4,
Fourth = 8,
Last = 16
}
export interface AgentJobInfo {
name: string;
owner: string;
description: string;
currentExecutionStatus: number;
lastRunOutcome: number;
currentExecutionStep: string;
@@ -1070,9 +1112,38 @@ 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 {
id: number;
name: string;
jobName: string;
isEnabled: boolean;
frequencyTypes: FrequencyTypes;
frequencySubDayTypes: FrequencySubDayTypes;
frequencySubDayInterval: number;
frequencyRelativeIntervals; FrequencyRelativeIntervals;
frequencyRecurrenceFactor: number;
frequencyInterval: number;
dateCreated: string;
activeStartTimeOfDay: string;
activeStartDate: string;
activeEndTimeOfDay: string;
jobCount: number;
activeEndDate: string;
scheduleUid: string;
}
export interface AgentJobStep {
jobId: string;
stepId: string;
stepName: string;
@@ -1081,6 +1152,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 +1196,7 @@ declare module 'sqlops' {
operatorPaged: string;
retriesAttempted: string;
server: string;
steps: AgentJobStepInfo[];
steps: AgentJobStep[];
}
export interface AgentProxyInfo {
@@ -1113,6 +1211,7 @@ declare module 'sqlops' {
export interface AgentAlertInfo {
id: number;
name: string;
delayBetweenResponses: number;
eventDescriptionKeyword: string;
eventSource: string;
@@ -1173,15 +1272,26 @@ declare module 'sqlops' {
job: AgentJobInfo;
}
export interface UpdateAgentJobResult extends ResultStatus {
export interface UpdateAgentJobResult extends ResultStatus {
job: AgentJobInfo;
}
export interface AgentJobCategory
{
id: string;
name: string;
}
export interface AgentJobDefaultsResult extends ResultStatus {
owner: string;
categories: AgentJobCategory[];
}
export interface CreateAgentJobStepResult extends ResultStatus {
step: AgentJobStepInfo;
}
export interface UpdateAgentJobStepResult extends ResultStatus {
export interface UpdateAgentJobStepResult extends ResultStatus {
step: AgentJobStepInfo;
}
@@ -1189,7 +1299,7 @@ declare module 'sqlops' {
step: AgentJobStepInfo;
}
export interface UpdateAgentProxyResult extends ResultStatus {
export interface UpdateAgentProxyResult extends ResultStatus {
step: AgentJobStepInfo;
}
@@ -1201,7 +1311,7 @@ declare module 'sqlops' {
alert: AgentJobStepInfo;
}
export interface UpdateAgentAlertResult extends ResultStatus {
export interface UpdateAgentAlertResult extends ResultStatus {
alert: AgentJobStepInfo;
}
@@ -1213,20 +1323,32 @@ declare module 'sqlops' {
operator: AgentOperatorInfo;
}
export interface UpdateAgentOperatorResult extends ResultStatus {
export interface UpdateAgentOperatorResult extends ResultStatus {
operator: AgentOperatorInfo;
}
export interface AgentProxiesResult extends ResultStatus {
operators: AgentOperatorInfo[];
proxies: AgentProxyInfo[];
}
export interface CreateAgentProxyResult extends ResultStatus {
operator: AgentOperatorInfo;
proxy: AgentProxyInfo;
}
export interface UpdateAgentProxyResult extends ResultStatus {
operator: AgentOperatorInfo;
export interface UpdateAgentProxyResult extends ResultStatus {
proxy: AgentProxyInfo;
}
export interface AgentJobSchedulesResult extends ResultStatus {
schedules: AgentJobScheduleInfo[];
}
export interface CreateAgentJobScheduleResult extends ResultStatus {
schedule: AgentJobScheduleInfo;
}
export interface UpdateAgentJobScheduleResult extends ResultStatus {
schedule: AgentJobScheduleInfo;
}
export interface AgentServicesProvider extends DataProvider {
@@ -1237,6 +1359,7 @@ declare module 'sqlops' {
createJob(ownerUri: string, jobInfo: AgentJobInfo): Thenable<CreateAgentJobResult>;
updateJob(ownerUri: string, originalJobName: string, jobInfo: AgentJobInfo): Thenable<UpdateAgentJobResult>;
deleteJob(ownerUri: string, jobInfo: AgentJobInfo): Thenable<ResultStatus>;
getJobDefaults(ownerUri: string): Thenable<AgentJobDefaultsResult>;
// Job Step management methods
createJobStep(ownerUri: string, jobInfo: AgentJobStepInfo): Thenable<CreateAgentJobStepResult>;
@@ -1260,6 +1383,13 @@ declare module 'sqlops' {
createProxy(ownerUri: string, proxyInfo: AgentProxyInfo): Thenable<CreateAgentOperatorResult>;
updateProxy(ownerUri: string, originalProxyName: string, proxyInfo: AgentProxyInfo): Thenable<UpdateAgentOperatorResult>;
deleteProxy(ownerUri: string, proxyInfo: AgentProxyInfo): Thenable<ResultStatus>;
// Job Schedule management methods
getJobSchedules(ownerUri: string): Thenable<AgentJobSchedulesResult>;
createJobSchedule(ownerUri: string, scheduleInfo: AgentJobScheduleInfo): Thenable<CreateAgentJobScheduleResult>;
updateJobSchedule(ownerUri: string, originalScheduleName: string, scheduleInfo: AgentJobScheduleInfo): Thenable<UpdateAgentJobScheduleResult>;
deleteJobSchedule(ownerUri: string, scheduleInfo: AgentJobScheduleInfo): Thenable<ResultStatus>;
}
// Task service interfaces ----------------------------------------------------------------------------

View File

@@ -133,6 +133,7 @@ declare module 'sqlops' {
component: Component;
title: string;
actions?: Component[];
required?: boolean;
}
export interface ToolbarComponent {
@@ -240,7 +241,6 @@ declare module 'sqlops' {
componentWidth?: number | string;
componentHeight?: number | string;
titleFontSize?: number | string;
required?: boolean;
info?: string;
}
@@ -301,7 +301,7 @@ declare module 'sqlops' {
}
export enum CardType {
VerticalButton = 'VerticalButton',
VerticalButton = 'VerticalButton',
Details = 'Details'
}
@@ -314,8 +314,16 @@ declare module 'sqlops' {
value?: string;
actions?: ActionDescriptor[];
status?: StatusIndicator;
/**
* Returns true if the card is selected
*/
selected?: boolean;
cardType: CardType;
/**
* Card Type, default: Details
*/
cardType?: CardType;
}
export type InputBoxInputType = 'color' | 'date' | 'datetime-local' | 'email' | 'month' | 'number' | 'password' | 'range' | 'search' | 'text' | 'time' | 'url' | 'week';
@@ -748,13 +756,18 @@ declare module 'sqlops' {
* able to advance to it. Defaults to true.
*/
enabled: boolean;
/**
* An optional description for the page. If provided it will be displayed underneath the page title.
*/
description: string;
}
export interface Wizard {
/**
* The title of the wizard
*/
title: string,
title: string;
/**
* The wizard's pages. Pages can be added/removed while the dialog is open by using
@@ -800,6 +813,12 @@ declare module 'sqlops' {
*/
customButtons: Button[];
/**
* When set to false page titles and descriptions will not be displayed at the top
* of each wizard page. The default is true.
*/
displayPageTitles: boolean;
/**
* Event fired when the wizard's page changes, containing information about the
* previous page and the new page

View File

@@ -63,8 +63,7 @@ export enum ScriptOperation {
Alter = 6
}
export enum WeekDays
{
export enum WeekDays {
sunday = 1,
monday = 2,
tuesday = 4,
@@ -77,8 +76,7 @@ export enum WeekDays
everyDay = 127
}
export enum NotifyMethods
{
export enum NotifyMethods {
none = 0,
notifyEmail = 1,
pager = 2,
@@ -86,14 +84,47 @@ export enum NotifyMethods
notifyAll = 7
}
export enum AlertType
{
export enum JobCompletionActionCondition {
Never = 0,
OnSuccess = 1,
OnFailure = 2,
Always = 3
}
export enum AlertType {
sqlServerEvent = 1,
sqlServerPerformanceCondition = 2,
nonSqlServerEvent = 3,
wmiEvent = 4
}
export enum FrequencyTypes {
Unknown ,
OneTime = 1 << 1,
Daily = 1 << 2,
Weekly = 1 << 3,
Monthly = 1 << 4,
MonthlyRelative = 1 << 5,
AutoStart = 1 << 6,
OnIdle = 1 << 7
}
export enum FrequencySubDayTypes {
Unknown = 0,
Once = 1,
Second = 2,
Minute = 4,
Hour = 8
}
export enum FrequencyRelativeIntervals {
First = 1,
Second = 2,
Third = 4,
Fourth = 8,
Last = 16
}
export enum ModelComponentTypes {
NavContainer,
FlexContainer,
@@ -168,6 +199,7 @@ export interface IModelViewWizardPageDetails {
content: string;
enabled: boolean;
customButtons: number[];
description: string;
}
export interface IModelViewWizardDetails {
@@ -181,6 +213,7 @@ export interface IModelViewWizardDetails {
backButton: number;
customButtons: number[];
message: DialogMessage;
displayPageTitles: boolean;
}
export enum MessageLevel {

Some files were not shown because too many files have changed in this diff Show More