mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-15 10:58:31 -05:00
Code refactoring for extension testing (#10529)
* Setting up tests on import extension * -Added API wrappers for all the azdata and vscode APIs to make them easily mockable -Added some unit tests for the import extension -Some code logic separations * -added code report for the import extension in ci * Did some more code refractoring * -Added json report generation * updated vscodetestcoverage to latest version in import extension. * -remove duplicate codecoverageConfig.json
This commit is contained in:
88
extensions/import/src/common/apiWrapper.ts
Normal file
88
extensions/import/src/common/apiWrapper.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 azdata from 'azdata';
|
||||
/**
|
||||
* 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 {
|
||||
|
||||
public createOutputChannel(name: string): vscode.OutputChannel {
|
||||
return vscode.window.createOutputChannel(name);
|
||||
}
|
||||
|
||||
public getExtension(extensionId: string): vscode.Extension<any> | undefined {
|
||||
return vscode.extensions.getExtension(extensionId);
|
||||
}
|
||||
|
||||
public getUriForConnection(connectionId: string): Thenable<string> {
|
||||
return azdata.connection.getUriForConnection(connectionId);
|
||||
}
|
||||
|
||||
public getProvider<T extends azdata.DataProvider>(providerId: string, providerType: azdata.DataProviderType): T {
|
||||
return azdata.dataprotocol.getProvider<T>(providerId, providerType);
|
||||
}
|
||||
|
||||
public getCurrentConnection(): Thenable<azdata.connection.ConnectionProfile> {
|
||||
return azdata.connection.getCurrentConnection();
|
||||
}
|
||||
|
||||
public openConnectionDialog(providers?: string[]): Thenable<azdata.connection.Connection> {
|
||||
return azdata.connection.openConnectionDialog(providers);
|
||||
}
|
||||
|
||||
public showErrorMessage(message: string, ...items: string[]): Thenable<string | undefined> {
|
||||
return vscode.window.showErrorMessage(message, ...items);
|
||||
}
|
||||
|
||||
public createWizard(title: string): azdata.window.Wizard {
|
||||
return azdata.window.createWizard(title);
|
||||
}
|
||||
|
||||
public createWizardPage(title: string): azdata.window.WizardPage {
|
||||
return azdata.window.createWizardPage(title);
|
||||
}
|
||||
|
||||
public createButton(lable: string) {
|
||||
return azdata.window.createButton(lable);
|
||||
}
|
||||
|
||||
public showOpenDialog(options: vscode.OpenDialogOptions): Thenable<vscode.Uri[] | undefined> {
|
||||
return vscode.window.showOpenDialog(options);
|
||||
}
|
||||
|
||||
public getActiveConnections(): Thenable<azdata.connection.Connection[]> {
|
||||
return azdata.connection.getActiveConnections();
|
||||
}
|
||||
|
||||
public listDatabases(connectionId: string): Thenable<string[]> {
|
||||
return azdata.connection.listDatabases(connectionId);
|
||||
}
|
||||
|
||||
public openExternal(target: vscode.Uri): Thenable<boolean> {
|
||||
return vscode.env.openExternal(target);
|
||||
}
|
||||
|
||||
public getConfiguration(section?: string, resource?: vscode.Uri | null): vscode.WorkspaceConfiguration {
|
||||
return vscode.workspace.getConfiguration(section, resource);
|
||||
}
|
||||
|
||||
public registerTask(task: string, callback: azdata.tasks.ITaskHandler) {
|
||||
azdata.tasks.registerTask(task, callback);
|
||||
}
|
||||
|
||||
public getCredentials(connectionId: string) {
|
||||
return azdata.connection.getCredentials(connectionId);
|
||||
}
|
||||
|
||||
public getConnectionString(connectionId: string, includePassword: boolean) {
|
||||
return azdata.connection.getConnectionString(connectionId, includePassword);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
60
extensions/import/src/common/constants.ts
Normal file
60
extensions/import/src/common/constants.ts
Normal file
@@ -0,0 +1,60 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import * as nls from 'vscode-nls';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export const extensionConfigSectionName = 'flatFileImport';
|
||||
export const serviceName = 'Flat File Import Service';
|
||||
export const providerId = 'FlatFileImport';
|
||||
export const configLogDebugInfo = 'logDebugInfo';
|
||||
export const sqlConfigSectionName = 'sql';
|
||||
export const mssqlProvider = 'MSSQL';
|
||||
|
||||
export const supportedProviders = [mssqlProvider];
|
||||
|
||||
// Links
|
||||
export const serviceCrashLink = 'https://github.com/Microsoft/azuredatastudio/issues/2090';
|
||||
|
||||
// Tasks
|
||||
export const flatFileImportStartCommand = 'flatFileImport.start';
|
||||
|
||||
// Localized texts
|
||||
export const crashButtonText = localize('import.serviceCrashButton', "Give Feedback");
|
||||
export const serviceCrashMessageText = localize('serviceCrashMessage', "service component could not start");
|
||||
export const serverDropDownTitleText = localize('flatFileImport.serverDropdownTitle', "Server the database is in");
|
||||
export const databaseDropdownTitleText = localize('flatFileImport.databaseDropdownTitle', "Database the table is created in");
|
||||
export const browseFilesText = localize('flatFileImport.browseFiles', "Browse");
|
||||
export const openFileText = localize('flatFileImport.openFile', "Open");
|
||||
export const fileTextboxTitleText = localize('flatFileImport.fileTextboxTitle', "Location of the file to be imported");
|
||||
export const tableTextboxTitleText = localize('flatFileImport.tableTextboxTitle', "New table name");
|
||||
export const schemaTextboxTitleText = localize('flatFileImport.schemaTextboxTitle', "Table schema");
|
||||
export const importDataText = localize('flatFileImport.importData', "Import Data");
|
||||
export const nextText = localize('flatFileImport.next', "Next");
|
||||
export const columnNameText = localize('flatFileImport.columnName', "Column Name");
|
||||
export const dataTypeText = localize('flatFileImport.dataType', "Data Type");
|
||||
export const primaryKeyText = localize('flatFileImport.primaryKey', "Primary Key");
|
||||
export const allowNullsText = localize('flatFileImport.allowNulls', "Allow Nulls");
|
||||
export const successTitleText = localize('flatFileImport.prosePreviewMessage', "This operation analyzed the input file structure to generate the preview below for up to the first 50 rows.");
|
||||
export const failureTitleText = localize('flatFileImport.prosePreviewMessageFail', "This operation was unsuccessful. Please try a different input file.");
|
||||
export const refreshText = localize('flatFileImport.refresh', "Refresh");
|
||||
export const importInformationText = localize('flatFileImport.importInformation', "Import information");
|
||||
export const importStatusText = localize('flatFileImport.importStatus', "Import status");
|
||||
export const serverNameText = localize('flatFileImport.serverName', "Server name");
|
||||
export const databaseText = localize('flatFileImport.databaseName', "Database name");
|
||||
export const tableNameText = localize('flatFileImport.tableName', "Table name");
|
||||
export const tableSchemaText = localize('flatFileImport.tableSchema', "Table schema");
|
||||
export const fileImportText = localize('flatFileImport.fileImport', "File to be imported");
|
||||
export const updateText = localize('flatFileImport.success.norows', "✔ You have successfully inserted the data into a table.");
|
||||
export const needConnectionText = localize('import.needConnection', "Please connect to a server before using this wizard.");
|
||||
export const needSqlConnectionText = localize('import.needSQLConnection', "SQL Server Import extension does not support this type of connection");
|
||||
export const wizardNameText = localize('flatFileImport.wizardName', "Import flat file wizard");
|
||||
export const page1NameText = localize('flatFileImport.page1Name', "Specify Input File");
|
||||
export const page2NameText = localize('flatFileImport.page2Name', "Preview Data");
|
||||
export const page3NameText = localize('flatFileImport.page3Name', "Modify Columns");
|
||||
export const page4NameText = localize('flatFileImport.page4Name', "Summary");
|
||||
export const importNewFileText = localize('flatFileImport.importNewFile', "Import new file");
|
||||
|
||||
// SQL Queries
|
||||
export const selectSchemaQuery = `SELECT name FROM sys.schemas`;
|
||||
@@ -1,12 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
export const extensionConfigSectionName = 'flatFileImport';
|
||||
export const serviceName = 'Flat File Import Service';
|
||||
export const providerId = 'FlatFileImport';
|
||||
export const configLogDebugInfo = 'logDebugInfo';
|
||||
export const sqlConfigSectionName = 'sql';
|
||||
|
||||
export const serviceCrashLink = 'https://github.com/Microsoft/azuredatastudio/issues/2090';
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as constants from '../constants';
|
||||
import * as constants from '../common/constants';
|
||||
import * as azdata from 'azdata';
|
||||
import ControllerBase from './controllerBase';
|
||||
import * as vscode from 'vscode';
|
||||
@@ -11,14 +11,22 @@ import { FlatFileWizard } from '../wizard/flatFileWizard';
|
||||
import { ServiceClient } from '../services/serviceClient';
|
||||
import { ApiType, managerInstance } from '../services/serviceApiManager';
|
||||
import { FlatFileProvider } from '../services/contracts';
|
||||
import { ApiWrapper } from '../common/apiWrapper';
|
||||
|
||||
/**
|
||||
* The main controller class that initializes the extension
|
||||
*/
|
||||
export default class MainController extends ControllerBase {
|
||||
private _outputChannel: vscode.OutputChannel;
|
||||
private _apiWrapper: ApiWrapper;
|
||||
|
||||
public constructor(context: vscode.ExtensionContext) {
|
||||
public constructor(
|
||||
context: vscode.ExtensionContext,
|
||||
apiWrapper: ApiWrapper
|
||||
) {
|
||||
super(context);
|
||||
this._apiWrapper = apiWrapper;
|
||||
this._outputChannel = this._apiWrapper.createOutputChannel(constants.serviceName);
|
||||
}
|
||||
/**
|
||||
*/
|
||||
@@ -27,18 +35,17 @@ export default class MainController extends ControllerBase {
|
||||
|
||||
public async activate(): Promise<boolean> {
|
||||
return new Promise<boolean>(async (resolve) => {
|
||||
const outputChannel = vscode.window.createOutputChannel(constants.serviceName);
|
||||
managerInstance.onRegisteredApi<FlatFileProvider>(ApiType.FlatFileProvider)(provider => {
|
||||
this.initializeFlatFileProvider(provider);
|
||||
resolve(true);
|
||||
});
|
||||
await new ServiceClient(outputChannel).startService(this._context);
|
||||
await new ServiceClient(this._outputChannel, this._apiWrapper).startService(this._context);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
private initializeFlatFileProvider(provider: FlatFileProvider) {
|
||||
azdata.tasks.registerTask('flatFileImport.start', (profile: azdata.IConnectionProfile, ...args: any[]) => new FlatFileWizard(provider).start(profile, args));
|
||||
this._apiWrapper.registerTask(constants.flatFileImportStartCommand, (profile: azdata.IConnectionProfile, ...args: any[]) => new FlatFileWizard(provider, this._apiWrapper).start(profile, args));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,16 +7,20 @@ import * as vscode from 'vscode';
|
||||
|
||||
import ControllerBase from './controllers/controllerBase';
|
||||
import MainController from './controllers/mainController';
|
||||
import { ApiWrapper } from './common/apiWrapper';
|
||||
|
||||
let controllers: ControllerBase[] = [];
|
||||
|
||||
export async function activate(context: vscode.ExtensionContext): Promise<boolean> {
|
||||
export async function activate(context: vscode.ExtensionContext): Promise<void> {
|
||||
|
||||
let apiWrapper = new ApiWrapper();
|
||||
|
||||
// Start the main controller
|
||||
let mainController = new MainController(context);
|
||||
let mainController = new MainController(context, apiWrapper);
|
||||
controllers.push(mainController);
|
||||
context.subscriptions.push(mainController);
|
||||
|
||||
await mainController.activate();
|
||||
return true;
|
||||
}
|
||||
|
||||
export function deactivate() {
|
||||
|
||||
@@ -13,31 +13,28 @@ import * as path from 'path';
|
||||
import { EventAndListener } from 'eventemitter2';
|
||||
|
||||
import { Telemetry, LanguageClientErrorHandler } from './telemetry';
|
||||
import * as Constants from '../constants';
|
||||
import * as Constants from '../common/constants';
|
||||
import { TelemetryFeature, FlatFileImportFeature } from './features';
|
||||
import { promises as fs } from 'fs';
|
||||
import { ApiWrapper } from '../common/apiWrapper';
|
||||
|
||||
export class ServiceClient {
|
||||
private statusView: vscode.StatusBarItem;
|
||||
|
||||
constructor(private outputChannel: vscode.OutputChannel) {
|
||||
constructor(
|
||||
private outputChannel: vscode.OutputChannel,
|
||||
private _apiWrapper: ApiWrapper
|
||||
) {
|
||||
this.statusView = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left);
|
||||
}
|
||||
|
||||
public async startService(context: vscode.ExtensionContext): Promise<SqlOpsDataClient> {
|
||||
const rawConfig = await fs.readFile(path.join(context.extensionPath, 'config.json'));
|
||||
const config = JSON.parse(rawConfig.toString());
|
||||
config.installDirectory = path.join(context.extensionPath, config.installDirectory);
|
||||
config.proxy = vscode.workspace.getConfiguration('http').get('proxy');
|
||||
config.strictSSL = vscode.workspace.getConfiguration('http').get('proxyStrictSSL') || true;
|
||||
const serverdownloader = new ServerProvider(config);
|
||||
serverdownloader.eventEmitter.onAny(this.generateHandleServerProviderEvent());
|
||||
let clientOptions: ClientOptions = this.createClientOptions();
|
||||
|
||||
try {
|
||||
const installationStart = Date.now();
|
||||
let client: SqlOpsDataClient;
|
||||
let serviceBinaries = await serverdownloader.getOrDownloadServer();
|
||||
let serviceBinaries = await this.downloadBinaries(context, rawConfig);
|
||||
const installationComplete = Date.now();
|
||||
let serverOptions = this.generateServerOptions(serviceBinaries, context);
|
||||
client = new SqlOpsDataClient(Constants.serviceName, serverOptions, clientOptions);
|
||||
@@ -69,6 +66,16 @@ export class ServiceClient {
|
||||
}
|
||||
}
|
||||
|
||||
public async downloadBinaries(context: vscode.ExtensionContext, rawConfig: Buffer): Promise<string> {
|
||||
const config = JSON.parse(rawConfig.toString());
|
||||
config.installDirectory = path.join(context.extensionPath, config.installDirectory);
|
||||
config.proxy = this._apiWrapper.getConfiguration('http').get('proxy');
|
||||
config.strictSSL = this._apiWrapper.getConfiguration('http').get('proxyStrictSSL') || true;
|
||||
const serverdownloader = new ServerProvider(config);
|
||||
serverdownloader.eventEmitter.onAny(this.generateHandleServerProviderEvent());
|
||||
return serverdownloader.getOrDownloadServer();
|
||||
}
|
||||
|
||||
private createClientOptions(): ClientOptions {
|
||||
return {
|
||||
providerId: Constants.providerId,
|
||||
|
||||
@@ -6,10 +6,8 @@
|
||||
import { ErrorAction, CloseAction } from 'vscode-languageclient';
|
||||
import TelemetryReporter from 'vscode-extension-telemetry';
|
||||
import * as vscode from 'vscode';
|
||||
import * as nls from 'vscode-nls';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
import * as constants from '../constants';
|
||||
import * as constants from '../common/constants';
|
||||
import { IMessage, ITelemetryEventProperties, ITelemetryEventMeasures } from './contracts';
|
||||
|
||||
|
||||
@@ -33,12 +31,11 @@ export class LanguageClientErrorHandler {
|
||||
showOnErrorPrompt(): void {
|
||||
// TODO add telemetry
|
||||
// Telemetry.sendTelemetryEvent('SqlToolsServiceCrash');
|
||||
let crashButtonText = localize('import.serviceCrashButton', "Give Feedback");
|
||||
vscode.window.showErrorMessage(
|
||||
localize('serviceCrashMessage', "service component could not start"),
|
||||
crashButtonText
|
||||
constants.serviceCrashMessageText,
|
||||
constants.crashButtonText
|
||||
).then(action => {
|
||||
if (action && action === crashButtonText) {
|
||||
if (action && action === constants.crashButtonText) {
|
||||
vscode.env.openExternal(vscode.Uri.parse(constants.serviceCrashLink));
|
||||
}
|
||||
});
|
||||
@@ -71,21 +68,7 @@ export class LanguageClientErrorHandler {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters error paths to only include source files. Exported to support testing
|
||||
*/
|
||||
function FilterErrorPath(line: string): string | undefined {
|
||||
if (line) {
|
||||
let values: string[] = line.split('/out/');
|
||||
if (values.length <= 1) {
|
||||
// Didn't match expected format
|
||||
return line;
|
||||
} else {
|
||||
return values[1];
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
||||
export class Telemetry {
|
||||
private static reporter: TelemetryReporter;
|
||||
@@ -113,25 +96,6 @@ export class Telemetry {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a telemetry event for an exception
|
||||
*/
|
||||
public static sendTelemetryEventForException(
|
||||
err: any, methodName: string): void {
|
||||
let stackArray: string[];
|
||||
let firstLine: string = '';
|
||||
if (err !== undefined && err.stack !== undefined) {
|
||||
stackArray = err.stack.split('\n');
|
||||
if (stackArray !== undefined && stackArray.length >= 2) {
|
||||
firstLine = stackArray[1]; // The fist line is the error message and we don't want to send that telemetry event
|
||||
firstLine = FilterErrorPath(firstLine);
|
||||
}
|
||||
}
|
||||
|
||||
// Only adding the method name and the fist line of the stack trace. We don't add the error message because it might have PII
|
||||
this.sendTelemetryEvent('Exception', { methodName: methodName, errorLine: firstLine });
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a telemetry event using application insights
|
||||
*/
|
||||
|
||||
225
extensions/import/src/test/import.test.ts
Normal file
225
extensions/import/src/test/import.test.ts
Normal file
@@ -0,0 +1,225 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'mocha';
|
||||
import * as TypeMoq from 'typemoq';
|
||||
import * as azdata from 'azdata';
|
||||
import { FlatFileWizard } from '../wizard/flatFileWizard';
|
||||
import { ImportDataModel } from '../wizard/api/models';
|
||||
import { ApiWrapper } from '../common/apiWrapper';
|
||||
import { FileConfigPage } from '../wizard/pages/fileConfigPage';
|
||||
import * as should from 'should';
|
||||
|
||||
describe('import extension tests', function (): void {
|
||||
describe('import extension wizard pages', () => {
|
||||
|
||||
let mockFlatFileWizard: TypeMoq.IMock<FlatFileWizard>;
|
||||
let mockApiWrapper: TypeMoq.IMock<ApiWrapper>;
|
||||
let mockImportModel: TypeMoq.IMock<ImportDataModel>;
|
||||
|
||||
this.beforeEach(() => {
|
||||
mockApiWrapper = TypeMoq.Mock.ofType(ApiWrapper);
|
||||
mockFlatFileWizard = TypeMoq.Mock.ofType(FlatFileWizard, TypeMoq.MockBehavior.Loose, undefined, TypeMoq.It.isAny(), mockApiWrapper.object);
|
||||
mockImportModel = TypeMoq.Mock.ofType(TestImportDataModel, TypeMoq.MockBehavior.Loose);
|
||||
});
|
||||
|
||||
it('BasePage- getDatabaseValue returns active database first', async () => {
|
||||
// setting up the environment
|
||||
let databases: string[] = ['testdb1', 'testdb2', 'testdb3'];
|
||||
let activeDatabase: string = 'testdb2';
|
||||
|
||||
// setting up mocks
|
||||
let importPage = new FileConfigPage(mockFlatFileWizard.object, TypeMoq.It.isAny(), mockImportModel.object, TypeMoq.It.isAny(), TypeMoq.It.isAny(), mockApiWrapper.object);
|
||||
mockApiWrapper.setup(x => x.listDatabases(TypeMoq.It.isAnyString())).returns(async () => { return databases; });
|
||||
mockImportModel.object.server = {
|
||||
providerName: 'MSSQL',
|
||||
connectionId: 'testConnectionId',
|
||||
options: {}
|
||||
};
|
||||
mockImportModel.object.database = activeDatabase;
|
||||
|
||||
// Creating assert variables
|
||||
let expectedDatabaseValues = [
|
||||
{ displayName: 'testdb2', name: 'testdb2' }, // This should be the first database as it is active in the extension.
|
||||
{ displayName: 'testdb1', name: 'testdb1' },
|
||||
{ displayName: 'testdb3', name: 'testdb3' }
|
||||
];
|
||||
|
||||
let actualDatabaseValues = await importPage.getDatabaseValues();
|
||||
should(expectedDatabaseValues).deepEqual(actualDatabaseValues);
|
||||
});
|
||||
|
||||
it('BasePage- getServerValue returns null on no active connection', async () => {
|
||||
|
||||
let importPage = new FileConfigPage(mockFlatFileWizard.object, TypeMoq.It.isAny(), mockImportModel.object, TypeMoq.It.isAny(), TypeMoq.It.isAny(), mockApiWrapper.object);
|
||||
|
||||
// mocking getActive connection to return null
|
||||
mockApiWrapper.setup(x => x.getActiveConnections()).returns(async () => { return undefined; });
|
||||
|
||||
let serverValues = await importPage.getServerValues();
|
||||
|
||||
should(serverValues).undefined();
|
||||
|
||||
// mocking getActive connection returns empty array
|
||||
mockApiWrapper.setup(x => x.getActiveConnections()).returns(async () => { return [] as azdata.connection.Connection[]; });
|
||||
|
||||
serverValues = await importPage.getServerValues();
|
||||
should(serverValues).undefined();
|
||||
});
|
||||
|
||||
it('BasePage- getServerValue return active server value first', async () => {
|
||||
// settign up the enviornment
|
||||
let testActiveConnections: azdata.connection.Connection[] = [
|
||||
{
|
||||
providerName: 'MSSQL',
|
||||
connectionId: 'testConnection1Id',
|
||||
options: {
|
||||
user: 'testcon1user',
|
||||
server: 'testcon1server'
|
||||
}
|
||||
},
|
||||
{
|
||||
providerName: 'MSSQL',
|
||||
connectionId: 'testConnection2Id',
|
||||
options: {
|
||||
user: 'testcon2user',
|
||||
server: 'testcon2server'
|
||||
}
|
||||
},
|
||||
{
|
||||
providerName: 'PGSQL',
|
||||
connectionId: 'testConnection3Id',
|
||||
options: {
|
||||
user: null, // setting it null to check if function return user as 'default
|
||||
server: 'testcon3server'
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
let importPage = new FileConfigPage(mockFlatFileWizard.object, TypeMoq.It.isAny(), mockImportModel.object, TypeMoq.It.isAny(), TypeMoq.It.isAny(), mockApiWrapper.object);
|
||||
mockApiWrapper.setup(x => x.getActiveConnections()).returns(async () => { return testActiveConnections; });
|
||||
mockImportModel.object.server = ImportTestUtils.getTestServer();
|
||||
|
||||
let expectedConnectionValues = [
|
||||
{
|
||||
connection: testActiveConnections[1],
|
||||
displayName: 'testcon2server (testcon2user)',
|
||||
name: 'testConnection2Id'
|
||||
},
|
||||
{
|
||||
connection: testActiveConnections[0],
|
||||
displayName: 'testcon1server (testcon1user)',
|
||||
name: 'testConnection1Id'
|
||||
},
|
||||
{
|
||||
connection: testActiveConnections[2],
|
||||
displayName: 'testcon3server (default)',
|
||||
name: 'testConnection3Id'
|
||||
}
|
||||
];
|
||||
let actualConnectionValues = await importPage.getServerValues();
|
||||
should(expectedConnectionValues).deepEqual(actualConnectionValues);
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
describe('import extension flat file wizard', () => {
|
||||
it('FlatFileWizard opens connectionDialog when there are no active connections', async () => {
|
||||
let testConnection: azdata.connection.Connection = {
|
||||
providerName: 'MSSQL',
|
||||
connectionId: 'testConnectionId',
|
||||
options: {}
|
||||
};
|
||||
|
||||
let mockApiWrapper: TypeMoq.IMock<ApiWrapper> = TypeMoq.Mock.ofType(ApiWrapper);
|
||||
// There is no current connection.
|
||||
mockApiWrapper.setup(x => x.getCurrentConnection()).returns(() => { return undefined; });
|
||||
|
||||
|
||||
// openConnectionDialog returns a test connection
|
||||
mockApiWrapper.setup(x => x.openConnectionDialog(TypeMoq.It.isAny())).returns(async () => { return testConnection; });
|
||||
|
||||
let testFlatFileWizard = new FlatFileWizard(TypeMoq.It.isAny(), mockApiWrapper.object);
|
||||
|
||||
await testFlatFileWizard.getConnectionId();
|
||||
|
||||
// openConnectionDialog will be called once
|
||||
mockApiWrapper.verify(x => x.openConnectionDialog(TypeMoq.It.isAny()), TypeMoq.Times.once());
|
||||
|
||||
});
|
||||
|
||||
it('FlatFileWizard- shows error message when an invalid connection is selected', async () => {
|
||||
let mockApiWrapper: TypeMoq.IMock<ApiWrapper> = TypeMoq.Mock.ofType(ApiWrapper);
|
||||
// The active connection doesn't have a valid Provider
|
||||
let testConnectionProfile: azdata.connection.ConnectionProfile = ImportTestUtils.getTestConnectionProfile();
|
||||
mockApiWrapper.setup(x => x.getCurrentConnection()).returns(() => { return Promise.resolve(testConnectionProfile); });
|
||||
mockApiWrapper.setup(x => x.openConnectionDialog(TypeMoq.It.isAny())).returns(() => { return undefined; });
|
||||
|
||||
let testFlatFileWizard = new FlatFileWizard(TypeMoq.It.isAny(), mockApiWrapper.object);
|
||||
|
||||
await testFlatFileWizard.getConnectionId();
|
||||
|
||||
mockApiWrapper.verify(x => x.showErrorMessage(TypeMoq.It.isAny()), TypeMoq.Times.once());
|
||||
|
||||
});
|
||||
|
||||
it('FlatFileWizard- shows error message when no connection is selected', async () => {
|
||||
let mockApiWrapper: TypeMoq.IMock<ApiWrapper> = TypeMoq.Mock.ofType(ApiWrapper);
|
||||
// The active connection doesn't have a valid Provider
|
||||
mockApiWrapper.setup(x => x.getCurrentConnection()).returns(() => { return undefined; });
|
||||
mockApiWrapper.setup(x => x.openConnectionDialog(TypeMoq.It.isAny())).returns(() => { return undefined; });
|
||||
|
||||
let testFlatFileWizard = new FlatFileWizard(TypeMoq.It.isAny(), mockApiWrapper.object);
|
||||
|
||||
await testFlatFileWizard.getConnectionId();
|
||||
|
||||
mockApiWrapper.verify(x => x.showErrorMessage(TypeMoq.It.isAny()), TypeMoq.Times.once());
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
export class ImportTestUtils {
|
||||
|
||||
public static getTestServer(): azdata.connection.Connection {
|
||||
return {
|
||||
providerName: 'MSSQL',
|
||||
connectionId: 'testConnection2Id',
|
||||
options: {}
|
||||
};
|
||||
}
|
||||
|
||||
public static getTestConnectionProfile(): azdata.connection.ConnectionProfile {
|
||||
return {
|
||||
providerId: 'InvalidProvider',
|
||||
databaseName: 'databaseName',
|
||||
serverName: 'testServerName',
|
||||
connectionId: 'testConnectionId',
|
||||
groupId: 'testGroupId',
|
||||
connectionName: 'testConnectionName',
|
||||
userName: 'testUserName',
|
||||
password: 'testPassword',
|
||||
authenticationType: 'testAuthenticationType',
|
||||
savePassword: true,
|
||||
saveProfile: true,
|
||||
groupFullName: 'testGroupFullName',
|
||||
options: {}
|
||||
} as azdata.connection.ConnectionProfile;
|
||||
}
|
||||
}
|
||||
|
||||
export class TestImportDataModel implements ImportDataModel {
|
||||
server: azdata.connection.Connection;
|
||||
serverId: string;
|
||||
ownerUri: string;
|
||||
proseColumns: import("../wizard/api/models").ColumnMetadata[];
|
||||
proseDataPreview: string[][];
|
||||
database: string;
|
||||
table: string;
|
||||
schema: string;
|
||||
filePath: string;
|
||||
fileType: string;
|
||||
}
|
||||
48
extensions/import/src/test/index.ts
Normal file
48
extensions/import/src/test/index.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as path from 'path';
|
||||
const testRunner = require('vscodetestcover');
|
||||
|
||||
const suite = 'import Extension Tests';
|
||||
|
||||
const mochaOptions: any = {
|
||||
ui: 'bdd',
|
||||
useColors: true,
|
||||
timeout: 10000
|
||||
};
|
||||
|
||||
// set relevant mocha options from the environment
|
||||
if (process.env.ADS_TEST_GREP) {
|
||||
mochaOptions.grep = process.env.ADS_TEST_GREP;
|
||||
console.log(`setting options.grep to: ${mochaOptions.grep}`);
|
||||
}
|
||||
if (process.env.ADS_TEST_INVERT_GREP) {
|
||||
mochaOptions.invert = parseInt(process.env.ADS_TEST_INVERT_GREP);
|
||||
console.log(`setting options.invert to: ${mochaOptions.invert}`);
|
||||
}
|
||||
if (process.env.ADS_TEST_TIMEOUT) {
|
||||
mochaOptions.timeout = parseInt(process.env.ADS_TEST_TIMEOUT);
|
||||
console.log(`setting options.timeout to: ${mochaOptions.timeout}`);
|
||||
}
|
||||
if (process.env.ADS_TEST_RETRIES) {
|
||||
mochaOptions.retries = parseInt(process.env.ADS_TEST_RETRIES);
|
||||
console.log(`setting options.retries to: ${mochaOptions.retries}`);
|
||||
}
|
||||
|
||||
if (process.env.BUILD_ARTIFACTSTAGINGDIRECTORY) {
|
||||
mochaOptions.reporter = 'mocha-multi-reporters';
|
||||
mochaOptions.reporterOptions = {
|
||||
reporterEnabled: 'spec, mocha-junit-reporter',
|
||||
mochaJunitReporterReporterOptions: {
|
||||
testsuitesTitle: `${suite} ${process.platform}`,
|
||||
mochaFile: path.join(process.env.BUILD_ARTIFACTSTAGINGDIRECTORY, `test-results/${process.platform}-${suite.toLowerCase().replace(/[^\w]/g, '-')}-results.xml`)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
testRunner.configure(mochaOptions, { coverConfig: '../../coverConfig.json' });
|
||||
|
||||
export = testRunner;
|
||||
@@ -5,12 +5,14 @@
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import { ImportDataModel } from './models';
|
||||
import { ApiWrapper } from '../../common/apiWrapper';
|
||||
|
||||
export abstract class BasePage {
|
||||
|
||||
protected readonly wizardPage: azdata.window.WizardPage;
|
||||
protected readonly model: ImportDataModel;
|
||||
protected readonly view: azdata.ModelView;
|
||||
protected _apiWrapper: ApiWrapper;
|
||||
|
||||
/**
|
||||
* This method constructs all the elements of the page.
|
||||
@@ -42,8 +44,8 @@ export abstract class BasePage {
|
||||
*/
|
||||
public abstract setupNavigationValidator(): void;
|
||||
|
||||
protected async getServerValues(): Promise<{ connection: azdata.connection.Connection, displayName: string, name: string }[]> {
|
||||
let cons = await azdata.connection.getActiveConnections();
|
||||
public async getServerValues(): Promise<{ connection: azdata.connection.Connection, displayName: string, name: string }[]> {
|
||||
let cons = await this._apiWrapper.getActiveConnections();
|
||||
// This user has no active connections ABORT MISSION
|
||||
if (!cons || cons.length === 0) {
|
||||
return undefined;
|
||||
@@ -90,10 +92,10 @@ export abstract class BasePage {
|
||||
return values;
|
||||
}
|
||||
|
||||
protected async getDatabaseValues(): Promise<{ displayName: string, name: string }[]> {
|
||||
public async getDatabaseValues(): Promise<{ displayName: string, name: string }[]> {
|
||||
let idx = -1;
|
||||
let count = -1;
|
||||
let values = (await azdata.connection.listDatabases(this.model.server.connectionId)).map(db => {
|
||||
let values = (await this._apiWrapper.listDatabases(this.model.server.connectionId)).map(db => {
|
||||
count++;
|
||||
if (this.model.database && db === this.model.database) {
|
||||
idx = count;
|
||||
@@ -109,10 +111,7 @@ export abstract class BasePage {
|
||||
let tmp = values[0];
|
||||
values[0] = values[idx];
|
||||
values[idx] = tmp;
|
||||
} else {
|
||||
this.deleteDatabaseValues();
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
|
||||
@@ -121,8 +120,4 @@ export abstract class BasePage {
|
||||
delete this.model.serverId;
|
||||
delete this.model.database;
|
||||
}
|
||||
|
||||
protected deleteDatabaseValues() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import * as azdata from 'azdata';
|
||||
import { FlatFileProvider } from '../../services/contracts';
|
||||
import { FlatFileWizard } from '../flatFileWizard';
|
||||
import { BasePage } from './basePage';
|
||||
import { ApiWrapper } from '../../common/apiWrapper';
|
||||
|
||||
export abstract class ImportPage extends BasePage {
|
||||
|
||||
@@ -17,12 +18,14 @@ export abstract class ImportPage extends BasePage {
|
||||
protected readonly view: azdata.ModelView;
|
||||
protected readonly provider: FlatFileProvider;
|
||||
|
||||
protected constructor(instance: FlatFileWizard, wizardPage: azdata.window.WizardPage, model: ImportDataModel, view: azdata.ModelView, provider: FlatFileProvider) {
|
||||
|
||||
constructor(instance: FlatFileWizard, wizardPage: azdata.window.WizardPage, model: ImportDataModel, view: azdata.ModelView, provider: FlatFileProvider, apiWrapper: ApiWrapper) {
|
||||
super();
|
||||
this.instance = instance;
|
||||
this.wizardPage = wizardPage;
|
||||
this.model = model;
|
||||
this.view = view;
|
||||
this.provider = provider;
|
||||
this._apiWrapper = apiWrapper;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import * as nls from 'vscode-nls';
|
||||
import * as azdata from 'azdata';
|
||||
import { FlatFileProvider } from '../services/contracts';
|
||||
import { ImportDataModel } from './api/models';
|
||||
@@ -14,16 +12,23 @@ import { FileConfigPage } from './pages/fileConfigPage';
|
||||
import { ProsePreviewPage } from './pages/prosePreviewPage';
|
||||
import { ModifyColumnsPage } from './pages/modifyColumnsPage';
|
||||
import { SummaryPage } from './pages/summaryPage';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
import { ApiWrapper } from '../common/apiWrapper';
|
||||
import * as constants from '../common/constants';
|
||||
|
||||
export class FlatFileWizard {
|
||||
private readonly provider: FlatFileProvider;
|
||||
private wizard: azdata.window.Wizard;
|
||||
public wizard: azdata.window.Wizard;
|
||||
public page1: azdata.window.WizardPage;
|
||||
public page2: azdata.window.WizardPage;
|
||||
public page3: azdata.window.WizardPage;
|
||||
public page4: azdata.window.WizardPage;
|
||||
|
||||
private importAnotherFileButton: azdata.window.Button;
|
||||
|
||||
constructor(provider: FlatFileProvider) {
|
||||
constructor(
|
||||
provider: FlatFileProvider,
|
||||
private _apiWrapper: ApiWrapper
|
||||
) {
|
||||
this.provider = provider;
|
||||
}
|
||||
|
||||
@@ -38,37 +43,24 @@ export class FlatFileWizard {
|
||||
|
||||
let pages: Map<number, ImportPage> = new Map<number, ImportPage>();
|
||||
|
||||
let currentConnection = await azdata.connection.getCurrentConnection();
|
||||
let connectionId: string = await this.getConnectionId();
|
||||
|
||||
let connectionId: string;
|
||||
|
||||
if (!currentConnection) {
|
||||
connectionId = (await azdata.connection.openConnectionDialog(['MSSQL'])).connectionId;
|
||||
if (!connectionId) {
|
||||
vscode.window.showErrorMessage(localize('import.needConnection', "Please connect to a server before using this wizard."));
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (currentConnection.providerId !== 'MSSQL') {
|
||||
vscode.window.showErrorMessage(localize('import.needSQLConnection', "SQL Server Import extension does not support this type of connection"));
|
||||
return;
|
||||
}
|
||||
connectionId = currentConnection.connectionId;
|
||||
if (!connectionId) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
model.serverId = connectionId;
|
||||
|
||||
this.wizard = azdata.window.createWizard(localize('flatFileImport.wizardName', "Import flat file wizard"));
|
||||
let page1 = azdata.window.createWizardPage(localize('flatFileImport.page1Name', "Specify Input File"));
|
||||
let page2 = azdata.window.createWizardPage(localize('flatFileImport.page2Name', "Preview Data"));
|
||||
let page3 = azdata.window.createWizardPage(localize('flatFileImport.page3Name', "Modify Columns"));
|
||||
let page4 = azdata.window.createWizardPage(localize('flatFileImport.page4Name', "Summary"));
|
||||
this.wizard = this._apiWrapper.createWizard(constants.wizardNameText);
|
||||
this.page1 = this._apiWrapper.createWizardPage(constants.page1NameText);
|
||||
this.page2 = this._apiWrapper.createWizardPage(constants.page2NameText);
|
||||
this.page3 = this._apiWrapper.createWizardPage(constants.page3NameText);
|
||||
this.page4 = this._apiWrapper.createWizardPage(constants.page4NameText);
|
||||
|
||||
let fileConfigPage: FileConfigPage;
|
||||
|
||||
page1.registerContent(async (view) => {
|
||||
fileConfigPage = new FileConfigPage(this, page1, model, view, this.provider);
|
||||
this.page1.registerContent(async (view) => {
|
||||
fileConfigPage = new FileConfigPage(this, this.page1, model, view, this.provider, this._apiWrapper);
|
||||
pages.set(0, fileConfigPage);
|
||||
await fileConfigPage.start().then(() => {
|
||||
fileConfigPage.setupNavigationValidator();
|
||||
@@ -77,29 +69,29 @@ export class FlatFileWizard {
|
||||
});
|
||||
|
||||
let prosePreviewPage: ProsePreviewPage;
|
||||
page2.registerContent(async (view) => {
|
||||
prosePreviewPage = new ProsePreviewPage(this, page2, model, view, this.provider);
|
||||
this.page2.registerContent(async (view) => {
|
||||
prosePreviewPage = new ProsePreviewPage(this, this.page2, model, view, this.provider, this._apiWrapper);
|
||||
pages.set(1, prosePreviewPage);
|
||||
await prosePreviewPage.start();
|
||||
});
|
||||
|
||||
let modifyColumnsPage: ModifyColumnsPage;
|
||||
page3.registerContent(async (view) => {
|
||||
modifyColumnsPage = new ModifyColumnsPage(this, page3, model, view, this.provider);
|
||||
this.page3.registerContent(async (view) => {
|
||||
modifyColumnsPage = new ModifyColumnsPage(this, this.page3, model, view, this.provider, this._apiWrapper);
|
||||
pages.set(2, modifyColumnsPage);
|
||||
await modifyColumnsPage.start();
|
||||
});
|
||||
|
||||
let summaryPage: SummaryPage;
|
||||
|
||||
page4.registerContent(async (view) => {
|
||||
summaryPage = new SummaryPage(this, page4, model, view, this.provider);
|
||||
this.page4.registerContent(async (view) => {
|
||||
summaryPage = new SummaryPage(this, this.page4, model, view, this.provider, this._apiWrapper);
|
||||
pages.set(3, summaryPage);
|
||||
await summaryPage.start();
|
||||
});
|
||||
|
||||
|
||||
this.importAnotherFileButton = azdata.window.createButton(localize('flatFileImport.importNewFile', "Import new file"));
|
||||
this.importAnotherFileButton = this._apiWrapper.createButton(constants.importNewFileText);
|
||||
this.importAnotherFileButton.onClick(() => {
|
||||
//TODO replace this with proper cleanup for all the pages
|
||||
this.wizard.close();
|
||||
@@ -126,11 +118,33 @@ export class FlatFileWizard {
|
||||
//not needed for this wizard
|
||||
this.wizard.generateScriptButton.hidden = true;
|
||||
|
||||
this.wizard.pages = [page1, page2, page3, page4];
|
||||
this.wizard.pages = [this.page1, this.page2, this.page3, this.page4];
|
||||
|
||||
this.wizard.open();
|
||||
}
|
||||
|
||||
public async getConnectionId(): Promise<string> {
|
||||
let currentConnection = await this._apiWrapper.getCurrentConnection();
|
||||
|
||||
let connectionId: string;
|
||||
|
||||
if (!currentConnection) {
|
||||
let connection = await this._apiWrapper.openConnectionDialog(constants.supportedProviders);
|
||||
if (!connection) {
|
||||
this._apiWrapper.showErrorMessage(constants.needConnectionText);
|
||||
return undefined;
|
||||
}
|
||||
connectionId = connection.connectionId;
|
||||
} else {
|
||||
if (currentConnection.providerId !== 'MSSQL') {
|
||||
this._apiWrapper.showErrorMessage(constants.needSqlConnectionText);
|
||||
return undefined;
|
||||
}
|
||||
connectionId = currentConnection.connectionId;
|
||||
}
|
||||
return connectionId;
|
||||
}
|
||||
|
||||
public setImportAnotherFileVisibility(visibility: boolean) {
|
||||
this.importAnotherFileButton.hidden = !visibility;
|
||||
}
|
||||
|
||||
@@ -5,13 +5,8 @@
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import * as vscode from 'vscode';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { ImportDataModel } from '../api/models';
|
||||
import { ImportPage } from '../api/importPage';
|
||||
import { FlatFileProvider } from '../../services/contracts';
|
||||
import { FlatFileWizard } from '../flatFileWizard';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
import * as constants from '../../common/constants';
|
||||
|
||||
export class FileConfigPage extends ImportPage {
|
||||
|
||||
@@ -28,10 +23,6 @@ export class FileConfigPage extends ImportPage {
|
||||
|
||||
private tableNames: string[] = [];
|
||||
|
||||
public constructor(instance: FlatFileWizard, wizardPage: azdata.window.WizardPage, model: ImportDataModel, view: azdata.ModelView, provider: FlatFileProvider) {
|
||||
super(instance, wizardPage, model, view, provider);
|
||||
}
|
||||
|
||||
async start(): Promise<boolean> {
|
||||
let schemaComponent = await this.createSchemaDropdown();
|
||||
let tableNameComponent = await this.createTableNameBox();
|
||||
@@ -96,7 +87,7 @@ export class FileConfigPage extends ImportPage {
|
||||
|
||||
return {
|
||||
component: this.serverDropdown,
|
||||
title: localize('flatFileImport.serverDropdownTitle', "Server the database is in")
|
||||
title: constants.serverDropDownTitleText
|
||||
};
|
||||
}
|
||||
|
||||
@@ -124,8 +115,8 @@ export class FileConfigPage extends ImportPage {
|
||||
this.databaseDropdown.onValueChanged(async (db) => {
|
||||
this.model.database = (<azdata.CategoryValue>this.databaseDropdown.value).name;
|
||||
//this.populateTableNames();
|
||||
let connectionProvider = azdata.dataprotocol.getProvider<azdata.ConnectionProvider>(this.model.server.providerName, azdata.DataProviderType.ConnectionProvider);
|
||||
let connectionUri = await azdata.connection.getUriForConnection(this.model.server.connectionId);
|
||||
let connectionProvider = this._apiWrapper.getProvider<azdata.ConnectionProvider>(this.model.server.providerName, azdata.DataProviderType.ConnectionProvider);
|
||||
let connectionUri = await this._apiWrapper.getUriForConnection(this.model.server.connectionId);
|
||||
connectionProvider.changeDatabase(connectionUri, this.model.database);
|
||||
this.populateSchemaDropdown();
|
||||
});
|
||||
@@ -134,7 +125,7 @@ export class FileConfigPage extends ImportPage {
|
||||
|
||||
return {
|
||||
component: this.databaseLoader,
|
||||
title: localize('flatFileImport.databaseDropdownTitle', "Database the table is created in")
|
||||
title: constants.databaseDropdownTitleText
|
||||
};
|
||||
}
|
||||
|
||||
@@ -178,7 +169,7 @@ export class FileConfigPage extends ImportPage {
|
||||
required: true
|
||||
}).component();
|
||||
this.fileButton = this.view.modelBuilder.button().withProperties({
|
||||
label: localize('flatFileImport.browseFiles', "Browse"),
|
||||
label: constants.browseFilesText,
|
||||
}).component();
|
||||
|
||||
this.fileButton.onDidClick(async (click) => {
|
||||
@@ -187,7 +178,7 @@ export class FileConfigPage extends ImportPage {
|
||||
canSelectFiles: true,
|
||||
canSelectFolders: false,
|
||||
canSelectMany: false,
|
||||
openLabel: localize('flatFileImport.openFile', "Open"),
|
||||
openLabel: constants.openFileText,
|
||||
filters: {
|
||||
'CSV/TXT Files': ['csv', 'txt'],
|
||||
'All Files': ['*']
|
||||
@@ -227,7 +218,7 @@ export class FileConfigPage extends ImportPage {
|
||||
|
||||
return {
|
||||
component: this.fileTextBox,
|
||||
title: localize('flatFileImport.fileTextboxTitle', "Location of the file to be imported"),
|
||||
title: constants.fileTextboxTitleText,
|
||||
actions: [this.fileButton]
|
||||
};
|
||||
}
|
||||
@@ -256,7 +247,7 @@ export class FileConfigPage extends ImportPage {
|
||||
|
||||
return {
|
||||
component: this.tableNameTextBox,
|
||||
title: localize('flatFileImport.tableTextboxTitle', "New table name"),
|
||||
title: constants.tableTextboxTitleText,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -274,20 +265,31 @@ export class FileConfigPage extends ImportPage {
|
||||
|
||||
return {
|
||||
component: this.schemaLoader,
|
||||
title: localize('flatFileImport.schemaTextboxTitle', "Table schema"),
|
||||
title: constants.schemaTextboxTitleText,
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
private async populateSchemaDropdown(): Promise<boolean> {
|
||||
public async populateSchemaDropdown(): Promise<boolean> {
|
||||
this.schemaLoader.loading = true;
|
||||
|
||||
let connectionUri = await azdata.connection.getUriForConnection(this.model.server.connectionId);
|
||||
let queryProvider = azdata.dataprotocol.getProvider<azdata.QueryProvider>(this.model.server.providerName, azdata.DataProviderType.QueryProvider);
|
||||
let values = await this.getSchemaValues();
|
||||
|
||||
const query = `SELECT name FROM sys.schemas`;
|
||||
this.model.schema = values[0].name;
|
||||
|
||||
let results = await queryProvider.runQueryAndReturn(connectionUri, query);
|
||||
this.schemaDropdown.updateProperties({
|
||||
values: values
|
||||
});
|
||||
|
||||
this.schemaLoader.loading = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public async getSchemaValues(): Promise<{ displayName: string, name: string }[]> {
|
||||
let connectionUri = await this._apiWrapper.getUriForConnection(this.model.server.connectionId);
|
||||
let queryProvider = this._apiWrapper.getProvider<azdata.QueryProvider>(this.model.server.providerName, azdata.DataProviderType.QueryProvider);
|
||||
|
||||
let results = await queryProvider.runQueryAndReturn(connectionUri, constants.selectSchemaQuery);
|
||||
|
||||
let idx = -1;
|
||||
let count = -1;
|
||||
@@ -311,15 +313,7 @@ export class FileConfigPage extends ImportPage {
|
||||
values[0] = values[idx];
|
||||
values[idx] = tmp;
|
||||
}
|
||||
|
||||
this.model.schema = values[0].name;
|
||||
|
||||
this.schemaDropdown.updateProperties({
|
||||
values: values
|
||||
});
|
||||
|
||||
this.schemaLoader.loading = false;
|
||||
return true;
|
||||
return values;
|
||||
}
|
||||
|
||||
protected deleteServerValues() {
|
||||
|
||||
@@ -4,13 +4,9 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { ColumnMetadata, ImportDataModel } from '../api/models';
|
||||
import { ColumnMetadata } from '../api/models';
|
||||
import { ImportPage } from '../api/importPage';
|
||||
import { FlatFileProvider } from '../../services/contracts';
|
||||
import { FlatFileWizard } from '../flatFileWizard';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
import * as constants from '../../common/constants';
|
||||
|
||||
export class ModifyColumnsPage extends ImportPage {
|
||||
private readonly categoryValues = [
|
||||
@@ -54,11 +50,6 @@ export class ModifyColumnsPage extends ImportPage {
|
||||
private text: azdata.TextComponent;
|
||||
private form: azdata.FormContainer;
|
||||
|
||||
public constructor(instance: FlatFileWizard, wizardPage: azdata.window.WizardPage, model: ImportDataModel, view: azdata.ModelView, provider: FlatFileProvider) {
|
||||
super(instance, wizardPage, model, view, provider);
|
||||
}
|
||||
|
||||
|
||||
private static convertMetadata(column: ColumnMetadata): any[] {
|
||||
return [column.columnName, column.dataType, false, column.nullable];
|
||||
}
|
||||
@@ -105,20 +96,20 @@ export class ModifyColumnsPage extends ImportPage {
|
||||
async onPageEnter(): Promise<boolean> {
|
||||
this.loading.loading = true;
|
||||
await this.populateTable();
|
||||
this.instance.changeNextButtonLabel(localize('flatFileImport.importData', "Import Data"));
|
||||
this.instance.changeNextButtonLabel(constants.importDataText);
|
||||
this.loading.loading = false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async onPageLeave(): Promise<boolean> {
|
||||
this.instance.changeNextButtonLabel(localize('flatFileImport.next', "Next"));
|
||||
this.instance.changeNextButtonLabel(constants.nextText);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
async cleanup(): Promise<boolean> {
|
||||
delete this.model.proseColumns;
|
||||
this.instance.changeNextButtonLabel(localize('flatFileImport.next', "Next"));
|
||||
this.instance.changeNextButtonLabel(constants.nextText);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -139,23 +130,23 @@ export class ModifyColumnsPage extends ImportPage {
|
||||
this.table.updateProperties({
|
||||
height: 400,
|
||||
columns: [{
|
||||
displayName: localize('flatFileImport.columnName', "Column Name"),
|
||||
displayName: constants.columnNameText,
|
||||
valueType: azdata.DeclarativeDataType.string,
|
||||
width: '150px',
|
||||
isReadOnly: false
|
||||
}, {
|
||||
displayName: localize('flatFileImport.dataType', "Data Type"),
|
||||
displayName: constants.dataTypeText,
|
||||
valueType: azdata.DeclarativeDataType.editableCategory,
|
||||
width: '150px',
|
||||
isReadOnly: false,
|
||||
categoryValues: this.categoryValues
|
||||
}, {
|
||||
displayName: localize('flatFileImport.primaryKey', "Primary Key"),
|
||||
displayName: constants.primaryKeyText,
|
||||
valueType: azdata.DeclarativeDataType.boolean,
|
||||
width: '100px',
|
||||
isReadOnly: false
|
||||
}, {
|
||||
displayName: localize('flatFileImport.allowNulls', "Allow Nulls"),
|
||||
displayName: constants.allowNullsText,
|
||||
valueType: azdata.DeclarativeDataType.boolean,
|
||||
isReadOnly: false,
|
||||
width: '100px'
|
||||
|
||||
@@ -4,19 +4,11 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { ImportDataModel } from '../api/models';
|
||||
import { ImportPage } from '../api/importPage';
|
||||
import { FlatFileProvider } from '../../services/contracts';
|
||||
import { FlatFileWizard } from '../flatFileWizard';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
import * as constants from '../../common/constants';
|
||||
|
||||
export class ProsePreviewPage extends ImportPage {
|
||||
|
||||
private readonly successTitle: string = localize('flatFileImport.prosePreviewMessage', "This operation analyzed the input file structure to generate the preview below for up to the first 50 rows.");
|
||||
private readonly failureTitle: string = localize('flatFileImport.prosePreviewMessageFail', "This operation was unsuccessful. Please try a different input file.");
|
||||
|
||||
private table: azdata.TableComponent;
|
||||
private loading: azdata.LoadingComponent;
|
||||
private form: azdata.FormContainer;
|
||||
@@ -24,10 +16,6 @@ export class ProsePreviewPage extends ImportPage {
|
||||
private resultTextComponent: azdata.TextComponent;
|
||||
private isSuccess: boolean;
|
||||
|
||||
public constructor(instance: FlatFileWizard, wizardPage: azdata.window.WizardPage, model: ImportDataModel, view: azdata.ModelView, provider: FlatFileProvider) {
|
||||
super(instance, wizardPage, model, view, provider);
|
||||
}
|
||||
|
||||
async start(): Promise<boolean> {
|
||||
this.table = this.view.modelBuilder.table().withProperties<azdata.TableComponentProperties>({
|
||||
data: undefined,
|
||||
@@ -35,7 +23,7 @@ export class ProsePreviewPage extends ImportPage {
|
||||
forceFitColumns: azdata.ColumnSizingMode.DataFit
|
||||
}).component();
|
||||
this.refresh = this.view.modelBuilder.button().withProperties({
|
||||
label: localize('flatFileImport.refresh', "Refresh"),
|
||||
label: constants.refreshText,
|
||||
isFile: false
|
||||
}).component();
|
||||
|
||||
@@ -47,7 +35,7 @@ export class ProsePreviewPage extends ImportPage {
|
||||
|
||||
this.resultTextComponent = this.view.modelBuilder.text()
|
||||
.withProperties({
|
||||
value: this.isSuccess ? this.successTitle : this.failureTitle
|
||||
value: this.isSuccess ? constants.successTitleText : constants.failureTitleText
|
||||
}).component();
|
||||
|
||||
this.form = this.view.modelBuilder.formContainer().withFormItems([
|
||||
@@ -84,14 +72,14 @@ export class ProsePreviewPage extends ImportPage {
|
||||
await this.populateTable(this.model.proseDataPreview, this.model.proseColumns.map(c => c.columnName));
|
||||
this.isSuccess = true;
|
||||
if (this.form) {
|
||||
this.resultTextComponent.value = this.successTitle;
|
||||
this.resultTextComponent.value = constants.successTitleText;
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
await this.populateTable([], []);
|
||||
this.isSuccess = false;
|
||||
if (this.form) {
|
||||
this.resultTextComponent.value = this.failureTitle + '\n' + (error ?? '');
|
||||
this.resultTextComponent.value = constants.failureTitleText + '\n' + (error ?? '');
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -4,15 +4,10 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as azdata from 'azdata';
|
||||
import * as nls from 'vscode-nls';
|
||||
|
||||
import { ImportDataModel } from '../api/models';
|
||||
import { ImportPage } from '../api/importPage';
|
||||
import { FlatFileProvider, InsertDataResponse } from '../../services/contracts';
|
||||
import { FlatFileWizard } from '../flatFileWizard';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
import { InsertDataResponse } from '../../services/contracts';
|
||||
import * as constants from '../../common/constants';
|
||||
|
||||
export class SummaryPage extends ImportPage {
|
||||
private table: azdata.TableComponent;
|
||||
@@ -20,10 +15,6 @@ export class SummaryPage extends ImportPage {
|
||||
private loading: azdata.LoadingComponent;
|
||||
private form: azdata.FormContainer;
|
||||
|
||||
public constructor(instance: FlatFileWizard, wizardPage: azdata.window.WizardPage, model: ImportDataModel, view: azdata.ModelView, provider: FlatFileProvider) {
|
||||
super(instance, wizardPage, model, view, provider);
|
||||
}
|
||||
|
||||
async start(): Promise<boolean> {
|
||||
this.table = this.view.modelBuilder.table().component();
|
||||
this.statusText = this.view.modelBuilder.text().component();
|
||||
@@ -33,11 +24,11 @@ export class SummaryPage extends ImportPage {
|
||||
[
|
||||
{
|
||||
component: this.table,
|
||||
title: localize('flatFileImport.importInformation', "Import information")
|
||||
title: constants.importInformationText
|
||||
},
|
||||
{
|
||||
component: this.loading,
|
||||
title: localize('flatFileImport.importStatus', "Import status")
|
||||
title: constants.importStatusText
|
||||
}
|
||||
]
|
||||
).component();
|
||||
@@ -70,11 +61,11 @@ export class SummaryPage extends ImportPage {
|
||||
private populateTable() {
|
||||
this.table.updateProperties({
|
||||
data: [
|
||||
[localize('flatFileImport.serverName', "Server name"), this.model.server.providerName],
|
||||
[localize('flatFileImport.databaseName', "Database name"), this.model.database],
|
||||
[localize('flatFileImport.tableName', "Table name"), this.model.table],
|
||||
[localize('flatFileImport.tableSchema', "Table schema"), this.model.schema],
|
||||
[localize('flatFileImport.fileImport', "File to be imported"), this.model.filePath]],
|
||||
[constants.serverNameText, this.model.server.providerName],
|
||||
[constants.databaseText, this.model.database],
|
||||
[constants.tableNameText, this.model.table],
|
||||
[constants.tableSchemaText, this.model.schema],
|
||||
[constants.fileImportText, this.model.filePath]],
|
||||
columns: ['Object type', 'Name'],
|
||||
width: 600,
|
||||
height: 200
|
||||
@@ -96,9 +87,11 @@ export class SummaryPage extends ImportPage {
|
||||
|
||||
let result: InsertDataResponse;
|
||||
let err;
|
||||
let includePasswordInConnectionString = (this.model.server.options.connectionId === 'Integrated') ? false : true;
|
||||
|
||||
try {
|
||||
result = await this.provider.sendInsertDataRequest({
|
||||
connectionString: await this.getConnectionString(),
|
||||
connectionString: await this._apiWrapper.getConnectionString(this.model.server.connectionId, includePasswordInConnectionString),
|
||||
//TODO check what SSMS uses as batch size
|
||||
batchSize: 500
|
||||
});
|
||||
@@ -118,7 +111,7 @@ export class SummaryPage extends ImportPage {
|
||||
// TODO: When sql statements are in, implement this.
|
||||
//let rows = await this.getCountRowsInserted();
|
||||
//if (rows < 0) {
|
||||
updateText = localize('flatFileImport.success.norows', "✔ You have successfully inserted the data into a table.");
|
||||
updateText = constants.updateText;
|
||||
//} else {
|
||||
//updateText = localize('flatFileImport.success.rows', '✔ You have successfully inserted {0} rows.', rows);
|
||||
//}
|
||||
@@ -129,25 +122,6 @@ export class SummaryPage extends ImportPage {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the connection string to send to the middleware
|
||||
*/
|
||||
private async getConnectionString(): Promise<string> {
|
||||
let options = this.model.server.options;
|
||||
let connectionString: string;
|
||||
|
||||
if (options.authenticationType === 'Integrated') {
|
||||
connectionString = `Data Source=${options.server + (options.port ? `,${options.port}` : '')};Initial Catalog=${this.model.database};Integrated Security=True`;
|
||||
} else {
|
||||
let credentials = await azdata.connection.getCredentials(this.model.server.connectionId);
|
||||
connectionString = `Data Source=${options.server + (options.port ? `,${options.port}` : '')};Initial Catalog=${this.model.database};Integrated Security=False;User Id=${options.user};Password=${credentials.password}`;
|
||||
}
|
||||
|
||||
// TODO: Fix this, it's returning undefined string.
|
||||
//await azdata.connection.getConnectionString(this.model.server.connectionId, true);
|
||||
return connectionString;
|
||||
}
|
||||
|
||||
// private async getCountRowsInserted(): Promise<Number> {
|
||||
// let connectionUri = await azdata.connection.getUriForConnection(this.model.server.connectionId);
|
||||
// let queryProvider = azdata.dataprotocol.getProvider<azdata.QueryProvider>(this.model.server.providerName, azdata.DataProviderType.QueryProvider);
|
||||
|
||||
Reference in New Issue
Block a user