Compare commits
41 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cccb9327c7 | ||
|
|
a0db32a688 | ||
|
|
f0b6180e15 | ||
|
|
cd4660abaa | ||
|
|
8320686170 | ||
|
|
0a6779f96e | ||
|
|
3e17618056 | ||
|
|
7d27d2f4f4 | ||
|
|
30ae023d73 | ||
|
|
c36596d3b3 | ||
|
|
afbe004c4f | ||
|
|
539f2b2b2e | ||
|
|
43e360165f | ||
|
|
c2727264ad | ||
|
|
3c17ac1333 | ||
|
|
218dad1b17 | ||
|
|
2ff4b51fe6 | ||
|
|
423ad40210 | ||
|
|
788103b2d3 | ||
|
|
c4ce709dfb | ||
|
|
e875e4a271 | ||
|
|
ba99be6ec1 | ||
|
|
146aa48c51 | ||
|
|
60f82785c2 | ||
|
|
460c739a8d | ||
|
|
ba4c98cb0a | ||
|
|
f09241d4bf | ||
|
|
ca4a8bf485 | ||
|
|
965d418766 | ||
|
|
11b6b6f3a7 | ||
|
|
667051c57a | ||
|
|
aa0f27bf7f | ||
|
|
071585f17e | ||
|
|
b5d4cead84 | ||
|
|
770e38e53f | ||
|
|
73364777b5 | ||
|
|
1b16ef2961 | ||
|
|
83c5f92b19 | ||
|
|
12c9d41c99 | ||
|
|
8745f38320 | ||
|
|
acc85c2c23 |
@@ -65,6 +65,7 @@ const indentationFilter = [
|
||||
|
||||
// except multiple specific files
|
||||
'!**/package.json',
|
||||
'!**/package-lock.json', // {{SQL CARBON EDIT}}
|
||||
'!**/yarn.lock',
|
||||
'!**/yarn-error.log',
|
||||
|
||||
@@ -158,6 +159,7 @@ const copyrightFilter = [
|
||||
'!extensions/notebook/src/prompts/**',
|
||||
'!extensions/mssql/src/prompts/**',
|
||||
'!extensions/notebook/resources/jupyter_config/**',
|
||||
'!extensions/query-history/images/**',
|
||||
'!**/*.gif',
|
||||
'!**/*.xlf',
|
||||
'!**/*.dacpac',
|
||||
|
||||
@@ -199,7 +199,8 @@ const sqlBuiltInExtensions = [
|
||||
'admin-pack',
|
||||
'dacpac',
|
||||
'schema-compare',
|
||||
'cms'
|
||||
'cms',
|
||||
'query-history'
|
||||
];
|
||||
// make resource deployment and BDC extension only available in insiders
|
||||
if (process.env['VSCODE_QUALITY'] === 'stable') {
|
||||
|
||||
@@ -235,7 +235,8 @@ const sqlBuiltInExtensions = [
|
||||
'admin-pack',
|
||||
'dacpac',
|
||||
'schema-compare',
|
||||
'cms'
|
||||
'cms',
|
||||
'query-history'
|
||||
];
|
||||
|
||||
// make resource deployment and BDC extension only available in insiders
|
||||
|
||||
@@ -20,8 +20,8 @@ Compression=lzma
|
||||
SolidCompression=yes
|
||||
AppMutex={code:GetAppMutex}
|
||||
SetupMutex={#AppMutex}setup
|
||||
WizardImageFile="{#RepoDir}\resources\win32\inno-big-100.bmp,{#RepoDir}\resources\win32\inno-big-125.bmp,{#RepoDir}\resources\win32\inno-big-150.bmp,{#RepoDir}\resources\win32\inno-big-175.bmp,{#RepoDir}\resources\win32\inno-big-200.bmp,{#RepoDir}\resources\win32\inno-big-225.bmp,{#RepoDir}\resources\win32\inno-big-250.bmp"
|
||||
WizardSmallImageFile="{#RepoDir}\resources\win32\inno-small-100.bmp,{#RepoDir}\resources\win32\inno-small-125.bmp,{#RepoDir}\resources\win32\inno-small-150.bmp,{#RepoDir}\resources\win32\inno-small-175.bmp,{#RepoDir}\resources\win32\inno-small-200.bmp,{#RepoDir}\resources\win32\inno-small-225.bmp,{#RepoDir}\resources\win32\inno-small-250.bmp"
|
||||
WizardImageFile="{#RepoDir}\resources\win32\sql-big.bmp"
|
||||
WizardSmallImageFile="{#RepoDir}\resources\win32\sql-small.bmp"
|
||||
SetupIconFile={#RepoDir}\resources\win32\code.ico
|
||||
UninstallDisplayIcon={app}\{#ExeBasename}.exe
|
||||
ChangesEnvironment=true
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "agent",
|
||||
"displayName": "SQL Server Agent",
|
||||
"description": "Manage and troubleshoot SQL Server Agent jobs",
|
||||
"version": "0.41.0",
|
||||
"version": "0.43.0",
|
||||
"publisher": "Microsoft",
|
||||
"preview": true,
|
||||
"license": "https://raw.githubusercontent.com/Microsoft/azuredatastudio/master/LICENSE.txt",
|
||||
@@ -39,7 +39,43 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
],
|
||||
"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": "Update Template",
|
||||
"icon": {
|
||||
"dark": "resources/dark/open_notebook_inverse.svg",
|
||||
"light": "resources/light/open_notebook.svg"
|
||||
}
|
||||
}
|
||||
],
|
||||
"menus": {
|
||||
"notebook/toolbar": [
|
||||
{
|
||||
"command": "agent.openNotebookDialog",
|
||||
"when": "providerId == sql && !agent:trackedTemplate"
|
||||
},
|
||||
{
|
||||
"command": "agent.reuploadTemplate",
|
||||
"when": "agent:trackedTemplate"
|
||||
}
|
||||
],
|
||||
"explorer/context": [
|
||||
{
|
||||
"command": "agent.openNotebookDialog",
|
||||
"when": "resourceExtname == .ipynb"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"vscode-nls": "^3.2.1"
|
||||
@@ -52,11 +88,6 @@
|
||||
"mocha": "^5.2.0",
|
||||
"should": "^13.2.1",
|
||||
"typemoq": "^2.1.0",
|
||||
"vscode": "1.1.5"
|
||||
},
|
||||
"__metadata": {
|
||||
"id": "10",
|
||||
"publisherDisplayName": "Microsoft",
|
||||
"publisherId": "Microsoft"
|
||||
}
|
||||
"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: '',
|
||||
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -723,4 +723,4 @@ export class JobDialog extends AgentDialog<JobData> {
|
||||
this.model.alerts = this.alerts;
|
||||
this.model.categoryId = +this.model.jobCategoryIdsMap.find(cat => cat.name === this.model.category).id;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 => {
|
||||
|
||||
338
extensions/agent/src/dialogs/notebookDialog.ts
Normal file
@@ -0,0 +1,338 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -70,3 +70,8 @@ export class IconPathHelper {
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export namespace cssStyles {
|
||||
export const hyperlink = { 'user-select': 'text', 'color': '#0078d4', 'text-decoration': 'underline', 'cursor': 'pointer' };
|
||||
export const text = { 'margin-block-start': '0px', 'margin-block-end': '0px' };
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import * as azdata from 'azdata';
|
||||
import * as vscode from 'vscode';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { BdcDashboardModel } from './bdcDashboardModel';
|
||||
import { IconPathHelper } from '../constants';
|
||||
import { IconPathHelper, cssStyles } from '../constants';
|
||||
import { getStateDisplayText, getHealthStatusDisplayText, getEndpointDisplayText, getHealthStatusIcon, getServiceNameDisplayText, Endpoint } from '../utils';
|
||||
import { EndpointModel, ServiceStatusModel, BdcStatusModel } from '../controller/apiGenerated';
|
||||
import { BdcDashboard } from './bdcDashboard';
|
||||
@@ -250,13 +250,21 @@ function createServiceEndpointRow(modelBuilder: azdata.ModelBuilder, container:
|
||||
endPointRow.addItem(nameCell, { CSSStyles: { 'width': serviceEndpointRowServiceNameCellWidth, 'min-width': serviceEndpointRowServiceNameCellWidth, 'user-select': 'text', 'text-align': 'center' } });
|
||||
if (isHyperlink) {
|
||||
const endpointCell = modelBuilder.hyperlink()
|
||||
.withProperties({ label: endpoint.endpoint, url: endpoint.endpoint, CSSStyles: { 'height': '15px' } })
|
||||
.withProperties<azdata.HyperlinkComponentProperties>({
|
||||
label: endpoint.endpoint,
|
||||
title: endpoint.endpoint,
|
||||
url: endpoint.endpoint, CSSStyles: { 'height': '15px' }
|
||||
})
|
||||
.component();
|
||||
endPointRow.addItem(endpointCell, { CSSStyles: { 'width': serviceEndpointRowEndpointCellWidth, 'min-width': serviceEndpointRowEndpointCellWidth, 'color': '#0078d4', 'text-decoration': 'underline', 'overflow': 'hidden', 'padding-left': '10px' } });
|
||||
endPointRow.addItem(endpointCell, { CSSStyles: { 'width': serviceEndpointRowEndpointCellWidth, 'min-width': serviceEndpointRowEndpointCellWidth, 'overflow': 'hidden', 'text-overflow': 'ellipsis', ...cssStyles.hyperlink } });
|
||||
}
|
||||
else if (endpoint.name === Endpoint.sqlServerMaster) {
|
||||
const endpointCell = modelBuilder.text()
|
||||
.withProperties({ value: endpoint.endpoint, CSSStyles: { 'margin-block-start': '0px', 'margin-block-end': '0px', 'user-select': 'text', 'cursor': 'pointer', 'color': '#0078d4', 'text-decoration': 'underline' } })
|
||||
.withProperties<azdata.TextComponentProperties>({
|
||||
value: endpoint.endpoint,
|
||||
title: endpoint.endpoint,
|
||||
CSSStyles: { 'overflow': 'hidden', 'text-overflow': 'ellipsis', ...cssStyles.text, ...cssStyles.hyperlink }
|
||||
})
|
||||
.component();
|
||||
endpointCell.onDidClick(async () => {
|
||||
const connProfile = bdcModel.getSqlServerMasterConnectionProfile();
|
||||
@@ -271,13 +279,17 @@ function createServiceEndpointRow(modelBuilder: azdata.ModelBuilder, container:
|
||||
azdata.connection.openConnectionDialog(undefined, connProfile);
|
||||
}
|
||||
});
|
||||
endPointRow.addItem(endpointCell, { CSSStyles: { 'width': serviceEndpointRowEndpointCellWidth, 'min-width': serviceEndpointRowEndpointCellWidth, 'overflow': 'hidden', 'padding-left': '10px' } });
|
||||
endPointRow.addItem(endpointCell, { CSSStyles: { 'width': serviceEndpointRowEndpointCellWidth, 'min-width': serviceEndpointRowEndpointCellWidth } });
|
||||
}
|
||||
else {
|
||||
const endpointCell = modelBuilder.text()
|
||||
.withProperties({ value: endpoint.endpoint, CSSStyles: { 'margin-block-start': '0px', 'margin-block-end': '0px', 'user-select': 'text' } })
|
||||
.withProperties<azdata.TextComponentProperties>({
|
||||
value: endpoint.endpoint,
|
||||
title: endpoint.endpoint,
|
||||
CSSStyles: { 'overflow': 'hidden', 'text-overflow': 'ellipsis', ...cssStyles.text }
|
||||
})
|
||||
.component();
|
||||
endPointRow.addItem(endpointCell, { CSSStyles: { 'width': serviceEndpointRowEndpointCellWidth, 'min-width': serviceEndpointRowEndpointCellWidth, 'overflow': 'hidden', 'padding-left': '10px' } });
|
||||
endPointRow.addItem(endpointCell, { CSSStyles: { 'width': serviceEndpointRowEndpointCellWidth, 'min-width': serviceEndpointRowEndpointCellWidth } });
|
||||
}
|
||||
const copyValueCell = modelBuilder.button().component();
|
||||
copyValueCell.iconPath = IconPathHelper.copy;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "cms",
|
||||
"displayName": "%cms.displayName%",
|
||||
"description": "%cms.description%",
|
||||
"version": "0.4.0",
|
||||
"version": "0.5.0",
|
||||
"publisher": "Microsoft",
|
||||
"preview": true,
|
||||
"license": "https://raw.githubusercontent.com/Microsoft/azuredatastudio/master/LICENSE.txt",
|
||||
|
||||
@@ -43,13 +43,12 @@ export class CmsResourceTreeNode extends CmsResourceTreeNodeBase {
|
||||
this.connection.options.password = await this.appContext.cmsUtils.getPassword(this.connection.options.user);
|
||||
}
|
||||
}
|
||||
return this.appContext.cmsUtils.createCmsServer(this.connection, this.name, this.description).then((result) => {
|
||||
// cache new connection is different from old one
|
||||
if (this.appContext.cmsUtils.didConnectionChange(this._connection, result.connection)) {
|
||||
this._connection = result.connection;
|
||||
this._ownerUri = result.ownerUri;
|
||||
this.appContext.cmsUtils.cacheRegisteredCmsServer(this.name, this.description, this.ownerUri, this.connection);
|
||||
}
|
||||
return this.appContext.cmsUtils.createCmsServer(this.connection, this.name, this.description).then(async (result) => {
|
||||
// update the owner uri and the connection
|
||||
this._ownerUri = result.ownerUri;
|
||||
this._connection = result.connection;
|
||||
await this.appContext.cmsUtils.cacheRegisteredCmsServer(this.name, this.description, this.ownerUri, this.connection);
|
||||
|
||||
if (result.listRegisteredServersResult.registeredServersList) {
|
||||
result.listRegisteredServersResult.registeredServersList.forEach((registeredServer) => {
|
||||
nodes.push(new RegisteredServerTreeNode(
|
||||
|
||||
@@ -46,7 +46,7 @@ export class CmsResourceTreeProvider implements TreeDataProvider<TreeNode>, ICms
|
||||
server.ownerUri,
|
||||
server.connection,
|
||||
this._appContext, this, null));
|
||||
this.appContext.cmsUtils.cacheRegisteredCmsServer(server.name, server.description,
|
||||
await this.appContext.cmsUtils.cacheRegisteredCmsServer(server.name, server.description,
|
||||
server.ownerUri, server.connection);
|
||||
});
|
||||
return servers;
|
||||
|
||||
@@ -126,7 +126,7 @@ export class CmsUtils {
|
||||
}
|
||||
|
||||
public async deleteCmsServer(cmsServerName: string, connection: azdata.connection.Connection): Promise<void> {
|
||||
const servers: ICmsResourceNodeInfo[] = this._memento.get('servers');
|
||||
const servers: ICmsResourceNodeInfo[] = this._memento.get('centralManagementServers');
|
||||
if (servers) {
|
||||
const newServers: ICmsResourceNodeInfo[] = servers.filter((cachedServer) => {
|
||||
return cachedServer.name !== cmsServerName;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "dacpac",
|
||||
"displayName": "SQL Server Dacpac",
|
||||
"description": "SQL Server Dacpac for Azure Data Studio.",
|
||||
"version": "0.5.0",
|
||||
"version": "0.6.0",
|
||||
"publisher": "Microsoft",
|
||||
"preview": true,
|
||||
"engines": {
|
||||
@@ -66,4 +66,4 @@
|
||||
"publisherDisplayName": "Microsoft",
|
||||
"publisherId": "Microsoft"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "import",
|
||||
"displayName": "SQL Server Import",
|
||||
"description": "SQL Server Import for Azure Data Studio supports importing CSV or JSON files into SQL Server.",
|
||||
"version": "0.9.0",
|
||||
"version": "0.11.0",
|
||||
"publisher": "Microsoft",
|
||||
"preview": true,
|
||||
"engines": {
|
||||
@@ -50,6 +50,17 @@
|
||||
"group": "import"
|
||||
}
|
||||
]
|
||||
},
|
||||
"configuration":{
|
||||
"type": "object",
|
||||
"title": "%flatfileimport.configuration.title%",
|
||||
"properties": {
|
||||
"flatFileImport.logDebugInfo": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "%flatfileimport.logDebugInfo%"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -65,4 +76,4 @@
|
||||
"publisherDisplayName": "Microsoft",
|
||||
"publisherId": "Microsoft"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
4
extensions/import/package.nls.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"flatfileimport.configuration.title": "Flat File Import configuration",
|
||||
"flatfileimport.logDebugInfo": "[Optional] Log debug output to the console (View -> Output) and then select appropriate output channel from the dropdown"
|
||||
}
|
||||
@@ -44,7 +44,7 @@ export class ServiceClient {
|
||||
return new Promise((resolve, reject) => {
|
||||
serverdownloader.getOrDownloadServer().then(e => {
|
||||
const installationComplete = Date.now();
|
||||
let serverOptions = this.generateServerOptions(e);
|
||||
let serverOptions = this.generateServerOptions(e, context);
|
||||
client = new SqlOpsDataClient(Constants.serviceName, serverOptions, clientOptions);
|
||||
const processStart = Date.now();
|
||||
client.onReady().then(() => {
|
||||
@@ -90,10 +90,10 @@ export class ServiceClient {
|
||||
};
|
||||
}
|
||||
|
||||
private generateServerOptions(executablePath: string): ServerOptions {
|
||||
private generateServerOptions(executablePath: string, context: vscode.ExtensionContext): ServerOptions {
|
||||
let launchArgs = [];
|
||||
launchArgs.push('--log-dir');
|
||||
let logFileLocation = path.join(serviceUtils.getDefaultLogLocation(), 'flatfileimport');
|
||||
let logFileLocation = context.logPath;
|
||||
launchArgs.push(logFileLocation);
|
||||
let config = vscode.workspace.getConfiguration(Constants.extensionConfigSectionName);
|
||||
if (config) {
|
||||
|
||||
@@ -20,10 +20,6 @@ export function getAppDataPath(): string {
|
||||
}
|
||||
}
|
||||
|
||||
export function getDefaultLogLocation(): string {
|
||||
return path.join(getAppDataPath(), 'azuredatastudio');
|
||||
}
|
||||
|
||||
export function ensure(target: object, key: string): any {
|
||||
if (target[key] === void 0) {
|
||||
target[key] = {} as any;
|
||||
|
||||
@@ -417,6 +417,7 @@
|
||||
},
|
||||
{
|
||||
"name": "%title.books%",
|
||||
"when": "notebookQuality != stable",
|
||||
"row": 0,
|
||||
"col": 2,
|
||||
"colspan": 1,
|
||||
|
||||
@@ -306,7 +306,7 @@
|
||||
"body": [
|
||||
"-- Create a nonclustered index with or without a unique constraint",
|
||||
"-- Or create a clustered index on table '[${1:TableName}]' in schema '[${2:dbo}]' in database '[${3:DatabaseName}]'",
|
||||
"CREATE ${5:/*UNIQUE or CLUSTERED*/} INDEX IX_${4:IndexName} ON [${3:DatabaseName}].[${2:dbo}].[${1:TableName}] ([${6:ColumnName1}] DESC /*Change sort order as needed*/",
|
||||
"CREATE ${5:/*UNIQUE or CLUSTERED*/} INDEX IX_${4:IndexName} ON [${3:DatabaseName}].[${2:dbo}].[${1:TableName}] ([${6:ColumnName1}] DESC) /*Change sort order as needed*/",
|
||||
"GO"
|
||||
],
|
||||
"description": "Create a new Index"
|
||||
|
||||
@@ -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.17",
|
||||
"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/deletematerializednotebook');
|
||||
}
|
||||
|
||||
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');
|
||||
@@ -528,3 +631,26 @@ export namespace SerializeDataContinueRequest {
|
||||
export const type = new RequestType<azdata.SerializeDataContinueRequestParams, azdata.SerializeDataResult, void, void>('serialize/continue');
|
||||
}
|
||||
// ------------------------------- <Serialization> -----------------------------
|
||||
|
||||
// ------------------------------- < Load Completion Extension Request > ------------------------------------
|
||||
/**
|
||||
* Completion extension load parameters
|
||||
*/
|
||||
export class CompletionExtensionParams {
|
||||
/// <summary>
|
||||
/// Absolute path for the assembly containing the completion extension
|
||||
/// </summary>
|
||||
public assemblyPath: string;
|
||||
/// <summary>
|
||||
/// The type name for the completion extension
|
||||
/// </summary>
|
||||
public typeName: string;
|
||||
/// <summary>
|
||||
/// Property bag for initializing the completion extension
|
||||
/// </summary>
|
||||
public properties: {};
|
||||
}
|
||||
|
||||
export namespace CompletionExtLoadRequest {
|
||||
export const type = new RequestType<CompletionExtensionParams, boolean, void, void>('completion/extLoad');
|
||||
}
|
||||
|
||||
@@ -60,16 +60,28 @@ export function registerServiceEndpoints(context: vscode.ExtensionContext): void
|
||||
|
||||
const container = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'column', width: '100%', height: '100%', alignItems: 'left' }).component();
|
||||
endpointsArray.forEach(endpointInfo => {
|
||||
|
||||
const endPointRow = view.modelBuilder.flexContainer().withLayout({ flexFlow: 'row' }).component();
|
||||
const nameCell = view.modelBuilder.text().withProperties<azdata.TextComponentProperties>({ value: endpointInfo.description }).component();
|
||||
endPointRow.addItem(nameCell, { CSSStyles: { 'width': '35%', 'font-weight': '600', 'user-select': 'text' } });
|
||||
if (hyperlinkedEndpoints.findIndex(e => e === endpointInfo.serviceName) >= 0) {
|
||||
const linkCell = view.modelBuilder.hyperlink().withProperties<azdata.HyperlinkComponentProperties>({ label: endpointInfo.endpoint, url: endpointInfo.endpoint }).component();
|
||||
endPointRow.addItem(linkCell, { CSSStyles: { 'width': '62%', 'color': '#0078d4', 'text-decoration': 'underline', 'padding-top': '10px' } });
|
||||
const linkCell = view.modelBuilder.hyperlink()
|
||||
.withProperties<azdata.HyperlinkComponentProperties>({
|
||||
label: endpointInfo.endpoint,
|
||||
title: endpointInfo.endpoint,
|
||||
url: endpointInfo.endpoint
|
||||
}).component();
|
||||
endPointRow.addItem(linkCell, { CSSStyles: { 'width': '62%', 'color': '#0078d4', 'text-decoration': 'underline', 'padding-top': '10px', 'overflow': 'hidden', 'text-overflow': 'ellipsis' } });
|
||||
}
|
||||
else {
|
||||
const endpointCell = view.modelBuilder.text().withProperties<azdata.TextComponentProperties>({ value: endpointInfo.endpoint }).component();
|
||||
const endpointCell =
|
||||
view.modelBuilder.text()
|
||||
.withProperties<azdata.TextComponentProperties>(
|
||||
{
|
||||
value: endpointInfo.endpoint,
|
||||
title: endpointInfo.endpoint,
|
||||
CSSStyles: { 'overflow': 'hidden', 'text-overflow': 'ellipsis' }
|
||||
})
|
||||
.component();
|
||||
endPointRow.addItem(endpointCell, { CSSStyles: { 'width': '62%', 'user-select': 'text' } });
|
||||
}
|
||||
const copyValueCell = view.modelBuilder.button().component();
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -19,6 +19,7 @@ import { SchemaCompareService } from './schemaCompare/schemaCompareService';
|
||||
import { AppContext } from './appContext';
|
||||
import { DacFxService } from './dacfx/dacFxService';
|
||||
import { CmsService } from './cms/cmsService';
|
||||
import { CompletionExtensionParams, CompletionExtLoadRequest } from './contracts';
|
||||
|
||||
const baseConfig = require('./config.json');
|
||||
|
||||
@@ -46,6 +47,9 @@ export class SqlToolsServer {
|
||||
setTimeout(() => {
|
||||
statusView.hide();
|
||||
}, 1500);
|
||||
vscode.commands.registerCommand('mssql.loadCompletionExtension', (params: CompletionExtensionParams) => {
|
||||
this.client.sendRequest(CompletionExtLoadRequest.type, params);
|
||||
});
|
||||
Telemetry.sendTelemetryEvent('startup/LanguageClientStarted', {
|
||||
installationTime: String(installationComplete - installationStart),
|
||||
processStartupTime: String(processEnd - processStart),
|
||||
|
||||
@@ -153,6 +153,13 @@
|
||||
"configuration": "./language-configuration.json"
|
||||
}
|
||||
],
|
||||
"grammars": [
|
||||
{
|
||||
"language": "notebook",
|
||||
"scopeName": "source.notebook",
|
||||
"path": "./syntaxes/notebook.tmLanguage.json"
|
||||
}
|
||||
],
|
||||
"menus": {
|
||||
"commandPalette": [
|
||||
{
|
||||
@@ -362,7 +369,8 @@
|
||||
"books-explorer": [
|
||||
{
|
||||
"id": "bookTreeView",
|
||||
"name": "Books"
|
||||
"name": "Books",
|
||||
"when": "notebookQuality != stable"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
213
extensions/notebook/syntaxes/notebook.tmLanguage.json
Normal file
@@ -0,0 +1,213 @@
|
||||
{
|
||||
"information_for_contributors": [
|
||||
"This file has been converted from https://github.com/Microsoft/vscode-JSON.tmLanguage/blob/master/JSON.tmLanguage",
|
||||
"If you want to provide a fix or improvement, please create a pull request against the original repository.",
|
||||
"Once accepted there, we are happy to receive an update request."
|
||||
],
|
||||
"version": "https://github.com/Microsoft/vscode-JSON.tmLanguage/commit/9bd83f1c252b375e957203f21793316203f61f70",
|
||||
"name": "notebook",
|
||||
"scopeName": "source.notebook",
|
||||
"patterns": [
|
||||
{
|
||||
"include": "#value"
|
||||
}
|
||||
],
|
||||
"repository": {
|
||||
"array": {
|
||||
"begin": "\\[",
|
||||
"beginCaptures": {
|
||||
"0": {
|
||||
"name": "punctuation.definition.array.begin.notebook"
|
||||
}
|
||||
},
|
||||
"end": "\\]",
|
||||
"endCaptures": {
|
||||
"0": {
|
||||
"name": "punctuation.definition.array.end.notebook"
|
||||
}
|
||||
},
|
||||
"name": "meta.structure.array.notebook",
|
||||
"patterns": [
|
||||
{
|
||||
"include": "#value"
|
||||
},
|
||||
{
|
||||
"match": ",",
|
||||
"name": "punctuation.separator.array.notebook"
|
||||
},
|
||||
{
|
||||
"match": "[^\\s\\]]",
|
||||
"name": "invalid.illegal.expected-array-separator.notebook"
|
||||
}
|
||||
]
|
||||
},
|
||||
"comments": {
|
||||
"patterns": [
|
||||
{
|
||||
"begin": "/\\*\\*(?!/)",
|
||||
"captures": {
|
||||
"0": {
|
||||
"name": "punctuation.definition.comment.notebook"
|
||||
}
|
||||
},
|
||||
"end": "\\*/",
|
||||
"name": "comment.block.documentation.notebook"
|
||||
},
|
||||
{
|
||||
"begin": "/\\*",
|
||||
"captures": {
|
||||
"0": {
|
||||
"name": "punctuation.definition.comment.notebook"
|
||||
}
|
||||
},
|
||||
"end": "\\*/",
|
||||
"name": "comment.block.notebook"
|
||||
},
|
||||
{
|
||||
"captures": {
|
||||
"1": {
|
||||
"name": "punctuation.definition.comment.notebook"
|
||||
}
|
||||
},
|
||||
"match": "(//).*$\\n?",
|
||||
"name": "comment.line.double-slash.js"
|
||||
}
|
||||
]
|
||||
},
|
||||
"constant": {
|
||||
"match": "\\b(?:true|false|null)\\b",
|
||||
"name": "constant.language.notebook"
|
||||
},
|
||||
"number": {
|
||||
"match": "(?x) # turn on extended mode\n -? # an optional minus\n (?:\n 0 # a zero\n | # ...or...\n [1-9] # a 1-9 character\n \\d* # followed by zero or more digits\n )\n (?:\n (?:\n \\. # a period\n \\d+ # followed by one or more digits\n )?\n (?:\n [eE] # an e character\n [+-]? # followed by an option +/-\n \\d+ # followed by one or more digits\n )? # make exponent optional\n )? # make decimal portion optional",
|
||||
"name": "constant.numeric.notebook"
|
||||
},
|
||||
"object": {
|
||||
"begin": "\\{",
|
||||
"beginCaptures": {
|
||||
"0": {
|
||||
"name": "punctuation.definition.dictionary.begin.notebook"
|
||||
}
|
||||
},
|
||||
"end": "\\}",
|
||||
"endCaptures": {
|
||||
"0": {
|
||||
"name": "punctuation.definition.dictionary.end.notebook"
|
||||
}
|
||||
},
|
||||
"name": "meta.structure.dictionary.notebook",
|
||||
"patterns": [
|
||||
{
|
||||
"comment": "the notebook object key",
|
||||
"include": "#objectkey"
|
||||
},
|
||||
{
|
||||
"include": "#comments"
|
||||
},
|
||||
{
|
||||
"begin": ":",
|
||||
"beginCaptures": {
|
||||
"0": {
|
||||
"name": "punctuation.separator.dictionary.key-value.notebook"
|
||||
}
|
||||
},
|
||||
"end": "(,)|(?=\\})",
|
||||
"endCaptures": {
|
||||
"1": {
|
||||
"name": "punctuation.separator.dictionary.pair.notebook"
|
||||
}
|
||||
},
|
||||
"name": "meta.structure.dictionary.value.notebook",
|
||||
"patterns": [
|
||||
{
|
||||
"comment": "the notebook object value",
|
||||
"include": "#value"
|
||||
},
|
||||
{
|
||||
"match": "[^\\s,]",
|
||||
"name": "invalid.illegal.expected-dictionary-separator.notebook"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"match": "[^\\s\\}]",
|
||||
"name": "invalid.illegal.expected-dictionary-separator.notebook"
|
||||
}
|
||||
]
|
||||
},
|
||||
"string": {
|
||||
"begin": "\"",
|
||||
"beginCaptures": {
|
||||
"0": {
|
||||
"name": "punctuation.definition.string.begin.notebook"
|
||||
}
|
||||
},
|
||||
"end": "\"",
|
||||
"endCaptures": {
|
||||
"0": {
|
||||
"name": "punctuation.definition.string.end.notebook"
|
||||
}
|
||||
},
|
||||
"name": "string.quoted.double.notebook",
|
||||
"patterns": [
|
||||
{
|
||||
"include": "#stringcontent"
|
||||
}
|
||||
]
|
||||
},
|
||||
"objectkey": {
|
||||
"begin": "\"",
|
||||
"beginCaptures": {
|
||||
"0": {
|
||||
"name": "punctuation.support.type.property-name.begin.notebook"
|
||||
}
|
||||
},
|
||||
"end": "\"",
|
||||
"endCaptures": {
|
||||
"0": {
|
||||
"name": "punctuation.support.type.property-name.end.notebook"
|
||||
}
|
||||
},
|
||||
"name": "string.notebook support.type.property-name.notebook",
|
||||
"patterns": [
|
||||
{
|
||||
"include": "#stringcontent"
|
||||
}
|
||||
]
|
||||
},
|
||||
"stringcontent": {
|
||||
"patterns": [
|
||||
{
|
||||
"match": "(?x) # turn on extended mode\n \\\\ # a literal backslash\n (?: # ...followed by...\n [\"\\\\/bfnrt] # one of these characters\n | # ...or...\n u # a u\n [0-9a-fA-F]{4}) # and four hex digits",
|
||||
"name": "constant.character.escape.notebook"
|
||||
},
|
||||
{
|
||||
"match": "\\\\.",
|
||||
"name": "invalid.illegal.unrecognized-string-escape.notebook"
|
||||
}
|
||||
]
|
||||
},
|
||||
"value": {
|
||||
"patterns": [
|
||||
{
|
||||
"include": "#constant"
|
||||
},
|
||||
{
|
||||
"include": "#number"
|
||||
},
|
||||
{
|
||||
"include": "#string"
|
||||
},
|
||||
{
|
||||
"include": "#array"
|
||||
},
|
||||
{
|
||||
"include": "#object"
|
||||
},
|
||||
{
|
||||
"include": "#comments"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
1
extensions/query-history/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
*.vsix
|
||||
5
extensions/query-history/.vscodeignore
Normal file
@@ -0,0 +1,5 @@
|
||||
src/**
|
||||
.gitignore
|
||||
tsconfig.json
|
||||
cgmanifest.json
|
||||
.vscode
|
||||
50
extensions/query-history/README.md
Normal file
@@ -0,0 +1,50 @@
|
||||
# Query History *(preview)*
|
||||
|
||||
Adds a Query History panel for viewing and running past executed queries.
|
||||
|
||||
### How do I view the history?
|
||||
|
||||
Query History is displayed as a tab in the tab panel, which is toggled by the *View: Toggle Panel* command.
|
||||
|
||||

|
||||
|
||||
Initially this view will be empty but once you execute a query editor that will be captured in the window - with a separate row displayed for every query executed.
|
||||
|
||||

|
||||
|
||||
Each row consists of 3 parts :
|
||||
1. Status icon : This will be a ✔️ if the query executed successfully. If any errors occurred a ❌is shown.
|
||||
2. Query Text : This is the text of the query that was executed
|
||||
3. Connection Info : The Server and Database the query was executed against
|
||||
4. Timestamp : The date and time the query was executed
|
||||
|
||||
### Query History row actions
|
||||
|
||||
Right clicking on a history row will bring up a menu with a number of actions available.
|
||||
|
||||

|
||||
|
||||
#### Open Query
|
||||
|
||||
This will open a new query editor window populated with the query text from the query executed and using the connection of that query.
|
||||
|
||||
#### Run Query
|
||||
|
||||
This will do the same thing as Open Query but will additionally run the statement immediately.
|
||||
|
||||
#### Delete
|
||||
|
||||
This will permanently delete the row from the view.
|
||||
|
||||
## Code of Conduct
|
||||
This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.
|
||||
|
||||
## Privacy Statement
|
||||
|
||||
The [Microsoft Enterprise and Developer Privacy Statement](https://privacy.microsoft.com/en-us/privacystatement) describes the privacy statement of this software.
|
||||
|
||||
## License
|
||||
|
||||
Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
|
||||
Licensed under the [Source EULA](https://raw.githubusercontent.com/Microsoft/azuredatastudio/master/LICENSE.txt).
|
||||
BIN
extensions/query-history/images/QueryHistoryActionMenu.PNG
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
extensions/query-history/images/QueryHistoryTab.PNG
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
extensions/query-history/images/QueryHistoryTabWithQueries.PNG
Normal file
|
After Width: | Height: | Size: 7.5 KiB |
BIN
extensions/query-history/images/sqlserver.png
Normal file
|
After Width: | Height: | Size: 37 KiB |
54
extensions/query-history/package.json
Normal file
@@ -0,0 +1,54 @@
|
||||
{
|
||||
"name": "query-history",
|
||||
"displayName": "%queryHistory.displayName%",
|
||||
"description": "%queryHistory.description%",
|
||||
"version": "0.1.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.30.1",
|
||||
"azdata": ">=1.12.0"
|
||||
},
|
||||
"activationEvents": [
|
||||
"*"
|
||||
],
|
||||
"main": "./out/main",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Microsoft/azuredatastudio.git"
|
||||
},
|
||||
"extensionDependencies": [
|
||||
],
|
||||
"contributes": {
|
||||
"commands": [
|
||||
{
|
||||
"command": "queryHistory.clear",
|
||||
"title": "%queryHistory.clear%",
|
||||
"category": "%queryHistory.displayName%"
|
||||
},
|
||||
{
|
||||
"command": "queryHistory.toggleCapture",
|
||||
"title": "%queryHistory.toggleCapture%",
|
||||
"category": "%queryHistory.displayName%"
|
||||
}
|
||||
],
|
||||
"menus": {
|
||||
"commandPalette": [
|
||||
{
|
||||
"command": "queryHistory.clear"
|
||||
},
|
||||
{
|
||||
"command": "queryHistory.toggleCapture"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
},
|
||||
"devDependencies": {
|
||||
"vscode": "1.0.1"
|
||||
}
|
||||
}
|
||||
6
extensions/query-history/package.nls.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"queryHistory.displayName": "Query History",
|
||||
"queryHistory.description": "View and run previously executed queries",
|
||||
"queryHistory.clear": "Clear All History",
|
||||
"queryHistory.toggleCapture": "Toggle Query History Capture"
|
||||
}
|
||||
17
extensions/query-history/src/main.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
export async function activate(context: vscode.ExtensionContext): Promise<void> {
|
||||
// Currently all the functionality for this is contained within the core ADS
|
||||
// code as the extensibility API doesn't currently support all the required
|
||||
// functionality (such as contributing tab panels)
|
||||
vscode.commands.executeCommand('queryHistory.enableQueryHistory');
|
||||
}
|
||||
|
||||
export async function deactivate(): Promise<void> {
|
||||
|
||||
}
|
||||
6
extensions/query-history/src/typings/ref.d.ts
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
/// <reference path='../../../../src/vs/vscode.d.ts'/>
|
||||
21
extensions/query-history/tsconfig.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"compileOnSave": true,
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es6",
|
||||
"outDir": "./out",
|
||||
"lib": [
|
||||
"es6", "es2015.promise"
|
||||
],
|
||||
"typeRoots": [
|
||||
"./node_modules/@types"
|
||||
],
|
||||
"sourceMap": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"moduleResolution": "node"
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
2307
extensions/query-history/yarn.lock
Normal file
@@ -23,7 +23,13 @@
|
||||
["\"", "\""],
|
||||
["'", "'"],
|
||||
["`", "`"]
|
||||
]
|
||||
],
|
||||
"folding": {
|
||||
"markers": {
|
||||
"start": "^\\s*--\\s*#region\\s*.*$",
|
||||
"end": "^\\s*--\\s*#endregion\\s*.*$"
|
||||
}
|
||||
}
|
||||
|
||||
// enhancedBrackets:[
|
||||
// { openTrigger: 'n', open: /begin$/i, closeComplete: 'end', matchCase: true },
|
||||
@@ -31,4 +37,4 @@
|
||||
// { openTrigger: 'n', open: /when$/i, closeComplete: 'then', matchCase: true }
|
||||
// ],
|
||||
// noindentBrackets: '()',
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
"Programming Languages"
|
||||
],
|
||||
"activationEvents": [
|
||||
"onLanguage:sql",
|
||||
"onLanguage:xml"
|
||||
],
|
||||
"scripts": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "azuredatastudio",
|
||||
"version": "1.11.0",
|
||||
"version": "1.12.1",
|
||||
"distro": "8c3e97e3425cc9814496472ab73e076de2ba99ee",
|
||||
"author": {
|
||||
"name": "Microsoft Corporation"
|
||||
|
||||
BIN
resources/win32/sql-big.bmp
Normal file
|
After Width: | Height: | Size: 201 KiB |
BIN
resources/win32/sql-small.bmp
Normal file
|
After Width: | Height: | Size: 12 KiB |
14301
samples/serverReports/package-lock.json
generated
@@ -1,236 +1,404 @@
|
||||
{
|
||||
"name": "server-report",
|
||||
"displayName": "Server Reports",
|
||||
"description": "Server Reports",
|
||||
"version": "0.1.5",
|
||||
"publisher": "Microsoft",
|
||||
"preview": true,
|
||||
"engines": {
|
||||
"vscode": "^1.26.0",
|
||||
"azdata": "*"
|
||||
},
|
||||
"icon": "images/sqlserver.png",
|
||||
"license": "SEE LICENSE IN LICENSE.txt",
|
||||
"repository": "https://github.com/Microsoft/azuredatastudio",
|
||||
"categories": [
|
||||
"Other"
|
||||
],
|
||||
"activationEvents": [
|
||||
"*"
|
||||
],
|
||||
"main": "./out/src/extension",
|
||||
"contributes": {
|
||||
"configuration": [],
|
||||
"commands": [],
|
||||
"views": {},
|
||||
"menus": {},
|
||||
"dashboard.tabs": [
|
||||
{
|
||||
"id": "Server-Reports",
|
||||
"title": "Server Reports",
|
||||
"description": "This extension shows useful reports for a server.",
|
||||
"container": {
|
||||
"nav-section": [
|
||||
{
|
||||
"id": "server-reports-monitoring",
|
||||
"title": "Monitor",
|
||||
"icon": {
|
||||
"light": "./out/src/media/monitor.svg",
|
||||
"dark": "./out/src/media/monitor_inverse.svg"
|
||||
},
|
||||
"container": {
|
||||
"server-reports-monitoring-container": {}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "server-reports-performance",
|
||||
"title": "Performance",
|
||||
"icon": {
|
||||
"light": "./out/src/media/performance.svg",
|
||||
"dark": "./out/src/media/performance_inverse.svg"
|
||||
},
|
||||
"container": {
|
||||
"server-reports-performance-container": {}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"dashboard.insights": [
|
||||
{
|
||||
"id": "extension-dbspace-usage",
|
||||
"contrib": {
|
||||
"type": {
|
||||
"horizontalBar": {
|
||||
"dataDirection": "vertical",
|
||||
"dataType": "number",
|
||||
"legendPosition": "top",
|
||||
"labelFirstColumn": false,
|
||||
"columnsAsLabels": true
|
||||
}
|
||||
},
|
||||
"queryFile": "./out/src/sql/all_db_space_used.sql"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "extension-cpu-utilization",
|
||||
"contrib": {
|
||||
"type": {
|
||||
"timeSeries": {
|
||||
"dataDirection": "horizontal",
|
||||
"dataType": "point",
|
||||
"legendPosition": "top",
|
||||
"labelFirstColumn": false,
|
||||
"columnsAsLabels": false
|
||||
}
|
||||
},
|
||||
"queryFile": "./out/src/sql/cpumetric.sql"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "extension-backup-growth-trend",
|
||||
"details": "Abbie wants it",
|
||||
"contrib": {
|
||||
"type": {
|
||||
"timeSeries": {
|
||||
"dataDirection": "horizontal",
|
||||
"dataType": "point",
|
||||
"legendPosition": "none",
|
||||
"labelFirstColumn": false,
|
||||
"columnsAsLabels": false
|
||||
}
|
||||
},
|
||||
"queryFile": "./out/src/sql/backup_size_trend.sql"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "extension-wait-counts-by-Paul-Randal",
|
||||
"contrib": {
|
||||
"type": {
|
||||
"horizontalBar": {
|
||||
"dataDirection": "vertical",
|
||||
"dataType": "number",
|
||||
"legendPosition": "none",
|
||||
"labelFirstColumn": false,
|
||||
"columnsAsLabels": true
|
||||
}
|
||||
},
|
||||
"queryFile": "./out/src/sql/waits_paul_randal.sql",
|
||||
"details": {
|
||||
"queryFile": "./out/src/sql/waits_detail_paul_randal.sql",
|
||||
"label": {
|
||||
"column": "WaitType",
|
||||
"state": []
|
||||
},
|
||||
"value": "Percentage"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "extension-dbbuffer-usage",
|
||||
"contrib": {
|
||||
"type": {
|
||||
"horizontalBar": {
|
||||
"dataDirection": "vertical",
|
||||
"dataType": "number",
|
||||
"legendPosition": "top",
|
||||
"labelFirstColumn": false,
|
||||
"columnsAsLabels": true
|
||||
}
|
||||
},
|
||||
"queryFile": "./out/src/sql/memorybydb.sql"
|
||||
}
|
||||
}
|
||||
],
|
||||
"dashboard.containers": [
|
||||
{
|
||||
"id": "server-reports-monitoring-container",
|
||||
"container": {
|
||||
"widgets-container": [
|
||||
{
|
||||
"name": "Top 10 DB Space Usage",
|
||||
"gridItemConfig": {
|
||||
"sizex": 2,
|
||||
"sizey": 2
|
||||
},
|
||||
"widget": {
|
||||
"extension-dbspace-usage": {}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Top 10 DB Buffer Usage",
|
||||
"gridItemConfig": {
|
||||
"sizex": 2,
|
||||
"sizey": 2
|
||||
},
|
||||
"widget": {
|
||||
"extension-dbbuffer-usage": {}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "CPU Utilization",
|
||||
"gridItemConfig": {
|
||||
"sizex": 2,
|
||||
"sizey": 1
|
||||
},
|
||||
"widget": {
|
||||
"extension-cpu-utilization": {}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Backup Growth Trend",
|
||||
"gridItemConfig": {
|
||||
"sizex": 2,
|
||||
"sizey": 1
|
||||
},
|
||||
"widget": {
|
||||
"extension-backup-growth-trend": {}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "server-reports-performance-container",
|
||||
"container": {
|
||||
"widgets-container": [
|
||||
{
|
||||
"name": "Wait Counts by Paul Randal",
|
||||
"gridItemConfig": {
|
||||
"sizex": 2,
|
||||
"sizey": 2
|
||||
},
|
||||
"widget": {
|
||||
"extension-wait-counts-by-Paul-Randal": {}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"snippets": []
|
||||
},
|
||||
"scripts": {
|
||||
"build": "gulp build",
|
||||
"compile": "gulp compile",
|
||||
"watch": "gulp watch",
|
||||
"postinstall": "node ./node_modules/vscode/bin/install"
|
||||
},
|
||||
"dependencies": {},
|
||||
"devDependencies": {
|
||||
"child-process-promise": "^2.2.1",
|
||||
"del": "^3.0.0",
|
||||
"gulp": "^4.0.0",
|
||||
"gulp-color": "0.0.1",
|
||||
"gulp-sourcemaps": "^2.6.4",
|
||||
"gulp-tslint": "^6.0.2",
|
||||
"gulp-typescript": "^3.2.4",
|
||||
"should": "^13.2.1",
|
||||
"tslint": "^3.14.0",
|
||||
"typemoq": "^2.1.0",
|
||||
"typescript": "^2.6.1",
|
||||
"vsce": "1.36.2",
|
||||
"vscode": "^1.1.6"
|
||||
}
|
||||
}
|
||||
"name": "server-report",
|
||||
"displayName": "Server Reports",
|
||||
"description": "Server Reports",
|
||||
"version": "0.1.5",
|
||||
"publisher": "Microsoft",
|
||||
"preview": true,
|
||||
"engines": {
|
||||
"vscode": "^1.26.0",
|
||||
"azdata": "*"
|
||||
},
|
||||
"icon": "images/sqlserver.png",
|
||||
"license": "SEE LICENSE IN LICENSE.txt",
|
||||
"repository": "https://github.com/Microsoft/azuredatastudio",
|
||||
"categories": [
|
||||
"Other"
|
||||
],
|
||||
"activationEvents": [
|
||||
"*"
|
||||
],
|
||||
"main": "./out/src/extension",
|
||||
"contributes": {
|
||||
"commands": [
|
||||
{
|
||||
"command": "tempdb.startEvent",
|
||||
"title": "Start",
|
||||
"icon": {
|
||||
"light": "./out/src/media/launch.svg",
|
||||
"dark": "./out/src/media/launch_inverse.svg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"command": "tempdb.stopEvent",
|
||||
"title": "Stop",
|
||||
"icon": {
|
||||
"light": "./out/src/media/blocker.svg",
|
||||
"dark": "./out/src/media/blocker_inverse.svg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"command": "tempdb.contention",
|
||||
"title": "Contention Help for Tempdb",
|
||||
"icon": {
|
||||
"light": "./out/src/media/documentation.svg",
|
||||
"dark": "./out/src/media/documentation_inverse.svg"
|
||||
}
|
||||
},
|
||||
{
|
||||
"command": "tempdb.pauseEvent",
|
||||
"title": "Pause",
|
||||
"icon": {
|
||||
"light": "./out/src/media/insights.svg",
|
||||
"dark": "./out/src/media/insights_inverse.svg"
|
||||
}
|
||||
}
|
||||
],
|
||||
"configuration": [],
|
||||
"views": {},
|
||||
"menus": {},
|
||||
"dashboard.tabs": [
|
||||
{
|
||||
"id": "Server-Reports",
|
||||
"title": "Server Reports",
|
||||
"description": "This extension shows useful reports for a server.",
|
||||
"container": {
|
||||
"nav-section": [
|
||||
{
|
||||
"id": "server-reports-monitoring",
|
||||
"title": "Monitor",
|
||||
"icon": {
|
||||
"light": "./out/src/media/monitor.svg",
|
||||
"dark": "./out/src/media/monitor_inverse.svg"
|
||||
},
|
||||
"container": {
|
||||
"server-reports-monitoring-container": {}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "server-reports-performance",
|
||||
"title": "Performance",
|
||||
"icon": {
|
||||
"light": "./out/src/media/performance.svg",
|
||||
"dark": "./out/src/media/performance_inverse.svg"
|
||||
},
|
||||
"container": {
|
||||
"server-reports-performance-container": {}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "server-reports-tempdb",
|
||||
"title": "TempDB",
|
||||
"container": {
|
||||
"server-reports-tempdb-container": {}
|
||||
},
|
||||
"icon": {
|
||||
"light": "./out/src/media/tempdb.svg",
|
||||
"dark": "./out/src/media/tempdb_inverse.svg"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"dashboard.insights": [
|
||||
{
|
||||
"id": "extension-dbspace-usage",
|
||||
"contrib": {
|
||||
"type": {
|
||||
"horizontalBar": {
|
||||
"dataDirection": "vertical",
|
||||
"dataType": "number",
|
||||
"legendPosition": "top",
|
||||
"labelFirstColumn": false,
|
||||
"columnsAsLabels": true
|
||||
}
|
||||
},
|
||||
"queryFile": "./out/src/sql/all_db_space_used.sql"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "extension-cpu-utilization",
|
||||
"contrib": {
|
||||
"type": {
|
||||
"timeSeries": {
|
||||
"dataDirection": "horizontal",
|
||||
"dataType": "point",
|
||||
"legendPosition": "top",
|
||||
"labelFirstColumn": false,
|
||||
"columnsAsLabels": false
|
||||
}
|
||||
},
|
||||
"queryFile": "./out/src/sql/cpumetric.sql"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "extension-backup-growth-trend",
|
||||
"details": "Abbie wants it",
|
||||
"contrib": {
|
||||
"type": {
|
||||
"timeSeries": {
|
||||
"dataDirection": "horizontal",
|
||||
"dataType": "point",
|
||||
"legendPosition": "none",
|
||||
"labelFirstColumn": false,
|
||||
"columnsAsLabels": false
|
||||
}
|
||||
},
|
||||
"queryFile": "./out/src/sql/backup_size_trend.sql"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "extension-wait-counts-by-Paul-Randal",
|
||||
"contrib": {
|
||||
"type": {
|
||||
"horizontalBar": {
|
||||
"dataDirection": "vertical",
|
||||
"dataType": "number",
|
||||
"legendPosition": "none",
|
||||
"labelFirstColumn": false,
|
||||
"columnsAsLabels": true
|
||||
}
|
||||
},
|
||||
"queryFile": "./out/src/sql/waits_paul_randal.sql",
|
||||
"details": {
|
||||
"queryFile": "./out/src/sql/waits_detail_paul_randal.sql",
|
||||
"label": {
|
||||
"column": "WaitType",
|
||||
"state": []
|
||||
},
|
||||
"value": "Percentage"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "extension-wait-resource-statistics",
|
||||
"contrib": {
|
||||
"type": {
|
||||
"bar": {
|
||||
"dataDirection": "horizontal",
|
||||
"dataType": "number",
|
||||
"legendPosition": "none",
|
||||
"labelFirstColumn": false,
|
||||
"columnsAsLabels": true
|
||||
}
|
||||
},
|
||||
"queryFile": "./out/src/sql/wait_resources.sql",
|
||||
"details": {
|
||||
"queryFile": "./out/src/sql/wait_resources.sql",
|
||||
"label": {
|
||||
"column": "WaitType",
|
||||
"state": []
|
||||
},
|
||||
"value": "Percentage"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "extension-dbbuffer-usage",
|
||||
"contrib": {
|
||||
"type": {
|
||||
"horizontalBar": {
|
||||
"dataDirection": "vertical",
|
||||
"dataType": "number",
|
||||
"legendPosition": "top",
|
||||
"labelFirstColumn": false,
|
||||
"columnsAsLabels": true
|
||||
}
|
||||
},
|
||||
"queryFile": "./out/src/sql/memorybydb.sql"
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "type-of-contention",
|
||||
"contrib": {
|
||||
"type": {
|
||||
"bar": {
|
||||
"dataDirection": "vertical",
|
||||
"columnsAsLabels": true,
|
||||
"labelFirstColumn": false,
|
||||
"legendPosition": "none"
|
||||
}
|
||||
},
|
||||
"queryFile": "./out/src/sql/typeofContentions.sql",
|
||||
"autoRefreshInterval": 0.05
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "metadata-contention",
|
||||
"contrib": {
|
||||
"type": {
|
||||
"bar": {
|
||||
"dataDirection": "vertical",
|
||||
"columnsAsLabels": true,
|
||||
"labelFirstColumn": false,
|
||||
"legendPosition": "none",
|
||||
"xAxisLabel": "Object Ids for System Tables"
|
||||
}
|
||||
},
|
||||
"queryFile": "./out/src/sql/metadataContention.sql",
|
||||
"autoRefreshInterval": 0.05
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "allocation-contention",
|
||||
"contrib": {
|
||||
"type": {
|
||||
"bar": {
|
||||
"dataDirection": "vertical",
|
||||
"columnsAsLabels": true,
|
||||
"labelFirstColumn": false,
|
||||
"legendPosition": "none",
|
||||
"xAxisLabel": "Page Types"
|
||||
}
|
||||
},
|
||||
"queryFile": "./out/src/sql/allocationContention.sql",
|
||||
"autoRefreshInterval": 0.05
|
||||
}
|
||||
}
|
||||
],
|
||||
"dashboard.containers": [
|
||||
{
|
||||
"id": "server-reports-monitoring-container",
|
||||
"container": {
|
||||
"widgets-container": [
|
||||
{
|
||||
"name": "Top 10 DB Space Usage",
|
||||
"gridItemConfig": {
|
||||
"sizex": 2,
|
||||
"sizey": 2
|
||||
},
|
||||
"widget": {
|
||||
"extension-dbspace-usage": {}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Top 10 DB Buffer Usage",
|
||||
"gridItemConfig": {
|
||||
"sizex": 2,
|
||||
"sizey": 2
|
||||
},
|
||||
"widget": {
|
||||
"extension-dbbuffer-usage": {}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "CPU Utilization",
|
||||
"gridItemConfig": {
|
||||
"sizex": 2,
|
||||
"sizey": 1
|
||||
},
|
||||
"widget": {
|
||||
"extension-cpu-utilization": {}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Backup Growth Trend",
|
||||
"gridItemConfig": {
|
||||
"sizex": 2,
|
||||
"sizey": 1
|
||||
},
|
||||
"widget": {
|
||||
"extension-backup-growth-trend": {}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "server-reports-performance-container",
|
||||
"container": {
|
||||
"widgets-container": [
|
||||
{
|
||||
"name": "Wait Counts by Paul Randal",
|
||||
"gridItemConfig": {
|
||||
"sizex": 2,
|
||||
"sizey": 2
|
||||
},
|
||||
"widget": {
|
||||
"extension-wait-counts-by-Paul-Randal": {}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": "server-reports-tempdb-container",
|
||||
"container": {
|
||||
"widgets-container": [
|
||||
{
|
||||
"name": "Tasks",
|
||||
"widget": {
|
||||
"tasks-widget": [
|
||||
"tempdb.startEvent",
|
||||
"tempdb.contention",
|
||||
"tempdb.pauseEvent",
|
||||
"tempdb.stopEvent"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Overall Contention",
|
||||
"gridItemConfig": {
|
||||
"sizex": 2,
|
||||
"sizey": 1
|
||||
},
|
||||
"widget": {
|
||||
"type-of-contention": {}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Metadata Contention",
|
||||
"gridItemConfig": {
|
||||
"sizex": 2,
|
||||
"sizey": 1
|
||||
},
|
||||
"widget": {
|
||||
"metadata-contention": {}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Allocation Contention",
|
||||
"gridItemConfig": {
|
||||
"sizex": 2,
|
||||
"sizey": 1
|
||||
},
|
||||
"widget": {
|
||||
"allocation-contention": {}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"snippets": []
|
||||
},
|
||||
"scripts": {
|
||||
"build": "gulp build",
|
||||
"compile": "gulp compile",
|
||||
"watch": "gulp watch",
|
||||
"postinstall": "node ./node_modules/vscode/bin/install"
|
||||
},
|
||||
"dependencies": {
|
||||
"fs": "0.0.1-security",
|
||||
"fs-extra": "^8.1.0",
|
||||
"generator-sqlops": "^0.10.4",
|
||||
"handlebars": "^4.1.2",
|
||||
"openurl": "^1.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"child-process-promise": "^2.2.1",
|
||||
"del": "^3.0.0",
|
||||
"gulp": "^4.0.0",
|
||||
"gulp-color": "0.0.1",
|
||||
"gulp-sourcemaps": "^2.6.4",
|
||||
"gulp-tslint": "^6.0.2",
|
||||
"gulp-typescript": "^3.2.4",
|
||||
"should": "^13.2.1",
|
||||
"tslint": "^3.14.0",
|
||||
"typemoq": "^2.1.0",
|
||||
"typescript": "^2.9.2",
|
||||
"vsce": "1.36.2",
|
||||
"vscode": "^1.1.6"
|
||||
}
|
||||
}
|
||||
25
samples/serverReports/src/controllers/controllerBase.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 vscode from 'vscode';
|
||||
|
||||
export default abstract class ControllerBase implements vscode.Disposable {
|
||||
protected _context: vscode.ExtensionContext;
|
||||
|
||||
public constructor(context: vscode.ExtensionContext) {
|
||||
this._context = context;
|
||||
}
|
||||
|
||||
abstract activate(): Promise<boolean>;
|
||||
|
||||
abstract deactivate(): void;
|
||||
|
||||
public dispose(): void {
|
||||
this.deactivate();
|
||||
}
|
||||
}
|
||||
|
||||
64
samples/serverReports/src/controllers/mainController.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 sqlops from 'sqlops';
|
||||
import * as azdata from 'azdata';
|
||||
import * as Utils from '../utils';
|
||||
import ControllerBase from './controllerBase';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as vscode from 'vscode';
|
||||
import * as openurl from 'openurl';
|
||||
|
||||
|
||||
/**
|
||||
* The main controller class that initializes the extension
|
||||
*/
|
||||
export default class MainController extends ControllerBase {
|
||||
|
||||
public apiWrapper;
|
||||
// PUBLIC METHODS //////////////////////////////////////////////////////
|
||||
/**
|
||||
* Deactivates the extension
|
||||
*/
|
||||
public deactivate(): void {
|
||||
Utils.logDebug('Main controller deactivated');
|
||||
}
|
||||
|
||||
public activate(): Promise<boolean> {
|
||||
azdata.tasks.registerTask("tempdb.startEvent", e => this.onExecute(e, 'startEvent.sql'));
|
||||
azdata.tasks.registerTask("tempdb.stopEvent", e => this.onExecute(e, 'stopEvent.sql'));
|
||||
azdata.tasks.registerTask("tempdb.contention", () => this.openurl('https://aka.ms/tempdbblog'));
|
||||
azdata.tasks.registerTask("tempdb.pauseEvent", e => this.stopAutoRefresh(e));
|
||||
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
private openurl(link: string): void {
|
||||
openurl.open(link);
|
||||
}
|
||||
|
||||
private onExecute(connection: azdata.IConnectionProfile, fileName: string): void {
|
||||
//Command to start/stop autorefresh and run the query
|
||||
vscode.commands.executeCommand('azdata.widget.setAutoRefreshState', 'type-of-contention', connection.id, true);
|
||||
vscode.commands.executeCommand('azdata.widget.setAutoRefreshState', 'metadata-contention', connection.id, true);
|
||||
vscode.commands.executeCommand('azdata.widget.setAutoRefreshState', 'allocation-contention', connection.id, true);
|
||||
let sqlContent = fs.readFileSync(path.join(__dirname, '..', 'sql', fileName)).toString();
|
||||
vscode.workspace.openTextDocument({ language: 'sql', content: sqlContent }).then(doc => {
|
||||
vscode.window.showTextDocument(doc, vscode.ViewColumn.Active, false).then(() => {
|
||||
let filePath = doc.uri.toString();
|
||||
azdata.queryeditor.connect(filePath, connection.id).then(() => azdata.queryeditor.runQuery(filePath, undefined, false));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private stopAutoRefresh(connection: azdata.IConnectionProfile) {
|
||||
vscode.commands.executeCommand('azdata.widget.setAutoRefreshState', 'type-of-contention', connection.id, false);
|
||||
vscode.commands.executeCommand('azdata.widget.setAutoRefreshState', 'metadata-contention', connection.id, false);
|
||||
vscode.commands.executeCommand('azdata.widget.setAutoRefreshState', 'allocation-contention', connection.id, false);
|
||||
}
|
||||
}
|
||||
@@ -7,10 +7,33 @@
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
export function activate(context: vscode.ExtensionContext): void {
|
||||
// No-op
|
||||
import ControllerBase from './controllers/controllerBase';
|
||||
import MainController from './controllers/mainController';
|
||||
|
||||
let controllers: ControllerBase[] = [];
|
||||
|
||||
export function activate(context: vscode.ExtensionContext): Promise<boolean> {
|
||||
let activations: Promise<boolean>[] = [];
|
||||
|
||||
// Start the main controller
|
||||
let mainController = new MainController(context);
|
||||
controllers.push(mainController);
|
||||
context.subscriptions.push(mainController);
|
||||
activations.push(mainController.activate());
|
||||
|
||||
return Promise.all(activations)
|
||||
.then((results: boolean[]) => {
|
||||
for (let result of results) {
|
||||
if (!result) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
export function deactivate(): void {
|
||||
// No-op
|
||||
for (let controller of controllers) {
|
||||
controller.deactivate();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,4 +4,4 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
// TODO: localize!
|
||||
export const msgErrorLoadingTab = 'An error occurred while loading the tab.';
|
||||
|
||||
1
samples/serverReports/src/media/blocker.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:#212121;}</style></defs><title>blocker</title><polygon class="cls-1" points="0.99 3.99 -0.01 3.99 -0.01 0.03 3.98 0.03 3.98 1.03 0.99 1.03 0.99 3.99"/><polygon class="cls-1" points="16.01 3.99 15.01 3.99 15.01 1.03 12.02 1.03 12.02 0.03 16.01 0.03 16.01 3.99"/><polygon class="cls-1" points="16.01 15.97 12.02 15.97 12.02 14.97 15.01 14.97 15.01 12.01 16.01 12.01 16.01 15.97"/><polygon class="cls-1" points="4 15.97 0.01 15.97 0.01 12.01 1.01 12.01 1.01 14.97 4 14.97 4 15.97"/><path class="cls-1" d="M8.41,3.18A4.82,4.82,0,1,0,13.23,8,4.83,4.83,0,0,0,8.41,3.18Zm0,.74A4.08,4.08,0,0,1,12.49,8a4,4,0,0,1-.85,2.47L5.69,5A4,4,0,0,1,8.41,3.93Zm0,8.15A4.08,4.08,0,0,1,4.34,8a4,4,0,0,1,.85-2.47L11.14,11A4,4,0,0,1,8.41,12.07Z"/></svg>
|
||||
|
After Width: | Height: | Size: 847 B |
1
samples/serverReports/src/media/blocker_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>blocker_inverse</title><polygon class="cls-1" points="0.99 3.99 -0.01 3.99 -0.01 0.03 3.98 0.03 3.98 1.03 0.99 1.03 0.99 3.99"/><polygon class="cls-1" points="16.01 3.99 15.01 3.99 15.01 1.03 12.02 1.03 12.02 0.03 16.01 0.03 16.01 3.99"/><polygon class="cls-1" points="16.01 15.97 12.02 15.97 12.02 14.97 15.01 14.97 15.01 12.01 16.01 12.01 16.01 15.97"/><polygon class="cls-1" points="4 15.97 0.01 15.97 0.01 12.01 1.01 12.01 1.01 14.97 4 14.97 4 15.97"/><path class="cls-1" d="M8.41,3.18A4.82,4.82,0,1,0,13.23,8,4.83,4.83,0,0,0,8.41,3.18Zm0,.74A4.08,4.08,0,0,1,12.49,8a4,4,0,0,1-.85,2.47L5.69,5A4,4,0,0,1,8.41,3.93Zm0,8.15A4.08,4.08,0,0,1,4.34,8a4,4,0,0,1,.85-2.47L11.14,11A4,4,0,0,1,8.41,12.07Z"/></svg>
|
||||
|
After Width: | Height: | Size: 852 B |
1
samples/serverReports/src/media/documentation.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:#212121;}</style></defs><title>documentation</title><path class="cls-1" d="M14,4.29V16H1V0H9.72ZM13,5H9V1H2V15H13ZM3,4H8V5H3ZM3,8H8V9H3Zm0,4H8v1H3ZM9,7h3v3H9Zm0,4h3v3H9Zm1-7H12.3L10,1.71Zm0,5h1V8H10Zm0,4h1V12H10Z"/></svg>
|
||||
|
After Width: | Height: | Size: 339 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>documentation_1</title><path class="cls-1" d="M14,4.29V16H1V0H9.72ZM13,5H9V1H2V15H13ZM3,4H8V5H3ZM3,8H8V9H3Zm0,4H8v1H3ZM9,7h3v3H9Zm0,4h3v3H9Zm1-7H12.3L10,1.71Zm0,5h1V8H10Zm0,4h1V12H10Z"/></svg>
|
||||
|
After Width: | Height: | Size: 338 B |
1
samples/serverReports/src/media/insights.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:#212121;}</style></defs><title>insights</title><path class="cls-1" d="M15,4V8H14V5.71L9.49,10.2l-2-2L2,13.71V14H15v1H1V1H2V12.29L7.49,6.8l2,2L13.28,5H11V4Z"/></svg>
|
||||
|
After Width: | Height: | Size: 282 B |
1
samples/serverReports/src/media/insights_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>insights_inverse</title><path class="cls-1" d="M15,4V8H14V5.71L9.49,10.2l-2-2L2,13.71V14H15v1H1V1H2V12.29L7.49,6.8l2,2L13.28,5H11V4Z"/></svg>
|
||||
|
After Width: | Height: | Size: 287 B |
1
samples/serverReports/src/media/launch.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>launch</title><path d="M12.8,8.4l1.07-1.07v6.4H0V4.13H10.67L9.6,5.2H1.07v7.47H12.8Zm0-6.4H16V5.2H14.93V3.82l-3.8,3.8-.76-.76,3.8-3.8H12.8Z"/></svg>
|
||||
|
After Width: | Height: | Size: 247 B |
1
samples/serverReports/src/media/launch_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>launch_inverse</title><path class="cls-1" d="M12.8,8.4l1.07-1.07v6.4H0V4.13H10.67L9.6,5.2H1.07v7.47H12.8Zm0-6.4H16V5.2H14.93V3.82l-3.8,3.8-.76-.76,3.8-3.8H12.8Z"/></svg>
|
||||
|
After Width: | Height: | Size: 315 B |
9
samples/serverReports/src/media/tempdb.svg
Normal file
@@ -0,0 +1,9 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<title>TemporaryDatabase</title>
|
||||
<g>
|
||||
<path d="M3.086,3.758a1.565,1.565,0,0,0,.492.445,3.788,3.788,0,0,0,.719.336,6.428,6.428,0,0,0,.848.234q.449.095.878.153t.817.082c.258.015.477.023.66.023s.4-.008.66-.023.53-.043.817-.082.579-.09.878-.153a6.428,6.428,0,0,0,.848-.234,3.842,3.842,0,0,0,.715-.332,1.6,1.6,0,0,0,.5-.449.555.555,0,0,0,.063-.11A.3.3,0,0,0,12,3.531a.462.462,0,0,0-.133-.312,1.7,1.7,0,0,0-.32-.274,3.616,3.616,0,0,0-.383-.222c-.13-.065-.234-.114-.312-.145a6.1,6.1,0,0,0-.79-.254q-.421-.1-.859-.172t-.871-.093c-.289-.018-.566-.028-.832-.028s-.543.01-.832.028-.58.049-.871.093-.578.1-.859.172a6.047,6.047,0,0,0-.79.254c-.078.031-.182.08-.312.145a3.616,3.616,0,0,0-.383.222,1.673,1.673,0,0,0-.32.274A.462.462,0,0,0,3,3.531a.3.3,0,0,0,.023.117A.555.555,0,0,0,3.086,3.758Z" fill="none"/>
|
||||
<path d="M7.435,14.03c-.282,0-.6-.017-.951-.046a10.214,10.214,0,0,1-1.148-.16,7.424,7.424,0,0,1-1.106-.3,3.184,3.184,0,0,1-.894-.481,1.519,1.519,0,0,1-.223-.219A.436.436,0,0,1,3,12.531V5.016A5.236,5.236,0,0,0,4.023,5.5a8.163,8.163,0,0,0,1.149.312,10.5,10.5,0,0,0,1.191.168c.4.035.778.051,1.137.051s.738-.016,1.137-.051a10.368,10.368,0,0,0,1.187-.168A8.39,8.39,0,0,0,10.973,5.5,5.218,5.218,0,0,0,12,5.016V7a5.092,5.092,0,0,1,1,.1V3.531a1.333,1.333,0,0,0-.152-.625,1.97,1.97,0,0,0-.4-.515,3.518,3.518,0,0,0-1.039-.664,7.269,7.269,0,0,0-1.3-.418,10.78,10.78,0,0,0-1.367-.215c-.456-.041-.871-.063-1.246-.063-.245,0-.511.01-.8.028s-.583.048-.891.089-.617.1-.929.164a7.689,7.689,0,0,0-.9.254,5.562,5.562,0,0,0-.8.356,2.788,2.788,0,0,0-.637.469,2.164,2.164,0,0,0-.395.511A1.3,1.3,0,0,0,2,3.531v9a1.316,1.316,0,0,0,.176.676,2.049,2.049,0,0,0,.465.543,3.278,3.278,0,0,0,.656.426,6.65,6.65,0,0,0,.75.32,7.384,7.384,0,0,0,.75.227c.245.06.463.1.656.136a11.923,11.923,0,0,0,2.047.172c.177,0,.354-.005.532-.013A5.006,5.006,0,0,1,7.435,14.03ZM3.133,3.219a1.673,1.673,0,0,1,.32-.274,3.616,3.616,0,0,1,.383-.222c.13-.065.234-.114.312-.145a6.047,6.047,0,0,1,.79-.254q.422-.1.859-.172c.291-.044.582-.075.871-.093s.566-.028.832-.028.543.01.832.028.579.049.871.093.578.1.859.172a6.1,6.1,0,0,1,.79.254c.078.031.182.08.312.145a3.616,3.616,0,0,1,.383.222,1.7,1.7,0,0,1,.32.274A.462.462,0,0,1,12,3.531a.3.3,0,0,1-.023.117.555.555,0,0,1-.063.11,1.6,1.6,0,0,1-.5.449,3.842,3.842,0,0,1-.715.332,6.428,6.428,0,0,1-.848.234q-.449.095-.878.153t-.817.082q-.387.022-.66.023c-.183,0-.4-.008-.66-.023s-.53-.043-.817-.082-.579-.09-.878-.153A6.428,6.428,0,0,1,4.3,4.539,3.788,3.788,0,0,1,3.578,4.2a1.565,1.565,0,0,1-.492-.445.555.555,0,0,1-.063-.11A.3.3,0,0,1,3,3.531.462.462,0,0,1,3.133,3.219Z"/>
|
||||
<polygon points="12 10 11 10 11 13 13.5 13 13.5 12 12 12 12 10"/>
|
||||
<path d="M15.688,10.441a3.99,3.99,0,0,0-2.129-2.129,4.051,4.051,0,0,0-3.118,0,3.99,3.99,0,0,0-2.129,2.129,4.051,4.051,0,0,0,0,3.118,3.99,3.99,0,0,0,2.129,2.129,4.051,4.051,0,0,0,3.118,0,3.99,3.99,0,0,0,2.129-2.129,4.051,4.051,0,0,0,0-3.118Zm-.922,2.727a3.029,3.029,0,0,1-1.6,1.6,3.022,3.022,0,0,1-2.332,0,3.04,3.04,0,0,1-1.6-1.6,3.022,3.022,0,0,1,0-2.332,3.029,3.029,0,0,1,1.6-1.6,3.022,3.022,0,0,1,2.332,0,3.019,3.019,0,0,1,1.6,1.6,3.022,3.022,0,0,1,0,2.332Z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.1 KiB |
9
samples/serverReports/src/media/tempdb_inverse.svg
Normal file
@@ -0,0 +1,9 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">
|
||||
<title>TemporaryDatabase_Inverse</title>
|
||||
<g>
|
||||
<path d="M3.086,3.758a1.565,1.565,0,0,0,.492.445,3.788,3.788,0,0,0,.719.336,6.428,6.428,0,0,0,.848.234q.449.095.878.153t.817.082c.258.015.477.023.66.023s.4-.008.66-.023.53-.043.817-.082.579-.09.878-.153a6.428,6.428,0,0,0,.848-.234,3.842,3.842,0,0,0,.715-.332,1.6,1.6,0,0,0,.5-.449.555.555,0,0,0,.063-.11A.3.3,0,0,0,12,3.531a.462.462,0,0,0-.133-.312,1.7,1.7,0,0,0-.32-.274,3.616,3.616,0,0,0-.383-.222c-.13-.065-.234-.114-.312-.145a6.1,6.1,0,0,0-.79-.254q-.421-.1-.859-.172t-.871-.093c-.289-.018-.566-.028-.832-.028s-.543.01-.832.028-.58.049-.871.093-.578.1-.859.172a6.047,6.047,0,0,0-.79.254c-.078.031-.182.08-.312.145a3.616,3.616,0,0,0-.383.222,1.673,1.673,0,0,0-.32.274A.462.462,0,0,0,3,3.531a.3.3,0,0,0,.023.117A.555.555,0,0,0,3.086,3.758Z" fill="#fff"/>
|
||||
<path d="M7.435,14.03c-.282,0-.6-.017-.951-.046a10.214,10.214,0,0,1-1.148-.16,7.424,7.424,0,0,1-1.106-.3,3.184,3.184,0,0,1-.894-.481,1.519,1.519,0,0,1-.223-.219A.436.436,0,0,1,3,12.531V5.016A5.236,5.236,0,0,0,4.023,5.5a8.163,8.163,0,0,0,1.149.312,10.5,10.5,0,0,0,1.191.168c.4.035.778.051,1.137.051s.738-.016,1.137-.051a10.368,10.368,0,0,0,1.187-.168A8.39,8.39,0,0,0,10.973,5.5,5.218,5.218,0,0,0,12,5.016V7a5.092,5.092,0,0,1,1,.1V3.531a1.333,1.333,0,0,0-.152-.625,1.97,1.97,0,0,0-.4-.515,3.518,3.518,0,0,0-1.039-.664,7.269,7.269,0,0,0-1.3-.418,10.78,10.78,0,0,0-1.367-.215c-.456-.041-.871-.063-1.246-.063-.245,0-.511.01-.8.028s-.583.048-.891.089-.617.1-.929.164a7.689,7.689,0,0,0-.9.254,5.562,5.562,0,0,0-.8.356,2.788,2.788,0,0,0-.637.469,2.164,2.164,0,0,0-.395.511A1.3,1.3,0,0,0,2,3.531v9a1.316,1.316,0,0,0,.176.676,2.049,2.049,0,0,0,.465.543,3.278,3.278,0,0,0,.656.426,6.65,6.65,0,0,0,.75.32,7.384,7.384,0,0,0,.75.227c.245.06.463.1.656.136a11.923,11.923,0,0,0,2.047.172c.177,0,.354-.005.532-.013A5.006,5.006,0,0,1,7.435,14.03ZM3.133,3.219a1.673,1.673,0,0,1,.32-.274,3.616,3.616,0,0,1,.383-.222c.13-.065.234-.114.312-.145a6.047,6.047,0,0,1,.79-.254q.422-.1.859-.172c.291-.044.582-.075.871-.093s.566-.028.832-.028.543.01.832.028.579.049.871.093.578.1.859.172a6.1,6.1,0,0,1,.79.254c.078.031.182.08.312.145a3.616,3.616,0,0,1,.383.222,1.7,1.7,0,0,1,.32.274A.462.462,0,0,1,12,3.531a.3.3,0,0,1-.023.117.555.555,0,0,1-.063.11,1.6,1.6,0,0,1-.5.449,3.842,3.842,0,0,1-.715.332,6.428,6.428,0,0,1-.848.234q-.449.095-.878.153t-.817.082q-.387.022-.66.023c-.183,0-.4-.008-.66-.023s-.53-.043-.817-.082-.579-.09-.878-.153A6.428,6.428,0,0,1,4.3,4.539,3.788,3.788,0,0,1,3.578,4.2a1.565,1.565,0,0,1-.492-.445.555.555,0,0,1-.063-.11A.3.3,0,0,1,3,3.531.462.462,0,0,1,3.133,3.219Z" fill="#fff"/>
|
||||
<polygon points="12 10 11 10 11 13 13.5 13 13.5 12 12 12 12 10" fill="#fff"/>
|
||||
<path d="M15.688,10.441a3.99,3.99,0,0,0-2.129-2.129,4.051,4.051,0,0,0-3.118,0,3.99,3.99,0,0,0-2.129,2.129,4.051,4.051,0,0,0,0,3.118,3.99,3.99,0,0,0,2.129,2.129,4.051,4.051,0,0,0,3.118,0,3.99,3.99,0,0,0,2.129-2.129,4.051,4.051,0,0,0,0-3.118Zm-.922,2.727a3.029,3.029,0,0,1-1.6,1.6,3.022,3.022,0,0,1-2.332,0,3.04,3.04,0,0,1-1.6-1.6,3.022,3.022,0,0,1,0-2.332,3.029,3.029,0,0,1,1.6-1.6,3.022,3.022,0,0,1,2.332,0,3.019,3.019,0,0,1,1.6,1.6,3.022,3.022,0,0,1,0,2.332Z" fill="#fff"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.2 KiB |
34
samples/serverReports/src/resources/templateTab.html
Normal file
@@ -0,0 +1,34 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<script type="text/javascript">
|
||||
window.onload = function () {
|
||||
document.getElementById('frame').src = "{{{url}}}";
|
||||
};
|
||||
</script>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#frame {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<iframe id="frame" width="100%" height="100%" frameborder="0"></iframe>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
19
samples/serverReports/src/sql/allocationContention.sql
Normal file
@@ -0,0 +1,19 @@
|
||||
--SQL script to grab allocation contention from histogram
|
||||
Use tempdb
|
||||
DECLARE @target_data XML;
|
||||
SELECT @target_data = CAST(t.target_data AS XML)
|
||||
FROM sys.dm_xe_sessions AS s
|
||||
JOIN sys.dm_xe_session_targets AS t
|
||||
ON t.event_session_address = s.address
|
||||
WHERE s.name = N'PageContention' and t.target_name = N'histogram';
|
||||
|
||||
with wait_stats as
|
||||
(
|
||||
SELECT
|
||||
n.value('(value)[1]','bigint') AS id,
|
||||
n.value('(@count)[1]', 'bigint') AS [Count]
|
||||
FROM @target_data.nodes('//HistogramTarget/Slot') AS q(n)
|
||||
)
|
||||
|
||||
SELECT [dbo].[mapPageType](wait_stats.id), wait_stats.Count
|
||||
FROM wait_stats
|
||||
22
samples/serverReports/src/sql/metadataContention.sql
Normal file
@@ -0,0 +1,22 @@
|
||||
--SQL script to grab metadata contention from histogram
|
||||
Use tempdb
|
||||
DECLARE @target_data XML;
|
||||
SELECT @target_data = CAST(t.target_data AS XML)
|
||||
FROM sys.dm_xe_sessions AS s
|
||||
JOIN sys.dm_xe_session_targets AS t
|
||||
ON t.event_session_address = s.address
|
||||
WHERE s.name = N'ObjectContention' and t.target_name = N'histogram';
|
||||
|
||||
with wait_stats as
|
||||
(
|
||||
SELECT
|
||||
n.value('(value)[1]','bigint') AS alloc_unit_id,
|
||||
n.value('(@count)[1]', 'bigint') AS [Count]
|
||||
FROM @target_data.nodes('//HistogramTarget/Slot') AS q(n)
|
||||
)
|
||||
|
||||
SELECT objects.id, SUM(objects.count) as [Count] FROM
|
||||
(SELECT [dbo].[isSystemTable](wait_stats.alloc_unit_id) AS id, wait_stats.Count AS [count]
|
||||
FROM wait_stats
|
||||
WHERE [dbo].[isSystemTable](wait_stats.alloc_unit_id) not in (0, 99)) AS objects
|
||||
GROUP BY objects.id
|
||||
80
samples/serverReports/src/sql/startEvent.sql
Normal file
@@ -0,0 +1,80 @@
|
||||
--Starts the XEvents sessions and creates the functions needed to find object id and give name to the page types
|
||||
use tempdb
|
||||
|
||||
BEGIN TRY
|
||||
IF NOT EXISTS (SELECT * FROM sys.dm_xe_sessions WHERE name = 'PageContention')
|
||||
BEGIN
|
||||
CREATE EVENT SESSION [PageContention] ON SERVER
|
||||
ADD EVENT latch_suspend_end(
|
||||
WHERE class = 28
|
||||
AND (page_type_id = 8
|
||||
OR page_type_id = 9
|
||||
OR page_type_id = 11))
|
||||
ADD TARGET package0.histogram(SET slots=16, filtering_event_name=N'latch_suspend_end', source=N'page_type_id', source_type=(0))
|
||||
ALTER EVENT SESSION [PageContention] ON SERVER
|
||||
STATE = START
|
||||
END
|
||||
IF NOT EXISTS (SELECT * FROM sys.dm_xe_sessions WHERE name = 'ObjectContention')
|
||||
BEGIN
|
||||
CREATE EVENT SESSION [ObjectContention] ON SERVER
|
||||
ADD EVENT latch_suspend_end(
|
||||
WHERE class = 28
|
||||
AND database_id = 2)
|
||||
ADD TARGET package0.histogram(SET slots=256, filtering_event_name=N'latch_suspend_end', source=N'page_alloc_unit_id', source_type=(0))
|
||||
ALTER EVENT SESSION [ObjectContention] ON SERVER
|
||||
STATE = START
|
||||
END
|
||||
END TRY
|
||||
BEGIN CATCH
|
||||
PRINT 'XEvent fields not supported'
|
||||
END CATCH
|
||||
GO
|
||||
|
||||
IF OBJECT_ID(N'[dbo].[isSystemTable]', N'FN') IS NOT NULL
|
||||
DROP FUNCTION [dbo].[isSystemTable]
|
||||
GO
|
||||
|
||||
CREATE FUNCTION [dbo].[isSystemTable] (@alloc bigint)
|
||||
RETURNS bigint
|
||||
|
||||
AS BEGIN
|
||||
|
||||
DECLARE @index BIGINT;
|
||||
DECLARE @objId BIGINT;
|
||||
|
||||
SELECT @index =
|
||||
CONVERT (BIGINT,
|
||||
CONVERT (FLOAT, @alloc)
|
||||
* (1 / POWER (2.0, 48))
|
||||
);
|
||||
SELECT @objId =
|
||||
CONVERT (BIGINT,
|
||||
CONVERT (FLOAT, @alloc - (@index * CONVERT (BIGINT, POWER (2.0, 48))))
|
||||
* (1 / POWER (2.0, 16))
|
||||
);
|
||||
|
||||
IF (@objId > 0 AND @objId <= 100 AND @index <= 255)
|
||||
return @objId
|
||||
|
||||
return 0
|
||||
|
||||
END
|
||||
GO
|
||||
|
||||
IF OBJECT_ID(N'[dbo].[mapPageType]', N'FN') IS NOT NULL
|
||||
DROP FUNCTION [dbo].[mapPageType]
|
||||
GO
|
||||
|
||||
CREATE FUNCTION [dbo].[mapPageType] (@pageTypeId bigint)
|
||||
RETURNS varchar(20)
|
||||
|
||||
AS BEGIN
|
||||
IF @pageTypeId = 8
|
||||
return 'GAM_PAGE'
|
||||
ELSE IF @pageTypeId = 9
|
||||
return 'SGAM_PAGE'
|
||||
ELSE IF @pageTypeId = 11
|
||||
return 'PFS_PAGE'
|
||||
return ''
|
||||
END
|
||||
GO
|
||||
3
samples/serverReports/src/sql/stopEvent.sql
Normal file
@@ -0,0 +1,3 @@
|
||||
--Stops the XEvent Sessions
|
||||
DROP EVENT SESSION [PageContention] ON SERVER
|
||||
DROP EVENT SESSION [ObjectContention] ON SERVER
|
||||
33
samples/serverReports/src/sql/typeofContentions.sql
Normal file
@@ -0,0 +1,33 @@
|
||||
--SQL script to grab all contention
|
||||
Use tempdb
|
||||
DECLARE @pc XML;
|
||||
DECLARE @obj XML;
|
||||
|
||||
SELECT @pc = CAST(t.target_data AS XML)
|
||||
FROM sys.dm_xe_sessions AS s
|
||||
JOIN sys.dm_xe_session_targets AS t
|
||||
ON t.event_session_address = s.address
|
||||
WHERE s.name = N'PageContention' and t.target_name = N'histogram';
|
||||
|
||||
SELECT @obj = CAST(t.target_data AS XML)
|
||||
FROM sys.dm_xe_sessions AS s
|
||||
JOIN sys.dm_xe_session_targets AS t
|
||||
ON t.event_session_address = s.address
|
||||
WHERE s.name = N'ObjectContention' and t.target_name = N'histogram';
|
||||
|
||||
SELECT 'Metadata Contention' AS wait_type, SUM(obj.count) AS [Count]
|
||||
FROM (
|
||||
SELECT
|
||||
n.value('(value)[1]','bigint') AS alloc_unit_id,
|
||||
n.value('(@count)[1]', 'bigint') AS [count]
|
||||
FROM @obj.nodes('//HistogramTarget/Slot') AS q(n)
|
||||
) obj
|
||||
WHERE [dbo].[isSystemTable](obj.alloc_unit_id) not in (0, 99)
|
||||
UNION
|
||||
SELECT 'Allocation Contention' AS wait_type, SUM(pc.count) AS [Count]
|
||||
FROM
|
||||
(
|
||||
SELECT
|
||||
n.value('(@count)[1]', 'bigint') AS [count]
|
||||
FROM @pc.nodes('//HistogramTarget/Slot') AS q(n)
|
||||
) pc
|
||||
95
samples/serverReports/src/sql/wait_resources.sql
Normal file
@@ -0,0 +1,95 @@
|
||||
WITH [Waits] AS
|
||||
(
|
||||
SELECT wait_type, resource_description, count(resource_description) AS RESOURCE_USE, COUNT(*) AS TOTAL FROM sys.dm_os_waiting_tasks
|
||||
WHERE [wait_type] NOT IN (
|
||||
-- These wait types are almost 100% never a problem and so they are
|
||||
-- filtered out to avoid them skewing the results. Click on the URL
|
||||
-- for more information.
|
||||
N'BROKER_EVENTHANDLER', -- https://www.sqlskills.com/help/waits/BROKER_EVENTHANDLER
|
||||
N'BROKER_RECEIVE_WAITFOR', -- https://www.sqlskills.com/help/waits/BROKER_RECEIVE_WAITFOR
|
||||
N'BROKER_TASK_STOP', -- https://www.sqlskills.com/help/waits/BROKER_TASK_STOP
|
||||
N'BROKER_TO_FLUSH', -- https://www.sqlskills.com/help/waits/BROKER_TO_FLUSH
|
||||
N'BROKER_TRANSMITTER', -- https://www.sqlskills.com/help/waits/BROKER_TRANSMITTER
|
||||
N'CHECKPOINT_QUEUE', -- https://www.sqlskills.com/help/waits/CHECKPOINT_QUEUE
|
||||
N'CHKPT', -- https://www.sqlskills.com/help/waits/CHKPT
|
||||
N'CLR_AUTO_EVENT', -- https://www.sqlskills.com/help/waits/CLR_AUTO_EVENT
|
||||
N'CLR_MANUAL_EVENT', -- https://www.sqlskills.com/help/waits/CLR_MANUAL_EVENT
|
||||
N'CLR_SEMAPHORE', -- https://www.sqlskills.com/help/waits/CLR_SEMAPHORE
|
||||
N'CXCONSUMER', -- https://www.sqlskills.com/help/waits/CXCONSUMER
|
||||
|
||||
-- Maybe comment these four out if you have mirroring issues
|
||||
N'DBMIRROR_DBM_EVENT', -- https://www.sqlskills.com/help/waits/DBMIRROR_DBM_EVENT
|
||||
N'DBMIRROR_EVENTS_QUEUE', -- https://www.sqlskills.com/help/waits/DBMIRROR_EVENTS_QUEUE
|
||||
N'DBMIRROR_WORKER_QUEUE', -- https://www.sqlskills.com/help/waits/DBMIRROR_WORKER_QUEUE
|
||||
N'DBMIRRORING_CMD', -- https://www.sqlskills.com/help/waits/DBMIRRORING_CMD
|
||||
|
||||
N'DIRTY_PAGE_POLL', -- https://www.sqlskills.com/help/waits/DIRTY_PAGE_POLL
|
||||
N'DISPATCHER_QUEUE_SEMAPHORE', -- https://www.sqlskills.com/help/waits/DISPATCHER_QUEUE_SEMAPHORE
|
||||
N'EXECSYNC', -- https://www.sqlskills.com/help/waits/EXECSYNC
|
||||
N'FSAGENT', -- https://www.sqlskills.com/help/waits/FSAGENT
|
||||
N'FT_IFTS_SCHEDULER_IDLE_WAIT', -- https://www.sqlskills.com/help/waits/FT_IFTS_SCHEDULER_IDLE_WAIT
|
||||
N'FT_IFTSHC_MUTEX', -- https://www.sqlskills.com/help/waits/FT_IFTSHC_MUTEX
|
||||
|
||||
-- Maybe comment these six out if you have AG issues
|
||||
N'HADR_CLUSAPI_CALL', -- https://www.sqlskills.com/help/waits/HADR_CLUSAPI_CALL
|
||||
N'HADR_FILESTREAM_IOMGR_IOCOMPLETION', -- https://www.sqlskills.com/help/waits/HADR_FILESTREAM_IOMGR_IOCOMPLETION
|
||||
N'HADR_LOGCAPTURE_WAIT', -- https://www.sqlskills.com/help/waits/HADR_LOGCAPTURE_WAIT
|
||||
N'HADR_NOTIFICATION_DEQUEUE', -- https://www.sqlskills.com/help/waits/HADR_NOTIFICATION_DEQUEUE
|
||||
N'HADR_TIMER_TASK', -- https://www.sqlskills.com/help/waits/HADR_TIMER_TASK
|
||||
N'HADR_WORK_QUEUE', -- https://www.sqlskills.com/help/waits/HADR_WORK_QUEUE
|
||||
|
||||
N'KSOURCE_WAKEUP', -- https://www.sqlskills.com/help/waits/KSOURCE_WAKEUP
|
||||
N'LAZYWRITER_SLEEP', -- https://www.sqlskills.com/help/waits/LAZYWRITER_SLEEP
|
||||
N'LOGMGR_QUEUE', -- https://www.sqlskills.com/help/waits/LOGMGR_QUEUE
|
||||
N'MEMORY_ALLOCATION_EXT', -- https://www.sqlskills.com/help/waits/MEMORY_ALLOCATION_EXT
|
||||
N'ONDEMAND_TASK_QUEUE', -- https://www.sqlskills.com/help/waits/ONDEMAND_TASK_QUEUE
|
||||
N'PARALLEL_REDO_DRAIN_WORKER', -- https://www.sqlskills.com/help/waits/PARALLEL_REDO_DRAIN_WORKER
|
||||
N'PARALLEL_REDO_LOG_CACHE', -- https://www.sqlskills.com/help/waits/PARALLEL_REDO_LOG_CACHE
|
||||
N'PARALLEL_REDO_TRAN_LIST', -- https://www.sqlskills.com/help/waits/PARALLEL_REDO_TRAN_LIST
|
||||
N'PARALLEL_REDO_WORKER_SYNC', -- https://www.sqlskills.com/help/waits/PARALLEL_REDO_WORKER_SYNC
|
||||
N'PARALLEL_REDO_WORKER_WAIT_WORK', -- https://www.sqlskills.com/help/waits/PARALLEL_REDO_WORKER_WAIT_WORK
|
||||
N'PREEMPTIVE_OS_FLUSHFILEBUFFERS', -- https://www.sqlskills.com/help/waits/PREEMPTIVE_OS_FLUSHFILEBUFFERS
|
||||
N'PREEMPTIVE_XE_GETTARGETSTATE', -- https://www.sqlskills.com/help/waits/PREEMPTIVE_XE_GETTARGETSTATE
|
||||
N'PWAIT_ALL_COMPONENTS_INITIALIZED', -- https://www.sqlskills.com/help/waits/PWAIT_ALL_COMPONENTS_INITIALIZED
|
||||
N'PWAIT_DIRECTLOGCONSUMER_GETNEXT', -- https://www.sqlskills.com/help/waits/PWAIT_DIRECTLOGCONSUMER_GETNEXT
|
||||
N'QDS_PERSIST_TASK_MAIN_LOOP_SLEEP', -- https://www.sqlskills.com/help/waits/QDS_PERSIST_TASK_MAIN_LOOP_SLEEP
|
||||
N'QDS_ASYNC_QUEUE', -- https://www.sqlskills.com/help/waits/QDS_ASYNC_QUEUE
|
||||
N'QDS_CLEANUP_STALE_QUERIES_TASK_MAIN_LOOP_SLEEP',
|
||||
-- https://www.sqlskills.com/help/waits/QDS_CLEANUP_STALE_QUERIES_TASK_MAIN_LOOP_SLEEP
|
||||
N'QDS_SHUTDOWN_QUEUE', -- https://www.sqlskills.com/help/waits/QDS_SHUTDOWN_QUEUE
|
||||
N'REDO_THREAD_PENDING_WORK', -- https://www.sqlskills.com/help/waits/REDO_THREAD_PENDING_WORK
|
||||
N'REQUEST_FOR_DEADLOCK_SEARCH', -- https://www.sqlskills.com/help/waits/REQUEST_FOR_DEADLOCK_SEARCH
|
||||
N'RESOURCE_QUEUE', -- https://www.sqlskills.com/help/waits/RESOURCE_QUEUE
|
||||
N'SERVER_IDLE_CHECK', -- https://www.sqlskills.com/help/waits/SERVER_IDLE_CHECK
|
||||
N'SLEEP_BPOOL_FLUSH', -- https://www.sqlskills.com/help/waits/SLEEP_BPOOL_FLUSH
|
||||
N'SLEEP_DBSTARTUP', -- https://www.sqlskills.com/help/waits/SLEEP_DBSTARTUP
|
||||
N'SLEEP_DCOMSTARTUP', -- https://www.sqlskills.com/help/waits/SLEEP_DCOMSTARTUP
|
||||
N'SLEEP_MASTERDBREADY', -- https://www.sqlskills.com/help/waits/SLEEP_MASTERDBREADY
|
||||
N'SLEEP_MASTERMDREADY', -- https://www.sqlskills.com/help/waits/SLEEP_MASTERMDREADY
|
||||
N'SLEEP_MASTERUPGRADED', -- https://www.sqlskills.com/help/waits/SLEEP_MASTERUPGRADED
|
||||
N'SLEEP_MSDBSTARTUP', -- https://www.sqlskills.com/help/waits/SLEEP_MSDBSTARTUP
|
||||
N'SLEEP_SYSTEMTASK', -- https://www.sqlskills.com/help/waits/SLEEP_SYSTEMTASK
|
||||
N'SLEEP_TASK', -- https://www.sqlskills.com/help/waits/SLEEP_TASK
|
||||
N'SLEEP_TEMPDBSTARTUP', -- https://www.sqlskills.com/help/waits/SLEEP_TEMPDBSTARTUP
|
||||
N'SNI_HTTP_ACCEPT', -- https://www.sqlskills.com/help/waits/SNI_HTTP_ACCEPT
|
||||
N'SOS_WORK_DISPATCHER', -- https://www.sqlskills.com/help/waits/SOS_WORK_DISPATCHER
|
||||
N'SP_SERVER_DIAGNOSTICS_SLEEP', -- https://www.sqlskills.com/help/waits/SP_SERVER_DIAGNOSTICS_SLEEP
|
||||
N'SQLTRACE_BUFFER_FLUSH', -- https://www.sqlskills.com/help/waits/SQLTRACE_BUFFER_FLUSH
|
||||
N'SQLTRACE_INCREMENTAL_FLUSH_SLEEP', -- https://www.sqlskills.com/help/waits/SQLTRACE_INCREMENTAL_FLUSH_SLEEP
|
||||
N'SQLTRACE_WAIT_ENTRIES', -- https://www.sqlskills.com/help/waits/SQLTRACE_WAIT_ENTRIES
|
||||
N'VDI_CLIENT_OTHER', -- https://www.sqlskills.com/help/waits/VDI_CLIENT_OTHER
|
||||
N'WAIT_FOR_RESULTS', -- https://www.sqlskills.com/help/waits/WAIT_FOR_RESULTS
|
||||
N'WAITFOR', -- https://www.sqlskills.com/help/waits/WAITFOR
|
||||
N'WAITFOR_TASKSHUTDOWN', -- https://www.sqlskills.com/help/waits/WAITFOR_TASKSHUTDOWN
|
||||
N'WAIT_XTP_RECOVERY', -- https://www.sqlskills.com/help/waits/WAIT_XTP_RECOVERY
|
||||
N'WAIT_XTP_HOST_WAIT', -- https://www.sqlskills.com/help/waits/WAIT_XTP_HOST_WAIT
|
||||
N'WAIT_XTP_OFFLINE_CKPT_NEW_LOG', -- https://www.sqlskills.com/help/waits/WAIT_XTP_OFFLINE_CKPT_NEW_LOG
|
||||
N'WAIT_XTP_CKPT_CLOSE', -- https://www.sqlskills.com/help/waits/WAIT_XTP_CKPT_CLOSE
|
||||
N'XE_DISPATCHER_JOIN', -- https://www.sqlskills.com/help/waits/XE_DISPATCHER_JOIN
|
||||
N'XE_DISPATCHER_WAIT', -- https://www.sqlskills.com/help/waits/XE_DISPATCHER_WAIT
|
||||
N'XE_TIMER_EVENT' -- https://www.sqlskills.com/help/waits/XE_TIMER_EVENT
|
||||
)
|
||||
GROUP BY sys.dm_os_waiting_tasks.resource_description, wait_type
|
||||
)
|
||||
|
||||
SELECT [TASKS].[wait_type] AS [WaitType], [TASKS].[resource_description] as [Resource], 100.0 * [TASKS].[TOTAL] / SUM ([TASKS].[TOTAL]) OVER () AS [Percentage] FROM Waits AS TASKS GROUP BY [TASKS].[wait_type], [TASKS].[resource_description], [TASKS].[TOTAL]
|
||||
@@ -5,21 +5,42 @@
|
||||
|
||||
'use strict';
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs-extra';
|
||||
import * as handlebars from 'handlebars';
|
||||
import * as Constants from './constants';
|
||||
import * as LocalizedConstants from './localizedConstants';
|
||||
|
||||
/**
|
||||
* Helper to log messages to the developer console if enabled
|
||||
* @param msg Message to log to the console
|
||||
*/
|
||||
export function logDebug(msg: any): void {
|
||||
let config = vscode.workspace.getConfiguration(Constants.extensionConfigSectionName);
|
||||
let logDebugInfo = config[Constants.configLogDebugInfo];
|
||||
if (logDebugInfo === true) {
|
||||
let currentTime = new Date().toLocaleTimeString();
|
||||
let outputMsg = '[' + currentTime + ']: ' + msg ? msg.toString() : '';
|
||||
console.log(outputMsg);
|
||||
}
|
||||
let config = vscode.workspace.getConfiguration(Constants.extensionConfigSectionName);
|
||||
let logDebugInfo = config[Constants.configLogDebugInfo];
|
||||
if (logDebugInfo === true) {
|
||||
let currentTime = new Date().toLocaleTimeString();
|
||||
let outputMsg = '[' + currentTime + ']: ' + msg ? msg.toString() : '';
|
||||
console.log(outputMsg);
|
||||
}
|
||||
}
|
||||
|
||||
export function renderTemplateHtml(extensionPath: string, templateName: string, templateValues: object): Thenable<string> {
|
||||
let templatePath = path.join(extensionPath, 'resources', templateName);
|
||||
|
||||
// 1) Read the template from the disk
|
||||
// 2) Compile it as a handlebars template and render the HTML
|
||||
// 3) On failure, return a simple string as an error
|
||||
return fs.readFile(templatePath, 'utf-8')
|
||||
.then(templateText => {
|
||||
let template = handlebars.compile(templateText);
|
||||
return template(templateValues);
|
||||
})
|
||||
.then(
|
||||
undefined,
|
||||
error => {
|
||||
logDebug(error);
|
||||
return LocalizedConstants.msgErrorLoadingTab;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -18,67 +18,67 @@ let tsProject = ts.createProject('tsconfig.json');
|
||||
|
||||
|
||||
// GULP TASKS //////////////////////////////////////////////////////////////
|
||||
gulp.task('clean', function(done) {
|
||||
return del('out', done);
|
||||
gulp.task('clean', function (done) {
|
||||
return del('out', done);
|
||||
});
|
||||
|
||||
gulp.task('lint', () => {
|
||||
return gulp.src([
|
||||
config.paths.project.root + '/src/**/*.ts',
|
||||
config.paths.project.root + '/test/**/*.ts'
|
||||
])
|
||||
.pipe((tslint({
|
||||
formatter: "verbose"
|
||||
})))
|
||||
.pipe(tslint.report());
|
||||
return gulp.src([
|
||||
config.paths.project.root + '/src/**/*.ts',
|
||||
config.paths.project.root + '/test/**/*.ts'
|
||||
])
|
||||
.pipe((tslint({
|
||||
formatter: "verbose"
|
||||
})))
|
||||
.pipe(tslint.report());
|
||||
});
|
||||
|
||||
gulp.task('compile:src', function(done) {
|
||||
gulp.src([
|
||||
config.paths.project.root + '/src/**/*.sql',
|
||||
config.paths.project.root + '/src/**/*.svg',
|
||||
config.paths.project.root + '/src/**/*.html'
|
||||
]).pipe(gulp.dest('out/src/'));
|
||||
gulp.task('compile:src', function (done) {
|
||||
gulp.src([
|
||||
config.paths.project.root + '/src/**/*.sql',
|
||||
config.paths.project.root + '/src/**/*.svg',
|
||||
config.paths.project.root + '/src/**/*.html'
|
||||
]).pipe(gulp.dest('out/src/'));
|
||||
|
||||
let srcFiles = [
|
||||
config.paths.project.root + '/src/**/*.ts',
|
||||
config.paths.project.root + '/src/**/*.js',
|
||||
config.paths.project.root + '/typings/**/*.ts'
|
||||
];
|
||||
let srcFiles = [
|
||||
config.paths.project.root + '/src/**/*.ts',
|
||||
config.paths.project.root + '/src/**/*.js',
|
||||
config.paths.project.root + '/typings/**/*.ts'
|
||||
];
|
||||
|
||||
return gulp.src(srcFiles)
|
||||
.pipe(srcmap.init())
|
||||
.pipe(tsProject())
|
||||
.on('error', function() {
|
||||
if(process.env.BUILDMACHINE) {
|
||||
done('Failed to compile extension source, see above.');
|
||||
process.exit(1);
|
||||
}
|
||||
})
|
||||
// TODO: Reinstate localization code
|
||||
// .pipe(nls.rewriteLocalizeCalls())
|
||||
// .pipe(nls.createAdditionalLanguageFiles(nls.coreLanguages, config.paths.project.root + '/localization/i18n', undefined, false))
|
||||
.pipe(srcmap.write('.', { sourceRoot: function(file) { return file.cwd + '/src'; }}))
|
||||
.pipe(gulp.dest('out/src/'));
|
||||
return gulp.src(srcFiles)
|
||||
.pipe(srcmap.init())
|
||||
.pipe(tsProject())
|
||||
.on('error', function () {
|
||||
if (process.env.BUILDMACHINE) {
|
||||
done('Failed to compile extension source, see above.');
|
||||
process.exit(1);
|
||||
}
|
||||
})
|
||||
// TODO: Reinstate localization code
|
||||
// .pipe(nls.rewriteLocalizeCalls())
|
||||
// .pipe(nls.createAdditionalLanguageFiles(nls.coreLanguages, config.paths.project.root + '/localization/i18n', undefined, false))
|
||||
.pipe(srcmap.write('.', { sourceRoot: function (file) { return file.cwd + '/src'; } }))
|
||||
.pipe(gulp.dest('out/src/'));
|
||||
});
|
||||
|
||||
gulp.task('compile:test', function(done) {
|
||||
let srcFiles = [
|
||||
config.paths.project.root + '/test/**/*.ts',
|
||||
config.paths.project.root + '/typings/**/*.ts'
|
||||
];
|
||||
gulp.task('compile:test', function (done) {
|
||||
let srcFiles = [
|
||||
config.paths.project.root + '/test/**/*.ts',
|
||||
config.paths.project.root + '/typings/**/*.ts'
|
||||
];
|
||||
|
||||
return gulp.src(srcFiles)
|
||||
.pipe(srcmap.init())
|
||||
.pipe(tsProject())
|
||||
.on('error', function() {
|
||||
if(process.env.BUILDMACHINE) {
|
||||
done('Failed to compile test source, see above.');
|
||||
process.exit(1);
|
||||
}
|
||||
})
|
||||
.pipe(srcmap.write('.', {sourceRoot: function(file) { return file.cwd + '/test'; }}))
|
||||
.pipe(gulp.dest('out/test/'));
|
||||
return gulp.src(srcFiles)
|
||||
.pipe(srcmap.init())
|
||||
.pipe(tsProject())
|
||||
.on('error', function () {
|
||||
if (process.env.BUILDMACHINE) {
|
||||
done('Failed to compile test source, see above.');
|
||||
process.exit(1);
|
||||
}
|
||||
})
|
||||
.pipe(srcmap.write('.', { sourceRoot: function (file) { return file.cwd + '/test'; } }))
|
||||
.pipe(gulp.dest('out/test/'));
|
||||
});
|
||||
|
||||
// COMPOSED GULP TASKS /////////////////////////////////////////////////////
|
||||
@@ -86,33 +86,38 @@ gulp.task("compile", gulp.series("compile:src", "compile:test"));
|
||||
|
||||
gulp.task("build", gulp.series("clean", "lint", "compile"));
|
||||
|
||||
gulp.task("watch", function() {
|
||||
gulp.watch([config.paths.project.root + '/src/**/*',
|
||||
config.paths.project.root + '/test/**/*.ts'],
|
||||
gulp.series('build'))
|
||||
gulp.task("watch", function () {
|
||||
gulp.watch([config.paths.project.root + '/src/**/*',
|
||||
config.paths.project.root + '/test/**/*.ts'],
|
||||
gulp.series('build'));
|
||||
});
|
||||
|
||||
gulp.task('test', (done) => {
|
||||
let workspace = process.env['WORKSPACE'];
|
||||
if (!workspace) {
|
||||
workspace = process.cwd();
|
||||
}
|
||||
process.env.JUNIT_REPORT_PATH = workspace + '/test-reports/ext_xunit.xml';
|
||||
let workspace = process.env['WORKSPACE'];
|
||||
if (!workspace) {
|
||||
workspace = process.cwd();
|
||||
}
|
||||
process.env.JUNIT_REPORT_PATH = workspace + '/test-reports/ext_xunit.xml';
|
||||
|
||||
let azuredatastudioPath = 'azuredatastudio';
|
||||
if (process.env['SQLOPS_DEV']) {
|
||||
let suffix = os.platform === 'win32' ? 'bat' : 'sh';
|
||||
azuredatastudioPath = `${process.env['SQLOPS_DEV']}/scripts/sql-cli.${suffix}`;
|
||||
}
|
||||
console.log(`Using SQLOPS Path of ${azuredatastudioPath}`);
|
||||
let azuredatastudioPath = 'azuredatastudio';
|
||||
if (process.env['SQLOPS_DEV']) {
|
||||
let suffix = os.platform === 'win32' ? 'bat' : 'sh';
|
||||
azuredatastudioPath = `${process.env['SQLOPS_DEV']}/scripts/sql-cli.${suffix}`;
|
||||
}
|
||||
console.log(`Using SQLOPS Path of ${azuredatastudioPath}`);
|
||||
|
||||
cproc.exec(`${azuredatastudioPath} --extensionDevelopmentPath="${workspace}" --extensionTestsPath="${workspace}/out/test" --verbose`, (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
console.error(`exec error: ${error}`);
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(`stdout: ${stdout}`);
|
||||
console.log(`stderr: ${stderr}`);
|
||||
done();
|
||||
});
|
||||
cproc.exec(`${azuredatastudioPath} --extensionDevelopmentPath="${workspace}" --extensionTestsPath="${workspace}/out/test" --verbose`, (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
console.error(`exec error: ${error}`);
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(`stdout: ${stdout}`);
|
||||
console.log(`stderr: ${stderr}`);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
gulp.task('copytypings', function () {
|
||||
return gulp.src(config.paths.project.root + '/../../src/sql/sqlops.proposed.d.ts')
|
||||
.pipe(gulp.dest('typings/'));
|
||||
});
|
||||
|
||||
2
samples/sp_whoIsActive/.gitignore
vendored
@@ -6,4 +6,4 @@ node_modules
|
||||
.DS_Store
|
||||
.idea
|
||||
test-reports/**
|
||||
typings/sqlops.proposed.d.ts
|
||||
typings/azdata.d.ts
|
||||
|
||||
46
samples/sp_whoIsActive/.vscode/launch.json
vendored
@@ -9,7 +9,7 @@
|
||||
// - open Azure Data Studio
|
||||
// - run the command "Install 'azuredatastudio' command in PATH"
|
||||
{
|
||||
"version": "0.2.0",
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
|
||||
{
|
||||
@@ -21,37 +21,37 @@
|
||||
"--extensionDevelopmentPath=${workspaceFolder}"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "node",
|
||||
"request": "attach",
|
||||
"name": "Attach to Ops Studio",
|
||||
"protocol": "inspector",
|
||||
"port": 5870,
|
||||
"restart": true,
|
||||
{
|
||||
"type": "node",
|
||||
"request": "attach",
|
||||
"name": "Attach to Azure Data Studio",
|
||||
"protocol": "inspector",
|
||||
"port": 5870,
|
||||
"restart": true,
|
||||
"sourceMaps": true,
|
||||
"outFiles": [
|
||||
"${workspaceRoot}/out/**/*.js"
|
||||
],
|
||||
"outFiles": [
|
||||
"${workspaceRoot}/out/**/*.js"
|
||||
],
|
||||
"preLaunchTask": "",
|
||||
"timeout": 25000
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "Debug in enlistment",
|
||||
"type": "sqlopsExtensionHost",
|
||||
"type": "azuredatastudioExtensionHost",
|
||||
"request": "launch",
|
||||
"windows": {
|
||||
"runtimeExecutable": "${workspaceFolder}/../../scripts/sql.bat"
|
||||
},
|
||||
"osx": {
|
||||
"runtimeExecutable": "${workspaceFolder}/../../scripts/sql.sh"
|
||||
},
|
||||
"linux": {
|
||||
"runtimeExecutable": "${workspaceFolder}/../../scripts/sql.sh"
|
||||
},
|
||||
"windows": {
|
||||
"runtimeExecutable": "${workspaceFolder}/../../scripts/sql.bat"
|
||||
},
|
||||
"osx": {
|
||||
"runtimeExecutable": "${workspaceFolder}/../../scripts/sql.sh"
|
||||
},
|
||||
"linux": {
|
||||
"runtimeExecutable": "${workspaceFolder}/../../scripts/sql.sh"
|
||||
},
|
||||
"args": [
|
||||
"--extensionDevelopmentPath=${workspaceFolder}"
|
||||
],
|
||||
"timeout": 20000
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
773
samples/sp_whoIsActive/package-lock.json
generated
@@ -2,12 +2,12 @@
|
||||
"name": "whoisactive",
|
||||
"displayName": "whoisactive",
|
||||
"description": "sp_whoisactive for Azure Data Studio",
|
||||
"version": "0.1.1",
|
||||
"version": "0.1.2",
|
||||
"publisher": "Microsoft",
|
||||
"preview": true,
|
||||
"engines": {
|
||||
"vscode": "^1.26.0",
|
||||
"azdata": "*"
|
||||
"azdata": "^1.11.0"
|
||||
},
|
||||
"icon": "images/sqlserver.png",
|
||||
"license": "SEE LICENSE IN LICENSE.txt",
|
||||
@@ -241,17 +241,17 @@
|
||||
"compile": "gulp compile",
|
||||
"watch": "gulp watch",
|
||||
"typings": "gulp copytypings",
|
||||
"postinstall": "node ./node_modules/vscode/bin/install && node ./node_modules/sqlops/bin/install && gulp copytypings"
|
||||
"postinstall": "node ./node_modules/vscode/bin/install && node ./node_modules/azdata/bin/install && gulp copytypings"
|
||||
},
|
||||
"dependencies": {
|
||||
"fs-extra": "^5.0.0",
|
||||
"handlebars": "^4.0.11",
|
||||
"openurl": "^1.1.1",
|
||||
"sqlops": "github:anthonydresser/sqlops-extension-sqlops"
|
||||
"openurl": "^1.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/mocha": "^2.2.42",
|
||||
"@types/node": "^7.0.43",
|
||||
"azdata": "github:microsoft/azdata-extension-azdata",
|
||||
"child-process-promise": "^2.2.1",
|
||||
"del": "^3.0.0",
|
||||
"gulp": "^4.0.0",
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
@@ -9,18 +8,18 @@
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
export default abstract class ControllerBase implements vscode.Disposable {
|
||||
protected _context: vscode.ExtensionContext;
|
||||
protected _context: vscode.ExtensionContext;
|
||||
|
||||
public constructor(context: vscode.ExtensionContext) {
|
||||
this._context = context;
|
||||
}
|
||||
public constructor(context: vscode.ExtensionContext) {
|
||||
this._context = context;
|
||||
}
|
||||
|
||||
abstract activate(): Promise<boolean>;
|
||||
abstract activate(): Promise<boolean>;
|
||||
|
||||
abstract deactivate(): void;
|
||||
abstract deactivate(): void;
|
||||
|
||||
public dispose(): void {
|
||||
this.deactivate();
|
||||
}
|
||||
public dispose(): void {
|
||||
this.deactivate();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import * as sqlops from 'sqlops';
|
||||
import * as azdata from 'azdata';
|
||||
import * as Utils from '../utils';
|
||||
import ControllerBase from './controllerBase';
|
||||
import * as fs from 'fs';
|
||||
@@ -19,35 +19,35 @@ import * as openurl from 'openurl';
|
||||
*/
|
||||
export default class MainController extends ControllerBase {
|
||||
|
||||
public apiWrapper;
|
||||
// PUBLIC METHODS //////////////////////////////////////////////////////
|
||||
/**
|
||||
* Deactivates the extension
|
||||
*/
|
||||
public deactivate(): void {
|
||||
Utils.logDebug('Main controller deactivated');
|
||||
}
|
||||
public apiWrapper;
|
||||
// PUBLIC METHODS //////////////////////////////////////////////////////
|
||||
/**
|
||||
* Deactivates the extension
|
||||
*/
|
||||
public deactivate(): void {
|
||||
Utils.logDebug('Main controller deactivated');
|
||||
}
|
||||
|
||||
public activate(): Promise<boolean> {
|
||||
sqlops.tasks.registerTask('sp_whoisactive.install', e => this.openurl('http://whoisactive.com/downloads/'));
|
||||
sqlops.tasks.registerTask('sp_whoisactive.documentation', e => this.openurl('http://whoisactive.com/docs/'));
|
||||
sqlops.tasks.registerTask('sp_whoisactive.findBlockLeaders', e => this.onExecute(e, 'findBlockLeaders.sql'));
|
||||
sqlops.tasks.registerTask('sp_whoisactive.getPlans', e => this.onExecute(e, 'getPlans.sql'));
|
||||
public activate(): Promise<boolean> {
|
||||
azdata.tasks.registerTask('sp_whoisactive.install', e => this.openurl('http://whoisactive.com/downloads/'));
|
||||
azdata.tasks.registerTask('sp_whoisactive.documentation', e => this.openurl('http://whoisactive.com/docs/'));
|
||||
azdata.tasks.registerTask('sp_whoisactive.findBlockLeaders', e => this.onExecute(e, 'findBlockLeaders.sql'));
|
||||
azdata.tasks.registerTask('sp_whoisactive.getPlans', e => this.onExecute(e, 'getPlans.sql'));
|
||||
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
private openurl(link: string): void {
|
||||
openurl.open(link);
|
||||
}
|
||||
private openurl(link: string): void {
|
||||
openurl.open(link);
|
||||
}
|
||||
|
||||
private onExecute(connection: sqlops.IConnectionProfile, fileName: string): void {
|
||||
let sqlContent = fs.readFileSync(path.join(__dirname, '..', 'sql', fileName)).toString();
|
||||
vscode.workspace.openTextDocument({language: 'sql', content: sqlContent}).then(doc => {
|
||||
vscode.window.showTextDocument(doc, vscode.ViewColumn.Active, false).then(() => {
|
||||
let filePath = doc.uri.toString();
|
||||
sqlops.queryeditor.connect(filePath, connection.id).then(() => sqlops.queryeditor.runQuery(filePath));
|
||||
});
|
||||
});
|
||||
}
|
||||
private onExecute(connection: azdata.IConnectionProfile, fileName: string): void {
|
||||
let sqlContent = fs.readFileSync(path.join(__dirname, '..', 'sql', fileName)).toString();
|
||||
vscode.workspace.openTextDocument({language: 'sql', content: sqlContent}).then(doc => {
|
||||
vscode.window.showTextDocument(doc, vscode.ViewColumn.Active, false).then(() => {
|
||||
let filePath = doc.uri.toString();
|
||||
azdata.queryeditor.connect(filePath, connection.id).then(() => azdata.queryeditor.runQuery(filePath));
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ let tslint = require('gulp-tslint');
|
||||
let ts = require('gulp-typescript');
|
||||
let cproc = require('child_process');
|
||||
let os = require('os');
|
||||
let path = require('path');
|
||||
|
||||
let config = require('./config');
|
||||
let tsProject = ts.createProject('tsconfig.json');
|
||||
@@ -19,66 +20,66 @@ let tsProject = ts.createProject('tsconfig.json');
|
||||
|
||||
// GULP TASKS //////////////////////////////////////////////////////////////
|
||||
gulp.task('clean', function(done) {
|
||||
return del('out', done);
|
||||
return del('out', done);
|
||||
});
|
||||
|
||||
gulp.task('lint', () => {
|
||||
return gulp.src([
|
||||
config.paths.project.root + '/src/**/*.ts',
|
||||
config.paths.project.root + '/test/**/*.ts'
|
||||
])
|
||||
.pipe((tslint({
|
||||
formatter: "verbose"
|
||||
})))
|
||||
.pipe(tslint.report());
|
||||
return gulp.src([
|
||||
config.paths.project.root + '/src/**/*.ts',
|
||||
config.paths.project.root + '/test/**/*.ts'
|
||||
])
|
||||
.pipe((tslint({
|
||||
formatter: "verbose"
|
||||
})))
|
||||
.pipe(tslint.report());
|
||||
});
|
||||
|
||||
gulp.task('compile:src', function(done) {
|
||||
gulp.src([
|
||||
config.paths.project.root + '/src/**/*.sql',
|
||||
config.paths.project.root + '/src/**/*.svg',
|
||||
config.paths.project.root + '/src/**/*.html'
|
||||
]).pipe(gulp.dest('out/src/'));
|
||||
gulp.src([
|
||||
config.paths.project.root + '/src/**/*.sql',
|
||||
config.paths.project.root + '/src/**/*.svg',
|
||||
config.paths.project.root + '/src/**/*.html'
|
||||
]).pipe(gulp.dest('out/src/'));
|
||||
|
||||
let srcFiles = [
|
||||
config.paths.project.root + '/src/**/*.ts',
|
||||
config.paths.project.root + '/src/**/*.js',
|
||||
config.paths.project.root + '/typings/**/*.ts'
|
||||
];
|
||||
let srcFiles = [
|
||||
config.paths.project.root + '/src/**/*.ts',
|
||||
config.paths.project.root + '/src/**/*.js',
|
||||
config.paths.project.root + '/typings/**/*.ts'
|
||||
];
|
||||
|
||||
return gulp.src(srcFiles)
|
||||
.pipe(srcmap.init())
|
||||
.pipe(tsProject())
|
||||
.on('error', function() {
|
||||
if(process.env.BUILDMACHINE) {
|
||||
done('Failed to compile extension source, see above.');
|
||||
process.exit(1);
|
||||
}
|
||||
})
|
||||
// TODO: Reinstate localization code
|
||||
// .pipe(nls.rewriteLocalizeCalls())
|
||||
// .pipe(nls.createAdditionalLanguageFiles(nls.coreLanguages, config.paths.project.root + '/localization/i18n', undefined, false))
|
||||
.pipe(srcmap.write('.', { sourceRoot: function(file) { return file.cwd + '/src'; }}))
|
||||
.pipe(gulp.dest('out/src/'));
|
||||
return gulp.src(srcFiles)
|
||||
.pipe(srcmap.init())
|
||||
.pipe(tsProject())
|
||||
.on('error', function() {
|
||||
if(process.env.BUILDMACHINE) {
|
||||
done('Failed to compile extension source, see above.');
|
||||
process.exit(1);
|
||||
}
|
||||
})
|
||||
// TODO: Reinstate localization code
|
||||
// .pipe(nls.rewriteLocalizeCalls())
|
||||
// .pipe(nls.createAdditionalLanguageFiles(nls.coreLanguages, config.paths.project.root + '/localization/i18n', undefined, false))
|
||||
.pipe(srcmap.write('.', { sourceRoot: function(file) { return file.cwd + '/src'; }}))
|
||||
.pipe(gulp.dest('out/src/'));
|
||||
});
|
||||
|
||||
gulp.task('compile:test', function(done) {
|
||||
let srcFiles = [
|
||||
config.paths.project.root + '/test/**/*.ts',
|
||||
config.paths.project.root + '/typings/**/*.ts'
|
||||
];
|
||||
let srcFiles = [
|
||||
config.paths.project.root + '/test/**/*.ts',
|
||||
config.paths.project.root + '/typings/**/*.ts'
|
||||
];
|
||||
|
||||
return gulp.src(srcFiles)
|
||||
.pipe(srcmap.init())
|
||||
.pipe(tsProject())
|
||||
.on('error', function() {
|
||||
if(process.env.BUILDMACHINE) {
|
||||
done('Failed to compile test source, see above.');
|
||||
process.exit(1);
|
||||
}
|
||||
})
|
||||
.pipe(srcmap.write('.', {sourceRoot: function(file) { return file.cwd + '/test'; }}))
|
||||
.pipe(gulp.dest('out/test/'));
|
||||
return gulp.src(srcFiles)
|
||||
.pipe(srcmap.init())
|
||||
.pipe(tsProject())
|
||||
.on('error', function() {
|
||||
if(process.env.BUILDMACHINE) {
|
||||
done('Failed to compile test source, see above.');
|
||||
process.exit(1);
|
||||
}
|
||||
})
|
||||
.pipe(srcmap.write('.', {sourceRoot: function(file) { return file.cwd + '/test'; }}))
|
||||
.pipe(gulp.dest('out/test/'));
|
||||
});
|
||||
|
||||
// COMPOSED GULP TASKS /////////////////////////////////////////////////////
|
||||
@@ -87,37 +88,37 @@ gulp.task("compile", gulp.series("compile:src", "compile:test"));
|
||||
gulp.task("build", gulp.series("clean", "lint", "compile"));
|
||||
|
||||
gulp.task("watch", function() {
|
||||
gulp.watch([config.paths.project.root + '/src/**/*',
|
||||
config.paths.project.root + '/test/**/*.ts'],
|
||||
gulp.series('build'))
|
||||
gulp.watch([config.paths.project.root + '/src/**/*',
|
||||
config.paths.project.root + '/test/**/*.ts'],
|
||||
gulp.series('build'));
|
||||
});
|
||||
|
||||
gulp.task('test', (done) => {
|
||||
let workspace = process.env['WORKSPACE'];
|
||||
if (!workspace) {
|
||||
workspace = process.cwd();
|
||||
}
|
||||
process.env.JUNIT_REPORT_PATH = workspace + '/test-reports/ext_xunit.xml';
|
||||
let workspace = process.env['WORKSPACE'];
|
||||
if (!workspace) {
|
||||
workspace = process.cwd();
|
||||
}
|
||||
process.env.JUNIT_REPORT_PATH = workspace + '/test-reports/ext_xunit.xml';
|
||||
|
||||
let azuredatastudioPath = 'azuredatastudio';
|
||||
if (process.env['SQLOPS_DEV']) {
|
||||
let suffix = os.platform === 'win32' ? 'bat' : 'sh';
|
||||
azuredatastudioPath = `${process.env['SQLOPS_DEV']}/scripts/sql-cli.${suffix}`;
|
||||
}
|
||||
console.log(`Using SQLOPS Path of ${azuredatastudioPath}`);
|
||||
let azuredatastudioPath = 'azuredatastudio';
|
||||
if (process.env['VSCODE_DEV']) {
|
||||
let suffix = os.platform === 'win32' ? 'bat' : 'sh';
|
||||
azuredatastudioPath = `${process.env['VSCODE_DEV']}/scripts/sql-cli.${suffix}`;
|
||||
}
|
||||
console.log(`Using ADS Path of ${azuredatastudioPath}`);
|
||||
|
||||
cproc.exec(`${azuredatastudioPath} --extensionDevelopmentPath="${workspace}" --extensionTestsPath="${workspace}/out/test" --verbose`, (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
console.error(`exec error: ${error}`);
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(`stdout: ${stdout}`);
|
||||
console.log(`stderr: ${stderr}`);
|
||||
done();
|
||||
});
|
||||
cproc.exec(`${azuredatastudioPath} --extensionDevelopmentPath="${workspace}" --extensionTestsPath="${workspace}/out/test" --verbose`, (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
console.error(`exec error: ${error}`);
|
||||
process.exit(1);
|
||||
}
|
||||
console.log(`stdout: ${stdout}`);
|
||||
console.log(`stderr: ${stderr}`);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
gulp.task('copytypings', function() {
|
||||
return gulp.src(config.paths.project.root + '/../../src/sql/sqlops.proposed.d.ts')
|
||||
.pipe(gulp.dest('typings/'));
|
||||
return gulp.src(config.paths.project.root + '/../../src/sql/azdata.d.ts')
|
||||
.pipe(gulp.dest('typings/'));
|
||||
});
|
||||
|
||||
@@ -1,123 +1,123 @@
|
||||
{
|
||||
"rules": {
|
||||
"align": [
|
||||
true,
|
||||
"parameters",
|
||||
"statements"
|
||||
],
|
||||
"ban": false,
|
||||
"class-name": true,
|
||||
"comment-format": [
|
||||
true,
|
||||
"check-space"
|
||||
],
|
||||
"curly": true,
|
||||
"eofline": true,
|
||||
"forin": true,
|
||||
"indent": [
|
||||
true,
|
||||
"spaces"
|
||||
],
|
||||
"interface-name": true,
|
||||
"jsdoc-format": true,
|
||||
"label-position": true,
|
||||
"label-undefined": true,
|
||||
"max-line-length": [
|
||||
true,
|
||||
160
|
||||
],
|
||||
"member-access": false,
|
||||
"member-ordering": false,
|
||||
"no-any": false,
|
||||
"no-arg": true,
|
||||
"no-bitwise": true,
|
||||
"no-conditional-assignment": true,
|
||||
"no-consecutive-blank-lines": false,
|
||||
"no-console": [
|
||||
true,
|
||||
"debug",
|
||||
"info",
|
||||
"time",
|
||||
"timeEnd",
|
||||
"trace"
|
||||
],
|
||||
"no-construct": true,
|
||||
"no-constructor-vars": false,
|
||||
"no-debugger": true,
|
||||
"no-duplicate-key": true,
|
||||
"no-duplicate-variable": true,
|
||||
"no-empty": true,
|
||||
"no-eval": true,
|
||||
"no-inferrable-types": false,
|
||||
"no-internal-module": true,
|
||||
"no-null-keyword": true,
|
||||
"no-require-imports": false,
|
||||
"no-shadowed-variable": true,
|
||||
"no-string-literal": false,
|
||||
"no-switch-case-fall-through": false,
|
||||
"no-trailing-whitespace": true,
|
||||
"no-unreachable": true,
|
||||
"no-unused-expression": false,
|
||||
"no-unused-variable": true,
|
||||
"no-use-before-declare": true,
|
||||
"no-var-keyword": true,
|
||||
"no-var-requires": false,
|
||||
"object-literal-sort-keys": false,
|
||||
"one-line": [
|
||||
true,
|
||||
"check-open-brace",
|
||||
"check-catch",
|
||||
"check-else",
|
||||
"check-finally",
|
||||
"check-whitespace"
|
||||
],
|
||||
"quotemark": [
|
||||
true,
|
||||
"single",
|
||||
"avoid-escape"
|
||||
],
|
||||
"radix": true,
|
||||
"semicolon": true,
|
||||
"switch-default": true,
|
||||
"trailing-comma": [
|
||||
true,
|
||||
{
|
||||
"multiline": "never",
|
||||
"singleline": "never"
|
||||
}
|
||||
],
|
||||
"triple-equals": [
|
||||
true,
|
||||
"allow-null-check"
|
||||
],
|
||||
"typedef": [
|
||||
true,
|
||||
"call-signature",
|
||||
"property-declaration"
|
||||
],
|
||||
"typedef-whitespace": [
|
||||
true,
|
||||
{
|
||||
"call-signature": "nospace",
|
||||
"index-signature": "nospace",
|
||||
"parameter": "nospace",
|
||||
"property-declaration": "nospace",
|
||||
"variable-declaration": "nospace"
|
||||
}
|
||||
],
|
||||
"use-strict": false,
|
||||
"variable-name": [
|
||||
true,
|
||||
"allow-leading-underscore",
|
||||
"ban-keywords"
|
||||
],
|
||||
"whitespace": [
|
||||
true,
|
||||
"check-branch",
|
||||
"check-decl",
|
||||
"check-operator",
|
||||
"check-separator",
|
||||
"check-type"
|
||||
]
|
||||
}
|
||||
}
|
||||
"rules": {
|
||||
"align": [
|
||||
true,
|
||||
"parameters",
|
||||
"statements"
|
||||
],
|
||||
"ban": false,
|
||||
"class-name": true,
|
||||
"comment-format": [
|
||||
true,
|
||||
"check-space"
|
||||
],
|
||||
"curly": true,
|
||||
"eofline": true,
|
||||
"forin": true,
|
||||
"indent": [
|
||||
true,
|
||||
"tabs"
|
||||
],
|
||||
"interface-name": true,
|
||||
"jsdoc-format": true,
|
||||
"label-position": true,
|
||||
"label-undefined": true,
|
||||
"max-line-length": [
|
||||
true,
|
||||
160
|
||||
],
|
||||
"member-access": false,
|
||||
"member-ordering": false,
|
||||
"no-any": false,
|
||||
"no-arg": true,
|
||||
"no-bitwise": true,
|
||||
"no-conditional-assignment": true,
|
||||
"no-consecutive-blank-lines": false,
|
||||
"no-console": [
|
||||
true,
|
||||
"debug",
|
||||
"info",
|
||||
"time",
|
||||
"timeEnd",
|
||||
"trace"
|
||||
],
|
||||
"no-construct": true,
|
||||
"no-constructor-vars": false,
|
||||
"no-debugger": true,
|
||||
"no-duplicate-key": true,
|
||||
"no-duplicate-variable": true,
|
||||
"no-empty": true,
|
||||
"no-eval": true,
|
||||
"no-inferrable-types": false,
|
||||
"no-internal-module": true,
|
||||
"no-null-keyword": true,
|
||||
"no-require-imports": false,
|
||||
"no-shadowed-variable": true,
|
||||
"no-string-literal": false,
|
||||
"no-switch-case-fall-through": false,
|
||||
"no-trailing-whitespace": true,
|
||||
"no-unreachable": true,
|
||||
"no-unused-expression": false,
|
||||
"no-unused-variable": true,
|
||||
"no-use-before-declare": true,
|
||||
"no-var-keyword": true,
|
||||
"no-var-requires": false,
|
||||
"object-literal-sort-keys": false,
|
||||
"one-line": [
|
||||
true,
|
||||
"check-open-brace",
|
||||
"check-catch",
|
||||
"check-else",
|
||||
"check-finally",
|
||||
"check-whitespace"
|
||||
],
|
||||
"quotemark": [
|
||||
true,
|
||||
"single",
|
||||
"avoid-escape"
|
||||
],
|
||||
"radix": true,
|
||||
"semicolon": true,
|
||||
"switch-default": true,
|
||||
"trailing-comma": [
|
||||
true,
|
||||
{
|
||||
"multiline": "never",
|
||||
"singleline": "never"
|
||||
}
|
||||
],
|
||||
"triple-equals": [
|
||||
true,
|
||||
"allow-null-check"
|
||||
],
|
||||
"typedef": [
|
||||
true,
|
||||
"call-signature",
|
||||
"property-declaration"
|
||||
],
|
||||
"typedef-whitespace": [
|
||||
true,
|
||||
{
|
||||
"call-signature": "nospace",
|
||||
"index-signature": "nospace",
|
||||
"parameter": "nospace",
|
||||
"property-declaration": "nospace",
|
||||
"variable-declaration": "nospace"
|
||||
}
|
||||
],
|
||||
"use-strict": false,
|
||||
"variable-name": [
|
||||
true,
|
||||
"allow-leading-underscore",
|
||||
"ban-keywords"
|
||||
],
|
||||
"whitespace": [
|
||||
true,
|
||||
"check-branch",
|
||||
"check-decl",
|
||||
"check-operator",
|
||||
"check-separator",
|
||||
"check-type"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
89
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>;
|
||||
@@ -2984,10 +3051,9 @@ declare module 'azdata' {
|
||||
focused?: boolean;
|
||||
}
|
||||
|
||||
export interface TextComponentProperties {
|
||||
export interface TextComponentProperties extends ComponentProperties, TitledComponentProperties {
|
||||
value?: string;
|
||||
links?: LinkArea[];
|
||||
CSSStyles?: { [key: string]: string };
|
||||
}
|
||||
|
||||
export interface LinkArea {
|
||||
@@ -2995,7 +3061,7 @@ declare module 'azdata' {
|
||||
url: string;
|
||||
}
|
||||
|
||||
export interface HyperlinkComponentProperties extends ComponentProperties {
|
||||
export interface HyperlinkComponentProperties extends ComponentProperties, TitledComponentProperties {
|
||||
label: string;
|
||||
url: string;
|
||||
}
|
||||
@@ -3006,6 +3072,7 @@ declare module 'azdata' {
|
||||
editable?: boolean;
|
||||
fireOnTextChange?: boolean;
|
||||
ariaLabel?: string;
|
||||
required?: boolean;
|
||||
}
|
||||
|
||||
export interface DeclarativeTableColumn {
|
||||
@@ -3097,6 +3164,13 @@ declare module 'azdata' {
|
||||
clickable?: boolean;
|
||||
}
|
||||
|
||||
export interface TitledComponentProperties {
|
||||
/**
|
||||
* The title for the component. This title will show when hovered over
|
||||
*/
|
||||
title?: string;
|
||||
}
|
||||
|
||||
export interface CardComponent extends Component, CardProperties {
|
||||
onDidActionClick: vscode.Event<ActionDescriptor>;
|
||||
onCardSelectedChanged: vscode.Event<any>;
|
||||
@@ -3106,8 +3180,7 @@ declare module 'azdata' {
|
||||
|
||||
}
|
||||
|
||||
export interface TextComponent extends Component, ComponentProperties {
|
||||
value: string;
|
||||
export interface TextComponent extends Component, TextComponentProperties {
|
||||
/**
|
||||
* An event called when the text is clicked
|
||||
*/
|
||||
@@ -3115,8 +3188,6 @@ declare module 'azdata' {
|
||||
}
|
||||
|
||||
export interface HyperlinkComponent extends Component, HyperlinkComponentProperties {
|
||||
label: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
export interface InputBoxComponent extends Component, InputBoxProperties {
|
||||
@@ -3717,7 +3788,7 @@ declare module 'azdata' {
|
||||
* Namespace for interacting with query editor
|
||||
*/
|
||||
export namespace queryeditor {
|
||||
export type QueryEvent =
|
||||
export type QueryEventType =
|
||||
| 'queryStart'
|
||||
| 'queryUpdate'
|
||||
| 'queryStop'
|
||||
@@ -3732,7 +3803,7 @@ declare module 'azdata' {
|
||||
* visualize: ResultSetSummary
|
||||
*/
|
||||
export interface QueryEventListener {
|
||||
onQueryEvent(type: QueryEvent, document: queryeditor.QueryDocument, args: ResultSetSummary | string | undefined): void;
|
||||
onQueryEvent(type: QueryEventType, document: queryeditor.QueryDocument, args: ResultSetSummary | string | undefined): void;
|
||||
}
|
||||
|
||||
// new extensibility interfaces
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -190,9 +190,9 @@ export class TabbedPanel extends Disposable {
|
||||
}));
|
||||
|
||||
const insertBefore = !isUndefinedOrNull(index) ? this.tabList.children.item(index) : undefined;
|
||||
if (insertBefore && index) {
|
||||
this._tabOrder.copyWithin(index + 1, index);
|
||||
this._tabOrder[index] = tab.tab.identifier;
|
||||
if (insertBefore) {
|
||||
this._tabOrder.copyWithin(index! + 1, index!);
|
||||
this._tabOrder[index!] = tab.tab.identifier;
|
||||
this.tabList.insertBefore(tabHeaderElement, insertBefore);
|
||||
} else {
|
||||
this.tabList.append(tabHeaderElement);
|
||||
|
||||
@@ -28,6 +28,7 @@ export interface ICellRangeSelectorOptions {
|
||||
export interface ICellRangeSelector<T> extends Slick.Plugin<T> {
|
||||
onCellRangeSelected: Slick.Event<Slick.Range>;
|
||||
onBeforeCellRangeSelected: Slick.Event<Slick.Cell>;
|
||||
onAppendCellRangeSelected: Slick.Event<Slick.Range>;
|
||||
}
|
||||
|
||||
export interface ICellRangeDecorator {
|
||||
@@ -45,6 +46,7 @@ export class CellRangeSelector<T> implements ICellRangeSelector<T> {
|
||||
|
||||
public onBeforeCellRangeSelected = new Slick.Event<Slick.Cell>();
|
||||
public onCellRangeSelected = new Slick.Event<Slick.Range>();
|
||||
public onAppendCellRangeSelected = new Slick.Event<Slick.Range>();
|
||||
|
||||
constructor(private options: ICellRangeSelectorOptions) {
|
||||
require.__$__nodeRequire('slickgrid/plugins/slick.cellrangedecorator');
|
||||
@@ -138,11 +140,18 @@ export class CellRangeSelector<T> implements ICellRangeSelector<T> {
|
||||
if (!dd || !dd.range || !dd.range.start || !dd.range.end) {
|
||||
return;
|
||||
}
|
||||
this.onCellRangeSelected.notify(new Slick.Range(
|
||||
|
||||
let newRange = new Slick.Range(
|
||||
dd.range.start.row,
|
||||
dd.range.start.cell,
|
||||
dd.range.end.row,
|
||||
dd.range.end.cell
|
||||
));
|
||||
);
|
||||
|
||||
if (e.ctrlKey) {
|
||||
this.onAppendCellRangeSelected.notify(newRange);
|
||||
} else {
|
||||
this.onCellRangeSelected.notify(newRange);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,11 +36,14 @@ export class CellSelectionModel<T> implements Slick.SelectionModel<T, Array<Slic
|
||||
|
||||
public init(grid: Slick.Grid<T>) {
|
||||
this.grid = grid;
|
||||
this._handler.subscribe(this.grid.onActiveCellChanged, (e: Event, args: Slick.OnActiveCellChangedEventArgs<T>) => this.handleActiveCellChange(e, args));
|
||||
this._handler.subscribe(this.grid.onClick, (e: MouseEvent, args: Slick.OnActiveCellChangedEventArgs<T>) => this.handleActiveCellChange(e, args));
|
||||
this._handler.subscribe(this.grid.onKeyDown, (e: KeyboardEvent) => this.handleKeyDown(e));
|
||||
this._handler.subscribe(this.grid.onClick, (e: MouseEvent, args: Slick.OnClickEventArgs<T>) => this.handleIndividualCellSelection(e, args));
|
||||
this._handler.subscribe(this.grid.onHeaderClick, (e: MouseEvent, args: Slick.OnHeaderClickEventArgs<T>) => this.handleHeaderClick(e, args));
|
||||
this.grid.registerPlugin(this.selector);
|
||||
this._handler.subscribe(this.selector.onCellRangeSelected, (e: Event, range: Slick.Range) => this.handleCellRangeSelected(e, range));
|
||||
this._handler.subscribe(this.selector.onCellRangeSelected, (e: Event, range: Slick.Range) => this.handleCellRangeSelected(e, range, false));
|
||||
this._handler.subscribe(this.selector.onAppendCellRangeSelected, (e: Event, range: Slick.Range) => this.handleCellRangeSelected(e, range, true));
|
||||
|
||||
this._handler.subscribe(this.selector.onBeforeCellRangeSelected, (e: Event, cell: Slick.Cell) => this.handleBeforeCellRangeSelected(e, cell));
|
||||
}
|
||||
|
||||
@@ -87,13 +90,18 @@ export class CellSelectionModel<T> implements Slick.SelectionModel<T, Array<Slic
|
||||
return true;
|
||||
}
|
||||
|
||||
private handleCellRangeSelected(e: Event, range: Slick.Range) {
|
||||
private handleCellRangeSelected(e: Event, range: Slick.Range, append: boolean) {
|
||||
this.grid.setActiveCell(range.fromRow, range.fromCell, false, false, true);
|
||||
this.setSelectedRanges([range]);
|
||||
|
||||
if (append) {
|
||||
this.setSelectedRanges(this.insertIntoSelections(this.getSelectedRanges(), range));
|
||||
} else {
|
||||
this.setSelectedRanges([range]);
|
||||
}
|
||||
}
|
||||
|
||||
private handleActiveCellChange(e: Event, args: Slick.OnActiveCellChangedEventArgs<T>) {
|
||||
if (this.options.selectActiveCell && !isUndefinedOrNull(args.row) && !isUndefinedOrNull(args.cell)) {
|
||||
private handleActiveCellChange(e: MouseEvent, args: Slick.OnActiveCellChangedEventArgs<T>) {
|
||||
if (this.options.selectActiveCell && !isUndefinedOrNull(args.row) && !isUndefinedOrNull(args.cell) && !e.ctrlKey) {
|
||||
this.setSelectedRanges([new Slick.Range(args.row, args.cell)]);
|
||||
} else if (!this.options.selectActiveCell) {
|
||||
// clear the previous selection once the cell changes
|
||||
@@ -118,6 +126,119 @@ export class CellSelectionModel<T> implements Slick.SelectionModel<T, Array<Slic
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* DO NOT CALL THIS DIRECTLY - GO THROUGH INSERT INTO SELECTIONS
|
||||
*
|
||||
*/
|
||||
private mergeSelections(ranges: Array<Slick.Range>, range: Slick.Range) {
|
||||
// New ranges selection
|
||||
let newRanges: Array<Slick.Range> = [];
|
||||
|
||||
// Have we handled this value
|
||||
let handled = false;
|
||||
for (let current of ranges) {
|
||||
// We've already processed everything. Add everything left back to the list.
|
||||
if (handled) {
|
||||
newRanges.push(current);
|
||||
continue;
|
||||
}
|
||||
let newRange: Slick.Range | undefined = undefined;
|
||||
|
||||
// if the ranges are the same.
|
||||
if (current.fromRow === range.fromRow &&
|
||||
current.fromCell === range.fromCell &&
|
||||
current.toRow === range.toRow &&
|
||||
current.toCell === range.toCell) {
|
||||
// If we're actually not going to handle it during this loop
|
||||
// this region will be added with the handled boolean check
|
||||
continue;
|
||||
}
|
||||
|
||||
// Rows are the same - horizontal merging of the selection area
|
||||
if (current.fromRow === range.fromRow && current.toRow === range.toRow) {
|
||||
// Check if the new region is adjacent to the old selection group
|
||||
if (range.toCell + 1 === current.fromCell || range.fromCell - 1 === current.toCell) {
|
||||
handled = true;
|
||||
let fromCell = Math.min(range.fromCell, current.fromCell, range.toCell, current.toCell);
|
||||
let toCell = Math.max(range.fromCell, current.fromCell, range.toCell, current.toCell);
|
||||
newRange = new Slick.Range(range.fromRow, fromCell, range.toRow, toCell);
|
||||
}
|
||||
// Cells are the same - vertical merging of the selection area
|
||||
} else if (current.fromCell === range.fromCell && current.toCell === range.toCell) {
|
||||
// Check if the new region is adjacent to the old selection group
|
||||
if (range.toRow + 1 === current.fromRow || range.fromRow - 1 === current.toRow) {
|
||||
handled = true;
|
||||
let fromRow = Math.min(range.fromRow, current.fromRow, range.fromRow, current.fromRow);
|
||||
let toRow = Math.max(range.toRow, current.toRow, range.toRow, current.toRow);
|
||||
newRange = new Slick.Range(fromRow, range.fromCell, toRow, range.toCell);
|
||||
}
|
||||
}
|
||||
|
||||
if (newRange) {
|
||||
newRanges.push(newRange);
|
||||
} else {
|
||||
newRanges.push(current);
|
||||
}
|
||||
}
|
||||
|
||||
if (!handled) {
|
||||
newRanges.push(range);
|
||||
}
|
||||
|
||||
return {
|
||||
newRanges,
|
||||
handled
|
||||
};
|
||||
}
|
||||
|
||||
private insertIntoSelections(ranges: Array<Slick.Range>, range: Slick.Range): Array<Slick.Range> {
|
||||
let result = this.mergeSelections(ranges, range);
|
||||
let newRanges = result.newRanges;
|
||||
|
||||
// Keep merging the rows until we stop having changes
|
||||
let i = 0;
|
||||
while (true) {
|
||||
if (i++ > 10000) {
|
||||
console.error('InsertIntoSelection infinite loop: Report this error on github');
|
||||
break;
|
||||
}
|
||||
let shouldContinue = false;
|
||||
for (let current of newRanges) {
|
||||
result = this.mergeSelections(newRanges, current);
|
||||
if (result.handled) {
|
||||
shouldContinue = true;
|
||||
newRanges = result.newRanges;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldContinue) {
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return newRanges;
|
||||
}
|
||||
|
||||
private handleIndividualCellSelection(e: MouseEvent, args: Slick.OnClickEventArgs<T>) {
|
||||
if (!e.ctrlKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
let ranges: Array<Slick.Range>;
|
||||
|
||||
ranges = this.getSelectedRanges();
|
||||
ranges = this.insertIntoSelections(ranges, new Slick.Range(args.row, args.cell));
|
||||
|
||||
this.grid.setActiveCell(args.row, args.cell);
|
||||
this.setSelectedRanges(ranges);
|
||||
|
||||
e.preventDefault();
|
||||
e.stopImmediatePropagation();
|
||||
}
|
||||
|
||||
private handleKeyDown(e: KeyboardEvent) {
|
||||
/***
|
||||
* Кey codes
|
||||
|
||||
@@ -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,183 @@ 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");
|
||||
|
||||
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");
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
export class OpenLatestRunMaterializedNotebook extends Action {
|
||||
public static ID = 'notebookaction.openLatestRun';
|
||||
public static LABEL = nls.localize('notebookaction.openLatestRun', "Open Latest Run");
|
||||
|
||||
constructor(
|
||||
@ICommandService private _commandService: ICommandService,
|
||||
) {
|
||||
super(OpenLatestRunMaterializedNotebook.ID, OpenLatestRunMaterializedNotebook.LABEL);
|
||||
}
|
||||
|
||||
public run(actionInfo: IJobActionInfo): Promise<boolean> {
|
||||
actionInfo.component.openLastNRun(actionInfo.targetObject.job, 0, 1);
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,22 +43,27 @@ export interface IGridDataProvider {
|
||||
}
|
||||
|
||||
export async function getResultsString(provider: IGridDataProvider, selection: Slick.Range[], includeHeaders?: boolean): Promise<string> {
|
||||
let headers: Map<Number, string> = new Map();
|
||||
let rows: Map<Number, Map<Number, string>> = new Map();
|
||||
let copyTable: string[][] = [];
|
||||
const eol = provider.getEolString();
|
||||
|
||||
// create a mapping of the ranges to get promises
|
||||
let tasks = selection.map((range, i) => {
|
||||
return async () => {
|
||||
let selectionsCopy = selection;
|
||||
let startCol = range.fromCell;
|
||||
let startRow = range.fromRow;
|
||||
|
||||
const result = await provider.getRowData(range.fromRow, range.toRow - range.fromRow + 1);
|
||||
// If there was a previous selection separate it with a line break. Currently
|
||||
// when there are multiple selections they are never on the same line
|
||||
if (provider.shouldIncludeHeaders(includeHeaders)) {
|
||||
let columnHeaders = provider.getColumnHeaders(range);
|
||||
if (columnHeaders !== undefined) {
|
||||
if (copyTable[0] === undefined) {
|
||||
copyTable[0] = [];
|
||||
}
|
||||
copyTable[0].push(...columnHeaders);
|
||||
let columnHeaders = provider.getColumnHeaders(range);
|
||||
if (columnHeaders !== undefined) {
|
||||
let idx = 0;
|
||||
for (let header of columnHeaders) {
|
||||
headers.set(startCol + idx, header);
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
// Iterate over the rows to paste into the copy string
|
||||
@@ -70,11 +75,17 @@ export async function getResultsString(provider: IGridDataProvider, selection: S
|
||||
? cellObjects.map(x => removeNewLines(x.displayValue))
|
||||
: cellObjects.map(x => x.displayValue);
|
||||
|
||||
let idx = rowIndex + 1;
|
||||
if (copyTable[idx] === undefined) {
|
||||
copyTable[idx] = [];
|
||||
let idx = 0;
|
||||
for (let cell of cells) {
|
||||
let map = rows.get(rowIndex + startRow);
|
||||
if (!map) {
|
||||
map = new Map();
|
||||
rows.set(rowIndex + startRow, map);
|
||||
}
|
||||
|
||||
map.set(startCol + idx, cell);
|
||||
idx++;
|
||||
}
|
||||
copyTable[idx].push(...cells);
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -88,12 +99,26 @@ export async function getResultsString(provider: IGridDataProvider, selection: S
|
||||
}
|
||||
|
||||
let copyString = '';
|
||||
copyTable.forEach((row) => {
|
||||
if (row === undefined) {
|
||||
return;
|
||||
if (includeHeaders) {
|
||||
copyString = [...headers.values()].join('\t').concat(eol);
|
||||
}
|
||||
|
||||
const rowKeys = [...headers.keys()];
|
||||
|
||||
for (let rowEntry of rows) {
|
||||
let rowMap = rowEntry[1];
|
||||
for (let rowIdx of rowKeys) {
|
||||
|
||||
let value = rowMap.get(rowIdx);
|
||||
if (value) {
|
||||
copyString = copyString.concat(value);
|
||||
}
|
||||
copyString = copyString.concat('\t');
|
||||
}
|
||||
copyString = copyString.concat(row.join('\t').concat(eol));
|
||||
});
|
||||
copyString = copyString.concat(eol);
|
||||
}
|
||||
// Removes EoL from the end of the string
|
||||
copyString = copyString.slice(0, -1 * eol.length);
|
||||
|
||||
return copyString;
|
||||
}
|
||||
@@ -110,4 +135,4 @@ function removeNewLines(inputString: string): string {
|
||||
|
||||
let outputString: string = inputString.replace(/(\r\n|\n|\r)/gm, '');
|
||||
return outputString;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import QueryRunner from 'sql/platform/query/common/queryRunner';
|
||||
import QueryRunner, { IQueryMessage } from 'sql/platform/query/common/queryRunner';
|
||||
import { DataService } from 'sql/workbench/parts/grid/common/dataService';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
@@ -31,9 +31,15 @@ export interface IQueryPlanInfo {
|
||||
planXml: string;
|
||||
}
|
||||
|
||||
export interface IQueryInfo {
|
||||
selection: ISelectionData[];
|
||||
messages: IQueryMessage[];
|
||||
}
|
||||
|
||||
export interface IQueryEvent {
|
||||
type: queryeditor.QueryEvent;
|
||||
type: queryeditor.QueryEventType;
|
||||
uri: string;
|
||||
queryInfo: IQueryInfo;
|
||||
params?: any;
|
||||
}
|
||||
|
||||
|
||||
@@ -283,7 +283,12 @@ export class QueryModelService implements IQueryModelService {
|
||||
// fire extensibility API event
|
||||
let event: IQueryEvent = {
|
||||
type: 'queryStop',
|
||||
uri: uri
|
||||
uri: uri,
|
||||
queryInfo:
|
||||
{
|
||||
selection: info.selection,
|
||||
messages: info.queryRunner.messages
|
||||
}
|
||||
};
|
||||
this._onQueryEvent.fire(event);
|
||||
|
||||
@@ -296,7 +301,12 @@ export class QueryModelService implements IQueryModelService {
|
||||
// fire extensibility API event
|
||||
let event: IQueryEvent = {
|
||||
type: 'queryStart',
|
||||
uri: uri
|
||||
uri: uri,
|
||||
queryInfo:
|
||||
{
|
||||
selection: info.selection,
|
||||
messages: info.queryRunner.messages
|
||||
}
|
||||
};
|
||||
this._onQueryEvent.fire(event);
|
||||
|
||||
@@ -307,7 +317,12 @@ export class QueryModelService implements IQueryModelService {
|
||||
|
||||
let event: IQueryEvent = {
|
||||
type: 'queryUpdate',
|
||||
uri: uri
|
||||
uri: uri,
|
||||
queryInfo:
|
||||
{
|
||||
selection: info.selection,
|
||||
messages: info.queryRunner.messages
|
||||
}
|
||||
};
|
||||
this._onQueryEvent.fire(event);
|
||||
|
||||
@@ -319,6 +334,11 @@ export class QueryModelService implements IQueryModelService {
|
||||
let event: IQueryEvent = {
|
||||
type: 'executionPlan',
|
||||
uri: planInfo.fileUri,
|
||||
queryInfo:
|
||||
{
|
||||
selection: info.selection,
|
||||
messages: info.queryRunner.messages
|
||||
},
|
||||
params: planInfo
|
||||
};
|
||||
this._onQueryEvent.fire(event);
|
||||
@@ -328,6 +348,11 @@ export class QueryModelService implements IQueryModelService {
|
||||
let event: IQueryEvent = {
|
||||
type: 'visualize',
|
||||
uri: uri,
|
||||
queryInfo:
|
||||
{
|
||||
selection: info.selection,
|
||||
messages: info.queryRunner.messages
|
||||
},
|
||||
params: resultSetInfo
|
||||
};
|
||||
this._onQueryEvent.fire(event);
|
||||
@@ -443,7 +468,12 @@ export class QueryModelService implements IQueryModelService {
|
||||
// fire extensibility API event
|
||||
let event: IQueryEvent = {
|
||||
type: 'queryStop',
|
||||
uri: ownerUri
|
||||
uri: ownerUri,
|
||||
queryInfo:
|
||||
{
|
||||
selection: info.selection,
|
||||
messages: info.queryRunner.messages
|
||||
},
|
||||
};
|
||||
this._onQueryEvent.fire(event);
|
||||
|
||||
@@ -455,7 +485,12 @@ export class QueryModelService implements IQueryModelService {
|
||||
// fire extensibility API event
|
||||
let event: IQueryEvent = {
|
||||
type: 'queryStart',
|
||||
uri: ownerUri
|
||||
uri: ownerUri,
|
||||
queryInfo:
|
||||
{
|
||||
selection: info.selection,
|
||||
messages: info.queryRunner.messages
|
||||
},
|
||||
};
|
||||
this._onQueryEvent.fire(event);
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@ export interface IQueryMessage extends azdata.IResultMessage {
|
||||
export default class QueryRunner extends Disposable {
|
||||
// MEMBER VARIABLES ////////////////////////////////////////////////////
|
||||
private _resultLineOffset: number;
|
||||
private _resultColumnOffset: number;
|
||||
private _totalElapsedMilliseconds: number = 0;
|
||||
private _isExecuting: boolean = false;
|
||||
private _hasCompleted: boolean = false;
|
||||
@@ -179,6 +180,7 @@ export default class QueryRunner extends Disposable {
|
||||
if (types.isObject(input) || types.isUndefinedOrNull(input)) {
|
||||
// Update internal state to show that we're executing the query
|
||||
this._resultLineOffset = input ? input.startLine : 0;
|
||||
this._resultColumnOffset = input ? input.startColumn : 0;
|
||||
this._isExecuting = true;
|
||||
this._totalElapsedMilliseconds = 0;
|
||||
// TODO issue #228 add statusview callbacks here
|
||||
@@ -243,8 +245,10 @@ export default class QueryRunner extends Disposable {
|
||||
|
||||
this._batchSets.map(batch => {
|
||||
if (batch.selection) {
|
||||
batch.selection.startLine = batch.selection.startLine + this._resultLineOffset;
|
||||
batch.selection.endLine = batch.selection.endLine + this._resultLineOffset;
|
||||
batch.selection.startLine += this._resultLineOffset;
|
||||
batch.selection.startColumn += this._resultColumnOffset;
|
||||
batch.selection.endLine += this._resultLineOffset;
|
||||
batch.selection.endColumn += this._resultColumnOffset;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -271,7 +275,9 @@ export default class QueryRunner extends Disposable {
|
||||
// Recalculate the start and end lines, relative to the result line offset
|
||||
if (batch.selection) {
|
||||
batch.selection.startLine += this._resultLineOffset;
|
||||
batch.selection.startColumn += this._resultColumnOffset;
|
||||
batch.selection.endLine += this._resultLineOffset;
|
||||
batch.selection.endColumn += this._resultColumnOffset;
|
||||
}
|
||||
|
||||
// Set the result sets as an empty array so that as result sets complete we can add to the list
|
||||
|
||||
34
src/sql/platform/queryHistory/common/queryHistoryInfo.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import { IConnectionProfile } from 'sql/platform/connection/common/interfaces';
|
||||
|
||||
export enum QueryStatus {
|
||||
Succeeded = 0,
|
||||
Failed = 1,
|
||||
Nothing = 2
|
||||
}
|
||||
|
||||
/**
|
||||
* Contains information about a query that was ran
|
||||
*/
|
||||
export class QueryHistoryInfo {
|
||||
|
||||
public database: string;
|
||||
|
||||
public status: QueryStatus;
|
||||
|
||||
public readonly id = generateUuid();
|
||||
|
||||
constructor(
|
||||
public queryText: string,
|
||||
public connectionProfile: IConnectionProfile,
|
||||
public startTime: Date,
|
||||
status?: QueryStatus) {
|
||||
this.database = connectionProfile ? connectionProfile.databaseName : '';
|
||||
this.status = status;
|
||||
}
|
||||
}
|
||||