mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-17 02:51:36 -05:00
Compare commits
80 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 |
@@ -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;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "agent",
|
||||
"displayName": "SQL Server Agent",
|
||||
"description": "Manage and troubleshoot SQL Server Agent jobs",
|
||||
"version": "0.31.4",
|
||||
"version": "0.32.4",
|
||||
"publisher": "Microsoft",
|
||||
"preview": true,
|
||||
"license": "https://raw.githubusercontent.com/Microsoft/sqlopsstudio/master/LICENSE.txt",
|
||||
|
||||
@@ -143,13 +143,16 @@ export class JobStepDialog {
|
||||
width: '80px',
|
||||
isFile: true
|
||||
}).component();
|
||||
this.openButton.enabled = false;
|
||||
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 => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
|
||||
|
||||
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"
|
||||
@@ -658,17 +658,11 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"dataprotocol-client": "github:Microsoft/sqlops-dataprotocolclient#0.2.0",
|
||||
"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.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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"downloadUrl": "https://github.com/Microsoft/sqltoolsservice/releases/download/v{#version#}/microsoft.sqltools.servicelayer-{#fileName#}",
|
||||
"version": "1.5.0-alpha.12",
|
||||
"version": "1.5.0-alpha.27",
|
||||
"downloadFileNames": {
|
||||
"Windows_86": "win-x86-netcoreapp2.1.zip",
|
||||
"Windows_64": "win-x64-netcoreapp2.1.zip",
|
||||
|
||||
@@ -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.2.0":
|
||||
version "0.2.0"
|
||||
resolved "https://codeload.github.com/Microsoft/sqlops-dataprotocolclient/tar.gz/c7a691c7fc5ad54a147090784a7f11efda301f4b"
|
||||
"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"
|
||||
@@ -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.2",
|
||||
"version": "0.1.3",
|
||||
"publisher": "Microsoft",
|
||||
"preview": true,
|
||||
"license": "https://raw.githubusercontent.com/Microsoft/sqlopsstudio/master/LICENSE.txt",
|
||||
@@ -27,7 +27,7 @@
|
||||
],
|
||||
"contributes": {
|
||||
|
||||
"commands": [
|
||||
"commands": [
|
||||
{
|
||||
"command": "profiler.newProfiler",
|
||||
"title": "New Profiler",
|
||||
@@ -42,12 +42,20 @@
|
||||
"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"
|
||||
|
||||
12
package.json
12
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "sqlops",
|
||||
"version": "0.32.1",
|
||||
"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": "github:Microsoft/angular2-slickgrid#1.3.12",
|
||||
"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",
|
||||
@@ -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": {
|
||||
|
||||
@@ -21,6 +21,10 @@
|
||||
"command": "sqlservices.openDialog",
|
||||
"title": "sqlservices.openDialog"
|
||||
},
|
||||
{
|
||||
"command": "sqlservices.openConnectionDialog",
|
||||
"title": "sqlservices.openConnectionDialog"
|
||||
},
|
||||
{
|
||||
"command": "sqlservices.openEditor",
|
||||
"title": "sqlservices.openEditor"
|
||||
@@ -100,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();
|
||||
});
|
||||
@@ -67,6 +75,77 @@ export default class MainController implements vscode.Disposable {
|
||||
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({
|
||||
@@ -88,7 +167,7 @@ export default class MainController implements vscode.Disposable {
|
||||
})
|
||||
.component();
|
||||
checkbox.onChanged(e => {
|
||||
console.info("inputBox.enabled " + inputBox.enabled);
|
||||
console.info('inputBox.enabled ' + inputBox.enabled);
|
||||
inputBox.enabled = !inputBox.enabled;
|
||||
});
|
||||
let button = view.modelBuilder.button()
|
||||
@@ -271,8 +350,9 @@ export default class MainController implements vscode.Disposable {
|
||||
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';
|
||||
@@ -285,6 +365,10 @@ export default class MainController implements vscode.Disposable {
|
||||
tab1.registerContent(async (view) => {
|
||||
await this.getTabContent(view, customButton1, customButton2, 400);
|
||||
});
|
||||
|
||||
tab3.registerContent(async (view) => {
|
||||
await this.getTab3Content(view);
|
||||
});
|
||||
sqlops.window.modelviewdialog.openDialog(dialog);
|
||||
}
|
||||
|
||||
@@ -301,16 +385,19 @@ export default class MainController implements vscode.Disposable {
|
||||
page1.registerContent(async (view) => {
|
||||
await this.getTabContent(view, customButton1, customButton2, 800);
|
||||
});
|
||||
|
||||
wizard.registerOperation({
|
||||
displayName: 'test task',
|
||||
description: 'task description',
|
||||
isCancelable: true
|
||||
}, op => {
|
||||
op.updateStatus(sqlops.TaskStatus.InProgress);
|
||||
op.updateStatus(sqlops.TaskStatus.InProgress, 'Task is running');
|
||||
setTimeout(() => {
|
||||
op.updateStatus(sqlops.TaskStatus.Succeeded);
|
||||
}, 5000);
|
||||
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();
|
||||
@@ -354,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();
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -233,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;
|
||||
}
|
||||
}
|
||||
@@ -228,7 +228,7 @@ export abstract class Modal extends Disposable implements IThemable {
|
||||
}
|
||||
|
||||
// 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) => {
|
||||
@@ -357,6 +357,21 @@ 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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -63,7 +63,9 @@ export class RowNumberColumn<T> implements Slick.Plugin<T> {
|
||||
|
||||
private formatter(row, cell, value, columnDef: Slick.Column<T>, dataContext): string {
|
||||
if (dataContext) {
|
||||
return `<span>${row}</span>`;
|
||||
// row is zero-based, we need make it 1 based for display in the result grid
|
||||
//
|
||||
return `<span>${row + 1}</span>`;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ export interface IFindPosition {
|
||||
row: number;
|
||||
}
|
||||
|
||||
export function defaultSort<T>(args: Slick.OnSortEventArgs<T>, data: Array<T>): Array<T> {
|
||||
function defaultSort<T>(args: Slick.OnSortEventArgs<T>, data: Array<T>): Array<T> {
|
||||
let field = args.sortCol.field;
|
||||
let sign = args.sortAsc ? 1 : -1;
|
||||
return data.sort((a, b) => (a[field] === b[field] ? 0 : (a[field] > b[field] ? 1 : -1)) * sign);
|
||||
|
||||
1
src/sql/base/browser/ui/taskbar/media/add.svg
Normal file
1
src/sql/base/browser/ui/taskbar/media/add.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#f6f6f6;}.icon-canvas-transparent{opacity:0;}.icon-vs-bg{fill:#424242;}</style></defs><title>add</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,16H0V0H16Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M14,6v4H10v4H6V10H2V6H6V2h4V6Z"/></g><g id="iconBg"><path class="icon-vs-bg" d="M13,7V9H9v4H7V9H3V7H7V3H9V7Z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 486 B |
@@ -18,6 +18,12 @@
|
||||
background-image: url('start.svg');
|
||||
}
|
||||
|
||||
.vs .icon.add,
|
||||
.vs-dark .icon.add,
|
||||
.hc-black .icon.add {
|
||||
background-image: url('add.svg');
|
||||
}
|
||||
|
||||
.vs .icon.stop,
|
||||
.vs-dark .icon.stop,
|
||||
.hc-black .icon.stop {
|
||||
|
||||
@@ -42,7 +42,10 @@ export function resolveFilePath(uri: string, filePath: string, rootPath: string)
|
||||
export function getRootPath(contextService: IWorkspaceContextService): string {
|
||||
let isWorkspace = contextService.getWorkbenchState() === WorkbenchState.WORKSPACE;
|
||||
if (isWorkspace) {
|
||||
return contextService.getWorkspace().folders[0].uri.fsPath;
|
||||
let folder = contextService.getWorkspace().folders[0];
|
||||
if (folder && folder.uri) {
|
||||
return folder.uri.fsPath;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IExtensionGalleryService, IExtensionTipsService } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { IEditorRegistry, Extensions as EditorExtensions } from 'vs/workbench/browser/editor';
|
||||
import { EditorDescriptor, IEditorRegistry, Extensions as EditorExtensions } from 'vs/workbench/browser/editor';
|
||||
import { IExtensionsWorkbenchService } from 'vs/workbench/parts/extensions/common/extensions';
|
||||
import { IConfigurationRegistry, Extensions as ConfigExtensions } from 'vs/platform/configuration/common/configurationRegistry';
|
||||
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||
@@ -13,10 +13,9 @@ import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { DashboardEditor } from 'sql/parts/dashboard/dashboardEditor';
|
||||
import { DashboardInput } from 'sql/parts/dashboard/dashboardInput';
|
||||
import { AddServerGroupAction, AddServerAction } from 'sql/parts/objectExplorer/viewlet/connectionTreeAction';
|
||||
import { ClearRecentConnectionsAction } from 'sql/parts/connection/common/connectionActions';
|
||||
import { ClearRecentConnectionsAction, GetCurrentConnectionStringAction } from 'sql/parts/connection/common/connectionActions';
|
||||
|
||||
import { ExtensionGalleryService } from 'vs/platform/extensionManagement/node/extensionGalleryService';
|
||||
import { EditorDescriptor } from 'vs/workbench/browser/editor';
|
||||
import { ExtensionTipsService } from 'vs/workbench/parts/extensions/electron-browser/extensionTipsService';
|
||||
import { ExtensionsWorkbenchService } from 'vs/workbench/parts/extensions/node/extensionsWorkbenchService';
|
||||
import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions';
|
||||
@@ -49,6 +48,7 @@ actionRegistry.registerWorkbenchAction(
|
||||
),
|
||||
ClearRecentConnectionsAction.LABEL
|
||||
);
|
||||
|
||||
actionRegistry.registerWorkbenchAction(
|
||||
new SyncActionDescriptor(
|
||||
AddServerGroupAction,
|
||||
@@ -67,6 +67,15 @@ actionRegistry.registerWorkbenchAction(
|
||||
AddServerAction.LABEL
|
||||
);
|
||||
|
||||
actionRegistry.registerWorkbenchAction(
|
||||
new SyncActionDescriptor(
|
||||
GetCurrentConnectionStringAction,
|
||||
GetCurrentConnectionStringAction.ID,
|
||||
GetCurrentConnectionStringAction.LABEL
|
||||
),
|
||||
GetCurrentConnectionStringAction.LABEL
|
||||
);
|
||||
|
||||
let configurationRegistry = <IConfigurationRegistry>Registry.as(ConfigExtensions.Configuration);
|
||||
configurationRegistry.registerConfiguration({
|
||||
'id': 'connection',
|
||||
|
||||
@@ -13,6 +13,11 @@ import { IConnectionManagementService } from 'sql/parts/connection/common/connec
|
||||
import { INotificationService, INotificationActions } from 'vs/platform/notification/common/notification';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import { IDialogService, IConfirmation, IConfirmationResult } from 'vs/platform/dialogs/common/dialogs';
|
||||
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IObjectExplorerService } from '../../objectExplorer/common/objectExplorerService';
|
||||
import { QueryInput } from 'sql/parts/query/common/queryInput';
|
||||
import { EditDataInput } from 'sql/parts/editData/common/editDataInput';
|
||||
import { DashboardInput } from 'sql/parts/dashboard/dashboardInput';
|
||||
|
||||
/**
|
||||
* Workbench action to clear the recent connnections list
|
||||
@@ -126,4 +131,44 @@ export class ClearSingleRecentConnectionAction extends Action {
|
||||
this._onRecentConnectionRemoved.fire();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Action to retrieve the current connection string
|
||||
*/
|
||||
export class GetCurrentConnectionStringAction extends Action {
|
||||
|
||||
public static ID = 'getCurrentConnectionStringAction';
|
||||
public static LABEL = nls.localize('connectionAction.GetCurrentConnectionString', "Get Current Connection String");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
|
||||
@IWorkbenchEditorService private _editorService: IWorkbenchEditorService,
|
||||
@IObjectExplorerService private _objectExplorerService: IObjectExplorerService,
|
||||
@INotificationService private readonly _notificationService: INotificationService
|
||||
) {
|
||||
super(GetCurrentConnectionStringAction.ID, GetCurrentConnectionStringAction.LABEL);
|
||||
this.enabled = true;
|
||||
}
|
||||
|
||||
public run(): TPromise<void> {
|
||||
return new TPromise<void>((resolve, reject) => {
|
||||
let activeInput = this._editorService.getActiveEditorInput();
|
||||
if (activeInput && (activeInput instanceof QueryInput || activeInput instanceof EditDataInput || activeInput instanceof DashboardInput)
|
||||
&& this._connectionManagementService.isConnected(activeInput.uri)) {
|
||||
let includePassword = false;
|
||||
this._connectionManagementService.getConnectionString(activeInput.uri, includePassword).then(result => {
|
||||
let message = result
|
||||
? result
|
||||
: nls.localize('connectionAction.connectionString', "Connection string not available");
|
||||
this._notificationService.info(message);
|
||||
});
|
||||
} else {
|
||||
let message = nls.localize('connectionAction.noConnection', "No active connection available");
|
||||
this._notificationService.info(message);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,6 +59,7 @@ export interface IConnectionResult {
|
||||
errorCode: number;
|
||||
callStack: string;
|
||||
errorHandled?: boolean;
|
||||
connectionProfile?: IConnectionProfile;
|
||||
}
|
||||
|
||||
export interface IConnectionCallbacks {
|
||||
@@ -130,15 +131,15 @@ export interface IConnectionManagementService {
|
||||
|
||||
onConnectionChangedNotification(handle: number, changedConnInfo: sqlops.ChangedConnectionInfo);
|
||||
|
||||
getConnectionGroups(): ConnectionProfileGroup[];
|
||||
getConnectionGroups(providers?: string[]): ConnectionProfileGroup[];
|
||||
|
||||
getRecentConnections(): ConnectionProfile[];
|
||||
getRecentConnections(providers?: string[]): ConnectionProfile[];
|
||||
|
||||
clearRecentConnectionsList(): void;
|
||||
|
||||
clearRecentConnection(connectionProfile: IConnectionProfile): void;
|
||||
|
||||
getActiveConnections(): ConnectionProfile[];
|
||||
getActiveConnections(providers?: string[]): ConnectionProfile[];
|
||||
|
||||
saveProfileGroup(profile: IConnectionProfileGroup): Promise<string>;
|
||||
|
||||
@@ -152,10 +153,12 @@ export interface IConnectionManagementService {
|
||||
|
||||
getAdvancedProperties(): sqlops.ConnectionOption[];
|
||||
|
||||
getConnectionId(connectionProfile: IConnectionProfile): string;
|
||||
getConnectionUri(connectionProfile: IConnectionProfile): string;
|
||||
|
||||
getFormattedUri(uri: string, connectionProfile: IConnectionProfile): string;
|
||||
|
||||
getConnectionUriFromId(connectionId: string): string;
|
||||
|
||||
isConnected(fileUri: string): boolean;
|
||||
|
||||
/**
|
||||
@@ -258,12 +261,34 @@ export interface IConnectionManagementService {
|
||||
* in the connection profile's options dictionary, or undefined if the profile is not connected
|
||||
*/
|
||||
getActiveConnectionCredentials(profileId: string): { [name: string]: string };
|
||||
|
||||
/**
|
||||
* Get the connection string for the provided connection profile
|
||||
*/
|
||||
getConnectionString(ownerUri: string, includePassword: boolean): Thenable<string>;
|
||||
}
|
||||
|
||||
export const IConnectionDialogService = createDecorator<IConnectionDialogService>('connectionDialogService');
|
||||
export interface IConnectionDialogService {
|
||||
_serviceBrand: any;
|
||||
/**
|
||||
* Opens the connection dialog and returns the promise for successfully opening the dialog
|
||||
* @param connectionManagementService
|
||||
* @param params
|
||||
* @param model
|
||||
* @param connectionResult
|
||||
*/
|
||||
showDialog(connectionManagementService: IConnectionManagementService, params: INewConnectionParams, model: IConnectionProfile, connectionResult?: IConnectionResult): Thenable<void>;
|
||||
|
||||
/**
|
||||
* Opens the connection dialog and returns the promise when connection is made
|
||||
* or dialog is closed
|
||||
* @param connectionManagementService
|
||||
* @param params
|
||||
* @param model
|
||||
* @param connectionResult
|
||||
*/
|
||||
openDialogAndWait(connectionManagementService: IConnectionManagementService, params?: INewConnectionParams, model?: IConnectionProfile, connectionResult?: IConnectionResult): Thenable<IConnectionProfile>;
|
||||
}
|
||||
|
||||
export interface IServerGroupDialogCallbacks {
|
||||
@@ -297,6 +322,7 @@ export interface INewConnectionParams {
|
||||
runQueryOnCompletion?: RunQueryOnConnectionMode;
|
||||
querySelection?: sqlops.ISelectionData;
|
||||
showDashboard?: boolean;
|
||||
providers?: string[];
|
||||
}
|
||||
|
||||
export interface IConnectableInput {
|
||||
|
||||
@@ -627,12 +627,12 @@ export class ConnectionManagementService extends Disposable implements IConnecti
|
||||
}
|
||||
}
|
||||
|
||||
public getConnectionGroups(): ConnectionProfileGroup[] {
|
||||
return this._connectionStore.getConnectionProfileGroups();
|
||||
public getConnectionGroups(providers?: string[]): ConnectionProfileGroup[] {
|
||||
return this._connectionStore.getConnectionProfileGroups(false, providers);
|
||||
}
|
||||
|
||||
public getRecentConnections(): ConnectionProfile[] {
|
||||
return this._connectionStore.getRecentlyUsedConnections();
|
||||
public getRecentConnections(providers?: string[]): ConnectionProfile[] {
|
||||
return this._connectionStore.getRecentlyUsedConnections(providers);
|
||||
}
|
||||
|
||||
|
||||
@@ -644,10 +644,19 @@ export class ConnectionManagementService extends Disposable implements IConnecti
|
||||
this._connectionStore.removeConnectionToMemento(connectionProfile, Constants.recentConnections);
|
||||
}
|
||||
|
||||
public getActiveConnections(): ConnectionProfile[] {
|
||||
public getActiveConnections(providers?: string[]): ConnectionProfile[] {
|
||||
return this._connectionStatusManager.getActiveConnectionProfiles();
|
||||
}
|
||||
|
||||
public getConnectionUriFromId(connectionId: string): string {
|
||||
let connection = this.getActiveConnections().find(connection => connection.id === connectionId);
|
||||
if (connection) {
|
||||
return this.getConnectionUri(connection);
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
public saveProfileGroup(profile: IConnectionProfileGroup): Promise<string> {
|
||||
TelemetryUtils.addTelemetry(this._telemetryService, TelemetryKeys.AddServerGroup);
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
@@ -704,7 +713,7 @@ export class ConnectionManagementService extends Disposable implements IConnecti
|
||||
return false;
|
||||
}
|
||||
|
||||
public getConnectionId(connectionProfile: IConnectionProfile): string {
|
||||
public getConnectionUri(connectionProfile: IConnectionProfile): string {
|
||||
return this._connectionStatusManager.getOriginalOwnerUri(Utils.generateUri(connectionProfile));
|
||||
}
|
||||
|
||||
@@ -716,7 +725,7 @@ export class ConnectionManagementService extends Disposable implements IConnecti
|
||||
*/
|
||||
public getFormattedUri(uri: string, connectionProfile: IConnectionProfile): string {
|
||||
if (this._connectionStatusManager.isDefaultTypeUri(uri)) {
|
||||
return this.getConnectionId(connectionProfile);
|
||||
return this.getConnectionUri(connectionProfile);
|
||||
} else {
|
||||
return uri;
|
||||
}
|
||||
@@ -993,14 +1002,14 @@ export class ConnectionManagementService extends Disposable implements IConnecti
|
||||
let connectionMngInfo = this._connectionStatusManager.findConnection(uri);
|
||||
if (connectionMngInfo && connectionMngInfo.deleted) {
|
||||
this._connectionStatusManager.deleteConnection(uri);
|
||||
resolve({ connected: connectResult, errorMessage: undefined, errorCode: undefined, callStack: undefined, errorHandled: true });
|
||||
resolve({ connected: connectResult, errorMessage: undefined, errorCode: undefined, callStack: undefined, errorHandled: true, connectionProfile: connection });
|
||||
} else {
|
||||
if (errorMessage) {
|
||||
// Connection to the server failed
|
||||
this._connectionStatusManager.deleteConnection(uri);
|
||||
resolve({ connected: connectResult, errorMessage: errorMessage, errorCode: errorCode, callStack: callStack });
|
||||
resolve({ connected: connectResult, errorMessage: errorMessage, errorCode: errorCode, callStack: callStack, connectionProfile: connection });
|
||||
} else {
|
||||
resolve({ connected: connectResult, errorMessage: errorMessage, errorCode: errorCode, callStack: callStack });
|
||||
resolve({ connected: connectResult, errorMessage: errorMessage, errorCode: errorCode, callStack: callStack, connectionProfile: connection });
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -1111,7 +1120,8 @@ export class ConnectionManagementService extends Disposable implements IConnecti
|
||||
resolve(result);
|
||||
});
|
||||
} else {
|
||||
resolve(self.disconnectEditor(owner));
|
||||
// If the editor is connected then there is nothing to cancel
|
||||
resolve(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1336,4 +1346,24 @@ export class ConnectionManagementService extends Disposable implements IConnecti
|
||||
credentials[passwordOption.name] = profile.options[passwordOption.name];
|
||||
return credentials;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the connection string for the provided connection profile
|
||||
*/
|
||||
public getConnectionString(ownerUri: string, includePassword: boolean = false): Thenable<string> {
|
||||
if (!ownerUri) {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
let providerId = this.getProviderIdFromUri(ownerUri);
|
||||
if (!providerId) {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
return this._providers.get(providerId).onReady.then(provider => {
|
||||
return provider.getConnectionString(ownerUri, includePassword).then(connectionString => {
|
||||
return connectionString;
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -193,9 +193,14 @@ export class ConnectionStatusManager {
|
||||
/**
|
||||
* Get a list of the active connection profiles managed by the status manager
|
||||
*/
|
||||
public getActiveConnectionProfiles(): ConnectionProfile[] {
|
||||
public getActiveConnectionProfiles(providers?: string[]): ConnectionProfile[] {
|
||||
let profiles = Object.values(this._connections).map((connectionInfo: ConnectionManagementInfo) => connectionInfo.connectionProfile);
|
||||
// Remove duplicate profiles that may be listed multiple times under different URIs by filtering for profiles that don't have the same ID as an earlier profile in the list
|
||||
return profiles.filter((profile, index) => profiles.findIndex(otherProfile => otherProfile.id === profile.id) === index);
|
||||
profiles = profiles.filter((profile, index) => profiles.findIndex(otherProfile => otherProfile.id === profile.id) === index);
|
||||
|
||||
if (providers) {
|
||||
profiles = profiles.filter(f => providers.includes(f.providerName));
|
||||
}
|
||||
return profiles;
|
||||
}
|
||||
}
|
||||
@@ -213,13 +213,16 @@ export class ConnectionStore {
|
||||
*
|
||||
* @returns {sqlops.ConnectionInfo} the array of connections, empty if none are found
|
||||
*/
|
||||
public getRecentlyUsedConnections(): ConnectionProfile[] {
|
||||
public getRecentlyUsedConnections(providers?: string[]): ConnectionProfile[] {
|
||||
let configValues: IConnectionProfile[] = this._memento[Constants.recentConnections];
|
||||
if (!configValues) {
|
||||
configValues = [];
|
||||
}
|
||||
|
||||
configValues = configValues.filter(c => !!(c));
|
||||
if (providers && providers.length > 0) {
|
||||
configValues = configValues.filter(c => providers.includes(c.providerName));
|
||||
}
|
||||
return this.convertConfigValuesToConnectionProfiles(configValues);
|
||||
}
|
||||
|
||||
@@ -429,10 +432,13 @@ export class ConnectionStore {
|
||||
});
|
||||
}
|
||||
|
||||
public getConnectionProfileGroups(withoutConnections?: boolean): ConnectionProfileGroup[] {
|
||||
public getConnectionProfileGroups(withoutConnections?: boolean, providers?: string[]): ConnectionProfileGroup[] {
|
||||
let profilesInConfiguration: ConnectionProfile[];
|
||||
if (!withoutConnections) {
|
||||
profilesInConfiguration = this._connectionConfig.getConnections(true);
|
||||
if (providers && providers.length > 0) {
|
||||
profilesInConfiguration = profilesInConfiguration.filter(x => providers.includes(x.providerName));
|
||||
}
|
||||
}
|
||||
let groups = this._connectionConfig.getAllGroups();
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ export class ConnectionController implements IConnectionComponentController {
|
||||
tempProfile.password = password;
|
||||
tempProfile.groupFullName = '';
|
||||
tempProfile.saveProfile = false;
|
||||
let uri = this._connectionManagementService.getConnectionId(tempProfile);
|
||||
let uri = this._connectionManagementService.getConnectionUri(tempProfile);
|
||||
return new Promise<string[]>((resolve, reject) => {
|
||||
if (this._databaseCache.has(uri)) {
|
||||
let cachedDatabases: string[] = this._databaseCache.get(uri);
|
||||
@@ -132,8 +132,8 @@ export class ConnectionController implements IConnectionComponentController {
|
||||
}
|
||||
}
|
||||
|
||||
private getAllServerGroups(): IConnectionProfileGroup[] {
|
||||
var connectionGroupRoot = this._connectionManagementService.getConnectionGroups();
|
||||
private getAllServerGroups(providers?: string[]): IConnectionProfileGroup[] {
|
||||
var connectionGroupRoot = this._connectionManagementService.getConnectionGroups(providers);
|
||||
var connectionGroupNames: IConnectionProfileGroup[] = [];
|
||||
if (connectionGroupRoot && connectionGroupRoot.length > 0) {
|
||||
this.getServerGroupHelper(connectionGroupRoot[0], connectionGroupNames);
|
||||
@@ -149,8 +149,8 @@ export class ConnectionController implements IConnectionComponentController {
|
||||
return connectionGroupNames;
|
||||
}
|
||||
|
||||
public initDialog(connectionInfo: IConnectionProfile): void {
|
||||
this._connectionWidget.updateServerGroup(this.getAllServerGroups());
|
||||
public initDialog(providers: string[], connectionInfo: IConnectionProfile): void {
|
||||
this._connectionWidget.updateServerGroup(this.getAllServerGroups(providers));
|
||||
this._model = connectionInfo;
|
||||
this._model.providerName = this._providerName;
|
||||
let appNameOption = this._providerOptions.find(option => option.specialValueType === ConnectionOptionSpecialType.appName);
|
||||
|
||||
@@ -33,6 +33,7 @@ import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import * as types from 'vs/base/common/types';
|
||||
import { trim } from 'vs/base/common/strings';
|
||||
import { Deferred } from 'sql/base/common/promise';
|
||||
|
||||
export interface IConnectionValidateResult {
|
||||
isValid: boolean;
|
||||
@@ -49,7 +50,7 @@ export interface IConnectionComponentCallbacks {
|
||||
|
||||
export interface IConnectionComponentController {
|
||||
showUiComponent(container: HTMLElement): void;
|
||||
initDialog(model: IConnectionProfile): void;
|
||||
initDialog(providers: string[], model: IConnectionProfile): void;
|
||||
validateConnection(): IConnectionValidateResult;
|
||||
fillInConnectionInputs(connectionInfo: IConnectionProfile): void;
|
||||
handleOnConnecting(): void;
|
||||
@@ -75,6 +76,7 @@ export class ConnectionDialogService implements IConnectionDialogService {
|
||||
private _currentProviderType: string = 'Microsoft SQL Server';
|
||||
private _connecting: boolean = false;
|
||||
private _connectionErrorTitle = localize('connectionError', 'Connection error');
|
||||
private _dialogDeferredPromise: Deferred<IConnectionProfile>;
|
||||
|
||||
constructor(
|
||||
@IPartService private _partService: IPartService,
|
||||
@@ -82,17 +84,24 @@ export class ConnectionDialogService implements IConnectionDialogService {
|
||||
@ICapabilitiesService private _capabilitiesService: ICapabilitiesService,
|
||||
@IErrorMessageService private _errorMessageService: IErrorMessageService,
|
||||
@IWorkspaceConfigurationService private _workspaceConfigurationService: IWorkspaceConfigurationService,
|
||||
@IWindowsService private _windowsService: IWindowsService,
|
||||
@IClipboardService private _clipboardService: IClipboardService,
|
||||
@ICommandService private _commandService: ICommandService
|
||||
) { }
|
||||
|
||||
private getDefaultProviderName() {
|
||||
if (this._workspaceConfigurationService) {
|
||||
let defaultProvider = WorkbenchUtils.getSqlConfigValue<string>(this._workspaceConfigurationService, Constants.defaultEngine);
|
||||
let defaultProvider: string;
|
||||
if (this._providerNameToDisplayNameMap) {
|
||||
let keys = Object.keys(this._providerNameToDisplayNameMap);
|
||||
if (keys && keys.length > 0) {
|
||||
defaultProvider = keys[0];
|
||||
}
|
||||
}
|
||||
|
||||
if (!defaultProvider && this._workspaceConfigurationService) {
|
||||
defaultProvider = WorkbenchUtils.getSqlConfigValue<string>(this._workspaceConfigurationService, Constants.defaultEngine);
|
||||
}
|
||||
// as a fallback, default to MSSQL if the value from settings is not available
|
||||
return Constants.mssqlProviderName;
|
||||
return defaultProvider || Constants.mssqlProviderName;
|
||||
}
|
||||
|
||||
private handleOnConnect(params: INewConnectionParams, profile?: IConnectionProfile): void {
|
||||
@@ -144,33 +153,36 @@ export class ConnectionDialogService implements IConnectionDialogService {
|
||||
} else {
|
||||
this._connectionManagementService.cancelConnection(this._model);
|
||||
}
|
||||
if (params && params.input && params.input.onConnectReject) {
|
||||
params.input.onConnectReject();
|
||||
}
|
||||
this._connectionDialog.resetConnection();
|
||||
this._connecting = false;
|
||||
}
|
||||
this.uiController.databaseDropdownExpanded = false;
|
||||
if (this._dialogDeferredPromise) {
|
||||
this._dialogDeferredPromise.resolve(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
private handleDefaultOnConnect(params: INewConnectionParams, connection: IConnectionProfile): Thenable<void> {
|
||||
let fromEditor = params && params.connectionType === ConnectionType.editor;
|
||||
let uri: string = undefined;
|
||||
if (fromEditor && params.input) {
|
||||
if (fromEditor && params && params.input) {
|
||||
uri = params.input.uri;
|
||||
}
|
||||
let options: IConnectionCompletionOptions = {
|
||||
params: params,
|
||||
saveTheConnection: !fromEditor,
|
||||
showDashboard: params.showDashboard !== undefined ? params.showDashboard : !fromEditor,
|
||||
showDashboard: params && params.showDashboard !== undefined ? params.showDashboard : !fromEditor,
|
||||
showConnectionDialogOnError: false,
|
||||
showFirewallRuleOnError: true
|
||||
};
|
||||
|
||||
return this._connectionManagementService.connectAndSaveProfile(connection, uri, options, params.input).then(connectionResult => {
|
||||
return this._connectionManagementService.connectAndSaveProfile(connection, uri, options, params && params.input).then(connectionResult => {
|
||||
this._connecting = false;
|
||||
if (connectionResult && connectionResult.connected) {
|
||||
this._connectionDialog.close();
|
||||
if (this._dialogDeferredPromise) {
|
||||
this._dialogDeferredPromise.resolve(connectionResult.connectionProfile);
|
||||
}
|
||||
} else if (connectionResult && connectionResult.errorHandled) {
|
||||
this._connectionDialog.resetConnection();
|
||||
} else {
|
||||
@@ -216,7 +228,7 @@ export class ConnectionDialogService implements IConnectionDialogService {
|
||||
}
|
||||
|
||||
private handleInitDialog() {
|
||||
this.uiController.initDialog(this._model);
|
||||
this.uiController.initDialog(this._params && this._params.providers, this._model);
|
||||
}
|
||||
|
||||
private handleFillInConnectionInputs(connectionInfo: IConnectionProfile): void {
|
||||
@@ -268,9 +280,25 @@ export class ConnectionDialogService implements IConnectionDialogService {
|
||||
});
|
||||
}
|
||||
|
||||
public openDialogAndWait(connectionManagementService: IConnectionManagementService,
|
||||
params?: INewConnectionParams,
|
||||
model?: IConnectionProfile,
|
||||
connectionResult?: IConnectionResult): Thenable<IConnectionProfile> {
|
||||
this._dialogDeferredPromise = new Deferred<IConnectionProfile>();
|
||||
|
||||
this.showDialog(connectionManagementService,
|
||||
params,
|
||||
model,
|
||||
connectionResult).then(() => {
|
||||
}, error => {
|
||||
this._dialogDeferredPromise.reject(error);
|
||||
});
|
||||
return this._dialogDeferredPromise;
|
||||
}
|
||||
|
||||
public showDialog(
|
||||
connectionManagementService: IConnectionManagementService,
|
||||
params: INewConnectionParams,
|
||||
params?: INewConnectionParams,
|
||||
model?: IConnectionProfile,
|
||||
connectionResult?: IConnectionResult): Thenable<void> {
|
||||
|
||||
@@ -300,11 +328,12 @@ export class ConnectionDialogService implements IConnectionDialogService {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private doShowDialog(params: INewConnectionParams): TPromise<void> {
|
||||
if (!this._connectionDialog) {
|
||||
let container = document.getElementById(this._partService.getWorkbenchElementId()).parentElement;
|
||||
this._container = container;
|
||||
this._connectionDialog = this._instantiationService.createInstance(ConnectionDialogWidget, this._providerTypes, this._providerNameToDisplayNameMap[this._model.providerName]);
|
||||
this._connectionDialog = this._instantiationService.createInstance(ConnectionDialogWidget, this._providerTypes, this._providerNameToDisplayNameMap[this._model.providerName], this._providerNameToDisplayNameMap);
|
||||
this._connectionDialog.onCancel(() => {
|
||||
this._connectionDialog.databaseDropdownExpanded = this.uiController.databaseDropdownExpanded;
|
||||
this.handleOnCancel(this._connectionDialog.newConnectionParams);
|
||||
@@ -319,7 +348,7 @@ export class ConnectionDialogService implements IConnectionDialogService {
|
||||
this._connectionDialog.newConnectionParams = params;
|
||||
|
||||
return new TPromise<void>(() => {
|
||||
this._connectionDialog.open(this._connectionManagementService.getRecentConnections().length > 0);
|
||||
this._connectionDialog.open(this._connectionManagementService.getRecentConnections(params.providers).length > 0);
|
||||
this.uiController.focusOnOpen();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -59,6 +59,7 @@ export class ConnectionDialogWidget extends Modal {
|
||||
private $connectionUIContainer: Builder;
|
||||
private _databaseDropdownExpanded: boolean;
|
||||
private _actionbar: ActionBar;
|
||||
private _providers: string[];
|
||||
|
||||
private _panel: TabbedPanel;
|
||||
private _recentConnectionTabId: PanelTabIdentifier;
|
||||
@@ -84,6 +85,7 @@ export class ConnectionDialogWidget extends Modal {
|
||||
constructor(
|
||||
private providerTypeOptions: string[],
|
||||
private selectedProviderType: string,
|
||||
private providerNameToDisplayNameMap: { [providerDisplayName: string]: string },
|
||||
@IInstantiationService private _instantiationService: IInstantiationService,
|
||||
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
|
||||
@IWorkbenchThemeService private _themeService: IWorkbenchThemeService,
|
||||
@@ -96,6 +98,22 @@ export class ConnectionDialogWidget extends Modal {
|
||||
super(localize('connection', 'Connection'), TelemetryKeys.Connection, _partService, telemetryService, contextKeyService, { hasSpinner: true, hasErrors: true });
|
||||
}
|
||||
|
||||
public refresh(): void {
|
||||
let filteredProviderTypes = this.providerTypeOptions;
|
||||
|
||||
if (this._newConnectionParams && this._newConnectionParams.providers) {
|
||||
let validProviderNames = Object.keys(this.providerNameToDisplayNameMap).filter(x => this.includeProvider(x, this._newConnectionParams));
|
||||
if (validProviderNames && validProviderNames.length > 0) {
|
||||
filteredProviderTypes = filteredProviderTypes.filter(x => validProviderNames.find( v => this.providerNameToDisplayNameMap[v] === x) !== undefined);
|
||||
}
|
||||
}
|
||||
this._providerTypeSelectBox.setOptions(filteredProviderTypes);
|
||||
}
|
||||
|
||||
private includeProvider(providerName: string, params?: INewConnectionParams): Boolean {
|
||||
return params === undefined || params.providers === undefined || params.providers.find(x => x === providerName) !== undefined;
|
||||
}
|
||||
|
||||
protected renderBody(container: HTMLElement): void {
|
||||
let connectionContainer = $('.connection-dialog');
|
||||
container.appendChild(connectionContainer.getHTMLElement());
|
||||
@@ -147,7 +165,7 @@ export class ConnectionDialogWidget extends Modal {
|
||||
this._panel.onTabChange(c => {
|
||||
if (c === savedConnectionTabId && this._savedConnectionTree.getContentHeight() === 0) {
|
||||
// Update saved connection tree
|
||||
TreeUpdateUtils.structuralTreeUpdate(this._savedConnectionTree, 'saved', this._connectionManagementService);
|
||||
TreeUpdateUtils.structuralTreeUpdate(this._savedConnectionTree, 'saved', this._connectionManagementService, this._providers);
|
||||
|
||||
if (this._savedConnectionTree.getContentHeight() > 0) {
|
||||
this._noSavedConnectionBuilder.hide();
|
||||
@@ -366,7 +384,7 @@ export class ConnectionDialogWidget extends Modal {
|
||||
this._recentConnectionBuilder.hide();
|
||||
this._noRecentConnectionBuilder.show();
|
||||
}
|
||||
TreeUpdateUtils.structuralTreeUpdate(this._recentConnectionTree, 'recent', this._connectionManagementService);
|
||||
TreeUpdateUtils.structuralTreeUpdate(this._recentConnectionTree, 'recent', this._connectionManagementService, this._providers);
|
||||
|
||||
// reset saved connection tree
|
||||
this._savedConnectionTree.setInput([]);
|
||||
@@ -415,6 +433,8 @@ export class ConnectionDialogWidget extends Modal {
|
||||
|
||||
public set newConnectionParams(params: INewConnectionParams) {
|
||||
this._newConnectionParams = params;
|
||||
this._providers = params && params.providers;
|
||||
this.refresh();
|
||||
}
|
||||
|
||||
public updateProvider(displayName: string) {
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
border-top-color: transparent;
|
||||
height: calc(100% - 350px);
|
||||
display: block;
|
||||
min-height: 120px;
|
||||
}
|
||||
|
||||
.connection-recent, .connection-saved {
|
||||
|
||||
@@ -57,13 +57,14 @@ import { OperatorsViewComponent } from 'sql/parts/jobManagement/views/operatorsV
|
||||
import { ProxiesViewComponent } from 'sql/parts/jobManagement/views/proxiesView.component';
|
||||
import { Checkbox } from 'sql/base/browser/ui/checkbox/checkbox.component';
|
||||
import { SelectBox } from 'sql/base/browser/ui/selectBox/selectBox.component';
|
||||
import { EditableDropDown } from 'sql/base/browser/ui/editableDropdown/editableDropdown.component';
|
||||
import { InputBox } from 'sql/base/browser/ui/inputBox/inputBox.component';
|
||||
|
||||
let baseComponents = [DashboardHomeContainer, DashboardComponent, DashboardWidgetWrapper, DashboardWebviewContainer,
|
||||
DashboardWidgetContainer, DashboardGridContainer, DashboardErrorContainer, DashboardNavSection, ModelViewContent, WebviewContent, WidgetContent,
|
||||
ComponentHostDirective, BreadcrumbComponent, ControlHostContent, DashboardControlHostContainer,
|
||||
JobsViewComponent, AgentViewComponent, JobHistoryComponent, JobStepsViewComponent, AlertsViewComponent, ProxiesViewComponent, OperatorsViewComponent,
|
||||
DashboardModelViewContainer, ModelComponentWrapper, Checkbox, SelectBox, InputBox,];
|
||||
DashboardModelViewContainer, ModelComponentWrapper, Checkbox, EditableDropDown, SelectBox, InputBox,];
|
||||
|
||||
/* Panel */
|
||||
import { PanelModule } from 'sql/base/browser/ui/panel/panel.module';
|
||||
|
||||
@@ -231,7 +231,7 @@ export class InsightsWidget extends DashboardWidget implements IDashboardWidget,
|
||||
if (componentInstance.setConfig) {
|
||||
componentInstance.setConfig(this.insightConfig.type[this._typeKey]);
|
||||
}
|
||||
componentInstance.data = { columns: result.columnInfo.map(item => item.columnName), rows: result.rows.map(row => row.map(item => item.displayValue)) };
|
||||
componentInstance.data = { columns: result.columnInfo.map(item => item.columnName), rows: result.rows.map(row => row.map(item => (item.invariantCultureDisplayValue === null || item.invariantCultureDisplayValue === undefined) ? item.displayValue : item.invariantCultureDisplayValue)) };
|
||||
|
||||
if (componentInstance.init) {
|
||||
componentInstance.init();
|
||||
|
||||
@@ -16,7 +16,7 @@ const properties: IJSONSchema = {
|
||||
properties: {
|
||||
yAxisMin: {
|
||||
type: 'number',
|
||||
description: nls.localize('yAxisMin', "Minumum value of the y axis")
|
||||
description: nls.localize('yAxisMin', "Minimum value of the y axis")
|
||||
},
|
||||
yAxisMax: {
|
||||
type: 'number',
|
||||
@@ -28,7 +28,7 @@ const properties: IJSONSchema = {
|
||||
},
|
||||
xAxisMin: {
|
||||
type: 'number',
|
||||
description: nls.localize('xAxisMin', "Minumum value of the x axis")
|
||||
description: nls.localize('xAxisMin', "Minimum value of the x axis")
|
||||
},
|
||||
xAxisMax: {
|
||||
type: 'number',
|
||||
|
||||
@@ -146,7 +146,7 @@ export class BackupUiService implements IBackupUiService {
|
||||
|
||||
let backupOptions = this.getOptions(this._currentProvider);
|
||||
return new TPromise<void>(() => {
|
||||
let uri = this._connectionManagementService.getConnectionId(connection)
|
||||
let uri = this._connectionManagementService.getConnectionUri(connection)
|
||||
+ ProviderConnectionInfo.idSeparator
|
||||
+ ConnectionUtils.ConnectionUriBackupIdAttributeName
|
||||
+ ProviderConnectionInfo.nameValueSeparator
|
||||
|
||||
@@ -293,7 +293,7 @@ export class RestoreDialogController implements IRestoreDialogController {
|
||||
return new TPromise<void>((resolve, reject) => {
|
||||
let result: void;
|
||||
|
||||
this._ownerUri = this._connectionService.getConnectionId(connection)
|
||||
this._ownerUri = this._connectionService.getConnectionUri(connection)
|
||||
+ ProviderConnectionInfo.idSeparator
|
||||
+ Utils.ConnectionUriRestoreIdAttributeName
|
||||
+ ProviderConnectionInfo.nameValueSeparator
|
||||
|
||||
@@ -16,6 +16,7 @@ import { INotificationService } from 'vs/platform/notification/common/notificati
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
|
||||
import { EditDataResultsInput } from 'sql/parts/editData/common/editDataResultsInput';
|
||||
import { IEditorViewState } from 'vs/editor/common/editorCommon';
|
||||
|
||||
/**
|
||||
* Input for the EditDataEditor.
|
||||
@@ -37,6 +38,8 @@ export class EditDataInput extends EditorInput implements IConnectableInput {
|
||||
private _css: HTMLStyleElement;
|
||||
private _useQueryFilter: boolean;
|
||||
|
||||
public savedViewState: IEditorViewState;
|
||||
|
||||
constructor(
|
||||
private _uri: URI,
|
||||
private _schemaName,
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
import { localize } from 'vs/nls';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { EditorInput } from 'vs/workbench/common/editor';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
|
||||
/**
|
||||
* Input for the EditDataResultsEditor. This input helps with logic for the viewing and editing of
|
||||
@@ -25,6 +26,9 @@ export class EditDataResultsInput extends EditorInput {
|
||||
private _editorContainer: HTMLElement;
|
||||
public css: HTMLStyleElement;
|
||||
|
||||
public readonly onRestoreViewStateEmitter = new Emitter<void>();
|
||||
public readonly onSaveViewStateEmitter = new Emitter<void>();
|
||||
|
||||
constructor(private _uri: string) {
|
||||
super();
|
||||
this._visible = false;
|
||||
|
||||
@@ -13,7 +13,7 @@ import { Builder } from 'vs/base/browser/builder';
|
||||
|
||||
import { EditorOptions, EditorInput } from 'vs/workbench/common/editor';
|
||||
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
|
||||
import { Position, IEditorControl, IEditor } from 'vs/platform/editor/common/editor';
|
||||
import { Position, IEditorControl, IEditor, IEditorInput } from 'vs/platform/editor/common/editor';
|
||||
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
@@ -43,6 +43,8 @@ import { IFlexibleSash, VerticalFlexibleSash, HorizontalFlexibleSash } from 'sql
|
||||
import { Orientation } from 'vs/base/browser/ui/sash/sash';
|
||||
import { EditDataResultsEditor } from 'sql/parts/editData/editor/editDataResultsEditor';
|
||||
import { EditDataResultsInput } from 'sql/parts/editData/common/editDataResultsInput';
|
||||
import { IEditorViewState } from 'vs/editor/common/editorCommon';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
|
||||
/**
|
||||
* Editor that hosts an action bar and a resultSetInput for an edit data session
|
||||
@@ -96,6 +98,14 @@ export class EditDataEditor extends BaseEditor {
|
||||
if (contextKeyService) {
|
||||
this._queryEditorVisible = queryContext.QueryEditorVisibleContext.bindTo(contextKeyService);
|
||||
}
|
||||
|
||||
if (_editorGroupService) {
|
||||
_editorGroupService.onEditorOpening(e => {
|
||||
if (this.isVisible() && (e.input !== this.input || e.position !== this.position)) {
|
||||
this.saveEditorViewState();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// PUBLIC METHODS ////////////////////////////////////////////////////////////
|
||||
@@ -594,7 +604,15 @@ export class EditDataEditor extends BaseEditor {
|
||||
// Run all three steps synchronously
|
||||
return createEditors()
|
||||
.then(onEditorsCreated)
|
||||
.then(doLayout);
|
||||
.then(doLayout)
|
||||
.then(() => {
|
||||
if (newInput.results) {
|
||||
newInput.results.onRestoreViewStateEmitter.fire();
|
||||
}
|
||||
if (newInput.savedViewState) {
|
||||
this._sqlEditor.getControl().restoreViewState(newInput.savedViewState);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private _setSashDimension(): void {
|
||||
@@ -631,7 +649,6 @@ export class EditDataEditor extends BaseEditor {
|
||||
* has been opened with the same editor, or we are opening the editor for the first time).
|
||||
*/
|
||||
private _updateInput(oldInput: EditDataInput, newInput: EditDataInput, options?: EditorOptions): TPromise<void> {
|
||||
|
||||
if (this._sqlEditor) {
|
||||
this._sqlEditor.clearInput();
|
||||
}
|
||||
@@ -727,4 +744,16 @@ export class EditDataEditor extends BaseEditor {
|
||||
public queryPaneEnabled(): boolean {
|
||||
return this.editDataInput.queryPaneEnabled;
|
||||
}
|
||||
|
||||
private saveEditorViewState(): void {
|
||||
let editDataInput = this.input as EditDataInput;
|
||||
if (editDataInput) {
|
||||
if (this._sqlEditor) {
|
||||
editDataInput.savedViewState = this._sqlEditor.getControl().saveViewState();
|
||||
}
|
||||
if (editDataInput.results) {
|
||||
editDataInput.results.onSaveViewStateEmitter.fire();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import { IEditDataComponentParams } from 'sql/services/bootstrap/bootstrapParams
|
||||
import { EditDataModule } from 'sql/parts/grid/views/editData/editData.module';
|
||||
import { EDITDATA_SELECTOR } from 'sql/parts/grid/views/editData/editData.component';
|
||||
import { EditDataResultsInput } from 'sql/parts/editData/common/editDataResultsInput';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
|
||||
export class EditDataResultsEditor extends BaseEditor {
|
||||
|
||||
@@ -109,7 +110,11 @@ export class EditDataResultsEditor extends BaseEditor {
|
||||
// Otherwise many components will be left around and be subscribed
|
||||
// to events from the backing data service
|
||||
const parent = input.container;
|
||||
let params: IEditDataComponentParams = { dataService: dataService };
|
||||
let params: IEditDataComponentParams = {
|
||||
dataService: dataService,
|
||||
onSaveViewState: input.onSaveViewStateEmitter.event,
|
||||
onRestoreViewState: input.onRestoreViewStateEmitter.event
|
||||
};
|
||||
bootstrapAngular(this._instantiationService,
|
||||
EditDataModule,
|
||||
parent,
|
||||
|
||||
@@ -15,7 +15,7 @@ import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
export class FileBrowserController extends treedefaults.DefaultController {
|
||||
|
||||
constructor() {
|
||||
super({ clickBehavior: treedefaults.ClickBehavior.ON_MOUSE_DOWN });
|
||||
super({ clickBehavior: treedefaults.ClickBehavior.ON_MOUSE_DOWN, openMode: treedefaults.OpenMode.SINGLE_CLICK });
|
||||
}
|
||||
|
||||
protected onLeftClick(tree: ITree, element: any, event: IMouseEvent, origin: string = 'mouse'): boolean {
|
||||
|
||||
@@ -18,12 +18,9 @@
|
||||
showHeader="true"
|
||||
[resized]="dataSet.resized"
|
||||
[plugins]="plugins[i]"
|
||||
(activeCellChanged)="onActiveCellChanged($event)"
|
||||
(cellEditBegin)="onCellEditBegin($event)"
|
||||
(cellEditExit)="onCellEditEnd($event)"
|
||||
(rowEditBegin)="onRowEditBegin($event)"
|
||||
(rowEditExit)="onRowEditEnd($event)"
|
||||
(contextMenu)="openContextMenu($event, dataSet.batchId, dataSet.resultId, i)"
|
||||
(onActiveCellChanged)="onActiveCellChanged($event)"
|
||||
(onCellChange)="onCellEditEnd($event)"
|
||||
(onContextMenu)="openContextMenu($event, dataSet.batchId, dataSet.resultId, i)"
|
||||
[isCellEditValid]="onIsCellEditValid"
|
||||
[overrideCellFn]="overrideCellFn"
|
||||
enableEditing="true"
|
||||
|
||||
@@ -13,7 +13,7 @@ import 'vs/css!sql/parts/grid/media/slickGrid';
|
||||
import 'vs/css!./media/editData';
|
||||
|
||||
import { ElementRef, ChangeDetectorRef, OnInit, OnDestroy, Component, Inject, forwardRef, EventEmitter } from '@angular/core';
|
||||
import { IGridDataRow, VirtualizedCollection } from 'angular2-slickgrid';
|
||||
import { IGridDataRow, VirtualizedCollection, ISlickRange } from 'angular2-slickgrid';
|
||||
|
||||
import { IGridDataSet } from 'sql/parts/grid/common/interfaces';
|
||||
import * as Services from 'sql/parts/grid/services/sharedServices';
|
||||
@@ -71,16 +71,19 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
|
||||
protected plugins = new Array<Array<Slick.Plugin<any>>>();
|
||||
|
||||
// Edit Data functions
|
||||
public onActiveCellChanged: (event: { row: number, column: number }) => void;
|
||||
public onCellEditEnd: (event: { row: number, column: number, newValue: any }) => void;
|
||||
public onCellEditBegin: (event: { row: number, column: number }) => void;
|
||||
public onRowEditBegin: (event: { row: number }) => void;
|
||||
public onRowEditEnd: (event: { row: number }) => void;
|
||||
public onActiveCellChanged: (event: Slick.OnActiveCellChangedEventArgs<any>) => void;
|
||||
public onCellEditEnd: (event: Slick.OnCellChangeEventArgs<any>) => void;
|
||||
public onIsCellEditValid: (row: number, column: number, newValue: any) => boolean;
|
||||
public onIsColumnEditable: (column: number) => boolean;
|
||||
public overrideCellFn: (rowNumber, columnId, value?, data?) => string;
|
||||
public loadDataFunction: (offset: number, count: number) => Promise<IGridDataRow[]>;
|
||||
|
||||
private savedViewState: {
|
||||
gridSelections: ISlickRange[];
|
||||
scrollTop;
|
||||
scrollLeft;
|
||||
};
|
||||
|
||||
constructor(
|
||||
@Inject(forwardRef(() => ElementRef)) el: ElementRef,
|
||||
@Inject(forwardRef(() => ChangeDetectorRef)) cd: ChangeDetectorRef,
|
||||
@@ -98,6 +101,8 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
|
||||
this._el.nativeElement.className = 'slickgridContainer';
|
||||
this.dataService = params.dataService;
|
||||
this.actionProvider = this.instantiationService.createInstance(EditDataGridActionProvider, this.dataService, this.onGridSelectAll(), this.onDeleteRow(), this.onRevertRow());
|
||||
params.onRestoreViewState(() => this.restoreViewState());
|
||||
params.onSaveViewState(() => this.saveViewState());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -159,17 +164,11 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
|
||||
|
||||
this.onActiveCellChanged = this.onCellSelect;
|
||||
|
||||
this.onCellEditEnd = (event: { row: number, column: number, newValue: any }): void => {
|
||||
this.onCellEditEnd = (event: Slick.OnCellChangeEventArgs<any>): void => {
|
||||
// Store the value that was set
|
||||
self.currentEditCellValue = event.newValue;
|
||||
self.currentEditCellValue = event.item[event.cell - 1];
|
||||
};
|
||||
|
||||
this.onCellEditBegin = (event: { row: number, column: number }): void => { };
|
||||
|
||||
this.onRowEditBegin = (event: { row: number }): void => { };
|
||||
|
||||
this.onRowEditEnd = (event: { row: number }): void => { };
|
||||
|
||||
this.overrideCellFn = (rowNumber, columnId, value?, data?): string => {
|
||||
let returnVal = '';
|
||||
if (Services.DBCellValue.isDBCellValue(value)) {
|
||||
@@ -225,10 +224,10 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
|
||||
};
|
||||
}
|
||||
|
||||
onCellSelect(event: { row: number, column: number }): void {
|
||||
onCellSelect(event: Slick.OnActiveCellChangedEventArgs<any>): void {
|
||||
let self = this;
|
||||
let row = event.row;
|
||||
let column = event.column;
|
||||
let column = event.cell;
|
||||
|
||||
// Skip processing if the newly selected cell is undefined or we don't have column
|
||||
// definition for the column (ie, the selection was reset)
|
||||
@@ -458,9 +457,11 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
|
||||
}
|
||||
} finally {
|
||||
// The operation may fail if there were no changes sent to the service to revert,
|
||||
// so clear any existing client-side edit and refresh the table regardless
|
||||
// so clear any existing client-side edit and refresh on-screen data
|
||||
// do not refresh the whole dataset as it will move the focus away to the first row.
|
||||
//
|
||||
this.currentEditCellValue = null;
|
||||
this.refreshResultsets();
|
||||
this.dataSet.dataRows.resetWindowsAroundIndex(this.currentCell.row);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -578,4 +579,25 @@ export class EditDataComponent extends GridParentComponent implements OnInit, On
|
||||
? (this._defaultNumShowingRows + 1) * this._rowHeight + 10
|
||||
: this.getMaxHeight(rowCount);
|
||||
}
|
||||
|
||||
private saveViewState(): void {
|
||||
let gridSelections = this.slickgrids.toArray()[0].getSelectedRanges();
|
||||
let viewport = ((this.slickgrids.toArray()[0] as any)._grid.getCanvasNode() as HTMLElement).parentElement;
|
||||
|
||||
this.savedViewState = {
|
||||
gridSelections,
|
||||
scrollTop: viewport.scrollTop,
|
||||
scrollLeft: viewport.scrollLeft
|
||||
};
|
||||
}
|
||||
|
||||
private restoreViewState(): void {
|
||||
if (this.savedViewState) {
|
||||
this.slickgrids.toArray()[0].selection = this.savedViewState.gridSelections;
|
||||
let viewport = ((this.slickgrids.toArray()[0] as any)._grid.getCanvasNode() as HTMLElement).parentElement;
|
||||
viewport.scrollLeft = this.savedViewState.scrollLeft;
|
||||
viewport.scrollTop = this.savedViewState.scrollTop;
|
||||
this.savedViewState = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,16 +80,6 @@ export abstract class GridParentComponent {
|
||||
|
||||
@ViewChildren('slickgrid') slickgrids: QueryList<SlickGrid>;
|
||||
|
||||
// Edit Data functions
|
||||
public onActiveCellChanged: (event: { row: number, column: number }) => void;
|
||||
public onCellEditEnd: (event: { row: number, column: number, newValue: any }) => void;
|
||||
public onCellEditBegin: (event: { row: number, column: number }) => void;
|
||||
public onRowEditBegin: (event: { row: number }) => void;
|
||||
public onRowEditEnd: (event: { row: number }) => void;
|
||||
public onIsCellEditValid: (row: number, column: number, newValue: any) => boolean;
|
||||
public overrideCellFn: (rowNumber, columnId, value?, data?) => string;
|
||||
public loadDataFunction: (offset: number, count: number) => Promise<IGridDataRow[]>;
|
||||
|
||||
set messageActive(input: boolean) {
|
||||
this._messageActive = input;
|
||||
if (this.resultActive) {
|
||||
@@ -236,21 +226,29 @@ export abstract class GridParentComponent {
|
||||
this.messagesFocussedContextKey.set(false);
|
||||
}
|
||||
|
||||
protected getSelection(index?: number): ISlickRange[] {
|
||||
let selection = this.slickgrids.toArray()[index || this.activeGrid].getSelectedRanges();
|
||||
if (selection) {
|
||||
selection = selection.map(c => { return <ISlickRange>{ fromCell: c.fromCell - 1, toCell: c.toCell - 1, toRow: c.toRow, fromRow: c.fromRow }; });
|
||||
return selection;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private copySelection(): void {
|
||||
let messageText = this.getMessageText();
|
||||
if (messageText.length > 0) {
|
||||
this.clipboardService.writeText(messageText);
|
||||
} else {
|
||||
let activeGrid = this.activeGrid;
|
||||
let selection = this.slickgrids.toArray()[activeGrid].getSelectedRanges();
|
||||
this.dataService.copyResults(selection, this.renderedDataSets[activeGrid].batchId, this.renderedDataSets[activeGrid].resultId);
|
||||
this.dataService.copyResults(this.getSelection(activeGrid), this.renderedDataSets[activeGrid].batchId, this.renderedDataSets[activeGrid].resultId);
|
||||
}
|
||||
}
|
||||
|
||||
private copyWithHeaders(): void {
|
||||
let activeGrid = this.activeGrid;
|
||||
let selection = this.slickgrids.toArray()[activeGrid].getSelectedRanges();
|
||||
this.dataService.copyResults(selection, this.renderedDataSets[activeGrid].batchId,
|
||||
this.dataService.copyResults(this.getSelection(activeGrid), this.renderedDataSets[activeGrid].batchId,
|
||||
this.renderedDataSets[activeGrid].resultId, true);
|
||||
}
|
||||
|
||||
@@ -364,8 +362,7 @@ export abstract class GridParentComponent {
|
||||
let activeGrid = this.activeGrid;
|
||||
let batchId = this.renderedDataSets[activeGrid].batchId;
|
||||
let resultId = this.renderedDataSets[activeGrid].resultId;
|
||||
let selection = this.slickgrids.toArray()[activeGrid].getSelectedRanges();
|
||||
this.dataService.sendSaveRequest({ batchIndex: batchId, resultSetNumber: resultId, format: format, selection: selection });
|
||||
this.dataService.sendSaveRequest({ batchIndex: batchId, resultSetNumber: resultId, format: format, selection: this.getSelection(activeGrid) });
|
||||
}
|
||||
|
||||
protected _keybindingFor(action: IAction): ResolvedKeybinding {
|
||||
@@ -377,7 +374,7 @@ export abstract class GridParentComponent {
|
||||
let slick: any = this.slickgrids.toArray()[index];
|
||||
let grid = slick._grid;
|
||||
|
||||
let selection = this.slickgrids.toArray()[index].getSelectedRanges();
|
||||
let selection = this.getSelection(index);
|
||||
|
||||
if (selection && selection.length === 0) {
|
||||
let cell = (grid as Slick.Grid<any>).getCellFromEvent(event);
|
||||
|
||||
@@ -329,7 +329,7 @@ export class ChartViewerComponent implements OnInit, OnDestroy, IChartViewAction
|
||||
this._executeResult = <IInsightData>{};
|
||||
this._executeResult.columns = dataSet.columnDefinitions.map(def => def.name);
|
||||
this._executeResult.rows = dataSet.dataRows.getRange(0, dataSet.dataRows.getLength()).map(gridRow => {
|
||||
return gridRow.values.map(cell => cell.displayValue);
|
||||
return gridRow.values.map(cell => (cell.invariantCultureDisplayValue === null || cell.invariantCultureDisplayValue === undefined) ? cell.displayValue : cell.invariantCultureDisplayValue);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -10,23 +10,25 @@
|
||||
<span> {{LocalizedConstants.resultPaneLabel}} </span>
|
||||
<span class="queryResultsShortCut"> {{resultShortcut}} </span>
|
||||
</div>
|
||||
<div id="results" *ngIf="renderedDataSets.length > 0" class="results vertBox scrollable"
|
||||
(onScroll)="onScroll($event)" [scrollEnabled]="scrollEnabled" [class.hidden]="!resultActive"
|
||||
(focusin)="onGridFocus()" (focusout)="onGridFocusout()">
|
||||
<div #resultsScrollBox id="results" *ngIf="renderedDataSets.length > 0" class="results vertBox scrollable"
|
||||
(onScroll)="onScroll($event)" [scrollEnabled]="scrollEnabled" [class.hidden]="!resultActive"
|
||||
(focusin)="onGridFocus()" (focusout)="onGridFocusout()">
|
||||
<div class="boxRow content horzBox slickgrid" *ngFor="let dataSet of renderedDataSets; let i = index"
|
||||
[style.max-height]="dataSet.maxHeight" [style.min-height]="dataSet.minHeight">
|
||||
<slick-grid #slickgrid id="slickgrid_{{i}}" [columnDefinitions]="dataSet.columnDefinitions"
|
||||
[ngClass]="i === activeGrid ? 'active' : ''"
|
||||
[dataRows]="dataSet.dataRows"
|
||||
(contextMenu)="openContextMenu($event, dataSet.batchId, dataSet.resultId, i)"
|
||||
enableAsyncPostRender="true"
|
||||
showHeader="true"
|
||||
[resized]="dataSet.resized"
|
||||
(mousedown)="navigateToGrid(i)"
|
||||
[selectionModel]="selectionModel"
|
||||
[plugins]="plugins[i]"
|
||||
class="boxCol content vertBox slickgrid"
|
||||
[rowHeight]="rowHeight">
|
||||
<slick-grid #slickgrid id="slickgrid_{{i}}"
|
||||
class="boxCol content vertBox slickgrid"
|
||||
enableAsyncPostRender="true"
|
||||
showHeader="true"
|
||||
[columnDefinitions]="dataSet.columnDefinitions"
|
||||
[ngClass]="i === activeGrid ? 'active' : ''"
|
||||
[dataRows]="dataSet.dataRows"
|
||||
[resized]="dataSet.resized"
|
||||
[selectionModel]="selectionModel"
|
||||
[plugins]="plugins[i]"
|
||||
[rowHeight]="rowHeight"
|
||||
(onContextMenu)="openContextMenu($event, dataSet.batchId, dataSet.resultId, i)"
|
||||
(onActiveCellChanged)="onActiveCellChanged(i)"
|
||||
(mousedown)="navigateToGrid(i)">
|
||||
</slick-grid>
|
||||
<span class="boxCol content vertBox">
|
||||
<div class="boxRow content maxHeight" *ngFor="let icon of dataIcons">
|
||||
|
||||
@@ -15,7 +15,7 @@ import {
|
||||
ElementRef, QueryList, ChangeDetectorRef, OnInit, OnDestroy, Component, Inject,
|
||||
ViewChildren, forwardRef, EventEmitter, Input, ViewChild
|
||||
} from '@angular/core';
|
||||
import { IGridDataRow, SlickGrid, VirtualizedCollection } from 'angular2-slickgrid';
|
||||
import { IGridDataRow, SlickGrid, VirtualizedCollection, ISlickRange } from 'angular2-slickgrid';
|
||||
|
||||
import * as LocalizedConstants from 'sql/parts/query/common/localizedConstants';
|
||||
import * as Services from 'sql/parts/grid/services/sharedServices';
|
||||
@@ -91,7 +91,7 @@ export class QueryComponent extends GridParentComponent implements OnInit, OnDes
|
||||
icon: () => { return 'saveCsv'; },
|
||||
hoverText: () => { return LocalizedConstants.saveCSVLabel; },
|
||||
functionality: (batchId, resultId, index) => {
|
||||
let selection = this.slickgrids.toArray()[index].getSelectedRanges();
|
||||
let selection = this.getSelection(index);
|
||||
if (selection.length <= 1) {
|
||||
this.handleContextClick({ type: 'savecsv', batchId: batchId, resultId: resultId, index: index, selection: selection });
|
||||
} else {
|
||||
@@ -104,7 +104,7 @@ export class QueryComponent extends GridParentComponent implements OnInit, OnDes
|
||||
icon: () => { return 'saveJson'; },
|
||||
hoverText: () => { return LocalizedConstants.saveJSONLabel; },
|
||||
functionality: (batchId, resultId, index) => {
|
||||
let selection = this.slickgrids.toArray()[index].getSelectedRanges();
|
||||
let selection = this.getSelection(index);
|
||||
if (selection.length <= 1) {
|
||||
this.handleContextClick({ type: 'savejson', batchId: batchId, resultId: resultId, index: index, selection: selection });
|
||||
} else {
|
||||
@@ -117,7 +117,7 @@ export class QueryComponent extends GridParentComponent implements OnInit, OnDes
|
||||
icon: () => { return 'saveExcel'; },
|
||||
hoverText: () => { return LocalizedConstants.saveExcelLabel; },
|
||||
functionality: (batchId, resultId, index) => {
|
||||
let selection = this.slickgrids.toArray()[index].getSelectedRanges();
|
||||
let selection = this.getSelection(index);
|
||||
if (selection.length <= 1) {
|
||||
this.handleContextClick({ type: 'saveexcel', batchId: batchId, resultId: resultId, index: index, selection: selection });
|
||||
} else {
|
||||
@@ -160,6 +160,14 @@ export class QueryComponent extends GridParentComponent implements OnInit, OnDes
|
||||
public queryPlanAvailable: EventEmitter<string> = new EventEmitter<string>();
|
||||
public showChartRequested: EventEmitter<IGridDataSet> = new EventEmitter<IGridDataSet>();
|
||||
public goToNextQueryOutputTabRequested: EventEmitter<void> = new EventEmitter<void>();
|
||||
public onActiveCellChanged: (gridIndex: number) => void;
|
||||
|
||||
private savedViewState: {
|
||||
gridSelections: ISlickRange[][];
|
||||
resultsScroll: number;
|
||||
messagePaneScroll: number;
|
||||
slickGridScrolls: { vertical: number; horizontal: number }[];
|
||||
};
|
||||
|
||||
@Input() public queryParameters: IQueryComponentParams;
|
||||
|
||||
@@ -168,6 +176,8 @@ export class QueryComponent extends GridParentComponent implements OnInit, OnDes
|
||||
@ViewChild('resultsPane', { read: ElementRef }) private _resultsPane: ElementRef;
|
||||
@ViewChild('queryLink', { read: ElementRef }) private _queryLinkElement: ElementRef;
|
||||
@ViewChild('messagesContainer', { read: ElementRef }) private _messagesContainer: ElementRef;
|
||||
@ViewChild('resultsScrollBox', { read: ElementRef }) private _resultsScrollBox: ElementRef;
|
||||
@ViewChildren('slickgrid', { read: ElementRef }) private _slickgridElements: QueryList<ElementRef>;
|
||||
constructor(
|
||||
@Inject(forwardRef(() => ElementRef)) el: ElementRef,
|
||||
@Inject(forwardRef(() => ChangeDetectorRef)) cd: ChangeDetectorRef,
|
||||
@@ -225,6 +235,10 @@ export class QueryComponent extends GridParentComponent implements OnInit, OnDes
|
||||
}
|
||||
self._cd.detectChanges();
|
||||
});
|
||||
|
||||
this.queryParameters.onSaveViewState(() => this.saveViewState());
|
||||
this.queryParameters.onRestoreViewState(() => this.restoreViewState());
|
||||
|
||||
this.dataService.onAngularLoaded();
|
||||
}
|
||||
|
||||
@@ -253,6 +267,8 @@ export class QueryComponent extends GridParentComponent implements OnInit, OnDes
|
||||
self.complete = false;
|
||||
self.activeGrid = 0;
|
||||
|
||||
this.onActiveCellChanged = this.onCellSelect;
|
||||
|
||||
// reset query plan info and send notification to subscribers
|
||||
self.hasQueryPlan = false;
|
||||
self.sentPlans = new Map<number, string>();
|
||||
@@ -389,6 +405,10 @@ export class QueryComponent extends GridParentComponent implements OnInit, OnDes
|
||||
self.onScroll(0);
|
||||
}
|
||||
|
||||
onCellSelect(gridIndex: number): void {
|
||||
this.activeGrid = gridIndex;
|
||||
}
|
||||
|
||||
openMessagesContextMenu(event: any): void {
|
||||
let self = this;
|
||||
event.preventDefault();
|
||||
@@ -651,6 +671,43 @@ export class QueryComponent extends GridParentComponent implements OnInit, OnDes
|
||||
}
|
||||
}
|
||||
|
||||
private saveViewState(): void {
|
||||
let gridSelections = this.slickgrids.map(grid => grid.getSelectedRanges());
|
||||
let resultsScrollElement = (this._resultsScrollBox.nativeElement as HTMLElement);
|
||||
let resultsScroll = resultsScrollElement.scrollTop;
|
||||
let messagePaneScroll = (this._messagesContainer.nativeElement as HTMLElement).scrollTop;
|
||||
let slickGridScrolls = this._slickgridElements.map(element => {
|
||||
// Get the slick grid's viewport element and save its scroll position
|
||||
let scrollElement = (element.nativeElement as HTMLElement).children[0].children[3];
|
||||
return {
|
||||
vertical: scrollElement.scrollTop,
|
||||
horizontal: scrollElement.scrollLeft
|
||||
};
|
||||
});
|
||||
|
||||
this.savedViewState = {
|
||||
gridSelections,
|
||||
messagePaneScroll,
|
||||
resultsScroll,
|
||||
slickGridScrolls
|
||||
};
|
||||
}
|
||||
|
||||
private restoreViewState(): void {
|
||||
if (this.savedViewState) {
|
||||
this.slickgrids.forEach((grid, index) => grid.selection = this.savedViewState.gridSelections[index]);
|
||||
(this._resultsScrollBox.nativeElement as HTMLElement).scrollTop = this.savedViewState.resultsScroll;
|
||||
(this._messagesContainer.nativeElement as HTMLElement).scrollTop = this.savedViewState.messagePaneScroll;
|
||||
this._slickgridElements.forEach((element, index) => {
|
||||
let scrollElement = (element.nativeElement as HTMLElement).children[0].children[3];
|
||||
let savedScroll = this.savedViewState.slickGridScrolls[index];
|
||||
scrollElement.scrollTop = savedScroll.vertical;
|
||||
scrollElement.scrollLeft = savedScroll.horizontal;
|
||||
});
|
||||
this.savedViewState = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
layout() {
|
||||
this.resizeGrids();
|
||||
}
|
||||
|
||||
@@ -295,8 +295,9 @@ export class InsightsDialogView extends Modal {
|
||||
for (let action of this._insight.actions.types) {
|
||||
let task = tasks.includes(action);
|
||||
let commandAction = MenuRegistry.getCommand(action);
|
||||
if (task) {
|
||||
let button = this.addFooterButton(types.isString(commandAction.title) ? commandAction.title : commandAction.title.value, () => {
|
||||
let commandLabel = types.isString(commandAction.title) ? commandAction.title : commandAction.title.value;
|
||||
if (task && !this.findFooterButton(commandLabel)) {
|
||||
let button = this.addFooterButton(commandLabel, () => {
|
||||
let element = this._topTable.getSelectedRows();
|
||||
let resource: ListResource;
|
||||
if (element && element.length > 0) {
|
||||
|
||||
@@ -421,3 +421,7 @@ jobsview-component .jobview-grid .slick-cell.error-row {
|
||||
.jobproxiesview-grid > .monaco-table .slick-header-columns .slick-resizable-handle {
|
||||
border-left: 1px dotted #444444;
|
||||
}
|
||||
|
||||
.overview-container > .overview-tab > label {
|
||||
margin-bottom: 0px;
|
||||
}
|
||||
@@ -120,7 +120,11 @@ export class JobHistoryComponent extends JobManagementView implements OnInit {
|
||||
} else {
|
||||
tree.setFocus(element, payload);
|
||||
tree.setSelection([element], payload);
|
||||
self.setStepsTree(element);
|
||||
if (element.rowID) {
|
||||
self.setStepsTree(element);
|
||||
} else {
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
@@ -27,7 +27,7 @@ import { Color } from 'vs/base/common/color';
|
||||
<div>
|
||||
<label for={{this.label}}>
|
||||
<div #input style="width: 100%">
|
||||
<input *ngIf="this.isFile === true" id={{this.label}} type="file" style="display: none">
|
||||
<input #fileInput *ngIf="this.isFile === true" id={{this.label}} type="file" accept=".sql" style="display: none">
|
||||
</div>
|
||||
</label>
|
||||
</div>
|
||||
@@ -39,17 +39,16 @@ export default class ButtonComponent extends ComponentWithIconBase implements IC
|
||||
private _button: Button;
|
||||
|
||||
@ViewChild('input', { read: ElementRef }) private _inputContainer: ElementRef;
|
||||
@ViewChild('fileInput', { read: ElementRef }) private _fileInputContainer: ElementRef;
|
||||
constructor(
|
||||
@Inject(forwardRef(() => ChangeDetectorRef)) changeRef: ChangeDetectorRef,
|
||||
@Inject(IWorkbenchThemeService) private themeService: IWorkbenchThemeService
|
||||
) {
|
||||
super(changeRef);
|
||||
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.baseInit();
|
||||
|
||||
}
|
||||
|
||||
ngAfterViewInit(): void {
|
||||
@@ -61,10 +60,27 @@ export default class ButtonComponent extends ComponentWithIconBase implements IC
|
||||
buttonBackground: SIDE_BAR_BACKGROUND, buttonHoverBackground: SIDE_BAR_BACKGROUND, buttonForeground: SIDE_BAR_TITLE_FOREGROUND
|
||||
}));
|
||||
this._register(this._button.onDidClick(e => {
|
||||
this._onEventEmitter.fire({
|
||||
eventType: ComponentEventType.onDidClick,
|
||||
args: e
|
||||
});
|
||||
if (this._fileInputContainer) {
|
||||
const self = this;
|
||||
this._fileInputContainer.nativeElement.onchange = () => {
|
||||
let file = self._fileInputContainer.nativeElement.files[0];
|
||||
let reader = new FileReader();
|
||||
reader.onload = (e) => {
|
||||
let text = (<FileReader>e.target).result;
|
||||
self.fileContent = text;
|
||||
self._onEventEmitter.fire({
|
||||
eventType: ComponentEventType.onDidClick,
|
||||
args: self.fileContent
|
||||
});
|
||||
};
|
||||
reader.readAsText(file);
|
||||
};
|
||||
} else {
|
||||
this._onEventEmitter.fire({
|
||||
eventType: ComponentEventType.onDidClick,
|
||||
args: e
|
||||
});
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
@@ -133,6 +149,18 @@ export default class ButtonComponent extends ComponentWithIconBase implements IC
|
||||
this.setPropertyFromUI<sqlops.ButtonProperties, boolean>(this.setFileProperties, newValue);
|
||||
}
|
||||
|
||||
private get fileContent(): string {
|
||||
return this.getPropertyOrDefault<sqlops.ButtonProperties, string>((props) => props.fileContent, '');
|
||||
}
|
||||
|
||||
private set fileContent(newValue: string) {
|
||||
this.setPropertyFromUI<sqlops.ButtonProperties, string>(this.setFileContentProperties, newValue);
|
||||
}
|
||||
|
||||
private setFileContentProperties(properties: sqlops.ButtonProperties, fileContent: string) : void {
|
||||
properties.fileContent = fileContent;
|
||||
}
|
||||
|
||||
private setValueProperties(properties: sqlops.ButtonProperties, label: string): void {
|
||||
properties.label = label;
|
||||
}
|
||||
|
||||
@@ -71,6 +71,12 @@ export abstract class ComponentBase extends Disposable implements IComponent, On
|
||||
|
||||
abstract setLayout(layout: any): void;
|
||||
|
||||
public setDataProvider(handle: number, componentId: string, context: any): void {
|
||||
}
|
||||
|
||||
public refreshDataProvider(item: any): void {
|
||||
}
|
||||
|
||||
public setProperties(properties: { [key: string]: any; }): void {
|
||||
if (!properties) {
|
||||
this.properties = {};
|
||||
|
||||
@@ -14,12 +14,14 @@ import DeclarativeTableComponent from './declarativeTable.component';
|
||||
import ListBoxComponent from './listbox.component';
|
||||
import ButtonComponent from './button.component';
|
||||
import CheckBoxComponent from './checkbox.component';
|
||||
import TreeComponent from './tree/tree.component';
|
||||
import RadioButtonComponent from './radioButton.component';
|
||||
import WebViewComponent from './webview.component';
|
||||
import TableComponent from './table.component';
|
||||
import TextComponent from './text.component';
|
||||
import LoadingComponent from './loadingComponent.component';
|
||||
import FileBrowserTreeComponent from './fileBrowserTree.component';
|
||||
import EditorComponent from './editor.component';
|
||||
import { registerComponentType } from 'sql/platform/dashboard/common/modelComponentRegistry';
|
||||
import { ModelComponentTypes } from 'sql/workbench/api/common/sqlExtHostTypes';
|
||||
|
||||
@@ -72,5 +74,11 @@ registerComponentType(TABLE_COMPONENT, ModelComponentTypes.Table, TableComponent
|
||||
export const LOADING_COMPONENT = 'loading-component';
|
||||
registerComponentType(LOADING_COMPONENT, ModelComponentTypes.LoadingComponent, LoadingComponent);
|
||||
|
||||
export const TREE_COMPONENT = 'tree-component';
|
||||
registerComponentType(TREE_COMPONENT, ModelComponentTypes.TreeComponent, TreeComponent);
|
||||
|
||||
export const FILEBROWSERTREE_COMPONENT = 'filebrowsertree-component';
|
||||
registerComponentType(FILEBROWSERTREE_COMPONENT, ModelComponentTypes.FileBrowserTree, FileBrowserTreeComponent);
|
||||
|
||||
export const EDITOR_COMPONENT = 'editor-component';
|
||||
registerComponentType(EDITOR_COMPONENT, ModelComponentTypes.Editor, EditorComponent);
|
||||
|
||||
@@ -17,6 +17,7 @@ import { IContextViewService } from 'vs/platform/contextview/browser/contextView
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { Checkbox } from 'sql/base/browser/ui/checkbox/checkbox.component';
|
||||
import { SelectBox } from 'sql/base/browser/ui/selectBox/selectBox.component';
|
||||
import { EditableDropDown } from 'sql/base/browser/ui/editableDropdown/editableDropdown.component';
|
||||
import { ISelectData } from 'vs/base/browser/ui/selectBox/selectBox';
|
||||
import { InputBox } from 'sql/base/browser/ui/inputBox/inputBox.component';
|
||||
import * as nls from 'vs/nls';
|
||||
@@ -24,29 +25,38 @@ import * as nls from 'vs/nls';
|
||||
export enum DeclarativeDataType {
|
||||
string = 'string',
|
||||
category = 'category',
|
||||
boolean = 'boolean'
|
||||
boolean = 'boolean',
|
||||
editableCategory = 'editableCategory'
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'modelview-declarativeTable',
|
||||
template: `
|
||||
<table role=grid aria-labelledby="ID_REF" #container *ngIf="columns" class="declarative-table">
|
||||
<thead>
|
||||
<tr style="display:block;">
|
||||
<ng-container *ngFor="let column of columns;let h = index">
|
||||
<th class="declarative-table-header" tabindex="-1" role="button" aria-sort="none">{{column.displayName}}</th>
|
||||
<td class="declarative-table-header" tabindex="-1" [style.width]="getColumnWidth(h)" role="button" aria-sort="none">{{column.displayName}}</td>
|
||||
</ng-container>
|
||||
</tr>
|
||||
</thead>
|
||||
<ng-container *ngIf="data">
|
||||
<tbody style="display: block; width:100%; overflow-y: scroll" [style.height]="getHeight()">
|
||||
<ng-container *ngFor="let row of data;let r = index">
|
||||
<tr class="declarative-table-row">
|
||||
<ng-container *ngFor="let cellData of row;let c = index">
|
||||
<td class="declarative-table-cell" tabindex="-1" role="button" [style.width]="getColumnWidth(c)">
|
||||
<checkbox *ngIf="isCheckBox(c)" label="" (onChange)="onCheckBoxChanged($event,r,c)" [enabled]="isControlEnabled(c)" [checked]="isChecked(r,c)"></checkbox>
|
||||
<select-box *ngIf="isSelectBox(c)" [options]="GetOptions(c)" (onDidSelect)="onSelectBoxChanged($event,r,c)" [selectedOption]="GetSelectedOptionDisplayName(r,c)"></select-box>
|
||||
<editable-select-box *ngIf="isEditableSelectBox(c)" [options]="GetOptions(c)" (onDidSelect)="onSelectBoxChanged($event,r,c)" [selectedOption]="GetSelectedOptionDisplayName(r,c)"></editable-select-box>
|
||||
<input-box *ngIf="isInputBox(c)" [value]="cellData" (onDidChange)="onInputBoxChanged($event,r,c)"></input-box>
|
||||
<ng-container *ngIf="isLabel(c)" >{{cellData}}</ng-container>
|
||||
</td>
|
||||
</ng-container>
|
||||
</tr>
|
||||
|
||||
</ng-container>
|
||||
</tbody>
|
||||
</ng-container>
|
||||
</table>
|
||||
`
|
||||
@@ -109,10 +119,20 @@ export default class DeclarativeTableComponent extends ComponentBase implements
|
||||
this.onCellDataChanged(e, row, cell);
|
||||
}
|
||||
|
||||
private onSelectBoxChanged(e: ISelectData, row: number, cell: number): void {
|
||||
private onSelectBoxChanged(e: ISelectData | string, row: number, cell: number): void {
|
||||
|
||||
let column: sqlops.DeclarativeTableColumn = this.columns[cell];
|
||||
if (column.categoryValues) {
|
||||
this.onCellDataChanged(column.categoryValues[e.index].name, row, cell);
|
||||
if (typeof e === 'string') {
|
||||
let category = column.categoryValues.find(c => c.displayName === e);
|
||||
if (category) {
|
||||
this.onCellDataChanged(category.name, row, cell);
|
||||
} else {
|
||||
this.onCellDataChanged(e, row, cell);
|
||||
}
|
||||
} else {
|
||||
this.onCellDataChanged(column.categoryValues[e.index].name, row, cell);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,6 +155,11 @@ export default class DeclarativeTableComponent extends ComponentBase implements
|
||||
return column.valueType === DeclarativeDataType.category;
|
||||
}
|
||||
|
||||
private isEditableSelectBox(cell: number): boolean {
|
||||
let column: sqlops.DeclarativeTableColumn = this.columns[cell];
|
||||
return column.valueType === DeclarativeDataType.editableCategory;
|
||||
}
|
||||
|
||||
private isInputBox(cell: number): boolean {
|
||||
let column: sqlops.DeclarativeTableColumn = this.columns[cell];
|
||||
return column.valueType === DeclarativeDataType.string && !column.isReadOnly;
|
||||
@@ -142,7 +167,7 @@ export default class DeclarativeTableComponent extends ComponentBase implements
|
||||
|
||||
private getColumnWidth(cell: number): string {
|
||||
let column: sqlops.DeclarativeTableColumn = this.columns[cell];
|
||||
return this.convertSize(column.width);
|
||||
return this.convertSize(column.width, '30px');
|
||||
}
|
||||
|
||||
private GetOptions(cell: number): string[] {
|
||||
@@ -155,7 +180,13 @@ export default class DeclarativeTableComponent extends ComponentBase implements
|
||||
let cellData = this.data[row][cell];
|
||||
if (cellData && column.categoryValues) {
|
||||
let category = column.categoryValues.find(v => v.name === cellData);
|
||||
return category.displayName;
|
||||
if (category) {
|
||||
return category.displayName;
|
||||
} else if (this.isEditableSelectBox(cell)) {
|
||||
return cellData;
|
||||
} else {
|
||||
return undefined;
|
||||
}
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
.declarative-table {
|
||||
padding: 5px 30px 0px 30px;
|
||||
box-sizing: border-box;
|
||||
width:100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
@@ -13,6 +12,7 @@
|
||||
padding: 5px;
|
||||
border: 1px solid gray;
|
||||
background-color: #F5F5F5;
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
.vs-dark .declarative-table-header {
|
||||
|
||||
135
src/sql/parts/modelComponents/editor.component.ts
Normal file
135
src/sql/parts/modelComponents/editor.component.ts
Normal file
@@ -0,0 +1,135 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import 'vs/css!./editor';
|
||||
import {
|
||||
Component, Input, Inject, ChangeDetectorRef, forwardRef, ComponentFactoryResolver,
|
||||
ViewChild, ViewChildren, ElementRef, Injector, OnDestroy, QueryList
|
||||
} from '@angular/core';
|
||||
|
||||
import * as sqlops from 'sqlops';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
import { UntitledEditorInput } from 'vs/workbench/common/editor/untitledEditorInput';
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { IModeService } from 'vs/editor/common/services/modeService';
|
||||
import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
|
||||
import { ComponentBase } from 'sql/parts/modelComponents/componentBase';
|
||||
import { IComponent, IComponentDescriptor, IModelStore } from 'sql/parts/modelComponents/interfaces';
|
||||
import { QueryTextEditor } from 'sql/parts/modelComponents/queryTextEditor';
|
||||
|
||||
@Component({
|
||||
template: '',
|
||||
selector: 'modelview-editor-component'
|
||||
})
|
||||
export default class EditorComponent extends ComponentBase implements IComponent, OnDestroy {
|
||||
@Input() descriptor: IComponentDescriptor;
|
||||
@Input() modelStore: IModelStore;
|
||||
private _editor: QueryTextEditor;
|
||||
private _editorInput: UntitledEditorInput;
|
||||
private _editorModel: ITextModel;
|
||||
private _renderedContent: string;
|
||||
private _langaugeMode: string;
|
||||
|
||||
constructor(
|
||||
@Inject(forwardRef(() => ChangeDetectorRef)) changeRef: ChangeDetectorRef,
|
||||
@Inject(forwardRef(() => ElementRef)) private _el: ElementRef,
|
||||
@Inject(IInstantiationService) private _instantiationService: IInstantiationService,
|
||||
@Inject(IModelService) private _modelService: IModelService,
|
||||
@Inject(IModeService) private _modeService: IModeService
|
||||
) {
|
||||
super(changeRef);
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.baseInit();
|
||||
this._createEditor();
|
||||
this._register(DOM.addDisposableListener(window, DOM.EventType.RESIZE, e => {
|
||||
this.layout();
|
||||
}));
|
||||
}
|
||||
|
||||
private _createEditor(): void {
|
||||
this._editor = this._instantiationService.createInstance(QueryTextEditor);
|
||||
this._editor.create(this._el.nativeElement);
|
||||
this._editor.setVisible(true);
|
||||
this._editorInput = this._instantiationService.createInstance(UntitledEditorInput, URI.from({ scheme: Schemas.untitled, path: `${this.descriptor.type}-${this.descriptor.id}` }), false, 'sql', '', '');
|
||||
this._editor.setInput(this._editorInput, undefined);
|
||||
this._editorInput.resolve().then(model => this._editorModel = model.textEditorModel);
|
||||
|
||||
this._register(this._editor);
|
||||
this._register(this._editorInput);
|
||||
this._register(this._editorModel.onDidChangeContent(e => {
|
||||
this.content = this._editorModel.getValue();
|
||||
}));
|
||||
}
|
||||
|
||||
ngOnDestroy(): void {
|
||||
this.baseDestroy();
|
||||
}
|
||||
|
||||
/// IComponent implementation
|
||||
|
||||
public layout(): void {
|
||||
let width: number = this.convertSizeToNumber(this.width);
|
||||
let height: number = this.convertSizeToNumber(this.height);
|
||||
this._editor.layout(new DOM.Dimension(
|
||||
width && width > 0 ? width : DOM.getContentWidth(this._el.nativeElement),
|
||||
height && height > 0 ? height : DOM.getContentHeight(this._el.nativeElement)));
|
||||
}
|
||||
|
||||
/// Editor Functions
|
||||
private updateModel() {
|
||||
if (this._editorModel) {
|
||||
this._renderedContent = this.content;
|
||||
this._modelService.updateModel(this._editorModel, this._renderedContent);
|
||||
}
|
||||
}
|
||||
|
||||
private updateLanguageMode() {
|
||||
if (this._editorModel && this._editor) {
|
||||
this._langaugeMode = this.languageMode;
|
||||
this._modeService.getOrCreateMode(this._langaugeMode).then((modeValue) => {
|
||||
this._modelService.setMode(this._editorModel, modeValue);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// IComponent implementation
|
||||
|
||||
public setLayout(layout: any): void {
|
||||
// TODO allow configuring the look and feel
|
||||
this.layout();
|
||||
}
|
||||
|
||||
public setProperties(properties: { [key: string]: any; }): void {
|
||||
super.setProperties(properties);
|
||||
if (this.content !== this._renderedContent) {
|
||||
this.updateModel();
|
||||
}
|
||||
if (this.languageMode !== this._langaugeMode) {
|
||||
this.updateLanguageMode();
|
||||
}
|
||||
}
|
||||
|
||||
// CSS-bound properties
|
||||
public get content(): string {
|
||||
return this.getPropertyOrDefault<sqlops.EditorProperties, string>((props) => props.content, undefined);
|
||||
}
|
||||
|
||||
public set content(newValue: string) {
|
||||
this.setPropertyFromUI<sqlops.EditorProperties, string>((properties, content) => { properties.content = content; }, newValue);
|
||||
}
|
||||
|
||||
public get languageMode(): string {
|
||||
return this.getPropertyOrDefault<sqlops.EditorProperties, string>((props) => props.languageMode, undefined);
|
||||
}
|
||||
|
||||
public set languageMode(newValue: string) {
|
||||
this.setPropertyFromUI<sqlops.EditorProperties, string>((properties, languageMode) => { properties.languageMode = languageMode; }, newValue);
|
||||
}
|
||||
}
|
||||
10
src/sql/parts/modelComponents/editor.css
Normal file
10
src/sql/parts/modelComponents/editor.css
Normal file
@@ -0,0 +1,10 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
modelview-editor-component {
|
||||
height: 100%;
|
||||
width : 100%;
|
||||
display: block;
|
||||
}
|
||||
@@ -26,7 +26,7 @@ class FlexItem {
|
||||
template: `
|
||||
<div *ngIf="items" class="flexContainer" [style.flexFlow]="flexFlow" [style.justifyContent]="justifyContent"
|
||||
[style.alignItems]="alignItems" [style.alignContent]="alignContent" [style.height]="height" [style.width]="width">
|
||||
<div *ngFor="let item of items" [style.flex]="getItemFlex(item)" [style.textAlign]="textAlign" [style.order]="getItemOrder(item)" >
|
||||
<div *ngFor="let item of items" [style.flex]="getItemFlex(item)" [style.textAlign]="textAlign" [style.order]="getItemOrder(item)" [ngStyle]="getItemStyles(item)">
|
||||
<model-component-wrapper [descriptor]="item.descriptor" [modelStore]="modelStore">
|
||||
</model-component-wrapper>
|
||||
</div>
|
||||
@@ -108,4 +108,7 @@ export default class FlexContainer extends ContainerBase<FlexItemLayout> impleme
|
||||
private getItemOrder(item: FlexItem): number {
|
||||
return item.config ? item.config.order : 0;
|
||||
}
|
||||
private getItemStyles(item: FlexItem): { [key: string]: string } {
|
||||
return item.config && item.config.CSSStyles ? item.config.CSSStyles : {};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,6 +26,8 @@ export interface IComponent {
|
||||
enabled: boolean;
|
||||
readonly valid?: boolean;
|
||||
validate(): Thenable<boolean>;
|
||||
setDataProvider(handle: number, componentId: string, context: any): void;
|
||||
refreshDataProvider(item: any): void;
|
||||
}
|
||||
|
||||
export const COMPONENT_CONFIG = new InjectionToken<IComponentConfig>('component_config');
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user