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.
@@ -1,62 +1,93 @@
|
||||
{
|
||||
"name": "agent",
|
||||
"displayName": "SQL Server Agent",
|
||||
"description": "Manage and troubleshoot SQL Server Agent jobs",
|
||||
"version": "0.42.0",
|
||||
"publisher": "Microsoft",
|
||||
"preview": true,
|
||||
"license": "https://raw.githubusercontent.com/Microsoft/azuredatastudio/master/LICENSE.txt",
|
||||
"icon": "images/sqlserver.png",
|
||||
"aiKey": "AIF-5574968e-856d-40d2-af67-c89a14e76412",
|
||||
"engines": {
|
||||
"vscode": "^1.25.0"
|
||||
},
|
||||
"activationEvents": [
|
||||
"*"
|
||||
],
|
||||
"main": "./out/main",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Microsoft/azuredatastudio.git"
|
||||
},
|
||||
"extensionDependencies": [
|
||||
"Microsoft.mssql"
|
||||
],
|
||||
"contributes": {
|
||||
"outputChannels": [
|
||||
"sqlagent"
|
||||
],
|
||||
"dashboard.tabs": [
|
||||
{
|
||||
"id": "data-management-agent",
|
||||
"description": "Manage and troubleshoot SQL Agent jobs",
|
||||
"provider": "MSSQL",
|
||||
"title": "SQL Agent",
|
||||
"when": "connectionProvider == 'MSSQL' && !mssql:iscloud",
|
||||
"container": {
|
||||
"controlhost-container": {
|
||||
"type": "agent"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"vscode-nls": "^3.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"mocha-junit-reporter": "^1.17.0",
|
||||
"mocha-multi-reporters": "^1.1.7",
|
||||
"@types/mocha": "^5.2.5",
|
||||
"@types/node": "^8.10.25",
|
||||
"mocha": "^5.2.0",
|
||||
"should": "^13.2.1",
|
||||
"typemoq": "^2.1.0",
|
||||
"vscode": "1.1.5"
|
||||
},
|
||||
"__metadata": {
|
||||
"id": "10",
|
||||
"publisherDisplayName": "Microsoft",
|
||||
"publisherId": "Microsoft"
|
||||
}
|
||||
"name": "agent",
|
||||
"displayName": "SQL Server Agent",
|
||||
"description": "Manage and troubleshoot SQL Server Agent jobs",
|
||||
"version": "0.41.0",
|
||||
"publisher": "Microsoft",
|
||||
"preview": true,
|
||||
"license": "https://raw.githubusercontent.com/Microsoft/azuredatastudio/master/LICENSE.txt",
|
||||
"icon": "images/sqlserver.png",
|
||||
"aiKey": "AIF-5574968e-856d-40d2-af67-c89a14e76412",
|
||||
"engines": {
|
||||
"vscode": "^1.25.0"
|
||||
},
|
||||
"activationEvents": [
|
||||
"*"
|
||||
],
|
||||
"main": "./out/main",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Microsoft/azuredatastudio.git"
|
||||
},
|
||||
"extensionDependencies": [
|
||||
"Microsoft.mssql"
|
||||
],
|
||||
"contributes": {
|
||||
"outputChannels": [
|
||||
"sqlagent"
|
||||
],
|
||||
"dashboard.tabs": [
|
||||
{
|
||||
"id": "data-management-agent",
|
||||
"description": "Manage and troubleshoot SQL Agent jobs",
|
||||
"provider": "MSSQL",
|
||||
"title": "SQL Agent",
|
||||
"when": "connectionProvider == 'MSSQL' && !mssql:iscloud",
|
||||
"container": {
|
||||
"controlhost-container": {
|
||||
"type": "agent"
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"commands": [
|
||||
{
|
||||
"command": "agent.openNotebookDialog",
|
||||
"title": "Schedule Notebook",
|
||||
"icon": {
|
||||
"dark": "resources/dark/open_notebook_inverse.svg",
|
||||
"light": "resources/light/open_notebook.svg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"command": "agent.reuploadTemplate",
|
||||
"title": "Reupload Template",
|
||||
"icon": {
|
||||
"dark": "resources/dark/open_notebook_inverse.svg",
|
||||
"light": "resources/light/open_notebook.svg"
|
||||
}
|
||||
}
|
||||
],
|
||||
"menus": {
|
||||
"notebook/toolbar": [
|
||||
{
|
||||
"command": "agent.openNotebookDialog",
|
||||
"when": "providerId == sql"
|
||||
},
|
||||
{
|
||||
"command": "agent.reuploadTemplate",
|
||||
"when": "agent:trackedTemplate"
|
||||
}
|
||||
],
|
||||
"explorer/context": [
|
||||
{
|
||||
"command": "agent.openNotebookDialog",
|
||||
"when": "resourceExtname == .ipynb"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"vscode-nls": "^3.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"mocha-junit-reporter": "^1.17.0",
|
||||
"mocha-multi-reporters": "^1.1.7",
|
||||
"@types/mocha": "^5.2.5",
|
||||
"@types/node": "^8.10.25",
|
||||
"mocha": "^5.2.0",
|
||||
"should": "^13.2.1",
|
||||
"typemoq": "^2.1.0",
|
||||
"vscode": "1.1.5"
|
||||
}
|
||||
}
|
||||
|
||||
1
extensions/agent/resources/dark/notebook_inverse.svg
Normal 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 |
@@ -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 |
1
extensions/agent/resources/light/notebook.svg
Normal 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 |
1
extensions/agent/resources/light/open_notebook.svg
Normal 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 |
@@ -6,6 +6,9 @@
|
||||
'use strict';
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import * as fs from 'fs';
|
||||
import { promisify } from 'util';
|
||||
|
||||
|
||||
export class AgentUtils {
|
||||
|
||||
@@ -13,6 +16,12 @@ export class AgentUtils {
|
||||
private static _connectionService: azdata.ConnectionProvider;
|
||||
private static _queryProvider: azdata.QueryProvider;
|
||||
|
||||
public static async setupProvidersFromConnection(connection?: azdata.connection.Connection) {
|
||||
this._agentService = azdata.dataprotocol.getProvider<azdata.AgentServicesProvider>(connection.providerName, azdata.DataProviderType.AgentServicesProvider);
|
||||
this._connectionService = azdata.dataprotocol.getProvider<azdata.ConnectionProvider>(connection.providerName, azdata.DataProviderType.ConnectionProvider);
|
||||
this._queryProvider = azdata.dataprotocol.getProvider<azdata.QueryProvider>(connection.providerName, azdata.DataProviderType.QueryProvider);
|
||||
}
|
||||
|
||||
public static async getAgentService(): Promise<azdata.AgentServicesProvider> {
|
||||
if (!AgentUtils._agentService) {
|
||||
let currentConnection = await azdata.connection.getCurrentConnection();
|
||||
@@ -41,4 +50,20 @@ export class AgentUtils {
|
||||
return this._queryProvider;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
export function exists(path: string): Promise<boolean> {
|
||||
return promisify(fs.exists)(path);
|
||||
}
|
||||
|
||||
export function mkdir(path: string): Promise<void> {
|
||||
return promisify(fs.mkdir)(path);
|
||||
}
|
||||
|
||||
export function unlink(path: string): Promise<void> {
|
||||
return promisify(fs.unlink)(path);
|
||||
}
|
||||
|
||||
export function writeFile(path: string, data: string): Promise<void> {
|
||||
return promisify(fs.writeFile)(path, data);
|
||||
}
|
||||
|
||||
260
extensions/agent/src/data/notebookData.ts
Normal file
@@ -0,0 +1,260 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import * as nls from 'vscode-nls';
|
||||
import * as azdata from 'azdata';
|
||||
import * as vscode from 'vscode';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { AgentUtils } from '../agentUtils';
|
||||
import { IAgentDialogData, AgentDialogMode } from '../interfaces';
|
||||
import { NotebookDialogOptions } from '../dialogs/notebookDialog';
|
||||
import { createConnection } from 'net';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
const NotebookCompletionActionCondition_Always: string = localize('notebookData.whenJobCompletes', 'When the notebook completes');
|
||||
const NotebookCompletionActionCondition_OnFailure: string = localize('notebookData.whenJobFails', 'When the notebook fails');
|
||||
const NotebookCompletionActionCondition_OnSuccess: string = localize('notebookData.whenJobSucceeds', 'When the notebook succeeds');
|
||||
|
||||
// Error Messages
|
||||
const CreateNotebookErrorMessage_NameIsEmpty = localize('notebookData.jobNameRequired', 'Notebook name must be provided');
|
||||
const TemplatePathEmptyErrorMessage = localize('notebookData.templatePathRequired', 'Template path must be provided');
|
||||
const InvalidNotebookPathErrorMessage = localize('notebookData.invalidNotebookPath', 'Invalid notebook path');
|
||||
const SelectStorageDatabaseErrorMessage = localize('notebookData.selectStorageDatabase', 'Select storage database');
|
||||
const SelectExecutionDatabaseErrorMessage = localize('notebookData.selectExecutionDatabase', 'Select execution database');
|
||||
const JobWithSameNameExistsErrorMessage = localize('notebookData.jobExists', 'Job with similar name already exists');
|
||||
|
||||
export class NotebookData implements IAgentDialogData {
|
||||
|
||||
private _ownerUri: string;
|
||||
private _jobCategories: string[];
|
||||
private _operators: string[];
|
||||
private _defaultOwner: string;
|
||||
private _jobCompletionActionConditions: azdata.CategoryValue[];
|
||||
private _jobCategoryIdsMap: azdata.AgentJobCategory[];
|
||||
|
||||
public dialogMode: AgentDialogMode = AgentDialogMode.CREATE;
|
||||
public name: string;
|
||||
public originalName: string;
|
||||
public enabled: boolean = true;
|
||||
public description: string;
|
||||
public category: string;
|
||||
public categoryId: number;
|
||||
public owner: string;
|
||||
public emailLevel: azdata.JobCompletionActionCondition = azdata.JobCompletionActionCondition.OnFailure;
|
||||
public pageLevel: azdata.JobCompletionActionCondition = azdata.JobCompletionActionCondition.OnFailure;
|
||||
public eventLogLevel: azdata.JobCompletionActionCondition = azdata.JobCompletionActionCondition.OnFailure;
|
||||
public deleteLevel: azdata.JobCompletionActionCondition = azdata.JobCompletionActionCondition.OnSuccess;
|
||||
public operatorToEmail: string;
|
||||
public operatorToPage: string;
|
||||
public jobSteps: azdata.AgentJobStepInfo[];
|
||||
public jobSchedules: azdata.AgentJobScheduleInfo[];
|
||||
public alerts: azdata.AgentAlertInfo[];
|
||||
public jobId: string;
|
||||
public startStepId: number;
|
||||
public categoryType: number;
|
||||
public targetDatabase: string;
|
||||
public executeDatabase: string;
|
||||
public templateId: number;
|
||||
public templatePath: string;
|
||||
public static jobLists: azdata.AgentJobInfo[];
|
||||
public connection: azdata.connection.Connection;
|
||||
|
||||
constructor(
|
||||
ownerUri: string,
|
||||
options: NotebookDialogOptions = undefined,
|
||||
private _agentService: azdata.AgentServicesProvider = undefined) {
|
||||
this._ownerUri = ownerUri;
|
||||
this.enabled = true;
|
||||
if (options.notebookInfo) {
|
||||
let notebookInfo = options.notebookInfo;
|
||||
this.dialogMode = AgentDialogMode.EDIT;
|
||||
this.name = notebookInfo.name;
|
||||
this.originalName = notebookInfo.name;
|
||||
this.owner = notebookInfo.owner;
|
||||
this.category = notebookInfo.category;
|
||||
this.description = notebookInfo.description;
|
||||
this.enabled = notebookInfo.enabled;
|
||||
this.jobSteps = notebookInfo.jobSteps;
|
||||
this.jobSchedules = notebookInfo.jobSchedules;
|
||||
this.alerts = notebookInfo.alerts;
|
||||
this.jobId = notebookInfo.jobId;
|
||||
this.startStepId = notebookInfo.startStepId;
|
||||
this.categoryId = notebookInfo.categoryId;
|
||||
this.categoryType = notebookInfo.categoryType;
|
||||
this.targetDatabase = notebookInfo.targetDatabase;
|
||||
this.executeDatabase = notebookInfo.executeDatabase;
|
||||
}
|
||||
if (options.filePath) {
|
||||
this.name = path.basename(options.filePath).split('.').slice(0, -1).join('.');
|
||||
this.templatePath = options.filePath;
|
||||
}
|
||||
if (options.connection) {
|
||||
this.connection = options.connection;
|
||||
}
|
||||
}
|
||||
|
||||
public get jobCategories(): string[] {
|
||||
return this._jobCategories;
|
||||
}
|
||||
|
||||
public get jobCategoryIdsMap(): azdata.AgentJobCategory[] {
|
||||
return this._jobCategoryIdsMap;
|
||||
}
|
||||
|
||||
public get operators(): string[] {
|
||||
return this._operators;
|
||||
}
|
||||
|
||||
public get ownerUri(): string {
|
||||
return this._ownerUri;
|
||||
}
|
||||
|
||||
public get defaultOwner(): string {
|
||||
return this._defaultOwner;
|
||||
}
|
||||
|
||||
public get JobCompletionActionConditions(): azdata.CategoryValue[] {
|
||||
return this._jobCompletionActionConditions;
|
||||
}
|
||||
|
||||
public async initialize() {
|
||||
if (this.connection) {
|
||||
await AgentUtils.setupProvidersFromConnection(this.connection);
|
||||
}
|
||||
this._agentService = await AgentUtils.getAgentService();
|
||||
let jobDefaults = await this._agentService.getJobDefaults(this.ownerUri);
|
||||
if (jobDefaults && jobDefaults.success) {
|
||||
this._jobCategories = jobDefaults.categories.map((cat) => {
|
||||
return cat.name;
|
||||
});
|
||||
this._jobCategoryIdsMap = jobDefaults.categories;
|
||||
this._defaultOwner = jobDefaults.owner;
|
||||
|
||||
this._operators = ['', this._defaultOwner];
|
||||
this.owner = this.owner ? this.owner : this._defaultOwner;
|
||||
}
|
||||
|
||||
this._jobCompletionActionConditions = [{
|
||||
displayName: NotebookCompletionActionCondition_OnSuccess,
|
||||
name: azdata.JobCompletionActionCondition.OnSuccess.toString()
|
||||
}, {
|
||||
displayName: NotebookCompletionActionCondition_OnFailure,
|
||||
name: azdata.JobCompletionActionCondition.OnFailure.toString()
|
||||
}, {
|
||||
displayName: NotebookCompletionActionCondition_Always,
|
||||
name: azdata.JobCompletionActionCondition.Always.toString()
|
||||
}];
|
||||
|
||||
this._agentService.getJobs(this.ownerUri).then((value) => {
|
||||
NotebookData.jobLists = value.jobs;
|
||||
});
|
||||
}
|
||||
|
||||
public async save() {
|
||||
let notebookInfo: azdata.AgentNotebookInfo = this.toAgentJobInfo();
|
||||
let result = this.dialogMode === AgentDialogMode.CREATE
|
||||
? await this._agentService.createNotebook(this.ownerUri, notebookInfo, this.templatePath)
|
||||
: await this._agentService.updateNotebook(this.ownerUri, this.originalName, notebookInfo, this.templatePath);
|
||||
if (!result || !result.success) {
|
||||
if (this.dialogMode === AgentDialogMode.EDIT) {
|
||||
vscode.window.showErrorMessage(
|
||||
localize('notebookData.saveErrorMessage', "Notebook update failed '{0}'", result.errorMessage ? result.errorMessage : 'Unknown'));
|
||||
} else {
|
||||
vscode.window.showErrorMessage(
|
||||
localize('notebookData.newJobErrorMessage', "Notebook creation failed '{0}'", result.errorMessage ? result.errorMessage : 'Unknown'));
|
||||
}
|
||||
} else {
|
||||
if (this.dialogMode === AgentDialogMode.EDIT) {
|
||||
vscode.window.showInformationMessage(
|
||||
localize('notebookData.saveSucessMessage', "Notebook '{0}' updated successfully", notebookInfo.name));
|
||||
} else {
|
||||
vscode.window.showInformationMessage(
|
||||
localize('notebookData.newJobSuccessMessage', "Notebook '{0}' created successfully", notebookInfo.name));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public validate(): { valid: boolean, errorMessages: string[] } {
|
||||
let validationErrors: string[] = [];
|
||||
if (this.dialogMode !== AgentDialogMode.EDIT) {
|
||||
if (!(this.name && this.name.trim())) {
|
||||
validationErrors.push(CreateNotebookErrorMessage_NameIsEmpty);
|
||||
}
|
||||
if (!(this.templatePath && this.name.trim())) {
|
||||
validationErrors.push(TemplatePathEmptyErrorMessage);
|
||||
}
|
||||
if (!fs.existsSync(this.templatePath)) {
|
||||
validationErrors.push(InvalidNotebookPathErrorMessage);
|
||||
}
|
||||
if (NotebookData.jobLists) {
|
||||
for (let i = 0; i < NotebookData.jobLists.length; i++) {
|
||||
if (this.name === NotebookData.jobLists[i].name) {
|
||||
validationErrors.push(JobWithSameNameExistsErrorMessage);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (this.templatePath && this.templatePath !== '' && !fs.existsSync(this.templatePath)) {
|
||||
validationErrors.push(InvalidNotebookPathErrorMessage);
|
||||
}
|
||||
}
|
||||
if (this.targetDatabase === 'Select Database') {
|
||||
validationErrors.push(SelectStorageDatabaseErrorMessage);
|
||||
}
|
||||
if (this.executeDatabase === 'Select Database') {
|
||||
validationErrors.push(SelectExecutionDatabaseErrorMessage);
|
||||
}
|
||||
|
||||
return {
|
||||
valid: validationErrors.length === 0,
|
||||
errorMessages: validationErrors
|
||||
};
|
||||
}
|
||||
|
||||
public toAgentJobInfo(): azdata.AgentNotebookInfo {
|
||||
return {
|
||||
name: this.name,
|
||||
owner: this.owner ? this.owner : this.defaultOwner,
|
||||
description: this.description,
|
||||
emailLevel: this.emailLevel,
|
||||
pageLevel: this.pageLevel,
|
||||
eventLogLevel: this.eventLogLevel,
|
||||
deleteLevel: this.deleteLevel,
|
||||
operatorToEmail: this.operatorToEmail,
|
||||
operatorToPage: this.operatorToPage,
|
||||
enabled: this.enabled,
|
||||
category: this.category,
|
||||
alerts: this.alerts,
|
||||
jobSchedules: this.jobSchedules,
|
||||
jobSteps: this.jobSteps,
|
||||
targetDatabase: this.targetDatabase,
|
||||
executeDatabase: this.executeDatabase,
|
||||
// The properties below are not collected from UI
|
||||
// We could consider using a seperate class for create job request
|
||||
//
|
||||
templateId: this.templateId,
|
||||
currentExecutionStatus: 0,
|
||||
lastRunOutcome: 0,
|
||||
currentExecutionStep: '',
|
||||
hasTarget: true,
|
||||
hasSchedule: false,
|
||||
hasStep: false,
|
||||
runnable: true,
|
||||
categoryId: this.categoryId,
|
||||
categoryType: this.categoryType,
|
||||
lastRun: '',
|
||||
nextRun: '',
|
||||
jobId: this.jobId,
|
||||
startStepId: this.startStepId,
|
||||
lastRunNotebookError: '',
|
||||
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -169,7 +169,7 @@ export class JobStepDialog extends AgentDialog<JobStepData> {
|
||||
isFile: false
|
||||
}).component();
|
||||
this.openButton.onDidClick(e => {
|
||||
let queryContent = e;
|
||||
let queryContent = e.fileContent;
|
||||
this.commandTextBox.value = queryContent;
|
||||
});
|
||||
this.parseButton.onDidClick(e => {
|
||||
|
||||
340
extensions/agent/src/dialogs/notebookDialog.ts
Normal file
@@ -0,0 +1,340 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
import * as nls from 'vscode-nls';
|
||||
import * as path from 'path';
|
||||
import * as azdata from 'azdata';
|
||||
import { PickScheduleDialog } from './pickScheduleDialog';
|
||||
import { AgentDialog } from './agentDialog';
|
||||
import { AgentUtils } from '../agentUtils';
|
||||
import { NotebookData } from '../data/notebookData';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
// TODO: localize
|
||||
// Top level
|
||||
const CreateDialogTitle: string = localize('notebookDialog.newJob', "New Notebook Job");
|
||||
const EditDialogTitle: string = localize('notebookDialog.editJob', "Edit Notebook Job");
|
||||
const GeneralTabText: string = localize('notebookDialog.general', "General");
|
||||
const BlankJobNameErrorText: string = localize('notebookDialog.blankJobNameError', "The name of the job cannot be blank.");
|
||||
|
||||
// Notebook details strings
|
||||
const NotebookDetailsSeparatorTitle: string = localize('notebookDialog.notebookSection', "Notebook Details");
|
||||
const TemplateNotebookTextBoxLabel: string = localize('notebookDialog.templateNotebook', "Notebook Path");
|
||||
const TargetDatabaseDropdownLabel: string = localize('notebookDialog.targetDatabase', "Storage Database");
|
||||
const ExecuteDatabaseDropdownLabel: string = localize('notebookDialog.executeDatabase', "Execution Database");
|
||||
const DefaultDropdownString: string = localize('notebookDialog.defaultDropdownString', "Select Database");
|
||||
|
||||
// Job details string
|
||||
const JobDetailsSeparatorTitle: string = localize('notebookDialog.jobSection', "Job Details");
|
||||
const NameTextBoxLabel: string = localize('notebookDialog.name', "Name");
|
||||
const OwnerTextBoxLabel: string = localize('notebookDialog.owner', "Owner");
|
||||
const SchedulesTopLabelString: string = localize('notebookDialog.schedulesaLabel', "Schedules list");
|
||||
const PickScheduleButtonString: string = localize('notebookDialog.pickSchedule', "Pick Schedule");
|
||||
const RemoveScheduleButtonString: string = localize('notebookDialog.removeSchedule', "Remove Schedule");
|
||||
const ScheduleNameLabelString: string = localize('notebookDialog.scheduleNameLabel', "Schedule Name");
|
||||
const DescriptionTextBoxLabel: string = localize('notebookDialog.description', "Description");
|
||||
|
||||
// Event Name strings
|
||||
const NewJobDialogEvent: string = 'NewNotebookJobDialogOpened';
|
||||
const EditJobDialogEvent: string = 'EditNotebookJobDialogOpened';
|
||||
|
||||
export class NotebookDialogOptions {
|
||||
notebookInfo?: azdata.AgentNotebookInfo;
|
||||
filePath?: string;
|
||||
connection?: azdata.connection.Connection;
|
||||
}
|
||||
|
||||
export class NotebookDialog extends AgentDialog<NotebookData> {
|
||||
|
||||
// UI Components
|
||||
private generalTab: azdata.window.DialogTab;
|
||||
|
||||
// Notebook Details controls
|
||||
private templateFilePathBox: azdata.InputBoxComponent;
|
||||
private openTemplateFileButton: azdata.ButtonComponent;
|
||||
private targetDatabaseDropDown: azdata.DropDownComponent;
|
||||
private executeDatabaseDropDown: azdata.DropDownComponent;
|
||||
|
||||
// Job Details controls
|
||||
|
||||
private nameTextBox: azdata.InputBoxComponent;
|
||||
private ownerTextBox: azdata.InputBoxComponent;
|
||||
private schedulesTable: azdata.TableComponent;
|
||||
private pickScheduleButton: azdata.ButtonComponent;
|
||||
private removeScheduleButton: azdata.ButtonComponent;
|
||||
private descriptionTextBox: azdata.InputBoxComponent;
|
||||
|
||||
|
||||
|
||||
private isEdit: boolean = false;
|
||||
|
||||
// Job objects
|
||||
private steps: azdata.AgentJobStepInfo[];
|
||||
private schedules: azdata.AgentJobScheduleInfo[];
|
||||
|
||||
constructor(ownerUri: string, options: NotebookDialogOptions = undefined) {
|
||||
super(
|
||||
ownerUri,
|
||||
new NotebookData(ownerUri, options),
|
||||
options.notebookInfo ? EditDialogTitle : CreateDialogTitle);
|
||||
this.steps = this.model.jobSteps ? this.model.jobSteps : [];
|
||||
this.schedules = this.model.jobSchedules ? this.model.jobSchedules : [];
|
||||
this.isEdit = options.notebookInfo ? true : false;
|
||||
this.dialogName = this.isEdit ? EditJobDialogEvent : NewJobDialogEvent;
|
||||
}
|
||||
|
||||
protected async initializeDialog() {
|
||||
this.generalTab = azdata.window.createTab(GeneralTabText);
|
||||
this.initializeGeneralTab();
|
||||
this.dialog.content = [this.generalTab];
|
||||
this.dialog.registerCloseValidator(() => {
|
||||
this.updateModel();
|
||||
let validationResult = this.model.validate();
|
||||
if (!validationResult.valid) {
|
||||
// TODO: Show Error Messages
|
||||
this.dialog.message = { text: validationResult.errorMessages[0] };
|
||||
console.error(validationResult.errorMessages.join(','));
|
||||
}
|
||||
|
||||
return validationResult.valid;
|
||||
});
|
||||
}
|
||||
|
||||
private initializeGeneralTab() {
|
||||
this.generalTab.registerContent(async view => {
|
||||
this.templateFilePathBox = view.modelBuilder.inputBox()
|
||||
.withProperties({
|
||||
width: 400,
|
||||
inputType: 'text'
|
||||
}).component();
|
||||
this.openTemplateFileButton = view.modelBuilder.button()
|
||||
.withProperties({
|
||||
label: '...',
|
||||
title: '...',
|
||||
width: '20px',
|
||||
isFile: true,
|
||||
fileType: '.ipynb'
|
||||
}).component();
|
||||
this.openTemplateFileButton.onDidClick(e => {
|
||||
if (e) {
|
||||
this.templateFilePathBox.value = e.filePath;
|
||||
if (!this.isEdit) {
|
||||
let fileName = path.basename(e.filePath).split('.').slice(0, -1).join('.');
|
||||
this.nameTextBox.value = fileName;
|
||||
}
|
||||
}
|
||||
});
|
||||
let outputButtonContainer = view.modelBuilder.flexContainer()
|
||||
.withLayout({
|
||||
flexFlow: 'row',
|
||||
textAlign: 'right',
|
||||
width: 20
|
||||
}).withItems([this.openTemplateFileButton], { flex: '1 1 80%' }).component();
|
||||
let notebookPathFlexBox = view.modelBuilder.flexContainer()
|
||||
.withLayout({
|
||||
flexFlow: 'row',
|
||||
width: '100%',
|
||||
}).withItems([this.templateFilePathBox, outputButtonContainer], {
|
||||
flex: '1 1 50%'
|
||||
}).component();
|
||||
this.targetDatabaseDropDown = view.modelBuilder.dropDown().component();
|
||||
this.executeDatabaseDropDown = view.modelBuilder.dropDown().component();
|
||||
let databases = await AgentUtils.getDatabases(this.ownerUri);
|
||||
databases.unshift(DefaultDropdownString);
|
||||
this.targetDatabaseDropDown = view.modelBuilder.dropDown()
|
||||
.withProperties({
|
||||
value: databases[0],
|
||||
values: databases
|
||||
}).component();
|
||||
this.descriptionTextBox = view.modelBuilder.inputBox().withProperties({
|
||||
multiline: true,
|
||||
height: 50
|
||||
}).component();
|
||||
this.executeDatabaseDropDown = view.modelBuilder.dropDown()
|
||||
.withProperties({
|
||||
value: databases[0],
|
||||
values: databases
|
||||
}).component();
|
||||
this.targetDatabaseDropDown.required = true;
|
||||
this.executeDatabaseDropDown.required = true;
|
||||
this.descriptionTextBox = view.modelBuilder.inputBox().withProperties({
|
||||
multiline: true,
|
||||
height: 50
|
||||
}).component();
|
||||
this.nameTextBox = view.modelBuilder.inputBox().component();
|
||||
this.nameTextBox.required = true;
|
||||
this.nameTextBox.onTextChanged(() => {
|
||||
if (this.nameTextBox.value && this.nameTextBox.value.length > 0) {
|
||||
this.dialog.message = null;
|
||||
// Change the job name immediately since steps
|
||||
// depends on the job name
|
||||
this.model.name = this.nameTextBox.value;
|
||||
}
|
||||
});
|
||||
|
||||
this.ownerTextBox = view.modelBuilder.inputBox().component();
|
||||
this.schedulesTable = view.modelBuilder.table()
|
||||
.withProperties({
|
||||
columns: [
|
||||
PickScheduleDialog.SchedulesIDText,
|
||||
PickScheduleDialog.ScheduleNameLabelText,
|
||||
PickScheduleDialog.ScheduleDescription
|
||||
],
|
||||
data: [],
|
||||
height: 50,
|
||||
width: 420
|
||||
}).component();
|
||||
|
||||
this.pickScheduleButton = view.modelBuilder.button().withProperties({
|
||||
label: PickScheduleButtonString,
|
||||
width: 100
|
||||
}).component();
|
||||
this.removeScheduleButton = view.modelBuilder.button().withProperties({
|
||||
label: RemoveScheduleButtonString,
|
||||
width: 100
|
||||
}).component();
|
||||
this.pickScheduleButton.onDidClick(() => {
|
||||
let pickScheduleDialog = new PickScheduleDialog(this.model.ownerUri, this.model.name);
|
||||
pickScheduleDialog.onSuccess((dialogModel) => {
|
||||
let selectedSchedule = dialogModel.selectedSchedule;
|
||||
if (selectedSchedule) {
|
||||
let existingSchedule = this.schedules.find(item => item.name === selectedSchedule.name);
|
||||
if (!existingSchedule) {
|
||||
selectedSchedule.jobName = this.model.name ? this.model.name : this.nameTextBox.value;
|
||||
this.schedules.push(selectedSchedule);
|
||||
}
|
||||
this.populateScheduleTable();
|
||||
}
|
||||
});
|
||||
pickScheduleDialog.showDialog();
|
||||
});
|
||||
this.removeScheduleButton.onDidClick(() => {
|
||||
if (this.schedulesTable.selectedRows.length === 1) {
|
||||
let selectedRow = this.schedulesTable.selectedRows[0];
|
||||
let selectedScheduleName = this.schedulesTable.data[selectedRow][1];
|
||||
for (let i = 0; i < this.schedules.length; i++) {
|
||||
if (this.schedules[i].name === selectedScheduleName) {
|
||||
this.schedules.splice(i, 1);
|
||||
}
|
||||
}
|
||||
this.populateScheduleTable();
|
||||
}
|
||||
});
|
||||
|
||||
let formModel = view.modelBuilder.formContainer()
|
||||
.withFormItems([
|
||||
{
|
||||
components: [{
|
||||
component: notebookPathFlexBox,
|
||||
title: TemplateNotebookTextBoxLabel,
|
||||
layout: {
|
||||
info: localize('notebookDialog.templatePath', 'Select a notebook to schedule from PC')
|
||||
}
|
||||
},
|
||||
{
|
||||
component: this.targetDatabaseDropDown,
|
||||
title: TargetDatabaseDropdownLabel,
|
||||
layout: {
|
||||
info: localize('notebookDialog.targetDatabaseInfo', 'Select a database to store all notebook job metadata and results')
|
||||
}
|
||||
}, {
|
||||
component: this.executeDatabaseDropDown,
|
||||
title: ExecuteDatabaseDropdownLabel,
|
||||
layout: {
|
||||
info: localize('notebookDialog.executionDatabaseInfo', 'Select a database against which notebook queries will run')
|
||||
}
|
||||
}],
|
||||
title: NotebookDetailsSeparatorTitle
|
||||
}, {
|
||||
components: [{
|
||||
component: this.nameTextBox,
|
||||
title: NameTextBoxLabel
|
||||
}, {
|
||||
component: this.ownerTextBox,
|
||||
title: OwnerTextBoxLabel
|
||||
}, {
|
||||
component: this.schedulesTable,
|
||||
title: SchedulesTopLabelString,
|
||||
actions: [this.pickScheduleButton, this.removeScheduleButton]
|
||||
}, {
|
||||
component: this.descriptionTextBox,
|
||||
title: DescriptionTextBoxLabel
|
||||
}],
|
||||
title: JobDetailsSeparatorTitle
|
||||
}]).withLayout({ width: '100%' }).component();
|
||||
|
||||
await view.initializeModel(formModel);
|
||||
|
||||
|
||||
|
||||
this.nameTextBox.value = this.model.name;
|
||||
this.ownerTextBox.value = this.model.owner;
|
||||
this.templateFilePathBox.value = this.model.templatePath;
|
||||
if (this.isEdit) {
|
||||
this.templateFilePathBox.placeHolder = this.model.targetDatabase + '\\' + this.model.name;
|
||||
this.targetDatabaseDropDown.value = this.model.targetDatabase;
|
||||
this.executeDatabaseDropDown.value = this.model.executeDatabase;
|
||||
this.targetDatabaseDropDown.enabled = false;
|
||||
this.schedules = this.model.jobSchedules;
|
||||
}
|
||||
else {
|
||||
this.templateFilePathBox.required = true;
|
||||
}
|
||||
let idx: number = undefined;
|
||||
if (this.model.category && this.model.category !== '') {
|
||||
idx = this.model.jobCategories.indexOf(this.model.category);
|
||||
}
|
||||
this.descriptionTextBox.value = this.model.description;
|
||||
this.openTemplateFileButton.onDidClick(e => {
|
||||
});
|
||||
this.populateScheduleTable();
|
||||
});
|
||||
}
|
||||
|
||||
private populateScheduleTable() {
|
||||
let data = this.convertSchedulesToData(this.schedules);
|
||||
this.schedulesTable.data = data;
|
||||
this.schedulesTable.height = 100;
|
||||
|
||||
}
|
||||
|
||||
private createRowContainer(view: azdata.ModelView): azdata.FlexBuilder {
|
||||
return view.modelBuilder.flexContainer().withLayout({
|
||||
flexFlow: 'row',
|
||||
alignItems: 'left',
|
||||
justifyContent: 'space-between'
|
||||
});
|
||||
}
|
||||
private convertSchedulesToData(jobSchedules: azdata.AgentJobScheduleInfo[]): any[][] {
|
||||
let result = [];
|
||||
jobSchedules.forEach(schedule => {
|
||||
let cols = [];
|
||||
cols.push(schedule.id);
|
||||
cols.push(schedule.name);
|
||||
cols.push(schedule.description);
|
||||
result.push(cols);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
protected updateModel() {
|
||||
this.model.name = this.nameTextBox.value;
|
||||
this.model.owner = this.ownerTextBox.value;
|
||||
this.model.description = this.descriptionTextBox.value;
|
||||
this.model.templatePath = this.templateFilePathBox.value;
|
||||
this.model.targetDatabase = this.targetDatabaseDropDown.value as string;
|
||||
this.model.executeDatabase = this.executeDatabaseDropDown.value as string;
|
||||
if (!this.model.jobSchedules) {
|
||||
this.model.jobSchedules = [];
|
||||
}
|
||||
this.model.alerts = [];
|
||||
this.model.jobSteps = [];
|
||||
this.model.jobSchedules = this.schedules;
|
||||
this.model.category = '[Uncategorized (Local)]';
|
||||
this.model.categoryId = 0;
|
||||
this.model.eventLogLevel = 0;
|
||||
|
||||
}
|
||||
}
|
||||
@@ -82,9 +82,22 @@ export class PickScheduleDialog {
|
||||
}]).withLayout({ width: '100%' }).component();
|
||||
|
||||
this.loadingComponent = view.modelBuilder.loadingComponent().withItem(formModel).component();
|
||||
this.loadingComponent.loading = true;
|
||||
this.model.initialize().then((result) => {
|
||||
this.loadingComponent.loading = false;
|
||||
if (this.model.schedules) {
|
||||
let data: any[][] = [];
|
||||
for (let i = 0; i < this.model.schedules.length; ++i) {
|
||||
let schedule = this.model.schedules[i];
|
||||
data[i] = [schedule.id, schedule.name, schedule.description];
|
||||
}
|
||||
this.schedulesTable.data = data;
|
||||
}
|
||||
});
|
||||
this.loadingComponent.loading = !this.model.isInitialized();
|
||||
await view.initializeModel(this.loadingComponent);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private async execute() {
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
import * as nls from 'vscode-nls';
|
||||
import * as azdata from 'azdata';
|
||||
import * as vscode from 'vscode';
|
||||
import * as fs from 'fs';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import { AlertDialog } from './dialogs/alertDialog';
|
||||
import { JobDialog } from './dialogs/jobDialog';
|
||||
import { OperatorDialog } from './dialogs/operatorDialog';
|
||||
@@ -14,13 +17,22 @@ import { ProxyDialog } from './dialogs/proxyDialog';
|
||||
import { JobStepDialog } from './dialogs/jobStepDialog';
|
||||
import { PickScheduleDialog } from './dialogs/pickScheduleDialog';
|
||||
import { JobData } from './data/jobData';
|
||||
import { AgentUtils } from './agentUtils';
|
||||
import { AgentUtils, exists, mkdir, unlink, writeFile } from './agentUtils';
|
||||
import { NotebookDialog, NotebookDialogOptions } from './dialogs/notebookDialog';
|
||||
import { promisify } from 'util';
|
||||
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
/**
|
||||
* The main controller class that initializes the extension
|
||||
*/
|
||||
export class TemplateMapObject {
|
||||
notebookInfo: azdata.AgentNotebookInfo;
|
||||
fileUri: vscode.Uri;
|
||||
tempPath: string;
|
||||
ownerUri: string;
|
||||
}
|
||||
export class MainController {
|
||||
|
||||
protected _context: vscode.ExtensionContext;
|
||||
@@ -29,7 +41,8 @@ export class MainController {
|
||||
private alertDialog: AlertDialog;
|
||||
private operatorDialog: OperatorDialog;
|
||||
private proxyDialog: ProxyDialog;
|
||||
|
||||
private notebookDialog: NotebookDialog;
|
||||
private notebookTemplateMap = new Map<string, TemplateMapObject>();
|
||||
// PUBLIC METHODS //////////////////////////////////////////////////////
|
||||
public constructor(context: vscode.ExtensionContext) {
|
||||
this._context = context;
|
||||
@@ -82,6 +95,26 @@ export class MainController {
|
||||
this.operatorDialog.dialogName ? await this.operatorDialog.openDialog(this.operatorDialog.dialogName) : await this.operatorDialog.openDialog();
|
||||
}
|
||||
});
|
||||
|
||||
vscode.commands.registerCommand('agent.reuploadTemplate', async (ownerUri: string, operatorInfo: azdata.AgentOperatorInfo) => {
|
||||
let nbEditor = azdata.nb.activeNotebookEditor;
|
||||
// await nbEditor.document.save();
|
||||
let templateMap = this.notebookTemplateMap.get(nbEditor.document.uri.toString());
|
||||
let vsEditor = await vscode.workspace.openTextDocument(templateMap.fileUri);
|
||||
let content = vsEditor.getText();
|
||||
promisify(fs.writeFile)(templateMap.tempPath, content);
|
||||
AgentUtils.getAgentService().then(async (agentService) => {
|
||||
let result = await agentService.updateNotebook(templateMap.ownerUri, templateMap.notebookInfo.name, templateMap.notebookInfo, templateMap.tempPath);
|
||||
if (result.success) {
|
||||
vscode.window.showInformationMessage(localize('agent.templateUploadSuccessful', 'Template updated successfully'));
|
||||
}
|
||||
else {
|
||||
vscode.window.showInformationMessage(localize('agent.templateUploadError', 'Template update failure'));
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
vscode.commands.registerCommand('agent.openProxyDialog', async (ownerUri: string, proxyInfo: azdata.AgentProxyInfo, credentials: azdata.CredentialInfo[]) => {
|
||||
if (!this.proxyDialog || (this.proxyDialog && !this.proxyDialog.isOpen)) {
|
||||
this.proxyDialog = new ProxyDialog(ownerUri, proxyInfo, credentials);
|
||||
@@ -91,6 +124,117 @@ export class MainController {
|
||||
}
|
||||
this.proxyDialog.dialogName ? await this.proxyDialog.openDialog(this.proxyDialog.dialogName) : await this.proxyDialog.openDialog();
|
||||
});
|
||||
|
||||
vscode.commands.registerCommand('agent.openNotebookEditorFromJsonString', async (filename: string, jsonNotebook: string, notebookInfo?: azdata.AgentNotebookInfo, ownerUri?: string) => {
|
||||
const tempfilePath = path.join(os.tmpdir(), 'mssql_notebooks', filename + '.ipynb');
|
||||
if (!await exists(path.join(os.tmpdir(), 'mssql_notebooks'))) {
|
||||
await mkdir(path.join(os.tmpdir(), 'mssql_notebooks'));
|
||||
}
|
||||
let editors = azdata.nb.visibleNotebookEditors;
|
||||
if (await exists(tempfilePath)) {
|
||||
await unlink(tempfilePath);
|
||||
}
|
||||
try {
|
||||
await writeFile(tempfilePath, jsonNotebook);
|
||||
let uri = vscode.Uri.parse(`untitled:${path.basename(tempfilePath)}`);
|
||||
if (notebookInfo) {
|
||||
this.notebookTemplateMap.set(uri.toString(), { notebookInfo: notebookInfo, fileUri: uri, ownerUri: ownerUri, tempPath: tempfilePath });
|
||||
vscode.commands.executeCommand('setContext', 'agent:trackedTemplate', true);
|
||||
}
|
||||
await azdata.nb.showNotebookDocument(uri, {
|
||||
initialContent: jsonNotebook,
|
||||
initialDirtyState: false
|
||||
});
|
||||
vscode.commands.executeCommand('setContext', 'agent:trackedTemplate', false);
|
||||
}
|
||||
catch (e) {
|
||||
vscode.window.showErrorMessage(e);
|
||||
}
|
||||
});
|
||||
|
||||
vscode.commands.registerCommand('agent.openNotebookDialog', async (ownerUri: any, notebookInfo: azdata.AgentNotebookInfo) => {
|
||||
|
||||
/*
|
||||
There are four entry points to this commands:
|
||||
1. Explorer context menu:
|
||||
The first arg becomes a vscode URI
|
||||
the second argument is undefined
|
||||
2. Notebook toolbar:
|
||||
both the args are undefined
|
||||
3. Agent New Notebook Action
|
||||
the first arg is database OwnerUri
|
||||
the second arg is undefined
|
||||
4. Agent Edit Notebook Action
|
||||
the first arg is database OwnerUri
|
||||
the second arg is notebookInfo from database
|
||||
*/
|
||||
if (!ownerUri || ownerUri instanceof vscode.Uri) {
|
||||
let path: string;
|
||||
if (!ownerUri) {
|
||||
if (azdata.nb.activeNotebookEditor.document.isDirty) {
|
||||
vscode.window.showErrorMessage(localize('agent.unsavedFileSchedulingError', 'Save file before scheduling'), { modal: true });
|
||||
return;
|
||||
}
|
||||
path = azdata.nb.activeNotebookEditor.document.fileName;
|
||||
} else {
|
||||
path = ownerUri.fsPath;
|
||||
}
|
||||
|
||||
let connection = await this.getConnectionFromUser();
|
||||
ownerUri = await azdata.connection.getUriForConnection(connection.connectionId);
|
||||
this.notebookDialog = new NotebookDialog(ownerUri, <NotebookDialogOptions>{ filePath: path, connection: connection });
|
||||
if (!this.notebookDialog.isOpen) {
|
||||
this.notebookDialog.dialogName ? await this.notebookDialog.openDialog(this.notebookDialog.dialogName) : await this.notebookDialog.openDialog();
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (!this.notebookDialog || (this.notebookDialog && !this.notebookDialog.isOpen)) {
|
||||
this.notebookDialog = new NotebookDialog(ownerUri, <NotebookDialogOptions>{ notebookInfo: notebookInfo });
|
||||
}
|
||||
if (!this.notebookDialog.isOpen) {
|
||||
this.notebookDialog.dialogName ? await this.notebookDialog.openDialog(this.notebookDialog.dialogName) : await this.notebookDialog.openDialog();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public async getConnectionFromUser(): Promise<azdata.connection.Connection> {
|
||||
let connection: azdata.connection.Connection = null;
|
||||
|
||||
let connections = await azdata.connection.getActiveConnections();
|
||||
if (!connections || connections.length === 0) {
|
||||
connection = await azdata.connection.openConnectionDialog();
|
||||
}
|
||||
else {
|
||||
let sqlConnectionsPresent: boolean;
|
||||
for (let i = 0; i < connections.length; i++) {
|
||||
if (connections[i].providerName === 'MSSQL') {
|
||||
sqlConnectionsPresent = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
let connectionNames: azdata.connection.Connection[] = [];
|
||||
let connectionDisplayString: string[] = [];
|
||||
for (let i = 0; i < connections.length; i++) {
|
||||
let currentConnectionString = connections[i].options.server + ' (' + connections[i].options.user + ')';
|
||||
connectionNames.push(connections[i]);
|
||||
connectionDisplayString.push(currentConnectionString);
|
||||
}
|
||||
connectionDisplayString.push(localize('agent.AddNewConnection', 'Add new connection'));
|
||||
let connectionName = await vscode.window.showQuickPick(connectionDisplayString, { placeHolder: localize('agent.selectConnection', 'Select a connection') });
|
||||
if (connectionDisplayString.indexOf(connectionName) !== -1) {
|
||||
if (connectionName === localize('agent.AddNewConnection', 'Add new connection')) {
|
||||
connection = await azdata.connection.openConnectionDialog();
|
||||
}
|
||||
else {
|
||||
connection = connections[connectionDisplayString.indexOf(connectionName)];
|
||||
}
|
||||
}
|
||||
else {
|
||||
vscode.window.showErrorMessage(localize('agent.selectValidConnection', 'Please select a valid connection'), { modal: true });
|
||||
}
|
||||
}
|
||||
return connection;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"downloadUrl": "https://github.com/Microsoft/sqltoolsservice/releases/download/v{#version#}/microsoft.sqltools.servicelayer-{#fileName#}",
|
||||
"version": "2.0.0-release.6",
|
||||
"version": "2.0.0-release.10",
|
||||
"downloadFileNames": {
|
||||
"Windows_86": "win-x86-netcoreapp2.2.zip",
|
||||
"Windows_64": "win-x64-netcoreapp2.2.zip",
|
||||
|
||||
@@ -87,6 +87,68 @@ export interface DeleteAgentJobStepParams {
|
||||
step: azdata.AgentJobStepInfo;
|
||||
}
|
||||
|
||||
// Notebook management parameters
|
||||
export interface AgentNotebookParams {
|
||||
ownerUri: string;
|
||||
}
|
||||
|
||||
export interface AgentNotebookHistoryParams {
|
||||
ownerUri: string;
|
||||
jobId: string;
|
||||
jobName: string;
|
||||
targetDatabase: string;
|
||||
}
|
||||
|
||||
export interface AgentNotebookMaterializedParams {
|
||||
ownerUri: string;
|
||||
targetDatabase: string;
|
||||
notebookMaterializedId: number;
|
||||
}
|
||||
|
||||
export interface AgentNotebookTemplateParams {
|
||||
ownerUri: string;
|
||||
targetDatabase: string;
|
||||
jobId: string;
|
||||
}
|
||||
|
||||
export interface CreateAgentNotebookParams {
|
||||
ownerUri: string;
|
||||
notebook: azdata.AgentNotebookInfo;
|
||||
templateFilePath: string;
|
||||
}
|
||||
|
||||
export interface UpdateAgentNotebookParams {
|
||||
ownerUri: string;
|
||||
originalNotebookName: string;
|
||||
notebook: azdata.AgentJobInfo;
|
||||
templateFilePath: string;
|
||||
}
|
||||
|
||||
export interface UpdateAgentNotebookRunPinParams {
|
||||
ownerUri: string;
|
||||
targetDatabase: string;
|
||||
agentNotebookHistory: azdata.AgentNotebookHistoryInfo;
|
||||
materializedNotebookPin: boolean;
|
||||
}
|
||||
|
||||
export interface UpdateAgentNotebookRunNameParams {
|
||||
ownerUri: string;
|
||||
targetDatabase: string;
|
||||
agentNotebookHistory: azdata.AgentNotebookHistoryInfo;
|
||||
materializedNotebookName: string;
|
||||
}
|
||||
|
||||
export interface DeleteAgentNotebookParams {
|
||||
ownerUri: string;
|
||||
notebook: azdata.AgentNotebookInfo;
|
||||
}
|
||||
|
||||
export interface DeleteAgentMaterializedNotebookParams {
|
||||
ownerUri: string;
|
||||
targetDatabase: string;
|
||||
agentNotebookHistory: azdata.AgentNotebookHistoryInfo;
|
||||
}
|
||||
|
||||
// Alert management parameters
|
||||
export interface AgentAlertsParams {
|
||||
ownerUri: string;
|
||||
@@ -218,6 +280,47 @@ export namespace DeleteAgentJobStepRequest {
|
||||
export const type = new RequestType<DeleteAgentJobStepParams, azdata.ResultStatus, void, void>('agent/deletejobstep');
|
||||
}
|
||||
|
||||
// Notebooks request
|
||||
export namespace AgentNotebooksRequest {
|
||||
export const type = new RequestType<AgentNotebookParams, azdata.AgentNotebooksResult, void, void>('agent/notebooks');
|
||||
}
|
||||
|
||||
export namespace AgentNotebookHistoryRequest {
|
||||
export const type = new RequestType<AgentNotebookHistoryParams, azdata.AgentNotebookHistoryResult, void, void>('agent/notebookhistory');
|
||||
}
|
||||
|
||||
export namespace AgentNotebookMaterializedRequest {
|
||||
export const type = new RequestType<AgentNotebookMaterializedParams, azdata.AgentNotebookMaterializedResult, void, void>('agent/notebookmaterialized');
|
||||
}
|
||||
|
||||
export namespace UpdateAgentNotebookRunNameRequest {
|
||||
export const type = new RequestType<UpdateAgentNotebookRunNameParams, azdata.UpdateAgentNotebookResult, void, void>('agent/updatenotebookname');
|
||||
}
|
||||
|
||||
export namespace DeleteMaterializedNotebookRequest {
|
||||
export const type = new RequestType<DeleteAgentMaterializedNotebookParams, azdata.ResultStatus, void, void>('agent/deletenotebookmaterialized');
|
||||
}
|
||||
|
||||
export namespace UpdateAgentNotebookRunPinRequest {
|
||||
export const type = new RequestType<UpdateAgentNotebookRunPinParams, azdata.ResultStatus, void, void>('agent/updatenotebookpin');
|
||||
}
|
||||
|
||||
export namespace AgentNotebookTemplateRequest {
|
||||
export const type = new RequestType<AgentNotebookTemplateParams, azdata.ResultStatus, void, void>('agent/notebooktemplate');
|
||||
}
|
||||
|
||||
export namespace CreateAgentNotebookRequest {
|
||||
export const type = new RequestType<CreateAgentNotebookParams, azdata.CreateAgentNotebookResult, void, void>('agent/createnotebook');
|
||||
}
|
||||
|
||||
export namespace DeleteAgentNotebookRequest {
|
||||
export const type = new RequestType<DeleteAgentNotebookParams, azdata.ResultStatus, void, void>('agent/deletenotebook');
|
||||
}
|
||||
|
||||
export namespace UpdateAgentNotebookRequest {
|
||||
export const type = new RequestType<UpdateAgentNotebookParams, azdata.UpdateAgentNotebookResult, void, void>('agent/updatenotebook');
|
||||
}
|
||||
|
||||
// Alerts requests
|
||||
export namespace AgentAlertsRequest {
|
||||
export const type = new RequestType<CreateAgentAlertParams, azdata.AgentAlertsResult, void, void>('agent/alerts');
|
||||
|
||||
@@ -227,6 +227,151 @@ export class AgentServicesFeature extends SqlOpsFeature<undefined> {
|
||||
);
|
||||
};
|
||||
|
||||
// Notebook Management methods
|
||||
const getNotebooks = (ownerUri: string): Thenable<azdata.AgentNotebooksResult> => {
|
||||
let params: contracts.AgentNotebookParams = { ownerUri: ownerUri };
|
||||
return client.sendRequest(contracts.AgentNotebooksRequest.type, params).then(
|
||||
r => r,
|
||||
e => {
|
||||
client.logFailedRequest(contracts.AgentNotebooksRequest.type, e);
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const getNotebookHistory = (ownerUri: string, jobID: string, jobName: string, targetDatabase: string): Thenable<azdata.AgentNotebookHistoryResult> => {
|
||||
let params: contracts.AgentNotebookHistoryParams = { ownerUri: ownerUri, jobId: jobID, jobName: jobName, targetDatabase: targetDatabase };
|
||||
|
||||
return client.sendRequest(contracts.AgentNotebookHistoryRequest
|
||||
.type, params).then(
|
||||
r => r,
|
||||
e => {
|
||||
client.logFailedRequest(contracts.AgentNotebookHistoryRequest.type, e);
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const getMaterializedNotebook = (ownerUri: string, targetDatabase: string, notebookMaterializedId: number): Thenable<azdata.AgentNotebookMaterializedResult> => {
|
||||
let params: contracts.AgentNotebookMaterializedParams = { ownerUri: ownerUri, targetDatabase: targetDatabase, notebookMaterializedId: notebookMaterializedId };
|
||||
return client.sendRequest(contracts.AgentNotebookMaterializedRequest
|
||||
.type, params).then(
|
||||
r => r,
|
||||
e => {
|
||||
client.logFailedRequest(contracts.AgentNotebookMaterializedRequest.type, e);
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const getTemplateNotebook = (ownerUri: string, targetDatabase: string, jobId: string): Thenable<azdata.AgentNotebookTemplateResult> => {
|
||||
let params: contracts.AgentNotebookTemplateParams = { ownerUri: ownerUri, targetDatabase: targetDatabase, jobId: jobId };
|
||||
return client.sendRequest(contracts.AgentNotebookTemplateRequest
|
||||
.type, params).then(
|
||||
r => r,
|
||||
e => {
|
||||
client.logFailedRequest(contracts.AgentNotebookTemplateRequest.type, e);
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const createNotebook = (ownerUri: string, notebookInfo: azdata.AgentNotebookInfo, templateFilePath: string): Thenable<azdata.CreateAgentNotebookResult> => {
|
||||
let params: contracts.CreateAgentNotebookParams = {
|
||||
ownerUri: ownerUri,
|
||||
notebook: notebookInfo,
|
||||
templateFilePath: templateFilePath
|
||||
};
|
||||
let requestType = contracts.CreateAgentNotebookRequest.type;
|
||||
return client.sendRequest(requestType, params).then(
|
||||
r => {
|
||||
fireOnUpdated();
|
||||
return r;
|
||||
},
|
||||
e => {
|
||||
client.logFailedRequest(requestType, e);
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
const updateNotebook = (ownerUri: string, originalNotebookName: string, notebookInfo: azdata.AgentNotebookInfo, templateFilePath: string): Thenable<azdata.UpdateAgentNotebookResult> => {
|
||||
let params: contracts.UpdateAgentNotebookParams = {
|
||||
ownerUri: ownerUri,
|
||||
originalNotebookName: originalNotebookName,
|
||||
notebook: notebookInfo,
|
||||
templateFilePath: templateFilePath
|
||||
};
|
||||
let requestType = contracts.UpdateAgentNotebookRequest.type;
|
||||
return client.sendRequest(requestType, params).then(
|
||||
r => {
|
||||
fireOnUpdated();
|
||||
return r;
|
||||
},
|
||||
e => {
|
||||
client.logFailedRequest(requestType, e);
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const deleteNotebook = (ownerUri: string, notebookInfo: azdata.AgentNotebookInfo): Thenable<azdata.ResultStatus> => {
|
||||
let params: contracts.DeleteAgentNotebookParams = {
|
||||
ownerUri: ownerUri,
|
||||
notebook: notebookInfo
|
||||
};
|
||||
let requestType = contracts.DeleteAgentNotebookRequest.type;
|
||||
return client.sendRequest(requestType, params).then(
|
||||
r => {
|
||||
fireOnUpdated();
|
||||
return r;
|
||||
},
|
||||
e => {
|
||||
client.logFailedRequest(requestType, e);
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const deleteMaterializedNotebook = (ownerUri: string, agentNotebookHistory: azdata.AgentNotebookHistoryInfo, targetDatabase: string): Thenable<azdata.ResultStatus> => {
|
||||
let params: contracts.DeleteAgentMaterializedNotebookParams = { ownerUri: ownerUri, targetDatabase: targetDatabase, agentNotebookHistory: agentNotebookHistory };
|
||||
return client.sendRequest(contracts.DeleteMaterializedNotebookRequest
|
||||
.type, params).then(
|
||||
r => r,
|
||||
e => {
|
||||
client.logFailedRequest(contracts.DeleteMaterializedNotebookRequest.type, e);
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const updateNotebookMaterializedName = (ownerUri: string, agentNotebookHistory: azdata.AgentNotebookHistoryInfo, targetDatabase: string, name: string): Thenable<azdata.ResultStatus> => {
|
||||
let params: contracts.UpdateAgentNotebookRunNameParams = { ownerUri: ownerUri, targetDatabase: targetDatabase, agentNotebookHistory: agentNotebookHistory, materializedNotebookName: name };
|
||||
return client.sendRequest(contracts.UpdateAgentNotebookRunNameRequest
|
||||
.type, params).then(
|
||||
r => r,
|
||||
e => {
|
||||
client.logFailedRequest(contracts.UpdateAgentNotebookRunNameRequest.type, e);
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const updateNotebookMaterializedPin = (ownerUri: string, agentNotebookHistory: azdata.AgentNotebookHistoryInfo, targetDatabase: string, pin: boolean): Thenable<azdata.ResultStatus> => {
|
||||
let params: contracts.UpdateAgentNotebookRunPinParams = { ownerUri: ownerUri, targetDatabase: targetDatabase, agentNotebookHistory: agentNotebookHistory, materializedNotebookPin: pin };
|
||||
return client.sendRequest(contracts.UpdateAgentNotebookRunPinRequest
|
||||
.type, params).then(
|
||||
r => r,
|
||||
e => {
|
||||
client.logFailedRequest(contracts.UpdateAgentNotebookRunPinRequest.type, e);
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
|
||||
// Alert management methods
|
||||
let getAlerts = (ownerUri: string): Thenable<azdata.AgentAlertsResult> => {
|
||||
let params: contracts.AgentAlertsParams = {
|
||||
@@ -535,6 +680,16 @@ export class AgentServicesFeature extends SqlOpsFeature<undefined> {
|
||||
createJobStep,
|
||||
updateJobStep,
|
||||
deleteJobStep,
|
||||
getNotebooks,
|
||||
getNotebookHistory,
|
||||
getMaterializedNotebook,
|
||||
getTemplateNotebook,
|
||||
createNotebook,
|
||||
updateNotebook,
|
||||
deleteMaterializedNotebook,
|
||||
updateNotebookMaterializedName,
|
||||
updateNotebookMaterializedPin,
|
||||
deleteNotebook,
|
||||
getAlerts,
|
||||
createAlert,
|
||||
updateAlert,
|
||||
|
||||
68
src/sql/azdata.d.ts
vendored
@@ -1387,6 +1387,20 @@ declare module 'azdata' {
|
||||
alerts: AgentAlertInfo[];
|
||||
}
|
||||
|
||||
export interface AgentNotebookInfo extends AgentJobInfo {
|
||||
templateId: number;
|
||||
targetDatabase: string;
|
||||
lastRunNotebookError: string;
|
||||
executeDatabase: string;
|
||||
}
|
||||
|
||||
export interface AgentNotebookMaterializedInfo {
|
||||
materializedId: number;
|
||||
targetDatabase: string;
|
||||
materializedName: string;
|
||||
favorite: boolean;
|
||||
}
|
||||
|
||||
export interface AgentJobScheduleInfo {
|
||||
id: number;
|
||||
name: string;
|
||||
@@ -1487,6 +1501,14 @@ declare module 'azdata' {
|
||||
steps: AgentJobStep[];
|
||||
}
|
||||
|
||||
export interface AgentNotebookHistoryInfo extends AgentJobHistoryInfo {
|
||||
materializedNotebookId: number;
|
||||
materializedNotebookName: string;
|
||||
materializedNotebookPin: boolean;
|
||||
materializedNotebookErrorInfo: string;
|
||||
materializedNotebookDeleted: boolean;
|
||||
}
|
||||
|
||||
export interface AgentProxyInfo {
|
||||
id: number;
|
||||
accountName: string;
|
||||
@@ -1577,6 +1599,39 @@ declare module 'azdata' {
|
||||
categories: AgentJobCategory[];
|
||||
}
|
||||
|
||||
export interface AgentNotebooksResult extends ResultStatus {
|
||||
notebooks: AgentNotebookInfo[];
|
||||
}
|
||||
|
||||
export interface AgentJobHistoryResult extends ResultStatus {
|
||||
histories: AgentJobHistoryInfo[];
|
||||
schedules: AgentJobScheduleInfo[];
|
||||
alerts: AgentAlertInfo[];
|
||||
steps: AgentJobStepInfo[];
|
||||
}
|
||||
|
||||
export interface AgentNotebookHistoryResult extends ResultStatus {
|
||||
histories: AgentNotebookHistoryInfo[];
|
||||
schedules: AgentJobScheduleInfo[];
|
||||
steps: AgentJobStepInfo[];
|
||||
}
|
||||
|
||||
export interface AgentNotebookMaterializedResult extends ResultStatus {
|
||||
notebookMaterialized: string;
|
||||
}
|
||||
|
||||
export interface AgentNotebookTemplateResult extends ResultStatus {
|
||||
notebookTemplate: string;
|
||||
}
|
||||
|
||||
export interface CreateAgentNotebookResult extends ResultStatus {
|
||||
notebook: AgentNotebookInfo;
|
||||
}
|
||||
|
||||
export interface UpdateAgentNotebookResult extends ResultStatus {
|
||||
notebook: AgentNotebookInfo;
|
||||
}
|
||||
|
||||
export interface CreateAgentJobStepResult extends ResultStatus {
|
||||
step: AgentJobStepInfo;
|
||||
}
|
||||
@@ -1651,6 +1706,18 @@ declare module 'azdata' {
|
||||
deleteJob(ownerUri: string, jobInfo: AgentJobInfo): Thenable<ResultStatus>;
|
||||
getJobDefaults(ownerUri: string): Thenable<AgentJobDefaultsResult>;
|
||||
|
||||
// Notebook management methods
|
||||
getNotebooks(ownerUri: string): Thenable<AgentNotebooksResult>;
|
||||
getNotebookHistory(ownerUri: string, jobId: string, jobName: string, targetDatabase: string): Thenable<AgentNotebookHistoryResult>;
|
||||
getMaterializedNotebook(ownerUri: string, targetDatabase: string, notebookMaterializedId: number): Thenable<AgentNotebookMaterializedResult>;
|
||||
getTemplateNotebook(ownerUri: string, targetDatabase: string, jobId: string): Thenable<AgentNotebookTemplateResult>;
|
||||
createNotebook(ownerUri: string, notebook: AgentNotebookInfo, templateFilePath: string): Thenable<CreateAgentNotebookResult>;
|
||||
deleteNotebook(ownerUri: string, notebook: AgentNotebookInfo): Thenable<ResultStatus>;
|
||||
updateNotebook(ownerUri: string, originialNotebookName: string, notebook: AgentNotebookInfo, templateFilePath: string): Thenable<UpdateAgentNotebookResult>;
|
||||
updateNotebookMaterializedName(ownerUri: string, agentNotebookHistory: AgentNotebookHistoryInfo, targetDatabase: string, name: string): Thenable<ResultStatus>;
|
||||
updateNotebookMaterializedPin(ownerUri: string, agentNotebookHistory: AgentNotebookHistoryInfo, targetDatabase: string, pin: boolean): Thenable<ResultStatus>;
|
||||
deleteMaterializedNotebook(ownerUri: string, agentNotebookHistory: AgentNotebookHistoryInfo, targetDatabase: string): Thenable<ResultStatus>;
|
||||
|
||||
// Job Step management methods
|
||||
createJobStep(ownerUri: string, stepInfo: AgentJobStepInfo): Thenable<CreateAgentJobStepResult>;
|
||||
updateJobStep(ownerUri: string, originalJobStepName: string, stepInfo: AgentJobStepInfo): Thenable<UpdateAgentJobStepResult>;
|
||||
@@ -3006,6 +3073,7 @@ declare module 'azdata' {
|
||||
editable?: boolean;
|
||||
fireOnTextChange?: boolean;
|
||||
ariaLabel?: string;
|
||||
required?: boolean;
|
||||
}
|
||||
|
||||
export interface DeclarativeTableColumn {
|
||||
|
||||
1
src/sql/azdata.proposed.d.ts
vendored
@@ -74,7 +74,6 @@ declare module 'azdata' {
|
||||
continueSerialization(requestParams: SerializeDataContinueRequestParams): Thenable<SerializeDataResult>;
|
||||
}
|
||||
|
||||
|
||||
export namespace dataprotocol {
|
||||
export function registerSerializationProvider(provider: SerializationProvider): vscode.Disposable;
|
||||
}
|
||||
|
||||
@@ -20,6 +20,8 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import * as TelemetryKeys from 'sql/platform/telemetry/common/telemetryKeys';
|
||||
import { IErrorMessageService } from 'sql/platform/errorMessage/common/errorMessageService';
|
||||
import { JobManagementView } from 'sql/workbench/parts/jobManagement/browser/jobManagementView';
|
||||
import { NotebooksViewComponent } from 'sql/workbench/parts/jobManagement/browser/notebooksView.component';
|
||||
import { NotebookHistoryComponent } from 'sql/workbench/parts/jobManagement/browser/notebookHistory.component';
|
||||
|
||||
export const successLabel: string = nls.localize('jobaction.successLabel', "Success");
|
||||
export const errorLabel: string = nls.localize('jobaction.faillabel', "Error");
|
||||
@@ -171,6 +173,22 @@ export class EditJobAction extends Action {
|
||||
}
|
||||
}
|
||||
|
||||
export class OpenMaterializedNotebookAction extends Action {
|
||||
public static ID = 'notebookAction.openNotebook';
|
||||
public static LABEL = nls.localize('notebookAction.openNotebook', "Open");
|
||||
|
||||
constructor(
|
||||
@ICommandService private _commandService: ICommandService
|
||||
) {
|
||||
super(OpenMaterializedNotebookAction.ID, OpenMaterializedNotebookAction.LABEL, 'open');
|
||||
}
|
||||
|
||||
public run(context: any): Promise<boolean> {
|
||||
context.component.openNotebook(context.history);
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
|
||||
export class DeleteJobAction extends Action {
|
||||
public static ID = 'jobaction.deleteJob';
|
||||
public static LABEL = nls.localize('jobaction.deleteJob', "Delete Job");
|
||||
@@ -282,7 +300,6 @@ export class DeleteStepAction extends Action {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Alert Actions
|
||||
|
||||
export class NewAlertAction extends Action {
|
||||
@@ -551,3 +568,167 @@ export class DeleteProxyAction extends Action {
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
|
||||
//Notebook Actions
|
||||
|
||||
export class NewNotebookJobAction extends Action {
|
||||
public static ID = 'notebookaction.newJob';
|
||||
public static LABEL = nls.localize('notebookaction.newJob', "New Notebook Job");
|
||||
|
||||
constructor(
|
||||
) {
|
||||
super(NewNotebookJobAction.ID, NewNotebookJobAction.LABEL, 'newStepIcon');
|
||||
}
|
||||
|
||||
public async run(context: IJobActionInfo): Promise<boolean> {
|
||||
let component = context.component as NotebooksViewComponent;
|
||||
await component.openCreateNotebookDialog();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export class EditNotebookJobAction extends Action {
|
||||
public static ID = 'notebookaction.editNotebook';
|
||||
public static LABEL = nls.localize('notebookaction.editJob', "Edit Notebook Job");
|
||||
|
||||
constructor(
|
||||
@ICommandService private _commandService: ICommandService
|
||||
) {
|
||||
super(EditNotebookJobAction.ID, EditNotebookJobAction.LABEL, 'edit');
|
||||
}
|
||||
|
||||
public run(actionInfo: IJobActionInfo): Promise<boolean> {
|
||||
this._commandService.executeCommand(
|
||||
'agent.openNotebookDialog',
|
||||
actionInfo.ownerUri,
|
||||
actionInfo.targetObject.job);
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
|
||||
export class OpenTemplateNotebookAction extends Action {
|
||||
public static ID = 'notebookaction.openTemplate';
|
||||
public static LABEL = nls.localize('notebookaction.openNotebook', "Open Template Notebook");
|
||||
|
||||
constructor(
|
||||
@ICommandService private _commandService: ICommandService
|
||||
) {
|
||||
super(OpenTemplateNotebookAction.ID, OpenTemplateNotebookAction.LABEL, 'opennotebook');
|
||||
}
|
||||
|
||||
public run(actionInfo: any): Promise<boolean> {
|
||||
actionInfo.component.openTemplateNotebook();
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
|
||||
export class DeleteNotebookAction extends Action {
|
||||
public static ID = 'notebookaction.deleteNotebook';
|
||||
public static LABEL = nls.localize('notebookaction.deleteNotebook', "Delete Notebook");
|
||||
|
||||
constructor(
|
||||
@INotificationService private _notificationService: INotificationService,
|
||||
@IErrorMessageService private _errorMessageService: IErrorMessageService,
|
||||
@IJobManagementService private _jobService: IJobManagementService,
|
||||
@IInstantiationService private instantationService: IInstantiationService,
|
||||
@ITelemetryService private _telemetryService: ITelemetryService
|
||||
) {
|
||||
super(DeleteNotebookAction.ID, DeleteNotebookAction.LABEL);
|
||||
}
|
||||
|
||||
public run(actionInfo: IJobActionInfo): Promise<boolean> {
|
||||
let self = this;
|
||||
let notebook = actionInfo.targetObject.job as azdata.AgentNotebookInfo;
|
||||
let refreshAction = this.instantationService.createInstance(JobsRefreshAction);
|
||||
self._notificationService.prompt(
|
||||
Severity.Info,
|
||||
nls.localize('jobaction.deleteNotebookConfirm', "Are you sure you'd like to delete the notebook '{0}'?", notebook.name),
|
||||
[{
|
||||
label: DeleteNotebookAction.LABEL,
|
||||
run: () => {
|
||||
this._telemetryService.publicLog(TelemetryKeys.DeleteAgentJob);
|
||||
self._jobService.deleteNotebook(actionInfo.ownerUri, actionInfo.targetObject.job).then(async (result) => {
|
||||
if (!result || !result.success) {
|
||||
await refreshAction.run(actionInfo);
|
||||
let errorMessage = nls.localize("jobaction.failedToDeleteNotebook", "Could not delete notebook '{0}'.\nError: {1}",
|
||||
notebook.name, result.errorMessage ? result.errorMessage : 'Unknown error');
|
||||
self._errorMessageService.showDialog(Severity.Error, errorLabel, errorMessage);
|
||||
} else {
|
||||
let successMessage = nls.localize('jobaction.deletedNotebook', "The notebook was successfully deleted");
|
||||
self._notificationService.info(successMessage);
|
||||
}
|
||||
});
|
||||
}
|
||||
}, {
|
||||
label: DeleteAlertAction.CancelLabel,
|
||||
run: () => { }
|
||||
}]
|
||||
);
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class PinNotebookMaterializedAction extends Action {
|
||||
public static ID = 'notebookaction.openTemplate';
|
||||
public static LABEL = nls.localize('notebookaction.pinNotebook', "Pin");
|
||||
|
||||
constructor(
|
||||
@ICommandService private _commandService: ICommandService
|
||||
) {
|
||||
super(PinNotebookMaterializedAction.ID, PinNotebookMaterializedAction.LABEL);
|
||||
}
|
||||
|
||||
public run(actionInfo: any): Promise<boolean> {
|
||||
actionInfo.component.toggleNotebookPin(actionInfo.history, true);
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
|
||||
export class DeleteMaterializedNotebookAction extends Action {
|
||||
public static ID = 'notebookaction.deleteMaterializedNotebook';
|
||||
public static LABEL = nls.localize('notebookaction.deleteMaterializedNotebook', "Delete");
|
||||
|
||||
constructor(
|
||||
@ICommandService private _commandService: ICommandService
|
||||
) {
|
||||
super(DeleteMaterializedNotebookAction.ID, DeleteMaterializedNotebookAction.LABEL);
|
||||
}
|
||||
|
||||
public run(actionInfo: any): Promise<boolean> {
|
||||
actionInfo.component.deleteMaterializedNotebook(actionInfo.history);
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
|
||||
export class UnpinNotebookMaterializedAction extends Action {
|
||||
public static ID = 'notebookaction.unpinNotebook';
|
||||
public static LABEL = nls.localize('notebookaction.unpinNotebook', "Unpin");
|
||||
|
||||
constructor(
|
||||
@ICommandService private _commandService: ICommandService
|
||||
) {
|
||||
super(UnpinNotebookMaterializedAction.ID, UnpinNotebookMaterializedAction.LABEL);
|
||||
}
|
||||
|
||||
public run(actionInfo: any): Promise<boolean> {
|
||||
actionInfo.component.toggleNotebookPin(actionInfo.history, false);
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
|
||||
export class RenameNotebookMaterializedAction extends Action {
|
||||
public static ID = 'notebookaction.openTemplate';
|
||||
public static LABEL = nls.localize('notebookaction.renameNotebook', "Rename");
|
||||
|
||||
constructor(
|
||||
@ICommandService private _commandService: ICommandService,
|
||||
) {
|
||||
super(RenameNotebookMaterializedAction.ID, RenameNotebookMaterializedAction.LABEL);
|
||||
}
|
||||
|
||||
public run(actionInfo: any): Promise<boolean> {
|
||||
actionInfo.component.renameNotebook(actionInfo.history);
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { JobCacheObject, AlertsCacheObject, ProxiesCacheObject, OperatorsCacheObject } from './jobManagementService';
|
||||
import { JobCacheObject, AlertsCacheObject, ProxiesCacheObject, OperatorsCacheObject, NotebookCacheObject } from './jobManagementService';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
|
||||
export const SERVICE_ID = 'jobManagementService';
|
||||
@@ -25,6 +25,15 @@ export interface IJobManagementService {
|
||||
|
||||
deleteJobStep(connectionUri: string, step: azdata.AgentJobStepInfo): Thenable<azdata.ResultStatus>;
|
||||
|
||||
getNotebooks(connectionUri: string): Thenable<azdata.AgentNotebooksResult>;
|
||||
getNotebookHistory(connectionUri: string, jobId: string, jobName: string, targetDatabase: string): Thenable<azdata.AgentNotebookHistoryResult>;
|
||||
getMaterialziedNotebook(connectionUri: string, targetDatabase: string, notebookMaterializedId: number): Thenable<azdata.AgentNotebookMaterializedResult>;
|
||||
getTemplateNotebook(connectionUri: string, targetDatabase: string, jobId: string): Thenable<azdata.AgentNotebookTemplateResult>;
|
||||
deleteNotebook(connectionUri: string, notebook: azdata.AgentNotebookInfo): Thenable<azdata.ResultStatus>;
|
||||
deleteMaterializedNotebook(connectionUri: string, agentNotebookHistory: azdata.AgentNotebookHistoryInfo, targetDatabase: string): Thenable<azdata.ResultStatus>;
|
||||
updateNotebookMaterializedName(connectionUri: string, agentNotebookHistory: azdata.AgentNotebookHistoryInfo, targetDatabase: string, name: string);
|
||||
updateNotebookMaterializedPin(connectionUri: string, agentNotebookHistory: azdata.AgentNotebookHistoryInfo, targetDatabase: string, pin: boolean);
|
||||
|
||||
getAlerts(connectionUri: string): Thenable<azdata.AgentAlertsResult>;
|
||||
deleteAlert(connectionUri: string, alert: azdata.AgentAlertInfo): Thenable<azdata.ResultStatus>;
|
||||
|
||||
@@ -37,10 +46,11 @@ export interface IJobManagementService {
|
||||
getCredentials(connectionUri: string): Thenable<azdata.GetCredentialsResult>;
|
||||
|
||||
jobAction(connectionUri: string, jobName: string, action: string): Thenable<azdata.ResultStatus>;
|
||||
addToCache(server: string, cache: JobCacheObject | OperatorsCacheObject);
|
||||
addToCache(server: string, cache: JobCacheObject | OperatorsCacheObject | NotebookCacheObject);
|
||||
jobCacheObjectMap: { [server: string]: JobCacheObject; };
|
||||
notebookCacheObjectMap: { [server: string]: NotebookCacheObject; };
|
||||
operatorsCacheObjectMap: { [server: string]: OperatorsCacheObject; };
|
||||
alertsCacheObjectMap: { [server: string]: AlertsCacheObject; };
|
||||
proxiesCacheObjectMap: { [server: string]: ProxiesCacheObject };
|
||||
addToCache(server: string, cache: JobCacheObject | ProxiesCacheObject | AlertsCacheObject | OperatorsCacheObject);
|
||||
}
|
||||
addToCache(server: string, cache: JobCacheObject | ProxiesCacheObject | AlertsCacheObject | OperatorsCacheObject | NotebookCacheObject);
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ export class JobManagementService implements IJobManagementService {
|
||||
private _operatorsCacheObjectMap: { [server: string]: OperatorsCacheObject; } = {};
|
||||
private _alertsCacheObject: { [server: string]: AlertsCacheObject; } = {};
|
||||
private _proxiesCacheObjectMap: { [server: string]: ProxiesCacheObject; } = {};
|
||||
|
||||
private _notebookCacheObjectMap: { [server: string]: NotebookCacheObject; } = {};
|
||||
constructor(
|
||||
@IConnectionManagementService private _connectionService: IConnectionManagementService
|
||||
) {
|
||||
@@ -62,6 +62,54 @@ export class JobManagementService implements IJobManagementService {
|
||||
});
|
||||
}
|
||||
|
||||
// Notebooks
|
||||
public getNotebooks(connectionUri: string): Thenable<azdata.AgentNotebooksResult> {
|
||||
return this._runAction(connectionUri, (runner) => {
|
||||
return runner.getNotebooks(connectionUri);
|
||||
});
|
||||
}
|
||||
|
||||
public getNotebookHistory(connectionUri: string, jobID: string, jobName: string, targetDatabase: string): Thenable<azdata.AgentNotebookHistoryResult> {
|
||||
return this._runAction(connectionUri, (runner) => {
|
||||
return runner.getNotebookHistory(connectionUri, jobID, jobName, targetDatabase);
|
||||
});
|
||||
}
|
||||
|
||||
public getMaterialziedNotebook(connectionUri: string, targetDatabase: string, notebookMaterializedId: number): Thenable<azdata.AgentNotebookMaterializedResult> {
|
||||
return this._runAction(connectionUri, (runner) => {
|
||||
return runner.getMaterializedNotebook(connectionUri, targetDatabase, notebookMaterializedId);
|
||||
});
|
||||
}
|
||||
|
||||
public getTemplateNotebook(connectionUri: string, targetDatabase: string, jobId: string): Thenable<azdata.AgentNotebookTemplateResult> {
|
||||
return this._runAction(connectionUri, (runner) => {
|
||||
return runner.getTemplateNotebook(connectionUri, targetDatabase, jobId);
|
||||
});
|
||||
}
|
||||
|
||||
public deleteNotebook(connectionUri: string, notebook: azdata.AgentNotebookInfo): Thenable<azdata.ResultStatus> {
|
||||
return this._runAction(connectionUri, (runner) => {
|
||||
return runner.deleteNotebook(connectionUri, notebook);
|
||||
});
|
||||
}
|
||||
|
||||
public deleteMaterializedNotebook(connectionUri: string, agentNotebookHistory: azdata.AgentNotebookHistoryInfo, targetDatabase: string): Thenable<azdata.ResultStatus> {
|
||||
return this._runAction(connectionUri, (runner) => {
|
||||
return runner.deleteMaterializedNotebook(connectionUri, agentNotebookHistory, targetDatabase);
|
||||
});
|
||||
}
|
||||
|
||||
public updateNotebookMaterializedName(connectionUri: string, agentNotebookHistory: azdata.AgentNotebookHistoryInfo, targetDatabase: string, name: string): Thenable<azdata.ResultStatus> {
|
||||
return this._runAction(connectionUri, (runner) => {
|
||||
return runner.updateNotebookMaterializedName(connectionUri, agentNotebookHistory, targetDatabase, name);
|
||||
});
|
||||
}
|
||||
|
||||
public updateNotebookMaterializedPin(connectionUri: string, agentNotebookHistory: azdata.AgentNotebookHistoryInfo, targetDatabase: string, pin: boolean): Thenable<azdata.ResultStatus> {
|
||||
return this._runAction(connectionUri, (runner) => {
|
||||
return runner.updateNotebookMaterializedPin(connectionUri, agentNotebookHistory, targetDatabase, pin);
|
||||
});
|
||||
}
|
||||
|
||||
// Alerts
|
||||
public getAlerts(connectionUri: string): Thenable<azdata.AgentAlertsResult> {
|
||||
@@ -134,6 +182,10 @@ export class JobManagementService implements IJobManagementService {
|
||||
return this._alertsCacheObject;
|
||||
}
|
||||
|
||||
public get notebookCacheObjectMap(): { [server: string]: NotebookCacheObject; } {
|
||||
return this._notebookCacheObjectMap;
|
||||
}
|
||||
|
||||
public get proxiesCacheObjectMap(): { [server: string]: ProxiesCacheObject; } {
|
||||
return this._proxiesCacheObjectMap;
|
||||
}
|
||||
@@ -142,7 +194,7 @@ export class JobManagementService implements IJobManagementService {
|
||||
return this._operatorsCacheObjectMap;
|
||||
}
|
||||
|
||||
public addToCache(server: string, cacheObject: JobCacheObject | OperatorsCacheObject | ProxiesCacheObject | AlertsCacheObject) {
|
||||
public addToCache(server: string, cacheObject: JobCacheObject | OperatorsCacheObject | ProxiesCacheObject | AlertsCacheObject | NotebookCacheObject) {
|
||||
if (cacheObject instanceof JobCacheObject) {
|
||||
this._jobCacheObjectMap[server] = cacheObject;
|
||||
} else if (cacheObject instanceof OperatorsCacheObject) {
|
||||
@@ -151,6 +203,8 @@ export class JobManagementService implements IJobManagementService {
|
||||
this._alertsCacheObject[server] = cacheObject;
|
||||
} else if (cacheObject instanceof ProxiesCacheObject) {
|
||||
this._proxiesCacheObjectMap[server] = cacheObject;
|
||||
} else if (cacheObject instanceof NotebookCacheObject) {
|
||||
this._notebookCacheObjectMap[server] = cacheObject;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -252,6 +306,94 @@ export class JobCacheObject {
|
||||
this._jobSchedules[jobID] = value;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Server level caching of Operators
|
||||
*/
|
||||
export class NotebookCacheObject {
|
||||
_serviceBrand: any;
|
||||
private _notebooks: azdata.AgentNotebookInfo[] = [];
|
||||
private _notebookHistories: { [jobID: string]: azdata.AgentNotebookHistoryInfo[]; } = {};
|
||||
private _jobSteps: { [jobID: string]: azdata.AgentJobStepInfo[]; } = {};
|
||||
private _jobSchedules: { [jobID: string]: azdata.AgentJobScheduleInfo[]; } = {};
|
||||
private _runCharts: { [jobID: string]: string[]; } = {};
|
||||
private _prevJobID: string;
|
||||
private _serverName: string;
|
||||
private _dataView: Slick.Data.DataView<any>;
|
||||
|
||||
/* Getters */
|
||||
public get notebooks(): azdata.AgentNotebookInfo[] {
|
||||
return this._notebooks;
|
||||
}
|
||||
|
||||
public get notebookHistories(): { [jobID: string]: azdata.AgentNotebookHistoryInfo[] } {
|
||||
return this._notebookHistories;
|
||||
}
|
||||
|
||||
public get prevJobID(): string {
|
||||
return this._prevJobID;
|
||||
}
|
||||
|
||||
public getNotebookHistory(jobID: string): azdata.AgentNotebookHistoryInfo[] {
|
||||
return this._notebookHistories[jobID];
|
||||
}
|
||||
|
||||
public get serverName(): string {
|
||||
return this._serverName;
|
||||
}
|
||||
|
||||
public get dataView(): Slick.Data.DataView<any> {
|
||||
return this._dataView;
|
||||
}
|
||||
|
||||
public getRunChart(jobID: string): string[] {
|
||||
return this._runCharts[jobID];
|
||||
}
|
||||
|
||||
public getJobSteps(jobID: string): azdata.AgentJobStepInfo[] {
|
||||
return this._jobSteps[jobID];
|
||||
}
|
||||
|
||||
public getJobSchedules(jobID: string): azdata.AgentJobScheduleInfo[] {
|
||||
return this._jobSchedules[jobID];
|
||||
}
|
||||
|
||||
/* Setters */
|
||||
public set notebooks(value: azdata.AgentNotebookInfo[]) {
|
||||
this._notebooks = value;
|
||||
}
|
||||
|
||||
public set notebookHistories(value: { [jobID: string]: azdata.AgentNotebookHistoryInfo[]; }) {
|
||||
this._notebookHistories = value;
|
||||
}
|
||||
|
||||
public set prevJobID(value: string) {
|
||||
this._prevJobID = value;
|
||||
}
|
||||
|
||||
public setNotebookHistory(jobID: string, value: azdata.AgentNotebookHistoryInfo[]) {
|
||||
this._notebookHistories[jobID] = value;
|
||||
}
|
||||
|
||||
public setRunChart(jobID: string, value: string[]) {
|
||||
this._runCharts[jobID] = value;
|
||||
}
|
||||
|
||||
public set serverName(value: string) {
|
||||
this._serverName = value;
|
||||
}
|
||||
|
||||
public set dataView(value: Slick.Data.DataView<any>) {
|
||||
this._dataView = value;
|
||||
}
|
||||
|
||||
public setJobSteps(jobID: string, value: azdata.AgentJobStepInfo[]) {
|
||||
this._jobSteps[jobID] = value;
|
||||
}
|
||||
|
||||
public setJobSchedules(jobID: string, value: azdata.AgentJobScheduleInfo[]) {
|
||||
this._jobSchedules[jobID] = value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Server level caching of Operators
|
||||
@@ -364,4 +506,4 @@ export class ProxiesCacheObject {
|
||||
public set serverName(value: string) {
|
||||
this._serverName = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
1
src/sql/sqlops.proposed.d.ts
vendored
@@ -631,6 +631,7 @@ declare module 'sqlops' {
|
||||
isFile?: boolean;
|
||||
fileContent?: string;
|
||||
title?: string;
|
||||
fileType?: string;
|
||||
}
|
||||
|
||||
export interface LoadingComponentProperties {
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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, '');
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -130,3 +130,8 @@ export interface JobActionContext {
|
||||
canEdit: boolean;
|
||||
job: azdata.AgentJobInfo;
|
||||
}
|
||||
|
||||
export interface NotebookActionContext {
|
||||
canEdit: boolean;
|
||||
notebook: azdata.AgentNotebookInfo;
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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 |
@@ -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>
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||