Agent Notebooks Scheduler (#6786)

* added agent notebooks, notebook history view and view materialized notebook button

* Got a basic UI running for viewing notebook history

* made some changes to make UI look good

* Added new notebook dialog

* Added new notebook Dialog

* Added create notebook dialog

* Added edit and delete notebook job

* Added some notebook history features

* Added new notebook job icons, fixed a minor bug
in openmaterializednotebookAPI and added fixed the
schedule Picker API.

* Fixed Bugs in Notebook Grid expansion

* Fixed Notebook table highlighting and
grid generation is done using code.

* fixed some UI bugs

* Added changes to reflect sqltoolservice api

* Fixed some localize keys

* Made changes in the PR and added
ability to open Template Notebooks from
notebook history view.

* Added pin and renaming to notebook history

* made some library calls async

* fixed an import bug caused by merging from master

* Validation in NotebookJobDialog

* Added entry points for scheduling notebooks
on file explorer and notebook editor

* Handled no active connections and
a small bug in collapsing grid

* fix a bug in scheduling notebook from explorer
and toolbar

* setting up agent providers from connection now

* changed modals

* Reupload edited template

* Add dialog info, solved an edit bug and localized
UI strings.

* Bug fixes in UI, notebook renaming and
editing template on fly.

* fixed a bug that failed editing notebook jobs from notebook jobs table

* Fixed a cyclic dependency, made strings const and
some other changes in the PR

* Made some cyclic dependency and some fixes from PR

* made some changes mentioned in the PR

* Changed storage database health text

* Changed the sqltoolservice version to the point to the latest build.
This commit is contained in:
Aasim Khan
2019-09-04 15:12:35 -07:00
committed by GitHub
parent 0a393400b2
commit fbb2accacb
48 changed files with 3948 additions and 149 deletions

View File

@@ -395,6 +395,30 @@ export class MainThreadDataProtocol extends Disposable implements MainThreadData
deleteJobStep(connectionUri: string, stepInfo: azdata.AgentJobStepInfo): Thenable<azdata.ResultStatus> {
return self._proxy.$deleteJobStep(handle, connectionUri, stepInfo);
},
getNotebooks(connectionUri: string): Thenable<azdata.AgentNotebooksResult> {
return self._proxy.$getNotebooks(handle, connectionUri);
},
getNotebookHistory(connectionUri: string, jobID: string, jobName: string, targetDatabase: string): Thenable<azdata.AgentNotebookHistoryResult> {
return self._proxy.$getNotebookHistory(handle, connectionUri, jobID, jobName, targetDatabase);
},
getMaterializedNotebook(connectionUri: string, targetDatabase: string, notebookMaterializedId: number): Thenable<azdata.AgentNotebookMaterializedResult> {
return self._proxy.$getMaterializedNotebook(handle, connectionUri, targetDatabase, notebookMaterializedId);
},
updateNotebookMaterializedName(connectionUri: string, agentNotebookHistory: azdata.AgentNotebookHistoryInfo, targetDatabase: string, name: string): Thenable<azdata.ResultStatus> {
return self._proxy.$updateNotebookMaterializedName(handle, connectionUri, agentNotebookHistory, targetDatabase, name);
},
deleteMaterializedNotebook(connectionUri: string, agentNotebookHistory: azdata.AgentNotebookHistoryInfo, targetDatabase: string): Thenable<azdata.ResultStatus> {
return self._proxy.$deleteMaterializedNotebook(handle, connectionUri, agentNotebookHistory, targetDatabase);
},
updateNotebookMaterializedPin(connectionUri: string, agentNotebookHistory: azdata.AgentNotebookHistoryInfo, targetDatabase: string, pin: boolean): Thenable<azdata.ResultStatus> {
return self._proxy.$updateNotebookMaterializedPin(handle, connectionUri, agentNotebookHistory, targetDatabase, pin);
},
getTemplateNotebook(connectionUri: string, targetDatabase: string, jobId: string): Thenable<azdata.AgentNotebookTemplateResult> {
return self._proxy.$getTemplateNotebook(handle, connectionUri, targetDatabase, jobId);
},
deleteNotebook(connectionUri: string, notebook: azdata.AgentNotebookInfo): Thenable<azdata.ResultStatus> {
return self._proxy.$deleteNotebook(handle, connectionUri, notebook);
},
getAlerts(connectionUri: string): Thenable<azdata.AgentAlertsResult> {
return self._proxy.$getAlerts(handle, connectionUri);
},

View File

@@ -98,7 +98,7 @@ class MainThreadNotebookEditor extends Disposable {
if (!input) {
return false;
}
return input === this.editor.notebookParams.input;
return input.notebookUri.toString() === this.editor.notebookParams.input.notebookUri.toString();
}
public applyEdits(versionIdCheck: number, edits: ISingleNotebookEditOperation[], opts: IUndoStopOptions): boolean {

View File

@@ -679,14 +679,14 @@ export class ExtHostDataProtocol extends ExtHostDataProtocolShape {
* Deletes a job
*/
$deleteJob(handle: number, ownerUri: string, job: azdata.AgentJobInfo): Thenable<azdata.ResultStatus> {
throw this._resolveProvider<azdata.AgentServicesProvider>(handle).deleteJob(ownerUri, job);
return this._resolveProvider<azdata.AgentServicesProvider>(handle).deleteJob(ownerUri, job);
}
/**
* Deletes a job step
*/
$deleteJobStep(handle: number, ownerUri: string, step: azdata.AgentJobStepInfo): Thenable<azdata.ResultStatus> {
throw this._resolveProvider<azdata.AgentServicesProvider>(handle).deleteJobStep(ownerUri, step);
return this._resolveProvider<azdata.AgentServicesProvider>(handle).deleteJobStep(ownerUri, step);
}
/**
@@ -703,6 +703,62 @@ export class ExtHostDataProtocol extends ExtHostDataProtocolShape {
return this._resolveProvider<azdata.AgentServicesProvider>(handle).deleteAlert(ownerUri, alert);
}
/**
* Get Agent Notebook list
*/
public $getNotebooks(handle: number, ownerUri: string): Thenable<azdata.AgentNotebooksResult> {
return this._resolveProvider<azdata.AgentServicesProvider>(handle).getNotebooks(ownerUri);
}
/**
* Get a Agent Notebook's history
*/
public $getNotebookHistory(handle: number, ownerUri: string, jobID: string, jobName: string, targetDatabase: string): Thenable<azdata.AgentNotebookHistoryResult> {
return this._resolveProvider<azdata.AgentServicesProvider>(handle).getNotebookHistory(ownerUri, jobID, jobName, targetDatabase);
}
/**
* Get a Agent Materialized Notebook
*/
public $getMaterializedNotebook(handle: number, ownerUri: string, targetDatabase: string, notebookMaterializedId: number): Thenable<azdata.AgentNotebookMaterializedResult> {
return this._resolveProvider<azdata.AgentServicesProvider>(handle).getMaterializedNotebook(ownerUri, targetDatabase, notebookMaterializedId);
}
/**
* Get a Agent Template Notebook
*/
public $getTemplateNotebook(handle: number, ownerUri: string, targetDatabase: string, jobId: string): Thenable<azdata.AgentNotebookTemplateResult> {
return this._resolveProvider<azdata.AgentServicesProvider>(handle).getTemplateNotebook(ownerUri, targetDatabase, jobId);
}
/**
* Delete a Agent Notebook
*/
public $deleteNotebook(handle: number, ownerUri: string, notebook: azdata.AgentNotebookInfo): Thenable<azdata.ResultStatus> {
return this._resolveProvider<azdata.AgentServicesProvider>(handle).deleteNotebook(ownerUri, notebook);
}
/**
* Update a Agent Materialized Notebook Name
*/
public $updateNotebookMaterializedName(handle: number, ownerUri: string, agentNotebookHistory: azdata.AgentNotebookHistoryInfo, targetDatabase: string, name: string): Thenable<azdata.ResultStatus> {
return this._resolveProvider<azdata.AgentServicesProvider>(handle).updateNotebookMaterializedName(ownerUri, agentNotebookHistory, targetDatabase, name);
}
/**
* Get a Agent Materialized Notebook
*/
public $deleteMaterializedNotebook(handle: number, ownerUri: string, agentNotebookHistory: azdata.AgentNotebookHistoryInfo, targetDatabase: string): Thenable<azdata.ResultStatus> {
return this._resolveProvider<azdata.AgentServicesProvider>(handle).deleteMaterializedNotebook(ownerUri, agentNotebookHistory, targetDatabase);
}
/**
* Update a Agent Materialized Notebook Pin
*/
public $updateNotebookMaterializedPin(handle: number, ownerUri: string, agentNotebookHistory: azdata.AgentNotebookHistoryInfo, targetDatabase: string, pin: boolean): Thenable<azdata.ResultStatus> {
return this._resolveProvider<azdata.AgentServicesProvider>(handle).updateNotebookMaterializedPin(ownerUri, agentNotebookHistory, targetDatabase, pin);
}
/**
* Get Agent Oeprators list
*/

View File

@@ -397,6 +397,47 @@ export abstract class ExtHostDataProtocolShape {
*/
$deleteJobStep(handle: number, ownerUri: string, step: azdata.AgentJobStepInfo): Thenable<azdata.ResultStatus> { throw ni(); }
/**
* Get Agent Notebook list
*/
$getNotebooks(handle: number, ownerUri: string): Thenable<azdata.AgentNotebooksResult> { throw ni(); }
/**
* Get a Agent Notebook's history
*/
$getNotebookHistory(handle: number, ownerUri: string, jobID: string, jobName: string, targetDatabase: string): Thenable<azdata.AgentNotebookHistoryResult> { throw ni(); }
/**
* Get a Agent materialized notebook
*/
$getMaterializedNotebook(handle: number, ownerUri: string, targetDatabase: string, notebookMaterializedId: number): Thenable<azdata.AgentNotebookMaterializedResult> { throw ni(); }
/**
* Get a Agent Template notebook
*/
$getTemplateNotebook(handle: number, ownerUri: string, targetDatabase: string, jobId: string): Thenable<azdata.AgentNotebookTemplateResult> { throw ni(); }
/**
* Deletes a notebook
*/
$deleteNotebook(handle: number, ownerUri: string, notebook: azdata.AgentNotebookInfo): Thenable<azdata.ResultStatus> { throw ni(); }
/**
* Update materialzied Notebook Name
*/
$updateNotebookMaterializedName(handle: number, ownerUri: string, agentNotebookHistory: azdata.AgentNotebookHistoryInfo, targetDatabase: string, name: string): Thenable<azdata.ResultStatus> { throw ni(); }
/**
* Update materialzied Notebook Name
*/
$deleteMaterializedNotebook(handle: number, ownerUri: string, agentNotebookHistory: azdata.AgentNotebookHistoryInfo, targetDatabase: string): Thenable<azdata.ResultStatus> { throw ni(); }
/**
* Update materialzied Notebook Pin
*/
$updateNotebookMaterializedPin(handle: number, ownerUri: string, agentNotebookHistory: azdata.AgentNotebookHistoryInfo, targetDatabase: string, pin: boolean): Thenable<azdata.ResultStatus> { throw ni(); }
/**
* Get Agent Alerts list
*/

View File

@@ -20,14 +20,13 @@ import { focusBorder, foreground } from 'vs/platform/theme/common/colorRegistry'
import { Button } from 'sql/base/browser/ui/button/button';
import { Color } from 'vs/base/common/color';
@Component({
selector: 'modelview-button',
template: `
<div>
<label for={{this.label}}>
<div #input style="width: 100%">
<input #fileInput *ngIf="this.isFile === true" id={{this.label}} type="file" accept=".sql" style="display: none">
<input #fileInput *ngIf="this.isFile === true" id={{this.label}} type="file" accept="{{ this.fileType }}" style="display: none">
</div>
</label>
</div>
@@ -37,6 +36,7 @@ export default class ButtonComponent extends ComponentWithIconBase implements IC
@Input() descriptor: IComponentDescriptor;
@Input() modelStore: IModelStore;
private _button: Button;
private fileType: string = '.sql';
@ViewChild('input', { read: ElementRef }) private _inputContainer: ElementRef;
@ViewChild('fileInput', { read: ElementRef }) private _fileInputContainer: ElementRef;
@@ -71,7 +71,10 @@ export default class ButtonComponent extends ComponentWithIconBase implements IC
self.fileContent = text.toString();
self.fireEvent({
eventType: ComponentEventType.onDidClick,
args: self.fileContent
args: {
filePath: file.path,
fileContent: self.fileContent
}
});
};
reader.readAsText(file);
@@ -101,6 +104,9 @@ export default class ButtonComponent extends ComponentWithIconBase implements IC
super.setProperties(properties);
this._button.enabled = this.enabled;
this._button.label = this.label;
if (this.properties.fileType) {
this.fileType = properties.fileType;
}
this._button.title = this.title;
// Button's ariaLabel gets set to the label by default.
@@ -116,6 +122,7 @@ export default class ButtonComponent extends ComponentWithIconBase implements IC
this._button.setWidth(this.convertSize(this.height.toString()));
}
this.updateIcon();
this._changeRef.detectChanges();
}
protected updateIcon() {
@@ -182,6 +189,10 @@ export default class ButtonComponent extends ComponentWithIconBase implements IC
this.setPropertyFromUI<azdata.ButtonProperties, string>((properties, title) => { properties.title = title; }, newValue);
}
private setFileType(value: string) {
this.properties.fileType = value;
}
private get ariaLabel(): string {
return this.getPropertyOrDefault<azdata.ButtonProperties, string>((properties) => properties.ariaLabel, '');
}

View File

@@ -56,6 +56,8 @@ import { AlertsViewComponent } from 'sql/workbench/parts/jobManagement/browser/a
import { JobHistoryComponent } from 'sql/workbench/parts/jobManagement/browser/jobHistory.component';
import { OperatorsViewComponent } from 'sql/workbench/parts/jobManagement/browser/operatorsView.component';
import { ProxiesViewComponent } from 'sql/workbench/parts/jobManagement/browser/proxiesView.component';
import { NotebooksViewComponent } from 'sql/workbench/parts/jobManagement/browser/notebooksView.component';
import { NotebookHistoryComponent } from 'sql/workbench/parts/jobManagement/browser/notebookHistory.component';
import LoadingSpinner from 'sql/workbench/browser/modelComponents/loadingSpinner.component';
import { Checkbox } from 'sql/base/browser/ui/checkbox/checkbox.component';
import { SelectBox } from 'sql/platform/browser/selectBox/selectBox.component';
@@ -65,7 +67,7 @@ import { EditableDropDown } from 'sql/platform/browser/editableDropdown/editable
const baseComponents = [DashboardHomeContainer, DashboardComponent, DashboardWidgetWrapper, DashboardWebviewContainer,
DashboardWidgetContainer, DashboardGridContainer, DashboardErrorContainer, DashboardNavSection, ModelViewContent, WebviewContent, WidgetContent,
ComponentHostDirective, BreadcrumbComponent, ControlHostContent, DashboardControlHostContainer,
JobsViewComponent, AgentViewComponent, JobHistoryComponent, JobStepsViewComponent, AlertsViewComponent, ProxiesViewComponent, OperatorsViewComponent,
JobsViewComponent, NotebooksViewComponent, AgentViewComponent, JobHistoryComponent, NotebookHistoryComponent, JobStepsViewComponent, AlertsViewComponent, ProxiesViewComponent, OperatorsViewComponent,
DashboardModelViewContainer, ModelComponentWrapper, Checkbox, EditableDropDown, SelectBox, InputBox, LoadingSpinner];
/* Panel */
@@ -89,6 +91,7 @@ import { JobStepsViewComponent } from 'sql/workbench/parts/jobManagement/browser
import { IInstantiationService, _util } from 'vs/platform/instantiation/common/instantiation';
import { ILogService } from 'vs/platform/log/common/log';
const widgetComponents = [
PropertiesWidgetComponent,
ExplorerWidget,

View File

@@ -17,6 +17,17 @@
</div>
</ng-template>
</tab>
<tab [title]="notebooksComponentTitle" class="fullsize" [identifier]="notebooksTabIdentifier"
[iconClass]="notebooksIconClass">
<ng-template>
<div id="notebooksDiv" class="fullsize" *ngIf="showNotebookHistory === false">
<notebooksview-component></notebooksview-component>
</div>
<div id="historyDiv" class="fullsize" *ngIf="showNotebookHistory === true">
<notebookhistory-component></notebookhistory-component>
</div>
</ng-template>
</tab>
<tab [title]="alertsComponentTitle" class="fullsize" [identifier]="alertsTabIdentifier"
[iconClass]="alertsIconClass">
<ng-template>

View File

@@ -7,7 +7,7 @@ import 'vs/css!./media/jobs';
import * as nls from 'vs/nls';
import { Component, Inject, forwardRef, ChangeDetectorRef, ViewChild, Injectable } from '@angular/core';
import { AgentJobInfo } from 'azdata';
import { AgentJobInfo, AgentNotebookInfo } from 'azdata';
import { PanelComponent, IPanelOptions, NavigationBarLayout } from 'sql/base/browser/ui/panel/panel.component';
import { IJobManagementService } from 'sql/platform/jobManagement/common/interfaces';
import { IDashboardService } from 'sql/platform/dashboard/browser/dashboardService';
@@ -25,17 +25,23 @@ export class AgentViewComponent {
@ViewChild(PanelComponent) private _panel: PanelComponent;
private _showHistory: boolean = false;
private _showNotebookHistory: boolean = false;
private _jobId: string = null;
private _notebookId: string = null;
private _agentJobInfo: AgentJobInfo = null;
private _agentNotebookInfo: AgentNotebookInfo = null;
private _refresh: boolean = undefined;
private _expanded: Map<string, string>;
private _expandedNotebook: Map<string, string>;
public jobsIconClass: string = 'jobsview-icon';
public notebooksIconClass: string = 'notebooksview-icon';
public alertsIconClass: string = 'alertsview-icon';
public proxiesIconClass: string = 'proxiesview-icon';
public operatorsIconClass: string = 'operatorsview-icon';
private readonly jobsComponentTitle: string = nls.localize('jobview.Jobs', "Jobs");
private readonly notebooksComponentTitle: string = nls.localize('jobview.Notebooks', "Notebooks");
private readonly alertsComponentTitle: string = nls.localize('jobview.Alerts', "Alerts");
private readonly proxiesComponentTitle: string = nls.localize('jobview.Proxies', "Proxies");
private readonly operatorsComponentTitle: string = nls.localize('jobview.Operators', "Operators");
@@ -67,14 +73,26 @@ export class AgentViewComponent {
return this._jobId;
}
public get notebookId(): string {
return this._notebookId;
}
public get showHistory(): boolean {
return this._showHistory;
}
public get showNotebookHistory(): boolean {
return this._showNotebookHistory;
}
public get agentJobInfo(): AgentJobInfo {
return this._agentJobInfo;
}
public get agentNotebookInfo(): AgentNotebookInfo {
return this._agentNotebookInfo;
}
public get refresh(): boolean {
return this._refresh;
}
@@ -83,6 +101,10 @@ export class AgentViewComponent {
return this._expanded;
}
public get expandedNotebook(): Map<string, string> {
return this._expandedNotebook;
}
/**
* Public Setters
*/
@@ -91,15 +113,28 @@ export class AgentViewComponent {
this._jobId = value;
}
public set notebookId(value: string) {
this._notebookId = value;
}
public set showHistory(value: boolean) {
this._showHistory = value;
this._cd.detectChanges();
}
public set showNotebookHistory(value: boolean) {
this._showNotebookHistory = value;
this._cd.detectChanges();
}
public set agentJobInfo(value: AgentJobInfo) {
this._agentJobInfo = value;
}
public set agentNotebookInfo(value: AgentNotebookInfo) {
this._agentNotebookInfo = value;
}
public set refresh(value: boolean) {
this._refresh = value;
this._cd.detectChanges();
@@ -109,10 +144,18 @@ export class AgentViewComponent {
this._expanded.set(jobId, errorMessage);
}
public setExpandedNotebook(jobId: string, errorMessage: string) {
this._expandedNotebook.set(jobId, errorMessage);
}
public set expanded(value: Map<string, string>) {
this._expanded = value;
}
public set expandedNotebook(value: Map<string, string>) {
this._expandedNotebook = value;
}
public layout() {
this._panel.layout();
}

View File

@@ -130,3 +130,8 @@ export interface JobActionContext {
canEdit: boolean;
job: azdata.AgentJobInfo;
}
export interface NotebookActionContext {
canEdit: boolean;
notebook: azdata.AgentNotebookInfo;
}

View File

@@ -507,11 +507,11 @@ export class JobsViewComponent extends JobManagementView implements OnInit, OnDe
if (runChart && runChart.length > 0) {
return `<table class="jobprevruns" id="${dataContext.id}">
<tr>
<td>${runChart[0] ? runChart[0] : '<div></div>'}</td>
<td>${runChart[1] ? runChart[1] : '<div></div>'}</td>
<td>${runChart[2] ? runChart[2] : '<div></div>'}</td>
<td>${runChart[3] ? runChart[3] : '<div></div>'}</td>
<td>${runChart[4] ? runChart[4] : '<div></div>'}</td>
<td>${runChart[0] ? runChart[0] : '<div class="bar0"></div>'}</td>
<td>${runChart[1] ? runChart[1] : '<div class="bar1"></div>'}</td>
<td>${runChart[2] ? runChart[2] : '<div class="bar2"></div>'}</td>
<td>${runChart[3] ? runChart[3] : '<div class="bar3"></div>'}</td>
<td>${runChart[4] ? runChart[4] : '<div class="bar4"></div>'}</td>
</tr>
</table>`;
} else {

View File

@@ -0,0 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<title>Artboard 240</title>
<g>
<path d="M11.5,7,7,16h9Zm.5,8H11V14h1Zm-1-1.5V11h1v2.5Z" fill="#db7500"/>
<path d="M5,6h6V3H5ZM6,4h4V5H6Z"/>
<polygon points="5.882 16 6.382 15 2 15 2 13 3 13 3 12 2 12 2 10.023 3 10.023 3 9.023 2 9.023 2 7.039 3 7.039 3 6.039 2 6.039 2 4 3 4 3 3 2 3 2 1 13 1 13 7.764 14 9.764 14 0 1 0 1 3 0 3 0 4 1 4 1 6.039 0 6.039 0 7.039 1 7.039 1 9.023 0 9.023 0 10.023 1 10.023 1 12 0 12 0 13 1 13 1 16 5.882 16"/>
<path d="M11,15h1V14H11Zm0-4v2.5h1V11Z" fill="#fff"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 610 B

View File

@@ -0,0 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<title>Artboard 210</title>
<g>
<path d="M11.5,7,7,16h9Zm.5,8H11V14h1Zm-1-1.5V11h1v2.5Z" fill="#db7500"/>
<path d="M5,6h6V3H5ZM6,4h4V5H6Z" fill="#fff"/>
<polygon points="5.882 16 6.382 15 2 15 2 13 3 13 3 12 2 12 2 10.023 3 10.023 3 9.023 2 9.023 2 7.039 3 7.039 3 6.039 2 6.039 2 4 3 4 3 3 2 3 2 1 13 1 13 7.764 14 9.764 14 0 1 0 1 3 0 3 0 4 1 4 1 6.039 0 6.039 0 7.039 1 7.039 1 9.023 0 9.023 0 10.023 1 10.023 1 12 0 12 0 13 1 13 1 16 5.882 16" fill="#fff"/>
<path d="M11,15h1V14H11Zm0-4v2.5h1V11Z" fill="#fff"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 634 B

View File

@@ -0,0 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<title>Artboard 260</title>
<g>
<path d="M15.645,9.75A4.528,4.528,0,0,0,13.25,7.356a4.479,4.479,0,0,0-3.5,0A4.528,4.528,0,0,0,7.355,9.75a4.491,4.491,0,0,0,0,3.5A4.526,4.526,0,0,0,9.75,15.645a4.491,4.491,0,0,0,3.5,0,4.526,4.526,0,0,0,2.395-2.395,4.491,4.491,0,0,0,0-3.5Z" fill="#e00b1c"/>
<path d="M5,6h6V3H5ZM6,4h4V5H6Z"/>
<path d="M1,0V3H0V4H1V6.039H0v1H1V9.023H0v1H1V12H0v1H1v3H7.938a6.581,6.581,0,0,1-.524-.464A3.812,3.812,0,0,1,6.961,15H2V13H3V12H2V10.023H3v-1H2V7.039H3v-1H2V4H3V3H2V1H13V5.961a4.87,4.87,0,0,1,1,.375V0Z"/>
<polygon points="12.648 9.649 11.5 10.797 10.352 9.649 9.648 10.352 10.797 11.5 9.648 12.649 10.352 13.352 11.5 12.204 12.648 13.352 13.352 12.649 12.203 11.5 13.352 10.352 12.648 9.649" fill="#fff"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 847 B

View File

@@ -0,0 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<title>Artboard 230</title>
<g>
<path d="M15.645,9.75A4.528,4.528,0,0,0,13.25,7.356a4.479,4.479,0,0,0-3.5,0A4.528,4.528,0,0,0,7.355,9.75a4.491,4.491,0,0,0,0,3.5A4.526,4.526,0,0,0,9.75,15.645a4.491,4.491,0,0,0,3.5,0,4.526,4.526,0,0,0,2.395-2.395,4.491,4.491,0,0,0,0-3.5Z" fill="#e00b1c"/>
<path d="M5,6h6V3H5ZM6,4h4V5H6Z" fill="#fff"/>
<path d="M1,0V3H0V4H1V6.039H0v1H1V9.023H0v1H1V12H0v1H1v3H7.938a6.581,6.581,0,0,1-.524-.464A3.812,3.812,0,0,1,6.961,15H2V13H3V12H2V10.023H3v-1H2V7.039H3v-1H2V4H3V3H2V1H13V5.961a4.87,4.87,0,0,1,1,.375V0Z" fill="#fff"/>
<polygon points="12.648 9.649 11.5 10.797 10.352 9.649 9.648 10.352 10.797 11.5 9.648 12.649 10.352 13.352 11.5 12.204 12.648 13.352 13.352 12.649 12.203 11.5 13.352 10.352 12.648 9.649" fill="#fff"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 871 B

View File

@@ -0,0 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<title>Artboard 250</title>
<g>
<path d="M15.645,9.75A4.528,4.528,0,0,0,13.25,7.356a4.479,4.479,0,0,0-3.5,0A4.528,4.528,0,0,0,7.355,9.75a4.491,4.491,0,0,0,0,3.5A4.526,4.526,0,0,0,9.75,15.645a4.491,4.491,0,0,0,3.5,0,4.526,4.526,0,0,0,2.395-2.395,4.491,4.491,0,0,0,0-3.5Z" fill="#57a300"/>
<path d="M5,6h6V3H5ZM6,4h4V5H6Z"/>
<path d="M1,0V3H0V4H1V6.039H0v1H1V9.023H0v1H1V12H0v1H1v3H7.938a6.581,6.581,0,0,1-.524-.464A3.812,3.812,0,0,1,6.961,15H2V13H3V12H2V10.023H3v-1H2V7.039H3v-1H2V4H3V3H2V1H13V5.961a4.87,4.87,0,0,1,1,.375V0Z"/>
<path d="M13.224,9.794l.7.7-3,3L9.073,11.648l.7-.7,1.148,1.149Z" fill="#fff"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 725 B

View File

@@ -0,0 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
<title>Artboard 220</title>
<g>
<path d="M15.645,9.75A4.528,4.528,0,0,0,13.25,7.356a4.479,4.479,0,0,0-3.5,0A4.528,4.528,0,0,0,7.355,9.75a4.491,4.491,0,0,0,0,3.5A4.526,4.526,0,0,0,9.75,15.645a4.491,4.491,0,0,0,3.5,0,4.526,4.526,0,0,0,2.395-2.395,4.491,4.491,0,0,0,0-3.5Z" fill="#57a300"/>
<path d="M5,6h6V3H5ZM6,4h4V5H6Z" fill="#fff"/>
<path d="M1,0V3H0V4H1V6.039H0v1H1V9.023H0v1H1V12H0v1H1v3H7.938a6.581,6.581,0,0,1-.524-.464A3.812,3.812,0,0,1,6.961,15H2V13H3V12H2V10.023H3v-1H2V7.039H3v-1H2V4H3V3H2V1H13V5.961a4.87,4.87,0,0,1,1,.375V0Z" fill="#fff"/>
<path d="M13.224,9.794l.7.7-3,3L9.073,11.648l.7-.7,1.148,1.149Z" fill="#fff"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 749 B

View File

@@ -3,15 +3,22 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
jobhistory-component .all-jobs {
display: inline-block;
cursor: pointer;
font-size: 15px;
width: 10%;
margin-top: 10px;
}
notebookhistory-component .all-jobs {
display: inline-block;
cursor: pointer;
font-size: 15px;
width: 100%;
}
.overview-container .overview-tab .resultsViewCollapsible {
.overview-container .overview-tab .resultsViewCollapsible,
.overview-container .overview-tab .notebooksgridViewCollapsible {
padding: 15px;
display: inline;
}
@@ -26,6 +33,11 @@ jobhistory-component .all-jobs {
padding-top: 10px;
}
.history-container {
position: relative;
overflow-y: auto;
}
.vs-dark .overview-container .overview-tab {
color: #fff;
}
@@ -42,7 +54,13 @@ jobhistory-component .all-jobs {
overflow: hidden;
}
input#accordion {
input#accordion{
position: absolute;
opacity: 0;
z-index: -1;
}
.grid-arrow{
position: absolute;
opacity: 0;
z-index: -1;
@@ -72,7 +90,8 @@ input#accordion {
background: #333333;
}
.hc-black .overview-tab .accordion-content {
.hc-black .overview-tab .accordion-content,
.grid-arrow {
background: #000000;
border: 1px solid #2b56f2;
}
@@ -81,9 +100,9 @@ input#accordion {
max-height: 0;
overflow: hidden;
background: #eaeaea;
-webkit-transition: max-height .35s;
-o-transition: max-height .35s;
transition: max-height .35s;
-webkit-transition: max-height 0.35s;
-o-transition: max-height 0.35s;
transition: max-height 0.35s;
width: 100%;
}
@@ -92,7 +111,13 @@ input#accordion {
}
/* :checked */
input#accordion:checked ~ .accordion-content {
input#accordion:checked ~ .accordion-content,
.grid-arrow:checked {
max-height: 10em;
}
input#accordion:checked ~ .accordion-content,
.grid-arrow:checked {
max-height: 10em;
}
@@ -106,13 +131,13 @@ input#accordion:checked ~ .accordion-content {
height: 3em;
line-height: 3;
text-align: center;
-webkit-transition: all .3s;
-o-transition: all .3s;
transition: all .3s;
-webkit-transition: all 0.3s;
-o-transition: all 0.3s;
transition: all 0.3s;
}
.all-jobs > .back-button-icon {
content: url('back.svg');
content: url("back.svg");
width: 20px;
margin-right: 10px;
float: left;
@@ -121,30 +146,43 @@ input#accordion:checked ~ .accordion-content {
padding-bottom: 10px;
}
.vs-dark .all-jobs >.back-button-icon,
.hc-black .all-jobs >.back-button-icon {
content: url('back_inverse.svg');
.vs-dark .all-jobs > .back-button-icon,
.hc-black .all-jobs > .back-button-icon {
content: url("back_inverse.svg");
}
.vs .action-label.icon.newStepIcon {
background-image: url('new.svg');
background-image: url("new.svg");
}
.vs-dark .action-label.icon.newStepIcon,
.hc-black .action-label.icon.newStepIcon {
background-image: url('new_inverse.svg');
background-image: url("new_inverse.svg");
}
.vs .action-label.icon.opennotebook {
background-image: url("open_notebook.svg");
}
.vs-dark .action-label.icon.opennotebook,
.hc-black .action-label.icon.newStepIcon {
background-image: url("open_notebook_inverse.svg");
}
jobhistory-component .hc-black .icon.edit,
jobhistory-component .vs-dark .icon.edit {
background-image: url('edit_inverse.svg');
jobhistory-component .vs-dark .icon.edit,
notebookhistory-component .hc-black .icon.edit,
notebookhistory-component .vs-dark .icon.edit {
background-image: url("edit_inverse.svg");
}
jobhistory-component .vs .icon.edit {
background-image: url('edit.svg');
jobhistory-component .vs .icon.edit,
notebookhistory-component .vs .icon.edit {
background-image: url("edit.svg");
}
jobhistory-component .actions-container .icon.edit {
jobhistory-component .actions-container .icon.edit,
notebookhistory-component .actions-container .icon.edit {
background-position: 0% 50%;
background-repeat: no-repeat;
background-size: 12px;
@@ -210,12 +248,20 @@ table.step-list tr.step-row td {
max-height: 200px;
}
.step-table .monaco-tree .monaco-tree-rows.show-twisties > .monaco-tree-row.has-children > .content:before {
.step-table
.monaco-tree
.monaco-tree-rows.show-twisties
> .monaco-tree-row.has-children
> .content:before {
background: none;
background-image: none;
}
.step-table .monaco-tree.focused .monaco-tree-rows.show-twisties > .monaco-tree-row.has-children.selected:not(.loading) > .content:before {
.step-table
.monaco-tree.focused
.monaco-tree-rows.show-twisties
> .monaco-tree-row.has-children.selected:not(.loading)
> .content:before {
background-image: none;
}
@@ -258,40 +304,182 @@ table.step-list tr.step-row td {
flex-direction: column;
}
jobhistory-component {
jobhistory-component,
notebookhistory-component {
display: flex;
flex-direction: column;
height: 100%;
width: 100%;
}
jobhistory-component > .jobhistory-heading-container {
jobhistory-component > .jobhistory-heading-container,
notebookhistory-component > .jobhistory-heading-container {
display: flex;
}
jobhistory-component > .jobhistory-heading-container > .icon.in-progress {
jobhistory-component > .jobhistory-heading-container > .icon.in-progress,
notebookhistory-component > .jobhistory-heading-container > .icon.in-progress {
width: 20px;
height: 20px;
padding-top: 16px;
padding-left: 20px;
}
jobhistory-component > .agent-actionbar-container {
jobhistory-component > .agent-actionbar-container,
notebookhistory-component > .agent-actionbar-container {
border-top: 3px solid #f4f4f4;
}
.vs-dark jobhistory-component > .agent-actionbar-container {
.vs-dark jobhistory-component > .agent-actionbar-container,
.vs-dark notebookhistory-component > .agent-actionbar-container {
border-top: 3px solid #444444;
}
.hc-black jobhistory-component > .agent-actionbar-container {
.hc-black jobhistory-component > .agent-actionbar-container,
.hc-black notebookhistory-component > .agent-actionbar-container {
border-top: 3px solid #2b56f2;
}
jobhistory-component .step-table.prev-run-list .monaco-tree-wrapper .monaco-tree-row {
jobhistory-component
.step-table.prev-run-list
.monaco-tree-wrapper
.monaco-tree-row,
notebookhistory-component
.step-table.prev-run-list
.monaco-tree-wrapper
.monaco-tree-row {
width: 96%;
}
jobhistory-component .agent-actionbar-container > .monaco-toolbar.carbon-taskbar {
jobhistory-component
.agent-actionbar-container
> .monaco-toolbar.carbon-taskbar,
notebookhistory-component
.agent-actionbar-container
> .monaco-toolbar.carbon-taskbar {
margin: 10px 0px 5px 0px;
}
.notebook-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
grid-gap: 10px;
padding: 10px;
}
.notebook-grid-item {
border-radius: 5px;
padding-bottom: 5px;
height: 95px;
display: block;
border-radius: 0px;
}
.notebook-grid-item > img {
display: block;
margin-left: auto;
margin-right: auto;
margin-top: 10px;
width: 50px;
height: 50px;
}
.notebook-grid-item:hover {
background: #dcdcdc !important;
}
.vs-dark .notebook-grid-item:hover,
.hc-black .notebook-grid-item:hover {
background: #444444 !important;
}
.notebook-grid-item > .img-success {
background-image: url(./NotebookSuccess_16x.svg);
background-position: center;
display: block;
margin-left: auto;
margin-right: auto;
margin-top: 10px;
width: 40px;
height: 40px;
background-size: 40px 40px;
background-repeat: no-repeat;
}
.vs-dark .notebook-grid-item > .img-success,
.hc-black .notebook-grid-item > .img-success {
background-image: url(./NotebookSuccess_16x_white.svg);
background-position: center;
}
.notebook-grid-item > .img-failure {
background-image: url(./NotebookFail_16x.svg);
background-position: center;
display: block;
margin-left: auto;
margin-right: auto;
margin-top: 10px;
width: 40px;
height: 40px;
background-size: 40px 40px;
background-repeat: no-repeat;
}
.vs-dark .notebook-grid-item > .img-failure,
.hc-black .notebook-grid-item > .img-failure {
background-image: url(./NotebookFail_16x_white.svg);
background-position: center;
}
.notebook-grid-item > .img-error {
background-image: url(./NotebookError_16x.svg);
background-position: center;
display: block;
margin-left: auto;
margin-right: auto;
margin-top: 10px;
width: 40px;
height: 40px;
background-size: 40px 40px;
background-repeat: no-repeat;
}
.vs-dark .notebook-grid-item > .img-error,
.hc-black .notebook-grid-item > .img-error {
background-image: url(./NotebookError_16x_white.svg);
background-position: center;
}
.notebook-grid-item > p {
display: absolute;
margin-left: auto;
margin-right: auto;
margin-top: 10px;
width: 100%;
text-align: center;
}
.notebook-history-container {
flex: 1 1 auto;
position: relative;
overflow-y: auto;
}
.grid-title {
position: relative;
display: block;
padding: 0 0 0 1em;
background: #f4f4f4;
font-weight: bold;
line-height: 3;
cursor: pointer;
width: auto;
}
.vs-dark .grid-title {
background: #333333;
}
.hc-black .grid-title {
background: #000000;
border: 1px solid #2b56f2;
}

View File

@@ -9,12 +9,14 @@ agentview-component {
display: block;
}
jobsview-component {
jobsview-component,
notebooksview-component {
height: 100%;
width : 100%;
display: block;
}
.job-heading-container {
height: 50px;
border-bottom: 3px solid #f4f4f4;
@@ -35,6 +37,12 @@ jobsview-component {
display: block;
}
.jobnotebooksview-grid {
height: calc(100% - 75px);
width : 100%;
display: block;
}
.vs-dark #agentViewDiv .slick-header-column {
background: #333333 !important;
}
@@ -45,52 +53,63 @@ jobsview-component {
font-weight: bold;
}
.hc-black #jobsDiv jobsview-component .jobview-grid .grid-canvas .ui-widget-content.slick-row .slick-cell {
.hc-black #jobsDiv jobsview-component .jobview-grid .grid-canvas .ui-widget-content.slick-row .slick-cell,
.hc-black #notebooksDiv notebooks-component .jobnotebooksview-grid .grid-canvas .ui-widget-content.slick-row .slick-cell {
border: 1px solid #2b56f2;
}
#jobsDiv jobsview-component .jobview-grid .grid-canvas .ui-widget-content.slick-row .slick-cell {
#jobsDiv jobsview-component .jobview-grid .grid-canvas .ui-widget-content.slick-row .slick-cell,
#notebooksDiv notebooksview-component .jobnotebooksview-grid .grid-canvas .ui-widget-content.slick-row .slick-cell {
border-right: transparent !important;
border-left: transparent !important;
line-height: 33px !important;
}
#jobsDiv .jobview-joblist {
#jobsDiv .jobview-joblist,
#notebooksDiv .jobview-joblist {
height: 100%;
width: 100%;
}
#jobsDiv .jobview-jobnametable {
#jobsDiv .jobview-jobnametable,
#notebooksDiv .jobview-jobnametable {
border: 0px;
width: 100%;
height: 100%;
}
#jobsDiv .jobview-jobnameindicatorsuccess {
#jobsDiv .jobview-jobnameindicatorsuccess,
#notebooksDiv .jobview-jobnameindicatorsuccess {
width: 5px;
background: green;
}
#jobsDiv .slick-cell.l1.r1 .jobview-jobnameindicatorfailure {
#jobsDiv .slick-cell.l1.r1 .jobview-jobnameindicatorfailure,
#notebooksDiv .slick-cell.l1.r1 .jobview-jobnameindicatorfailure {
width: 5px;
background: red;
}
#jobsDiv .jobview-jobnameindicatorcancel {
#jobsDiv .jobview-jobnameindicatorcancel,
#notebooksDiv .jobview-jobnameindicatorcancel {
width: 5px;
background: orange;
}
#jobsDiv .jobview-grid .jobview-jobnameindicatorunknown {
#jobsDiv .jobview-grid .jobview-jobnameindicatorunknown,
#notebooks .jobnotebooksview-grid .jobview-jobnameindicatorunknown {
width: 5px;
background: grey;
}
#jobsDiv .jobview-grid .slick-cell.l1.r1.error-row .jobview-jobnametext {
#jobsDiv .jobview-grid .slick-cell.l1.r1.error-row .jobview-jobnametext,
#notebooksDiv .jobnotebooksview-grid .slick-cell.l1.r1.error-row .jobview-jobnametext,
#notebooksDiv .jobnotebooksview-grid .slick-cell.l1.r1.notebook-error-row .jobview-jobnametext {
width: 100%;
}
#jobsDiv .jobview-grid .slick-cell.l1.r1 .jobview-jobnametext {
#jobsDiv .jobview-grid .slick-cell.l1.r1 .jobview-jobnametext,
#notebooksDiv .jobnotebooksview-grid .slick-cell.l1.r1 .jobview-jobnametext {
text-overflow: ellipsis;
width: 250px;
overflow: hidden;
@@ -108,28 +127,44 @@ jobsview-component {
display: inline-block;
}
#jobsDiv .job-with-error {
#jobsDiv .job-with-error,
#notebooksDiv .job-with-error {
border-bottom: none;
}
.jobview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row .slick-cell.l1.r1.error-row {
.jobview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row .slick-cell.l1.r1.error-row,
.jobnotebooksview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row .slick-cell.l1.r1.error-row {
width: 100%;
opacity: 1;
font-weight: 700;
text-overflow: ellipsis;
color: orangered;
}
.jobview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row .slick-cell._detail_selector.error-row {
.jobnotebooksview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row .slick-cell.l1.r1.notebook-error-row {
width: 100%;
opacity: 1;
font-weight: 700;
overflow: hidden;
text-overflow: ellipsis;
color: orange;
}
.jobview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row .slick-cell._detail_selector.error-row,
.jobnotebooksview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row .slick-cell._detail_selector.error-row,
.jobnotebooksview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row .slick-cell._detail_selector.notebook-error-row {
opacity: 1;
}
#jobsDiv .jobview-splitter {
#jobsDiv .jobview-splitter,
#notebooksDiv .jobview-splitter {
height: 1px;
width: 100%;
background-color: gray;
}
#jobsDiv .jobview-jobitem {
#jobsDiv .jobview-jobitem,
#notebooksDiv .jobview-jobitem {
display: flex;
flex-direction: row;
flex-wrap: nowrap;
@@ -137,30 +172,36 @@ jobsview-component {
white-space: nowrap;
}
#jobsDiv .jobview-label {
#jobsDiv .jobview-label,
#notebooksDiv .jobview-label {
padding-bottom: 10px;
padding-top: 10px;
}
#jobsDiv .jobview-highlight-none {
#jobsDiv .jobview-highlight-none,
#notebooksDiv .jobview-highlight-none {
width: 5px;
margin-right: 10px;
}
#jobsDiv .detail-container {
#jobsDiv .detail-container,
#notebooksDiv .detail-container {
max-height: 100px !important;
line-height: 20px;
}
#jobsDiv .detail {
#jobsDiv .detail,
#notebooksDiv .detail {
padding: 5px
}
#jobsDiv .preload {
#jobsDiv .preload,
#notebooksDiv .preload {
font-size: 13px;
}
#jobsDiv .dynamic-cell-detail > :first-child {
#jobsDiv .dynamic-cell-detail > :first-child,
#notebooksDiv .dynamic-cell-detail > :first-child {
vertical-align: middle;
line-height: 13px;
padding: 10px;
@@ -171,11 +212,22 @@ jobsview-component {
background-image: url('./job.svg');
}
.vs-dark .jobsview-icon,
.hc-black .jobsview-icon {
background-image: url('./job_inverse.svg');
}
.notebooksview-icon {
background-image: url('./notebook.svg');
}
.vs-dark .notebooksview-icon,
.hc-black .notebooksview-icon {
background-image: url('./notebook_inverse.svg');
}
.alertsview-icon {
background-image: url('./alert.svg');
}
@@ -204,7 +256,7 @@ jobsview-component {
}
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 {
agentview-component .jobnotebooksview-grid .grid-canvas > .ui-widget-content.slick-row.odd > .slick-cell {
cursor: pointer;
}
@@ -227,26 +279,37 @@ agentview-component .jobview-grid .grid-canvas > .ui-widget-content.slick-row.od
padding-left: 15px;
}
#jobsDiv jobsview-component .jobview-grid .slick-cell.l1.r1.error-row td.jobview-jobnameindicatorfailure {
#jobsDiv jobsview-component .jobview-grid .slick-cell.l1.r1.error-row td.jobview-jobnameindicatorfailure,
#notebooksDiv notebooksview-component .jobnotebooksview-grid .slick-cell.l1.r1.error-row td.jobview-jobnameindicatorfailure,
#notebooksDiv notebooksview-component .jobnotebooksview-grid .slick-cell.l1.r1.notebook-error-row td.jobview-jobnameindicatorfailure {
width: 0;
background: none;
}
#jobsDiv .jobview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row:hover > .slick-cell,
#jobsDiv .jobview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row.hovered > .slick-cell,
#jobsDiv .jobview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row> .slick-cell.hovered {
#jobsDiv .jobview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row> .slick-cell.hovered,
#notebooksDiv .jobnotebooksview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row:hover > .slick-cell,
#notebooksDiv .jobnotebooksview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row.hovered > .slick-cell,
#notebooksDiv .jobnotebooksview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row> .slick-cell.hovered {
background: #dcdcdc !important;
}
.vs-dark #jobsDiv .jobview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row:hover > .slick-cell,
.vs-dark #jobsDiv .jobview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row > .slick-cell.hovered,
.vs-dark #jobsDiv .jobview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row.hovered > .slick-cell {
.vs-dark #jobsDiv .jobview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row.hovered > .slick-cell,
.vs-dark #notebooksDiv .jobnotebooksview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row:hover > .slick-cell,
.vs-dark #notebooksDiv .jobnotebooksview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row > .slick-cell.hovered,
.vs-dark #notebooksDiv .jobnotebooksview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row.hovered > .slick-cell {
background: #444444 !important;
}
.hc-black #jobsDiv .jobview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row:hover > .slick-cell,
.hc-black #jobsDiv .jobview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row > .slick-cell.hovered,
.hc-black #jobsDiv .jobview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row.hovered > .slick-cell {
.hc-black #jobsDiv .jobview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row.hovered > .slick-cell,
.hc-black #notebooksDiv .jobnotebooksview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row:hover > .slick-cell,
.hc-black #notebooksDiv .jobnotebooksview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row > .slick-cell.hovered,
.hc-black #notebooksDiv .jobnotebooksview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row.hovered > .slick-cell {
background: none !important;
}
@@ -257,7 +320,8 @@ table.jobprevruns div.bar3, table.jobprevruns div.bar4, table.jobprevruns div.ba
width: 10px;
}
.jobview-grid .slick-cell.l10.r10 {
.jobview-grid .slick-cell.l10.r10,
.jobnotebooksview-grid .slick-cell.l10.r10 {
text-align: center;
display: inline-flex;
}
@@ -270,6 +334,12 @@ table.jobprevruns > tbody {
vertical-align: bottom;
}
#notebooksDiv .jobnotebooksview-grid {
height: calc(100% - 75px);
width : 100%;
display: block;
}
#alertsDiv .jobalertsview-grid {
height: calc(100% - 75px);
width : 100%;
@@ -289,6 +359,8 @@ table.jobprevruns > tbody {
display: block;
}
.vs .action-label.icon.refreshIcon {
background-image: url('refresh.svg');
}
@@ -298,14 +370,52 @@ table.jobprevruns > tbody {
background-image: url('refresh_inverse.svg');
}
.vs .action-label.icon.openNotebook {
background-image: url('open_notebook.svg');
}
.vs-dark .action-label.icon.openNotebook,
.hc-black .action-label.icon.openNotebook {
background-image: url('open_notebook_inverse.svg');
}
.agent-actionbar-container .monaco-action-bar > ul.actions-container > li.action-item {
padding-left: 20px;
}
jobsview-component .jobview-grid .slick-cell.error-row {
jobsview-component .jobview-grid .slick-cell.error-row,
notebooksview-component .jobnotebooksview-grid .slick-cell.error-row,
notebooksview-component .jobnotebooksview-grid .slick-cell.notebook-error-row {
opacity: 0;
}
#notebooksDiv notebooksview-component .jobnotebooksview-grid .grid-canvas .ui-widget-content.slick-row .slick-cell {
border-right: transparent !important;
border-left: transparent !important;
line-height: 33px !important;
}
#notebooksDiv .jobnotebooksview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row:hover > .slick-cell,
#notebooksDiv .jobnotebooksview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row.hovered > .slick-cell,
#notebooksDiv .jobnotebooksview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row> .slick-cell.hovered {
background: #dcdcdc !important;
}
.vs-dark #notebooksDiv .jobnotebooksview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row:hover > .slick-cell,
.vs-dark #notebooksDiv .jobnotebooksview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row > .slick-cell.hovered,
.vs-dark #notebooksDiv .jobnotebooksview-grid > .monaco-table .slick-viewport > .grid-canvas > .ui-widget-content.slick-row.hovered > .slick-cell {
background: #444444 !important;
}
.vs-dark .jobnotebooksview-grid > .monaco-table .slick-header-columns .slick-resizable-handle {
border-left: 1px dotted white;
}
.jobnotebooksview-grid > .monaco-table .slick-header-columns .slick-resizable-handle {
border-left: 1px dotted #444444;
}
#alertsDiv jobalertsview-component .jobalertsview-grid .grid-canvas .ui-widget-content.slick-row .slick-cell {
border-right: transparent !important;
border-left: transparent !important;
@@ -403,8 +513,9 @@ jobsview-component .jobview-grid .slick-cell.error-row {
}
#jobsDiv jobsview-component .monaco-toolbar.carbon-taskbar,
#notebooksDiv notebooksview-component .monaco-toolbar.carbon-taskbar,
#operatorsDiv joboperatorsview-component .monaco-toolbar.carbon-taskbar,
#alertsDiv jobalertsview-component .monaco-toolbar.carbon-taskbar,
#proxiesDiv jobproxiesview-component .monaco-toolbar.carbon-taskbar {
margin: 10px 0px 10px 0px;
}
}

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><title>notebook</title><path d="M15.5,2V15H.5V2h2V1h3a4.19,4.19,0,0,1,1.32.21A3.87,3.87,0,0,1,8,1.84a3.87,3.87,0,0,1,1.18-.63A4.19,4.19,0,0,1,10.5,1h3V2ZM1.5,14H7.8a4.43,4.43,0,0,0-.51-.43,3.41,3.41,0,0,0-.54-.31,2.74,2.74,0,0,0-.59-.2A3.2,3.2,0,0,0,5.5,13h-3V3h-1Zm2-2h2a4.18,4.18,0,0,1,1,.13,4,4,0,0,1,1,.39V2.72a3,3,0,0,0-.94-.54A3.15,3.15,0,0,0,5.5,2h-2Zm11-9h-1V13h-3a3.2,3.2,0,0,0-.67.07,2.74,2.74,0,0,0-.59.2,3.41,3.41,0,0,0-.54.31A4.43,4.43,0,0,0,8.2,14h6.3Zm-4-1a3.15,3.15,0,0,0-1.06.18,3,3,0,0,0-.94.54v9.8a4,4,0,0,1,1-.39,4.18,4.18,0,0,1,1-.13h2V2Z"/></svg>

After

Width:  |  Height:  |  Size: 661 B

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#fff;}</style></defs><title>notebook_inverse</title><path class="cls-1" d="M15.46,2V15H.46V2h2V1h3a4.19,4.19,0,0,1,1.32.21A3.87,3.87,0,0,1,8,1.84a3.87,3.87,0,0,1,1.18-.63A4.19,4.19,0,0,1,10.46,1h3V2Zm-14,12h6.3a4.43,4.43,0,0,0-.51-.43,3.41,3.41,0,0,0-.54-.31,2.74,2.74,0,0,0-.59-.2A3.2,3.2,0,0,0,5.46,13h-3V3h-1Zm2-2h2a4.18,4.18,0,0,1,1,.13,4,4,0,0,1,1,.39V2.72a3,3,0,0,0-.94-.54A3.15,3.15,0,0,0,5.46,2h-2Zm11-9h-1V13h-3a3.2,3.2,0,0,0-.67.07,2.74,2.74,0,0,0-.59.2,3.41,3.41,0,0,0-.54.31,4.43,4.43,0,0,0-.51.43h6.3Zm-4-1a3.15,3.15,0,0,0-1.06.18,3,3,0,0,0-.94.54v9.8a4,4,0,0,1,1-.39,4.18,4.18,0,0,1,1-.13h2V2Z"/></svg>

After

Width:  |  Height:  |  Size: 734 B

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#00539c;}</style></defs><title>open_notebook</title><path d="M12.4,4.21l-.08-.11h-.56l-.69.06a1.54,1.54,0,0,0-.23.29v8.69H9a3.32,3.32,0,0,0-.93.13,3.34,3.34,0,0,0-.87.34V4.76a2.88,2.88,0,0,1,.43-.31A5.58,5.58,0,0,1,8.14,3.3a2.63,2.63,0,0,0-.3.09A3.62,3.62,0,0,0,6.78,4a3.68,3.68,0,0,0-1.07-.57A3.58,3.58,0,0,0,4.52,3.2H1.81v.9H0V15.85H13.57V5.48ZM2.71,4.1H4.52a2.61,2.61,0,0,1,1,.17,2.32,2.32,0,0,1,.86.49v8.85a3.27,3.27,0,0,0-.88-.34,3.22,3.22,0,0,0-.93-.13H2.71ZM.9,15V5h.91v9H4.52a3.94,3.94,0,0,1,.61.06,3.2,3.2,0,0,1,.52.18,4.19,4.19,0,0,1,.49.29,2.28,2.28,0,0,1,.45.39Zm11.75,0H7a2.7,2.7,0,0,1,.47-.39,2.83,2.83,0,0,1,.47-.29,3.42,3.42,0,0,1,.54-.18A3.81,3.81,0,0,1,9,14h2.73V5h.89Z"/><polygon class="cls-1" points="13.05 3.56 13.05 3.58 13.04 3.57 13.05 3.56"/><path class="cls-1" d="M13,3.57h0v0Z"/><polygon class="cls-1" points="13.05 3.56 13.05 3.58 13.04 3.57 13.05 3.56"/><polygon class="cls-1" points="14.06 1.65 14.04 1.65 14.04 1.63 14.06 1.65"/><path class="cls-1" d="M15.76,2.1,14,3.81l-.38.38L13,3.58v0l1-1H12.64a3.35,3.35,0,0,0-1.09.26h0a3.94,3.94,0,0,0-.86.52l-.24.21s0,0,0,0a3.3,3.3,0,0,0-.51.67,3.1,3.1,0,0,0-.26.47,3.41,3.41,0,0,0-.27,1.39h-.9a4.68,4.68,0,0,1,.16-1.19,4.74,4.74,0,0,1,.25-.66,2.21,2.21,0,0,1,.2-.41,4.66,4.66,0,0,1,.36-.51c.1-.13.22-.26.34-.39a4.14,4.14,0,0,1,.66-.53,1.19,1.19,0,0,1,.23-.16A2.79,2.79,0,0,1,11,2.08l.31-.13.42-.14a4.32,4.32,0,0,1,1.19-.16h1.15l-1-1L13.67,0Z"/></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#fff;}.cls-2{fill:#0095d7;}</style></defs><title>open_notebook_inverse</title><path class="cls-1" d="M12.55,4.21l-.08-.11h-.56l-.69.06a1.54,1.54,0,0,0-.23.29v8.69H9.18a3.32,3.32,0,0,0-.93.13,3.34,3.34,0,0,0-.87.34V4.76a2.88,2.88,0,0,1,.43-.31A5.58,5.58,0,0,1,8.29,3.3a2.63,2.63,0,0,0-.3.09A3.62,3.62,0,0,0,6.93,4a3.68,3.68,0,0,0-1.07-.57A3.58,3.58,0,0,0,4.67,3.2H2v.9H.15V15.85H13.72V5.48ZM2.86,4.1H4.67a2.61,2.61,0,0,1,1,.17,2.32,2.32,0,0,1,.86.49v8.85a3.27,3.27,0,0,0-.88-.34,3.22,3.22,0,0,0-.93-.13H2.86ZM1,15V5H2v9H4.67a3.94,3.94,0,0,1,.61.06,3.2,3.2,0,0,1,.52.18,4.19,4.19,0,0,1,.49.29,2.28,2.28,0,0,1,.45.39ZM12.8,15H7.11a2.7,2.7,0,0,1,.47-.39A2.83,2.83,0,0,1,8,14.28a3.42,3.42,0,0,1,.54-.18A3.81,3.81,0,0,1,9.18,14h2.73V5h.89Z"/><polygon class="cls-2" points="13.2 3.56 13.2 3.58 13.19 3.57 13.2 3.56"/><path class="cls-2" d="M13.19,3.57h0v0Z"/><polygon class="cls-2" points="13.2 3.56 13.2 3.58 13.19 3.57 13.2 3.56"/><polygon class="cls-2" points="14.21 1.65 14.19 1.65 14.19 1.63 14.21 1.65"/><path class="cls-2" d="M15.91,2.1,14.2,3.81l-.38.38-.62-.61v0l1-1H12.79a3.35,3.35,0,0,0-1.09.26h0a3.94,3.94,0,0,0-.86.52l-.24.21s0,0,0,0a3.3,3.3,0,0,0-.51.67,3.1,3.1,0,0,0-.26.47A3.41,3.41,0,0,0,9.5,6.11H8.6a4.68,4.68,0,0,1,.16-1.19A4.74,4.74,0,0,1,9,4.26a2.21,2.21,0,0,1,.2-.41,4.66,4.66,0,0,1,.36-.51c.1-.13.22-.26.34-.39a4.14,4.14,0,0,1,.66-.53,1.19,1.19,0,0,1,.23-.16,2.79,2.79,0,0,1,.34-.18l.31-.13.42-.14a4.32,4.32,0,0,1,1.19-.16h1.15l-1-1L13.82,0Z"/></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#d02e00;}.cls-2{fill:#fff;}</style></defs><title>error_16x16</title><circle class="cls-1" cx="8.07" cy="8.07" r="7.93"/><polygon class="cls-2" points="8.82 8.07 11.53 10.78 10.83 11.48 8.12 8.78 5.41 11.48 4.7 10.78 7.42 8.07 4.65 5.31 5.36 4.61 8.12 7.37 10.83 4.67 11.53 5.36 8.82 8.07"/></svg>

After

Width:  |  Height:  |  Size: 414 B

View File

@@ -0,0 +1 @@
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1{fill:#3bb44a;}</style></defs><title>success_16x16</title><path class="cls-1" d="M16,3.16,5.48,13.69,0,8.2l.89-.89,4.6,4.59,9.63-9.62Z"/></svg>

After

Width:  |  Height:  |  Size: 255 B

View File

@@ -0,0 +1,163 @@
<!--
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
-->
<div class="jobhistory-heading-container">
<h1 class="job-heading">Notebook | {{ this._agentNotebookInfo?.name }}</h1>
<div class="icon in-progress" *ngIf="showProgressWheel()"></div>
</div>
<!-- Back -->
<div
class="all-jobs"
tabindex="0"
(click)="goToJobs()"
(keyup.enter)="goToJobs()"
>
<div
class="back-button-icon"
(click)="goToJobs()"
(keyup.enter)="goToJobs()"
></div>
All Notebooks
</div>
<!-- Actions -->
<div #actionbarContainer class="agent-actionbar-container"></div>
<!-- Overview -->
<div class="overview-container">
<div class="overview-tab" (click)="toggleCollapse()" tabindex="0">
<input id="accordion" type="checkbox" />
<label for="accordion">
<div
class="resultsViewCollapsible collapsed"
(click)="toggleCollapse()"
></div>
Overview
</label>
<div class="accordion-content">
<table align="left">
<tr>
<td id="col1">
TargetDatabase:
</td>
<td id="col2">
{{ this._agentNotebookInfo?.targetDatabase }}
</td>
<td id="col3">
Enabled:
</td>
<td id="col4">
{{ this._agentNotebookInfo?.enabled }}
</td>
</tr>
<tr>
<td id="col1">
Last Run:
</td>
<td id="col2">
{{ this._agentNotebookInfo?.lastRun }}
</td>
<td id="col3">
Next Run:
</td>
<td id="col4">
{{ this._agentNotebookInfo?.nextRun }}
</td>
</tr>
</table>
</div>
</div>
</div>
<!-- Overview -->
<div class="history-container">
<div
class="overview-container"
*ngFor="let grid of this._grids; let i = index"
>
<div
class="overview-tab"
(click)="toggleGridCollapse(i)"
tabindex="{{i}}"
*ngIf="grid.histories?.length"
>
<input id="accordion{{ i }}" type="checkbox" class="grid-arrow" />
<label for="accordion{{ i }}">
<div id= "history-grid-icon{{i}}" (click)="toggleGridCollapse(i)"
class="resultsViewCollapsible"></div>
{{ grid.title }}
</label>
<div id="notebook-grid{{ i }}" class="notebook-grid {{ i }}" >
<div
*ngFor="let history of grid.histories"
class="notebook-grid-item"
(dblclick)="openNotebook(history)"
title="{{ createdTooltip(history) }}"
(contextmenu)="
openHistoryContextMenu($event, history, grid.contextMenuType)
"
>
<div
*ngIf="history.materializedNotebookErrorInfo"
class="img-error"
></div>
<div
*ngIf="
history.runStatus === 1 && !history.materializedNotebookErrorInfo
"
class="img-success"
></div>
<div
*ngIf="
history.runStatus === 0 && !history.materializedNotebookErrorInfo
"
class="img-failure"
></div>
<div
*ngIf="
!history.materializedNotebookName || history.materializedNotebookName === '';
else notebookRunName
"
>
<p style="text-align: center;">
{{ formatDateTimetoLocaleDate(history.runDate) }}
<br />
{{ formatDateTimetoLocaleTime(history.runDate) }}
</p>
</div>
<div
*ngIf="
history.materializedNotebookName !== '';
else notebookRunName
"
>
<p
style="text-align: center; text-overflow: ellipsis; overflow:hidden; white-space: nowrap"
>
{{ history.materializedNotebookName }}
</p>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Job History details -->
<div class="history-details">
<!-- Previous run list -->
<div class="prev-run-list-container" style="min-width: 270px">
<h3 *ngIf="_showPreviousRuns === false" style="text-align: center">
No Previous Runs Available
</h3>
<div
class="step-table prev-run-list"
style="position: relative; width: 100%"
>
<div #table style="position: absolute; width: 100%; height: 100%"></div>
</div>
</div>
</div>

View File

@@ -0,0 +1,608 @@
/*---------------------------------------------------------------------------------------------
* 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!./media/jobHistory';
import * as azdata from 'azdata';
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, PipeTransform, Pipe } from '@angular/core';
import { Taskbar } from 'sql/base/browser/ui/taskbar/taskbar';
import { AgentViewComponent } from 'sql/workbench/parts/jobManagement/browser/agentView.component';
import { CommonServiceInterface } from 'sql/platform/bootstrap/browser/commonServiceInterface.service';
import { RunJobAction, StopJobAction, JobsRefreshAction, EditNotebookJobAction, EditJobAction, OpenMaterializedNotebookAction, OpenTemplateNotebookAction, RenameNotebookMaterializedAction, PinNotebookMaterializedAction, UnpinNotebookMaterializedAction, DeleteMaterializedNotebookAction } from 'sql/platform/jobManagement/browser/jobActions';
import { NotebookCacheObject } from 'sql/platform/jobManagement/common/jobManagementService';
import { JobManagementUtilities } from 'sql/platform/jobManagement/browser/jobManagementUtilities';
import { IJobManagementService } from 'sql/platform/jobManagement/common/interfaces';
import { JobHistoryRow } from 'sql/workbench/parts/jobManagement/browser/jobHistoryTree';
import { JobStepsViewRow } from 'sql/workbench/parts/jobManagement/browser/jobStepsViewTree';
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IAction } from 'vs/base/common/actions';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { JobManagementView, JobActionContext } from 'sql/workbench/parts/jobManagement/browser/jobManagementView';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { TabChild } from 'sql/base/browser/ui/panel/tab.component';
import { IDashboardService } from 'sql/platform/dashboard/browser/dashboardService';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import * as TelemetryKeys from 'sql/platform/telemetry/common/telemetryKeys';
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
export const DASHBOARD_SELECTOR: string = 'notebookhistory-component';
export class GridSection {
title: string;
histories: azdata.AgentNotebookHistoryInfo[];
contextMenuType: number;
style: string;
}
@Component({
selector: DASHBOARD_SELECTOR,
templateUrl: decodeURI(require.toUrl('./notebookHistory.component.html')),
providers: [{ provide: TabChild, useExisting: forwardRef(() => NotebookHistoryComponent) }],
changeDetection: ChangeDetectionStrategy.OnPush
})
@Injectable()
export class NotebookHistoryComponent extends JobManagementView implements OnInit {
@ViewChild('table') private _tableContainer: ElementRef;
@ViewChild('jobsteps') private _jobStepsView: ElementRef;
@ViewChild('notebookHistoryActionbarContainer') private _notebookHistoryActionbarView: ElementRef;
@ViewChild('notebookgriditem') private _notebookGridItem: ElementRef;
@Input() public agentNotebookInfo: azdata.AgentNotebookInfo = undefined;
@Input() public agentJobHistories: azdata.AgentJobHistoryInfo[] = undefined;
public notebookHistories: azdata.AgentNotebookHistoryInfo[] = undefined;
public agentNotebookHistoryInfo: azdata.AgentNotebookHistoryInfo = undefined;
private _isVisible: boolean = false;
private _stepRows: JobStepsViewRow[] = [];
private _showSteps: boolean = undefined;
private _showPreviousRuns: boolean = undefined;
private _runStatus: string = undefined;
private _notebookCacheObject: NotebookCacheObject;
private _agentNotebookInfo: azdata.AgentNotebookInfo;
private _noJobsAvailable: boolean = false;
protected _notebookHistoryActionBar: Taskbar;
// Job Actions
private _editNotebookJobAction: EditNotebookJobAction;
private _runJobAction: RunJobAction;
private _stopJobAction: StopJobAction;
private _refreshAction: JobsRefreshAction;
private _openNotebookTemplateAction: OpenTemplateNotebookAction;
private static readonly HEADING_HEIGHT: number = 24;
private _grids: GridSection[] = [];
constructor(
@Inject(forwardRef(() => ChangeDetectorRef)) private _cd: ChangeDetectorRef,
@Inject(forwardRef(() => CommonServiceInterface)) commonService: CommonServiceInterface,
@Inject(forwardRef(() => AgentViewComponent)) _agentViewComponent: AgentViewComponent,
@Inject(IWorkbenchThemeService) private themeService: IWorkbenchThemeService,
@Inject(IInstantiationService) private instantiationService: IInstantiationService,
@Inject(IContextMenuService) contextMenuService: IContextMenuService,
@Inject(IJobManagementService) private _jobManagementService: IJobManagementService,
@Inject(ICommandService) private _commandService: ICommandService,
@Inject(IKeybindingService) keybindingService: IKeybindingService,
@Inject(IDashboardService) dashboardService: IDashboardService,
@Inject(ITelemetryService) private _telemetryService: ITelemetryService,
@Inject(IQuickInputService) private _quickInputService: IQuickInputService
) {
super(commonService, dashboardService, contextMenuService, keybindingService, instantiationService, _agentViewComponent);
let notebookCacheObjectMap = this._jobManagementService.notebookCacheObjectMap;
this._serverName = commonService.connectionManagementService.connectionInfo.connectionProfile.serverName;
let notebookCache = notebookCacheObjectMap[this._serverName];
if (notebookCache) {
this._notebookCacheObject = notebookCache;
} else {
this._notebookCacheObject = new NotebookCacheObject();
this._notebookCacheObject.serverName = this._serverName;
this._jobManagementService.addToCache(this._serverName, this._notebookCacheObject);
}
}
ngOnInit() {
// set base class elements
this._visibilityElement = this._tableContainer;
this._parentComponent = this._agentViewComponent;
this._agentNotebookInfo = this._agentViewComponent.agentNotebookInfo;
this.initActionBar();
const self = this;
this._telemetryService.publicLog(TelemetryKeys.JobHistoryView);
}
private loadHistory() {
const self = this;
let ownerUri: string = this._commonService.connectionManagementService.connectionInfo.ownerUri;
let jobName = this._agentViewComponent.agentNotebookInfo.name;
let jobId = this._agentViewComponent.notebookId;
let targetDatabase = this._agentViewComponent.agentNotebookInfo.targetDatabase;
this._jobManagementService.getNotebookHistory(ownerUri, jobId, jobName, targetDatabase).then((result) => {
if (result && result.histories) {
this.notebookHistories = result.histories;
self._notebookCacheObject.setNotebookHistory(jobId, this.notebookHistories);
self._notebookCacheObject.setJobSchedules(jobId, result.schedules);
self._notebookCacheObject.setJobSteps(jobId, result.steps);
this._agentViewComponent.agentNotebookInfo.jobSteps = this._notebookCacheObject.getJobSteps(jobId);
this._agentViewComponent.agentNotebookInfo.jobSchedules = this._notebookCacheObject.getJobSchedules(jobId);
this._agentNotebookInfo = this._agentViewComponent.agentNotebookInfo;
if (result.histories.length > 0) {
self._noJobsAvailable = false;
self._showPreviousRuns = true;
} else {
self._notebookCacheObject.setNotebookHistory(self._agentViewComponent.notebookId, result.histories);
self._noJobsAvailable = true;
self._showPreviousRuns = false;
}
} else {
self._noJobsAvailable = true;
self._showPreviousRuns = false;
self._showSteps = false;
}
this._actionBar.context = { targetObject: { canEdit: true, notebook: this._agentNotebookInfo, job: this._agentNotebookInfo }, ownerUri: this.ownerUri, component: this };
this._editNotebookJobAction.enabled = true;
this._actionBar.setContent([
{ action: this._runJobAction },
{ action: this._stopJobAction },
{ action: this._refreshAction },
{ action: this._editNotebookJobAction },
{ action: this._openNotebookTemplateAction }
]);
this.createGrid();
if (self._agentViewComponent.showNotebookHistory) {
self._cd.detectChanges();
this.collapseGrid();
}
});
}
private setStepsTree(element: JobHistoryRow) {
const self = this;
let cachedHistory = self._notebookCacheObject.getNotebookHistory(element.jobID);
if (cachedHistory) {
self.agentNotebookHistoryInfo = cachedHistory.find(
history => self.formatTime(history.runDate) === self.formatTime(element.runDate));
}
if (self.agentNotebookHistoryInfo) {
self.agentNotebookHistoryInfo.runDate = self.formatTime(self.agentNotebookHistoryInfo.runDate);
if (self.agentNotebookHistoryInfo.steps) {
let jobStepStatus = this.didJobFail(self.agentNotebookHistoryInfo);
self._stepRows = self.agentNotebookHistoryInfo.steps.map(step => {
let stepViewRow = new JobStepsViewRow();
stepViewRow.message = step.message;
stepViewRow.runStatus = jobStepStatus ? JobManagementUtilities.convertToStatusString(0) :
JobManagementUtilities.convertToStatusString(step.runStatus);
self._runStatus = JobManagementUtilities.convertToStatusString(self.agentNotebookHistoryInfo.runStatus);
stepViewRow.stepName = step.stepDetails.stepName;
stepViewRow.stepId = step.stepDetails.id.toString();
return stepViewRow;
});
self._stepRows.unshift(new JobStepsViewRow());
self._stepRows[0].rowID = 'stepsColumn' + self._agentNotebookInfo.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 {
self._showSteps = false;
}
if (self._agentViewComponent.showNotebookHistory) {
self._cd.detectChanges();
this.collapseGrid();
}
}
}
private didJobFail(job: azdata.AgentJobHistoryInfo): boolean {
for (let i = 0; i < job.steps.length; i++) {
if (job.steps[i].runStatus === 0) {
return true;
}
}
return false;
}
private toggleCollapse(): void {
let arrow: HTMLElement = jQuery('.resultsViewCollapsible').get(0);
let checkbox: any = document.getElementById('accordion');
if (arrow.className === 'resultsViewCollapsible' && checkbox.checked === false) {
arrow.className = 'resultsViewCollapsible collapsed';
} else if (arrow.className === 'resultsViewCollapsible collapsed' && checkbox.checked === true) {
arrow.className = 'resultsViewCollapsible';
}
}
private toggleGridCollapse(i): void {
let notebookGrid = document.getElementById('notebook-grid' + i);
let checkbox: any = document.getElementById('accordion' + i);
let arrow = document.getElementById('history-grid-icon' + i);
if (notebookGrid.className === 'notebook-grid ' + i && checkbox.checked === true) {
notebookGrid.className = 'notebook-grid ' + i + ' collapsed';
notebookGrid.style.display = 'none';
arrow.className = 'resultsViewCollapsible collapsed';
} else if (notebookGrid.className === 'notebook-grid ' + i + ' collapsed' && checkbox.checked === false) {
notebookGrid.className = 'notebook-grid ' + i;
notebookGrid.style.display = 'grid';
arrow.className = 'resultsViewCollapsible';
}
}
private toggleHistoryDisplay(event): void {
let header = event.srcElement.attributes;
}
private goToJobs(): void {
this._isVisible = false;
this._agentViewComponent.showNotebookHistory = false;
}
private convertToJobHistoryRow(historyInfo: azdata.AgentJobHistoryInfo): JobHistoryRow {
let jobHistoryRow = new JobHistoryRow();
jobHistoryRow.runDate = this.formatTime(historyInfo.runDate);
jobHistoryRow.runStatus = JobManagementUtilities.convertToStatusString(historyInfo.runStatus);
jobHistoryRow.instanceID = historyInfo.instanceId;
jobHistoryRow.jobID = historyInfo.jobId;
return jobHistoryRow;
}
private formatTime(time: string): string {
return time.replace('T', ' ');
}
private formatDateTimetoLocaleDate(time: string) {
let dateInstance = new Date(time);
return dateInstance.toLocaleDateString();
}
private formatDateTimetoLocaleTime(time: string) {
let dateInstance = new Date(time);
return dateInstance.toLocaleTimeString();
}
private showProgressWheel(): boolean {
return this._showPreviousRuns !== true && this._noJobsAvailable === false;
}
public onFirstVisible() {
this._agentNotebookInfo = this._agentViewComponent.agentNotebookInfo;
if (!this.agentNotebookInfo) {
this.agentNotebookInfo = this._agentNotebookInfo;
}
if (this.isRefreshing) {
this.loadHistory();
return;
}
else {
this.createGrid();
}
let notebookHistories = this._notebookCacheObject.notebookHistories[this._agentViewComponent.notebookId];
if (notebookHistories) {
if (notebookHistories.length > 0) {
const self = this;
this._noJobsAvailable = false;
if (this._notebookCacheObject.prevJobID === this._agentViewComponent.notebookId || notebookHistories[0].jobId === this._agentViewComponent.notebookId) {
this._showPreviousRuns = true;
this._agentViewComponent.agentNotebookInfo.jobSteps = this._notebookCacheObject.getJobSteps(this._agentNotebookInfo.jobId);
this._agentViewComponent.agentNotebookInfo.jobSchedules = this._notebookCacheObject.getJobSchedules(this._agentNotebookInfo.jobId);
this._agentNotebookInfo = this._agentViewComponent.agentNotebookInfo;
}
} else if (notebookHistories.length === 0) {
this._showPreviousRuns = false;
this._showSteps = false;
this._noJobsAvailable = true;
}
this._editNotebookJobAction.enabled = true;
this._actionBar.setContent([
{ action: this._runJobAction },
{ action: this._stopJobAction },
{ action: this._refreshAction },
{ action: this._editNotebookJobAction },
{ action: this._openNotebookTemplateAction }
]);
this._cd.detectChanges();
this.collapseGrid();
} else {
this.loadHistory();
}
this._notebookCacheObject.prevJobID = this._agentViewComponent.notebookId;
}
public layout() {
let historyDetails = jQuery('.overview-container').get(0);
let statusBar = jQuery('.part.statusbar').get(0);
if (historyDetails && statusBar) {
let historyBottom = historyDetails.getBoundingClientRect().bottom;
let statusTop = statusBar.getBoundingClientRect().top;
let height: number = statusTop - historyBottom - NotebookHistoryComponent.HEADING_HEIGHT;
if (this._table) {
this._table.layout(new dom.Dimension(
dom.getContentWidth(this._tableContainer.nativeElement),
height));
}
}
}
protected initActionBar() {
this._runJobAction = this.instantiationService.createInstance(RunJobAction);
this._stopJobAction = this.instantiationService.createInstance(StopJobAction);
this._editNotebookJobAction = this.instantiationService.createInstance(EditNotebookJobAction);
this._refreshAction = this.instantiationService.createInstance(JobsRefreshAction);
this._openNotebookTemplateAction = this.instantiationService.createInstance(OpenTemplateNotebookAction);
let taskbar = <HTMLElement>this.actionBarContainer.nativeElement;
this._actionBar = new Taskbar(taskbar);
this._editNotebookJobAction.enabled = !this.showProgressWheel();
let targetObject: JobActionContext = { canEdit: !this.showProgressWheel(), job: this._agentNotebookInfo };
this._actionBar.context = { targetObject: targetObject, ownerUri: this.ownerUri, component: this };
this._actionBar.setContent([
{ action: this._runJobAction },
{ action: this._stopJobAction },
{ action: this._refreshAction },
{ action: this._editNotebookJobAction },
{ action: this._openNotebookTemplateAction }
]);
}
public openNotebook(history: azdata.AgentNotebookHistoryInfo) {
if (history.runStatus === 0) {
return;
}
let ownerUri: string = this._commonService.connectionManagementService.connectionInfo.ownerUri;
let targetDatabase = this._agentViewComponent.agentNotebookInfo.targetDatabase;
this._jobManagementService.getMaterialziedNotebook(ownerUri, targetDatabase, history.materializedNotebookId).then(async (result) => {
if (result) {
let regex = /:|-/gi;
let readableDataTimeString = history.runDate.replace(regex, '').replace(' ', '');
let tempNotebookFileName = this._agentViewComponent.agentNotebookInfo.name + '_' + readableDataTimeString;
await this._commandService.executeCommand('agent.openNotebookEditorFromJsonString', tempNotebookFileName, result.notebookMaterialized);
}
});
}
public deleteMaterializedNotebook(history: azdata.AgentNotebookHistoryInfo) {
//TODO: Implement deletenotebook context menu action
let ownerUri: string = this._commonService.connectionManagementService.connectionInfo.ownerUri;
let targetDatabase = this._agentViewComponent.agentNotebookInfo.targetDatabase;
this._jobManagementService.deleteMaterializedNotebook(ownerUri, history, targetDatabase).then(async (result) => {
if (result) {
this.loadHistory();
}
});
}
public openTemplateNotebook() {
let ownerUri: string = this._commonService.connectionManagementService.connectionInfo.ownerUri;
let targetDatabase = this._agentViewComponent.agentNotebookInfo.targetDatabase;
let jobId = this._agentViewComponent.agentNotebookInfo.jobId;
this._jobManagementService.getTemplateNotebook(ownerUri, targetDatabase, jobId).then(async (result) => {
if (result) {
await this._commandService.executeCommand('agent.openNotebookEditorFromJsonString', this._agentViewComponent.agentNotebookInfo.name, result.notebookTemplate, this.agentNotebookInfo, ownerUri);
}
});
}
public renameNotebook(history: azdata.AgentNotebookHistoryInfo) {
const defaultDateTime = new Date(history.runDate).toLocaleDateString() + ' ' + new Date(history.runDate).toLocaleTimeString();
let notebookRunName = (history.materializedNotebookName === '') ? defaultDateTime : history.materializedNotebookName;
let ownerUri: string = this._commonService.connectionManagementService.connectionInfo.ownerUri;
let targetDatabase = this._agentViewComponent.agentNotebookInfo.targetDatabase;
let materializedNotebookId = history.materializedNotebookId;
this._quickInputService.input({ placeHolder: notebookRunName }).then(async (value) => {
if (value) {
if (!/\S/.test(value)) {
value = '';
}
await this._jobManagementService.updateNotebookMaterializedName(ownerUri, history, targetDatabase, value).then(async (result) => {
if (result) {
history.materializedNotebookName = value;
this.loadHistory();
}
});
}
});
}
public toggleNotebookPin(history: azdata.AgentNotebookHistoryInfo, pin: boolean) {
let ownerUri: string = this._commonService.connectionManagementService.connectionInfo.ownerUri;
let targetDatabase = this._agentViewComponent.agentNotebookInfo.targetDatabase;
let materializedNotebookId = history.materializedNotebookId;
this._jobManagementService.updateNotebookMaterializedPin(ownerUri, history, targetDatabase, pin).then(async (result) => {
if (result) {
history.materializedNotebookPin = pin;
this.loadHistory();
}
});
}
public openHistoryContextMenu(event: MouseEvent, history: azdata.AgentNotebookHistoryInfo, contextMenuType: number) {
let anchor = {
x: event.clientX,
y: event.clientY
};
let runDate = event.target['runDate'];
let gridActions = this.getGridActions();
let actionContext = {
component: this,
history: history
};
this._contextMenuService.showContextMenu({
getAnchor: () => anchor,
getActions: () => (contextMenuType === 1) ? this.getPinnedGridActions() : this.getGridActions(),
getKeyBinding: (action) => this._keybindingFor(action),
getActionsContext: () => (actionContext)
});
}
protected getGridActions(): IAction[] {
const openNotebookAction = this._instantiationService.createInstance(OpenMaterializedNotebookAction);
const renameNotebookAction = this._instantiationService.createInstance(RenameNotebookMaterializedAction);
const pinNotebookAction = this._instantiationService.createInstance(PinNotebookMaterializedAction);
const deleteMaterializedNotebookAction = this._instantiationService.createInstance(DeleteMaterializedNotebookAction);
return [
openNotebookAction,
renameNotebookAction,
pinNotebookAction,
deleteMaterializedNotebookAction
];
}
protected getPinnedGridActions(): IAction[] {
const openNotebookAction = this._instantiationService.createInstance(OpenMaterializedNotebookAction);
const renameNotebookAction = this._instantiationService.createInstance(RenameNotebookMaterializedAction);
const unpinNotebookAction = this._instantiationService.createInstance(UnpinNotebookMaterializedAction);
const deleteMaterializedNotebookAction = this._instantiationService.createInstance(DeleteMaterializedNotebookAction);
return [
openNotebookAction,
renameNotebookAction,
unpinNotebookAction,
deleteMaterializedNotebookAction
];
}
public createdTooltip(history: azdata.AgentNotebookHistoryInfo) {
let tooltipString: string = '';
if (history.materializedNotebookName && history.materializedNotebookName !== '') {
tooltipString = history.materializedNotebookName;
}
let dateOptions = {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric'
};
tooltipString += '\n' + nls.localize('notebookHistory.dateCreatedTooltip', "Date Created: ") + new Date(history.runDate).toLocaleDateString(undefined, dateOptions);
if (history.materializedNotebookErrorInfo && /\S/.test(history.materializedNotebookErrorInfo)) {
tooltipString += '\n' + nls.localize('notebookHistory.notebookErrorTooltip', "Notebook Error: ") + history.materializedNotebookErrorInfo;
}
if (history.runStatus === 0 && history.message && /\S/.test(history.message)) {
tooltipString += '\n' + nls.localize('notebookHistory.ErrorTooltip', "Job Error: ") + history.message;
}
return tooltipString;
}
public createGrid() {
let histories = this._notebookCacheObject.getNotebookHistory(this._agentViewComponent.notebookId);
histories = histories.sort((h1, h2) => {
return new Date(h2.runDate).getTime() - new Date(h1.runDate).getTime();
});
this._grids = [];
let tempHistory: azdata.AgentNotebookHistoryInfo[] = [];
for (let i = 0; i < histories.length; i++) {
if (histories[i].materializedNotebookPin) {
tempHistory.push(histories[i]);
}
}
this._grids.push({
title: nls.localize('notebookHistory.pinnedTitle', "Pinned"),
histories: tempHistory,
contextMenuType: 1,
style: 'grid'
});
// Pushing the pinned notebooks grid
tempHistory = [];
let count = 0;
let i = 0;
for (; i < histories.length; i++) {
if (!histories[i].materializedNotebookPin && count < 10) {
tempHistory.push(histories[i]);
count++;
}
if (count === 10) {
break;
}
}
this._grids.push({
title: nls.localize('notebookHistory.recentRunsTitle', "Recent Runs"),
histories: tempHistory,
contextMenuType: 0,
style: 'grid'
});
tempHistory = [];
for (i += 1; i < histories.length; i++) {
if (!histories[i].materializedNotebookPin) {
tempHistory.push(histories[i]);
}
}
this._grids.push({
title: nls.localize('notebookHistory.pastRunsTitle', "Past Runs"),
histories: tempHistory,
contextMenuType: 0,
style: 'none'
});
}
public collapseGrid() {
for (let i = 0; i < this._grids.length; i++) {
let notebookGrid = document.getElementById('notebook-grid' + i);
let arrow = document.getElementById('history-grid-icon' + i);
if (notebookGrid) {
let checkbox: any = document.getElementById('accordion' + i);
if (this._grids[i].style === 'none') {
notebookGrid.className = 'notebook-grid ' + i + ' collapsed';
arrow.className = 'resultsViewCollapsible collapsed';
notebookGrid.style.display = 'none';
checkbox.checked = true;
}
else {
notebookGrid.className = 'notebook-grid ' + i;
notebookGrid.style.display = 'grid';
arrow.className = 'resultsViewCollapsible';
checkbox.checked = false;
}
}
}
}
public refreshJobs() {
this._agentViewComponent.refresh = true;
this.loadHistory();
}
/** GETTERS */
public get showSteps(): boolean {
return this._showSteps;
}
public get stepRows() {
return this._stepRows;
}
public get ownerUri(): string {
return this._commonService.connectionManagementService.connectionInfo.ownerUri;
}
public get serverName(): string {
return this._serverName;
}
/** SETTERS */
public set showSteps(value: boolean) {
this._showSteps = value;
this._cd.detectChanges();
}
}

View File

@@ -0,0 +1,15 @@
<!--
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
-->
<div class="job-heading-container">
<h1 class="job-heading" *ngIf="_isCloud === false">Notebook Jobs</h1>
<h1 class="job-heading" *ngIf="_isCloud === true">No Notebooks Jobs Available</h1>
<div class="icon in-progress" *ngIf="_showProgressWheel === true"></div>
</div>
<div #actionbarContainer class="agent-actionbar-container"></div>
<div #notebooksgrid class="jobnotebooksview-grid"></div>

View File

@@ -0,0 +1,984 @@
/*---------------------------------------------------------------------------------------------
* 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!./media/jobs';
import * as azdata from 'azdata';
import * as nls from 'vs/nls';
import * as dom from 'vs/base/browser/dom';
import { Component, Inject, forwardRef, ElementRef, ChangeDetectorRef, ViewChild, OnInit, OnDestroy } from '@angular/core';
import { TabChild } from 'sql/base/browser/ui/panel/tab.component';
import { Table } from 'sql/base/browser/ui/table/table';
import { AgentViewComponent } from 'sql/workbench/parts/jobManagement/browser/agentView.component';
import { RowDetailView } from 'sql/base/browser/ui/table/plugins/rowDetailView';
import { NotebookCacheObject } from 'sql/platform/jobManagement/common/jobManagementService';
import { EditJobAction, NewNotebookJobAction, RunJobAction, EditNotebookJobAction, JobsRefreshAction, IJobActionInfo, DeleteNotebookAction } from 'sql/platform/jobManagement/browser/jobActions';
import { JobManagementUtilities } from 'sql/platform/jobManagement/browser/jobManagementUtilities';
import { HeaderFilter } from 'sql/base/browser/ui/table/plugins/headerFilter.plugin';
import { IJobManagementService } from 'sql/platform/jobManagement/common/interfaces';
import { JobManagementView, JobActionContext } from 'sql/workbench/parts/jobManagement/browser/jobManagementView';
import { CommonServiceInterface } from 'sql/platform/bootstrap/browser/commonServiceInterface.service';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IAction } from 'vs/base/common/actions';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IDashboardService } from 'sql/platform/dashboard/browser/dashboardService';
import { escape } from 'sql/base/common/strings';
import { IWorkbenchThemeService, IColorTheme } from 'vs/workbench/services/themes/common/workbenchThemeService';
import { tableBackground, cellBackground, cellBorderColor } from 'sql/platform/theme/common/colors';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import * as TelemetryKeys from 'sql/platform/telemetry/common/telemetryKeys';
import { attachButtonStyler } from 'sql/platform/theme/common/styler';
import { Taskbar } from 'sql/base/browser/ui/taskbar/taskbar';
export const NOTEBOOKSVIEW_SELECTOR: string = 'notebooksview-component';
export const ROW_HEIGHT: number = 45;
export const ACTIONBAR_PADDING: number = 10;
interface IItem extends Slick.SlickData {
notebookId?: string;
id: string;
}
@Component({
selector: NOTEBOOKSVIEW_SELECTOR,
templateUrl: decodeURI(require.toUrl('./notebooksView.component.html')),
providers: [{ provide: TabChild, useExisting: forwardRef(() => NotebooksViewComponent) }],
})
export class NotebooksViewComponent extends JobManagementView implements OnInit, OnDestroy {
private columns: Array<Slick.Column<any>> = [
{
name: nls.localize('notebookColumns.name', "Name"),
field: 'name',
formatter: (row, cell, value, columnDef, dataContext) => this.renderName(row, cell, value, columnDef, dataContext),
width: 150,
id: 'name'
},
{ name: nls.localize('notebookColumns.targetDatbase', "Target Database"), field: 'targetDatabase', width: 80, id: 'targetDatabase' },
{ name: nls.localize('notebookColumns.lastRun', "Last Run"), field: 'lastRun', width: 80, id: 'lastRun' },
{ name: nls.localize('notebookColumns.nextRun', "Next Run"), field: 'nextRun', width: 80, id: 'nextRun' },
{ name: nls.localize('notebookColumns.status', "Status"), field: 'currentExecutionStatus', width: 50, id: 'currentExecutionStatus' },
{ name: nls.localize('notebookColumns.lastRunOutcome', "Last Run Outcome"), field: 'lastRunOutcome', width: 100, id: 'lastRunOutcome' },
{
name: nls.localize('notebookColumns.previousRuns', "Previous Runs"),
formatter: (row, cell, value, columnDef, dataContext) => this.renderChartsPostHistory(row, cell, value, columnDef, dataContext),
field: 'previousRuns',
width: 100,
id: 'previousRuns'
}
];
private _notebookCacheObject: NotebookCacheObject;
private rowDetail: RowDetailView<IItem>;
private filterPlugin: any;
private dataView: any;
private _isCloud: boolean;
private filterStylingMap: { [columnName: string]: [any]; } = {};
private filterStack = ['start'];
private filterValueMap: { [columnName: string]: string[]; } = {};
private sortingStylingMap: { [columnName: string]: any; } = {};
public notebooks: azdata.AgentNotebookInfo[];
private notebookHistories: { [jobId: string]: azdata.AgentNotebookHistoryInfo[]; } = Object.create(null);
private jobSteps: { [jobId: string]: azdata.AgentJobStepInfo[]; } = Object.create(null);
private jobAlerts: { [jobId: string]: azdata.AgentAlertInfo[]; } = Object.create(null);
private jobSchedules: { [jobId: string]: azdata.AgentJobScheduleInfo[]; } = Object.create(null);
public contextAction = NewNotebookJobAction;
@ViewChild('notebooksgrid') _gridEl: ElementRef;
constructor(
@Inject(forwardRef(() => CommonServiceInterface)) commonService: CommonServiceInterface,
@Inject(forwardRef(() => ChangeDetectorRef)) private _cd: ChangeDetectorRef,
@Inject(forwardRef(() => ElementRef)) private _el: ElementRef,
@Inject(forwardRef(() => AgentViewComponent)) _agentViewComponent: AgentViewComponent,
@Inject(IJobManagementService) private _jobManagementService: IJobManagementService,
@Inject(IWorkbenchThemeService) private _themeService: IWorkbenchThemeService,
@Inject(ICommandService) private _commandService: ICommandService,
@Inject(IInstantiationService) instantiationService: IInstantiationService,
@Inject(IContextMenuService) contextMenuService: IContextMenuService,
@Inject(IKeybindingService) keybindingService: IKeybindingService,
@Inject(IDashboardService) _dashboardService: IDashboardService,
@Inject(ITelemetryService) private _telemetryService: ITelemetryService
) {
super(commonService, _dashboardService, contextMenuService, keybindingService, instantiationService, _agentViewComponent);
let notebookCacheObjectMap = this._jobManagementService.notebookCacheObjectMap;
let jobCache = notebookCacheObjectMap[this._serverName];
if (jobCache) {
this._notebookCacheObject = jobCache;
} else {
this._notebookCacheObject = new NotebookCacheObject();
this._notebookCacheObject.serverName = this._serverName;
this._jobManagementService.addToCache(this._serverName, this._notebookCacheObject);
}
this._isCloud = commonService.connectionManagementService.connectionInfo.serverInfo.isCloud;
}
ngOnInit() {
// set base class elements
this._visibilityElement = this._gridEl;
this._parentComponent = this._agentViewComponent;
this._register(this._themeService.onDidColorThemeChange(e => this.updateTheme(e)));
this._telemetryService.publicLog(TelemetryKeys.JobsView);
}
ngOnDestroy() {
}
public layout() {
let jobsViewToolbar = jQuery('notebooksview-component .agent-actionbar-container').get(0);
let statusBar = jQuery('.part.statusbar').get(0);
if (jobsViewToolbar && statusBar) {
let toolbarBottom = jobsViewToolbar.getBoundingClientRect().bottom + ACTIONBAR_PADDING;
let statusTop = statusBar.getBoundingClientRect().top;
this._table.layout(new dom.Dimension(
dom.getContentWidth(this._gridEl.nativeElement),
statusTop - toolbarBottom));
}
}
onFirstVisible() {
let self = this;
let cached: boolean = false;
if (this._notebookCacheObject.serverName === this._serverName && this._notebookCacheObject.notebooks.length > 0) {
cached = true;
this.notebooks = this._notebookCacheObject.notebooks;
}
let columns = this.columns.map((column) => {
column.rerenderOnResize = true;
return column;
});
let options = <Slick.GridOptions<any>>{
syncColumnCellResize: true,
enableColumnReorder: false,
rowHeight: ROW_HEIGHT,
enableCellNavigation: true,
forceFitColumns: false
};
this.dataView = new Slick.Data.DataView({ inlineFilters: false });
let rowDetail = new RowDetailView<IItem>({
cssClass: '_detail_selector',
process: (job) => {
(<any>rowDetail).onAsyncResponse.notify({
'itemDetail': job
}, undefined, this);
},
useRowClick: false,
panelRows: 1,
postTemplate: () => '', // I'm assuming these code paths are just never hit...
preTemplate: () => '',
});
this.rowDetail = rowDetail;
columns.unshift(this.rowDetail.getColumnDefinition());
let filterPlugin = new HeaderFilter<{ inlineFilters: false }>();
this._register(attachButtonStyler(filterPlugin, this._themeService));
this.filterPlugin = filterPlugin;
jQuery(this._gridEl.nativeElement).empty();
jQuery(this.actionBarContainer.nativeElement).empty();
this.initActionBar();
this._table = this._register(new Table(this._gridEl.nativeElement, { columns }, options));
this._table.grid.setData(this.dataView, true);
this._table.grid.onClick.subscribe((e, args) => {
let notebook = self.getNotebook(args);
self._agentViewComponent.notebookId = notebook.jobId;
self._agentViewComponent.agentNotebookInfo = notebook;
self._agentViewComponent.showNotebookHistory = true;
});
this._register(this._table.onContextMenu(e => {
self.openContextMenu(e);
}));
if (cached && this._agentViewComponent.refresh !== true) {
this.onNotebooksAvailable(null);
this._showProgressWheel = false;
if (this.isVisible) {
this._cd.detectChanges();
}
} else {
let ownerUri: string = this._commonService.connectionManagementService.connectionInfo.ownerUri;
this._jobManagementService.getNotebooks(ownerUri).then((result) => {
if (result && result.notebooks) {
self.notebooks = result.notebooks;
self._notebookCacheObject.notebooks = self.notebooks;
self.onNotebooksAvailable(result.notebooks);
}
this._showProgressWheel = false;
if (this.isVisible) {
this._cd.detectChanges();
}
});
}
}
protected initActionBar() {
let refreshAction = this._instantiationService.createInstance(JobsRefreshAction);
let newAction = this._instantiationService.createInstance(NewNotebookJobAction);
let taskbar = <HTMLElement>this.actionBarContainer.nativeElement;
this._actionBar = new Taskbar(taskbar);
this._actionBar.setContent([
{ action: refreshAction },
{ action: newAction }
]);
let context: IJobActionInfo = { component: this, ownerUri: this._commonService.connectionManagementService.connectionInfo.ownerUri };
this._actionBar.context = context;
}
private onNotebooksAvailable(notebooks: azdata.AgentNotebookInfo[]) {
let jobViews: any;
let start: boolean = true;
if (!notebooks) {
let dataView = this._notebookCacheObject.dataView;
jobViews = dataView.getItems();
start = false;
} else {
jobViews = notebooks.map((job) => {
return {
id: 'notebook' + job.jobId,
notebookId: job.jobId,
name: job.name,
targetDatabase: job.targetDatabase,
lastRun: JobManagementUtilities.convertToLastRun(job.lastRun),
nextRun: JobManagementUtilities.convertToNextRun(job.nextRun),
currentExecutionStatus: JobManagementUtilities.convertToExecutionStatusString(job.currentExecutionStatus),
lastRunOutcome: (job.lastRunNotebookError === '') ? JobManagementUtilities.convertToStatusString(job.lastRunOutcome) : 'Notebook Error'
};
});
}
this._table.registerPlugin(<any>this.rowDetail);
this.filterPlugin.onFilterApplied.subscribe((e, args) => {
this.dataView.refresh();
this._table.grid.resetActiveCell();
let filterValues = args.column.filterValues;
if (filterValues) {
if (filterValues.length === 0) {
// if an associated styling exists with the current filters
if (this.filterStylingMap[args.column.name]) {
let filterLength = this.filterStylingMap[args.column.name].length;
// then remove the filtered styling
for (let i = 0; i < filterLength; i++) {
let lastAppliedStyle = this.filterStylingMap[args.column.name].pop();
this._table.grid.removeCellCssStyles(lastAppliedStyle[0]);
}
delete this.filterStylingMap[args.column.name];
let index = this.filterStack.indexOf(args.column.name, 0);
if (index > -1) {
this.filterStack.splice(index, 1);
delete this.filterValueMap[args.column.name];
}
// apply the previous filter styling
let currentItems = this.dataView.getFilteredItems();
let styledItems = this.filterValueMap[this.filterStack[this.filterStack.length - 1]][1];
if (styledItems === currentItems) {
let lastColStyle = this.filterStylingMap[this.filterStack[this.filterStack.length - 1]];
for (let i = 0; i < lastColStyle.length; i++) {
this._table.grid.setCellCssStyles(lastColStyle[i][0], lastColStyle[i][1]);
}
} else {
// style it all over again
let seenJobs = 0;
for (let i = 0; i < currentItems.length; i++) {
this._table.grid.removeCellCssStyles('error-row' + i.toString());
this._table.grid.removeCellCssStyles('notebook-error-row' + i.toString());
let item = this.dataView.getFilteredItems()[i];
if (item.lastRunOutcome === 'Failed') {
this.addToStyleHash(seenJobs, false, this.filterStylingMap, args.column.name);
if (this.filterStack.indexOf(args.column.name) < 0) {
this.filterStack.push(args.column.name);
this.filterValueMap[args.column.name] = [filterValues];
}
// one expansion for the row and one for
// the error detail
seenJobs++;
i++;
}
seenJobs++;
}
this.dataView.refresh();
this.filterValueMap[args.column.name].push(this.dataView.getFilteredItems());
this._table.grid.resetActiveCell();
}
if (this.filterStack.length === 0) {
this.filterStack = ['start'];
}
}
} else {
let seenNotebooks = 0;
for (let i = 0; i < this.notebooks.length; i++) {
this._table.grid.removeCellCssStyles('error-row' + i.toString());
this._table.grid.removeCellCssStyles('notebook-error-row' + i.toString());
let item = this.dataView.getItemByIdx(i);
// current filter
if (_.contains(filterValues, item[args.column.field])) {
// check all previous filters
if (this.checkPreviousFilters(item)) {
if (item.lastRunOutcome === 'Failed') {
this.addToStyleHash(seenNotebooks, false, this.filterStylingMap, args.column.name);
if (this.filterStack.indexOf(args.column.name) < 0) {
this.filterStack.push(args.column.name);
this.filterValueMap[args.column.name] = [filterValues];
}
// one expansion for the row and one for
// the error detail
seenNotebooks++;
i++;
}
seenNotebooks++;
}
}
}
this.dataView.refresh();
if (this.filterValueMap[args.column.name]) {
this.filterValueMap[args.column.name].push(this.dataView.getFilteredItems());
} else {
this.filterValueMap[args.column.name] = this.dataView.getFilteredItems();
}
this._table.grid.resetActiveCell();
}
} else {
this.expandJobs(false);
}
});
this.filterPlugin.onCommand.subscribe((e, args: any) => {
this.columnSort(args.column.name, args.command === 'sort-asc');
});
this._table.registerPlugin(this.filterPlugin);
this.dataView.beginUpdate();
this.dataView.setItems(jobViews);
this.dataView.setFilter((item) => this.filter(item));
this.dataView.endUpdate();
this._table.autosizeColumns();
this._table.resizeCanvas();
this.expandJobs(start);
// tooltip for job name
jQuery('.jobview-jobnamerow').hover(e => {
let currentTarget = e.currentTarget;
currentTarget.title = currentTarget.innerText;
});
const self = this;
this._table.grid.onColumnsResized.subscribe((e, data: any) => {
let nameWidth: number = data.grid.getColumns()[1].width;
// adjust job name when resized
jQuery('#notebooksDiv .jobnotebooksview-grid .slick-cell.l1.r1 .jobview-jobnametext').css('width', `${nameWidth - 10}px`);
// adjust error message when resized
jQuery('#notebooksDiv .jobnotebooksview-grid .slick-cell.l1.r1.error-row .jobview-jobnametext').css('width', '100%');
jQuery('#notebooksDiv .jobnotebooksview-grid .slick-cell.l1.r1.notebook-error-row .jobview-jobnametext').css('width', '100%');
// generate job charts again
self.notebooks.forEach(job => {
let jobHistories = self._notebookCacheObject.getNotebookHistory(job.jobId);
if (jobHistories) {
let previousRuns = jobHistories.slice(jobHistories.length - 5, jobHistories.length);
self.createJobChart('notebook' + job.jobId, previousRuns);
}
});
});
jQuery('#notebooksDiv .jobnotebooksview-grid .monaco-table .slick-viewport .grid-canvas .ui-widget-content.slick-row').hover((e1) =>
this.highlightErrorRows(e1), (e2) => this.hightlightNonErrorRows(e2));
this._table.grid.onScroll.subscribe((e) => {
jQuery('#notebooksDiv .jobnotebooksview-grid .monaco-table .slick-viewport .grid-canvas .ui-widget-content.slick-row').hover((e1) =>
this.highlightErrorRows(e1), (e2) => this.hightlightNonErrorRows(e2));
});
// cache the dataview for future use
this._notebookCacheObject.dataView = this.dataView;
this.filterValueMap['start'] = [[], this.dataView.getItems()];
this.loadJobHistories();
}
private highlightErrorRows(e) {
// highlight the error row as well if a failing job row is hovered
if (e.currentTarget.children.item(0).classList.contains('job-with-error')) {
let target = jQuery(e.currentTarget);
let targetChildren = jQuery(e.currentTarget.children);
let siblings = target.nextAll().toArray();
let top = parseInt(target.css('top'), 10);
for (let i = 0; i < siblings.length; i++) {
let sibling = siblings[i];
let siblingTop = parseInt(jQuery(sibling).css('top'), 10);
if (siblingTop === top + ROW_HEIGHT) {
jQuery(sibling.children).addClass('hovered');
sibling.onmouseenter = (e) => {
targetChildren.addClass('hovered');
};
sibling.onmouseleave = (e) => {
targetChildren.removeClass('hovered');
};
break;
}
}
}
}
private hightlightNonErrorRows(e) {
// switch back to original background
if (e.currentTarget.children.item(0).classList.contains('job-with-error')) {
let target = jQuery(e.currentTarget);
let siblings = target.nextAll().toArray();
let top = parseInt(target.css('top'), 10);
for (let i = 0; i < siblings.length; i++) {
let sibling = siblings[i];
let siblingTop = parseInt(jQuery(sibling).css('top'), 10);
if (siblingTop === top + ROW_HEIGHT) {
jQuery(sibling.children).removeClass('hovered');
break;
}
}
}
}
private setRowWithErrorClass(hash: { [index: number]: { [id: string]: string; } }, row: number, errorClass: string) {
hash[row] = {
'_detail_selector': errorClass,
'id': errorClass,
'jobId': errorClass,
'name': errorClass,
'targetDatabase': errorClass,
'lastRun': errorClass,
'nextRun': errorClass,
'currentExecutionStatus': errorClass,
'lastRunOutcome': errorClass,
'previousRuns': errorClass
};
return hash;
}
private addToStyleHash(row: number, start: boolean, map: any, columnName: string) {
let hash: {
[index: number]: {
[id: string]: string;
}
} = {};
hash = this.setRowWithErrorClass(hash, row, 'job-with-error');
hash = this.setRowWithErrorClass(hash, row + 1, 'error-row');
if (start) {
if (map['start']) {
map['start'].push(['error-row' + row.toString(), hash]);
} else {
map['start'] = [['error-row' + row.toString(), hash]];
}
} else {
if (map[columnName]) {
map[columnName].push(['error-row' + row.toString(), hash]);
} else {
map[columnName] = [['error-row' + row.toString(), hash]];
}
}
this._table.grid.setCellCssStyles('error-row' + row.toString(), hash);
}
private addToErrorStyleHash(row: number, start: boolean, map: any, columnName: string) {
let hash: {
[index: number]: {
[id: string]: string;
}
} = {};
hash = this.setRowWithErrorClass(hash, row, 'job-with-error');
hash = this.setRowWithErrorClass(hash, row + 1, 'notebook-error-row');
if (start) {
if (map['start']) {
map['start'].push(['notebook-error-row' + row.toString(), hash]);
} else {
map['start'] = [['notebook-error-row' + row.toString(), hash]];
}
} else {
if (map[columnName]) {
map[columnName].push(['notebook-error-row' + row.toString(), hash]);
} else {
map[columnName] = [['notebook-error-row' + row.toString(), hash]];
}
}
this._table.grid.setCellCssStyles('notebook-error-row' + row.toString(), hash);
}
private 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 ('Cancelled'):
resultIndicatorClass = 'jobview-jobnameindicatorcancel';
break;
case ('Status Unknown'):
resultIndicatorClass = 'jobview-jobnameindicatorunknown';
break;
case ('Notebook Error'):
resultIndicatorClass = 'jobview-jobnameindicatorcancel';
break;
default:
resultIndicatorClass = 'jobview-jobnameindicatorfailure';
break;
}
return '<table class="jobview-jobnametable"><tr class="jobview-jobnamerow">' +
'<td nowrap class=' + resultIndicatorClass + '></td>' +
'<td nowrap class="jobview-jobnametext">' + escape(dataContext.name) + '</td>' +
'</tr></table>';
}
private renderChartsPostHistory(row, cell, value, columnDef, dataContext) {
let runChart = this._notebookCacheObject.getRunChart(dataContext.id);
if (runChart && runChart.length > 0) {
return `<table class="jobprevruns" id="${dataContext.id}">
<tr>
<td>${runChart[0] ? runChart[0] : '<div class="bar0"></div>'}</td>
<td>${runChart[1] ? runChart[1] : '<div class="bar1"></div>'}</td>
<td>${runChart[2] ? runChart[2] : '<div class="bar2"></div>'}</td>
<td>${runChart[3] ? runChart[3] : '<div class="bar3"></div>'}</td>
<td>${runChart[4] ? runChart[4] : '<div class="bar4"></div>'}</td>
</tr>
</table>`;
} else {
return `<table class="jobprevruns" id="${dataContext.id}">
<tr>
<td><div class="bar0"></div></td>
<td><div class="bar1"></div></td>
<td><div class="bar2"></div></td>
<td><div class="bar3"></div></td>
<td><div class="bar4"></div></td>
</tr>
</table>`;
}
}
private expandJobRowDetails(rowIdx: number, message?: string): void {
let item = this.dataView.getItemByIdx(rowIdx);
item.message = this._agentViewComponent.expandedNotebook.get(item.notebookId);
this.rowDetail.applyTemplateNewLineHeight(item, true);
}
private async loadJobHistories() {
if (this.notebooks) {
let ownerUri: string = this._commonService.connectionManagementService.connectionInfo.ownerUri;
let separatedJobs = this.separateFailingJobs();
// grab histories of the failing jobs first
// so they can be expanded quicker
let failing = separatedJobs[0];
let passing = separatedJobs[1];
Promise.all([this.curateJobHistory(failing, ownerUri), this.curateJobHistory(passing, ownerUri)]);
}
}
private separateFailingJobs(): azdata.AgentNotebookInfo[][] {
let failing = [];
let nonFailing = [];
for (let i = 0; i < this.notebooks.length; i++) {
if (this.notebooks[i].lastRunOutcome === 0) {
failing.push(this.notebooks[i]);
} else {
nonFailing.push(this.notebooks[i]);
}
}
return [failing, nonFailing];
}
private checkPreviousFilters(item): boolean {
for (let column in this.filterValueMap) {
if (column !== 'start' && this.filterValueMap[column][0].length > 0) {
if (!_.contains(this.filterValueMap[column][0], item[JobManagementUtilities.convertColNameToField(column)])) {
return false;
}
}
}
return true;
}
private isErrorRow(cell: HTMLElement) {
return cell.classList.contains('error-row') || cell.classList.contains('notebook-error-row');
}
private getNotebook(args: Slick.OnClickEventArgs<any>): azdata.AgentNotebookInfo {
let row = args.row;
let notebookName: string;
let cell = args.grid.getCellNode(row, 1);
if (this.isErrorRow(cell)) {
notebookName = args.grid.getCellNode(row - 1, 1).innerText.trim();
} else {
notebookName = cell.innerText.trim();
}
let notebook = this.notebooks.filter(job => job.name === notebookName)[0];
return notebook;
}
private async curateJobHistory(notebooks: azdata.AgentNotebookInfo[], ownerUri: string) {
const self = this;
for (let notebook of notebooks) {
let result = await this._jobManagementService.getNotebookHistory(ownerUri, notebook.jobId, notebook.name, notebook.targetDatabase);
if (result) {
self.jobSteps[notebook.jobId] = result.steps ? result.steps : [];
self.jobSchedules[notebook.jobId] = result.schedules ? result.schedules : [];
self.notebookHistories[notebook.jobId] = result.histories ? result.histories : [];
self._notebookCacheObject.setJobSteps(notebook.jobId, self.jobSteps[notebook.jobId]);
self._notebookCacheObject.setNotebookHistory(notebook.jobId, self.notebookHistories[notebook.jobId]);
self._notebookCacheObject.setJobSchedules(notebook.jobId, self.jobSchedules[notebook.jobId]);
let notebookHistories = self._notebookCacheObject.getNotebookHistory(notebook.jobId);
let previousRuns: azdata.AgentNotebookHistoryInfo[];
if (notebookHistories.length >= 5) {
previousRuns = notebookHistories.slice(notebookHistories.length - 5, notebookHistories.length);
} else {
previousRuns = notebookHistories;
}
if (self._agentViewComponent.expandedNotebook.has(notebook.jobId)) {
let lastJobHistory = notebookHistories[notebookHistories.length - 1];
let item = self.dataView.getItemById('notebook' + notebook.jobId + '.error');
let noStepsMessage = nls.localize('notebooksView.noSteps', "No Steps available for this job.");
let errorMessage = lastJobHistory ? lastJobHistory.message : noStepsMessage;
if (item) {
if (notebook.lastRunNotebookError.length === 0) {
item['name'] = nls.localize('notebooksView.error', "Error: ") + errorMessage;
}
else {
item['name'] = nls.localize('notebooksView.notebookError', "Notebook Error: ") + notebook.lastRunNotebookError;
}
self._agentViewComponent.setExpandedNotebook(notebook.jobId, item['name']);
self.dataView.updateItem('notebook' + notebook.jobId + '.error', item);
}
}
self.createJobChart('notebook' + notebook.jobId, previousRuns);
}
}
}
private createJobChart(jobId: string, jobHistories: azdata.AgentNotebookHistoryInfo[]): void {
let chartHeights = this.getChartHeights(jobHistories);
let runCharts = [];
for (let i = 0; i < chartHeights.length; i++) {
let bgColor = jobHistories[i].runStatus === 0 ? 'red' : 'green';
if (jobHistories[i].materializedNotebookErrorInfo !== null && jobHistories[i].materializedNotebookErrorInfo.length > 0) {
bgColor = 'orange';
}
let runGraph = jQuery(`table.jobprevruns#${jobId} > tbody > tr > td > div.bar${i}`);
if (runGraph.length > 0) {
runGraph.css('height', chartHeights[i]);
runGraph.css('background', bgColor);
runGraph.hover((e) => {
let currentTarget = e.currentTarget;
currentTarget.title = jobHistories[i].runDuration;
});
runCharts.push(runGraph.get(0).outerHTML);
}
}
if (runCharts.length > 0) {
this._notebookCacheObject.setRunChart(jobId, runCharts);
}
this._cd.detectChanges();
}
// chart height normalization logic
private getChartHeights(jobHistories: azdata.AgentJobHistoryInfo[]): string[] {
if (!jobHistories || jobHistories.length === 0) {
return [];
}
let maxDuration: number = 0;
jobHistories.forEach(history => {
let historyDuration = JobManagementUtilities.convertDurationToSeconds(history.runDuration);
if (historyDuration > maxDuration) {
maxDuration = historyDuration;
}
});
maxDuration = maxDuration === 0 ? 1 : maxDuration;
let maxBarHeight: number = 24;
let chartHeights = [];
let zeroDurationJobCount = 0;
for (let i = 0; i < jobHistories.length; i++) {
let duration = jobHistories[i].runDuration;
let chartHeight = (maxBarHeight * JobManagementUtilities.convertDurationToSeconds(duration)) / maxDuration;
chartHeights.push(`${chartHeight}px`);
if (chartHeight === 0) {
zeroDurationJobCount++;
}
}
// if the durations are all 0 secs, show minimal chart
// instead of nothing
if (zeroDurationJobCount === jobHistories.length) {
return Array(jobHistories.length).fill('5px');
} else {
return chartHeights;
}
}
private expandJobs(start: boolean): void {
if (start) {
this._agentViewComponent.expandedNotebook = new Map<string, string>();
}
let expandedJobs = this._agentViewComponent.expandedNotebook;
let expansions = 0;
for (let i = 0; i < this.notebooks.length; i++) {
let notebook = this.notebooks[i];
if (notebook.lastRunOutcome === 0 && !expandedJobs.get(notebook.jobId)) {
this.expandJobRowDetails(i + expandedJobs.size);
this.addToStyleHash(i + expandedJobs.size, start, this.filterStylingMap, undefined);
this._agentViewComponent.setExpandedNotebook(notebook.jobId, 'Loading Error...');
} else if (notebook.lastRunOutcome === 0 && expandedJobs.get(notebook.jobId)) {
this.addToStyleHash(i + expansions, start, this.filterStylingMap, undefined);
expansions++;
} else if (notebook.lastRunNotebookError !== '' && !expandedJobs.get(notebook.jobId)) {
this.expandJobRowDetails(i + expandedJobs.size);
this.addToErrorStyleHash(i + expandedJobs.size, start, this.filterStylingMap, undefined);
this._agentViewComponent.setExpandedNotebook(notebook.jobId, notebook.lastRunNotebookError);
} else if (notebook.lastRunNotebookError !== '' && expandedJobs.get(notebook.jobId)) {
this.addToErrorStyleHash(i + expansions, start, this.filterStylingMap, undefined);
expansions++;
}
}
}
private filter(item: any) {
let columns = this._table.grid.getColumns();
let value = true;
for (let i = 0; i < columns.length; i++) {
let col: any = columns[i];
let filterValues = col.filterValues;
if (filterValues && filterValues.length > 0) {
if (item._parent) {
value = value && _.contains(filterValues, item._parent[col.field]);
} else {
value = value && _.contains(filterValues, item[col.field]);
}
}
}
return value;
}
private columnSort(column: string, isAscending: boolean) {
let items = this.dataView.getItems();
// get error items here and remove them
let jobItems = items.filter(x => x._parent === undefined);
let errorItems = items.filter(x => x._parent !== undefined);
this.sortingStylingMap[column] = items;
switch (column) {
case ('Name'): {
this.dataView.setItems(jobItems);
// sort the actual jobs
this.dataView.sort((item1, item2) => {
return item1.name.localeCompare(item2.name);
}, isAscending);
break;
}
case ('Last Run'): {
this.dataView.setItems(jobItems);
// sort the actual jobs
this.dataView.sort((item1, item2) => this.dateCompare(item1, item2, true), isAscending);
break;
}
case ('Next Run'): {
this.dataView.setItems(jobItems);
// sort the actual jobs
this.dataView.sort((item1, item2) => this.dateCompare(item1, item2, false), isAscending);
break;
}
case ('Status'): {
this.dataView.setItems(jobItems);
// sort the actual jobs
this.dataView.sort((item1, item2) => {
return item1.currentExecutionStatus.localeCompare(item2.currentExecutionStatus);
}, isAscending);
break;
}
case ('Last Run Outcome'): {
this.dataView.setItems(jobItems);
// sort the actual jobs
this.dataView.sort((item1, item2) => {
return item1.lastRunOutcome.localeCompare(item2.lastRunOutcome);
}, isAscending);
break;
}
}
// insert the errors back again
let jobItemsLength = jobItems.length;
for (let i = 0; i < jobItemsLength; i++) {
let item = jobItems[i];
if (item._child) {
let child = errorItems.find(error => error === item._child);
jobItems.splice(i + 1, 0, child);
jobItemsLength++;
}
}
this.dataView.setItems(jobItems);
// remove old style
if (this.filterStylingMap[column]) {
let filterLength = this.filterStylingMap[column].length;
for (let i = 0; i < filterLength; i++) {
let lastAppliedStyle = this.filterStylingMap[column].pop();
this._table.grid.removeCellCssStyles(lastAppliedStyle[0]);
}
} else {
for (let i = 0; i < this.notebooks.length; i++) {
this._table.grid.removeCellCssStyles('error-row' + i.toString());
this._table.grid.removeCellCssStyles('notebook-error-row' + i.toString());
}
}
// add new style to the items back again
items = this.filterStack.length > 1 ? this.dataView.getFilteredItems() : this.dataView.getItems();
for (let i = 0; i < items.length; i++) {
let item = items[i];
if (item.lastRunOutcome === 'Failed') {
this.addToStyleHash(i, false, this.sortingStylingMap, column);
}
}
}
private dateCompare(item1: any, item2: any, lastRun: boolean): number {
let exceptionString = lastRun ? 'Never Run' : 'Not Scheduled';
if (item2.lastRun === exceptionString && item1.lastRun !== exceptionString) {
return -1;
} else if (item1.lastRun === exceptionString && item2.lastRun !== exceptionString) {
return 1;
} else if (item1.lastRun === exceptionString && item2.lastRun === exceptionString) {
return 0;
} else {
let date1 = new Date(item1.lastRun);
let date2 = new Date(item2.lastRun);
if (date1 > date2) {
return 1;
} else if (date1 === date2) {
return 0;
} else {
return -1;
}
}
}
private updateTheme(theme: IColorTheme) {
let bgColor = theme.getColor(tableBackground);
let cellColor = theme.getColor(cellBackground);
let borderColor = theme.getColor(cellBorderColor);
let headerColumns = jQuery('#agentViewDiv .slick-header-column');
let cells = jQuery('.grid-canvas .ui-widget-content.slick-row .slick-cell');
let cellDetails = jQuery('#notebooksDiv .dynamic-cell-detail');
headerColumns.toArray().forEach(col => {
col.style.background = bgColor.toString();
});
cells.toArray().forEach(cell => {
cell.style.background = bgColor.toString();
cell.style.border = borderColor ? '1px solid ' + borderColor.toString() : null;
});
cellDetails.toArray().forEach(cellDetail => {
cellDetail.style.background = cellColor.toString();
});
}
protected getTableActions(targetObject: JobActionContext): IAction[] {
const editAction = this._instantiationService.createInstance(EditJobAction);
const editNotebookAction = this._instantiationService.createInstance(EditNotebookJobAction);
const runJobAction = this._instantiationService.createInstance(RunJobAction);
return [
runJobAction,
editNotebookAction,
editAction,
this._instantiationService.createInstance(DeleteNotebookAction)
];
}
protected convertStepsToStepInfos(steps: azdata.AgentJobStep[], job: azdata.AgentJobInfo): azdata.AgentJobStepInfo[] {
let result = [];
steps.forEach(step => {
let stepInfo: azdata.AgentJobStepInfo = {
jobId: job.jobId,
jobName: job.name,
script: null,
scriptName: null,
stepName: step.stepName,
subSystem: null,
id: +step.stepId,
failureAction: null,
successAction: null,
failStepId: null,
successStepId: null,
command: null,
commandExecutionSuccessCode: null,
databaseName: null,
databaseUserName: null,
server: null,
outputFileName: null,
appendToLogFile: null,
appendToStepHist: null,
writeLogToTable: null,
appendLogToTable: null,
retryAttempts: null,
retryInterval: null,
proxyName: null
};
result.push(stepInfo);
});
return result;
}
protected getCurrentTableObject(rowIndex: number): JobActionContext {
let data = this._table.grid.getData() as Slick.DataProvider<IItem>;
if (!data || rowIndex >= data.getLength()) {
return undefined;
}
let notebookID = data.getItem(rowIndex).notebookId;
if (!notebookID) {
// if we couldn't find the ID, check if it's an
// error row
let isErrorRow: boolean = data.getItem(rowIndex).id.indexOf('error') >= 0;
if (isErrorRow) {
notebookID = data.getItem(rowIndex - 1).notebookId;
}
}
let notebook: azdata.AgentNotebookInfo[] = this.notebooks.filter(job => {
return job.jobId === notebookID;
});
if (notebook && notebook.length > 0) {
// add steps
if (this.jobSteps && this.jobSteps[notebookID]) {
let steps = this.jobSteps[notebookID];
notebook[0].jobSteps = steps;
}
// add schedules
if (this.jobSchedules && this.jobSchedules[notebookID]) {
let schedules = this.jobSchedules[notebookID];
notebook[0].jobSchedules = schedules;
}
// add alerts
if (this.jobAlerts && this.jobAlerts[notebookID]) {
let alerts = this.jobAlerts[notebookID];
notebook[0].alerts = alerts;
}
if (notebook[0].jobSteps && notebook[0].jobSchedules && notebook[0].alerts) {
return { job: notebook[0], canEdit: true };
}
return { job: notebook[0], canEdit: false };
}
return undefined;
}
public async openCreateJobDialog() {
let ownerUri: string = this._commonService.connectionManagementService.connectionInfo.ownerUri;
await this._commandService.executeCommand('agent.openJobDialog', ownerUri);
}
public async openCreateNotebookDialog() {
let ownerUri: string = this._commonService.connectionManagementService.connectionInfo.ownerUri;
await this._commandService.executeCommand('agent.openNotebookDialog', ownerUri);
}
}