mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-23 01:25:38 -05:00
Agent - bug fixes and mini features (#3637)
* fixed scrollbar in jobs * show steps tree when job history is opened * cleaned and added edit job to job history * scrollbars on step details * steps scrolling done * fixed styling * fixed keyboard selection, navigation and UI * fixed tabbing accessibility * added refresh action to job history * fixed focus on move step * added remove schedule button * fixed various bugs * added errors for all actions * review comments
This commit is contained in:
@@ -19,17 +19,21 @@ import { ProxiesViewComponent } from 'sql/parts/jobManagement/views/proxiesView.
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import * as TelemetryKeys from 'sql/common/telemetryKeys';
|
||||
import { telemetryURIDescriptor } from 'vs/platform/telemetry/common/telemetryUtils';
|
||||
import { IErrorMessageService } from 'sql/parts/connection/common/connectionManagement';
|
||||
|
||||
export const successLabel: string = nls.localize('jobaction.successLabel', 'Success');
|
||||
export const errorLabel: string = nls.localize('jobaction.faillabel', 'Error');
|
||||
|
||||
export enum JobActions {
|
||||
Run = 'run',
|
||||
Stop = 'stop'
|
||||
}
|
||||
|
||||
export interface IJobActionInfo {
|
||||
export class IJobActionInfo {
|
||||
ownerUri: string;
|
||||
targetObject: any;
|
||||
jobHistoryComponent?: JobHistoryComponent;
|
||||
jobViewComponent?: JobsViewComponent;
|
||||
}
|
||||
|
||||
// Job actions
|
||||
@@ -43,10 +47,10 @@ export class JobsRefreshAction extends Action {
|
||||
super(JobsRefreshAction.ID, JobsRefreshAction.LABEL, 'refreshIcon');
|
||||
}
|
||||
|
||||
public run(context: JobsViewComponent | JobHistoryComponent): TPromise<boolean> {
|
||||
public run(context: IJobActionInfo): TPromise<boolean> {
|
||||
return new TPromise<boolean>((resolve, reject) => {
|
||||
if (context) {
|
||||
context.refreshJobs();
|
||||
context.jobHistoryComponent.refreshJobs();
|
||||
resolve(true);
|
||||
} else {
|
||||
reject(false);
|
||||
@@ -82,33 +86,28 @@ export class RunJobAction extends Action {
|
||||
|
||||
constructor(
|
||||
@INotificationService private notificationService: INotificationService,
|
||||
@IErrorMessageService private errorMessageService: IErrorMessageService,
|
||||
@IJobManagementService private jobManagementService: IJobManagementService,
|
||||
@IInstantiationService private instantationService: IInstantiationService,
|
||||
@ITelemetryService private telemetryService: ITelemetryService
|
||||
) {
|
||||
super(RunJobAction.ID, RunJobAction.LABEL, 'runJobIcon');
|
||||
super(RunJobAction.ID, RunJobAction.LABEL, 'start');
|
||||
}
|
||||
|
||||
public run(context: JobHistoryComponent): TPromise<boolean> {
|
||||
let jobName = context.agentJobInfo.name;
|
||||
public run(context: IJobActionInfo): TPromise<boolean> {
|
||||
let jobName = context.targetObject.name;
|
||||
let ownerUri = context.ownerUri;
|
||||
let refreshAction = this.instantationService.createInstance(JobsRefreshAction);
|
||||
this.telemetryService.publicLog(TelemetryKeys.RunAgentJob);
|
||||
return new TPromise<boolean>((resolve, reject) => {
|
||||
this.jobManagementService.jobAction(ownerUri, jobName, JobActions.Run).then(result => {
|
||||
if (result.success) {
|
||||
refreshAction.run(context);
|
||||
var startMsg = nls.localize('jobSuccessfullyStarted', ': The job was successfully started.');
|
||||
this.notificationService.notify({
|
||||
severity: Severity.Info,
|
||||
message: jobName+ startMsg
|
||||
});
|
||||
this.notificationService.info(jobName+startMsg);
|
||||
refreshAction.run(context);
|
||||
resolve(true);
|
||||
} else {
|
||||
this.notificationService.notify({
|
||||
severity: Severity.Error,
|
||||
message: result.errorMessage
|
||||
});
|
||||
this.errorMessageService.showDialog(Severity.Error, errorLabel, result.errorMessage);
|
||||
resolve(false);
|
||||
}
|
||||
});
|
||||
@@ -122,15 +121,16 @@ export class StopJobAction extends Action {
|
||||
|
||||
constructor(
|
||||
@INotificationService private notificationService: INotificationService,
|
||||
@IErrorMessageService private errorMessageService: IErrorMessageService,
|
||||
@IJobManagementService private jobManagementService: IJobManagementService,
|
||||
@IInstantiationService private instantationService: IInstantiationService,
|
||||
@ITelemetryService private telemetryService: ITelemetryService
|
||||
) {
|
||||
super(StopJobAction.ID, StopJobAction.LABEL, 'stopJobIcon');
|
||||
super(StopJobAction.ID, StopJobAction.LABEL, 'stop');
|
||||
}
|
||||
|
||||
public run(context: JobHistoryComponent): TPromise<boolean> {
|
||||
let jobName = context.agentJobInfo.name;
|
||||
public run(context: IJobActionInfo): TPromise<boolean> {
|
||||
let jobName = context.targetObject.name;
|
||||
let ownerUri = context.ownerUri;
|
||||
let refreshAction = this.instantationService.createInstance(JobsRefreshAction);
|
||||
this.telemetryService.publicLog(TelemetryKeys.StopAgentJob);
|
||||
@@ -139,16 +139,10 @@ export class StopJobAction extends Action {
|
||||
if (result.success) {
|
||||
refreshAction.run(context);
|
||||
var stopMsg = nls.localize('jobSuccessfullyStopped', ': The job was successfully stopped.');
|
||||
this.notificationService.notify({
|
||||
severity: Severity.Info,
|
||||
message: jobName+ stopMsg
|
||||
});
|
||||
this.notificationService.info(jobName+stopMsg);
|
||||
resolve(true);
|
||||
} else {
|
||||
this.notificationService.notify({
|
||||
severity: Severity.Error,
|
||||
message: result.errorMessage
|
||||
});
|
||||
this.errorMessageService.showDialog(Severity.Error, 'Error', result.errorMessage);
|
||||
resolve(false);
|
||||
}
|
||||
});
|
||||
@@ -163,7 +157,7 @@ export class EditJobAction extends Action {
|
||||
constructor(
|
||||
@ICommandService private _commandService: ICommandService
|
||||
) {
|
||||
super(EditJobAction.ID, EditJobAction.LABEL);
|
||||
super(EditJobAction.ID, EditJobAction.LABEL, 'edit');
|
||||
}
|
||||
|
||||
public run(actionInfo: IJobActionInfo): TPromise<boolean> {
|
||||
@@ -181,6 +175,7 @@ export class DeleteJobAction extends Action {
|
||||
|
||||
constructor(
|
||||
@INotificationService private _notificationService: INotificationService,
|
||||
@IErrorMessageService private _errorMessageService: IErrorMessageService,
|
||||
@IJobManagementService private _jobService: IJobManagementService,
|
||||
@ITelemetryService private _telemetryService: ITelemetryService
|
||||
) {
|
||||
@@ -201,7 +196,10 @@ export class DeleteJobAction extends Action {
|
||||
if (!result || !result.success) {
|
||||
let errorMessage = nls.localize("jobaction.failedToDeleteJob", "Could not delete job '{0}'.\nError: {1}",
|
||||
job.name, result.errorMessage ? result.errorMessage : 'Unknown error');
|
||||
self._notificationService.error(errorMessage);
|
||||
self._errorMessageService.showDialog(Severity.Error, errorLabel, errorMessage);
|
||||
} else {
|
||||
let successMessage = nls.localize('jobaction.deletedJob', 'The job was successfully deleted');
|
||||
self._notificationService.info(successMessage);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -242,6 +240,7 @@ export class DeleteStepAction extends Action {
|
||||
|
||||
constructor(
|
||||
@INotificationService private _notificationService: INotificationService,
|
||||
@IErrorMessageService private _errorMessageService: IErrorMessageService,
|
||||
@IJobManagementService private _jobService: IJobManagementService,
|
||||
@IInstantiationService private instantationService: IInstantiationService,
|
||||
@ITelemetryService private _telemetryService: ITelemetryService
|
||||
@@ -262,11 +261,13 @@ export class DeleteStepAction extends Action {
|
||||
this._telemetryService.publicLog(TelemetryKeys.DeleteAgentJobStep);
|
||||
self._jobService.deleteJobStep(actionInfo.ownerUri, actionInfo.targetObject).then(result => {
|
||||
if (!result || !result.success) {
|
||||
let errorMessage = nls.localize("jobaction.failedToDeleteStep", "Could not delete step '{0}'.\nError: {1}",
|
||||
let errorMessage = nls.localize('jobaction.failedToDeleteStep', "Could not delete step '{0}'.\nError: {1}",
|
||||
step.stepName, result.errorMessage ? result.errorMessage : 'Unknown error');
|
||||
self._notificationService.error(errorMessage);
|
||||
self._errorMessageService.showDialog(Severity.Error, errorLabel, errorMessage);
|
||||
refreshAction.run(actionInfo);
|
||||
} else {
|
||||
refreshAction.run(actionInfo.jobHistoryComponent);
|
||||
let successMessage = nls.localize('jobaction.deletedStep', 'The job step was successfully deleted');
|
||||
self._notificationService.info(successMessage);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -329,6 +330,7 @@ export class DeleteAlertAction extends Action {
|
||||
|
||||
constructor(
|
||||
@INotificationService private _notificationService: INotificationService,
|
||||
@IErrorMessageService private _errorMessageService: IErrorMessageService,
|
||||
@IJobManagementService private _jobService: IJobManagementService,
|
||||
@ITelemetryService private _telemetryService: ITelemetryService
|
||||
) {
|
||||
@@ -349,7 +351,10 @@ export class DeleteAlertAction extends Action {
|
||||
if (!result || !result.success) {
|
||||
let errorMessage = nls.localize("jobaction.failedToDeleteAlert", "Could not delete alert '{0}'.\nError: {1}",
|
||||
alert.name, result.errorMessage ? result.errorMessage : 'Unknown error');
|
||||
self._notificationService.error(errorMessage);
|
||||
self._errorMessageService.showDialog(Severity.Error, errorLabel, errorMessage);
|
||||
} else {
|
||||
let successMessage = nls.localize('jobaction.deletedAlert', 'The alert was successfully deleted');
|
||||
self._notificationService.info(successMessage);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -410,6 +415,7 @@ export class DeleteOperatorAction extends Action {
|
||||
|
||||
constructor(
|
||||
@INotificationService private _notificationService: INotificationService,
|
||||
@IErrorMessageService private _errorMessageService: IErrorMessageService,
|
||||
@IJobManagementService private _jobService: IJobManagementService,
|
||||
@ITelemetryService private _telemetryService: ITelemetryService
|
||||
) {
|
||||
@@ -417,7 +423,7 @@ export class DeleteOperatorAction extends Action {
|
||||
}
|
||||
|
||||
public run(actionInfo: IJobActionInfo): TPromise<boolean> {
|
||||
let self = this;
|
||||
const self = this;
|
||||
let operator = actionInfo.targetObject as sqlops.AgentOperatorInfo;
|
||||
self._notificationService.prompt(
|
||||
Severity.Info,
|
||||
@@ -425,12 +431,15 @@ export class DeleteOperatorAction extends Action {
|
||||
[{
|
||||
label: DeleteOperatorAction.LABEL,
|
||||
run: () => {
|
||||
this._telemetryService.publicLog(TelemetryKeys.DeleteAgentOperator);
|
||||
self._telemetryService.publicLog(TelemetryKeys.DeleteAgentOperator);
|
||||
self._jobService.deleteOperator(actionInfo.ownerUri, actionInfo.targetObject).then(result => {
|
||||
if (!result || !result.success) {
|
||||
let errorMessage = nls.localize("jobaction.failedToDeleteOperator", "Could not delete operator '{0}'.\nError: {1}",
|
||||
operator.name, result.errorMessage ? result.errorMessage : 'Unknown error');
|
||||
self._notificationService.error(errorMessage);
|
||||
self._errorMessageService.showDialog(Severity.Error, errorLabel, errorMessage);
|
||||
} else {
|
||||
let successMessage = nls.localize('joaction.deletedOperator', 'The operator was deleted successfully');
|
||||
self._notificationService.info(successMessage);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -492,6 +501,7 @@ export class DeleteProxyAction extends Action {
|
||||
|
||||
constructor(
|
||||
@INotificationService private _notificationService: INotificationService,
|
||||
@IErrorMessageService private _errorMessageService: IErrorMessageService,
|
||||
@IJobManagementService private _jobService: IJobManagementService,
|
||||
@ITelemetryService private _telemetryService: ITelemetryService
|
||||
) {
|
||||
@@ -512,7 +522,10 @@ export class DeleteProxyAction extends Action {
|
||||
if (!result || !result.success) {
|
||||
let errorMessage = nls.localize("jobaction.failedToDeleteProxy", "Could not delete proxy '{0}'.\nError: {1}",
|
||||
proxy.accountName, result.errorMessage ? result.errorMessage : 'Unknown error');
|
||||
self._notificationService.error(errorMessage);
|
||||
self._errorMessageService.showDialog(Severity.Error, errorLabel, errorMessage);
|
||||
} else {
|
||||
let successMessage = nls.localize('jobaction.deletedProxy', 'The proxy was deleted successfully');
|
||||
self._notificationService.info(successMessage);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#3bb44a;}</style></defs><title>run</title><path class="cls-1" d="M3.24,0,14.61,8,3.24,16Zm2,12.07L11.13,8,5.24,3.88Z"/><path class="cls-1" d="M3.74,1l10,7-10,7Zm1,1.92V13.07L12,8Z"/></svg>
|
||||
|
Before Width: | Height: | Size: 306 B |
@@ -1 +0,0 @@
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#d02e00;}</style></defs><title>stop</title><path class="cls-1" d="M.5,15.3V.3h15v15Zm13-2V2.3H2.5v11Z"/><path class="cls-1" d="M1,.8H15v14H1Zm13,13V1.8H2v12Z"/></svg>
|
||||
|
Before Width: | Height: | Size: 284 B |
@@ -77,7 +77,7 @@
|
||||
<!-- Job History details -->
|
||||
<div class='history-details'>
|
||||
<!-- Previous run list -->
|
||||
<div class="prev-run-list-container" style="min-width: 250px">
|
||||
<div class="prev-run-list-container" style="min-width: 270px">
|
||||
<table *ngIf="_showPreviousRuns === true">
|
||||
<tr>
|
||||
<td class="date-column">
|
||||
|
||||
@@ -7,12 +7,13 @@ import 'vs/css!./jobHistory';
|
||||
import 'vs/css!sql/media/icons/common-icons';
|
||||
|
||||
import * as sqlops from 'sqlops';
|
||||
import * as nls from 'vs/nls';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { OnInit, Component, Inject, Input, forwardRef, ElementRef, ChangeDetectorRef, ViewChild, ChangeDetectionStrategy, Injectable } from '@angular/core';
|
||||
import { Taskbar } from 'sql/base/browser/ui/taskbar/taskbar';
|
||||
import { AgentViewComponent } from 'sql/parts/jobManagement/agent/agentView.component';
|
||||
import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service';
|
||||
import { RunJobAction, StopJobAction, NewStepAction } from 'sql/parts/jobManagement/common/jobActions';
|
||||
import { RunJobAction, StopJobAction, EditJobAction, JobsRefreshAction } from 'sql/parts/jobManagement/common/jobActions';
|
||||
import { JobCacheObject } from 'sql/parts/jobManagement/common/jobManagementService';
|
||||
import { JobManagementUtilities } from 'sql/parts/jobManagement/common/jobManagementUtilities';
|
||||
import { IJobManagementService } from 'sql/parts/jobManagement/common/interfaces';
|
||||
@@ -103,6 +104,7 @@ export class JobHistoryComponent extends JobManagementView implements OnInit {
|
||||
this._parentComponent = this._agentViewComponent;
|
||||
|
||||
let ownerUri: string = this._commonService.connectionManagementService.connectionInfo.ownerUri;
|
||||
this._agentJobInfo = this._agentViewComponent.agentJobInfo;
|
||||
const self = this;
|
||||
this._treeController.onClick = (tree, element, event, origin = 'mouse') => {
|
||||
const payload = { origin: origin };
|
||||
@@ -143,16 +145,25 @@ export class JobHistoryComponent extends JobManagementView implements OnInit {
|
||||
}, {verticalScrollMode: ScrollbarVisibility.Visible});
|
||||
this._register(attachListStyler(this._tree, this.themeService));
|
||||
this._tree.layout(dom.getContentHeight(this._tableContainer.nativeElement));
|
||||
this.initActionBar();
|
||||
this._telemetryService.publicLog(TelemetryKeys.JobHistoryView);
|
||||
this.initActionBar();
|
||||
}
|
||||
|
||||
private loadHistory() {
|
||||
const self = this;
|
||||
let ownerUri: string = this._commonService.connectionManagementService.connectionInfo.ownerUri;
|
||||
let jobName = this._agentViewComponent.agentJobInfo.name;
|
||||
this._jobManagementService.getJobHistory(ownerUri, this._agentViewComponent.jobId, jobName).then((result) => {
|
||||
let jobId = this._agentViewComponent.jobId;
|
||||
this._jobManagementService.getJobHistory(ownerUri, jobId, jobName).then((result) => {
|
||||
if (result && result.histories) {
|
||||
self._jobCacheObject.setJobHistory(jobId, result.histories);
|
||||
self._jobCacheObject.setJobAlerts(jobId, result.alerts);
|
||||
self._jobCacheObject.setJobSchedules(jobId, result.schedules);
|
||||
self._jobCacheObject.setJobSteps(jobId, result.steps);
|
||||
this._agentViewComponent.agentJobInfo.jobSteps = this._jobCacheObject.getJobSteps(jobId);
|
||||
this._agentViewComponent.agentJobInfo.jobSchedules = this._jobCacheObject.getJobSchedules(jobId);
|
||||
this._agentViewComponent.agentJobInfo.alerts = this._jobCacheObject.getJobAlerts(jobId);
|
||||
this._agentJobInfo = this._agentViewComponent.agentJobInfo;
|
||||
if (result.histories.length > 0) {
|
||||
self._showPreviousRuns = true;
|
||||
self.buildHistoryTree(self, result.histories);
|
||||
@@ -189,9 +200,14 @@ export class JobHistoryComponent extends JobManagementView implements OnInit {
|
||||
stepViewRow.stepId = step.stepDetails.id.toString();
|
||||
return stepViewRow;
|
||||
});
|
||||
this._showSteps = self._stepRows.length > 0;
|
||||
self._stepRows.unshift(new JobStepsViewRow());
|
||||
self._stepRows[0].rowID = 'stepsColumn' + self._agentJobInfo.jobId;
|
||||
self._stepRows[0].stepId = nls.localize('stepRow.stepID','Step ID');
|
||||
self._stepRows[0].stepName = nls.localize('stepRow.stepName', 'Step Name');
|
||||
self._stepRows[0].message = nls.localize('stepRow.message', 'Message');
|
||||
this._showSteps = self._stepRows.length > 1;
|
||||
} else {
|
||||
this._showSteps = false;
|
||||
self._showSteps = false;
|
||||
}
|
||||
self._cd.detectChanges();
|
||||
}
|
||||
@@ -208,7 +224,6 @@ export class JobHistoryComponent extends JobManagementView implements OnInit {
|
||||
|
||||
private buildHistoryTree(self: any, jobHistories: sqlops.AgentJobHistoryInfo[]) {
|
||||
self._treeController.jobHistories = jobHistories;
|
||||
self._jobCacheObject.setJobHistory(self._agentViewComponent.jobId, jobHistories);
|
||||
let jobHistoryRows = this._treeController.jobHistories.map(job => self.convertToJobHistoryRow(job));
|
||||
self._treeDataSource.data = jobHistoryRows;
|
||||
self._tree.setInput(new JobHistoryModel());
|
||||
@@ -216,6 +231,13 @@ export class JobHistoryComponent extends JobManagementView implements OnInit {
|
||||
if (self.agentJobHistoryInfo) {
|
||||
self.agentJobHistoryInfo.runDate = self.formatTime(self.agentJobHistoryInfo.runDate);
|
||||
}
|
||||
const payload = { origin: 'origin' };
|
||||
let element = this._treeDataSource.getFirstElement();
|
||||
this._tree.setFocus(element, payload);
|
||||
this._tree.setSelection([element], payload);
|
||||
if (element.rowID) {
|
||||
self.setStepsTree(element);
|
||||
}
|
||||
}
|
||||
|
||||
private toggleCollapse(): void {
|
||||
@@ -249,17 +271,10 @@ export class JobHistoryComponent extends JobManagementView implements OnInit {
|
||||
return this._showPreviousRuns !== true && this._noJobsAvailable === false;
|
||||
}
|
||||
|
||||
private setActions(): void {
|
||||
let startIcon: HTMLElement = $('.action-label.icon.runJobIcon').get(0);
|
||||
let stopIcon: HTMLElement = $('.action-label.icon.stopJobIcon').get(0);
|
||||
JobManagementUtilities.getActionIconClassName(startIcon, stopIcon, this.agentJobInfo.currentExecutionStatus);
|
||||
}
|
||||
|
||||
public onFirstVisible() {
|
||||
this._agentJobInfo = this._agentViewComponent.agentJobInfo;
|
||||
if (!this.agentJobInfo) {
|
||||
this.agentJobInfo = this._agentJobInfo;
|
||||
this.setActions();
|
||||
}
|
||||
|
||||
if (this.isRefreshing ) {
|
||||
@@ -272,9 +287,12 @@ export class JobHistoryComponent extends JobManagementView implements OnInit {
|
||||
const self = this;
|
||||
if (this._jobCacheObject.prevJobID === this._agentViewComponent.jobId || jobHistories[0].jobId === this._agentViewComponent.jobId) {
|
||||
this._showPreviousRuns = true;
|
||||
this._agentViewComponent.agentJobInfo.jobSteps = this._jobCacheObject.getJobSteps(this._agentJobInfo.jobId);
|
||||
this._agentViewComponent.agentJobInfo.jobSchedules = this._jobCacheObject.getJobSchedules(this._agentJobInfo.jobId);
|
||||
this._agentViewComponent.agentJobInfo.alerts = this._jobCacheObject.getJobAlerts(this._agentJobInfo.jobId);
|
||||
this._agentJobInfo = this._agentViewComponent.agentJobInfo;
|
||||
this.buildHistoryTree(self, jobHistories);
|
||||
$('jobhistory-component .history-details .prev-run-list .monaco-tree').attr('tabIndex', '-1');
|
||||
$('jobhistory-component .history-details .prev-run-list .monaco-tree-row').attr('tabIndex', '0');
|
||||
this._actionBar.context = { targetObject: this._agentJobInfo, ownerUri: this.ownerUri, jobHistoryComponent: this };
|
||||
this._cd.detectChanges();
|
||||
}
|
||||
} else if (jobHistories && jobHistories.length === 0 ){
|
||||
@@ -316,14 +334,25 @@ export class JobHistoryComponent extends JobManagementView implements OnInit {
|
||||
protected initActionBar() {
|
||||
let runJobAction = this.instantiationService.createInstance(RunJobAction);
|
||||
let stopJobAction = this.instantiationService.createInstance(StopJobAction);
|
||||
let newStepAction = this.instantiationService.createInstance(NewStepAction);
|
||||
switch(this._agentJobInfo.currentExecutionStatus) {
|
||||
case(1):
|
||||
case(2):
|
||||
case(3):
|
||||
stopJobAction.enabled = true;
|
||||
break;
|
||||
default:
|
||||
stopJobAction.enabled = false;
|
||||
}
|
||||
let editJobAction = this.instantiationService.createInstance(EditJobAction);
|
||||
let refreshAction = this.instantiationService.createInstance(JobsRefreshAction);
|
||||
let taskbar = <HTMLElement>this.actionBarContainer.nativeElement;
|
||||
this._actionBar = new Taskbar(taskbar, this.contextMenuService);
|
||||
this._actionBar.context = this;
|
||||
this._actionBar.context = { targetObject: this._agentJobInfo, ownerUri: this.ownerUri, jobHistoryComponent: this };
|
||||
this._actionBar.setContent([
|
||||
{ action: runJobAction },
|
||||
{ action: stopJobAction },
|
||||
{ action: newStepAction }
|
||||
{ action: refreshAction },
|
||||
{ action: editJobAction }
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
|
||||
.all-jobs {
|
||||
display: inline;
|
||||
jobhistory-component .all-jobs {
|
||||
display: inline-block;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
.job-heading {
|
||||
text-align: left;
|
||||
padding-left: 13px;
|
||||
font-size: 1.5vw;
|
||||
}
|
||||
|
||||
.overview-container {
|
||||
@@ -123,19 +124,6 @@ input#accordion:checked ~ .accordion-content {
|
||||
content: url('../common/media/back_inverse.svg');
|
||||
}
|
||||
|
||||
.vs .action-label.icon.runJobIcon,
|
||||
.vs-dark .action-label.icon.runJobIcon,
|
||||
.hc-black .action-label.icon.runJobIcon {
|
||||
background-image: url('../common/media/start.svg');
|
||||
padding-right: 5px;
|
||||
}
|
||||
|
||||
.vs .action-label.icon.stopJobIcon,
|
||||
.vs-dark .action-label.icon.stopJobIcon,
|
||||
.hc-black .action-label.icon.stopJobIcon {
|
||||
background-image: url('../common/media/stop.svg');
|
||||
}
|
||||
|
||||
.vs .action-label.icon.newStepIcon {
|
||||
background-image: url('../common/media/new.svg');
|
||||
}
|
||||
@@ -145,6 +133,21 @@ input#accordion:checked ~ .accordion-content {
|
||||
background-image: url('../common/media/new_inverse.svg');
|
||||
}
|
||||
|
||||
jobhistory-component .hc-black .icon.edit,
|
||||
jobhistory-component .vs-dark .icon.edit {
|
||||
background-image: url('../../../media/icons/edit_inverse.svg');
|
||||
}
|
||||
|
||||
jobhistory-component .vs .icon.edit {
|
||||
background-image: url('../../../media/icons/edit.svg');
|
||||
}
|
||||
|
||||
jobhistory-component .actions-container .icon.edit {
|
||||
background-position: 0% 50%;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 12px;
|
||||
}
|
||||
|
||||
a.action-label.icon.runJobIcon.non-runnable {
|
||||
opacity: 0.4;
|
||||
cursor: default;
|
||||
@@ -197,8 +200,12 @@ table.step-list tr.step-row td {
|
||||
border-left: 3px solid #2b56f2;
|
||||
}
|
||||
|
||||
.history-details > .job-steps > .step-list {
|
||||
.history-details > .job-steps > table.step-list {
|
||||
padding-bottom: 10px;
|
||||
display: flex;
|
||||
flex: 1 1;
|
||||
overflow: scroll;
|
||||
max-height: 200px;
|
||||
}
|
||||
|
||||
.step-table .monaco-tree .monaco-tree-rows.show-twisties > .monaco-tree-row.has-children > .content:before {
|
||||
@@ -255,7 +262,7 @@ jobhistory-component {
|
||||
}
|
||||
|
||||
jobhistory-component > .jobhistory-heading-container {
|
||||
display: flex;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
jobhistory-component > .jobhistory-heading-container > .icon.in-progress {
|
||||
|
||||
@@ -50,11 +50,12 @@ export class JobHistoryController extends TreeDefaults.DefaultController {
|
||||
} else if (event.code === 'ArrowUp' || event.keyCode === 38) {
|
||||
super.onUp(tree, event);
|
||||
return super.onEnter(tree, event);
|
||||
} else {
|
||||
} else if (event.code !== 'Tab' && event.keyCode !== 2) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,6 +97,10 @@ export class JobHistoryDataSource implements tree.IDataSource {
|
||||
public set data(data: JobHistoryRow[]) {
|
||||
this._data = data;
|
||||
}
|
||||
|
||||
public getFirstElement() {
|
||||
return this._data[0];
|
||||
}
|
||||
}
|
||||
|
||||
export interface IListTemplate {
|
||||
|
||||
@@ -9,19 +9,6 @@
|
||||
<div class="steps-icon"></div>
|
||||
<h1 style="display: inline">Steps</h1>
|
||||
</div>
|
||||
<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' style="flex: 1 1 auto; position: relative">
|
||||
<div #table style="position: absolute; height: 100%; width: 100%" ></div>
|
||||
</div>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import 'vs/css!./jobStepsView';
|
||||
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { OnInit, Component, Inject, forwardRef, ElementRef, ChangeDetectorRef, ViewChild, Injectable, AfterContentChecked } from '@angular/core';
|
||||
import { OnInit, Component, Inject, forwardRef, ElementRef, ChangeDetectorRef, ViewChild, AfterContentChecked } from '@angular/core';
|
||||
import { attachListStyler } from 'vs/platform/theme/common/styler';
|
||||
import { Tree } from 'vs/base/parts/tree/browser/treeImpl';
|
||||
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
|
||||
@@ -57,23 +57,51 @@ export class JobStepsViewComponent extends JobManagementView implements OnInit,
|
||||
}
|
||||
|
||||
ngAfterContentChecked() {
|
||||
if (this._jobHistoryComponent.stepRows.length > 0) {
|
||||
this._treeDataSource.data = this._jobHistoryComponent.stepRows;
|
||||
this._tree.setInput(new JobStepsViewModel());
|
||||
this.layout();
|
||||
$('jobstepsview-component .steps-tree .monaco-tree').attr('tabIndex', '-1');
|
||||
$('jobstepsview-component .steps-tree .monaco-tree-row').attr('tabIndex', '0');
|
||||
}
|
||||
$('.steps-tree .step-column-heading').closest('.monaco-tree-row').addClass('step-column-row');
|
||||
this.layout();
|
||||
this._tree.setInput(new JobStepsViewModel());
|
||||
this._tree.onDidScroll(() => {
|
||||
$('.steps-tree .step-column-heading').closest('.monaco-tree-row').addClass('step-column-row');
|
||||
});
|
||||
this._treeController.onClick = (tree, element, event, origin = 'mouse') => {
|
||||
const payload = { origin: origin };
|
||||
const isDoubleClick = (origin === 'mouse' && event.detail === 2);
|
||||
// Cancel Event
|
||||
const isMouseDown = event && event.browserEvent && event.browserEvent.type === 'mousedown';
|
||||
if (!isMouseDown) {
|
||||
event.preventDefault(); // we cannot preventDefault onMouseDown because this would break DND otherwise
|
||||
}
|
||||
event.stopPropagation();
|
||||
tree.setFocus(element, payload);
|
||||
if (element && isDoubleClick) {
|
||||
event.preventDefault(); // focus moves to editor, we need to prevent default
|
||||
} else {
|
||||
tree.setFocus(element, payload);
|
||||
tree.setSelection([element], payload);
|
||||
}
|
||||
$('.steps-tree .step-column-heading').closest('.monaco-tree-row').addClass('step-column-row');
|
||||
return true;
|
||||
};
|
||||
this._treeController.onKeyDown = (tree, event) => {
|
||||
this._treeController.onKeyDownWrapper(tree, event);
|
||||
$('.steps-tree .step-column-heading').closest('.monaco-tree-row').addClass('step-column-row');
|
||||
return true;
|
||||
};
|
||||
this._tree.onDidFocus(() => {
|
||||
this._tree.focusNth(1);
|
||||
let element = this._tree.getFocus();
|
||||
this._tree.select(element);
|
||||
});
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
this._treeDataSource.data = this._jobHistoryComponent.stepRows;
|
||||
this._tree = new Tree(this._tableContainer.nativeElement, {
|
||||
controller: this._treeController,
|
||||
dataSource: this._treeDataSource,
|
||||
filter: this._treeFilter,
|
||||
renderer: this._treeRenderer
|
||||
}, {verticalScrollMode: ScrollbarVisibility.Visible, horizontalScrollMode: ScrollbarVisibility.Visible });
|
||||
this.layout();
|
||||
this._register(attachListStyler(this._tree, this.themeService));
|
||||
this._telemetryService.publicLog(TelemetryKeys.JobStepsView);
|
||||
}
|
||||
|
||||
@@ -21,14 +21,6 @@
|
||||
user-select: initial;
|
||||
}
|
||||
|
||||
.steps-tree .list-row .status-icon.step-unknown {
|
||||
height: 10px;
|
||||
width: 10px;
|
||||
display: inline-block;
|
||||
margin-top: 4px;
|
||||
background: yellow;
|
||||
}
|
||||
|
||||
.steps-tree .list-row {
|
||||
display: inline-flex;
|
||||
height: 20px
|
||||
@@ -39,26 +31,31 @@
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.step-columns .step-id-col, .steps-tree .tree-id-col {
|
||||
.steps-tree .tree-id-col {
|
||||
padding-left: 10px;
|
||||
white-space: normal;
|
||||
text-align: center;
|
||||
text-align: left;
|
||||
width: 60px;
|
||||
|
||||
}
|
||||
|
||||
.step-columns .step-name-col, .steps-tree .tree-name-col {
|
||||
.steps-tree .tree-name-col {
|
||||
padding-right: 10px;
|
||||
white-space: normal;
|
||||
text-align: center;
|
||||
width: 350px;
|
||||
text-align: left;
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.step-columns .step-message-col, .steps-tree .tree-message-col {
|
||||
.steps-tree .tree-message-col {
|
||||
padding-right: 10px;
|
||||
white-space: normal;
|
||||
text-align: left;
|
||||
width: 700px;
|
||||
}
|
||||
|
||||
.steps-tree .step-column-heading {
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
width: 680px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.steps-header > .steps-icon {
|
||||
@@ -87,4 +84,27 @@ jobstepsview-component {
|
||||
|
||||
jobstepsview-component .steps-tree .monaco-tree-wrapper .monaco-tree-row {
|
||||
width: 99.2%;
|
||||
}
|
||||
|
||||
.vs-dark jobstepsview-component .steps-tree .monaco-tree-wrapper .monaco-tree-row.step-column-row,
|
||||
.vs-dark jobstepsview-component .steps-tree .monaco-tree-wrapper .monaco-tree-row.step-column-row.focused,
|
||||
.vs-dark jobstepsview-component .steps-tree .monaco-tree-wrapper .monaco-tree-row.step-column-row.selected,
|
||||
.vs-dark jobstepsview-component .steps-tree .monaco-tree-wrapper .monaco-tree-row.step-column-row.focused.selected {
|
||||
background: #444444 !important;
|
||||
}
|
||||
|
||||
.hc-black jobstepsview-component .steps-tree .monaco-tree-wrapper .monaco-tree-row.step-column-row,
|
||||
.hc-black jobstepsview-component .steps-tree .monaco-tree-wrapper .monaco-tree-row.step-column-row.selected,
|
||||
.hc-black jobstepsview-component .steps-tree .monaco-tree-wrapper .monaco-tree-row.step-column-row.focused,
|
||||
.hc-black jobstepsview-component .steps-tree .monaco-tree-wrapper .monaco-tree-row.step-column-row.focused.selected {
|
||||
background: none !important;
|
||||
}
|
||||
|
||||
jobstepsview-component .steps-tree .monaco-tree-wrapper .monaco-tree-row.step-column-row,
|
||||
jobstepsview-component .steps-tree .monaco-tree-wrapper .monaco-tree-row.step-column-row.focused,
|
||||
jobstepsview-component .steps-tree .monaco-tree-wrapper .monaco-tree-row.step-column-row.selected,
|
||||
jobstepsview-component .steps-tree .monaco-tree-wrapper .monaco-tree-row.step-column-row.focused.selected {
|
||||
background: #dcdcdc !important;
|
||||
cursor: none;
|
||||
padding-left: 0px;
|
||||
}
|
||||
@@ -11,7 +11,7 @@ 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 { generateUuid } from 'vs/base/common/uuid';
|
||||
import { JobManagementUtilities } from 'sql/parts/jobManagement/common/jobManagementUtilities';
|
||||
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
|
||||
export class JobStepsViewRow {
|
||||
public stepId: string;
|
||||
@@ -36,6 +36,20 @@ export class JobStepsViewController extends TreeDefaults.DefaultController {
|
||||
return true;
|
||||
}
|
||||
|
||||
public onKeyDownWrapper(tree: tree.ITree, event: IKeyboardEvent): boolean {
|
||||
if (event.code === 'ArrowDown' || event.keyCode === 40) {
|
||||
super.onDown(tree, event);
|
||||
return super.onEnter(tree, event);
|
||||
} else if (event.code === 'ArrowUp' || event.keyCode === 38) {
|
||||
super.onUp(tree, event);
|
||||
return super.onEnter(tree, event);
|
||||
} else if (event.code !== 'Tab' && event.keyCode !== 2) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export class JobStepsViewDataSource implements tree.IDataSource {
|
||||
@@ -84,7 +98,6 @@ export interface IListTemplate {
|
||||
}
|
||||
|
||||
export class JobStepsViewRenderer implements tree.IRenderer {
|
||||
private _statusIcon: HTMLElement;
|
||||
|
||||
public getHeight(tree: tree.ITree, element: JobStepsViewRow): number {
|
||||
return 40;
|
||||
@@ -101,11 +114,10 @@ export class JobStepsViewRenderer implements tree.IRenderer {
|
||||
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);
|
||||
let statusIcon = this.createStatusIcon();
|
||||
row.appendChild(statusIcon);
|
||||
row.appendChild(label);
|
||||
container.appendChild(row);
|
||||
let statusIcon = this._statusIcon;
|
||||
return { statusIcon, label };
|
||||
}
|
||||
|
||||
@@ -119,19 +131,26 @@ export class JobStepsViewRenderer implements tree.IRenderer {
|
||||
let stepMessageCol: HTMLElement = DOM.$('div');
|
||||
stepMessageCol.className = 'tree-message-col';
|
||||
stepMessageCol.innerText = element.message;
|
||||
if (element.rowID.includes('stepsColumn')) {
|
||||
stepNameCol.className += ' step-column-heading';
|
||||
stepIdCol.className += ' step-column-heading';
|
||||
stepMessageCol.className += ' step-column-heading';
|
||||
}
|
||||
$(templateData.label).empty();
|
||||
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';
|
||||
if (element.runStatus) {
|
||||
if (element.runStatus === 'Succeeded') {
|
||||
templateData.statusIcon.className = 'status-icon step-passed';
|
||||
} else if (element.runStatus === 'Failed') {
|
||||
templateData.statusIcon.className = 'status-icon step-failed';
|
||||
} else {
|
||||
templateData.statusIcon.className = 'status-icon step-unknown';
|
||||
}
|
||||
} else {
|
||||
statusClass = ' step-unknown';
|
||||
templateData.statusIcon.className = '';
|
||||
}
|
||||
this._statusIcon.className += statusClass;
|
||||
}
|
||||
|
||||
public disposeTemplate(tree: tree.ITree, templateId: string, templateData: IListTemplate): void {
|
||||
@@ -140,7 +159,6 @@ export class JobStepsViewRenderer implements tree.IRenderer {
|
||||
|
||||
private createStatusIcon(): HTMLElement {
|
||||
let statusIcon: HTMLElement = DOM.$('div');
|
||||
statusIcon.className += 'status-icon';
|
||||
return statusIcon;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ import * as TelemetryKeys from 'sql/common/telemetryKeys';
|
||||
|
||||
export const JOBSVIEW_SELECTOR: string = 'jobsview-component';
|
||||
export const ROW_HEIGHT: number = 45;
|
||||
export const ACTIONBAR_PADDING: number = 10;
|
||||
|
||||
@Component({
|
||||
selector: JOBSVIEW_SELECTOR,
|
||||
@@ -141,7 +142,7 @@ export class JobsViewComponent extends JobManagementView implements OnInit, OnDe
|
||||
let jobsViewToolbar = $('jobsview-component .agent-actionbar-container').get(0);
|
||||
let statusBar = $('.part.statusbar').get(0);
|
||||
if (jobsViewToolbar && statusBar) {
|
||||
let toolbarBottom = jobsViewToolbar.getBoundingClientRect().bottom;
|
||||
let toolbarBottom = jobsViewToolbar.getBoundingClientRect().bottom + ACTIONBAR_PADDING;
|
||||
let statusTop = statusBar.getBoundingClientRect().top;
|
||||
this._table.layout(new dom.Dimension(
|
||||
dom.getContentWidth(this._gridEl.nativeElement),
|
||||
@@ -600,6 +601,8 @@ export class JobsViewComponent extends JobManagementView implements OnInit, OnDe
|
||||
self.jobHistories[job.jobId] = result.histories ? result.histories : [];
|
||||
self._jobCacheObject.setJobSteps(job.jobId, self.jobSteps[job.jobId]);
|
||||
self._jobCacheObject.setJobHistory(job.jobId, self.jobHistories[job.jobId]);
|
||||
self._jobCacheObject.setJobAlerts(job.jobId, self.jobAlerts[job.jobId]);
|
||||
self._jobCacheObject.setJobSchedules(job.jobId, self.jobSchedules[job.jobId]);
|
||||
let jobHistories = self._jobCacheObject.getJobHistory(job.jobId);
|
||||
let previousRuns: sqlops.AgentJobHistoryInfo[];
|
||||
if (jobHistories.length >= 5) {
|
||||
@@ -630,14 +633,14 @@ export class JobsViewComponent extends JobManagementView implements OnInit, OnDe
|
||||
let runCharts = [];
|
||||
for (let i = 0; i < chartHeights.length; i++) {
|
||||
let runGraph = $(`table#${jobId}.jobprevruns > tbody > tr > td > div.bar${i}`);
|
||||
runGraph.css('height', chartHeights[i]);
|
||||
let bgColor = jobHistories[i].runStatus === 0 ? 'red' : 'green';
|
||||
runGraph.css('background', bgColor);
|
||||
runGraph.hover((e) => {
|
||||
let currentTarget = e.currentTarget;
|
||||
currentTarget.title = jobHistories[i].runDuration;
|
||||
});
|
||||
if (runGraph.get(0)) {
|
||||
if (runGraph.length > 0) {
|
||||
runGraph.css('height', chartHeights[i]);
|
||||
let bgColor = jobHistories[i].runStatus === 0 ? 'red' : 'green';
|
||||
runGraph.css('background', bgColor);
|
||||
runGraph.hover((e) => {
|
||||
let currentTarget = e.currentTarget;
|
||||
currentTarget.title = jobHistories[i].runDuration;
|
||||
});
|
||||
runCharts.push(runGraph.get(0).outerHTML);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user