mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 10:58:30 -05:00
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"
|
||||
Reference in New Issue
Block a user