Initial SQL Agent merge for March release (#961)

* WIP

* wip

* SQL Agent wip

* wip

* Initial control host (wip)

* Initial hookup of SQL Agent service to job component

* Update agent package.json

* Hook up getJobs call

* A couple job view updates

* Add some more agent views

* Rename some 'agent' classes to 'jobManagement'

* job history page (#852)

* added back button, run actions and overview accordion

* refactoring

* overview table complete

* fixed the dropdown arrow for the overview section

* added table for prev job list

* fixed agent job result type

* code cleaning and code review comments

* fixed yarn.lock conflicts

* added function for job history

* changed vscode-languageclient version

* changed yarn lock file

* fixed yarn lock file

* fixed yarn file

* fixed css paths

* added images to packaging step

* fix resource path for packaging

* Switch back getJobs return type

* Make enum const

* Remove sqlops const

* WIP

* WIP

* Feature/agent1 adbist (#899)

* added back button, run actions and overview accordion

* refactoring

* overview table complete

* fixed the dropdown arrow for the overview section

* added table for prev job list

* fixed agent job result type

* code cleaning and code review comments

* fixed yarn.lock conflicts

* added function for job history

* changed vscode-languageclient version

* changed yarn lock file

* fixed yarn lock file

* fixed yarn file

* fixed css paths

* added images to packaging step

* fix resource path for packaging

* added steps lists

* fixed style and dimensions

* fixed conflicts

* implemented job list

* added the Date and Status columns

* update yarn files

* merged feature/agent1

* added theme styling for light theme

* changed yarn lock files

* made job history page css more specific

* Add visiblity check to job view

* Clean up jobs styling and call getJobHistory

* Add more Job Table styling

* Enable detail view in job table

* Use updated slickgrid repo

* vbumped slickgrid

* Convert rowdetail slickgrid plug to TypeScript

* Feature/agent1 adbist (#945)

* added back button, run actions and overview accordion

* refactoring

* overview table complete

* fixed the dropdown arrow for the overview section

* added table for prev job list

* fixed agent job result type

* code cleaning and code review comments

* fixed yarn.lock conflicts

* added function for job history

* changed vscode-languageclient version

* changed yarn lock file

* fixed yarn lock file

* fixed yarn file

* fixed css paths

* added images to packaging step

* fix resource path for packaging

* added steps lists

* fixed style and dimensions

* fixed conflicts

* implemented job list

* added the Date and Status columns

* update yarn files

* merged feature/agent1

* added theme styling for light theme

* changed yarn lock files

* added method signatures for job history with DMP

* added methods for job running

* added job actions to sqlops

* Refer to dataprotocol from feature/agentDmp1 branch

* Update SQL Tools version to 1.4.0-alpha.13

* Change Feb to March in release note prompt

* SQL Agent extension metadata

* add feature explicitly in client creation

* Update Agent job registration

* Update package.json

* Feature/agent1 adbist (#955)

* added back button, run actions and overview accordion

* refactoring

* overview table complete

* fixed the dropdown arrow for the overview section

* added table for prev job list

* fixed agent job result type

* code cleaning and code review comments

* fixed yarn.lock conflicts

* added function for job history

* changed vscode-languageclient version

* changed yarn lock file

* fixed yarn lock file

* fixed yarn file

* fixed css paths

* added images to packaging step

* fix resource path for packaging

* added steps lists

* fixed style and dimensions

* fixed conflicts

* implemented job list

* added the Date and Status columns

* update yarn files

* merged feature/agent1

* added theme styling for light theme

* changed yarn lock files

* added method signatures for job history with DMP

* added methods for job running

* added job actions to sqlops

* navigation works but is really slow to load data

* Add jobs view icon

* Misc. cleanups
This commit is contained in:
Karl Burtram
2018-03-23 13:27:55 -07:00
committed by GitHub
parent 67a9ff3e16
commit 357bb1916e
59 changed files with 4741 additions and 275 deletions

View File

@@ -0,0 +1,393 @@
// Adopted and converted to typescript from https://github.com/6pac/SlickGrid/blob/master/plugins/slick.rowdetailview.js
// heavily modified
import { mixin } from 'vs/base/common/objects';
export class RowDetailView {
public onAsyncResponse = new Slick.Event<any>();
public onAsyncEndUpdate = new Slick.Event<any>();
public onAfterRowDetailToggle = new Slick.Event<any>();
public onBeforeRowDetailToggle = new Slick.Event<any>();
private _grid: any;
private _expandedRows: any = [];
private _handler = new Slick.EventHandler();
private _defaults: any = {
columnId: '_detail_selector',
cssClass: null,
toolTip: '',
width: 30
};
private _dataView: any;
private _options: any;
constructor(options) {
this._options = mixin(options, this._defaults, false);
}
public init(grid: any) {
this._grid = grid;
this._dataView = this._grid.getData();
// Update the minRowBuffer so that the view doesn't disappear when it's at top of screen + the original default 3
this._grid.getOptions().minRowBuffer = this._options.panelRows + 3;
this._handler
.subscribe(this._grid.onClick, (e, args) => this.handleClick(e, args))
.subscribe(this._grid.onSort, (e, args) => this.handleSort(e, args))
.subscribe(this._grid.onScroll, (e, args) => this.handleScroll(e, args));
this._grid.getData().onRowCountChanged.subscribe(() => { this._grid.updateRowCount(); this._grid.render(); });
this._grid.getData().onRowsChanged.subscribe((e, a) => { this._grid.invalidateRows(a.rows); this._grid.render(); });
// subscribe to the onAsyncResponse so that the plugin knows when the user server side calls finished
this.subscribeToOnAsyncResponse();
}
public destroy() {
this._handler.unsubscribeAll();
this.onAsyncResponse.unsubscribe(undefined);
this.onAsyncEndUpdate.unsubscribe(undefined);
this.onAfterRowDetailToggle.unsubscribe(undefined);
this.onBeforeRowDetailToggle.unsubscribe(undefined);
}
public getOptions(options: any) {
return this._options;
}
public setOptions(options: any) {
this._options = $.extend(true, {}, this._options, options);
}
public handleClick(e: any, args: any) {
// clicking on a row select checkbox
if (this._options.useRowClick || this._grid.getColumns()[args.cell].id === this._options.columnId && $(e.target).hasClass("detailView-toggle")) {
// if editing, try to commit
if (this._grid.getEditorLock().isActive() && !this._grid.getEditorLock().commitCurrentEdit()) {
e.preventDefault();
e.stopImmediatePropagation();
return;
}
var item = this._dataView.getItem(args.row);
// trigger an event before toggling
this.onBeforeRowDetailToggle.notify({
grid: this._grid,
item: item
}, e, this);
this.toggleRowSelection(item);
// trigger an event after toggling
this.onAfterRowDetailToggle.notify({
grid: this._grid,
item: item
}, e, this);
e.stopPropagation();
e.stopImmediatePropagation();
}
}
// Sort will just collapse all of the open items
public handleSort(e, args) {
this.collapseAll();
}
// If we scroll save detail views that go out of cache range
public handleScroll(e, args) {
var range = this._grid.getRenderedRange();
var start = (range.top > 0 ? range.top : 0);
var end = (range.bottom > this._dataView.getLength() ? range.bottom : this._dataView.getLength());
// Get the item at the top of the view
var topMostItem = this._dataView.getItemByIdx(start);
// Check it is a parent item
if (topMostItem._parent === undefined)
{
// This is a standard row as we have no parent.
var nextItem = this._dataView.getItemByIdx(start + 1);
if(nextItem !== undefined && nextItem._parent !== undefined)
{
// This is likely the expanded Detail Row View
// Check for safety
if(nextItem._parent === topMostItem)
{
this.saveDetailView(topMostItem);
}
}
}
// Find the bottom most item that is likely to go off screen
var bottomMostItem = this._dataView.getItemByIdx(end - 1);
// If we are a detailView and we are about to go out of cache view
if(bottomMostItem._parent !== undefined)
{
this.saveDetailView(bottomMostItem._parent);
}
}
// Toggle between showing and hiding a row
public toggleRowSelection(row) {
this._grid.getData().beginUpdate();
this.handleAccordionShowHide(row);
this._grid.getData().endUpdate();
}
// Collapse all of the open items
public collapseAll() {
for (var i = this._expandedRows.length - 1; i >= 0; i--) {
this.collapseItem(this._expandedRows[i]);
}
}
// Saves the current state of the detail view
public saveDetailView(item) {
var view = $('#innerDetailView_' + item.id);
if (view) {
var html = $('#innerDetailView_' + item.id).html();
if(html !== undefined) {
item._detailContent = html;
}
}
}
// Colapse an Item so it is notlonger seen
public collapseItem(item) {
// Save the details on the collapse assuming onetime loading
if (this._options.loadOnce) {
this.saveDetailView(item);
}
item._collapsed = true;
for (let idx = 1; idx <= item._sizePadding; idx++) {
this._dataView.deleteItem(item.id + '.' + idx);
}
item._sizePadding = 0;
this._dataView.updateItem(item.id, item);
// Remove the item from the expandedRows
this._expandedRows = this._expandedRows.filter((r) => {
return r.id !== item.id;
});
}
// Expand a row given the dataview item that is to be expanded
public expandItem(item) {
item._collapsed = false;
this._expandedRows.push(item);
// In the case something went wrong loading it the first time such a scroll of screen before loaded
if (!item._detailContent) {
item._detailViewLoaded = false;
}
// display pre-loading template
if (!item._detailViewLoaded || this._options.loadOnce !== true) {
item._detailContent = this._options.preTemplate(item);
} else {
this.onAsyncResponse.notify({
itemDetail: item,
detailView: item._detailContent
}, undefined, this);
this.applyTemplateNewLineHeight(item);
this._dataView.updateItem(item.id, item);
return;
}
this.applyTemplateNewLineHeight(item);
this._dataView.updateItem(item.id, item);
// async server call
this._options.process(item);
}
/**
* subscribe to the onAsyncResponse so that the plugin knows when the user server side calls finished
* the response has to be as "args.itemDetail" with it's data back
*/
public subscribeToOnAsyncResponse() {
this.onAsyncResponse.subscribe((e, args) => {
if (!args || !args.itemDetail) {
throw 'Slick.RowDetailView plugin requires the onAsyncResponse() to supply "args.itemDetail" property.';
}
// If we just want to load in a view directly we can use detailView property to do so
if (args.detailView) {
args.itemDetail._detailContent = args.detailView;
} else {
args.itemDetail._detailContent = this._options.postTemplate(args.itemDetail);
}
args.itemDetail._detailViewLoaded = true;
var idxParent = this._dataView.getIdxById(args.itemDetail.id);
this._dataView.updateItem(args.itemDetail.id, args.itemDetail);
// trigger an event once the post template is finished loading
this.onAsyncEndUpdate.notify({
grid: this._grid,
itemDetail: args.itemDetail
}, e, this);
});
}
public handleAccordionShowHide(item) {
if (item) {
if (!item._collapsed) {
this.collapseItem(item);
} else {
this.expandItem(item);
}
}
}
//////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////
public getPaddingItem(parent, offset) {
var item: any = {};
for (let prop in this._grid.getData()) {
item[prop] = null;
}
item.id = parent.id + '.' + offset;
//additional hidden padding metadata fields
item._collapsed = true;
item._isPadding = true;
item._parent = parent;
item._offset = offset;
return item;
}
//////////////////////////////////////////////////////////////
//create the detail ctr node. this belongs to the dev & can be custom-styled as per
//////////////////////////////////////////////////////////////
public applyTemplateNewLineHeight(item) {
// the height seems to be calculated by the template row count (how many line of items does the template have)
let rowCount = this._options.panelRows;
//calculate padding requirements based on detail-content..
//ie. worst-case: create an invisible dom node now &find it's height.
let lineHeight = 13; //we know cuz we wrote the custom css innit ;)
item._sizePadding = Math.ceil(((rowCount * 2) * lineHeight) / this._grid.getOptions().rowHeight);
item._height = (item._sizePadding * this._grid.getOptions().rowHeight);
let idxParent = this._dataView.getIdxById(item.id);
for (var idx = 1; idx <= item._sizePadding; idx++) {
this._dataView.insertItem(idxParent + idx, this.getPaddingItem(item, idx));
}
}
public getColumnDefinition() {
return {
id: this._options.columnId,
name: '',
toolTip: this._options.toolTip,
field: 'sel',
width: this._options.width,
resizable: false,
sortable: false,
cssClass: this._options.cssClass,
formatter: (row, cell, value, columnDef, dataContext) => this.detailSelectionFormatter(row, cell, value, columnDef, dataContext)
};
}
public detailSelectionFormatter(row, cell, value, columnDef, dataContext) {
if (dataContext._collapsed === undefined) {
dataContext._collapsed = true,
dataContext._sizePadding = 0, //the required number of pading rows
dataContext._height = 0, //the actual height in pixels of the detail field
dataContext._isPadding = false,
dataContext._parent = undefined,
dataContext._offset = 0
}
if (dataContext._isPadding === true) {
//render nothing
} else if (dataContext._collapsed) {
return '<div class=\'detailView-toggle expand\'></div>';
} else {
var html = [];
var rowHeight = this._grid.getOptions().rowHeight;
var bottomMargin = 5;
//V313HAX:
//putting in an extra closing div after the closing toggle div and ommiting a
//final closing div for the detail ctr div causes the slickgrid renderer to
//insert our detail div as a new column ;) ~since it wraps whatever we provide
//in a generic div column container. so our detail becomes a child directly of
//the row not the cell. nice =) ~no need to apply a css change to the parent
//slick-cell to escape the cell overflow clipping.
//sneaky extra </div> inserted here-----------------v
html.push("<div class='detailView-toggle collapse'></div></div>");
html.push("<div id='cellDetailView_", dataContext.id, "' class='dynamic-cell-detail' "); //apply custom css to detail
html.push("style='height:", dataContext._height, "px;"); //set total height of padding
html.push("top:", rowHeight, "px'>"); //shift detail below 1st row
html.push("<div id='detailViewContainer_", dataContext.id, "' class='detail-container' style='max-height:" + (dataContext._height - rowHeight + bottomMargin) + "px'>"); //sub ctr for custom styling
html.push("<div id='innerDetailView_" , dataContext.id , "'>" , dataContext._detailContent, "</div></div>");
//&omit a final closing detail container </div> that would come next
return html.join('');
}
return null;
}
public resizeDetailView(item) {
if (!item) return;
// Grad each of the dom items
var mainContainer = document.getElementById('detailViewContainer_' + item.id);
var cellItem = document.getElementById('cellDetailView_' + item.id);
var inner = document.getElementById('innerDetailView_' + item.id);
if (!mainContainer || !cellItem || !inner) return;
for (var idx = 1; idx <= item._sizePadding; idx++) {
this._dataView.deleteItem(item.id + "." + idx);
}
var rowHeight = this._grid.getOptions().rowHeight; // height of a row
var lineHeight = 13; //we know cuz we wrote the custom css innit ;)
// Get the inner Item height as this will be the actual size
var itemHeight = inner.clientHeight;
// Now work out how many rows
var rowCount = Math.ceil(itemHeight / rowHeight) + 1;
item._sizePadding = Math.ceil(((rowCount * 2) * lineHeight) / rowHeight);
item._height = (item._sizePadding * rowHeight);
// If the padding is now more than the original minRowBuff we need to increase it
if (this._grid.getOptions().minRowBuffer < item._sizePadding)
{
// Update the minRowBuffer so that the view doesn't disappear when it's at top of screen + the original default 3
this._grid.getOptions().minRowBuffer =item._sizePadding + 3;
}
mainContainer.setAttribute("style", "max-height: " + item._height + "px");
if (cellItem) {
cellItem.setAttribute("style", "height: " + item._height + "px;top:" + rowHeight + "px");
}
let idxParent = this._dataView.getIdxById(item.id);
for (var idx = 1; idx <= item._sizePadding; idx++) {
this._dataView.insertItem(idxParent + idx, this.getPaddingItem(item, idx));
}
}
}

View File

@@ -16,6 +16,7 @@ import { DashboardServiceInterface } from 'sql/parts/dashboard/services/dashboar
import { WIDGETS_CONTAINER } from 'sql/parts/dashboard/containers/dashboardWidgetContainer.contribution';
import { GRID_CONTAINER } from 'sql/parts/dashboard/containers/dashboardGridContainer.contribution';
import { WEBVIEW_CONTAINER } from 'sql/parts/dashboard/containers/dashboardWebviewContainer.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';
@@ -26,6 +27,7 @@ const containerTypes = [
WIDGETS_CONTAINER,
GRID_CONTAINER,
WEBVIEW_CONTAINER,
CONTROLHOST_CONTAINER,
NAV_SECTION
];

View File

@@ -13,6 +13,8 @@
</dashboard-webview-container>
<dashboard-widget-container *ngIf="getContentType(tab) === 'widgets-container'" [tab]="tab">
</dashboard-widget-container>
<dashboard-controlhost-container *ngIf="getContentType(tab) === 'controlhost-container'" [tab]="tab">
</dashboard-controlhost-container>
<dashboard-nav-section *ngIf="getContentType(tab) === 'nav-section'" [tab]="tab">
</dashboard-nav-section>
<dashboard-grid-container *ngIf="getContentType(tab) === 'grid-container'" [tab]="tab">

View File

@@ -0,0 +1,58 @@
/*---------------------------------------------------------------------------------------------
* 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!./dashboardControlHostContainer';
import { Component, forwardRef, Input, AfterContentInit, ViewChild } from '@angular/core';
import Event, { Emitter } from 'vs/base/common/event';
import { DashboardTab } from 'sql/parts/dashboard/common/interfaces';
import { TabConfig } from 'sql/parts/dashboard/common/dashboardWidget';
import { ControlHostContent } from 'sql/parts/dashboard/contents/controlHostContent.component';
@Component({
selector: 'dashboard-controlhost-container',
providers: [{ provide: DashboardTab, useExisting: forwardRef(() => DashboardControlHostContainer) }],
template: `
<controlhost-content [webviewId]="tab.id">
</controlhost-content>
`
})
export class DashboardControlHostContainer extends DashboardTab implements AfterContentInit {
@Input() private tab: TabConfig;
private _onResize = new Emitter<void>();
public readonly onResize: Event<void> = this._onResize.event;
@ViewChild(ControlHostContent) private _hostContent: ControlHostContent;
constructor() {
super();
}
ngAfterContentInit(): void {
this._register(this._hostContent.onResize(() => {
this._onResize.fire();
}));
let container = <any>this.tab.container;
if (container['controlhost-container'] && container['controlhost-container'].type) {
this._hostContent.setControlType(container['controlhost-container'].type);
}
}
public layout(): void {
this._hostContent.layout();
}
public get id(): string {
return this.tab.id;
}
public get editable(): boolean {
return this.tab.editable;
}
public refresh(): void {
// no op
}
}

View File

@@ -0,0 +1,19 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IJSONSchema } from 'vs/base/common/jsonSchema';
import * as nls from 'vs/nls';
import { registerContainerType, registerNavSectionContainerType } from 'sql/platform/dashboard/common/dashboardContainerRegistry';
export const CONTROLHOST_CONTAINER = 'controlhost-container';
let webviewSchema: IJSONSchema = {
type: 'null',
description: nls.localize('dashboard.container.controlhost', "The controlhost that will be displayed in this tab."),
default: null
};
registerContainerType(CONTROLHOST_CONTAINER, webviewSchema);
registerNavSectionContainerType(CONTROLHOST_CONTAINER, webviewSchema);

View File

@@ -0,0 +1,10 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
dashboard-controlhost-container {
height: 100%;
width : 100%;
display: block;
}

View File

@@ -0,0 +1,9 @@
<!--
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
-->
<agentview-component *ngIf="(controlType) === 'agent'">
</agentview-component>

View File

@@ -0,0 +1,76 @@
/*---------------------------------------------------------------------------------------------
* 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!./controlHostContent';
import { Component, forwardRef, Input, OnInit, Inject, ChangeDetectorRef, ElementRef } from '@angular/core';
import Event, { Emitter } from 'vs/base/common/event';
import { Parts } from 'vs/workbench/services/part/common/partService';
import { IDisposable } from 'vs/base/common/lifecycle';
import { DashboardTab } from 'sql/parts/dashboard/common/interfaces';
import { TabConfig } from 'sql/parts/dashboard/common/dashboardWidget';
import { DashboardServiceInterface } from 'sql/parts/dashboard/services/dashboardServiceInterface.service';
import * as sqlops from 'sqlops';
import { memoize } from 'vs/base/common/decorators';
@Component({
templateUrl: decodeURI(require.toUrl('sql/parts/dashboard/contents/controlHostContent.component.html')),
selector: 'controlhost-content'
})
export class ControlHostContent implements OnInit {
@Input() private webviewId: string;
private _onResize = new Emitter<void>();
public readonly onResize: Event<void> = this._onResize.event;
private _onMessage = new Emitter<string>();
public readonly onMessage: Event<string> = this._onMessage.event;
private _onMessageDisposable: IDisposable;
private _type: string;
constructor(
@Inject(forwardRef(() => DashboardServiceInterface)) private _dashboardService: DashboardServiceInterface,
@Inject(forwardRef(() => ChangeDetectorRef)) private _changeRef: ChangeDetectorRef,
@Inject(forwardRef(() => ElementRef)) private _el: ElementRef
) {
}
ngOnInit() {
}
public layout(): void {
}
public get id(): string {
return this.webviewId;
}
@memoize
public get connection(): sqlops.connection.Connection {
let currentConnection = this._dashboardService.connectionManagementService.connectionInfo.connectionProfile;
let connection: sqlops.connection.Connection = {
providerName: currentConnection.providerName,
connectionId: currentConnection.id,
options: currentConnection.options
};
return connection;
}
@memoize
public get serverInfo(): sqlops.ServerInfo {
return this._dashboardService.connectionManagementService.connectionInfo.serverInfo;
}
public setControlType(type: string): void {
this._type = type;
this._changeRef.detectChanges();
}
public get controlType(): string {
return this._type;
}
}

View File

@@ -0,0 +1,10 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
controlhost-content {
height: 100%;
width : 100%;
display: block;
}

View File

@@ -42,8 +42,16 @@ import { WebviewContent } from 'sql/parts/dashboard/contents/webviewContent.comp
import { BreadcrumbComponent } from 'sql/base/browser/ui/breadcrumb/breadcrumb.component';
import { IBreadcrumbService } from 'sql/base/browser/ui/breadcrumb/interfaces';
import { DashboardHomeContainer } from 'sql/parts/dashboard/containers/dashboardHomeContainer.component';
import { ControlHostContent } from 'sql/parts/dashboard/contents/controlHostContent.component';
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 { JobHistoryComponent } from 'sql/parts/jobManagement/views/jobHistory.component';
let baseComponents = [DashboardHomeContainer, DashboardComponent, DashboardWidgetWrapper, DashboardWebviewContainer, DashboardWidgetContainer, DashboardGridContainer, DashboardErrorContainer, DashboardNavSection, WidgetContent, WebviewContent, ComponentHostDirective, BreadcrumbComponent];
let baseComponents = [DashboardHomeContainer, DashboardComponent, DashboardWidgetWrapper, DashboardWebviewContainer,
DashboardWidgetContainer, DashboardGridContainer, DashboardErrorContainer, DashboardNavSection, WebviewContent, WidgetContent,
ComponentHostDirective, BreadcrumbComponent, ControlHostContent, DashboardControlHostContainer,
JobsViewComponent, AgentViewComponent, JobHistoryComponent];
/* Panel */
import { PanelModule } from 'sql/base/browser/ui/panel/panel.module';

View File

@@ -0,0 +1,18 @@
<!--
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
-->
<panel class="fullsize" [options]="panelOpt">
<tab [title]="jobsComponentTitle" class="fullsize" [identifier]="jobsTabIdentifier"
[iconClass]="jobsIconClass">
<div id="jobsDiv" class="fullsize" *ngIf="showHistory === false">
<jobsview-component ></jobsview-component>
</div>
<div id="historyDiv" class="fullsize" *ngIf="showHistory === true">
<jobhistory-component [jobId]="jobId" [agentJobInfo]="agentJobInfo"></jobhistory-component>
</div>
</tab>
</panel>

View File

@@ -0,0 +1,100 @@
/*---------------------------------------------------------------------------------------------
* 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!../common/media/jobs';
import { OnInit, Component, Inject, forwardRef, ElementRef, ChangeDetectorRef, OnDestroy, ViewChild, Injectable } from '@angular/core';
import * as Utils from 'sql/parts/connection/common/utils';
import { RefreshWidgetAction, EditDashboardAction } from 'sql/parts/dashboard/common/actions';
import { IColorTheme } from 'vs/workbench/services/themes/common/workbenchThemeService';
import { IDisposable } from 'vs/base/common/lifecycle';
import * as themeColors from 'vs/workbench/common/theme';
import { DashboardPage } from 'sql/parts/dashboard/common/dashboardPage.component';
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
import { IBootstrapService, BOOTSTRAP_SERVICE_ID } from 'sql/services/bootstrap/bootstrapService';
import { IJobManagementService } from '../common/interfaces';
import { DashboardServiceInterface } from 'sql/parts/dashboard/services/dashboardServiceInterface.service';
import { AgentJobInfo, AgentJobHistoryInfo } from 'sqlops';
import { PanelComponent, IPanelOptions, NavigationBarLayout } from 'sql/base/browser/ui/panel/panel.component';
import * as nls from 'vs/nls';
export const DASHBOARD_SELECTOR: string = 'agentview-component';
@Component({
selector: DASHBOARD_SELECTOR,
templateUrl: decodeURI(require.toUrl('./agentView.component.html'))
})
@Injectable()
export class AgentViewComponent {
@ViewChild(PanelComponent) private _panel: PanelComponent;
// 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 schedulesComponentTitle: string = nls.localize('jobview.Schedules', "Schedules");
private readonly operatorsComponentTitle: string = nls.localize('jobview.Operator', "Operators");
private readonly jobHistoryComponentTitle: string = nls.localize('jobview.History', "History");
private _showHistory: boolean = false;
private _jobId: string = null;
private _agentJobInfo: AgentJobInfo = null;
private _agentJobHistoryInfo: AgentJobHistoryInfo[] = null;
public jobsIconClass: string = 'jobsview-icon';
// tslint:disable-next-line:no-unused-variable
private readonly panelOpt: IPanelOptions = {
showTabsWhenOne: true,
layout: NavigationBarLayout.vertical,
showIcon: true
};
constructor(
@Inject(forwardRef(() => ChangeDetectorRef)) private _cd: ChangeDetectorRef,
){}
/**
* Public Getters
*/
public get jobId(): string {
return this._jobId;
}
public get showHistory(): boolean {
return this._showHistory;
}
public get agentJobInfo(): AgentJobInfo {
return this._agentJobInfo;
}
public get agentJobHistoryInfo(): AgentJobHistoryInfo[] {
return this._agentJobHistoryInfo;
}
/**
* Public Setters
*/
public set jobId(value: string) {
this._jobId = value;
this._cd.detectChanges();
}
public set showHistory(value: boolean) {
this._showHistory = value;
this._cd.detectChanges();
}
public set agentJobInfo(value: AgentJobInfo) {
this._agentJobInfo = value;
this._cd.detectChanges();
}
public set agentJobHistoryInfo(value: AgentJobHistoryInfo[]) {
this._agentJobHistoryInfo = value;
this._cd.detectChanges();
}
}

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 { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import * as sqlops from 'sqlops';
export const SERVICE_ID = 'jobManagementService';
export const IJobManagementService = createDecorator<IJobManagementService>(SERVICE_ID);
export interface IJobManagementService {
_serviceBrand: any;
registerProvider(providerId: string, provider: sqlops.AgentServicesProvider): void;
getJobs(connectionUri: string): Thenable<sqlops.AgentJobsResult>;
getJobHistory(connectionUri: string, jobID: string): Thenable<sqlops.AgentJobHistoryResult>;
jobAction(connectionUri: string, jobName: string, action: string): Thenable<sqlops.AgentJobActionResult>;
}

View File

@@ -0,0 +1,63 @@
/*---------------------------------------------------------------------------------------------
* 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 { localize } from 'vs/nls';
import { IJobManagementService } from 'sql/parts/jobManagement/common/interfaces';
import { ICapabilitiesService } from 'sql/services/capabilities/capabilitiesService';
import { IConnectionManagementService } from 'sql/parts/connection/common/connectionManagement';
import { TPromise } from 'vs/base/common/winjs.base';
import * as sqlops from 'sqlops';
export class JobManagementService implements IJobManagementService {
_serviceBrand: any;
private _providers: { [handle: string]: sqlops.AgentServicesProvider; } = Object.create(null);
constructor(
@IConnectionManagementService private _connectionService: IConnectionManagementService,
@ICapabilitiesService private _capabilitiesService: ICapabilitiesService
) {
}
public getJobs(connectionUri: string): Thenable<sqlops.AgentJobsResult> {
return this._runAction(connectionUri, (runner) => {
return runner.getJobs(connectionUri);
});
}
public getJobHistory(connectionUri: string, jobID: string): Thenable<sqlops.AgentJobHistoryResult> {
return this._runAction(connectionUri, (runner) => {
return runner.getJobHistory(connectionUri, jobID);
});
}
public jobAction(connectionUri: string, jobName: string, action: string): Thenable<sqlops.AgentJobActionResult> {
return this._runAction(connectionUri, (runner) => {
return runner.jobAction(connectionUri, jobName, action);
});
}
private _runAction<T>(uri: string, action: (handler: sqlops.AgentServicesProvider) => Thenable<T>): Thenable<T> {
let providerId: string = this._connectionService.getProviderIdFromUri(uri);
if (!providerId) {
return TPromise.wrapError(new Error(localize('providerIdNotValidError', "Connection is required in order to interact with JobManagementService")));
}
let handler = this._providers[providerId];
if (handler) {
return action(handler);
} else {
return TPromise.wrapError(new Error(localize('noHandlerRegistered', "No Handler Registered")));
}
}
public registerProvider(providerId: string, provider: sqlops.AgentServicesProvider): void {
this._providers[providerId] = provider;
}
}

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>back_16x16</title><path class="cls-1" d="M16.15,8.5H2.1l6.15,6.15-.7.7L.19,8,7.55.65l.7.7L2.1,7.5h14Z"/></svg>

After

Width:  |  Height:  |  Size: 259 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>back_inverse_16x16</title><path class="cls-1" d="M16.15,8.5H2.1l6.15,6.15-.7.7L.19,8,7.55.65l.7.7L2.1,7.5h14Z"/></svg>

After

Width:  |  Height:  |  Size: 264 B

View File

@@ -0,0 +1,39 @@
.detailView-toggle
{
display: inline-block;
cursor: pointer;
}
.detailView-toggle.expand
{
height: 20px;
width: 20px;
background: url(../images/arrow-right.gif) no-repeat center center;
}
.detailView-toggle.collapse
{
height: 20px;
width: 20px;
background: url(../images/sort-desc.gif) no-repeat center center;
}
.dynamic-cell-detail
{
z-index: 10000;
position: absolute;
background-color: #dae5e8;
margin: 0;
padding: 0;
width: 100%;
overflow: auto;
}
.dynamic-cell-detail > :first-child
{
vertical-align: middle;
line-height: 13px;
}
.dynamic-cell-detail > .detail-container {
overflow: auto;
display: block !important;
}

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>job</title><path d="M11,4h5v9H0V4H5V2h6Zm4,1H1V7H4V6H5V7h6V6h1V7h3ZM1,12H15V8H12V9H11V8H5V9H4V8H1ZM6,3V4h4V3Z"/></svg>

After

Width:  |  Height:  |  Size: 218 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>job_inverse</title><path class="cls-1" d="M11,4h5v9H0V4H5V2h6Zm4,1H1V7H4V6H5V7h6V6h1V7h3ZM1,12H15V8H12V9H11V8H5V9H4V8H1ZM6,3V4h4V3Z"/></svg>

After

Width:  |  Height:  |  Size: 286 B

View File

@@ -0,0 +1,134 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
agentview-component {
height: 100%;
width : 100%;
display: block;
}
jobsview-component {
height: 100%;
width : 100%;
display: block;
}
jobhistory-component {
height: 100%;
width : 100%;
display: block;
}
#jobsDiv .jobview-grid {
height: 100%;
width : 100%;
display: block;
}
.vs-dark #jobsDiv .slick-header-column {
background: #333333 !important;
}
#jobsDiv .slick-header-column {
background-color: transparent !important;
background: white !important;
border: 0px !important;
font-weight: bold;
font-size: larger;
}
.vs-dark #jobsDiv .slick-cell {
background:#333333 !important;
}
#jobsDiv .slick-cell {
background: white !important;
border-right: transparent !important;
border-left: transparent !important;
line-height: 33px !important;
vertical-align: middle;
}
#jobsDiv .jobview-joblist {
height: 100%;
width: 100%;
}
#jobsDiv .jobview-jobnametable {
border: 0px;
width: 100%;
height: 100%;
}
#jobsDiv .jobview-jobnameindicatorsuccess {
width: 5px;
background: green;
}
#jobsDiv .jobview-jobnametext {
width: 100%;
}
#jobsDiv .jobview-splitter {
height: 1px;
width: 100%;
background-color: gray;
}
#jobsDiv .jobview-jobitem {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
flex-flow: row wrap;
white-space: nowrap;
}
#jobsDiv .jobview-label {
padding-bottom: 10px;
padding-top: 10px;
}
#jobsDiv .jobview-highlight-none {
width: 5px;
margin-right: 10px;
}
#jobsDiv .detail-container {
max-height: 100px !important;
line-height: 20px;
}
#jobsDiv .detail {
padding: 5px
}
#jobsDiv .preload {
font-size: 18px;
}
#jobsDiv .dynamic-cell-detail > :first-child {
vertical-align: middle;
line-height: 13px;
padding: 10px;
margin-left: 20px;
}
.vs-dark #jobsDiv .dynamic-cell-detail {
background: black !important;
}
#jobsDiv .dynamic-cell-detail {
background: #faf5f8 !important;
}
.jobsview-icon {
content: url('./job.svg');
width: 25px;
}
.vs-dark .jobsview-icon {
content: url('./job_inverse.svg');
}

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:#3bb44a;}</style></defs><title>run</title><path class="cls-1" d="M3.24,0,14.61,8,3.24,16Zm2,12.07L11.13,8,5.24,3.88Z"/><path class="cls-1" d="M3.74,1l10,7-10,7Zm1,1.92V13.07L12,8Z"/></svg>

After

Width:  |  Height:  |  Size: 306 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:#d02e00;}</style></defs><title>stop</title><path class="cls-1" d="M.5,15.3V.3h15v15Zm13-2V2.3H2.5v11Z"/><path class="cls-1" d="M1,.8H15v14H1Zm13,13V1.8H2v12Z"/></svg>

After

Width:  |  Height:  |  Size: 284 B

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.
*--------------------------------------------------------------------------------------------*/
-->
<h1 class="job-heading">Jobs | {{agentJobInfo.name}} </h1>
<!-- Back -->
<div class="all-jobs">
<div class="back-button-icon" (click)="goToJobs()"></div>All Jobs
</div>
<!-- Actions -->
<ul class="action-buttons">
<li>
<div class="icon-start" (click)="jobAction('start')">Run</div>
</li>
<li>
<div class="icon-stop" (click)="jobAction('stop')">Stop</div>
</li>
</ul>
<!-- Overview -->
<div class="overview-container">
<div class="overview-tab" (click)='toggleCollapse()'>
<input id="accordion" type="checkbox">
<label for="accordion">
<div class="resultsViewCollapsible collapsed" (click)='toggleCollapse()'></div>
Overview
</label>
<div class="accordion-content">
<table align='left'>
<tr>
<td id='col1'>
User:
</td>
<td id='col2'>
</td>
<td id='col3'>
Enabled:
</td>
<td id='col4'>
{{agentJobInfo.enabled}}
</td>
</tr>
<tr>
<td id='col1'>
Alert:
</td>
<td id='col2'>
</td>
<td id='col3'>
Notification:
</td>
<td id='col4'>
</td>
</tr>
<tr>
<td id='col1'>
Schedule:
</td>
<td id='col2'>
</td>
<td id='col3'>
Target Server:
</td>
<td id='col4'>
</td>
</tr>
</table>
</div>
</div>
</div>
<!-- Job History details -->
<div class='history-details'>
<!-- Previous run list -->
<div style="width: 20%">
<table>
<tr>
<td class="date-column">
<b>Date</b>
</td>
<td>
<b>Status</b>
</td>
</tr>
</table>
<div #table class="step-table prev-run-list" style="position: relative; height: 100%; width: 100%"></div>
</div>
<!-- Job Steps -->
<div class="job-steps">
<h1 class="job-heading">
{{agentJobInfo.lastRun}}
</h1>
<table class="step-list">
<tr class="step-row">
<td height="30">
<h3>Status:</h3>
</td>
<td>
<h3></h3>
</td>
</tr>
<tr class="step-row">
<td height="30">
Error Message:
</td>
<td>
</td>
</tr>
<tr class="step-row">
<td height="30">
Duration:
</td>
<td>
</td>
</tr>
<tr class="step-row">
<td height="30">
Log:
</td>
<td>
</td>
</tr>
<tr class="step-row">
<td height="30">
SQL message ID:
</td>
<td>
</td>
</tr>
<tr class="step-row">
<td height="30">
Retries Attempted:
</td>
<td>
</td>
</tr>
</table>
</div>
</div>

View File

@@ -0,0 +1,132 @@
/*---------------------------------------------------------------------------------------------
* 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!./jobHistory';
import { OnInit, Component, Inject, forwardRef, ElementRef, ChangeDetectorRef, OnDestroy, ViewChild, Input } from '@angular/core';
import { ICancelableEvent } from 'vs/base/parts/tree/browser/treeDefaults';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { attachListStyler } from 'vs/platform/theme/common/styler';
import { getContentHeight } from 'vs/base/browser/dom';
import { Tree } from 'vs/base/parts/tree/browser/treeImpl';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
import { PanelComponent } from 'sql/base/browser/ui/panel/panel.component';
import { IBootstrapService, BOOTSTRAP_SERVICE_ID } from 'sql/services/bootstrap/bootstrapService';
import { IJobManagementService } from '../common/interfaces';
import { ExplorerDataSource } from 'sql/parts/dashboard/widgets/explorer/explorerTree';
import { TreeCreationUtils } from 'sql/parts/registeredServer/viewlet/treeCreationUtils';
import { DashboardServiceInterface } from 'sql/parts/dashboard/services/dashboardServiceInterface.service';
import { AgentViewComponent } from 'sql/parts/jobManagement/agent/agentView.component';
import { JobHistoryController, JobHistoryDataSource,
JobHistoryRenderer, JobHistoryFilter, JobHistoryModel, JobHistoryRow } from 'sql/parts/jobManagement/views/jobHistoryTree';
import { AgentJobHistoryInfo, AgentJobInfo } from 'sqlops';
import { toDisposableSubscription } from '../../common/rxjsUtils';
export const DASHBOARD_SELECTOR: string = 'jobhistory-component';
@Component({
selector: DASHBOARD_SELECTOR,
templateUrl: decodeURI(require.toUrl('./jobHistory.component.html'))
})
export class JobHistoryComponent extends Disposable implements OnInit, OnDestroy {
private _jobManagementService: IJobManagementService;
private _tree: Tree;
private _treeController = new JobHistoryController();
private _treeDataSource = new JobHistoryDataSource();
private _treeRenderer = new JobHistoryRenderer();
private _treeFilter = new JobHistoryFilter();
@ViewChild('table') private _tableContainer: ElementRef;
@Input() public agentJobInfo: AgentJobInfo = undefined;
@Input() public jobId: string = undefined;
@Input() public agentJobHistoryInfo: AgentJobHistoryInfo = undefined;
private prevJobId: string = undefined;
private jobName: string = undefined;
private isVisible: boolean = false;
constructor(
@Inject(BOOTSTRAP_SERVICE_ID) private bootstrapService: IBootstrapService,
@Inject(forwardRef(() => ElementRef)) el: ElementRef,
@Inject(forwardRef(() => ChangeDetectorRef)) private _cd: ChangeDetectorRef,
@Inject(forwardRef(() => DashboardServiceInterface)) private _dashboardService: DashboardServiceInterface,
@Inject(forwardRef(() => AgentViewComponent)) private _agentViewComponent: AgentViewComponent
) {
super();
this._jobManagementService = bootstrapService.jobManagementService;
}
ngOnInit() {
let ownerUri: string = this._dashboardService.connectionManagementService.connectionInfo.ownerUri;
this.loadHistory();
this._treeDataSource.data = [];
this._tree = new Tree(this._tableContainer.nativeElement, {
controller: this._treeController,
dataSource: this._treeDataSource,
filter: this._treeFilter,
renderer: this._treeRenderer
});
this._register(attachListStyler(this._tree, this.bootstrapService.themeService));
this._tree.layout(1024);
//this._tree.setInput(new JobHistoryModel());
}
ngOnDestroy() {
}
ngAfterContentChecked() {
if (this.isVisible === false && this._tableContainer.nativeElement.offsetParent !== null) {
if (this.prevJobId !== undefined && this.prevJobId !== this.jobId) {
this.loadHistory();
this.prevJobId = this.jobId;
}
}
}
loadHistory() {
let ownerUri: string = this._dashboardService.connectionManagementService.connectionInfo.ownerUri;
this._jobManagementService.getJobHistory(ownerUri, this.jobId).then((result) => {
if (result.jobs) {
let jobHistory = result.jobs;
this._treeDataSource.data = jobHistory.map(job => this.convertToJobHistoryRow(job));
this._tree.setInput(new JobHistoryModel());
}
});
}
private toggleCollapse(): void {
let arrow: HTMLElement = $('.resultsViewCollapsible').get(0);
let checkbox: any = document.getElementById('accordion');
if (arrow.className === 'resultsViewCollapsible' && checkbox.checked === false) {
arrow.className = 'resultsViewCollapsible collapsed';
} else if (arrow.className === 'resultsViewCollapsible collapsed' && checkbox.checked === true) {
arrow.className = 'resultsViewCollapsible';
}
}
private jobAction(action: string): void {
let ownerUri: string = this._dashboardService.connectionManagementService.connectionInfo.ownerUri;
this._jobManagementService.jobAction(ownerUri, 'jobName', action);
}
private goToJobs(): void {
this._agentViewComponent.showHistory = false;
}
private convertToJobHistoryRow(historyInfo: AgentJobHistoryInfo): JobHistoryRow {
let jobHistoryRow = {
runDate: historyInfo.runDate,
runStatus: JobHistoryRow.convertToStatusString(historyInfo.runStatus),
jobID: historyInfo.jobID
};
return jobHistoryRow;
}
}

View File

@@ -0,0 +1,221 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.all-jobs {
display: inline;
font-size: 15px;
padding-bottom: 15px;
}
.vs-dark ul.action-buttons {
border-top: 3px solid #444444;
}
.action-buttons {
padding-top: 15px;
padding-left: 10px;
border-top: 3px solid #f4f4f4;
list-style-type: none;
}
ul.action-buttons .icon-start, .icon-stop {
padding-left: 25px;
}
ul.action-buttons li {
padding-right: 25px;
display: inline-block;
width: 50px;
cursor: pointer;
}
.overview-container .overview-tab .resultsViewCollapsible {
padding: 15px;
display: inline;
}
.job-heading {
text-align: left;
padding-left: 13px;
}
.vs-dark .overview-container .overview-tab {
color: #fff;
}
.overview-container > .overview-tab {
position: relative;
margin-bottom: 1px;
width: 100%;
color: #4a4a4a;
overflow: hidden;
}
input#accordion {
position: absolute;
opacity: 0;
z-index: -1;
}
.vs-dark .overview-container .overview-tab label {
background: #444444;
}
.overview-container .overview-tab label {
position: relative;
display: block;
padding: 0 0 0 1em;
background: #f4f4f4;
font-weight: bold;
line-height: 3;
cursor: pointer;
width: 100%;
}
.vs-dark .overview-tab .accordion-content {
background: #333333;
}
.overview-tab .accordion-content {
max-height: 0;
overflow: hidden;
background: #eaeaea;
-webkit-transition: max-height .35s;
-o-transition: max-height .35s;
transition: max-height .35s;
width: 100%;
}
.overview-tab .accordion-content p {
margin: 1em;
}
/* :checked */
input#accordion:checked ~ .accordion-content {
max-height: 10em;
}
/* Icon */
.overview-container .overview-tab label::after {
position: absolute;
right: 0;
top: 0;
display: block;
width: 3em;
height: 3em;
line-height: 3;
text-align: center;
-webkit-transition: all .3s;
-o-transition: all .3s;
transition: all .3s;
}
.all-jobs > .back-button-icon {
content: url('../common/media/back.svg');
width: 20px;
margin-right: 10px;
float: left;
cursor: pointer;
padding-left: 13px;
}
.vs-dark.monaco-shell .all-jobs >.back-button-icon {
content: url('../common/media/back_inverse.svg');
}
.vs ul.action-buttons div.icon-start,
.vs-dark ul.action-buttons div.icon-start,
.hc-black ul.action-buttons div.icon-start {
display: inline-block;
height: 20px;
width: 20px;
background-image: url('../common/media/start.svg');
background-repeat: no-repeat;
}
.vs ul.action-buttons .icon-stop,
.vs-dark ul.action-buttons .icon-stop,
.hc-black ul.action-buttons .icon-stop {
display: inline-block;
background-image: url('../common/media/stop.svg');
background-repeat: no-repeat;
height: 20px;
width: 20px;
}
.accordion-content #col1,
.accordion-content #col2,
.accordion-content #col3,
.accordion-content #col4 {
padding: 10px;
}
.accordion-content #col2 {
padding-right: 300px;
}
table.step-list tr.step-row td {
padding-right: 100px;
}
.history-details {
height: 100%;
display: flex;
}
.vs-dark .history-details > .job-steps {
display: inline-block;
border-left: 3px solid #444444;
padding-left: 10px;
height: 100%;
}
.history-details > .job-steps {
display: inline-block;
border-left: 3px solid #f4f4f4;
padding-left: 10px;
height: 100%;
}
.vs-dark .step-table .monaco-tree .monaco-tree-rows.show-twisties > .monaco-tree-row.has-children > .content:before {
background-image: none;
}
.step-table .monaco-tree .monaco-tree-rows.show-twisties > .monaco-tree-row.has-children > .content:before {
background: none;
}
.step-table .monaco-tree.focused .monaco-tree-rows.show-twisties > .monaco-tree-row.has-children.selected:not(.loading) > .content:before {
background-image: none;
}
.step-table .list-row .status-icon {
height: 10px;
width: 10px;
display: inline-block;
}
.step-table .list-row .label {
padding-left: 10px;
display: inline-block;
}
.passed {
background: green;
}
.failed {
background: red;
}
.unknown {
background: yellow;
}
.date-column {
padding-left: 50px;
width: 140px;
}

View File

@@ -0,0 +1,190 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Router } from '@angular/router';
import { IConnectionProfile } from 'sql/parts/connection/common/interfaces';
import { MetadataType } from 'sql/parts/connection/common/connectionManagement';
import { SingleConnectionManagementService } from 'sql/parts/dashboard/services/dashboardServiceInterface.service';
import {
NewQueryAction, ScriptSelectAction, EditDataAction, ScriptCreateAction, ScriptExecuteAction, ScriptAlterAction,
BackupAction, ManageActionContext, BaseActionContext, ManageAction, RestoreAction
} from 'sql/workbench/common/actions';
import { ICapabilitiesService } from 'sql/services/capabilities/capabilitiesService';
import { ConnectionManagementInfo } from 'sql/parts/connection/common/connectionManagementInfo';
import * as Constants from 'sql/parts/connection/common/constants';
import * as tree from 'vs/base/parts/tree/browser/tree';
import * as TreeDefaults from 'vs/base/parts/tree/browser/treeDefaults';
import { Promise, TPromise } from 'vs/base/common/winjs.base';
import { IMouseEvent } from 'vs/base/browser/mouseEvent';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IAction } from 'vs/base/common/actions';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { generateUuid } from 'vs/base/common/uuid';
import * as DOM from 'vs/base/browser/dom';
import { OEAction } from 'sql/parts/registeredServer/viewlet/objectExplorerActions';
import { Builder, $, withElementById } from 'vs/base/browser/builder';
export class JobHistoryRow {
runDate: string;
runStatus: string;
jobID: string;
public static convertToStatusString(status: number): string {
switch(status) {
case(1): return 'Succeeded';
case(0): return 'Failed';
default: return 'Unknown';
}
}
}
// Empty class just for tree input
export class JobHistoryModel {
public static readonly id = generateUuid();
}
export class JobHistoryController extends TreeDefaults.DefaultController {
protected onLeftClick(tree: tree.ITree, element: JobHistoryRow, event: IMouseEvent, origin: string = 'mouse'): boolean {
const payload = { origin: origin };
const isDoubleClick = (origin === 'mouse' && event.detail === 2);
// Cancel Event
const isMouseDown = event && event.browserEvent && event.browserEvent.type === 'mousedown';
if (!isMouseDown) {
event.preventDefault(); // we cannot preventDefault onMouseDown because this would break DND otherwise
}
event.stopPropagation();
tree.setFocus(element, payload);
if (element && isDoubleClick) {
event.preventDefault(); // focus moves to editor, we need to prevent default
} else {
tree.setFocus(element, payload);
tree.setSelection([element], payload);
}
return true;
}
public onContextMenu(tree: tree.ITree, element: JobHistoryRow, event: tree.ContextMenuEvent): boolean {
return true;
}
}
export class JobHistoryDataSource implements tree.IDataSource {
private _data: JobHistoryRow[];
public getId(tree: tree.ITree, element: JobHistoryRow | JobHistoryModel): string {
if (element instanceof JobHistoryModel) {
return JobHistoryModel.id;
} else {
return (element as JobHistoryRow).jobID;
}
}
public hasChildren(tree: tree.ITree, element: JobHistoryRow | JobHistoryModel): boolean {
if (element instanceof JobHistoryModel) {
return true;
} else {
return false;
}
}
public getChildren(tree: tree.ITree, element: JobHistoryRow | JobHistoryModel): Promise {
if (element instanceof JobHistoryModel) {
return TPromise.as(this._data);
} else {
return TPromise.as(undefined);
}
}
public getParent(tree: tree.ITree, element: JobHistoryRow | JobHistoryModel): Promise {
if (element instanceof JobHistoryModel) {
return TPromise.as(undefined);
} else {
return TPromise.as(new JobHistoryModel());
}
}
public set data(data: JobHistoryRow[]) {
this._data = data;
}
}
export interface IListTemplate {
statusIcon: HTMLElement;
label: HTMLElement;
}
export class JobHistoryRenderer implements tree.IRenderer {
private _statusIcon: HTMLElement;
public getHeight(tree: tree.ITree, element: JobHistoryRow): number {
return 22;
}
public getTemplateId(tree: tree.ITree, element: JobHistoryRow | JobHistoryModel): string {
if (element instanceof JobHistoryModel) {
return 'jobHistoryModel';
} else {
return 'jobHistoryInfo';
}
}
public renderTemplate(tree: tree.ITree, templateId: string, container: HTMLElement): IListTemplate {
let row = DOM.$('.list-row');
let label = DOM.$('.label');
this._statusIcon = this.createStatusIcon();
row.appendChild(this._statusIcon);
row.appendChild(label);
container.appendChild(row);
let statusIcon = this._statusIcon;
return { statusIcon, label };
}
public renderElement(tree: tree.ITree, element: JobHistoryRow, templateId: string, templateData: IListTemplate): void {
templateData.label.innerText = element.runDate + '\t\t\t' + element.runStatus;
let statusClass: string;
if (element.runStatus === 'Succeeded') {
statusClass = ' passed';
} else if (element.runStatus === 'Failed') {
statusClass = ' failed';
} else {
statusClass = ' unknown';
}
this._statusIcon.className += statusClass;
}
public disposeTemplate(tree: tree.ITree, templateId: string, templateData: IListTemplate): void {
// no op
}
private createStatusIcon(): HTMLElement {
let statusIcon: HTMLElement = DOM.$('div');
statusIcon.className += ' status-icon';
return statusIcon;
}
}
export class JobHistoryFilter implements tree.IFilter {
private _filterString: string;
public isVisible(tree: tree.ITree, element: JobHistoryRow): boolean {
return this._isJobVisible();
}
private _isJobVisible(): boolean {
return true;
}
public set filterString(val: string) {
this._filterString = val;
}
}

View File

@@ -0,0 +1,9 @@
<!--
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
-->
<h1 class="job-heading">Jobs</h1>
<div #jobsgrid class="jobview-grid"></div>

View File

@@ -0,0 +1,255 @@
/*---------------------------------------------------------------------------------------------
* 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!../common/media/detailview';
import { OnInit, Component, Inject, forwardRef, ElementRef, ChangeDetectorRef, OnDestroy, ViewChild } from '@angular/core';
import * as Utils from 'sql/parts/connection/common/utils';
import { RefreshWidgetAction, EditDashboardAction } from 'sql/parts/dashboard/common/actions';
import { IColorTheme } from 'vs/workbench/services/themes/common/workbenchThemeService';
import { IDisposable } from 'vs/base/common/lifecycle';
import * as themeColors from 'vs/workbench/common/theme';
import { DashboardPage } from 'sql/parts/dashboard/common/dashboardPage.component';
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
import { IBootstrapService, BOOTSTRAP_SERVICE_ID } from 'sql/services/bootstrap/bootstrapService';
import { IJobManagementService } from '../common/interfaces';
import { DashboardServiceInterface } from 'sql/parts/dashboard/services/dashboardServiceInterface.service';
import * as sqlops from 'sqlops';
import * as vscode from 'vscode';
import * as nls from 'vs/nls';
import { IGridDataSet } from 'sql/parts/grid/common/interfaces';
import { FieldType, IObservableCollection, CollectionChange, SlickGrid } from 'angular2-slickgrid';
import { Table } from 'sql/base/browser/ui/table/table';
import { attachTableStyler } from 'sql/common/theme/styler';
import { JobHistoryComponent } from './jobHistory.component';
import { AgentViewComponent } from '../agent/agentView.component';
import { RowDetailView } from 'sql/base/browser/ui/table/plugins/rowdetailview';
export const JOBSVIEW_SELECTOR: string = 'jobsview-component';
@Component({
selector: JOBSVIEW_SELECTOR,
templateUrl: decodeURI(require.toUrl('./jobsView.component.html'))
})
export class JobsViewComponent implements OnInit, OnDestroy {
private _jobManagementService: IJobManagementService;
private _disposables = new Array<vscode.Disposable>();
private columns: Array<Slick.Column<any>> = [
{ name: 'Name', field: 'name', formatter: this.renderName, width: 200, },
{ name: 'Last Run', field: 'lastRun' },
{ name: 'Next Run', field: 'nextRun' },
{ name: 'Enabled', field: 'enabled' },
{ name: 'Status', field: 'currentExecutionStatus' },
{ name: 'Category', field: 'category' },
{ name: 'Runnable', field: 'runnable' },
{ name: 'Schedule', field: 'hasSchedule' },
{ name: 'Category ID', field: 'categoryId' },
{ name: 'Last Run Outcome', field: 'lastRunOutcome' },
];
private rowDetail: any;
private dataView: any;
@ViewChild('jobsgrid') _gridEl: ElementRef;
private isVisible: boolean = false;
private isInitialized: boolean = false;
private _table: Table<any>;
public jobs: sqlops.AgentJobInfo[];
public jobHistories: { [jobId: string]: sqlops.AgentJobHistoryInfo[]; } = Object.create(null);
constructor(
@Inject(BOOTSTRAP_SERVICE_ID) private bootstrapService: IBootstrapService,
@Inject(forwardRef(() => DashboardServiceInterface)) private _dashboardService: DashboardServiceInterface,
@Inject(forwardRef(() => ChangeDetectorRef)) private _cd: ChangeDetectorRef,
@Inject(forwardRef(() => ElementRef)) private _el: ElementRef,
@Inject(forwardRef(() => AgentViewComponent)) private _agentViewComponent: AgentViewComponent
) {
this._jobManagementService = bootstrapService.jobManagementService;
}
ngAfterContentChecked() {
if (this.isVisible === false && this._gridEl.nativeElement.offsetParent !== null) {
this.isVisible = true;
if (!this.isInitialized) {
this.onFirstVisible();
this.isInitialized = true;
}
} else if (this.isVisible === true && this._gridEl.nativeElement.offsetParent === null) {
this.isVisible = false;
}
}
loadJobHistories() {
if (this.jobs) {
this.jobs.forEach((job) => {
let ownerUri: string = this._dashboardService.connectionManagementService.connectionInfo.ownerUri;
this._jobManagementService.getJobHistory(ownerUri, job.jobId).then((result) => {
if (result.jobs) {
this.jobHistories[job.jobId] = result.jobs;
this.expandJobsWithFailures();
}
});
});
}
}
private expandJobsWithFailures(): void {
for (let i: number = 0; i < this.jobs.length; ++i) {
let job = this.jobs[i];
let jobHistory = this.jobHistories[job.jobId];
if (jobHistory && jobHistory.length > 0) {
let latestExecution = jobHistory[jobHistory.length - 1];
if (latestExecution.runStatus !== 0) {
this.expandJobRowDetails(i);
}
}
}
}
private expandJobRowDetails(rowIdx: number): void {
}
onFirstVisible() {
let self = this;
let columns = this.columns.map((column) => {
column.rerenderOnResize = true;
return column;
});
let options = <Slick.GridOptions<any>>{
syncColumnCellResize: true,
enableColumnReorder: false,
rowHeight: 45,
enableCellNavigation: true
};
this.dataView = new Slick.Data.DataView({ inlineFilters: false });
let rowDetail = new RowDetailView({
cssClass: 'detailView-toggle',
preTemplate: this.loadingTemplate,
process: (job) => {
(<any>rowDetail).onAsyncResponse.notify({
'itemDetail': job
}, undefined, this);
},
panelRows: 2
});
this.rowDetail = rowDetail;
columns.unshift(this.rowDetail.getColumnDefinition());
this._table = new Table(this._gridEl.nativeElement, undefined, columns, options);
this._table.grid.setData(this.dataView, true);
this._table.grid.onClick.subscribe((e, args) => {
let job = self.getJob(args);
self._agentViewComponent.jobId = job.jobId;
self._agentViewComponent.agentJobInfo = job;
self.getJobHistoryInfo(ownerUri, job).then(result => {
if (result) {
this._agentViewComponent.agentJobHistoryInfo = result;
}
});
self.isVisible = false;
self._agentViewComponent.showHistory = true;
});
this._cd.detectChanges();
let ownerUri: string = this._dashboardService.connectionManagementService.connectionInfo.ownerUri;
this._jobManagementService.getJobs(ownerUri).then((result) => {
if (result && result.jobs) {
this.jobs = result.jobs;
this.onJobsAvailable(result.jobs);
}
});
}
getJobHistoryInfo(ownerUri: string, job: any): Thenable<sqlops.AgentJobHistoryInfo[]> {
return new Promise<sqlops.AgentJobHistoryInfo[]>((resolve, reject) => {
if (this.jobHistories[job.jobId]){
Promise.resolve(this.jobHistories[job.jobId]);
} else {
this._jobManagementService.getJobHistory(ownerUri, job.jobId).then(result => {
if (result && result.jobs) {
Promise.resolve(result.jobs);
} else {
Promise.reject(undefined);
}
});
}
});
}
onJobsAvailable(jobs: sqlops.AgentJobInfo[]) {
let jobViews = jobs.map((job) => {
return {
id: job.jobId,
jobId: job.jobId,
name: job.name,
lastRun: job.lastRun,
nextRun: job.nextRun,
enabled: job.enabled,
currentExecutionStatus: job.currentExecutionStatus,
category: job.category,
runnable: job.runnable,
hasSchedule: job.hasSchedule,
categoryId: job.categoryId,
lastRunOutcome: job.lastRunOutcome
};
});
this._table.registerPlugin(<any>this.rowDetail);
this.rowDetail.onBeforeRowDetailToggle.subscribe(function(e, args) {
});
this.rowDetail.onAfterRowDetailToggle.subscribe(function(e, args) {
});
this.rowDetail.onAsyncEndUpdate.subscribe(function(e, args) {
});
this.dataView.beginUpdate();
this.dataView.setItems(jobViews);
this.dataView.endUpdate();
this._table.resizeCanvas();
this._table.autosizeColumns();
this.loadJobHistories();
}
ngOnInit() {
}
ngOnDestroy() {
}
loadingTemplate() {
return '<div class="preload">Loading...</div>';
}
renderName(row, cell, value, columnDef, dataContext) {
return '<table class="jobview-jobnametable"><tr class="jobview-jobnamerow">' +
'<td nowrap class="jobview-jobnameindicatorsuccess"></td>' +
'<td nowrap class="jobview-jobnametext">' + dataContext.name + '</td>' +
'</tr></table>';
}
private getJob(args: Slick.OnClickEventArgs<any>): sqlops.AgentJobInfo {
let cell = args.cell;
let jobName = args.grid.getCellNode(1, cell).innerText.trim();
let job = this.jobs.filter(job => job.name === jobName)[0];
return job;
}
}

View File

@@ -41,6 +41,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { ConfigurationEditingService } from 'vs/workbench/services/configuration/node/configurationEditingService';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IJobManagementService } from 'sql/parts/jobManagement/common/interfaces';
export const BOOTSTRAP_SERVICE_ID = 'bootstrapService';
export const IBootstrapService = createDecorator<IBootstrapService>(BOOTSTRAP_SERVICE_ID);
@@ -92,6 +93,7 @@ export interface IBootstrapService {
configurationEditorService: ConfigurationEditingService;
commandService: ICommandService;
dashboardWebviewService: IDashboardWebviewService;
jobManagementService: IJobManagementService;
/*
* Bootstraps the Angular module described. Components that need singleton services should inject the

View File

@@ -45,6 +45,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { ConfigurationEditingService } from 'vs/workbench/services/configuration/node/configurationEditingService';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IJobManagementService } from 'sql/parts/jobManagement/common/interfaces';
export class BootstrapService implements IBootstrapService {
@@ -100,7 +101,8 @@ export class BootstrapService implements IBootstrapService {
@IClipboardService public clipboardService: IClipboardService,
@ICapabilitiesService public capabilitiesService: ICapabilitiesService,
@ICommandService public commandService: ICommandService,
@IDashboardWebviewService public dashboardWebviewService: IDashboardWebviewService
@IDashboardWebviewService public dashboardWebviewService: IDashboardWebviewService,
@IJobManagementService public jobManagementService: IJobManagementService
) {
this.configurationEditorService = this.instantiationService.createInstance(ConfigurationEditingService);
this._bootstrapParameterMap = new Map<string, BootstrapParams>();

View File

@@ -53,7 +53,7 @@ export class ExtHostDataProtocol extends ExtHostDataProtocolShape {
provider.handle = this._nextHandle();
this._adapter.set(provider.handle, provider);
return this._createDisposable(provider.handle);
};
}
$registerConnectionProvider(provider: sqlops.ConnectionProvider): vscode.Disposable {
let rt = this.registerProvider(provider);
@@ -121,6 +121,12 @@ export class ExtHostDataProtocol extends ExtHostDataProtocolShape {
return rt;
}
$registerAgentServiceProvider(provider: sqlops.AgentServicesProvider): vscode.Disposable {
let rt = this.registerProvider(provider);
this._proxy.$registerAgentServicesProvider(provider.providerId, provider.handle);
return rt;
}
$registerCapabilitiesServiceProvider(provider: sqlops.CapabilitiesProvider): vscode.Disposable {
let rt = this.registerProvider(provider);
this._proxy.$registerCapabilitiesServiceProvider(provider.providerId, provider.handle);
@@ -481,4 +487,30 @@ export class ExtHostDataProtocol extends ExtHostDataProtocolShape {
public $onSessionEventsAvailable(handle: number, response: sqlops.ProfilerSessionEvents): void {
this._proxy.$onSessionEventsAvailable(handle, response);
}
/**
* Agent Job Provider methods
*/
/**
* Get Agent Job list
*/
public $getJobs(handle: number, ownerUri: string): Thenable<sqlops.AgentJobsResult> {
return this._resolveProvider<sqlops.AgentServicesProvider>(handle).getJobs(ownerUri);
}
/**
* Get a Agent Job's history
*/
public $getJobHistory(handle: number, ownerUri: string, jobID: string): Thenable<sqlops.AgentJobHistoryResult> {
return this._resolveProvider<sqlops.AgentServicesProvider>(handle).getJobHistory(ownerUri, jobID);
}
/**
* Run an action on a job
*/
public $jobAction(handle: number, ownerUri: string, jobName: string, action: string): Thenable<sqlops.AgentJobActionResult> {
return this._resolveProvider<sqlops.AgentServicesProvider>(handle).jobAction(ownerUri, jobName, action);
}
}

View File

@@ -18,6 +18,7 @@ import { IMetadataService } from 'sql/services/metadata/metadataService';
import { IObjectExplorerService } from 'sql/parts/registeredServer/common/objectExplorerService';
import { IScriptingService } from 'sql/services/scripting/scriptingService';
import { IAdminService } from 'sql/parts/admin/common/adminService';
import { IJobManagementService } from 'sql/parts/jobManagement/common/interfaces';
import { IBackupService } from 'sql/parts/disasterRecovery/backup/common/backupService';
import { IRestoreService } from 'sql/parts/disasterRecovery/restore/common/restoreService';
import { ITaskService } from 'sql/parts/taskHistory/common/taskService';
@@ -50,6 +51,7 @@ export class MainThreadDataProtocol implements MainThreadDataProtocolShape {
@IObjectExplorerService private _objectExplorerService: IObjectExplorerService,
@IScriptingService private _scriptingService: IScriptingService,
@IAdminService private _adminService: IAdminService,
@IJobManagementService private _jobManagementService: IJobManagementService,
@IBackupService private _backupService: IBackupService,
@IRestoreService private _restoreService: IRestoreService,
@ITaskService private _taskService: ITaskService,
@@ -329,6 +331,24 @@ export class MainThreadDataProtocol implements MainThreadDataProtocolShape {
return undefined;
}
public $registerAgentServicesProvider(providerId: string, handle: number): TPromise<any> {
const self = this;
this._jobManagementService.registerProvider(providerId, <sqlops.AgentServicesProvider> {
providerId: providerId,
getJobs(connectionUri: string): Thenable<sqlops.AgentJobsResult> {
return self._proxy.$getJobs(handle, connectionUri);
},
getJobHistory(connectionUri: string, jobID: string): Thenable<sqlops.AgentJobHistoryResult> {
return self._proxy.$getJobHistory(handle, connectionUri, jobID);
},
jobAction(connectionUri: string, jobName: string, action: string): Thenable<sqlops.AgentJobActionResult> {
return self._proxy.$jobAction(handle, connectionUri, jobName, action);
}
});
return undefined;
}
public $registerCapabilitiesServiceProvider(providerId: string, handle: number): TPromise<any> {
const self = this;
this._capabilitiesService.registerProvider(<sqlops.CapabilitiesProvider>{

View File

@@ -28,7 +28,6 @@ import { ExtHostConfiguration } from 'vs/workbench/api/node/extHostConfiguration
import { ExtHostModalDialogs } from 'sql/workbench/api/node/extHostModalDialog';
import { ExtHostTasks } from 'sql/workbench/api/node/extHostTasks';
import { ILogService } from 'vs/platform/log/common/log';
import { IExtensionApiFactory } from 'vs/workbench/api/node/extHost.api.impl';
import { ExtHostDashboardWebviews } from 'sql/workbench/api/node/extHostDashboardWebview';
import { ExtHostConnectionManagement } from 'sql/workbench/api/node/extHostConnectionManagement';
import { ExtHostDashboard } from 'sql/workbench/api/node/extHostDashboard';
@@ -256,7 +255,7 @@ export function createApiFactory(
};
let registerAgentServicesProvider = (provider: sqlops.AgentServicesProvider): vscode.Disposable => {
return undefined;
return extHostDataProvider.$registerAgentServiceProvider(provider);
};
// namespace: dataprotocol
@@ -272,6 +271,7 @@ export function createApiFactory(
registerTaskServicesProvider,
registerQueryProvider,
registerAdminServicesProvider,
registerAgentServicesProvider,
registerCapabilitiesServiceProvider,
registerAgentServicesProvider,
onDidChangeLanguageFlavor(listener: (e: sqlops.DidChangeLanguageFlavorParams) => any, thisArgs?: any, disposables?: extHostTypes.Disposable[]) {

View File

@@ -306,6 +306,22 @@ export abstract class ExtHostDataProtocolShape {
* Stop a profiler session
*/
$stopSession(handle: number, sessionId: string): Thenable<boolean> { throw ni(); }
/**
* Get Agent Job list
*/
$getJobs(handle: number, ownerUri: string): Thenable<sqlops.AgentJobsResult>{ throw ni(); }
/**
* Get a Agent Job's history
*/
$getJobHistory(handle: number, ownerUri: string, jobID: string): Thenable<sqlops.AgentJobHistoryResult>{ throw ni(); }
/**
* Run an action on a Job
*/
$jobAction(handle: number, ownerUri: string, jobName: string, action: string): Thenable<sqlops.AgentJobActionResult>{ throw ni(); }
}
/**
@@ -370,6 +386,7 @@ export interface MainThreadDataProtocolShape extends IDisposable {
$registerFileBrowserProvider(providerId: string, handle: number): TPromise<any>;
$registerCapabilitiesServiceProvider(providerId: string, handle: number): TPromise<any>;
$registerAdminServicesProvider(providerId: string, handle: number): TPromise<any>;
$registerAgentServicesProvider(providerId: string, handle: number): TPromise<any>;
$unregisterProvider(handle: number): TPromise<any>;
$onConnectionComplete(handle: number, connectionInfoSummary: sqlops.ConnectionInfoSummary): void;
$onIntelliSenseCacheComplete(handle: number, connectionUri: string): void;

View File

@@ -68,7 +68,7 @@ export class ProductContribution implements IWorkbenchContribution {
text => editorService.openEditor(instantiationService.createInstance(ReleaseNotesInput, pkg.version, text), { pinned: true }),
() => {
messageService.show(Severity.Info, {
message: nls.localize('read the release notes', "Welcome to {0} February Public Preview! Would you like to view the Getting Started Guide?", product.nameLong, pkg.version),
message: nls.localize('read the release notes', "Welcome to {0} March Public Preview! Would you like to view the Getting Started Guide?", product.nameLong, pkg.version),
actions: [
instantiationService.createInstance(OpenGettingStartedInBrowserAction),
CloseAction