Compare commits

...

29 Commits

Author SHA1 Message Date
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
82 changed files with 3220 additions and 190 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.4",
"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

@@ -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.4":
version "0.2.4"
resolved "https://codeload.github.com/Microsoft/sqlops-dataprotocolclient/tar.gz/b4e2696563d0075e1b674ac8b179b3b8c441f59f"
"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

@@ -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",
@@ -45,6 +45,7 @@
},
{
"command": "profiler.openCreateSessionDialog",
"title": "Create Profiler Session",
"category": "Profiler"
}
],

View File

@@ -1,6 +1,6 @@
{
"name": "sqlops",
"version": "0.32.3",
"version": "0.32.4",
"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.1",
"applicationinsights": "0.18.0",
"chart.js": "^2.6.0",
"fast-plist": "0.1.2",
@@ -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

@@ -21,6 +21,10 @@
"command": "sqlservices.openDialog",
"title": "sqlservices.openDialog"
},
{
"command": "sqlservices.openConnectionDialog",
"title": "sqlservices.openConnectionDialog"
},
{
"command": "sqlservices.openEditor",
"title": "sqlservices.openEditor"

View File

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

@@ -75,6 +75,14 @@ export class TreeNode implements sqlops.TreeComponentItem {
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;
}
@@ -265,6 +273,7 @@ export class TreeDataProvider implements sqlops.TreeComponentDataProvider<TreeNo
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;
}

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

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

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

@@ -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>;
@@ -270,7 +271,24 @@ export interface IConnectionManagementService {
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 {
@@ -304,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 });
}
}
});

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 {
@@ -148,26 +157,32 @@ export class ConnectionDialogService implements IConnectionDialogService {
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 {
@@ -213,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 {
@@ -265,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> {
@@ -297,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);
@@ -316,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

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

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

@@ -71,11 +71,8 @@ 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;
@@ -167,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)) {
@@ -233,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)

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

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

@@ -22,6 +22,7 @@
enableAsyncPostRender="true"
showHeader="true"
[resized]="dataSet.resized"
(onActiveCellChanged)="onActiveCellChanged(i)"
(mousedown)="navigateToGrid(i)"
[selectionModel]="selectionModel"
[plugins]="plugins[i]"

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

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

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

@@ -23,14 +23,15 @@ 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, IModelViewTreeViewDataProvider } from 'sql/workbench/common/views';
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 = 0;
collapsibleState = 2;
children = void 0;
options = undefined;
}
@@ -123,13 +124,21 @@ export default class TreeComponent extends ComponentBase implements IComponent,
public layout(): void {
this._changeRef.detectChanges();
this.createTreeControl();
if (this._tree) {
this._tree.layout(this.convertSizeToNumber(this.width), this.convertSizeToNumber(this.height));
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

View File

@@ -8,6 +8,7 @@
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
@@ -34,7 +35,7 @@ export class TreeComponentDataSource implements IDataSource {
* Returns a boolean value indicating whether the element has children.
*/
public hasChildren(tree: ITree, node: ITreeComponentItem): boolean {
return this._dataProvider !== undefined;
return this._dataProvider !== undefined && node.collapsibleState !== TreeItemCollapsibleState.None;
}
/**
@@ -54,4 +55,8 @@ export class TreeComponentDataSource implements IDataSource {
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

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

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

@@ -30,7 +30,7 @@ export interface DialogComponentParams extends IBootstrapParams {
<h1 class="wizardPageTitle">{{_dialogPane.title}}</h1>
<div *ngIf="_dialogPane.description">{{_dialogPane.description}}</div>
</div>
<modelview-content [modelViewId]="modelViewId">
<modelview-content [modelViewId]="modelViewId" style="flex: 1 1 auto; position: relative;">
</modelview-content>
</div>
<modelview-content [modelViewId]="modelViewId" *ngIf="!_dialogPane || !_dialogPane.displayPageTitle">

View File

@@ -59,5 +59,7 @@
.dialogContainer {
display: flex;
flex-direction: column
flex-direction: column;
height: 100%;
width: 100%;
}

1
src/sql/sqlops.d.ts vendored
View File

@@ -816,6 +816,7 @@ declare module 'sqlops' {
export interface DbCellValue {
displayValue: string;
isNull: boolean;
invariantCultureDisplayValue: string;
}
export interface ResultSetSubset {

View File

@@ -270,6 +270,10 @@ declare module 'sqlops' {
* Default is "1 1 auto".
*/
flex?: string;
/**
* Matches the CSS style key and its available values.
*/
CSSStyles?: { [key: string]: string }
}
export interface FormItemLayout {
@@ -408,7 +412,7 @@ declare module 'sqlops' {
label?: string;
}
export interface TreeProperties {
export interface TreeProperties extends ComponentProperties {
withCheckbox?: boolean;
}
@@ -1106,7 +1110,7 @@ declare module 'sqlops' {
/**
* Connection information
*/
connection: connection.Connection;
connection?: connection.Connection;
/**
* Operation Display Name
@@ -1153,5 +1157,12 @@ declare module 'sqlops' {
* @param connectionId The ID of the connection
*/
export function getUriForConnection(connectionId: string): Thenable<string>;
/**
* Opens the connection dialog, calls the callback with the result. If connection was successful
* returns the connection otherwise returns undefined
* @param callback
*/
export function openConnectionDialog(provider?: string[]): Thenable<connection.Connection>;
}
}

View File

@@ -6,6 +6,7 @@
import { ExtHostConnectionManagementShape, SqlMainContext, MainThreadConnectionManagementShape } from 'sql/workbench/api/node/sqlExtHost.protocol';
import { IMainContext } from 'vs/workbench/api/node/extHost.protocol';
import { generateUuid } from 'vs/base/common/uuid';
import * as sqlops from 'sqlops';
export class ExtHostConnectionManagement extends ExtHostConnectionManagementShape {
@@ -31,6 +32,10 @@ export class ExtHostConnectionManagement extends ExtHostConnectionManagementShap
return this._proxy.$getCredentials(connectionId);
}
public $openConnectionDialog(providers?: string[]): Thenable<sqlops.connection.Connection> {
return this._proxy.$openConnectionDialog(providers);
}
public $listDatabases(connectionId: string): Thenable<string[]> {
return this._proxy.$listDatabases(connectionId);
}

View File

@@ -8,7 +8,7 @@ import { SqlExtHostContext, SqlMainContext, ExtHostConnectionManagementShape, Ma
import * as sqlops from 'sqlops';
import { IExtHostContext } from 'vs/workbench/api/node/extHost.protocol';
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
import { IConnectionManagementService } from 'sql/parts/connection/common/connectionManagement';
import { IConnectionManagementService, IConnectionDialogService } from 'sql/parts/connection/common/connectionManagement';
import { IObjectExplorerService } from 'sql/parts/objectExplorer/common/objectExplorerService';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import * as TaskUtilities from 'sql/workbench/common/taskUtilities';
@@ -25,7 +25,8 @@ export class MainThreadConnectionManagement implements MainThreadConnectionManag
extHostContext: IExtHostContext,
@IConnectionManagementService private _connectionManagementService: IConnectionManagementService,
@IObjectExplorerService private _objectExplorerService: IObjectExplorerService,
@IWorkbenchEditorService private _workbenchEditorService: IWorkbenchEditorService
@IWorkbenchEditorService private _workbenchEditorService: IWorkbenchEditorService,
@IConnectionDialogService private _connectionDialogService: IConnectionDialogService,
) {
if (extHostContext) {
this._proxy = extHostContext.getProxy(SqlExtHostContext.ExtHostConnectionManagement);
@@ -49,6 +50,16 @@ export class MainThreadConnectionManagement implements MainThreadConnectionManag
return Promise.resolve(this._connectionManagementService.getActiveConnectionCredentials(connectionId));
}
public async $openConnectionDialog(providers: string[]): Promise<sqlops.connection.Connection> {
let connectionProfile = await this._connectionDialogService.openDialogAndWait(this._connectionManagementService, { connectionType: 1, providers: providers });
return connectionProfile ? {
connectionId: connectionProfile.id,
options: connectionProfile.options,
providerName: connectionProfile.providerName
} : undefined;
}
public async $listDatabases(connectionId: string): Promise<string[]> {
let connection = this._connectionManagementService.getActiveConnections().find(profile => profile.id === connectionId);
let connectionUri = this._connectionManagementService.getConnectionUri(connection);

View File

@@ -104,6 +104,9 @@ export function createApiFactory(
getCredentials(connectionId: string): Thenable<{ [name: string]: string }> {
return extHostConnectionManagement.$getCredentials(connectionId);
},
openConnectionDialog(providers?: string[]): Thenable<sqlops.connection.Connection> {
return extHostConnectionManagement.$openConnectionDialog(providers);
},
listDatabases(connectionId: string): Thenable<string[]> {
return extHostConnectionManagement.$listDatabases(connectionId);
},

View File

@@ -33,7 +33,9 @@ export abstract class ExtHostAccountManagementShape {
$refresh(handle: number, account: sqlops.Account): Thenable<sqlops.Account> { throw ni(); }
}
export abstract class ExtHostConnectionManagementShape { }
export abstract class ExtHostConnectionManagementShape {
$onConnectionOpened(handleId: string, connection: sqlops.connection.Connection): void { throw ni; }
}
export abstract class ExtHostDataProtocolShape {
@@ -491,6 +493,7 @@ export interface MainThreadConnectionManagementShape extends IDisposable {
$getActiveConnections(): Thenable<sqlops.connection.Connection[]>;
$getCurrentConnection(): Thenable<sqlops.connection.Connection>;
$getCredentials(connectionId: string): Thenable<{ [name: string]: string }>;
$openConnectionDialog(providers: string[]): Thenable<sqlops.connection.Connection>;
$listDatabases(connectionId: string): Thenable<string[]>;
$getConnectionString(connectionId: string, includePassword: boolean): Thenable<string>;
$getUriForConnection(connectionId: string): Thenable<string>;

View File

@@ -23,7 +23,7 @@ suite('ConnectionDialogService tests', () => {
setup(() => {
let errorMessageService = getMockErrorMessageService();
connectionDialogService = new ConnectionDialogService(undefined, undefined, undefined, errorMessageService.object,
undefined, undefined, undefined, undefined);
undefined, undefined, undefined);
mockConnectionManagementService = TypeMoq.Mock.ofType(ConnectionManagementService, TypeMoq.MockBehavior.Strict, {}, {});
(connectionDialogService as any)._connectionManagementService = mockConnectionManagementService.object;
mockConnectionDialog = TypeMoq.Mock.ofType(ConnectionDialogWidget, TypeMoq.MockBehavior.Strict,
@@ -34,6 +34,7 @@ suite('ConnectionDialogService tests', () => {
undefined,
undefined,
undefined,
undefined,
new ContextKeyServiceStub()
);
mockConnectionDialog.setup(c => c.resetConnection());

View File

@@ -108,7 +108,7 @@ suite('SQL ConnectionManagementService tests', () => {
c => c.serverName === connectionProfileWithEmptyUnsavedPassword.serverName))).returns(
() => Promise.resolve({ profile: connectionProfileWithEmptyUnsavedPassword, savedCred: false }));
connectionStore.setup(x => x.isPasswordRequired(TypeMoq.It.isAny())).returns(() => true);
connectionStore.setup(x => x.getConnectionProfileGroups()).returns(() => [root]);
connectionStore.setup(x => x.getConnectionProfileGroups(false, undefined)).returns(() => [root]);
mssqlConnectionProvider.setup(x => x.connect(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => undefined);
@@ -172,7 +172,7 @@ suite('SQL ConnectionManagementService tests', () => {
connectionDialogService.verify(x => x.showDialog(
TypeMoq.It.isAny(),
TypeMoq.It.is<INewConnectionParams>(p => p.connectionType === connectionType && (uri === undefined || p.input.uri === uri)),
TypeMoq.It.is<IConnectionProfile>(c => c.serverName === connectionProfile.serverName),
TypeMoq.It.is<IConnectionProfile>(c => c !== undefined && c.serverName === connectionProfile.serverName),
connectionResult ? TypeMoq.It.is<IConnectionResult>(r => r.errorMessage === connectionResult.errorMessage && r.callStack === connectionResult.callStack) : undefined),
didShow ? TypeMoq.Times.once() : TypeMoq.Times.never());

View File

@@ -26,6 +26,7 @@ import { ConnectionProviderProperties } from 'sql/workbench/parts/connection/com
suite('SQL ConnectionStore tests', () => {
let defaultNamedProfile: IConnectionProfile;
let defaultUnnamedProfile: IConnectionProfile;
let profileForProvider2: IConnectionProfile;
let context: TypeMoq.Mock<Memento>;
let credentialStore: TypeMoq.Mock<CredentialsService>;
let connectionConfig: TypeMoq.Mock<ConnectionConfig>;
@@ -35,6 +36,7 @@ suite('SQL ConnectionStore tests', () => {
let mementoArray: any = [];
let maxRecent = 5;
let msSQLCapabilities: ConnectionProviderProperties;
let provider2Capabilities: ConnectionProviderProperties;
let defaultNamedConnectionProfile: ConnectionProfile;
setup(() => {
@@ -72,6 +74,23 @@ suite('SQL ConnectionStore tests', () => {
id: undefined
});
profileForProvider2 = Object.assign({}, {
serverName: 'unnamedServer',
databaseName: undefined,
authenticationType: 'SqlLogin',
userName: 'aUser',
password: 'asdf!@#$',
savePassword: true,
groupId: '',
groupFullName: '',
getOptionsKey: undefined,
matches: undefined,
providerName: 'MSSQL',
options: {},
saveProfile: true,
id: undefined
});
let momento = new Memento('ConnectionManagement');
context = TypeMoq.Mock.ofInstance(momento);
context.setup(x => x.getMemento(TypeMoq.It.isAny(), TypeMoq.It.isAny())).returns(() => mementoArray);
@@ -164,7 +183,14 @@ suite('SQL ConnectionStore tests', () => {
displayName: 'MSSQL',
connectionOptions: connectionProvider
};
provider2Capabilities = {
providerId: 'MSSQL',
displayName: 'MSSQL',
connectionOptions: connectionProvider
};
capabilitiesService.capabilities['MSSQL'] = { connection: msSQLCapabilities };
capabilitiesService.capabilities['Provider2'] = { connection: provider2Capabilities };
let groups: IConnectionProfileGroup[] = [
{
id: 'root',
@@ -226,6 +252,14 @@ suite('SQL ConnectionStore tests', () => {
});
});
test('getRecentlyUsedConnections should return connection for given provider', () => {
let connectionStore = new ConnectionStore(storageServiceMock.object, context.object, undefined, workspaceConfigurationServiceMock.object,
credentialStore.object, capabilitiesService, connectionConfig.object);
let connections = connectionStore.getRecentlyUsedConnections(['Provider2']);
assert.notEqual(connections, undefined);
assert.equal(connections.every(c => c.providerName === 'Provider2'), true);
});
test('addActiveConnection should add same connection exactly once', (done) => {
// setup memento for MRU to return a list we have access to
credentialStore.setup(x => x.saveCredential(TypeMoq.It.isAny(), TypeMoq.It.isAny()))

View File

@@ -19,4 +19,8 @@ export class ConnectionDialogTestService implements IConnectionDialogService {
return TPromise.as(none);
}
public openDialogAndWait(connectionManagementService: IConnectionManagementService,
params?: INewConnectionParams, model?: IConnectionProfile, connectionResult?: IConnectionResult): TPromise<IConnectionProfile> {
return TPromise.as(undefined);
}
}

View File

@@ -63,11 +63,11 @@ export class TestConnectionManagementService implements IConnectionManagementSer
return undefined;
}
getConnectionGroups(): ConnectionProfileGroup[] {
getConnectionGroups(providers?: string[]): ConnectionProfileGroup[] {
return [];
}
getActiveConnections(): ConnectionProfile[] {
getActiveConnections(providers?: string[]): ConnectionProfile[] {
return [];
}
@@ -75,7 +75,7 @@ export class TestConnectionManagementService implements IConnectionManagementSer
return undefined;
}
getRecentConnections(): ConnectionProfile[] {
getRecentConnections(providers?: string[]): ConnectionProfile[] {
return [];
}

View File

@@ -324,13 +324,21 @@ export class SelectBoxList implements ISelectBoxDelegate, IDelegate<ISelectOptio
// Style parent select
// {{SQL CARBON EDIT}}
let background = null;
let foreground = null;
let border = null;
if (this.selectElement) {
background = this.styles.selectBackground ? this.styles.selectBackground.toString() : null;
const foreground = this.styles.selectForeground ? this.styles.selectForeground.toString() : null;
const border = this.styles.selectBorder ? this.styles.selectBorder.toString() : null;
if (this.selectElement.disabled) {
background = (<any>this.styles).disabledSelectBackground ? (<any>this.styles).disabledSelectBackground.toString() : null;
foreground = (<any>this.styles).disabledSelectForeground ? (<any>this.styles).disabledSelectForeground.toString() : null;
border = null;
} else {
background = this.styles.selectBackground ? this.styles.selectBackground.toString() : null;
foreground = this.styles.selectForeground ? this.styles.selectForeground.toString() : null;
border = this.styles.selectBorder ? this.styles.selectBorder.toString() : null;
}
this.selectElement.style.backgroundColor = background;
this.selectElement.style.color = foreground;
this.selectElement.style.borderColor = border;

View File

@@ -14,7 +14,11 @@ export enum ContextKeyExprType {
Equals = 3,
NotEquals = 4,
And = 5,
Regex = 6
Regex = 6,
// {{SQL CARBON EDIT}}
GreaterThanEquals = 7,
LessThanEquals = 8
//
}
export abstract class ContextKeyExpr {
@@ -43,6 +47,15 @@ export abstract class ContextKeyExpr {
return new ContextKeyAndExpr(expr);
}
// {{SQL CARBON EDIT}}
public static greaterThanEquals(key: string, value: any): ContextKeyExpr {
return new ContextKeyGreaterThanEqualsExpr(key, value);
}
public static lessThanEquals(key: string, value: any): ContextKeyExpr {
return new ContextKeyLessThanEqualsExpr(key, value);
}
//
public static deserialize(serialized: string): ContextKeyExpr {
if (!serialized) {
return null;
@@ -71,6 +84,17 @@ export abstract class ContextKeyExpr {
return new ContextKeyRegexExpr(pieces[0].trim(), this._deserializeRegexValue(pieces[1]));
}
// {{SQL CARBON EDIT}}
if (serializedOne.indexOf('>=') >= 0) {
let pieces = serializedOne.split('>=');
return new ContextKeyGreaterThanEqualsExpr(pieces[0].trim(), this._deserializeValue(pieces[1]));
}
if (serializedOne.indexOf('<=') >= 0) {
let pieces = serializedOne.split('<=');
return new ContextKeyLessThanEqualsExpr(pieces[0].trim(), this._deserializeValue(pieces[1]));
}
//
if (/^\!\s*/.test(serializedOne)) {
return new ContextKeyNotExpr(serializedOne.substr(1).trim());
}
@@ -146,6 +170,12 @@ function cmp(a: ContextKeyExpr, b: ContextKeyExpr): number {
return (<ContextKeyNotEqualsExpr>a).cmp(<ContextKeyNotEqualsExpr>b);
case ContextKeyExprType.Regex:
return (<ContextKeyRegexExpr>a).cmp(<ContextKeyRegexExpr>b);
// {{SQL CARBON EDIT}}
case ContextKeyExprType.GreaterThanEquals:
return (<ContextKeyGreaterThanEqualsExpr>a).cmp(<ContextKeyGreaterThanEqualsExpr>b);
case ContextKeyExprType.LessThanEquals:
return (<ContextKeyLessThanEqualsExpr>a).cmp(<ContextKeyLessThanEqualsExpr>b);
//
default:
throw new Error('Unknown ContextKeyExpr!');
}
@@ -504,6 +534,108 @@ export class ContextKeyAndExpr implements ContextKeyExpr {
}
}
// {{SQL CARBON EDIT}}
export class ContextKeyGreaterThanEqualsExpr implements ContextKeyExpr {
constructor(private key: string, private value: any) {
}
public getType(): ContextKeyExprType {
return ContextKeyExprType.GreaterThanEquals;
}
public cmp(other: ContextKeyGreaterThanEqualsExpr): number {
if (this.key < other.key) {
return -1;
}
if (this.key > other.key) {
return 1;
}
if (this.value < other.value) {
return -1;
}
if (this.value > other.value) {
return 1;
}
return 0;
}
public equals(other: ContextKeyExpr): boolean {
if (other instanceof ContextKeyGreaterThanEqualsExpr) {
return (this.key === other.key && this.value === other.value);
}
return false;
}
public evaluate(context: IContext): boolean {
let keyInt = parseFloat(context.getValue(this.key));
let valueInt = parseFloat(this.value);
return (keyInt >= valueInt);
}
public normalize(): ContextKeyExpr {
return this;
}
public serialize(): string {
return this.key + ' >= \'' + this.value + '\'';
}
public keys(): string[] {
return [this.key];
}
}
// {{SQL CARBON EDIT}}
export class ContextKeyLessThanEqualsExpr implements ContextKeyExpr {
constructor(private key: string, private value: any) {
}
public getType(): ContextKeyExprType {
return ContextKeyExprType.LessThanEquals;
}
public cmp(other: ContextKeyLessThanEqualsExpr): number {
if (this.key < other.key) {
return -1;
}
if (this.key > other.key) {
return 1;
}
if (this.value < other.value) {
return -1;
}
if (this.value > other.value) {
return 1;
}
return 0;
}
public equals(other: ContextKeyExpr): boolean {
if (other instanceof ContextKeyLessThanEqualsExpr) {
return (this.key === other.key && this.value === other.value);
}
return false;
}
public evaluate(context: IContext): boolean {
let keyInt = parseFloat(context.getValue(this.key));
let valueInt = parseFloat(this.value);
return (keyInt <= valueInt);
}
public normalize(): ContextKeyExpr {
return this;
}
public serialize(): string {
return this.key + ' <= \'' + this.value + '\'';
}
public keys(): string[] {
return [this.key];
}
}
export class RawContextKey<T> extends ContextKeyDefinedExpr {
private _defaultValue: T;

View File

@@ -28,18 +28,29 @@ suite('ContextKeyExpr', () => {
ContextKeyExpr.notEquals('c1', 'cc1'),
ContextKeyExpr.notEquals('c2', 'cc2'),
ContextKeyExpr.not('d1'),
ContextKeyExpr.not('d2')
ContextKeyExpr.not('d2'),
// {{SQL CARBON EDIT}}
ContextKeyExpr.greaterThanEquals('e1', 'ee1'),
ContextKeyExpr.greaterThanEquals('e2', 'ee2'),
ContextKeyExpr.lessThanEquals('f1', 'ff1'),
ContextKeyExpr.lessThanEquals('f2', 'ff2')
//
);
let b = ContextKeyExpr.and(
// {{SQL CARBON EDIT}}
ContextKeyExpr.greaterThanEquals('e2', 'ee2'),
ContextKeyExpr.equals('b2', 'bb2'),
ContextKeyExpr.notEquals('c1', 'cc1'),
ContextKeyExpr.not('d1'),
ContextKeyExpr.lessThanEquals('f1', 'ff1'),
ContextKeyExpr.regex('d4', /\*\*3*/),
ContextKeyExpr.greaterThanEquals('e1', 'ee1'),
ContextKeyExpr.notEquals('c2', 'cc2'),
ContextKeyExpr.has('a2'),
ContextKeyExpr.equals('b1', 'bb1'),
ContextKeyExpr.regex('d3', /d.*/),
ContextKeyExpr.has('a1'),
ContextKeyExpr.lessThanEquals('f2', 'ff2'),
ContextKeyExpr.and(ContextKeyExpr.equals('and.a', true)),
ContextKeyExpr.not('d2')
);
@@ -82,6 +93,10 @@ suite('ContextKeyExpr', () => {
testExpression('!' + expr, !value);
testExpression(expr + ' =~ /d.*/', /d.*/.test(value));
testExpression(expr + ' =~ /D/i', /D/i.test(value));
// {{SQL CARBON EDIT}}
testExpression(expr + ' >= 10', parseFloat(value) >= 10);
testExpression(expr + ' <= 10', parseFloat(value) <= 10);
//
}
testBatch('a', true);

View File

@@ -174,9 +174,9 @@ angular2-grid@2.0.6:
version "2.0.6"
resolved "https://registry.yarnpkg.com/angular2-grid/-/angular2-grid-2.0.6.tgz#01fe225dc13b2822370b6c61f9a6913b3a26f989"
"angular2-slickgrid@github:Microsoft/angular2-slickgrid#1.3.12":
version "1.3.12"
resolved "https://codeload.github.com/Microsoft/angular2-slickgrid/tar.gz/19aafe8888d2f2eb70aec858e4b86e3c1b7b3fc8"
"angular2-slickgrid@github:Microsoft/angular2-slickgrid#1.4.1":
version "1.4.1"
resolved "https://codeload.github.com/Microsoft/angular2-slickgrid/tar.gz/e13c098aa50f43de21a5fdcd1a1a0263455b92d6"
ansi-colors@^1.0.1:
version "1.1.0"
@@ -4042,6 +4042,10 @@ lodash@^4.0.0, lodash@^4.13.1, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.3.0:
version "4.17.4"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae"
lodash@^4.17.10:
version "4.17.10"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7"
lodash@~1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-1.0.2.tgz#8f57560c83b59fc270bd3d561b690043430e2551"
@@ -6816,15 +6820,15 @@ vinyl@~2.0.1:
remove-trailing-separator "^1.0.1"
replace-ext "^1.0.0"
vsce@1.33.2:
version "1.33.2"
resolved "https://registry.yarnpkg.com/vsce/-/vsce-1.33.2.tgz#3645f69aaf984e22f74ea49d35f38dd18d66ff5f"
vsce@1.46.0:
version "1.46.0"
resolved "https://registry.yarnpkg.com/vsce/-/vsce-1.46.0.tgz#709ef40540c53d78e4896fd76e09c23e50ddd538"
dependencies:
cheerio "^1.0.0-rc.1"
commander "^2.8.1"
denodeify "^1.2.1"
glob "^7.0.6"
lodash "^4.15.0"
lodash "^4.17.10"
markdown-it "^8.3.1"
mime "^1.3.4"
minimatch "^3.0.3"
@@ -6834,7 +6838,7 @@ vsce@1.33.2:
semver "^5.1.0"
tmp "0.0.29"
url-join "^1.1.0"
vso-node-api "^6.1.2-preview"
vso-node-api "6.1.2-preview"
yauzl "^2.3.1"
yazl "^2.2.2"
@@ -6904,7 +6908,7 @@ vscode-xterm@3.4.0-beta3:
version "3.4.0-beta3"
resolved "https://registry.yarnpkg.com/vscode-xterm/-/vscode-xterm-3.4.0-beta3.tgz#349db387bd3669ad4d6044a6ea6d699e6d649fb5"
vso-node-api@^6.1.2-preview:
vso-node-api@6.1.2-preview:
version "6.1.2-preview"
resolved "https://registry.yarnpkg.com/vso-node-api/-/vso-node-api-6.1.2-preview.tgz#aab3546df2451ecd894e071bb99b5df19c5fa78f"
dependencies: