mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-17 11:01:37 -05:00
Compare commits
198 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d10e08e63e | ||
|
|
a8f21b56f0 | ||
|
|
173842510c | ||
|
|
19c08fe0eb | ||
|
|
db817a7192 | ||
|
|
2c8e93cc96 | ||
|
|
44e9a97f09 | ||
|
|
298ddc4195 | ||
|
|
d9134d6085 | ||
|
|
b17b4ce880 | ||
|
|
2304c32453 | ||
|
|
2b68e4a7df | ||
|
|
8f06e72318 | ||
|
|
5fa740ead4 | ||
|
|
e5096e61e5 | ||
|
|
2a3195636e | ||
|
|
5fb9b8ccd3 | ||
|
|
7089e2299a | ||
|
|
b553cbb68c | ||
|
|
c712411e77 | ||
|
|
48d5cc554c | ||
|
|
39bfd69dc9 | ||
|
|
d690b80493 | ||
|
|
eb48a9f993 | ||
|
|
5a54abaf44 | ||
|
|
47c161f9f1 | ||
|
|
807f8e68f3 | ||
|
|
fba8536c33 | ||
|
|
36fc1bb71a | ||
|
|
ccaa96c81e | ||
|
|
ac2f279c88 | ||
|
|
91cb99b8c8 | ||
|
|
02b1a525e3 | ||
|
|
9bf4a4b18c | ||
|
|
8a01553c49 | ||
|
|
21b913845f | ||
|
|
220e4feb1d | ||
|
|
13884c0457 | ||
|
|
b3bb6ebc6e | ||
|
|
424eb90dd8 | ||
|
|
df804d0729 | ||
|
|
79269cdfd5 | ||
|
|
2a650d4d74 | ||
|
|
017b4ecdb3 | ||
|
|
9c84bf3fd5 | ||
|
|
397b54a8c3 | ||
|
|
cb3604c0a1 | ||
|
|
1a7f0673ea | ||
|
|
0d043207b9 | ||
|
|
f995dea971 | ||
|
|
49e20488bc | ||
|
|
3e47b27192 | ||
|
|
f4fa18ec05 | ||
|
|
c6d1fa2b7d | ||
|
|
c4df7667ff | ||
|
|
6a303cfa25 | ||
|
|
4ffa5cc1da | ||
|
|
172e1cf3bf | ||
|
|
e6a32e52f5 | ||
|
|
d2b6f6844d | ||
|
|
e9ef95ef1f | ||
|
|
10eeb5374f | ||
|
|
332951bc8e | ||
|
|
4daf3280ff | ||
|
|
87bb2c74d9 | ||
|
|
ba011853a0 | ||
|
|
461a158ac3 | ||
|
|
85f59f1103 | ||
|
|
335b9f445f | ||
|
|
7cda45c904 | ||
|
|
4159fdc1a3 | ||
|
|
190da30979 | ||
|
|
ed9c74b900 | ||
|
|
6783766c33 | ||
|
|
b27018b379 | ||
|
|
021d07e04a | ||
|
|
bf0baec392 | ||
|
|
923cbac400 | ||
|
|
ab938f2536 | ||
|
|
a55b1804e9 | ||
|
|
4bfa6b3a5d | ||
|
|
d20f24be18 | ||
|
|
8a17bae7a6 | ||
|
|
d14c73fad5 | ||
|
|
ce878e1def | ||
|
|
a64a0d1db6 | ||
|
|
feab43f16d | ||
|
|
0d60fe775f | ||
|
|
6680be6a73 | ||
|
|
e026ab85a7 | ||
|
|
e53c903205 | ||
|
|
03dbe8565f | ||
|
|
708793cb23 | ||
|
|
43ae4fb0aa | ||
|
|
6b1d552277 | ||
|
|
f24f576b72 | ||
|
|
c23328564f | ||
|
|
cd6dd3dafa | ||
|
|
4081e15bef | ||
|
|
b05e3813d1 | ||
|
|
3048311f40 | ||
|
|
1045392d91 | ||
|
|
7b23ca8ee7 | ||
|
|
4d67eca8bb | ||
|
|
74c4b7311e | ||
|
|
713c74adfd | ||
|
|
408a8a6f19 | ||
|
|
e1485e49d3 | ||
|
|
30b66934cd | ||
|
|
fd49c081c2 | ||
|
|
1327120024 | ||
|
|
d2b5043972 | ||
|
|
a0e55ea3fd | ||
|
|
1f32de29c1 | ||
|
|
12be06d682 | ||
|
|
27ca9b13f8 | ||
|
|
be45905830 | ||
|
|
05d0a89655 | ||
|
|
3ba575dcd0 | ||
|
|
3e200b7f0f | ||
|
|
cbce1f7008 | ||
|
|
e99101447e | ||
|
|
0ddb326e44 | ||
|
|
460446a15c | ||
|
|
4eea24997f | ||
|
|
0b1e9c7c66 | ||
|
|
d51a7a9eb7 | ||
|
|
0f0b959e14 | ||
|
|
b2ceb09e4d | ||
|
|
53953f5cda | ||
|
|
fbd5e819a2 | ||
|
|
bdc391d376 | ||
|
|
6b618fb121 | ||
|
|
1f3e59c9f9 | ||
|
|
1956078c8c | ||
|
|
11230f59fc | ||
|
|
21bad7a01f | ||
|
|
6f9a27ecc7 | ||
|
|
c504113d13 | ||
|
|
c92ff60592 | ||
|
|
e9013d1a2a | ||
|
|
9c4580fe40 | ||
|
|
cb060cb5db | ||
|
|
6c3d85cc45 | ||
|
|
14ae89e87c | ||
|
|
24c48f025d | ||
|
|
f0a556f004 | ||
|
|
fd4d6abb4d | ||
|
|
41cc839380 | ||
|
|
c2a4380b96 | ||
|
|
6f402ac79f | ||
|
|
bf7c1306b1 | ||
|
|
c1509cf09d | ||
|
|
014bca031c | ||
|
|
4f864fd5bd | ||
|
|
2da67567e4 | ||
|
|
5b19d2b1fc | ||
|
|
a6837dcd40 | ||
|
|
af80751a1f | ||
|
|
dd02597c3b | ||
|
|
2926a3cbd8 | ||
|
|
b02bb3bfd4 | ||
|
|
67a4683bb1 | ||
|
|
9baee1c22c | ||
|
|
8cb67b4f9d | ||
|
|
0cd47bc328 | ||
|
|
07fb58d5e1 | ||
|
|
2d80d5e611 | ||
|
|
4bd63b615b | ||
|
|
335f667507 | ||
|
|
1819036d7d | ||
|
|
4f76f116ac | ||
|
|
1eba7c7d2a | ||
|
|
83234dd52c | ||
|
|
bae23b7fce | ||
|
|
3db61eaa82 | ||
|
|
5cf85a0361 | ||
|
|
ffe27f5bde | ||
|
|
78bcd9d54c | ||
|
|
1a9797f0ff | ||
|
|
0de94ff8a4 | ||
|
|
472233d9a7 | ||
|
|
f69e31b0d5 | ||
|
|
60b696cc31 | ||
|
|
549037f744 | ||
|
|
ca5e1e6133 | ||
|
|
3e3ff163db | ||
|
|
ca755365ce | ||
|
|
ea979de19f | ||
|
|
473ddfcdf1 | ||
|
|
a627285a4c | ||
|
|
322847469d | ||
|
|
6c5fac997f | ||
|
|
1871fd383e | ||
|
|
f5b147ca4b | ||
|
|
9d2b206156 | ||
|
|
a372c76e07 | ||
|
|
b1ce07d3ae |
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -38,5 +38,6 @@
|
||||
}
|
||||
}
|
||||
],
|
||||
"typescript.tsdk": "node_modules/typescript/lib"
|
||||
"typescript.tsdk": "node_modules/typescript/lib",
|
||||
"git.ignoreLimitWarning": true
|
||||
}
|
||||
|
||||
23
CHANGELOG.md
23
CHANGELOG.md
@@ -1,5 +1,26 @@
|
||||
# Change Log
|
||||
|
||||
## Version 0.31.4
|
||||
* Release date: July 19, 2018
|
||||
* Release status: Public Preview
|
||||
|
||||
## What's new in this version
|
||||
* SQL Server Agent for SQL Operations Studio extension improvements
|
||||
* Added view of Alerts, Operators, and Proxies and icons on left pane
|
||||
* Added dialogs for New Job, New Job Step, New Alert, and New Operator
|
||||
* Added Delete Job, Delete Alert, and Delete Operator (right-click)
|
||||
* Added Previous Runs visualization
|
||||
* Added Filters for each column name
|
||||
* SQL Server Profiler for SQL Operations Studio extension improvements
|
||||
* Added Hotkeys to quickly launch and start/stop Profiler
|
||||
* Added 5 Default Templates to view Extended Events
|
||||
* Added Server/Database connection name
|
||||
* Added support for Azure SQL Database instances
|
||||
* Added suggestion to exit Profiler when tab is closed when Profiler is still running
|
||||
* Release of Combine Scripts Extension
|
||||
* Wizard and Dialog Extensibility
|
||||
* Fix GitHub Issues
|
||||
|
||||
## Version 0.30.6
|
||||
* Release date: June 20, 2018
|
||||
* Release status: Public Preview
|
||||
@@ -22,7 +43,7 @@ The May release is focused on stabilization and bug fixes leading up to the Buil
|
||||
|
||||
* Announcing **Redgate SQL Search** extension available in Extension Manager
|
||||
* Community Localization available for 10 languages: **German, Spanish, French, Italian, Japanese, Korean, Portuguese, Russian, Simplified Chinese and Traditional Chinese!**
|
||||
* **GDPR-compliant** build has reduced telemetry collection, improved [opt-out](https://github.com/Microsoft/sqlopsstudio/wiki/How-to-Disable-Telemetry-Reporting) experience and in-product links to [Privacy Statement](https://privacy.microsoft.com/en-us/privacystatement)
|
||||
* Reduced telemetry collection, improved [opt-out](https://github.com/Microsoft/sqlopsstudio/wiki/How-to-Disable-Telemetry-Reporting) experience and in-product links to [Privacy Statement](https://privacy.microsoft.com/en-us/privacystatement)
|
||||
* Extension Manager has improved Marketplace experience to easily discover community extensions
|
||||
* SQL Agent extension Jobs and Job History view improvement
|
||||
* Updates for **whoisactive** and **Server Reports** extensions
|
||||
|
||||
14
README.md
14
README.md
@@ -8,12 +8,12 @@ SQL Operations Studio is a data management tool that enables you to work with SQ
|
||||
|
||||
Platform | Link
|
||||
-- | --
|
||||
Windows Setup Installer | https://go.microsoft.com/fwlink/?linkid=875602
|
||||
Windows ZIP | https://go.microsoft.com/fwlink/?linkid=875603
|
||||
macOS ZIP | https://go.microsoft.com/fwlink/?linkid=875604
|
||||
Linux TAR.GZ | https://go.microsoft.com/fwlink/?linkid=875605
|
||||
Linux RPM | https://go.microsoft.com/fwlink/?linkid=875606
|
||||
Linux DEB | https://go.microsoft.com/fwlink/?linkid=875607
|
||||
Windows Setup Installer | https://go.microsoft.com/fwlink/?linkid=2005949
|
||||
Windows ZIP | https://go.microsoft.com/fwlink/?linkid=2005950
|
||||
macOS ZIP | https://go.microsoft.com/fwlink/?linkid=2005959
|
||||
Linux TAR.GZ | https://go.microsoft.com/fwlink/?linkid=2005960
|
||||
Linux RPM | https://go.microsoft.com/fwlink/?linkid=2006083
|
||||
Linux DEB | https://go.microsoft.com/fwlink/?linkid=2006084
|
||||
|
||||
Go to our [download page](https://aka.ms/sqlopsstudio) for more specific instructions.
|
||||
|
||||
@@ -85,7 +85,7 @@ We would like to thank all our users who raised issues, and in particular the fo
|
||||
* Chinese (Traditional): Bruce Chen, Chiayi Yen, Kevin Yang, Winnie Lin, 保哥 Will, 謝政廷
|
||||
* Korean: Do-Kyun Kim, Evelyn Kim, Helen Jung, Hong Jmee, jeongwoo choi, Jun Hyoung Lee, Jungsun Kim정선, Justin Yoo, Kavrith mucha, Kiwoong Youm, MinGyu Ju, MVP_JUNO BEA, Sejun Kim, SOONMAN KWON, sung man ko, Yeongrak Choi, younggun kim, Youngjae Kim, 소영 이
|
||||
* Russian: Andrey Veselov, Anton Fontanov, Anton Savin, Elena Ostrovskaia, Igor Babichev, Maxim Zelensky, Rodion Fedechkin, Tasha T, Vladimir Zyryanov
|
||||
* Portuguese Brazil: Daniel de Sousa, Diogo Duarte, Douglas Correa, Douglas Eccker, José Emanuel Mendes, Marcelo Fernandes, Marcondes Alexandre, Roberto Fonseca, Rodrigo Crespi
|
||||
* Portuguese Brazil: Daniel de Sousa, Diogo Duarte, Douglas Correa, Douglas Eccker, José Emanuel Mendes, Marcelo Fernandes, Marcondes Alexandre, Roberto Fonseca, Rodrigo Crespi
|
||||
|
||||
|
||||
And of course we'd like to thank the authors of all upstream dependencies. Please see a full list in the [ThirdPartyNotices.txt](https://raw.githubusercontent.com/Microsoft/sqlopsstudio/master/ThirdPartyNotices.txt)
|
||||
|
||||
@@ -78,6 +78,7 @@ const sqlBuiltInExtensions = [
|
||||
// Add SQL built-in extensions here.
|
||||
// the extension will be excluded from SQLOps package and will have separate vsix packages
|
||||
'agent',
|
||||
'import',
|
||||
'profiler'
|
||||
];
|
||||
|
||||
@@ -273,7 +274,8 @@ function packageBuiltInExtensions() {
|
||||
console.info('Creating vsix for ' + element.path + ' result:' + packagePath);
|
||||
vsce.createVSIX({
|
||||
cwd: element.path,
|
||||
packagePath: packagePath
|
||||
packagePath: packagePath,
|
||||
useYarn: true
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -60,7 +60,9 @@ Type: files; Name: "{app}\resources\app\Credits_45.0.2454.85.html"; Check: IsNot
|
||||
Type: filesandordirs; Name: "{app}\_"
|
||||
|
||||
[Tasks]
|
||||
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
|
||||
Name: "quicklaunchicon"; Description: "{cm:CreateQuickLaunchIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked; OnlyBelowVersion: 0,6.1
|
||||
Name: "associatewithfiles"; Description: "{cm:AssociateWithFiles,{#NameShort}}"; GroupDescription: "{cm:Other}"; Flags: unchecked
|
||||
Name: "addtopath"; Description: "{cm:AddToPath}"; GroupDescription: "{cm:Other}"
|
||||
Name: "runcode"; Description: "{cm:RunAfter,{#NameShort}}"; GroupDescription: "{cm:Other}"; Check: WizardSilent
|
||||
|
||||
@@ -82,6 +84,13 @@ Root: HKCR; Subkey: "{#RegValueName}SourceFile\DefaultIcon"; ValueType: string;
|
||||
Root: HKCR; Subkey: "{#RegValueName}SourceFile\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""
|
||||
Root: HKCU; Subkey: "Environment"; ValueType: expandsz; ValueName: "Path"; ValueData: "{olddata};{app}\bin"; Tasks: addtopath; Check: NeedsAddPath(ExpandConstant('{app}\bin'))
|
||||
|
||||
Root: HKCU; Subkey: "Software\Classes\.sql\OpenWithProgids"; ValueType: none; ValueName: "{#RegValueName}"; Flags: deletevalue uninsdeletevalue; Tasks: associatewithfiles
|
||||
Root: HKCU; Subkey: "Software\Classes\.sql\OpenWithProgids"; ValueType: string; ValueName: "{#RegValueName}.sql"; ValueData: ""; Flags: uninsdeletevalue; Tasks: associatewithfiles
|
||||
Root: HKCU; Subkey: "Software\Classes\{#RegValueName}.sql"; ValueType: string; ValueName: ""; ValueData: "{cm:SourceFile,SQL}"; Flags: uninsdeletekey; Tasks: associatewithfiles
|
||||
Root: HKCU; Subkey: "Software\Classes\{#RegValueName}.sql"; ValueType: string; ValueName: "AppUserModelID"; ValueData: "{#AppUserId}"; Flags: uninsdeletekey; Tasks: associatewithfiles
|
||||
Root: HKCU; Subkey: "Software\Classes\{#RegValueName}.sql\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\resources\app\resources\win32\code_file.ico"; Tasks: associatewithfiles
|
||||
Root: HKCU; Subkey: "Software\Classes\{#RegValueName}.sql\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#ExeBasename}.exe"" ""%1"""; Tasks: associatewithfiles
|
||||
|
||||
[Code]
|
||||
// Don't allow installing conflicting architectures
|
||||
function InitializeSetup(): Boolean;
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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';
|
||||
import * as data from 'sqlops';
|
||||
import { ApiWrapper } from './apiWrapper';
|
||||
|
||||
/**
|
||||
* The main controller class that initializes the extension
|
||||
*/
|
||||
export class MainController {
|
||||
protected _apiWrapper: ApiWrapper;
|
||||
protected _context: vscode.ExtensionContext;
|
||||
|
||||
// PUBLIC METHODS //////////////////////////////////////////////////////
|
||||
public constructor(context: vscode.ExtensionContext, apiWrapper?: ApiWrapper) {
|
||||
this._apiWrapper = apiWrapper || new ApiWrapper();
|
||||
this._context = context;
|
||||
|
||||
console.log('Got: ' + apiWrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deactivates the extension
|
||||
*/
|
||||
public deactivate(): void {
|
||||
}
|
||||
|
||||
public activate(): void {
|
||||
|
||||
this._apiWrapper.registerWebviewProvider('data-management-agent', webview => {
|
||||
webview.html = '<div><h1>SQL Agent</h1></div>';
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,23 +1,20 @@
|
||||
{
|
||||
"name": "agent",
|
||||
"displayName": "SQL Server Agent",
|
||||
"description": "Manage and troubleshoot SQL Server Agent jobs (early preview)",
|
||||
"version": "0.29.0",
|
||||
"description": "Manage and troubleshoot SQL Server Agent jobs",
|
||||
"version": "0.32.4",
|
||||
"publisher": "Microsoft",
|
||||
"preview": true,
|
||||
"license": "https://raw.githubusercontent.com/Microsoft/sqlopsstudio/master/LICENSE.txt",
|
||||
"icon": "images/sqlserver.png",
|
||||
"aiKey": "AIF-5574968e-856d-40d2-af67-c89a14e76412",
|
||||
"engines": {
|
||||
"vscode": "0.10.x"
|
||||
"vscode": "0.10.x"
|
||||
},
|
||||
"activationEvents": [
|
||||
"*"
|
||||
],
|
||||
"main": "./client/out/main",
|
||||
"scripts": {
|
||||
"compile": "gulp compile-extension:agent-client"
|
||||
},
|
||||
"main": "./out/main",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Microsoft/sqlopsstudio.git"
|
||||
@@ -29,22 +26,32 @@
|
||||
"outputChannels": [
|
||||
"sqlagent"
|
||||
],
|
||||
"commands": [
|
||||
{
|
||||
"command": "agent.openNewStepDialog",
|
||||
"title": "agent.openNewStepDialog"
|
||||
}
|
||||
],
|
||||
"dashboard.tabs": [
|
||||
{
|
||||
"id": "data-management-agent",
|
||||
"description": "Manage and troubleshoot SQL Agent jobs",
|
||||
"provider": "MSSQL",
|
||||
"title": "SQL Agent",
|
||||
"when": "connectionProvider == 'MSSQL' && !mssql:iscloud",
|
||||
"container": {
|
||||
"controlhost-container": {
|
||||
"type": "agent"
|
||||
}
|
||||
}
|
||||
{
|
||||
"id": "data-management-agent",
|
||||
"description": "Manage and troubleshoot SQL Agent jobs",
|
||||
"provider": "MSSQL",
|
||||
"title": "SQL Agent",
|
||||
"when": "connectionProvider == 'MSSQL' && !mssql:iscloud",
|
||||
"container": {
|
||||
"controlhost-container": {
|
||||
"type": "agent"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"vscode-nls": "^3.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vscode": "1.0.1"
|
||||
}
|
||||
"mocha-junit-reporter": "^1.17.0",
|
||||
"mocha-multi-reporters": "^1.1.7"
|
||||
}
|
||||
}
|
||||
|
||||
38
extensions/agent/src/agentUtils.ts
Normal file
38
extensions/agent/src/agentUtils.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
'use strict';
|
||||
|
||||
import * as sqlops from 'sqlops';
|
||||
|
||||
export class AgentUtils {
|
||||
|
||||
private static _agentService: sqlops.AgentServicesProvider;
|
||||
private static _connectionService: sqlops.ConnectionProvider;
|
||||
private static _queryProvider: sqlops.QueryProvider;
|
||||
|
||||
public static async getAgentService(): Promise<sqlops.AgentServicesProvider> {
|
||||
if (!AgentUtils._agentService) {
|
||||
let currentConnection = await sqlops.connection.getCurrentConnection();
|
||||
this._agentService = sqlops.dataprotocol.getProvider<sqlops.AgentServicesProvider>(currentConnection.providerName, sqlops.DataProviderType.AgentServicesProvider);
|
||||
}
|
||||
return AgentUtils._agentService;
|
||||
}
|
||||
|
||||
public static async getDatabases(ownerUri: string): Promise<string[]> {
|
||||
if (!AgentUtils._connectionService) {
|
||||
let currentConnection = await sqlops.connection.getCurrentConnection();
|
||||
this._connectionService = sqlops.dataprotocol.getProvider<sqlops.ConnectionProvider>(currentConnection.providerName, sqlops.DataProviderType.ConnectionProvider);
|
||||
}
|
||||
return AgentUtils._connectionService.listDatabases(ownerUri).then(result => {
|
||||
if (result && result.databaseNames && result.databaseNames.length > 0) {
|
||||
return result.databaseNames;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static async getQueryProvider(): Promise<sqlops.QueryProvider> {
|
||||
if (!AgentUtils._queryProvider) {
|
||||
let currentConnection = await sqlops.connection.getCurrentConnection();
|
||||
this._queryProvider = sqlops.dataprotocol.getProvider<sqlops.QueryProvider>(currentConnection.providerName, sqlops.DataProviderType.QueryProvider);
|
||||
}
|
||||
return this._queryProvider;
|
||||
}
|
||||
}
|
||||
132
extensions/agent/src/data/alertData.ts
Normal file
132
extensions/agent/src/data/alertData.ts
Normal file
@@ -0,0 +1,132 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 vscode from 'vscode';
|
||||
import * as sqlops from 'sqlops';
|
||||
import { AgentUtils } from '../agentUtils';
|
||||
import { IAgentDialogData, AgentDialogMode } from '../interfaces';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export class AlertData implements IAgentDialogData {
|
||||
public static readonly AlertTypeSqlServerEventString: string = localize('alertData.DefaultAlertTypString', 'SQL Server event alert');
|
||||
public static readonly AlertTypePerformanceConditionString: string = localize('alertDialog.PerformanceCondition', 'SQL Server performance condition alert');
|
||||
public static readonly AlertTypeWmiEventString: string = localize('alertDialog.WmiEvent', 'WMI event alert');
|
||||
public static readonly DefaultAlertTypeString: string = AlertData.AlertTypeSqlServerEventString;
|
||||
|
||||
ownerUri: string;
|
||||
dialogMode: AgentDialogMode = AgentDialogMode.CREATE;
|
||||
id: number;
|
||||
name: string;
|
||||
originalName: string;
|
||||
delayBetweenResponses: number;
|
||||
eventDescriptionKeyword: string;
|
||||
eventSource: string;
|
||||
hasNotification: number;
|
||||
includeEventDescription: string;
|
||||
isEnabled: boolean = true;
|
||||
jobId: string;
|
||||
jobName: string;
|
||||
lastOccurrenceDate: string;
|
||||
lastResponseDate: string;
|
||||
messageId: number;
|
||||
notificationMessage: string;
|
||||
occurrenceCount: number;
|
||||
performanceCondition: string;
|
||||
severity: number;
|
||||
databaseName: string;
|
||||
countResetDate: string;
|
||||
categoryName: string;
|
||||
alertType: string = AlertData.DefaultAlertTypeString;
|
||||
wmiEventNamespace: string;
|
||||
wmiEventQuery: string;
|
||||
|
||||
constructor(ownerUri:string, alertInfo: sqlops.AgentAlertInfo) {
|
||||
this.ownerUri = ownerUri;
|
||||
|
||||
if (alertInfo) {
|
||||
this.dialogMode = AgentDialogMode.EDIT;
|
||||
this.id = alertInfo.id;
|
||||
this.name = alertInfo.name;
|
||||
this.originalName = alertInfo.name;
|
||||
this.delayBetweenResponses = alertInfo.delayBetweenResponses;
|
||||
this.eventDescriptionKeyword = alertInfo.eventDescriptionKeyword;
|
||||
this.eventSource = alertInfo.eventSource;
|
||||
this.hasNotification = alertInfo.hasNotification;
|
||||
this.includeEventDescription = alertInfo.includeEventDescription.toString();
|
||||
this.isEnabled = alertInfo.isEnabled;
|
||||
this.jobId = alertInfo.jobId;
|
||||
this.jobName = alertInfo.jobName;
|
||||
this.lastOccurrenceDate = alertInfo.lastOccurrenceDate;
|
||||
this.lastResponseDate = alertInfo.lastResponseDate;
|
||||
this.messageId = alertInfo.messageId;
|
||||
this.notificationMessage = alertInfo.notificationMessage;
|
||||
this.occurrenceCount = alertInfo.occurrenceCount;
|
||||
this.performanceCondition = alertInfo.performanceCondition;
|
||||
this.severity = alertInfo.severity;
|
||||
this.databaseName = alertInfo.databaseName;
|
||||
this.countResetDate = alertInfo.countResetDate;
|
||||
this.categoryName = alertInfo.categoryName;
|
||||
this.alertType = alertInfo.alertType.toString();
|
||||
this.wmiEventNamespace = alertInfo.wmiEventNamespace;
|
||||
this.wmiEventQuery = alertInfo.wmiEventQuery;
|
||||
}
|
||||
}
|
||||
|
||||
public async initialize() {
|
||||
}
|
||||
|
||||
public async save() {
|
||||
let agentService = await AgentUtils.getAgentService();
|
||||
let result = this.dialogMode === AgentDialogMode.CREATE
|
||||
? await agentService.createAlert(this.ownerUri, this.toAgentAlertInfo())
|
||||
: await agentService.updateAlert(this.ownerUri, this.originalName, this.toAgentAlertInfo());
|
||||
|
||||
if (!result || !result.success) {
|
||||
vscode.window.showErrorMessage(
|
||||
localize('alertData.saveErrorMessage', "Alert update failed '{0}'", result.errorMessage ? result.errorMessage : 'Unknown'));
|
||||
}
|
||||
}
|
||||
|
||||
public toAgentAlertInfo(): sqlops.AgentAlertInfo {
|
||||
return {
|
||||
id: this.id,
|
||||
name: this.name,
|
||||
delayBetweenResponses: this.delayBetweenResponses,
|
||||
eventDescriptionKeyword: this.eventDescriptionKeyword,
|
||||
eventSource: this.eventSource,
|
||||
hasNotification: this.hasNotification,
|
||||
includeEventDescription: sqlops.NotifyMethods.none, // this.includeEventDescription,
|
||||
isEnabled: this.isEnabled,
|
||||
jobId: this.jobId,
|
||||
jobName: this.jobName,
|
||||
lastOccurrenceDate: this.lastOccurrenceDate,
|
||||
lastResponseDate: this.lastResponseDate,
|
||||
messageId: this.messageId,
|
||||
notificationMessage: this.notificationMessage,
|
||||
occurrenceCount: this.occurrenceCount,
|
||||
performanceCondition: this.performanceCondition,
|
||||
severity: this.severity,
|
||||
databaseName: this.databaseName,
|
||||
countResetDate: this.countResetDate,
|
||||
categoryName: this.categoryName,
|
||||
alertType: AlertData.getAlertTypeFromString(this.alertType),
|
||||
wmiEventNamespace: this.wmiEventNamespace,
|
||||
wmiEventQuery: this.wmiEventQuery
|
||||
};
|
||||
}
|
||||
|
||||
private static getAlertTypeFromString(alertTypeString: string): sqlops.AlertType {
|
||||
if (alertTypeString === AlertData.AlertTypePerformanceConditionString) {
|
||||
return sqlops.AlertType.sqlServerPerformanceCondition;
|
||||
} else if (alertTypeString === AlertData.AlertTypeWmiEventString) {
|
||||
return sqlops.AlertType.wmiEvent;
|
||||
} else {
|
||||
return sqlops.AlertType.sqlServerEvent;
|
||||
}
|
||||
}
|
||||
}
|
||||
177
extensions/agent/src/data/jobData.ts
Normal file
177
extensions/agent/src/data/jobData.ts
Normal file
@@ -0,0 +1,177 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 sqlops from 'sqlops';
|
||||
import * as vscode from 'vscode';
|
||||
import { AgentUtils } from '../agentUtils';
|
||||
import { IAgentDialogData, AgentDialogMode } from '../interfaces';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export class JobData implements IAgentDialogData {
|
||||
|
||||
private readonly JobCompletionActionCondition_Always: string = localize('jobData.whenJobCompletes', 'When the job completes');
|
||||
private readonly JobCompletionActionCondition_OnFailure: string = localize('jobData.whenJobFails', 'When the job fails');
|
||||
private readonly JobCompletionActionCondition_OnSuccess: string = localize('jobData.whenJobSucceeds', 'When the job succeeds');
|
||||
|
||||
// Error Messages
|
||||
private readonly CreateJobErrorMessage_NameIsEmpty = localize('jobData.jobNameRequired', 'Job name must be provided');
|
||||
|
||||
private _ownerUri: string;
|
||||
private _jobCategories: string[];
|
||||
private _operators: string[];
|
||||
private _defaultOwner: string;
|
||||
private _jobCompletionActionConditions: sqlops.CategoryValue[];
|
||||
|
||||
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: sqlops.JobCompletionActionCondition = sqlops.JobCompletionActionCondition.OnFailure;
|
||||
public pageLevel: sqlops.JobCompletionActionCondition = sqlops.JobCompletionActionCondition.OnFailure;
|
||||
public eventLogLevel: sqlops.JobCompletionActionCondition = sqlops.JobCompletionActionCondition.OnFailure;
|
||||
public deleteLevel: sqlops.JobCompletionActionCondition = sqlops.JobCompletionActionCondition.OnSuccess;
|
||||
public operatorToEmail: string;
|
||||
public operatorToPage: string;
|
||||
public jobSteps: sqlops.AgentJobStepInfo[];
|
||||
public jobSchedules: sqlops.AgentJobScheduleInfo[];
|
||||
public alerts: sqlops.AgentAlertInfo[];
|
||||
|
||||
constructor(
|
||||
ownerUri: string,
|
||||
jobInfo: sqlops.AgentJobInfo = undefined,
|
||||
private _agentService: sqlops.AgentServicesProvider = undefined) {
|
||||
|
||||
this._ownerUri = ownerUri;
|
||||
if (jobInfo) {
|
||||
this.dialogMode = AgentDialogMode.EDIT;
|
||||
this.name = jobInfo.name;
|
||||
this.originalName = jobInfo.name;
|
||||
this.owner = jobInfo.owner;
|
||||
this.category = jobInfo.category;
|
||||
this.description = jobInfo.description;
|
||||
this.enabled = jobInfo.enabled;
|
||||
}
|
||||
}
|
||||
|
||||
public get jobCategories(): string[] {
|
||||
return this._jobCategories;
|
||||
}
|
||||
|
||||
public get operators(): string[] {
|
||||
return this._operators;
|
||||
}
|
||||
|
||||
public get ownerUri(): string {
|
||||
return this._ownerUri;
|
||||
}
|
||||
|
||||
public get defaultOwner(): string {
|
||||
return this._defaultOwner;
|
||||
}
|
||||
|
||||
public get JobCompletionActionConditions(): sqlops.CategoryValue[] {
|
||||
return this._jobCompletionActionConditions;
|
||||
}
|
||||
|
||||
public async initialize() {
|
||||
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._defaultOwner = jobDefaults.owner;
|
||||
|
||||
this._operators = ['', this._defaultOwner];
|
||||
}
|
||||
|
||||
this._jobCompletionActionConditions = [{
|
||||
displayName: this.JobCompletionActionCondition_OnSuccess,
|
||||
name: sqlops.JobCompletionActionCondition.OnSuccess.toString()
|
||||
}, {
|
||||
displayName: this.JobCompletionActionCondition_OnFailure,
|
||||
name: sqlops.JobCompletionActionCondition.OnFailure.toString()
|
||||
}, {
|
||||
displayName: this.JobCompletionActionCondition_Always,
|
||||
name: sqlops.JobCompletionActionCondition.Always.toString()
|
||||
}];
|
||||
|
||||
this.jobSchedules = [];
|
||||
}
|
||||
|
||||
public async save() {
|
||||
let jobInfo: sqlops.AgentJobInfo = this.toAgentJobInfo();
|
||||
let result = this.dialogMode === AgentDialogMode.CREATE
|
||||
? await this._agentService.createJob(this.ownerUri, jobInfo)
|
||||
: await this._agentService.updateJob(this.ownerUri, this.originalName, jobInfo);
|
||||
|
||||
if (!result || !result.success) {
|
||||
vscode.window.showErrorMessage(
|
||||
localize('jobData.saveErrorMessage', "Job update failed '{0}'", result.errorMessage ? result.errorMessage : 'Unknown'));
|
||||
}
|
||||
}
|
||||
|
||||
public validate(): { valid: boolean, errorMessages: string[] } {
|
||||
let validationErrors: string[] = [];
|
||||
|
||||
if (!(this.name && this.name.trim())) {
|
||||
validationErrors.push(this.CreateJobErrorMessage_NameIsEmpty);
|
||||
}
|
||||
|
||||
return {
|
||||
valid: validationErrors.length === 0,
|
||||
errorMessages: validationErrors
|
||||
};
|
||||
}
|
||||
|
||||
public addJobSchedule(schedule: sqlops.AgentJobScheduleInfo) {
|
||||
let existingSchedule = this.jobSchedules.find(item => item.name === schedule.name);
|
||||
if (!existingSchedule) {
|
||||
this.jobSchedules.push(schedule);
|
||||
}
|
||||
}
|
||||
|
||||
public toAgentJobInfo(): sqlops.AgentJobInfo {
|
||||
return {
|
||||
name: this.name,
|
||||
owner: this.owner,
|
||||
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,
|
||||
// The properties below are not collected from UI
|
||||
// We could consider using a seperate class for create job request
|
||||
//
|
||||
currentExecutionStatus: 0,
|
||||
lastRunOutcome: 0,
|
||||
currentExecutionStep: '',
|
||||
hasTarget: true,
|
||||
hasSchedule: false,
|
||||
hasStep: false,
|
||||
runnable: true,
|
||||
categoryId: 0,
|
||||
categoryType: 1, // LocalJob, hard-coding the value, corresponds to the target tab in SSMS
|
||||
lastRun: '',
|
||||
nextRun: '',
|
||||
jobId: ''
|
||||
};
|
||||
}
|
||||
}
|
||||
76
extensions/agent/src/data/jobStepData.ts
Normal file
76
extensions/agent/src/data/jobStepData.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { AgentUtils } from '../agentUtils';
|
||||
import { IAgentDialogData, AgentDialogMode } from '../interfaces';
|
||||
|
||||
export class JobStepData implements IAgentDialogData {
|
||||
public dialogMode: AgentDialogMode = AgentDialogMode.CREATE;
|
||||
public ownerUri: string;
|
||||
public jobId: string; //
|
||||
public jobName: string;
|
||||
public script: string; //
|
||||
public scriptName: string;
|
||||
public stepName: string; //
|
||||
public subSystem: string; //
|
||||
public id: number;
|
||||
public failureAction: string; //
|
||||
public successAction: string; //
|
||||
public failStepId: number;
|
||||
public successStepId: number;
|
||||
public command: string;
|
||||
public commandExecutionSuccessCode: number;
|
||||
public databaseName: string; //
|
||||
public databaseUserName: string;
|
||||
public server: string;
|
||||
public outputFileName: string; //
|
||||
public appendToLogFile: boolean;
|
||||
public appendToStepHist: boolean;
|
||||
public writeLogToTable: boolean;
|
||||
public appendLogToTable: boolean;
|
||||
public retryAttempts: number; //
|
||||
public retryInterval: number; //
|
||||
public proxyName: string;
|
||||
|
||||
constructor(ownerUri:string) {
|
||||
this.ownerUri = ownerUri;
|
||||
}
|
||||
|
||||
public async initialize() {
|
||||
}
|
||||
|
||||
public async save() {
|
||||
let agentService = await AgentUtils.getAgentService();
|
||||
agentService.createJobStep(this.ownerUri, {
|
||||
jobId: this.jobId,
|
||||
jobName: this.jobName,
|
||||
script: this.script,
|
||||
scriptName: this.scriptName,
|
||||
stepName: this.stepName,
|
||||
subSystem: this.subSystem,
|
||||
id: 1,
|
||||
failureAction: this.failureAction,
|
||||
successAction: this.successAction,
|
||||
failStepId: this.failStepId,
|
||||
successStepId: this.successStepId,
|
||||
command: this.command,
|
||||
commandExecutionSuccessCode: this.commandExecutionSuccessCode,
|
||||
databaseName: this.databaseName,
|
||||
databaseUserName: this.databaseUserName,
|
||||
server: this.server,
|
||||
outputFileName: this.outputFileName,
|
||||
appendToLogFile: this.appendToLogFile,
|
||||
appendToStepHist: this.appendToStepHist,
|
||||
writeLogToTable: this.writeLogToTable,
|
||||
appendLogToTable: this.appendLogToTable,
|
||||
retryAttempts: this.retryAttempts,
|
||||
retryInterval: this.retryInterval,
|
||||
proxyName: this.proxyName
|
||||
}).then(result => {
|
||||
console.info(result);
|
||||
});
|
||||
}
|
||||
}
|
||||
74
extensions/agent/src/data/operatorData.ts
Normal file
74
extensions/agent/src/data/operatorData.ts
Normal file
@@ -0,0 +1,74 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { AgentUtils } from '../agentUtils';
|
||||
import { IAgentDialogData, AgentDialogMode } from '../interfaces';
|
||||
|
||||
export class OperatorData implements IAgentDialogData {
|
||||
public dialogMode: AgentDialogMode = AgentDialogMode.CREATE;
|
||||
ownerUri: string;
|
||||
name: string;
|
||||
id: number;
|
||||
emailAddress: string;
|
||||
enabled: boolean;
|
||||
lastEmailDate: string;
|
||||
lastNetSendDate: string;
|
||||
lastPagerDate: string;
|
||||
pagerAddress: string;
|
||||
categoryName: string;
|
||||
pagerDays: string;
|
||||
saturdayPagerEndTime: string;
|
||||
saturdayPagerStartTime: string;
|
||||
sundayPagerEndTime: string;
|
||||
sundayPagerStartTime: string;
|
||||
netSendAddress: string;
|
||||
weekdayPagerStartTime: string;
|
||||
weekdayPagerEndTime: string;
|
||||
|
||||
constructor(ownerUri:string, operatorInfo: sqlops.AgentOperatorInfo) {
|
||||
this.ownerUri = ownerUri;
|
||||
|
||||
if (operatorInfo) {
|
||||
this.dialogMode = AgentDialogMode.EDIT;
|
||||
this.name = operatorInfo.name;
|
||||
this.enabled = operatorInfo.enabled;
|
||||
}
|
||||
}
|
||||
|
||||
public async initialize() {
|
||||
}
|
||||
|
||||
public async save() {
|
||||
let agentService = await AgentUtils.getAgentService();
|
||||
let result = await agentService.createOperator(this.ownerUri, this.toAgentOperatorInfo());
|
||||
if (!result || !result.success) {
|
||||
// TODO handle error here
|
||||
}
|
||||
}
|
||||
|
||||
public toAgentOperatorInfo(): sqlops.AgentOperatorInfo {
|
||||
return {
|
||||
name: this.name,
|
||||
id: this.id,
|
||||
emailAddress: this.emailAddress,
|
||||
enabled: this.enabled,
|
||||
lastEmailDate: this.lastEmailDate,
|
||||
lastNetSendDate: this.lastNetSendDate,
|
||||
lastPagerDate: this.lastPagerDate,
|
||||
pagerAddress: this.pagerAddress,
|
||||
categoryName: this.categoryName,
|
||||
pagerDays: sqlops.WeekDays.weekDays, //this.pagerDays,
|
||||
saturdayPagerEndTime: this.saturdayPagerEndTime,
|
||||
saturdayPagerStartTime: this.saturdayPagerStartTime,
|
||||
sundayPagerEndTime: this.sundayPagerEndTime,
|
||||
sundayPagerStartTime: this.sundayPagerStartTime,
|
||||
netSendAddress: this.netSendAddress,
|
||||
weekdayPagerStartTime: this.weekdayPagerStartTime,
|
||||
weekdayPagerEndTime: this.weekdayPagerEndTime
|
||||
};
|
||||
}
|
||||
}
|
||||
31
extensions/agent/src/data/pickScheduleData.ts
Normal file
31
extensions/agent/src/data/pickScheduleData.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { AgentUtils } from '../agentUtils';
|
||||
import { IAgentDialogData, AgentDialogMode } from '../interfaces';
|
||||
|
||||
export class PickScheduleData implements IAgentDialogData {
|
||||
public dialogMode: AgentDialogMode = AgentDialogMode.VIEW;
|
||||
public ownerUri: string;
|
||||
public schedules: sqlops.AgentJobScheduleInfo[];
|
||||
public selectedSchedule: sqlops.AgentJobScheduleInfo;
|
||||
|
||||
constructor(ownerUri:string) {
|
||||
this.ownerUri = ownerUri;
|
||||
}
|
||||
|
||||
public async initialize() {
|
||||
let agentService = await AgentUtils.getAgentService();
|
||||
let result = await agentService.getJobSchedules(this.ownerUri);
|
||||
if (result && result.success) {
|
||||
this.schedules = result.schedules;
|
||||
}
|
||||
}
|
||||
|
||||
public async save() {
|
||||
}
|
||||
}
|
||||
54
extensions/agent/src/data/proxyData.ts
Normal file
54
extensions/agent/src/data/proxyData.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { AgentUtils } from '../agentUtils';
|
||||
import { IAgentDialogData, AgentDialogMode } from '../interfaces';
|
||||
|
||||
export class ProxyData implements IAgentDialogData {
|
||||
public dialogMode: AgentDialogMode = AgentDialogMode.CREATE;
|
||||
ownerUri: string;
|
||||
id: number;
|
||||
accountName: string;
|
||||
description: string;
|
||||
credentialName: string;
|
||||
credentialIdentity: string;
|
||||
credentialId: number;
|
||||
isEnabled: boolean;
|
||||
|
||||
constructor(ownerUri:string, proxyInfo: sqlops.AgentProxyInfo) {
|
||||
this.ownerUri = ownerUri;
|
||||
|
||||
if (proxyInfo) {
|
||||
this.accountName = proxyInfo.accountName;
|
||||
this.credentialName = proxyInfo.credentialName;
|
||||
this.description = proxyInfo.description;
|
||||
}
|
||||
}
|
||||
|
||||
public async initialize() {
|
||||
}
|
||||
|
||||
public async save() {
|
||||
let agentService = await AgentUtils.getAgentService();
|
||||
let result = await agentService.createProxy(this.ownerUri, this.toAgentProxyInfo());
|
||||
if (!result || !result.success) {
|
||||
// TODO handle error here
|
||||
}
|
||||
}
|
||||
|
||||
public toAgentProxyInfo(): sqlops.AgentProxyInfo {
|
||||
return {
|
||||
id: this.id,
|
||||
accountName: this.accountName,
|
||||
description: this.description,
|
||||
credentialName: this.credentialName,
|
||||
credentialIdentity: this.credentialIdentity,
|
||||
credentialId: this.credentialId,
|
||||
isEnabled: this.isEnabled
|
||||
};
|
||||
}
|
||||
}
|
||||
31
extensions/agent/src/data/scheduleData.ts
Normal file
31
extensions/agent/src/data/scheduleData.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { AgentUtils } from '../agentUtils';
|
||||
import { IAgentDialogData, AgentDialogMode } from '../interfaces';
|
||||
|
||||
export class ScheduleData implements IAgentDialogData {
|
||||
public dialogMode: AgentDialogMode = AgentDialogMode.CREATE;
|
||||
public ownerUri: string;
|
||||
public schedules: sqlops.AgentJobScheduleInfo[];
|
||||
public selectedSchedule: sqlops.AgentJobScheduleInfo;
|
||||
|
||||
constructor(ownerUri:string) {
|
||||
this.ownerUri = ownerUri;
|
||||
}
|
||||
|
||||
public async initialize() {
|
||||
let agentService = await AgentUtils.getAgentService();
|
||||
let result = await agentService.getJobSchedules(this.ownerUri);
|
||||
if (result && result.success) {
|
||||
this.schedules = result.schedules;
|
||||
}
|
||||
}
|
||||
|
||||
public async save() {
|
||||
}
|
||||
}
|
||||
78
extensions/agent/src/dialogs/agentDialog.ts
Normal file
78
extensions/agent/src/dialogs/agentDialog.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 sqlops from 'sqlops';
|
||||
import * as vscode from 'vscode';
|
||||
import { IAgentDialogData, AgentDialogMode } from '../interfaces';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export abstract class AgentDialog<T extends IAgentDialogData> {
|
||||
|
||||
private static readonly OkButtonText: string = localize('agentDialog.OK', 'OK');
|
||||
private static readonly CancelButtonText: string = localize('agentDialog.Cancel', 'Cancel');
|
||||
|
||||
protected _onSuccess: vscode.EventEmitter<T> = new vscode.EventEmitter<T>();
|
||||
public readonly onSuccess: vscode.Event<T> = this._onSuccess.event;
|
||||
public dialog: sqlops.window.modelviewdialog.Dialog;
|
||||
|
||||
constructor(public ownerUri: string, public model: T, public title: string) {
|
||||
}
|
||||
|
||||
public get dialogMode(): AgentDialogMode {
|
||||
return this.model.dialogMode;
|
||||
}
|
||||
|
||||
protected abstract async updateModel();
|
||||
|
||||
protected abstract async initializeDialog(dialog: sqlops.window.modelviewdialog.Dialog);
|
||||
|
||||
public async openDialog() {
|
||||
this.dialog = sqlops.window.modelviewdialog.createDialog(this.title);
|
||||
|
||||
await this.model.initialize();
|
||||
|
||||
await this.initializeDialog(this.dialog);
|
||||
|
||||
this.dialog.okButton.label = AgentDialog.OkButtonText;
|
||||
this.dialog.okButton.onClick(async () => await this.execute());
|
||||
|
||||
this.dialog.cancelButton.label = AgentDialog.CancelButtonText;
|
||||
this.dialog.cancelButton.onClick(async () => await this.cancel());
|
||||
|
||||
sqlops.window.modelviewdialog.openDialog(this.dialog);
|
||||
}
|
||||
|
||||
protected async execute() {
|
||||
this.updateModel();
|
||||
let success = await this.model.save();
|
||||
if (success) {
|
||||
this._onSuccess.fire(this.model);
|
||||
}
|
||||
}
|
||||
|
||||
protected async cancel() {
|
||||
}
|
||||
|
||||
protected getActualConditionValue(checkbox: sqlops.CheckBoxComponent, dropdown: sqlops.DropDownComponent): sqlops.JobCompletionActionCondition {
|
||||
return checkbox.checked ? Number(this.getDropdownValue(dropdown)) : sqlops.JobCompletionActionCondition.Never;
|
||||
}
|
||||
|
||||
protected getDropdownValue(dropdown: sqlops.DropDownComponent): string {
|
||||
return (typeof dropdown.value === 'string') ? dropdown.value : dropdown.value.name;
|
||||
}
|
||||
|
||||
protected setConditionDropdownSelectedValue(dropdown: sqlops.DropDownComponent, selectedValue: number) {
|
||||
let idx: number = 0;
|
||||
for (idx = 0; idx < dropdown.values.length; idx++) {
|
||||
if (Number((<sqlops.CategoryValue>dropdown.values[idx]).name) === selectedValue) {
|
||||
dropdown.value = dropdown.values[idx];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
528
extensions/agent/src/dialogs/alertDialog.ts
Normal file
528
extensions/agent/src/dialogs/alertDialog.ts
Normal file
@@ -0,0 +1,528 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 sqlops from 'sqlops';
|
||||
import { AgentDialog } from './agentDialog';
|
||||
import { AgentUtils } from '../agentUtils';
|
||||
import { AlertData } from '../data/alertData';
|
||||
import { OperatorDialog } from './operatorDialog';
|
||||
import { JobDialog } from './jobDialog';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export class AlertDialog extends AgentDialog<AlertData> {
|
||||
|
||||
// Top level
|
||||
private static readonly CreateDialogTitle: string = localize('alertDialog.createAlert', 'Create Alert');
|
||||
private static readonly EditDialogTitle: string = localize('alertDialog.editAlert', 'Edit Alert');
|
||||
private static readonly GeneralTabText: string = localize('alertDialog.General', 'General');
|
||||
private static readonly ResponseTabText: string = localize('alertDialog.Response', 'Response');
|
||||
private static readonly OptionsTabText: string = localize('alertDialog.Options', 'Options');
|
||||
private static readonly EventAlertText: string = localize('alertDialog.eventAlert', 'Event alert definition');
|
||||
|
||||
// General tab strings
|
||||
private static readonly NameLabel: string = localize('alertDialog.Name', 'Name');
|
||||
private static readonly TypeLabel: string = localize('alertDialog.Type', 'Type');
|
||||
private static readonly EnabledCheckboxLabel: string = localize('alertDialog.Enabled', 'Enabled');
|
||||
private static readonly DatabaseLabel: string = localize('alertDialog.DatabaseName', 'Database name');
|
||||
private static readonly ErrorNumberLabel: string = localize('alertDialog.ErrorNumber', 'Error number');
|
||||
private static readonly SeverityLabel: string = localize('alertDialog.Severity', 'Severity');
|
||||
private static readonly RaiseIfMessageContainsLabel: string = localize('alertDialog.RaiseAlertContains', 'Raise alert when message contains');
|
||||
private static readonly MessageTextLabel: string = localize('alertDialog.MessageText', 'Message text');
|
||||
private static readonly AlertSeverity001Label: string = localize('alertDialog.Severity001', '001 - Miscellaneous System Information');
|
||||
private static readonly AlertSeverity002Label: string = localize('alertDialog.Severity002', '002 - Reserved');
|
||||
private static readonly AlertSeverity003Label: string = localize('alertDialog.Severity003', '003 - Reserved');
|
||||
private static readonly AlertSeverity004Label: string = localize('alertDialog.Severity004', '004 - Reserved');
|
||||
private static readonly AlertSeverity005Label: string = localize('alertDialog.Severity005', '005 - Reserved');
|
||||
private static readonly AlertSeverity006Label: string = localize('alertDialog.Severity006', '006 - Reserved');
|
||||
private static readonly AlertSeverity007Label: string = localize('alertDialog.Severity007', '007 - Notification: Status Information');
|
||||
private static readonly AlertSeverity008Label: string = localize('alertDialog.Severity008', '008 - Notification: User Intervention Required');
|
||||
private static readonly AlertSeverity009Label: string = localize('alertDialog.Severity009', '009 - User Defined');
|
||||
private static readonly AlertSeverity010Label: string = localize('alertDialog.Severity010', '010 - Information');
|
||||
private static readonly AlertSeverity011Label: string = localize('alertDialog.Severity011', '011 - Specified Database Object Not Found');
|
||||
private static readonly AlertSeverity012Label: string = localize('alertDialog.Severity012', '012 - Unused');
|
||||
private static readonly AlertSeverity013Label: string = localize('alertDialog.Severity013', '013 - User Transaction Syntax Error');
|
||||
private static readonly AlertSeverity014Label: string = localize('alertDialog.Severity014', '014 - Insufficient Permission');
|
||||
private static readonly AlertSeverity015Label: string = localize('alertDialog.Severity015', '015 - Syntax Error in SQL Statements');
|
||||
private static readonly AlertSeverity016Label: string = localize('alertDialog.Severity016', '016 - Miscellaneous User Error');
|
||||
private static readonly AlertSeverity017Label: string = localize('alertDialog.Severity017', '017 - Insufficient Resources');
|
||||
private static readonly AlertSeverity018Label: string = localize('alertDialog.Severity018', '018 - Nonfatal Internal Error');
|
||||
private static readonly AlertSeverity019Label: string = localize('alertDialog.Severity019', '019 - Fatal Error in Resource');
|
||||
private static readonly AlertSeverity020Label: string = localize('alertDialog.Severity020', '020 - Fatal Error in Current Process');
|
||||
private static readonly AlertSeverity021Label: string = localize('alertDialog.Severity021', '021 - Fatal Error in Database Processes');
|
||||
private static readonly AlertSeverity022Label: string = localize('alertDialog.Severity022', '022 - Fatal Error: Table Integrity Suspect');
|
||||
private static readonly AlertSeverity023Label: string = localize('alertDialog.Severity023', '023 - Fatal Error: Database Integrity Suspect');
|
||||
private static readonly AlertSeverity024Label: string = localize('alertDialog.Severity024', '024 - Fatal Error: Hardware Error');
|
||||
private static readonly AlertSeverity025Label: string = localize('alertDialog.Severity025', '025 - Fatal Error');
|
||||
private static readonly AllDatabases: string = localize('alertDialog.AllDatabases', '<all databases>');
|
||||
|
||||
private static readonly AlertTypes: string[] = [
|
||||
AlertData.AlertTypeSqlServerEventString,
|
||||
// Disabled until next release
|
||||
// AlertData.AlertTypePerformanceConditionString,
|
||||
// AlertData.AlertTypeWmiEventString
|
||||
];
|
||||
|
||||
private static readonly AlertSeverities: string[] = [
|
||||
AlertDialog.AlertSeverity001Label,
|
||||
AlertDialog.AlertSeverity002Label,
|
||||
AlertDialog.AlertSeverity003Label,
|
||||
AlertDialog.AlertSeverity004Label,
|
||||
AlertDialog.AlertSeverity005Label,
|
||||
AlertDialog.AlertSeverity006Label,
|
||||
AlertDialog.AlertSeverity007Label,
|
||||
AlertDialog.AlertSeverity008Label,
|
||||
AlertDialog.AlertSeverity009Label,
|
||||
AlertDialog.AlertSeverity010Label,
|
||||
AlertDialog.AlertSeverity011Label,
|
||||
AlertDialog.AlertSeverity012Label,
|
||||
AlertDialog.AlertSeverity013Label,
|
||||
AlertDialog.AlertSeverity014Label,
|
||||
AlertDialog.AlertSeverity015Label,
|
||||
AlertDialog.AlertSeverity016Label,
|
||||
AlertDialog.AlertSeverity017Label,
|
||||
AlertDialog.AlertSeverity018Label,
|
||||
AlertDialog.AlertSeverity019Label,
|
||||
AlertDialog.AlertSeverity020Label,
|
||||
AlertDialog.AlertSeverity021Label,
|
||||
AlertDialog.AlertSeverity022Label,
|
||||
AlertDialog.AlertSeverity023Label,
|
||||
AlertDialog.AlertSeverity024Label,
|
||||
AlertDialog.AlertSeverity025Label
|
||||
];
|
||||
|
||||
// Response tab strings
|
||||
private static readonly ExecuteJobCheckBoxLabel: string = localize('alertDialog.ExecuteJob', 'Execute Job');
|
||||
private static readonly ExecuteJobTextBoxLabel: string = localize('alertDialog.ExecuteJobName', 'Job Name');
|
||||
private static readonly NotifyOperatorsTextBoxLabel: string = localize('alertDialog.NotifyOperators', 'Notify Operators');
|
||||
private static readonly NewJobButtonLabel: string = localize('alertDialog.NewJob', 'New Job');
|
||||
private static readonly OperatorListLabel: string = localize('alertDialog.OperatorList', 'Operator List');
|
||||
private static readonly OperatorNameColumnLabel: string = localize('alertDialog.OperatorName', 'Operator');
|
||||
private static readonly OperatorEmailColumnLabel: string = localize('alertDialog.OperatorEmail', 'E-mail');
|
||||
private static readonly OperatorPagerColumnLabel: string = localize('alertDialog.OperatorPager', 'Pager');
|
||||
private static readonly NewOperatorButtonLabel: string = localize('alertDialog.NewOperator', 'New Operator');
|
||||
|
||||
// Options tab strings
|
||||
private static readonly IncludeErrorInEmailCheckBoxLabel: string = localize('alertDialog.IncludeErrorInEmail', 'Include alert error text in e-mail');
|
||||
private static readonly IncludeErrorInPagerCheckBoxLabel: string = localize('alertDialog.IncludeErrorInPager', 'Include alert error text in pager');
|
||||
private static readonly AdditionalMessageTextBoxLabel: string = localize('alertDialog.AdditionalNotification', 'Additional notification message to send');
|
||||
private static readonly DelayBetweenResponsesTextBoxLabel: string = localize('alertDialog.DelayBetweenResponse', 'Delay between responses');
|
||||
private static readonly DelayMinutesTextBoxLabel: string = localize('alertDialog.DelayMinutes', 'Delay Minutes');
|
||||
private static readonly DelaySecondsTextBoxLabel: string = localize('alertDialog.DelaySeconds', 'Delay Seconds');
|
||||
|
||||
// UI Components
|
||||
private generalTab: sqlops.window.modelviewdialog.DialogTab;
|
||||
private responseTab: sqlops.window.modelviewdialog.DialogTab;
|
||||
private optionsTab: sqlops.window.modelviewdialog.DialogTab;
|
||||
|
||||
// General tab controls
|
||||
private nameTextBox: sqlops.InputBoxComponent;
|
||||
private typeDropDown: sqlops.DropDownComponent;
|
||||
private severityDropDown: sqlops.DropDownComponent;
|
||||
private databaseDropDown: sqlops.DropDownComponent;
|
||||
private enabledCheckBox: sqlops.CheckBoxComponent;
|
||||
private errorNumberRadioButton: sqlops.RadioButtonComponent;
|
||||
private severityRadioButton: sqlops.RadioButtonComponent;
|
||||
private errorNumberTextBox: sqlops.InputBoxComponent;
|
||||
|
||||
private raiseAlertMessageCheckBox: sqlops.CheckBoxComponent;
|
||||
private raiseAlertMessageTextBox: sqlops.InputBoxComponent;
|
||||
|
||||
// Response tab controls
|
||||
private executeJobTextBox: sqlops.InputBoxComponent;
|
||||
private executeJobCheckBox: sqlops.CheckBoxComponent;
|
||||
private newJobButton: sqlops.ButtonComponent;
|
||||
private notifyOperatorsCheckBox: sqlops.CheckBoxComponent;
|
||||
private operatorsTable: sqlops.TableComponent;
|
||||
private newOperatorButton: sqlops.ButtonComponent;
|
||||
|
||||
// Options tab controls
|
||||
private additionalMessageTextBox: sqlops.InputBoxComponent;
|
||||
private includeErrorInEmailTextBox: sqlops.CheckBoxComponent;
|
||||
private includeErrorInPagerTextBox: sqlops.CheckBoxComponent;
|
||||
private delayMinutesTextBox: sqlops.InputBoxComponent;
|
||||
private delaySecondsTextBox: sqlops.InputBoxComponent;
|
||||
|
||||
private jobs: string[];
|
||||
private databases: string[];
|
||||
|
||||
constructor(ownerUri: string, alertInfo: sqlops.AgentAlertInfo = undefined, jobs: string[]) {
|
||||
super(ownerUri,
|
||||
new AlertData(ownerUri, alertInfo),
|
||||
alertInfo ? AlertDialog.EditDialogTitle : AlertDialog.CreateDialogTitle);
|
||||
this.jobs = jobs;
|
||||
}
|
||||
|
||||
protected async initializeDialog(dialog: sqlops.window.modelviewdialog.Dialog) {
|
||||
this.databases = await AgentUtils.getDatabases(this.ownerUri);
|
||||
this.databases.unshift(AlertDialog.AllDatabases);
|
||||
|
||||
this.generalTab = sqlops.window.modelviewdialog.createTab(AlertDialog.GeneralTabText);
|
||||
this.responseTab = sqlops.window.modelviewdialog.createTab(AlertDialog.ResponseTabText);
|
||||
this.optionsTab = sqlops.window.modelviewdialog.createTab(AlertDialog.OptionsTabText);
|
||||
|
||||
this.initializeGeneralTab(this.databases, dialog);
|
||||
this.initializeResponseTab();
|
||||
this.initializeOptionsTab();
|
||||
|
||||
dialog.content = [this.generalTab, this.responseTab, this.optionsTab];
|
||||
}
|
||||
|
||||
private initializeGeneralTab(databases: string[], dialog: sqlops.window.modelviewdialog.Dialog) {
|
||||
this.generalTab.registerContent(async view => {
|
||||
// create controls
|
||||
this.nameTextBox = view.modelBuilder.inputBox().component();
|
||||
this.nameTextBox.required = true;
|
||||
this.nameTextBox.onTextChanged(() => {
|
||||
if (this.nameTextBox.value.length > 0) {
|
||||
dialog.okButton.enabled = true;
|
||||
} else {
|
||||
dialog.okButton.enabled = false;
|
||||
}
|
||||
});
|
||||
this.enabledCheckBox = view.modelBuilder.checkBox()
|
||||
.withProperties({
|
||||
label: AlertDialog.EnabledCheckboxLabel
|
||||
}).component();
|
||||
|
||||
this.enabledCheckBox.checked = true;
|
||||
|
||||
this.databaseDropDown = view.modelBuilder.dropDown()
|
||||
.withProperties({
|
||||
value: databases[0],
|
||||
values: databases,
|
||||
width: '100%'
|
||||
}).component();
|
||||
|
||||
this.typeDropDown = view.modelBuilder.dropDown()
|
||||
.withProperties({
|
||||
value: '',
|
||||
values: AlertDialog.AlertTypes,
|
||||
width: '100%'
|
||||
}).component();
|
||||
|
||||
this.severityRadioButton = view.modelBuilder.radioButton()
|
||||
.withProperties({
|
||||
value: 'serverity',
|
||||
name: 'alertTypeOptions',
|
||||
label: AlertDialog.SeverityLabel,
|
||||
checked: true
|
||||
}).component();
|
||||
this.severityRadioButton.checked = true;
|
||||
|
||||
this.severityDropDown = view.modelBuilder.dropDown()
|
||||
.withProperties({
|
||||
value: AlertDialog.AlertSeverities[0],
|
||||
values: AlertDialog.AlertSeverities,
|
||||
width: '100%'
|
||||
}).component();
|
||||
|
||||
this.errorNumberRadioButton = view.modelBuilder.radioButton()
|
||||
.withProperties({
|
||||
value: 'errorNumber',
|
||||
name: 'alertTypeOptions',
|
||||
label: AlertDialog.ErrorNumberLabel
|
||||
}).component();
|
||||
|
||||
this.errorNumberTextBox = view.modelBuilder.inputBox()
|
||||
.withProperties({
|
||||
width: '100%'
|
||||
})
|
||||
.component();
|
||||
this.errorNumberTextBox.enabled = false;
|
||||
|
||||
this.errorNumberRadioButton.onDidClick(() => {
|
||||
this.errorNumberTextBox.enabled = true;
|
||||
this.severityDropDown.enabled = false;
|
||||
});
|
||||
|
||||
this.severityRadioButton.onDidClick(() => {
|
||||
this.errorNumberTextBox.enabled = false;
|
||||
this.severityDropDown.enabled = true;
|
||||
});
|
||||
|
||||
this.raiseAlertMessageCheckBox = view.modelBuilder.checkBox()
|
||||
.withProperties({
|
||||
label: AlertDialog.RaiseIfMessageContainsLabel
|
||||
}).component();
|
||||
|
||||
this.raiseAlertMessageTextBox = view.modelBuilder.inputBox().component();
|
||||
this.raiseAlertMessageTextBox.enabled = false;
|
||||
|
||||
this.raiseAlertMessageCheckBox.onChanged(() => {
|
||||
this.raiseAlertMessageTextBox.enabled = this.raiseAlertMessageCheckBox.checked;
|
||||
});
|
||||
|
||||
let formModel = view.modelBuilder.formContainer()
|
||||
.withFormItems([{
|
||||
component: this.nameTextBox,
|
||||
title: AlertDialog.NameLabel
|
||||
}, {
|
||||
component: this.enabledCheckBox,
|
||||
title: ''
|
||||
}, {
|
||||
component: this.typeDropDown,
|
||||
title: AlertDialog.TypeLabel
|
||||
}, {
|
||||
components: [{
|
||||
component: this.databaseDropDown,
|
||||
title: AlertDialog.DatabaseLabel
|
||||
},
|
||||
{
|
||||
component: this.severityRadioButton,
|
||||
title: ''
|
||||
},
|
||||
{
|
||||
component: this.severityDropDown,
|
||||
title: ''
|
||||
},
|
||||
{
|
||||
component: this.errorNumberRadioButton,
|
||||
title: ''
|
||||
},
|
||||
{
|
||||
component: this.errorNumberTextBox,
|
||||
title: ''
|
||||
},
|
||||
{
|
||||
component: this.raiseAlertMessageCheckBox,
|
||||
title: ''
|
||||
}, {
|
||||
component: this.raiseAlertMessageTextBox,
|
||||
title: AlertDialog.MessageTextLabel
|
||||
}],
|
||||
title: AlertDialog.EventAlertText
|
||||
}
|
||||
]).withLayout({ width: '100%' }).component();
|
||||
|
||||
await view.initializeModel(formModel);
|
||||
|
||||
// initialize control values
|
||||
this.nameTextBox.value = this.model.name;
|
||||
this.raiseAlertMessageTextBox.value = this.model.eventDescriptionKeyword;
|
||||
this.typeDropDown.value = this.model.alertType;
|
||||
this.enabledCheckBox.checked = this.model.isEnabled;
|
||||
|
||||
if (this.model.messageId > 0) {
|
||||
this.errorNumberRadioButton.checked = true;
|
||||
this.errorNumberTextBox.value = this.model.messageId.toString();
|
||||
}
|
||||
|
||||
if (this.model.severity > 0) {
|
||||
this.severityRadioButton.checked = true;
|
||||
this.severityDropDown.value = this.severityDropDown.values[this.model.severity-1];
|
||||
}
|
||||
|
||||
if (this.model.databaseName) {
|
||||
let idx = this.databases.indexOf(this.model.databaseName);
|
||||
if (idx >= 0) {
|
||||
this.databaseDropDown.value = this.databases[idx];
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private initializeResponseTab() {
|
||||
this.responseTab.registerContent(async view => {
|
||||
this.executeJobCheckBox = view.modelBuilder.checkBox()
|
||||
.withProperties({
|
||||
label: AlertDialog.ExecuteJobCheckBoxLabel
|
||||
}).component();
|
||||
|
||||
this.executeJobTextBox = view.modelBuilder.inputBox()
|
||||
.withProperties({ width: 375 })
|
||||
.component();
|
||||
this.executeJobTextBox.enabled = false;
|
||||
this.newJobButton = view.modelBuilder.button().withProperties({
|
||||
label: AlertDialog.NewJobButtonLabel,
|
||||
width: 80
|
||||
}).component();
|
||||
this.newJobButton.enabled = false;
|
||||
this.newJobButton.onDidClick(() => {
|
||||
let jobDialog = new JobDialog(this.ownerUri);
|
||||
jobDialog.openDialog();
|
||||
});
|
||||
|
||||
this.executeJobCheckBox.onChanged(() => {
|
||||
if (this.executeJobCheckBox.checked) {
|
||||
this.executeJobTextBox.enabled = true;
|
||||
this.newJobButton.enabled = true;
|
||||
} else {
|
||||
this.executeJobTextBox.enabled = false;
|
||||
this.newJobButton.enabled = false;
|
||||
}
|
||||
});
|
||||
|
||||
let executeJobContainer = view.modelBuilder.formContainer()
|
||||
.withFormItems([{
|
||||
component: this.executeJobTextBox,
|
||||
title: AlertDialog.ExecuteJobTextBoxLabel
|
||||
}, {
|
||||
component: this.newJobButton,
|
||||
title: AlertDialog.NewJobButtonLabel
|
||||
}], { componentWidth: '100%'}).component();
|
||||
|
||||
this.notifyOperatorsCheckBox = view.modelBuilder.checkBox()
|
||||
.withProperties({
|
||||
label: AlertDialog.NotifyOperatorsTextBoxLabel
|
||||
}).component();
|
||||
|
||||
this.operatorsTable = view.modelBuilder.table()
|
||||
.withProperties({
|
||||
columns: [
|
||||
AlertDialog.OperatorNameColumnLabel,
|
||||
AlertDialog.OperatorEmailColumnLabel,
|
||||
AlertDialog.OperatorPagerColumnLabel
|
||||
],
|
||||
data: [],
|
||||
height: 500,
|
||||
width: 375
|
||||
}).component();
|
||||
|
||||
this.newOperatorButton = view.modelBuilder.button().withProperties({
|
||||
label: AlertDialog.NewOperatorButtonLabel,
|
||||
width: 80
|
||||
}).component();
|
||||
|
||||
this.operatorsTable.enabled = false;
|
||||
this.newOperatorButton.enabled = false;
|
||||
|
||||
this.newOperatorButton.onDidClick(() => {
|
||||
let operatorDialog = new OperatorDialog(this.ownerUri);
|
||||
operatorDialog.openDialog();
|
||||
});
|
||||
|
||||
this.notifyOperatorsCheckBox.onChanged(() => {
|
||||
if (this.notifyOperatorsCheckBox.checked) {
|
||||
this.operatorsTable.enabled = true;
|
||||
this.newOperatorButton.enabled = true;
|
||||
} else {
|
||||
this.operatorsTable.enabled = false;
|
||||
this.newOperatorButton.enabled = false;
|
||||
}
|
||||
});
|
||||
|
||||
let notifyOperatorContainer = view.modelBuilder.formContainer()
|
||||
.withFormItems([{
|
||||
component: this.operatorsTable,
|
||||
title: AlertDialog.OperatorListLabel
|
||||
}, {
|
||||
component: this.newOperatorButton,
|
||||
title: ''
|
||||
}], { componentWidth: '100%'}).component();
|
||||
|
||||
let formModel = view.modelBuilder.formContainer()
|
||||
.withFormItems([{
|
||||
component: this.executeJobCheckBox,
|
||||
title: ''
|
||||
}, {
|
||||
component: executeJobContainer,
|
||||
title: ''
|
||||
}, {
|
||||
component: this.notifyOperatorsCheckBox,
|
||||
title: ''
|
||||
}, {
|
||||
component: notifyOperatorContainer,
|
||||
title: ''
|
||||
}])
|
||||
.withLayout({ width: '100%' }).component();
|
||||
|
||||
await view.initializeModel(formModel);
|
||||
});
|
||||
}
|
||||
|
||||
private initializeOptionsTab() {
|
||||
this.optionsTab.registerContent(async view => {
|
||||
|
||||
this.includeErrorInEmailTextBox = view.modelBuilder.checkBox()
|
||||
.withProperties({
|
||||
label: AlertDialog.IncludeErrorInEmailCheckBoxLabel
|
||||
}).component();
|
||||
|
||||
this.includeErrorInPagerTextBox = view.modelBuilder.checkBox()
|
||||
.withProperties({
|
||||
label: AlertDialog.IncludeErrorInPagerCheckBoxLabel
|
||||
}).component();
|
||||
|
||||
this.additionalMessageTextBox = view.modelBuilder.inputBox().component();
|
||||
|
||||
this.delayMinutesTextBox = view.modelBuilder.inputBox()
|
||||
.withProperties({
|
||||
inputType: 'number',
|
||||
placeHolder: 0
|
||||
})
|
||||
.component();
|
||||
|
||||
this.delaySecondsTextBox = view.modelBuilder.inputBox()
|
||||
.withProperties({
|
||||
inputType: 'number',
|
||||
placeHolder: 0
|
||||
})
|
||||
.component();
|
||||
|
||||
let formModel = view.modelBuilder.formContainer()
|
||||
.withFormItems([{
|
||||
component: this.includeErrorInEmailTextBox,
|
||||
title: ''
|
||||
}, {
|
||||
component: this.includeErrorInPagerTextBox,
|
||||
title: ''
|
||||
}, {
|
||||
component: this.additionalMessageTextBox,
|
||||
title: AlertDialog.AdditionalMessageTextBoxLabel
|
||||
}, {
|
||||
component: this.delayMinutesTextBox,
|
||||
title: AlertDialog.DelayMinutesTextBoxLabel
|
||||
}, {
|
||||
component: this.delaySecondsTextBox,
|
||||
title: AlertDialog.DelaySecondsTextBoxLabel
|
||||
}]).withLayout({ width: '100%' }).component();
|
||||
|
||||
await view.initializeModel(formModel);
|
||||
});
|
||||
}
|
||||
|
||||
private getSeverityNumber(): number {
|
||||
let selected = this.getDropdownValue(this.severityDropDown);
|
||||
let severityNumber: number = 0;
|
||||
if (selected) {
|
||||
let index = AlertDialog.AlertSeverities.indexOf(selected);
|
||||
if (index >= 0) {
|
||||
severityNumber = index + 1;
|
||||
}
|
||||
}
|
||||
return severityNumber;
|
||||
}
|
||||
|
||||
protected updateModel() {
|
||||
this.model.name = this.nameTextBox.value;
|
||||
this.model.isEnabled = this.enabledCheckBox.checked;
|
||||
|
||||
this.model.alertType = this.getDropdownValue(this.typeDropDown);
|
||||
let databaseName = this.getDropdownValue(this.databaseDropDown);
|
||||
this.model.databaseName = (databaseName !== AlertDialog.AllDatabases) ? databaseName : undefined;
|
||||
|
||||
if (this.severityRadioButton.checked) {
|
||||
this.model.severity = this.getSeverityNumber();
|
||||
this.model.messageId = 0;
|
||||
} else {
|
||||
this.model.severity = 0;
|
||||
this.model.messageId = +this.errorNumberTextBox.value;
|
||||
}
|
||||
|
||||
if (this.raiseAlertMessageCheckBox.checked) {
|
||||
this.model.eventDescriptionKeyword = this.raiseAlertMessageTextBox.value;
|
||||
} else {
|
||||
this.model.eventDescriptionKeyword = '';
|
||||
}
|
||||
let minutes = this.delayMinutesTextBox.value ? +this.delayMinutesTextBox.value : 0;
|
||||
let seconds = this.delaySecondsTextBox.value ? +this.delaySecondsTextBox : 0;
|
||||
this.model.delayBetweenResponses = minutes + seconds;
|
||||
|
||||
}
|
||||
}
|
||||
466
extensions/agent/src/dialogs/jobDialog.ts
Normal file
466
extensions/agent/src/dialogs/jobDialog.ts
Normal file
@@ -0,0 +1,466 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 sqlops from 'sqlops';
|
||||
import { JobData } from '../data/jobData';
|
||||
import { JobStepDialog } from './jobStepDialog';
|
||||
import { PickScheduleDialog } from './pickScheduleDialog';
|
||||
import { AlertDialog } from './alertDialog';
|
||||
import { AgentDialog } from './agentDialog';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export class JobDialog extends AgentDialog<JobData> {
|
||||
|
||||
// TODO: localize
|
||||
// Top level
|
||||
private static readonly CreateDialogTitle: string = localize('jobDialog.newJob', 'New Job');
|
||||
private static readonly EditDialogTitle: string = localize('jobDialog.editJob', 'Edit Job');
|
||||
private readonly GeneralTabText: string = localize('jobDialog.general', 'General');
|
||||
private readonly StepsTabText: string = localize('jobDialog.steps', 'Steps');
|
||||
private readonly SchedulesTabText: string = localize('jobDialog.schedules', 'Schedules');
|
||||
private readonly AlertsTabText: string = localize('jobDialog.alerts', 'Alerts');
|
||||
private readonly NotificationsTabText: string = localize('jobDialog.notifications', 'Notifications');
|
||||
private readonly BlankJobNameErrorText: string = localize('jobDialog.blankJobNameError', 'The name of the job cannot be blank.');
|
||||
|
||||
// General tab strings
|
||||
private readonly NameTextBoxLabel: string = localize('jobDialog.name', 'Name');
|
||||
private readonly OwnerTextBoxLabel: string = localize('jobDialog.owner', 'Owner');
|
||||
private readonly CategoryDropdownLabel: string = localize('jobDialog.category', 'Category');
|
||||
private readonly DescriptionTextBoxLabel: string = localize('jobDialog.description', 'Description');
|
||||
private readonly EnabledCheckboxLabel: string = localize('jobDialog.enabled', 'Enabled');
|
||||
|
||||
// Steps tab strings
|
||||
private readonly JobStepsTopLabelString: string = localize('jobDialog.jobStepList', 'Job step list');
|
||||
private readonly StepsTable_StepColumnString: string = localize('jobDialog.step', 'Step');
|
||||
private readonly StepsTable_NameColumnString: string = localize('jobDialog.name', 'Name');
|
||||
private readonly StepsTable_TypeColumnString: string = localize('jobDialog.type', 'Type');
|
||||
private readonly StepsTable_SuccessColumnString: string = localize('jobDialog.onSuccess', 'On Success');
|
||||
private readonly StepsTable_FailureColumnString: string = localize('jobDialog.onFailure', 'On Failure');
|
||||
private readonly NewStepButtonString: string = localize('jobDialog.new', 'New...');
|
||||
private readonly EditStepButtonString: string = localize('jobDialog.edit', 'Edit');
|
||||
private readonly DeleteStepButtonString: string = localize('jobDialog.delete', 'Delete');
|
||||
private readonly MoveStepUpButtonString: string = localize('jobDialog.moveUp', 'Move Step Up');
|
||||
private readonly MoveStepDownButtonString: string = localize('jobDialog.moveDown', 'Move Step Up');
|
||||
|
||||
// Notifications tab strings
|
||||
private readonly NotificationsTabTopLabelString: string = localize('jobDialog.notificationsTabTop', 'Actions to perform when the job completes');
|
||||
private readonly EmailCheckBoxString: string = localize('jobDialog.email', 'Email');
|
||||
private readonly PagerCheckBoxString: string = localize('jobDialog.page', 'Page');
|
||||
private readonly EventLogCheckBoxString: string = localize('jobDialog.eventLogCheckBoxLabel', 'Write to the Windows Application event log');
|
||||
private readonly DeleteJobCheckBoxString: string = localize('jobDialog.deleteJobLabel', 'Automatically delete job');
|
||||
|
||||
// Schedules tab strings
|
||||
private readonly SchedulesTopLabelString: string = localize('jobDialog.schedulesaLabel', 'Schedules list');
|
||||
private readonly PickScheduleButtonString: string = localize('jobDialog.pickSchedule', 'Pick Schedule');
|
||||
private readonly ScheduleNameLabelString: string = localize('jobDialog.scheduleNameLabel', 'Schedule Name');
|
||||
|
||||
// Alerts tab strings
|
||||
private readonly AlertsTopLabelString: string = localize('jobDialog.alertsList', 'Alerts list');
|
||||
private readonly NewAlertButtonString: string = localize('jobDialog.newAlert', 'New Alert');
|
||||
private readonly AlertNameLabelString: string = localize('jobDialog.alertNameLabel', 'Alert Name');
|
||||
|
||||
// UI Components
|
||||
private generalTab: sqlops.window.modelviewdialog.DialogTab;
|
||||
private stepsTab: sqlops.window.modelviewdialog.DialogTab;
|
||||
private alertsTab: sqlops.window.modelviewdialog.DialogTab;
|
||||
private schedulesTab: sqlops.window.modelviewdialog.DialogTab;
|
||||
private notificationsTab: sqlops.window.modelviewdialog.DialogTab;
|
||||
|
||||
// General tab controls
|
||||
private nameTextBox: sqlops.InputBoxComponent;
|
||||
private ownerTextBox: sqlops.InputBoxComponent;
|
||||
private categoryDropdown: sqlops.DropDownComponent;
|
||||
private descriptionTextBox: sqlops.InputBoxComponent;
|
||||
private enabledCheckBox: sqlops.CheckBoxComponent;
|
||||
|
||||
// Steps tab controls
|
||||
private stepsTable: sqlops.TableComponent;
|
||||
private newStepButton: sqlops.ButtonComponent;
|
||||
private moveStepUpButton: sqlops.ButtonComponent;
|
||||
private moveStepDownButton: sqlops.ButtonComponent;
|
||||
private editStepButton: sqlops.ButtonComponent;
|
||||
private deleteStepButton: sqlops.ButtonComponent;
|
||||
|
||||
// Notifications tab controls
|
||||
private notificationsTabTopLabel: sqlops.TextComponent;
|
||||
private emailCheckBox: sqlops.CheckBoxComponent;
|
||||
private emailOperatorDropdown: sqlops.DropDownComponent;
|
||||
private emailConditionDropdown: sqlops.DropDownComponent;
|
||||
private pagerCheckBox: sqlops.CheckBoxComponent;
|
||||
private pagerOperatorDropdown: sqlops.DropDownComponent;
|
||||
private pagerConditionDropdown: sqlops.DropDownComponent;
|
||||
private eventLogCheckBox: sqlops.CheckBoxComponent;
|
||||
private eventLogConditionDropdown: sqlops.DropDownComponent;
|
||||
private deleteJobCheckBox: sqlops.CheckBoxComponent;
|
||||
private deleteJobConditionDropdown: sqlops.DropDownComponent;
|
||||
|
||||
// Schedule tab controls
|
||||
private schedulesTable: sqlops.TableComponent;
|
||||
private pickScheduleButton: sqlops.ButtonComponent;
|
||||
|
||||
// Alert tab controls
|
||||
private alertsTable: sqlops.TableComponent;
|
||||
private newAlertButton: sqlops.ButtonComponent;
|
||||
|
||||
constructor(ownerUri: string, jobInfo: sqlops.AgentJobInfo = undefined) {
|
||||
super(
|
||||
ownerUri,
|
||||
new JobData(ownerUri, jobInfo),
|
||||
jobInfo ? JobDialog.EditDialogTitle : JobDialog.CreateDialogTitle);
|
||||
}
|
||||
|
||||
protected async initializeDialog() {
|
||||
this.generalTab = sqlops.window.modelviewdialog.createTab(this.GeneralTabText);
|
||||
this.stepsTab = sqlops.window.modelviewdialog.createTab(this.StepsTabText);
|
||||
this.alertsTab = sqlops.window.modelviewdialog.createTab(this.AlertsTabText);
|
||||
this.schedulesTab = sqlops.window.modelviewdialog.createTab(this.SchedulesTabText);
|
||||
this.notificationsTab = sqlops.window.modelviewdialog.createTab(this.NotificationsTabText);
|
||||
this.initializeGeneralTab();
|
||||
this.initializeStepsTab();
|
||||
this.initializeAlertsTab();
|
||||
this.initializeSchedulesTab();
|
||||
this.initializeNotificationsTab();
|
||||
this.dialog.content = [this.generalTab, this.stepsTab, this.schedulesTab, this.alertsTab, this.notificationsTab];
|
||||
|
||||
this.dialog.registerCloseValidator(() => {
|
||||
this.updateModel();
|
||||
let validationResult = this.model.validate();
|
||||
if (!validationResult.valid) {
|
||||
// TODO: Show Error Messages
|
||||
console.error(validationResult.errorMessages.join(','));
|
||||
}
|
||||
|
||||
return validationResult.valid;
|
||||
});
|
||||
}
|
||||
|
||||
private initializeGeneralTab() {
|
||||
this.generalTab.registerContent(async view => {
|
||||
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;
|
||||
}
|
||||
});
|
||||
this.ownerTextBox = view.modelBuilder.inputBox().component();
|
||||
this.categoryDropdown = view.modelBuilder.dropDown().component();
|
||||
this.descriptionTextBox = view.modelBuilder.inputBox().withProperties({
|
||||
multiline: true,
|
||||
height: 200
|
||||
}).component();
|
||||
this.enabledCheckBox = view.modelBuilder.checkBox()
|
||||
.withProperties({
|
||||
label: this.EnabledCheckboxLabel
|
||||
}).component();
|
||||
let formModel = view.modelBuilder.formContainer()
|
||||
.withFormItems([{
|
||||
component: this.nameTextBox,
|
||||
title: this.NameTextBoxLabel
|
||||
}, {
|
||||
component: this.ownerTextBox,
|
||||
title: this.OwnerTextBoxLabel
|
||||
}, {
|
||||
component: this.categoryDropdown,
|
||||
title: this.CategoryDropdownLabel
|
||||
}, {
|
||||
component: this.descriptionTextBox,
|
||||
title: this.DescriptionTextBoxLabel
|
||||
}, {
|
||||
component: this.enabledCheckBox,
|
||||
title: ''
|
||||
}]).withLayout({ width: '100%' }).component();
|
||||
|
||||
await view.initializeModel(formModel);
|
||||
|
||||
this.nameTextBox.value = this.model.name;
|
||||
this.ownerTextBox.value = this.model.defaultOwner;
|
||||
this.categoryDropdown.values = this.model.jobCategories;
|
||||
|
||||
let idx: number = undefined;
|
||||
if (this.model.category && this.model.category !== '') {
|
||||
idx = this.model.jobCategories.indexOf(this.model.category);
|
||||
}
|
||||
this.categoryDropdown.value = this.model.jobCategories[idx > 0 ? idx : 0];
|
||||
|
||||
this.enabledCheckBox.checked = this.model.enabled;
|
||||
this.descriptionTextBox.value = this.model.description;
|
||||
});
|
||||
}
|
||||
|
||||
private initializeStepsTab() {
|
||||
this.stepsTab.registerContent(async view => {
|
||||
this.stepsTable = view.modelBuilder.table()
|
||||
.withProperties({
|
||||
columns: [
|
||||
this.StepsTable_StepColumnString,
|
||||
this.StepsTable_NameColumnString,
|
||||
this.StepsTable_TypeColumnString,
|
||||
this.StepsTable_SuccessColumnString,
|
||||
this.StepsTable_FailureColumnString
|
||||
],
|
||||
data: [],
|
||||
height: 430
|
||||
}).component();
|
||||
|
||||
this.moveStepUpButton = view.modelBuilder.button()
|
||||
.withProperties({
|
||||
label: this.MoveStepUpButtonString,
|
||||
width: 80
|
||||
}).component();
|
||||
|
||||
this.moveStepDownButton = view.modelBuilder.button()
|
||||
.withProperties({
|
||||
label: this.MoveStepDownButtonString,
|
||||
width: 80
|
||||
}).component();
|
||||
|
||||
this.moveStepUpButton.enabled = false;
|
||||
this.moveStepDownButton.enabled = false;
|
||||
|
||||
this.newStepButton = view.modelBuilder.button().withProperties({
|
||||
label: this.NewStepButtonString,
|
||||
width: 80
|
||||
}).component();
|
||||
|
||||
this.newStepButton.onDidClick((e)=>{
|
||||
if (this.nameTextBox.value && this.nameTextBox.value.length > 0) {
|
||||
let stepDialog = new JobStepDialog(this.model.ownerUri, this.nameTextBox.value, '' , 1, this.model);
|
||||
stepDialog.openNewStepDialog();
|
||||
} else {
|
||||
this.dialog.message = { text: this.BlankJobNameErrorText };
|
||||
}
|
||||
});
|
||||
|
||||
this.editStepButton = view.modelBuilder.button().withProperties({
|
||||
label: this.EditStepButtonString,
|
||||
width: 80
|
||||
}).component();
|
||||
|
||||
this.deleteStepButton = view.modelBuilder.button().withProperties({
|
||||
label: this.DeleteStepButtonString,
|
||||
width: 80
|
||||
}).component();
|
||||
|
||||
let formModel = view.modelBuilder.formContainer()
|
||||
.withFormItems([{
|
||||
component: this.stepsTable,
|
||||
title: this.JobStepsTopLabelString,
|
||||
actions: [this.moveStepUpButton, this.moveStepDownButton, this.newStepButton, this.editStepButton, this.deleteStepButton]
|
||||
}]).withLayout({ width: '100%' }).component();
|
||||
await view.initializeModel(formModel);
|
||||
});
|
||||
}
|
||||
|
||||
private initializeAlertsTab() {
|
||||
this.alertsTab.registerContent(async view => {
|
||||
this.alertsTable = view.modelBuilder.table()
|
||||
.withProperties({
|
||||
columns: [
|
||||
this.AlertNameLabelString
|
||||
],
|
||||
data: [],
|
||||
height: 430,
|
||||
width: 400
|
||||
}).component();
|
||||
|
||||
this.newAlertButton = view.modelBuilder.button().withProperties({
|
||||
label: this.NewAlertButtonString,
|
||||
width: 80
|
||||
}).component();
|
||||
|
||||
this.newAlertButton.onDidClick((e)=>{
|
||||
let alertDialog = new AlertDialog(this.model.ownerUri, null, []);
|
||||
alertDialog.onSuccess((dialogModel) => {
|
||||
});
|
||||
alertDialog.openDialog();
|
||||
});
|
||||
|
||||
let formModel = view.modelBuilder.formContainer()
|
||||
.withFormItems([{
|
||||
component: this.alertsTable,
|
||||
title: this.AlertsTopLabelString,
|
||||
actions: [this.newAlertButton]
|
||||
}]).withLayout({ width: '100%' }).component();
|
||||
|
||||
await view.initializeModel(formModel);
|
||||
});
|
||||
}
|
||||
|
||||
private initializeSchedulesTab() {
|
||||
this.schedulesTab.registerContent(async view => {
|
||||
this.schedulesTable = view.modelBuilder.table()
|
||||
.withProperties({
|
||||
columns: [
|
||||
this.ScheduleNameLabelString
|
||||
],
|
||||
data: [],
|
||||
height: 430,
|
||||
width: 420
|
||||
}).component();
|
||||
|
||||
this.pickScheduleButton = view.modelBuilder.button().withProperties({
|
||||
label: this.PickScheduleButtonString,
|
||||
width: 80
|
||||
}).component();
|
||||
|
||||
this.pickScheduleButton.onDidClick((e)=>{
|
||||
let pickScheduleDialog = new PickScheduleDialog(this.model.ownerUri);
|
||||
pickScheduleDialog.onSuccess((dialogModel) => {
|
||||
let selectedSchedule = dialogModel.selectedSchedule;
|
||||
if (selectedSchedule) {
|
||||
this.model.addJobSchedule(selectedSchedule);
|
||||
this.populateScheduleTable();
|
||||
}
|
||||
});
|
||||
pickScheduleDialog.showDialog();
|
||||
});
|
||||
|
||||
let formModel = view.modelBuilder.formContainer()
|
||||
.withFormItems([{
|
||||
component: this.schedulesTable,
|
||||
title: this.SchedulesTopLabelString,
|
||||
actions: [this.pickScheduleButton]
|
||||
}]).withLayout({ width: '100%' }).component();
|
||||
|
||||
await view.initializeModel(formModel);
|
||||
|
||||
this.populateScheduleTable();
|
||||
});
|
||||
}
|
||||
|
||||
private populateScheduleTable() {
|
||||
if (this.model.jobSchedules) {
|
||||
let data: any[][] = [];
|
||||
for (let i = 0; i < this.model.jobSchedules.length; ++i) {
|
||||
let schedule = this.model.jobSchedules[i];
|
||||
data[i] = [ schedule.name ];
|
||||
}
|
||||
this.schedulesTable.data = data;
|
||||
}
|
||||
}
|
||||
|
||||
private initializeNotificationsTab() {
|
||||
this.notificationsTab.registerContent(async view => {
|
||||
|
||||
this.notificationsTabTopLabel = view.modelBuilder.text().withProperties({ value: this.NotificationsTabTopLabelString }).component();
|
||||
this.emailCheckBox = view.modelBuilder.checkBox().withProperties({
|
||||
label: this.EmailCheckBoxString,
|
||||
width: 80
|
||||
}).component();
|
||||
|
||||
this.pagerCheckBox = view.modelBuilder.checkBox().withProperties({
|
||||
label: this.PagerCheckBoxString,
|
||||
width: 80
|
||||
}).component();
|
||||
this.eventLogCheckBox = view.modelBuilder.checkBox().withProperties({
|
||||
label: this.EventLogCheckBoxString,
|
||||
width: 250
|
||||
}).component();
|
||||
this.deleteJobCheckBox = view.modelBuilder.checkBox().withProperties({
|
||||
label: this.DeleteJobCheckBoxString,
|
||||
width: 250
|
||||
}).component();
|
||||
|
||||
this.emailCheckBox.onChanged(() => {
|
||||
this.emailConditionDropdown.enabled = this.emailCheckBox.checked;
|
||||
this.emailOperatorDropdown.enabled = this.emailCheckBox.checked;
|
||||
});
|
||||
|
||||
this.pagerCheckBox.onChanged(() => {
|
||||
this.pagerConditionDropdown.enabled = this.pagerCheckBox.checked;
|
||||
this.pagerOperatorDropdown.enabled = this.pagerCheckBox.checked;
|
||||
});
|
||||
this.eventLogCheckBox.onChanged(() => {
|
||||
this.eventLogConditionDropdown.enabled = this.eventLogCheckBox.checked;
|
||||
});
|
||||
|
||||
this.deleteJobCheckBox.onChanged(() => {
|
||||
this.deleteJobConditionDropdown.enabled = this.deleteJobCheckBox.checked;
|
||||
});
|
||||
|
||||
this.emailOperatorDropdown = view.modelBuilder.dropDown().withProperties({ width: 150 }).component();
|
||||
this.pagerOperatorDropdown = view.modelBuilder.dropDown().withProperties({ width: 150 }).component();
|
||||
this.emailConditionDropdown = view.modelBuilder.dropDown().withProperties({ width: 150 }).component();
|
||||
this.pagerConditionDropdown = view.modelBuilder.dropDown().withProperties({ width: 150 }).component();
|
||||
this.eventLogConditionDropdown = view.modelBuilder.dropDown().withProperties({ width: 150 }).component();
|
||||
this.deleteJobConditionDropdown = view.modelBuilder.dropDown().withProperties({ width: 150 }).component();
|
||||
|
||||
let emailContainer = this.createRowContainer(view).withItems([this.emailCheckBox, this.emailOperatorDropdown, this.emailConditionDropdown]).component();
|
||||
|
||||
let pagerContainer = this.createRowContainer(view).withItems([this.pagerCheckBox, this.pagerOperatorDropdown, this.pagerConditionDropdown]).component();
|
||||
|
||||
let eventLogContainer = this.createRowContainer(view).withItems([this.eventLogCheckBox, this.eventLogConditionDropdown]).component();
|
||||
|
||||
let deleteJobContainer = this.createRowContainer(view).withItems([this.deleteJobCheckBox, this.deleteJobConditionDropdown]).component();
|
||||
|
||||
let formModel = view.modelBuilder.formContainer().withFormItems([
|
||||
{
|
||||
components:
|
||||
[{
|
||||
component: emailContainer,
|
||||
title: ''
|
||||
},
|
||||
{
|
||||
component: pagerContainer,
|
||||
title: ''
|
||||
},
|
||||
{
|
||||
component: eventLogContainer,
|
||||
title: ''
|
||||
},
|
||||
{
|
||||
component: deleteJobContainer,
|
||||
title: ''
|
||||
}], title: this.NotificationsTabTopLabelString}]).withLayout({ width: '100%' }).component();
|
||||
|
||||
await view.initializeModel(formModel);
|
||||
this.emailConditionDropdown.values = this.model.JobCompletionActionConditions;
|
||||
this.pagerConditionDropdown.values = this.model.JobCompletionActionConditions;
|
||||
this.eventLogConditionDropdown.values = this.model.JobCompletionActionConditions;
|
||||
this.deleteJobConditionDropdown.values = this.model.JobCompletionActionConditions;
|
||||
this.setConditionDropdownSelectedValue(this.emailConditionDropdown, this.model.emailLevel);
|
||||
this.setConditionDropdownSelectedValue(this.pagerConditionDropdown, this.model.pageLevel);
|
||||
this.setConditionDropdownSelectedValue(this.eventLogConditionDropdown, this.model.eventLogLevel);
|
||||
this.setConditionDropdownSelectedValue(this.deleteJobConditionDropdown, this.model.deleteLevel);
|
||||
this.emailOperatorDropdown.values = this.model.operators;
|
||||
this.pagerOperatorDropdown.values = this.model.operators;
|
||||
this.emailCheckBox.checked = false;
|
||||
this.pagerCheckBox.checked = false;
|
||||
this.eventLogCheckBox.checked = false;
|
||||
this.deleteJobCheckBox.checked = false;
|
||||
this.emailOperatorDropdown.enabled = false;
|
||||
this.pagerOperatorDropdown.enabled = false;
|
||||
this.emailConditionDropdown.enabled = false;
|
||||
this.pagerConditionDropdown.enabled = false;
|
||||
this.eventLogConditionDropdown.enabled = false;
|
||||
this.deleteJobConditionDropdown.enabled = false;
|
||||
});
|
||||
}
|
||||
|
||||
private createRowContainer(view: sqlops.ModelView): sqlops.FlexBuilder {
|
||||
return view.modelBuilder.flexContainer().withLayout({
|
||||
flexFlow: 'row',
|
||||
alignItems: 'left',
|
||||
justifyContent: 'space-between'
|
||||
});
|
||||
}
|
||||
|
||||
protected updateModel() {
|
||||
this.model.name = this.nameTextBox.value;
|
||||
this.model.owner = this.ownerTextBox.value;
|
||||
this.model.enabled = this.enabledCheckBox.checked;
|
||||
this.model.description = this.descriptionTextBox.value;
|
||||
this.model.category = this.getDropdownValue(this.categoryDropdown);
|
||||
this.model.emailLevel = this.getActualConditionValue(this.emailCheckBox, this.emailConditionDropdown);
|
||||
this.model.operatorToEmail = this.getDropdownValue(this.emailOperatorDropdown);
|
||||
this.model.operatorToPage = this.getDropdownValue(this.pagerOperatorDropdown);
|
||||
this.model.pageLevel = this.getActualConditionValue(this.pagerCheckBox, this.pagerConditionDropdown);
|
||||
this.model.eventLogLevel = this.getActualConditionValue(this.eventLogCheckBox, this.eventLogConditionDropdown);
|
||||
this.model.deleteLevel = this.getActualConditionValue(this.deleteJobCheckBox, this.deleteJobConditionDropdown);
|
||||
}
|
||||
}
|
||||
510
extensions/agent/src/dialogs/jobStepDialog.ts
Normal file
510
extensions/agent/src/dialogs/jobStepDialog.ts
Normal file
@@ -0,0 +1,510 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 sqlops from 'sqlops';
|
||||
import * as vscode from 'vscode';
|
||||
import { JobStepData } from '../data/jobStepData';
|
||||
import { AgentUtils } from '../agentUtils';
|
||||
import { JobData } from '../data/jobData';
|
||||
const path = require('path');
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export class JobStepDialog {
|
||||
|
||||
// TODO: localize
|
||||
// Top level
|
||||
//
|
||||
private readonly DialogTitle: string = localize('jobStepDialog.newJobStep', 'New Job Step');
|
||||
private readonly FileBrowserDialogTitle: string = localize('jobStepDialog.fileBrowserTitle', 'Locate Database Files - ');
|
||||
private readonly OkButtonText: string = localize('jobStepDialog.ok', 'OK');
|
||||
private readonly CancelButtonText: string = localize('jobStepDialog.cancel', 'Cancel');
|
||||
private readonly GeneralTabText: string = localize('jobStepDialog.general', 'General');
|
||||
private readonly AdvancedTabText: string = localize('jobStepDialog.advanced', 'Advanced');
|
||||
private readonly OpenCommandText: string = localize('jobStepDialog.open', 'Open...');
|
||||
private readonly ParseCommandText: string = localize('jobStepDialog.parse','Parse');
|
||||
private readonly NextButtonText: string = localize('jobStepDialog.next', 'Next');
|
||||
private readonly PreviousButtonText: string = localize('jobStepDialog.previous','Previous');
|
||||
private readonly SuccessfulParseText: string = localize('jobStepDialog.successParse', 'The command was successfully parsed.');
|
||||
private readonly FailureParseText: string = localize('jobStepDialog.failParse', 'The command failed.');
|
||||
private readonly BlankStepNameErrorText: string = localize('jobStepDialog.blankStepName', 'The step name cannot be left blank');
|
||||
|
||||
// General Control Titles
|
||||
private readonly StepNameLabelString: string = localize('jobStepDialog.stepNameLabel', 'Step Name');
|
||||
private readonly TypeLabelString: string = localize('jobStepDialog.typeLabel', 'Type');
|
||||
private readonly RunAsLabelString: string = localize('jobStepDialog.runAsLabel', 'Run as');
|
||||
private readonly DatabaseLabelString: string = localize('jobStepDialog.databaseLabel', 'Database');
|
||||
private readonly CommandLabelString: string = localize('jobStepDialog.commandLabel', 'Command');
|
||||
|
||||
// Advanced Control Titles
|
||||
private readonly SuccessActionLabel: string = localize('jobStepDialog.successAction', 'On success action');
|
||||
private readonly FailureActionLabel: string = localize('jobStepDialog.failureAction', 'On failure action');
|
||||
private readonly RunAsUserLabel: string = localize('jobStepDialog.runAsUser', 'Run as user');
|
||||
private readonly RetryAttemptsLabel: string = localize('jobStepDialog.retryAttempts', 'Retry Attempts');
|
||||
private readonly RetryIntervalLabel: string = localize('jobStepDialog.retryInterval', 'Retry Interval (minutes)');
|
||||
private readonly LogToTableLabel: string = localize('jobStepDialog.logToTable', 'Log to table');
|
||||
private readonly AppendExistingTableEntryLabel: string = localize('jobStepDialog.appendExistingTableEntry', 'Append output to exisiting entry in table');
|
||||
private readonly IncludeStepOutputHistoryLabel: string = localize('jobStepDialog.includeStepOutputHistory', 'Include step output in history');
|
||||
private readonly OutputFileNameLabel: string = localize('jobStepDialog.outputFile', 'Output File');
|
||||
private readonly AppendOutputToFileLabel: string = localize('jobStepDialog.appendOutputToFile', 'Append output to existing file');
|
||||
|
||||
// File Browser Control Titles
|
||||
private readonly SelectedPathLabelString: string = localize('jobStepDialog.selectedPath', 'Selected path');
|
||||
private readonly FilesOfTypeLabelString: string = localize('jobStepDialog.filesOfType', 'Files of type');
|
||||
private readonly FileNameLabelString: string = localize('jobStepDialog.fileName', 'File name');
|
||||
private readonly AllFilesLabelString: string = localize('jobStepDialog.allFiles', 'All Files (*)');
|
||||
|
||||
// Dropdown options
|
||||
private readonly TSQLScript: string = localize('jobStepDialog.TSQL', 'Transact-SQL script (T-SQL)');
|
||||
private readonly AgentServiceAccount: string = localize('jobStepDialog.agentServiceAccount', 'SQL Server Agent Service Account');
|
||||
private readonly NextStep: string = localize('jobStepDialog.nextStep', 'Go to the next step');
|
||||
private readonly QuitJobReportingSuccess: string = localize('jobStepDialog.quitJobSuccess', 'Quit the job reporting success');
|
||||
private readonly QuitJobReportingFailure: string = localize('jobStepDialog.quitJobFailure', 'Quit the job reporting failure');
|
||||
|
||||
// UI Components
|
||||
|
||||
// Dialogs
|
||||
private dialog: sqlops.window.modelviewdialog.Dialog;
|
||||
private fileBrowserDialog: sqlops.window.modelviewdialog.Dialog;
|
||||
|
||||
// Dialog tabs
|
||||
private generalTab: sqlops.window.modelviewdialog.DialogTab;
|
||||
private advancedTab: sqlops.window.modelviewdialog.DialogTab;
|
||||
|
||||
//Input boxes
|
||||
private nameTextBox: sqlops.InputBoxComponent;
|
||||
private commandTextBox: sqlops.InputBoxComponent;
|
||||
private selectedPathTextBox: sqlops.InputBoxComponent;
|
||||
private retryAttemptsBox: sqlops.InputBoxComponent;
|
||||
private retryIntervalBox: sqlops.InputBoxComponent;
|
||||
private outputFileNameBox: sqlops.InputBoxComponent;
|
||||
private fileBrowserNameBox: sqlops.InputBoxComponent;
|
||||
private userInputBox: sqlops.InputBoxComponent;
|
||||
|
||||
// Dropdowns
|
||||
private typeDropdown: sqlops.DropDownComponent;
|
||||
private runAsDropdown: sqlops.DropDownComponent;
|
||||
private databaseDropdown: sqlops.DropDownComponent;
|
||||
private successActionDropdown: sqlops.DropDownComponent;
|
||||
private failureActionDropdown: sqlops.DropDownComponent;
|
||||
private fileTypeDropdown: sqlops.DropDownComponent;
|
||||
|
||||
// Buttons
|
||||
private openButton: sqlops.ButtonComponent;
|
||||
private parseButton: sqlops.ButtonComponent;
|
||||
private nextButton: sqlops.ButtonComponent;
|
||||
private previousButton: sqlops.ButtonComponent;
|
||||
private outputFileBrowserButton: sqlops.ButtonComponent;
|
||||
|
||||
// Checkbox
|
||||
private appendToExistingFileCheckbox: sqlops.CheckBoxComponent;
|
||||
private logToTableCheckbox: sqlops.CheckBoxComponent;
|
||||
|
||||
private fileBrowserTree: sqlops.FileBrowserTreeComponent;
|
||||
private jobModel: JobData;
|
||||
private model: JobStepData;
|
||||
private ownerUri: string;
|
||||
private jobName: string;
|
||||
private server: string;
|
||||
private stepId: number;
|
||||
|
||||
constructor(
|
||||
ownerUri: string,
|
||||
jobName: string,
|
||||
server: string,
|
||||
stepId: number,
|
||||
jobModel?: JobData
|
||||
) {
|
||||
this.model = new JobStepData(ownerUri);
|
||||
this.stepId = stepId;
|
||||
this.ownerUri = ownerUri;
|
||||
this.jobName = jobName;
|
||||
this.server = server;
|
||||
this.jobModel = jobModel;
|
||||
}
|
||||
|
||||
private initializeUIComponents() {
|
||||
this.dialog = sqlops.window.modelviewdialog.createDialog(this.DialogTitle);
|
||||
this.generalTab = sqlops.window.modelviewdialog.createTab(this.GeneralTabText);
|
||||
this.advancedTab = sqlops.window.modelviewdialog.createTab(this.AdvancedTabText);
|
||||
this.dialog.content = [this.generalTab, this.advancedTab];
|
||||
this.dialog.okButton.onClick(async () => await this.execute());
|
||||
this.dialog.okButton.label = this.OkButtonText;
|
||||
this.dialog.cancelButton.label = this.CancelButtonText;
|
||||
}
|
||||
|
||||
private createCommands(view, queryProvider: sqlops.QueryProvider) {
|
||||
this.openButton = view.modelBuilder.button()
|
||||
.withProperties({
|
||||
label: this.OpenCommandText,
|
||||
width: '80px',
|
||||
isFile: true
|
||||
}).component();
|
||||
this.parseButton = view.modelBuilder.button()
|
||||
.withProperties({
|
||||
label: this.ParseCommandText,
|
||||
width: '80px',
|
||||
isFile: false
|
||||
}).component();
|
||||
this.openButton.onDidClick(e => {
|
||||
let queryContent = e;
|
||||
this.commandTextBox.value = queryContent;
|
||||
});
|
||||
this.parseButton.onDidClick(e => {
|
||||
if (this.commandTextBox.value) {
|
||||
queryProvider.parseSyntax(this.ownerUri, this.commandTextBox.value).then(result => {
|
||||
if (result && result.parseable) {
|
||||
this.dialog.message = { text: this.SuccessfulParseText, level: 2};
|
||||
} else if (result && !result.parseable) {
|
||||
this.dialog.message = { text: this.FailureParseText };
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
this.commandTextBox = view.modelBuilder.inputBox()
|
||||
.withProperties({
|
||||
height: 300,
|
||||
width: 400,
|
||||
multiline: true,
|
||||
inputType: 'text'
|
||||
})
|
||||
.component();
|
||||
this.nextButton = view.modelBuilder.button()
|
||||
.withProperties({
|
||||
label: this.NextButtonText,
|
||||
enabled: false,
|
||||
width: '80px'
|
||||
}).component();
|
||||
this.previousButton = view.modelBuilder.button()
|
||||
.withProperties({
|
||||
label: this.PreviousButtonText,
|
||||
enabled: false,
|
||||
width: '80px'
|
||||
}).component();
|
||||
}
|
||||
|
||||
private createGeneralTab(databases: string[], queryProvider: sqlops.QueryProvider) {
|
||||
this.generalTab.registerContent(async (view) => {
|
||||
this.nameTextBox = view.modelBuilder.inputBox()
|
||||
.withProperties({
|
||||
}).component();
|
||||
this.nameTextBox.required = true;
|
||||
this.nameTextBox.onTextChanged(() => {
|
||||
if (this.nameTextBox.value.length > 0) {
|
||||
this.dialog.message = null;
|
||||
}
|
||||
});
|
||||
|
||||
this.typeDropdown = view.modelBuilder.dropDown()
|
||||
.withProperties({
|
||||
value: this.TSQLScript,
|
||||
values: [this.TSQLScript]
|
||||
})
|
||||
.component();
|
||||
this.runAsDropdown = view.modelBuilder.dropDown()
|
||||
.withProperties({
|
||||
value: '',
|
||||
values: ['']
|
||||
})
|
||||
.component();
|
||||
this.runAsDropdown.enabled = false;
|
||||
this.typeDropdown.onValueChanged((type) => {
|
||||
if (type.selected !== this.TSQLScript) {
|
||||
this.runAsDropdown.value = this.AgentServiceAccount;
|
||||
this.runAsDropdown.values = [this.runAsDropdown.value];
|
||||
} else {
|
||||
this.runAsDropdown.value = '';
|
||||
this.runAsDropdown.values = [''];
|
||||
}
|
||||
});
|
||||
this.databaseDropdown = view.modelBuilder.dropDown()
|
||||
.withProperties({
|
||||
value: databases[0],
|
||||
values: databases
|
||||
}).component();
|
||||
|
||||
// create the commands section
|
||||
this.createCommands(view, queryProvider);
|
||||
|
||||
let buttonContainer = view.modelBuilder.flexContainer()
|
||||
.withLayout({
|
||||
flexFlow: 'row',
|
||||
justifyContent: 'space-between',
|
||||
width: 420
|
||||
}).withItems([this.openButton, this.parseButton, this.previousButton, this.nextButton], {
|
||||
flex: '1 1 50%'
|
||||
}).component();
|
||||
|
||||
let formModel = view.modelBuilder.formContainer()
|
||||
.withFormItems([{
|
||||
component: this.nameTextBox,
|
||||
title: this.StepNameLabelString
|
||||
}, {
|
||||
component: this.typeDropdown,
|
||||
title: this.TypeLabelString
|
||||
}, {
|
||||
component: this.runAsDropdown,
|
||||
title: this.RunAsLabelString
|
||||
}, {
|
||||
component: this.databaseDropdown,
|
||||
title: this.DatabaseLabelString
|
||||
}, {
|
||||
component: this.commandTextBox,
|
||||
title: this.CommandLabelString,
|
||||
actions: [buttonContainer]
|
||||
}], {
|
||||
horizontal: false,
|
||||
componentWidth: 420
|
||||
}).component();
|
||||
let formWrapper = view.modelBuilder.loadingComponent().withItem(formModel).component();
|
||||
formWrapper.loading = false;
|
||||
await view.initializeModel(formWrapper);
|
||||
});
|
||||
}
|
||||
|
||||
private createAdvancedTab() {
|
||||
this.advancedTab.registerContent(async (view) => {
|
||||
this.successActionDropdown = view.modelBuilder.dropDown()
|
||||
.withProperties({
|
||||
width: '100%',
|
||||
value: this.NextStep,
|
||||
values: [this.NextStep, this.QuitJobReportingSuccess, this.QuitJobReportingFailure]
|
||||
})
|
||||
.component();
|
||||
let retryFlexContainer = this.createRetryCounters(view);
|
||||
|
||||
this.failureActionDropdown = view.modelBuilder.dropDown()
|
||||
.withProperties({
|
||||
value: this.QuitJobReportingFailure,
|
||||
values: [this.QuitJobReportingFailure, this.NextStep, this.QuitJobReportingSuccess]
|
||||
})
|
||||
.component();
|
||||
let optionsGroup = this.createTSQLOptions(view);
|
||||
this.logToTableCheckbox = view.modelBuilder.checkBox()
|
||||
.withProperties({
|
||||
label: this.LogToTableLabel
|
||||
}).component();
|
||||
let appendToExistingEntryInTableCheckbox = view.modelBuilder.checkBox()
|
||||
.withProperties({ label: this.AppendExistingTableEntryLabel }).component();
|
||||
appendToExistingEntryInTableCheckbox.enabled = false;
|
||||
this.logToTableCheckbox.onChanged(e => {
|
||||
appendToExistingEntryInTableCheckbox.enabled = e;
|
||||
});
|
||||
let appendCheckboxContainer = view.modelBuilder.groupContainer()
|
||||
.withItems([appendToExistingEntryInTableCheckbox]).component();
|
||||
let logToTableContainer = view.modelBuilder.flexContainer()
|
||||
.withLayout({ flexFlow: 'row', justifyContent: 'space-between', width: 300 })
|
||||
.withItems([this.logToTableCheckbox]).component();
|
||||
let logStepOutputHistoryCheckbox = view.modelBuilder.checkBox()
|
||||
.withProperties({ label: this.IncludeStepOutputHistoryLabel }).component();
|
||||
this.userInputBox = view.modelBuilder.inputBox()
|
||||
.withProperties({ inputType: 'text', width: '100%' }).component();
|
||||
let formModel = view.modelBuilder.formContainer()
|
||||
.withFormItems(
|
||||
[{
|
||||
component: this.successActionDropdown,
|
||||
title: this.SuccessActionLabel
|
||||
}, {
|
||||
component: retryFlexContainer,
|
||||
title: ''
|
||||
}, {
|
||||
component: this.failureActionDropdown,
|
||||
title: this.FailureActionLabel
|
||||
}, {
|
||||
component: optionsGroup,
|
||||
title: this.TSQLScript
|
||||
}, {
|
||||
component: logToTableContainer,
|
||||
title: ''
|
||||
}, {
|
||||
component: appendCheckboxContainer,
|
||||
title: ' '
|
||||
}, {
|
||||
component: logStepOutputHistoryCheckbox,
|
||||
title: ''
|
||||
}, {
|
||||
component: this.userInputBox,
|
||||
title: this.RunAsUserLabel
|
||||
}], {
|
||||
componentWidth: 400
|
||||
}).component();
|
||||
|
||||
let formWrapper = view.modelBuilder.loadingComponent().withItem(formModel).component();
|
||||
formWrapper.loading = false;
|
||||
view.initializeModel(formWrapper);
|
||||
});
|
||||
}
|
||||
|
||||
private createRetryCounters(view) {
|
||||
this.retryAttemptsBox = view.modelBuilder.inputBox()
|
||||
.withValidation(component => component.value >= 0)
|
||||
.withProperties({
|
||||
inputType: 'number',
|
||||
width: '100%',
|
||||
placeHolder: '0'
|
||||
})
|
||||
.component();
|
||||
this.retryIntervalBox = view.modelBuilder.inputBox()
|
||||
.withValidation(component => component.value >= 0)
|
||||
.withProperties({
|
||||
inputType: 'number',
|
||||
width: '100%',
|
||||
placeHolder: '0'
|
||||
}).component();
|
||||
|
||||
let retryAttemptsContainer = view.modelBuilder.formContainer()
|
||||
.withFormItems(
|
||||
[{
|
||||
component: this.retryAttemptsBox,
|
||||
title: this.RetryAttemptsLabel
|
||||
}], {
|
||||
horizontal: false,
|
||||
componentWidth: '100%'
|
||||
})
|
||||
.component();
|
||||
|
||||
let retryIntervalContainer = view.modelBuilder.formContainer()
|
||||
.withFormItems(
|
||||
[{
|
||||
component: this.retryIntervalBox,
|
||||
title: this.RetryIntervalLabel
|
||||
}], {
|
||||
horizontal: false
|
||||
})
|
||||
.component();
|
||||
|
||||
let retryFlexContainer = view.modelBuilder.flexContainer()
|
||||
.withLayout({
|
||||
flexFlow: 'row',
|
||||
}).withItems([retryAttemptsContainer, retryIntervalContainer]).component();
|
||||
return retryFlexContainer;
|
||||
}
|
||||
|
||||
private openFileBrowserDialog() {
|
||||
let fileBrowserTitle = this.FileBrowserDialogTitle + `${this.server}`;
|
||||
this.fileBrowserDialog = sqlops.window.modelviewdialog.createDialog(fileBrowserTitle);
|
||||
let fileBrowserTab = sqlops.window.modelviewdialog.createTab('File Browser');
|
||||
this.fileBrowserDialog.content = [fileBrowserTab];
|
||||
fileBrowserTab.registerContent(async (view) => {
|
||||
this.fileBrowserTree = view.modelBuilder.fileBrowserTree()
|
||||
.withProperties({ ownerUri: this.ownerUri, width: 420, height: 700 })
|
||||
.component();
|
||||
this.selectedPathTextBox = view.modelBuilder.inputBox()
|
||||
.withProperties({ inputType: 'text'})
|
||||
.component();
|
||||
this.fileBrowserTree.onDidChange((args) => {
|
||||
this.selectedPathTextBox.value = args.fullPath;
|
||||
this.fileBrowserNameBox.value = args.isFile ? path.win32.basename(args.fullPath) : '';
|
||||
});
|
||||
this.fileTypeDropdown = view.modelBuilder.dropDown()
|
||||
.withProperties({
|
||||
value: this.AllFilesLabelString,
|
||||
values: [this.AllFilesLabelString]
|
||||
})
|
||||
.component();
|
||||
this.fileBrowserNameBox = view.modelBuilder.inputBox()
|
||||
.withProperties({})
|
||||
.component();
|
||||
let fileBrowserContainer = view.modelBuilder.formContainer()
|
||||
.withFormItems([{
|
||||
component: this.fileBrowserTree,
|
||||
title: ''
|
||||
}, {
|
||||
component: this.selectedPathTextBox,
|
||||
title: this.SelectedPathLabelString
|
||||
}, {
|
||||
component: this.fileTypeDropdown,
|
||||
title: this.FilesOfTypeLabelString
|
||||
}, {
|
||||
component: this.fileBrowserNameBox,
|
||||
title: this.FileNameLabelString
|
||||
}
|
||||
]).component();
|
||||
view.initializeModel(fileBrowserContainer);
|
||||
});
|
||||
this.fileBrowserDialog.okButton.onClick(() => {
|
||||
this.outputFileNameBox.value = path.join(path.dirname(this.selectedPathTextBox.value), this.fileBrowserNameBox.value);
|
||||
});
|
||||
this.fileBrowserDialog.okButton.label = this.OkButtonText;
|
||||
this.fileBrowserDialog.cancelButton.label = this.CancelButtonText;
|
||||
sqlops.window.modelviewdialog.openDialog(this.fileBrowserDialog);
|
||||
}
|
||||
|
||||
private createTSQLOptions(view) {
|
||||
this.outputFileBrowserButton = view.modelBuilder.button()
|
||||
.withProperties({ width: '20px', label: '...' }).component();
|
||||
this.outputFileBrowserButton.onDidClick(() => this.openFileBrowserDialog());
|
||||
this.outputFileNameBox = view.modelBuilder.inputBox()
|
||||
.withProperties({
|
||||
width: 250,
|
||||
inputType: 'text'
|
||||
}).component();
|
||||
let outputButtonContainer = view.modelBuilder.flexContainer()
|
||||
.withLayout({
|
||||
flexFlow: 'row',
|
||||
textAlign: 'right',
|
||||
width: '100%'
|
||||
}).withItems([this.outputFileBrowserButton], { flex: '1 1 50%' }).component();
|
||||
let outputFlexBox = view.modelBuilder.flexContainer()
|
||||
.withLayout({
|
||||
flexFlow: 'row',
|
||||
width: 350
|
||||
}).withItems([this.outputFileNameBox, outputButtonContainer], {
|
||||
flex: '1 1 50%'
|
||||
}).component();
|
||||
this.appendToExistingFileCheckbox = view.modelBuilder.checkBox()
|
||||
.withProperties({
|
||||
label: this.AppendOutputToFileLabel
|
||||
}).component();
|
||||
this.appendToExistingFileCheckbox.enabled = false;
|
||||
this.outputFileNameBox.onTextChanged((input) => {
|
||||
if (input !== '') {
|
||||
this.appendToExistingFileCheckbox.enabled = true;
|
||||
} else {
|
||||
this.appendToExistingFileCheckbox.enabled = false;
|
||||
}
|
||||
});
|
||||
let outputFileForm = view.modelBuilder.formContainer()
|
||||
.withFormItems([{
|
||||
component: outputFlexBox,
|
||||
title: this.OutputFileNameLabel
|
||||
}, {
|
||||
component: this.appendToExistingFileCheckbox,
|
||||
title: ''
|
||||
}], { horizontal: false, componentWidth: 200 }).component();
|
||||
return outputFileForm;
|
||||
}
|
||||
|
||||
protected execute() {
|
||||
this.model.stepName = this.nameTextBox.value;
|
||||
if (!this.model.stepName || this.model.stepName.length === 0) {
|
||||
this.dialog.message = this.dialog.message = { text: this.BlankStepNameErrorText };
|
||||
return;
|
||||
}
|
||||
this.model.jobName = this.jobName;
|
||||
this.model.id = this.stepId;
|
||||
this.model.server = this.server;
|
||||
this.model.stepName = this.nameTextBox.value;
|
||||
this.model.subSystem = this.typeDropdown.value as string;
|
||||
this.model.databaseName = this.databaseDropdown.value as string;
|
||||
this.model.script = this.commandTextBox.value;
|
||||
this.model.successAction = this.successActionDropdown.value as string;
|
||||
this.model.retryAttempts = this.retryAttemptsBox.value ? +this.retryAttemptsBox.value : 0;
|
||||
this.model.retryInterval = +this.retryIntervalBox.value ? +this.retryIntervalBox.value : 0;
|
||||
this.model.failureAction = this.failureActionDropdown.value as string;
|
||||
this.model.outputFileName = this.outputFileNameBox.value;
|
||||
this.model.appendToLogFile = this.appendToExistingFileCheckbox.checked;
|
||||
}
|
||||
|
||||
public async openNewStepDialog() {
|
||||
let databases = await AgentUtils.getDatabases(this.ownerUri);
|
||||
let queryProvider = await AgentUtils.getQueryProvider();
|
||||
this.initializeUIComponents();
|
||||
this.createGeneralTab(databases, queryProvider);
|
||||
this.createAdvancedTab();
|
||||
sqlops.window.modelviewdialog.openDialog(this.dialog);
|
||||
}
|
||||
}
|
||||
404
extensions/agent/src/dialogs/operatorDialog.ts
Normal file
404
extensions/agent/src/dialogs/operatorDialog.ts
Normal file
@@ -0,0 +1,404 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 vscode from 'vscode';
|
||||
import { OperatorData } from '../data/operatorData';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { AgentDialog } from './agentDialog';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export class OperatorDialog extends AgentDialog<OperatorData> {
|
||||
|
||||
// Top level
|
||||
private static readonly CreateDialogTitle: string = localize('createOperator.createOperator', 'Create Operator');
|
||||
private static readonly EditDialogTitle: string = localize('createOperator.editOperator', 'Edit Operator');
|
||||
private static readonly GeneralTabText: string = localize('createOperator.General', 'General');
|
||||
private static readonly NotificationsTabText: string = localize('createOperator.Notifications', 'Notifications');
|
||||
|
||||
// General tab strings
|
||||
private static readonly NameLabel: string = localize('createOperator.Name', 'Name');
|
||||
private static readonly EnabledCheckboxLabel: string = localize('createOperator.Enabled', 'Enabled');
|
||||
private static readonly EmailNameTextLabel: string = localize('createOperator.EmailName', 'E-mail Name');
|
||||
private static readonly PagerEmailNameTextLabel: string = localize('createOperator.PagerEmailName', 'Pager E-mail Name');
|
||||
private static readonly PagerMondayCheckBoxLabel: string = localize('createOperator.PagerMondayCheckBox', 'Monday');
|
||||
private static readonly PagerTuesdayCheckBoxLabel: string = localize('createOperator.PagerTuesdayCheckBox', 'Tuesday');
|
||||
private static readonly PagerWednesdayCheckBoxLabel: string = localize('createOperator.PagerWednesdayCheckBox', 'Wednesday');
|
||||
private static readonly PagerThursdayCheckBoxLabel: string = localize('createOperator.PagerThursdayCheckBox', 'Thursday');
|
||||
private static readonly PagerFridayCheckBoxLabel: string = localize('createOperator.PagerFridayCheckBox', 'Friday ');
|
||||
private static readonly PagerSaturdayCheckBoxLabel: string = localize('createOperator.PagerSaturdayCheckBox', 'Saturday');
|
||||
private static readonly PagerSundayCheckBoxLabel: string = localize('createOperator.PagerSundayCheckBox', 'Sunday');
|
||||
private static readonly WorkdayBeginLabel: string = localize('createOperator.workdayBegin', 'Workday begin');
|
||||
private static readonly WorkdayEndLabel: string = localize('createOperator.workdayEnd', 'Workday end');
|
||||
private static readonly PagerDutyScheduleLabel: string = localize('createOperator.PagerDutySchedule', 'Pager on duty schdule');
|
||||
|
||||
// Notifications tab strings
|
||||
private static readonly AlertsTableLabel: string = localize('createOperator.AlertListHeading', 'Alert list');
|
||||
private static readonly AlertNameColumnLabel: string = localize('createOperator.AlertNameColumnLabel', 'Alert name');
|
||||
private static readonly AlertEmailColumnLabel: string = localize('createOperator.AlertEmailColumnLabel', 'E-mail');
|
||||
private static readonly AlertPagerColumnLabel: string = localize('createOperator.AlertPagerColumnLabel', 'Pager');
|
||||
|
||||
// UI Components
|
||||
private generalTab: sqlops.window.modelviewdialog.DialogTab;
|
||||
private notificationsTab: sqlops.window.modelviewdialog.DialogTab;
|
||||
|
||||
// General tab controls
|
||||
private nameTextBox: sqlops.InputBoxComponent;
|
||||
private enabledCheckBox: sqlops.CheckBoxComponent;
|
||||
private emailNameTextBox: sqlops.InputBoxComponent;
|
||||
private pagerEmailNameTextBox: sqlops.InputBoxComponent;
|
||||
private pagerMondayCheckBox: sqlops.CheckBoxComponent;
|
||||
private pagerTuesdayCheckBox: sqlops.CheckBoxComponent;
|
||||
private pagerWednesdayCheckBox: sqlops.CheckBoxComponent;
|
||||
private pagerThursdayCheckBox: sqlops.CheckBoxComponent;
|
||||
private pagerFridayCheckBox: sqlops.CheckBoxComponent;
|
||||
private pagerSaturdayCheckBox: sqlops.CheckBoxComponent;
|
||||
private pagerSundayCheckBox: sqlops.CheckBoxComponent;
|
||||
private weekdayPagerStartTimeInput: sqlops.InputBoxComponent;
|
||||
private weekdayPagerEndTimeInput: sqlops.InputBoxComponent;
|
||||
private saturdayPagerStartTimeInput: sqlops.InputBoxComponent;
|
||||
private saturdayPagerEndTimeInput: sqlops.InputBoxComponent;
|
||||
private sundayPagerStartTimeInput: sqlops.InputBoxComponent;
|
||||
private sundayPagerEndTimeInput: sqlops.InputBoxComponent;
|
||||
|
||||
// Notification tab controls
|
||||
private alertsTable: sqlops.TableComponent;
|
||||
|
||||
constructor(ownerUri: string, operatorInfo: sqlops.AgentOperatorInfo = undefined) {
|
||||
super(
|
||||
ownerUri,
|
||||
new OperatorData(ownerUri, operatorInfo),
|
||||
operatorInfo ? OperatorDialog.EditDialogTitle : OperatorDialog.CreateDialogTitle);
|
||||
}
|
||||
|
||||
protected async initializeDialog(dialog: sqlops.window.modelviewdialog.Dialog) {
|
||||
this.generalTab = sqlops.window.modelviewdialog.createTab(OperatorDialog.GeneralTabText);
|
||||
this.notificationsTab = sqlops.window.modelviewdialog.createTab(OperatorDialog.NotificationsTabText);
|
||||
|
||||
this.initializeGeneralTab();
|
||||
this.initializeNotificationTab();
|
||||
|
||||
this.dialog.content = [this.generalTab, this.notificationsTab];
|
||||
}
|
||||
|
||||
private initializeGeneralTab() {
|
||||
this.generalTab.registerContent(async view => {
|
||||
|
||||
this.nameTextBox = view.modelBuilder.inputBox().component();
|
||||
|
||||
this.enabledCheckBox = view.modelBuilder.checkBox()
|
||||
.withProperties({
|
||||
label: OperatorDialog.EnabledCheckboxLabel
|
||||
}).component();
|
||||
this.enabledCheckBox.checked = true;
|
||||
this.emailNameTextBox = view.modelBuilder.inputBox().component();
|
||||
|
||||
this.pagerEmailNameTextBox = view.modelBuilder.inputBox().component();
|
||||
|
||||
this.enabledCheckBox = view.modelBuilder.checkBox()
|
||||
.withProperties({
|
||||
label: OperatorDialog.EnabledCheckboxLabel
|
||||
}).component();
|
||||
|
||||
this.pagerMondayCheckBox = view.modelBuilder.checkBox()
|
||||
.withProperties({
|
||||
label: OperatorDialog.PagerMondayCheckBoxLabel
|
||||
}).component();
|
||||
|
||||
this.pagerMondayCheckBox.onChanged(() => {
|
||||
if (this.pagerMondayCheckBox.checked) {
|
||||
this.weekdayPagerStartTimeInput.enabled = true;
|
||||
this.weekdayPagerEndTimeInput.enabled = true;
|
||||
} else {
|
||||
if (!this.pagerTuesdayCheckBox.checked && !this.pagerWednesdayCheckBox.checked &&
|
||||
!this.pagerThursdayCheckBox.checked && !this.pagerFridayCheckBox.checked) {
|
||||
this.weekdayPagerStartTimeInput.enabled = false;
|
||||
this.weekdayPagerEndTimeInput.enabled = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.pagerTuesdayCheckBox = view.modelBuilder.checkBox()
|
||||
.withProperties({
|
||||
label: OperatorDialog.PagerTuesdayCheckBoxLabel
|
||||
}).component();
|
||||
|
||||
|
||||
this.pagerTuesdayCheckBox.onChanged(() => {
|
||||
if (this.pagerTuesdayCheckBox .checked) {
|
||||
this.weekdayPagerStartTimeInput.enabled = true;
|
||||
this.weekdayPagerEndTimeInput.enabled = true;
|
||||
} else {
|
||||
if (!this.pagerMondayCheckBox.checked && !this.pagerWednesdayCheckBox.checked &&
|
||||
!this.pagerThursdayCheckBox.checked && !this.pagerFridayCheckBox.checked) {
|
||||
this.weekdayPagerStartTimeInput.enabled = false;
|
||||
this.weekdayPagerEndTimeInput.enabled = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.pagerWednesdayCheckBox = view.modelBuilder.checkBox()
|
||||
.withProperties({
|
||||
label: OperatorDialog.PagerWednesdayCheckBoxLabel
|
||||
}).component();
|
||||
|
||||
this.pagerWednesdayCheckBox.onChanged(() => {
|
||||
if (this.pagerWednesdayCheckBox .checked) {
|
||||
this.weekdayPagerStartTimeInput.enabled = true;
|
||||
this.weekdayPagerEndTimeInput.enabled = true;
|
||||
} else {
|
||||
if (!this.pagerMondayCheckBox.checked && !this.pagerTuesdayCheckBox.checked &&
|
||||
!this.pagerThursdayCheckBox.checked && !this.pagerFridayCheckBox.checked) {
|
||||
this.weekdayPagerStartTimeInput.enabled = false;
|
||||
this.weekdayPagerEndTimeInput.enabled = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.pagerThursdayCheckBox = view.modelBuilder.checkBox()
|
||||
.withProperties({
|
||||
label: OperatorDialog.PagerThursdayCheckBoxLabel
|
||||
}).component();
|
||||
|
||||
this.pagerThursdayCheckBox.onChanged(() => {
|
||||
if (this.pagerThursdayCheckBox .checked) {
|
||||
this.weekdayPagerStartTimeInput.enabled = true;
|
||||
this.weekdayPagerEndTimeInput.enabled = true;
|
||||
} else {
|
||||
if (!this.pagerMondayCheckBox.checked && !this.pagerWednesdayCheckBox.checked &&
|
||||
!this.pagerTuesdayCheckBox.checked && !this.pagerFridayCheckBox.checked) {
|
||||
this.weekdayPagerStartTimeInput.enabled = false;
|
||||
this.weekdayPagerEndTimeInput.enabled = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
this.weekdayPagerStartTimeInput = view.modelBuilder.inputBox()
|
||||
.withProperties({
|
||||
inputType: 'time',
|
||||
placeHolder: '08:00:00',
|
||||
}).component();
|
||||
this.weekdayPagerStartTimeInput.enabled = false;
|
||||
let weekdayStartInputContainer = view.modelBuilder.formContainer()
|
||||
.withFormItems([{
|
||||
component: this.weekdayPagerStartTimeInput,
|
||||
title: OperatorDialog.WorkdayBeginLabel
|
||||
}]).component();
|
||||
|
||||
this.weekdayPagerEndTimeInput = view.modelBuilder.inputBox()
|
||||
.withProperties({
|
||||
inputType: 'time',
|
||||
placeHolder: '06:00:00'
|
||||
}).component();
|
||||
this.weekdayPagerEndTimeInput.enabled = false;
|
||||
let weekdayEndInputContainer = view.modelBuilder.formContainer()
|
||||
.withFormItems([{
|
||||
component: this.weekdayPagerEndTimeInput,
|
||||
title: OperatorDialog.WorkdayEndLabel
|
||||
}]).component();
|
||||
|
||||
this.pagerFridayCheckBox = view.modelBuilder.checkBox()
|
||||
.withProperties({
|
||||
label: OperatorDialog.PagerFridayCheckBoxLabel,
|
||||
width: 80
|
||||
}).component();
|
||||
this.pagerFridayCheckBox.onChanged(() => {
|
||||
if (this.pagerFridayCheckBox.checked) {
|
||||
this.weekdayPagerStartTimeInput.enabled = true;
|
||||
this.weekdayPagerEndTimeInput.enabled = true;
|
||||
} else {
|
||||
if (!this.pagerMondayCheckBox.checked && !this.pagerWednesdayCheckBox.checked &&
|
||||
!this.pagerThursdayCheckBox.checked && !this.pagerTuesdayCheckBox.checked) {
|
||||
this.weekdayPagerStartTimeInput.enabled = false;
|
||||
this.weekdayPagerEndTimeInput.enabled = false;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let pagerFridayCheckboxContainer = view.modelBuilder.flexContainer()
|
||||
.withLayout({
|
||||
flexFlow: 'row',
|
||||
alignItems: 'baseline',
|
||||
width: '100%'
|
||||
}).withItems([this.pagerFridayCheckBox, weekdayStartInputContainer, weekdayEndInputContainer])
|
||||
.component();
|
||||
|
||||
this.pagerSaturdayCheckBox = view.modelBuilder.checkBox()
|
||||
.withProperties({
|
||||
label: OperatorDialog.PagerSaturdayCheckBoxLabel,
|
||||
width: 80
|
||||
}).component();
|
||||
|
||||
this.pagerSaturdayCheckBox.onChanged(() => {
|
||||
if (this.pagerSaturdayCheckBox.checked) {
|
||||
this.saturdayPagerStartTimeInput.enabled = true;
|
||||
this.saturdayPagerEndTimeInput.enabled = true;
|
||||
} else {
|
||||
this.saturdayPagerStartTimeInput.enabled = false;
|
||||
this.saturdayPagerEndTimeInput.enabled = false;
|
||||
}
|
||||
});
|
||||
|
||||
this.saturdayPagerStartTimeInput = view.modelBuilder.inputBox()
|
||||
.withProperties({
|
||||
inputType: 'time',
|
||||
placeHolder: '08:00:00'
|
||||
}).component();
|
||||
this.saturdayPagerStartTimeInput.enabled = false;
|
||||
let saturdayStartInputContainer = view.modelBuilder.formContainer()
|
||||
.withFormItems([{
|
||||
component: this.saturdayPagerStartTimeInput,
|
||||
title: OperatorDialog.WorkdayBeginLabel
|
||||
}]).component();
|
||||
|
||||
this.saturdayPagerEndTimeInput = view.modelBuilder.inputBox()
|
||||
.withProperties({
|
||||
inputType: 'time',
|
||||
placeHolder: '06:00:00'
|
||||
}).component();
|
||||
this.saturdayPagerEndTimeInput.enabled = false;
|
||||
let saturdayEndInputContainer = view.modelBuilder.formContainer()
|
||||
.withFormItems([{
|
||||
component: this.saturdayPagerEndTimeInput,
|
||||
title: OperatorDialog.WorkdayEndLabel
|
||||
}]).component();
|
||||
|
||||
let pagerSaturdayCheckboxContainer = view.modelBuilder.flexContainer()
|
||||
.withLayout({
|
||||
flexFlow: 'row',
|
||||
alignItems: 'baseline'
|
||||
}).withItems([this.pagerSaturdayCheckBox, saturdayStartInputContainer, saturdayEndInputContainer])
|
||||
.component();
|
||||
|
||||
this.pagerSundayCheckBox = view.modelBuilder.checkBox()
|
||||
.withProperties({
|
||||
label: OperatorDialog.PagerSundayCheckBoxLabel,
|
||||
width: 80
|
||||
}).component();
|
||||
|
||||
this.pagerSundayCheckBox.onChanged(() => {
|
||||
if (this.pagerSundayCheckBox.checked) {
|
||||
this.sundayPagerStartTimeInput.enabled = true;
|
||||
this.sundayPagerEndTimeInput.enabled = true;
|
||||
} else {
|
||||
this.sundayPagerStartTimeInput.enabled = false;
|
||||
this.sundayPagerEndTimeInput.enabled = false;
|
||||
}
|
||||
});
|
||||
|
||||
this.sundayPagerStartTimeInput = view.modelBuilder.inputBox()
|
||||
.withProperties({
|
||||
inputType: 'time',
|
||||
placeHolder: '08:00:00'
|
||||
}).component();
|
||||
this.sundayPagerStartTimeInput.enabled = false;
|
||||
let sundayStartInputContainer = view.modelBuilder.formContainer()
|
||||
.withFormItems([{
|
||||
component: this.sundayPagerStartTimeInput,
|
||||
title: OperatorDialog.WorkdayBeginLabel
|
||||
}]).component();
|
||||
|
||||
this.sundayPagerEndTimeInput = view.modelBuilder.inputBox()
|
||||
.withProperties({
|
||||
inputType: 'time',
|
||||
placeHolder: '06:00:00'
|
||||
}).component();
|
||||
this.sundayPagerEndTimeInput.enabled = false;
|
||||
let sundayEndInputContainer = view.modelBuilder.formContainer()
|
||||
.withFormItems([{
|
||||
component: this.sundayPagerEndTimeInput,
|
||||
title: OperatorDialog.WorkdayEndLabel
|
||||
}]).component();
|
||||
|
||||
let pagerSundayCheckboxContainer = view.modelBuilder.flexContainer()
|
||||
.withLayout({
|
||||
flexFlow: 'row',
|
||||
alignItems: 'baseline'
|
||||
}).withItems([this.pagerSundayCheckBox, sundayStartInputContainer, sundayEndInputContainer])
|
||||
.component();
|
||||
|
||||
let formModel = view.modelBuilder.formContainer()
|
||||
.withFormItems([{
|
||||
component: this.nameTextBox,
|
||||
title: OperatorDialog.NameLabel
|
||||
}, {
|
||||
component: this.enabledCheckBox,
|
||||
title: ''
|
||||
}, {
|
||||
component: this.emailNameTextBox,
|
||||
title: OperatorDialog.EmailNameTextLabel
|
||||
}, {
|
||||
component: this.pagerEmailNameTextBox,
|
||||
title: OperatorDialog.PagerEmailNameTextLabel
|
||||
}, {
|
||||
components: [{
|
||||
component: this.pagerMondayCheckBox,
|
||||
title: ''
|
||||
}, {
|
||||
component: this.pagerTuesdayCheckBox,
|
||||
title: ''
|
||||
}, {
|
||||
component: this.pagerWednesdayCheckBox,
|
||||
title: ''
|
||||
}, {
|
||||
component: this.pagerThursdayCheckBox,
|
||||
title: ''
|
||||
}, {
|
||||
component: pagerFridayCheckboxContainer,
|
||||
title: ''
|
||||
}, {
|
||||
component: pagerSaturdayCheckboxContainer,
|
||||
title: ''
|
||||
}, {
|
||||
component: pagerSundayCheckboxContainer,
|
||||
title: ''
|
||||
}] ,
|
||||
title: OperatorDialog.PagerDutyScheduleLabel
|
||||
}]).withLayout({ width: '100%' }).component();
|
||||
|
||||
await view.initializeModel(formModel);
|
||||
});
|
||||
}
|
||||
|
||||
private initializeNotificationTab() {
|
||||
this.notificationsTab.registerContent(async view => {
|
||||
|
||||
this.alertsTable = view.modelBuilder.table()
|
||||
.withProperties({
|
||||
columns: [
|
||||
OperatorDialog.AlertNameColumnLabel,
|
||||
OperatorDialog.AlertEmailColumnLabel,
|
||||
OperatorDialog.AlertPagerColumnLabel
|
||||
],
|
||||
data: [],
|
||||
height: 500
|
||||
}).component();
|
||||
|
||||
let formModel = view.modelBuilder.formContainer()
|
||||
.withFormItems([{
|
||||
component: this.alertsTable,
|
||||
title: OperatorDialog.AlertsTableLabel
|
||||
}]).withLayout({ width: '100%' }).component();
|
||||
|
||||
await view.initializeModel(formModel);
|
||||
});
|
||||
}
|
||||
|
||||
protected updateModel() {
|
||||
this.model.name = this.nameTextBox.value;
|
||||
this.model.enabled = this.enabledCheckBox.checked;
|
||||
this.model.emailAddress = this.emailNameTextBox.value;
|
||||
this.model.pagerAddress = this.pagerEmailNameTextBox.value;
|
||||
this.model.weekdayPagerStartTime = this.weekdayPagerStartTimeInput.value;
|
||||
this.model.weekdayPagerEndTime = this.weekdayPagerEndTimeInput.value;
|
||||
this.model.saturdayPagerStartTime = this.saturdayPagerStartTimeInput.value;
|
||||
this.model.saturdayPagerEndTime = this.saturdayPagerEndTimeInput.value;
|
||||
this.model.sundayPagerStartTime = this.sundayPagerStartTimeInput.value;
|
||||
this.model.sundayPagerEndTime = this.sundayPagerEndTimeInput.value;
|
||||
}
|
||||
}
|
||||
95
extensions/agent/src/dialogs/pickScheduleDialog.ts
Normal file
95
extensions/agent/src/dialogs/pickScheduleDialog.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 sqlops from 'sqlops';
|
||||
import * as vscode from 'vscode';
|
||||
import { PickScheduleData } from '../data/pickScheduleData';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export class PickScheduleDialog {
|
||||
|
||||
// TODO: localize
|
||||
// Top level
|
||||
private readonly DialogTitle: string = localize('pickSchedule.jobSchedules', 'Job Schedules');
|
||||
private readonly OkButtonText: string = localize('pickSchedule.ok', 'OK');
|
||||
private readonly CancelButtonText: string = localize('pickSchedule.cancel', 'Cancel');
|
||||
private readonly ScheduleNameLabelText: string = localize('pickSchedule.scheduleName', 'Schedule Name');
|
||||
private readonly SchedulesLabelText: string = localize('pickSchedule.schedules', 'Schedules');
|
||||
|
||||
// UI Components
|
||||
private dialog: sqlops.window.modelviewdialog.Dialog;
|
||||
private schedulesTable: sqlops.TableComponent;
|
||||
|
||||
private model: PickScheduleData;
|
||||
|
||||
private _onSuccess: vscode.EventEmitter<PickScheduleData> = new vscode.EventEmitter<PickScheduleData>();
|
||||
public readonly onSuccess: vscode.Event<PickScheduleData> = this._onSuccess.event;
|
||||
|
||||
constructor(ownerUri: string) {
|
||||
this.model = new PickScheduleData(ownerUri);
|
||||
}
|
||||
|
||||
public async showDialog() {
|
||||
await this.model.initialize();
|
||||
this.dialog = sqlops.window.modelviewdialog.createDialog(this.DialogTitle);
|
||||
this.initializeContent();
|
||||
this.dialog.okButton.onClick(async () => await this.execute());
|
||||
this.dialog.cancelButton.onClick(async () => await this.cancel());
|
||||
this.dialog.okButton.label = this.OkButtonText;
|
||||
this.dialog.cancelButton.label = this.CancelButtonText;
|
||||
sqlops.window.modelviewdialog.openDialog(this.dialog);
|
||||
}
|
||||
|
||||
private initializeContent() {
|
||||
this.dialog.registerContent(async view => {
|
||||
this.schedulesTable = view.modelBuilder.table()
|
||||
.withProperties({
|
||||
columns: [
|
||||
this.ScheduleNameLabelText
|
||||
],
|
||||
data: [],
|
||||
height: '80em',
|
||||
width: '40em'
|
||||
}).component();
|
||||
|
||||
let formModel = view.modelBuilder.formContainer()
|
||||
.withFormItems([{
|
||||
component: this.schedulesTable,
|
||||
title: this.SchedulesLabelText
|
||||
}]).withLayout({ width: '100%' }).component();
|
||||
|
||||
await view.initializeModel(formModel);
|
||||
|
||||
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.name ];
|
||||
}
|
||||
this.schedulesTable.data = data;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async execute() {
|
||||
this.updateModel();
|
||||
await this.model.save();
|
||||
this._onSuccess.fire(this.model);
|
||||
}
|
||||
|
||||
private async cancel() {
|
||||
}
|
||||
|
||||
private updateModel() {
|
||||
let selectedRows = this.schedulesTable.selectedRows;
|
||||
if (selectedRows && selectedRows.length > 0) {
|
||||
let selectedRow = selectedRows[0];
|
||||
this.model.selectedSchedule = this.model.schedules[selectedRow];
|
||||
}
|
||||
}
|
||||
}
|
||||
219
extensions/agent/src/dialogs/proxyDialog.ts
Normal file
219
extensions/agent/src/dialogs/proxyDialog.ts
Normal file
@@ -0,0 +1,219 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 sqlops from 'sqlops';
|
||||
import { AgentDialog } from './agentDialog';
|
||||
import { ProxyData } from '../data/proxyData';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export class ProxyDialog extends AgentDialog<ProxyData> {
|
||||
|
||||
// Top level
|
||||
private static readonly CreateDialogTitle: string = localize('createProxy.createProxy', 'Create Proxy');
|
||||
private static readonly EditDialogTitle: string = localize('createProxy.editProxy', 'Edit Proxy');
|
||||
private static readonly GeneralTabText: string = localize('createProxy.General', 'General');
|
||||
|
||||
// General tab strings
|
||||
private static readonly ProxyNameTextBoxLabel: string = localize('createProxy.ProxyName', 'Proxy name');
|
||||
private static readonly CredentialNameTextBoxLabel: string = localize('createProxy.CredentialName', 'Credential name');
|
||||
private static readonly DescriptionTextBoxLabel: string = localize('createProxy.Description', 'Description');
|
||||
private static readonly SubsystemLabel: string = localize('createProxy.SubsystemName', 'Subsystem');
|
||||
private static readonly OperatingSystemLabel: string = localize('createProxy.OperatingSystem', 'Operating system (CmdExec)');
|
||||
private static readonly ReplicationSnapshotLabel: string = localize('createProxy.ReplicationSnapshot', 'Replication Snapshot');
|
||||
private static readonly ReplicationTransactionLogLabel: string = localize('createProxy.ReplicationTransactionLog', 'Replication Transaction-Log Reader');
|
||||
private static readonly ReplicationDistributorLabel: string = localize('createProxy.ReplicationDistributor', 'Replication Distributor');
|
||||
private static readonly ReplicationMergeLabel: string = localize('createProxy.ReplicationMerge', 'Replication Merge');
|
||||
private static readonly ReplicationQueueReaderLabel: string = localize('createProxy.ReplicationQueueReader', 'Replication Queue Reader');
|
||||
private static readonly SSASQueryLabel: string = localize('createProxy.SSASQueryLabel', 'SQL Server Analysis Services Query');
|
||||
private static readonly SSASCommandLabel: string = localize('createProxy.SSASCommandLabel', 'SQL Server Analysis Services Command');
|
||||
private static readonly SSISPackageLabel: string = localize('createProxy.SSISPackage', 'SQL Server Integration Services Package');
|
||||
private static readonly PowerShellLabel: string = localize('createProxy.PowerShell', 'PowerShell');
|
||||
private static readonly SubSystemHeadingLabel: string = localize('createProxy.subSystemHeading', 'Active to the following subsytems');
|
||||
|
||||
// UI Components
|
||||
private generalTab: sqlops.window.modelviewdialog.DialogTab;
|
||||
|
||||
// General tab controls
|
||||
private proxyNameTextBox: sqlops.InputBoxComponent;
|
||||
private credentialNameDropDown: sqlops.DropDownComponent;
|
||||
private descriptionTextBox: sqlops.InputBoxComponent;
|
||||
private subsystemCheckBox: sqlops.CheckBoxComponent;
|
||||
private operatingSystemCheckBox: sqlops.CheckBoxComponent;
|
||||
private replicationSnapshotCheckBox: sqlops.CheckBoxComponent;
|
||||
private replicationTransactionLogCheckBox: sqlops.CheckBoxComponent;
|
||||
private replicationDistributorCheckBox: sqlops.CheckBoxComponent;
|
||||
private replicationMergeCheckbox: sqlops.CheckBoxComponent;
|
||||
private replicationQueueReaderCheckbox: sqlops.CheckBoxComponent;
|
||||
private sqlQueryCheckBox: sqlops.CheckBoxComponent;
|
||||
private sqlCommandCheckBox: sqlops.CheckBoxComponent;
|
||||
private sqlIntegrationServicesPackageCheckbox: sqlops.CheckBoxComponent;
|
||||
private powershellCheckBox: sqlops.CheckBoxComponent;
|
||||
|
||||
private credentials: sqlops.CredentialInfo[];
|
||||
|
||||
constructor(ownerUri: string, proxyInfo: sqlops.AgentProxyInfo = undefined, credentials: sqlops.CredentialInfo[]) {
|
||||
super(
|
||||
ownerUri,
|
||||
new ProxyData(ownerUri, proxyInfo),
|
||||
proxyInfo ? ProxyDialog.EditDialogTitle : ProxyDialog.CreateDialogTitle);
|
||||
this.credentials = credentials;
|
||||
}
|
||||
|
||||
protected async initializeDialog(dialog: sqlops.window.modelviewdialog.Dialog) {
|
||||
this.generalTab = sqlops.window.modelviewdialog.createTab(ProxyDialog.GeneralTabText);
|
||||
|
||||
|
||||
this.initializeGeneralTab();
|
||||
|
||||
this.dialog.content = [this.generalTab];
|
||||
}
|
||||
|
||||
private initializeGeneralTab() {
|
||||
this.generalTab.registerContent(async view => {
|
||||
|
||||
this.proxyNameTextBox = view.modelBuilder.inputBox()
|
||||
.withProperties({width: 420})
|
||||
.component();
|
||||
|
||||
this.credentialNameDropDown = view.modelBuilder.dropDown()
|
||||
.withProperties({
|
||||
width: 432,
|
||||
value: '',
|
||||
editable: true,
|
||||
values: this.credentials.length > 0 ? this.credentials.map(c => c.name) : ['']
|
||||
})
|
||||
.component();
|
||||
|
||||
this.descriptionTextBox = view.modelBuilder.inputBox()
|
||||
.withProperties({
|
||||
width: 420,
|
||||
multiline: true,
|
||||
height: 300
|
||||
})
|
||||
.component();
|
||||
|
||||
this.subsystemCheckBox = view.modelBuilder.checkBox()
|
||||
.withProperties({
|
||||
label: ProxyDialog.SubsystemLabel
|
||||
}).component();
|
||||
|
||||
this.subsystemCheckBox.onChanged(() => {
|
||||
if (this.subsystemCheckBox.checked) {
|
||||
this.operatingSystemCheckBox.checked = true;
|
||||
this.replicationSnapshotCheckBox.checked = true;
|
||||
this.replicationTransactionLogCheckBox.checked = true;
|
||||
this.replicationDistributorCheckBox.checked = true;
|
||||
this.replicationMergeCheckbox.checked = true;
|
||||
this.replicationQueueReaderCheckbox.checked = true;
|
||||
this.sqlQueryCheckBox.checked = true;
|
||||
this.sqlCommandCheckBox.checked = true;
|
||||
this.sqlIntegrationServicesPackageCheckbox.checked = true;
|
||||
this.powershellCheckBox.checked = true;
|
||||
} else {
|
||||
this.operatingSystemCheckBox.checked = false;
|
||||
this.replicationSnapshotCheckBox.checked = false;
|
||||
this.replicationTransactionLogCheckBox.checked = false;
|
||||
this.replicationDistributorCheckBox.checked = false;
|
||||
this.replicationMergeCheckbox.checked = false;
|
||||
this.replicationQueueReaderCheckbox.checked = false;
|
||||
this.sqlQueryCheckBox.checked = false;
|
||||
this.sqlCommandCheckBox.checked = false;
|
||||
this.sqlIntegrationServicesPackageCheckbox.checked = false;
|
||||
this.powershellCheckBox.checked = false;
|
||||
}
|
||||
});
|
||||
|
||||
this.operatingSystemCheckBox = view.modelBuilder.checkBox()
|
||||
.withProperties({
|
||||
label: ProxyDialog.OperatingSystemLabel
|
||||
}).component();
|
||||
|
||||
this.replicationSnapshotCheckBox = view.modelBuilder.checkBox()
|
||||
.withProperties({
|
||||
label: ProxyDialog.ReplicationSnapshotLabel
|
||||
}).component();
|
||||
|
||||
this.replicationTransactionLogCheckBox = view.modelBuilder.checkBox()
|
||||
.withProperties({
|
||||
label: ProxyDialog.ReplicationTransactionLogLabel
|
||||
}).component();
|
||||
|
||||
this.replicationDistributorCheckBox = view.modelBuilder.checkBox()
|
||||
.withProperties({
|
||||
label: ProxyDialog.ReplicationDistributorLabel
|
||||
}).component();
|
||||
|
||||
this.replicationMergeCheckbox = view.modelBuilder.checkBox()
|
||||
.withProperties({
|
||||
label: ProxyDialog.ReplicationMergeLabel
|
||||
}).component();
|
||||
|
||||
this.replicationQueueReaderCheckbox = view.modelBuilder.checkBox()
|
||||
.withProperties({
|
||||
label: ProxyDialog.ReplicationQueueReaderLabel
|
||||
}).component();
|
||||
|
||||
this.sqlQueryCheckBox = view.modelBuilder.checkBox()
|
||||
.withProperties({
|
||||
label: ProxyDialog.SSASQueryLabel
|
||||
}).component();
|
||||
|
||||
this.sqlCommandCheckBox = view.modelBuilder.checkBox()
|
||||
.withProperties({
|
||||
label: ProxyDialog.SSASCommandLabel
|
||||
}).component();
|
||||
|
||||
this.sqlIntegrationServicesPackageCheckbox = view.modelBuilder.checkBox()
|
||||
.withProperties({
|
||||
label: ProxyDialog.SSISPackageLabel
|
||||
}).component();
|
||||
|
||||
this.powershellCheckBox = view.modelBuilder.checkBox()
|
||||
.withProperties({
|
||||
label: ProxyDialog.PowerShellLabel
|
||||
}).component();
|
||||
|
||||
let checkBoxContainer = view.modelBuilder.groupContainer()
|
||||
.withItems([this.operatingSystemCheckBox, this.replicationSnapshotCheckBox,
|
||||
this.replicationTransactionLogCheckBox, this.replicationDistributorCheckBox, this.replicationMergeCheckbox,
|
||||
this.replicationQueueReaderCheckbox, this.sqlQueryCheckBox, this.sqlCommandCheckBox, this.sqlIntegrationServicesPackageCheckbox,
|
||||
this.powershellCheckBox])
|
||||
.component();
|
||||
|
||||
let formModel = view.modelBuilder.formContainer()
|
||||
.withFormItems([{
|
||||
component: this.proxyNameTextBox,
|
||||
title: ProxyDialog.ProxyNameTextBoxLabel
|
||||
}, {
|
||||
component: this.credentialNameDropDown,
|
||||
title: ProxyDialog.CredentialNameTextBoxLabel
|
||||
}, {
|
||||
component: this.descriptionTextBox,
|
||||
title: ProxyDialog.DescriptionTextBoxLabel
|
||||
}]).withLayout({ width: 420 }).component();
|
||||
|
||||
await view.initializeModel(formModel);
|
||||
|
||||
this.proxyNameTextBox.value = this.model.accountName;
|
||||
this.credentialNameDropDown.value = this.model.credentialName;
|
||||
this.descriptionTextBox.value = this.model.description;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
protected updateModel() {
|
||||
this.model.accountName = this.proxyNameTextBox.value;
|
||||
this.model.credentialName = this.credentialNameDropDown.value as string;
|
||||
this.model.credentialId = this.credentials.find(
|
||||
c => c.name === this.model.credentialName).id;
|
||||
this.model.credentialIdentity = this.credentials.find(
|
||||
c => c.name === this.model.credentialName).identity;
|
||||
this.model.description = this.descriptionTextBox.value;
|
||||
}
|
||||
}
|
||||
95
extensions/agent/src/dialogs/scheduleDialog.ts
Normal file
95
extensions/agent/src/dialogs/scheduleDialog.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 sqlops from 'sqlops';
|
||||
import * as vscode from 'vscode';
|
||||
import { ScheduleData } from '../data/scheduleData';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export class ScheduleDialog {
|
||||
|
||||
// Top level
|
||||
private readonly DialogTitle: string = localize('scheduleDialog.newSchedule', 'New Schedule');
|
||||
private readonly OkButtonText: string = localize('scheduleDialog.ok', 'OK');
|
||||
private readonly CancelButtonText: string = localize('scheduleDialog.cancel', 'Cancel');
|
||||
private readonly ScheduleNameText: string = localize('scheduleDialog.scheduleName', 'Schedule Name');
|
||||
private readonly SchedulesLabelText: string = localize('scheduleDialog.schedules', 'Schedules');
|
||||
|
||||
// UI Components
|
||||
private dialog: sqlops.window.modelviewdialog.Dialog;
|
||||
private schedulesTable: sqlops.TableComponent;
|
||||
|
||||
private model: ScheduleData;
|
||||
|
||||
private _onSuccess: vscode.EventEmitter<ScheduleData> = new vscode.EventEmitter<ScheduleData>();
|
||||
public readonly onSuccess: vscode.Event<ScheduleData> = this._onSuccess.event;
|
||||
|
||||
constructor(ownerUri: string) {
|
||||
this.model = new ScheduleData(ownerUri);
|
||||
}
|
||||
|
||||
public async showDialog() {
|
||||
await this.model.initialize();
|
||||
this.dialog = sqlops.window.modelviewdialog.createDialog(this.DialogTitle);
|
||||
this.initializeContent();
|
||||
this.dialog.okButton.onClick(async () => await this.execute());
|
||||
this.dialog.cancelButton.onClick(async () => await this.cancel());
|
||||
this.dialog.okButton.label = this.OkButtonText;
|
||||
this.dialog.cancelButton.label = this.CancelButtonText;
|
||||
|
||||
sqlops.window.modelviewdialog.openDialog(this.dialog);
|
||||
}
|
||||
|
||||
private initializeContent() {
|
||||
this.dialog.registerContent(async view => {
|
||||
this.schedulesTable = view.modelBuilder.table()
|
||||
.withProperties({
|
||||
columns: [
|
||||
this.ScheduleNameText
|
||||
],
|
||||
data: [],
|
||||
height: 600,
|
||||
width: 400
|
||||
}).component();
|
||||
|
||||
let formModel = view.modelBuilder.formContainer()
|
||||
.withFormItems([{
|
||||
component: this.schedulesTable,
|
||||
title: this.SchedulesLabelText
|
||||
}]).withLayout({ width: '100%' }).component();
|
||||
|
||||
await view.initializeModel(formModel);
|
||||
|
||||
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.name ];
|
||||
}
|
||||
this.schedulesTable.data = data;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private async execute() {
|
||||
this.updateModel();
|
||||
await this.model.save();
|
||||
this._onSuccess.fire(this.model);
|
||||
}
|
||||
|
||||
private async cancel() {
|
||||
}
|
||||
|
||||
private updateModel() {
|
||||
let selectedRows = this.schedulesTable.selectedRows;
|
||||
if (selectedRows && selectedRows.length > 0) {
|
||||
let selectedRow = selectedRows[0];
|
||||
this.model.selectedSchedule = this.model.schedules[selectedRow];
|
||||
}
|
||||
}
|
||||
}
|
||||
17
extensions/agent/src/interfaces.ts
Normal file
17
extensions/agent/src/interfaces.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.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
export enum AgentDialogMode {
|
||||
CREATE = 1,
|
||||
EDIT = 2,
|
||||
VIEW = 3
|
||||
}
|
||||
|
||||
export interface IAgentDialogData {
|
||||
dialogMode: AgentDialogMode;
|
||||
initialize(): void;
|
||||
save(): void;
|
||||
}
|
||||
@@ -6,12 +6,10 @@
|
||||
|
||||
import vscode = require('vscode');
|
||||
import { MainController } from './mainController';
|
||||
import { ApiWrapper } from './apiWrapper';
|
||||
export let controller: MainController;
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
let apiWrapper = new ApiWrapper();
|
||||
controller = new MainController(context, apiWrapper);
|
||||
controller = new MainController(context);
|
||||
controller.activate();
|
||||
}
|
||||
|
||||
72
extensions/agent/src/mainController.ts
Normal file
72
extensions/agent/src/mainController.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 sqlops from 'sqlops';
|
||||
import * as vscode from 'vscode';
|
||||
import { AlertDialog } from './dialogs/alertDialog';
|
||||
import { JobDialog } from './dialogs/jobDialog';
|
||||
import { OperatorDialog } from './dialogs/operatorDialog';
|
||||
import { ProxyDialog } from './dialogs/proxyDialog';
|
||||
import { JobStepDialog } from './dialogs/jobStepDialog';
|
||||
import { PickScheduleDialog } from './dialogs/pickScheduleDialog';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
/**
|
||||
* The main controller class that initializes the extension
|
||||
*/
|
||||
export class MainController {
|
||||
protected _context: vscode.ExtensionContext;
|
||||
|
||||
// PUBLIC METHODS //////////////////////////////////////////////////////
|
||||
public constructor(context: vscode.ExtensionContext) {
|
||||
this._context = context;
|
||||
}
|
||||
|
||||
public static showNotYetImplemented(): void {
|
||||
vscode.window.showInformationMessage(
|
||||
localize('mainController.notImplemented', "This feature is under development. Check-out the latest insiders build if you'd like to try out the most recent changes!"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Activates the extension
|
||||
*/
|
||||
public activate(): void {
|
||||
vscode.commands.registerCommand('agent.openJobDialog', (ownerUri: string, jobInfo: sqlops.AgentJobInfo) => {
|
||||
let dialog = new JobDialog(ownerUri, jobInfo);
|
||||
dialog.openDialog();
|
||||
});
|
||||
vscode.commands.registerCommand('agent.openNewStepDialog', (ownerUri: string, jobId: string, server: string, stepId: number) => {
|
||||
let dialog = new JobStepDialog(ownerUri, jobId, server, stepId);
|
||||
dialog.openNewStepDialog();
|
||||
});
|
||||
vscode.commands.registerCommand('agent.openPickScheduleDialog', (ownerUri: string) => {
|
||||
let dialog = new PickScheduleDialog(ownerUri);
|
||||
dialog.showDialog();
|
||||
});
|
||||
vscode.commands.registerCommand('agent.openAlertDialog', (ownerUri: string, alertInfo: sqlops.AgentAlertInfo, jobs: string[]) => {
|
||||
let dialog = new AlertDialog(ownerUri, alertInfo, jobs);
|
||||
dialog.openDialog();
|
||||
});
|
||||
vscode.commands.registerCommand('agent.openOperatorDialog', (ownerUri: string, operatorInfo: sqlops.AgentOperatorInfo) => {
|
||||
let dialog = new OperatorDialog(ownerUri, operatorInfo);
|
||||
dialog.openDialog();
|
||||
});
|
||||
vscode.commands.registerCommand('agent.openProxyDialog', (ownerUri: string, proxyInfo: sqlops.AgentProxyInfo, credentials: sqlops.CredentialInfo[]) => {
|
||||
//@TODO: reenable create proxy after snapping July release (7/14/18)
|
||||
// let dialog = new ProxyDialog(ownerUri, proxyInfo, credentials);
|
||||
// dialog.openDialog();
|
||||
MainController.showNotYetImplemented();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Deactivates the extension
|
||||
*/
|
||||
public deactivate(): void {
|
||||
}
|
||||
}
|
||||
21
extensions/agent/src/test/agent.test.ts
Normal file
21
extensions/agent/src/test/agent.test.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import 'mocha';
|
||||
import * as vscode from 'vscode';
|
||||
import * as sqlops from 'sqlops';
|
||||
import { JobData } from '../data/jobData';
|
||||
import { TestAgentService } from './testAgentService';
|
||||
|
||||
const testOwnerUri = 'agent://testuri';
|
||||
|
||||
suite('Agent extension', () => {
|
||||
test('Create Job Data', async () => {
|
||||
let testAgentService = new TestAgentService();
|
||||
let data = new JobData(testOwnerUri, undefined, testAgentService);
|
||||
data.save();
|
||||
});
|
||||
});
|
||||
110
extensions/agent/src/test/testAgentService.ts
Normal file
110
extensions/agent/src/test/testAgentService.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
|
||||
import * as sqlops from 'sqlops';
|
||||
|
||||
export class TestAgentService implements sqlops.AgentServicesProvider {
|
||||
handle?: number;
|
||||
readonly providerId: string = 'Test Provider';
|
||||
|
||||
// Job management methods
|
||||
getJobs(ownerUri: string): Thenable<sqlops.AgentJobsResult> {
|
||||
return undefined;
|
||||
}
|
||||
getJobHistory(ownerUri: string, jobId: string): Thenable<sqlops.AgentJobHistoryResult> {
|
||||
return undefined;
|
||||
}
|
||||
jobAction(ownerUri: string, jobName: string, action: string): Thenable<sqlops.ResultStatus> {
|
||||
return undefined;
|
||||
}
|
||||
createJob(ownerUri: string, jobInfo: sqlops.AgentJobInfo): Thenable<sqlops.CreateAgentJobResult> {
|
||||
return undefined;
|
||||
}
|
||||
updateJob(ownerUri: string, originalJobName: string, jobInfo: sqlops.AgentJobInfo): Thenable<sqlops.UpdateAgentJobResult> {
|
||||
return undefined;
|
||||
}
|
||||
deleteJob(ownerUri: string, jobInfo: sqlops.AgentJobInfo): Thenable<sqlops.ResultStatus> {
|
||||
return undefined;
|
||||
}
|
||||
getJobDefaults(ownerUri: string): Thenable<sqlops.AgentJobDefaultsResult> {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Job Step management methods
|
||||
createJobStep(ownerUri: string, jobInfo: sqlops.AgentJobStepInfo): Thenable<sqlops.CreateAgentJobStepResult> {
|
||||
return undefined;
|
||||
}
|
||||
updateJobStep(ownerUri: string, originalJobStepName: string, jobInfo: sqlops.AgentJobStepInfo): Thenable<sqlops.UpdateAgentJobStepResult> {
|
||||
return undefined;
|
||||
}
|
||||
deleteJobStep(ownerUri: string, jobInfo: sqlops.AgentJobStepInfo): Thenable<sqlops.ResultStatus> {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Alert management methods
|
||||
getAlerts(ownerUri: string): Thenable<sqlops.AgentAlertsResult> {
|
||||
return undefined;
|
||||
}
|
||||
createAlert(ownerUri: string, alertInfo: sqlops.AgentAlertInfo): Thenable<sqlops.CreateAgentAlertResult> {
|
||||
return undefined;
|
||||
}
|
||||
updateAlert(ownerUri: string, originalAlertName: string, alertInfo: sqlops.AgentAlertInfo): Thenable<sqlops.UpdateAgentAlertResult> {
|
||||
return undefined;
|
||||
}
|
||||
deleteAlert(ownerUri: string, alertInfo: sqlops.AgentAlertInfo): Thenable<sqlops.ResultStatus> {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Operator management methods
|
||||
getOperators(ownerUri: string): Thenable<sqlops.AgentOperatorsResult> {
|
||||
return undefined;
|
||||
}
|
||||
createOperator(ownerUri: string, operatorInfo: sqlops.AgentOperatorInfo): Thenable<sqlops.CreateAgentOperatorResult> {
|
||||
return undefined;
|
||||
}
|
||||
updateOperator(ownerUri: string, originalOperatorName: string, operatorInfo: sqlops.AgentOperatorInfo): Thenable<sqlops.UpdateAgentOperatorResult> {
|
||||
return undefined;
|
||||
}
|
||||
deleteOperator(ownerUri: string, operatorInfo: sqlops.AgentOperatorInfo): Thenable<sqlops.ResultStatus> {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Proxy management methods
|
||||
getProxies(ownerUri: string): Thenable<sqlops.AgentProxiesResult> {
|
||||
return undefined;
|
||||
}
|
||||
createProxy(ownerUri: string, proxyInfo: sqlops.AgentProxyInfo): Thenable<sqlops.CreateAgentOperatorResult> {
|
||||
return undefined;
|
||||
}
|
||||
updateProxy(ownerUri: string, originalProxyName: string, proxyInfo: sqlops.AgentProxyInfo): Thenable<sqlops.UpdateAgentOperatorResult> {
|
||||
return undefined;
|
||||
}
|
||||
deleteProxy(ownerUri: string, proxyInfo: sqlops.AgentProxyInfo): Thenable<sqlops.ResultStatus> {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Agent Credential method
|
||||
getCredentials(ownerUri: string): Thenable<sqlops.GetCredentialsResult> {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Job Schedule management methods
|
||||
getJobSchedules(ownerUri: string): Thenable<sqlops.AgentJobSchedulesResult> {
|
||||
return undefined;
|
||||
}
|
||||
createJobSchedule(ownerUri: string, scheduleInfo: sqlops.AgentJobScheduleInfo): Thenable<sqlops.CreateAgentJobScheduleResult> {
|
||||
return undefined;
|
||||
}
|
||||
updateJobSchedule(ownerUri: string, originalScheduleName: string, scheduleInfo: sqlops.AgentJobScheduleInfo): Thenable<sqlops.UpdateAgentJobScheduleResult> {
|
||||
return undefined;
|
||||
}
|
||||
deleteJobSchedule(ownerUri: string, scheduleInfo: sqlops.AgentJobScheduleInfo): Thenable<sqlops.ResultStatus> {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
registerOnUpdated(handler: () => any): void {
|
||||
}
|
||||
}
|
||||
9
extensions/agent/src/typings/ref.d.ts
vendored
Normal file
9
extensions/agent/src/typings/ref.d.ts
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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'/>
|
||||
/// <reference path='../../../../src/sql/sqlops.d.ts'/>
|
||||
/// <reference path='../../../../src/sql/sqlops.proposed.d.ts'/>
|
||||
/// <reference types='@types/node'/>
|
||||
File diff suppressed because it is too large
Load Diff
1
extensions/import/.gitignore
vendored
Normal file
1
extensions/import/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
flatfileimportservice/
|
||||
2
extensions/import/.vscodeignore
Normal file
2
extensions/import/.vscodeignore
Normal file
@@ -0,0 +1,2 @@
|
||||
client/src/**
|
||||
client/tsconfig.json
|
||||
25
extensions/import/README.md
Normal file
25
extensions/import/README.md
Normal file
@@ -0,0 +1,25 @@
|
||||
# Microsoft SQL Server Import for SQL Operations Studio
|
||||
|
||||
|
||||
Import Flat File Wizard is a simple way to copy data from a flat file (.csv, .txt) to a destination. This overview describes the reasons for using this wizard, how to find this wizard, and a simple example to follow.
|
||||
|
||||

|
||||
|
||||
## Why would I use this wizard?
|
||||
This wizard was created to improve the current import experience leveraging an intelligent framework known as Program Synthesis using Examples ([PROSE](https://microsoft.github.io/prose/)). For a user without specialized domain knowledge, importing data can often be a complex, error prone, and tedious task. This wizard streamlines the import process as simple as selecting an input file and unique table name, and the PROSE framework handles the rest.
|
||||
|
||||
PROSE analyzes data patterns in your input file to infer column names, types, delimiters, and more. This framework learns the structure of the file and does all of the hard work so users don't have to.
|
||||
|
||||
## 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/sqlopsstudio/master/LICENSE.txt).
|
||||
1
extensions/import/images/dark_icon.svg
Normal file
1
extensions/import/images/dark_icon.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 21 21"><defs><style>.cls-1{fill:#fff;}.cls-2{fill:#231f20;}.cls-3{fill:#0095d7;}</style></defs><title>importflatfile_inverse</title><path class="cls-1" d="M13.34,1.57c-2.81,0-7.52.53-7.65,2.49v3.2L7,8.54V5.66a17.11,17.11,0,0,0,6.37,1,17.1,17.1,0,0,0,6.38-1V17.58c-.17.46-2.55,1.35-6.38,1.35a19.63,19.63,0,0,1-3.43-.27V20a23.78,23.78,0,0,0,3.43.25c2.86,0,7.66-.57,7.66-2.64V4.06C20.87,2.1,16.16,1.57,13.34,1.57Zm6.38,2.55c-.2.45-2.56,1.28-6.38,1.28S7.24,4.6,7,4.14c.27-.47,2.6-1.29,6.37-1.29s6.16.85,6.38,1.25h0Z"/><polygon class="cls-2" points="18.55 3.06 18.53 3.07 18.53 3.04 18.55 3.06"/><path class="cls-1" d="M7,10,5.69,8.68,5,8H0V19.85H8.91v-8ZM5.2,9.24l.49.49L7,11l.67.67H5.2Zm3,9.86H.74V8.71H4.46v3.71H8.17Z"/><path class="cls-3" d="M16.5,15a.27.27,0,0,1-.08.2L14.2,17.4a.26.26,0,0,1-.19.08.28.28,0,0,1-.2-.08.26.26,0,0,1-.08-.2.82.82,0,0,1,0-.14l.06-.25.08-.32.08-.32.07-.27,0-.17H4.5v-1.5h9.59l0-.17L14,13.79l-.08-.32-.08-.31-.06-.25a.91.91,0,0,1,0-.14.26.26,0,0,1,.08-.2.28.28,0,0,1,.2-.08.26.26,0,0,1,.19.08l2.22,2.22A.26.26,0,0,1,16.5,15Z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
1
extensions/import/images/light_icon.svg
Normal file
1
extensions/import/images/light_icon.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg id="Layer_1" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 21 21"><defs><style>.cls-1{fill:#212121;}.cls-2{fill:#231f20;}.cls-3{fill:#00539c;}</style></defs><title>importflatfile</title><path class="cls-1" d="M13.34,1.3c-2.81,0-7.52.53-7.65,2.49V7L7,8.27V5.39a17.11,17.11,0,0,0,6.37,1,17.1,17.1,0,0,0,6.38-1V17.31c-.17.46-2.55,1.35-6.38,1.35a19.63,19.63,0,0,1-3.43-.27V19.7a23.78,23.78,0,0,0,3.43.25C16.2,20,21,19.38,21,17.31V3.79C20.87,1.83,16.16,1.3,13.34,1.3Zm6.38,2.55c-.2.45-2.56,1.28-6.38,1.28S7.24,4.33,7,3.87c.27-.47,2.6-1.29,6.37-1.29s6.16.85,6.38,1.25h0Z"/><polygon class="cls-2" points="18.55 2.79 18.53 2.81 18.53 2.78 18.55 2.79"/><path class="cls-1" d="M7,9.69,5.69,8.41,5,7.7H0V19.58H8.91v-8ZM5.2,9l.49.49L7,10.74l.67.67H5.2Zm3,9.86H.74V8.44H4.46v3.71H8.17Z"/><path class="cls-3" d="M16.5,14.72a.27.27,0,0,1-.08.2L14.2,17.14a.26.26,0,0,1-.19.08.28.28,0,0,1-.2-.08.26.26,0,0,1-.08-.2.82.82,0,0,1,0-.14l.06-.25.08-.32.08-.32.07-.27,0-.17H4.5V14h9.59l0-.17L14,13.53l-.08-.32-.08-.31-.06-.25a.91.91,0,0,1,0-.14.26.26,0,0,1,.08-.2.28.28,0,0,1,.2-.08.26.26,0,0,1,.19.08l2.22,2.22A.26.26,0,0,1,16.5,14.72Z"/></svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
BIN
extensions/import/images/sqlserver.png
Normal file
BIN
extensions/import/images/sqlserver.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 37 KiB |
91
extensions/import/package.json
Normal file
91
extensions/import/package.json
Normal file
@@ -0,0 +1,91 @@
|
||||
{
|
||||
"name": "import",
|
||||
"displayName": "SQL Server Import",
|
||||
"description": "Imports data from a flat file.",
|
||||
"version": "0.0.1",
|
||||
"publisher": "Microsoft",
|
||||
"preview": true,
|
||||
"engines": {
|
||||
"vscode": "^1.25.0",
|
||||
"sqlops": "*"
|
||||
},
|
||||
"license": "https://raw.githubusercontent.com/Microsoft/sqlopsstudio/master/LICENSE.txt",
|
||||
"icon": "images/sqlserver.png",
|
||||
"aiKey": "AIF-5574968e-856d-40d2-af67-c89a14e76412",
|
||||
"activationEvents": [
|
||||
"*"
|
||||
],
|
||||
"main": "./out/main",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/Microsoft/sqlopsstudio.git"
|
||||
},
|
||||
"extensionDependencies": [
|
||||
"Microsoft.mssql"
|
||||
],
|
||||
"contributes": {
|
||||
"commands": [
|
||||
{
|
||||
"command": "flatFileImport.start",
|
||||
"title": "Import wizard",
|
||||
"category": "Flat File Import",
|
||||
"icon": {
|
||||
"light": "./images/light_icon.svg",
|
||||
"dark": "./images/dark_icon.svg"
|
||||
}
|
||||
}
|
||||
],
|
||||
"keybindings": [
|
||||
{
|
||||
"command": "flatFileImport.start",
|
||||
"key": "ctrl+i",
|
||||
"mac": "ctrl+i"
|
||||
}
|
||||
],
|
||||
"dashboard.tabs": [
|
||||
{
|
||||
"id": "flat-file-import",
|
||||
"title": "Flat File Import",
|
||||
"description": "The flat file importer.",
|
||||
"container": {
|
||||
"flat-file-import-container": {}
|
||||
}
|
||||
}
|
||||
],
|
||||
"dashboard.containers": [
|
||||
{
|
||||
"id": "flat-file-import-container",
|
||||
"container": {
|
||||
"widgets-container": [
|
||||
{
|
||||
"name": "Tasks",
|
||||
"widget": {
|
||||
"tasks-widget": [
|
||||
"flatFileImport.start"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"menus": {
|
||||
"objectExplorer/item/context": [
|
||||
{
|
||||
"command": "flatFileImport.start",
|
||||
"when": "connectionProvider == MSSQL && nodeType && nodeType == Database",
|
||||
"group": "import"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"dataprotocol-client": "github:Microsoft/sqlops-dataprotocolclient#0.2.5",
|
||||
"opener": "^1.4.3",
|
||||
"service-downloader": "github:anthonydresser/service-downloader#0.1.4",
|
||||
"vscode-extension-telemetry": "^0.0.5",
|
||||
"vscode-nls": "^3.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
}
|
||||
}
|
||||
14
extensions/import/src/constants.ts
Normal file
14
extensions/import/src/constants.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
export const extensionConfigSectionName = 'flatFileImport';
|
||||
export const serviceName = 'Flat File Import Service';
|
||||
export const providerId = 'FlatFileImport';
|
||||
export const configLogDebugInfo = 'logDebugInfo';
|
||||
export const sqlConfigSectionName = 'sql';
|
||||
|
||||
export const serviceCrashLink = 'https://github.com/Microsoft/sqlopsstudio/issues/2090';
|
||||
|
||||
30
extensions/import/src/controllers/controllerBase.ts
Normal file
30
extensions/import/src/controllers/controllerBase.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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;
|
||||
|
||||
protected constructor(context: vscode.ExtensionContext) {
|
||||
this._context = context;
|
||||
}
|
||||
|
||||
public get extensionContext(): vscode.ExtensionContext {
|
||||
return this._context;
|
||||
}
|
||||
|
||||
abstract activate(): Promise<boolean>;
|
||||
|
||||
abstract deactivate(): void;
|
||||
|
||||
public dispose(): void {
|
||||
this.deactivate();
|
||||
}
|
||||
}
|
||||
|
||||
44
extensions/import/src/controllers/mainController.ts
Normal file
44
extensions/import/src/controllers/mainController.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 constants from '../constants';
|
||||
import * as sqlops from 'sqlops';
|
||||
import ControllerBase from './controllerBase';
|
||||
import * as vscode from 'vscode';
|
||||
import { FlatFileWizard } from '../wizard/flatFileWizard';
|
||||
import { ServiceClient } from '../services/serviceClient';
|
||||
import { ApiType, managerInstance } from '../services/serviceApiManager';
|
||||
import { FlatFileProvider } from '../services/contracts';
|
||||
|
||||
/**
|
||||
* The main controller class that initializes the extension
|
||||
*/
|
||||
export default class MainController extends ControllerBase {
|
||||
|
||||
public constructor(context: vscode.ExtensionContext) {
|
||||
super(context);
|
||||
}
|
||||
/**
|
||||
*/
|
||||
public deactivate(): void {
|
||||
}
|
||||
|
||||
public activate(): Promise<boolean> {
|
||||
const outputChannel = vscode.window.createOutputChannel(constants.serviceName);
|
||||
new ServiceClient(outputChannel).startService(this._context);
|
||||
|
||||
managerInstance.onRegisteredApi<FlatFileProvider>(ApiType.FlatFileProvider)(provider => {
|
||||
this.initializeFlatFileProvider(provider);
|
||||
});
|
||||
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
private initializeFlatFileProvider(provider: FlatFileProvider) {
|
||||
sqlops.tasks.registerTask('flatFileImport.start', (profile: sqlops.IConnectionProfile, ...args: any[]) => new FlatFileWizard(provider).start(profile, args));
|
||||
}
|
||||
}
|
||||
37
extensions/import/src/main.ts
Normal file
37
extensions/import/src/main.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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';
|
||||
|
||||
import ControllerBase from './controllers/controllerBase';
|
||||
import MainController from './controllers/mainController';
|
||||
|
||||
let controllers: ControllerBase[] = [];
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
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() {
|
||||
for (let controller of controllers) {
|
||||
controller.deactivate();
|
||||
}
|
||||
}
|
||||
16
extensions/import/src/services/config.json
Normal file
16
extensions/import/src/services/config.json
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"downloadUrl": "https://sqlopsextensions.blob.core.windows.net/extensions/import/{#fileName#}",
|
||||
"useDefaultLinuxRuntime": true,
|
||||
"version": "0.0.1",
|
||||
"downloadFileNames": {
|
||||
"Windows_64": "win-x64.zip",
|
||||
"Windows_86": "win-x86.zip",
|
||||
"OSX": "osx.tar.gz",
|
||||
"Linux_64": "linux-x64.tar.gz"
|
||||
},
|
||||
"installDirectory": "flatfileimportservice/{#platform#}/{#version#}",
|
||||
"executableFiles": [
|
||||
"MicrosoftSqlToolsFlatFileImport",
|
||||
"MicrosoftSqlToolsFlatFileImport.exe"
|
||||
]
|
||||
}
|
||||
149
extensions/import/src/services/contracts.ts
Normal file
149
extensions/import/src/services/contracts.ts
Normal file
@@ -0,0 +1,149 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { RequestType, NotificationType } from 'vscode-languageclient';
|
||||
|
||||
/**
|
||||
* @interface IMessage
|
||||
*/
|
||||
export interface IMessage {
|
||||
jsonrpc: string;
|
||||
}
|
||||
|
||||
// ------------------------------- < Telemetry Sent Event > ------------------------------------
|
||||
|
||||
/**
|
||||
* Event sent when the language service send a telemetry event
|
||||
*/
|
||||
export namespace TelemetryNotification {
|
||||
export const type = new NotificationType<TelemetryParams, void>('telemetry/sqlevent');
|
||||
}
|
||||
|
||||
/**
|
||||
* Update event parameters
|
||||
*/
|
||||
export class TelemetryParams {
|
||||
public params: {
|
||||
eventName: string;
|
||||
properties: ITelemetryEventProperties;
|
||||
measures: ITelemetryEventMeasures;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ITelemetryEventProperties {
|
||||
[key: string]: string;
|
||||
}
|
||||
|
||||
export interface ITelemetryEventMeasures {
|
||||
[key: string]: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Contract Classes
|
||||
*/
|
||||
export interface Result {
|
||||
success: boolean;
|
||||
errorMessage: string;
|
||||
}
|
||||
|
||||
export interface ColumnInfo {
|
||||
name: string;
|
||||
sqlType: string;
|
||||
isNullable: boolean;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* PROSEDiscoveryRequest
|
||||
* Send this request to create a new PROSE session with a new file and preview it
|
||||
*/
|
||||
const proseDiscoveryRequestName = 'flatfile/proseDiscovery';
|
||||
|
||||
export interface PROSEDiscoveryParams {
|
||||
filePath: string;
|
||||
tableName: string;
|
||||
schemaName?: string;
|
||||
fileType?: string;
|
||||
}
|
||||
|
||||
export interface PROSEDiscoveryResponse {
|
||||
dataPreview: string[][];
|
||||
columnInfo: ColumnInfo[];
|
||||
}
|
||||
|
||||
/**
|
||||
* InsertDataRequest
|
||||
*/
|
||||
const insertDataRequestName = 'flatfile/insertData';
|
||||
|
||||
export interface InsertDataParams {
|
||||
connectionString: string;
|
||||
batchSize: number;
|
||||
}
|
||||
|
||||
export interface InsertDataResponse {
|
||||
result: Result;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* GetColumnInfoRequest
|
||||
*/
|
||||
const getColumnInfoRequestName = 'flatfile/getColumnInfo';
|
||||
|
||||
export interface GetColumnInfoParams {
|
||||
}
|
||||
|
||||
export interface GetColumnInfoResponse {
|
||||
columnInfo: ColumnInfo[];
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* ChangeColumnSettingsRequest
|
||||
*/
|
||||
const changeColumnSettingsRequestName = 'flatfile/changeColumnSettings';
|
||||
|
||||
export interface ChangeColumnSettingsParams {
|
||||
index: number;
|
||||
newName?: string;
|
||||
newDataType?: string;
|
||||
newNullable?: boolean;
|
||||
newInPrimaryKey?: boolean;
|
||||
}
|
||||
|
||||
export interface ChangeColumnSettingsResponse {
|
||||
result: Result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests
|
||||
*/
|
||||
export namespace PROSEDiscoveryRequest {
|
||||
export const type = new RequestType<PROSEDiscoveryParams, PROSEDiscoveryResponse, void, void>(proseDiscoveryRequestName);
|
||||
}
|
||||
|
||||
export namespace InsertDataRequest {
|
||||
export const type = new RequestType<InsertDataParams, InsertDataResponse, void, void>(insertDataRequestName);
|
||||
}
|
||||
|
||||
export namespace GetColumnInfoRequest {
|
||||
export const type = new RequestType<GetColumnInfoParams, GetColumnInfoResponse, void, void>(getColumnInfoRequestName);
|
||||
}
|
||||
|
||||
export namespace ChangeColumnSettingsRequest {
|
||||
export const type = new RequestType<ChangeColumnSettingsParams, ChangeColumnSettingsResponse, void, void>(changeColumnSettingsRequestName);
|
||||
}
|
||||
|
||||
|
||||
export interface FlatFileProvider {
|
||||
providerId?: string;
|
||||
|
||||
sendPROSEDiscoveryRequest(params: PROSEDiscoveryParams): Thenable<PROSEDiscoveryResponse>;
|
||||
sendInsertDataRequest(params: InsertDataParams): Thenable<InsertDataResponse>;
|
||||
sendGetColumnInfoRequest(params: GetColumnInfoParams): Thenable<GetColumnInfoResponse>;
|
||||
sendChangeColumnSettingsRequest(params: ChangeColumnSettingsParams): Thenable<ChangeColumnSettingsResponse>;
|
||||
}
|
||||
96
extensions/import/src/services/features.ts
Normal file
96
extensions/import/src/services/features.ts
Normal file
@@ -0,0 +1,96 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { SqlOpsDataClient, SqlOpsFeature } from 'dataprotocol-client';
|
||||
import {
|
||||
ClientCapabilities,
|
||||
StaticFeature,
|
||||
RPCMessageType,
|
||||
ServerCapabilities
|
||||
} from 'vscode-languageclient';
|
||||
import * as UUID from 'vscode-languageclient/lib/utils/uuid';
|
||||
import { Disposable } from 'vscode';
|
||||
|
||||
import { Telemetry } from './telemetry';
|
||||
import * as serviceUtils from './serviceUtils';
|
||||
import * as Contracts from './contracts';
|
||||
import { managerInstance, ApiType } from './serviceApiManager';
|
||||
|
||||
export class TelemetryFeature implements StaticFeature {
|
||||
|
||||
constructor(private _client: SqlOpsDataClient) {
|
||||
}
|
||||
|
||||
fillClientCapabilities(capabilities: ClientCapabilities): void {
|
||||
serviceUtils.ensure(capabilities, 'telemetry')!.telemetry = true;
|
||||
}
|
||||
|
||||
initialize(): void {
|
||||
this._client.onNotification(Contracts.TelemetryNotification.type, e => {
|
||||
Telemetry.sendTelemetryEvent(e.params.eventName, e.params.properties, e.params.measures);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class FlatFileImportFeature extends SqlOpsFeature<undefined> {
|
||||
private static readonly messagesTypes: RPCMessageType[] = [
|
||||
Contracts.PROSEDiscoveryRequest.type
|
||||
];
|
||||
|
||||
constructor(client: SqlOpsDataClient) {
|
||||
super(client, FlatFileImportFeature.messagesTypes);
|
||||
}
|
||||
|
||||
public fillClientCapabilities(capabilities: ClientCapabilities): void {
|
||||
}
|
||||
|
||||
public initialize(capabilities: ServerCapabilities): void {
|
||||
this.register(this.messages, {
|
||||
id: UUID.generateUuid(),
|
||||
registerOptions: undefined
|
||||
});
|
||||
}
|
||||
|
||||
protected registerProvider(options: undefined): Disposable {
|
||||
const client = this._client;
|
||||
|
||||
let requestSender = (requestType, params) => {
|
||||
return client.sendRequest(requestType, params).then(
|
||||
r => {
|
||||
return r as any;
|
||||
},
|
||||
e => {
|
||||
client.logFailedRequest(requestType, e);
|
||||
return Promise.reject(e);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
let sendPROSEDiscoveryRequest = (params: Contracts.PROSEDiscoveryParams): Thenable<Contracts.PROSEDiscoveryResponse> => {
|
||||
return requestSender(Contracts.PROSEDiscoveryRequest.type, params);
|
||||
};
|
||||
|
||||
let sendInsertDataRequest = (params: Contracts.InsertDataParams): Thenable<Contracts.InsertDataResponse> => {
|
||||
return requestSender(Contracts.InsertDataRequest.type, params);
|
||||
};
|
||||
|
||||
let sendGetColumnInfoRequest = (params: Contracts.GetColumnInfoParams): Thenable<Contracts.GetColumnInfoResponse> => {
|
||||
return requestSender(Contracts.GetColumnInfoRequest.type, params);
|
||||
};
|
||||
|
||||
let sendChangeColumnSettingsRequest = (params: Contracts.ChangeColumnSettingsParams): Thenable<Contracts.ChangeColumnSettingsResponse> => {
|
||||
return requestSender(Contracts.ChangeColumnSettingsRequest.type, params);
|
||||
};
|
||||
|
||||
return managerInstance.registerApi<Contracts.FlatFileProvider>(ApiType.FlatFileProvider, {
|
||||
providerId: client.providerId,
|
||||
sendPROSEDiscoveryRequest,
|
||||
sendChangeColumnSettingsRequest,
|
||||
sendGetColumnInfoRequest,
|
||||
sendInsertDataRequest
|
||||
});
|
||||
}
|
||||
}
|
||||
64
extensions/import/src/services/serviceApiManager.ts
Normal file
64
extensions/import/src/services/serviceApiManager.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 vscode from 'vscode';
|
||||
import * as contracts from './contracts';
|
||||
import { SqlOpsDataClient } from 'dataprotocol-client/lib/main';
|
||||
|
||||
export enum ApiType {
|
||||
FlatFileProvider = 'FlatFileProvider'
|
||||
}
|
||||
|
||||
export interface IServiceApi {
|
||||
onRegisteredApi<T>(type: ApiType): vscode.Event<T>;
|
||||
registerApi<T>(type: ApiType, feature: T): vscode.Disposable;
|
||||
}
|
||||
|
||||
export interface IModelViewDefinition {
|
||||
id: string;
|
||||
modelView: sqlops.ModelView;
|
||||
}
|
||||
|
||||
export class ServiceApiManager implements IServiceApi {
|
||||
private modelViewRegistrations: { [id: string]: boolean } = {};
|
||||
private featureEventChannels: { [type: string]: vscode.EventEmitter<any> } = {};
|
||||
private _onRegisteredModelView = new vscode.EventEmitter<IModelViewDefinition>();
|
||||
|
||||
public onRegisteredApi<T>(type: ApiType): vscode.Event<T> {
|
||||
let featureEmitter = this.featureEventChannels[type];
|
||||
if (!featureEmitter) {
|
||||
featureEmitter = new vscode.EventEmitter<T>();
|
||||
this.featureEventChannels[type] = featureEmitter;
|
||||
}
|
||||
return featureEmitter.event;
|
||||
}
|
||||
|
||||
public registerApi<T>(type: ApiType, feature: T): vscode.Disposable {
|
||||
let featureEmitter = this.featureEventChannels[type];
|
||||
if (featureEmitter) {
|
||||
featureEmitter.fire(feature);
|
||||
}
|
||||
// TODO handle unregistering API on close
|
||||
return {
|
||||
dispose: () => undefined
|
||||
};
|
||||
}
|
||||
|
||||
public get onRegisteredModelView(): vscode.Event<IModelViewDefinition> {
|
||||
return this._onRegisteredModelView.event;
|
||||
}
|
||||
|
||||
public registerModelView(id: string, modelView: sqlops.ModelView): void {
|
||||
this._onRegisteredModelView.fire({
|
||||
id: id,
|
||||
modelView: modelView
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export let managerInstance = new ServiceApiManager();
|
||||
165
extensions/import/src/services/serviceClient.ts
Normal file
165
extensions/import/src/services/serviceClient.ts
Normal file
@@ -0,0 +1,165 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { SqlOpsDataClient, ClientOptions } from 'dataprotocol-client';
|
||||
import { IConfig, ServerProvider, Events } from 'service-downloader';
|
||||
import { ServerOptions, TransportKind } from 'vscode-languageclient';
|
||||
import * as vscode from 'vscode';
|
||||
import * as nls from 'vscode-nls';
|
||||
const localize = nls.loadMessageBundle();
|
||||
import * as path from 'path';
|
||||
import { EventAndListener } from 'eventemitter2';
|
||||
|
||||
import { Telemetry, LanguageClientErrorHandler } from './telemetry';
|
||||
import * as Constants from '../constants';
|
||||
import { TelemetryFeature, FlatFileImportFeature } from './features';
|
||||
import * as serviceUtils from './serviceUtils';
|
||||
|
||||
const baseConfig = require('./config.json');
|
||||
|
||||
export class ServiceClient {
|
||||
private statusView: vscode.StatusBarItem;
|
||||
|
||||
constructor(private outputChannel: vscode.OutputChannel) {
|
||||
this.statusView = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left);
|
||||
}
|
||||
|
||||
public startService(context: vscode.ExtensionContext): Promise<SqlOpsDataClient> {
|
||||
let config: IConfig = JSON.parse(JSON.stringify(baseConfig));
|
||||
config.installDirectory = path.join(context.extensionPath, config.installDirectory);
|
||||
config.proxy = vscode.workspace.getConfiguration('http').get('proxy');
|
||||
config.strictSSL = vscode.workspace.getConfiguration('http').get('proxyStrictSSL') || true;
|
||||
|
||||
const serverdownloader = new ServerProvider(config);
|
||||
serverdownloader.eventEmitter.onAny(this.generateHandleServerProviderEvent());
|
||||
|
||||
let clientOptions: ClientOptions = this.createClientOptions();
|
||||
|
||||
const installationStart = Date.now();
|
||||
let client: SqlOpsDataClient;
|
||||
return new Promise((resolve, reject) => {
|
||||
serverdownloader.getOrDownloadServer().then(e => {
|
||||
const installationComplete = Date.now();
|
||||
let serverOptions = this.generateServerOptions(e);
|
||||
client = new SqlOpsDataClient(Constants.serviceName, serverOptions, clientOptions);
|
||||
const processStart = Date.now();
|
||||
client.onReady().then(() => {
|
||||
const processEnd = Date.now();
|
||||
this.statusView.text = localize('serviceStarted', 'Service Started');
|
||||
setTimeout(() => {
|
||||
this.statusView.hide();
|
||||
}, 1500);
|
||||
Telemetry.sendTelemetryEvent('startup/LanguageClientStarted', {
|
||||
installationTime: String(installationComplete - installationStart),
|
||||
processStartupTime: String(processEnd - processStart),
|
||||
totalTime: String(processEnd - installationStart),
|
||||
beginningTimestamp: String(installationStart)
|
||||
});
|
||||
});
|
||||
this.statusView.show();
|
||||
this.statusView.text = localize('serviceStarting', 'Starting service');
|
||||
let disposable = client.start();
|
||||
context.subscriptions.push(disposable);
|
||||
resolve(client);
|
||||
}, e => {
|
||||
Telemetry.sendTelemetryEvent('ServiceInitializingFailed');
|
||||
vscode.window.showErrorMessage(localize('flatFileImport.serviceStartFailed', 'Failed to start Import service{0}', e));
|
||||
// Just resolve to avoid unhandled promise. We show the error to the user.
|
||||
resolve(undefined);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private createClientOptions(): ClientOptions {
|
||||
return {
|
||||
providerId: Constants.providerId,
|
||||
errorHandler: new LanguageClientErrorHandler(),
|
||||
synchronize: {
|
||||
configurationSection: [Constants.extensionConfigSectionName, Constants.sqlConfigSectionName]
|
||||
},
|
||||
features: [
|
||||
// we only want to add new features
|
||||
TelemetryFeature,
|
||||
FlatFileImportFeature
|
||||
],
|
||||
outputChannel: new CustomOutputChannel()
|
||||
};
|
||||
}
|
||||
|
||||
private generateServerOptions(executablePath: string): ServerOptions {
|
||||
let launchArgs = [];
|
||||
launchArgs.push('--log-dir');
|
||||
let logFileLocation = path.join(serviceUtils.getDefaultLogLocation(), 'flatfileimport');
|
||||
launchArgs.push(logFileLocation);
|
||||
let config = vscode.workspace.getConfiguration(Constants.extensionConfigSectionName);
|
||||
if (config) {
|
||||
let logDebugInfo = config[Constants.configLogDebugInfo];
|
||||
if (logDebugInfo) {
|
||||
launchArgs.push('--enable-logging');
|
||||
}
|
||||
}
|
||||
|
||||
return { command: executablePath, args: launchArgs, transport: TransportKind.stdio };
|
||||
}
|
||||
|
||||
private generateHandleServerProviderEvent(): EventAndListener {
|
||||
let dots = 0;
|
||||
return (e: string, ...args: any[]) => {
|
||||
this.outputChannel.show();
|
||||
this.statusView.show();
|
||||
switch (e) {
|
||||
case Events.INSTALL_START:
|
||||
this.outputChannel.appendLine(localize('installingServiceDetailed', 'Installing {0} service to {1}', Constants.serviceName, args[0]));
|
||||
this.statusView.text = localize('installingService', 'Installing Service');
|
||||
break;
|
||||
case Events.INSTALL_END:
|
||||
this.outputChannel.appendLine(localize('serviceInstalled', 'Installed'));
|
||||
break;
|
||||
case Events.DOWNLOAD_START:
|
||||
this.outputChannel.appendLine(localize('downloadingService', 'Downloading {0}', args[0]));
|
||||
this.outputChannel.append(`(${Math.ceil(args[1] / 1024)} KB)`);
|
||||
this.statusView.text = localize('downloadingServiceStatus', 'Downloading Service');
|
||||
break;
|
||||
case Events.DOWNLOAD_PROGRESS:
|
||||
let newDots = Math.ceil(args[0] / 5);
|
||||
if (newDots > dots) {
|
||||
this.outputChannel.append('.'.repeat(newDots - dots));
|
||||
dots = newDots;
|
||||
}
|
||||
break;
|
||||
case Events.DOWNLOAD_END:
|
||||
this.outputChannel.appendLine(localize('downloadingServiceComplete', 'Done!'));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class CustomOutputChannel implements vscode.OutputChannel {
|
||||
name: string;
|
||||
append(value: string): void {
|
||||
}
|
||||
appendLine(value: string): void {
|
||||
}
|
||||
// tslint:disable-next-line:no-empty
|
||||
clear(): void {
|
||||
}
|
||||
show(preserveFocus?: boolean): void;
|
||||
show(column?: vscode.ViewColumn, preserveFocus?: boolean): void;
|
||||
// tslint:disable-next-line:no-empty
|
||||
show(column?: any, preserveFocus?: any): void {
|
||||
}
|
||||
// tslint:disable-next-line:no-empty
|
||||
hide(): void {
|
||||
}
|
||||
// tslint:disable-next-line:no-empty
|
||||
dispose(): void {
|
||||
}
|
||||
}
|
||||
|
||||
156
extensions/import/src/services/serviceUtils.ts
Normal file
156
extensions/import/src/services/serviceUtils.ts
Normal file
@@ -0,0 +1,156 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 path from 'path';
|
||||
import * as crypto from 'crypto';
|
||||
import * as os from 'os';
|
||||
|
||||
const baseConfig = require('./config.json');
|
||||
|
||||
// The function is a duplicate of \src\paths.js. IT would be better to import path.js but it doesn't
|
||||
// work for now because the extension is running in different process.
|
||||
export function getAppDataPath(): string {
|
||||
let platform = process.platform;
|
||||
switch (platform) {
|
||||
case 'win32': return process.env['APPDATA'] || path.join(process.env['USERPROFILE'], 'AppData', 'Roaming');
|
||||
case 'darwin': return path.join(os.homedir(), 'Library', 'Application Support');
|
||||
case 'linux': return process.env['XDG_CONFIG_HOME'] || path.join(os.homedir(), '.config');
|
||||
default: throw new Error('Platform not supported');
|
||||
}
|
||||
}
|
||||
|
||||
export function getDefaultLogLocation(): string {
|
||||
return path.join(getAppDataPath(), 'sqlops');
|
||||
}
|
||||
|
||||
export function ensure(target: object, key: string): any {
|
||||
if (target[key] === void 0) {
|
||||
target[key] = {} as any;
|
||||
}
|
||||
return target[key];
|
||||
}
|
||||
|
||||
export interface IPackageInfo {
|
||||
name: string;
|
||||
version: string;
|
||||
aiKey: string;
|
||||
}
|
||||
|
||||
export function getPackageInfo(packageJson: any): IPackageInfo {
|
||||
if (packageJson) {
|
||||
return {
|
||||
name: packageJson.name,
|
||||
version: packageJson.version,
|
||||
aiKey: packageJson.aiKey
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export function generateUserId(): Promise<string> {
|
||||
return new Promise<string>(resolve => {
|
||||
try {
|
||||
let interfaces = os.networkInterfaces();
|
||||
let mac;
|
||||
for (let key of Object.keys(interfaces)) {
|
||||
let item = interfaces[key][0];
|
||||
if (!item.internal) {
|
||||
mac = item.mac;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (mac) {
|
||||
resolve(crypto.createHash('sha256').update(mac + os.homedir(), 'utf8').digest('hex'));
|
||||
} else {
|
||||
resolve(generateGuid());
|
||||
}
|
||||
} catch (err) {
|
||||
resolve(generateGuid()); // fallback
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function generateGuid(): string {
|
||||
let hexValues: string[] = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'];
|
||||
// c.f. rfc4122 (UUID version 4 = xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx)
|
||||
let oct: string = '';
|
||||
let tmp: number;
|
||||
/* tslint:disable:no-bitwise */
|
||||
for (let a: number = 0; a < 4; a++) {
|
||||
tmp = (4294967296 * Math.random()) | 0;
|
||||
oct += hexValues[tmp & 0xF] +
|
||||
hexValues[tmp >> 4 & 0xF] +
|
||||
hexValues[tmp >> 8 & 0xF] +
|
||||
hexValues[tmp >> 12 & 0xF] +
|
||||
hexValues[tmp >> 16 & 0xF] +
|
||||
hexValues[tmp >> 20 & 0xF] +
|
||||
hexValues[tmp >> 24 & 0xF] +
|
||||
hexValues[tmp >> 28 & 0xF];
|
||||
}
|
||||
|
||||
// 'Set the two most significant bits (bits 6 and 7) of the clock_seq_hi_and_reserved to zero and one, respectively'
|
||||
let clockSequenceHi: string = hexValues[8 + (Math.random() * 4) | 0];
|
||||
return oct.substr(0, 8) + '-' + oct.substr(9, 4) + '-4' + oct.substr(13, 3) + '-' + clockSequenceHi + oct.substr(16, 3) + '-' + oct.substr(19, 12);
|
||||
/* tslint:enable:no-bitwise */
|
||||
}
|
||||
|
||||
export function verifyPlatform(): Thenable<boolean> {
|
||||
if (os.platform() === 'darwin' && parseFloat(os.release()) < 16.0) {
|
||||
return Promise.resolve(false);
|
||||
} else {
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
}
|
||||
|
||||
export function getServiceInstallConfig(basePath?: string): any {
|
||||
if (!basePath) {
|
||||
basePath = __dirname;
|
||||
}
|
||||
let config = JSON.parse(JSON.stringify(baseConfig));
|
||||
config.installDirectory = path.join(basePath, config.installDirectory);
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
export function getResolvedServiceInstallationPath(runtime: Runtime, basePath?: string): string {
|
||||
let config = getServiceInstallConfig(basePath);
|
||||
let dir = config.installDirectory;
|
||||
dir = dir.replace('{#version#}', config.version);
|
||||
dir = dir.replace('{#platform#}', getRuntimeDisplayName(runtime));
|
||||
|
||||
return dir;
|
||||
}
|
||||
|
||||
export function getRuntimeDisplayName(runtime: Runtime): string {
|
||||
switch (runtime) {
|
||||
case Runtime.Windows_64:
|
||||
return 'Windows';
|
||||
case Runtime.Windows_86:
|
||||
return 'Windows';
|
||||
case Runtime.OSX:
|
||||
return 'OSX';
|
||||
case Runtime.Linux_64:
|
||||
return 'Linux';
|
||||
default:
|
||||
return 'Unknown';
|
||||
}
|
||||
}
|
||||
|
||||
export enum Runtime {
|
||||
Unknown = <any>'Unknown',
|
||||
Windows_86 = <any>'Windows_86',
|
||||
Windows_64 = <any>'Windows_64',
|
||||
OSX = <any>'OSX',
|
||||
CentOS_7 = <any>'CentOS_7',
|
||||
Debian_8 = <any>'Debian_8',
|
||||
Fedora_23 = <any>'Fedora_23',
|
||||
OpenSUSE_13_2 = <any>'OpenSUSE_13_2',
|
||||
SLES_12_2 = <any>'SLES_12_2',
|
||||
RHEL_7 = <any>'RHEL_7',
|
||||
Ubuntu_14 = <any>'Ubuntu_14',
|
||||
Ubuntu_16 = <any>'Ubuntu_16',
|
||||
Linux_64 = <any>'Linux_64',
|
||||
Linux_86 = <any>'Linux-86'
|
||||
}
|
||||
216
extensions/import/src/services/telemetry.ts
Normal file
216
extensions/import/src/services/telemetry.ts
Normal file
@@ -0,0 +1,216 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { ErrorAction, CloseAction } from 'vscode-languageclient';
|
||||
import TelemetryReporter from 'vscode-extension-telemetry';
|
||||
import { PlatformInformation } from 'service-downloader/out/platform';
|
||||
import * as opener from 'opener';
|
||||
import * as vscode from 'vscode';
|
||||
import * as nls from 'vscode-nls';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
import * as constants from '../constants';
|
||||
import * as serviceUtils from './serviceUtils';
|
||||
import { IMessage, ITelemetryEventProperties, ITelemetryEventMeasures } from './contracts';
|
||||
|
||||
|
||||
/**
|
||||
* Handle Language Service client errors
|
||||
* @class LanguageClientErrorHandler
|
||||
*/
|
||||
export class LanguageClientErrorHandler {
|
||||
|
||||
/**
|
||||
* Creates an instance of LanguageClientErrorHandler.
|
||||
* @memberOf LanguageClientErrorHandler
|
||||
*/
|
||||
constructor() {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Show an error message prompt with a link to known issues wiki page
|
||||
* @memberOf LanguageClientErrorHandler
|
||||
*/
|
||||
showOnErrorPrompt(): void {
|
||||
// TODO add telemetry
|
||||
// Telemetry.sendTelemetryEvent('SqlToolsServiceCrash');
|
||||
let crashButtonText = localize('import.serviceCrashButton', 'Give Feedback');
|
||||
vscode.window.showErrorMessage(
|
||||
localize('serviceCrashMessage', 'service component could not start'),
|
||||
crashButtonText
|
||||
).then(action => {
|
||||
if (action && action === crashButtonText) {
|
||||
opener(constants.serviceCrashLink);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for language service client error
|
||||
*
|
||||
* @param {Error} error
|
||||
* @param {Message} message
|
||||
* @param {number} count
|
||||
* @returns {ErrorAction}
|
||||
*
|
||||
* @memberOf LanguageClientErrorHandler
|
||||
*/
|
||||
error(error: Error, message: IMessage, count: number): ErrorAction {
|
||||
this.showOnErrorPrompt();
|
||||
|
||||
// we don't retry running the service since crashes leave the extension
|
||||
// in a bad, unrecovered state
|
||||
return ErrorAction.Shutdown;
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback for language service client closed
|
||||
*
|
||||
* @returns {CloseAction}
|
||||
*
|
||||
* @memberOf LanguageClientErrorHandler
|
||||
*/
|
||||
closed(): CloseAction {
|
||||
this.showOnErrorPrompt();
|
||||
|
||||
// we don't retry running the service since crashes leave the extension
|
||||
// in a bad, unrecovered state
|
||||
return CloseAction.DoNotRestart;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters error paths to only include source files. Exported to support testing
|
||||
*/
|
||||
export function FilterErrorPath(line: string): string {
|
||||
if (line) {
|
||||
let values: string[] = line.split('/out/');
|
||||
if (values.length <= 1) {
|
||||
// Didn't match expected format
|
||||
return line;
|
||||
} else {
|
||||
return values[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class Telemetry {
|
||||
private static reporter: TelemetryReporter;
|
||||
private static userId: string;
|
||||
private static platformInformation: PlatformInformation;
|
||||
private static disabled: boolean;
|
||||
|
||||
// Get the unique ID for the current user of the extension
|
||||
public static getUserId(): Promise<string> {
|
||||
return new Promise<string>(resolve => {
|
||||
// Generate the user id if it has not been created already
|
||||
if (typeof this.userId === 'undefined') {
|
||||
let id = serviceUtils.generateUserId();
|
||||
id.then(newId => {
|
||||
this.userId = newId;
|
||||
resolve(this.userId);
|
||||
});
|
||||
} else {
|
||||
resolve(this.userId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static getPlatformInformation(): Promise<PlatformInformation> {
|
||||
if (this.platformInformation) {
|
||||
return Promise.resolve(this.platformInformation);
|
||||
} else {
|
||||
return new Promise<PlatformInformation>(resolve => {
|
||||
PlatformInformation.getCurrent().then(info => {
|
||||
this.platformInformation = info;
|
||||
resolve(this.platformInformation);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable telemetry reporting
|
||||
*/
|
||||
public static disable(): void {
|
||||
this.disabled = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the telemetry reporter for use.
|
||||
*/
|
||||
public static initialize(): void {
|
||||
if (typeof this.reporter === 'undefined') {
|
||||
// Check if the user has opted out of telemetry
|
||||
if (!vscode.workspace.getConfiguration('telemetry').get<boolean>('enableTelemetry', true)) {
|
||||
this.disable();
|
||||
return;
|
||||
}
|
||||
let packageInfo = vscode.extensions.getExtension('Microsoft.import').packageJSON;
|
||||
this.reporter = new TelemetryReporter(packageInfo.name, packageInfo.version, packageInfo.aiKey);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a telemetry event for an exception
|
||||
*/
|
||||
public static sendTelemetryEventForException(
|
||||
err: any, methodName: string, extensionConfigName: string): void {
|
||||
try {
|
||||
let stackArray: string[];
|
||||
let firstLine: string = '';
|
||||
if (err !== undefined && err.stack !== undefined) {
|
||||
stackArray = err.stack.split('\n');
|
||||
if (stackArray !== undefined && stackArray.length >= 2) {
|
||||
firstLine = stackArray[1]; // The fist line is the error message and we don't want to send that telemetry event
|
||||
firstLine = FilterErrorPath(firstLine);
|
||||
}
|
||||
}
|
||||
|
||||
// Only adding the method name and the fist line of the stack trace. We don't add the error message because it might have PII
|
||||
this.sendTelemetryEvent('Exception', { methodName: methodName, errorLine: firstLine });
|
||||
// Utils.logDebug('Unhandled Exception occurred. error: ' + err + ' method: ' + methodName, extensionConfigName);
|
||||
} catch (telemetryErr) {
|
||||
// If sending telemetry event fails ignore it so it won't break the extension
|
||||
// Utils.logDebug('Failed to send telemetry event. error: ' + telemetryErr, extensionConfigName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a telemetry event using application insights
|
||||
*/
|
||||
public static sendTelemetryEvent(
|
||||
eventName: string,
|
||||
properties?: ITelemetryEventProperties,
|
||||
measures?: ITelemetryEventMeasures): void {
|
||||
|
||||
if (typeof this.disabled === 'undefined') {
|
||||
this.disabled = false;
|
||||
}
|
||||
|
||||
if (this.disabled || typeof (this.reporter) === 'undefined') {
|
||||
// Don't do anything if telemetry is disabled
|
||||
return;
|
||||
}
|
||||
|
||||
if (!properties || typeof properties === 'undefined') {
|
||||
properties = {};
|
||||
}
|
||||
|
||||
// Augment the properties structure with additional common properties before sending
|
||||
Promise.all([this.getUserId(), this.getPlatformInformation()]).then(() => {
|
||||
properties['userId'] = this.userId;
|
||||
properties['distribution'] = (this.platformInformation && this.platformInformation.distribution) ?
|
||||
`${this.platformInformation.distribution.name}, ${this.platformInformation.distribution.version}` : '';
|
||||
|
||||
this.reporter.sendTelemetryEvent(eventName, properties, measures);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Telemetry.initialize();
|
||||
9
extensions/import/src/typings/ref.d.ts
vendored
Normal file
9
extensions/import/src/typings/ref.d.ts
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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'/>
|
||||
/// <reference path='../../../../src/sql/sqlops.d.ts'/>
|
||||
/// <reference path='../../../../src/sql/sqlops.proposed.d.ts'/>
|
||||
/// <reference types='@types/node'/>
|
||||
59
extensions/import/src/wizard/api/importPage.ts
Normal file
59
extensions/import/src/wizard/api/importPage.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { ImportDataModel } from './models';
|
||||
import * as sqlops from 'sqlops';
|
||||
import { FlatFileProvider } from '../../services/contracts';
|
||||
import { FlatFileWizard } from '../flatFileWizard';
|
||||
|
||||
export abstract class ImportPage {
|
||||
|
||||
protected readonly wizardPage: sqlops.window.modelviewdialog.WizardPage;
|
||||
protected readonly instance: FlatFileWizard;
|
||||
protected readonly model: ImportDataModel;
|
||||
protected readonly view: sqlops.ModelView;
|
||||
protected readonly provider: FlatFileProvider;
|
||||
|
||||
protected constructor(instance: FlatFileWizard, wizardPage: sqlops.window.modelviewdialog.WizardPage, model: ImportDataModel, view: sqlops.ModelView, provider: FlatFileProvider) {
|
||||
this.instance = instance;
|
||||
this.wizardPage = wizardPage;
|
||||
this.model = model;
|
||||
this.view = view;
|
||||
this.provider = provider;
|
||||
}
|
||||
|
||||
/**
|
||||
* This method constructs all the elements of the page.
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
public async abstract start(): Promise<boolean>;
|
||||
|
||||
/**
|
||||
* This method is called when the user is entering the page.
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
public async abstract onPageEnter(): Promise<boolean>;
|
||||
|
||||
/**
|
||||
* This method is called when the user is leaving the page.
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
public async abstract onPageLeave(): Promise<boolean>;
|
||||
|
||||
/**
|
||||
* Sets up a navigation validator.
|
||||
* This will be called right before onPageEnter().
|
||||
*/
|
||||
public abstract setupNavigationValidator();
|
||||
|
||||
/**
|
||||
* Override this method to cleanup what you don't need cached in the page.
|
||||
* @returns {Promise<boolean>}
|
||||
*/
|
||||
public async cleanup(): Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
33
extensions/import/src/wizard/api/models.ts
Normal file
33
extensions/import/src/wizard/api/models.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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';
|
||||
|
||||
/**
|
||||
* The main data model that communicates between the pages.
|
||||
*/
|
||||
export interface ImportDataModel {
|
||||
ownerUri: string;
|
||||
proseColumns: ColumnMetadata[];
|
||||
proseDataPreview: string[][];
|
||||
server: sqlops.connection.Connection;
|
||||
serverId: string;
|
||||
database: string;
|
||||
table: string;
|
||||
schema: string;
|
||||
filePath: string;
|
||||
fileType: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Metadata of a column
|
||||
*/
|
||||
export interface ColumnMetadata {
|
||||
columnName: string;
|
||||
dataType: string;
|
||||
primaryKey: boolean;
|
||||
nullable: boolean;
|
||||
}
|
||||
144
extensions/import/src/wizard/flatFileWizard.ts
Normal file
144
extensions/import/src/wizard/flatFileWizard.ts
Normal file
@@ -0,0 +1,144 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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';
|
||||
import * as nls from 'vscode-nls';
|
||||
import * as sqlops from 'sqlops';
|
||||
import { FlatFileProvider } from '../services/contracts';
|
||||
import { ImportDataModel } from './api/models';
|
||||
import { ImportPage } from './api/importPage';
|
||||
// pages
|
||||
import { FileConfigPage } from './pages/fileConfigPage';
|
||||
import { ProsePreviewPage } from './pages/prosePreviewPage';
|
||||
import { ModifyColumnsPage } from './pages/modifyColumnsPage';
|
||||
import { SummaryPage } from './pages/summaryPage';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export class FlatFileWizard {
|
||||
private readonly provider: FlatFileProvider;
|
||||
private wizard: sqlops.window.modelviewdialog.Wizard;
|
||||
|
||||
private importAnotherFileButton: sqlops.window.modelviewdialog.Button;
|
||||
|
||||
constructor(provider: FlatFileProvider) {
|
||||
this.provider = provider;
|
||||
}
|
||||
|
||||
public async start(p: any, ...args: any[]) {
|
||||
let model = <ImportDataModel>{};
|
||||
|
||||
let profile = <sqlops.IConnectionProfile>p.connectionProfile;
|
||||
if (profile) {
|
||||
model.serverId = profile.id;
|
||||
model.database = profile.databaseName;
|
||||
}
|
||||
|
||||
let pages: Map<number, ImportPage> = new Map<number, ImportPage>();
|
||||
|
||||
|
||||
let connections = await sqlops.connection.getActiveConnections();
|
||||
if (!connections || connections.length === 0) {
|
||||
vscode.window.showErrorMessage(localize('import.needConnection', 'Please connect to a server before using this wizard.'));
|
||||
return;
|
||||
}
|
||||
|
||||
this.wizard = sqlops.window.modelviewdialog.createWizard(localize('flatFileImport.wizardName', 'Import flat file wizard'));
|
||||
let page1 = sqlops.window.modelviewdialog.createWizardPage(localize('flatFileImport.page1Name', 'Specify Input File'));
|
||||
let page2 = sqlops.window.modelviewdialog.createWizardPage(localize('flatFileImport.page2Name', 'Preview Data'));
|
||||
let page3 = sqlops.window.modelviewdialog.createWizardPage(localize('flatFileImport.page3Name', 'Modify Columns'));
|
||||
let page4 = sqlops.window.modelviewdialog.createWizardPage(localize('flatFileImport.page4Name', 'Summary'));
|
||||
|
||||
let fileConfigPage: FileConfigPage;
|
||||
|
||||
page1.registerContent(async (view) => {
|
||||
fileConfigPage = new FileConfigPage(this, page1, model, view, this.provider);
|
||||
pages.set(0, fileConfigPage);
|
||||
await fileConfigPage.start().then(() => {
|
||||
fileConfigPage.setupNavigationValidator();
|
||||
fileConfigPage.onPageEnter();
|
||||
});
|
||||
});
|
||||
|
||||
let prosePreviewPage: ProsePreviewPage;
|
||||
page2.registerContent(async (view) => {
|
||||
prosePreviewPage = new ProsePreviewPage(this, page2, model, view, this.provider);
|
||||
pages.set(1, prosePreviewPage);
|
||||
await prosePreviewPage.start();
|
||||
});
|
||||
|
||||
let modifyColumnsPage: ModifyColumnsPage;
|
||||
page3.registerContent(async (view) => {
|
||||
modifyColumnsPage = new ModifyColumnsPage(this, page3, model, view, this.provider);
|
||||
pages.set(2, modifyColumnsPage);
|
||||
await modifyColumnsPage.start();
|
||||
});
|
||||
|
||||
let summaryPage: SummaryPage;
|
||||
|
||||
page4.registerContent(async (view) => {
|
||||
summaryPage = new SummaryPage(this, page4, model, view, this.provider);
|
||||
pages.set(3, summaryPage);
|
||||
await summaryPage.start();
|
||||
});
|
||||
|
||||
|
||||
this.importAnotherFileButton = sqlops.window.modelviewdialog.createButton(localize('flatFileImport.importNewFile', 'Import new file'));
|
||||
this.importAnotherFileButton.onClick(() => {
|
||||
//TODO replace this with proper cleanup for all the pages
|
||||
this.wizard.close();
|
||||
pages.forEach((page) => page.cleanup());
|
||||
this.wizard.open();
|
||||
});
|
||||
|
||||
this.importAnotherFileButton.hidden = true;
|
||||
this.wizard.customButtons = [this.importAnotherFileButton];
|
||||
|
||||
this.wizard.onPageChanged(async (event) => {
|
||||
let idx = event.newPage;
|
||||
|
||||
let page = pages.get(idx);
|
||||
|
||||
if (page) {
|
||||
page.setupNavigationValidator();
|
||||
page.onPageEnter();
|
||||
}
|
||||
});
|
||||
|
||||
this.wizard.onPageChanged(async (event) => {
|
||||
let idx = event.lastPage;
|
||||
|
||||
let page = pages.get(idx);
|
||||
if (page) {
|
||||
page.onPageLeave();
|
||||
}
|
||||
});
|
||||
|
||||
//not needed for this wizard
|
||||
this.wizard.generateScriptButton.hidden = true;
|
||||
|
||||
this.wizard.pages = [page1, page2, page3, page4];
|
||||
|
||||
this.wizard.open();
|
||||
}
|
||||
|
||||
public setImportAnotherFileVisibility(visibility: boolean) {
|
||||
this.importAnotherFileButton.hidden = !visibility;
|
||||
}
|
||||
|
||||
public registerNavigationValidator(validator: (pageChangeInfo: sqlops.window.modelviewdialog.WizardPageChangeInfo) => boolean) {
|
||||
this.wizard.registerNavigationValidator(validator);
|
||||
}
|
||||
|
||||
public changeNextButtonLabel(label: string) {
|
||||
this.wizard.nextButton.label = label;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
410
extensions/import/src/wizard/pages/fileConfigPage.ts
Normal file
410
extensions/import/src/wizard/pages/fileConfigPage.ts
Normal file
@@ -0,0 +1,410 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 vscode from 'vscode';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { ImportDataModel } from '../api/models';
|
||||
import { ImportPage } from '../api/importPage';
|
||||
import { FlatFileProvider } from '../../services/contracts';
|
||||
import { FlatFileWizard } from '../flatFileWizard';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export class FileConfigPage extends ImportPage {
|
||||
|
||||
private serverDropdown: sqlops.DropDownComponent;
|
||||
private databaseDropdown: sqlops.DropDownComponent;
|
||||
private fileTextBox: sqlops.InputBoxComponent;
|
||||
private fileButton: sqlops.ButtonComponent;
|
||||
private tableNameTextBox: sqlops.InputBoxComponent;
|
||||
private schemaDropdown: sqlops.DropDownComponent;
|
||||
private form: sqlops.FormContainer;
|
||||
|
||||
private databaseLoader: sqlops.LoadingComponent;
|
||||
private schemaLoader: sqlops.LoadingComponent;
|
||||
|
||||
private tableNames: string[] = [];
|
||||
|
||||
public constructor(instance: FlatFileWizard, wizardPage: sqlops.window.modelviewdialog.WizardPage, model: ImportDataModel, view: sqlops.ModelView, provider: FlatFileProvider) {
|
||||
super(instance, wizardPage, model, view, provider);
|
||||
}
|
||||
|
||||
async start(): Promise<boolean> {
|
||||
let schemaComponent = await this.createSchemaDropdown();
|
||||
let tableNameComponent = await this.createTableNameBox();
|
||||
let fileBrowserComponent = await this.createFileBrowser();
|
||||
let databaseComponent = await this.createDatabaseDropdown();
|
||||
let serverComponent = await this.createServerDropdown();
|
||||
|
||||
this.form = this.view.modelBuilder.formContainer()
|
||||
.withFormItems(
|
||||
[
|
||||
serverComponent,
|
||||
databaseComponent,
|
||||
fileBrowserComponent,
|
||||
tableNameComponent,
|
||||
schemaComponent
|
||||
]).component();
|
||||
|
||||
await this.view.initializeModel(this.form);
|
||||
return true;
|
||||
}
|
||||
|
||||
async onPageEnter(): Promise<boolean> {
|
||||
let r1 = await this.populateServerDropdown();
|
||||
let r2 = await this.populateDatabaseDropdown();
|
||||
let r3 = await this.populateSchemaDropdown();
|
||||
return r1 && r2 && r3;
|
||||
}
|
||||
|
||||
async onPageLeave(): Promise<boolean> {
|
||||
delete this.model.serverId;
|
||||
return true;
|
||||
}
|
||||
|
||||
public async cleanup(): Promise<boolean> {
|
||||
delete this.model.filePath;
|
||||
delete this.model.table;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public setupNavigationValidator() {
|
||||
this.instance.registerNavigationValidator((info) => {
|
||||
if (this.schemaLoader.loading || this.databaseLoader.loading) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
private async createServerDropdown(): Promise<sqlops.FormComponent> {
|
||||
this.serverDropdown = this.view.modelBuilder.dropDown().withProperties({
|
||||
required: true
|
||||
}).component();
|
||||
|
||||
// Handle server changes
|
||||
this.serverDropdown.onValueChanged(async (params) => {
|
||||
this.model.server = (this.serverDropdown.value as ConnectionDropdownValue).connection;
|
||||
|
||||
await this.populateDatabaseDropdown();
|
||||
await this.populateSchemaDropdown();
|
||||
});
|
||||
|
||||
return {
|
||||
component: this.serverDropdown,
|
||||
title: localize('flatFileImport.serverDropdownTitle', 'Server the database is in')
|
||||
};
|
||||
}
|
||||
|
||||
private async populateServerDropdown(): Promise<boolean> {
|
||||
let cons = await sqlops.connection.getActiveConnections();
|
||||
// This user has no active connections ABORT MISSION
|
||||
if (!cons || cons.length === 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
let count = -1;
|
||||
let idx = -1;
|
||||
|
||||
|
||||
let values = cons.map(c => {
|
||||
// Handle the code to remember what the user's choice was from before
|
||||
count++;
|
||||
if (idx === -1) {
|
||||
if (this.model.server && c.connectionId === this.model.server.connectionId) {
|
||||
idx = count;
|
||||
} else if (this.model.serverId && c.connectionId === this.model.serverId) {
|
||||
idx = count;
|
||||
}
|
||||
}
|
||||
|
||||
let db = c.options.databaseDisplayName;
|
||||
let usr = c.options.user;
|
||||
let srv = c.options.server;
|
||||
|
||||
if (!db) {
|
||||
db = '<default>';
|
||||
}
|
||||
|
||||
if (!usr) {
|
||||
usr = 'default';
|
||||
}
|
||||
|
||||
let finalName = `${srv}, ${db} (${usr})`;
|
||||
return {
|
||||
connection: c,
|
||||
displayName: finalName,
|
||||
name: c.connectionId
|
||||
};
|
||||
});
|
||||
|
||||
if (idx >= 0) {
|
||||
let tmp = values[0];
|
||||
values[0] = values[idx];
|
||||
values[idx] = tmp;
|
||||
} else {
|
||||
delete this.model.server;
|
||||
delete this.model.serverId;
|
||||
delete this.model.database;
|
||||
delete this.model.schema;
|
||||
}
|
||||
|
||||
this.model.server = values[0].connection;
|
||||
|
||||
|
||||
this.serverDropdown.updateProperties({
|
||||
values: values
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
private async createDatabaseDropdown(): Promise<sqlops.FormComponent> {
|
||||
this.databaseDropdown = this.view.modelBuilder.dropDown().withProperties({
|
||||
required: true
|
||||
}).component();
|
||||
|
||||
// Handle database changes
|
||||
this.databaseDropdown.onValueChanged(async (db) => {
|
||||
this.model.database = (<sqlops.CategoryValue>this.databaseDropdown.value).name;
|
||||
//this.populateTableNames();
|
||||
this.populateSchemaDropdown();
|
||||
});
|
||||
|
||||
this.databaseLoader = this.view.modelBuilder.loadingComponent().withItem(this.databaseDropdown).component();
|
||||
|
||||
return {
|
||||
component: this.databaseLoader,
|
||||
title: localize('flatFileImport.databaseDropdownTitle', 'Database the table is created in')
|
||||
};
|
||||
}
|
||||
|
||||
private async populateDatabaseDropdown(): Promise<boolean> {
|
||||
this.databaseLoader.loading = true;
|
||||
this.databaseDropdown.updateProperties({ values: [] });
|
||||
this.schemaDropdown.updateProperties({ values: [] });
|
||||
|
||||
if (!this.model.server) {
|
||||
//TODO handle error case
|
||||
this.databaseLoader.loading = false;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
let idx = -1;
|
||||
let count = -1;
|
||||
let values = (await sqlops.connection.listDatabases(this.model.server.connectionId)).map(db => {
|
||||
count++;
|
||||
if (this.model.database && db === this.model.database) {
|
||||
idx = count;
|
||||
}
|
||||
|
||||
return {
|
||||
displayName: db,
|
||||
name: db
|
||||
};
|
||||
});
|
||||
|
||||
if (idx >= 0) {
|
||||
let tmp = values[0];
|
||||
values[0] = values[idx];
|
||||
values[idx] = tmp;
|
||||
} else {
|
||||
delete this.model.database;
|
||||
delete this.model.schema;
|
||||
}
|
||||
|
||||
this.model.database = values[0].name;
|
||||
|
||||
this.databaseDropdown.updateProperties({
|
||||
values: values
|
||||
});
|
||||
this.databaseLoader.loading = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private async createFileBrowser(): Promise<sqlops.FormComponent> {
|
||||
this.fileTextBox = this.view.modelBuilder.inputBox().withProperties({
|
||||
required: true
|
||||
}).component();
|
||||
this.fileButton = this.view.modelBuilder.button().withProperties({
|
||||
label: localize('flatFileImport.browseFiles', 'Browse'),
|
||||
}).component();
|
||||
|
||||
this.fileButton.onDidClick(async (click) => {
|
||||
let fileUris = await vscode.window.showOpenDialog(
|
||||
{
|
||||
canSelectFiles: true,
|
||||
canSelectFolders: false,
|
||||
canSelectMany: false,
|
||||
openLabel: localize('flatFileImport.openFile', 'Open'),
|
||||
filters: {
|
||||
'Files': ['csv', 'txt']
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
if (!fileUris || fileUris.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let fileUri = fileUris[0];
|
||||
this.fileTextBox.value = fileUri.fsPath;
|
||||
|
||||
// Get the name of the file.
|
||||
let nameStart = fileUri.path.lastIndexOf('/');
|
||||
let nameEnd = fileUri.path.lastIndexOf('.');
|
||||
|
||||
// Handle files without extensions
|
||||
if (nameEnd === 0) {
|
||||
nameEnd = fileUri.path.length;
|
||||
}
|
||||
this.model.fileType = 'TXT';
|
||||
let extension = fileUri.path.substring(nameEnd + 1, fileUri.path.length);
|
||||
|
||||
if (extension.toLowerCase() === 'json') {
|
||||
this.model.fileType = 'JSON';
|
||||
}
|
||||
|
||||
this.tableNameTextBox.value = fileUri.path.substring(nameStart + 1, nameEnd);
|
||||
this.model.table = this.tableNameTextBox.value;
|
||||
this.tableNameTextBox.validate();
|
||||
|
||||
// Let then model know about the file path
|
||||
this.model.filePath = fileUri.fsPath;
|
||||
});
|
||||
|
||||
return {
|
||||
component: this.fileTextBox,
|
||||
title: localize('flatFileImport.fileTextboxTitle', 'Location of the file to be imported'),
|
||||
actions: [this.fileButton]
|
||||
};
|
||||
}
|
||||
|
||||
private async createTableNameBox(): Promise<sqlops.FormComponent> {
|
||||
this.tableNameTextBox = this.view.modelBuilder.inputBox().withValidation((name) => {
|
||||
let tableName = name.value;
|
||||
|
||||
if (!tableName || tableName.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// This won't actually do anything until table names are brought back in.
|
||||
if (this.tableNames.indexOf(tableName) !== -1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}).withProperties({
|
||||
required: true,
|
||||
}).component();
|
||||
|
||||
this.tableNameTextBox.onTextChanged((tableName) => {
|
||||
this.model.table = tableName;
|
||||
});
|
||||
|
||||
return {
|
||||
component: this.tableNameTextBox,
|
||||
title: localize('flatFileImport.tableTextboxTitle', 'New table name'),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
private async createSchemaDropdown(): Promise<sqlops.FormComponent> {
|
||||
this.schemaDropdown = this.view.modelBuilder.dropDown().withProperties({
|
||||
required: true
|
||||
}).component();
|
||||
this.schemaLoader = this.view.modelBuilder.loadingComponent().withItem(this.schemaDropdown).component();
|
||||
|
||||
this.schemaDropdown.onValueChanged(() => {
|
||||
this.model.schema = (<sqlops.CategoryValue>this.schemaDropdown.value).name;
|
||||
});
|
||||
|
||||
|
||||
return {
|
||||
component: this.schemaLoader,
|
||||
title: localize('flatFileImport.schemaTextboxTitle', 'Table schema'),
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
private async populateSchemaDropdown(): Promise<boolean> {
|
||||
this.schemaLoader.loading = true;
|
||||
let connectionUri = await sqlops.connection.getUriForConnection(this.model.server.connectionId);
|
||||
let queryProvider = sqlops.dataprotocol.getProvider<sqlops.QueryProvider>(this.model.server.providerName, sqlops.DataProviderType.QueryProvider);
|
||||
|
||||
let query = `SELECT name FROM sys.schemas`;
|
||||
|
||||
let results = await queryProvider.runQueryAndReturn(connectionUri, query);
|
||||
|
||||
let idx = -1;
|
||||
let count = -1;
|
||||
|
||||
let values = results.rows.map(row => {
|
||||
let schemaName = row[0].displayValue;
|
||||
count++;
|
||||
if (this.model.schema && schemaName === this.model.schema) {
|
||||
idx = count;
|
||||
}
|
||||
let val = row[0].displayValue;
|
||||
|
||||
return {
|
||||
name: val,
|
||||
displayName: val
|
||||
};
|
||||
});
|
||||
|
||||
if (idx >= 0) {
|
||||
let tmp = values[0];
|
||||
values[0] = values[idx];
|
||||
values[idx] = tmp;
|
||||
}
|
||||
|
||||
this.model.schema = values[0].name;
|
||||
|
||||
this.schemaDropdown.updateProperties({
|
||||
values: values
|
||||
});
|
||||
|
||||
this.schemaLoader.loading = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// private async populateTableNames(): Promise<boolean> {
|
||||
// this.tableNames = [];
|
||||
// let databaseName = (<sqlops.CategoryValue>this.databaseDropdown.value).name;
|
||||
//
|
||||
// if (!databaseName || databaseName.length === 0) {
|
||||
// this.tableNames = [];
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// let connectionUri = await sqlops.connection.getUriForConnection(this.model.server.connectionId);
|
||||
// let queryProvider = sqlops.dataprotocol.getProvider<sqlops.QueryProvider>(this.model.server.providerName, sqlops.DataProviderType.QueryProvider);
|
||||
// let results: sqlops.SimpleExecuteResult;
|
||||
//
|
||||
// try {
|
||||
// //let query = sqlstring.format('USE ?; SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = \'BASE TABLE\'', [databaseName]);
|
||||
// //results = await queryProvider.runQueryAndReturn(connectionUri, query);
|
||||
// } catch (e) {
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// this.tableNames = results.rows.map(row => {
|
||||
// return row[0].displayValue;
|
||||
// });
|
||||
//
|
||||
// return true;
|
||||
// }
|
||||
}
|
||||
|
||||
|
||||
interface ConnectionDropdownValue extends sqlops.CategoryValue {
|
||||
connection: sqlops.connection.Connection;
|
||||
}
|
||||
171
extensions/import/src/wizard/pages/modifyColumnsPage.ts
Normal file
171
extensions/import/src/wizard/pages/modifyColumnsPage.ts
Normal file
@@ -0,0 +1,171 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 nls from 'vscode-nls';
|
||||
import { ColumnMetadata, ImportDataModel } from '../api/models';
|
||||
import { ImportPage } from '../api/importPage';
|
||||
import { FlatFileProvider } from '../../services/contracts';
|
||||
import { FlatFileWizard } from '../flatFileWizard';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export class ModifyColumnsPage extends ImportPage {
|
||||
private readonly categoryValues = [
|
||||
{ name: 'bigint', displayName: 'bigint' },
|
||||
{ name: 'binary(50)', displayName: 'binary(50)' },
|
||||
{ name: 'bit', displayName: 'bit' },
|
||||
{ name: 'char(10)', displayName: 'char(10)' },
|
||||
{ name: 'date', displayName: 'date' },
|
||||
{ name: 'datetime', displayName: 'datetime' },
|
||||
{ name: 'datetime2(7)', displayName: 'datetime2(7)' },
|
||||
{ name: 'datetimeoffset(7)', displayName: 'datetimeoffset(7)' },
|
||||
{ name: 'decimal(18, 10)', displayName: 'decimal(18, 10)' },
|
||||
{ name: 'float', displayName: 'float' },
|
||||
{ name: 'geography', displayName: 'geography' },
|
||||
{ name: 'geometry', displayName: 'geometry' },
|
||||
{ name: 'hierarchyid', displayName: 'hierarchyid' },
|
||||
{ name: 'int', displayName: 'int' },
|
||||
{ name: 'money', displayName: 'money' },
|
||||
{ name: 'nchar(10)', displayName: 'nchar(10)' },
|
||||
{ name: 'ntext', displayName: 'ntext' },
|
||||
{ name: 'numeric(18, 0)', displayName: 'numeric(18, 0)' },
|
||||
{ name: 'nvarchar(50)', displayName: 'nvarchar(50)' },
|
||||
{ name: 'nvarchar(MAX)', displayName: 'nvarchar(MAX)' },
|
||||
{ name: 'real', displayName: 'real' },
|
||||
{ name: 'smalldatetime', displayName: 'smalldatetime' },
|
||||
{ name: 'smallint', displayName: 'smallint' },
|
||||
{ name: 'smallmoney', displayName: 'smallmoney' },
|
||||
{ name: 'sql_variant', displayName: 'sql_variant' },
|
||||
{ name: 'text', displayName: 'text' },
|
||||
{ name: 'time(7)', displayName: 'time(7)' },
|
||||
{ name: 'timestamp', displayName: 'timestamp' },
|
||||
{ name: 'tinyint', displayName: 'tinyint' },
|
||||
{ name: 'uniqueidentifier', displayName: 'uniqueidentifier' },
|
||||
{ name: 'varbinary(50)', displayName: 'varbinary(50)' },
|
||||
{ name: 'varbinary(MAX)', displayName: 'varbinary(MAX)' },
|
||||
{ name: 'varchar(50)', displayName: 'varchar(50)' },
|
||||
{ name: 'varchar(MAX)', displayName: 'varchar(MAX)' }
|
||||
];
|
||||
private table: sqlops.DeclarativeTableComponent;
|
||||
private loading: sqlops.LoadingComponent;
|
||||
private text: sqlops.TextComponent;
|
||||
private form: sqlops.FormContainer;
|
||||
|
||||
public constructor(instance: FlatFileWizard, wizardPage: sqlops.window.modelviewdialog.WizardPage, model: ImportDataModel, view: sqlops.ModelView, provider: FlatFileProvider) {
|
||||
super(instance, wizardPage, model, view, provider);
|
||||
}
|
||||
|
||||
|
||||
private static convertMetadata(column: ColumnMetadata): any[] {
|
||||
return [column.columnName, column.dataType, false, column.nullable];
|
||||
}
|
||||
|
||||
async start(): Promise<boolean> {
|
||||
this.loading = this.view.modelBuilder.loadingComponent().component();
|
||||
this.table = this.view.modelBuilder.declarativeTable().component();
|
||||
this.text = this.view.modelBuilder.text().component();
|
||||
|
||||
this.table.onDataChanged((e) => {
|
||||
this.model.proseColumns = [];
|
||||
this.table.data.forEach((row) => {
|
||||
this.model.proseColumns.push({
|
||||
columnName: row[0],
|
||||
dataType: row[1],
|
||||
primaryKey: row[2],
|
||||
nullable: row[3]
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
this.form = this.view.modelBuilder.formContainer()
|
||||
.withFormItems(
|
||||
[
|
||||
{
|
||||
component: this.text,
|
||||
title: ''
|
||||
},
|
||||
{
|
||||
component: this.table,
|
||||
title: ''
|
||||
}
|
||||
], {
|
||||
horizontal: false,
|
||||
componentWidth: '100%'
|
||||
}).component();
|
||||
|
||||
this.loading.component = this.form;
|
||||
await this.view.initializeModel(this.form);
|
||||
return true;
|
||||
}
|
||||
|
||||
async onPageEnter(): Promise<boolean> {
|
||||
this.loading.loading = true;
|
||||
await this.populateTable();
|
||||
this.instance.changeNextButtonLabel(localize('flatFileImport.importData', 'Import Data'));
|
||||
this.loading.loading = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async onPageLeave(): Promise<boolean> {
|
||||
this.instance.changeNextButtonLabel(localize('flatFileImport.next', 'Next'));
|
||||
return undefined;
|
||||
}
|
||||
|
||||
async cleanup(): Promise<boolean> {
|
||||
delete this.model.proseColumns;
|
||||
this.instance.changeNextButtonLabel(localize('flatFileImport.next', 'Next'));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public setupNavigationValidator() {
|
||||
this.instance.registerNavigationValidator((info) => {
|
||||
return !this.loading.loading;
|
||||
});
|
||||
}
|
||||
|
||||
private async populateTable() {
|
||||
let data: any[][] = [];
|
||||
|
||||
this.model.proseColumns.forEach((column) => {
|
||||
data.push(ModifyColumnsPage.convertMetadata(column));
|
||||
});
|
||||
|
||||
this.table.updateProperties({
|
||||
height: 400,
|
||||
columns: [{
|
||||
displayName: localize('flatFileImport.columnName', 'Column Name'),
|
||||
valueType: sqlops.DeclarativeDataType.string,
|
||||
width: '150px',
|
||||
isReadOnly: false
|
||||
}, {
|
||||
displayName: localize('flatFileImport.dataType', 'Data Type'),
|
||||
valueType: sqlops.DeclarativeDataType.editableCategory,
|
||||
width: '150px',
|
||||
isReadOnly: false,
|
||||
categoryValues: this.categoryValues
|
||||
}, {
|
||||
displayName: localize('flatFileImport.primaryKey', 'Primary Key'),
|
||||
valueType: sqlops.DeclarativeDataType.boolean,
|
||||
width: '100px',
|
||||
isReadOnly: false
|
||||
}, {
|
||||
displayName: localize('flatFileImport.allowNulls', 'Allow Nulls'),
|
||||
valueType: sqlops.DeclarativeDataType.boolean,
|
||||
isReadOnly: false,
|
||||
width: '100px'
|
||||
}],
|
||||
data: data
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
123
extensions/import/src/wizard/pages/prosePreviewPage.ts
Normal file
123
extensions/import/src/wizard/pages/prosePreviewPage.ts
Normal file
@@ -0,0 +1,123 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 nls from 'vscode-nls';
|
||||
import { ImportDataModel } from '../api/models';
|
||||
import { ImportPage } from '../api/importPage';
|
||||
import { FlatFileProvider } from '../../services/contracts';
|
||||
import { FlatFileWizard } from '../flatFileWizard';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export class ProsePreviewPage extends ImportPage {
|
||||
private table: sqlops.TableComponent;
|
||||
private loading: sqlops.LoadingComponent;
|
||||
private form: sqlops.FormContainer;
|
||||
private refresh: sqlops.ButtonComponent;
|
||||
|
||||
public constructor(instance: FlatFileWizard, wizardPage: sqlops.window.modelviewdialog.WizardPage, model: ImportDataModel, view: sqlops.ModelView, provider: FlatFileProvider) {
|
||||
super(instance, wizardPage, model, view, provider);
|
||||
}
|
||||
|
||||
async start(): Promise<boolean> {
|
||||
this.table = this.view.modelBuilder.table().component();
|
||||
this.refresh = this.view.modelBuilder.button().withProperties({
|
||||
label: localize('flatFileImport.refresh', 'Refresh'),
|
||||
isFile: false
|
||||
}).component();
|
||||
|
||||
this.refresh.onDidClick(async () => {
|
||||
this.onPageEnter();
|
||||
});
|
||||
|
||||
this.loading = this.view.modelBuilder.loadingComponent().component();
|
||||
|
||||
this.form = this.view.modelBuilder.formContainer().withFormItems([
|
||||
{
|
||||
component: this.table,
|
||||
title: localize('flatFileImport.prosePreviewMessage', 'This operation analyzed the input file structure to generate the preview below for up to the first 50 rows.'),
|
||||
actions: [this.refresh]
|
||||
}
|
||||
]).component();
|
||||
|
||||
this.loading.component = this.form;
|
||||
|
||||
await this.view.initializeModel(this.loading);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async onPageEnter(): Promise<boolean> {
|
||||
this.loading.loading = true;
|
||||
await this.handleProse();
|
||||
await this.populateTable(this.model.proseDataPreview, this.model.proseColumns.map(c => c.columnName));
|
||||
this.loading.loading = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async onPageLeave(): Promise<boolean> {
|
||||
await this.emptyTable();
|
||||
return true;
|
||||
}
|
||||
|
||||
async cleanup(): Promise<boolean> {
|
||||
delete this.model.proseDataPreview;
|
||||
return true;
|
||||
}
|
||||
|
||||
public setupNavigationValidator() {
|
||||
this.instance.registerNavigationValidator((info) => {
|
||||
return !this.loading.loading;
|
||||
});
|
||||
}
|
||||
|
||||
private async handleProse() {
|
||||
await this.provider.sendPROSEDiscoveryRequest({
|
||||
filePath: this.model.filePath,
|
||||
tableName: this.model.table,
|
||||
schemaName: this.model.schema,
|
||||
fileType: this.model.fileType
|
||||
}).then((result) => {
|
||||
this.model.proseDataPreview = result.dataPreview;
|
||||
this.model.proseColumns = [];
|
||||
result.columnInfo.forEach((column) => {
|
||||
this.model.proseColumns.push({
|
||||
columnName: column.name,
|
||||
dataType: column.sqlType,
|
||||
primaryKey: false,
|
||||
nullable: column.isNullable
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private async populateTable(tableData: string[][], columnHeaders: string[]) {
|
||||
let rows;
|
||||
let rowsLength = tableData.length;
|
||||
|
||||
if (rowsLength > 50) {
|
||||
rows = tableData;
|
||||
}
|
||||
else {
|
||||
rows = tableData.slice(0, rowsLength);
|
||||
}
|
||||
|
||||
this.table.updateProperties({
|
||||
data: rows,
|
||||
columns: columnHeaders,
|
||||
height: 400,
|
||||
width: '700',
|
||||
});
|
||||
}
|
||||
|
||||
private async emptyTable() {
|
||||
this.table.updateProperties([]);
|
||||
}
|
||||
|
||||
}
|
||||
173
extensions/import/src/wizard/pages/summaryPage.ts
Normal file
173
extensions/import/src/wizard/pages/summaryPage.ts
Normal file
@@ -0,0 +1,173 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 nls from 'vscode-nls';
|
||||
|
||||
import { ImportDataModel } from '../api/models';
|
||||
import { ImportPage } from '../api/importPage';
|
||||
import { FlatFileProvider, InsertDataResponse } from '../../services/contracts';
|
||||
import { FlatFileWizard } from '../flatFileWizard';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
|
||||
export class SummaryPage extends ImportPage {
|
||||
private table: sqlops.TableComponent;
|
||||
private statusText: sqlops.TextComponent;
|
||||
private loading: sqlops.LoadingComponent;
|
||||
private form: sqlops.FormContainer;
|
||||
|
||||
public constructor(instance: FlatFileWizard, wizardPage: sqlops.window.modelviewdialog.WizardPage, model: ImportDataModel, view: sqlops.ModelView, provider: FlatFileProvider) {
|
||||
super(instance, wizardPage, model, view, provider);
|
||||
}
|
||||
|
||||
async start(): Promise<boolean> {
|
||||
this.table = this.view.modelBuilder.table().component();
|
||||
this.statusText = this.view.modelBuilder.text().component();
|
||||
this.loading = this.view.modelBuilder.loadingComponent().withItem(this.statusText).component();
|
||||
|
||||
this.form = this.view.modelBuilder.formContainer().withFormItems(
|
||||
[
|
||||
{
|
||||
component: this.table,
|
||||
title: localize('flatFileImport.importInformation', 'Import information')
|
||||
},
|
||||
{
|
||||
component: this.loading,
|
||||
title: localize('flatFileImport.importStatus', 'Import status')
|
||||
}
|
||||
]
|
||||
).component();
|
||||
|
||||
await this.view.initializeModel(this.form);
|
||||
return true;
|
||||
}
|
||||
|
||||
async onPageEnter(): Promise<boolean> {
|
||||
this.loading.loading = true;
|
||||
this.populateTable();
|
||||
await this.handleImport();
|
||||
this.loading.loading = false;
|
||||
this.instance.setImportAnotherFileVisibility(true);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async onPageLeave(): Promise<boolean> {
|
||||
this.instance.setImportAnotherFileVisibility(false);
|
||||
|
||||
return true;
|
||||
}
|
||||
public setupNavigationValidator() {
|
||||
this.instance.registerNavigationValidator((info) => {
|
||||
return !this.loading.loading;
|
||||
});
|
||||
}
|
||||
|
||||
private populateTable() {
|
||||
this.table.updateProperties({
|
||||
data: [
|
||||
[localize('flatFileImport.serverName', 'Server name'), this.model.server.providerName],
|
||||
[localize('flatFileImport.databaseName', 'Database name'), this.model.database],
|
||||
[localize('flatFileImport.tableName', 'Table name'), this.model.table],
|
||||
[localize('flatFileImport.tableSchema', 'Table schema'), this.model.schema],
|
||||
[localize('flatFileImport.fileImport', 'File to be imported'), this.model.filePath]],
|
||||
columns: ['Object type', 'Name'],
|
||||
width: 600,
|
||||
height: 200
|
||||
});
|
||||
}
|
||||
|
||||
private async handleImport(): Promise<boolean> {
|
||||
let changeColumnResults = [];
|
||||
this.model.proseColumns.forEach((val, i, arr) => {
|
||||
let columnChangeParams = {
|
||||
index: i,
|
||||
newName: val.columnName,
|
||||
newDataType: val.dataType,
|
||||
newNullable: val.nullable,
|
||||
newInPrimaryKey: val.primaryKey
|
||||
};
|
||||
changeColumnResults.push(this.provider.sendChangeColumnSettingsRequest(columnChangeParams));
|
||||
});
|
||||
|
||||
let result: InsertDataResponse;
|
||||
let err;
|
||||
try {
|
||||
result = await this.provider.sendInsertDataRequest({
|
||||
connectionString: await this.getConnectionString(),
|
||||
//TODO check what SSMS uses as batch size
|
||||
batchSize: 500
|
||||
});
|
||||
} catch (e) {
|
||||
err = e.toString();
|
||||
}
|
||||
|
||||
let updateText: string;
|
||||
if (!result || !result.result.success) {
|
||||
updateText = '✗ ';
|
||||
if (!result) {
|
||||
updateText += err;
|
||||
} else {
|
||||
updateText += result.result.errorMessage;
|
||||
}
|
||||
} else {
|
||||
// TODO: When sql statements are in, implement this.
|
||||
//let rows = await this.getCountRowsInserted();
|
||||
//if (rows < 0) {
|
||||
updateText = localize('flatFileImport.success.norows', '✔ You have successfully inserted the data into a table.');
|
||||
//} else {
|
||||
//updateText = localize('flatFileImport.success.rows', '✔ You have successfully inserted {0} rows.', rows);
|
||||
//}
|
||||
}
|
||||
this.statusText.updateProperties({
|
||||
value: updateText
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the connection string to send to the middleware
|
||||
* @returns {Promise<string>}
|
||||
*/
|
||||
private async getConnectionString(): Promise<string> {
|
||||
let options = this.model.server.options;
|
||||
let connectionString: string;
|
||||
|
||||
if (options.authenticationType === 'Integrated') {
|
||||
connectionString = `Data Source=${options.server + (options.port ? `,${options.port}` : '')};Initial Catalog=${this.model.database};Integrated Security=True`;
|
||||
} else {
|
||||
let credentials = await sqlops.connection.getCredentials(this.model.server.connectionId);
|
||||
connectionString = `Data Source=${options.server + (options.port ? `,${options.port}` : '')};Initial Catalog=${this.model.database};Integrated Security=False;User Id=${options.user};Password=${credentials.password}`;
|
||||
}
|
||||
|
||||
// TODO: Fix this, it's returning undefined string.
|
||||
//await sqlops.connection.getConnectionString(this.model.server.connectionId, true);
|
||||
return connectionString;
|
||||
}
|
||||
|
||||
// private async getCountRowsInserted(): Promise<Number> {
|
||||
// let connectionUri = await sqlops.connection.getUriForConnection(this.model.server.connectionId);
|
||||
// let queryProvider = sqlops.dataprotocol.getProvider<sqlops.QueryProvider>(this.model.server.providerName, sqlops.DataProviderType.QueryProvider);
|
||||
// try {
|
||||
// let query = sqlstring.format('USE ?; SELECT COUNT(*) FROM ?', [this.model.database, this.model.table]);
|
||||
// let results = await queryProvider.runQueryAndReturn(connectionUri, query);
|
||||
// let cell = results.rows[0][0];
|
||||
// if (!cell || cell.isNull) {
|
||||
// return -1;
|
||||
// }
|
||||
// let numericCell = Number(cell.displayValue);
|
||||
// if (isNaN(numericCell)) {
|
||||
// return -1;
|
||||
// }
|
||||
// return numericCell;
|
||||
// } catch (e) {
|
||||
// return -1;
|
||||
// }
|
||||
// }
|
||||
}
|
||||
19
extensions/import/tsconfig.json
Normal file
19
extensions/import/tsconfig.json
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"compileOnSave": true,
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"target": "es6",
|
||||
"outDir": "./out",
|
||||
"lib": [
|
||||
"es6", "es2015.promise"
|
||||
],
|
||||
"sourceMap": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"moduleResolution": "node",
|
||||
"declaration": true
|
||||
},
|
||||
"exclude": [
|
||||
"node_modules"
|
||||
]
|
||||
}
|
||||
409
extensions/import/yarn.lock
Normal file
409
extensions/import/yarn.lock
Normal file
@@ -0,0 +1,409 @@
|
||||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
agent-base@4, agent-base@^4.1.0:
|
||||
version "4.2.1"
|
||||
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.1.tgz#d89e5999f797875674c07d87f260fc41e83e8ca9"
|
||||
dependencies:
|
||||
es6-promisify "^5.0.0"
|
||||
|
||||
applicationinsights@0.15.6:
|
||||
version "0.15.6"
|
||||
resolved "https://registry.yarnpkg.com/applicationinsights/-/applicationinsights-0.15.6.tgz#201a0682c0704fe4bdd9a92d0b2cbe34d2ae5972"
|
||||
|
||||
base64-js@0.0.8:
|
||||
version "0.0.8"
|
||||
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-0.0.8.tgz#1101e9544f4a76b1bc3b26d452ca96d7a35e7978"
|
||||
|
||||
bl@^1.0.0:
|
||||
version "1.2.2"
|
||||
resolved "https://registry.yarnpkg.com/bl/-/bl-1.2.2.tgz#a160911717103c07410cef63ef51b397c025af9c"
|
||||
dependencies:
|
||||
readable-stream "^2.3.5"
|
||||
safe-buffer "^5.1.1"
|
||||
|
||||
buffer-alloc-unsafe@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz#bd7dc26ae2972d0eda253be061dba992349c19f0"
|
||||
|
||||
buffer-alloc@^1.1.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/buffer-alloc/-/buffer-alloc-1.2.0.tgz#890dd90d923a873e08e10e5fd51a57e5b7cce0ec"
|
||||
dependencies:
|
||||
buffer-alloc-unsafe "^1.1.0"
|
||||
buffer-fill "^1.0.0"
|
||||
|
||||
buffer-crc32@~0.2.3:
|
||||
version "0.2.13"
|
||||
resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
|
||||
|
||||
buffer-fill@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/buffer-fill/-/buffer-fill-1.0.0.tgz#f8f78b76789888ef39f205cd637f68e702122b2c"
|
||||
|
||||
buffer@^3.0.1:
|
||||
version "3.6.0"
|
||||
resolved "https://registry.yarnpkg.com/buffer/-/buffer-3.6.0.tgz#a72c936f77b96bf52f5f7e7b467180628551defb"
|
||||
dependencies:
|
||||
base64-js "0.0.8"
|
||||
ieee754 "^1.1.4"
|
||||
isarray "^1.0.0"
|
||||
|
||||
commander@~2.8.1:
|
||||
version "2.8.1"
|
||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.8.1.tgz#06be367febfda0c330aa1e2a072d3dc9762425d4"
|
||||
dependencies:
|
||||
graceful-readlink ">= 1.0.0"
|
||||
|
||||
core-util-is@~1.0.0:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
|
||||
|
||||
"dataprotocol-client@github:Microsoft/sqlops-dataprotocolclient#0.2.5":
|
||||
version "0.2.5"
|
||||
resolved "https://codeload.github.com/Microsoft/sqlops-dataprotocolclient/tar.gz/6aca9c6850d2b2e1e59f09d67e15922f5acec5ea"
|
||||
dependencies:
|
||||
vscode-languageclient "3.5.1"
|
||||
|
||||
debug@3.1.0, debug@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
|
||||
dependencies:
|
||||
ms "2.0.0"
|
||||
|
||||
decompress-tar@^4.0.0, decompress-tar@^4.1.0, decompress-tar@^4.1.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/decompress-tar/-/decompress-tar-4.1.1.tgz#718cbd3fcb16209716e70a26b84e7ba4592e5af1"
|
||||
dependencies:
|
||||
file-type "^5.2.0"
|
||||
is-stream "^1.1.0"
|
||||
tar-stream "^1.5.2"
|
||||
|
||||
decompress-tarbz2@^4.0.0:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/decompress-tarbz2/-/decompress-tarbz2-4.1.1.tgz#3082a5b880ea4043816349f378b56c516be1a39b"
|
||||
dependencies:
|
||||
decompress-tar "^4.1.0"
|
||||
file-type "^6.1.0"
|
||||
is-stream "^1.1.0"
|
||||
seek-bzip "^1.0.5"
|
||||
unbzip2-stream "^1.0.9"
|
||||
|
||||
decompress-targz@^4.0.0:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/decompress-targz/-/decompress-targz-4.1.1.tgz#c09bc35c4d11f3de09f2d2da53e9de23e7ce1eee"
|
||||
dependencies:
|
||||
decompress-tar "^4.1.1"
|
||||
file-type "^5.2.0"
|
||||
is-stream "^1.1.0"
|
||||
|
||||
decompress-unzip@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/decompress-unzip/-/decompress-unzip-4.0.1.tgz#deaaccdfd14aeaf85578f733ae8210f9b4848f69"
|
||||
dependencies:
|
||||
file-type "^3.8.0"
|
||||
get-stream "^2.2.0"
|
||||
pify "^2.3.0"
|
||||
yauzl "^2.4.2"
|
||||
|
||||
decompress@^4.2.0:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/decompress/-/decompress-4.2.0.tgz#7aedd85427e5a92dacfe55674a7c505e96d01f9d"
|
||||
dependencies:
|
||||
decompress-tar "^4.0.0"
|
||||
decompress-tarbz2 "^4.0.0"
|
||||
decompress-targz "^4.0.0"
|
||||
decompress-unzip "^4.0.1"
|
||||
graceful-fs "^4.1.10"
|
||||
make-dir "^1.0.0"
|
||||
pify "^2.3.0"
|
||||
strip-dirs "^2.0.0"
|
||||
|
||||
end-of-stream@^1.0.0:
|
||||
version "1.4.1"
|
||||
resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.1.tgz#ed29634d19baba463b6ce6b80a37213eab71ec43"
|
||||
dependencies:
|
||||
once "^1.4.0"
|
||||
|
||||
es6-promise@^4.0.3:
|
||||
version "4.2.4"
|
||||
resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.2.4.tgz#dc4221c2b16518760bd8c39a52d8f356fc00ed29"
|
||||
|
||||
es6-promisify@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203"
|
||||
dependencies:
|
||||
es6-promise "^4.0.3"
|
||||
|
||||
eventemitter2@^5.0.1:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-5.0.1.tgz#6197a095d5fb6b57e8942f6fd7eaad63a09c9452"
|
||||
|
||||
fd-slicer@~1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e"
|
||||
dependencies:
|
||||
pend "~1.2.0"
|
||||
|
||||
file-type@^3.8.0:
|
||||
version "3.9.0"
|
||||
resolved "https://registry.yarnpkg.com/file-type/-/file-type-3.9.0.tgz#257a078384d1db8087bc449d107d52a52672b9e9"
|
||||
|
||||
file-type@^5.2.0:
|
||||
version "5.2.0"
|
||||
resolved "https://registry.yarnpkg.com/file-type/-/file-type-5.2.0.tgz#2ddbea7c73ffe36368dfae49dc338c058c2b8ad6"
|
||||
|
||||
file-type@^6.1.0:
|
||||
version "6.2.0"
|
||||
resolved "https://registry.yarnpkg.com/file-type/-/file-type-6.2.0.tgz#e50cd75d356ffed4e306dc4f5bcf52a79903a919"
|
||||
|
||||
fs-constants@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad"
|
||||
|
||||
get-stream@^2.2.0:
|
||||
version "2.3.1"
|
||||
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-2.3.1.tgz#5f38f93f346009666ee0150a054167f91bdd95de"
|
||||
dependencies:
|
||||
object-assign "^4.0.1"
|
||||
pinkie-promise "^2.0.0"
|
||||
|
||||
graceful-fs@^4.1.10:
|
||||
version "4.1.11"
|
||||
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658"
|
||||
|
||||
"graceful-readlink@>= 1.0.0":
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725"
|
||||
|
||||
http-proxy-agent@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz#e4821beef5b2142a2026bd73926fe537631c5405"
|
||||
dependencies:
|
||||
agent-base "4"
|
||||
debug "3.1.0"
|
||||
|
||||
https-proxy-agent@^2.1.1:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz#51552970fa04d723e04c56d04178c3f92592bbc0"
|
||||
dependencies:
|
||||
agent-base "^4.1.0"
|
||||
debug "^3.1.0"
|
||||
|
||||
ieee754@^1.1.4:
|
||||
version "1.1.12"
|
||||
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.12.tgz#50bf24e5b9c8bb98af4964c941cdb0918da7b60b"
|
||||
|
||||
inherits@~2.0.3:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
|
||||
|
||||
is-natural-number@^4.0.1:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/is-natural-number/-/is-natural-number-4.0.1.tgz#ab9d76e1db4ced51e35de0c72ebecf09f734cde8"
|
||||
|
||||
is-stream@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
|
||||
|
||||
isarray@^1.0.0, isarray@~1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
|
||||
|
||||
make-dir@^1.0.0:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c"
|
||||
dependencies:
|
||||
pify "^3.0.0"
|
||||
|
||||
minimist@0.0.8:
|
||||
version "0.0.8"
|
||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
|
||||
|
||||
mkdirp@^0.5.1:
|
||||
version "0.5.1"
|
||||
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
|
||||
dependencies:
|
||||
minimist "0.0.8"
|
||||
|
||||
ms@2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
|
||||
|
||||
object-assign@^4.0.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
|
||||
|
||||
once@^1.4.0:
|
||||
version "1.4.0"
|
||||
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
|
||||
dependencies:
|
||||
wrappy "1"
|
||||
|
||||
opener@^1.4.3:
|
||||
version "1.4.3"
|
||||
resolved "https://registry.yarnpkg.com/opener/-/opener-1.4.3.tgz#5c6da2c5d7e5831e8ffa3964950f8d6674ac90b8"
|
||||
|
||||
os-tmpdir@~1.0.2:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
|
||||
|
||||
pend@~1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
|
||||
|
||||
pify@^2.3.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
|
||||
|
||||
pify@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176"
|
||||
|
||||
pinkie-promise@^2.0.0:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa"
|
||||
dependencies:
|
||||
pinkie "^2.0.0"
|
||||
|
||||
pinkie@^2.0.0:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870"
|
||||
|
||||
process-nextick-args@~2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa"
|
||||
|
||||
readable-stream@^2.3.0, readable-stream@^2.3.5:
|
||||
version "2.3.6"
|
||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf"
|
||||
dependencies:
|
||||
core-util-is "~1.0.0"
|
||||
inherits "~2.0.3"
|
||||
isarray "~1.0.0"
|
||||
process-nextick-args "~2.0.0"
|
||||
safe-buffer "~5.1.1"
|
||||
string_decoder "~1.1.1"
|
||||
util-deprecate "~1.0.1"
|
||||
|
||||
safe-buffer@^5.1.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
|
||||
version "5.1.2"
|
||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
|
||||
|
||||
seek-bzip@^1.0.5:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/seek-bzip/-/seek-bzip-1.0.5.tgz#cfe917cb3d274bcffac792758af53173eb1fabdc"
|
||||
dependencies:
|
||||
commander "~2.8.1"
|
||||
|
||||
"service-downloader@github:anthonydresser/service-downloader#0.1.4":
|
||||
version "0.1.4"
|
||||
resolved "https://codeload.github.com/anthonydresser/service-downloader/tar.gz/3c0abdf8603aca85d2eacfac3c547173e41bf0c7"
|
||||
dependencies:
|
||||
decompress "^4.2.0"
|
||||
eventemitter2 "^5.0.1"
|
||||
http-proxy-agent "^2.0.0"
|
||||
https-proxy-agent "^2.1.1"
|
||||
mkdirp "^0.5.1"
|
||||
tmp "^0.0.33"
|
||||
|
||||
string_decoder@~1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
|
||||
dependencies:
|
||||
safe-buffer "~5.1.0"
|
||||
|
||||
strip-dirs@^2.0.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/strip-dirs/-/strip-dirs-2.1.0.tgz#4987736264fc344cf20f6c34aca9d13d1d4ed6c5"
|
||||
dependencies:
|
||||
is-natural-number "^4.0.1"
|
||||
|
||||
tar-stream@^1.5.2:
|
||||
version "1.6.1"
|
||||
resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.6.1.tgz#f84ef1696269d6223ca48f6e1eeede3f7e81f395"
|
||||
dependencies:
|
||||
bl "^1.0.0"
|
||||
buffer-alloc "^1.1.0"
|
||||
end-of-stream "^1.0.0"
|
||||
fs-constants "^1.0.0"
|
||||
readable-stream "^2.3.0"
|
||||
to-buffer "^1.1.0"
|
||||
xtend "^4.0.0"
|
||||
|
||||
through@^2.3.6:
|
||||
version "2.3.8"
|
||||
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
|
||||
|
||||
tmp@^0.0.33:
|
||||
version "0.0.33"
|
||||
resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9"
|
||||
dependencies:
|
||||
os-tmpdir "~1.0.2"
|
||||
|
||||
to-buffer@^1.1.0:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/to-buffer/-/to-buffer-1.1.1.tgz#493bd48f62d7c43fcded313a03dcadb2e1213a80"
|
||||
|
||||
unbzip2-stream@^1.0.9:
|
||||
version "1.2.5"
|
||||
resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.2.5.tgz#73a033a567bbbde59654b193c44d48a7e4f43c47"
|
||||
dependencies:
|
||||
buffer "^3.0.1"
|
||||
through "^2.3.6"
|
||||
|
||||
util-deprecate@~1.0.1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
|
||||
|
||||
vscode-extension-telemetry@^0.0.5:
|
||||
version "0.0.5"
|
||||
resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.0.5.tgz#21e2abb4cbce3326e469ddbb322123b3702f3f85"
|
||||
dependencies:
|
||||
applicationinsights "0.15.6"
|
||||
winreg "0.0.13"
|
||||
|
||||
vscode-jsonrpc@3.5.0:
|
||||
version "3.5.0"
|
||||
resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-3.5.0.tgz#87239d9e166b2d7352245b8a813597804c1d63aa"
|
||||
|
||||
vscode-languageclient@3.5.1:
|
||||
version "3.5.1"
|
||||
resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-3.5.1.tgz#c78e582459c24e58f88020dfa34065e976186a98"
|
||||
dependencies:
|
||||
vscode-languageserver-protocol "3.5.1"
|
||||
|
||||
vscode-languageserver-protocol@3.5.1:
|
||||
version "3.5.1"
|
||||
resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.5.1.tgz#5144a3a9eeccbd83fe2745bd4ed75fad6cc45f0d"
|
||||
dependencies:
|
||||
vscode-jsonrpc "3.5.0"
|
||||
vscode-languageserver-types "3.5.0"
|
||||
|
||||
vscode-languageserver-types@3.5.0:
|
||||
version "3.5.0"
|
||||
resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.5.0.tgz#e48d79962f0b8e02de955e3f524908e2b19c0374"
|
||||
|
||||
vscode-nls@^3.2.1:
|
||||
version "3.2.4"
|
||||
resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-3.2.4.tgz#2166b4183c8aea884d20727f5449e62be69fd398"
|
||||
|
||||
winreg@0.0.13:
|
||||
version "0.0.13"
|
||||
resolved "https://registry.yarnpkg.com/winreg/-/winreg-0.0.13.tgz#76bfe02e1dd0c9c8275fb9fdf17a9f36846e3483"
|
||||
|
||||
wrappy@1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
|
||||
|
||||
xtend@^4.0.0:
|
||||
version "4.0.1"
|
||||
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"
|
||||
|
||||
yauzl@^2.4.2:
|
||||
version "2.10.0"
|
||||
resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9"
|
||||
dependencies:
|
||||
buffer-crc32 "~0.2.3"
|
||||
fd-slicer "~1.1.0"
|
||||
@@ -2,4 +2,4 @@
|
||||
|
||||
See [documentation](https://code.visualstudio.com/docs/editor/versioncontrol#_merge-conflicts).
|
||||
|
||||
**Notice** This is a an extension that is bundled with Visual Studio Code.
|
||||
**Notice** This is a an extension that is bundled with SQL Operations Studio.
|
||||
|
||||
@@ -658,17 +658,11 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"dataprotocol-client": "github:Microsoft/sqlops-dataprotocolclient#0.1.9",
|
||||
"dataprotocol-client": "github:Microsoft/sqlops-dataprotocolclient#0.2.5",
|
||||
"opener": "^1.4.3",
|
||||
"service-downloader": "github:anthonydresser/service-downloader#0.1.2",
|
||||
"service-downloader": "github:anthonydresser/service-downloader#0.1.4",
|
||||
"vscode-extension-telemetry": "^0.0.15"
|
||||
},
|
||||
"devDependencies": {
|
||||
},
|
||||
"resolutions": {
|
||||
"vscode-jsonrpc": "3.5.0",
|
||||
"vscode-languageclient": "3.5.0",
|
||||
"vscode-languageserver-protocol": "3.5.0",
|
||||
"vscode-languageserver-types": "3.5.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,9 +8,9 @@
|
||||
"GO",
|
||||
"-- Create the new database if it does not exist already",
|
||||
"IF NOT EXISTS (",
|
||||
"\tSELECT name",
|
||||
"\tSELECT [name]",
|
||||
"\t\tFROM sys.databases",
|
||||
"\t\tWHERE name = N'${1:DatabaseName}'",
|
||||
"\t\tWHERE [name] = N'${1:DatabaseName}'",
|
||||
")",
|
||||
"CREATE DATABASE ${1:DatabaseName}",
|
||||
"GO"
|
||||
@@ -29,9 +29,9 @@
|
||||
"-- ALTER DATABASE ${1:DatabaseName} SET SINGLE_USER WITH ROLLBACK IMMEDIATE;",
|
||||
"-- Drop the database if it exists",
|
||||
"IF EXISTS (",
|
||||
" SELECT name",
|
||||
" SELECT [name]",
|
||||
" FROM sys.databases",
|
||||
" WHERE name = N'${1:DatabaseName}'",
|
||||
" WHERE [name] = N'${1:DatabaseName}'",
|
||||
")",
|
||||
"DROP DATABASE ${1:DatabaseName}",
|
||||
"GO"
|
||||
@@ -42,38 +42,33 @@
|
||||
"Create a new Table": {
|
||||
"prefix": "sqlCreateTable",
|
||||
"body": [
|
||||
"-- Create a new table called '${1:TableName}' in schema '${2:SchemaName}'",
|
||||
"-- Create a new table called '[${1:TableName}]' in schema '[${2:SchemaName}]' in database '[${3:DatabaseName}]'",
|
||||
"-- Drop the table if it already exists",
|
||||
"IF OBJECT_ID('${2:SchemaName}.${1:TableName}', 'U') IS NOT NULL",
|
||||
"DROP TABLE ${2:SchemaName}.${1:TableName}",
|
||||
"IF OBJECT_ID('[${3:DatabaseName}].[${2:SchemaName}].[${1:TableName}]', 'U') IS NOT NULL",
|
||||
"DROP TABLE [${3:DatabaseName}].[${2:SchemaName}].[${1:TableName}]",
|
||||
"GO",
|
||||
"-- Create the table in the specified schema",
|
||||
"CREATE TABLE ${2:SchemaName}.${1:TableName}",
|
||||
"-- Create the table in the specified database and schema",
|
||||
"CREATE TABLE [${3:DatabaseName}].[${2:SchemaName}].[${1:TableName}]",
|
||||
"(",
|
||||
"\t${1:TableName}Id INT NOT NULL PRIMARY KEY, -- primary key column",
|
||||
"\t$3Column1 [NVARCHAR](50) NOT NULL,",
|
||||
"\t$4Column2 [NVARCHAR](50) NOT NULL",
|
||||
"\t-- specify more columns here",
|
||||
"\t[${4:ColumnName}]Id INT NOT NULL PRIMARY KEY, -- Primary Key column",
|
||||
"\t[${5:ColumnName1}] [NVARCHAR](50) NOT NULL,",
|
||||
"\t[${6:ColumnName2}] [NVARCHAR](50) NOT NULL",
|
||||
"\t-- Specify more columns here",
|
||||
");",
|
||||
"GO"
|
||||
],
|
||||
"description": "Create a new Table"
|
||||
},
|
||||
|
||||
|
||||
"Drop a Table": {
|
||||
"prefix": "sqlDropTable",
|
||||
"body": [
|
||||
"-- Drop the table '${1:TableName}' in schema '${2:SchemaName}'",
|
||||
"IF EXISTS (",
|
||||
"\tSELECT *",
|
||||
"\t\tFROM sys.tables",
|
||||
"\t\tJOIN sys.schemas",
|
||||
"\t\t\tON sys.tables.schema_id = sys.schemas.schema_id",
|
||||
"\tWHERE sys.schemas.name = N'${2:SchemaName}'",
|
||||
"\t\tAND sys.tables.name = N'${1:TableName}'",
|
||||
")",
|
||||
"\tDROP TABLE ${2:SchemaName}.${1:TableName}",
|
||||
"GO"
|
||||
"-- Drop a table called '${3:TableName}' in schema '${2:SchemaName}' in Database '${1:DatabaseName}'",
|
||||
"-- Drop the table if it already exists",
|
||||
"IF OBJECT_ID('[${1:DatabaseName}].[${2:SchemaName}].[${3:TableName}]', 'U') IS NOT NULL",
|
||||
"DROP TABLE [${1:DatabaseName}].[${2:SchemaName}].[${3:TableName}]",
|
||||
"GO"
|
||||
],
|
||||
"description": "Drop a Table"
|
||||
},
|
||||
@@ -81,9 +76,9 @@
|
||||
"Add a new column to a Table": {
|
||||
"prefix": "sqlAddColumn",
|
||||
"body": [
|
||||
"-- Add a new column '${1:NewColumnName}' to table '${2:TableName}' in schema '${3:SchemaName}'",
|
||||
"ALTER TABLE ${3:SchemaName}.${2:TableName}",
|
||||
"\tADD ${1:NewColumnName} /*new_column_name*/ int /*new_column_datatype*/ NULL /*new_column_nullability*/",
|
||||
"-- Add a new column '[${1:NewColumnName}]' to table '[${2:TableName}]' in schema '[${3:SchemaName}]' in database '[${4:DatabaseName}]'",
|
||||
"ALTER TABLE [${4:DatabaseName}].[${3:SchemaName}].[${2:TableName}]",
|
||||
"\tADD [${1:NewColumnName}] /*new_column_name*/ ${5:int} /*new_column_datatype*/ ${6:NULL} /*new_column_nullability*/",
|
||||
"GO"
|
||||
],
|
||||
"description": "Add a new column to a Table"
|
||||
@@ -92,9 +87,9 @@
|
||||
"Drop a column from a Table": {
|
||||
"prefix": "sqlDropColumn",
|
||||
"body": [
|
||||
"-- Drop '${1:ColumnName}' from table '${2:TableName}' in schema '${3:SchemaName}'",
|
||||
"ALTER TABLE ${3:SchemaName}.${2:TableName}",
|
||||
"\tDROP COLUMN ${1:ColumnName}",
|
||||
"-- Drop '[${1:ColumnName}]' from table '[${2:TableName}]' in schema '[${3:SchemaName}]' in database '[${4:DatabaseName}]'",
|
||||
"ALTER TABLE [${4:DatabaseName}].[${3:SchemaName}].[${2:TableName}]",
|
||||
"\tDROP COLUMN [${1:ColumnName}]",
|
||||
"GO"
|
||||
],
|
||||
"description": "Add a new column to a Table"
|
||||
@@ -103,9 +98,9 @@
|
||||
"Select rows from a Table or a View": {
|
||||
"prefix": "sqlSelect",
|
||||
"body": [
|
||||
"-- Select rows from a Table or View '${1:TableOrViewName}' in schema '${2:SchemaName}'",
|
||||
"SELECT * FROM ${2:SchemaName}.${1:TableOrViewName}",
|
||||
"WHERE $3\t/* add search conditions here */",
|
||||
"-- Select rows from a Table or View '[${1:TableOrViewName}]' in schema '[${2:SchemaName}]' in database '[${3:DatabaseName}]'",
|
||||
"SELECT * FROM [${3:DatabaseName}].[${2:SchemaName}].[${1:TableOrViewName}]",
|
||||
"WHERE ${4:/* add search conditions here */}",
|
||||
"GO"
|
||||
],
|
||||
"description": "Select rows from a Table or a View"
|
||||
@@ -114,19 +109,19 @@
|
||||
"Insert rows into a Table": {
|
||||
"prefix": "sqlInsertRows",
|
||||
"body": [
|
||||
"-- Insert rows into table '${1:TableName}'",
|
||||
"INSERT INTO ${1:TableName}",
|
||||
"( -- columns to insert data into",
|
||||
" $2[Column1], [Column2], [Column3]",
|
||||
"-- Insert rows into table '${1:TableName}' in schema '[${2:SchemaName}]' in database '[${3:DatabaseName}]'",
|
||||
"INSERT INTO [${3:DatabaseName}].[${2:SchemaName}].[${1:TableName}]",
|
||||
"( -- Columns to insert data into",
|
||||
" ${4:[ColumnName1], [ColumnName2], [ColumnName3]}",
|
||||
")",
|
||||
"VALUES",
|
||||
"( -- first row: values for the columns in the list above",
|
||||
" $3Column1_Value, Column2_Value, Column3_Value",
|
||||
"( -- First row: values for the columns in the list above",
|
||||
" ${5:ColumnValue1, ColumnValue2, ColumnValue3}",
|
||||
"),",
|
||||
"( -- second row: values for the columns in the list above",
|
||||
" $4Column1_Value, Column2_Value, Column3_Value",
|
||||
"( -- Second row: values for the columns in the list above",
|
||||
" ${6:ColumnValue1, ColumnValue2, ColumnValue3}",
|
||||
")",
|
||||
"-- add more rows here",
|
||||
"-- Add more rows here",
|
||||
"GO"
|
||||
],
|
||||
"description": "Insert rows into a Table"
|
||||
@@ -135,9 +130,9 @@
|
||||
"Delete rows from a Table": {
|
||||
"prefix": "sqlDeleteRows",
|
||||
"body": [
|
||||
"-- Delete rows from table '${1:TableName}'",
|
||||
"DELETE FROM ${1:TableName}",
|
||||
"WHERE $2\t/* add search conditions here */",
|
||||
"-- Delete rows from table '[${1:TableName}]' in schema '[${2:SchemaName}]' in database '[${3:DatabaseName}]'",
|
||||
"DELETE FROM [${3:DatabaseName}].[${2:SchemaName}].[${1:TableName}]",
|
||||
"WHERE ${4:/* add search conditions here */}",
|
||||
"GO"
|
||||
],
|
||||
"description": "Delete rows from a Table"
|
||||
@@ -146,13 +141,13 @@
|
||||
"Update rows in a Table": {
|
||||
"prefix": "sqlUpdateRows",
|
||||
"body": [
|
||||
"-- Update rows in table '${1:TableName}'",
|
||||
"UPDATE ${1:TableName}",
|
||||
"-- Update rows in table '[${1:TableName}]' in schema '[${2:SchemaName}]' in database '[${3:DatabaseName}]'",
|
||||
"UPDATE [${3:DatabaseName}].[${2:SchemaName}].[${1:TableName}]",
|
||||
"SET",
|
||||
"\t$2[Colum1] = Colum1_Value,",
|
||||
"\t$3[Colum2] = Colum2_Value",
|
||||
"\t-- add more columns and values here",
|
||||
"WHERE $4\t/* add search conditions here */",
|
||||
"\t[${4:ColumnName1}] = ${5:ColumnValue1},",
|
||||
"\t[${6:ColumnName2}] = ${7:ColumnValue2}",
|
||||
"\t-- Add more columns and values here",
|
||||
"WHERE ${8:/* add search conditions here */}",
|
||||
"GO"
|
||||
],
|
||||
"description": "Update rows in a Table"
|
||||
@@ -207,8 +202,8 @@
|
||||
"prefix": "sqlListTablesAndViews",
|
||||
"body": [
|
||||
"-- Get a list of tables and views in the current database",
|
||||
"SELECT table_catalog [database], table_schema [schema], table_name name, table_type type",
|
||||
"FROM information_schema.tables",
|
||||
"SELECT table_catalog [database], table_schema [schema], table_name [name], table_type [type]",
|
||||
"FROM INFORMATION_SCHEMA.TABLES",
|
||||
"GO"
|
||||
],
|
||||
"description": "List tables and vies in the current database"
|
||||
@@ -218,7 +213,7 @@
|
||||
"prefix": "sqlListDatabases",
|
||||
"body": [
|
||||
"-- Get a list of databases",
|
||||
"SELECT name FROM sys.databases",
|
||||
"SELECT [name] FROM sys.databases",
|
||||
"GO"
|
||||
],
|
||||
"description": "List databases"
|
||||
@@ -232,8 +227,8 @@
|
||||
"\tTableName = tbl.table_schema + '.' + tbl.table_name, ",
|
||||
"\tColumnName = col.column_name, ",
|
||||
"\tColumnDataType = col.data_type",
|
||||
"FROM information_schema.tables tbl",
|
||||
"INNER JOIN information_schema.columns col ",
|
||||
"FROM INFORMATION_SCHEMA.TABLES tbl",
|
||||
"INNER JOIN INFORMATION_SCHEMA.COLUMNS col ",
|
||||
"\tON col.table_name = tbl.table_name",
|
||||
"\tAND col.table_schema = tbl.table_schema",
|
||||
"",
|
||||
@@ -247,20 +242,20 @@
|
||||
"prefix": "sqlCursor",
|
||||
"body": [
|
||||
"-- Declare a cursor for a Table or a View '${1:TableOrViewName}' in schema '${2:SchemaName}'",
|
||||
"DECLARE @Column1 NVARCHAR(50), @Column2 NVARCHAR(50)",
|
||||
"DECLARE @ColumnName1 NVARCHAR(50), @ColumnName2 NVARCHAR(50)",
|
||||
"",
|
||||
"DECLARE db_cursor CURSOR FOR",
|
||||
"SELECT Column1, Column2",
|
||||
"SELECT ColumnName1, Column2",
|
||||
"FROM $2.$1",
|
||||
"",
|
||||
"OPEN db_cursor",
|
||||
"FETCH NEXT FROM db_cursor INTO @Column1, @Column2",
|
||||
"FETCH NEXT FROM db_cursor INTO @ColumnName1, @ColumnName2",
|
||||
"",
|
||||
"WHILE @@FETCH_STATUS = 0",
|
||||
"BEGIN",
|
||||
"\t-- add instructions to be executed for every row",
|
||||
"\t$3",
|
||||
"\tFETCH NEXT FROM db_cursor INTO @Column1, @Column2",
|
||||
"\tFETCH NEXT FROM db_cursor INTO @ColumnName1, @ColumnName2",
|
||||
"END",
|
||||
"",
|
||||
"CLOSE db_cursor",
|
||||
@@ -303,5 +298,34 @@
|
||||
"GO"
|
||||
],
|
||||
"description": "Get Space Used by Tables"
|
||||
}
|
||||
},
|
||||
|
||||
"Create a new Index": {
|
||||
"prefix": "sqlCreateIndex",
|
||||
"body": [
|
||||
"-- Create a nonclustered index with or without a unique constraint",
|
||||
"-- Or create a clustered index on table '[${1:TableName}]' in schema '[${2:SchemaName}]' in database '[${3:DatabaseName}]'",
|
||||
"CREATE ${5:/*UNIQUE or CLUSTERED*/} INDEX IX_${4:IndexName} ON [${3:DatabaseName}].[${2:SchemaName}].[${1:TableName}] ([${6:ColumnName1}] DESC /*Change sort order as needed*/",
|
||||
"GO"
|
||||
],
|
||||
"description": "Create a new Index"
|
||||
},
|
||||
|
||||
"Create a new Temporary Table": {
|
||||
"prefix": "sqlCreateTempTable",
|
||||
"body": [
|
||||
"-- Drop a temporary table called '#${1:TableName}'",
|
||||
"-- Drop the table if it already exists",
|
||||
"IF OBJECT_ID('tempDB..#${1:TableName}', 'U') IS NOT NULL",
|
||||
"DROP TABLE #${1:TableName}",
|
||||
"GO",
|
||||
"-- Create the temporary table from a physical table called '${4:TableName}' in schema '${3:SchemaName}' in database '${2:DatabaseName}'",
|
||||
"SELECT *",
|
||||
"INTO #${1:TableName}",
|
||||
"FROM [${2:DatabaseName}].[${3:[SchemaName}].[${4:TableName}]",
|
||||
"WHERE ${5:/* add search conditions here */}"
|
||||
],
|
||||
"description": "Create a new Temporary Table"
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"downloadUrl": "https://github.com/Microsoft/sqltoolsservice/releases/download/v{#version#}/microsoft.sqltools.servicelayer-{#fileName#}",
|
||||
"version": "1.4.0-alpha.46",
|
||||
"version": "1.5.0-alpha.27",
|
||||
"downloadFileNames": {
|
||||
"Windows_86": "win-x86-netcoreapp2.1.zip",
|
||||
"Windows_64": "win-x64-netcoreapp2.1.zip",
|
||||
@@ -16,4 +16,4 @@
|
||||
},
|
||||
"installDirectory": "../sqltoolsservice/{#platform#}/{#version#}",
|
||||
"executableFiles": ["MicrosoftSqlToolsServiceLayer.exe", "MicrosoftSqlToolsServiceLayer"]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ export default class ContextProvider {
|
||||
public onDashboardOpen(e: sqlops.DashboardDocument): void {
|
||||
let iscloud: boolean;
|
||||
let edition: number;
|
||||
if (e.profile.providerName.toLowerCase() === 'mssql' && !types.isUndefinedOrNull(e.serverInfo.engineEditionId)) {
|
||||
if (e.profile.providerName.toLowerCase() === 'mssql' && !types.isUndefinedOrNull(e.serverInfo) && !types.isUndefinedOrNull(e.serverInfo.engineEditionId)) {
|
||||
if (isCloudEditions.some(i => i === e.serverInfo.engineEditionId)) {
|
||||
iscloud = true;
|
||||
} else {
|
||||
|
||||
@@ -64,6 +64,10 @@ export interface DeleteAgentJobParams {
|
||||
job: sqlops.AgentJobInfo;
|
||||
}
|
||||
|
||||
export interface AgentJobDefaultsParams {
|
||||
ownerUri: string;
|
||||
}
|
||||
|
||||
// Job Step management parameters
|
||||
export interface CreateAgentJobStepParams {
|
||||
ownerUri: string;
|
||||
@@ -144,6 +148,32 @@ export interface DeleteAgentProxyParams {
|
||||
proxy: sqlops.AgentProxyInfo;
|
||||
}
|
||||
|
||||
// Agent Credentials parameters
|
||||
export interface GetCredentialsParams {
|
||||
ownerUri: string;
|
||||
}
|
||||
|
||||
// Job Schedule management parameters
|
||||
export interface AgentJobScheduleParams {
|
||||
ownerUri: string;
|
||||
}
|
||||
|
||||
export interface CreateAgentJobScheduleParams {
|
||||
ownerUri: string;
|
||||
schedule: sqlops.AgentJobScheduleInfo;
|
||||
}
|
||||
|
||||
export interface UpdateAgentJobScheduleParams {
|
||||
ownerUri: string;
|
||||
originalScheduleName: string;
|
||||
schedule: sqlops.AgentJobScheduleInfo;
|
||||
}
|
||||
|
||||
export interface DeleteAgentJobScheduleParams {
|
||||
ownerUri: string;
|
||||
schedule: sqlops.AgentJobScheduleInfo;
|
||||
}
|
||||
|
||||
// Agent Job management requests
|
||||
export namespace AgentJobsRequest {
|
||||
export const type = new RequestType<AgentJobsParams, sqlops.AgentJobsResult, void, void>('agent/jobs');
|
||||
@@ -169,6 +199,10 @@ export namespace DeleteAgentJobRequest {
|
||||
export const type = new RequestType<DeleteAgentJobParams, sqlops.ResultStatus, void, void>('agent/deletejob');
|
||||
}
|
||||
|
||||
export namespace AgentJobDefaultsRequest {
|
||||
export const type = new RequestType<AgentJobDefaultsParams, sqlops.AgentJobDefaultsResult, void, void>('agent/jobdefaults');
|
||||
}
|
||||
|
||||
// Job Step requests
|
||||
export namespace CreateAgentJobStepRequest {
|
||||
export const type = new RequestType<CreateAgentJobStepParams, sqlops.CreateAgentJobStepResult, void, void>('agent/createjobstep');
|
||||
@@ -233,4 +267,26 @@ export namespace DeleteAgentProxyRequest {
|
||||
export const type = new RequestType<DeleteAgentProxyParams, sqlops.ResultStatus, void, void>('agent/deleteproxy');
|
||||
}
|
||||
|
||||
// Agent Credentials request
|
||||
export namespace AgentCredentialsRequest {
|
||||
export const type = new RequestType<GetCredentialsParams, sqlops.GetCredentialsResult, void, void>('security/credentials');
|
||||
}
|
||||
|
||||
// Job Schedules requests
|
||||
export namespace AgentJobSchedulesRequest {
|
||||
export const type = new RequestType<AgentJobScheduleParams, sqlops.AgentJobSchedulesResult, void, void>('agent/schedules');
|
||||
}
|
||||
|
||||
export namespace CreateAgentJobScheduleRequest {
|
||||
export const type = new RequestType<CreateAgentJobScheduleParams, sqlops.CreateAgentJobScheduleResult, void, void>('agent/createschedule');
|
||||
}
|
||||
|
||||
export namespace UpdateAgentJobScheduleRequest {
|
||||
export const type = new RequestType<UpdateAgentJobScheduleParams, sqlops.UpdateAgentJobScheduleResult, void, void>('agent/updateschedule');
|
||||
}
|
||||
|
||||
export namespace DeleteAgentJobScheduleRequest {
|
||||
export const type = new RequestType<DeleteAgentJobScheduleParams, sqlops.ResultStatus, void, void>('agent/deleteschedule');
|
||||
}
|
||||
|
||||
// ------------------------------- < Agent Management > ------------------------------------
|
||||
|
||||
@@ -35,6 +35,8 @@ export class AgentServicesFeature extends SqlOpsFeature<undefined> {
|
||||
contracts.AgentJobActionRequest.type
|
||||
];
|
||||
|
||||
private onUpdatedHandler: () => any;
|
||||
|
||||
constructor(client: SqlOpsDataClient) {
|
||||
super(client, AgentServicesFeature.messagesTypes);
|
||||
}
|
||||
@@ -53,6 +55,18 @@ export class AgentServicesFeature extends SqlOpsFeature<undefined> {
|
||||
|
||||
protected registerProvider(options: undefined): Disposable {
|
||||
const client = this._client;
|
||||
let self = this;
|
||||
|
||||
// On updated registration
|
||||
let registerOnUpdated = (handler: () => any): void => {
|
||||
self.onUpdatedHandler = handler;
|
||||
};
|
||||
|
||||
let fireOnUpdated = (): void => {
|
||||
if (self.onUpdatedHandler) {
|
||||
self.onUpdatedHandler();
|
||||
}
|
||||
};
|
||||
|
||||
// Job management methods
|
||||
let getJobs = (ownerUri: string): Thenable<sqlops.AgentJobsResult> => {
|
||||
@@ -96,7 +110,10 @@ export class AgentServicesFeature extends SqlOpsFeature<undefined> {
|
||||
};
|
||||
let requestType = contracts.CreateAgentJobRequest.type;
|
||||
return client.sendRequest(requestType, params).then(
|
||||
r => r,
|
||||
r => {
|
||||
fireOnUpdated();
|
||||
return r;
|
||||
},
|
||||
e => {
|
||||
client.logFailedRequest(requestType, e);
|
||||
return Promise.resolve(undefined);
|
||||
@@ -112,7 +129,10 @@ export class AgentServicesFeature extends SqlOpsFeature<undefined> {
|
||||
};
|
||||
let requestType = contracts.UpdateAgentJobRequest.type;
|
||||
return client.sendRequest(requestType, params).then(
|
||||
r => r,
|
||||
r => {
|
||||
fireOnUpdated();
|
||||
return r;
|
||||
},
|
||||
e => {
|
||||
client.logFailedRequest(requestType, e);
|
||||
return Promise.resolve(undefined);
|
||||
@@ -126,6 +146,23 @@ export class AgentServicesFeature extends SqlOpsFeature<undefined> {
|
||||
job: jobInfo
|
||||
};
|
||||
let requestType = contracts.DeleteAgentJobRequest.type;
|
||||
return client.sendRequest(requestType, params).then(
|
||||
r => {
|
||||
fireOnUpdated();
|
||||
return r;
|
||||
},
|
||||
e => {
|
||||
client.logFailedRequest(requestType, e);
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
let getJobDefaults = (ownerUri: string): Thenable<sqlops.AgentJobDefaultsResult> => {
|
||||
let params: contracts.AgentJobDefaultsParams = {
|
||||
ownerUri: ownerUri
|
||||
};
|
||||
let requestType = contracts.AgentJobDefaultsRequest.type;
|
||||
return client.sendRequest(requestType, params).then(
|
||||
r => r,
|
||||
e => {
|
||||
@@ -143,7 +180,10 @@ export class AgentServicesFeature extends SqlOpsFeature<undefined> {
|
||||
};
|
||||
let requestType = contracts.CreateAgentJobStepRequest.type;
|
||||
return client.sendRequest(requestType, params).then(
|
||||
r => r,
|
||||
r => {
|
||||
fireOnUpdated();
|
||||
return r;
|
||||
},
|
||||
e => {
|
||||
client.logFailedRequest(requestType, e);
|
||||
return Promise.resolve(undefined);
|
||||
@@ -159,7 +199,10 @@ export class AgentServicesFeature extends SqlOpsFeature<undefined> {
|
||||
};
|
||||
let requestType = contracts.UpdateAgentJobStepRequest.type;
|
||||
return client.sendRequest(requestType, params).then(
|
||||
r => r,
|
||||
r => {
|
||||
fireOnUpdated();
|
||||
return r;
|
||||
},
|
||||
e => {
|
||||
client.logFailedRequest(requestType, e);
|
||||
return Promise.resolve(undefined);
|
||||
@@ -174,7 +217,10 @@ export class AgentServicesFeature extends SqlOpsFeature<undefined> {
|
||||
};
|
||||
let requestType = contracts.DeleteAgentJobStepRequest.type;
|
||||
return client.sendRequest(requestType, params).then(
|
||||
r => r,
|
||||
r => {
|
||||
fireOnUpdated();
|
||||
return r;
|
||||
},
|
||||
e => {
|
||||
client.logFailedRequest(requestType, e);
|
||||
return Promise.resolve(undefined);
|
||||
@@ -204,7 +250,10 @@ export class AgentServicesFeature extends SqlOpsFeature<undefined> {
|
||||
};
|
||||
let requestType = contracts.CreateAgentAlertRequest.type;
|
||||
return client.sendRequest(requestType, params).then(
|
||||
r => r,
|
||||
r => {
|
||||
fireOnUpdated();
|
||||
return r;
|
||||
},
|
||||
e => {
|
||||
client.logFailedRequest(requestType, e);
|
||||
return Promise.resolve(undefined);
|
||||
@@ -220,7 +269,10 @@ export class AgentServicesFeature extends SqlOpsFeature<undefined> {
|
||||
};
|
||||
let requestType = contracts.UpdateAgentAlertRequest.type;
|
||||
return client.sendRequest(requestType, params).then(
|
||||
r => r,
|
||||
r => {
|
||||
fireOnUpdated();
|
||||
return r;
|
||||
},
|
||||
e => {
|
||||
client.logFailedRequest(requestType, e);
|
||||
return Promise.resolve(undefined);
|
||||
@@ -235,7 +287,10 @@ export class AgentServicesFeature extends SqlOpsFeature<undefined> {
|
||||
};
|
||||
let requestType = contracts.DeleteAgentAlertRequest.type;
|
||||
return client.sendRequest(requestType, params).then(
|
||||
r => r,
|
||||
r => {
|
||||
fireOnUpdated();
|
||||
return r;
|
||||
},
|
||||
e => {
|
||||
client.logFailedRequest(requestType, e);
|
||||
return Promise.resolve(undefined);
|
||||
@@ -265,7 +320,10 @@ export class AgentServicesFeature extends SqlOpsFeature<undefined> {
|
||||
};
|
||||
let requestType = contracts.CreateAgentOperatorRequest.type;
|
||||
return client.sendRequest(requestType, params).then(
|
||||
r => r,
|
||||
r => {
|
||||
fireOnUpdated();
|
||||
return r;
|
||||
},
|
||||
e => {
|
||||
client.logFailedRequest(requestType, e);
|
||||
return Promise.resolve(undefined);
|
||||
@@ -281,7 +339,10 @@ export class AgentServicesFeature extends SqlOpsFeature<undefined> {
|
||||
};
|
||||
let requestType = contracts.UpdateAgentOperatorRequest.type;
|
||||
return client.sendRequest(requestType, params).then(
|
||||
r => r,
|
||||
r => {
|
||||
fireOnUpdated();
|
||||
return r;
|
||||
},
|
||||
e => {
|
||||
client.logFailedRequest(requestType, e);
|
||||
return Promise.resolve(undefined);
|
||||
@@ -296,7 +357,10 @@ export class AgentServicesFeature extends SqlOpsFeature<undefined> {
|
||||
};
|
||||
let requestType = contracts.DeleteAgentOperatorRequest.type;
|
||||
return client.sendRequest(requestType, params).then(
|
||||
r => r,
|
||||
r => {
|
||||
fireOnUpdated();
|
||||
return r;
|
||||
},
|
||||
e => {
|
||||
client.logFailedRequest(requestType, e);
|
||||
return Promise.resolve(undefined);
|
||||
@@ -326,7 +390,10 @@ export class AgentServicesFeature extends SqlOpsFeature<undefined> {
|
||||
};
|
||||
let requestType = contracts.CreateAgentProxyRequest.type;
|
||||
return client.sendRequest(requestType, params).then(
|
||||
r => r,
|
||||
r => {
|
||||
fireOnUpdated();
|
||||
return r;
|
||||
},
|
||||
e => {
|
||||
client.logFailedRequest(requestType, e);
|
||||
return Promise.resolve(undefined);
|
||||
@@ -342,7 +409,10 @@ export class AgentServicesFeature extends SqlOpsFeature<undefined> {
|
||||
};
|
||||
let requestType = contracts.UpdateAgentProxyRequest.type;
|
||||
return client.sendRequest(requestType, params).then(
|
||||
r => r,
|
||||
r => {
|
||||
fireOnUpdated();
|
||||
return r;
|
||||
},
|
||||
e => {
|
||||
client.logFailedRequest(requestType, e);
|
||||
return Promise.resolve(undefined);
|
||||
@@ -356,6 +426,24 @@ export class AgentServicesFeature extends SqlOpsFeature<undefined> {
|
||||
proxy: proxyInfo
|
||||
};
|
||||
let requestType = contracts.DeleteAgentProxyRequest.type;
|
||||
return client.sendRequest(requestType, params).then(
|
||||
r => {
|
||||
fireOnUpdated();
|
||||
return r;
|
||||
},
|
||||
e => {
|
||||
client.logFailedRequest(requestType, e);
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
// Agent Credential Method
|
||||
let getCredentials = (ownerUri: string): Thenable<sqlops.GetCredentialsResult> => {
|
||||
let params: contracts.GetCredentialsParams = {
|
||||
ownerUri: ownerUri
|
||||
};
|
||||
let requestType = contracts.AgentCredentialsRequest.type;
|
||||
return client.sendRequest(requestType, params).then(
|
||||
r => r,
|
||||
e => {
|
||||
@@ -365,6 +453,77 @@ export class AgentServicesFeature extends SqlOpsFeature<undefined> {
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
// Job Schedule management methods
|
||||
let getJobSchedules = (ownerUri: string): Thenable<sqlops.AgentJobSchedulesResult> => {
|
||||
let params: contracts.AgentJobScheduleParams = {
|
||||
ownerUri: ownerUri
|
||||
};
|
||||
let requestType = contracts.AgentJobSchedulesRequest.type;
|
||||
return client.sendRequest(requestType, params).then(
|
||||
r => r,
|
||||
e => {
|
||||
client.logFailedRequest(requestType, e);
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
let createJobSchedule = (ownerUri: string, scheduleInfo: sqlops.AgentJobScheduleInfo): Thenable<sqlops.CreateAgentJobScheduleResult> => {
|
||||
let params: contracts.CreateAgentJobScheduleParams = {
|
||||
ownerUri: ownerUri,
|
||||
schedule: scheduleInfo
|
||||
};
|
||||
let requestType = contracts.CreateAgentJobScheduleRequest.type;
|
||||
return client.sendRequest(requestType, params).then(
|
||||
r => {
|
||||
fireOnUpdated();
|
||||
return r;
|
||||
},
|
||||
e => {
|
||||
client.logFailedRequest(requestType, e);
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
let updateJobSchedule = (ownerUri: string, originalScheduleName: string, scheduleInfo: sqlops.AgentJobScheduleInfo): Thenable<sqlops.UpdateAgentJobScheduleResult> => {
|
||||
let params: contracts.UpdateAgentJobScheduleParams = {
|
||||
ownerUri: ownerUri,
|
||||
originalScheduleName: originalScheduleName,
|
||||
schedule: scheduleInfo
|
||||
};
|
||||
let requestType = contracts.UpdateAgentJobScheduleRequest.type;
|
||||
return client.sendRequest(requestType, params).then(
|
||||
r => {
|
||||
fireOnUpdated();
|
||||
return r;
|
||||
},
|
||||
e => {
|
||||
client.logFailedRequest(requestType, e);
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
let deleteJobSchedule = (ownerUri: string, scheduleInfo: sqlops.AgentJobScheduleInfo): Thenable<sqlops.ResultStatus> => {
|
||||
let params: contracts.DeleteAgentJobScheduleParams = {
|
||||
ownerUri: ownerUri,
|
||||
schedule: scheduleInfo
|
||||
};
|
||||
let requestType = contracts.DeleteAgentJobScheduleRequest.type;
|
||||
return client.sendRequest(requestType, params).then(
|
||||
r => {
|
||||
fireOnUpdated();
|
||||
return r;
|
||||
},
|
||||
e => {
|
||||
client.logFailedRequest(requestType, e);
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
return sqlops.dataprotocol.registerAgentServicesProvider({
|
||||
providerId: client.providerId,
|
||||
getJobs,
|
||||
@@ -373,6 +532,7 @@ export class AgentServicesFeature extends SqlOpsFeature<undefined> {
|
||||
createJob,
|
||||
updateJob,
|
||||
deleteJob,
|
||||
getJobDefaults,
|
||||
createJobStep,
|
||||
updateJobStep,
|
||||
deleteJobStep,
|
||||
@@ -387,7 +547,13 @@ export class AgentServicesFeature extends SqlOpsFeature<undefined> {
|
||||
getProxies,
|
||||
createProxy,
|
||||
updateProxy,
|
||||
deleteProxy
|
||||
deleteProxy,
|
||||
getCredentials,
|
||||
getJobSchedules,
|
||||
createJobSchedule,
|
||||
updateJobSchedule,
|
||||
deleteJobSchedule,
|
||||
registerOnUpdated
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,22 +44,6 @@ export class Telemetry {
|
||||
private static platformInformation: PlatformInformation;
|
||||
private static disabled: boolean;
|
||||
|
||||
// Get the unique ID for the current user of the extension
|
||||
public static getUserId(): Promise<string> {
|
||||
return new Promise<string>(resolve => {
|
||||
// Generate the user id if it has not been created already
|
||||
if (typeof this.userId === 'undefined') {
|
||||
let id = Utils.generateUserId();
|
||||
id.then(newId => {
|
||||
this.userId = newId;
|
||||
resolve(this.userId);
|
||||
});
|
||||
} else {
|
||||
resolve(this.userId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public static getPlatformInformation(): Promise<PlatformInformation> {
|
||||
if (this.platformInformation) {
|
||||
return Promise.resolve(this.platformInformation);
|
||||
@@ -143,8 +127,7 @@ export class Telemetry {
|
||||
}
|
||||
|
||||
// Augment the properties structure with additional common properties before sending
|
||||
Promise.all([this.getUserId(), this.getPlatformInformation()]).then(() => {
|
||||
properties['userId'] = this.userId;
|
||||
Promise.all([this.getPlatformInformation()]).then(() => {
|
||||
properties['distribution'] = (this.platformInformation && this.platformInformation.distribution) ?
|
||||
`${this.platformInformation.distribution.name}, ${this.platformInformation.distribution.version}` : '';
|
||||
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
|
||||
|
||||
agent-base@4, agent-base@^4.1.0:
|
||||
version "4.2.0"
|
||||
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.0.tgz#9838b5c3392b962bad031e6a4c5e1024abec45ce"
|
||||
version "4.2.1"
|
||||
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.1.tgz#d89e5999f797875674c07d87f260fc41e83e8ca9"
|
||||
dependencies:
|
||||
es6-promisify "^5.0.0"
|
||||
|
||||
@@ -27,10 +27,25 @@ bl@^1.0.0:
|
||||
readable-stream "^2.3.5"
|
||||
safe-buffer "^5.1.1"
|
||||
|
||||
buffer-alloc-unsafe@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz#bd7dc26ae2972d0eda253be061dba992349c19f0"
|
||||
|
||||
buffer-alloc@^1.1.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/buffer-alloc/-/buffer-alloc-1.2.0.tgz#890dd90d923a873e08e10e5fd51a57e5b7cce0ec"
|
||||
dependencies:
|
||||
buffer-alloc-unsafe "^1.1.0"
|
||||
buffer-fill "^1.0.0"
|
||||
|
||||
buffer-crc32@~0.2.3:
|
||||
version "0.2.13"
|
||||
resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
|
||||
|
||||
buffer-fill@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/buffer-fill/-/buffer-fill-1.0.0.tgz#f8f78b76789888ef39f205cd637f68e702122b2c"
|
||||
|
||||
buffer@^3.0.1:
|
||||
version "3.6.0"
|
||||
resolved "https://registry.yarnpkg.com/buffer/-/buffer-3.6.0.tgz#a72c936f77b96bf52f5f7e7b467180628551defb"
|
||||
@@ -49,11 +64,11 @@ core-util-is@~1.0.0:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
|
||||
|
||||
"dataprotocol-client@github:Microsoft/sqlops-dataprotocolclient#0.1.7":
|
||||
version "0.1.7"
|
||||
resolved "https://codeload.github.com/Microsoft/sqlops-dataprotocolclient/tar.gz/d50285b03d0d5073c086362c5c96afb279320607"
|
||||
"dataprotocol-client@github:Microsoft/sqlops-dataprotocolclient#0.2.5":
|
||||
version "0.2.5"
|
||||
resolved "https://codeload.github.com/Microsoft/sqlops-dataprotocolclient/tar.gz/6aca9c6850d2b2e1e59f09d67e15922f5acec5ea"
|
||||
dependencies:
|
||||
vscode-languageclient "3.5.0"
|
||||
vscode-languageclient "3.5.1"
|
||||
|
||||
debug@3.1.0, debug@^3.1.0:
|
||||
version "3.1.0"
|
||||
@@ -139,9 +154,9 @@ eventemitter2@^5.0.1:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-5.0.1.tgz#6197a095d5fb6b57e8942f6fd7eaad63a09c9452"
|
||||
|
||||
fd-slicer@~1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.0.1.tgz#8b5bcbd9ec327c5041bf9ab023fd6750f1177e65"
|
||||
fd-slicer@~1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e"
|
||||
dependencies:
|
||||
pend "~1.2.0"
|
||||
|
||||
@@ -157,6 +172,10 @@ file-type@^6.1.0:
|
||||
version "6.2.0"
|
||||
resolved "https://registry.yarnpkg.com/file-type/-/file-type-6.2.0.tgz#e50cd75d356ffed4e306dc4f5bcf52a79903a919"
|
||||
|
||||
fs-constants@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad"
|
||||
|
||||
get-stream@^2.2.0:
|
||||
version "2.3.1"
|
||||
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-2.3.1.tgz#5f38f93f346009666ee0150a054167f91bdd95de"
|
||||
@@ -180,15 +199,15 @@ http-proxy-agent@^2.0.0:
|
||||
debug "3.1.0"
|
||||
|
||||
https-proxy-agent@^2.1.1:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.0.tgz#7fbba856be8cd677986f42ebd3664f6317257887"
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz#51552970fa04d723e04c56d04178c3f92592bbc0"
|
||||
dependencies:
|
||||
agent-base "^4.1.0"
|
||||
debug "^3.1.0"
|
||||
|
||||
ieee754@^1.1.4:
|
||||
version "1.1.11"
|
||||
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.11.tgz#c16384ffe00f5b7835824e67b6f2bd44a5229455"
|
||||
version "1.1.12"
|
||||
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.12.tgz#50bf24e5b9c8bb98af4964c941cdb0918da7b60b"
|
||||
|
||||
inherits@~2.0.3:
|
||||
version "2.0.3"
|
||||
@@ -207,8 +226,8 @@ isarray@^1.0.0, isarray@~1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
|
||||
|
||||
make-dir@^1.0.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.2.0.tgz#6d6a49eead4aae296c53bbf3a1a008bd6c89469b"
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c"
|
||||
dependencies:
|
||||
pify "^3.0.0"
|
||||
|
||||
@@ -270,21 +289,21 @@ process-nextick-args@~2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa"
|
||||
|
||||
readable-stream@^2.0.0, readable-stream@^2.3.5:
|
||||
version "2.3.5"
|
||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.5.tgz#b4f85003a938cbb6ecbce2a124fb1012bd1a838d"
|
||||
readable-stream@^2.3.0, readable-stream@^2.3.5:
|
||||
version "2.3.6"
|
||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf"
|
||||
dependencies:
|
||||
core-util-is "~1.0.0"
|
||||
inherits "~2.0.3"
|
||||
isarray "~1.0.0"
|
||||
process-nextick-args "~2.0.0"
|
||||
safe-buffer "~5.1.1"
|
||||
string_decoder "~1.0.3"
|
||||
string_decoder "~1.1.1"
|
||||
util-deprecate "~1.0.1"
|
||||
|
||||
safe-buffer@^5.1.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
|
||||
version "5.1.1"
|
||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853"
|
||||
version "5.1.2"
|
||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
|
||||
|
||||
seek-bzip@^1.0.5:
|
||||
version "1.0.5"
|
||||
@@ -296,9 +315,9 @@ semver@^5.3.0:
|
||||
version "5.5.0"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.0.tgz#dc4bbc7a6ca9d916dee5d43516f0092b58f7b8ab"
|
||||
|
||||
"service-downloader@github:anthonydresser/service-downloader#0.1.2":
|
||||
version "0.1.2"
|
||||
resolved "https://codeload.github.com/anthonydresser/service-downloader/tar.gz/2aa9b336b6442e17e24693ddc907030575539798"
|
||||
"service-downloader@github:anthonydresser/service-downloader#0.1.4":
|
||||
version "0.1.4"
|
||||
resolved "https://codeload.github.com/anthonydresser/service-downloader/tar.gz/3c0abdf8603aca85d2eacfac3c547173e41bf0c7"
|
||||
dependencies:
|
||||
decompress "^4.2.0"
|
||||
eventemitter2 "^5.0.1"
|
||||
@@ -307,9 +326,9 @@ semver@^5.3.0:
|
||||
mkdirp "^0.5.1"
|
||||
tmp "^0.0.33"
|
||||
|
||||
string_decoder@~1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.3.tgz#0fc67d7c141825de94282dd536bec6b9bce860ab"
|
||||
string_decoder@~1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
|
||||
dependencies:
|
||||
safe-buffer "~5.1.0"
|
||||
|
||||
@@ -320,12 +339,15 @@ strip-dirs@^2.0.0:
|
||||
is-natural-number "^4.0.1"
|
||||
|
||||
tar-stream@^1.5.2:
|
||||
version "1.5.5"
|
||||
resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.5.5.tgz#5cad84779f45c83b1f2508d96b09d88c7218af55"
|
||||
version "1.6.1"
|
||||
resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.6.1.tgz#f84ef1696269d6223ca48f6e1eeede3f7e81f395"
|
||||
dependencies:
|
||||
bl "^1.0.0"
|
||||
buffer-alloc "^1.1.0"
|
||||
end-of-stream "^1.0.0"
|
||||
readable-stream "^2.0.0"
|
||||
fs-constants "^1.0.0"
|
||||
readable-stream "^2.3.0"
|
||||
to-buffer "^1.1.0"
|
||||
xtend "^4.0.0"
|
||||
|
||||
through@^2.3.6:
|
||||
@@ -338,6 +360,10 @@ tmp@^0.0.33:
|
||||
dependencies:
|
||||
os-tmpdir "~1.0.2"
|
||||
|
||||
to-buffer@^1.1.0:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/to-buffer/-/to-buffer-1.1.1.tgz#493bd48f62d7c43fcded313a03dcadb2e1213a80"
|
||||
|
||||
unbzip2-stream@^1.0.9:
|
||||
version "1.2.5"
|
||||
resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.2.5.tgz#73a033a567bbbde59654b193c44d48a7e4f43c47"
|
||||
@@ -355,24 +381,24 @@ vscode-extension-telemetry@^0.0.15:
|
||||
dependencies:
|
||||
applicationinsights "1.0.1"
|
||||
|
||||
vscode-jsonrpc@3.5.0, vscode-jsonrpc@^3.5.0:
|
||||
vscode-jsonrpc@3.5.0:
|
||||
version "3.5.0"
|
||||
resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-3.5.0.tgz#87239d9e166b2d7352245b8a813597804c1d63aa"
|
||||
|
||||
vscode-languageclient@3.5.0:
|
||||
version "3.5.0"
|
||||
resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-3.5.0.tgz#36d02cc186a8365a4467719a290fb200a9ae490a"
|
||||
vscode-languageclient@3.5.1:
|
||||
version "3.5.1"
|
||||
resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-3.5.1.tgz#c78e582459c24e58f88020dfa34065e976186a98"
|
||||
dependencies:
|
||||
vscode-languageserver-protocol "^3.5.0"
|
||||
vscode-languageserver-protocol "3.5.1"
|
||||
|
||||
vscode-languageserver-protocol@3.5.0, vscode-languageserver-protocol@^3.5.0:
|
||||
version "3.5.0"
|
||||
resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.5.0.tgz#067c5cbe27709795398d119692c97ebba1452209"
|
||||
vscode-languageserver-protocol@3.5.1:
|
||||
version "3.5.1"
|
||||
resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.5.1.tgz#5144a3a9eeccbd83fe2745bd4ed75fad6cc45f0d"
|
||||
dependencies:
|
||||
vscode-jsonrpc "^3.5.0"
|
||||
vscode-languageserver-types "^3.5.0"
|
||||
vscode-jsonrpc "3.5.0"
|
||||
vscode-languageserver-types "3.5.0"
|
||||
|
||||
vscode-languageserver-types@3.5.0, vscode-languageserver-types@^3.5.0:
|
||||
vscode-languageserver-types@3.5.0:
|
||||
version "3.5.0"
|
||||
resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.5.0.tgz#e48d79962f0b8e02de955e3f524908e2b19c0374"
|
||||
|
||||
@@ -385,11 +411,11 @@ xtend@^4.0.0:
|
||||
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"
|
||||
|
||||
yauzl@^2.4.2:
|
||||
version "2.9.1"
|
||||
resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.9.1.tgz#a81981ea70a57946133883f029c5821a89359a7f"
|
||||
version "2.10.0"
|
||||
resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9"
|
||||
dependencies:
|
||||
buffer-crc32 "~0.2.3"
|
||||
fd-slicer "~1.0.1"
|
||||
fd-slicer "~1.1.0"
|
||||
|
||||
zone.js@0.7.6:
|
||||
version "0.7.6"
|
||||
|
||||
26
extensions/profiler/client/src/data/createSessionData.ts
Normal file
26
extensions/profiler/client/src/data/createSessionData.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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';
|
||||
|
||||
export class CreateSessionData {
|
||||
public ownerUri: string;
|
||||
public sessionName: string;
|
||||
public templates: Array<sqlops.ProfilerSessionTemplate> = new Array<sqlops.ProfilerSessionTemplate>();
|
||||
|
||||
constructor(ownerUri: string, templates: Array<sqlops.ProfilerSessionTemplate>) {
|
||||
this.ownerUri = ownerUri;
|
||||
this.templates = templates;
|
||||
}
|
||||
|
||||
public getTemplateNames(): string[] {
|
||||
return this.templates.map(e => e.name);
|
||||
}
|
||||
|
||||
public selectTemplate(name: string): sqlops.ProfilerSessionTemplate {
|
||||
return this.templates.find((t) => { return t.name === name; });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 sqlops from 'sqlops';
|
||||
import * as vscode from 'vscode';
|
||||
import { CreateSessionData } from '../data/createSessionData';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export class CreateSessionDialog {
|
||||
|
||||
// Top level
|
||||
private readonly DialogTitle: string = localize('createSessionDialog.newSession', 'New Session');
|
||||
private readonly CancelButtonText: string = localize('createSessionDialog.cancel', 'Cancel');
|
||||
private readonly CreateButtonText: string = localize('createSessionDialog.create', 'Create');
|
||||
private readonly DialogTitleText: string = localize('createSessionDialog.title', 'Create New Profiler Session');
|
||||
|
||||
// UI Components
|
||||
private dialog: sqlops.window.modelviewdialog.Dialog;
|
||||
private templatesBox: sqlops.DropDownComponent;
|
||||
private sessionNameBox: sqlops.InputBoxComponent;
|
||||
|
||||
private model: CreateSessionData;
|
||||
|
||||
private _onSuccess: vscode.EventEmitter<CreateSessionData> = new vscode.EventEmitter<CreateSessionData>();
|
||||
public readonly onSuccess: vscode.Event<CreateSessionData> = this._onSuccess.event;
|
||||
|
||||
|
||||
constructor(ownerUri: string, templates: Array<sqlops.ProfilerSessionTemplate>) {
|
||||
if (typeof (templates) === 'undefined' || templates === null) {
|
||||
throw new Error(localize('createSessionDialog.templatesInvalid', "Invalid templates list, cannot open dialog"));
|
||||
}
|
||||
if (typeof (ownerUri) === 'undefined' || ownerUri === null) {
|
||||
throw new Error(localize('createSessionDialog.dialogOwnerInvalid', "Invalid dialog owner, cannot open dialog"));
|
||||
}
|
||||
this.model = new CreateSessionData(ownerUri, templates);
|
||||
}
|
||||
|
||||
public async showDialog(): Promise<void> {
|
||||
this.dialog = sqlops.window.modelviewdialog.createDialog(this.DialogTitle);
|
||||
this.initializeContent();
|
||||
this.dialog.okButton.onClick(() => this.execute());
|
||||
this.dialog.cancelButton.onClick(() => { });
|
||||
this.dialog.okButton.label = this.CreateButtonText;
|
||||
this.dialog.cancelButton.label = this.CancelButtonText;
|
||||
|
||||
sqlops.window.modelviewdialog.openDialog(this.dialog);
|
||||
}
|
||||
|
||||
private initializeContent(): void {
|
||||
this.dialog.registerContent(async view => {
|
||||
this.templatesBox = view.modelBuilder.dropDown()
|
||||
.withProperties({
|
||||
values: []
|
||||
}).component();
|
||||
|
||||
this.sessionNameBox = view.modelBuilder.inputBox()
|
||||
.withProperties({
|
||||
required: true,
|
||||
multiline: false,
|
||||
value: ''
|
||||
}).component();
|
||||
|
||||
let formModel = view.modelBuilder.formContainer()
|
||||
.withFormItems([{
|
||||
components: [{
|
||||
component: this.templatesBox,
|
||||
title: localize('createSessionDialog.selectTemplates', "Select session template:")
|
||||
},
|
||||
{
|
||||
component: this.sessionNameBox,
|
||||
|
||||
title: localize('createSessionDialog.enterSessionName', "Enter session name:")
|
||||
}],
|
||||
title: this.DialogTitleText
|
||||
}]).withLayout({ width: '100%' }).component();
|
||||
|
||||
await view.initializeModel(formModel);
|
||||
|
||||
if (this.model.templates) {
|
||||
this.templatesBox.values = this.model.getTemplateNames();
|
||||
}
|
||||
|
||||
this.sessionNameBox.onTextChanged(() => {
|
||||
if (this.sessionNameBox.value.length > 0) {
|
||||
this.model.sessionName = this.sessionNameBox.value;
|
||||
this.dialog.okButton.enabled = true;
|
||||
} else {
|
||||
this.dialog.okButton.enabled = false;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private async execute(): Promise<void> {
|
||||
let currentConnection = await sqlops.connection.getCurrentConnection();
|
||||
let profilerService = sqlops.dataprotocol.getProvider<sqlops.ProfilerProvider>(currentConnection.providerName, sqlops.DataProviderType.ProfilerProvider);
|
||||
|
||||
let name = this.sessionNameBox.value;
|
||||
let selected = this.templatesBox.value.toString();
|
||||
let temp = this.model.selectTemplate(selected);
|
||||
profilerService.createSession(this.model.ownerUri, this.sessionNameBox.value, temp);
|
||||
}
|
||||
}
|
||||
@@ -5,28 +5,33 @@
|
||||
'use strict';
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import * as data from 'sqlops';
|
||||
import * as sqlops from 'sqlops';
|
||||
import { ApiWrapper } from './apiWrapper';
|
||||
import { CreateSessionDialog } from './dialogs/profilerCreateSessionDialog';
|
||||
|
||||
/**
|
||||
* The main controller class that initializes the extension
|
||||
*/
|
||||
export class MainController {
|
||||
protected _apiWrapper: ApiWrapper;
|
||||
protected _context: vscode.ExtensionContext;
|
||||
export class MainController {
|
||||
protected _apiWrapper: ApiWrapper;
|
||||
protected _context: vscode.ExtensionContext;
|
||||
|
||||
// PUBLIC METHODS //////////////////////////////////////////////////////
|
||||
public constructor(context: vscode.ExtensionContext, apiWrapper?: ApiWrapper) {
|
||||
this._apiWrapper = apiWrapper || new ApiWrapper();
|
||||
this._context = context;
|
||||
}
|
||||
// PUBLIC METHODS
|
||||
public constructor(context: vscode.ExtensionContext, apiWrapper?: ApiWrapper) {
|
||||
this._apiWrapper = apiWrapper || new ApiWrapper();
|
||||
this._context = context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deactivates the extension
|
||||
*/
|
||||
public deactivate(): void {
|
||||
}
|
||||
public deactivate(): void {
|
||||
}
|
||||
|
||||
public activate(): void {
|
||||
}
|
||||
public activate(): void {
|
||||
vscode.commands.registerCommand('profiler.openCreateSessionDialog', (ownerUri: string, templates: Array<sqlops.ProfilerSessionTemplate>) => {
|
||||
let dialog = new CreateSessionDialog(ownerUri, templates);
|
||||
dialog.showDialog();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,4 +5,5 @@
|
||||
|
||||
/// <reference path='../../../../../src/vs/vscode.d.ts'/>
|
||||
/// <reference path='../../../../../src/sql/sqlops.d.ts'/>
|
||||
/// <reference path='../../../../../src/sql/sqlops.proposed.d.ts'/>
|
||||
/// <reference types='@types/node'/>
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "profiler",
|
||||
"displayName": "SQL Server Profiler",
|
||||
"description": "SQL Server Profiler for SQL Operations Studio",
|
||||
"version": "0.1.1",
|
||||
"version": "0.1.3",
|
||||
"publisher": "Microsoft",
|
||||
"preview": true,
|
||||
"license": "https://raw.githubusercontent.com/Microsoft/sqlopsstudio/master/LICENSE.txt",
|
||||
@@ -27,17 +27,35 @@
|
||||
],
|
||||
"contributes": {
|
||||
|
||||
"commands": [
|
||||
"commands": [
|
||||
{
|
||||
"command": "profiler.newProfiler",
|
||||
"title": "New Profiler",
|
||||
"category": "Profiler"
|
||||
},
|
||||
{
|
||||
"command": "profiler.start",
|
||||
"title": "Start",
|
||||
"category": "Profiler"
|
||||
},
|
||||
{
|
||||
"command": "profiler.stop",
|
||||
"title": "Stop",
|
||||
"category": "Profiler"
|
||||
},
|
||||
{
|
||||
"command": "profiler.openCreateSessionDialog",
|
||||
"title": "Create Profiler Session",
|
||||
"category": "Profiler"
|
||||
}
|
||||
],
|
||||
"outputChannels": [
|
||||
"sqlprofiler"
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"vscode-nls": "^3.2.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"vscode": "1.0.1"
|
||||
}
|
||||
|
||||
@@ -2035,6 +2035,10 @@ vinyl@~2.0.1:
|
||||
remove-trailing-separator "^1.0.1"
|
||||
replace-ext "^1.0.0"
|
||||
|
||||
vscode-nls@^3.2.1:
|
||||
version "3.2.4"
|
||||
resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-3.2.4.tgz#2166b4183c8aea884d20727f5449e62be69fd398"
|
||||
|
||||
vscode@1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/vscode/-/vscode-1.0.1.tgz#3d161200615fe2af1d92ddc650751159411a513b"
|
||||
|
||||
@@ -12,8 +12,9 @@
|
||||
["{", "}"],
|
||||
["[", "]"],
|
||||
["(", ")"],
|
||||
["\"", "\""],
|
||||
["'", "'"]
|
||||
{ "open": "\"", "close": "\"", "notIn": ["string"] },
|
||||
{ "open": "N'", "close": "'", "notIn": ["string", "comment"] },
|
||||
{ "open": "'", "close": "'", "notIn": ["string", "comment"] }
|
||||
],
|
||||
"surroundingPairs": [
|
||||
["{", "}"],
|
||||
|
||||
@@ -936,11 +936,11 @@
|
||||
},
|
||||
"_shell_light": {
|
||||
"fontCharacter": "\\E073",
|
||||
"fontColor": "#455155"
|
||||
"fontColor": "#bfc2c1"
|
||||
},
|
||||
"_shell": {
|
||||
"fontCharacter": "\\E073",
|
||||
"fontColor": "#4d5a5e"
|
||||
"fontColor": "#d4d7d6"
|
||||
},
|
||||
"_slim_light": {
|
||||
"fontCharacter": "\\E074",
|
||||
@@ -1374,7 +1374,9 @@
|
||||
"cmakelists.txt": "_makefile_3",
|
||||
"procfile": "_heroku",
|
||||
"todo": "_todo",
|
||||
"npm-debug.log": "_npm_ignored"
|
||||
"npm-debug.log": "_npm_ignored",
|
||||
"dashboard": "_shell",
|
||||
"profiler": "_csv"
|
||||
},
|
||||
"languageIds": {
|
||||
"bat": "_windows",
|
||||
@@ -1409,7 +1411,7 @@
|
||||
"rust": "_rust",
|
||||
"scss": "_sass",
|
||||
"shellscript": "_shell",
|
||||
"sql": "_db",
|
||||
"sql": "_default",
|
||||
"swift": "_swift",
|
||||
"typescript": "_typescript",
|
||||
"typescriptreact": "_react",
|
||||
@@ -1614,7 +1616,7 @@
|
||||
"rust": "_rust_light",
|
||||
"scss": "_sass_light",
|
||||
"shellscript": "_shell_light",
|
||||
"sql": "_db_light",
|
||||
"sql": "_default_light",
|
||||
"swift": "_swift_light",
|
||||
"typescript": "_typescript_light",
|
||||
"typescriptreact": "_react_light",
|
||||
@@ -1660,7 +1662,9 @@
|
||||
"omakefile": "_makefile_2_light",
|
||||
"cmakelists.txt": "_makefile_3_light",
|
||||
"procfile": "_heroku_light",
|
||||
"npm-debug.log": "_npm_ignored_light"
|
||||
"npm-debug.log": "_npm_ignored_light",
|
||||
"dashboard": "_shell_light",
|
||||
"profiler": "_csv_light"
|
||||
}
|
||||
},
|
||||
"version": "https://github.com/jesseweed/seti-ui/commit/188dda34a56b9555c7d363771264c24f4693983d"
|
||||
|
||||
14
package.json
14
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "sqlops",
|
||||
"version": "0.30.6",
|
||||
"version": "0.32.5",
|
||||
"distro": "8c3e97e3425cc9814496472ab73e076de2ba99ee",
|
||||
"author": {
|
||||
"name": "Microsoft Corporation"
|
||||
@@ -34,7 +34,7 @@
|
||||
"@angular/router": "~4.1.3",
|
||||
"@angular/upgrade": "~4.1.3",
|
||||
"angular2-grid": "2.0.6",
|
||||
"angular2-slickgrid": "git://github.com/Microsoft/angular2-slickgrid.git#1.3.11",
|
||||
"angular2-slickgrid": "github:Microsoft/angular2-slickgrid#1.4.3",
|
||||
"applicationinsights": "0.18.0",
|
||||
"chart.js": "^2.6.0",
|
||||
"fast-plist": "0.1.2",
|
||||
@@ -43,8 +43,8 @@
|
||||
"getmac": "1.0.7",
|
||||
"graceful-fs": "4.1.11",
|
||||
"html-query-plan": "git://github.com/anthonydresser/html-query-plan.git#2.4",
|
||||
"http-proxy-agent": "0.2.7",
|
||||
"https-proxy-agent": "0.3.6",
|
||||
"http-proxy-agent": "^2.1.0",
|
||||
"https-proxy-agent": "^2.2.1",
|
||||
"iconv-lite": "0.4.19",
|
||||
"jquery": "3.1.0",
|
||||
"jschardet": "1.6.0",
|
||||
@@ -60,7 +60,7 @@
|
||||
"reflect-metadata": "^0.1.8",
|
||||
"rxjs": "5.4.0",
|
||||
"semver": "4.3.6",
|
||||
"slickgrid": "github:anthonydresser/SlickGrid#2.3.20",
|
||||
"slickgrid": "github:anthonydresser/SlickGrid#2.3.25",
|
||||
"spdlog": "0.6.0",
|
||||
"sudo-prompt": "^8.0.0",
|
||||
"svg.js": "^2.2.5",
|
||||
@@ -127,7 +127,7 @@
|
||||
"istanbul": "^0.3.17",
|
||||
"jsdom-no-contextify": "^3.1.0",
|
||||
"lazy.js": "^0.4.2",
|
||||
"mime": "1.2.11",
|
||||
"mime": "^1.4.1",
|
||||
"minimatch": "^2.0.10",
|
||||
"mkdirp": "^0.5.0",
|
||||
"mocha": "^2.2.5",
|
||||
@@ -149,7 +149,7 @@
|
||||
"underscore": "^1.8.3",
|
||||
"vinyl": "^0.4.5",
|
||||
"vinyl-fs": "^2.4.3",
|
||||
"vsce": "1.33.2",
|
||||
"vsce": "1.46.0",
|
||||
"vscode-nls-dev": "3.0.7"
|
||||
},
|
||||
"repository": {
|
||||
|
||||
@@ -30,10 +30,12 @@
|
||||
"gettingStartedUrl": "https://go.microsoft.com/fwlink/?linkid=862039",
|
||||
"releaseNotesUrl": "https://go.microsoft.com/fwlink/?linkid=875578",
|
||||
"documentationUrl": "https://go.microsoft.com/fwlink/?linkid=862277",
|
||||
"vscodeVersion": "1.23.1",
|
||||
"commit": "9ca6200018fc206d67a47229f991901a8a453781",
|
||||
"date": "2017-12-15T12:00:00.000Z",
|
||||
"recommendedExtensions": [
|
||||
"Microsoft.agent",
|
||||
"Microsoft.import",
|
||||
"Microsoft.profiler",
|
||||
"Microsoft.server-report",
|
||||
"Microsoft.whoisactive",
|
||||
@@ -42,4 +44,4 @@
|
||||
"extensionsGallery": {
|
||||
"serviceUrl": "https://sqlopsextensions.blob.core.windows.net/marketplace/v1/extensionsGallery.json"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[Desktop Entry]
|
||||
Name=@@NAME_LONG@@
|
||||
Comment=SQL Operations Studio
|
||||
Comment=Data Management Tool that enables you to work with SQL Server, Azure SQL DB and SQL DW from Windows, macOS and Linux.
|
||||
GenericName=Text Editor
|
||||
Exec=/usr/share/@@NAME@@/@@NAME@@ --unity-launch %F
|
||||
Icon=@@ICON@@
|
||||
|
||||
@@ -37,6 +37,10 @@ See [Paul Randal's wait types library] for more information about each wait type
|
||||
|
||||
We would like to thank all our users who raised issues, and in particular the following users who helped contribute fixes:
|
||||
* flyfishingdba for Add square brackets for ms_foreachdb call (#1023)
|
||||
* Peter-Schneider for Changed the stored procedure call to work on case sensitive instances (#1809)
|
||||
|
||||
## What's new in Server Reports v1.3?
|
||||
* Changed the stored procedure call to work on case sensitive instances
|
||||
|
||||
## What's new in Server Reports v1.2?
|
||||
* Created left nav bar and added 2 categories for insight widgets: monitor and performance
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "server-report",
|
||||
"displayName": "Server Reports",
|
||||
"description": "Server Reports",
|
||||
"version": "0.1.2",
|
||||
"version": "0.1.3",
|
||||
"publisher": "Microsoft",
|
||||
"preview": true,
|
||||
"engines": {
|
||||
|
||||
@@ -9,7 +9,7 @@ declare @dbsize table
|
||||
Free_Space_MB decimal(20,2) default (0))
|
||||
insert into @dbsize
|
||||
(Dbname,file_Size_MB,Space_Used_MB,Free_Space_MB)
|
||||
exec sp_msforeachdb
|
||||
exec sp_MSforeachdb
|
||||
'use [?];
|
||||
select DB_NAME() AS DbName,
|
||||
sum(size)/128.0 AS File_Size_MB,
|
||||
@@ -24,7 +24,7 @@ declare @logsize table
|
||||
log_Free_Space_MB decimal(20,2)default (0))
|
||||
insert into @logsize
|
||||
(Dbname,Log_File_Size_MB,log_Space_Used_MB,log_Free_Space_MB)
|
||||
exec sp_msforeachdb
|
||||
exec sp_MSforeachdb
|
||||
'use [?];
|
||||
select DB_NAME() AS DbName,
|
||||
sum(size)/128.0 AS Log_File_Size_MB,
|
||||
@@ -38,7 +38,7 @@ declare @dbfreesize table
|
||||
Freespace varchar(50)default (0.00))
|
||||
insert into @dbfreesize
|
||||
(name,database_size,Freespace)
|
||||
exec sp_msforeachdb
|
||||
exec sp_MSforeachdb
|
||||
'use [?];SELECT database_name = db_name()
|
||||
,database_size = ltrim(str((convert(DECIMAL(15, 2), dbsize) + convert(DECIMAL(15, 2), logsize)) * 8192 / 1048576, 15, 2) + ''MB'')
|
||||
,''unallocated space'' = ltrim(str((
|
||||
|
||||
@@ -19,7 +19,11 @@
|
||||
"commands": [
|
||||
{
|
||||
"command": "sqlservices.openDialog",
|
||||
"title": "openDialog"
|
||||
"title": "sqlservices.openDialog"
|
||||
},
|
||||
{
|
||||
"command": "sqlservices.openConnectionDialog",
|
||||
"title": "sqlservices.openConnectionDialog"
|
||||
},
|
||||
{
|
||||
"command": "sqlservices.openEditor",
|
||||
@@ -32,6 +36,10 @@
|
||||
{
|
||||
"command": "sqlservices.openEditorWithWebView2",
|
||||
"title": "sqlservices.openEditorWithWebView2"
|
||||
},
|
||||
{
|
||||
"command": "sqlservices.openWizard",
|
||||
"title": "sqlservices.openWizard"
|
||||
}
|
||||
],
|
||||
"dashboard.tabs": [
|
||||
@@ -96,7 +104,7 @@
|
||||
"sqlops": "github:anthonydresser/sqlops-extension-sqlops",
|
||||
"tslint": "^3.14.0",
|
||||
"typescript": "^2.6.1",
|
||||
"vscode": "^1.1.14",
|
||||
"vscode": "^1.1.18",
|
||||
"@types/handlebars": "^4.0.11",
|
||||
"vsce": "1.36.2"
|
||||
},
|
||||
|
||||
@@ -11,6 +11,7 @@ import * as vscode from 'vscode';
|
||||
import SplitPropertiesPanel from './splitPropertiesPanel';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import {TreeNode, TreeDataProvider} from './treeDataProvider';
|
||||
|
||||
/**
|
||||
* The main controller class that initializes the extension
|
||||
@@ -48,6 +49,13 @@ export default class MainController implements vscode.Disposable {
|
||||
this.openDialog();
|
||||
});
|
||||
|
||||
vscode.commands.registerCommand('sqlservices.openConnectionDialog', async () => {
|
||||
let connection = await sqlops.connection.openConnectionDialog();
|
||||
if (connection) {
|
||||
console.info('Connection Opened: ' + connection.options['server']);
|
||||
}
|
||||
});
|
||||
|
||||
vscode.commands.registerCommand('sqlservices.openEditor', () => {
|
||||
this.openEditor();
|
||||
});
|
||||
@@ -60,16 +68,291 @@ export default class MainController implements vscode.Disposable {
|
||||
this.openEditorWithWebview2();
|
||||
});
|
||||
|
||||
vscode.commands.registerCommand('sqlservices.openWizard', () => {
|
||||
this.openWizard();
|
||||
});
|
||||
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
|
||||
private async getTab3Content(view: sqlops.ModelView): Promise<void> {
|
||||
let treeData = {
|
||||
label: '1',
|
||||
children: [
|
||||
{
|
||||
label: '11',
|
||||
id: '11',
|
||||
children: [
|
||||
{
|
||||
label: '111',
|
||||
id: '111',
|
||||
checked: false
|
||||
},
|
||||
{
|
||||
label: '112',
|
||||
id: '112',
|
||||
children: [
|
||||
{
|
||||
label: '1121',
|
||||
id: '1121',
|
||||
checked: true
|
||||
},
|
||||
{
|
||||
label: '1122',
|
||||
id: '1122',
|
||||
checked: false
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: '12',
|
||||
id: '12',
|
||||
checked: true
|
||||
}
|
||||
],
|
||||
id: '1'
|
||||
};
|
||||
let root = TreeNode.createTree(treeData);
|
||||
|
||||
let treeDataProvider = new TreeDataProvider(root);
|
||||
|
||||
let tree: sqlops.TreeComponent<TreeNode> = view.modelBuilder.tree<TreeNode>().withProperties({
|
||||
'withCheckbox': true
|
||||
}).component();
|
||||
let treeView = tree.registerDataProvider(treeDataProvider);
|
||||
treeView.onNodeCheckedChanged(item => {
|
||||
if (item && item.element) {
|
||||
item.element.changeNodeCheckedState(item.checked);
|
||||
}
|
||||
});
|
||||
treeView.onDidChangeSelection(selectedNodes => {
|
||||
selectedNodes.forEach(node => {
|
||||
console.info('tree node selected: ' + node.label);
|
||||
});
|
||||
});
|
||||
let formModel = view.modelBuilder.formContainer()
|
||||
.withFormItems([{
|
||||
component: tree,
|
||||
title: 'Tree'
|
||||
}], {
|
||||
horizontal: false,
|
||||
componentWidth: 800,
|
||||
componentHeight: 800
|
||||
}).component();
|
||||
let formWrapper = view.modelBuilder.loadingComponent().withItem(formModel).component();
|
||||
formWrapper.loading = false;
|
||||
|
||||
await view.initializeModel(formWrapper);
|
||||
}
|
||||
private async getTabContent(view: sqlops.ModelView, customButton1: sqlops.window.modelviewdialog.Button, customButton2: sqlops.window.modelviewdialog.Button, componentWidth: number | string): Promise<void> {
|
||||
let inputBox = view.modelBuilder.inputBox()
|
||||
.withProperties({
|
||||
multiline: true,
|
||||
height: 100
|
||||
}).component();
|
||||
let inputBoxWrapper = view.modelBuilder.loadingComponent().withItem(inputBox).component();
|
||||
inputBoxWrapper.loading = false;
|
||||
customButton1.onClick(() => {
|
||||
inputBoxWrapper.loading = true;
|
||||
setTimeout(() => inputBoxWrapper.loading = false, 5000);
|
||||
});
|
||||
let inputBox2 = view.modelBuilder.inputBox().component();
|
||||
let backupFilesInputBox = view.modelBuilder.inputBox().component();
|
||||
|
||||
let checkbox = view.modelBuilder.checkBox()
|
||||
.withProperties({
|
||||
label: 'Copy-only backup'
|
||||
})
|
||||
.component();
|
||||
checkbox.onChanged(e => {
|
||||
console.info('inputBox.enabled ' + inputBox.enabled);
|
||||
inputBox.enabled = !inputBox.enabled;
|
||||
});
|
||||
let button = view.modelBuilder.button()
|
||||
.withProperties({
|
||||
label: '+'
|
||||
}).component();
|
||||
let button3 = view.modelBuilder.button()
|
||||
.withProperties({
|
||||
label: '-'
|
||||
|
||||
}).component();
|
||||
let button2 = view.modelBuilder.button()
|
||||
.component();
|
||||
button.onDidClick(e => {
|
||||
backupFilesInputBox.value = 'Button clicked';
|
||||
});
|
||||
let dropdown = view.modelBuilder.dropDown()
|
||||
.withProperties({
|
||||
value: 'Full',
|
||||
values: ['Full', 'Differential', 'Transaction Log']
|
||||
})
|
||||
.component();
|
||||
let f = 0;
|
||||
inputBox.onTextChanged((params) => {
|
||||
vscode.window.showInformationMessage(inputBox.value);
|
||||
f = f + 1;
|
||||
inputBox2.value = f.toString();
|
||||
});
|
||||
dropdown.onValueChanged((params) => {
|
||||
vscode.window.showInformationMessage(inputBox2.value);
|
||||
inputBox.value = dropdown.value.toString();
|
||||
});
|
||||
let radioButton = view.modelBuilder.radioButton()
|
||||
.withProperties({
|
||||
value: 'option1',
|
||||
name: 'radioButtonOptions',
|
||||
label: 'Option 1',
|
||||
checked: true
|
||||
//width: 300
|
||||
}).component();
|
||||
let radioButton2 = view.modelBuilder.radioButton()
|
||||
.withProperties({
|
||||
value: 'option2',
|
||||
name: 'radioButtonOptions',
|
||||
label: 'Option 2'
|
||||
}).component();
|
||||
let inputBox3 = view.modelBuilder.inputBox().component();
|
||||
let inputBox4 = view.modelBuilder.inputBox().component();
|
||||
let form2Model = view.modelBuilder.formContainer()
|
||||
.withFormItems([{
|
||||
component: inputBox3,
|
||||
title: 'inputBox3'
|
||||
}, {
|
||||
component: inputBox4,
|
||||
title: 'inputBox4'
|
||||
}], {
|
||||
horizontal: true
|
||||
}).component();
|
||||
let groupModel1 = view.modelBuilder.groupContainer()
|
||||
.withLayout({
|
||||
}).withItems([
|
||||
form2Model
|
||||
]).component();
|
||||
radioButton.onDidClick(() => {
|
||||
inputBox.value = radioButton.value;
|
||||
groupModel1.enabled = true;
|
||||
});
|
||||
radioButton2.onDidClick(() => {
|
||||
inputBox.value = radioButton.value;
|
||||
groupModel1.enabled = false;
|
||||
});
|
||||
let table = view.modelBuilder.table().withProperties({
|
||||
data: [
|
||||
['1', '2', '2'],
|
||||
['4', '5', '6'],
|
||||
['7', '8', '9']
|
||||
], columns: ['c1', 'c2', 'c3'],
|
||||
height: 250,
|
||||
selectedRows: [0]
|
||||
}).component();
|
||||
table.onRowSelected(e => {
|
||||
// TODO:
|
||||
});
|
||||
let listBox = view.modelBuilder.listBox().withProperties({
|
||||
values: ['1', '2', '3'],
|
||||
selectedRow: 2
|
||||
}).component();
|
||||
|
||||
let declarativeTable = view.modelBuilder.declarativeTable()
|
||||
.withProperties({
|
||||
columns: [{
|
||||
displayName: 'Column 1',
|
||||
valueType: sqlops.DeclarativeDataType.string,
|
||||
width: '20px',
|
||||
isReadOnly: true
|
||||
}, {
|
||||
displayName: 'Column 2',
|
||||
valueType: sqlops.DeclarativeDataType.string,
|
||||
width: '100px',
|
||||
isReadOnly: false
|
||||
}, {
|
||||
displayName: 'Column 3',
|
||||
valueType: sqlops.DeclarativeDataType.boolean,
|
||||
width: '20px',
|
||||
isReadOnly: false
|
||||
}, {
|
||||
displayName: 'Column 4',
|
||||
valueType: sqlops.DeclarativeDataType.category,
|
||||
isReadOnly: false,
|
||||
width: '120px',
|
||||
categoryValues: [
|
||||
{ name: 'options1', displayName: 'option 1' },
|
||||
{ name: 'options2', displayName: 'option 2' }
|
||||
]
|
||||
}
|
||||
],
|
||||
data: [
|
||||
['Data00', 'Data01', false, 'options2'],
|
||||
['Data10', 'Data11', true, 'options1']
|
||||
]
|
||||
}).component();
|
||||
|
||||
declarativeTable.onDataChanged(e => {
|
||||
inputBox2.value = e.row.toString() + ' ' + e.column.toString() + ' ' + e.value.toString();
|
||||
inputBox3.value = declarativeTable.data[e.row][e.column];
|
||||
});
|
||||
|
||||
let flexRadioButtonsModel = view.modelBuilder.flexContainer()
|
||||
.withLayout({
|
||||
flexFlow: 'column',
|
||||
alignItems: 'left',
|
||||
height: 150
|
||||
}).withItems([
|
||||
radioButton, groupModel1, radioButton2]
|
||||
, { flex: '1 1 50%' }).component();
|
||||
let formModel = view.modelBuilder.formContainer()
|
||||
.withFormItems([{
|
||||
component: inputBoxWrapper,
|
||||
title: 'Backup name'
|
||||
}, {
|
||||
component: inputBox2,
|
||||
title: 'Recovery model'
|
||||
}, {
|
||||
component: dropdown,
|
||||
title: 'Backup type'
|
||||
}, {
|
||||
component: checkbox,
|
||||
title: ''
|
||||
}, {
|
||||
component: backupFilesInputBox,
|
||||
title: 'Backup files',
|
||||
actions: [button, button3]
|
||||
}, {
|
||||
component: flexRadioButtonsModel,
|
||||
title: 'Options'
|
||||
}, {
|
||||
component: declarativeTable,
|
||||
title: 'Declarative Table'
|
||||
}, {
|
||||
component: table,
|
||||
title: 'Table'
|
||||
}, {
|
||||
component: listBox,
|
||||
title: 'List Box'
|
||||
}], {
|
||||
horizontal: false,
|
||||
componentWidth: componentWidth
|
||||
}).component();
|
||||
let formWrapper = view.modelBuilder.loadingComponent().withItem(formModel).component();
|
||||
formWrapper.loading = false;
|
||||
customButton2.onClick(() => {
|
||||
formWrapper.loading = true;
|
||||
setTimeout(() => formWrapper.loading = false, 5000);
|
||||
});
|
||||
await view.initializeModel(formWrapper);
|
||||
}
|
||||
|
||||
private openDialog(): void {
|
||||
let dialog = sqlops.window.modelviewdialog.createDialog('Test dialog');
|
||||
let tab1 = sqlops.window.modelviewdialog.createTab('Test tab 1');
|
||||
|
||||
let tab2 = sqlops.window.modelviewdialog.createTab('Test tab 2');
|
||||
let tab3 = sqlops.window.modelviewdialog.createTab('Test tab 3');
|
||||
tab2.content = 'sqlservices';
|
||||
dialog.content = [tab1, tab2];
|
||||
dialog.content = [tab1, tab2, tab3];
|
||||
dialog.okButton.onClick(() => console.log('ok clicked!'));
|
||||
dialog.cancelButton.onClick(() => console.log('cancel clicked!'));
|
||||
dialog.okButton.label = 'ok';
|
||||
@@ -80,207 +363,46 @@ export default class MainController implements vscode.Disposable {
|
||||
customButton2.onClick(() => console.log('button 2 clicked!'));
|
||||
dialog.customButtons = [customButton1, customButton2];
|
||||
tab1.registerContent(async (view) => {
|
||||
let inputBox = view.modelBuilder.inputBox()
|
||||
.withProperties({
|
||||
multiline: true,
|
||||
height: 100
|
||||
}).component();
|
||||
let inputBoxWrapper = view.modelBuilder.loadingComponent().withItem(inputBox).component();
|
||||
inputBoxWrapper.loading = false;
|
||||
customButton1.onClick(() => {
|
||||
inputBoxWrapper.loading = true;
|
||||
setTimeout(() => inputBoxWrapper.loading = false, 5000);
|
||||
});
|
||||
let inputBox2 = view.modelBuilder.inputBox().component();
|
||||
let backupFilesInputBox = view.modelBuilder.inputBox().component();
|
||||
|
||||
let checkbox = view.modelBuilder.checkBox()
|
||||
.withProperties({
|
||||
label: 'Copy-only backup'
|
||||
})
|
||||
.component();
|
||||
checkbox.onChanged(e => {
|
||||
console.info("inputBox.enabled " + inputBox.enabled);
|
||||
inputBox.enabled = !inputBox.enabled;
|
||||
});
|
||||
let button = view.modelBuilder.button()
|
||||
.withProperties({
|
||||
label: '+'
|
||||
}).component();
|
||||
let button3 = view.modelBuilder.button()
|
||||
.withProperties({
|
||||
label: '-'
|
||||
|
||||
}).component();
|
||||
let button2 = view.modelBuilder.button()
|
||||
.component();
|
||||
button.onDidClick(e => {
|
||||
backupFilesInputBox.value = 'Button clicked';
|
||||
});
|
||||
let dropdown = view.modelBuilder.dropDown()
|
||||
.withProperties({
|
||||
value: 'Full',
|
||||
values: ['Full', 'Differential', 'Transaction Log']
|
||||
})
|
||||
.component();
|
||||
let f = 0;
|
||||
inputBox.onTextChanged((params) => {
|
||||
vscode.window.showInformationMessage(inputBox.value);
|
||||
f = f + 1;
|
||||
inputBox2.value = f.toString();
|
||||
});
|
||||
dropdown.onValueChanged((params) => {
|
||||
vscode.window.showInformationMessage(inputBox2.value);
|
||||
inputBox.value = dropdown.value;
|
||||
});
|
||||
let radioButton = view.modelBuilder.radioButton()
|
||||
.withProperties({
|
||||
value: 'option1',
|
||||
name: 'radioButtonOptions',
|
||||
label: 'Option 1',
|
||||
checked: true
|
||||
//width: 300
|
||||
}).component();
|
||||
let radioButton2 = view.modelBuilder.radioButton()
|
||||
.withProperties({
|
||||
value: 'option2',
|
||||
name: 'radioButtonOptions',
|
||||
label: 'Option 2'
|
||||
}).component();
|
||||
let inputBox3 = view.modelBuilder.inputBox().component();
|
||||
let inputBox4 = view.modelBuilder.inputBox().component();
|
||||
let form2Model = view.modelBuilder.formContainer()
|
||||
.withFormItems([{
|
||||
component: inputBox3,
|
||||
title: 'inputBox3'
|
||||
}, {
|
||||
component: inputBox4,
|
||||
title: 'inputBox4'
|
||||
}], {
|
||||
horizontal: true
|
||||
}).component();
|
||||
let groupModel1 = view.modelBuilder.groupContainer()
|
||||
.withLayout({
|
||||
}).withItems([
|
||||
form2Model
|
||||
]).component();
|
||||
radioButton.onDidClick(() => {
|
||||
inputBox.value = radioButton.value;
|
||||
groupModel1.enabled = true;
|
||||
});
|
||||
radioButton2.onDidClick(() => {
|
||||
inputBox.value = radioButton.value;
|
||||
groupModel1.enabled = false;
|
||||
});
|
||||
let table = view.modelBuilder.table().withProperties({
|
||||
data: [
|
||||
['1', '2', '2'],
|
||||
['4', '5', '6'],
|
||||
['7', '8', '9']
|
||||
], columns: ['c1', 'c2', 'c3'],
|
||||
height: 250,
|
||||
selectedRows: [0]
|
||||
}).component();
|
||||
table.onRowSelected(e => {
|
||||
// TODO:
|
||||
});
|
||||
let listBox = view.modelBuilder.listBox().withProperties({
|
||||
values: ['1', '2', '3'],
|
||||
selectedRow: 2
|
||||
}).component();
|
||||
|
||||
let declarativeTable = view.modelBuilder.declarativeTable()
|
||||
.withProperties({
|
||||
columns: [{
|
||||
displayName: 'Column 1',
|
||||
valueType: sqlops.DeclarativeDataType.string,
|
||||
width: '20px',
|
||||
isReadOnly: true
|
||||
}, {
|
||||
displayName: 'Column 2',
|
||||
valueType: sqlops.DeclarativeDataType.string,
|
||||
width: '100px',
|
||||
isReadOnly: false
|
||||
}, {
|
||||
displayName: 'Column 3',
|
||||
valueType: sqlops.DeclarativeDataType.boolean,
|
||||
width: '20px',
|
||||
isReadOnly: false
|
||||
}, {
|
||||
displayName: 'Column 4',
|
||||
valueType: sqlops.DeclarativeDataType.category,
|
||||
isReadOnly: false,
|
||||
width: '120px',
|
||||
categoryValues: [
|
||||
{ name: 'options1', displayName: 'option 1' },
|
||||
{ name: 'options2', displayName: 'option 2' }
|
||||
]
|
||||
}
|
||||
],
|
||||
data: [
|
||||
['Data00', 'Data01', false, 'options2'],
|
||||
['Data10', 'Data11', true, 'options1']
|
||||
]
|
||||
}).component();
|
||||
|
||||
declarativeTable.onDataChanged(e => {
|
||||
inputBox2.value = e.row.toString() + ' ' + e.column.toString() + ' ' + e.value.toString();
|
||||
inputBox3.value = declarativeTable.data[e.row][e.column];
|
||||
});
|
||||
|
||||
let flexRadioButtonsModel = view.modelBuilder.flexContainer()
|
||||
.withLayout({
|
||||
flexFlow: 'column',
|
||||
alignItems: 'left',
|
||||
height: 150
|
||||
}).withItems([
|
||||
radioButton, groupModel1, radioButton2]
|
||||
, { flex: '1 1 50%' }).component();
|
||||
let formModel = view.modelBuilder.formContainer()
|
||||
.withFormItems([{
|
||||
component: inputBoxWrapper,
|
||||
title: 'Backup name'
|
||||
}, {
|
||||
component: inputBox2,
|
||||
title: 'Recovery model'
|
||||
}, {
|
||||
component: dropdown,
|
||||
title: 'Backup type'
|
||||
}, {
|
||||
component: checkbox,
|
||||
title: ''
|
||||
}, {
|
||||
component: backupFilesInputBox,
|
||||
title: 'Backup files',
|
||||
actions: [button, button3]
|
||||
}, {
|
||||
component: flexRadioButtonsModel,
|
||||
title: 'Options'
|
||||
}, {
|
||||
component: declarativeTable,
|
||||
title: 'Declarative Table'
|
||||
}, {
|
||||
component: table,
|
||||
title: 'Table'
|
||||
}, {
|
||||
component: listBox,
|
||||
title: 'List Box'
|
||||
}], {
|
||||
horizontal: false,
|
||||
componentWidth: 400
|
||||
}).component();
|
||||
let formWrapper = view.modelBuilder.loadingComponent().withItem(formModel).component();
|
||||
formWrapper.loading = false;
|
||||
customButton2.onClick(() => {
|
||||
formWrapper.loading = true;
|
||||
setTimeout(() => formWrapper.loading = false, 5000);
|
||||
});
|
||||
await view.initializeModel(formWrapper);
|
||||
await this.getTabContent(view, customButton1, customButton2, 400);
|
||||
});
|
||||
|
||||
tab3.registerContent(async (view) => {
|
||||
await this.getTab3Content(view);
|
||||
});
|
||||
sqlops.window.modelviewdialog.openDialog(dialog);
|
||||
}
|
||||
|
||||
private openWizard(): void {
|
||||
let wizard = sqlops.window.modelviewdialog.createWizard('Test wizard');
|
||||
let page1 = sqlops.window.modelviewdialog.createWizardPage('First wizard page');
|
||||
let page2 = sqlops.window.modelviewdialog.createWizardPage('Second wizard page');
|
||||
page2.content = 'sqlservices';
|
||||
let customButton1 = sqlops.window.modelviewdialog.createButton('Load name');
|
||||
customButton1.onClick(() => console.log('button 1 clicked!'));
|
||||
let customButton2 = sqlops.window.modelviewdialog.createButton('Load all');
|
||||
customButton2.onClick(() => console.log('button 2 clicked!'));
|
||||
wizard.customButtons = [customButton1, customButton2];
|
||||
page1.registerContent(async (view) => {
|
||||
await this.getTabContent(view, customButton1, customButton2, 800);
|
||||
});
|
||||
|
||||
wizard.registerOperation({
|
||||
displayName: 'test task',
|
||||
description: 'task description',
|
||||
isCancelable: true,
|
||||
connection: undefined,
|
||||
operation: op => {
|
||||
op.updateStatus(sqlops.TaskStatus.InProgress);
|
||||
op.updateStatus(sqlops.TaskStatus.InProgress, 'Task is running');
|
||||
setTimeout(() => {
|
||||
op.updateStatus(sqlops.TaskStatus.Succeeded);
|
||||
}, 5000);
|
||||
}
|
||||
});
|
||||
wizard.pages = [page1, page2];
|
||||
wizard.open();
|
||||
}
|
||||
|
||||
private openEditor(): void {
|
||||
let editor = sqlops.workspace.createModelViewEditor('Test Model View');
|
||||
editor.registerContent(async view => {
|
||||
@@ -319,15 +441,32 @@ export default class MainController implements vscode.Disposable {
|
||||
webview2.message = count;
|
||||
});
|
||||
|
||||
let flexModel = view.modelBuilder.flexContainer()
|
||||
.withLayout({
|
||||
flexFlow: 'column',
|
||||
alignItems: 'flex-start',
|
||||
height: 500
|
||||
}).withItems([
|
||||
webview1, webview2
|
||||
], { flex: '1 1 50%' })
|
||||
let editor1 = view.modelBuilder.editor()
|
||||
.withProperties({
|
||||
content: 'select * from sys.tables'
|
||||
})
|
||||
.component();
|
||||
|
||||
let editor2 = view.modelBuilder.editor()
|
||||
.withProperties({
|
||||
content: 'print("Hello World !")',
|
||||
languageMode: 'python'
|
||||
})
|
||||
.component();
|
||||
|
||||
let flexModel = view.modelBuilder.flexContainer().component();
|
||||
flexModel.addItem(editor1, { flex: '1' });
|
||||
flexModel.addItem(editor2, { flex: '1' });
|
||||
flexModel.setLayout({
|
||||
flexFlow: 'column',
|
||||
alignItems: 'stretch',
|
||||
height: '100%'
|
||||
});
|
||||
|
||||
view.onClosed((params) => {
|
||||
vscode.window.showInformationMessage('editor1: language: ' + editor1.languageMode + ' Content1: ' + editor1.content);
|
||||
vscode.window.showInformationMessage('editor2: language: ' + editor2.languageMode + ' Content2: ' + editor2.content);
|
||||
});
|
||||
await view.initializeModel(flexModel);
|
||||
});
|
||||
editor.openEditor();
|
||||
@@ -354,13 +493,14 @@ export default class MainController implements vscode.Disposable {
|
||||
let monitorLightPath = vscode.Uri.file(path.join(__dirname, '..', 'media', 'monitor.svg'));
|
||||
let monitorIcon = {
|
||||
light: monitorLightPath,
|
||||
dark: path.join(__dirname, '..', 'media', 'monitor_inverse.svg') };
|
||||
dark: path.join(__dirname, '..', 'media', 'monitor_inverse.svg')
|
||||
};
|
||||
|
||||
let monitorButton = view.modelBuilder.button()
|
||||
.withProperties({
|
||||
label: 'Monitor',
|
||||
iconPath: monitorIcon
|
||||
}).component();
|
||||
.withProperties({
|
||||
label: 'Monitor',
|
||||
iconPath: monitorIcon
|
||||
}).component();
|
||||
let toolbarModel = view.modelBuilder.toolbarContainer()
|
||||
.withToolbarItems([{
|
||||
component: inputBox,
|
||||
|
||||
303
samples/sqlservices/src/controllers/treeDataProvider.ts
Normal file
303
samples/sqlservices/src/controllers/treeDataProvider.ts
Normal file
@@ -0,0 +1,303 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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';
|
||||
import * as sqlops from 'sqlops';
|
||||
import * as path from 'path';
|
||||
|
||||
export enum TreeCheckboxState {
|
||||
Intermediate = 0,
|
||||
Checked = 1,
|
||||
Unchecked = 2
|
||||
}
|
||||
|
||||
export interface TreeComponentDataModel {
|
||||
label?: string;
|
||||
children?: TreeComponentDataModel[];
|
||||
id?: string;
|
||||
checked?: boolean;
|
||||
}
|
||||
|
||||
export class TreeNode implements sqlops.TreeComponentItem {
|
||||
private _onNodeChange = new vscode.EventEmitter<void>();
|
||||
private _onTreeChange = new vscode.EventEmitter<TreeNode>();
|
||||
private _data: TreeComponentDataModel;
|
||||
private _parent?: TreeNode;
|
||||
private _root: TreeNode;
|
||||
private _isAlwaysLeaf: boolean;
|
||||
private _nodeMap: Map<string, TreeNode>;
|
||||
private _children: TreeNode[];
|
||||
|
||||
public readonly onNodeChange: vscode.Event<void> = this._onNodeChange.event;
|
||||
public readonly onTreeChange: vscode.Event<TreeNode> = this._onTreeChange.event;
|
||||
|
||||
|
||||
/**
|
||||
* Creates new instance of tree node
|
||||
* @param data the underlining data that's bind to the tree node, any change in the tree will affect the same node in data
|
||||
* @param root the root node of the tree. If passed null, the current node will be the root
|
||||
*/
|
||||
constructor(data: TreeComponentDataModel, root: TreeNode) {
|
||||
if (!data) {
|
||||
throw new Error(`Invalid tree node data`);
|
||||
}
|
||||
if (root === undefined) {
|
||||
root = this;
|
||||
root._nodeMap = new Map<string, TreeNode>();
|
||||
}
|
||||
|
||||
this._root = root;
|
||||
if (this.findNode(data.id)) {
|
||||
throw new Error(`tree node with id: '${data.id}' already exists`);
|
||||
}
|
||||
this._data = data;
|
||||
}
|
||||
|
||||
/**
|
||||
* id for TreeNode
|
||||
*/
|
||||
public get id(): string {
|
||||
return this.data.id;
|
||||
}
|
||||
|
||||
public set id(value: string) {
|
||||
this.data.id = value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Label to display to the user, describing this node
|
||||
*/
|
||||
public set label(value: string) {
|
||||
this.data.label = value;
|
||||
}
|
||||
|
||||
public get collapsibleState(): vscode.TreeItemCollapsibleState {
|
||||
if (!this._isAlwaysLeaf) {
|
||||
return vscode.TreeItemCollapsibleState.Expanded;
|
||||
} else {
|
||||
vscode.TreeItemCollapsibleState.None;
|
||||
}
|
||||
}
|
||||
|
||||
public get label(): string {
|
||||
return this.data.label;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this a leaf node (in which case no children can be generated) or is it expandable?
|
||||
*/
|
||||
public get isAlwaysLeaf(): boolean {
|
||||
return this._isAlwaysLeaf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parent of this node
|
||||
*/
|
||||
public get parent(): TreeNode {
|
||||
return this._parent;
|
||||
}
|
||||
|
||||
public get root(): TreeNode {
|
||||
return this._root;
|
||||
}
|
||||
|
||||
/**
|
||||
* Path identifying this node
|
||||
*/
|
||||
public get nodePath(): string {
|
||||
return `${this.parent ? this.parent.nodePath + '-' : ''}${this.id}`;
|
||||
}
|
||||
|
||||
public get data(): TreeComponentDataModel {
|
||||
if (this._data === undefined) {
|
||||
this._data = {
|
||||
label: undefined
|
||||
};
|
||||
}
|
||||
return this._data;
|
||||
}
|
||||
|
||||
public changeNodeCheckedState(value: boolean, fromParent?: boolean): void {
|
||||
if (value !== this.checked) {
|
||||
if (value !== undefined && this.children) {
|
||||
this.children.forEach(child => {
|
||||
child.changeNodeCheckedState(value, true);
|
||||
});
|
||||
}
|
||||
|
||||
this.checked = value;
|
||||
if (!fromParent && this.parent) {
|
||||
this.parent.refreshState();
|
||||
}
|
||||
|
||||
this.onValueChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public set checked(value: boolean) {
|
||||
this.data.checked = value;
|
||||
}
|
||||
|
||||
public refreshState(): void {
|
||||
if (this.hasChildren) {
|
||||
if (this.children.every(c => c.checked)) {
|
||||
this.changeNodeCheckedState(true);
|
||||
} else if (this.children.every(c => c.checked !== undefined && !c.checked)) {
|
||||
this.changeNodeCheckedState(false);
|
||||
} else {
|
||||
this.changeNodeCheckedState(undefined);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public get hasChildren(): boolean {
|
||||
return this.children !== undefined && this.children.length > 0;
|
||||
}
|
||||
|
||||
public get checked(): boolean {
|
||||
return this.data.checked;
|
||||
}
|
||||
|
||||
private onValueChanged(): void {
|
||||
this._onNodeChange.fire();
|
||||
if (this.root) {
|
||||
this.root._onTreeChange.fire(this);
|
||||
}
|
||||
}
|
||||
|
||||
public get checkboxState(): TreeCheckboxState {
|
||||
if (this.checked === undefined) {
|
||||
return TreeCheckboxState.Intermediate;
|
||||
} else {
|
||||
return this.checked ? TreeCheckboxState.Checked : TreeCheckboxState.Unchecked;
|
||||
}
|
||||
}
|
||||
|
||||
public findNode(id: string): TreeNode {
|
||||
if (this.id === id) {
|
||||
return this;
|
||||
} else if (this.root) {
|
||||
return this.root._nodeMap.has(id) ? this.root._nodeMap.get(id) : undefined;
|
||||
} else {
|
||||
let node: TreeNode;
|
||||
if (this.children) {
|
||||
this.children.forEach(child => {
|
||||
node = child.findNode(id);
|
||||
if (node) {
|
||||
return;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Children of this node
|
||||
*/
|
||||
public get children(): TreeNode[] {
|
||||
return this._children;
|
||||
}
|
||||
|
||||
public addChildNode(node: TreeNode): void {
|
||||
|
||||
if (node) {
|
||||
if (!node.root) {
|
||||
node._root = this.root;
|
||||
}
|
||||
if (!node.parent) {
|
||||
node._parent = this;
|
||||
}
|
||||
if (node.root) {
|
||||
node.root._nodeMap.set(node.id, node);
|
||||
}
|
||||
this._children.push(node);
|
||||
}
|
||||
}
|
||||
|
||||
public static createNode(nodeData: TreeComponentDataModel, parent?: TreeNode, root?: TreeNode): TreeNode {
|
||||
let rootNode = root || (parent !== undefined ? parent.root : undefined);
|
||||
let treeNode = new TreeNode(nodeData, rootNode);
|
||||
|
||||
treeNode._parent = parent;
|
||||
return treeNode;
|
||||
}
|
||||
|
||||
public static createTree(nodeData: TreeComponentDataModel, parent?: TreeNode, root?: TreeNode): TreeNode {
|
||||
if (nodeData) {
|
||||
let treeNode = TreeNode.createNode(nodeData, parent, root);
|
||||
|
||||
if (nodeData.children && nodeData.children.length > 0) {
|
||||
treeNode._isAlwaysLeaf = false;
|
||||
treeNode._children = [];
|
||||
nodeData.children.forEach(childNode => {
|
||||
if (childNode) {
|
||||
let childTreeNode = TreeNode.createTree(childNode, treeNode, root || treeNode.root);
|
||||
treeNode.addChildNode(childTreeNode);
|
||||
}
|
||||
});
|
||||
treeNode.refreshState();
|
||||
} else {
|
||||
treeNode._isAlwaysLeaf = true;
|
||||
}
|
||||
return treeNode;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class TreeDataProvider implements sqlops.TreeComponentDataProvider<TreeNode> {
|
||||
private _onDidChangeTreeData = new vscode.EventEmitter<TreeNode>();
|
||||
constructor(private _root: TreeNode) {
|
||||
if(this._root) {
|
||||
this._root.onTreeChange(node => {
|
||||
this._onDidChangeTreeData.fire(node);
|
||||
});
|
||||
}
|
||||
}
|
||||
onDidChangeTreeData?: vscode.Event<TreeNode | undefined | null> = this._onDidChangeTreeData.event ;
|
||||
|
||||
/**
|
||||
* Get [TreeItem](#TreeItem) representation of the `element`
|
||||
*
|
||||
* @param element The element for which [TreeItem](#TreeItem) representation is asked for.
|
||||
* @return [TreeItem](#TreeItem) representation of the element
|
||||
*/
|
||||
getTreeItem(element: TreeNode): sqlops.TreeComponentItem | Thenable<sqlops.TreeComponentItem> {
|
||||
let item: sqlops.TreeComponentItem = {};
|
||||
item.label = element.label;
|
||||
item.checked = element.checked;
|
||||
item.collapsibleState = element.collapsibleState;
|
||||
item.iconPath = vscode.Uri.file(path.join(__dirname, '..', 'media', 'monitor.svg'));
|
||||
return item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the children of `element` or root if no element is passed.
|
||||
*
|
||||
* @param element The element from which the provider gets children. Can be `undefined`.
|
||||
* @return Children of `element` or root if no element is passed.
|
||||
*/
|
||||
getChildren(element?: TreeNode): vscode.ProviderResult<TreeNode[]> {
|
||||
if (element) {
|
||||
return Promise.resolve(element.children);
|
||||
} else {
|
||||
return Promise.resolve(this._root.children);
|
||||
}
|
||||
}
|
||||
|
||||
getParent(element?: TreeNode): vscode.ProviderResult<TreeNode> {
|
||||
if (element) {
|
||||
return Promise.resolve(element.parent);
|
||||
} else {
|
||||
return Promise.resolve(this._root);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -11,7 +11,7 @@ export class ToggleDropdownAction extends Action {
|
||||
private static readonly ID = 'dropdownAction.toggle';
|
||||
private static readonly ICON = 'dropdown-arrow';
|
||||
|
||||
constructor(label, private _fn: () => any) {
|
||||
constructor(private _fn: () => any, label: string) {
|
||||
super(ToggleDropdownAction.ID, label, ToggleDropdownAction.ICON);
|
||||
}
|
||||
|
||||
|
||||
@@ -53,6 +53,10 @@ export interface IDropdownOptions extends IDropdownStyles {
|
||||
* Value to use as aria-label for the input box
|
||||
*/
|
||||
ariaLabel?: string;
|
||||
/**
|
||||
* Label for the dropdown action
|
||||
*/
|
||||
actionLabel: string;
|
||||
}
|
||||
|
||||
export interface IDropdownStyles {
|
||||
@@ -66,7 +70,8 @@ const defaults: IDropdownOptions = {
|
||||
strictSelection: true,
|
||||
maxHeight: 300,
|
||||
errorMessage: errorMessage,
|
||||
contextBorder: Color.fromHex('#696969')
|
||||
contextBorder: Color.fromHex('#696969'),
|
||||
actionLabel: nls.localize('dropdownAction.toggle', "Toggle dropdown")
|
||||
};
|
||||
|
||||
interface ListResource {
|
||||
@@ -115,11 +120,11 @@ export class Dropdown extends Disposable {
|
||||
this.$input = $('.dropdown-input').style('width', '100%').appendTo(this.$el);
|
||||
this.$treeContainer = $('.dropdown-tree');
|
||||
|
||||
this._toggleAction = new ToggleDropdownAction(nls.localize('dropdown.toggle', '{0} Toggle Dropdown', this._options.ariaLabel), () => {
|
||||
this._toggleAction = new ToggleDropdownAction(() => {
|
||||
this._showList();
|
||||
this._tree.domFocus();
|
||||
this._tree.focusFirst();
|
||||
});
|
||||
}, opt.actionLabel);
|
||||
|
||||
this._input = new InputBox(this.$input.getHTMLElement(), contextViewService, {
|
||||
validationOptions: {
|
||||
@@ -228,8 +233,8 @@ export class Dropdown extends Disposable {
|
||||
this._layoutTree();
|
||||
return { dispose: () => { } };
|
||||
},
|
||||
onDOMEvent: (e, activeElement) => {
|
||||
if (!DOM.isAncestor(activeElement, this.$el.getHTMLElement()) && !DOM.isAncestor(activeElement, this.$treeContainer.getHTMLElement())) {
|
||||
onDOMEvent: e => {
|
||||
if (!DOM.isAncestor(e.srcElement, this.$el.getHTMLElement()) && !DOM.isAncestor(e.srcElement, this.$treeContainer.getHTMLElement())) {
|
||||
this._input.validate();
|
||||
this._onBlur.fire();
|
||||
this._contextView.hide();
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 {
|
||||
Component, Inject, forwardRef, ElementRef, OnInit, Input,
|
||||
Output, OnChanges, SimpleChanges, EventEmitter
|
||||
} from '@angular/core';
|
||||
|
||||
import { Dropdown, IDropdownOptions } from 'sql/base/browser/ui/editableDropdown/dropdown';
|
||||
import { AngularDisposable } from 'sql/base/common/lifecycle';
|
||||
|
||||
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { attachEditableDropdownStyler } from 'sql/common/theme/styler';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
|
||||
@Component({
|
||||
selector: 'editable-select-box',
|
||||
template: ''
|
||||
})
|
||||
export class EditableDropDown extends AngularDisposable implements OnInit, OnChanges {
|
||||
private _selectbox: Dropdown;
|
||||
|
||||
@Input() options: string[];
|
||||
@Input() selectedOption: string;
|
||||
@Input() onlyEmitOnChange = false;
|
||||
|
||||
@Output() onDidSelect = new EventEmitter<string>();
|
||||
|
||||
private _previousVal: string;
|
||||
|
||||
constructor(
|
||||
@Inject(forwardRef(() => ElementRef)) private _el: ElementRef,
|
||||
@Inject(IThemeService) private themeService: IThemeService,
|
||||
@Inject(IContextViewService) private contextViewService: IContextViewService
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
let dropdownOptions: IDropdownOptions = {
|
||||
values: [],
|
||||
strictSelection: false,
|
||||
placeholder: '',
|
||||
maxHeight: 125,
|
||||
ariaLabel: '',
|
||||
actionLabel: ''
|
||||
};
|
||||
this._selectbox = new Dropdown(this._el.nativeElement, this.contextViewService, this.themeService, dropdownOptions);
|
||||
this._selectbox.values = this.options;
|
||||
this._selectbox.value = this.selectedOption;
|
||||
|
||||
this._selectbox.onValueChange(e => {
|
||||
if (this.onlyEmitOnChange) {
|
||||
if (this._previousVal !== e) {
|
||||
this.onDidSelect.emit(e);
|
||||
this._previousVal = e;
|
||||
}
|
||||
} else {
|
||||
this.onDidSelect.emit(e);
|
||||
}
|
||||
});
|
||||
this._register(attachEditableDropdownStyler(this._selectbox, this.themeService));
|
||||
}
|
||||
|
||||
ngOnChanges(changes: SimpleChanges): void {
|
||||
}
|
||||
|
||||
public get value(): string {
|
||||
return this._selectbox.value;
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
'use strict';
|
||||
import { InputBox as vsInputBox, IInputOptions, IInputBoxStyles as vsIInputBoxStyles } from 'vs/base/browser/ui/inputbox/inputBox';
|
||||
import { InputBox as vsInputBox, IInputOptions, IInputBoxStyles as vsIInputBoxStyles, IMessage } from 'vs/base/browser/ui/inputbox/inputBox';
|
||||
import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
@@ -33,6 +33,7 @@ export class InputBox extends vsInputBox {
|
||||
public onLoseFocus: Event<OnLoseFocusParams> = this._onLoseFocus.event;
|
||||
|
||||
private _isTextAreaInput: boolean;
|
||||
private _hideErrors = false;
|
||||
|
||||
constructor(container: HTMLElement, contextViewProvider: IContextViewProvider, options?: IInputOptions) {
|
||||
super(container, contextViewProvider, options);
|
||||
@@ -62,13 +63,13 @@ export class InputBox extends vsInputBox {
|
||||
this.enabledInputBorder = this.inputBorder;
|
||||
this.disabledInputBackground = styles.disabledInputBackground;
|
||||
this.disabledInputForeground = styles.disabledInputForeground;
|
||||
this.updateInputEnabledDisabledColors();
|
||||
this.applyStyles();
|
||||
}
|
||||
|
||||
public enable(): void {
|
||||
super.enable();
|
||||
this.inputBackground = this.enabledInputBackground;
|
||||
this.inputForeground = this.enabledInputForeground;
|
||||
this.inputBorder = this.enabledInputBorder;
|
||||
this.updateInputEnabledDisabledColors();
|
||||
this.applyStyles();
|
||||
}
|
||||
|
||||
@@ -88,9 +89,7 @@ export class InputBox extends vsInputBox {
|
||||
|
||||
public disable(): void {
|
||||
super.disable();
|
||||
this.inputBackground = this.disabledInputBackground;
|
||||
this.inputForeground = this.disabledInputForeground;
|
||||
this.inputBorder = this.disabledInputBorder;
|
||||
this.updateInputEnabledDisabledColors();
|
||||
this.applyStyles();
|
||||
}
|
||||
|
||||
@@ -103,4 +102,28 @@ export class InputBox extends vsInputBox {
|
||||
public isEnabled(): boolean {
|
||||
return !this.inputElement.hasAttribute('disabled');
|
||||
}
|
||||
|
||||
public get hideErrors(): boolean {
|
||||
return this._hideErrors;
|
||||
}
|
||||
|
||||
public set hideErrors(hideErrors: boolean) {
|
||||
this._hideErrors = hideErrors;
|
||||
if (hideErrors) {
|
||||
this.hideMessage();
|
||||
}
|
||||
}
|
||||
|
||||
public showMessage(message: IMessage, force?: boolean): void {
|
||||
if (!this.hideErrors) {
|
||||
super.showMessage(message, force);
|
||||
}
|
||||
}
|
||||
|
||||
private updateInputEnabledDisabledColors(): void {
|
||||
let enabled = this.isEnabled();
|
||||
this.inputBackground = enabled ? this.enabledInputBackground : this.disabledInputBackground;
|
||||
this.inputForeground = enabled ? this.enabledInputForeground : this.disabledInputForeground;
|
||||
this.inputBorder = enabled ? this.enabledInputBorder : this.disabledInputBorder;
|
||||
}
|
||||
}
|
||||
@@ -17,7 +17,7 @@ export function appendRow(container: Builder, label: string, labelClass: string,
|
||||
container.element('tr', {}, (rowContainer) => {
|
||||
rowContainer.element('td', { class: labelClass }, (labelCellContainer) => {
|
||||
labelCellContainer.div({}, (labelContainer) => {
|
||||
labelContainer.innerHtml(label);
|
||||
labelContainer.text(label);
|
||||
});
|
||||
});
|
||||
rowContainer.element('td', { class: cellContainerClass }, (inputCellContainer) => {
|
||||
@@ -33,7 +33,7 @@ export function appendRowLink(container: Builder, label: string, labelClass: str
|
||||
container.element('tr', {}, (rowContainer) => {
|
||||
rowContainer.element('td', { class: labelClass }, (labelCellContainer) => {
|
||||
labelCellContainer.div({}, (labelContainer) => {
|
||||
labelContainer.innerHtml(label);
|
||||
labelContainer.text(label);
|
||||
});
|
||||
});
|
||||
rowContainer.element('td', { class: cellContainerClass }, (inputCellContainer) => {
|
||||
|
||||
@@ -172,4 +172,22 @@
|
||||
margin-right: 10px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.modal .modal-footer .dialogErrorMessage {
|
||||
align-items: center;
|
||||
max-height: 30px;
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.modal .dialogErrorMessage .icon {
|
||||
float: left;
|
||||
margin-right: 10px;
|
||||
width: auto;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
.modal .modal-footer .dialogErrorMessage .errorMessage {
|
||||
max-height: 100%;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
@@ -21,9 +21,13 @@ import { Button } from 'sql/base/browser/ui/button/button';
|
||||
import * as TelemetryUtils from 'sql/common/telemetryUtilities';
|
||||
import * as TelemetryKeys from 'sql/common/telemetryKeys';
|
||||
import { localize } from 'vs/nls';
|
||||
import { MessageLevel } from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||
|
||||
export const MODAL_SHOWING_KEY = 'modalShowing';
|
||||
export const MODAL_SHOWING_CONTEXT = new RawContextKey<Array<string>>(MODAL_SHOWING_KEY, []);
|
||||
const INFO_ALT_TEXT = localize('infoAltText', 'Info');
|
||||
const WARNING_ALT_TEXT = localize('warningAltText', 'Warning');
|
||||
const ERROR_ALT_TEXT = localize('errorAltText', 'Error');
|
||||
|
||||
export interface IModalDialogStyles {
|
||||
dialogForeground?: Color;
|
||||
@@ -145,7 +149,7 @@ export abstract class Modal extends Disposable implements IThemable {
|
||||
/**
|
||||
* Build and render the modal, will call {@link Modal#renderBody}
|
||||
*/
|
||||
public render() {
|
||||
public render(errorMessagesInFooter: boolean = false) {
|
||||
let modalBodyClass = (this._modalOptions.isAngular === false ? 'modal-body' : 'modal-body-and-footer');
|
||||
let parts: Array<HTMLElement> = [];
|
||||
// This modal header section refers to the header of of the dialog
|
||||
@@ -166,7 +170,7 @@ export abstract class Modal extends Disposable implements IThemable {
|
||||
}
|
||||
modalHeader.div({ class: 'modal-title' }, (modalTitle) => {
|
||||
this._modalTitle = modalTitle;
|
||||
modalTitle.innerHtml(this._title);
|
||||
modalTitle.text(this._title);
|
||||
});
|
||||
});
|
||||
parts.push(this._modalHeaderSection.getHTMLElement());
|
||||
@@ -182,17 +186,6 @@ export abstract class Modal extends Disposable implements IThemable {
|
||||
|
||||
this.renderBody(body.getHTMLElement());
|
||||
|
||||
if (this._modalOptions.isAngular === false && this._modalOptions.hasErrors) {
|
||||
body.div({ class: 'dialogErrorMessage', id: 'dialogErrorMessage' }, (errorMessageContainer) => {
|
||||
errorMessageContainer.div({ class: 'icon error' }, (iconContainer) => {
|
||||
this._errorIconElement = iconContainer.getHTMLElement();
|
||||
this._errorIconElement.style.visibility = 'hidden';
|
||||
});
|
||||
errorMessageContainer.div({ class: 'errorMessage' }, (messageContainer) => {
|
||||
this._errorMessage = messageContainer;
|
||||
});
|
||||
});
|
||||
}
|
||||
// This modal footer section refers to the footer of of the dialog
|
||||
if (this._modalOptions.isAngular === false) {
|
||||
this._modalFooterSection = $().div({ class: 'modal-footer' }, (modelFooter) => {
|
||||
@@ -221,8 +214,21 @@ export abstract class Modal extends Disposable implements IThemable {
|
||||
builderClass += ' wide';
|
||||
}
|
||||
|
||||
if (this._modalOptions.isAngular === false && this._modalOptions.hasErrors) {
|
||||
let builder = errorMessagesInFooter ? this._leftFooter : body;
|
||||
builder.div({ class: 'dialogErrorMessage', id: 'dialogErrorMessage' }, (errorMessageContainer) => {
|
||||
errorMessageContainer.div({ class: 'icon error' }, (iconContainer) => {
|
||||
this._errorIconElement = iconContainer.getHTMLElement();
|
||||
this._errorIconElement.style.visibility = 'hidden';
|
||||
});
|
||||
errorMessageContainer.div({ class: 'errorMessage' }, (messageContainer) => {
|
||||
this._errorMessage = messageContainer;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// The builder builds the dialog. It append header, body and footer sections.
|
||||
this._builder = $().div({ class: builderClass, 'role': 'dialog' }, (dialogContainer) => {
|
||||
this._builder = $().div({ class: builderClass, 'role': 'dialog', 'aria-label': this._title }, (dialogContainer) => {
|
||||
this._modalDialog = dialogContainer.div({ class: 'modal-dialog ', role: 'document' }, (modalDialog) => {
|
||||
modalDialog.div({ class: 'modal-content' }, (modelContent) => {
|
||||
parts.forEach((part) => {
|
||||
@@ -351,18 +357,52 @@ export abstract class Modal extends Disposable implements IThemable {
|
||||
return button;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a footer button matching the provided label
|
||||
* @param label Label to show on the button
|
||||
* @param onSelect The callback to call when the button is selected
|
||||
*/
|
||||
protected findFooterButton(label: string): Button {
|
||||
return this._footerButtons.find(e => {
|
||||
try {
|
||||
return e && e.element.innerText === label;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Show an error in the error message element
|
||||
* @param err Text to show in the error message
|
||||
*/
|
||||
protected setError(err: string) {
|
||||
protected setError(err: string, level: MessageLevel = MessageLevel.Error) {
|
||||
if (this._modalOptions.hasErrors) {
|
||||
if (err === '') {
|
||||
this._errorIconElement.style.visibility = 'hidden';
|
||||
} else {
|
||||
const levelClasses = ['info', 'warning', 'error'];
|
||||
let selectedLevel = levelClasses[2];
|
||||
let altText = ERROR_ALT_TEXT;
|
||||
if (level === MessageLevel.Information) {
|
||||
selectedLevel = levelClasses[0];
|
||||
altText = INFO_ALT_TEXT;
|
||||
} else if (level === MessageLevel.Warning) {
|
||||
selectedLevel = levelClasses[1];
|
||||
altText = WARNING_ALT_TEXT;
|
||||
}
|
||||
levelClasses.forEach(level => {
|
||||
if (selectedLevel === level) {
|
||||
this._errorIconElement.classList.add(level);
|
||||
} else {
|
||||
this._errorIconElement.classList.remove(level);
|
||||
}
|
||||
});
|
||||
|
||||
this._errorIconElement.title = altText;
|
||||
this._errorIconElement.style.visibility = 'visible';
|
||||
}
|
||||
this._errorMessage.innerHtml(err);
|
||||
this._errorMessage.text(err);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -408,7 +448,7 @@ export abstract class Modal extends Disposable implements IThemable {
|
||||
*/
|
||||
protected set title(title: string) {
|
||||
if (this._title !== undefined) {
|
||||
this._modalTitle.innerHtml(title);
|
||||
this._modalTitle.text(title);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -150,8 +150,8 @@ export class OptionsDialog extends Modal {
|
||||
|
||||
private onOptionLinkClicked(optionName: string): void {
|
||||
var option = this._optionElements[optionName].option;
|
||||
this._optionTitle.innerHtml(option.displayName);
|
||||
this._optionDescription.innerHtml(option.description);
|
||||
this._optionTitle.text(option.displayName);
|
||||
this._optionDescription.text(option.description);
|
||||
}
|
||||
|
||||
private fillInOptions(container: Builder, options: sqlops.ServiceOption[]): void {
|
||||
|
||||
@@ -5,8 +5,9 @@
|
||||
import { Component, Input, ContentChild, OnDestroy, TemplateRef, ChangeDetectorRef, forwardRef, Inject } from '@angular/core';
|
||||
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
|
||||
export abstract class TabChild {
|
||||
export abstract class TabChild extends Disposable {
|
||||
public abstract layout(): void;
|
||||
}
|
||||
|
||||
@@ -19,7 +20,7 @@ export abstract class TabChild {
|
||||
`
|
||||
})
|
||||
export class TabComponent implements OnDestroy {
|
||||
@ContentChild(TabChild) private _child: TabChild;
|
||||
private _child: TabChild;
|
||||
@ContentChild(TemplateRef) templateRef;
|
||||
@Input() public title: string;
|
||||
@Input() public canClose: boolean;
|
||||
@@ -29,19 +30,30 @@ export class TabComponent implements OnDestroy {
|
||||
@Input() public identifier: string;
|
||||
@Input() private visibilityType: 'if' | 'visibility' = 'if';
|
||||
private rendered = false;
|
||||
private destroyed: boolean = false;
|
||||
|
||||
|
||||
@ContentChild(TabChild) private set child(tab: TabChild) {
|
||||
this._child = tab;
|
||||
if (this.active && this._child) {
|
||||
this._child.layout();
|
||||
}
|
||||
}
|
||||
|
||||
constructor(
|
||||
@Inject(forwardRef(() => ChangeDetectorRef)) private _cd: ChangeDetectorRef
|
||||
) { }
|
||||
|
||||
public set active(val: boolean) {
|
||||
this._active = val;
|
||||
if (this.active) {
|
||||
this.rendered = true;
|
||||
}
|
||||
this._cd.detectChanges();
|
||||
if (this.active && this._child) {
|
||||
this._child.layout();
|
||||
if (!this.destroyed) {
|
||||
this._active = val;
|
||||
if (this.active) {
|
||||
this.rendered = true;
|
||||
}
|
||||
this._cd.detectChanges();
|
||||
if (this.active && this._child) {
|
||||
this._child.layout();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,6 +62,7 @@ export class TabComponent implements OnDestroy {
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.destroyed = true;
|
||||
if (this.actions && this.actions.length > 0) {
|
||||
this.actions.forEach((action) => action.dispose());
|
||||
}
|
||||
@@ -74,6 +87,8 @@ export class TabComponent implements OnDestroy {
|
||||
}
|
||||
|
||||
public layout() {
|
||||
this._child.layout();
|
||||
if (this._child) {
|
||||
this._child.layout();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ export class TabHeaderComponent extends Disposable implements AfterContentInit,
|
||||
tabLabelcontainer.classList.add(this.tab.iconClass);
|
||||
} else {
|
||||
tabLabelcontainer.className = 'tabLabel';
|
||||
tabLabelcontainer.innerHTML = this.tab.title;
|
||||
tabLabelcontainer.textContent = this.tab.title;
|
||||
}
|
||||
tabLabelcontainer.title = this.tab.title;
|
||||
}
|
||||
|
||||
@@ -84,6 +84,7 @@ export class SelectBox extends vsSelectBox {
|
||||
this.inputValidationWarningBackground = styles.inputValidationWarningBackground;
|
||||
this.inputValidationErrorBorder = styles.inputValidationErrorBorder;
|
||||
this.inputValidationErrorBackground = styles.inputValidationErrorBackground;
|
||||
this.applyStyles();
|
||||
}
|
||||
|
||||
public selectWithOptionName(optionName: string): void {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user