mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 10:58:30 -05:00
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:
18
src/sql/parts/jobManagement/agent/agentView.component.html
Normal file
18
src/sql/parts/jobManagement/agent/agentView.component.html
Normal 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>
|
||||
100
src/sql/parts/jobManagement/agent/agentView.component.ts
Normal file
100
src/sql/parts/jobManagement/agent/agentView.component.ts
Normal 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();
|
||||
}
|
||||
}
|
||||
25
src/sql/parts/jobManagement/common/interfaces.ts
Normal file
25
src/sql/parts/jobManagement/common/interfaces.ts
Normal 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>;
|
||||
}
|
||||
63
src/sql/parts/jobManagement/common/jobManagementService.ts
Normal file
63
src/sql/parts/jobManagement/common/jobManagementService.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
1
src/sql/parts/jobManagement/common/media/back.svg
Normal file
1
src/sql/parts/jobManagement/common/media/back.svg
Normal 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 |
@@ -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 |
39
src/sql/parts/jobManagement/common/media/detailview.css
Normal file
39
src/sql/parts/jobManagement/common/media/detailview.css
Normal 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;
|
||||
}
|
||||
1
src/sql/parts/jobManagement/common/media/job.svg
Normal file
1
src/sql/parts/jobManagement/common/media/job.svg
Normal 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 |
1
src/sql/parts/jobManagement/common/media/job_inverse.svg
Normal file
1
src/sql/parts/jobManagement/common/media/job_inverse.svg
Normal 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 |
134
src/sql/parts/jobManagement/common/media/jobs.css
Normal file
134
src/sql/parts/jobManagement/common/media/jobs.css
Normal 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');
|
||||
}
|
||||
1
src/sql/parts/jobManagement/common/media/start.svg
Normal file
1
src/sql/parts/jobManagement/common/media/start.svg
Normal 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 |
1
src/sql/parts/jobManagement/common/media/stop.svg
Normal file
1
src/sql/parts/jobManagement/common/media/stop.svg
Normal 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 |
155
src/sql/parts/jobManagement/views/jobHistory.component.html
Normal file
155
src/sql/parts/jobManagement/views/jobHistory.component.html
Normal 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>
|
||||
|
||||
132
src/sql/parts/jobManagement/views/jobHistory.component.ts
Normal file
132
src/sql/parts/jobManagement/views/jobHistory.component.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
221
src/sql/parts/jobManagement/views/jobHistory.css
Normal file
221
src/sql/parts/jobManagement/views/jobHistory.css
Normal 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;
|
||||
}
|
||||
190
src/sql/parts/jobManagement/views/jobHistoryTree.ts
Normal file
190
src/sql/parts/jobManagement/views/jobHistoryTree.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
255
src/sql/parts/jobManagement/views/jobsView.component.ts
Normal file
255
src/sql/parts/jobManagement/views/jobsView.component.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user