mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-17 02:51:36 -05:00
@@ -78,6 +78,7 @@ const sqlBuiltInExtensions = [
|
|||||||
// Add SQL built-in extensions here.
|
// Add SQL built-in extensions here.
|
||||||
// the extension will be excluded from SQLOps package and will have separate vsix packages
|
// the extension will be excluded from SQLOps package and will have separate vsix packages
|
||||||
'agent',
|
'agent',
|
||||||
|
'import',
|
||||||
'profiler'
|
'profiler'
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
1
extensions/import/.gitignore
vendored
Normal file
1
extensions/import/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
flatfileimportservice/
|
||||||
2
extensions/import/.vscodeignore
Normal file
2
extensions/import/.vscodeignore
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
client/src/**
|
||||||
|
client/tsconfig.json
|
||||||
16
extensions/import/README.md
Normal file
16
extensions/import/README.md
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# Microsoft SQL Server Import for SQL Operations Studio
|
||||||
|
|
||||||
|
--
|
||||||
|
## 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).
|
||||||
BIN
extensions/import/images/sqlserver.png
Normal file
BIN
extensions/import/images/sqlserver.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 37 KiB |
105
extensions/import/package.json
Normal file
105
extensions/import/package.json
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
{
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "flatFileImport.importFlatFile"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"command": "flatFileImport.listDatabases"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"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.1.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": {
|
||||||
|
"mocha-junit-reporter": "^1.17.0",
|
||||||
|
"mocha-multi-reporters": "^1.1.7"
|
||||||
|
},
|
||||||
|
"resolutions": {
|
||||||
|
"vscode-jsonrpc": "3.5.0",
|
||||||
|
"vscode-languageclient": "3.5.0",
|
||||||
|
"vscode-languageserver-protocol": "3.5.0",
|
||||||
|
"vscode-languageserver-types": "3.5.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
14
extensions/import/src/constants.ts
Normal file
14
extensions/import/src/constants.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
export const extensionConfigSectionName = 'flatFileImport';
|
||||||
|
export const serviceName = 'Flat File Import Service';
|
||||||
|
export const providerId = 'FlatFileImport';
|
||||||
|
export const configLogDebugInfo = 'logDebugInfo';
|
||||||
|
export const sqlConfigSectionName = 'sql';
|
||||||
|
|
||||||
|
export const serviceCrashLink = 'https://github.com/Microsoft/sqlopsstudio/issues/2090';
|
||||||
|
|
||||||
30
extensions/import/src/controllers/controllerBase.ts
Normal file
30
extensions/import/src/controllers/controllerBase.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
|
||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import * as vscode from 'vscode';
|
||||||
|
|
||||||
|
export default abstract class ControllerBase implements vscode.Disposable {
|
||||||
|
protected _context: vscode.ExtensionContext;
|
||||||
|
|
||||||
|
public 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
41
extensions/import/src/controllers/mainController.ts
Normal file
41
extensions/import/src/controllers/mainController.ts
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* 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 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', () => new FlatFileWizard(provider).start());
|
||||||
|
}
|
||||||
|
}
|
||||||
37
extensions/import/src/main.ts
Normal file
37
extensions/import/src/main.ts
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
'use strict';
|
||||||
|
import * as vscode from 'vscode';
|
||||||
|
|
||||||
|
import ControllerBase from './controllers/controllerBase';
|
||||||
|
import MainController from './controllers/mainController';
|
||||||
|
|
||||||
|
let controllers: ControllerBase[] = [];
|
||||||
|
|
||||||
|
export function activate(context: vscode.ExtensionContext) {
|
||||||
|
let activations: Promise<boolean>[] = [];
|
||||||
|
|
||||||
|
// Start the main controller
|
||||||
|
let mainController = new MainController(context);
|
||||||
|
controllers.push(mainController);
|
||||||
|
context.subscriptions.push(mainController);
|
||||||
|
activations.push(mainController.activate());
|
||||||
|
|
||||||
|
return Promise.all(activations)
|
||||||
|
.then((results: boolean[]) => {
|
||||||
|
for (let result of results) {
|
||||||
|
if (!result) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deactivate() {
|
||||||
|
for (let controller of controllers) {
|
||||||
|
controller.deactivate();
|
||||||
|
}
|
||||||
|
}
|
||||||
16
extensions/import/src/services/config.json
Normal file
16
extensions/import/src/services/config.json
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"downloadUrl": "https://sqlopsextensions.blob.core.windows.net/extensions/import/{#fileName#}",
|
||||||
|
"useDefaultLinuxRuntime": true,
|
||||||
|
"version": "0.0.1",
|
||||||
|
"downloadFileNames": {
|
||||||
|
"Windows_64": "win-x64.zip",
|
||||||
|
"Windows_86": "win-x86.zip",
|
||||||
|
"OSX": "osx.zip",
|
||||||
|
"Linux_64": "linux-x64.tar.gz"
|
||||||
|
},
|
||||||
|
"installDirectory": "flatfileimportservice/{#platform#}/{#version#}",
|
||||||
|
"executableFiles": [
|
||||||
|
"MicrosoftSqlToolsFlatFileImport",
|
||||||
|
"MicrosoftSqlToolsFlatFileImport.exe"
|
||||||
|
]
|
||||||
|
}
|
||||||
149
extensions/import/src/services/contracts.ts
Normal file
149
extensions/import/src/services/contracts.ts
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import { RequestType, NotificationType } from 'vscode-languageclient';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @interface IMessage
|
||||||
|
*/
|
||||||
|
export interface IMessage {
|
||||||
|
jsonrpc: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ------------------------------- < Telemetry Sent Event > ------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Event sent when the language service send a telemetry event
|
||||||
|
*/
|
||||||
|
export namespace TelemetryNotification {
|
||||||
|
export const type = new NotificationType<TelemetryParams, void>('telemetry/sqlevent');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update event parameters
|
||||||
|
*/
|
||||||
|
export class TelemetryParams {
|
||||||
|
public params: {
|
||||||
|
eventName: string;
|
||||||
|
properties: ITelemetryEventProperties;
|
||||||
|
measures: ITelemetryEventMeasures;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ITelemetryEventProperties {
|
||||||
|
[key: string]: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ITelemetryEventMeasures {
|
||||||
|
[key: string]: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contract Classes
|
||||||
|
*/
|
||||||
|
export interface Result {
|
||||||
|
success: boolean;
|
||||||
|
errorMessage: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ColumnInfo {
|
||||||
|
name: string;
|
||||||
|
sqlType: string;
|
||||||
|
isNullable: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* PROSEDiscoveryRequest
|
||||||
|
* Send this request to create a new PROSE session with a new file and preview it
|
||||||
|
*/
|
||||||
|
const proseDiscoveryRequestName = 'flatfile/proseDiscovery';
|
||||||
|
|
||||||
|
export interface PROSEDiscoveryParams {
|
||||||
|
filePath: string;
|
||||||
|
tableName: string;
|
||||||
|
schemaName?: string;
|
||||||
|
fileType?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PROSEDiscoveryResponse {
|
||||||
|
dataPreview: string[][];
|
||||||
|
columnInfo: ColumnInfo[];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* InsertDataRequest
|
||||||
|
*/
|
||||||
|
const insertDataRequestName = 'flatfile/insertData';
|
||||||
|
|
||||||
|
export interface InsertDataParams {
|
||||||
|
connectionString: string;
|
||||||
|
batchSize: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface InsertDataResponse {
|
||||||
|
result: Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GetColumnInfoRequest
|
||||||
|
*/
|
||||||
|
const getColumnInfoRequestName = 'flatfile/getColumnInfo';
|
||||||
|
|
||||||
|
export interface GetColumnInfoParams {
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GetColumnInfoResponse {
|
||||||
|
columnInfo: ColumnInfo[];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ChangeColumnSettingsRequest
|
||||||
|
*/
|
||||||
|
const changeColumnSettingsRequestName = 'flatfile/changeColumnSettings';
|
||||||
|
|
||||||
|
export interface ChangeColumnSettingsParams {
|
||||||
|
index: number;
|
||||||
|
newName?: string;
|
||||||
|
newDataType?: string;
|
||||||
|
newNullable?: boolean;
|
||||||
|
newInPrimaryKey?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ChangeColumnSettingsResponse {
|
||||||
|
result: Result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Requests
|
||||||
|
*/
|
||||||
|
export namespace PROSEDiscoveryRequest {
|
||||||
|
export const type = new RequestType<PROSEDiscoveryParams, PROSEDiscoveryResponse, void, void>(proseDiscoveryRequestName);
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace InsertDataRequest {
|
||||||
|
export const type = new RequestType<InsertDataParams, InsertDataResponse, void, void>(insertDataRequestName);
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace GetColumnInfoRequest {
|
||||||
|
export const type = new RequestType<GetColumnInfoParams, GetColumnInfoResponse, void, void>(getColumnInfoRequestName);
|
||||||
|
}
|
||||||
|
|
||||||
|
export namespace ChangeColumnSettingsRequest {
|
||||||
|
export const type = new RequestType<ChangeColumnSettingsParams, ChangeColumnSettingsResponse, void, void>(changeColumnSettingsRequestName);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
export interface FlatFileProvider {
|
||||||
|
providerId?: string;
|
||||||
|
|
||||||
|
sendPROSEDiscoveryRequest(params: PROSEDiscoveryParams): Thenable<PROSEDiscoveryResponse>;
|
||||||
|
sendInsertDataRequest(params: InsertDataParams): Thenable<InsertDataResponse>;
|
||||||
|
sendGetColumnInfoRequest(params: GetColumnInfoParams): Thenable<GetColumnInfoResponse>;
|
||||||
|
sendChangeColumnSettingsRequest(params: ChangeColumnSettingsParams): Thenable<ChangeColumnSettingsResponse>;
|
||||||
|
}
|
||||||
96
extensions/import/src/services/features.ts
Normal file
96
extensions/import/src/services/features.ts
Normal file
@@ -0,0 +1,96 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import { SqlOpsDataClient, SqlOpsFeature } from 'dataprotocol-client';
|
||||||
|
import {
|
||||||
|
ClientCapabilities,
|
||||||
|
StaticFeature,
|
||||||
|
RPCMessageType,
|
||||||
|
ServerCapabilities
|
||||||
|
} from 'vscode-languageclient';
|
||||||
|
import * as UUID from 'vscode-languageclient/lib/utils/uuid';
|
||||||
|
import { Disposable } from 'vscode';
|
||||||
|
|
||||||
|
import { Telemetry } from './telemetry';
|
||||||
|
import * as serviceUtils from './serviceUtils';
|
||||||
|
import * as Contracts from './contracts';
|
||||||
|
import { managerInstance, ApiType } from './serviceApiManager';
|
||||||
|
|
||||||
|
export class TelemetryFeature implements StaticFeature {
|
||||||
|
|
||||||
|
constructor(private _client: SqlOpsDataClient) {
|
||||||
|
}
|
||||||
|
|
||||||
|
fillClientCapabilities(capabilities: ClientCapabilities): void {
|
||||||
|
serviceUtils.ensure(capabilities, 'telemetry')!.telemetry = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
initialize(): void {
|
||||||
|
this._client.onNotification(Contracts.TelemetryNotification.type, e => {
|
||||||
|
Telemetry.sendTelemetryEvent(e.params.eventName, e.params.properties, e.params.measures);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class FlatFileImportFeature extends SqlOpsFeature<undefined> {
|
||||||
|
private static readonly messagesTypes: RPCMessageType[] = [
|
||||||
|
Contracts.PROSEDiscoveryRequest.type
|
||||||
|
];
|
||||||
|
|
||||||
|
constructor(client: SqlOpsDataClient) {
|
||||||
|
super(client, FlatFileImportFeature.messagesTypes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public fillClientCapabilities(capabilities: ClientCapabilities): void {
|
||||||
|
}
|
||||||
|
|
||||||
|
public initialize(capabilities: ServerCapabilities): void {
|
||||||
|
this.register(this.messages, {
|
||||||
|
id: UUID.generateUuid(),
|
||||||
|
registerOptions: undefined
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected registerProvider(options: undefined): Disposable {
|
||||||
|
const client = this._client;
|
||||||
|
|
||||||
|
let requestSender = (requestType, params) => {
|
||||||
|
return client.sendRequest(requestType, params).then(
|
||||||
|
r => {
|
||||||
|
return r as any;
|
||||||
|
},
|
||||||
|
e => {
|
||||||
|
client.logFailedRequest(requestType, e);
|
||||||
|
return Promise.reject(e);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
let sendPROSEDiscoveryRequest = (params: Contracts.PROSEDiscoveryParams): Thenable<Contracts.PROSEDiscoveryResponse> => {
|
||||||
|
return requestSender(Contracts.PROSEDiscoveryRequest.type, params);
|
||||||
|
};
|
||||||
|
|
||||||
|
let sendInsertDataRequest = (params: Contracts.InsertDataParams): Thenable<Contracts.InsertDataResponse> => {
|
||||||
|
return requestSender(Contracts.InsertDataRequest.type, params);
|
||||||
|
};
|
||||||
|
|
||||||
|
let sendGetColumnInfoRequest = (params: Contracts.GetColumnInfoParams): Thenable<Contracts.GetColumnInfoResponse> => {
|
||||||
|
return requestSender(Contracts.GetColumnInfoRequest.type, params);
|
||||||
|
};
|
||||||
|
|
||||||
|
let sendChangeColumnSettingsRequest = (params: Contracts.ChangeColumnSettingsParams): Thenable<Contracts.ChangeColumnSettingsResponse> => {
|
||||||
|
return requestSender(Contracts.ChangeColumnSettingsRequest.type, params);
|
||||||
|
};
|
||||||
|
|
||||||
|
return managerInstance.registerApi<Contracts.FlatFileProvider>(ApiType.FlatFileProvider, {
|
||||||
|
providerId: client.providerId,
|
||||||
|
sendPROSEDiscoveryRequest,
|
||||||
|
sendChangeColumnSettingsRequest,
|
||||||
|
sendGetColumnInfoRequest,
|
||||||
|
sendInsertDataRequest
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
64
extensions/import/src/services/serviceApiManager.ts
Normal file
64
extensions/import/src/services/serviceApiManager.ts
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import * as sqlops from 'sqlops';
|
||||||
|
import * as vscode from 'vscode';
|
||||||
|
import * as contracts from './contracts';
|
||||||
|
import { SqlOpsDataClient } from 'dataprotocol-client/lib/main';
|
||||||
|
|
||||||
|
export enum ApiType {
|
||||||
|
FlatFileProvider = 'FlatFileProvider'
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IServiceApi {
|
||||||
|
onRegisteredApi<T>(type: ApiType): vscode.Event<T>;
|
||||||
|
registerApi<T>(type: ApiType, feature: T): vscode.Disposable;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IModelViewDefinition {
|
||||||
|
id: string;
|
||||||
|
modelView: sqlops.ModelView;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ServiceApiManager implements IServiceApi {
|
||||||
|
private modelViewRegistrations: { [id: string]: boolean } = {};
|
||||||
|
private featureEventChannels: { [type: string]: vscode.EventEmitter<any> } = {};
|
||||||
|
private _onRegisteredModelView = new vscode.EventEmitter<IModelViewDefinition>();
|
||||||
|
|
||||||
|
public onRegisteredApi<T>(type: ApiType): vscode.Event<T> {
|
||||||
|
let featureEmitter = this.featureEventChannels[type];
|
||||||
|
if (!featureEmitter) {
|
||||||
|
featureEmitter = new vscode.EventEmitter<T>();
|
||||||
|
this.featureEventChannels[type] = featureEmitter;
|
||||||
|
}
|
||||||
|
return featureEmitter.event;
|
||||||
|
}
|
||||||
|
|
||||||
|
public registerApi<T>(type: ApiType, feature: T): vscode.Disposable {
|
||||||
|
let featureEmitter = this.featureEventChannels[type];
|
||||||
|
if (featureEmitter) {
|
||||||
|
featureEmitter.fire(feature);
|
||||||
|
}
|
||||||
|
// TODO handle unregistering API on close
|
||||||
|
return {
|
||||||
|
dispose: () => undefined
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public get onRegisteredModelView(): vscode.Event<IModelViewDefinition> {
|
||||||
|
return this._onRegisteredModelView.event;
|
||||||
|
}
|
||||||
|
|
||||||
|
public registerModelView(id: string, modelView: sqlops.ModelView): void {
|
||||||
|
this._onRegisteredModelView.fire({
|
||||||
|
id: id,
|
||||||
|
modelView: modelView
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export let managerInstance = new ServiceApiManager();
|
||||||
165
extensions/import/src/services/serviceClient.ts
Normal file
165
extensions/import/src/services/serviceClient.ts
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import { SqlOpsDataClient, ClientOptions } from 'dataprotocol-client';
|
||||||
|
import { IConfig, ServerProvider, Events } from 'service-downloader';
|
||||||
|
import { ServerOptions, TransportKind } from 'vscode-languageclient';
|
||||||
|
import * as vscode from 'vscode';
|
||||||
|
import * as nls from 'vscode-nls';
|
||||||
|
const localize = nls.loadMessageBundle();
|
||||||
|
import * as path from 'path';
|
||||||
|
import { EventAndListener } from 'eventemitter2';
|
||||||
|
|
||||||
|
import { Telemetry, LanguageClientErrorHandler } from './telemetry';
|
||||||
|
import * as Constants from '../constants';
|
||||||
|
import { TelemetryFeature, FlatFileImportFeature } from './features';
|
||||||
|
import * as serviceUtils from './serviceUtils';
|
||||||
|
|
||||||
|
const baseConfig = require('./config.json');
|
||||||
|
|
||||||
|
export class ServiceClient {
|
||||||
|
private statusView: vscode.StatusBarItem;
|
||||||
|
|
||||||
|
constructor(private outputChannel: vscode.OutputChannel) {
|
||||||
|
this.statusView = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left);
|
||||||
|
}
|
||||||
|
|
||||||
|
public startService(context: vscode.ExtensionContext): Promise<SqlOpsDataClient> {
|
||||||
|
let config: IConfig = JSON.parse(JSON.stringify(baseConfig));
|
||||||
|
config.installDirectory = path.join(context.extensionPath, config.installDirectory);
|
||||||
|
config.proxy = vscode.workspace.getConfiguration('http').get('proxy');
|
||||||
|
config.strictSSL = vscode.workspace.getConfiguration('http').get('proxyStrictSSL') || true;
|
||||||
|
|
||||||
|
const serverdownloader = new ServerProvider(config);
|
||||||
|
serverdownloader.eventEmitter.onAny(this.generateHandleServerProviderEvent());
|
||||||
|
|
||||||
|
let clientOptions: ClientOptions = this.createClientOptions();
|
||||||
|
|
||||||
|
const installationStart = Date.now();
|
||||||
|
let client: SqlOpsDataClient;
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
serverdownloader.getOrDownloadServer().then(e => {
|
||||||
|
const installationComplete = Date.now();
|
||||||
|
let serverOptions = this.generateServerOptions(e);
|
||||||
|
client = new SqlOpsDataClient(Constants.serviceName, serverOptions, clientOptions);
|
||||||
|
const processStart = Date.now();
|
||||||
|
client.onReady().then(() => {
|
||||||
|
const processEnd = Date.now();
|
||||||
|
this.statusView.text = localize('serviceStarted', 'Service Started');
|
||||||
|
setTimeout(() => {
|
||||||
|
this.statusView.hide();
|
||||||
|
}, 1500);
|
||||||
|
Telemetry.sendTelemetryEvent('startup/LanguageClientStarted', {
|
||||||
|
installationTime: String(installationComplete - installationStart),
|
||||||
|
processStartupTime: String(processEnd - processStart),
|
||||||
|
totalTime: String(processEnd - installationStart),
|
||||||
|
beginningTimestamp: String(installationStart)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
this.statusView.show();
|
||||||
|
this.statusView.text = localize('serviceStarting', 'Starting service');
|
||||||
|
let disposable = client.start();
|
||||||
|
context.subscriptions.push(disposable);
|
||||||
|
resolve(client);
|
||||||
|
}, e => {
|
||||||
|
Telemetry.sendTelemetryEvent('ServiceInitializingFailed');
|
||||||
|
vscode.window.showErrorMessage(localize('serviceStartFailed', 'Failed to start Scale Out Data service:{0}', e));
|
||||||
|
// Just resolve to avoid unhandled promise. We show the error to the user.
|
||||||
|
resolve(undefined);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private createClientOptions(): ClientOptions {
|
||||||
|
return {
|
||||||
|
providerId: Constants.providerId,
|
||||||
|
errorHandler: new LanguageClientErrorHandler(),
|
||||||
|
synchronize: {
|
||||||
|
configurationSection: [Constants.extensionConfigSectionName, Constants.sqlConfigSectionName]
|
||||||
|
},
|
||||||
|
features: [
|
||||||
|
// we only want to add new features
|
||||||
|
TelemetryFeature,
|
||||||
|
FlatFileImportFeature
|
||||||
|
],
|
||||||
|
outputChannel: new CustomOutputChannel()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private generateServerOptions(executablePath: string): ServerOptions {
|
||||||
|
let launchArgs = [];
|
||||||
|
launchArgs.push('--log-dir');
|
||||||
|
let logFileLocation = path.join(serviceUtils.getDefaultLogLocation(), 'flatfileimport');
|
||||||
|
launchArgs.push(logFileLocation);
|
||||||
|
let config = vscode.workspace.getConfiguration(Constants.extensionConfigSectionName);
|
||||||
|
if (config) {
|
||||||
|
let logDebugInfo = config[Constants.configLogDebugInfo];
|
||||||
|
if (logDebugInfo) {
|
||||||
|
launchArgs.push('--enable-logging');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { command: executablePath, args: launchArgs, transport: TransportKind.stdio };
|
||||||
|
}
|
||||||
|
|
||||||
|
private generateHandleServerProviderEvent(): EventAndListener {
|
||||||
|
let dots = 0;
|
||||||
|
return (e: string, ...args: any[]) => {
|
||||||
|
this.outputChannel.show();
|
||||||
|
this.statusView.show();
|
||||||
|
switch (e) {
|
||||||
|
case Events.INSTALL_START:
|
||||||
|
this.outputChannel.appendLine(localize('installingServiceDetailed', 'Installing {0} service to {1}', Constants.serviceName, args[0]));
|
||||||
|
this.statusView.text = localize('installingService', 'Installing Service');
|
||||||
|
break;
|
||||||
|
case Events.INSTALL_END:
|
||||||
|
this.outputChannel.appendLine(localize('serviceInstalled', 'Installed'));
|
||||||
|
break;
|
||||||
|
case Events.DOWNLOAD_START:
|
||||||
|
this.outputChannel.appendLine(localize('downloadingService', 'Downloading {0}', args[0]));
|
||||||
|
this.outputChannel.append(`(${Math.ceil(args[1] / 1024)} KB)`);
|
||||||
|
this.statusView.text = localize('downloadingServiceStatus', 'Downloading Service');
|
||||||
|
break;
|
||||||
|
case Events.DOWNLOAD_PROGRESS:
|
||||||
|
let newDots = Math.ceil(args[0] / 5);
|
||||||
|
if (newDots > dots) {
|
||||||
|
this.outputChannel.append('.'.repeat(newDots - dots));
|
||||||
|
dots = newDots;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case Events.DOWNLOAD_END:
|
||||||
|
this.outputChannel.appendLine(localize('downloadingServiceComplete', 'Done!'));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CustomOutputChannel implements vscode.OutputChannel {
|
||||||
|
name: string;
|
||||||
|
append(value: string): void {
|
||||||
|
}
|
||||||
|
appendLine(value: string): void {
|
||||||
|
}
|
||||||
|
// tslint:disable-next-line:no-empty
|
||||||
|
clear(): void {
|
||||||
|
}
|
||||||
|
show(preserveFocus?: boolean): void;
|
||||||
|
show(column?: vscode.ViewColumn, preserveFocus?: boolean): void;
|
||||||
|
// tslint:disable-next-line:no-empty
|
||||||
|
show(column?: any, preserveFocus?: any): void {
|
||||||
|
}
|
||||||
|
// tslint:disable-next-line:no-empty
|
||||||
|
hide(): void {
|
||||||
|
}
|
||||||
|
// tslint:disable-next-line:no-empty
|
||||||
|
dispose(): void {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
156
extensions/import/src/services/serviceUtils.ts
Normal file
156
extensions/import/src/services/serviceUtils.ts
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import * as path from 'path';
|
||||||
|
import * as crypto from 'crypto';
|
||||||
|
import * as os from 'os';
|
||||||
|
|
||||||
|
const baseConfig = require('./config.json');
|
||||||
|
|
||||||
|
// The function is a duplicate of \src\paths.js. IT would be better to import path.js but it doesn't
|
||||||
|
// work for now because the extension is running in different process.
|
||||||
|
export function getAppDataPath(): string {
|
||||||
|
let platform = process.platform;
|
||||||
|
switch (platform) {
|
||||||
|
case 'win32': return process.env['APPDATA'] || path.join(process.env['USERPROFILE'], 'AppData', 'Roaming');
|
||||||
|
case 'darwin': return path.join(os.homedir(), 'Library', 'Application Support');
|
||||||
|
case 'linux': return process.env['XDG_CONFIG_HOME'] || path.join(os.homedir(), '.config');
|
||||||
|
default: throw new Error('Platform not supported');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getDefaultLogLocation(): string {
|
||||||
|
return path.join(getAppDataPath(), 'sqlops');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ensure(target: object, key: string): any {
|
||||||
|
if (target[key] === void 0) {
|
||||||
|
target[key] = {} as any;
|
||||||
|
}
|
||||||
|
return target[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IPackageInfo {
|
||||||
|
name: string;
|
||||||
|
version: string;
|
||||||
|
aiKey: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getPackageInfo(packageJson: any): IPackageInfo {
|
||||||
|
if (packageJson) {
|
||||||
|
return {
|
||||||
|
name: packageJson.name,
|
||||||
|
version: packageJson.version,
|
||||||
|
aiKey: packageJson.aiKey
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function generateUserId(): Promise<string> {
|
||||||
|
return new Promise<string>(resolve => {
|
||||||
|
try {
|
||||||
|
let interfaces = os.networkInterfaces();
|
||||||
|
let mac;
|
||||||
|
for (let key of Object.keys(interfaces)) {
|
||||||
|
let item = interfaces[key][0];
|
||||||
|
if (!item.internal) {
|
||||||
|
mac = item.mac;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (mac) {
|
||||||
|
resolve(crypto.createHash('sha256').update(mac + os.homedir(), 'utf8').digest('hex'));
|
||||||
|
} else {
|
||||||
|
resolve(generateGuid());
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
resolve(generateGuid()); // fallback
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function generateGuid(): string {
|
||||||
|
let hexValues: string[] = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'];
|
||||||
|
// c.f. rfc4122 (UUID version 4 = xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx)
|
||||||
|
let oct: string = '';
|
||||||
|
let tmp: number;
|
||||||
|
/* tslint:disable:no-bitwise */
|
||||||
|
for (let a: number = 0; a < 4; a++) {
|
||||||
|
tmp = (4294967296 * Math.random()) | 0;
|
||||||
|
oct += hexValues[tmp & 0xF] +
|
||||||
|
hexValues[tmp >> 4 & 0xF] +
|
||||||
|
hexValues[tmp >> 8 & 0xF] +
|
||||||
|
hexValues[tmp >> 12 & 0xF] +
|
||||||
|
hexValues[tmp >> 16 & 0xF] +
|
||||||
|
hexValues[tmp >> 20 & 0xF] +
|
||||||
|
hexValues[tmp >> 24 & 0xF] +
|
||||||
|
hexValues[tmp >> 28 & 0xF];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 'Set the two most significant bits (bits 6 and 7) of the clock_seq_hi_and_reserved to zero and one, respectively'
|
||||||
|
let clockSequenceHi: string = hexValues[8 + (Math.random() * 4) | 0];
|
||||||
|
return oct.substr(0, 8) + '-' + oct.substr(9, 4) + '-4' + oct.substr(13, 3) + '-' + clockSequenceHi + oct.substr(16, 3) + '-' + oct.substr(19, 12);
|
||||||
|
/* tslint:enable:no-bitwise */
|
||||||
|
}
|
||||||
|
|
||||||
|
export function verifyPlatform(): Thenable<boolean> {
|
||||||
|
if (os.platform() === 'darwin' && parseFloat(os.release()) < 16.0) {
|
||||||
|
return Promise.resolve(false);
|
||||||
|
} else {
|
||||||
|
return Promise.resolve(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getServiceInstallConfig(basePath?: string): any {
|
||||||
|
if (!basePath) {
|
||||||
|
basePath = __dirname;
|
||||||
|
}
|
||||||
|
let config = JSON.parse(JSON.stringify(baseConfig));
|
||||||
|
config.installDirectory = path.join(basePath, config.installDirectory);
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getResolvedServiceInstallationPath(runtime: Runtime, basePath?: string): string {
|
||||||
|
let config = getServiceInstallConfig(basePath);
|
||||||
|
let dir = config.installDirectory;
|
||||||
|
dir = dir.replace('{#version#}', config.version);
|
||||||
|
dir = dir.replace('{#platform#}', getRuntimeDisplayName(runtime));
|
||||||
|
|
||||||
|
return dir;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getRuntimeDisplayName(runtime: Runtime): string {
|
||||||
|
switch (runtime) {
|
||||||
|
case Runtime.Windows_64:
|
||||||
|
return 'Windows';
|
||||||
|
case Runtime.Windows_86:
|
||||||
|
return 'Windows';
|
||||||
|
case Runtime.OSX:
|
||||||
|
return 'OSX';
|
||||||
|
case Runtime.Linux_64:
|
||||||
|
return 'Linux';
|
||||||
|
default:
|
||||||
|
return 'Unknown';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum Runtime {
|
||||||
|
Unknown = <any>'Unknown',
|
||||||
|
Windows_86 = <any>'Windows_86',
|
||||||
|
Windows_64 = <any>'Windows_64',
|
||||||
|
OSX = <any>'OSX',
|
||||||
|
CentOS_7 = <any>'CentOS_7',
|
||||||
|
Debian_8 = <any>'Debian_8',
|
||||||
|
Fedora_23 = <any>'Fedora_23',
|
||||||
|
OpenSUSE_13_2 = <any>'OpenSUSE_13_2',
|
||||||
|
SLES_12_2 = <any>'SLES_12_2',
|
||||||
|
RHEL_7 = <any>'RHEL_7',
|
||||||
|
Ubuntu_14 = <any>'Ubuntu_14',
|
||||||
|
Ubuntu_16 = <any>'Ubuntu_16',
|
||||||
|
Linux_64 = <any>'Linux_64',
|
||||||
|
Linux_86 = <any>'Linux-86'
|
||||||
|
}
|
||||||
216
extensions/import/src/services/telemetry.ts
Normal file
216
extensions/import/src/services/telemetry.ts
Normal file
@@ -0,0 +1,216 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import { ErrorAction, CloseAction } from 'vscode-languageclient';
|
||||||
|
import TelemetryReporter from 'vscode-extension-telemetry';
|
||||||
|
import { PlatformInformation } from 'service-downloader/out/platform';
|
||||||
|
import * as opener from 'opener';
|
||||||
|
import * as vscode from 'vscode';
|
||||||
|
import * as nls from 'vscode-nls';
|
||||||
|
const localize = nls.loadMessageBundle();
|
||||||
|
|
||||||
|
import * as constants from '../constants';
|
||||||
|
import * as serviceUtils from './serviceUtils';
|
||||||
|
import { IMessage, ITelemetryEventProperties, ITelemetryEventMeasures } from './contracts';
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle Language Service client errors
|
||||||
|
* @class LanguageClientErrorHandler
|
||||||
|
*/
|
||||||
|
export class LanguageClientErrorHandler {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance of LanguageClientErrorHandler.
|
||||||
|
* @memberOf LanguageClientErrorHandler
|
||||||
|
*/
|
||||||
|
constructor() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show an error message prompt with a link to known issues wiki page
|
||||||
|
* @memberOf LanguageClientErrorHandler
|
||||||
|
*/
|
||||||
|
showOnErrorPrompt(): void {
|
||||||
|
// TODO add telemetry
|
||||||
|
// Telemetry.sendTelemetryEvent('SqlToolsServiceCrash');
|
||||||
|
let crashButtonText = localize('import.serviceCrashButton', 'Give Feedback');
|
||||||
|
vscode.window.showErrorMessage(
|
||||||
|
localize('serviceCrashMessage', 'service component could not start'),
|
||||||
|
crashButtonText
|
||||||
|
).then(action => {
|
||||||
|
if (action && action === crashButtonText) {
|
||||||
|
opener(constants.serviceCrashLink);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback for language service client error
|
||||||
|
*
|
||||||
|
* @param {Error} error
|
||||||
|
* @param {Message} message
|
||||||
|
* @param {number} count
|
||||||
|
* @returns {ErrorAction}
|
||||||
|
*
|
||||||
|
* @memberOf LanguageClientErrorHandler
|
||||||
|
*/
|
||||||
|
error(error: Error, message: IMessage, count: number): ErrorAction {
|
||||||
|
this.showOnErrorPrompt();
|
||||||
|
|
||||||
|
// we don't retry running the service since crashes leave the extension
|
||||||
|
// in a bad, unrecovered state
|
||||||
|
return ErrorAction.Shutdown;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback for language service client closed
|
||||||
|
*
|
||||||
|
* @returns {CloseAction}
|
||||||
|
*
|
||||||
|
* @memberOf LanguageClientErrorHandler
|
||||||
|
*/
|
||||||
|
closed(): CloseAction {
|
||||||
|
this.showOnErrorPrompt();
|
||||||
|
|
||||||
|
// we don't retry running the service since crashes leave the extension
|
||||||
|
// in a bad, unrecovered state
|
||||||
|
return CloseAction.DoNotRestart;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters error paths to only include source files. Exported to support testing
|
||||||
|
*/
|
||||||
|
export function FilterErrorPath(line: string): string {
|
||||||
|
if (line) {
|
||||||
|
let values: string[] = line.split('/out/');
|
||||||
|
if (values.length <= 1) {
|
||||||
|
// Didn't match expected format
|
||||||
|
return line;
|
||||||
|
} else {
|
||||||
|
return values[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Telemetry {
|
||||||
|
private static reporter: TelemetryReporter;
|
||||||
|
private static userId: string;
|
||||||
|
private static platformInformation: PlatformInformation;
|
||||||
|
private static disabled: boolean;
|
||||||
|
|
||||||
|
// Get the unique ID for the current user of the extension
|
||||||
|
public static getUserId(): Promise<string> {
|
||||||
|
return new Promise<string>(resolve => {
|
||||||
|
// Generate the user id if it has not been created already
|
||||||
|
if (typeof this.userId === 'undefined') {
|
||||||
|
let id = serviceUtils.generateUserId();
|
||||||
|
id.then(newId => {
|
||||||
|
this.userId = newId;
|
||||||
|
resolve(this.userId);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
resolve(this.userId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static getPlatformInformation(): Promise<PlatformInformation> {
|
||||||
|
if (this.platformInformation) {
|
||||||
|
return Promise.resolve(this.platformInformation);
|
||||||
|
} else {
|
||||||
|
return new Promise<PlatformInformation>(resolve => {
|
||||||
|
PlatformInformation.getCurrent().then(info => {
|
||||||
|
this.platformInformation = info;
|
||||||
|
resolve(this.platformInformation);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disable telemetry reporting
|
||||||
|
*/
|
||||||
|
public static disable(): void {
|
||||||
|
this.disabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the telemetry reporter for use.
|
||||||
|
*/
|
||||||
|
public static initialize(): void {
|
||||||
|
if (typeof this.reporter === 'undefined') {
|
||||||
|
// Check if the user has opted out of telemetry
|
||||||
|
if (!vscode.workspace.getConfiguration('telemetry').get<boolean>('enableTelemetry', true)) {
|
||||||
|
this.disable();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let packageInfo = vscode.extensions.getExtension('Microsoft.import').packageJSON;
|
||||||
|
this.reporter = new TelemetryReporter(packageInfo.name, packageInfo.version, packageInfo.aiKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a telemetry event for an exception
|
||||||
|
*/
|
||||||
|
public static sendTelemetryEventForException(
|
||||||
|
err: any, methodName: string, extensionConfigName: string): void {
|
||||||
|
try {
|
||||||
|
let stackArray: string[];
|
||||||
|
let firstLine: string = '';
|
||||||
|
if (err !== undefined && err.stack !== undefined) {
|
||||||
|
stackArray = err.stack.split('\n');
|
||||||
|
if (stackArray !== undefined && stackArray.length >= 2) {
|
||||||
|
firstLine = stackArray[1]; // The fist line is the error message and we don't want to send that telemetry event
|
||||||
|
firstLine = FilterErrorPath(firstLine);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only adding the method name and the fist line of the stack trace. We don't add the error message because it might have PII
|
||||||
|
this.sendTelemetryEvent('Exception', { methodName: methodName, errorLine: firstLine });
|
||||||
|
// Utils.logDebug('Unhandled Exception occurred. error: ' + err + ' method: ' + methodName, extensionConfigName);
|
||||||
|
} catch (telemetryErr) {
|
||||||
|
// If sending telemetry event fails ignore it so it won't break the extension
|
||||||
|
// Utils.logDebug('Failed to send telemetry event. error: ' + telemetryErr, extensionConfigName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a telemetry event using application insights
|
||||||
|
*/
|
||||||
|
public static sendTelemetryEvent(
|
||||||
|
eventName: string,
|
||||||
|
properties?: ITelemetryEventProperties,
|
||||||
|
measures?: ITelemetryEventMeasures): void {
|
||||||
|
|
||||||
|
if (typeof this.disabled === 'undefined') {
|
||||||
|
this.disabled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.disabled || typeof (this.reporter) === 'undefined') {
|
||||||
|
// Don't do anything if telemetry is disabled
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!properties || typeof properties === 'undefined') {
|
||||||
|
properties = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Augment the properties structure with additional common properties before sending
|
||||||
|
Promise.all([this.getUserId(), this.getPlatformInformation()]).then(() => {
|
||||||
|
properties['userId'] = this.userId;
|
||||||
|
properties['distribution'] = (this.platformInformation && this.platformInformation.distribution) ?
|
||||||
|
`${this.platformInformation.distribution.name}, ${this.platformInformation.distribution.version}` : '';
|
||||||
|
|
||||||
|
this.reporter.sendTelemetryEvent(eventName, properties, measures);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Telemetry.initialize();
|
||||||
9
extensions/import/src/typings/ref.d.ts
vendored
Normal file
9
extensions/import/src/typings/ref.d.ts
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
/// <reference path='../../../../src/vs/vscode.d.ts'/>
|
||||||
|
/// <reference path='../../../../src/sql/sqlops.d.ts'/>
|
||||||
|
/// <reference path='../../../../src/sql/sqlops.proposed.d.ts'/>
|
||||||
|
/// <reference types='@types/node'/>
|
||||||
50
extensions/import/src/wizard/api/importPage.ts
Normal file
50
extensions/import/src/wizard/api/importPage.ts
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* 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 instance: FlatFileWizard;
|
||||||
|
protected readonly model: ImportDataModel;
|
||||||
|
protected readonly view: sqlops.ModelView;
|
||||||
|
protected readonly provider: FlatFileProvider;
|
||||||
|
|
||||||
|
protected constructor(instance: FlatFileWizard, model: ImportDataModel, view: sqlops.ModelView, provider: FlatFileProvider) {
|
||||||
|
this.instance = instance;
|
||||||
|
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>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Override this method to cleanup what you don't need cached in the page.
|
||||||
|
* @returns {Promise<boolean>}
|
||||||
|
*/
|
||||||
|
public async cleanup(): Promise<boolean> {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
32
extensions/import/src/wizard/api/models.ts
Normal file
32
extensions/import/src/wizard/api/models.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* 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;
|
||||||
|
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;
|
||||||
|
}
|
||||||
130
extensions/import/src/wizard/flatFileWizard.ts
Normal file
130
extensions/import/src/wizard/flatFileWizard.ts
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* 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() {
|
||||||
|
let model = <ImportDataModel>{};
|
||||||
|
let pages: Map<number, ImportPage> = new Map<number, ImportPage>();
|
||||||
|
|
||||||
|
|
||||||
|
// TODO localize this
|
||||||
|
let connections = await sqlops.connection.getActiveConnections();
|
||||||
|
if (!connections || connections.length === 0) {
|
||||||
|
vscode.window.showErrorMessage('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', 'New Table Details'));
|
||||||
|
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, model, view, this.provider);
|
||||||
|
pages.set(0, fileConfigPage);
|
||||||
|
await fileConfigPage.start();
|
||||||
|
fileConfigPage.onPageEnter();
|
||||||
|
});
|
||||||
|
|
||||||
|
let prosePreviewPage: ProsePreviewPage;
|
||||||
|
page2.registerContent(async (view) => {
|
||||||
|
prosePreviewPage = new ProsePreviewPage(this, model, view, this.provider);
|
||||||
|
pages.set(1, prosePreviewPage);
|
||||||
|
await prosePreviewPage.start();
|
||||||
|
});
|
||||||
|
|
||||||
|
let modifyColumnsPage: ModifyColumnsPage;
|
||||||
|
page3.registerContent(async (view) => {
|
||||||
|
modifyColumnsPage = new ModifyColumnsPage(this, model, view, this.provider);
|
||||||
|
pages.set(2, modifyColumnsPage);
|
||||||
|
await modifyColumnsPage.start();
|
||||||
|
});
|
||||||
|
|
||||||
|
let summaryPage: SummaryPage;
|
||||||
|
|
||||||
|
page4.registerContent(async (view) => {
|
||||||
|
summaryPage = new SummaryPage(this, 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.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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
393
extensions/import/src/wizard/pages/fileConfigPage.ts
Normal file
393
extensions/import/src/wizard/pages/fileConfigPage.ts
Normal file
@@ -0,0 +1,393 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* 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, model: ImportDataModel, view: sqlops.ModelView, provider: FlatFileProvider) {
|
||||||
|
super(instance, 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.setupNavigationValidator();
|
||||||
|
|
||||||
|
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> {
|
||||||
|
await this.populateServerDropdown();
|
||||||
|
await this.populateDatabaseDropdown();
|
||||||
|
await this.populateSchemaDropdown();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async onPageLeave(): Promise<boolean> {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async cleanup(): Promise<boolean> {
|
||||||
|
delete this.model.filePath;
|
||||||
|
delete this.model.table;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private 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().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 (this.model.server && c.connectionId === this.model.server.connectionId) {
|
||||||
|
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.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().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().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;
|
||||||
|
}).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().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;
|
||||||
|
}
|
||||||
161
extensions/import/src/wizard/pages/modifyColumnsPage.ts
Normal file
161
extensions/import/src/wizard/pages/modifyColumnsPage.ts
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* 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, model: ImportDataModel, view: sqlops.ModelView, provider: FlatFileProvider) {
|
||||||
|
super(instance, 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.loading.loading = false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
async onPageLeave(): Promise<boolean> {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
async cleanup(): Promise<boolean> {
|
||||||
|
delete this.model.proseColumns;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
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.allowNull', 'Allow null'),
|
||||||
|
valueType: sqlops.DeclarativeDataType.boolean,
|
||||||
|
isReadOnly: false,
|
||||||
|
width: '100px'
|
||||||
|
}],
|
||||||
|
data: data
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
128
extensions/import/src/wizard/pages/prosePreviewPage.ts
Normal file
128
extensions/import/src/wizard/pages/prosePreviewPage.ts
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* 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, model: ImportDataModel, view: sqlops.ModelView, provider: FlatFileProvider) {
|
||||||
|
super(instance, 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.setupNavigationValidator();
|
||||||
|
|
||||||
|
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'),
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
private setupNavigationValidator() {
|
||||||
|
this.instance.registerNavigationValidator((info) => {
|
||||||
|
if (this.loading.loading) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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([]);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
168
extensions/import/src/wizard/pages/summaryPage.ts
Normal file
168
extensions/import/src/wizard/pages/summaryPage.ts
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* 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, model: ImportDataModel, view: sqlops.ModelView, provider: FlatFileProvider) {
|
||||||
|
super(instance, 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
private populateTable() {
|
||||||
|
this.table.updateProperties({
|
||||||
|
data: [
|
||||||
|
['Server name', this.model.server.providerName],
|
||||||
|
['Database name', this.model.database],
|
||||||
|
['Table name', this.model.table],
|
||||||
|
['Table schema', this.model.schema],
|
||||||
|
['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', '✔ Awesome! You have successfully inserted the data into a table.');
|
||||||
|
//} else {
|
||||||
|
//updateText = localize('flatFileImport.success.rows', '✔ Awesome! You have successfully inserted {0} rows.', rows);
|
||||||
|
//}
|
||||||
|
}
|
||||||
|
this.statusText.updateProperties({
|
||||||
|
value: updateText
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the connection string to send to the middleware
|
||||||
|
* @returns {Promise<string>}
|
||||||
|
*/
|
||||||
|
private async getConnectionString(): Promise<string> {
|
||||||
|
let options = this.model.server.options;
|
||||||
|
let connectionString: string;
|
||||||
|
|
||||||
|
if (options.authenticationType === 'Integrated') {
|
||||||
|
connectionString = `Data Source=${options.server + (options.port ? `,${options.port}` : '')};Initial Catalog=${this.model.database};Integrated Security=True`;
|
||||||
|
} else {
|
||||||
|
let credentials = await sqlops.connection.getCredentials(this.model.server.connectionId);
|
||||||
|
connectionString = `Data Source=${options.server + (options.port ? `,${options.port}` : '')};Initial Catalog=${this.model.database};Integrated Security=False;User Id=${options.user};Password=${credentials.password}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Fix this, it's returning undefined string.
|
||||||
|
//await sqlops.connection.getConnectionString(this.model.server.connectionId, true);
|
||||||
|
return connectionString;
|
||||||
|
}
|
||||||
|
|
||||||
|
// private async getCountRowsInserted(): Promise<Number> {
|
||||||
|
// let connectionUri = await sqlops.connection.getUriForConnection(this.model.server.connectionId);
|
||||||
|
// let queryProvider = sqlops.dataprotocol.getProvider<sqlops.QueryProvider>(this.model.server.providerName, sqlops.DataProviderType.QueryProvider);
|
||||||
|
// try {
|
||||||
|
// let query = sqlstring.format('USE ?; SELECT COUNT(*) FROM ?', [this.model.database, this.model.table]);
|
||||||
|
// let results = await queryProvider.runQueryAndReturn(connectionUri, query);
|
||||||
|
// let cell = results.rows[0][0];
|
||||||
|
// if (!cell || cell.isNull) {
|
||||||
|
// return -1;
|
||||||
|
// }
|
||||||
|
// let numericCell = Number(cell.displayValue);
|
||||||
|
// if (isNaN(numericCell)) {
|
||||||
|
// return -1;
|
||||||
|
// }
|
||||||
|
// return numericCell;
|
||||||
|
// } catch (e) {
|
||||||
|
// return -1;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
19
extensions/import/tsconfig.json
Normal file
19
extensions/import/tsconfig.json
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"compileOnSave": true,
|
||||||
|
"compilerOptions": {
|
||||||
|
"module": "commonjs",
|
||||||
|
"target": "es6",
|
||||||
|
"outDir": "./out",
|
||||||
|
"lib": [
|
||||||
|
"es6", "es2015.promise"
|
||||||
|
],
|
||||||
|
"sourceMap": true,
|
||||||
|
"emitDecoratorMetadata": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"declaration": true
|
||||||
|
},
|
||||||
|
"exclude": [
|
||||||
|
"node_modules"
|
||||||
|
]
|
||||||
|
}
|
||||||
470
extensions/import/yarn.lock
Normal file
470
extensions/import/yarn.lock
Normal file
@@ -0,0 +1,470 @@
|
|||||||
|
# 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"
|
||||||
|
|
||||||
|
ansi-regex@^3.0.0:
|
||||||
|
version "3.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998"
|
||||||
|
|
||||||
|
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"
|
||||||
|
|
||||||
|
charenc@~0.0.1:
|
||||||
|
version "0.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667"
|
||||||
|
|
||||||
|
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"
|
||||||
|
|
||||||
|
crypt@~0.0.1:
|
||||||
|
version "0.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b"
|
||||||
|
|
||||||
|
"dataprotocol-client@github:Microsoft/sqlops-dataprotocolclient#0.1.5":
|
||||||
|
version "0.1.5"
|
||||||
|
resolved "https://codeload.github.com/Microsoft/sqlops-dataprotocolclient/tar.gz/21b0bacfc759689a6c280408528c6029a21b1abf"
|
||||||
|
dependencies:
|
||||||
|
vscode-languageclient "3.5.0"
|
||||||
|
|
||||||
|
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"
|
||||||
|
|
||||||
|
debug@^2.2.0:
|
||||||
|
version "2.6.9"
|
||||||
|
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
|
||||||
|
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-buffer@~1.1.1:
|
||||||
|
version "1.1.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
|
||||||
|
|
||||||
|
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"
|
||||||
|
|
||||||
|
lodash@^4.16.4:
|
||||||
|
version "4.17.10"
|
||||||
|
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7"
|
||||||
|
|
||||||
|
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"
|
||||||
|
|
||||||
|
md5@^2.1.0:
|
||||||
|
version "2.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/md5/-/md5-2.2.1.tgz#53ab38d5fe3c8891ba465329ea23fac0540126f9"
|
||||||
|
dependencies:
|
||||||
|
charenc "~0.0.1"
|
||||||
|
crypt "~0.0.1"
|
||||||
|
is-buffer "~1.1.1"
|
||||||
|
|
||||||
|
minimist@0.0.8:
|
||||||
|
version "0.0.8"
|
||||||
|
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
|
||||||
|
|
||||||
|
mkdirp@^0.5.1, 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"
|
||||||
|
|
||||||
|
mocha-junit-reporter@^1.17.0:
|
||||||
|
version "1.17.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/mocha-junit-reporter/-/mocha-junit-reporter-1.17.0.tgz#2e5149ed40fc5d2e3ca71e42db5ab1fec9c6d85c"
|
||||||
|
dependencies:
|
||||||
|
debug "^2.2.0"
|
||||||
|
md5 "^2.1.0"
|
||||||
|
mkdirp "~0.5.1"
|
||||||
|
strip-ansi "^4.0.0"
|
||||||
|
xml "^1.0.0"
|
||||||
|
|
||||||
|
mocha-multi-reporters@^1.1.7:
|
||||||
|
version "1.1.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/mocha-multi-reporters/-/mocha-multi-reporters-1.1.7.tgz#cc7f3f4d32f478520941d852abb64d9988587d82"
|
||||||
|
dependencies:
|
||||||
|
debug "^3.1.0"
|
||||||
|
lodash "^4.16.4"
|
||||||
|
|
||||||
|
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-ansi@^4.0.0:
|
||||||
|
version "4.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f"
|
||||||
|
dependencies:
|
||||||
|
ansi-regex "^3.0.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, 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"
|
||||||
|
dependencies:
|
||||||
|
vscode-languageserver-protocol "^3.5.0"
|
||||||
|
|
||||||
|
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"
|
||||||
|
dependencies:
|
||||||
|
vscode-jsonrpc "^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"
|
||||||
|
|
||||||
|
vscode-nls@^3.2.1:
|
||||||
|
version "3.2.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-3.2.2.tgz#3817eca5b985c2393de325197cf4e15eb2aa5350"
|
||||||
|
|
||||||
|
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"
|
||||||
|
|
||||||
|
xml@^1.0.0:
|
||||||
|
version "1.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/xml/-/xml-1.0.1.tgz#78ba72020029c5bc87b8a81a3cfcd74b4a2fc1e5"
|
||||||
|
|
||||||
|
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"
|
||||||
@@ -35,6 +35,7 @@
|
|||||||
"date": "2017-12-15T12:00:00.000Z",
|
"date": "2017-12-15T12:00:00.000Z",
|
||||||
"recommendedExtensions": [
|
"recommendedExtensions": [
|
||||||
"Microsoft.agent",
|
"Microsoft.agent",
|
||||||
|
"Microsoft.import",
|
||||||
"Microsoft.profiler",
|
"Microsoft.profiler",
|
||||||
"Microsoft.server-report",
|
"Microsoft.server-report",
|
||||||
"Microsoft.whoisactive",
|
"Microsoft.whoisactive",
|
||||||
|
|||||||
Reference in New Issue
Block a user