Feature/agent finishes (#1149)

* added icons for steps and fixed bugs in jobs view page

* put svgs in common folder

* added steps header logo

* added full path for import

* changed codes to text

* removed cat id, changed bools to yes/no and fixed steps theme

* localized the strings

* set the jobs table column widths

* added indicators for failure, unknown and canceled jobs in jobs view

* fixed jobs panel style and jobs view scrolling

* fixed jobs view page styling

* fixed job history tree size rows

* made error messages copy-able

* made job history tree work with keyboard
This commit is contained in:
Aditya Bist
2018-04-17 16:45:22 -07:00
committed by GitHub
parent b9c877a109
commit 134f76c17f
19 changed files with 273 additions and 138 deletions

View File

@@ -5,7 +5,7 @@
*--------------------------------------------------------------------------------------------*/
-->
<panel class="fullsize" [options]="panelOpt">
<panel class="dashboard-panel" [options]="panelOpt">
<tab [title]="jobsComponentTitle" class="fullsize" [identifier]="jobsTabIdentifier"
[iconClass]="jobsIconClass">
<div id="jobsDiv" class="fullsize" *ngIf="showHistory === false">

View File

@@ -4,6 +4,8 @@
*--------------------------------------------------------------------------------------------*/
import 'vs/css!../common/media/jobs';
import 'sql/parts/dashboard/common/dashboardPanelStyles';
import * as nls from 'vs/nls';
import { Component, Inject, forwardRef, ElementRef, ChangeDetectorRef, ViewChild, Injectable} from '@angular/core';
import * as Utils from 'sql/parts/connection/common/utils';

View File

@@ -0,0 +1,54 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as nls from 'vs/nls';
export class AgentJobUtilities {
public static convertToStatusString(status: number): string {
switch(status) {
case(0): return nls.localize('agentUtilities.failed','Failed');
case(1): return nls.localize('agentUtilities.succeeded', 'Succeeded');
case(3): return nls.localize('agentUtilities.canceled', 'Canceled');
case(5): return nls.localize('agentUtilities.statusUnknown', 'Status Unknown');
default: return nls.localize('agentUtilities.statusUnknown', 'Status Unknown');
}
}
public static convertToExecutionStatusString(status: number): string {
switch(status) {
case(1): return nls.localize('agentUtilities.executing', 'Executing');
case(2): return nls.localize('agentUtilities.waitingForThread', 'Waiting for Thread');
case(3): return nls.localize('agentUtilities.betweenRetries', 'Between Retries');
case(4): return nls.localize('agentUtilities.idle', 'Idle');
case(5): return nls.localize('agentUtilities.suspended', 'Suspended');
case(6): return nls.localize('agentUtilities.obsolete', '[Obsolete]');
case(7): return nls.localize('agentUtilities.performingCompletionActions', 'PerformingCompletionActions');
default: return nls.localize('agentUtilities.statusUnknown', 'Status Unknown');
}
}
public static convertToResponse(bool: boolean) {
return bool ? nls.localize('agentUtilities.yes', 'Yes') : nls.localize('agentUtilities.no', 'No');
}
public static convertToNextRun(date: string) {
if (date.includes('1/1/0001')) {
return nls.localize('agentUtilities.notScheduled', 'Not Scheduled');
} else {
return date;
}
}
public static convertToLastRun(date: string) {
if (date.includes('1/1/0001')) {
return nls.localize('agentUtilities.neverRun', 'Never Run');
} else {
return date;
}
}
}

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 16 16"><defs><style>.cls-1{fill:none;clip-rule:evenodd;}.cls-2{clip-path:url(#clip-path);}.cls-3{fill:#d02e00;}</style><clipPath id="clip-path"><path class="cls-1" d="M8.88,8l2.67-2.67-.88-.88L8,7.12,5.33,4.45l-.88.88L7.12,8,4.45,10.67l.88.88L8,8.88l2.67,2.67.88-.88ZM8,0a7.92,7.92,0,0,1,4,1.09A8.15,8.15,0,0,1,14.91,4a8,8,0,0,1,0,8.07A8.15,8.15,0,0,1,12,14.91a8,8,0,0,1-8.07,0A8.15,8.15,0,0,1,1.09,12,8,8,0,0,1,1.09,4,8.15,8.15,0,0,1,4,1.09,7.92,7.92,0,0,1,8,0Z"/></clipPath></defs><title>failed</title><g class="cls-2"><rect class="cls-3" x="-5" y="-5" width="26" height="26"/></g></svg>

After

Width:  |  Height:  |  Size: 718 B

View File

@@ -21,10 +21,16 @@ jobhistory-component {
display: block;
}
.vs-dark .job-heading-container {
height: 32px;
border-bottom: 3px solid #444444;
}
#jobsDiv .jobview-grid {
padding-top: 15px;
height: 100%;
width : 100%;
display: block;
display: block;
}
.vs-dark #jobsDiv .slick-header-column {
@@ -67,6 +73,21 @@ jobhistory-component {
background: green;
}
#jobsDiv .jobview-jobnameindicatorfailure {
width: 5px;
background: red;
}
#jobsDiv .jobview-jobnameindicatorcancel {
width: 5px;
background: orange;
}
#jobsDiv .jobview-jobnameindicatorunknown {
width: 5px;
background: yellow;
}
#jobsDiv .jobview-jobnametext {
width: 100%;
}
@@ -123,16 +144,15 @@ jobhistory-component {
background: #faf5f8 !important;
}
.jobsview-icon {
content: url('./job.svg');
width: 25px;
background-image: url('./job.svg');
}
.vs-dark .jobsview-icon {
content: url('./job_inverse.svg');
background-image: url('./job_inverse.svg');
}
agentview-component .tabbedPanel .tabList .tab .tabLabel.icon {
padding: 20px 15px !important;
agentview-component .jobview-grid .grid-canvas > .ui-widget-content.slick-row.even > .slick-cell,
agentview-component .jobview-grid .grid-canvas > .ui-widget-content.slick-row.odd > .slick-cell {
cursor: pointer;
}

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 16 16"><defs><style>.cls-1{fill:none;clip-rule:evenodd;}.cls-2{clip-path:url(#clip-path);}</style><clipPath id="clip-path"><path class="cls-1" d="M1.14,10.29H3.43V8H1.14ZM4.57,6.86H6.86V4.57H4.57ZM1.14,3.43H3.43V1.14H1.14ZM8,3.43h2.29V1.14H8Zm-6.86,8H4.57V8H8V4.57h3.43V1.14h3.43V14.86H1.14ZM6.86,0V3.43H4.57V0H0V4.57H3.43V6.86H0V16H16V0Z"/></clipPath></defs><title>step</title><g class="cls-2"><rect x="-5.71" y="-5.71" width="27.43" height="27.43"/></g></svg>

After

Width:  |  Height:  |  Size: 590 B

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 16 16"><defs><style>.cls-1,.cls-2,.cls-4{fill:#fff;}.cls-1{clip-rule:evenodd;}.cls-2{fill-rule:evenodd;}.cls-3{clip-path:url(#clip-path);}</style><clipPath id="clip-path"><path class="cls-1" d="M1.14,10.29H3.43V8H1.14ZM4.57,6.86H6.86V4.57H4.57ZM1.14,3.43H3.43V1.14H1.14ZM8,3.43h2.29V1.14H8Zm-6.86,8H4.57V8H8V4.57h3.43V1.14h3.43V14.86H1.14ZM6.86,0V3.43H4.57V0H0V4.57H3.43V6.86H0V16H16V0Z"/></clipPath></defs><title>step_inverse</title><path class="cls-2" d="M1.14,10.29H3.43V8H1.14ZM4.57,6.86H6.86V4.57H4.57ZM1.14,3.43H3.43V1.14H1.14ZM8,3.43h2.29V1.14H8Zm-6.86,8H4.57V8H8V4.57h3.43V1.14h3.43V14.86H1.14ZM6.86,0V3.43H4.57V0H0V4.57H3.43V6.86H0V16H16V0Z"/><g class="cls-3"><rect class="cls-4" x="-5.71" y="-5.71" width="27.43" height="27.43"/></g></svg>

After

Width:  |  Height:  |  Size: 878 B

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 16 16"><defs><style>.cls-1{fill:none;clip-rule:evenodd;}.cls-2{clip-path:url(#clip-path);}.cls-3{fill:#3bb44a;}.cls-4{clip-path:url(#clip-path-2);}.cls-5{fill:#fff;}</style><clipPath id="clip-path"><path class="cls-1" d="M16,8a7.92,7.92,0,0,1-1.09,4A8.15,8.15,0,0,1,12,14.91a8,8,0,0,1-8.07,0A8.15,8.15,0,0,1,1.09,12,8,8,0,0,1,1.09,4,8.15,8.15,0,0,1,4,1.09a8,8,0,0,1,8.07,0A8.15,8.15,0,0,1,14.91,4,7.92,7.92,0,0,1,16,8Z"/></clipPath><clipPath id="clip-path-2"><polygon class="cls-1" points="10.9 4.9 11.6 5.6 6.5 10.71 3.65 7.85 4.35 7.15 6.5 9.29 10.9 4.9"/></clipPath></defs><title>success_complete</title><g class="cls-2"><rect class="cls-3" x="-5" y="-5" width="26" height="26"/></g><g class="cls-4"><rect class="cls-5" x="-1.35" y="-0.1" width="17.95" height="15.81"/></g></svg>

After

Width:  |  Height:  |  Size: 911 B

View File

@@ -82,7 +82,7 @@
<!-- Job History details -->
<div class='history-details'>
<!-- Previous run list -->
<div style="width: 20%">
<div style="width: 20%; overflow-y: scroll">
<table *ngIf="_showPreviousRuns === true">
<tr>
<td class="date-column">
@@ -103,39 +103,39 @@
</h1>
<table class="step-list">
<tr class="step-row">
<td height="30">
<td height="20">
<h3>Status:</h3>
</td>
<td height="30">
<td height="20">
<h3>{{_runStatus}}</h3>
</td>
</tr>
<tr class="step-row">
<td height="30">
<td height="20">
Job ID:
</td>
<td height="30">
<td height="20" style="user-select: initial">
{{agentJobHistoryInfo?.jobId || agentJobInfo?.jobId}}
</td>
</tr>
<tr class="step-row">
<td height="30">
<td height="20">
Message:
</td>
<td height="30">
<td height="20" style="user-select: initial">
{{agentJobHistoryInfo?.message}}
</td>
</tr>
<tr class="step-row">
<td height="30">
<td height="20">
Duration:
</td>
<td height="30">
<td height="20">
{{agentJobHistoryInfo?.runDuration}}
</td>
</tr>
<tr class="step-row">
<td height="30">
<td height="20">
Server:
</td>
<td>
@@ -143,23 +143,23 @@
</td>
</tr>
<tr class="step-row">
<td height="30">
<td height="20">
SQL message ID:
</td>
<td height="30">
<td height="20">
{{agentJobHistoryInfo?.sqlMessageId}}
</td>
</tr>
<tr class="step-row">
<td height="30">
<td height="20">
Retries Attempted:
</td>
<td height="30">
<td height="20">
{{agentJobHistoryInfo?.retriesAttempted}}
</td>
</tr>
</table>
<jobstepsview-component *ngIf="showSteps === true" [stepRows]="_stepRows"></jobstepsview-component>
<jobstepsview-component *ngIf="showSteps === true"></jobstepsview-component>
<h3 *ngIf="showSteps === false">No Steps Available</h3>
</div>
</div>

View File

@@ -5,7 +5,7 @@
import 'vs/css!./jobHistory';
import { OnInit, OnChanges, Component, Inject, Input, forwardRef, ElementRef, ChangeDetectorRef, ViewChild, ChangeDetectionStrategy } from '@angular/core';
import { OnInit, OnChanges, Component, Inject, Input, forwardRef, ElementRef, ChangeDetectorRef, ViewChild, ChangeDetectionStrategy, Injectable } from '@angular/core';
import { AgentJobHistoryInfo, AgentJobInfo } from 'sqlops';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { attachListStyler } from 'vs/platform/theme/common/styler';
@@ -25,6 +25,7 @@ import { JobHistoryController, JobHistoryDataSource,
import { JobStepsViewComponent } from 'sql/parts/jobManagement/views/jobStepsView.component';
import { JobStepsViewRow } from './jobStepsViewTree';
import { JobCacheObject } from 'sql/parts/jobManagement/common/jobManagementService';
import { AgentJobUtilities } from '../common/agentJobUtilities';
export const DASHBOARD_SELECTOR: string = 'jobhistory-component';
@@ -33,6 +34,7 @@ export const DASHBOARD_SELECTOR: string = 'jobhistory-component';
templateUrl: decodeURI(require.toUrl('./jobHistory.component.html')),
changeDetection: ChangeDetectionStrategy.OnPush
})
@Injectable()
export class JobHistoryComponent extends Disposable implements OnInit {
private _jobManagementService: IJobManagementService;
@@ -102,28 +104,16 @@ export class JobHistoryComponent extends Disposable implements OnInit {
} else {
tree.setFocus(element, payload);
tree.setSelection([element], payload);
self.agentJobHistoryInfo = self._treeController.jobHistories.filter(history => history.instanceId === element.instanceID)[0];
if (self.agentJobHistoryInfo) {
self.agentJobHistoryInfo.runDate = self.formatTime(self.agentJobHistoryInfo.runDate);
if (self.agentJobHistoryInfo.steps) {
self._stepRows = self.agentJobHistoryInfo.steps.map(step => {
let stepViewRow = new JobStepsViewRow();
stepViewRow.message = step.message;
stepViewRow.runStatus = JobHistoryRow.convertToStatusString(self.agentJobHistoryInfo.runStatus);
self._runStatus = stepViewRow.runStatus;
stepViewRow.stepName = step.stepName;
stepViewRow.stepID = step.stepId.toString();
return stepViewRow;
});
this._showSteps = true;
} else {
this._showSteps = false;
}
self._cd.detectChanges();
}
self.setStepsTree(element);
}
return true;
};
this._treeController.onKeyDown = (tree, event) => {
this._treeController.onKeyDownWrapper(tree, event);
let element = tree.getFocus();
self.setStepsTree(element);
return true;
}
this._tree = new Tree(this._tableContainer.nativeElement, {
controller: this._treeController,
dataSource: this._treeDataSource,
@@ -145,6 +135,7 @@ export class JobHistoryComponent extends Disposable implements OnInit {
if (jobHistories && jobHistories.length > 0) {
const self = this;
if (this._jobCacheObject.prevJobID === this._agentViewComponent.jobId || jobHistories[0].jobId === this._agentViewComponent.jobId) {
this._showPreviousRuns = true;
this.buildHistoryTree(self, jobHistories);
this._cd.detectChanges();
}
@@ -185,6 +176,29 @@ export class JobHistoryComponent extends Disposable implements OnInit {
});
}
private setStepsTree(element: any) {
const self = this;
self.agentJobHistoryInfo = self._treeController.jobHistories.filter(history => history.instanceId === element.instanceID)[0];
if (self.agentJobHistoryInfo) {
self.agentJobHistoryInfo.runDate = self.formatTime(self.agentJobHistoryInfo.runDate);
if (self.agentJobHistoryInfo.steps) {
self._stepRows = self.agentJobHistoryInfo.steps.map(step => {
let stepViewRow = new JobStepsViewRow();
stepViewRow.message = step.message;
stepViewRow.runStatus = AgentJobUtilities.convertToStatusString(step.runStatus);
self._runStatus = AgentJobUtilities.convertToStatusString(self.agentJobHistoryInfo.runStatus);
stepViewRow.stepName = step.stepName;
stepViewRow.stepID = step.stepId.toString();
return stepViewRow;
});
this._showSteps = true;
} else {
this._showSteps = false;
}
self._cd.detectChanges();
}
}
private buildHistoryTree(self: any, jobHistories: AgentJobHistoryInfo[]) {
self._treeController.jobHistories = jobHistories;
self._jobCacheObject.setJobHistory(self._agentViewComponent.jobId, jobHistories);
@@ -247,7 +261,7 @@ export class JobHistoryComponent extends Disposable implements OnInit {
private convertToJobHistoryRow(historyInfo: AgentJobHistoryInfo): JobHistoryRow {
let jobHistoryRow = new JobHistoryRow();
jobHistoryRow.runDate = historyInfo.runDate;
jobHistoryRow.runStatus = JobHistoryRow.convertToStatusString(historyInfo.runStatus);
jobHistoryRow.runStatus = AgentJobUtilities.convertToStatusString(historyInfo.runStatus);
jobHistoryRow.instanceID = historyInfo.instanceId;
return jobHistoryRow;
}
@@ -264,5 +278,9 @@ export class JobHistoryComponent extends Disposable implements OnInit {
this._showSteps = value;
this._cd.detectChanges();
}
public get stepRows() {
return this._stepRows;
}
}

View File

@@ -186,6 +186,10 @@ table.step-list tr.step-row td {
height: 100%;
}
.history-details > .job-steps > .step-list {
padding-bottom: 10px;
}
.vs-dark .step-table .monaco-tree .monaco-tree-rows.show-twisties > .monaco-tree-row.has-children > .content:before {
background-image: none;
}
@@ -202,11 +206,13 @@ table.step-list tr.step-row td {
height: 10px;
width: 10px;
display: inline-block;
vertical-align: middle;
}
.step-table .list-row .label {
padding-left: 10px;
display: inline-block;
vertical-align: middle;
}
.job-passed {
@@ -228,5 +234,5 @@ table.step-list tr.step-row td {
.steps-tree .monaco-tree .monaco-tree-row {
white-space: normal;
height: 40px !important;
min-height: 40px !important;
}

View File

@@ -28,20 +28,14 @@ import { OEAction } from 'sql/parts/objectExplorer/viewlet/objectExplorerActions
import { Builder, $, withElementById } from 'vs/base/browser/builder';
import { AgentJobHistoryInfo } from 'sqlops';
import { Agent } from 'vs/base/node/request';
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { JobHistoryComponent } from './jobHistory.component';
export class JobHistoryRow {
runDate: string;
runStatus: string;
instanceID: number;
rowID: string = generateUuid();
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
@@ -56,10 +50,6 @@ export class JobHistoryController extends TreeDefaults.DefaultController {
return true;
}
public onContextMenu(tree: tree.ITree, element: JobHistoryRow, event: tree.ContextMenuEvent): boolean {
return true;
}
public set jobHistories(value: AgentJobHistoryInfo[]) {
this._jobHistories = value;
}
@@ -68,6 +58,17 @@ export class JobHistoryController extends TreeDefaults.DefaultController {
return this._jobHistories;
}
public onKeyDownWrapper(tree: tree.ITree, event: IKeyboardEvent): boolean {
if (event.code === 'ArrowDown' || event.keyCode === 40) {
return super.onDown(tree, event);
} else if (event.code === 'ArrowUp' || event.keyCode === 38) {
return super.onUp(tree, event);
} else {
event.preventDefault();
event.stopPropagation();
return true;
}
}
}
export class JobHistoryDataSource implements tree.IDataSource {
@@ -119,7 +120,7 @@ export class JobHistoryRenderer implements tree.IRenderer {
private _statusIcon: HTMLElement;
public getHeight(tree: tree.ITree, element: JobHistoryRow): number {
return 22;
return 30;
}
public getTemplateId(tree: tree.ITree, element: JobHistoryRow | JobHistoryModel): string {

View File

@@ -5,7 +5,10 @@
*--------------------------------------------------------------------------------------------*/
-->
<h1>Steps</h1>
<div class="steps-header">
<div class="steps-icon"></div>
<h1 style="display: inline">Steps</h1>
</div>
<table class='step-columns'>
<tr>
<td class='step-id-col'>

View File

@@ -5,7 +5,7 @@
import 'vs/css!./jobStepsView';
import { OnInit, Component, Inject, forwardRef, ElementRef, ChangeDetectorRef, OnChanges, ViewChild, Input, Injectable } from '@angular/core';
import { OnInit, Component, Inject, forwardRef, ElementRef, ChangeDetectorRef, ViewChild, Injectable, AfterContentChecked } from '@angular/core';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { attachListStyler } from 'vs/platform/theme/common/styler';
import { Tree } from 'vs/base/parts/tree/browser/treeImpl';
@@ -17,6 +17,7 @@ import { DashboardServiceInterface } from 'sql/parts/dashboard/services/dashboar
import { AgentJobHistoryInfo } from 'sqlops';
import { JobStepsViewController, JobStepsViewDataSource, JobStepsViewFilter,
JobStepsViewRenderer, JobStepsViewRow, JobStepsViewModel} from 'sql/parts/jobManagement/views/jobStepsViewTree';
import { JobHistoryComponent } from 'sql/parts/jobManagement/views/jobHistory.component';
export const JOBSTEPSVIEW_SELECTOR: string = 'jobstepsview-component';
@@ -24,7 +25,7 @@ export const JOBSTEPSVIEW_SELECTOR: string = 'jobstepsview-component';
selector: JOBSTEPSVIEW_SELECTOR,
templateUrl: decodeURI(require.toUrl('./jobStepsView.component.html'))
})
export class JobStepsViewComponent extends Disposable implements OnInit, OnChanges {
export class JobStepsViewComponent extends Disposable implements OnInit, AfterContentChecked {
private _jobManagementService: IJobManagementService;
private _tree: Tree;
@@ -36,18 +37,35 @@ export class JobStepsViewComponent extends Disposable implements OnInit, OnChang
@ViewChild('table') private _tableContainer: ElementRef;
@Input() public stepRows: JobStepsViewRow[] = [];
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(() => DashboardServiceInterface)) private _dashboardService: DashboardServiceInterface,
@Inject(forwardRef(() => JobHistoryComponent)) private _jobHistoryComponent: JobHistoryComponent
) {
super();
this._jobManagementService = bootstrapService.jobManagementService;
}
ngAfterContentChecked() {
if (this._jobHistoryComponent.stepRows.length > 0) {
this._treeDataSource.data = this._jobHistoryComponent.stepRows;
if (!this._tree) {
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(JobStepsViewComponent._pageSize);
this._tree.setInput(new JobStepsViewModel());
}
}
ngOnInit() {
let ownerUri: string = this._dashboardService.connectionManagementService.connectionInfo.ownerUri;
this._tree = new Tree(this._tableContainer.nativeElement, {
@@ -56,22 +74,7 @@ export class JobStepsViewComponent extends Disposable implements OnInit, OnChang
filter: this._treeFilter,
renderer: this._treeRenderer
});
}
ngOnChanges() {
if (this.stepRows.length > 0) {
this._treeDataSource.data = this.stepRows;
if (!this._tree) {
this._tree = new Tree(this._tableContainer.nativeElement, {
controller: this._treeController,
dataSource: this._treeDataSource,
filter: this._treeFilter,
renderer: this._treeRenderer
});
}
this._tree.layout(JobStepsViewComponent._pageSize);
this._tree.setInput(new JobStepsViewModel());
}
this._register(attachListStyler(this._tree, this.bootstrapService.themeService));
}
}

View File

@@ -3,28 +3,29 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.steps-tree .list-row .status-icon {
height: 10px;
width: 10px;
display: inline-block;
margin-top: 4px;
.steps-tree .list-row .status-icon.step-passed {
content: url("../common/media/success_complete.svg");
height: 100%;
}
.steps-tree .list-row .status-icon.step-failed {
content: url("../common/media/failed.svg");
height: 100%;
}
.steps-tree .list-row .label {
padding-left: 10px;
display: flex;
text-align: center;
vertical-align: middle;
user-select: initial;
}
.step-passed {
background: green;
}
.step-failed {
background: red;
}
.step-unknown {
.steps-tree .list-row .status-icon.step-unknown {
height: 10px;
width: 10px;
display: inline-block;
margin-top: 4px;
background: yellow;
}
@@ -35,6 +36,7 @@
.step-columns {
padding-left: 50px;
padding-top: 10px;
}
.step-columns .step-id-col, .steps-tree .tree-id-col {
@@ -57,4 +59,24 @@
white-space: normal;
text-align: center;
width: 680px;
}
}
.steps-header > .steps-icon {
height: 25px;
padding-right: 10px;
display: inline;
vertical-align: middle;
}
.vs-dark .steps-header > .steps-icon,
.hc-black .steps-header > .steps-icon {
content: url("../common/media/step_inverse.svg");
}
.steps-header > .steps-icon {
content: url("../common/media/step.svg");
}
jobstepsview-component {
padding-top: 10px;
}

View File

@@ -164,7 +164,7 @@ export class JobStepsViewRenderer implements tree.IRenderer {
private createStatusIcon(): HTMLElement {
let statusIcon: HTMLElement = DOM.$('div');
statusIcon.className += ' status-icon';
statusIcon.className += 'status-icon';
return statusIcon;
}
}

View File

@@ -4,6 +4,8 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
-->
<h1 class="job-heading">Jobs</h1>
<div class="job-heading-container">
<h1 class="job-heading">Jobs</h1>
</div>
<div #jobsgrid class="jobview-grid"></div>

View File

@@ -32,6 +32,7 @@ import { JobHistoryComponent } from './jobHistory.component';
import { AgentViewComponent } from '../agent/agentView.component';
import { RowDetailView } from 'sql/base/browser/ui/table/plugins/rowdetailview';
import { JobCacheObject } from 'sql/parts/jobManagement/common/jobManagementService';
import { AgentJobUtilities } from '../common/agentJobUtilities';
export const JOBSVIEW_SELECTOR: string = 'jobsview-component';
@@ -49,16 +50,15 @@ export class JobsViewComponent implements AfterContentChecked {
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' },
{ name: nls.localize('jobColumns.name','Name'), field: 'name', formatter: this.renderName, width: 200 },
{ name: nls.localize('jobColumns.lastRun','Last Run'), field: 'lastRun', minWidth: 150 },
{ name: nls.localize('jobColumns.nextRun','Next Run'), field: 'nextRun', minWidth: 150 },
{ name: nls.localize('jobColumns.enabled','Enabled'), field: 'enabled', minWidth: 70 },
{ name: nls.localize('jobColumns.status','Status'), field: 'currentExecutionStatus', minWidth: 60 },
{ name: nls.localize('jobColumns.category','Category'), field: 'category', minWidth: 150 },
{ name: nls.localize('jobColumns.runnable','Runnable'), field: 'runnable', minWidth: 50 },
{ name: nls.localize('jobColumns.schedule','Schedule'), field: 'hasSchedule', minWidth: 50 },
{ name: nls.localize('jobColumns.lastRunOutcome', 'Last Run Outcome'), field: 'lastRunOutcome', minWidth: 150 },
];
private rowDetail: any;
@@ -125,24 +125,14 @@ export class JobsViewComponent implements AfterContentChecked {
syncColumnCellResize: true,
enableColumnReorder: false,
rowHeight: 45,
enableCellNavigation: true
enableCellNavigation: true,
autoHeight: false,
forceFitColumns: false
};
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, null);
},
panelRows: 2,
postTemplate: () => ''
});
this.rowDetail = rowDetail;
this.rowDetail = new RowDetailView({});
columns.unshift(this.rowDetail.getColumnDefinition());
this._table = new Table(this._gridEl.nativeElement, undefined, columns, options);
this._table.grid.setData(this.dataView, true);
@@ -174,27 +164,17 @@ export class JobsViewComponent implements AfterContentChecked {
id: job.jobId,
jobId: job.jobId,
name: job.name,
lastRun: job.lastRun,
nextRun: job.nextRun,
enabled: job.enabled,
currentExecutionStatus: job.currentExecutionStatus,
lastRun: AgentJobUtilities.convertToLastRun(job.lastRun),
nextRun: AgentJobUtilities.convertToNextRun(job.nextRun),
enabled: AgentJobUtilities.convertToResponse(job.enabled),
currentExecutionStatus: AgentJobUtilities.convertToExecutionStatusString(job.currentExecutionStatus),
category: job.category,
runnable: job.runnable,
hasSchedule: job.hasSchedule,
categoryId: job.categoryId,
lastRunOutcome: job.lastRunOutcome
runnable: AgentJobUtilities.convertToResponse(job.runnable),
hasSchedule: AgentJobUtilities.convertToResponse(job.hasSchedule),
lastRunOutcome: AgentJobUtilities.convertToStatusString(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();
@@ -209,8 +189,27 @@ export class JobsViewComponent implements AfterContentChecked {
}
renderName(row, cell, value, columnDef, dataContext) {
let resultIndicatorClass: string;
switch (dataContext.lastRunOutcome) {
case ('Succeeded'):
resultIndicatorClass = 'jobview-jobnameindicatorsuccess';
break;
case ('Failed'):
resultIndicatorClass = 'jobview-jobnameindicatorfailure';
break;
case ('Canceled'):
resultIndicatorClass = 'jobview-jobnameindicatorcancel';
break;
case ('Status Unknown'):
resultIndicatorClass = 'jobview-jobnameindicatorunknown';
break;
default:
resultIndicatorClass = 'jobview-jobnameindicatorunknown';
break;
}
return '<table class="jobview-jobnametable"><tr class="jobview-jobnamerow">' +
'<td nowrap class="jobview-jobnameindicatorsuccess"></td>' +
'<td nowrap class=' + resultIndicatorClass + '></td>' +
'<td nowrap class="jobview-jobnametext">' + dataContext.name + '</td>' +
'</tr></table>';
}

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

@@ -1065,6 +1065,7 @@ declare module 'sqlops' {
stepName: string;
message: string;
runDate: string;
runStatus: number;
}
export interface AgentJobHistoryInfo {