mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-17 02:51:36 -05:00
Added Unified connection support (#3785)
* Added Unified connection support * Use generic way to do expandNode. Cleanup the ported code and removed unreference code. Added as needed later. Resolved PR comments. * Minor fixes and removed timer for all expanders for now. If any providers can't response, the tree node will spin and wait. We may improve later. * Change handSessionClose to not thenable. Added a node to OE to show error message instead of reject. So we could show partial expanded result if get any. Resolve PR comments * Minor fixes of PR comments
This commit is contained in:
@@ -18,10 +18,15 @@
|
|||||||
"update-grammar": "node ../../build/npm/update-grammar.js Microsoft/vscode-mssql syntaxes/SQL.plist ./syntaxes/sql.tmLanguage.json"
|
"update-grammar": "node ../../build/npm/update-grammar.js Microsoft/vscode-mssql syntaxes/SQL.plist ./syntaxes/sql.tmLanguage.json"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"clipboardy": "^1.2.3",
|
||||||
"dataprotocol-client": "github:Microsoft/sqlops-dataprotocolclient#0.2.15",
|
"dataprotocol-client": "github:Microsoft/sqlops-dataprotocolclient#0.2.15",
|
||||||
"opener": "^1.4.3",
|
"opener": "^1.4.3",
|
||||||
"service-downloader": "github:anthonydresser/service-downloader#0.1.5",
|
"service-downloader": "github:anthonydresser/service-downloader#0.1.5",
|
||||||
"vscode-extension-telemetry": "^0.0.15"
|
"stream-meter": "^1.0.4",
|
||||||
|
"uri-js": "^4.2.2",
|
||||||
|
"vscode-extension-telemetry": "^0.0.15",
|
||||||
|
"vscode-nls": "2.0.2",
|
||||||
|
"webhdfs": "^1.1.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
},
|
},
|
||||||
|
|||||||
93
extensions/mssql/src/apiWrapper.ts
Normal file
93
extensions/mssql/src/apiWrapper.ts
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import * as vscode from 'vscode';
|
||||||
|
import * as sqlops from 'sqlops';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper class to act as a facade over VSCode and Data APIs and allow us to test / mock callbacks into
|
||||||
|
* this API from our code
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @class ApiWrapper
|
||||||
|
*/
|
||||||
|
export class ApiWrapper {
|
||||||
|
// Data APIs
|
||||||
|
public registerConnectionProvider(provider: sqlops.ConnectionProvider): vscode.Disposable {
|
||||||
|
return sqlops.dataprotocol.registerConnectionProvider(provider);
|
||||||
|
}
|
||||||
|
|
||||||
|
public registerObjectExplorerNodeProvider(provider: sqlops.ObjectExplorerNodeProvider): vscode.Disposable {
|
||||||
|
return sqlops.dataprotocol.registerObjectExplorerNodeProvider(provider);
|
||||||
|
}
|
||||||
|
|
||||||
|
public registerTaskServicesProvider(provider: sqlops.TaskServicesProvider): vscode.Disposable {
|
||||||
|
return sqlops.dataprotocol.registerTaskServicesProvider(provider);
|
||||||
|
}
|
||||||
|
|
||||||
|
public registerFileBrowserProvider(provider: sqlops.FileBrowserProvider): vscode.Disposable {
|
||||||
|
return sqlops.dataprotocol.registerFileBrowserProvider(provider);
|
||||||
|
}
|
||||||
|
|
||||||
|
public registerTaskHandler(taskId: string, handler: (profile: sqlops.IConnectionProfile) => void): void {
|
||||||
|
sqlops.tasks.registerTask(taskId, handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
// VSCode APIs
|
||||||
|
|
||||||
|
public executeCommand(command: string, ...rest: any[]): Thenable<any> {
|
||||||
|
return vscode.commands.executeCommand(command, ...rest);
|
||||||
|
}
|
||||||
|
|
||||||
|
public registerCommand(command: string, callback: (...args: any[]) => any, thisArg?: any): vscode.Disposable {
|
||||||
|
return vscode.commands.registerCommand(command, callback, thisArg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public showOpenDialog(options: vscode.OpenDialogOptions): Thenable<vscode.Uri[] | undefined> {
|
||||||
|
return vscode.window.showOpenDialog(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
public showSaveDialog(options: vscode.SaveDialogOptions): Thenable<vscode.Uri> {
|
||||||
|
return vscode.window.showSaveDialog(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
public openTextDocument(uri: vscode.Uri): Thenable<vscode.TextDocument>;
|
||||||
|
public openTextDocument(options: { language?: string; content?: string; }): Thenable<vscode.TextDocument>;
|
||||||
|
public openTextDocument(uriOrOptions): Thenable<vscode.TextDocument> {
|
||||||
|
return vscode.workspace.openTextDocument(uriOrOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
public showTextDocument(document: vscode.TextDocument, column?: vscode.ViewColumn, preserveFocus?: boolean, preview?: boolean): Thenable<vscode.TextEditor> {
|
||||||
|
let options: vscode.TextDocumentShowOptions = {
|
||||||
|
viewColumn: column,
|
||||||
|
preserveFocus: preserveFocus,
|
||||||
|
preview: preview
|
||||||
|
};
|
||||||
|
return vscode.window.showTextDocument(document, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
public showErrorMessage(message: string, ...items: string[]): Thenable<string | undefined> {
|
||||||
|
return vscode.window.showErrorMessage(message, ...items);
|
||||||
|
}
|
||||||
|
|
||||||
|
public showWarningMessage(message: string, ...items: string[]): Thenable<string | undefined> {
|
||||||
|
return vscode.window.showWarningMessage(message, ...items);
|
||||||
|
}
|
||||||
|
|
||||||
|
public showInformationMessage(message: string, ...items: string[]): Thenable<string | undefined> {
|
||||||
|
return vscode.window.showInformationMessage(message, ...items);
|
||||||
|
}
|
||||||
|
|
||||||
|
public createStatusBarItem(alignment?: vscode.StatusBarAlignment, priority?: number): vscode.StatusBarItem {
|
||||||
|
return vscode.window.createStatusBarItem(alignment, priority);
|
||||||
|
}
|
||||||
|
|
||||||
|
public get workspaceFolders(): vscode.WorkspaceFolder[] {
|
||||||
|
return vscode.workspace.workspaceFolders;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
28
extensions/mssql/src/appContext.ts
Normal file
28
extensions/mssql/src/appContext.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* 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 { ApiWrapper } from './apiWrapper';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Global context for the application
|
||||||
|
*/
|
||||||
|
export class AppContext {
|
||||||
|
|
||||||
|
private serviceMap: Map<string, any> = new Map();
|
||||||
|
constructor(public readonly extensionContext: vscode.ExtensionContext, public readonly apiWrapper: ApiWrapper) {
|
||||||
|
this.apiWrapper = apiWrapper || new ApiWrapper();
|
||||||
|
}
|
||||||
|
|
||||||
|
public getService<T>(serviceName: string): T {
|
||||||
|
return this.serviceMap.get(serviceName) as T;
|
||||||
|
}
|
||||||
|
|
||||||
|
public registerService<T>(serviceName: string, service: T): void {
|
||||||
|
this.serviceMap.set(serviceName, service);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,3 +10,51 @@ export const serviceCrashMessage = 'SQL Tools Service component exited unexpecte
|
|||||||
export const serviceCrashButton = 'View Known Issues';
|
export const serviceCrashButton = 'View Known Issues';
|
||||||
export const serviceCrashLink = 'https://github.com/Microsoft/vscode-mssql/wiki/SqlToolsService-Known-Issues';
|
export const serviceCrashLink = 'https://github.com/Microsoft/vscode-mssql/wiki/SqlToolsService-Known-Issues';
|
||||||
export const extensionConfigSectionName = 'mssql';
|
export const extensionConfigSectionName = 'mssql';
|
||||||
|
|
||||||
|
// DATA PROTOCOL VALUES ///////////////////////////////////////////////////////////
|
||||||
|
export const mssqlClusterProviderName = 'mssqlCluster';
|
||||||
|
export const hadoopKnoxEndpointName = 'Knox';
|
||||||
|
export const protocolVersion = '1.0';
|
||||||
|
export const hostPropName = 'host';
|
||||||
|
export const userPropName = 'user';
|
||||||
|
export const knoxPortPropName = 'knoxport';
|
||||||
|
export const passwordPropName = 'password';
|
||||||
|
export const groupIdPropName = 'groupId';
|
||||||
|
export const defaultKnoxPort = '30443';
|
||||||
|
export const groupIdName = 'groupId';
|
||||||
|
export const sqlProviderName = 'MSSQL';
|
||||||
|
export const dataService = 'Data Services';
|
||||||
|
|
||||||
|
export const hdfsHost = 'host';
|
||||||
|
export const hdfsUser = 'user';
|
||||||
|
export const UNTITLED_SCHEMA = 'untitled';
|
||||||
|
|
||||||
|
export const hadoopConnectionTimeoutSeconds = 15;
|
||||||
|
export const hdfsRootPath = '/';
|
||||||
|
|
||||||
|
export const clusterEndpointsProperty = 'clusterEndpoints';
|
||||||
|
export const isBigDataClusterProperty = 'isBigDataCluster';
|
||||||
|
|
||||||
|
// SERVICE NAMES //////////////////////////////////////////////////////////
|
||||||
|
export const ObjectExplorerService = 'objectexplorer';
|
||||||
|
export const objectExplorerPrefix: string = 'objectexplorer://';
|
||||||
|
export const ViewType = 'view';
|
||||||
|
|
||||||
|
export enum BuiltInCommands {
|
||||||
|
SetContext = 'setContext'
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum CommandContext {
|
||||||
|
WizardServiceEnabled = 'wizardservice:enabled'
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum HdfsItems {
|
||||||
|
Connection = 'hdfs:connection',
|
||||||
|
Folder = 'hdfs:folder',
|
||||||
|
File = 'hdfs:file',
|
||||||
|
Message = 'hdfs:message'
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum HdfsItemsSubType {
|
||||||
|
Spark = 'hdfs:spark'
|
||||||
|
}
|
||||||
13
extensions/mssql/src/localizedConstants.ts
Normal file
13
extensions/mssql/src/localizedConstants.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import * as nls from 'vscode-nls';
|
||||||
|
const localize = nls.loadMessageBundle();
|
||||||
|
|
||||||
|
// HDFS Constants //////////////////////////////////////////////////////////
|
||||||
|
export const msgMissingNodeContext = localize('msgMissingNodeContext', 'Node Command called without any node passed');
|
||||||
|
export const msgTimeout = localize('connectionTimeout', 'connection timed out. Host name or port may be incorrect');
|
||||||
@@ -5,6 +5,7 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
|
import * as sqlops from 'sqlops';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import { SqlOpsDataClient, ClientOptions } from 'dataprotocol-client';
|
import { SqlOpsDataClient, ClientOptions } from 'dataprotocol-client';
|
||||||
import { IConfig, ServerProvider, Events } from 'service-downloader';
|
import { IConfig, ServerProvider, Events } from 'service-downloader';
|
||||||
@@ -17,6 +18,9 @@ import { AzureResourceProvider } from './resourceProvider/resourceProvider';
|
|||||||
import * as Utils from './utils';
|
import * as Utils from './utils';
|
||||||
import { Telemetry, LanguageClientErrorHandler } from './telemetry';
|
import { Telemetry, LanguageClientErrorHandler } from './telemetry';
|
||||||
import { TelemetryFeature, AgentServicesFeature, DacFxServicesFeature } from './features';
|
import { TelemetryFeature, AgentServicesFeature, DacFxServicesFeature } from './features';
|
||||||
|
import { AppContext } from './appContext';
|
||||||
|
import { ApiWrapper } from './apiWrapper';
|
||||||
|
import { MssqlObjectExplorerNodeProvider } from './objectExplorerNodeProvider/objectExplorerNodeProvider';
|
||||||
|
|
||||||
const baseConfig = require('./config.json');
|
const baseConfig = require('./config.json');
|
||||||
const outputChannel = vscode.window.createOutputChannel(Constants.serviceName);
|
const outputChannel = vscode.window.createOutputChannel(Constants.serviceName);
|
||||||
@@ -85,6 +89,8 @@ export async function activate(context: vscode.ExtensionContext) {
|
|||||||
languageClient.start();
|
languageClient.start();
|
||||||
credentialsStore.start();
|
credentialsStore.start();
|
||||||
resourceProvider.start();
|
resourceProvider.start();
|
||||||
|
let nodeProvider = new MssqlObjectExplorerNodeProvider(new AppContext(context, new ApiWrapper()));
|
||||||
|
sqlops.dataprotocol.registerObjectExplorerNodeProvider(nodeProvider);
|
||||||
}, e => {
|
}, e => {
|
||||||
Telemetry.sendTelemetryEvent('ServiceInitializingFailed');
|
Telemetry.sendTelemetryEvent('ServiceInitializingFailed');
|
||||||
vscode.window.showErrorMessage('Failed to start Sql tools service');
|
vscode.window.showErrorMessage('Failed to start Sql tools service');
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* 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 { Transform } from 'stream';
|
||||||
|
import * as vscode from 'vscode';
|
||||||
|
import * as nls from 'vscode-nls';
|
||||||
|
|
||||||
|
const localize = nls.loadMessageBundle();
|
||||||
|
|
||||||
|
export class CancelableStream extends Transform {
|
||||||
|
constructor(private cancelationToken: vscode.CancellationTokenSource) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public _transform(chunk: any, encoding: string, callback: Function): void {
|
||||||
|
if (this.cancelationToken && this.cancelationToken.token.isCancellationRequested) {
|
||||||
|
callback(new Error(localize('streamCanceled', 'Stream operation canceled by the user')));
|
||||||
|
} else {
|
||||||
|
this.push(chunk);
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
176
extensions/mssql/src/objectExplorerNodeProvider/command.ts
Normal file
176
extensions/mssql/src/objectExplorerNodeProvider/command.ts
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
import * as vscode from 'vscode';
|
||||||
|
import * as sqlops from 'sqlops';
|
||||||
|
import * as nls from 'vscode-nls';
|
||||||
|
const localize = nls.loadMessageBundle();
|
||||||
|
|
||||||
|
import { ApiWrapper } from '../apiWrapper';
|
||||||
|
import { TreeNode } from './treeNodes';
|
||||||
|
import { QuestionTypes, IPrompter, IQuestion } from '../prompts/question';
|
||||||
|
import * as utils from '../utils';
|
||||||
|
import * as constants from '../constants';
|
||||||
|
import { AppContext } from '../appContext';
|
||||||
|
|
||||||
|
export interface ICommandContextParsingOptions {
|
||||||
|
editor: boolean;
|
||||||
|
uri: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ICommandBaseContext {
|
||||||
|
command: string;
|
||||||
|
editor?: vscode.TextEditor;
|
||||||
|
uri?: vscode.Uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ICommandUnknownContext extends ICommandBaseContext {
|
||||||
|
type: 'unknown';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ICommandUriContext extends ICommandBaseContext {
|
||||||
|
type: 'uri';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ICommandViewContext extends ICommandBaseContext {
|
||||||
|
type: 'view';
|
||||||
|
node: TreeNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ICommandObjectExplorerContext extends ICommandBaseContext {
|
||||||
|
type: 'objectexplorer';
|
||||||
|
explorerContext: sqlops.ObjectExplorerContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CommandContext = ICommandObjectExplorerContext | ICommandViewContext | ICommandUriContext | ICommandUnknownContext;
|
||||||
|
|
||||||
|
function isTextEditor(editor: any): editor is vscode.TextEditor {
|
||||||
|
if (editor === undefined) { return false; }
|
||||||
|
|
||||||
|
return editor.id !== undefined && ((editor as vscode.TextEditor).edit !== undefined || (editor as vscode.TextEditor).document !== undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
export abstract class Command extends vscode.Disposable {
|
||||||
|
|
||||||
|
|
||||||
|
protected readonly contextParsingOptions: ICommandContextParsingOptions = { editor: false, uri: false };
|
||||||
|
|
||||||
|
private disposable: vscode.Disposable;
|
||||||
|
|
||||||
|
constructor(command: string | string[], protected appContext: AppContext) {
|
||||||
|
super(() => this.dispose());
|
||||||
|
|
||||||
|
if (typeof command === 'string') {
|
||||||
|
this.disposable = this.apiWrapper.registerCommand(command, (...args: any[]) => this._execute(command, ...args), this);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const subscriptions = command.map(cmd => this.apiWrapper.registerCommand(cmd, (...args: any[]) => this._execute(cmd, ...args), this));
|
||||||
|
this.disposable = vscode.Disposable.from(...subscriptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
dispose(): void {
|
||||||
|
this.disposable && this.disposable.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected get apiWrapper(): ApiWrapper {
|
||||||
|
return this.appContext.apiWrapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async preExecute(...args: any[]): Promise<any> {
|
||||||
|
return this.execute(...args);
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract execute(...args: any[]): any;
|
||||||
|
|
||||||
|
protected _execute(command: string, ...args: any[]): any {
|
||||||
|
// TODO consider using Telemetry.trackEvent(command);
|
||||||
|
|
||||||
|
const [context, rest] = Command.parseContext(command, this.contextParsingOptions, ...args);
|
||||||
|
return this.preExecute(context, ...rest);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static parseContext(command: string, options: ICommandContextParsingOptions, ...args: any[]): [CommandContext, any[]] {
|
||||||
|
let editor: vscode.TextEditor | undefined = undefined;
|
||||||
|
|
||||||
|
let firstArg = args[0];
|
||||||
|
if (options.editor && (firstArg === undefined || isTextEditor(firstArg))) {
|
||||||
|
editor = firstArg;
|
||||||
|
args = args.slice(1);
|
||||||
|
firstArg = args[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options.uri && (firstArg === undefined || firstArg instanceof vscode.Uri)) {
|
||||||
|
const [uri, ...rest] = args as [vscode.Uri, any];
|
||||||
|
return [{ command: command, type: 'uri', editor: editor, uri: uri }, rest];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (firstArg instanceof TreeNode) {
|
||||||
|
const [node, ...rest] = args as [TreeNode, any];
|
||||||
|
return [{ command: command, type: constants.ViewType, node: node }, rest];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (firstArg && utils.isObjectExplorerContext(firstArg)) {
|
||||||
|
const [explorerContext, ...rest] = args as [sqlops.ObjectExplorerContext, any];
|
||||||
|
return [{ command: command, type: constants.ObjectExplorerService, explorerContext: explorerContext }, rest];
|
||||||
|
}
|
||||||
|
|
||||||
|
return [{ command: command, type: 'unknown', editor: editor }, args];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export abstract class ProgressCommand extends Command {
|
||||||
|
static progressId = 0;
|
||||||
|
constructor(private command: string, protected prompter: IPrompter, appContext: AppContext) {
|
||||||
|
super(command, appContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async executeWithProgress(
|
||||||
|
execution: (cancelToken: vscode.CancellationTokenSource) => Promise<void>,
|
||||||
|
label: string,
|
||||||
|
isCancelable: boolean = false,
|
||||||
|
onCanceled?: () => void
|
||||||
|
): Promise<void> {
|
||||||
|
let disposables: vscode.Disposable[] = [];
|
||||||
|
const tokenSource = new vscode.CancellationTokenSource();
|
||||||
|
const statusBarItem = this.apiWrapper.createStatusBarItem(vscode.StatusBarAlignment.Left);
|
||||||
|
disposables.push(vscode.Disposable.from(statusBarItem));
|
||||||
|
statusBarItem.text = localize('progress', '$(sync~spin) {0}...', label);
|
||||||
|
if (isCancelable) {
|
||||||
|
const cancelCommandId = `cancelProgress${ProgressCommand.progressId++}`;
|
||||||
|
disposables.push(this.apiWrapper.registerCommand(cancelCommandId, async () => {
|
||||||
|
if (await this.confirmCancel()) {
|
||||||
|
tokenSource.cancel();
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
statusBarItem.tooltip = localize('cancelTooltip', 'Cancel');
|
||||||
|
statusBarItem.command = cancelCommandId;
|
||||||
|
}
|
||||||
|
statusBarItem.show();
|
||||||
|
|
||||||
|
try {
|
||||||
|
await execution(tokenSource);
|
||||||
|
} catch (error) {
|
||||||
|
if (isCancelable && onCanceled && tokenSource.token.isCancellationRequested) {
|
||||||
|
// The error can be assumed to be due to cancelation occurring. Do the callback
|
||||||
|
onCanceled();
|
||||||
|
} else {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
disposables.forEach(d => d.dispose());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async confirmCancel(): Promise<boolean> {
|
||||||
|
return await this.prompter.promptSingle<boolean>(<IQuestion>{
|
||||||
|
type: QuestionTypes.confirm,
|
||||||
|
message: localize('cancel', 'Cancel operation?'),
|
||||||
|
default: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
222
extensions/mssql/src/objectExplorerNodeProvider/connection.ts
Normal file
222
extensions/mssql/src/objectExplorerNodeProvider/connection.ts
Normal file
@@ -0,0 +1,222 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* 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 UUID from 'vscode-languageclient/lib/utils/uuid';
|
||||||
|
import * as nls from 'vscode-nls';
|
||||||
|
const localize = nls.loadMessageBundle();
|
||||||
|
|
||||||
|
import * as constants from '../constants';
|
||||||
|
import * as LocalizedConstants from '../localizedConstants';
|
||||||
|
import * as utils from '../utils';
|
||||||
|
import { IFileSource, HdfsFileSource, IHdfsOptions, IRequestParams, FileSourceFactory } from './fileSources';
|
||||||
|
|
||||||
|
function appendIfExists(uri: string, propName: string, propValue: string): string {
|
||||||
|
if (propValue) {
|
||||||
|
uri = `${uri};${propName}=${propValue}`;
|
||||||
|
}
|
||||||
|
return uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IValidationResult {
|
||||||
|
isValid: boolean;
|
||||||
|
errors: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Connection {
|
||||||
|
private _host: string;
|
||||||
|
private _knoxPort: string;
|
||||||
|
|
||||||
|
constructor(private connectionInfo: sqlops.ConnectionInfo, private connectionUri?: string, private _connectionId?: string) {
|
||||||
|
if (!this.connectionInfo) {
|
||||||
|
throw new Error(localize('connectionInfoMissing', 'connectionInfo is required'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this._connectionId) {
|
||||||
|
this._connectionId = UUID.generateUuid();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public get uri(): string {
|
||||||
|
return this.connectionUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
public saveUriWithPrefix(prefix: string): string {
|
||||||
|
let uri = `${prefix}${this.host}`;
|
||||||
|
uri = appendIfExists(uri, constants.knoxPortPropName, this.knoxport);
|
||||||
|
uri = appendIfExists(uri, constants.userPropName, this.user);
|
||||||
|
uri = appendIfExists(uri, constants.groupIdPropName, this.connectionInfo.options[constants.groupIdPropName]);
|
||||||
|
this.connectionUri = uri;
|
||||||
|
return this.connectionUri;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async tryConnect(factory?: FileSourceFactory): Promise<sqlops.ConnectionInfoSummary> {
|
||||||
|
let fileSource = this.createHdfsFileSource(factory, {
|
||||||
|
timeout: this.connecttimeout
|
||||||
|
});
|
||||||
|
let summary: sqlops.ConnectionInfoSummary = undefined;
|
||||||
|
try {
|
||||||
|
await fileSource.enumerateFiles(constants.hdfsRootPath);
|
||||||
|
summary = {
|
||||||
|
ownerUri: this.connectionUri,
|
||||||
|
connectionId: this.connectionId,
|
||||||
|
connectionSummary: {
|
||||||
|
serverName: this.host,
|
||||||
|
databaseName: undefined,
|
||||||
|
userName: this.user
|
||||||
|
},
|
||||||
|
errorMessage: undefined,
|
||||||
|
errorNumber: undefined,
|
||||||
|
messages: undefined,
|
||||||
|
serverInfo: this.getEmptyServerInfo()
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
summary = {
|
||||||
|
ownerUri: this.connectionUri,
|
||||||
|
connectionId: undefined,
|
||||||
|
connectionSummary: undefined,
|
||||||
|
errorMessage: this.getConnectError(error),
|
||||||
|
errorNumber: undefined,
|
||||||
|
messages: undefined,
|
||||||
|
serverInfo: undefined
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return summary;
|
||||||
|
}
|
||||||
|
|
||||||
|
private getConnectError(error: string | Error): string {
|
||||||
|
let errorMsg = utils.getErrorMessage(error);
|
||||||
|
if (errorMsg.indexOf('ETIMEDOUT') > -1) {
|
||||||
|
errorMsg = LocalizedConstants.msgTimeout;
|
||||||
|
} else if (errorMsg.indexOf('ENOTFOUND') > -1) {
|
||||||
|
errorMsg = LocalizedConstants.msgTimeout;
|
||||||
|
}
|
||||||
|
return localize('connectError', 'Connection failed with error: {0}', errorMsg);
|
||||||
|
}
|
||||||
|
|
||||||
|
private getEmptyServerInfo(): sqlops.ServerInfo {
|
||||||
|
let info: sqlops.ServerInfo = {
|
||||||
|
serverMajorVersion: 0,
|
||||||
|
serverMinorVersion: 0,
|
||||||
|
serverReleaseVersion: 0,
|
||||||
|
engineEditionId: 0,
|
||||||
|
serverVersion: '',
|
||||||
|
serverLevel: '',
|
||||||
|
serverEdition: '',
|
||||||
|
isCloud: false,
|
||||||
|
azureVersion: 0,
|
||||||
|
osVersion: '',
|
||||||
|
options: { isBigDataCluster: false, clusterEndpoints: []}
|
||||||
|
};
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get connectionId(): string {
|
||||||
|
return this._connectionId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get host(): string {
|
||||||
|
if (!this._host) {
|
||||||
|
this.ensureHostAndPort();
|
||||||
|
}
|
||||||
|
return this._host;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets host and port values, using any ',' or ':' delimited port in the hostname in
|
||||||
|
* preference to the built in port.
|
||||||
|
*/
|
||||||
|
private ensureHostAndPort(): void {
|
||||||
|
this._host = this.connectionInfo.options[constants.hostPropName];
|
||||||
|
this._knoxPort = Connection.getKnoxPortOrDefault(this.connectionInfo);
|
||||||
|
// determine whether the host has either a ',' or ':' in it
|
||||||
|
this.setHostAndPort(',');
|
||||||
|
this.setHostAndPort(':');
|
||||||
|
}
|
||||||
|
|
||||||
|
// set port and host correctly after we've identified that a delimiter exists in the host name
|
||||||
|
private setHostAndPort(delimeter: string): void {
|
||||||
|
let originalHost = this._host;
|
||||||
|
let index = originalHost.indexOf(delimeter);
|
||||||
|
if (index > -1) {
|
||||||
|
this._host = originalHost.slice(0, index);
|
||||||
|
this._knoxPort = originalHost.slice(index + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public get user(): string {
|
||||||
|
return this.connectionInfo.options[constants.userPropName];
|
||||||
|
}
|
||||||
|
|
||||||
|
public get password(): string {
|
||||||
|
return this.connectionInfo.options[constants.passwordPropName];
|
||||||
|
}
|
||||||
|
|
||||||
|
public get knoxport(): string {
|
||||||
|
if (!this._knoxPort) {
|
||||||
|
this.ensureHostAndPort();
|
||||||
|
}
|
||||||
|
return this._knoxPort;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static getKnoxPortOrDefault(connInfo: sqlops.ConnectionInfo): string {
|
||||||
|
let port = connInfo.options[constants.knoxPortPropName];
|
||||||
|
if (!port) {
|
||||||
|
port = constants.defaultKnoxPort;
|
||||||
|
}
|
||||||
|
return port;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get connecttimeout(): number {
|
||||||
|
let timeoutSeconds: number = this.connectionInfo.options['connecttimeout'];
|
||||||
|
if (!timeoutSeconds) {
|
||||||
|
timeoutSeconds = constants.hadoopConnectionTimeoutSeconds;
|
||||||
|
}
|
||||||
|
// connect timeout is in milliseconds
|
||||||
|
return timeoutSeconds * 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get sslverification(): string {
|
||||||
|
return this.connectionInfo.options['sslverification'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public get groupId(): string {
|
||||||
|
return this.connectionInfo.options[constants.groupIdName];
|
||||||
|
}
|
||||||
|
|
||||||
|
public isMatch(connectionInfo: sqlops.ConnectionInfo): boolean {
|
||||||
|
if (!connectionInfo) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let otherConnection = new Connection(connectionInfo);
|
||||||
|
return otherConnection.groupId === this.groupId
|
||||||
|
&& otherConnection.host === this.host
|
||||||
|
&& otherConnection.knoxport === this.knoxport
|
||||||
|
&& otherConnection.user === this.user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public createHdfsFileSource(factory?: FileSourceFactory, additionalRequestParams?: IRequestParams): IFileSource {
|
||||||
|
factory = factory || FileSourceFactory.instance;
|
||||||
|
let options: IHdfsOptions = {
|
||||||
|
protocol: 'https',
|
||||||
|
host: this.host,
|
||||||
|
port: this.knoxport,
|
||||||
|
user: this.user,
|
||||||
|
path: 'gateway/default/webhdfs/v1',
|
||||||
|
requestParams: {
|
||||||
|
auth: {
|
||||||
|
user: this.user,
|
||||||
|
pass: this.password
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (additionalRequestParams) {
|
||||||
|
options.requestParams = Object.assign(options.requestParams, additionalRequestParams);
|
||||||
|
}
|
||||||
|
return factory.createHdfsFileSource(options);
|
||||||
|
}
|
||||||
|
}
|
||||||
371
extensions/mssql/src/objectExplorerNodeProvider/fileSources.ts
Normal file
371
extensions/mssql/src/objectExplorerNodeProvider/fileSources.ts
Normal file
@@ -0,0 +1,371 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* 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 fspath from 'path';
|
||||||
|
import * as webhdfs from 'webhdfs';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import * as meter from 'stream-meter';
|
||||||
|
import * as bytes from 'bytes';
|
||||||
|
import * as https from 'https';
|
||||||
|
import * as readline from 'readline';
|
||||||
|
import * as os from 'os';
|
||||||
|
|
||||||
|
import * as constants from '../constants';
|
||||||
|
import * as utils from '../utils';
|
||||||
|
|
||||||
|
export function joinHdfsPath(parent: string, child: string): string {
|
||||||
|
if (parent === constants.hdfsRootPath) {
|
||||||
|
return `/${child}`;
|
||||||
|
}
|
||||||
|
return `${parent}/${child}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IFile {
|
||||||
|
path: string;
|
||||||
|
isDirectory: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class File implements IFile {
|
||||||
|
constructor(public path: string, public isDirectory: boolean) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static createPath(path: string, fileName: string): string {
|
||||||
|
return joinHdfsPath(path, fileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static createChild(parent: IFile, fileName: string, isDirectory: boolean): IFile {
|
||||||
|
return new File(File.createPath(parent.path, fileName), isDirectory);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static createFile(parent: IFile, fileName: string): File {
|
||||||
|
return File.createChild(parent, fileName, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static createDirectory(parent: IFile, fileName: string): IFile {
|
||||||
|
return File.createChild(parent, fileName, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static getBasename(file: IFile): string {
|
||||||
|
return fspath.basename(file.path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IFileSource {
|
||||||
|
|
||||||
|
enumerateFiles(path: string): Promise<IFile[]>;
|
||||||
|
mkdir(dirName: string, remoteBasePath: string): Promise<void>;
|
||||||
|
createReadStream(path: string): fs.ReadStream;
|
||||||
|
readFile(path: string, maxBytes?: number): Promise<Buffer>;
|
||||||
|
readFileLines(path: string, maxLines: number): Promise<Buffer>;
|
||||||
|
writeFile(localFile: IFile, remoteDir: string): Promise<string>;
|
||||||
|
delete(path: string, recursive?: boolean): Promise<void>;
|
||||||
|
exists(path: string): Promise<boolean>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IHttpAuthentication {
|
||||||
|
user: string;
|
||||||
|
pass: string;
|
||||||
|
}
|
||||||
|
export interface IHdfsOptions {
|
||||||
|
host?: string;
|
||||||
|
port?: string;
|
||||||
|
protocol?: string;
|
||||||
|
user?: string;
|
||||||
|
path?: string;
|
||||||
|
requestParams?: IRequestParams;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IRequestParams {
|
||||||
|
auth?: IHttpAuthentication;
|
||||||
|
/**
|
||||||
|
* Timeout in milliseconds to wait for response
|
||||||
|
*/
|
||||||
|
timeout?: number;
|
||||||
|
agent?: https.Agent;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IHdfsFileStatus {
|
||||||
|
type: 'FILE' | 'DIRECTORY';
|
||||||
|
pathSuffix: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IHdfsClient {
|
||||||
|
readdir(path: string, callback: (err: Error, files: any[]) => void): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create readable stream for given path
|
||||||
|
*
|
||||||
|
* @method createReadStream
|
||||||
|
* @fires Request#data
|
||||||
|
* @fires WebHDFS#finish
|
||||||
|
*
|
||||||
|
* @param {String} path
|
||||||
|
* @param {Object} [opts]
|
||||||
|
*
|
||||||
|
* @returns {Object}
|
||||||
|
*/
|
||||||
|
createReadStream (path: string, opts?: object): fs.ReadStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create writable stream for given path
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
*
|
||||||
|
* var WebHDFS = require('webhdfs');
|
||||||
|
* var hdfs = WebHDFS.createClient();
|
||||||
|
*
|
||||||
|
* var localFileStream = fs.createReadStream('/path/to/local/file');
|
||||||
|
* var remoteFileStream = hdfs.createWriteStream('/path/to/remote/file');
|
||||||
|
*
|
||||||
|
* localFileStream.pipe(remoteFileStream);
|
||||||
|
*
|
||||||
|
* remoteFileStream.on('error', function onError (err) {
|
||||||
|
* // Do something with the error
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* remoteFileStream.on('finish', function onFinish () {
|
||||||
|
* // Upload is done
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* @method createWriteStream
|
||||||
|
* @fires WebHDFS#finish
|
||||||
|
*
|
||||||
|
* @param {String} path
|
||||||
|
* @param {Boolean} [append] If set to true then append data to the file
|
||||||
|
* @param {Object} [opts]
|
||||||
|
*
|
||||||
|
* @returns {Object}
|
||||||
|
*/
|
||||||
|
createWriteStream(path: string, append?: boolean, opts?: object): fs.WriteStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make new directory
|
||||||
|
*
|
||||||
|
* @method mkdir
|
||||||
|
*
|
||||||
|
* @param {String} path
|
||||||
|
* @param {String} [mode=0777]
|
||||||
|
* @param {Function} callback
|
||||||
|
*
|
||||||
|
* @returns {Object}
|
||||||
|
*/
|
||||||
|
mkdir (path: string, callback: Function): void;
|
||||||
|
mkdir (path: string, mode: string, callback: Function): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete directory or file path
|
||||||
|
*
|
||||||
|
* @method unlink
|
||||||
|
*
|
||||||
|
* @param {String} path
|
||||||
|
* @param {Boolean} [recursive=false]
|
||||||
|
* @param {Function} callback
|
||||||
|
*
|
||||||
|
* @returns {Object}
|
||||||
|
*/
|
||||||
|
rmdir (path: string, recursive: boolean, callback: Function): void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check file existence
|
||||||
|
* Wraps stat method
|
||||||
|
*
|
||||||
|
* @method stat
|
||||||
|
* @see WebHDFS.stat
|
||||||
|
*
|
||||||
|
* @param {String} path
|
||||||
|
* @param {Function} callback
|
||||||
|
*
|
||||||
|
* @returns {Object}
|
||||||
|
*/
|
||||||
|
exists (path: string, callback: Function): boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class FileSourceFactory {
|
||||||
|
private static _instance: FileSourceFactory;
|
||||||
|
|
||||||
|
public static get instance(): FileSourceFactory {
|
||||||
|
if (!FileSourceFactory._instance) {
|
||||||
|
FileSourceFactory._instance = new FileSourceFactory();
|
||||||
|
}
|
||||||
|
return FileSourceFactory._instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
public createHdfsFileSource(options: IHdfsOptions): IFileSource {
|
||||||
|
options = options && options.host ? FileSourceFactory.removePortFromHost(options) : options;
|
||||||
|
let requestParams: IRequestParams = options.requestParams ? options.requestParams : {};
|
||||||
|
if (requestParams.auth) {
|
||||||
|
// TODO Remove handling of unsigned cert once we have real certs in our Knox service
|
||||||
|
let agentOptions = {
|
||||||
|
host: options.host,
|
||||||
|
port: options.port,
|
||||||
|
path: constants.hdfsRootPath,
|
||||||
|
rejectUnauthorized: false
|
||||||
|
};
|
||||||
|
let agent = new https.Agent(agentOptions);
|
||||||
|
requestParams['agent'] = agent;
|
||||||
|
}
|
||||||
|
return new HdfsFileSource(webhdfs.createClient(options, requestParams));
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove port from host when port is specified after a comma or colon
|
||||||
|
private static removePortFromHost(options: IHdfsOptions): IHdfsOptions {
|
||||||
|
// determine whether the host has either a ',' or ':' in it
|
||||||
|
options = this.setHostAndPort(options, ',');
|
||||||
|
options = this.setHostAndPort(options, ':');
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
// set port and host correctly after we've identified that a delimiter exists in the host name
|
||||||
|
private static setHostAndPort(options: IHdfsOptions, delimeter: string): IHdfsOptions {
|
||||||
|
let optionsHost: string = options.host;
|
||||||
|
if (options.host.indexOf(delimeter) > -1) {
|
||||||
|
options.host = options.host.slice(0, options.host.indexOf(delimeter));
|
||||||
|
options.port = optionsHost.replace(options.host + delimeter, '');
|
||||||
|
}
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class HdfsFileSource implements IFileSource {
|
||||||
|
constructor(private client: IHdfsClient) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public enumerateFiles(path: string): Promise<IFile[]> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.client.readdir(path, (error, files) => {
|
||||||
|
if (error) {
|
||||||
|
reject(error.message);
|
||||||
|
} else {
|
||||||
|
let hdfsFiles: IFile[] = files.map(file => {
|
||||||
|
let hdfsFile = <IHdfsFileStatus> file;
|
||||||
|
return new File(File.createPath(path, hdfsFile.pathSuffix), hdfsFile.type === 'DIRECTORY');
|
||||||
|
});
|
||||||
|
resolve(hdfsFiles);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public mkdir(dirName: string, remoteBasePath: string): Promise<void> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let remotePath = joinHdfsPath(remoteBasePath, dirName);
|
||||||
|
this.client.mkdir(remotePath, (err) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
resolve(undefined);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public createReadStream(path: string): fs.ReadStream {
|
||||||
|
return this.client.createReadStream(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public readFile(path: string, maxBytes?: number): Promise<Buffer> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let remoteFileStream = this.client.createReadStream(path);
|
||||||
|
if (maxBytes) {
|
||||||
|
remoteFileStream = remoteFileStream.pipe(meter(maxBytes));
|
||||||
|
}
|
||||||
|
let data = [];
|
||||||
|
let error = undefined;
|
||||||
|
remoteFileStream.on('error', (err) => {
|
||||||
|
error = err.toString();
|
||||||
|
if (error.includes('Stream exceeded specified max')) {
|
||||||
|
error = `File exceeds max size of ${bytes(maxBytes)}`;
|
||||||
|
}
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
|
||||||
|
remoteFileStream.on('data', (chunk) => {
|
||||||
|
data.push(chunk);
|
||||||
|
});
|
||||||
|
|
||||||
|
remoteFileStream.once('finish', () => {
|
||||||
|
if (!error) {
|
||||||
|
resolve(Buffer.concat(data));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public readFileLines(path: string, maxLines: number): Promise<Buffer> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let lineReader = readline.createInterface({
|
||||||
|
input: this.client.createReadStream(path)
|
||||||
|
});
|
||||||
|
|
||||||
|
let lineCount = 0;
|
||||||
|
let lineData: string[] = [];
|
||||||
|
let errorMsg = undefined;
|
||||||
|
lineReader.on('line', (line: string) => {
|
||||||
|
lineCount++;
|
||||||
|
lineData.push(line);
|
||||||
|
if (lineCount >= maxLines) {
|
||||||
|
resolve(Buffer.from(lineData.join(os.EOL)));
|
||||||
|
lineReader.close();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.on('error', (err) => {
|
||||||
|
errorMsg = utils.getErrorMessage(err);
|
||||||
|
reject(errorMsg);
|
||||||
|
})
|
||||||
|
.on('close', () => {
|
||||||
|
if (!errorMsg) {
|
||||||
|
resolve(Buffer.from(lineData.join(os.EOL)));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public writeFile(localFile: IFile, remoteDirPath: string): Promise<string> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let fileName = fspath.basename(localFile.path);
|
||||||
|
let remotePath = joinHdfsPath(remoteDirPath, fileName);
|
||||||
|
|
||||||
|
let writeStream = this.client.createWriteStream(remotePath);
|
||||||
|
|
||||||
|
let readStream = fs.createReadStream(localFile.path);
|
||||||
|
readStream.pipe(writeStream);
|
||||||
|
|
||||||
|
let error: string | Error = undefined;
|
||||||
|
|
||||||
|
// API always calls finish, so catch error then handle exit in the finish event
|
||||||
|
writeStream.on('error', (err => {
|
||||||
|
error = err;
|
||||||
|
reject(error);
|
||||||
|
}));
|
||||||
|
writeStream.on('finish', (location) => {
|
||||||
|
if (!error) {
|
||||||
|
resolve(location);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public delete(path: string, recursive: boolean = false): Promise<void> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.client.rmdir(path, recursive, (error) => {
|
||||||
|
if (error) {
|
||||||
|
reject(error);
|
||||||
|
} else {
|
||||||
|
resolve(undefined);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public exists(path: string): Promise<boolean> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.client.exists(path, (result) => {
|
||||||
|
resolve(result);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
437
extensions/mssql/src/objectExplorerNodeProvider/hdfsCommands.ts
Normal file
437
extensions/mssql/src/objectExplorerNodeProvider/hdfsCommands.ts
Normal file
@@ -0,0 +1,437 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import * as vscode from 'vscode';
|
||||||
|
import * as sqlops from 'sqlops';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import * as fspath from 'path';
|
||||||
|
import * as clipboardy from 'clipboardy';
|
||||||
|
import * as nls from 'vscode-nls';
|
||||||
|
const localize = nls.loadMessageBundle();
|
||||||
|
|
||||||
|
import { ApiWrapper } from '../apiWrapper';
|
||||||
|
import { Command, ICommandViewContext, ProgressCommand, ICommandObjectExplorerContext } from './command';
|
||||||
|
import { IHdfsOptions, HdfsFileSource, File, IFile, joinHdfsPath, FileSourceFactory } from './fileSources';
|
||||||
|
import { HdfsProvider, FolderNode, FileNode, HdfsFileSourceNode } from './hdfsProvider';
|
||||||
|
import { IPrompter, IQuestion, QuestionTypes } from '../prompts/question';
|
||||||
|
import * as constants from '../constants';
|
||||||
|
import * as LocalizedConstants from '../localizedConstants';
|
||||||
|
import * as utils from '../utils';
|
||||||
|
import { Connection } from './connection';
|
||||||
|
import { AppContext } from '../appContext';
|
||||||
|
import { TreeNode } from './treeNodes';
|
||||||
|
import { MssqlObjectExplorerNodeProvider } from './objectExplorerNodeProvider';
|
||||||
|
|
||||||
|
function getSaveableUri(apiWrapper: ApiWrapper, fileName: string, isPreview?: boolean): vscode.Uri {
|
||||||
|
let root = utils.getUserHome();
|
||||||
|
let workspaceFolders = apiWrapper.workspaceFolders;
|
||||||
|
if (workspaceFolders && workspaceFolders.length > 0) {
|
||||||
|
root = workspaceFolders[0].uri.fsPath;
|
||||||
|
}
|
||||||
|
// Cannot preview with a file path that already exists, so keep looking for a valid path that does not exist
|
||||||
|
if (isPreview) {
|
||||||
|
let fileNum = 1;
|
||||||
|
let fileNameWithoutExtension = fspath.parse(fileName).name;
|
||||||
|
let fileExtension = fspath.parse(fileName).ext;
|
||||||
|
while (fs.existsSync(fspath.join(root, fileName))) {
|
||||||
|
fileName = `${fileNameWithoutExtension}-${fileNum}${fileExtension}`;
|
||||||
|
fileNum++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return vscode.Uri.file(fspath.join(root, fileName));
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getNode<T extends TreeNode>(context: ICommandViewContext |ICommandObjectExplorerContext, appContext: AppContext): Promise<T> {
|
||||||
|
let node: T = undefined;
|
||||||
|
if (context && context.type === constants.ViewType && context.node) {
|
||||||
|
node = context.node as T;
|
||||||
|
} else if (context && context.type === constants.ObjectExplorerService) {
|
||||||
|
let oeProvider = appContext.getService<MssqlObjectExplorerNodeProvider>(constants.ObjectExplorerService);
|
||||||
|
if (oeProvider) {
|
||||||
|
node = await oeProvider.findNodeForContext<T>(context.explorerContext);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error(LocalizedConstants.msgMissingNodeContext);
|
||||||
|
}
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class UploadFilesCommand extends ProgressCommand {
|
||||||
|
|
||||||
|
constructor(prompter: IPrompter, appContext: AppContext) {
|
||||||
|
super('hdfs.uploadFiles', prompter, appContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async preExecute(context: ICommandViewContext | ICommandObjectExplorerContext, args: object = {}): Promise<any> {
|
||||||
|
return this.execute(context, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
async execute(context: ICommandViewContext | ICommandObjectExplorerContext, ...args: any[]): Promise<void> {
|
||||||
|
try {
|
||||||
|
let folderNode = await getNode<FolderNode>(context, this.appContext);
|
||||||
|
const allFilesFilter = localize('allFiles', 'All Files');
|
||||||
|
let filter = {};
|
||||||
|
filter[allFilesFilter] = '*';
|
||||||
|
if (folderNode) {
|
||||||
|
let options: vscode.OpenDialogOptions = {
|
||||||
|
canSelectFiles: true,
|
||||||
|
canSelectFolders: false,
|
||||||
|
canSelectMany: true,
|
||||||
|
openLabel: localize('lblUploadFiles', 'Upload'),
|
||||||
|
filters: filter
|
||||||
|
};
|
||||||
|
let fileUris: vscode.Uri[] = await this.apiWrapper.showOpenDialog(options);
|
||||||
|
if (fileUris) {
|
||||||
|
let files: IFile[] = fileUris.map(uri => uri.fsPath).map(this.mapPathsToFiles());
|
||||||
|
await this.executeWithProgress(
|
||||||
|
async (cancelToken: vscode.CancellationTokenSource) => this.writeFiles(files, folderNode, cancelToken),
|
||||||
|
localize('uploading', 'Uploading files to HDFS'), true,
|
||||||
|
() => this.apiWrapper.showInformationMessage(localize('uploadCanceled', 'Upload operation was canceled')));
|
||||||
|
if (context.type === constants.ObjectExplorerService) {
|
||||||
|
let objectExplorerNode = await sqlops.objectexplorer.getNode(context.explorerContext.connectionProfile.id, folderNode.getNodeInfo().nodePath);
|
||||||
|
await objectExplorerNode.refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
this.apiWrapper.showErrorMessage(localize('uploadError', 'Error uploading files: {0}', utils.getErrorMessage(err)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private mapPathsToFiles(): (value: string, index: number, array: string[]) => File {
|
||||||
|
return (path: string) => {
|
||||||
|
let isDir = fs.lstatSync(path).isDirectory();
|
||||||
|
return new File(path, isDir);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private async writeFiles(files: IFile[], folderNode: FolderNode, cancelToken: vscode.CancellationTokenSource): Promise<void> {
|
||||||
|
for (let file of files) {
|
||||||
|
if (cancelToken.token.isCancellationRequested) {
|
||||||
|
// Throw here so that all recursion is ended
|
||||||
|
throw new Error('Upload canceled');
|
||||||
|
}
|
||||||
|
if (file.isDirectory) {
|
||||||
|
let dirName = fspath.basename(file.path);
|
||||||
|
let subFolder = await folderNode.mkdir(dirName);
|
||||||
|
let children: IFile[] = fs.readdirSync(file.path)
|
||||||
|
.map(childFileName => joinHdfsPath(file.path, childFileName))
|
||||||
|
.map(this.mapPathsToFiles());
|
||||||
|
this.writeFiles(children, subFolder, cancelToken);
|
||||||
|
} else {
|
||||||
|
await folderNode.writeFile(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class MkDirCommand extends ProgressCommand {
|
||||||
|
|
||||||
|
constructor(prompter: IPrompter, appContext: AppContext) {
|
||||||
|
super('hdfs.mkdir', prompter, appContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async preExecute(context: ICommandViewContext | ICommandObjectExplorerContext, args: object = {}): Promise<any> {
|
||||||
|
return this.execute(context, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
async execute(context: ICommandViewContext | ICommandObjectExplorerContext, ...args: any[]): Promise<void> {
|
||||||
|
try {
|
||||||
|
let folderNode = await getNode<FolderNode>(context, this.appContext);
|
||||||
|
|
||||||
|
if (folderNode) {
|
||||||
|
let fileName: string = await this.getDirName();
|
||||||
|
if (fileName && fileName.length > 0) {
|
||||||
|
await this.executeWithProgress(
|
||||||
|
async (cancelToken: vscode.CancellationTokenSource) => this.mkDir(fileName, folderNode, cancelToken),
|
||||||
|
localize('makingDir', 'Creating directory'), true,
|
||||||
|
() => this.apiWrapper.showInformationMessage(localize('mkdirCanceled', 'Operation was canceled')));
|
||||||
|
if (context.type === constants.ObjectExplorerService) {
|
||||||
|
let objectExplorerNode = await sqlops.objectexplorer.getNode(context.explorerContext.connectionProfile.id, folderNode.getNodeInfo().nodePath);
|
||||||
|
await objectExplorerNode.refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
this.apiWrapper.showErrorMessage(localize('uploadError', 'Error uploading files: {0}', utils.getErrorMessage(err)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getDirName(): Promise<string> {
|
||||||
|
return await this.prompter.promptSingle(<IQuestion>{
|
||||||
|
type: QuestionTypes.input,
|
||||||
|
name: 'enterDirName',
|
||||||
|
message: localize('enterDirName', 'Enter directory name'),
|
||||||
|
default: ''
|
||||||
|
}).then(confirmed => <string>confirmed);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async mkDir(fileName, folderNode: FolderNode, cancelToken: vscode.CancellationTokenSource): Promise<void> {
|
||||||
|
let subFolder = await folderNode.mkdir(fileName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DeleteFilesCommand extends Command {
|
||||||
|
|
||||||
|
constructor(private prompter: IPrompter, appContext: AppContext) {
|
||||||
|
super('hdfs.deleteFiles', appContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async preExecute(context: ICommandViewContext |ICommandObjectExplorerContext, args: object = {}): Promise<any> {
|
||||||
|
return this.execute(context, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
async execute(context: ICommandViewContext |ICommandObjectExplorerContext, ...args: any[]): Promise<void> {
|
||||||
|
try {
|
||||||
|
let node = await getNode<TreeNode>(context, this.appContext);
|
||||||
|
if (node) {
|
||||||
|
// TODO ideally would let node define if it's deletable
|
||||||
|
// TODO also, would like to change this to getNodeInfo as OE is the primary use case now
|
||||||
|
let treeItem = await node.getTreeItem();
|
||||||
|
let oeNodeToRefresh: sqlops.objectexplorer.ObjectExplorerNode = undefined;
|
||||||
|
if (context.type === constants.ObjectExplorerService) {
|
||||||
|
let oeNodeToDelete = await sqlops.objectexplorer.getNode(context.explorerContext.connectionProfile.id, node.getNodeInfo().nodePath);
|
||||||
|
oeNodeToRefresh = await oeNodeToDelete.getParent();
|
||||||
|
}
|
||||||
|
switch (treeItem.contextValue) {
|
||||||
|
case constants.HdfsItems.Folder:
|
||||||
|
await this.deleteFolder(<FolderNode>node);
|
||||||
|
break;
|
||||||
|
case constants.HdfsItems.File:
|
||||||
|
await this.deleteFile(<FileNode>node);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (oeNodeToRefresh) {
|
||||||
|
await oeNodeToRefresh.refresh();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.apiWrapper.showErrorMessage(LocalizedConstants.msgMissingNodeContext);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
this.apiWrapper.showErrorMessage(localize('deleteError', 'Error deleting files {0}', err));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async confirmDelete(deleteMsg: string): Promise<boolean> {
|
||||||
|
return await this.prompter.promptSingle(<IQuestion>{
|
||||||
|
type: QuestionTypes.confirm,
|
||||||
|
message: deleteMsg,
|
||||||
|
default: false
|
||||||
|
}).then(confirmed => <boolean>confirmed);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async deleteFolder(node: FolderNode): Promise<void> {
|
||||||
|
if (node) {
|
||||||
|
let confirmed = await this.confirmDelete(localize('msgDeleteFolder', 'Are you sure you want to delete this folder and its contents?'));
|
||||||
|
if (confirmed) {
|
||||||
|
// TODO prompt for recursive delete if non-empty?
|
||||||
|
await node.delete(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async deleteFile(node: FileNode): Promise<void> {
|
||||||
|
if (node) {
|
||||||
|
let confirmed = await this.confirmDelete(localize('msgDeleteFile', 'Are you sure you want to delete this file?'));
|
||||||
|
if (confirmed) {
|
||||||
|
await node.delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SaveFileCommand extends ProgressCommand {
|
||||||
|
|
||||||
|
constructor(prompter: IPrompter, appContext: AppContext) {
|
||||||
|
super('hdfs.saveFile', prompter, appContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async preExecute(context: ICommandViewContext | ICommandObjectExplorerContext, args: object = {}): Promise<any> {
|
||||||
|
return this.execute(context, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
async execute(context: ICommandViewContext | ICommandObjectExplorerContext, ...args: any[]): Promise<void> {
|
||||||
|
try {
|
||||||
|
let fileNode = await getNode<FileNode>(context, this.appContext);
|
||||||
|
if (fileNode) {
|
||||||
|
let defaultUri = getSaveableUri(this.apiWrapper, fspath.basename(fileNode.hdfsPath));
|
||||||
|
let fileUri: vscode.Uri = await this.apiWrapper.showSaveDialog({
|
||||||
|
defaultUri: defaultUri
|
||||||
|
});
|
||||||
|
if (fileUri) {
|
||||||
|
await this.executeWithProgress(
|
||||||
|
async (cancelToken: vscode.CancellationTokenSource) => this.doSaveAndOpen(fileUri, fileNode, cancelToken),
|
||||||
|
localize('saving', 'Saving HDFS Files'), true,
|
||||||
|
() => this.apiWrapper.showInformationMessage(localize('saveCanceled', 'Save operation was canceled')));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.apiWrapper.showErrorMessage(LocalizedConstants.msgMissingNodeContext);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
this.apiWrapper.showErrorMessage(localize('saveError', 'Error saving file: {0}', err));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async doSaveAndOpen(fileUri: vscode.Uri, fileNode: FileNode, cancelToken: vscode.CancellationTokenSource): Promise<void> {
|
||||||
|
await fileNode.writeFileContentsToDisk(fileUri.fsPath, cancelToken);
|
||||||
|
await this.apiWrapper.executeCommand('vscode.open', fileUri);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class PreviewFileCommand extends ProgressCommand {
|
||||||
|
public static readonly DefaultMaxSize = 30 * 1024 * 1024;
|
||||||
|
|
||||||
|
constructor(prompter: IPrompter, appContext: AppContext) {
|
||||||
|
super('hdfs.previewFile', prompter, appContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async preExecute(context: ICommandViewContext | ICommandObjectExplorerContext, args: object = {}): Promise<any> {
|
||||||
|
return this.execute(context, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
async execute(context: ICommandViewContext | ICommandObjectExplorerContext, ...args: any[]): Promise<void> {
|
||||||
|
try {
|
||||||
|
let fileNode = await getNode<FileNode>(context, this.appContext);
|
||||||
|
if (fileNode) {
|
||||||
|
await this.executeWithProgress(
|
||||||
|
async (cancelToken: vscode.CancellationTokenSource) => {
|
||||||
|
let contents = await fileNode.getFileContentsAsString(PreviewFileCommand.DefaultMaxSize);
|
||||||
|
let doc = await this.openTextDocument(fspath.basename(fileNode.hdfsPath));
|
||||||
|
let editor = await this.apiWrapper.showTextDocument(doc, vscode.ViewColumn.Active, false);
|
||||||
|
await editor.edit(edit => {
|
||||||
|
edit.insert(new vscode.Position(0, 0), contents);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
localize('previewing', 'Generating preview'),
|
||||||
|
false);
|
||||||
|
} else {
|
||||||
|
this.apiWrapper.showErrorMessage(LocalizedConstants.msgMissingNodeContext);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
this.apiWrapper.showErrorMessage(localize('previewError', 'Error previewing file: {0}', err));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async openTextDocument(fileName: string): Promise<vscode.TextDocument> {
|
||||||
|
let docUri: vscode.Uri = getSaveableUri(this.apiWrapper, fileName, true);
|
||||||
|
if (docUri) {
|
||||||
|
docUri = docUri.with({ scheme: constants.UNTITLED_SCHEMA });
|
||||||
|
return await this.apiWrapper.openTextDocument(docUri);
|
||||||
|
} else {
|
||||||
|
// Can't reliably create a filename to save as so just use untitled
|
||||||
|
let language = fspath.extname(fileName);
|
||||||
|
if (language && language.length > 0) {
|
||||||
|
// trim the '.'
|
||||||
|
language = language.substring(1);
|
||||||
|
}
|
||||||
|
return await this.apiWrapper.openTextDocument({
|
||||||
|
language: language
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class CopyPathCommand extends Command {
|
||||||
|
public static readonly DefaultMaxSize = 30 * 1024 * 1024;
|
||||||
|
|
||||||
|
constructor(appContext: AppContext) {
|
||||||
|
super('hdfs.copyPath', appContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async preExecute(context: ICommandViewContext | ICommandObjectExplorerContext, args: object = {}): Promise<any> {
|
||||||
|
return this.execute(context, args);
|
||||||
|
}
|
||||||
|
|
||||||
|
async execute(context: ICommandViewContext | ICommandObjectExplorerContext, ...args: any[]): Promise<void> {
|
||||||
|
try {
|
||||||
|
let node = await getNode<HdfsFileSourceNode>(context, this.appContext);
|
||||||
|
if (node) {
|
||||||
|
let path = node.hdfsPath;
|
||||||
|
clipboardy.writeSync(path);
|
||||||
|
} else {
|
||||||
|
this.apiWrapper.showErrorMessage(LocalizedConstants.msgMissingNodeContext);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
this.apiWrapper.showErrorMessage(localize('copyPathError', 'Error copying path: {0}', err));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The connect task is only expected to work in the file-tree based APIs, not Object Explorer
|
||||||
|
*/
|
||||||
|
export class ConnectTask {
|
||||||
|
constructor(private hdfsProvider: HdfsProvider, private prompter: IPrompter, private apiWrapper: ApiWrapper) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
async execute(profile: sqlops.IConnectionProfile, ...args: any[]): Promise<void> {
|
||||||
|
if (profile) {
|
||||||
|
return this.createFromProfile(profile);
|
||||||
|
}
|
||||||
|
return this.createHdfsConnection();
|
||||||
|
}
|
||||||
|
|
||||||
|
private createFromProfile(profile: sqlops.IConnectionProfile): Promise<void> {
|
||||||
|
let connection = new Connection(profile);
|
||||||
|
if (profile.providerName === constants.mssqlClusterProviderName && connection.host) {
|
||||||
|
// TODO need to get the actual port and auth to be used since this will be non-default
|
||||||
|
// in future versions
|
||||||
|
this.hdfsProvider.addHdfsConnection(<IHdfsOptions> {
|
||||||
|
protocol: 'https',
|
||||||
|
host: connection.host,
|
||||||
|
port: connection.knoxport,
|
||||||
|
user: connection.user,
|
||||||
|
path: 'gateway/default/webhdfs/v1',
|
||||||
|
requestParams: {
|
||||||
|
auth: {
|
||||||
|
user: connection.user,
|
||||||
|
pass: connection.password
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return Promise.resolve(undefined);
|
||||||
|
}
|
||||||
|
|
||||||
|
private addConnection(options: IHdfsOptions): void {
|
||||||
|
let display: string = `${options.user}@${options.host}:${options.port}`;
|
||||||
|
this.hdfsProvider.addConnection(display, FileSourceFactory.instance.createHdfsFileSource(options));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async createHdfsConnection(profile?: sqlops.IConnectionProfile): Promise<void> {
|
||||||
|
let questions: IQuestion[] = [
|
||||||
|
{
|
||||||
|
type: QuestionTypes.input,
|
||||||
|
name: constants.hdfsHost,
|
||||||
|
message: localize('msgSetWebHdfsHost', 'HDFS URL and port'),
|
||||||
|
default: 'localhost:50070'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: QuestionTypes.input,
|
||||||
|
name: constants.hdfsUser,
|
||||||
|
message: localize('msgSetWebHdfsUser', 'User Name'),
|
||||||
|
default: 'root'
|
||||||
|
}];
|
||||||
|
|
||||||
|
let answers = await this.prompter.prompt(questions);
|
||||||
|
if (answers) {
|
||||||
|
let hostAndPort: string = answers[constants.hdfsHost];
|
||||||
|
let parts = hostAndPort.split(':');
|
||||||
|
let host: string = parts[0];
|
||||||
|
let port: string = parts.length > 1 ? parts[1] : undefined;
|
||||||
|
let user: string = answers[constants.hdfsUser];
|
||||||
|
|
||||||
|
|
||||||
|
let options: IHdfsOptions = {
|
||||||
|
host: host,
|
||||||
|
port: port,
|
||||||
|
user: user
|
||||||
|
};
|
||||||
|
this.addConnection(options);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
366
extensions/mssql/src/objectExplorerNodeProvider/hdfsProvider.ts
Normal file
366
extensions/mssql/src/objectExplorerNodeProvider/hdfsProvider.ts
Normal file
@@ -0,0 +1,366 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* 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 fspath from 'path';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
import * as nls from 'vscode-nls';
|
||||||
|
const localize = nls.loadMessageBundle();
|
||||||
|
|
||||||
|
import { ApiWrapper } from '../apiWrapper';
|
||||||
|
import * as Constants from '../constants';
|
||||||
|
import { IFileSource, IHdfsOptions, HdfsFileSource, IFile, File, FileSourceFactory } from './fileSources';
|
||||||
|
import { CancelableStream } from './cancelableStream';
|
||||||
|
import { TreeNode } from './treeNodes';
|
||||||
|
import * as utils from '../utils';
|
||||||
|
import { IFileNode } from './types';
|
||||||
|
|
||||||
|
export interface ITreeChangeHandler {
|
||||||
|
notifyNodeChanged(node: TreeNode): void;
|
||||||
|
}
|
||||||
|
export class TreeDataContext {
|
||||||
|
|
||||||
|
constructor(public extensionContext: vscode.ExtensionContext, public changeHandler: ITreeChangeHandler) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class HdfsProvider implements vscode.TreeDataProvider<TreeNode>, ITreeChangeHandler {
|
||||||
|
static readonly NoConnectionsMessage = 'No connections added';
|
||||||
|
static readonly ConnectionsLabel = 'Connections';
|
||||||
|
|
||||||
|
private connections: ConnectionNode[];
|
||||||
|
private _onDidChangeTreeData = new vscode.EventEmitter<TreeNode>();
|
||||||
|
private context: TreeDataContext;
|
||||||
|
|
||||||
|
constructor(extensionContext: vscode.ExtensionContext, private vscodeApi: ApiWrapper) {
|
||||||
|
this.connections = [];
|
||||||
|
this.context = new TreeDataContext(extensionContext, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public get onDidChangeTreeData(): vscode.Event<TreeNode> {
|
||||||
|
return this._onDidChangeTreeData.event;
|
||||||
|
}
|
||||||
|
|
||||||
|
getTreeItem(element: TreeNode): vscode.TreeItem | Thenable<vscode.TreeItem> {
|
||||||
|
return element.getTreeItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
getChildren(element?: TreeNode): vscode.ProviderResult<TreeNode[]> {
|
||||||
|
if (element) {
|
||||||
|
return element.getChildren(false);
|
||||||
|
} else {
|
||||||
|
return this.connections.length > 0 ? this.connections : [MessageNode.create(HdfsProvider.NoConnectionsMessage, element)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addConnection(displayName: string, fileSource: IFileSource): void {
|
||||||
|
if (!this.connections.find(c => c.getDisplayName() === displayName)) {
|
||||||
|
this.connections.push(new ConnectionNode(this.context, displayName, fileSource));
|
||||||
|
this._onDidChangeTreeData.fire();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addHdfsConnection(options: IHdfsOptions): void {
|
||||||
|
let displayName = `${options.user}@${options.host}:${options.port}`;
|
||||||
|
let fileSource = FileSourceFactory.instance.createHdfsFileSource(options);
|
||||||
|
this.addConnection(displayName, fileSource);
|
||||||
|
}
|
||||||
|
|
||||||
|
notifyNodeChanged(node: TreeNode): void {
|
||||||
|
this._onDidChangeTreeData.fire(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export abstract class HdfsFileSourceNode extends TreeNode {
|
||||||
|
constructor(protected context: TreeDataContext, protected _path: string, protected fileSource: IFileSource) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public get hdfsPath(): string {
|
||||||
|
return this._path;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get nodePathValue(): string {
|
||||||
|
return this.getDisplayName();
|
||||||
|
}
|
||||||
|
|
||||||
|
getDisplayName(): string {
|
||||||
|
return fspath.basename(this._path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async delete(recursive: boolean = false): Promise<void> {
|
||||||
|
await this.fileSource.delete(this.hdfsPath, recursive);
|
||||||
|
// Notify parent should be updated. If at top, will return undefined which will refresh whole tree
|
||||||
|
(<HdfsFileSourceNode>this.parent).onChildRemoved();
|
||||||
|
this.context.changeHandler.notifyNodeChanged(this.parent);
|
||||||
|
}
|
||||||
|
public abstract onChildRemoved(): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class FolderNode extends HdfsFileSourceNode {
|
||||||
|
private children: TreeNode[];
|
||||||
|
protected _nodeType: string;
|
||||||
|
constructor(context: TreeDataContext, path: string, fileSource: IFileSource, nodeType?: string) {
|
||||||
|
super(context, path, fileSource);
|
||||||
|
this._nodeType = nodeType ? nodeType : Constants.HdfsItems.Folder;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ensureChildrenExist(): void {
|
||||||
|
if (!this.children) {
|
||||||
|
this.children = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public onChildRemoved(): void {
|
||||||
|
this.children = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getChildren(refreshChildren: boolean): Promise<TreeNode[]> {
|
||||||
|
if (refreshChildren || !this.children) {
|
||||||
|
this.ensureChildrenExist();
|
||||||
|
try {
|
||||||
|
let files: IFile[] = await this.fileSource.enumerateFiles(this._path);
|
||||||
|
if (files) {
|
||||||
|
// Note: for now, assuming HDFS-provided sorting is sufficient
|
||||||
|
this.children = files.map((file) => {
|
||||||
|
let node: TreeNode = file.isDirectory ? new FolderNode(this.context, file.path, this.fileSource)
|
||||||
|
: new FileNode(this.context, file.path, this.fileSource);
|
||||||
|
node.parent = this;
|
||||||
|
return node;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.children = [MessageNode.create(localize('errorExpanding', 'Error: {0}', utils.getErrorMessage(error)), this)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this.children;
|
||||||
|
}
|
||||||
|
|
||||||
|
getTreeItem(): vscode.TreeItem | Promise<vscode.TreeItem> {
|
||||||
|
let item = new vscode.TreeItem(this.getDisplayName(), vscode.TreeItemCollapsibleState.Collapsed);
|
||||||
|
// For now, folder always looks the same. We're using SQL icons to differentiate remote vs local files
|
||||||
|
item.iconPath = {
|
||||||
|
dark: this.context.extensionContext.asAbsolutePath('resources/light/Folder.svg'),
|
||||||
|
light: this.context.extensionContext.asAbsolutePath('resources/light/Folder.svg')
|
||||||
|
};
|
||||||
|
item.contextValue = this._nodeType;
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
getNodeInfo(): sqlops.NodeInfo {
|
||||||
|
// TODO handle error message case by returning it in the OE API
|
||||||
|
// TODO support better mapping of node type
|
||||||
|
let nodeInfo: sqlops.NodeInfo = {
|
||||||
|
label: this.getDisplayName(),
|
||||||
|
isLeaf: false,
|
||||||
|
errorMessage: undefined,
|
||||||
|
metadata: undefined,
|
||||||
|
nodePath: this.generateNodePath(),
|
||||||
|
nodeStatus: undefined,
|
||||||
|
nodeType: this._nodeType,
|
||||||
|
nodeSubType: undefined,
|
||||||
|
iconType: 'Folder'
|
||||||
|
};
|
||||||
|
return nodeInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async writeFile(localFile: IFile): Promise<FileNode> {
|
||||||
|
return this.runChildAddAction<FileNode>(() => this.writeFileAsync(localFile));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async writeFileAsync(localFile: IFile): Promise<FileNode> {
|
||||||
|
await this.fileSource.writeFile(localFile, this._path);
|
||||||
|
let fileNode = new FileNode(this.context, File.createPath(this._path, File.getBasename(localFile)), this.fileSource);
|
||||||
|
return fileNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async mkdir(name: string): Promise<FolderNode> {
|
||||||
|
return this.runChildAddAction<FolderNode>(() => this.mkdirAsync(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
private async mkdirAsync(name: string): Promise<FolderNode> {
|
||||||
|
await this.fileSource.mkdir(name, this._path);
|
||||||
|
let subDir = new FolderNode(this.context, File.createPath(this._path, name), this.fileSource);
|
||||||
|
return subDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async runChildAddAction<T extends TreeNode>(action: () => Promise<T>): Promise<T> {
|
||||||
|
let node = await action();
|
||||||
|
await this.getChildren(true);
|
||||||
|
if (this.children) {
|
||||||
|
// Find the child matching the node. This is necessary
|
||||||
|
// since writing can add duplicates.
|
||||||
|
node = this.children.find(n => n.nodePathValue === node.nodePathValue) as T;
|
||||||
|
this.context.changeHandler.notifyNodeChanged(this);
|
||||||
|
} else {
|
||||||
|
// Failed to retrieve children from server so something went wrong
|
||||||
|
node = undefined;
|
||||||
|
}
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ConnectionNode extends FolderNode {
|
||||||
|
|
||||||
|
constructor(context: TreeDataContext, private displayName: string, fileSource: IFileSource) {
|
||||||
|
super(context, '/', fileSource, Constants.HdfsItems.Connection);
|
||||||
|
}
|
||||||
|
|
||||||
|
getDisplayName(): string {
|
||||||
|
return this.displayName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async delete(): Promise<void> {
|
||||||
|
throw new Error(localize('errDeleteConnectionNode', 'Cannot delete a connection. Only subfolders and files can be deleted.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
async getTreeItem(): Promise<vscode.TreeItem> {
|
||||||
|
let item = await super.getTreeItem();
|
||||||
|
item.contextValue = this._nodeType;
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class FileNode extends HdfsFileSourceNode implements IFileNode {
|
||||||
|
|
||||||
|
constructor(context: TreeDataContext, path: string, fileSource: IFileSource) {
|
||||||
|
super(context, path, fileSource);
|
||||||
|
}
|
||||||
|
|
||||||
|
public onChildRemoved(): void {
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
getChildren(refreshChildren: boolean): TreeNode[] | Promise<TreeNode[]> {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
getTreeItem(): vscode.TreeItem | Promise<vscode.TreeItem> {
|
||||||
|
let item = new vscode.TreeItem(this.getDisplayName(), vscode.TreeItemCollapsibleState.None);
|
||||||
|
item.iconPath = {
|
||||||
|
dark: this.context.extensionContext.asAbsolutePath('resources/dark/file_inverse.svg'),
|
||||||
|
light: this.context.extensionContext.asAbsolutePath('resources/light/file.svg')
|
||||||
|
};
|
||||||
|
item.contextValue = Constants.HdfsItems.File;
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
getNodeInfo(): sqlops.NodeInfo {
|
||||||
|
// TODO improve node type handling so it's not tied to SQL Server types
|
||||||
|
let nodeInfo: sqlops.NodeInfo = {
|
||||||
|
label: this.getDisplayName(),
|
||||||
|
isLeaf: true,
|
||||||
|
errorMessage: undefined,
|
||||||
|
metadata: undefined,
|
||||||
|
nodePath: this.generateNodePath(),
|
||||||
|
nodeStatus: undefined,
|
||||||
|
nodeType: Constants.HdfsItems.File,
|
||||||
|
nodeSubType: this.getSubType(),
|
||||||
|
iconType: 'FileGroupFile'
|
||||||
|
};
|
||||||
|
return nodeInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getFileContentsAsString(maxBytes?: number): Promise<string> {
|
||||||
|
let contents: Buffer = await this.fileSource.readFile(this.hdfsPath, maxBytes);
|
||||||
|
return contents ? contents.toString('utf8') : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getFileLinesAsString(maxLines: number): Promise<string> {
|
||||||
|
let contents: Buffer = await this.fileSource.readFileLines(this.hdfsPath, maxLines);
|
||||||
|
return contents ? contents.toString('utf8') : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
public writeFileContentsToDisk(localPath: string, cancelToken?: vscode.CancellationTokenSource): Promise<vscode.Uri> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
let readStream: fs.ReadStream = this.fileSource.createReadStream(this.hdfsPath);
|
||||||
|
let writeStream = fs.createWriteStream(localPath, {
|
||||||
|
encoding: 'utf8'
|
||||||
|
});
|
||||||
|
let cancelable = new CancelableStream(cancelToken);
|
||||||
|
cancelable.on('error', (err) => {
|
||||||
|
reject(err);
|
||||||
|
});
|
||||||
|
readStream.pipe(cancelable).pipe(writeStream);
|
||||||
|
|
||||||
|
let error: string | Error = undefined;
|
||||||
|
|
||||||
|
writeStream.on('error', (err) => {
|
||||||
|
error = err;
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
writeStream.on('finish', (location) => {
|
||||||
|
if (!error) {
|
||||||
|
resolve(vscode.Uri.file(localPath));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private getSubType(): string {
|
||||||
|
if (this.getDisplayName().toLowerCase().endsWith('.jar') || this.getDisplayName().toLowerCase().endsWith('.py')) {
|
||||||
|
return Constants.HdfsItemsSubType.Spark;
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MessageNode extends TreeNode {
|
||||||
|
static messageNum: number = 0;
|
||||||
|
|
||||||
|
private _nodePathValue: string;
|
||||||
|
constructor(private message: string) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static create(message: string, parent: TreeNode): MessageNode {
|
||||||
|
let node = new MessageNode(message);
|
||||||
|
node.parent = parent;
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ensureNodePathValue(): void {
|
||||||
|
if (!this._nodePathValue) {
|
||||||
|
this._nodePathValue = `message_${MessageNode.messageNum++}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public get nodePathValue(): string {
|
||||||
|
this.ensureNodePathValue();
|
||||||
|
return this._nodePathValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getChildren(refreshChildren: boolean): TreeNode[] | Promise<TreeNode[]> {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public getTreeItem(): vscode.TreeItem | Promise<vscode.TreeItem> {
|
||||||
|
let item = new vscode.TreeItem(this.message, vscode.TreeItemCollapsibleState.None);
|
||||||
|
item.contextValue = Constants.HdfsItems.Message;
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
getNodeInfo(): sqlops.NodeInfo {
|
||||||
|
let nodeInfo: sqlops.NodeInfo = {
|
||||||
|
label: this.message,
|
||||||
|
isLeaf: false,
|
||||||
|
errorMessage: undefined,
|
||||||
|
metadata: undefined,
|
||||||
|
nodePath: this.generateNodePath(),
|
||||||
|
nodeStatus: undefined,
|
||||||
|
nodeType: Constants.HdfsItems.Message,
|
||||||
|
nodeSubType: undefined,
|
||||||
|
iconType: 'MessageType'
|
||||||
|
};
|
||||||
|
return nodeInfo;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,347 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* 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';
|
||||||
|
const localize = nls.loadMessageBundle();
|
||||||
|
|
||||||
|
import * as UUID from 'vscode-languageclient/lib/utils/uuid';
|
||||||
|
import { ProviderBase } from './providerBase';
|
||||||
|
import { Connection } from './connection';
|
||||||
|
import * as utils from '../utils';
|
||||||
|
import { TreeNode } from './treeNodes';
|
||||||
|
import { ConnectionNode, TreeDataContext, ITreeChangeHandler } from './hdfsProvider';
|
||||||
|
import { IFileSource } from './fileSources';
|
||||||
|
import { AppContext } from '../appContext';
|
||||||
|
import * as constants from '../constants';
|
||||||
|
|
||||||
|
const outputChannel = vscode.window.createOutputChannel(constants.providerId);
|
||||||
|
interface IEndpoint {
|
||||||
|
serviceName: string;
|
||||||
|
ipAddress: string;
|
||||||
|
port: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MssqlObjectExplorerNodeProvider extends ProviderBase implements sqlops.ObjectExplorerNodeProvider, ITreeChangeHandler {
|
||||||
|
public readonly supportedProviderId: string = constants.providerId;
|
||||||
|
private sessionMap: Map<string, Session>;
|
||||||
|
private expandCompleteEmitter = new vscode.EventEmitter<sqlops.ObjectExplorerExpandInfo>();
|
||||||
|
|
||||||
|
constructor(private appContext: AppContext) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.sessionMap = new Map();
|
||||||
|
this.appContext.registerService<MssqlObjectExplorerNodeProvider>(constants.ObjectExplorerService, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSessionOpen(session: sqlops.ObjectExplorerSession): Thenable<boolean> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (!session) {
|
||||||
|
reject('handleSessionOpen requires a session object to be passed');
|
||||||
|
} else {
|
||||||
|
resolve(this.doSessionOpen(session));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async doSessionOpen(sessionInfo: sqlops.ObjectExplorerSession): Promise<boolean> {
|
||||||
|
let connectionProfile = await sqlops.objectexplorer.getSessionConnectionProfile(sessionInfo.sessionId);
|
||||||
|
if (!connectionProfile) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
let credentials = await sqlops.connection.getCredentials(connectionProfile.id);
|
||||||
|
let serverInfo = await sqlops.connection.getServerInfo(connectionProfile.id);
|
||||||
|
if (!serverInfo || !credentials || !serverInfo.options) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let endpoints: IEndpoint[] = serverInfo.options[constants.clusterEndpointsProperty];
|
||||||
|
if (!endpoints || endpoints.length === 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let index = endpoints.findIndex(ep => ep.serviceName === constants.hadoopKnoxEndpointName);
|
||||||
|
if (index === -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let connInfo: sqlops.connection.Connection = {
|
||||||
|
options: {
|
||||||
|
'host': endpoints[index].ipAddress,
|
||||||
|
'groupId': connectionProfile.options.groupId,
|
||||||
|
'knoxport': endpoints[index].port,
|
||||||
|
'user': 'root', //connectionProfile.options.userName cluster setup has to have the same user for master and big data cluster
|
||||||
|
'password': credentials.password,
|
||||||
|
},
|
||||||
|
providerName: constants.mssqlClusterProviderName,
|
||||||
|
connectionId: UUID.generateUuid()
|
||||||
|
};
|
||||||
|
|
||||||
|
let connection = new Connection(connInfo);
|
||||||
|
connection.saveUriWithPrefix(constants.objectExplorerPrefix);
|
||||||
|
let session = new Session(connection, sessionInfo.sessionId);
|
||||||
|
session.root = new RootNode(session, new TreeDataContext(this.appContext.extensionContext, this), sessionInfo.rootNode.nodePath);
|
||||||
|
this.sessionMap.set(sessionInfo.sessionId, session);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
expandNode(nodeInfo: sqlops.ExpandNodeInfo, isRefresh: boolean = false): Thenable<boolean> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (!nodeInfo) {
|
||||||
|
reject('expandNode requires a nodeInfo object to be passed');
|
||||||
|
} else {
|
||||||
|
resolve(this.doExpandNode(nodeInfo, isRefresh));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async doExpandNode(nodeInfo: sqlops.ExpandNodeInfo, isRefresh: boolean = false): Promise<boolean> {
|
||||||
|
let session = this.sessionMap.get(nodeInfo.sessionId);
|
||||||
|
let response = {
|
||||||
|
sessionId: nodeInfo.sessionId,
|
||||||
|
nodePath: nodeInfo.nodePath,
|
||||||
|
errorMessage: undefined,
|
||||||
|
nodes: []
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!session) {
|
||||||
|
// This is not an error case. Just fire reponse with empty nodes for example: request from standalone SQL instance
|
||||||
|
this.expandCompleteEmitter.fire(response);
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
setTimeout(() => {
|
||||||
|
|
||||||
|
// Running after promise resolution as we need the Ops Studio-side map to have been updated
|
||||||
|
// Intentionally not awaiting or catching errors.
|
||||||
|
// Any failure in startExpansion should be emitted in the expand complete result
|
||||||
|
// We want this to be async and ideally return true before it completes
|
||||||
|
this.startExpansion(session, nodeInfo, isRefresh);
|
||||||
|
}, 10);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async startExpansion(session: Session, nodeInfo: sqlops.ExpandNodeInfo, isRefresh: boolean = false): Promise<void> {
|
||||||
|
let expandResult: sqlops.ObjectExplorerExpandInfo = {
|
||||||
|
sessionId: session.uri,
|
||||||
|
nodePath: nodeInfo.nodePath,
|
||||||
|
errorMessage: undefined,
|
||||||
|
nodes: []
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
let node = await session.root.findNodeByPath(nodeInfo.nodePath, true);
|
||||||
|
if (node) {
|
||||||
|
expandResult.errorMessage = node.getNodeInfo().errorMessage;
|
||||||
|
let children = await node.getChildren(true);
|
||||||
|
if (children) {
|
||||||
|
expandResult.nodes = children.map(c => c.getNodeInfo());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
expandResult.errorMessage = utils.getErrorMessage(error);
|
||||||
|
}
|
||||||
|
this.expandCompleteEmitter.fire(expandResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
refreshNode(nodeInfo: sqlops.ExpandNodeInfo): Thenable<boolean> {
|
||||||
|
// TODO #3815 implement properly
|
||||||
|
return this.expandNode(nodeInfo, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleSessionClose(closeSessionInfo: sqlops.ObjectExplorerCloseSessionInfo): void {
|
||||||
|
this.sessionMap.delete(closeSessionInfo.sessionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
findNodes(findNodesInfo: sqlops.FindNodesInfo): Thenable<sqlops.ObjectExplorerFindNodesResponse> {
|
||||||
|
// TODO #3814 implement
|
||||||
|
let response: sqlops.ObjectExplorerFindNodesResponse = {
|
||||||
|
nodes: []
|
||||||
|
};
|
||||||
|
return Promise.resolve(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
registerOnExpandCompleted(handler: (response: sqlops.ObjectExplorerExpandInfo) => any): void {
|
||||||
|
this.expandCompleteEmitter.event(handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
notifyNodeChanged(node: TreeNode): void {
|
||||||
|
this.notifyNodeChangesAsync(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async notifyNodeChangesAsync(node: TreeNode): Promise<void> {
|
||||||
|
try {
|
||||||
|
let session = this.getSessionForNode(node);
|
||||||
|
if (!session) {
|
||||||
|
this.appContext.apiWrapper.showErrorMessage(localize('sessionNotFound', 'Session for node {0} does not exist', node.nodePathValue));
|
||||||
|
} else {
|
||||||
|
let nodeInfo = node.getNodeInfo();
|
||||||
|
let expandInfo: sqlops.ExpandNodeInfo = {
|
||||||
|
nodePath: nodeInfo.nodePath,
|
||||||
|
sessionId: session.uri
|
||||||
|
};
|
||||||
|
await this.refreshNode(expandInfo);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
outputChannel.appendLine(localize('notifyError', 'Error notifying of node change: {0}', err));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private getSessionForNode(node: TreeNode): Session {
|
||||||
|
let rootNode: DataServicesNode = undefined;
|
||||||
|
while (rootNode === undefined && node !== undefined) {
|
||||||
|
if (node instanceof DataServicesNode) {
|
||||||
|
rootNode = node;
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
node = node.parent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (rootNode) {
|
||||||
|
return rootNode.session;
|
||||||
|
}
|
||||||
|
// Not found
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
async findNodeForContext<T extends TreeNode>(explorerContext: sqlops.ObjectExplorerContext): Promise<T> {
|
||||||
|
let node: T = undefined;
|
||||||
|
let session = this.findSessionForConnection(explorerContext.connectionProfile);
|
||||||
|
if (session) {
|
||||||
|
if (explorerContext.isConnectionNode) {
|
||||||
|
// Note: ideally fix so we verify T matches RootNode and go from there
|
||||||
|
node = <T><any>session.root;
|
||||||
|
} else {
|
||||||
|
// Find the node under the session
|
||||||
|
node = <T><any>await session.root.findNodeByPath(explorerContext.nodeInfo.nodePath, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
private findSessionForConnection(connectionProfile: sqlops.IConnectionProfile): Session {
|
||||||
|
for (let session of this.sessionMap.values()) {
|
||||||
|
if (session.connection && session.connection.isMatch(connectionProfile)) {
|
||||||
|
return session;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Session {
|
||||||
|
private _root: RootNode;
|
||||||
|
constructor(private _connection: Connection, private sessionId?: string) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public get uri(): string {
|
||||||
|
return this.sessionId || this._connection.uri;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get connection(): Connection {
|
||||||
|
return this._connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public set root(node: RootNode) {
|
||||||
|
this._root = node;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get root(): RootNode {
|
||||||
|
return this._root;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class RootNode extends TreeNode {
|
||||||
|
private children: TreeNode[];
|
||||||
|
constructor(private _session: Session, private context: TreeDataContext, private nodePath: string) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public get session(): Session {
|
||||||
|
return this._session;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get nodePathValue(): string {
|
||||||
|
return this.nodePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getChildren(refreshChildren: boolean): TreeNode[] | Promise<TreeNode[]> {
|
||||||
|
if (refreshChildren || !this.children) {
|
||||||
|
this.children = [];
|
||||||
|
let dataServicesNode = new DataServicesNode(this._session, this.context, this.nodePath);
|
||||||
|
dataServicesNode.parent = this;
|
||||||
|
this.children.push(dataServicesNode);
|
||||||
|
}
|
||||||
|
return this.children;
|
||||||
|
}
|
||||||
|
|
||||||
|
getTreeItem(): vscode.TreeItem | Promise<vscode.TreeItem> {
|
||||||
|
throw new Error('Not intended for use in a file explorer view.');
|
||||||
|
}
|
||||||
|
|
||||||
|
getNodeInfo(): sqlops.NodeInfo {
|
||||||
|
let nodeInfo: sqlops.NodeInfo = {
|
||||||
|
label: localize('rootLabel', 'Root'),
|
||||||
|
isLeaf: false,
|
||||||
|
errorMessage: undefined,
|
||||||
|
metadata: undefined,
|
||||||
|
nodePath: this.generateNodePath(),
|
||||||
|
nodeStatus: undefined,
|
||||||
|
nodeType: 'sqlCluster:root',
|
||||||
|
nodeSubType: undefined,
|
||||||
|
iconType: 'folder'
|
||||||
|
};
|
||||||
|
return nodeInfo;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class DataServicesNode extends TreeNode {
|
||||||
|
private children: TreeNode[];
|
||||||
|
constructor(private _session: Session, private context: TreeDataContext, private nodePath: string) {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public get session(): Session {
|
||||||
|
return this._session;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get nodePathValue(): string {
|
||||||
|
return this.nodePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getChildren(refreshChildren: boolean): TreeNode[] | Promise<TreeNode[]> {
|
||||||
|
if (refreshChildren || !this.children) {
|
||||||
|
this.children = [];
|
||||||
|
let hdfsNode = new ConnectionNode(this.context, localize('hdfsFolder', 'HDFS'), this.createHdfsFileSource());
|
||||||
|
hdfsNode.parent = this;
|
||||||
|
this.children.push(hdfsNode);
|
||||||
|
}
|
||||||
|
return this.children;
|
||||||
|
}
|
||||||
|
|
||||||
|
private createHdfsFileSource(): IFileSource {
|
||||||
|
return this.session.connection.createHdfsFileSource();
|
||||||
|
}
|
||||||
|
|
||||||
|
getTreeItem(): vscode.TreeItem | Promise<vscode.TreeItem> {
|
||||||
|
throw new Error('Not intended for use in a file explorer view.');
|
||||||
|
}
|
||||||
|
|
||||||
|
getNodeInfo(): sqlops.NodeInfo {
|
||||||
|
let nodeInfo: sqlops.NodeInfo = {
|
||||||
|
label: localize('dataServicesLabel', 'Data Services'),
|
||||||
|
isLeaf: false,
|
||||||
|
errorMessage: undefined,
|
||||||
|
metadata: undefined,
|
||||||
|
nodePath: this.generateNodePath(),
|
||||||
|
nodeStatus: undefined,
|
||||||
|
nodeType: 'dataservices',
|
||||||
|
nodeSubType: undefined,
|
||||||
|
iconType: 'folder'
|
||||||
|
};
|
||||||
|
return nodeInfo;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
|
||||||
|
|
||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* 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';
|
||||||
|
|
||||||
|
export abstract class ProviderBase {
|
||||||
|
public readonly providerId: string = constants.mssqlClusterProviderName;
|
||||||
|
public handle: number;
|
||||||
|
}
|
||||||
78
extensions/mssql/src/objectExplorerNodeProvider/treeNodes.ts
Normal file
78
extensions/mssql/src/objectExplorerNodeProvider/treeNodes.ts
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
/*---------------------------------------------------------------------------------------------
|
||||||
|
* 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 { ITreeNode } from './types';
|
||||||
|
|
||||||
|
type TreeNodePredicate = (node: TreeNode) => boolean;
|
||||||
|
|
||||||
|
export abstract class TreeNode implements ITreeNode {
|
||||||
|
private _parent: TreeNode = undefined;
|
||||||
|
|
||||||
|
public get parent(): TreeNode {
|
||||||
|
return this._parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public set parent(node: TreeNode) {
|
||||||
|
this._parent = node;
|
||||||
|
}
|
||||||
|
|
||||||
|
public generateNodePath(): string {
|
||||||
|
let path = undefined;
|
||||||
|
if (this.parent) {
|
||||||
|
path = this.parent.generateNodePath();
|
||||||
|
}
|
||||||
|
path = path ? `${path}/${this.nodePathValue}` : this.nodePathValue;
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
public findNodeByPath(path: string, expandIfNeeded: boolean = false): Promise<TreeNode> {
|
||||||
|
let condition: TreeNodePredicate = (node: TreeNode) => node.getNodeInfo().nodePath === path || node.getNodeInfo().nodePath.startsWith(path);
|
||||||
|
let filter: TreeNodePredicate = (node: TreeNode) => path.startsWith(node.getNodeInfo().nodePath);
|
||||||
|
return TreeNode.findNode(this, condition, filter, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static async findNode(node: TreeNode, condition: TreeNodePredicate, filter: TreeNodePredicate, expandIfNeeded: boolean): Promise<TreeNode> {
|
||||||
|
if (!node) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (condition(node)) {
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
let nodeInfo = node.getNodeInfo();
|
||||||
|
if (nodeInfo.isLeaf) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO #3813 support filtering by already expanded / not yet expanded
|
||||||
|
let children = await node.getChildren(false);
|
||||||
|
if (children) {
|
||||||
|
for (let child of children) {
|
||||||
|
if (filter && filter(child)) {
|
||||||
|
let childNode = await this.findNode(child, condition, filter, expandIfNeeded);
|
||||||
|
if (childNode) {
|
||||||
|
return childNode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The value to use for this node in the node path
|
||||||
|
*/
|
||||||
|
public abstract get nodePathValue(): string;
|
||||||
|
|
||||||
|
abstract getChildren(refreshChildren: boolean): TreeNode[] | Promise<TreeNode[]>;
|
||||||
|
abstract getTreeItem(): vscode.TreeItem | Promise<vscode.TreeItem>;
|
||||||
|
|
||||||
|
abstract getNodeInfo(): sqlops.NodeInfo;
|
||||||
|
}
|
||||||
30
extensions/mssql/src/objectExplorerNodeProvider/types.d.ts
vendored
Normal file
30
extensions/mssql/src/objectExplorerNodeProvider/types.d.ts
vendored
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.
|
||||||
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
|
import * as vscode from 'vscode';
|
||||||
|
import * as sqlops from 'sqlops';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A tree node in the object explorer tree
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @interface ITreeNode
|
||||||
|
*/
|
||||||
|
export interface ITreeNode {
|
||||||
|
getNodeInfo(): sqlops.NodeInfo;
|
||||||
|
getChildren(refreshChildren: boolean): ITreeNode[] | Promise<ITreeNode[]>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A HDFS file node. This is a leaf node in the object explorer tree, and its contents
|
||||||
|
* can be queried
|
||||||
|
*
|
||||||
|
* @export
|
||||||
|
* @interface IFileNode
|
||||||
|
* @extends {ITreeNode}
|
||||||
|
*/
|
||||||
|
export interface IFileNode extends ITreeNode {
|
||||||
|
getFileContentsAsString(maxBytes?: number): Promise<string>;
|
||||||
|
}
|
||||||
68
extensions/mssql/src/prompts/question.ts
Normal file
68
extensions/mssql/src/prompts/question.ts
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import vscode = require('vscode');
|
||||||
|
|
||||||
|
export class QuestionTypes {
|
||||||
|
public static get input(): string { return 'input'; }
|
||||||
|
public static get password(): string { return 'password'; }
|
||||||
|
public static get list(): string { return 'list'; }
|
||||||
|
public static get confirm(): string { return 'confirm'; }
|
||||||
|
public static get checkbox(): string { return 'checkbox'; }
|
||||||
|
public static get expand(): string { return 'expand'; }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Question interface to clarify how to use the prompt feature
|
||||||
|
// based on Bower Question format: https://github.com/bower/bower/blob/89069784bb46bfd6639b4a75e98a0d7399a8c2cb/packages/bower-logger/README.md
|
||||||
|
export interface IQuestion {
|
||||||
|
// Type of question (see QuestionTypes)
|
||||||
|
type: string;
|
||||||
|
// Name of the question for disambiguation
|
||||||
|
name: string;
|
||||||
|
// Message to display to the user
|
||||||
|
message: string;
|
||||||
|
// Optional placeHolder to give more detailed information to the user
|
||||||
|
placeHolder?: any;
|
||||||
|
// Optional default value - this will be used instead of placeHolder
|
||||||
|
default?: any;
|
||||||
|
// optional set of choices to be used. Can be QuickPickItems or a simple name-value pair
|
||||||
|
choices?: Array<vscode.QuickPickItem | INameValueChoice>;
|
||||||
|
// Optional validation function that returns an error string if validation fails
|
||||||
|
validate?: (value: any) => string;
|
||||||
|
// Optional pre-prompt function. Takes in set of answers so far, and returns true if prompt should occur
|
||||||
|
shouldPrompt?: (answers: {[id: string]: any}) => boolean;
|
||||||
|
// Optional action to take on the question being answered
|
||||||
|
onAnswered?: (value: any) => void;
|
||||||
|
// Optional set of options to support matching choices.
|
||||||
|
matchOptions?: vscode.QuickPickOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pair used to display simple choices to the user
|
||||||
|
export interface INameValueChoice {
|
||||||
|
name: string;
|
||||||
|
value: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generic object that can be used to define a set of questions and handle the result
|
||||||
|
export interface IQuestionHandler {
|
||||||
|
// Set of questions to be answered
|
||||||
|
questions: IQuestion[];
|
||||||
|
// Optional callback, since questions may handle themselves
|
||||||
|
callback?: IPromptCallback;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IPrompter {
|
||||||
|
promptSingle<T>(question: IQuestion, ignoreFocusOut?: boolean): Promise<T>;
|
||||||
|
/**
|
||||||
|
* Prompts for multiple questions
|
||||||
|
*
|
||||||
|
* @returns {[questionId: string]: T} Map of question IDs to results, or undefined if
|
||||||
|
* the user canceled the question session
|
||||||
|
*/
|
||||||
|
prompt<T>(questions: IQuestion[], ignoreFocusOut?: boolean): Promise<{[questionId: string]: any}>;
|
||||||
|
promptCallback(questions: IQuestion[], callback: IPromptCallback): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IPromptCallback {
|
||||||
|
(answers: {[id: string]: any}): void;
|
||||||
|
}
|
||||||
1
extensions/mssql/src/typings/refs.d.ts
vendored
1
extensions/mssql/src/typings/refs.d.ts
vendored
@@ -4,4 +4,5 @@
|
|||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
/// <reference path='../../../../src/sql/sqlops.d.ts'/>
|
/// <reference path='../../../../src/sql/sqlops.d.ts'/>
|
||||||
|
/// <reference path='../../../../src/sql/sqlops.proposed.d.ts'/>
|
||||||
/// <reference path='../../../../src/vs/vscode.d.ts'/>
|
/// <reference path='../../../../src/vs/vscode.d.ts'/>
|
||||||
@@ -4,6 +4,8 @@
|
|||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
import * as sqlops from 'sqlops';
|
||||||
|
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import * as crypto from 'crypto';
|
import * as crypto from 'crypto';
|
||||||
import * as os from 'os';
|
import * as os from 'os';
|
||||||
@@ -169,3 +171,15 @@ export function verifyPlatform(): Thenable<boolean> {
|
|||||||
return Promise.resolve(true);
|
return Promise.resolve(true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getErrorMessage(error: Error | string): string {
|
||||||
|
return (error instanceof Error) ? error.message : error;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isObjectExplorerContext(object: any): object is sqlops.ObjectExplorerContext {
|
||||||
|
return 'connectionProfile' in object && 'isConnectionNode' in object;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getUserHome(): string {
|
||||||
|
return process.env.HOME || process.env.USERPROFILE;
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,6 +8,15 @@ agent-base@4, agent-base@^4.1.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
es6-promisify "^5.0.0"
|
es6-promisify "^5.0.0"
|
||||||
|
|
||||||
|
ajv@^6.5.5:
|
||||||
|
version "6.7.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.7.0.tgz#e3ce7bb372d6577bb1839f1dfdfcbf5ad2948d96"
|
||||||
|
dependencies:
|
||||||
|
fast-deep-equal "^2.0.1"
|
||||||
|
fast-json-stable-stringify "^2.0.0"
|
||||||
|
json-schema-traverse "^0.4.1"
|
||||||
|
uri-js "^4.2.2"
|
||||||
|
|
||||||
applicationinsights@1.0.1:
|
applicationinsights@1.0.1:
|
||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/applicationinsights/-/applicationinsights-1.0.1.tgz#53446b830fe8d5d619eee2a278b31d3d25030927"
|
resolved "https://registry.yarnpkg.com/applicationinsights/-/applicationinsights-1.0.1.tgz#53446b830fe8d5d619eee2a278b31d3d25030927"
|
||||||
@@ -16,10 +25,42 @@ applicationinsights@1.0.1:
|
|||||||
diagnostic-channel-publishers "0.2.1"
|
diagnostic-channel-publishers "0.2.1"
|
||||||
zone.js "0.7.6"
|
zone.js "0.7.6"
|
||||||
|
|
||||||
|
arch@^2.1.0:
|
||||||
|
version "2.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/arch/-/arch-2.1.1.tgz#8f5c2731aa35a30929221bb0640eed65175ec84e"
|
||||||
|
|
||||||
|
asn1@~0.2.3:
|
||||||
|
version "0.2.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136"
|
||||||
|
dependencies:
|
||||||
|
safer-buffer "~2.1.0"
|
||||||
|
|
||||||
|
assert-plus@1.0.0, assert-plus@^1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525"
|
||||||
|
|
||||||
|
asynckit@^0.4.0:
|
||||||
|
version "0.4.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
|
||||||
|
|
||||||
|
aws-sign2@~0.7.0:
|
||||||
|
version "0.7.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8"
|
||||||
|
|
||||||
|
aws4@^1.8.0:
|
||||||
|
version "1.8.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f"
|
||||||
|
|
||||||
base64-js@0.0.8:
|
base64-js@0.0.8:
|
||||||
version "0.0.8"
|
version "0.0.8"
|
||||||
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-0.0.8.tgz#1101e9544f4a76b1bc3b26d452ca96d7a35e7978"
|
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-0.0.8.tgz#1101e9544f4a76b1bc3b26d452ca96d7a35e7978"
|
||||||
|
|
||||||
|
bcrypt-pbkdf@^1.0.0:
|
||||||
|
version "1.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e"
|
||||||
|
dependencies:
|
||||||
|
tweetnacl "^0.14.3"
|
||||||
|
|
||||||
bl@^1.0.0:
|
bl@^1.0.0:
|
||||||
version "1.2.2"
|
version "1.2.2"
|
||||||
resolved "https://registry.yarnpkg.com/bl/-/bl-1.2.2.tgz#a160911717103c07410cef63ef51b397c025af9c"
|
resolved "https://registry.yarnpkg.com/bl/-/bl-1.2.2.tgz#a160911717103c07410cef63ef51b397c025af9c"
|
||||||
@@ -46,6 +87,10 @@ buffer-fill@^1.0.0:
|
|||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/buffer-fill/-/buffer-fill-1.0.0.tgz#f8f78b76789888ef39f205cd637f68e702122b2c"
|
resolved "https://registry.yarnpkg.com/buffer-fill/-/buffer-fill-1.0.0.tgz#f8f78b76789888ef39f205cd637f68e702122b2c"
|
||||||
|
|
||||||
|
buffer-stream-reader@^0.1.1:
|
||||||
|
version "0.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/buffer-stream-reader/-/buffer-stream-reader-0.1.1.tgz#ca8bf93631deedd8b8f8c3bb44991cc30951e259"
|
||||||
|
|
||||||
buffer@^3.0.1:
|
buffer@^3.0.1:
|
||||||
version "3.6.0"
|
version "3.6.0"
|
||||||
resolved "https://registry.yarnpkg.com/buffer/-/buffer-3.6.0.tgz#a72c936f77b96bf52f5f7e7b467180628551defb"
|
resolved "https://registry.yarnpkg.com/buffer/-/buffer-3.6.0.tgz#a72c936f77b96bf52f5f7e7b467180628551defb"
|
||||||
@@ -54,16 +99,47 @@ buffer@^3.0.1:
|
|||||||
ieee754 "^1.1.4"
|
ieee754 "^1.1.4"
|
||||||
isarray "^1.0.0"
|
isarray "^1.0.0"
|
||||||
|
|
||||||
|
caseless@~0.12.0:
|
||||||
|
version "0.12.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
|
||||||
|
|
||||||
|
clipboardy@^1.2.3:
|
||||||
|
version "1.2.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/clipboardy/-/clipboardy-1.2.3.tgz#0526361bf78724c1f20be248d428e365433c07ef"
|
||||||
|
dependencies:
|
||||||
|
arch "^2.1.0"
|
||||||
|
execa "^0.8.0"
|
||||||
|
|
||||||
|
combined-stream@^1.0.6, combined-stream@~1.0.6:
|
||||||
|
version "1.0.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.7.tgz#2d1d24317afb8abe95d6d2c0b07b57813539d828"
|
||||||
|
dependencies:
|
||||||
|
delayed-stream "~1.0.0"
|
||||||
|
|
||||||
commander@~2.8.1:
|
commander@~2.8.1:
|
||||||
version "2.8.1"
|
version "2.8.1"
|
||||||
resolved "https://registry.yarnpkg.com/commander/-/commander-2.8.1.tgz#06be367febfda0c330aa1e2a072d3dc9762425d4"
|
resolved "https://registry.yarnpkg.com/commander/-/commander-2.8.1.tgz#06be367febfda0c330aa1e2a072d3dc9762425d4"
|
||||||
dependencies:
|
dependencies:
|
||||||
graceful-readlink ">= 1.0.0"
|
graceful-readlink ">= 1.0.0"
|
||||||
|
|
||||||
core-util-is@~1.0.0:
|
core-util-is@1.0.2, core-util-is@~1.0.0:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
|
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
|
||||||
|
|
||||||
|
cross-spawn@^5.0.1:
|
||||||
|
version "5.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449"
|
||||||
|
dependencies:
|
||||||
|
lru-cache "^4.0.1"
|
||||||
|
shebang-command "^1.2.0"
|
||||||
|
which "^1.2.9"
|
||||||
|
|
||||||
|
dashdash@^1.12.0:
|
||||||
|
version "1.14.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0"
|
||||||
|
dependencies:
|
||||||
|
assert-plus "^1.0.0"
|
||||||
|
|
||||||
"dataprotocol-client@github:Microsoft/sqlops-dataprotocolclient#0.2.15":
|
"dataprotocol-client@github:Microsoft/sqlops-dataprotocolclient#0.2.15":
|
||||||
version "0.2.15"
|
version "0.2.15"
|
||||||
resolved "https://codeload.github.com/Microsoft/sqlops-dataprotocolclient/tar.gz/a2cd2db109de882f0959f7b6421c86afa585f460"
|
resolved "https://codeload.github.com/Microsoft/sqlops-dataprotocolclient/tar.gz/a2cd2db109de882f0959f7b6421c86afa585f460"
|
||||||
@@ -130,6 +206,10 @@ decompress@^4.2.0:
|
|||||||
pify "^2.3.0"
|
pify "^2.3.0"
|
||||||
strip-dirs "^2.0.0"
|
strip-dirs "^2.0.0"
|
||||||
|
|
||||||
|
delayed-stream@~1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
|
||||||
|
|
||||||
diagnostic-channel-publishers@0.2.1:
|
diagnostic-channel-publishers@0.2.1:
|
||||||
version "0.2.1"
|
version "0.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/diagnostic-channel-publishers/-/diagnostic-channel-publishers-0.2.1.tgz#8e2d607a8b6d79fe880b548bc58cc6beb288c4f3"
|
resolved "https://registry.yarnpkg.com/diagnostic-channel-publishers/-/diagnostic-channel-publishers-0.2.1.tgz#8e2d607a8b6d79fe880b548bc58cc6beb288c4f3"
|
||||||
@@ -140,6 +220,13 @@ diagnostic-channel@0.2.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
semver "^5.3.0"
|
semver "^5.3.0"
|
||||||
|
|
||||||
|
ecc-jsbn@~0.1.1:
|
||||||
|
version "0.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9"
|
||||||
|
dependencies:
|
||||||
|
jsbn "~0.1.0"
|
||||||
|
safer-buffer "^2.1.0"
|
||||||
|
|
||||||
end-of-stream@^1.0.0:
|
end-of-stream@^1.0.0:
|
||||||
version "1.4.1"
|
version "1.4.1"
|
||||||
resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.1.tgz#ed29634d19baba463b6ce6b80a37213eab71ec43"
|
resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.1.tgz#ed29634d19baba463b6ce6b80a37213eab71ec43"
|
||||||
@@ -160,6 +247,38 @@ eventemitter2@^5.0.1:
|
|||||||
version "5.0.1"
|
version "5.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-5.0.1.tgz#6197a095d5fb6b57e8942f6fd7eaad63a09c9452"
|
resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-5.0.1.tgz#6197a095d5fb6b57e8942f6fd7eaad63a09c9452"
|
||||||
|
|
||||||
|
execa@^0.8.0:
|
||||||
|
version "0.8.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/execa/-/execa-0.8.0.tgz#d8d76bbc1b55217ed190fd6dd49d3c774ecfc8da"
|
||||||
|
dependencies:
|
||||||
|
cross-spawn "^5.0.1"
|
||||||
|
get-stream "^3.0.0"
|
||||||
|
is-stream "^1.1.0"
|
||||||
|
npm-run-path "^2.0.0"
|
||||||
|
p-finally "^1.0.0"
|
||||||
|
signal-exit "^3.0.0"
|
||||||
|
strip-eof "^1.0.0"
|
||||||
|
|
||||||
|
extend@^3.0.0, extend@~3.0.2:
|
||||||
|
version "3.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
|
||||||
|
|
||||||
|
extsprintf@1.3.0:
|
||||||
|
version "1.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05"
|
||||||
|
|
||||||
|
extsprintf@^1.2.0:
|
||||||
|
version "1.4.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f"
|
||||||
|
|
||||||
|
fast-deep-equal@^2.0.1:
|
||||||
|
version "2.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49"
|
||||||
|
|
||||||
|
fast-json-stable-stringify@^2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2"
|
||||||
|
|
||||||
fd-slicer@~1.1.0:
|
fd-slicer@~1.1.0:
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e"
|
resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e"
|
||||||
@@ -178,6 +297,18 @@ file-type@^6.1.0:
|
|||||||
version "6.2.0"
|
version "6.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/file-type/-/file-type-6.2.0.tgz#e50cd75d356ffed4e306dc4f5bcf52a79903a919"
|
resolved "https://registry.yarnpkg.com/file-type/-/file-type-6.2.0.tgz#e50cd75d356ffed4e306dc4f5bcf52a79903a919"
|
||||||
|
|
||||||
|
forever-agent@~0.6.1:
|
||||||
|
version "0.6.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
|
||||||
|
|
||||||
|
form-data@~2.3.2:
|
||||||
|
version "2.3.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6"
|
||||||
|
dependencies:
|
||||||
|
asynckit "^0.4.0"
|
||||||
|
combined-stream "^1.0.6"
|
||||||
|
mime-types "^2.1.12"
|
||||||
|
|
||||||
fs-constants@^1.0.0:
|
fs-constants@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad"
|
resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad"
|
||||||
@@ -189,6 +320,16 @@ get-stream@^2.2.0:
|
|||||||
object-assign "^4.0.1"
|
object-assign "^4.0.1"
|
||||||
pinkie-promise "^2.0.0"
|
pinkie-promise "^2.0.0"
|
||||||
|
|
||||||
|
get-stream@^3.0.0:
|
||||||
|
version "3.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14"
|
||||||
|
|
||||||
|
getpass@^0.1.1:
|
||||||
|
version "0.1.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa"
|
||||||
|
dependencies:
|
||||||
|
assert-plus "^1.0.0"
|
||||||
|
|
||||||
graceful-fs@^4.1.10:
|
graceful-fs@^4.1.10:
|
||||||
version "4.1.15"
|
version "4.1.15"
|
||||||
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00"
|
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00"
|
||||||
@@ -197,6 +338,17 @@ graceful-fs@^4.1.10:
|
|||||||
version "1.0.1"
|
version "1.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725"
|
resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725"
|
||||||
|
|
||||||
|
har-schema@^2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92"
|
||||||
|
|
||||||
|
har-validator@~5.1.0:
|
||||||
|
version "5.1.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080"
|
||||||
|
dependencies:
|
||||||
|
ajv "^6.5.5"
|
||||||
|
har-schema "^2.0.0"
|
||||||
|
|
||||||
http-proxy-agent@^2.1.0:
|
http-proxy-agent@^2.1.0:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz#e4821beef5b2142a2026bd73926fe537631c5405"
|
resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz#e4821beef5b2142a2026bd73926fe537631c5405"
|
||||||
@@ -204,6 +356,14 @@ http-proxy-agent@^2.1.0:
|
|||||||
agent-base "4"
|
agent-base "4"
|
||||||
debug "3.1.0"
|
debug "3.1.0"
|
||||||
|
|
||||||
|
http-signature@~1.2.0:
|
||||||
|
version "1.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1"
|
||||||
|
dependencies:
|
||||||
|
assert-plus "^1.0.0"
|
||||||
|
jsprim "^1.2.2"
|
||||||
|
sshpk "^1.7.0"
|
||||||
|
|
||||||
https-proxy-agent@^2.2.1:
|
https-proxy-agent@^2.2.1:
|
||||||
version "2.2.1"
|
version "2.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz#51552970fa04d723e04c56d04178c3f92592bbc0"
|
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz#51552970fa04d723e04c56d04178c3f92592bbc0"
|
||||||
@@ -227,16 +387,70 @@ is-stream@^1.1.0:
|
|||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
|
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
|
||||||
|
|
||||||
|
is-typedarray@~1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
|
||||||
|
|
||||||
isarray@^1.0.0, isarray@~1.0.0:
|
isarray@^1.0.0, isarray@~1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
|
resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
|
||||||
|
|
||||||
|
isexe@^2.0.0:
|
||||||
|
version "2.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
|
||||||
|
|
||||||
|
isstream@~0.1.2:
|
||||||
|
version "0.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
|
||||||
|
|
||||||
|
jsbn@~0.1.0:
|
||||||
|
version "0.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
|
||||||
|
|
||||||
|
json-schema-traverse@^0.4.1:
|
||||||
|
version "0.4.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
|
||||||
|
|
||||||
|
json-schema@0.2.3:
|
||||||
|
version "0.2.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13"
|
||||||
|
|
||||||
|
json-stringify-safe@~5.0.1:
|
||||||
|
version "5.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
|
||||||
|
|
||||||
|
jsprim@^1.2.2:
|
||||||
|
version "1.4.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2"
|
||||||
|
dependencies:
|
||||||
|
assert-plus "1.0.0"
|
||||||
|
extsprintf "1.3.0"
|
||||||
|
json-schema "0.2.3"
|
||||||
|
verror "1.10.0"
|
||||||
|
|
||||||
|
lru-cache@^4.0.1:
|
||||||
|
version "4.1.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd"
|
||||||
|
dependencies:
|
||||||
|
pseudomap "^1.0.2"
|
||||||
|
yallist "^2.1.2"
|
||||||
|
|
||||||
make-dir@^1.0.0:
|
make-dir@^1.0.0:
|
||||||
version "1.3.0"
|
version "1.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c"
|
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c"
|
||||||
dependencies:
|
dependencies:
|
||||||
pify "^3.0.0"
|
pify "^3.0.0"
|
||||||
|
|
||||||
|
mime-db@~1.37.0:
|
||||||
|
version "1.37.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.37.0.tgz#0b6a0ce6fdbe9576e25f1f2d2fde8830dc0ad0d8"
|
||||||
|
|
||||||
|
mime-types@^2.1.12, mime-types@~2.1.19:
|
||||||
|
version "2.1.21"
|
||||||
|
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.21.tgz#28995aa1ecb770742fe6ae7e58f9181c744b3f96"
|
||||||
|
dependencies:
|
||||||
|
mime-db "~1.37.0"
|
||||||
|
|
||||||
minimist@0.0.8:
|
minimist@0.0.8:
|
||||||
version "0.0.8"
|
version "0.0.8"
|
||||||
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
|
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
|
||||||
@@ -255,6 +469,16 @@ ms@^2.1.1:
|
|||||||
version "2.1.1"
|
version "2.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a"
|
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a"
|
||||||
|
|
||||||
|
npm-run-path@^2.0.0:
|
||||||
|
version "2.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f"
|
||||||
|
dependencies:
|
||||||
|
path-key "^2.0.0"
|
||||||
|
|
||||||
|
oauth-sign@~0.9.0:
|
||||||
|
version "0.9.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455"
|
||||||
|
|
||||||
object-assign@^4.0.1:
|
object-assign@^4.0.1:
|
||||||
version "4.1.1"
|
version "4.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
|
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
|
||||||
@@ -273,10 +497,22 @@ os-tmpdir@~1.0.2:
|
|||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
|
resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
|
||||||
|
|
||||||
|
p-finally@^1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae"
|
||||||
|
|
||||||
|
path-key@^2.0.0:
|
||||||
|
version "2.0.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40"
|
||||||
|
|
||||||
pend@~1.2.0:
|
pend@~1.2.0:
|
||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
|
resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50"
|
||||||
|
|
||||||
|
performance-now@^2.1.0:
|
||||||
|
version "2.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
|
||||||
|
|
||||||
pify@^2.3.0:
|
pify@^2.3.0:
|
||||||
version "2.3.0"
|
version "2.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
|
resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
|
||||||
@@ -299,7 +535,27 @@ process-nextick-args@~2.0.0:
|
|||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa"
|
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:
|
pseudomap@^1.0.2:
|
||||||
|
version "1.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3"
|
||||||
|
|
||||||
|
psl@^1.1.24:
|
||||||
|
version "1.1.31"
|
||||||
|
resolved "https://registry.yarnpkg.com/psl/-/psl-1.1.31.tgz#e9aa86d0101b5b105cbe93ac6b784cd547276184"
|
||||||
|
|
||||||
|
punycode@^1.4.1:
|
||||||
|
version "1.4.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
|
||||||
|
|
||||||
|
punycode@^2.1.0:
|
||||||
|
version "2.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
|
||||||
|
|
||||||
|
qs@~6.5.2:
|
||||||
|
version "6.5.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
|
||||||
|
|
||||||
|
readable-stream@^2.1.4, readable-stream@^2.3.0, readable-stream@^2.3.5:
|
||||||
version "2.3.6"
|
version "2.3.6"
|
||||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf"
|
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf"
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -311,10 +567,39 @@ readable-stream@^2.3.0, readable-stream@^2.3.5:
|
|||||||
string_decoder "~1.1.1"
|
string_decoder "~1.1.1"
|
||||||
util-deprecate "~1.0.1"
|
util-deprecate "~1.0.1"
|
||||||
|
|
||||||
safe-buffer@^5.1.1, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
|
request@^2.74.0:
|
||||||
|
version "2.88.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef"
|
||||||
|
dependencies:
|
||||||
|
aws-sign2 "~0.7.0"
|
||||||
|
aws4 "^1.8.0"
|
||||||
|
caseless "~0.12.0"
|
||||||
|
combined-stream "~1.0.6"
|
||||||
|
extend "~3.0.2"
|
||||||
|
forever-agent "~0.6.1"
|
||||||
|
form-data "~2.3.2"
|
||||||
|
har-validator "~5.1.0"
|
||||||
|
http-signature "~1.2.0"
|
||||||
|
is-typedarray "~1.0.0"
|
||||||
|
isstream "~0.1.2"
|
||||||
|
json-stringify-safe "~5.0.1"
|
||||||
|
mime-types "~2.1.19"
|
||||||
|
oauth-sign "~0.9.0"
|
||||||
|
performance-now "^2.1.0"
|
||||||
|
qs "~6.5.2"
|
||||||
|
safe-buffer "^5.1.2"
|
||||||
|
tough-cookie "~2.4.3"
|
||||||
|
tunnel-agent "^0.6.0"
|
||||||
|
uuid "^3.3.2"
|
||||||
|
|
||||||
|
safe-buffer@^5.0.1, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
|
||||||
version "5.1.2"
|
version "5.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
|
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
|
||||||
|
|
||||||
|
safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0:
|
||||||
|
version "2.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
|
||||||
|
|
||||||
seek-bzip@^1.0.5:
|
seek-bzip@^1.0.5:
|
||||||
version "1.0.5"
|
version "1.0.5"
|
||||||
resolved "https://registry.yarnpkg.com/seek-bzip/-/seek-bzip-1.0.5.tgz#cfe917cb3d274bcffac792758af53173eb1fabdc"
|
resolved "https://registry.yarnpkg.com/seek-bzip/-/seek-bzip-1.0.5.tgz#cfe917cb3d274bcffac792758af53173eb1fabdc"
|
||||||
@@ -336,6 +621,40 @@ semver@^5.3.0:
|
|||||||
mkdirp "^0.5.1"
|
mkdirp "^0.5.1"
|
||||||
tmp "^0.0.33"
|
tmp "^0.0.33"
|
||||||
|
|
||||||
|
shebang-command@^1.2.0:
|
||||||
|
version "1.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea"
|
||||||
|
dependencies:
|
||||||
|
shebang-regex "^1.0.0"
|
||||||
|
|
||||||
|
shebang-regex@^1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3"
|
||||||
|
|
||||||
|
signal-exit@^3.0.0:
|
||||||
|
version "3.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d"
|
||||||
|
|
||||||
|
sshpk@^1.7.0:
|
||||||
|
version "1.16.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.0.tgz#1d4963a2fbffe58050aa9084ca20be81741c07de"
|
||||||
|
dependencies:
|
||||||
|
asn1 "~0.2.3"
|
||||||
|
assert-plus "^1.0.0"
|
||||||
|
bcrypt-pbkdf "^1.0.0"
|
||||||
|
dashdash "^1.12.0"
|
||||||
|
ecc-jsbn "~0.1.1"
|
||||||
|
getpass "^0.1.1"
|
||||||
|
jsbn "~0.1.0"
|
||||||
|
safer-buffer "^2.0.2"
|
||||||
|
tweetnacl "~0.14.0"
|
||||||
|
|
||||||
|
stream-meter@^1.0.4:
|
||||||
|
version "1.0.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/stream-meter/-/stream-meter-1.0.4.tgz#52af95aa5ea760a2491716704dbff90f73afdd1d"
|
||||||
|
dependencies:
|
||||||
|
readable-stream "^2.1.4"
|
||||||
|
|
||||||
string_decoder@~1.1.1:
|
string_decoder@~1.1.1:
|
||||||
version "1.1.1"
|
version "1.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
|
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
|
||||||
@@ -348,6 +667,10 @@ strip-dirs@^2.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
is-natural-number "^4.0.1"
|
is-natural-number "^4.0.1"
|
||||||
|
|
||||||
|
strip-eof@^1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf"
|
||||||
|
|
||||||
tar-stream@^1.5.2:
|
tar-stream@^1.5.2:
|
||||||
version "1.6.2"
|
version "1.6.2"
|
||||||
resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.6.2.tgz#8ea55dab37972253d9a9af90fdcd559ae435c555"
|
resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.6.2.tgz#8ea55dab37972253d9a9af90fdcd559ae435c555"
|
||||||
@@ -374,6 +697,23 @@ to-buffer@^1.1.1:
|
|||||||
version "1.1.1"
|
version "1.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/to-buffer/-/to-buffer-1.1.1.tgz#493bd48f62d7c43fcded313a03dcadb2e1213a80"
|
resolved "https://registry.yarnpkg.com/to-buffer/-/to-buffer-1.1.1.tgz#493bd48f62d7c43fcded313a03dcadb2e1213a80"
|
||||||
|
|
||||||
|
tough-cookie@~2.4.3:
|
||||||
|
version "2.4.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781"
|
||||||
|
dependencies:
|
||||||
|
psl "^1.1.24"
|
||||||
|
punycode "^1.4.1"
|
||||||
|
|
||||||
|
tunnel-agent@^0.6.0:
|
||||||
|
version "0.6.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd"
|
||||||
|
dependencies:
|
||||||
|
safe-buffer "^5.0.1"
|
||||||
|
|
||||||
|
tweetnacl@^0.14.3, tweetnacl@~0.14.0:
|
||||||
|
version "0.14.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64"
|
||||||
|
|
||||||
unbzip2-stream@^1.0.9:
|
unbzip2-stream@^1.0.9:
|
||||||
version "1.3.1"
|
version "1.3.1"
|
||||||
resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.3.1.tgz#7854da51622a7e63624221196357803b552966a1"
|
resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.3.1.tgz#7854da51622a7e63624221196357803b552966a1"
|
||||||
@@ -381,10 +721,28 @@ unbzip2-stream@^1.0.9:
|
|||||||
buffer "^3.0.1"
|
buffer "^3.0.1"
|
||||||
through "^2.3.6"
|
through "^2.3.6"
|
||||||
|
|
||||||
|
uri-js@^4.2.2:
|
||||||
|
version "4.2.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0"
|
||||||
|
dependencies:
|
||||||
|
punycode "^2.1.0"
|
||||||
|
|
||||||
util-deprecate@~1.0.1:
|
util-deprecate@~1.0.1:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
|
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
|
||||||
|
|
||||||
|
uuid@^3.3.2:
|
||||||
|
version "3.3.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131"
|
||||||
|
|
||||||
|
verror@1.10.0:
|
||||||
|
version "1.10.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400"
|
||||||
|
dependencies:
|
||||||
|
assert-plus "^1.0.0"
|
||||||
|
core-util-is "1.0.2"
|
||||||
|
extsprintf "^1.2.0"
|
||||||
|
|
||||||
vscode-extension-telemetry@^0.0.15:
|
vscode-extension-telemetry@^0.0.15:
|
||||||
version "0.0.15"
|
version "0.0.15"
|
||||||
resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.0.15.tgz#685c32f3b67e8fb85ba689c1d7f88ff90ff87856"
|
resolved "https://registry.yarnpkg.com/vscode-extension-telemetry/-/vscode-extension-telemetry-0.0.15.tgz#685c32f3b67e8fb85ba689c1d7f88ff90ff87856"
|
||||||
@@ -412,6 +770,24 @@ vscode-languageserver-types@3.5.0:
|
|||||||
version "3.5.0"
|
version "3.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.5.0.tgz#e48d79962f0b8e02de955e3f524908e2b19c0374"
|
resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.5.0.tgz#e48d79962f0b8e02de955e3f524908e2b19c0374"
|
||||||
|
|
||||||
|
vscode-nls@2.0.2:
|
||||||
|
version "2.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-2.0.2.tgz#808522380844b8ad153499af5c3b03921aea02da"
|
||||||
|
|
||||||
|
webhdfs@^1.1.1:
|
||||||
|
version "1.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/webhdfs/-/webhdfs-1.2.0.tgz#c41b08ae33944a0220863bfd4b6719b9aaec1d37"
|
||||||
|
dependencies:
|
||||||
|
buffer-stream-reader "^0.1.1"
|
||||||
|
extend "^3.0.0"
|
||||||
|
request "^2.74.0"
|
||||||
|
|
||||||
|
which@^1.2.9:
|
||||||
|
version "1.3.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
|
||||||
|
dependencies:
|
||||||
|
isexe "^2.0.0"
|
||||||
|
|
||||||
wrappy@1:
|
wrappy@1:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
|
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
|
||||||
@@ -420,6 +796,10 @@ xtend@^4.0.0:
|
|||||||
version "4.0.1"
|
version "4.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"
|
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af"
|
||||||
|
|
||||||
|
yallist@^2.1.2:
|
||||||
|
version "2.1.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52"
|
||||||
|
|
||||||
yauzl@^2.4.2:
|
yauzl@^2.4.2:
|
||||||
version "2.10.0"
|
version "2.10.0"
|
||||||
resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9"
|
resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9"
|
||||||
|
|||||||
@@ -26,6 +26,10 @@ export const SERVICE_ID = 'ObjectExplorerService';
|
|||||||
|
|
||||||
export const IObjectExplorerService = createDecorator<IObjectExplorerService>(SERVICE_ID);
|
export const IObjectExplorerService = createDecorator<IObjectExplorerService>(SERVICE_ID);
|
||||||
|
|
||||||
|
export interface NodeExpandInfoWithProviderId extends sqlops.ObjectExplorerExpandInfo {
|
||||||
|
providerId: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface IObjectExplorerService {
|
export interface IObjectExplorerService {
|
||||||
_serviceBrand: any;
|
_serviceBrand: any;
|
||||||
|
|
||||||
@@ -45,13 +49,15 @@ export interface IObjectExplorerService {
|
|||||||
|
|
||||||
onSessionDisconnected(handle: number, sessionResponse: sqlops.ObjectExplorerSession);
|
onSessionDisconnected(handle: number, sessionResponse: sqlops.ObjectExplorerSession);
|
||||||
|
|
||||||
onNodeExpanded(handle: number, sessionResponse: sqlops.ObjectExplorerExpandInfo);
|
onNodeExpanded(sessionResponse: NodeExpandInfoWithProviderId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register a ObjectExplorer provider
|
* Register a ObjectExplorer provider
|
||||||
*/
|
*/
|
||||||
registerProvider(providerId: string, provider: sqlops.ObjectExplorerProvider): void;
|
registerProvider(providerId: string, provider: sqlops.ObjectExplorerProvider): void;
|
||||||
|
|
||||||
|
registerNodeProvider(expander: sqlops.ObjectExplorerNodeProvider): void;
|
||||||
|
|
||||||
getObjectExplorerNode(connection: IConnectionProfile): TreeNode;
|
getObjectExplorerNode(connection: IConnectionProfile): TreeNode;
|
||||||
|
|
||||||
updateObjectExplorerNodes(connectionProfile: IConnectionProfile): Promise<void>;
|
updateObjectExplorerNodes(connectionProfile: IConnectionProfile): Promise<void>;
|
||||||
@@ -82,15 +88,18 @@ export interface IObjectExplorerService {
|
|||||||
* For Testing purpose only. Get the context menu actions for an object explorer node.
|
* For Testing purpose only. Get the context menu actions for an object explorer node.
|
||||||
*/
|
*/
|
||||||
getNodeActions(connectionId: string, nodePath: string): Thenable<string[]>;
|
getNodeActions(connectionId: string, nodePath: string): Thenable<string[]>;
|
||||||
|
|
||||||
|
getSessionConnectionProfile(sessionId: string): sqlops.IConnectionProfile;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SessionStatus {
|
interface SessionStatus {
|
||||||
nodes: { [nodePath: string]: NodeStatus };
|
nodes: { [nodePath: string]: NodeStatus };
|
||||||
connection: ConnectionProfile;
|
connection: ConnectionProfile;
|
||||||
|
expandNodeTimer?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface NodeStatus {
|
interface NodeStatus {
|
||||||
expandEmitter: Emitter<sqlops.ObjectExplorerExpandInfo>;
|
expandEmitter: Emitter<NodeExpandInfoWithProviderId>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ObjectExplorerNodeEventArgs {
|
export interface ObjectExplorerNodeEventArgs {
|
||||||
@@ -103,6 +112,14 @@ export interface NodeInfoWithConnection {
|
|||||||
nodeInfo: sqlops.NodeInfo;
|
nodeInfo: sqlops.NodeInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface TopLevelChildrenPath {
|
||||||
|
providerId: string;
|
||||||
|
supportedProviderId: string;
|
||||||
|
groupingId: number;
|
||||||
|
path: string[];
|
||||||
|
providerObject: sqlops.ObjectExplorerNodeProvider | sqlops.ObjectExplorerProvider;
|
||||||
|
}
|
||||||
|
|
||||||
export class ObjectExplorerService implements IObjectExplorerService {
|
export class ObjectExplorerService implements IObjectExplorerService {
|
||||||
|
|
||||||
public _serviceBrand: any;
|
public _serviceBrand: any;
|
||||||
@@ -111,6 +128,8 @@ export class ObjectExplorerService implements IObjectExplorerService {
|
|||||||
|
|
||||||
private _providers: { [handle: string]: sqlops.ObjectExplorerProvider; } = Object.create(null);
|
private _providers: { [handle: string]: sqlops.ObjectExplorerProvider; } = Object.create(null);
|
||||||
|
|
||||||
|
private _nodeProviders: { [handle: string]: sqlops.ObjectExplorerNodeProvider[]; } = Object.create(null);
|
||||||
|
|
||||||
private _activeObjectExplorerNodes: { [id: string]: TreeNode };
|
private _activeObjectExplorerNodes: { [id: string]: TreeNode };
|
||||||
private _sessions: { [sessionId: string]: SessionStatus };
|
private _sessions: { [sessionId: string]: SessionStatus };
|
||||||
|
|
||||||
@@ -129,6 +148,7 @@ export class ObjectExplorerService implements IObjectExplorerService {
|
|||||||
this._activeObjectExplorerNodes = {};
|
this._activeObjectExplorerNodes = {};
|
||||||
this._sessions = {};
|
this._sessions = {};
|
||||||
this._providers = {};
|
this._providers = {};
|
||||||
|
this._nodeProviders = {};
|
||||||
this._onSelectionOrFocusChange = new Emitter<void>();
|
this._onSelectionOrFocusChange = new Emitter<void>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,7 +186,7 @@ export class ObjectExplorerService implements IObjectExplorerService {
|
|||||||
/**
|
/**
|
||||||
* Gets called when expanded node response is ready
|
* Gets called when expanded node response is ready
|
||||||
*/
|
*/
|
||||||
public onNodeExpanded(handle: number, expandResponse: sqlops.ObjectExplorerExpandInfo) {
|
public onNodeExpanded(expandResponse: NodeExpandInfoWithProviderId) {
|
||||||
|
|
||||||
if (expandResponse.errorMessage) {
|
if (expandResponse.errorMessage) {
|
||||||
error(expandResponse.errorMessage);
|
error(expandResponse.errorMessage);
|
||||||
@@ -189,28 +209,43 @@ export class ObjectExplorerService implements IObjectExplorerService {
|
|||||||
/**
|
/**
|
||||||
* Gets called when session is created
|
* Gets called when session is created
|
||||||
*/
|
*/
|
||||||
public onSessionCreated(handle: number, session: sqlops.ObjectExplorerSession) {
|
public onSessionCreated(handle: number, session: sqlops.ObjectExplorerSession): void {
|
||||||
let connection: ConnectionProfile = undefined;
|
this.handleSessionCreated(session);
|
||||||
let errorMessage: string = undefined;
|
}
|
||||||
if (this._sessions[session.sessionId]) {
|
|
||||||
connection = this._sessions[session.sessionId].connection;
|
|
||||||
|
|
||||||
if (session && session.success && session.rootNode) {
|
private async handleSessionCreated(session: sqlops.ObjectExplorerSession): Promise<void> {
|
||||||
let server = this.toTreeNode(session.rootNode, null);
|
try {
|
||||||
server.connection = connection;
|
let connection: ConnectionProfile = undefined;
|
||||||
server.session = session;
|
let errorMessage: string = undefined;
|
||||||
this._activeObjectExplorerNodes[connection.id] = server;
|
if (this._sessions[session.sessionId]) {
|
||||||
} else {
|
connection = this._sessions[session.sessionId].connection;
|
||||||
errorMessage = session && session.errorMessage ? session.errorMessage :
|
|
||||||
nls.localize('OeSessionFailedError', 'Failed to create Object Explorer session');
|
if (session && session.success && session.rootNode) {
|
||||||
error(errorMessage);
|
let server = this.toTreeNode(session.rootNode, null);
|
||||||
|
server.connection = connection;
|
||||||
|
server.session = session;
|
||||||
|
this._activeObjectExplorerNodes[connection.id] = server;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
errorMessage = session && session.errorMessage ? session.errorMessage :
|
||||||
|
nls.localize('OeSessionFailedError', 'Failed to create Object Explorer session');
|
||||||
|
error(errorMessage);
|
||||||
|
}
|
||||||
|
// Send on session created about the session to all node providers so they can prepare for node expansion
|
||||||
|
let nodeProviders = this._nodeProviders[connection.providerName];
|
||||||
|
if (nodeProviders) {
|
||||||
|
let promises: Thenable<boolean>[] = nodeProviders.map(p => p.handleSessionOpen(session));
|
||||||
|
await Promise.all(promises);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
warn(`cannot find session ${session.sessionId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
this.sendUpdateNodeEvent(connection, errorMessage);
|
||||||
warn(`cannot find session ${session.sessionId}`);
|
} catch (error) {
|
||||||
|
warn(`cannot handle the session ${session.sessionId} in all nodeProviders`);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.sendUpdateNodeEvent(connection, errorMessage);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -291,7 +326,7 @@ export class ObjectExplorerService implements IObjectExplorerService {
|
|||||||
let provider = this._providers[providerId];
|
let provider = this._providers[providerId];
|
||||||
if (provider) {
|
if (provider) {
|
||||||
TelemetryUtils.addTelemetry(this._telemetryService, TelemetryKeys.ObjectExplorerExpand, { refresh: 0, provider: providerId });
|
TelemetryUtils.addTelemetry(this._telemetryService, TelemetryKeys.ObjectExplorerExpand, { refresh: 0, provider: providerId });
|
||||||
this.expandOrRefreshNode(provider, session, nodePath).then(result => {
|
this.expandOrRefreshNode(providerId, session, nodePath).then(result => {
|
||||||
resolve(result);
|
resolve(result);
|
||||||
}, error => {
|
}, error => {
|
||||||
reject(error);
|
reject(error);
|
||||||
@@ -301,7 +336,8 @@ export class ObjectExplorerService implements IObjectExplorerService {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
private callExpandOrRefreshFromProvider(provider: sqlops.ObjectExplorerProvider, nodeInfo: sqlops.ExpandNodeInfo, refresh: boolean = false) {
|
|
||||||
|
private callExpandOrRefreshFromProvider(provider: sqlops.ObjectExplorerProviderBase, nodeInfo: sqlops.ExpandNodeInfo, refresh: boolean = false) {
|
||||||
if (refresh) {
|
if (refresh) {
|
||||||
return provider.refreshNode(nodeInfo);
|
return provider.refreshNode(nodeInfo);
|
||||||
} else {
|
} else {
|
||||||
@@ -310,7 +346,7 @@ export class ObjectExplorerService implements IObjectExplorerService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private expandOrRefreshNode(
|
private expandOrRefreshNode(
|
||||||
provider: sqlops.ObjectExplorerProvider,
|
providerId: string,
|
||||||
session: sqlops.ObjectExplorerSession,
|
session: sqlops.ObjectExplorerSession,
|
||||||
nodePath: string,
|
nodePath: string,
|
||||||
refresh: boolean = false): Thenable<sqlops.ObjectExplorerExpandInfo> {
|
refresh: boolean = false): Thenable<sqlops.ObjectExplorerExpandInfo> {
|
||||||
@@ -320,29 +356,60 @@ export class ObjectExplorerService implements IObjectExplorerService {
|
|||||||
let newRequest = false;
|
let newRequest = false;
|
||||||
if (!self._sessions[session.sessionId].nodes[nodePath]) {
|
if (!self._sessions[session.sessionId].nodes[nodePath]) {
|
||||||
self._sessions[session.sessionId].nodes[nodePath] = {
|
self._sessions[session.sessionId].nodes[nodePath] = {
|
||||||
expandEmitter: new Emitter<sqlops.ObjectExplorerExpandInfo>()
|
expandEmitter: new Emitter<NodeExpandInfoWithProviderId>()
|
||||||
};
|
};
|
||||||
newRequest = true;
|
newRequest = true;
|
||||||
}
|
}
|
||||||
self._sessions[session.sessionId].nodes[nodePath].expandEmitter.event(((expandResult) => {
|
let provider = this._providers[providerId];
|
||||||
if (expandResult && !expandResult.errorMessage) {
|
if (provider) {
|
||||||
resolve(expandResult);
|
let resultMap: Map<string, sqlops.ObjectExplorerExpandInfo> = new Map<string, sqlops.ObjectExplorerExpandInfo>();
|
||||||
|
let allProviders: sqlops.ObjectExplorerProviderBase[] = [provider];
|
||||||
|
|
||||||
|
let nodeProviders = this._nodeProviders[providerId];
|
||||||
|
if (nodeProviders) {
|
||||||
|
nodeProviders = nodeProviders.sort((a, b) => a.group.toLowerCase().localeCompare(b.group.toLowerCase()));
|
||||||
|
allProviders.push(...nodeProviders);
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
reject(expandResult ? expandResult.errorMessage : undefined);
|
self._sessions[session.sessionId].nodes[nodePath].expandEmitter.event((expandResult) => {
|
||||||
}
|
if (expandResult && expandResult.providerId) {
|
||||||
if (newRequest) {
|
resultMap.set(expandResult.providerId, expandResult);
|
||||||
delete self._sessions[session.sessionId].nodes[nodePath];
|
} else {
|
||||||
}
|
console.log('OE provider returns empty result or providerId');
|
||||||
}));
|
}
|
||||||
if (newRequest) {
|
|
||||||
self.callExpandOrRefreshFromProvider(provider, {
|
// When get all responses from all providers, merge results
|
||||||
sessionId: session.sessionId,
|
if (resultMap.size === allProviders.length) {
|
||||||
nodePath: nodePath
|
resolve(self.mergeResults(allProviders, resultMap, nodePath));
|
||||||
}, refresh).then(result => {
|
|
||||||
}, error => {
|
// Have to delete it after get all reponses otherwise couldn't find session for not the first response
|
||||||
reject(error);
|
if (newRequest) {
|
||||||
|
delete self._sessions[session.sessionId].nodes[nodePath];
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
if (newRequest) {
|
||||||
|
allProviders.forEach(provider => {
|
||||||
|
TelemetryUtils.addTelemetry(this._telemetryService, TelemetryKeys.ObjectExplorerExpand, { refresh: 0, provider: providerId });
|
||||||
|
self.callExpandOrRefreshFromProvider(provider, {
|
||||||
|
sessionId: session.sessionId,
|
||||||
|
nodePath: nodePath
|
||||||
|
}, refresh).then(isExpanding => {
|
||||||
|
if (!isExpanding) {
|
||||||
|
// The provider stated it's not going to expand the node, therefore do not need to track when merging results
|
||||||
|
let emptyResult: sqlops.ObjectExplorerExpandInfo = {
|
||||||
|
errorMessage: undefined,
|
||||||
|
nodePath: nodePath,
|
||||||
|
nodes: [],
|
||||||
|
sessionId: session.sessionId
|
||||||
|
};
|
||||||
|
resultMap.set(provider.providerId, emptyResult);
|
||||||
|
}
|
||||||
|
}, error => {
|
||||||
|
reject(error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
reject(`session cannot find to expand node. id: ${session.sessionId} nodePath: ${nodePath}`);
|
reject(`session cannot find to expand node. id: ${session.sessionId} nodePath: ${nodePath}`);
|
||||||
@@ -350,11 +417,48 @@ export class ObjectExplorerService implements IObjectExplorerService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private mergeResults(allProviders: sqlops.ObjectExplorerProviderBase[], resultMap: Map<string, sqlops.ObjectExplorerExpandInfo>, nodePath: string): sqlops.ObjectExplorerExpandInfo {
|
||||||
|
let finalResult: sqlops.ObjectExplorerExpandInfo;
|
||||||
|
let allNodes: sqlops.NodeInfo[] = [];
|
||||||
|
let errorNode: sqlops.NodeInfo = {
|
||||||
|
nodePath: nodePath,
|
||||||
|
label: 'Error',
|
||||||
|
errorMessage: '',
|
||||||
|
nodeType: 'folder',
|
||||||
|
isLeaf: true,
|
||||||
|
nodeSubType: '',
|
||||||
|
nodeStatus: '',
|
||||||
|
metadata: null
|
||||||
|
};
|
||||||
|
|
||||||
|
for (let provider of allProviders) {
|
||||||
|
if (resultMap.has(provider.providerId)) {
|
||||||
|
let result = resultMap.get(provider.providerId);
|
||||||
|
if (result) {
|
||||||
|
if (!result.errorMessage) {
|
||||||
|
finalResult = result;
|
||||||
|
allNodes = allNodes.concat(result.nodes);
|
||||||
|
} else {
|
||||||
|
errorNode.errorMessage += provider.providerId + 'returns ' + result.errorMessage + ' ';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (finalResult) {
|
||||||
|
if (errorNode.errorMessage && errorNode.errorMessage.length > 0) {
|
||||||
|
allNodes = allNodes.concat([errorNode]);
|
||||||
|
}
|
||||||
|
|
||||||
|
finalResult.nodes = allNodes;
|
||||||
|
}
|
||||||
|
return finalResult;
|
||||||
|
}
|
||||||
|
|
||||||
public refreshNode(providerId: string, session: sqlops.ObjectExplorerSession, nodePath: string): Thenable<sqlops.ObjectExplorerExpandInfo> {
|
public refreshNode(providerId: string, session: sqlops.ObjectExplorerSession, nodePath: string): Thenable<sqlops.ObjectExplorerExpandInfo> {
|
||||||
let provider = this._providers[providerId];
|
let provider = this._providers[providerId];
|
||||||
if (provider) {
|
if (provider) {
|
||||||
TelemetryUtils.addTelemetry(this._telemetryService, TelemetryKeys.ObjectExplorerExpand, { refresh: 1, provider: providerId });
|
TelemetryUtils.addTelemetry(this._telemetryService, TelemetryKeys.ObjectExplorerExpand, { refresh: 1, provider: providerId });
|
||||||
return this.expandOrRefreshNode(provider, session, nodePath, true);
|
return this.expandOrRefreshNode(providerId, session, nodePath, true);
|
||||||
}
|
}
|
||||||
return Promise.resolve(undefined);
|
return Promise.resolve(undefined);
|
||||||
}
|
}
|
||||||
@@ -369,7 +473,8 @@ export class ObjectExplorerService implements IObjectExplorerService {
|
|||||||
sessionId: session.sessionId,
|
sessionId: session.sessionId,
|
||||||
nodes: [],
|
nodes: [],
|
||||||
nodePath: nodePath,
|
nodePath: nodePath,
|
||||||
errorMessage: undefined
|
errorMessage: undefined,
|
||||||
|
providerId: providerId
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -377,6 +482,14 @@ export class ObjectExplorerService implements IObjectExplorerService {
|
|||||||
|
|
||||||
let provider = this._providers[providerId];
|
let provider = this._providers[providerId];
|
||||||
if (provider) {
|
if (provider) {
|
||||||
|
let nodeProviders = this._nodeProviders[providerId];
|
||||||
|
if (nodeProviders) {
|
||||||
|
for (let nodeProvider of nodeProviders) {
|
||||||
|
nodeProvider.handleSessionClose({
|
||||||
|
sessionId: session ? session.sessionId : undefined
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
return provider.closeSession({
|
return provider.closeSession({
|
||||||
sessionId: session ? session.sessionId : undefined
|
sessionId: session ? session.sessionId : undefined
|
||||||
});
|
});
|
||||||
@@ -392,6 +505,12 @@ export class ObjectExplorerService implements IObjectExplorerService {
|
|||||||
this._providers[providerId] = provider;
|
this._providers[providerId] = provider;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public registerNodeProvider(nodeProvider: sqlops.ObjectExplorerNodeProvider): void {
|
||||||
|
let nodeProviders = this._nodeProviders[nodeProvider.supportedProviderId] || [];
|
||||||
|
nodeProviders.push(nodeProvider);
|
||||||
|
this._nodeProviders[nodeProvider.supportedProviderId] = nodeProviders;
|
||||||
|
}
|
||||||
|
|
||||||
public dispose(): void {
|
public dispose(): void {
|
||||||
this._disposables = dispose(this._disposables);
|
this._disposables = dispose(this._disposables);
|
||||||
}
|
}
|
||||||
@@ -528,7 +647,7 @@ export class ObjectExplorerService implements IObjectExplorerService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* For Testing purpose only. Get the context menu actions for an object explorer node
|
* For Testing purpose only. Get the context menu actions for an object explorer node
|
||||||
*/
|
*/
|
||||||
public getNodeActions(connectionId: string, nodePath: string): Thenable<string[]> {
|
public getNodeActions(connectionId: string, nodePath: string): Thenable<string[]> {
|
||||||
return this.getTreeNode(connectionId, nodePath).then(node => {
|
return this.getTreeNode(connectionId, nodePath).then(node => {
|
||||||
@@ -552,6 +671,10 @@ export class ObjectExplorerService implements IObjectExplorerService {
|
|||||||
return treeNode;
|
return treeNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getSessionConnectionProfile(sessionId: string): sqlops.IConnectionProfile {
|
||||||
|
return this._sessions[sessionId].connection.toIConnectionProfile();
|
||||||
|
}
|
||||||
|
|
||||||
private async setNodeExpandedState(treeNode: TreeNode, expandedState: TreeItemCollapsibleState): Promise<void> {
|
private async setNodeExpandedState(treeNode: TreeNode, expandedState: TreeItemCollapsibleState): Promise<void> {
|
||||||
treeNode = await this.getUpdatedTreeNode(treeNode);
|
treeNode = await this.getUpdatedTreeNode(treeNode);
|
||||||
let expandNode = this.getTreeItem(treeNode);
|
let expandNode = this.getTreeItem(treeNode);
|
||||||
|
|||||||
@@ -256,6 +256,13 @@ export interface IConnectionManagementService {
|
|||||||
*/
|
*/
|
||||||
getActiveConnectionCredentials(profileId: string): { [name: string]: string };
|
getActiveConnectionCredentials(profileId: string): { [name: string]: string };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the ServerInfo for a connected connection profile
|
||||||
|
* @param {string} profileId The id of the connection profile to get the password for
|
||||||
|
* @returns ServerInfo
|
||||||
|
*/
|
||||||
|
getServerInfo(profileId: string): sqlops.ServerInfo;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the connection string for the provided connection ID
|
* Get the connection string for the provided connection ID
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1367,6 +1367,17 @@ export class ConnectionManagementService extends Disposable implements IConnecti
|
|||||||
return credentials;
|
return credentials;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getServerInfo(profileId: string): sqlops.ServerInfo {
|
||||||
|
let profile = this._connectionStatusManager.findConnectionByProfileId(profileId);
|
||||||
|
if (!profile) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
let serverInfo = profile.serverInfo;
|
||||||
|
|
||||||
|
return serverInfo;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the connection string for the provided connection ID
|
* Get the connection string for the provided connection ID
|
||||||
*/
|
*/
|
||||||
|
|||||||
51
src/sql/sqlops.d.ts
vendored
51
src/sql/sqlops.d.ts
vendored
@@ -21,6 +21,8 @@ declare module 'sqlops' {
|
|||||||
|
|
||||||
export function registerObjectExplorerProvider(provider: ObjectExplorerProvider): vscode.Disposable;
|
export function registerObjectExplorerProvider(provider: ObjectExplorerProvider): vscode.Disposable;
|
||||||
|
|
||||||
|
export function registerObjectExplorerNodeProvider(provider: ObjectExplorerNodeProvider): vscode.Disposable;
|
||||||
|
|
||||||
export function registerTaskServicesProvider(provider: TaskServicesProvider): vscode.Disposable;
|
export function registerTaskServicesProvider(provider: TaskServicesProvider): vscode.Disposable;
|
||||||
|
|
||||||
export function registerFileBrowserProvider(provider: FileBrowserProvider): vscode.Disposable;
|
export function registerFileBrowserProvider(provider: FileBrowserProvider): vscode.Disposable;
|
||||||
@@ -101,6 +103,13 @@ declare module 'sqlops' {
|
|||||||
*/
|
*/
|
||||||
export function getCredentials(connectionId: string): Thenable<{ [name: string]: string }>;
|
export function getCredentials(connectionId: string): Thenable<{ [name: string]: string }>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get ServerInfo for a connectionId
|
||||||
|
* @param {string} connectionId The id of the connection
|
||||||
|
* @returns ServerInfo
|
||||||
|
*/
|
||||||
|
export function getServerInfo(connectionId: string): Thenable<ServerInfo>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface for representing a connection when working with connection APIs
|
* Interface for representing a connection when working with connection APIs
|
||||||
*/
|
*/
|
||||||
@@ -150,6 +159,13 @@ declare module 'sqlops' {
|
|||||||
*/
|
*/
|
||||||
export function findNodes(connectionId: string, type: string, schema: string, name: string, database: string, parentObjectNames: string[]): Thenable<ObjectExplorerNode[]>;
|
export function findNodes(connectionId: string, type: string, schema: string, name: string, database: string, parentObjectNames: string[]): Thenable<ObjectExplorerNode[]>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get connectionProfile from sessionId
|
||||||
|
* *@param {string} sessionId The id of the session that the node exists on
|
||||||
|
* @returns {IConnectionProfile} The IConnecitonProfile for the session
|
||||||
|
*/
|
||||||
|
export function getSessionConnectionProfile(sessionId: string): Thenable<IConnectionProfile>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface for representing and interacting with items in Object Explorer
|
* Interface for representing and interacting with items in Object Explorer
|
||||||
*/
|
*/
|
||||||
@@ -347,6 +363,10 @@ declare module 'sqlops' {
|
|||||||
* The Operating System version string of the machine running the instance.
|
* The Operating System version string of the machine running the instance.
|
||||||
*/
|
*/
|
||||||
osVersion: string;
|
osVersion: string;
|
||||||
|
/**
|
||||||
|
* options for all new server properties.
|
||||||
|
*/
|
||||||
|
options: {};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DataProvider {
|
export interface DataProvider {
|
||||||
@@ -1163,22 +1183,41 @@ declare module 'sqlops' {
|
|||||||
nodes: NodeInfo[];
|
nodes: NodeInfo[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ObjectExplorerProvider extends DataProvider {
|
export interface ObjectExplorerProviderBase extends DataProvider {
|
||||||
createNewSession(connInfo: ConnectionInfo): Thenable<ObjectExplorerSessionResponse>;
|
|
||||||
|
|
||||||
expandNode(nodeInfo: ExpandNodeInfo): Thenable<boolean>;
|
expandNode(nodeInfo: ExpandNodeInfo): Thenable<boolean>;
|
||||||
|
|
||||||
refreshNode(nodeInfo: ExpandNodeInfo): Thenable<boolean>;
|
refreshNode(nodeInfo: ExpandNodeInfo): Thenable<boolean>;
|
||||||
|
|
||||||
closeSession(closeSessionInfo: ObjectExplorerCloseSessionInfo): Thenable<ObjectExplorerCloseSessionResponse>;
|
|
||||||
|
|
||||||
findNodes(findNodesInfo: FindNodesInfo): Thenable<ObjectExplorerFindNodesResponse>;
|
findNodes(findNodesInfo: FindNodesInfo): Thenable<ObjectExplorerFindNodesResponse>;
|
||||||
|
|
||||||
|
registerOnExpandCompleted(handler: (response: ObjectExplorerExpandInfo) => any): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ObjectExplorerProvider extends ObjectExplorerProviderBase {
|
||||||
|
createNewSession(connInfo: ConnectionInfo): Thenable<ObjectExplorerSessionResponse>;
|
||||||
|
|
||||||
|
closeSession(closeSessionInfo: ObjectExplorerCloseSessionInfo): Thenable<ObjectExplorerCloseSessionResponse>;
|
||||||
|
|
||||||
registerOnSessionCreated(handler: (response: ObjectExplorerSession) => any): void;
|
registerOnSessionCreated(handler: (response: ObjectExplorerSession) => any): void;
|
||||||
|
|
||||||
registerOnSessionDisconnected?(handler: (response: ObjectExplorerSession) => any): void;
|
registerOnSessionDisconnected?(handler: (response: ObjectExplorerSession) => any): void;
|
||||||
|
}
|
||||||
|
|
||||||
registerOnExpandCompleted(handler: (response: ObjectExplorerExpandInfo) => any): void;
|
export interface ObjectExplorerNodeProvider extends ObjectExplorerProviderBase {
|
||||||
|
/**
|
||||||
|
* The providerId for whichever type of ObjectExplorer connection this can add folders and objects to
|
||||||
|
*/
|
||||||
|
readonly supportedProviderId: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional group name used to sort nodes in the tree. If not defined, the node order will be added in order based on provider ID, with
|
||||||
|
* nodes from the main ObjectExplorerProvider for this provider type added first
|
||||||
|
*/
|
||||||
|
readonly group?: string;
|
||||||
|
|
||||||
|
handleSessionOpen(session: ObjectExplorerSession): Thenable<boolean>;
|
||||||
|
|
||||||
|
handleSessionClose(closeSessionInfo: ObjectExplorerCloseSessionInfo): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Admin Services interfaces -----------------------------------------------------------------------
|
// Admin Services interfaces -----------------------------------------------------------------------
|
||||||
|
|||||||
1
src/sql/sqlops.proposed.d.ts
vendored
1
src/sql/sqlops.proposed.d.ts
vendored
@@ -1240,6 +1240,7 @@ declare module 'sqlops' {
|
|||||||
AgentServicesProvider = 'AgentServicesProvider',
|
AgentServicesProvider = 'AgentServicesProvider',
|
||||||
CapabilitiesProvider = 'CapabilitiesProvider',
|
CapabilitiesProvider = 'CapabilitiesProvider',
|
||||||
DacFxServicesProvider = 'DacFxServicesProvider',
|
DacFxServicesProvider = 'DacFxServicesProvider',
|
||||||
|
ObjectExplorerNodeProvider = 'ObjectExplorerNodeProvider',
|
||||||
}
|
}
|
||||||
|
|
||||||
export namespace dataprotocol {
|
export namespace dataprotocol {
|
||||||
|
|||||||
@@ -288,6 +288,7 @@ export enum DataProviderType {
|
|||||||
AgentServicesProvider = 'AgentServicesProvider',
|
AgentServicesProvider = 'AgentServicesProvider',
|
||||||
CapabilitiesProvider = 'CapabilitiesProvider',
|
CapabilitiesProvider = 'CapabilitiesProvider',
|
||||||
DacFxServicesProvider = 'DacFxServicesProvider',
|
DacFxServicesProvider = 'DacFxServicesProvider',
|
||||||
|
ObjectExplorerNodeProvider = 'ObjectExplorerNodeProvider',
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum DeclarativeDataType {
|
export enum DeclarativeDataType {
|
||||||
@@ -320,6 +321,17 @@ export enum AzureResource {
|
|||||||
Sql = 1
|
Sql = 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ServerInfoOption {
|
||||||
|
isBigDataCluster: boolean;
|
||||||
|
clusterEndpoints: ClusterEndpoint;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ClusterEndpoint {
|
||||||
|
serviceName: string;
|
||||||
|
ipAddress: string;
|
||||||
|
port: number;
|
||||||
|
}
|
||||||
|
|
||||||
export class SqlThemeIcon {
|
export class SqlThemeIcon {
|
||||||
|
|
||||||
static readonly Folder = new SqlThemeIcon('Folder');
|
static readonly Folder = new SqlThemeIcon('Folder');
|
||||||
|
|||||||
@@ -32,6 +32,10 @@ export class ExtHostConnectionManagement extends ExtHostConnectionManagementShap
|
|||||||
return this._proxy.$getCredentials(connectionId);
|
return this._proxy.$getCredentials(connectionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public $getServerInfo(connectionId: string): Thenable<sqlops.ServerInfo> {
|
||||||
|
return this._proxy.$getServerInfo(connectionId);
|
||||||
|
}
|
||||||
|
|
||||||
public $openConnectionDialog(providers?: string[], initialConnectionProfile?: sqlops.IConnectionProfile, connectionCompletionOptions?: sqlops.IConnectionCompletionOptions): Thenable<sqlops.connection.Connection> {
|
public $openConnectionDialog(providers?: string[], initialConnectionProfile?: sqlops.IConnectionProfile, connectionCompletionOptions?: sqlops.IConnectionCompletionOptions): Thenable<sqlops.connection.Connection> {
|
||||||
return this._proxy.$openConnectionDialog(providers, initialConnectionProfile, connectionCompletionOptions);
|
return this._proxy.$openConnectionDialog(providers, initialConnectionProfile, connectionCompletionOptions);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -132,6 +132,12 @@ export class ExtHostDataProtocol extends ExtHostDataProtocolShape {
|
|||||||
return rt;
|
return rt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$registerObjectExplorerNodeProvider(provider: sqlops.ObjectExplorerNodeProvider): vscode.Disposable {
|
||||||
|
let rt = this.registerProvider(provider, DataProviderType.ObjectExplorerNodeProvider);
|
||||||
|
this._proxy.$registerObjectExplorerNodeProvider(provider.providerId, provider.supportedProviderId, provider.group, provider.handle);
|
||||||
|
return rt;
|
||||||
|
}
|
||||||
|
|
||||||
$registerProfilerProvider(provider: sqlops.ProfilerProvider): vscode.Disposable {
|
$registerProfilerProvider(provider: sqlops.ProfilerProvider): vscode.Disposable {
|
||||||
let rt = this.registerProvider(provider, DataProviderType.ProfilerProvider);
|
let rt = this.registerProvider(provider, DataProviderType.ProfilerProvider);
|
||||||
this._proxy.$registerProfilerProvider(provider.providerId, provider.handle);
|
this._proxy.$registerProfilerProvider(provider.providerId, provider.handle);
|
||||||
@@ -342,20 +348,28 @@ export class ExtHostDataProtocol extends ExtHostDataProtocolShape {
|
|||||||
return this._resolveProvider<sqlops.ObjectExplorerProvider>(handle).createNewSession(connInfo);
|
return this._resolveProvider<sqlops.ObjectExplorerProvider>(handle).createNewSession(connInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public $createObjectExplorerNodeProviderSession(handle: number, session: sqlops.ObjectExplorerSession): Thenable<boolean> {
|
||||||
|
return this._resolveProvider<sqlops.ObjectExplorerNodeProvider>(handle).handleSessionOpen(session);
|
||||||
|
}
|
||||||
|
|
||||||
public $expandObjectExplorerNode(handle: number, nodeInfo: sqlops.ExpandNodeInfo): Thenable<boolean> {
|
public $expandObjectExplorerNode(handle: number, nodeInfo: sqlops.ExpandNodeInfo): Thenable<boolean> {
|
||||||
return this._resolveProvider<sqlops.ObjectExplorerProvider>(handle).expandNode(nodeInfo);
|
return this._resolveProvider<sqlops.ObjectExplorerProviderBase> (handle).expandNode(nodeInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
public $refreshObjectExplorerNode(handle: number, nodeInfo: sqlops.ExpandNodeInfo): Thenable<boolean> {
|
public $refreshObjectExplorerNode(handle: number, nodeInfo: sqlops.ExpandNodeInfo): Thenable<boolean> {
|
||||||
return this._resolveProvider<sqlops.ObjectExplorerProvider>(handle).refreshNode(nodeInfo);
|
return this._resolveProvider<sqlops.ObjectExplorerProviderBase> (handle).refreshNode(nodeInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
public $closeObjectExplorerSession(handle: number, closeSessionInfo: sqlops.ObjectExplorerCloseSessionInfo): Thenable<sqlops.ObjectExplorerCloseSessionResponse> {
|
public $closeObjectExplorerSession(handle: number, closeSessionInfo: sqlops.ObjectExplorerCloseSessionInfo): Thenable<sqlops.ObjectExplorerCloseSessionResponse> {
|
||||||
return this._resolveProvider<sqlops.ObjectExplorerProvider>(handle).closeSession(closeSessionInfo);
|
return this._resolveProvider<sqlops.ObjectExplorerProvider>(handle).closeSession(closeSessionInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public $handleSessionClose(handle: number, closeSessionInfo: sqlops.ObjectExplorerCloseSessionInfo): void {
|
||||||
|
return this._resolveProvider<sqlops.ObjectExplorerNodeProvider>(handle).handleSessionClose(closeSessionInfo);
|
||||||
|
}
|
||||||
|
|
||||||
public $findNodes(handle: number, findNodesInfo: sqlops.FindNodesInfo): Thenable<sqlops.ObjectExplorerFindNodesResponse> {
|
public $findNodes(handle: number, findNodesInfo: sqlops.FindNodesInfo): Thenable<sqlops.ObjectExplorerFindNodesResponse> {
|
||||||
return this._resolveProvider<sqlops.ObjectExplorerProvider>(handle).findNodes(findNodesInfo);
|
return this._resolveProvider<sqlops.ObjectExplorerProviderBase>(handle).findNodes(findNodesInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
public $onObjectExplorerSessionCreated(handle: number, response: sqlops.ObjectExplorerSession): void {
|
public $onObjectExplorerSessionCreated(handle: number, response: sqlops.ObjectExplorerSession): void {
|
||||||
@@ -366,8 +380,8 @@ export class ExtHostDataProtocol extends ExtHostDataProtocolShape {
|
|||||||
this._proxy.$onObjectExplorerSessionDisconnected(handle, response);
|
this._proxy.$onObjectExplorerSessionDisconnected(handle, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
public $onObjectExplorerNodeExpanded(handle: number, response: sqlops.ObjectExplorerExpandInfo): void {
|
public $onObjectExplorerNodeExpanded(providerId: string, response: sqlops.ObjectExplorerExpandInfo): void {
|
||||||
this._proxy.$onObjectExplorerNodeExpanded(handle, response);
|
this._proxy.$onObjectExplorerNodeExpanded(providerId, response);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Task Service
|
// Task Service
|
||||||
|
|||||||
@@ -34,6 +34,10 @@ export class ExtHostObjectExplorer implements ExtHostObjectExplorerShape {
|
|||||||
public $getNodeActions(connectionId: string, nodePath: string): Thenable<string[]> {
|
public $getNodeActions(connectionId: string, nodePath: string): Thenable<string[]> {
|
||||||
return this._proxy.$getNodeActions(connectionId, nodePath);
|
return this._proxy.$getNodeActions(connectionId, nodePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public $getSessionConnectionProfile(sessionId: string): Thenable<sqlops.IConnectionProfile> {
|
||||||
|
return this._proxy.$getSessionConnectionProfile(sessionId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ExtHostObjectExplorerNode implements sqlops.objectexplorer.ObjectExplorerNode {
|
class ExtHostObjectExplorerNode implements sqlops.objectexplorer.ObjectExplorerNode {
|
||||||
|
|||||||
@@ -55,6 +55,9 @@ export class MainThreadConnectionManagement implements MainThreadConnectionManag
|
|||||||
return Promise.resolve(this._connectionManagementService.getActiveConnectionCredentials(connectionId));
|
return Promise.resolve(this._connectionManagementService.getActiveConnectionCredentials(connectionId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public $getServerInfo(connectionId: string): Thenable<sqlops.ServerInfo> {
|
||||||
|
return Promise.resolve(this._connectionManagementService.getServerInfo(connectionId));
|
||||||
|
}
|
||||||
|
|
||||||
public async $openConnectionDialog(providers: string[], initialConnectionProfile?: IConnectionProfile, connectionCompletionOptions?: sqlops.IConnectionCompletionOptions): Promise<sqlops.connection.Connection> {
|
public async $openConnectionDialog(providers: string[], initialConnectionProfile?: IConnectionProfile, connectionCompletionOptions?: sqlops.IConnectionCompletionOptions): Promise<sqlops.connection.Connection> {
|
||||||
let connectionProfile = await this._connectionDialogService.openDialogAndWait(this._connectionManagementService, { connectionType: 1, providers: providers }, initialConnectionProfile);
|
let connectionProfile = await this._connectionDialogService.openDialogAndWait(this._connectionManagementService, { connectionType: 1, providers: providers }, initialConnectionProfile);
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilit
|
|||||||
import { IQueryManagementService } from 'sql/platform/query/common/queryManagement';
|
import { IQueryManagementService } from 'sql/platform/query/common/queryManagement';
|
||||||
import * as sqlops from 'sqlops';
|
import * as sqlops from 'sqlops';
|
||||||
import { IMetadataService } from 'sql/platform/metadata/common/metadataService';
|
import { IMetadataService } from 'sql/platform/metadata/common/metadataService';
|
||||||
import { IObjectExplorerService } from 'sql/parts/objectExplorer/common/objectExplorerService';
|
import { IObjectExplorerService, NodeExpandInfoWithProviderId } from 'sql/parts/objectExplorer/common/objectExplorerService';
|
||||||
import { IScriptingService } from 'sql/platform/scripting/common/scriptingService';
|
import { IScriptingService } from 'sql/platform/scripting/common/scriptingService';
|
||||||
import { IAdminService } from 'sql/workbench/services/admin/common/adminService';
|
import { IAdminService } from 'sql/workbench/services/admin/common/adminService';
|
||||||
import { IJobManagementService } from 'sql/platform/jobManagement/common/interfaces';
|
import { IJobManagementService } from 'sql/platform/jobManagement/common/interfaces';
|
||||||
@@ -231,6 +231,7 @@ export class MainThreadDataProtocol implements MainThreadDataProtocolShape {
|
|||||||
public $registerObjectExplorerProvider(providerId: string, handle: number): TPromise<any> {
|
public $registerObjectExplorerProvider(providerId: string, handle: number): TPromise<any> {
|
||||||
const self = this;
|
const self = this;
|
||||||
this._objectExplorerService.registerProvider(providerId, <sqlops.ObjectExplorerProvider>{
|
this._objectExplorerService.registerProvider(providerId, <sqlops.ObjectExplorerProvider>{
|
||||||
|
providerId: providerId,
|
||||||
createNewSession(connection: sqlops.ConnectionInfo): Thenable<sqlops.ObjectExplorerSessionResponse> {
|
createNewSession(connection: sqlops.ConnectionInfo): Thenable<sqlops.ObjectExplorerSessionResponse> {
|
||||||
return self._proxy.$createObjectExplorerSession(handle, connection);
|
return self._proxy.$createObjectExplorerSession(handle, connection);
|
||||||
},
|
},
|
||||||
@@ -251,6 +252,32 @@ export class MainThreadDataProtocol implements MainThreadDataProtocolShape {
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public $registerObjectExplorerNodeProvider(providerId: string, supportedProviderId: string, group: string, handle: number): TPromise<any> {
|
||||||
|
const self = this;
|
||||||
|
this._objectExplorerService.registerNodeProvider(<sqlops.ObjectExplorerNodeProvider> {
|
||||||
|
supportedProviderId: supportedProviderId,
|
||||||
|
providerId: providerId,
|
||||||
|
group: group,
|
||||||
|
expandNode(nodeInfo: sqlops.ExpandNodeInfo): Thenable<boolean> {
|
||||||
|
return self._proxy.$expandObjectExplorerNode(handle, nodeInfo);
|
||||||
|
},
|
||||||
|
refreshNode(nodeInfo: sqlops.ExpandNodeInfo): Thenable<boolean> {
|
||||||
|
return self._proxy.$refreshObjectExplorerNode(handle, nodeInfo);
|
||||||
|
},
|
||||||
|
findNodes(findNodesInfo: sqlops.FindNodesInfo): Thenable<sqlops.ObjectExplorerFindNodesResponse> {
|
||||||
|
return self._proxy.$findNodes(handle, findNodesInfo);
|
||||||
|
},
|
||||||
|
handleSessionOpen(session: sqlops.ObjectExplorerSession): Thenable<boolean> {
|
||||||
|
return self._proxy.$createObjectExplorerNodeProviderSession(handle, session);
|
||||||
|
},
|
||||||
|
handleSessionClose(closeSessionInfo: sqlops.ObjectExplorerCloseSessionInfo): void {
|
||||||
|
return self._proxy.$handleSessionClose(handle, closeSessionInfo);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
public $registerTaskServicesProvider(providerId: string, handle: number): TPromise<any> {
|
public $registerTaskServicesProvider(providerId: string, handle: number): TPromise<any> {
|
||||||
const self = this;
|
const self = this;
|
||||||
this._taskService.registerProvider(providerId, <sqlops.TaskServicesProvider>{
|
this._taskService.registerProvider(providerId, <sqlops.TaskServicesProvider>{
|
||||||
@@ -474,8 +501,9 @@ export class MainThreadDataProtocol implements MainThreadDataProtocolShape {
|
|||||||
this._objectExplorerService.onSessionDisconnected(handle, sessionResponse);
|
this._objectExplorerService.onSessionDisconnected(handle, sessionResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
public $onObjectExplorerNodeExpanded(handle: number, expandResponse: sqlops.ObjectExplorerExpandInfo): void {
|
public $onObjectExplorerNodeExpanded(providerId: string, expandResponse: sqlops.ObjectExplorerExpandInfo): void {
|
||||||
this._objectExplorerService.onNodeExpanded(handle, expandResponse);
|
let expandInfo: NodeExpandInfoWithProviderId = Object.assign({ providerId: providerId }, expandResponse);
|
||||||
|
this._objectExplorerService.onNodeExpanded(expandInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
//Tasks handlers
|
//Tasks handlers
|
||||||
|
|||||||
@@ -82,4 +82,8 @@ export class MainThreadObjectExplorer implements MainThreadObjectExplorerShape {
|
|||||||
public $getNodeActions(connectionId: string, nodePath: string): Thenable<string[]> {
|
public $getNodeActions(connectionId: string, nodePath: string): Thenable<string[]> {
|
||||||
return this._objectExplorerService.getNodeActions(connectionId, nodePath);
|
return this._objectExplorerService.getNodeActions(connectionId, nodePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public $getSessionConnectionProfile(sessionId: string): Thenable<sqlops.IConnectionProfile> {
|
||||||
|
return Promise.resolve(this._objectExplorerService.getSessionConnectionProfile(sessionId));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -120,6 +120,9 @@ export function createApiFactory(
|
|||||||
getCredentials(connectionId: string): Thenable<{ [name: string]: string }> {
|
getCredentials(connectionId: string): Thenable<{ [name: string]: string }> {
|
||||||
return extHostConnectionManagement.$getCredentials(connectionId);
|
return extHostConnectionManagement.$getCredentials(connectionId);
|
||||||
},
|
},
|
||||||
|
getServerInfo(connectionId: string): Thenable<sqlops.ServerInfo> {
|
||||||
|
return extHostConnectionManagement.$getServerInfo(connectionId);
|
||||||
|
},
|
||||||
openConnectionDialog(providers?: string[], initialConnectionProfile?: sqlops.IConnectionProfile, connectionCompletionOptions?: sqlops.IConnectionCompletionOptions): Thenable<sqlops.connection.Connection> {
|
openConnectionDialog(providers?: string[], initialConnectionProfile?: sqlops.IConnectionProfile, connectionCompletionOptions?: sqlops.IConnectionCompletionOptions): Thenable<sqlops.connection.Connection> {
|
||||||
return extHostConnectionManagement.$openConnectionDialog(providers, initialConnectionProfile, connectionCompletionOptions);
|
return extHostConnectionManagement.$openConnectionDialog(providers, initialConnectionProfile, connectionCompletionOptions);
|
||||||
},
|
},
|
||||||
@@ -160,6 +163,9 @@ export function createApiFactory(
|
|||||||
},
|
},
|
||||||
getNodeActions(connectionId: string, nodePath: string): Thenable<string[]> {
|
getNodeActions(connectionId: string, nodePath: string): Thenable<string[]> {
|
||||||
return extHostObjectExplorer.$getNodeActions(connectionId, nodePath);
|
return extHostObjectExplorer.$getNodeActions(connectionId, nodePath);
|
||||||
|
},
|
||||||
|
getSessionConnectionProfile(sessionId: string): Thenable<sqlops.IConnectionProfile> {
|
||||||
|
return extHostObjectExplorer.$getSessionConnectionProfile(sessionId);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -238,12 +244,20 @@ export function createApiFactory(
|
|||||||
}
|
}
|
||||||
|
|
||||||
provider.registerOnExpandCompleted((response: sqlops.ObjectExplorerExpandInfo) => {
|
provider.registerOnExpandCompleted((response: sqlops.ObjectExplorerExpandInfo) => {
|
||||||
extHostDataProvider.$onObjectExplorerNodeExpanded(provider.handle, response);
|
extHostDataProvider.$onObjectExplorerNodeExpanded(provider.providerId, response);
|
||||||
});
|
});
|
||||||
|
|
||||||
return extHostDataProvider.$registerObjectExplorerProvider(provider);
|
return extHostDataProvider.$registerObjectExplorerProvider(provider);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let registerObjectExplorerNodeProvider = (provider: sqlops.ObjectExplorerNodeProvider): vscode.Disposable => {
|
||||||
|
provider.registerOnExpandCompleted((response: sqlops.ObjectExplorerExpandInfo) => {
|
||||||
|
extHostDataProvider.$onObjectExplorerNodeExpanded(provider.providerId, response);
|
||||||
|
});
|
||||||
|
|
||||||
|
return extHostDataProvider.$registerObjectExplorerNodeProvider(provider);
|
||||||
|
};
|
||||||
|
|
||||||
let registerTaskServicesProvider = (provider: sqlops.TaskServicesProvider): vscode.Disposable => {
|
let registerTaskServicesProvider = (provider: sqlops.TaskServicesProvider): vscode.Disposable => {
|
||||||
provider.registerOnTaskCreated((response: sqlops.TaskInfo) => {
|
provider.registerOnTaskCreated((response: sqlops.TaskInfo) => {
|
||||||
extHostDataProvider.$onTaskCreated(provider.handle, response);
|
extHostDataProvider.$onTaskCreated(provider.handle, response);
|
||||||
@@ -335,6 +349,7 @@ export function createApiFactory(
|
|||||||
registerFileBrowserProvider,
|
registerFileBrowserProvider,
|
||||||
registerMetadataProvider,
|
registerMetadataProvider,
|
||||||
registerObjectExplorerProvider,
|
registerObjectExplorerProvider,
|
||||||
|
registerObjectExplorerNodeProvider,
|
||||||
registerProfilerProvider,
|
registerProfilerProvider,
|
||||||
registerRestoreProvider,
|
registerRestoreProvider,
|
||||||
registerScriptingProvider,
|
registerScriptingProvider,
|
||||||
|
|||||||
@@ -128,6 +128,10 @@ export abstract class ExtHostDataProtocolShape {
|
|||||||
|
|
||||||
$findNodes(handle: number, findNodesInfo: sqlops.FindNodesInfo): Thenable<sqlops.ObjectExplorerFindNodesResponse> { throw ni(); }
|
$findNodes(handle: number, findNodesInfo: sqlops.FindNodesInfo): Thenable<sqlops.ObjectExplorerFindNodesResponse> { throw ni(); }
|
||||||
|
|
||||||
|
$createObjectExplorerNodeProviderSession(handle: number, sessionInfo: sqlops.ObjectExplorerSession): Thenable<boolean> { throw ni(); }
|
||||||
|
|
||||||
|
$handleSessionClose(handle: number, closeSessionInfo: sqlops.ObjectExplorerCloseSessionInfo): void { throw ni(); }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tasks
|
* Tasks
|
||||||
*/
|
*/
|
||||||
@@ -507,6 +511,7 @@ export interface MainThreadDataProtocolShape extends IDisposable {
|
|||||||
$registerQueryProvider(providerId: string, handle: number): TPromise<any>;
|
$registerQueryProvider(providerId: string, handle: number): TPromise<any>;
|
||||||
$registerProfilerProvider(providerId: string, handle: number): TPromise<any>;
|
$registerProfilerProvider(providerId: string, handle: number): TPromise<any>;
|
||||||
$registerObjectExplorerProvider(providerId: string, handle: number): TPromise<any>;
|
$registerObjectExplorerProvider(providerId: string, handle: number): TPromise<any>;
|
||||||
|
$registerObjectExplorerNodeProvider(providerId: string, supportedProviderId: string, group: string, handle: number): TPromise<any>;
|
||||||
$registerMetadataProvider(providerId: string, handle: number): TPromise<any>;
|
$registerMetadataProvider(providerId: string, handle: number): TPromise<any>;
|
||||||
$registerTaskServicesProvider(providerId: string, handle: number): TPromise<any>;
|
$registerTaskServicesProvider(providerId: string, handle: number): TPromise<any>;
|
||||||
$registerFileBrowserProvider(providerId: string, handle: number): TPromise<any>;
|
$registerFileBrowserProvider(providerId: string, handle: number): TPromise<any>;
|
||||||
@@ -526,7 +531,7 @@ export interface MainThreadDataProtocolShape extends IDisposable {
|
|||||||
$onQueryMessage(handle: number, message: sqlops.QueryExecuteMessageParams): void;
|
$onQueryMessage(handle: number, message: sqlops.QueryExecuteMessageParams): void;
|
||||||
$onObjectExplorerSessionCreated(handle: number, message: sqlops.ObjectExplorerSession): void;
|
$onObjectExplorerSessionCreated(handle: number, message: sqlops.ObjectExplorerSession): void;
|
||||||
$onObjectExplorerSessionDisconnected(handle: number, message: sqlops.ObjectExplorerSession): void;
|
$onObjectExplorerSessionDisconnected(handle: number, message: sqlops.ObjectExplorerSession): void;
|
||||||
$onObjectExplorerNodeExpanded(handle: number, message: sqlops.ObjectExplorerExpandInfo): void;
|
$onObjectExplorerNodeExpanded(providerId: string, message: sqlops.ObjectExplorerExpandInfo): void;
|
||||||
$onTaskCreated(handle: number, sessionResponse: sqlops.TaskInfo): void;
|
$onTaskCreated(handle: number, sessionResponse: sqlops.TaskInfo): void;
|
||||||
$onTaskStatusChanged(handle: number, sessionResponse: sqlops.TaskProgressInfo): void;
|
$onTaskStatusChanged(handle: number, sessionResponse: sqlops.TaskProgressInfo): void;
|
||||||
$onFileBrowserOpened(handle: number, response: sqlops.FileBrowserOpenedParams): void;
|
$onFileBrowserOpened(handle: number, response: sqlops.FileBrowserOpenedParams): void;
|
||||||
@@ -548,6 +553,7 @@ export interface MainThreadConnectionManagementShape extends IDisposable {
|
|||||||
$getActiveConnections(): Thenable<sqlops.connection.Connection[]>;
|
$getActiveConnections(): Thenable<sqlops.connection.Connection[]>;
|
||||||
$getCurrentConnection(): Thenable<sqlops.connection.Connection>;
|
$getCurrentConnection(): Thenable<sqlops.connection.Connection>;
|
||||||
$getCredentials(connectionId: string): Thenable<{ [name: string]: string }>;
|
$getCredentials(connectionId: string): Thenable<{ [name: string]: string }>;
|
||||||
|
$getServerInfo(connectedId: string): Thenable<sqlops.ServerInfo>;
|
||||||
$openConnectionDialog(providers: string[], initialConnectionProfile?: sqlops.IConnectionProfile, connectionCompletionOptions?: sqlops.IConnectionCompletionOptions): Thenable<sqlops.connection.Connection>;
|
$openConnectionDialog(providers: string[], initialConnectionProfile?: sqlops.IConnectionProfile, connectionCompletionOptions?: sqlops.IConnectionCompletionOptions): Thenable<sqlops.connection.Connection>;
|
||||||
$listDatabases(connectionId: string): Thenable<string[]>;
|
$listDatabases(connectionId: string): Thenable<string[]>;
|
||||||
$getConnectionString(connectionId: string, includePassword: boolean): Thenable<string>;
|
$getConnectionString(connectionId: string, includePassword: boolean): Thenable<string>;
|
||||||
@@ -717,6 +723,7 @@ export interface MainThreadObjectExplorerShape extends IDisposable {
|
|||||||
$findNodes(connectionId: string, type: string, schema: string, name: string, database: string, parentObjectNames: string[]): Thenable<sqlops.NodeInfo[]>;
|
$findNodes(connectionId: string, type: string, schema: string, name: string, database: string, parentObjectNames: string[]): Thenable<sqlops.NodeInfo[]>;
|
||||||
$refresh(connectionId: string, nodePath: string): Thenable<sqlops.NodeInfo>;
|
$refresh(connectionId: string, nodePath: string): Thenable<sqlops.NodeInfo>;
|
||||||
$getNodeActions(connectionId: string, nodePath: string): Thenable<string[]>;
|
$getNodeActions(connectionId: string, nodePath: string): Thenable<string[]>;
|
||||||
|
$getSessionConnectionProfile(sessionId: string): Thenable<sqlops.IConnectionProfile>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ExtHostModelViewDialogShape {
|
export interface ExtHostModelViewDialogShape {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import { ObjectExplorerProviderTestService } from 'sqltest/stubs/objectExplorerP
|
|||||||
import { TestConnectionManagementService } from 'sqltest/stubs/connectionManagementService.test';
|
import { TestConnectionManagementService } from 'sqltest/stubs/connectionManagementService.test';
|
||||||
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
|
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
|
||||||
import { ConnectionProfileGroup } from 'sql/platform/connection/common/connectionProfileGroup';
|
import { ConnectionProfileGroup } from 'sql/platform/connection/common/connectionProfileGroup';
|
||||||
import { ObjectExplorerService } from 'sql/parts/objectExplorer/common/objectExplorerService';
|
import { ObjectExplorerService, NodeExpandInfoWithProviderId } from 'sql/parts/objectExplorer/common/objectExplorerService';
|
||||||
import { NodeType } from 'sql/parts/objectExplorer/common/nodeType';
|
import { NodeType } from 'sql/parts/objectExplorer/common/nodeType';
|
||||||
import { TreeNode, TreeItemCollapsibleState, ObjectExplorerCallbacks } from 'sql/parts/objectExplorer/common/treeNode';
|
import { TreeNode, TreeItemCollapsibleState, ObjectExplorerCallbacks } from 'sql/parts/objectExplorer/common/treeNode';
|
||||||
|
|
||||||
@@ -32,12 +32,13 @@ suite('SQL Object Explorer Service tests', () => {
|
|||||||
let objectExplorerSession: sqlops.ObjectExplorerSession;
|
let objectExplorerSession: sqlops.ObjectExplorerSession;
|
||||||
let objectExplorerFailedSession: sqlops.ObjectExplorerSession;
|
let objectExplorerFailedSession: sqlops.ObjectExplorerSession;
|
||||||
let objectExplorerCloseSessionResponse: sqlops.ObjectExplorerCloseSessionResponse;
|
let objectExplorerCloseSessionResponse: sqlops.ObjectExplorerCloseSessionResponse;
|
||||||
let objectExplorerExpandInfo: sqlops.ObjectExplorerExpandInfo;
|
let objectExplorerExpandInfo: NodeExpandInfoWithProviderId;
|
||||||
let objectExplorerExpandInfoRefresh: sqlops.ObjectExplorerExpandInfo;
|
let objectExplorerExpandInfoRefresh: NodeExpandInfoWithProviderId;
|
||||||
let sessionId = '1234';
|
let sessionId = '1234';
|
||||||
let failedSessionId = '12345';
|
let failedSessionId = '12345';
|
||||||
let numberOfFailedSession: number = 0;
|
let numberOfFailedSession: number = 0;
|
||||||
let serverTreeView: TypeMoq.Mock<ServerTreeView>;
|
let serverTreeView: TypeMoq.Mock<ServerTreeView>;
|
||||||
|
const providerId = 'MSSQL';
|
||||||
|
|
||||||
setup(() => {
|
setup(() => {
|
||||||
|
|
||||||
@@ -105,14 +106,16 @@ suite('SQL Object Explorer Service tests', () => {
|
|||||||
sessionId: sessionId,
|
sessionId: sessionId,
|
||||||
nodes: [NodeInfoTable1, NodeInfoTable2],
|
nodes: [NodeInfoTable1, NodeInfoTable2],
|
||||||
errorMessage: '',
|
errorMessage: '',
|
||||||
nodePath: objectExplorerSession.rootNode.nodePath
|
nodePath: objectExplorerSession.rootNode.nodePath,
|
||||||
|
providerId: providerId
|
||||||
};
|
};
|
||||||
|
|
||||||
objectExplorerExpandInfoRefresh = {
|
objectExplorerExpandInfoRefresh = {
|
||||||
sessionId: sessionId,
|
sessionId: sessionId,
|
||||||
nodes: [NodeInfoTable1, NodeInfoTable3],
|
nodes: [NodeInfoTable1, NodeInfoTable3],
|
||||||
errorMessage: '',
|
errorMessage: '',
|
||||||
nodePath: objectExplorerSession.rootNode.nodePath
|
nodePath: objectExplorerSession.rootNode.nodePath,
|
||||||
|
providerId: providerId
|
||||||
};
|
};
|
||||||
let response: sqlops.ObjectExplorerSessionResponse = {
|
let response: sqlops.ObjectExplorerSessionResponse = {
|
||||||
sessionId: objectExplorerSession.sessionId
|
sessionId: objectExplorerSession.sessionId
|
||||||
@@ -126,9 +129,8 @@ suite('SQL Object Explorer Service tests', () => {
|
|||||||
sqlOEProvider.callBase = true;
|
sqlOEProvider.callBase = true;
|
||||||
|
|
||||||
let onCapabilitiesRegistered = new Emitter<string>();
|
let onCapabilitiesRegistered = new Emitter<string>();
|
||||||
|
|
||||||
let sqlProvider = {
|
let sqlProvider = {
|
||||||
providerId: 'MSSQL',
|
providerId: providerId,
|
||||||
displayName: 'MSSQL',
|
displayName: 'MSSQL',
|
||||||
connectionOptions: [
|
connectionOptions: [
|
||||||
{
|
{
|
||||||
@@ -279,10 +281,10 @@ suite('SQL Object Explorer Service tests', () => {
|
|||||||
resolve(failedResponse);
|
resolve(failedResponse);
|
||||||
}));
|
}));
|
||||||
sqlOEProvider.setup(x => x.expandNode(TypeMoq.It.isAny())).callback(() => {
|
sqlOEProvider.setup(x => x.expandNode(TypeMoq.It.isAny())).callback(() => {
|
||||||
objectExplorerService.onNodeExpanded(1, objectExplorerExpandInfo);
|
objectExplorerService.onNodeExpanded(objectExplorerExpandInfo);
|
||||||
}).returns(() => TPromise.as(true));
|
}).returns(() => TPromise.as(true));
|
||||||
sqlOEProvider.setup(x => x.refreshNode(TypeMoq.It.isAny())).callback(() => {
|
sqlOEProvider.setup(x => x.refreshNode(TypeMoq.It.isAny())).callback(() => {
|
||||||
objectExplorerService.onNodeExpanded(1, objectExplorerExpandInfoRefresh);
|
objectExplorerService.onNodeExpanded(objectExplorerExpandInfoRefresh);
|
||||||
}).returns(() => TPromise.as(true));
|
}).returns(() => TPromise.as(true));
|
||||||
sqlOEProvider.setup(x => x.closeSession(TypeMoq.It.isAny())).returns(() => TPromise.as(objectExplorerCloseSessionResponse));
|
sqlOEProvider.setup(x => x.closeSession(TypeMoq.It.isAny())).returns(() => TPromise.as(objectExplorerCloseSessionResponse));
|
||||||
|
|
||||||
@@ -539,7 +541,8 @@ suite('SQL Object Explorer Service tests', () => {
|
|||||||
sessionId: sessionId,
|
sessionId: sessionId,
|
||||||
nodes: [],
|
nodes: [],
|
||||||
errorMessage: '',
|
errorMessage: '',
|
||||||
nodePath: table1NodePath
|
nodePath: table1NodePath,
|
||||||
|
providerId: providerId
|
||||||
};
|
};
|
||||||
serverTreeView.setup(x => x.isExpanded(TypeMoq.It.isAny())).returns(treeNode => {
|
serverTreeView.setup(x => x.isExpanded(TypeMoq.It.isAny())).returns(treeNode => {
|
||||||
return treeNode === connection || treeNode.nodePath === table1NodePath;
|
return treeNode === connection || treeNode.nodePath === table1NodePath;
|
||||||
@@ -549,7 +552,7 @@ suite('SQL Object Explorer Service tests', () => {
|
|||||||
objectExplorerService.onSessionCreated(1, objectExplorerSession);
|
objectExplorerService.onSessionCreated(1, objectExplorerSession);
|
||||||
objectExplorerService.resolveTreeNodeChildren(objectExplorerSession, objectExplorerService.getObjectExplorerNode(connection)).then(childNodes => {
|
objectExplorerService.resolveTreeNodeChildren(objectExplorerSession, objectExplorerService.getObjectExplorerNode(connection)).then(childNodes => {
|
||||||
sqlOEProvider.setup(x => x.expandNode(TypeMoq.It.isAny())).callback(() => {
|
sqlOEProvider.setup(x => x.expandNode(TypeMoq.It.isAny())).callback(() => {
|
||||||
objectExplorerService.onNodeExpanded(1, tableExpandInfo);
|
objectExplorerService.onNodeExpanded(tableExpandInfo);
|
||||||
}).returns(() => TPromise.as(true));
|
}).returns(() => TPromise.as(true));
|
||||||
let tableNode = childNodes.find(node => node.nodePath === table1NodePath);
|
let tableNode = childNodes.find(node => node.nodePath === table1NodePath);
|
||||||
objectExplorerService.resolveTreeNodeChildren(objectExplorerSession, tableNode).then(() => {
|
objectExplorerService.resolveTreeNodeChildren(objectExplorerSession, tableNode).then(() => {
|
||||||
@@ -596,7 +599,8 @@ suite('SQL Object Explorer Service tests', () => {
|
|||||||
sessionId: sessionId,
|
sessionId: sessionId,
|
||||||
nodes: [],
|
nodes: [],
|
||||||
errorMessage: '',
|
errorMessage: '',
|
||||||
nodePath: table1NodePath
|
nodePath: table1NodePath,
|
||||||
|
providerId: providerId
|
||||||
};
|
};
|
||||||
serverTreeView.setup(x => x.isExpanded(TypeMoq.It.isAny())).returns(treeNode => {
|
serverTreeView.setup(x => x.isExpanded(TypeMoq.It.isAny())).returns(treeNode => {
|
||||||
return treeNode.nodePath === table1NodePath;
|
return treeNode.nodePath === table1NodePath;
|
||||||
@@ -606,7 +610,7 @@ suite('SQL Object Explorer Service tests', () => {
|
|||||||
objectExplorerService.onSessionCreated(1, objectExplorerSession);
|
objectExplorerService.onSessionCreated(1, objectExplorerSession);
|
||||||
objectExplorerService.resolveTreeNodeChildren(objectExplorerSession, objectExplorerService.getObjectExplorerNode(connection)).then(childNodes => {
|
objectExplorerService.resolveTreeNodeChildren(objectExplorerSession, objectExplorerService.getObjectExplorerNode(connection)).then(childNodes => {
|
||||||
sqlOEProvider.setup(x => x.expandNode(TypeMoq.It.isAny())).callback(() => {
|
sqlOEProvider.setup(x => x.expandNode(TypeMoq.It.isAny())).callback(() => {
|
||||||
objectExplorerService.onNodeExpanded(1, tableExpandInfo);
|
objectExplorerService.onNodeExpanded(tableExpandInfo);
|
||||||
}).returns(() => TPromise.as(true));
|
}).returns(() => TPromise.as(true));
|
||||||
objectExplorerService.resolveTreeNodeChildren(objectExplorerSession, childNodes.find(node => node.nodePath === table1NodePath)).then(() => {
|
objectExplorerService.resolveTreeNodeChildren(objectExplorerSession, childNodes.find(node => node.nodePath === table1NodePath)).then(() => {
|
||||||
// If I check whether the table is expanded, the answer should be yes
|
// If I check whether the table is expanded, the answer should be yes
|
||||||
@@ -630,11 +634,12 @@ suite('SQL Object Explorer Service tests', () => {
|
|||||||
sessionId: sessionId,
|
sessionId: sessionId,
|
||||||
nodes: [],
|
nodes: [],
|
||||||
errorMessage: '',
|
errorMessage: '',
|
||||||
nodePath: table1NodePath
|
nodePath: table1NodePath,
|
||||||
|
providerId: providerId
|
||||||
};
|
};
|
||||||
// Set up the OE provider so that the second expand call expands the table
|
// Set up the OE provider so that the second expand call expands the table
|
||||||
sqlOEProvider.setup(x => x.expandNode(TypeMoq.It.is(nodeInfo => nodeInfo.nodePath === table1NodePath))).callback(() => {
|
sqlOEProvider.setup(x => x.expandNode(TypeMoq.It.is(nodeInfo => nodeInfo.nodePath === table1NodePath))).callback(() => {
|
||||||
objectExplorerService.onNodeExpanded(1, tableExpandInfo);
|
objectExplorerService.onNodeExpanded(tableExpandInfo);
|
||||||
}).returns(() => TPromise.as(true));
|
}).returns(() => TPromise.as(true));
|
||||||
serverTreeView.setup(x => x.setExpandedState(TypeMoq.It.isAny(), TypeMoq.It.is(state => state === TreeItemCollapsibleState.Expanded))).returns(treeNode => {
|
serverTreeView.setup(x => x.setExpandedState(TypeMoq.It.isAny(), TypeMoq.It.is(state => state === TreeItemCollapsibleState.Expanded))).returns(treeNode => {
|
||||||
if (treeNode instanceof ConnectionProfile) {
|
if (treeNode instanceof ConnectionProfile) {
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ suite('Dashboard Properties Widget Tests', () => {
|
|||||||
serverEdition: undefined,
|
serverEdition: undefined,
|
||||||
azureVersion: undefined,
|
azureVersion: undefined,
|
||||||
osVersion: undefined,
|
osVersion: undefined,
|
||||||
|
options: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
let databaseInfo = {
|
let databaseInfo = {
|
||||||
|
|||||||
@@ -254,6 +254,10 @@ export class TestConnectionManagementService implements IConnectionManagementSer
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getServerInfo(profileId: string): sqlops.ServerInfo {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
getConnectionString(connectionId: string): Thenable<string> {
|
getConnectionString(connectionId: string): Thenable<string> {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user