Compare commits

...

59 Commits

Author SHA1 Message Date
Karl Burtram
d10e08e63e Update SQL Ops Studio to 0.32.4 2018-08-16 17:35:54 -07:00
Anthony Dresser
a8f21b56f0 Fix context menus in grids (#2245)
* fix context menus

* fix edit data context menu
2018-08-16 20:34:52 -04:00
Karl Burtram
173842510c Bump yarn.lock package reference (#2248) 2018-08-16 20:33:19 -04:00
Alan Ren
19c08fe0eb Alanren/fixfor1782 (#2247)
* fix for issue #1782, refresh the cached data

* pick up the latest version of slickgrid and tools service
2018-08-16 16:54:07 -07:00
Amir Ali Omidi
db817a7192 Required fields and labelling of buttons (#2237)
* Required fields and labelling of buttons

* Update the readme.

* Change localized string
2018-08-16 11:16:06 -07:00
Amir Ali Omidi
2c8e93cc96 Support right clicking the database to start the importer task (#2233) 2018-08-15 14:11:54 -07:00
Alan Ren
44e9a97f09 fix for issue: Results Grid Row Indicator Zero Based #2152 (#2232) 2018-08-14 17:38:00 -07:00
Abbie Petchtes
298ddc4195 fix layout and add css styling for flex item (#2231) 2018-08-14 15:33:03 -07:00
Alan Ren
d9134d6085 Fix for: https://github.com/Microsoft/sqlopsstudio/issues/1317 (#2228)
use culture invariant display value for number conversion
2018-08-14 13:57:22 -07:00
Amir Ali Omidi
b17b4ce880 Entry Point updates (#2222)
* Entry point changes

* Navigator validation changes.

* Minor modifications to how the validator is called and setup
2018-08-14 13:18:07 -07:00
Leila Lali
2304c32453 fixed the issue caused by my latest check in in connection service (#2220) 2018-08-13 11:56:32 -07:00
Abbie Petchtes
2b68e4a7df fix issue where tree item doesn't expand when collapsible state is expanded (#2212) 2018-08-13 09:59:26 -07:00
Abbie Petchtes
8f06e72318 add aria label to the title of dialog (#2210) 2018-08-10 15:34:59 -07:00
Aditya Bist
5fa740ead4 added fix for disabling dropdown (#2203) 2018-08-10 11:07:54 -07:00
Leila Lali
e5096e61e5 Feature/ext connection dialog (#2201)
* Connection Dialog API for extensions
2018-08-10 09:29:46 -07:00
Amir Ali Omidi
2a3195636e Feat/import/language used (#2204)
Updated language used by flat file import
2018-08-09 15:44:58 -07:00
Amir Ali Omidi
5fb9b8ccd3 Clean importer startup (#2197)
* Cleans up issues from: https://github.com/Microsoft/sqlopsstudio/issues/2183
2018-08-09 14:34:17 -07:00
Amir Ali Omidi
7089e2299a Switches distribution of OSX binaries: https://github.com/Microsoft/sqlopsstudio/issues/2188 (#2198) 2018-08-09 14:00:23 -07:00
Anthony Dresser
b553cbb68c update build to use yarn to fix errors (#2191) 2018-08-08 15:09:12 -07:00
Amir Ali Omidi
c712411e77 Fixes the build issues. (#2190) 2018-08-08 14:21:21 -07:00
Leila Lali
48d5cc554c fixed the bug with collapsibleState (#2189) 2018-08-08 12:27:55 -07:00
Amir Ali Omidi
39bfd69dc9 Import Wizard (#2130)
Flat File Importer
2018-08-07 17:27:07 -07:00
Karl Burtram
d690b80493 Avoid null ref when workspace folder uri is undefined (#2179) 2018-08-07 19:48:26 -04:00
Karl Burtram
eb48a9f993 Fix Profiler missing title property error message (#2176) 2018-08-07 19:06:45 -04:00
Leila Lali
5a54abaf44 fixed the issue with tree component layout (#2174) 2018-08-07 14:10:57 -07:00
Karl Burtram
47c161f9f1 Bump SQL Ops to 0.32.4 and bump extensions (#2169) 2018-08-07 14:51:10 -04:00
Karl Burtram
807f8e68f3 Fix drag-n-drop null ref in OE (#2163) 2018-08-07 13:57:59 -04:00
Karl Burtram
fba8536c33 Update active grid tracking to fix copy bug (#2162) 2018-08-07 13:57:24 -04:00
Aditya Bist
36fc1bb71a added option for desktop icon (#2155) 2018-08-07 08:41:46 -07:00
Cory Rivera
ccaa96c81e Add >= and <= operators for context key expressions. (#2160) 2018-08-06 18:24:18 -07:00
Anthony Dresser
ac2f279c88 Fix edit data bugs (#2157)
* fix edit data bugs

* formatting
2018-08-06 20:42:52 -04:00
Aditya Bist
91cb99b8c8 changed linux packaging comment (#2158) 2018-08-06 17:25:21 -07:00
Aditya Bist
02b1a525e3 made the tree single click mode (#2159) 2018-08-06 17:17:05 -07:00
Leila Lali
9bf4a4b18c Added node selectdEvent (#2153)
* Added model view tree node selectedEvent
2018-08-06 13:19:37 -07:00
Karl Burtram
8a01553c49 Remove edit under vs directory from previous commit (#2154) 2018-08-06 15:32:15 -04:00
AlexFsmn
21b913845f Fixed bug where proper file extension wasn't appended to filename. (#2151)
#892
2018-08-06 14:54:58 -04:00
Karl Burtram
220e4feb1d Bump MIME module (#2148) 2018-08-03 21:24:26 -04:00
Matt Irvine
13884c0457 Support QueryInput when handling renamed files (#2146) 2018-08-03 21:12:31 -04:00
Matt Irvine
b3bb6ebc6e Use event element to determine whether to hide dropdown (#2145) 2018-08-03 21:12:18 -04:00
Matt Irvine
424eb90dd8 Save edit data scroll position when switching tabs (#2129) 2018-08-03 16:18:18 -07:00
Matt Irvine
df804d0729 Make sure chart viewer height gets set correctly (#2143) 2018-08-03 16:17:49 -07:00
Madeline MacDonald
79269cdfd5 Add session templates to profiler (#2115)
* Initial support for handling available sessions

* Displaying sessions in drop down, send session name in start profiling request

* More support for starting existing sessions

* New session dialog and session templates in user files

* Create profiler dialog and session templates

* Preliminary session template changes

* Saving some changes

* Send session templates when creating sessions

* Saving changes

* UI Fixes for dialog

* Formatting fixes

* removing comments

* Fixing PR comments

* bumping toolsservice and dataprotocolclient versions

* Fixing starting existing sessions
2018-08-03 15:24:50 -07:00
Matt Irvine
2a650d4d74 Do not disconnect editor when canceling change connection (#2144) 2018-08-03 15:22:11 -07:00
Aditya Bist
017b4ecdb3 fixed breaking bug in job history and fixed accordion style (#2138) 2018-08-03 13:04:47 -07:00
Matt Irvine
9c84bf3fd5 Fix directory name error exporting results multiple times (#2134) 2018-08-03 11:52:01 -07:00
Karl Burtram
397b54a8c3 Update dashboard, profiler and query icons (#2135) 2018-08-03 00:32:12 -04:00
Chris LaFreniere
cb3604c0a1 Add NodeLabel to TreeNodeContextKey (#2113) 2018-08-02 13:38:55 -07:00
Karl Burtram
1a7f0673ea Prevent the insights dialog from showing duplicate buttons (#2122) 2018-08-02 13:58:05 -04:00
Leila Lali
0d043207b9 Feature/tree component (#2077)
*added tree component to the model builder
2018-08-02 10:50:05 -07:00
Karl Burtram
f995dea971 Connect the editor for Script operations (#2123) 2018-08-02 13:48:47 -04:00
Sebastian Pfliegel
49e20488bc Added more saveAsCsv options (#2099)
Fixes #203
Options:
lineSeperator
textIdentifier
encoding
2018-08-01 22:51:31 -04:00
Karl Burtram
3e47b27192 Bump Tools Service to 1.5.20 (#2121)
This picks up a fix for https://github.com/Microsoft/sqlopsstudio/issues/2117
2018-08-01 21:05:30 -04:00
Karl Burtram
f4fa18ec05 Add GetConnectionString command (without build break) (#2120)
* Revert "Revert "Adds "Get Connection String" command (#2108)" (#2116)"

This reverts commit c6d1fa2b7d.

* Fix build breaks
2018-08-01 20:15:14 -04:00
Karl Burtram
c6d1fa2b7d Revert "Adds "Get Connection String" command (#2108)" (#2116)
This reverts commit c4df7667ff.
2018-08-01 19:00:24 -04:00
Karl Burtram
c4df7667ff Adds "Get Connection String" command (#2108)
* Initial connection string changes

* Add Get Connection String methods

* Add IncludePassword parameter

* Update Data Protocol and Tools Service versions

* Update dataprotocol to 0.2.2

* Try to update the dataprotocol component again

* Fix a few bugs related to getting the current editor connection uri
2018-08-01 18:44:54 -04:00
Karl Burtram
6a303cfa25 Bump tools service to 1.5.0-alpha.18 2018-08-01 00:17:49 -07:00
Karl Burtram
4ffa5cc1da Bump Server Reports extension to 0.1.3 (#2104) 2018-07-31 22:15:56 -04:00
Aditya Bist
172e1cf3bf put parse syntax in command palette instead of editor (#2103) 2018-07-31 16:27:11 -07:00
Karl Burtram
e6a32e52f5 Bump SQL Ops to 0.32.3 (#2088) 2018-07-30 18:06:44 -04:00
149 changed files with 5723 additions and 649 deletions

View File

@@ -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
});
});
}

View File

@@ -60,6 +60,7 @@ 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}"

View File

@@ -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",

1
extensions/import/.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
flatfileimportservice/

View File

@@ -0,0 +1,2 @@
client/src/**
client/tsconfig.json

View 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.
![image](https://user-images.githubusercontent.com/30873802/43433347-c958ed28-942b-11e8-8bbc-f4f2529c3978.png)
## 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).

View 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

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

View 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": {
}
}

View 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';

View 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();
}
}

View 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));
}
}

View 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();
}
}

View 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"
]
}

View 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>;
}

View 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
});
}
}

View 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();

View 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 {
}
}

View 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'
}

View 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();

View 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'/>

View 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;
}
}

View 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;
}

View 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;
}
}

View 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;
}

View 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
});
}
}

View 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([]);
}
}

View 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;
// }
// }
}

View 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
View 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"

View File

@@ -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"
}
}

View File

@@ -1,6 +1,6 @@
{
"downloadUrl": "https://github.com/Microsoft/sqltoolsservice/releases/download/v{#version#}/microsoft.sqltools.servicelayer-{#fileName#}",
"version": "1.5.0-alpha.14",
"version": "1.5.0-alpha.27",
"downloadFileNames": {
"Windows_86": "win-x86-netcoreapp2.1.zip",
"Windows_64": "win-x64-netcoreapp2.1.zip",

View File

@@ -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"

View 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; });
}
}

View File

@@ -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);
}
}

View File

@@ -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();
});
}
}

View File

@@ -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'/>

View File

@@ -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"
}

View File

@@ -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"

View File

@@ -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"

View File

@@ -1,6 +1,6 @@
{
"name": "sqlops",
"version": "0.32.2",
"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",
@@ -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": {

View File

@@ -35,6 +35,7 @@
"date": "2017-12-15T12:00:00.000Z",
"recommendedExtensions": [
"Microsoft.agent",
"Microsoft.import",
"Microsoft.profiler",
"Microsoft.server-report",
"Microsoft.whoisactive",

View File

@@ -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@@

View File

@@ -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

View File

@@ -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": {

View File

@@ -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"
},

View File

@@ -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,18 +385,20 @@ 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();
}

View 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);
}
}
}

View File

@@ -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();

View File

@@ -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

View File

@@ -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 {

View File

@@ -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;
}

View 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

View File

@@ -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 {

View File

@@ -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;

View File

@@ -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',

View File

@@ -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);
}
});
}
}

View File

@@ -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>;
@@ -260,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 {
@@ -299,6 +322,7 @@ export interface INewConnectionParams {
runQueryOnCompletion?: RunQueryOnConnectionMode;
querySelection?: sqlops.ISelectionData;
showDashboard?: boolean;
providers?: string[];
}
export interface IConnectableInput {

View File

@@ -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,7 +644,7 @@ 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();
}
@@ -1002,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 });
}
}
});
@@ -1120,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);
}
});
}
@@ -1345,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;
});
});
}
}

View File

@@ -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;
}
}

View File

@@ -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();

View File

@@ -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);

View File

@@ -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();
});
}

View File

@@ -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) {

View File

@@ -57,7 +57,7 @@ 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 { 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,

View File

@@ -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();

View File

@@ -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,

View File

@@ -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;

View File

@@ -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();
}
}
}
}

View File

@@ -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,

View File

@@ -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 {

View File

@@ -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"

View File

@@ -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;
}
}
}

View File

@@ -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) {
@@ -238,8 +228,12 @@ export abstract class GridParentComponent {
protected getSelection(index?: number): ISlickRange[] {
let selection = this.slickgrids.toArray()[index || this.activeGrid].getSelectedRanges();
selection = selection.map(c => { return <ISlickRange>{ fromCell: c.fromCell - 1, toCell: c.toCell - 1, toRow: c.toRow, fromRow: c.fromRow }; });
return selection;
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 {

View File

@@ -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);
});
}

View File

@@ -15,18 +15,20 @@
(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">

View File

@@ -160,6 +160,7 @@ 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[][];
@@ -266,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>();
@@ -402,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();

View File

@@ -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) {

View File

@@ -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;
}

View File

@@ -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;
};

View File

@@ -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 = {};

View File

@@ -14,6 +14,7 @@ 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';
@@ -73,6 +74,9 @@ 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);

View File

@@ -17,7 +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 { 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';

View File

@@ -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 : {};
}
}

View File

@@ -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');

View File

@@ -25,7 +25,7 @@ import * as sqlops from 'sqlops';
@Component({
selector: 'modelview-content',
template: `
<div *ngIf="rootDescriptor" style="width: 100%; height: 100%;">
<div *ngIf="rootDescriptor" style="width: 100%; height: 100%; position: absolute;">
<model-component-wrapper style="display: block; height: 100%" [descriptor]="rootDescriptor" [modelStore]="modelStore">
</model-component-wrapper>
</div>

View File

@@ -0,0 +1,160 @@
/*---------------------------------------------------------------------------------------------
* 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!sql/parts/modelComponents/tree/treeComponent';
import 'vs/css!sql/media/icons/common-icons';
import {
Component, Input, Inject, ChangeDetectorRef, forwardRef,
ViewChild, ElementRef, OnDestroy, AfterViewInit
} from '@angular/core';
import * as sqlops from 'sqlops';
import { ComponentBase } from 'sql/parts/modelComponents/componentBase';
import { IComponent, IComponentDescriptor, IModelStore, ComponentEventType } from 'sql/parts/modelComponents/interfaces';
import { Tree } from 'vs/base/parts/tree/browser/treeImpl';
import { CommonServiceInterface } from 'sql/services/common/commonServiceInterface.service';
import { TreeComponentRenderer } from 'sql/parts/modelComponents/tree/treeComponentRenderer';
import { TreeComponentDataSource } from 'sql/parts/modelComponents/tree/treeDataSource';
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { attachListStyler } from 'vs/platform/theme/common/styler';
import { DefaultFilter, DefaultAccessibilityProvider, DefaultController } from 'vs/base/parts/tree/browser/treeDefaults';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ITreeComponentItem } from 'sql/workbench/common/views';
import { TreeViewDataProvider } from './treeViewDataProvider';
import { getContentHeight, getContentWidth } from 'vs/base/browser/dom';
class Root implements ITreeComponentItem {
label = 'root';
handle = '0';
parentHandle = null;
collapsibleState = 2;
children = void 0;
options = undefined;
}
@Component({
selector: 'modelview-tree',
template: `
<div #input style="width: 100%;height:100%"></div>
`
})
export default class TreeComponent extends ComponentBase implements IComponent, OnDestroy, AfterViewInit {
@Input() descriptor: IComponentDescriptor;
@Input() modelStore: IModelStore;
private _tree: Tree;
private _treeRenderer: TreeComponentRenderer;
private _dataProvider: TreeViewDataProvider;
@ViewChild('input', { read: ElementRef }) private _inputContainer: ElementRef;
constructor(
@Inject(forwardRef(() => CommonServiceInterface)) private _commonService: CommonServiceInterface,
@Inject(forwardRef(() => ChangeDetectorRef)) changeRef: ChangeDetectorRef,
@Inject(IWorkbenchThemeService) private themeService: IWorkbenchThemeService,
@Inject(IContextViewService) private contextViewService: IContextViewService,
@Inject(IInstantiationService) private _instantiationService: IInstantiationService) {
super(changeRef);
}
ngOnInit(): void {
this.baseInit();
}
ngAfterViewInit(): void {
if (this._inputContainer) {
this.createTreeControl();
}
}
ngOnDestroy(): void {
this.baseDestroy();
}
public setDataProvider(handle: number, componentId: string, context: any): any {
this._dataProvider = new TreeViewDataProvider(handle, componentId, context);
this.createTreeControl();
}
public refreshDataProvider(itemsToRefreshByHandle: { [treeItemHandle: string]: ITreeComponentItem }): void {
if (this._dataProvider) {
this._dataProvider.refresh(itemsToRefreshByHandle);
}
if (this._tree) {
for (const item of Object.values(itemsToRefreshByHandle)) {
this._tree.refresh(<ITreeComponentItem>item);
}
}
}
private createTreeControl(): void {
if (!this._tree && this._dataProvider) {
const dataSource = this._instantiationService.createInstance(TreeComponentDataSource, this._dataProvider);
const renderer = this._instantiationService.createInstance(TreeComponentRenderer, this._dataProvider, this.themeService, { withCheckbox: this.withCheckbox });
this._treeRenderer = renderer;
const controller = new DefaultController();
const filter = new DefaultFilter();
const sorter = undefined;
const dnd = undefined;
const accessibilityProvider = new DefaultAccessibilityProvider();
this._tree = new Tree(this._inputContainer.nativeElement,
{ dataSource, renderer, controller, dnd, filter, sorter, accessibilityProvider },
{
indentPixels: 10,
twistiePixels: 20,
ariaLabel: 'Tree Node'
});
this._tree.setInput(new Root());
this._tree.domFocus();
this._register(this._tree);
this._register(attachListStyler(this._tree, this.themeService));
this._register(this._tree.onDidChangeSelection( e => {
this._dataProvider.onNodeSelected(e.selection);
}));
this._tree.refresh();
this.layout();
}
}
/// IComponent implementation
public layout(): void {
this._changeRef.detectChanges();
if (this._tree) {
this.layoutTree();
this._tree.refresh();
}
}
private layoutTree(): void {
let width: number = this.convertSizeToNumber(this.width);
let height: number = this.convertSizeToNumber(this.height);
this._tree.layout(
height && height > 0 ? height : getContentHeight(this._inputContainer.nativeElement),
width && width > 0 ? width : getContentWidth(this._inputContainer.nativeElement));
}
public setLayout(layout: any): void {
// TODO allow configuring the look and feel
this.layout();
}
public setProperties(properties: { [key: string]: any; }): void {
super.setProperties(properties);
this._treeRenderer.options.withCheckbox = this.withCheckbox;
}
public get withCheckbox(): boolean {
return this.getPropertyOrDefault<sqlops.TreeProperties, boolean>((props) => props.withCheckbox, false);
}
public set withCheckbox(newValue: boolean) {
this.setPropertyFromUI<sqlops.TreeProperties, boolean>((properties, value) => { properties.withCheckbox = value; }, newValue);
}
}

View File

@@ -0,0 +1,8 @@
.tree-component-node-tile {
display: flex;
}
.tree-component-node-tile .model-view-tree-node-item-icon{
width: 15px;
height: 15px;
}

View 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 'vs/css!sql/media/icons/common-icons';
import * as dom from 'vs/base/browser/dom';
import { localize } from 'vs/nls';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ITree, IRenderer } from 'vs/base/parts/tree/browser/tree';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { LIGHT } from 'vs/platform/theme/common/themeService';
import { Disposable } from 'vs/base/common/lifecycle';
import { Event, Emitter } from 'vs/base/common/event';
import { ITreeComponentItem } from 'sql/workbench/common/views';
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
import { TreeViewDataProvider } from './treeViewDataProvider';
export enum TreeCheckboxState {
Intermediate = 0,
Checked = 1,
Unchecked = 2
}
export class TreeDataTemplate extends Disposable {
root: HTMLElement;
label: HTMLSpanElement;
icon: HTMLElement;
private _checkbox: HTMLInputElement;
model: ITreeComponentItem;
private _onChange = new Emitter<boolean>();
public readonly onChange: Event<boolean> = this._onChange.event;
public set checkbox(input: HTMLInputElement) {
this._checkbox = input;
this.handleOnChange(this._checkbox, () => {
this._onChange.fire(this._checkbox.checked);
if (this.model && this.model.onCheckedChanged) {
this.model.onCheckedChanged(this._checkbox.checked);
}
});
}
public get checkboxState(): TreeCheckboxState {
if (this._checkbox.indeterminate) {
return TreeCheckboxState.Intermediate;
} else {
return this.checkbox.checked ? TreeCheckboxState.Checked : TreeCheckboxState.Unchecked;
}
}
public set checkboxState(value: TreeCheckboxState) {
if (this.checkboxState !== value) {
switch (value) {
case TreeCheckboxState.Checked:
this._checkbox.indeterminate = false;
this._checkbox.checked = true;
break;
case TreeCheckboxState.Unchecked:
this._checkbox.indeterminate = false;
this._checkbox.checked = false;
break;
case TreeCheckboxState.Intermediate:
this._checkbox.indeterminate = true;
break;
default:
break;
}
}
}
public get checkbox(): HTMLInputElement {
return this._checkbox;
}
protected handleOnChange(domNode: HTMLElement, listener: (e: Event<void>) => void): void {
this._register(dom.addDisposableListener(domNode, dom.EventType.CHANGE, listener));
}
}
/**
* Renders the tree items.
* Uses the dom template to render connection groups and connections.
*/
export class TreeComponentRenderer extends Disposable implements IRenderer {
public static DEFAULT_TEMPLATE = 'DEFAULT_TEMPLATE';
public static DEFAULT_HEIGHT = 20;
constructor(
private _dataProvider: TreeViewDataProvider,
private themeService: IWorkbenchThemeService,
public options?: { withCheckbox: boolean }
) {
super();
}
/**
* Returns the element's height in the tree, in pixels.
*/
public getHeight(tree: ITree, element: any): number {
return TreeComponentRenderer.DEFAULT_HEIGHT;
}
/**
* Returns a template ID for a given element.
*/
public getTemplateId(tree: ITree, element: any): string {
return TreeComponentRenderer.DEFAULT_TEMPLATE;
}
/**
* Render template in a dom element based on template id
*/
public renderTemplate(tree: ITree, templateId: string, container: HTMLElement): any {
if (templateId === TreeComponentRenderer.DEFAULT_TEMPLATE) {
const nodeTemplate: TreeDataTemplate = new TreeDataTemplate();
nodeTemplate.root = dom.append(container, dom.$('.tree-component-node-tile'));
nodeTemplate.icon = dom.append(nodeTemplate.root, dom.$('div.model-view-tree-node-item-icon'));
if (this.options && this.options.withCheckbox) {
let checkboxWrapper = dom.append(nodeTemplate.root, dom.$('div.checkboxWrapper'));
nodeTemplate.checkbox = dom.append(checkboxWrapper, dom.$<HTMLInputElement>('input.checkbox', { type: 'checkbox' }));
}
nodeTemplate.label = dom.append(nodeTemplate.root, dom.$('div.model-view-tree-node-item-label'));
return nodeTemplate;
}
}
/**
* Render a element, given an object bag returned by the template
*/
public renderElement(tree: ITree, element: ITreeComponentItem, templateId: string, templateData: TreeDataTemplate): void {
const icon = this.themeService.getTheme().type === LIGHT ? element.icon : element.iconDark;
templateData.icon.style.backgroundImage = icon ? `url('${icon}')` : '';
dom.toggleClass(templateData.icon, 'model-view-tree-node-item-icon', !!icon);
if (element) {
element.onCheckedChanged = (checked: boolean) => {
this._dataProvider.onNodeCheckedChanged(element.handle, checked);
}
templateData.model = element;
}
if (templateId === TreeComponentRenderer.DEFAULT_TEMPLATE) {
this.renderNode(element, templateData);
}
}
private renderNode(treeNode: ITreeComponentItem, templateData: TreeDataTemplate): void {
let label = treeNode.label;
templateData.label.textContent = label;
templateData.root.title = label;
templateData.checkboxState = this.getCheckboxState(treeNode);
}
private getCheckboxState(treeNode: ITreeComponentItem): TreeCheckboxState {
if (treeNode.checked === undefined) {
return TreeCheckboxState.Intermediate;
} else {
return treeNode.checked ? TreeCheckboxState.Checked : TreeCheckboxState.Unchecked;
}
}
public disposeTemplate(tree: ITree, templateId: string, templateData: any): void {
this.dispose();
// no op
}
}

View File

@@ -0,0 +1,62 @@
/*---------------------------------------------------------------------------------------------
* 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 { ITree, IDataSource } from 'vs/base/parts/tree/browser/tree';
import { TPromise } from 'vs/base/common/winjs.base';
import { IModelViewTreeViewDataProvider, ITreeComponentItem } from 'sql/workbench/common/views';
import { TreeItemCollapsibleState } from 'vs/workbench/common/views';
/**
* Implements the DataSource(that returns a parent/children of an element) for the recent connection tree
*/
export class TreeComponentDataSource implements IDataSource {
/**
*
*/
constructor(
private _dataProvider: IModelViewTreeViewDataProvider) {
}
/**
* Returns the unique identifier of the given element.
* No more than one element may use a given identifier.
*/
public getId(tree: ITree, node: ITreeComponentItem): string {
return node.handle;
}
/**
* Returns a boolean value indicating whether the element has children.
*/
public hasChildren(tree: ITree, node: ITreeComponentItem): boolean {
return this._dataProvider !== undefined && node.collapsibleState !== TreeItemCollapsibleState.None;
}
/**
* Returns the element's children as an array in a promise.
*/
public getChildren(tree: ITree, node: ITreeComponentItem): TPromise<any> {
if (this._dataProvider) {
if (node && node.handle === '0') {
return this._dataProvider.getChildren(undefined);
} else {
return this._dataProvider.getChildren(node);
}
}
return TPromise.as([]);
}
public getParent(tree: ITree, node: any): TPromise<any> {
return TPromise.as(null);
}
public shouldAutoexpand(tree: ITree, node: ITreeComponentItem): boolean {
return node.collapsibleState === TreeItemCollapsibleState.Expanded;
}
}

View File

@@ -0,0 +1,31 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { ExtHostModelViewTreeViewsShape, SqlExtHostContext } from 'sql/workbench/api/node/sqlExtHost.protocol';
import { IExtHostContext } from 'vs/workbench/api/node/extHost.protocol';
import { IModelViewTreeViewDataProvider, ITreeComponentItem } from 'sql/workbench/common/views';
import { INotificationService } from 'vs/platform/notification/common/notification';
import * as vsTreeView from 'vs/workbench/api/electron-browser/mainThreadTreeViews';
export class TreeViewDataProvider extends vsTreeView.TreeViewDataProvider implements IModelViewTreeViewDataProvider {
constructor(handle: number, treeViewId: string,
context: IExtHostContext,
notificationService?: INotificationService
) {
super(`${handle}-${treeViewId}`, context.getProxy(SqlExtHostContext.ExtHostModelViewTreeViews), notificationService);
}
onNodeCheckedChanged(treeItemHandle?: string, checked?: boolean) {
(<ExtHostModelViewTreeViewsShape>this._proxy).$onNodeCheckedChanged(this.treeViewId, treeItemHandle, checked);
}
onNodeSelected(items: ITreeComponentItem[]) {
if (items) {
(<ExtHostModelViewTreeViewsShape>this._proxy).$onNodeSelected(this.treeViewId, items.map(i => i.handle));
}
}
}

View File

@@ -10,7 +10,7 @@ import { Registry } from 'vs/platform/registry/common/platform';
import nls = require('vs/nls');
import * as sqlops from 'sqlops';
import { IModelStore, IComponentDescriptor, IComponent, IComponentEventArgs } from './interfaces';
import { IModelStore, IComponentDescriptor, IComponent } from './interfaces';
import { IItemConfig, ModelComponentTypes, IComponentShape } from 'sql/workbench/api/common/sqlExtHostTypes';
import { IModelView, IModelViewEventArgs } from 'sql/services/model/modelViewService';
import { Extensions, IComponentRegistry } from 'sql/platform/dashboard/common/modelComponentRegistry';
@@ -97,6 +97,10 @@ export abstract class ViewBase extends AngularDisposable implements IModelView {
this.queueAction(componentId, (component) => component.setProperties(properties));
}
refreshDataProvider(componentId: string, item: any): void {
this.queueAction(componentId, (component) => component.refreshDataProvider(item));
}
private queueAction<T>(componentId: string, action: (component: IComponent) => T): void {
this.modelStore.eventuallyRunOnComponent(componentId, action).catch(err => {
// TODO add error handling
@@ -122,4 +126,8 @@ export abstract class ViewBase extends AngularDisposable implements IModelView {
public validate(componentId: string): Thenable<boolean> {
return new Promise(resolve => this.modelStore.eventuallyRunOnComponent(componentId, component => resolve(component.validate())));
}
public setDataProvider(handle: number, componentId: string, context: any): any {
return this.queueAction(componentId, (component) => component.setDataProvider(handle, componentId, context));
}
}

View File

@@ -96,24 +96,24 @@ export class ServerTreeDragAndDrop implements IDragAndDrop {
let targetConnectionProfileGroup: ConnectionProfileGroup = this.getTargetGroup(targetElement);
const source = data.getData()[0];
let oldParent: ConnectionProfileGroup = source.getParent();
const self = this;
if (this.isDropAllowed(targetConnectionProfileGroup, oldParent, source)) {
if (source && source.getParent) {
let oldParent: ConnectionProfileGroup = source.getParent();
const self = this;
if (this.isDropAllowed(targetConnectionProfileGroup, oldParent, source)) {
if (source instanceof ConnectionProfile) {
// Change group id of profile
this._connectionManagementService.changeGroupIdForConnection(source, targetConnectionProfileGroup.id).then(() => {
TreeUpdateUtils.registeredServerUpdate(tree, self._connectionManagementService, targetConnectionProfileGroup);
});
} else if (source instanceof ConnectionProfileGroup) {
// Change parent id of group
this._connectionManagementService.changeGroupIdForConnectionGroup(source, targetConnectionProfileGroup).then(() => {
TreeUpdateUtils.registeredServerUpdate(tree, self._connectionManagementService);
});
if (source instanceof ConnectionProfile) {
// Change group id of profile
this._connectionManagementService.changeGroupIdForConnection(source, targetConnectionProfileGroup.id).then(() => {
TreeUpdateUtils.registeredServerUpdate(tree, self._connectionManagementService, targetConnectionProfileGroup);
});
} else if (source instanceof ConnectionProfileGroup) {
// Change parent id of group
this._connectionManagementService.changeGroupIdForConnectionGroup(source, targetConnectionProfileGroup).then(() => {
TreeUpdateUtils.registeredServerUpdate(tree, self._connectionManagementService);
});
}
}
}
return;
}
public dropAbort(tree: ITree, data: IDragAndDropData): void {

View File

@@ -15,11 +15,13 @@ export class TreeNodeContextKey implements IContextKey<TreeNode> {
static SubType = new RawContextKey<string>('nodeSubType', undefined);
static Status = new RawContextKey<string>('nodeStatus', undefined);
static TreeNode = new RawContextKey<TreeNode>('treeNode', undefined);
static NodeLabel = new RawContextKey<string>('nodeLabel', undefined);
private _nodeTypeKey: IContextKey<string>;
private _subTypeKey: IContextKey<string>;
private _statusKey: IContextKey<string>;
private _treeNodeKey: IContextKey<TreeNode>;
private _nodeLabelKey: IContextKey<string>;
constructor(
@IContextKeyService contextKeyService: IContextKeyService
@@ -28,6 +30,7 @@ export class TreeNodeContextKey implements IContextKey<TreeNode> {
this._subTypeKey = TreeNodeContextKey.SubType.bindTo(contextKeyService);
this._statusKey = TreeNodeContextKey.Status.bindTo(contextKeyService);
this._treeNodeKey = TreeNodeContextKey.TreeNode.bindTo(contextKeyService);
this._nodeLabelKey = TreeNodeContextKey.NodeLabel.bindTo(contextKeyService);
}
set(value: TreeNode) {
@@ -35,6 +38,7 @@ export class TreeNodeContextKey implements IContextKey<TreeNode> {
this._nodeTypeKey.set(value && value.nodeTypeId);
this._subTypeKey.set(value && value.nodeSubType);
this._statusKey.set(value && value.nodeStatus);
this._nodeLabelKey.set(value && value.label);
}
reset(): void {
@@ -42,6 +46,7 @@ export class TreeNodeContextKey implements IContextKey<TreeNode> {
this._subTypeKey.reset();
this._statusKey.reset();
this._treeNodeKey.reset();
this._nodeLabelKey.reset();
}
public get(): TreeNode {

View File

@@ -22,7 +22,7 @@ export class TreeUpdateUtils {
/**
* Set input for the tree.
*/
public static structuralTreeUpdate(tree: ITree, viewKey: string, connectionManagementService: IConnectionManagementService): void {
public static structuralTreeUpdate(tree: ITree, viewKey: string, connectionManagementService: IConnectionManagementService, providers?: string[]): void {
let selectedElement: any;
let targetsToExpand: any[];
if (tree) {
@@ -35,13 +35,13 @@ export class TreeUpdateUtils {
let groups;
let treeInput = new ConnectionProfileGroup('root', null, undefined, undefined, undefined);
if (viewKey === 'recent') {
groups = connectionManagementService.getRecentConnections();
groups = connectionManagementService.getRecentConnections(providers);
treeInput.addConnections(groups);
} else if (viewKey === 'active') {
groups = connectionManagementService.getActiveConnections();
groups = connectionManagementService.getActiveConnections(providers);
treeInput.addConnections(groups);
} else if (viewKey === 'saved') {
treeInput = TreeUpdateUtils.getTreeInput(connectionManagementService);
treeInput = TreeUpdateUtils.getTreeInput(connectionManagementService, providers);
}
tree.setInput(treeInput).done(() => {
@@ -96,9 +96,9 @@ export class TreeUpdateUtils {
}
}
public static getTreeInput(connectionManagementService: IConnectionManagementService): ConnectionProfileGroup {
public static getTreeInput(connectionManagementService: IConnectionManagementService, providers?: string[]): ConnectionProfileGroup {
let groups = connectionManagementService.getConnectionGroups();
let groups = connectionManagementService.getConnectionGroups(providers);
if (groups && groups.length > 0) {
let treeInput = groups[0];
treeInput.name = 'root';

View File

@@ -13,7 +13,7 @@ import * as nls from 'vs/nls';
import { ProfilerInput } from 'sql/parts/profiler/editor/profilerInput';
import { ProfilerEditor } from 'sql/parts/profiler/editor/profilerEditor';
import { PROFILER_VIEW_TEMPLATE_SETTINGS, IProfilerViewTemplate } from 'sql/parts/profiler/service/interfaces';
import { PROFILER_VIEW_TEMPLATE_SETTINGS, PROFILER_SESSION_TEMPLATE_SETTINGS, IProfilerViewTemplate, IProfilerSessionTemplate } from 'sql/parts/profiler/service/interfaces';
const profilerDescriptor = new EditorDescriptor(
ProfilerEditor,
@@ -25,212 +25,300 @@ Registry.as<IEditorRegistry>(EditorExtensions.Editors)
.registerEditor(profilerDescriptor, [new SyncDescriptor(ProfilerInput)]);
const profilerViewTemplateSchema: IJSONSchema = {
description: nls.localize('profiler.settings.viewTemplates', "Specifies view templates"),
type: 'array',
items: <IJSONSchema>{
type: 'object',
properties: {
name: {
type: 'string'
description: nls.localize('profiler.settings.viewTemplates', "Specifies view templates"),
type: 'array',
items: <IJSONSchema>{
type: 'object',
properties: {
name: {
type: 'string'
}
}
},
default: <Array<IProfilerViewTemplate>>[
{
name: 'Standard View',
columns: [
{
name: 'EventClass',
eventsMapped: ['name']
},
{
name: 'TextData',
eventsMapped: ['options_text', 'batch_text']
},
{
name: 'ApplicationName',
width: '1',
eventsMapped: ['client_app_name']
},
{
name: 'NTUserName',
eventsMapped: ['nt_username']
},
{
name: 'LoginName',
eventsMapped: ['server_principal_name']
},
{
name: 'ClientProcessID',
eventsMapped: ['client_pid']
},
{
name: 'SPID',
eventsMapped: ['session_id']
},
{
name: 'StartTime',
eventsMapped: ['timestamp']
},
{
name: 'CPU',
eventsMapped: ['cpu_time']
},
{
name: 'Reads',
eventsMapped: ['logical_reads']
},
{
name: 'Writes',
eventsMapped: ['writes']
},
{
name: 'Duration',
eventsMapped: ['duration']
}
}
]
},
default: <Array<IProfilerViewTemplate>>[
{
name: 'Standard View',
columns: [
{
name: 'EventClass',
eventsMapped: ['name']
},
{
name: 'TextData',
eventsMapped: ['options_text', 'batch_text']
},
{
name: 'ApplicationName',
width: '1',
eventsMapped: ['client_app_name']
},
{
name: 'NTUserName',
eventsMapped: ['nt_username']
},
{
name: 'LoginName',
eventsMapped: ['server_principal_name']
},
{
name: 'ClientProcessID',
eventsMapped: ['client_pid']
},
{
name: 'SPID',
eventsMapped: ['session_id']
},
{
name: 'StartTime',
eventsMapped: ['timestamp']
},
{
name: 'CPU',
eventsMapped: ['cpu_time']
},
{
name: 'Reads',
eventsMapped: ['logical_reads']
},
{
name: 'Writes',
eventsMapped: ['writes']
},
{
name: 'Duration',
eventsMapped: ['duration']
}
]
},
{
name: 'TSQL View',
columns: [
{
name: 'EventClass',
eventsMapped: ['name']
},
{
name: 'TextData',
eventsMapped: ['options_text', 'batch_text']
},
{
name: 'SPID',
eventsMapped: ['session_id']
},
{
name: 'StartTime',
eventsMapped: ['timestamp']
}
]
},
{
name: 'Tuning View',
columns: [
{
name: 'EventClass',
eventsMapped: ['name']
},
{
name: 'TextData',
eventsMapped: ['options_text', 'batch_text']
},
{
name: 'Duration',
eventsMapped: ['duration']
},
{
name: 'SPID',
eventsMapped: ['session_id']
},
{
name: 'DatabaseID',
eventsMapped: ['database_id']
},
{
name: 'DatabaseName',
eventsMapped: ['database_name']
},
{
name: 'ObjectType',
eventsMapped: ['object_type']
},
{
name: 'LoginName',
eventsMapped: ['server_principal_name']
}
]
},
{
name: 'TSQL_Locks View',
columns: [
{
name: 'EventClass',
eventsMapped: ['name']
},
{
name: 'TextData',
eventsMapped: ['options_text', 'batch_text']
},
{
name: 'ApplicationName',
eventsMapped: ['client_app_name']
},
{
name: 'NTUserName',
eventsMapped: ['nt_username']
},
{
name: 'LoginName',
eventsMapped: ['server_principal_name']
},
{
name: 'ClientProcessID',
eventsMapped: ['client_pid']
},
{
name: 'SPID',
eventsMapped: ['session_id']
},
{
name: 'StartTime',
eventsMapped: ['timestamp']
},
{
name: 'CPU',
eventsMapped: ['cpu_time']
},
{
name: 'Reads',
eventsMapped: ['logical_reads']
},
{
name: 'Writes',
eventsMapped: ['writes']
},
{
name: 'Duration',
eventsMapped: ['duration']
}
]
},
{
name: 'TSQL_Duration View',
columns: [
{
name: 'EventClass',
eventsMapped: ['name']
},
{
name: 'Duration',
eventsMapped: ['duration']
},
{
name: 'TextData',
eventsMapped: ['options_text', 'batch_text']
},
{
name: 'SPID',
eventsMapped: ['session_id']
}
]
{
name: 'TSQL View',
columns: [
{
name: 'EventClass',
eventsMapped: ['name']
},
{
name: 'TextData',
eventsMapped: ['options_text', 'batch_text']
},
{
name: 'SPID',
eventsMapped: ['session_id']
},
{
name: 'StartTime',
eventsMapped: ['timestamp']
}
]
},
{
name: 'Tuning View',
columns: [
{
name: 'EventClass',
eventsMapped: ['name']
},
{
name: 'TextData',
eventsMapped: ['options_text', 'batch_text']
},
{
name: 'Duration',
eventsMapped: ['duration']
},
{
name: 'SPID',
eventsMapped: ['session_id']
},
{
name: 'DatabaseID',
eventsMapped: ['database_id']
},
{
name: 'DatabaseName',
eventsMapped: ['database_name']
},
{
name: 'ObjectType',
eventsMapped: ['object_type']
},
{
name: 'LoginName',
eventsMapped: ['server_principal_name']
}
]
},
{
name: 'TSQL_Locks View',
columns: [
{
name: 'EventClass',
eventsMapped: ['name']
},
{
name: 'TextData',
eventsMapped: ['options_text', 'batch_text']
},
{
name: 'ApplicationName',
eventsMapped: ['client_app_name']
},
{
name: 'NTUserName',
eventsMapped: ['nt_username']
},
{
name: 'LoginName',
eventsMapped: ['server_principal_name']
},
{
name: 'ClientProcessID',
eventsMapped: ['client_pid']
},
{
name: 'SPID',
eventsMapped: ['session_id']
},
{
name: 'StartTime',
eventsMapped: ['timestamp']
},
{
name: 'CPU',
eventsMapped: ['cpu_time']
},
{
name: 'Reads',
eventsMapped: ['logical_reads']
},
{
name: 'Writes',
eventsMapped: ['writes']
},
{
name: 'Duration',
eventsMapped: ['duration']
}
]
},
{
name: 'TSQL_Duration View',
columns: [
{
name: 'EventClass',
eventsMapped: ['name']
},
{
name: 'Duration',
eventsMapped: ['duration']
},
{
name: 'TextData',
eventsMapped: ['options_text', 'batch_text']
},
{
name: 'SPID',
eventsMapped: ['session_id']
}
]
}
]
};
const profilerSessionTemplateSchema: IJSONSchema = {
description: nls.localize('profiler.settings.sessionTemplates', "Specifies session templates"),
type: 'array',
items: <IJSONSchema>{
type: 'object',
properties: {
name: {
type: 'string'
}
]
};
}
},
default: <Array<IProfilerSessionTemplate>>[
{
name: 'Standard_OnPrem',
defaultView: 'Standard View',
createStatement:
`CREATE EVENT SESSION [{sessionName}] ON SERVER
ADD EVENT sqlserver.attention(
ACTION(package0.event_sequence,sqlserver.client_app_name,sqlserver.client_pid,sqlserver.database_id,sqlserver.nt_username,sqlserver.query_hash,sqlserver.server_principal_name,sqlserver.session_id)
WHERE ([package0].[equal_boolean]([sqlserver].[is_system],(0)))),
ADD EVENT sqlserver.existing_connection(SET collect_options_text=(1)
ACTION(package0.event_sequence,sqlserver.client_app_name,sqlserver.client_pid,sqlserver.nt_username,sqlserver.server_principal_name,sqlserver.session_id)),
ADD EVENT sqlserver.login(SET collect_options_text=(1)
ACTION(package0.event_sequence,sqlserver.client_app_name,sqlserver.client_pid,sqlserver.nt_username,sqlserver.server_principal_name,sqlserver.session_id)),
ADD EVENT sqlserver.logout(
ACTION(package0.event_sequence,sqlserver.client_app_name,sqlserver.client_pid,sqlserver.nt_username,sqlserver.server_principal_name,sqlserver.session_id)),
ADD EVENT sqlserver.rpc_completed(
ACTION(package0.event_sequence,sqlserver.client_app_name,sqlserver.client_pid,sqlserver.database_id,sqlserver.nt_username,sqlserver.query_hash,sqlserver.server_principal_name,sqlserver.session_id)
WHERE ([package0].[equal_boolean]([sqlserver].[is_system],(0)))),
ADD EVENT sqlserver.sql_batch_completed(
ACTION(package0.event_sequence,sqlserver.client_app_name,sqlserver.client_pid,sqlserver.database_id,sqlserver.nt_username,sqlserver.query_hash,sqlserver.server_principal_name,sqlserver.session_id)
WHERE ([package0].[equal_boolean]([sqlserver].[is_system],(0)))),
ADD EVENT sqlserver.sql_batch_starting(
ACTION(package0.event_sequence,sqlserver.client_app_name,sqlserver.client_pid,sqlserver.database_id,sqlserver.nt_username,sqlserver.query_hash,sqlserver.server_principal_name,sqlserver.session_id)
WHERE ([package0].[equal_boolean]([sqlserver].[is_system],(0))))
ADD TARGET package0.ring_buffer(SET max_events_limit=(1000),max_memory=(51200))
WITH (MAX_MEMORY=8192 KB,EVENT_RETENTION_MODE=ALLOW_SINGLE_EVENT_LOSS,MAX_DISPATCH_LATENCY=5 SECONDS,MAX_EVENT_SIZE=0 KB,MEMORY_PARTITION_MODE=PER_CPU,TRACK_CAUSALITY=ON,STARTUP_STATE=OFF)`
},
{
name: 'Standard_Azure',
defaultView: 'Standard View',
createStatement:
`CREATE EVENT SESSION [{sessionName}] ON DATABASE
ADD EVENT sqlserver.attention(
ACTION(package0.event_sequence,sqlserver.client_app_name,sqlserver.client_pid,sqlserver.database_id,sqlserver.username,sqlserver.query_hash,sqlserver.session_id)
WHERE ([package0].[equal_boolean]([sqlserver].[is_system],(0)))),
ADD EVENT sqlserver.existing_connection(SET collect_options_text=(1)
ACTION(package0.event_sequence,sqlserver.client_app_name,sqlserver.client_pid,sqlserver.username,sqlserver.session_id)),
ADD EVENT sqlserver.login(SET collect_options_text=(1)
ACTION(package0.event_sequence,sqlserver.client_app_name,sqlserver.client_pid,sqlserver.username,sqlserver.session_id)),
ADD EVENT sqlserver.logout(
ACTION(package0.event_sequence,sqlserver.client_app_name,sqlserver.client_pid,sqlserver.username,sqlserver.session_id)),
ADD EVENT sqlserver.rpc_completed(
ACTION(package0.event_sequence,sqlserver.client_app_name,sqlserver.client_pid,sqlserver.database_id,sqlserver.username,sqlserver.query_hash,sqlserver.session_id)
WHERE ([package0].[equal_boolean]([sqlserver].[is_system],(0)))),
ADD EVENT sqlserver.sql_batch_completed(
ACTION(package0.event_sequence,sqlserver.client_app_name,sqlserver.client_pid,sqlserver.database_id,sqlserver.username,sqlserver.query_hash,sqlserver.session_id)
WHERE ([package0].[equal_boolean]([sqlserver].[is_system],(0)))),
ADD EVENT sqlserver.sql_batch_starting(
ACTION(package0.event_sequence,sqlserver.client_app_name,sqlserver.client_pid,sqlserver.database_id,sqlserver.username,sqlserver.query_hash,sqlserver.session_id)
WHERE ([package0].[equal_boolean]([sqlserver].[is_system],(0))))
ADD TARGET package0.ring_buffer(SET max_events_limit=(1000),max_memory=(51200))
WITH (MAX_MEMORY=8192 KB,EVENT_RETENTION_MODE=ALLOW_SINGLE_EVENT_LOSS,MAX_DISPATCH_LATENCY=5 SECONDS,MAX_EVENT_SIZE=0 KB,MEMORY_PARTITION_MODE=PER_CPU,TRACK_CAUSALITY=ON,STARTUP_STATE=OFF)`
},
{
name: 'TSQL_OnPrem',
defaultView: 'TSQL View',
createStatement:
`CREATE EVENT SESSION [{sessionName}] ON SERVER
ADD EVENT sqlserver.existing_connection(
ACTION(package0.event_sequence,sqlserver.session_id,sqlserver.client_hostname)),
ADD EVENT sqlserver.login(SET collect_options_text=(1)
ACTION(package0.event_sequence,sqlserver.session_id,sqlserver.client_hostname)),
ADD EVENT sqlserver.logout(
ACTION(package0.event_sequence,sqlserver.session_id)),
ADD EVENT sqlserver.rpc_starting(
ACTION(package0.event_sequence,sqlserver.session_id,sqlserver.database_name)
WHERE ([package0].[equal_boolean]([sqlserver].[is_system],(0)))),
ADD EVENT sqlserver.sql_batch_starting(
ACTION(package0.event_sequence,sqlserver.session_id,sqlserver.database_name)
WHERE ([package0].[equal_boolean]([sqlserver].[is_system],(0))))
ADD TARGET package0.ring_buffer(SET max_events_limit=(1000),max_memory=(51200))
WITH (MAX_MEMORY=8192 KB,EVENT_RETENTION_MODE=ALLOW_SINGLE_EVENT_LOSS,MAX_DISPATCH_LATENCY=5 SECONDS,MAX_EVENT_SIZE=0 KB,MEMORY_PARTITION_MODE=PER_CPU,TRACK_CAUSALITY=ON,STARTUP_STATE=OFF)`
}
]
};
const configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);
const dashboardConfig: IConfigurationNode = {
id: 'Profiler',
type: 'object',
properties: {
[PROFILER_VIEW_TEMPLATE_SETTINGS]: profilerViewTemplateSchema
[PROFILER_VIEW_TEMPLATE_SETTINGS]: profilerViewTemplateSchema,
[PROFILER_SESSION_TEMPLATE_SETTINGS]: profilerSessionTemplateSchema
}
};

View File

@@ -23,7 +23,7 @@ import { IObjectExplorerService } from '../../objectExplorer/common/objectExplor
import { ProfilerInput } from 'sql/parts/profiler/editor/profilerInput';
import { TPromise } from 'vs/base/common/winjs.base';
import * as TaskUtilities from 'sql/workbench/common/taskUtilities';
import { IProfilerService} from '../service/interfaces';
import { IProfilerService } from '../service/interfaces';
import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { KeyCode, KeyMod } from 'vs/editor/editor.api';
import { ProfilerEditor } from '../editor/profilerEditor';
@@ -73,12 +73,12 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({
let activeEditor = editorService.getActiveEditor();
if (activeEditor instanceof ProfilerEditor) {
let profilerInput = activeEditor.input;
if (profilerInput.state.isRunning){
if (profilerInput.state.isRunning) {
return profilerService.stopSession(profilerInput.id);
} else {
// clear data when profiler is started
profilerInput.data.clear();
return profilerService.startSession(profilerInput.id);
return profilerService.startSession(profilerInput.id, profilerInput.sessionName);
}
}
return TPromise.as(false);

View File

@@ -20,7 +20,9 @@ import { Action } from 'vs/base/common/actions';
import * as nls from 'vs/nls';
import { IEditorAction } from 'vs/editor/common/editorCommon';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { ICommandService } from 'vs/platform/commands/common/commands'
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { INotificationService } from 'vs/platform/notification/common/notification';
export class ProfilerConnect extends Action {
public static ID = 'profiler.connect';
@@ -78,7 +80,27 @@ export class ProfilerStart extends Action {
public run(input: ProfilerInput): TPromise<boolean> {
input.data.clear();
return TPromise.wrap(this._profilerService.startSession(input.id));
return TPromise.wrap(this._profilerService.startSession(input.id, input.sessionName));
}
}
export class ProfilerCreate extends Action {
public static ID = 'profiler.create';
public static LABEL = nls.localize('create', "Create");
constructor(
id: string, label: string,
@ICommandService private _commandService: ICommandService,
@IProfilerService private _profilerService: IProfilerService,
@INotificationService private _notificationService: INotificationService
) {
super(id, label, 'add');
}
public run(input: ProfilerInput): TPromise<boolean> {
return TPromise.wrap(this._profilerService.launchCreateSessionDialog(input).then(() => {
return true;
}));
}
}

View File

@@ -27,6 +27,12 @@ import { Event, Emitter } from 'vs/base/common/event';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { Dimension } from 'vs/base/browser/dom';
import { textFormatter } from 'sql/parts/grid/services/sharedServices';
import { IEditorInput } from 'vs/platform/editor/common/editor';
export interface ProfilerTableViewState {
scrollTop: number;
scrollLeft: number;
}
export class ProfilerTableEditor extends BaseEditor implements IProfilerController, ITableController {
@@ -219,4 +225,18 @@ export class ProfilerTableEditor extends BaseEditor implements IProfilerControll
this._findState.changeMatchInfo(0, 0, undefined);
}
}
public saveViewState(): ProfilerTableViewState {
let viewElement = this._profilerTable.grid.getCanvasNode().parentElement;
return {
scrollTop: viewElement.scrollTop,
scrollLeft: viewElement.scrollLeft
};
}
public restoreViewState(state: ProfilerTableViewState): void {
let viewElement = this._profilerTable.grid.getCanvasNode().parentElement;
viewElement.scrollTop = state.scrollTop;
viewElement.scrollLeft = state.scrollLeft;
}
}

Some files were not shown because too many files have changed in this diff Show More