mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-17 02:51:36 -05:00
Agent work (WIP) (#1012)
* 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 * 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 * Rename some 'agent' classes to 'jobManagement' * 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 * 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 * added steps lists * fixed style and dimensions * fixed conflicts * Switch back getJobs return type * Make enum const * Remove sqlops const * WIP * WIP * 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 * 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 * added method signatures for job history with DMP * Clean up jobs styling and call getJobHistory * Add more Job Table styling * Enable detail view in job table * Use updated slickgrid repo * vbumped slickgrid * added methods for job running * added job actions to sqlops * 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 * navigation works but is really slow to load data * Update package.json * fixed conflicts * 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 * fixed bug where not all steps were being shown * Misc. cleanups * added more to history page * added loadHistories and code review comments * made the params standard * fixed json local paths * added step implementation * fixed conflict * cleaned up code * removed extension-modules * CR comments * fix css * fixed data injection * steps now support big messages * improve history page UX
This commit is contained in:
@@ -51,7 +51,7 @@ import { JobHistoryComponent } from 'sql/parts/jobManagement/views/jobHistory.co
|
|||||||
let baseComponents = [DashboardHomeContainer, DashboardComponent, DashboardWidgetWrapper, DashboardWebviewContainer,
|
let baseComponents = [DashboardHomeContainer, DashboardComponent, DashboardWidgetWrapper, DashboardWebviewContainer,
|
||||||
DashboardWidgetContainer, DashboardGridContainer, DashboardErrorContainer, DashboardNavSection, WebviewContent, WidgetContent,
|
DashboardWidgetContainer, DashboardGridContainer, DashboardErrorContainer, DashboardNavSection, WebviewContent, WidgetContent,
|
||||||
ComponentHostDirective, BreadcrumbComponent, ControlHostContent, DashboardControlHostContainer,
|
ComponentHostDirective, BreadcrumbComponent, ControlHostContent, DashboardControlHostContainer,
|
||||||
JobsViewComponent, AgentViewComponent, JobHistoryComponent];
|
JobsViewComponent, AgentViewComponent, JobHistoryComponent, JobStepsViewComponent];
|
||||||
|
|
||||||
/* Panel */
|
/* Panel */
|
||||||
import { PanelModule } from 'sql/base/browser/ui/panel/panel.module';
|
import { PanelModule } from 'sql/base/browser/ui/panel/panel.module';
|
||||||
@@ -68,6 +68,7 @@ import { ExplorerWidget } from 'sql/parts/dashboard/widgets/explorer/explorerWid
|
|||||||
import { TasksWidget } from 'sql/parts/dashboard/widgets/tasks/tasksWidget.component';
|
import { TasksWidget } from 'sql/parts/dashboard/widgets/tasks/tasksWidget.component';
|
||||||
import { InsightsWidget } from 'sql/parts/dashboard/widgets/insights/insightsWidget.component';
|
import { InsightsWidget } from 'sql/parts/dashboard/widgets/insights/insightsWidget.component';
|
||||||
import { WebviewWidget } from 'sql/parts/dashboard/widgets/webview/webviewWidget.component';
|
import { WebviewWidget } from 'sql/parts/dashboard/widgets/webview/webviewWidget.component';
|
||||||
|
import { JobStepsViewComponent } from '../jobManagement/views/jobStepsView.component';
|
||||||
|
|
||||||
let widgetComponents = [
|
let widgetComponents = [
|
||||||
PropertiesWidgetComponent,
|
PropertiesWidgetComponent,
|
||||||
|
|||||||
@@ -106,7 +106,15 @@
|
|||||||
<h3>Status:</h3>
|
<h3>Status:</h3>
|
||||||
</td>
|
</td>
|
||||||
<td height="30">
|
<td height="30">
|
||||||
<h3></h3>
|
<h3>{{_runStatus}}</h3>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="step-row">
|
||||||
|
<td height="30">
|
||||||
|
Job ID:
|
||||||
|
</td>
|
||||||
|
<td height="30">
|
||||||
|
{{agentJobHistoryInfo?.jobId}}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr class="step-row">
|
<tr class="step-row">
|
||||||
@@ -127,10 +135,10 @@
|
|||||||
</tr>
|
</tr>
|
||||||
<tr class="step-row">
|
<tr class="step-row">
|
||||||
<td height="30">
|
<td height="30">
|
||||||
Log:
|
Server:
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
|
{{agentJobHistoryInfo?.server}}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr class="step-row">
|
<tr class="step-row">
|
||||||
@@ -138,7 +146,7 @@
|
|||||||
SQL message ID:
|
SQL message ID:
|
||||||
</td>
|
</td>
|
||||||
<td height="30">
|
<td height="30">
|
||||||
|
{{agentJobHistoryInfo?.sqlMessageId}}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr class="step-row">
|
<tr class="step-row">
|
||||||
@@ -150,6 +158,7 @@
|
|||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
<jobstepsview-component *ngIf="showSteps() === true" [stepRows]="_stepRows"></jobstepsview-component>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,8 @@
|
|||||||
|
|
||||||
import 'vs/css!./jobHistory';
|
import 'vs/css!./jobHistory';
|
||||||
|
|
||||||
import { OnInit, Component, Inject, forwardRef, ElementRef, ChangeDetectorRef, OnDestroy, ViewChild, Input, Injectable } from '@angular/core';
|
import { OnInit, OnChanges, Component, Inject, forwardRef, ElementRef, ChangeDetectorRef, OnDestroy, ViewChild, Input, Injectable } from '@angular/core';
|
||||||
|
import { AgentJobHistoryInfo, AgentJobInfo } from 'sqlops';
|
||||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||||
import { attachListStyler } from 'vs/platform/theme/common/styler';
|
import { attachListStyler } from 'vs/platform/theme/common/styler';
|
||||||
import { Tree } from 'vs/base/parts/tree/browser/treeImpl';
|
import { Tree } from 'vs/base/parts/tree/browser/treeImpl';
|
||||||
@@ -18,8 +19,8 @@ import { DashboardServiceInterface } from 'sql/parts/dashboard/services/dashboar
|
|||||||
import { AgentViewComponent } from 'sql/parts/jobManagement/agent/agentView.component';
|
import { AgentViewComponent } from 'sql/parts/jobManagement/agent/agentView.component';
|
||||||
import { JobHistoryController, JobHistoryDataSource,
|
import { JobHistoryController, JobHistoryDataSource,
|
||||||
JobHistoryRenderer, JobHistoryFilter, JobHistoryModel, JobHistoryRow } from 'sql/parts/jobManagement/views/jobHistoryTree';
|
JobHistoryRenderer, JobHistoryFilter, JobHistoryModel, JobHistoryRow } from 'sql/parts/jobManagement/views/jobHistoryTree';
|
||||||
import { AgentJobHistoryInfo, AgentJobInfo } from 'sqlops';
|
import { JobStepsViewComponent } from 'sql/parts/jobManagement/views/jobStepsView.component';
|
||||||
|
import { JobStepsViewRow } from './jobStepsViewTree';
|
||||||
|
|
||||||
export const DASHBOARD_SELECTOR: string = 'jobhistory-component';
|
export const DASHBOARD_SELECTOR: string = 'jobhistory-component';
|
||||||
|
|
||||||
@@ -27,7 +28,7 @@ export const DASHBOARD_SELECTOR: string = 'jobhistory-component';
|
|||||||
selector: DASHBOARD_SELECTOR,
|
selector: DASHBOARD_SELECTOR,
|
||||||
templateUrl: decodeURI(require.toUrl('./jobHistory.component.html'))
|
templateUrl: decodeURI(require.toUrl('./jobHistory.component.html'))
|
||||||
})
|
})
|
||||||
export class JobHistoryComponent extends Disposable implements OnInit, OnDestroy {
|
export class JobHistoryComponent extends Disposable implements OnInit {
|
||||||
|
|
||||||
private _jobManagementService: IJobManagementService;
|
private _jobManagementService: IJobManagementService;
|
||||||
private _tree: Tree;
|
private _tree: Tree;
|
||||||
@@ -42,8 +43,11 @@ export class JobHistoryComponent extends Disposable implements OnInit, OnDestroy
|
|||||||
@Input() public jobId: string = undefined;
|
@Input() public jobId: string = undefined;
|
||||||
@Input() public agentJobHistoryInfo: AgentJobHistoryInfo = undefined;
|
@Input() public agentJobHistoryInfo: AgentJobHistoryInfo = undefined;
|
||||||
|
|
||||||
private prevJobId: string = undefined;
|
private _prevJobId: string = undefined;
|
||||||
private isVisible: boolean = false;
|
private _isVisible: boolean = false;
|
||||||
|
private _stepRows: JobStepsViewRow[] = [];
|
||||||
|
private _showSteps: boolean = false;
|
||||||
|
private _runStatus: string = undefined;
|
||||||
|
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@@ -65,24 +69,32 @@ export class JobHistoryComponent extends Disposable implements OnInit, OnDestroy
|
|||||||
const isDoubleClick = (origin === 'mouse' && event.detail === 2);
|
const isDoubleClick = (origin === 'mouse' && event.detail === 2);
|
||||||
// Cancel Event
|
// Cancel Event
|
||||||
const isMouseDown = event && event.browserEvent && event.browserEvent.type === 'mousedown';
|
const isMouseDown = event && event.browserEvent && event.browserEvent.type === 'mousedown';
|
||||||
|
|
||||||
if (!isMouseDown) {
|
if (!isMouseDown) {
|
||||||
event.preventDefault(); // we cannot preventDefault onMouseDown because this would break DND otherwise
|
event.preventDefault(); // we cannot preventDefault onMouseDown because this would break DND otherwise
|
||||||
}
|
}
|
||||||
|
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
|
||||||
tree.setFocus(element, payload);
|
tree.setFocus(element, payload);
|
||||||
|
|
||||||
if (element && isDoubleClick) {
|
if (element && isDoubleClick) {
|
||||||
event.preventDefault(); // focus moves to editor, we need to prevent default
|
event.preventDefault(); // focus moves to editor, we need to prevent default
|
||||||
} else {
|
} else {
|
||||||
tree.setFocus(element, payload);
|
tree.setFocus(element, payload);
|
||||||
tree.setSelection([element], payload);
|
tree.setSelection([element], payload);
|
||||||
self.agentJobHistoryInfo = self._treeController.jobHistories.filter(history => history.instanceId === element.instanceID)[0];
|
self.agentJobHistoryInfo = self._treeController.jobHistories.filter(history => history.instanceId === element.instanceID)[0];
|
||||||
|
if (self.agentJobHistoryInfo) {
|
||||||
self.agentJobHistoryInfo.runDate = self.formatTime(self.agentJobHistoryInfo.runDate);
|
self.agentJobHistoryInfo.runDate = self.formatTime(self.agentJobHistoryInfo.runDate);
|
||||||
|
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;
|
||||||
self._cd.detectChanges();
|
self._cd.detectChanges();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
this._tree = new Tree(this._tableContainer.nativeElement, {
|
this._tree = new Tree(this._tableContainer.nativeElement, {
|
||||||
@@ -95,14 +107,11 @@ export class JobHistoryComponent extends Disposable implements OnInit, OnDestroy
|
|||||||
this._tree.layout(1024);
|
this._tree.layout(1024);
|
||||||
}
|
}
|
||||||
|
|
||||||
ngOnDestroy() {
|
|
||||||
}
|
|
||||||
|
|
||||||
ngAfterContentChecked() {
|
ngAfterContentChecked() {
|
||||||
if (this.isVisible === false && this._tableContainer.nativeElement.offsetParent !== null) {
|
if (this._isVisible === false && this._tableContainer.nativeElement.offsetParent !== null) {
|
||||||
if (this.prevJobId !== this.jobId) {
|
if (this._prevJobId !== this.jobId) {
|
||||||
this.loadHistory();
|
this.loadHistory();
|
||||||
this.prevJobId = this.jobId;
|
this._prevJobId = this.jobId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -139,7 +148,7 @@ export class JobHistoryComponent extends Disposable implements OnInit, OnDestroy
|
|||||||
}
|
}
|
||||||
|
|
||||||
private goToJobs(): void {
|
private goToJobs(): void {
|
||||||
this.isVisible = false;
|
this._isVisible = false;
|
||||||
this._agentViewComponent.showHistory = false;
|
this._agentViewComponent.showHistory = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,5 +163,9 @@ export class JobHistoryComponent extends Disposable implements OnInit, OnDestroy
|
|||||||
private formatTime(time: string): string {
|
private formatTime(time: string): string {
|
||||||
return time.replace('T', ' ');
|
return time.replace('T', ' ');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public showSteps(): boolean {
|
||||||
|
return this._showSteps;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -153,6 +153,11 @@ input#accordion:checked ~ .accordion-content {
|
|||||||
padding: 10px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.accordion-content #col1,
|
||||||
|
.accordion-content #col3 {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
.accordion-content #col2 {
|
.accordion-content #col2 {
|
||||||
padding-right: 300px;
|
padding-right: 300px;
|
||||||
}
|
}
|
||||||
@@ -171,6 +176,7 @@ table.step-list tr.step-row td {
|
|||||||
border-left: 3px solid #444444;
|
border-left: 3px solid #444444;
|
||||||
padding-left: 10px;
|
padding-left: 10px;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
width: 90%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.history-details > .job-steps {
|
.history-details > .job-steps {
|
||||||
@@ -203,15 +209,15 @@ table.step-list tr.step-row td {
|
|||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.passed {
|
.job-passed {
|
||||||
background: green;
|
background: green;
|
||||||
}
|
}
|
||||||
|
|
||||||
.failed {
|
.job-failed {
|
||||||
background: red;
|
background: red;
|
||||||
}
|
}
|
||||||
|
|
||||||
.unknown {
|
.job-unknown {
|
||||||
background: yellow;
|
background: yellow;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -219,3 +225,8 @@ table.step-list tr.step-row td {
|
|||||||
padding-left: 50px;
|
padding-left: 50px;
|
||||||
width: 140px;
|
width: 140px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.steps-tree .monaco-tree .monaco-tree-row {
|
||||||
|
white-space: normal;
|
||||||
|
height: 40px !important;
|
||||||
|
}
|
||||||
@@ -145,11 +145,11 @@ export class JobHistoryRenderer implements tree.IRenderer {
|
|||||||
templateData.label.innerText = element.runDate + '\t\t\t' + element.runStatus;
|
templateData.label.innerText = element.runDate + '\t\t\t' + element.runStatus;
|
||||||
let statusClass: string;
|
let statusClass: string;
|
||||||
if (element.runStatus === 'Succeeded') {
|
if (element.runStatus === 'Succeeded') {
|
||||||
statusClass = ' passed';
|
statusClass = ' job-passed';
|
||||||
} else if (element.runStatus === 'Failed') {
|
} else if (element.runStatus === 'Failed') {
|
||||||
statusClass = ' failed';
|
statusClass = ' job-failed';
|
||||||
} else {
|
} else {
|
||||||
statusClass = ' unknown';
|
statusClass = ' job-unknown';
|
||||||
}
|
}
|
||||||
this._statusIcon.className += statusClass;
|
this._statusIcon.className += statusClass;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
<!--
|
||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
-->
|
||||||
|
|
||||||
|
<h1>Steps</h1>
|
||||||
|
<table class='step-columns'>
|
||||||
|
<tr>
|
||||||
|
<td class='step-id-col'>
|
||||||
|
<b>Step ID</b>
|
||||||
|
</td>
|
||||||
|
<td class='step-name-col'>
|
||||||
|
<b>Step Name</b>
|
||||||
|
</td>
|
||||||
|
<td class='step-message-col'>
|
||||||
|
<b>Message</b>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<div class='steps-tree' #table style="height: 100%; width: 100%"></div>
|
||||||
80
src/sql/parts/jobManagement/views/jobStepsView.component.ts
Normal file
80
src/sql/parts/jobManagement/views/jobStepsView.component.ts
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* 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!./jobStepsView';
|
||||||
|
|
||||||
|
import { OnInit, Component, Inject, forwardRef, ElementRef, ChangeDetectorRef, OnChanges, ViewChild, Input, Injectable } 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';
|
||||||
|
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||||
|
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
|
||||||
|
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 { AgentJobHistoryInfo } from 'sqlops';
|
||||||
|
import { JobStepsViewController, JobStepsViewDataSource, JobStepsViewFilter,
|
||||||
|
JobStepsViewRenderer, JobStepsViewRow, JobStepsViewModel} from 'sql/parts/jobManagement/views/jobStepsViewTree';
|
||||||
|
|
||||||
|
export const JOBSTEPSVIEW_SELECTOR: string = 'jobstepsview-component';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: JOBSTEPSVIEW_SELECTOR,
|
||||||
|
templateUrl: decodeURI(require.toUrl('./jobStepsView.component.html'))
|
||||||
|
})
|
||||||
|
export class JobStepsViewComponent extends Disposable implements OnInit, OnChanges {
|
||||||
|
|
||||||
|
private _jobManagementService: IJobManagementService;
|
||||||
|
private _tree: Tree;
|
||||||
|
private _treeController = new JobStepsViewController();
|
||||||
|
private _treeDataSource = new JobStepsViewDataSource();
|
||||||
|
private _treeRenderer = new JobStepsViewRenderer();
|
||||||
|
private _treeFilter = new JobStepsViewFilter();
|
||||||
|
private static _pageSize = 1024;
|
||||||
|
|
||||||
|
@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
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
this._jobManagementService = bootstrapService.jobManagementService;
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
let ownerUri: string = this._dashboardService.connectionManagementService.connectionInfo.ownerUri;
|
||||||
|
this._tree = new Tree(this._tableContainer.nativeElement, {
|
||||||
|
controller: this._treeController,
|
||||||
|
dataSource: this._treeDataSource,
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ngAfterContentChecked() {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
61
src/sql/parts/jobManagement/views/jobStepsView.css
Normal file
61
src/sql/parts/jobManagement/views/jobStepsView.css
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
.list-row .label {
|
||||||
|
padding-left: 10px;
|
||||||
|
display: flex;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-passed {
|
||||||
|
background: green;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-failed {
|
||||||
|
background: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-unknown {
|
||||||
|
background: yellow;
|
||||||
|
}
|
||||||
|
|
||||||
|
.steps-tree .list-row {
|
||||||
|
display: inline-flex;
|
||||||
|
height: 20px
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-columns {
|
||||||
|
padding-left: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-id-col, .tree-id-col {
|
||||||
|
padding-left: 10px;
|
||||||
|
white-space: normal;
|
||||||
|
text-align: center;
|
||||||
|
width: 60px;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-name-col, .tree-name-col {
|
||||||
|
padding-right: 10px;
|
||||||
|
white-space: normal;
|
||||||
|
text-align: center;
|
||||||
|
width: 350px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-message-col, .tree-message-col {
|
||||||
|
padding-right: 10px;
|
||||||
|
white-space: normal;
|
||||||
|
text-align: center;
|
||||||
|
width: 680px;
|
||||||
|
height:
|
||||||
|
}
|
||||||
186
src/sql/parts/jobManagement/views/jobStepsViewTree.ts
Normal file
186
src/sql/parts/jobManagement/views/jobStepsViewTree.ts
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* 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';
|
||||||
|
import { AgentJobHistoryInfo } from 'sqlops';
|
||||||
|
import { Agent } from 'vs/base/node/request';
|
||||||
|
|
||||||
|
export class JobStepsViewRow {
|
||||||
|
public stepID: string;
|
||||||
|
public stepName: string;
|
||||||
|
public message: string;
|
||||||
|
public rowID: string = generateUuid();
|
||||||
|
public runStatus: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Empty class just for tree input
|
||||||
|
export class JobStepsViewModel {
|
||||||
|
public static readonly id = generateUuid();
|
||||||
|
}
|
||||||
|
|
||||||
|
export class JobStepsViewController extends TreeDefaults.DefaultController {
|
||||||
|
private _jobHistories: AgentJobHistoryInfo[];
|
||||||
|
|
||||||
|
protected onLeftClick(tree: tree.ITree, element: JobStepsViewRow, event: IMouseEvent, origin: string = 'mouse'): boolean {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public onContextMenu(tree: tree.ITree, element: JobStepsViewRow, event: tree.ContextMenuEvent): boolean {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public set jobHistories(value: AgentJobHistoryInfo[]) {
|
||||||
|
this._jobHistories = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get jobHistories(): AgentJobHistoryInfo[] {
|
||||||
|
return this._jobHistories;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export class JobStepsViewDataSource implements tree.IDataSource {
|
||||||
|
private _data: JobStepsViewRow[];
|
||||||
|
|
||||||
|
public getId(tree: tree.ITree, element: JobStepsViewRow | JobStepsViewModel): string {
|
||||||
|
if (element instanceof JobStepsViewModel) {
|
||||||
|
return JobStepsViewModel.id;
|
||||||
|
} else {
|
||||||
|
return (element as JobStepsViewRow).rowID;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public hasChildren(tree: tree.ITree, element: JobStepsViewRow | JobStepsViewModel): boolean {
|
||||||
|
if (element instanceof JobStepsViewModel) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public getChildren(tree: tree.ITree, element: JobStepsViewRow | JobStepsViewModel): Promise {
|
||||||
|
if (element instanceof JobStepsViewModel) {
|
||||||
|
return TPromise.as(this._data);
|
||||||
|
} else {
|
||||||
|
return TPromise.as(undefined);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public getParent(tree: tree.ITree, element: JobStepsViewRow | JobStepsViewModel): Promise {
|
||||||
|
if (element instanceof JobStepsViewModel) {
|
||||||
|
return TPromise.as(undefined);
|
||||||
|
} else {
|
||||||
|
return TPromise.as(new JobStepsViewModel());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public set data(data: JobStepsViewRow[]) {
|
||||||
|
this._data = data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IListTemplate {
|
||||||
|
statusIcon: HTMLElement;
|
||||||
|
label: HTMLElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class JobStepsViewRenderer implements tree.IRenderer {
|
||||||
|
private _statusIcon: HTMLElement;
|
||||||
|
|
||||||
|
public getHeight(tree: tree.ITree, element: JobStepsViewRow): number {
|
||||||
|
return 22;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getTemplateId(tree: tree.ITree, element: JobStepsViewRow | JobStepsViewModel): string {
|
||||||
|
if (element instanceof JobStepsViewModel) {
|
||||||
|
return 'jobStepsViewModel';
|
||||||
|
} else {
|
||||||
|
return 'jobStepsViewRow';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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: JobStepsViewRow, templateId: string, templateData: IListTemplate): void {
|
||||||
|
let stepIdCol: HTMLElement = DOM.$('div');
|
||||||
|
stepIdCol.className = 'tree-id-col';
|
||||||
|
stepIdCol.innerText = element.stepID;
|
||||||
|
let stepNameCol: HTMLElement = DOM.$('div');
|
||||||
|
stepNameCol.className = 'tree-name-col';
|
||||||
|
stepNameCol.innerText = element.stepName;
|
||||||
|
let stepMessageCol: HTMLElement = DOM.$('div');
|
||||||
|
stepMessageCol.className = 'tree-message-col';
|
||||||
|
stepMessageCol.innerText = element.message;
|
||||||
|
templateData.label.appendChild(stepIdCol);
|
||||||
|
templateData.label.appendChild(stepNameCol);
|
||||||
|
templateData.label.appendChild(stepMessageCol);
|
||||||
|
let statusClass: string;
|
||||||
|
if (element.runStatus === 'Succeeded') {
|
||||||
|
statusClass = ' step-passed';
|
||||||
|
} else if (element.runStatus === 'Failed') {
|
||||||
|
statusClass = ' step-failed';
|
||||||
|
} else {
|
||||||
|
statusClass = ' step-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 JobStepsViewFilter implements tree.IFilter {
|
||||||
|
private _filterString: string;
|
||||||
|
|
||||||
|
public isVisible(tree: tree.ITree, element: JobStepsViewRow): boolean {
|
||||||
|
return this._isJobVisible();
|
||||||
|
}
|
||||||
|
|
||||||
|
private _isJobVisible(): boolean {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public set filterString(val: string) {
|
||||||
|
this._filterString = val;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -208,8 +208,8 @@ export class JobsViewComponent implements OnInit, OnDestroy {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private getJob(args: Slick.OnClickEventArgs<any>): sqlops.AgentJobInfo {
|
private getJob(args: Slick.OnClickEventArgs<any>): sqlops.AgentJobInfo {
|
||||||
let cell = args.cell;
|
let row = args.row;
|
||||||
let jobName = args.grid.getCellNode(1, cell).innerText.trim();
|
let jobName = args.grid.getCellNode(row, 1).innerText.trim();
|
||||||
let job = this.jobs.filter(job => job.name === jobName)[0];
|
let job = this.jobs.filter(job => job.name === jobName)[0];
|
||||||
return job;
|
return job;
|
||||||
}
|
}
|
||||||
|
|||||||
8
src/sql/sqlops.d.ts
vendored
8
src/sql/sqlops.d.ts
vendored
@@ -1060,6 +1060,13 @@ declare module 'sqlops' {
|
|||||||
jobId: string;
|
jobId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface AgentJobStep {
|
||||||
|
stepId: number;
|
||||||
|
stepName: string;
|
||||||
|
message: string;
|
||||||
|
runDate: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface AgentJobHistoryInfo {
|
export interface AgentJobHistoryInfo {
|
||||||
instanceId: number;
|
instanceId: number;
|
||||||
sqlMessageId: number;
|
sqlMessageId: number;
|
||||||
@@ -1077,6 +1084,7 @@ declare module 'sqlops' {
|
|||||||
operatorPaged: string;
|
operatorPaged: string;
|
||||||
retriesAttempted: number;
|
retriesAttempted: number;
|
||||||
server: string;
|
server: string;
|
||||||
|
steps: AgentJobStep[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AgentServicesProvider extends DataProvider {
|
export interface AgentServicesProvider extends DataProvider {
|
||||||
|
|||||||
Reference in New Issue
Block a user