mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-31 09:35:39 -05:00
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:
@@ -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">
|
||||
|
||||
@@ -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';
|
||||
|
||||
54
src/sql/parts/jobManagement/common/agentJobUtilities.ts
Normal file
54
src/sql/parts/jobManagement/common/agentJobUtilities.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
1
src/sql/parts/jobManagement/common/media/failed.svg
Normal file
1
src/sql/parts/jobManagement/common/media/failed.svg
Normal 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 |
@@ -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;
|
||||
}
|
||||
1
src/sql/parts/jobManagement/common/media/step.svg
Normal file
1
src/sql/parts/jobManagement/common/media/step.svg
Normal 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 |
@@ -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 |
@@ -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 |
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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'>
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
1
src/sql/sqlops.d.ts
vendored
@@ -1065,6 +1065,7 @@ declare module 'sqlops' {
|
||||
stepName: string;
|
||||
message: string;
|
||||
runDate: string;
|
||||
runStatus: number;
|
||||
}
|
||||
|
||||
export interface AgentJobHistoryInfo {
|
||||
|
||||
Reference in New Issue
Block a user